2015-04-12 02:16:11 -05:00
// 12 april 2015
# include "uipriv_windows.h"
struct tab {
2015-04-16 08:32:34 -05:00
uiTab t ;
2015-04-17 10:18:45 -05:00
HWND hwnd ;
2015-05-06 12:37:23 -05:00
struct ptrArray * pages ;
2015-05-04 13:42:23 -05:00
void ( * baseEnable ) ( uiControl * ) ;
void ( * baseDisable ) ( uiControl * ) ;
void ( * baseSysFunc ) ( uiControl * , uiControlSysFuncParams * ) ;
2015-04-12 02:16:11 -05:00
} ;
2015-05-06 12:37:23 -05:00
struct tabPage {
uiContainer * bin ;
int margined ;
} ;
2015-04-12 02:16:11 -05:00
static BOOL onWM_COMMAND ( uiControl * c , WORD code , LRESULT * lResult )
{
return FALSE ;
}
// we have to handle hiding and showing of tab pages ourselves
static BOOL onWM_NOTIFY ( uiControl * c , NMHDR * nm , LRESULT * lResult )
{
2015-04-16 08:32:34 -05:00
struct tab * t = ( struct tab * ) c ;
2015-05-06 12:37:23 -05:00
struct tabPage * page ;
2015-04-12 02:16:11 -05:00
LRESULT n ;
2015-05-06 12:37:23 -05:00
if ( nm - > code ! = TCN_SELCHANGING & & nm - > code ! = TCN_SELCHANGE )
return FALSE ;
n = SendMessageW ( t - > hwnd , TCM_GETCURSEL , 0 , 0 ) ;
if ( n = = ( LRESULT ) ( - 1 ) ) // not changing from/to a page; nothing to do
return FALSE ;
page = ptrArrayIndex ( t - > pages , struct tabPage * , n ) ;
if ( nm - > code = = TCN_SELCHANGING ) {
// changing from a real page
uiControlHide ( uiControl ( page - > bin ) ) ;
2015-04-12 02:16:11 -05:00
* lResult = FALSE ; // and allow the change
return TRUE ;
}
2015-05-06 12:37:23 -05:00
// otherwise it's TCN_SELCHANGE
// and we're changing to a real page
uiControlShow ( uiControl ( page - > bin ) ) ;
// because we only resize the current child on resize, we'll need to trigger an update here
// don't call uiParentUpdate(); doing that won't size the content area (so we'll still have a 0x0 content area, for instance)
SendMessageW ( t - > hwnd , msgUpdateChild , 0 , 0 ) ;
* lResult = 0 ;
return TRUE ;
2015-04-12 02:16:11 -05:00
}
2015-04-18 17:02:16 -05:00
static void onDestroy ( void * data )
2015-04-12 02:16:11 -05:00
{
2015-04-18 17:02:16 -05:00
struct tab * t = ( struct tab * ) data ;
2015-05-06 12:37:23 -05:00
struct tabPage * p ;
2015-04-12 02:16:11 -05:00
2015-04-29 00:53:39 -05:00
// first, hide the widget to avoid flicker
ShowWindow ( t - > hwnd , SW_HIDE ) ;
// because the pages don't have by a libui paent, we can simply destroy them
// we don't have to worry about the Windows tab control holding a reference to our bin; there is no reference holding anyway
2015-05-06 12:37:23 -05:00
while ( t - > pages - > len ! = 0 ) {
p = ptrArrayIndex ( t - > pages , struct tabPage * , 0 ) ;
2015-05-05 23:52:24 -05:00
// we do have to remove the page from the tab control, though
2015-05-06 12:37:23 -05:00
binSetParent ( p - > bin , 0 ) ;
uiControlDestroy ( uiControl ( p - > bin ) ) ;
ptrArrayDelete ( t - > pages , 0 ) ;
uiFree ( p ) ;
2015-05-05 23:52:24 -05:00
}
2015-04-29 00:53:39 -05:00
// and finally destroy ourselves
2015-05-06 12:37:23 -05:00
ptrArrayDestroy ( t - > pages ) ;
2015-04-12 02:16:11 -05:00
uiFree ( t ) ;
}
2015-05-04 13:42:23 -05:00
static void tabPreferredSize ( uiControl * c , uiSizing * d , intmax_t * width , intmax_t * height )
2015-04-12 02:16:11 -05:00
{
2015-05-03 16:13:40 -05:00
struct tab * t = ( struct tab * ) c ;
LRESULT current ;
2015-05-06 12:37:23 -05:00
struct tabPage * curpage ;
2015-05-03 16:13:40 -05:00
intmax_t curwid , curht ;
RECT r ;
r . left = 0 ;
r . top = 0 ;
r . right = 0 ;
r . bottom = 0 ;
2015-05-06 12:37:23 -05:00
if ( t - > pages - > len ! = 0 ) {
2015-05-03 16:13:40 -05:00
current = SendMessageW ( t - > hwnd , TCM_GETCURSEL , 0 , 0 ) ;
if ( current ! = ( LRESULT ) ( - 1 ) ) {
2015-05-06 12:37:23 -05:00
curpage = ptrArrayIndex ( t - > pages , struct tabPage * , current ) ;
uiControlPreferredSize ( uiControl ( curpage - > bin ) , d , & curwid , & curht ) ;
2015-05-03 16:13:40 -05:00
r . right = curwid ;
r . bottom = curht ;
}
}
// otherwise just use the rect [0 0 0 0]
2015-05-03 16:21:48 -05:00
// the following will take the tabs themselves into account
2015-05-03 16:13:40 -05:00
SendMessageW ( t - > hwnd , TCM_ADJUSTRECT , ( WPARAM ) TRUE , ( LPARAM ) ( & r ) ) ;
* width = r . right - r . left ;
* height = r . bottom - r . top ;
2015-04-12 02:16:11 -05:00
}
2015-05-04 13:42:23 -05:00
static void tabEnable ( uiControl * c )
{
struct tab * t = ( struct tab * ) c ;
2015-05-06 12:37:23 -05:00
struct tabPage * p ;
2015-05-04 13:45:18 -05:00
uintmax_t i ;
2015-05-04 13:42:23 -05:00
( * ( t - > baseEnable ) ) ( uiControl ( t ) ) ;
2015-05-06 12:37:23 -05:00
for ( i = 0 ; i < t - > pages - > len ; i + + ) {
p = ptrArrayIndex ( t - > pages , struct tabPage * , i ) ;
uiControlEnable ( uiControl ( p - > bin ) ) ;
}
2015-05-04 13:42:23 -05:00
}
static void tabDisable ( uiControl * c )
{
struct tab * t = ( struct tab * ) c ;
2015-05-06 12:37:23 -05:00
struct tabPage * p ;
2015-05-04 13:45:18 -05:00
uintmax_t i ;
2015-05-04 13:42:23 -05:00
( * ( t - > baseDisable ) ) ( uiControl ( t ) ) ;
2015-05-06 12:37:23 -05:00
for ( i = 0 ; i < t - > pages - > len ; i + + ) {
p = ptrArrayIndex ( t - > pages , struct tabPage * , i ) ;
uiControlDisable ( uiControl ( p - > bin ) ) ;
}
2015-05-04 13:42:23 -05:00
}
static void tabSysFunc ( uiControl * c , uiControlSysFuncParams * p )
{
struct tab * t = ( struct tab * ) c ;
2015-05-06 12:37:23 -05:00
struct tabPage * page ;
2015-05-04 13:45:18 -05:00
uintmax_t i ;
2015-05-04 13:42:23 -05:00
2015-05-06 17:05:07 -05:00
// we handle tab stops specially
if ( p - > Func = = uiWindowsSysFuncHasTabStops ) {
2015-05-06 17:37:21 -05:00
// if disabled, not a tab stop
if ( IsWindowEnabled ( t - > hwnd ) ! = 0 )
// if there are no tabs, it is not a tab stop
if ( t - > pages - > len ! = 0 )
p - > HasTabStops = TRUE ;
2015-05-06 17:05:07 -05:00
return ;
}
// otherwise distribute it throughout all pages
2015-05-04 13:42:23 -05:00
( * ( t - > baseSysFunc ) ) ( uiControl ( t ) , p ) ;
2015-05-06 12:37:23 -05:00
for ( i = 0 ; i < t - > pages - > len ; i + + ) {
page = ptrArrayIndex ( t - > pages , struct tabPage * , i ) ;
uiControlSysFunc ( uiControl ( page - > bin ) , p ) ;
}
2015-05-04 13:42:23 -05:00
}
2015-04-12 02:16:11 -05:00
// common code for resizes
2015-04-17 10:18:45 -05:00
static void resizeTab ( struct tab * t , LONG width , LONG height )
2015-04-12 02:16:11 -05:00
{
LRESULT n ;
2015-04-12 21:39:36 -05:00
RECT r ;
2015-05-06 12:37:23 -05:00
struct tabPage * p ;
2015-04-29 01:22:15 -05:00
HWND binHWND ;
2015-04-12 02:16:11 -05:00
2015-04-17 10:18:45 -05:00
n = SendMessageW ( t - > hwnd , TCM_GETCURSEL , 0 , 0 ) ;
2015-04-12 02:16:11 -05:00
if ( n = = ( LRESULT ) ( - 1 ) ) // no child selected; do nothing
return ;
// make a rect at (0, 0) of the given window size
// this should give us the correct client coordinates
r . left = 0 ;
r . top = 0 ;
r . right = width ;
r . bottom = height ;
// convert to the display rectangle
2015-04-17 10:18:45 -05:00
SendMessageW ( t - > hwnd , TCM_ADJUSTRECT , FALSE , ( LPARAM ) ( & r ) ) ;
2015-04-12 02:16:11 -05:00
2015-05-06 12:37:23 -05:00
p = ptrArrayIndex ( t - > pages , struct tabPage * , n ) ;
binHWND = ( HWND ) uiControlHandle ( uiControl ( p - > bin ) ) ;
2015-04-29 01:22:15 -05:00
if ( MoveWindow ( binHWND , r . left , r . top , r . right - r . left , r . bottom - r . top , TRUE ) = = 0 )
logLastError ( " error resizing uiTab page in resizeTab() " ) ;
2015-04-12 02:16:11 -05:00
}
2015-04-12 21:39:36 -05:00
// and finally, because we have to resize parents, we have to handle resizes and updates
2015-05-07 09:14:49 -05:00
// this is also partially where tab navigation is handled
2015-04-12 02:16:11 -05:00
static LRESULT CALLBACK tabSubProc ( HWND hwnd , UINT uMsg , WPARAM wParam , LPARAM lParam , UINT_PTR uIdSubclass , DWORD_PTR dwRefData )
{
2015-04-17 10:18:45 -05:00
struct tab * t = ( struct tab * ) dwRefData ;
2015-04-12 02:16:11 -05:00
WINDOWPOS * wp = ( WINDOWPOS * ) lParam ;
2015-04-12 21:57:05 -05:00
LRESULT lResult ;
2015-04-13 09:15:36 -05:00
RECT r ;
2015-05-06 17:37:21 -05:00
LRESULT n ;
uiControlSysFuncParams p ;
struct tabPage * page ;
2015-04-12 02:16:11 -05:00
switch ( uMsg ) {
case WM_WINDOWPOSCHANGED :
if ( ( wp - > flags & SWP_NOSIZE ) ! = 0 )
break ;
// first, let the tab control handle it
lResult = ( * fv_DefSubclassProc ) ( hwnd , uMsg , wParam , lParam ) ;
// we have the window rect width as part of the WINDOWPOS; resize
2015-04-17 10:18:45 -05:00
resizeTab ( t , wp - > cx , wp - > cy ) ;
2015-04-12 02:16:11 -05:00
return lResult ;
2015-04-13 09:15:36 -05:00
case msgUpdateChild :
2015-04-17 10:18:45 -05:00
if ( GetWindowRect ( t - > hwnd , & r ) = = 0 )
2015-04-13 09:15:36 -05:00
logLastError ( " error getting Tab window rect for synthesized resize message in tabSubProc() " ) ;
2015-05-03 15:56:58 -05:00
// these are in screen coordinates, which match what WM_WINDOWPOSCHANGED gave us (see http://stackoverflow.com/questions/29598334/are-the-coordinates-in-windowpos-on-wm-windowposchanged-in-parent-coordinates-or)
2015-04-17 10:18:45 -05:00
resizeTab ( t , r . right - r . left , r . bottom - r . top ) ;
2015-04-13 09:15:36 -05:00
return 0 ;
2015-05-06 17:37:21 -05:00
case msgHasTabStops :
n = SendMessageW ( t - > hwnd , TCM_GETCURSEL , 0 , 0 ) ;
if ( n = = ( LRESULT ) ( - 1 ) ) // no current selection == no tab stops
return FALSE ;
p . Func = uiWindowsSysFuncHasTabStops ;
p . HasTabStops = FALSE ;
page = ptrArrayIndex ( t - > pages , struct tabPage * , n ) ;
uiControlSysFunc ( uiControl ( page - > bin ) , & p ) ;
return p . HasTabStops ;
2015-04-12 02:16:11 -05:00
case WM_NCDESTROY :
if ( ( * fv_RemoveWindowSubclass ) ( hwnd , tabSubProc , uIdSubclass ) = = FALSE )
logLastError ( " error removing Tab resize handling subclass in tabSubProc() " ) ;
break ;
}
return ( * fv_DefSubclassProc ) ( hwnd , uMsg , wParam , lParam ) ;
}
# define tabCapGrow 32
2015-04-29 00:53:39 -05:00
static void tabAppendPage ( uiTab * tt , const char * name , uiControl * child )
2015-04-12 02:16:11 -05:00
{
2015-04-16 08:32:34 -05:00
struct tab * t = ( struct tab * ) tt ;
2015-04-12 02:16:11 -05:00
TCITEMW item ;
LRESULT n ;
2015-05-06 12:37:23 -05:00
struct tabPage * page ;
2015-04-12 02:16:11 -05:00
WCHAR * wname ;
2015-05-06 12:37:23 -05:00
page = uiNew ( struct tabPage ) ;
2015-04-17 10:18:45 -05:00
n = SendMessageW ( t - > hwnd , TCM_GETITEMCOUNT , 0 , 0 ) ;
2015-04-12 02:16:11 -05:00
2015-05-06 12:37:23 -05:00
page - > bin = newBin ( ) ;
binSetMainControl ( page - > bin , child ) ;
binSetParent ( page - > bin , ( uintptr_t ) ( t - > hwnd ) ) ;
2015-04-12 02:16:11 -05:00
if ( n ! = 0 ) // if this isn't the first page, we have to hide the other controls
2015-05-06 12:37:23 -05:00
uiControlHide ( uiControl ( page - > bin ) ) ;
ptrArrayAppend ( t - > pages , page ) ;
2015-04-12 02:16:11 -05:00
ZeroMemory ( & item , sizeof ( TCITEMW ) ) ;
item . mask = TCIF_TEXT ;
wname = toUTF16 ( name ) ;
item . pszText = wname ;
// MSDN's example code uses the first invalid index directly for this
2015-04-17 10:18:45 -05:00
if ( SendMessageW ( t - > hwnd , TCM_INSERTITEM , ( WPARAM ) n , ( LPARAM ) ( & item ) ) = = ( LRESULT ) - 1 )
2015-04-12 02:16:11 -05:00
logLastError ( " error adding tab to Tab in uiTabAddPage() " ) ;
uiFree ( wname ) ;
2015-04-13 09:15:36 -05:00
// if this is the first tab, Windows will automatically show it /without/ sending a TCN_SELCHANGE notification
// so we need to manually resize the tab ourselves
2015-05-07 09:14:49 -05:00
// don't use uiContainerUpdate() for the same reason as in the TCN_SELCHANGE handler
2015-04-17 10:18:45 -05:00
SendMessageW ( t - > hwnd , msgUpdateChild , 0 , 0 ) ;
2015-04-12 02:16:11 -05:00
}
2015-04-16 08:32:34 -05:00
2015-05-06 13:09:20 -05:00
static void tabInsertPageBefore ( uiTab * tt , const char * name , uintmax_t n , uiControl * child )
{
struct tab * t = ( struct tab * ) tt ;
TCITEMW item ;
struct tabPage * page ;
WCHAR * wname ;
page = uiNew ( struct tabPage ) ;
page - > bin = newBin ( ) ;
binSetMainControl ( page - > bin , child ) ;
binSetParent ( page - > bin , ( uintptr_t ) ( t - > hwnd ) ) ;
// always hide; the current tab doesn't change
uiControlHide ( uiControl ( page - > bin ) ) ;
ptrArrayInsertBefore ( t - > pages , n , page ) ;
ZeroMemory ( & item , sizeof ( TCITEMW ) ) ;
item . mask = TCIF_TEXT ;
wname = toUTF16 ( name ) ;
item . pszText = wname ;
if ( SendMessageW ( t - > hwnd , TCM_INSERTITEM , ( WPARAM ) n , ( LPARAM ) ( & item ) ) = = ( LRESULT ) - 1 )
logLastError ( " error adding tab to Tab in uiTabInsertPageBefore() " ) ;
uiFree ( wname ) ;
}
2015-04-18 13:41:28 -05:00
static void tabDeletePage ( uiTab * tt , uintmax_t n )
{
struct tab * t = ( struct tab * ) tt ;
2015-05-06 12:37:23 -05:00
struct tabPage * page ;
2015-04-18 13:41:28 -05:00
// first delete the tab from the tab control
// if this is the current tab, no tab will be selected, which is good
if ( SendMessageW ( t - > hwnd , TCM_DELETEITEM , ( WPARAM ) n , 0 ) = = FALSE )
logLastError ( " error deleting Tab page in tabDeletePage() " ) ;
// now delete the page itself
2015-05-06 12:37:23 -05:00
page = ptrArrayIndex ( t - > pages , struct tabPage * , n ) ;
ptrArrayDelete ( t - > pages , n ) ;
2015-04-18 13:41:28 -05:00
// make sure the page's control isn't destroyed
2015-05-06 12:37:23 -05:00
binSetMainControl ( page - > bin , NULL ) ;
2015-04-29 00:53:39 -05:00
// see tabDestroy() above for details
2015-05-06 12:37:23 -05:00
binSetParent ( page - > bin , 0 ) ;
uiControlDestroy ( uiControl ( page - > bin ) ) ;
uiFree ( page ) ;
2015-04-18 13:41:28 -05:00
}
2015-04-30 14:50:03 -05:00
static uintmax_t tabNumPages ( uiTab * tt )
{
struct tab * t = ( struct tab * ) tt ;
2015-05-06 12:37:23 -05:00
return t - > pages - > len ;
2015-04-30 14:50:03 -05:00
}
static int tabMargined ( uiTab * tt , uintmax_t n )
{
struct tab * t = ( struct tab * ) tt ;
2015-05-06 12:37:23 -05:00
struct tabPage * page ;
2015-04-30 14:50:03 -05:00
2015-05-06 12:37:23 -05:00
page = ptrArrayIndex ( t - > pages , struct tabPage * , n ) ;
return page - > margined ;
2015-04-30 14:50:03 -05:00
}
// from http://msdn.microsoft.com/en-us/library/windows/desktop/bb226818%28v=vs.85%29.aspx
# define tabMargin 7
static void tabSetMargined ( uiTab * tt , uintmax_t n , int margined )
{
struct tab * t = ( struct tab * ) tt ;
2015-05-06 12:37:23 -05:00
struct tabPage * page ;
2015-04-30 14:50:03 -05:00
2015-05-06 12:37:23 -05:00
page = ptrArrayIndex ( t - > pages , struct tabPage * , n ) ;
page - > margined = margined ;
if ( page - > margined )
binSetMargins ( page - > bin , tabMargin , tabMargin , tabMargin , tabMargin ) ;
2015-04-30 14:50:03 -05:00
else
2015-05-06 12:37:23 -05:00
binSetMargins ( page - > bin , 0 , 0 , 0 , 0 ) ;
2015-04-30 14:50:03 -05:00
}
2015-04-16 08:32:34 -05:00
uiTab * uiNewTab ( void )
{
struct tab * t ;
2015-05-02 19:51:00 -05:00
uiWindowsMakeControlParams p ;
2015-04-16 08:32:34 -05:00
t = uiNew ( struct tab ) ;
p . dwExStyle = 0 ; // don't set WS_EX_CONTROLPARENT yet; we do that dynamically in the message loop (see main_windows.c)
p . lpClassName = WC_TABCONTROLW ;
p . lpWindowName = L " " ;
2015-05-06 17:37:21 -05:00
p . dwStyle = TCS_TOOLTIPS | WS_TABSTOP ; // start with this; we will alternate between this and WS_EX_CONTROLPARENT as needed (see main.c and msgHasTabStops above and the toggling functions below)
2015-04-16 08:32:34 -05:00
p . hInstance = hInstance ;
p . useStandardControlFont = TRUE ;
p . onWM_COMMAND = onWM_COMMAND ;
p . onWM_NOTIFY = onWM_NOTIFY ;
2015-04-18 17:02:16 -05:00
p . onDestroy = onDestroy ;
p . onDestroyData = t ;
2015-05-02 19:51:00 -05:00
uiWindowsMakeControl ( uiControl ( t ) , & p ) ;
2015-04-16 08:32:34 -05:00
2015-05-01 09:16:02 -05:00
t - > hwnd = ( HWND ) uiControlHandle ( uiControl ( t ) ) ;
2015-05-06 12:37:23 -05:00
t - > pages = newPtrArray ( ) ;
2015-04-17 10:18:45 -05:00
if ( ( * fv_SetWindowSubclass ) ( t - > hwnd , tabSubProc , 0 , ( DWORD_PTR ) t ) = = FALSE )
2015-04-16 08:32:34 -05:00
logLastError ( " error subclassing Tab to give it its own resize handler in uiNewTab() " ) ;
2015-05-04 13:42:23 -05:00
uiControl ( t ) - > PreferredSize = tabPreferredSize ;
t - > baseEnable = uiControl ( t ) - > Enable ;
uiControl ( t ) - > Enable = tabEnable ;
t - > baseDisable = uiControl ( t ) - > Disable ;
uiControl ( t ) - > Disable = tabDisable ;
t - > baseSysFunc = uiControl ( t ) - > SysFunc ;
uiControl ( t ) - > SysFunc = tabSysFunc ;
2015-04-16 08:32:34 -05:00
2015-04-29 00:53:39 -05:00
uiTab ( t ) - > AppendPage = tabAppendPage ;
2015-05-06 13:09:20 -05:00
uiTab ( t ) - > InsertPageBefore = tabInsertPageBefore ;
2015-04-18 13:41:28 -05:00
uiTab ( t ) - > DeletePage = tabDeletePage ;
2015-04-30 14:50:03 -05:00
uiTab ( t ) - > NumPages = tabNumPages ;
uiTab ( t ) - > Margined = tabMargined ;
uiTab ( t ) - > SetMargined = tabSetMargined ;
2015-04-16 08:32:34 -05:00
return uiTab ( t ) ;
}
2015-05-06 17:37:21 -05:00
// unfortunately WS_TABSTOP and WS_EX_CONTROLPARENT are mutually exclusive, so we have to toggle between them
// see main.c for more details
void tabEnterTabNavigation ( HWND hwnd )
{
setStyle ( hwnd , getStyle ( hwnd ) & ~ WS_TABSTOP ) ;
setExStyle ( hwnd , getExStyle ( hwnd ) | WS_EX_CONTROLPARENT ) ;
}
void tabLeaveTabNavigation ( HWND hwnd )
{
setExStyle ( hwnd , getExStyle ( hwnd ) & ~ WS_EX_CONTROLPARENT ) ;
setStyle ( hwnd , getStyle ( hwnd ) | WS_TABSTOP ) ;
}