8697 lines
250 KiB
C++
8697 lines
250 KiB
C++
/* Cockos SWELL (Simple/Small Win32 Emulation Layer for Linux/OSX)
|
|
Copyright (C) 2006 and later, Cockos, Inc.
|
|
|
|
This software is provided 'as-is', without any express or implied
|
|
warranty. In no event will the authors be held liable for any damages
|
|
arising from the use of this software.
|
|
|
|
Permission is granted to anyone to use this software for any purpose,
|
|
including commercial applications, and to alter it and redistribute it
|
|
freely, subject to the following restrictions:
|
|
|
|
1. The origin of this software must not be misrepresented; you must not
|
|
claim that you wrote the original software. If you use this software
|
|
in a product, an acknowledgment in the product documentation would be
|
|
appreciated but is not required.
|
|
2. Altered source versions must be plainly marked as such, and must not be
|
|
misrepresented as being the original software.
|
|
3. This notice may not be removed or altered from any source distribution.
|
|
|
|
|
|
*/
|
|
|
|
|
|
#ifndef SWELL_PROVIDED_BY_APP
|
|
|
|
#ifndef WDL_NO_DEFINE_MINMAX
|
|
#define WDL_NO_DEFINE_MINMAX
|
|
#endif
|
|
#include "swell.h"
|
|
|
|
#define SWELL_INTERNAL_MERGESORT_IMPL
|
|
#define SWELL_INTERNAL_HTREEITEM_IMPL
|
|
#include "swell-internal.h"
|
|
|
|
#include <math.h>
|
|
#include "../mutex.h"
|
|
#include "../ptrlist.h"
|
|
#include "../assocarray.h"
|
|
#include "../queue.h"
|
|
#include "../wdlcstring.h"
|
|
#include "../wdlutf8.h"
|
|
|
|
#include "swell-dlggen.h"
|
|
|
|
#define EDIT_CURSOR_BLINK_LEN 500
|
|
#define EDIT_CURSOR_CYCLE_INTERVAL 3
|
|
|
|
bool swell_is_likely_capslock; // only used when processing dit events for a-zA-Z
|
|
SWELL_OSWINDOW SWELL_focused_oswindow; // top level window which has focus (might not map to a HWND__!)
|
|
HWND swell_captured_window;
|
|
|
|
#define STATEIMAGEMASKTOINDEX(x) (((x)>>16)&0xff)
|
|
|
|
static bool swell_is_virtkey_char(int c)
|
|
{
|
|
return (c >= 'A' && c <= 'Z') || (c >= '0' && c <= '9');
|
|
}
|
|
|
|
HWND__ *SWELL_topwindows;
|
|
HWND swell_oswindow_to_hwnd(SWELL_OSWINDOW w)
|
|
{
|
|
if (!w) return NULL;
|
|
HWND a = SWELL_topwindows;
|
|
while (a && a->m_oswindow != w) a=a->m_next;
|
|
return a;
|
|
}
|
|
|
|
SWELL_OSWINDOW swell_oswindow_from_hwnd(HWND hwnd)
|
|
{
|
|
while (hwnd && !hwnd->m_oswindow)
|
|
hwnd = hwnd->m_parent;
|
|
return hwnd ? hwnd->m_oswindow : NULL;
|
|
}
|
|
|
|
HWND SWELL_GetFocusedChild(HWND h);
|
|
|
|
void swell_on_toplevel_raise(SWELL_OSWINDOW wnd) // called by swell-generic-gdk when a window is focused
|
|
{
|
|
HWND hwnd = swell_oswindow_to_hwnd(wnd);
|
|
if (SWELL_topwindows && hwnd != SWELL_topwindows)
|
|
{
|
|
HWND c = SWELL_GetFocusedChild(SWELL_topwindows);
|
|
if (c) SendMessage(c,WM_KILLFOCUS,0,0);
|
|
}
|
|
if (hwnd && hwnd != SWELL_topwindows)
|
|
{
|
|
// implies hwnd->m_prev
|
|
|
|
VALIDATE_HWND_LIST(SWELL_topwindows,NULL);
|
|
|
|
// remove from list
|
|
hwnd->m_prev->m_next = hwnd->m_next;
|
|
if (hwnd->m_next) hwnd->m_next->m_prev = hwnd->m_prev;
|
|
|
|
// insert at front of list
|
|
hwnd->m_prev = NULL;
|
|
hwnd->m_next = SWELL_topwindows;
|
|
if (SWELL_topwindows) SWELL_topwindows->m_prev = hwnd;
|
|
SWELL_topwindows = hwnd;
|
|
VALIDATE_HWND_LIST(SWELL_topwindows,NULL);
|
|
}
|
|
if (hwnd)
|
|
{
|
|
HWND c = SWELL_GetFocusedChild(hwnd);
|
|
if (c) SendMessage(c,WM_SETFOCUS,0,0);
|
|
}
|
|
}
|
|
|
|
HWND__::HWND__(HWND par, int wID, const RECT *wndr, const char *label, bool visible, WNDPROC wndproc, DLGPROC dlgproc, HWND ownerWindow)
|
|
{
|
|
m_refcnt=1;
|
|
m_private_data=0;
|
|
m_israised=false;
|
|
m_has_had_position=false;
|
|
m_is_maximized = false;
|
|
m_oswindow_private=0;
|
|
m_oswindow_fullscreen=0;
|
|
|
|
m_classname = "unknown";
|
|
m_wndproc=wndproc?wndproc:dlgproc?(WNDPROC)SwellDialogDefaultWindowProc:(WNDPROC)DefWindowProc;
|
|
m_dlgproc=dlgproc;
|
|
m_userdata=0;
|
|
m_style=0;
|
|
m_exstyle=0;
|
|
m_id=wID;
|
|
m_owned_list=m_owner=m_owned_next=m_owned_prev=NULL;
|
|
m_children=m_parent=m_next=m_prev=NULL;
|
|
m_focused_child=NULL;
|
|
if (wndr) m_position = *wndr;
|
|
else memset(&m_position,0,sizeof(m_position));
|
|
memset(&m_extra,0,sizeof(m_extra));
|
|
m_visible=visible;
|
|
m_hashaddestroy=0;
|
|
m_enabled=true;
|
|
m_wantfocus=true;
|
|
m_menu=NULL;
|
|
m_font=NULL;
|
|
m_oswindow = NULL;
|
|
|
|
#ifdef SWELL_LICE_GDI
|
|
m_paintctx=0;
|
|
m_invalidated=true;
|
|
m_child_invalidated=true;
|
|
m_backingstore=0;
|
|
#endif
|
|
|
|
if (label) m_title.Set(label);
|
|
|
|
SetParent(this, par);
|
|
if (!par && ownerWindow)
|
|
{
|
|
m_owned_next = ownerWindow->m_owned_list;
|
|
ownerWindow->m_owned_list = this;
|
|
if (m_owned_next) m_owned_next->m_owned_prev = this;
|
|
m_owner = ownerWindow;
|
|
}
|
|
}
|
|
|
|
static HWND s_last_rbuttondown;
|
|
|
|
HWND__::~HWND__()
|
|
{
|
|
if (m_wndproc)
|
|
m_wndproc(this,WM_NCDESTROY,0,0);
|
|
|
|
if (this == s_last_rbuttondown) s_last_rbuttondown = NULL;
|
|
}
|
|
|
|
|
|
|
|
HWND GetParent(HWND hwnd)
|
|
{
|
|
if (WDL_NORMALLY(hwnd))
|
|
{
|
|
return hwnd->m_parent ? hwnd->m_parent : hwnd->m_owner;
|
|
}
|
|
return NULL;
|
|
}
|
|
|
|
HWND GetDlgItem(HWND hwnd, int idx)
|
|
{
|
|
if (!idx) return hwnd;
|
|
WDL_ASSERT(hwnd != NULL);
|
|
if (hwnd) hwnd=hwnd->m_children;
|
|
while (hwnd && hwnd->m_id != (UINT)idx) hwnd=hwnd->m_next;
|
|
return hwnd;
|
|
}
|
|
|
|
|
|
LONG_PTR SetWindowLong(HWND hwnd, int idx, LONG_PTR val)
|
|
{
|
|
if (WDL_NOT_NORMALLY(!hwnd)) return 0;
|
|
if (idx==GWL_STYLE)
|
|
{
|
|
LONG ret = hwnd->m_style;
|
|
hwnd->m_style=val & ~WS_VISIBLE;
|
|
swell_oswindow_update_style(hwnd,ret);
|
|
return ret & ~WS_VISIBLE;
|
|
}
|
|
if (idx==GWL_EXSTYLE)
|
|
{
|
|
LONG ret = hwnd->m_exstyle;
|
|
hwnd->m_exstyle=val;
|
|
return ret;
|
|
}
|
|
if (idx==GWL_USERDATA)
|
|
{
|
|
LONG_PTR ret = hwnd->m_userdata;
|
|
hwnd->m_userdata=val;
|
|
return ret;
|
|
}
|
|
if (idx==GWL_ID)
|
|
{
|
|
LONG ret = hwnd->m_id;
|
|
hwnd->m_id=val;
|
|
return ret;
|
|
}
|
|
|
|
if (idx==GWL_WNDPROC)
|
|
{
|
|
LONG_PTR ret = (LONG_PTR)hwnd->m_wndproc;
|
|
hwnd->m_wndproc=(WNDPROC)val;
|
|
return ret;
|
|
}
|
|
if (idx==DWL_DLGPROC)
|
|
{
|
|
LONG_PTR ret = (LONG_PTR)hwnd->m_dlgproc;
|
|
hwnd->m_dlgproc=(DLGPROC)val;
|
|
return ret;
|
|
}
|
|
if (idx == GWL_HWNDPARENT)
|
|
{
|
|
const LONG_PTR ret = (LONG_PTR)hwnd->m_owner;
|
|
if (val != ret)
|
|
{
|
|
if (hwnd->m_owned_next) hwnd->m_owned_next->m_owned_prev = hwnd->m_owned_prev;
|
|
if (hwnd->m_owned_prev) hwnd->m_owned_prev->m_owned_next = hwnd->m_owned_next;
|
|
if (hwnd->m_owner && hwnd->m_owner->m_owned_list == hwnd) hwnd->m_owner->m_owned_list = hwnd->m_owned_next;
|
|
hwnd->m_owned_next = hwnd->m_owned_prev = hwnd->m_owner = NULL;
|
|
HWND ownerWindow = (HWND) val;
|
|
if (ownerWindow)
|
|
{
|
|
hwnd->m_owned_next = ownerWindow->m_owned_list;
|
|
ownerWindow->m_owned_list = hwnd;
|
|
if (hwnd->m_owned_next) hwnd->m_owned_next->m_owned_prev = hwnd;
|
|
hwnd->m_owner = ownerWindow;
|
|
hwnd->m_israised=true;
|
|
}
|
|
}
|
|
return ret;
|
|
}
|
|
|
|
if (idx>=0 && idx < 64*(int)sizeof(INT_PTR))
|
|
{
|
|
INT_PTR ret = hwnd->m_extra[idx/sizeof(INT_PTR)];
|
|
hwnd->m_extra[idx/sizeof(INT_PTR)]=val;
|
|
return (LONG_PTR)ret;
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
LONG_PTR GetWindowLong(HWND hwnd, int idx)
|
|
{
|
|
if (WDL_NOT_NORMALLY(!hwnd)) return 0;
|
|
if (idx==GWL_STYLE)
|
|
{
|
|
LONG_PTR ret = hwnd->m_style;
|
|
if (hwnd->m_visible) ret|=WS_VISIBLE;
|
|
else ret &= ~WS_VISIBLE;
|
|
return ret;
|
|
}
|
|
if (idx==GWL_EXSTYLE)
|
|
{
|
|
return hwnd->m_exstyle;
|
|
}
|
|
if (idx==GWL_USERDATA)
|
|
{
|
|
return hwnd->m_userdata;
|
|
}
|
|
if (idx==GWL_ID)
|
|
{
|
|
return hwnd->m_id;
|
|
}
|
|
|
|
if (idx==GWL_WNDPROC)
|
|
{
|
|
return (LONG_PTR)hwnd->m_wndproc;
|
|
}
|
|
if (idx==DWL_DLGPROC)
|
|
{
|
|
return (LONG_PTR)hwnd->m_dlgproc;
|
|
}
|
|
if (idx == GWL_HWNDPARENT)
|
|
{
|
|
return (LONG_PTR)hwnd->m_owner;
|
|
}
|
|
|
|
if (idx>=0 && idx < 64*(int)sizeof(INT_PTR))
|
|
{
|
|
return (LONG_PTR)hwnd->m_extra[idx/sizeof(INT_PTR)];
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
|
|
static bool __isWindow(HWND hc, HWND hFind)
|
|
{
|
|
while (hc)
|
|
{
|
|
if (hc == hFind || (hc->m_children && __isWindow(hc->m_children,hFind))) return true;
|
|
hc = hc->m_next;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
bool IsWindow(HWND hwnd)
|
|
{
|
|
if (!hwnd) return false;
|
|
HWND h = SWELL_topwindows;
|
|
while (h)
|
|
{
|
|
if (hwnd == h || (h->m_children && __isWindow(h->m_children,hwnd))) return true;
|
|
h=h->m_next;
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
bool IsWindowVisible(HWND hwnd)
|
|
{
|
|
if (WDL_NOT_NORMALLY(!hwnd)) return false;
|
|
while (hwnd->m_visible)
|
|
{
|
|
hwnd = hwnd->m_parent;
|
|
if (!hwnd) return true;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
bool IsModalDialogBox(HWND hwnd);
|
|
|
|
LRESULT SendMessage(HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam)
|
|
{
|
|
if (WDL_NOT_NORMALLY(!hwnd)) return 0;
|
|
WNDPROC wp = hwnd->m_wndproc;
|
|
|
|
if (msg == WM_DESTROY)
|
|
{
|
|
if (hwnd->m_hashaddestroy) return 0;
|
|
hwnd->m_hashaddestroy=1;
|
|
|
|
if (GetCapture()==hwnd) ReleaseCapture();
|
|
SWELL_MessageQueue_Clear(hwnd);
|
|
}
|
|
else if (hwnd->m_hashaddestroy == 2) return 0;
|
|
else if (msg==WM_CAPTURECHANGED && hwnd->m_hashaddestroy) return 0;
|
|
|
|
hwnd->Retain();
|
|
|
|
LRESULT ret = wp ? wp(hwnd,msg,wParam,lParam) : 0;
|
|
|
|
if (msg == WM_DESTROY)
|
|
{
|
|
if (GetCapture()==hwnd) ReleaseCapture();
|
|
|
|
SWELL_MessageQueue_Clear(hwnd);
|
|
// send WM_DESTROY to all children
|
|
HWND tmp=hwnd->m_children;
|
|
while (tmp)
|
|
{
|
|
HWND old = tmp;
|
|
tmp=tmp->m_next;
|
|
SendMessage(old,WM_DESTROY,0,0);
|
|
}
|
|
{
|
|
tmp=hwnd->m_owned_list;
|
|
while (tmp)
|
|
{
|
|
HWND old = tmp;
|
|
tmp=tmp->m_owned_next;
|
|
if (!IsModalDialogBox(old)) SendMessage(old,WM_DESTROY,0,0);
|
|
}
|
|
}
|
|
if (SWELL_focused_oswindow && SWELL_focused_oswindow == hwnd->m_oswindow)
|
|
{
|
|
HWND h = hwnd->m_owner;
|
|
while (h && !h->m_oswindow) h = h->m_parent ? h->m_parent : h->m_owner;
|
|
swell_oswindow_focus(h && h->m_oswindow ? h : NULL);
|
|
}
|
|
hwnd->m_wndproc=NULL;
|
|
hwnd->m_hashaddestroy=2;
|
|
KillTimer(hwnd,-1);
|
|
}
|
|
hwnd->Release();
|
|
return ret;
|
|
}
|
|
|
|
static void swell_removeWindowFromParentOrTop(HWND__ *hwnd, bool removeFromOwner)
|
|
{
|
|
HWND par = hwnd->m_parent;
|
|
if (hwnd->m_next) hwnd->m_next->m_prev = hwnd->m_prev;
|
|
if (hwnd->m_prev) hwnd->m_prev->m_next = hwnd->m_next;
|
|
if (par)
|
|
{
|
|
if (par->m_focused_child == hwnd) par->m_focused_child=NULL;
|
|
if (par->m_children == hwnd) par->m_children = hwnd->m_next;
|
|
}
|
|
if (hwnd == SWELL_topwindows)
|
|
{
|
|
SWELL_topwindows = hwnd->m_next;
|
|
VALIDATE_HWND_LIST(SWELL_topwindows,NULL);
|
|
}
|
|
hwnd->m_next = hwnd->m_prev = hwnd->m_parent = NULL;
|
|
if (par) VALIDATE_HWND_LIST(par->m_children,par);
|
|
|
|
if (removeFromOwner)
|
|
{
|
|
if (hwnd->m_owned_next) hwnd->m_owned_next->m_owned_prev = hwnd->m_owned_prev;
|
|
if (hwnd->m_owned_prev) hwnd->m_owned_prev->m_owned_next = hwnd->m_owned_next;
|
|
if (hwnd->m_owner && hwnd->m_owner->m_owned_list == hwnd) hwnd->m_owner->m_owned_list = hwnd->m_owned_next;
|
|
hwnd->m_owned_next = hwnd->m_owned_prev = hwnd->m_owner = NULL;
|
|
}
|
|
|
|
if (par && !par->m_hashaddestroy) InvalidateRect(par,NULL,FALSE);
|
|
}
|
|
|
|
void RecurseDestroyWindow(HWND hwnd)
|
|
{
|
|
HWND tmp=hwnd->m_children;
|
|
hwnd->m_children=NULL;
|
|
|
|
while (tmp)
|
|
{
|
|
HWND old = tmp;
|
|
tmp=tmp->m_next;
|
|
if (tmp) tmp->m_prev = NULL;
|
|
|
|
old->m_prev = old->m_next = NULL;
|
|
RecurseDestroyWindow(old);
|
|
}
|
|
tmp=hwnd->m_owned_list;
|
|
hwnd->m_owned_list = NULL;
|
|
|
|
while (tmp)
|
|
{
|
|
HWND old = tmp;
|
|
tmp=tmp->m_owned_next;
|
|
if (tmp) tmp->m_owned_prev = NULL;
|
|
|
|
old->m_owned_prev = old->m_owned_next = NULL;
|
|
old->m_owner = NULL;
|
|
if (old->m_hashaddestroy) RecurseDestroyWindow(old);
|
|
}
|
|
|
|
if (swell_captured_window == hwnd) swell_captured_window=NULL;
|
|
|
|
swell_oswindow_destroy(hwnd);
|
|
|
|
if (hwnd->m_menu) DestroyMenu(hwnd->m_menu);
|
|
hwnd->m_menu=0;
|
|
|
|
#ifdef SWELL_LICE_GDI
|
|
delete hwnd->m_backingstore;
|
|
hwnd->m_backingstore=0;
|
|
#endif
|
|
|
|
// remove from parent/global lists
|
|
swell_removeWindowFromParentOrTop(hwnd, true);
|
|
|
|
SWELL_MessageQueue_Clear(hwnd);
|
|
KillTimer(hwnd,-1);
|
|
hwnd->Release();
|
|
}
|
|
|
|
|
|
void DestroyWindow(HWND hwnd)
|
|
{
|
|
if (WDL_NOT_NORMALLY(!hwnd)) return;
|
|
if (hwnd->m_hashaddestroy) return;
|
|
|
|
// broadcast WM_DESTROY
|
|
SendMessage(hwnd,WM_DESTROY,0,0);
|
|
|
|
// safe to delete this window and all children directly
|
|
RecurseDestroyWindow(hwnd);
|
|
|
|
}
|
|
|
|
|
|
bool IsWindowEnabled(HWND hwnd)
|
|
{
|
|
if (WDL_NOT_NORMALLY(!hwnd)) return false;
|
|
while (hwnd && hwnd->m_enabled)
|
|
{
|
|
hwnd=hwnd->m_parent;
|
|
}
|
|
return !hwnd;
|
|
}
|
|
|
|
void EnableWindow(HWND hwnd, int enable)
|
|
{
|
|
if (WDL_NOT_NORMALLY(!hwnd)) return;
|
|
if (!!hwnd->m_enabled == !!enable) return;
|
|
|
|
hwnd->m_enabled=!!enable;
|
|
swell_oswindow_update_enable(hwnd);
|
|
|
|
if (!enable)
|
|
{
|
|
if (hwnd->m_parent && hwnd->m_parent->m_focused_child == hwnd)
|
|
hwnd->m_parent->m_focused_child = NULL;
|
|
}
|
|
InvalidateRect(hwnd,NULL,FALSE);
|
|
}
|
|
|
|
void SetForegroundWindow(HWND hwnd)
|
|
{
|
|
if (WDL_NOT_NORMALLY(!hwnd)) return;
|
|
|
|
HWND oldFoc = GetFocus();
|
|
// if a child window has focus, preserve that focus
|
|
while (hwnd->m_parent && !hwnd->m_oswindow)
|
|
{
|
|
hwnd->m_parent->m_focused_child = hwnd;
|
|
hwnd = hwnd->m_parent;
|
|
}
|
|
if (hwnd) swell_oswindow_focus(hwnd);
|
|
HWND foc = GetFocus();
|
|
if (foc && foc != oldFoc)
|
|
SendMessage(foc,WM_SETFOCUS,(WPARAM)oldFoc,0);
|
|
}
|
|
|
|
void SetFocus(HWND hwnd)
|
|
{
|
|
if (!hwnd) return; // windows allows SetFocus(NULL) to defocus, but we do not support that yet
|
|
HWND oldFoc = GetFocus();
|
|
if (oldFoc && oldFoc != hwnd)
|
|
SendMessage(oldFoc,WM_KILLFOCUS,(WPARAM)hwnd,0);
|
|
hwnd->m_focused_child=NULL; // make sure this window has focus, not a child
|
|
|
|
HWND p = hwnd;
|
|
while (p->m_parent && !p->m_oswindow)
|
|
{
|
|
p->m_parent->m_focused_child = p;
|
|
p = p->m_parent;
|
|
}
|
|
if (p) swell_oswindow_focus(p);
|
|
|
|
if (hwnd != oldFoc)
|
|
SendMessage(hwnd,WM_SETFOCUS,(WPARAM)oldFoc,0);
|
|
}
|
|
|
|
void SWELL_OnNavigationFocus(HWND ch)
|
|
{
|
|
if (ch && ch->m_classname && (
|
|
(!strcmp(ch->m_classname,"Edit") && !(ch->m_style & ES_READONLY)) ||
|
|
!strcmp(ch->m_classname,"combobox")
|
|
))
|
|
{
|
|
SendMessage(ch,EM_SETSEL,0,-1);
|
|
}
|
|
|
|
}
|
|
|
|
int IsChild(HWND hwndParent, HWND hwndChild)
|
|
{
|
|
if (!hwndParent || !hwndChild || hwndParent == hwndChild) return 0;
|
|
|
|
while (hwndChild && hwndChild != hwndParent) hwndChild = hwndChild->m_parent;
|
|
|
|
return hwndChild == hwndParent;
|
|
}
|
|
|
|
HWND SWELL_GetFocusedChild(HWND h)
|
|
{
|
|
while (h)
|
|
{
|
|
HWND fc = h->m_focused_child;
|
|
if (!fc) break;
|
|
HWND s = h->m_children;
|
|
while (s && s != fc) s = s->m_next;
|
|
if (!s) break;
|
|
h = s; // descend to focused child
|
|
}
|
|
return h;
|
|
}
|
|
|
|
HWND GetFocusIncludeMenus()
|
|
{
|
|
HWND h = swell_is_app_inactive()>0 ? NULL : swell_oswindow_to_hwnd(SWELL_focused_oswindow);
|
|
return SWELL_GetFocusedChild(h);
|
|
}
|
|
|
|
HWND GetForegroundWindow()
|
|
{
|
|
return GetFocus();
|
|
}
|
|
|
|
HWND GetFocus()
|
|
{
|
|
HWND h =GetFocusIncludeMenus();
|
|
HWND ho;
|
|
while (h && (ho=(HWND)GetProp(h,"SWELL_MenuOwner"))) h=ho;
|
|
return h;
|
|
}
|
|
|
|
void ScreenToClient(HWND hwnd, POINT *pt)
|
|
{
|
|
if (WDL_NOT_NORMALLY(!hwnd)) return;
|
|
|
|
HWND tmp=hwnd;
|
|
while (tmp)
|
|
{
|
|
NCCALCSIZE_PARAMS p = {{ tmp->m_position, }, };
|
|
if (tmp->m_wndproc) tmp->m_wndproc(tmp,WM_NCCALCSIZE,0,(LPARAM)&p);
|
|
|
|
pt->x -= p.rgrc[0].left;
|
|
pt->y -= p.rgrc[0].top;
|
|
tmp = tmp->m_parent;
|
|
}
|
|
}
|
|
|
|
void ClientToScreen(HWND hwnd, POINT *pt)
|
|
{
|
|
if (WDL_NOT_NORMALLY(!hwnd)) return;
|
|
|
|
HWND tmp=hwnd;
|
|
while (tmp)
|
|
{
|
|
NCCALCSIZE_PARAMS p={{tmp->m_position, }, };
|
|
if (tmp->m_wndproc) tmp->m_wndproc(tmp,WM_NCCALCSIZE,0,(LPARAM)&p);
|
|
pt->x += p.rgrc[0].left;
|
|
pt->y += p.rgrc[0].top;
|
|
tmp = tmp->m_parent;
|
|
}
|
|
}
|
|
|
|
void GetWindowContentViewRect(HWND hwnd, RECT *r)
|
|
{
|
|
if (WDL_NORMALLY(hwnd) && hwnd->m_oswindow)
|
|
{
|
|
*r = hwnd->m_position;
|
|
return;
|
|
}
|
|
GetWindowRect(hwnd,r);
|
|
}
|
|
|
|
void GetClientRect(HWND hwnd, RECT *r)
|
|
{
|
|
r->left=r->top=r->right=r->bottom=0;
|
|
if (WDL_NOT_NORMALLY(!hwnd)) return;
|
|
|
|
r->right = hwnd->m_position.right - hwnd->m_position.left;
|
|
r->bottom = hwnd->m_position.bottom - hwnd->m_position.top;
|
|
|
|
NCCALCSIZE_PARAMS tr={{*r, },};
|
|
SendMessage(hwnd,WM_NCCALCSIZE,FALSE,(LPARAM)&tr);
|
|
r->right = r->left + (tr.rgrc[0].right-tr.rgrc[0].left);
|
|
r->bottom=r->top + (tr.rgrc[0].bottom-tr.rgrc[0].top);
|
|
}
|
|
|
|
|
|
|
|
void SetWindowPos(HWND hwnd, HWND zorder, int x, int y, int cx, int cy, int flags)
|
|
{
|
|
if (WDL_NOT_NORMALLY(!hwnd)) return;
|
|
// todo: handle SWP_SHOWWINDOW
|
|
RECT f = hwnd->m_position;
|
|
int reposflag = 0;
|
|
if (!(flags&SWP_NOZORDER))
|
|
{
|
|
if (hwnd->m_parent && zorder != hwnd)
|
|
{
|
|
HWND par = hwnd->m_parent;
|
|
HWND tmp = par->m_children;
|
|
while (tmp && tmp != hwnd) tmp=tmp->m_next;
|
|
if (tmp) // we are in the list, so we can do a reorder
|
|
{
|
|
// take hwnd out of list
|
|
if (hwnd->m_prev) hwnd->m_prev->m_next = hwnd->m_next;
|
|
else par->m_children = hwnd->m_next;
|
|
if (hwnd->m_next) hwnd->m_next->m_prev = hwnd->m_prev;
|
|
hwnd->m_next=hwnd->m_prev=NULL;// leave hwnd->m_parent valid since it wont change
|
|
|
|
// add back in
|
|
tmp = par->m_children;
|
|
if (zorder == HWND_BOTTOM || !tmp) // insert at front of list
|
|
{
|
|
if (tmp) tmp->m_prev=hwnd;
|
|
hwnd->m_next = tmp;
|
|
par->m_children = hwnd;
|
|
}
|
|
else
|
|
{
|
|
// zorder could be HWND_TOP here
|
|
while (tmp && tmp != zorder && tmp->m_next) tmp=tmp->m_next;
|
|
|
|
// tmp is either zorder or the last item in the list
|
|
hwnd->m_next = tmp->m_next;
|
|
tmp->m_next = hwnd;
|
|
if (hwnd->m_next) hwnd->m_next->m_prev = hwnd;
|
|
hwnd->m_prev = tmp;
|
|
}
|
|
reposflag|=4;
|
|
}
|
|
}
|
|
}
|
|
if (!(flags&SWP_NOMOVE))
|
|
{
|
|
int oldw = f.right-f.left;
|
|
int oldh = f.bottom-f.top;
|
|
f.left=x;
|
|
f.right=x+oldw;
|
|
f.top=y;
|
|
f.bottom=y+oldh;
|
|
reposflag|=1;
|
|
hwnd->m_has_had_position=true;
|
|
}
|
|
if (!(flags&SWP_NOSIZE))
|
|
{
|
|
f.right = f.left + cx;
|
|
f.bottom = f.top + cy;
|
|
reposflag|=2;
|
|
}
|
|
if (reposflag)
|
|
{
|
|
if (hwnd->m_oswindow && (reposflag&2))
|
|
{
|
|
swell_oswindow_begin_resize(hwnd->m_oswindow);
|
|
}
|
|
|
|
if (reposflag&3)
|
|
{
|
|
hwnd->m_position = f;
|
|
}
|
|
|
|
if (hwnd->m_oswindow && !hwnd->m_oswindow_fullscreen)
|
|
{
|
|
swell_oswindow_resize(hwnd->m_oswindow,reposflag,f);
|
|
if (reposflag&2) SendMessage(hwnd,WM_SIZE,SIZE_RESTORED,0);
|
|
}
|
|
else
|
|
{
|
|
if (reposflag&2) SendMessage(hwnd,WM_SIZE,SIZE_RESTORED,0);
|
|
InvalidateRect(hwnd->m_parent ? hwnd->m_parent : hwnd,NULL,FALSE);
|
|
}
|
|
}
|
|
swell_oswindow_postresize(hwnd,f);
|
|
}
|
|
|
|
|
|
BOOL EnumWindows(BOOL (*proc)(HWND, LPARAM), LPARAM lp)
|
|
{
|
|
HWND h = SWELL_topwindows;
|
|
if (!proc) return FALSE;
|
|
while (h)
|
|
{
|
|
if (!proc(h,lp)) return FALSE;
|
|
h = h->m_next;
|
|
}
|
|
return TRUE;
|
|
}
|
|
|
|
HWND GetWindow(HWND hwnd, int what)
|
|
{
|
|
if (WDL_NOT_NORMALLY(!hwnd)) return 0;
|
|
|
|
if (what == GW_CHILD) return hwnd->m_children;
|
|
if (what == GW_OWNER) return hwnd->m_owner;
|
|
if (what == GW_HWNDNEXT) return hwnd->m_next;
|
|
if (what == GW_HWNDPREV) return hwnd->m_prev;
|
|
if (what == GW_HWNDFIRST)
|
|
{
|
|
while (hwnd->m_prev) hwnd = hwnd->m_prev;
|
|
return hwnd;
|
|
}
|
|
if (what == GW_HWNDLAST)
|
|
{
|
|
while (hwnd->m_next) hwnd = hwnd->m_next;
|
|
return hwnd;
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
HWND SetParent(HWND hwnd, HWND newPar)
|
|
{
|
|
if (WDL_NOT_NORMALLY(!hwnd)) return NULL;
|
|
|
|
HWND oldPar = hwnd->m_parent;
|
|
|
|
swell_removeWindowFromParentOrTop(hwnd, newPar != NULL && newPar != oldPar);
|
|
|
|
if (newPar)
|
|
{
|
|
HWND fc = newPar->m_children;
|
|
if (!fc)
|
|
{
|
|
newPar->m_children = hwnd;
|
|
}
|
|
else
|
|
{
|
|
while (fc->m_next) fc = fc->m_next;
|
|
hwnd->m_prev = fc;
|
|
fc->m_next = hwnd;
|
|
}
|
|
hwnd->m_parent = newPar;
|
|
hwnd->m_style |= WS_CHILD;
|
|
|
|
if (newPar) VALIDATE_HWND_LIST(newPar->m_children,newPar);
|
|
}
|
|
else // add to top level windows
|
|
{
|
|
hwnd->m_next=SWELL_topwindows;
|
|
if (hwnd->m_next) hwnd->m_next->m_prev = hwnd;
|
|
SWELL_topwindows = hwnd;
|
|
VALIDATE_HWND_LIST(SWELL_topwindows,NULL);
|
|
hwnd->m_style &= ~WS_CHILD;
|
|
}
|
|
|
|
swell_oswindow_manage(hwnd,false);
|
|
return oldPar;
|
|
}
|
|
|
|
|
|
|
|
|
|
// timer stuff
|
|
typedef struct TimerInfoRec
|
|
{
|
|
UINT_PTR timerid;
|
|
HWND hwnd;
|
|
UINT interval;
|
|
|
|
DWORD lastFire;
|
|
int refcnt; // 0 normally, 1 if currently processing this timer
|
|
|
|
TIMERPROC tProc;
|
|
struct TimerInfoRec *_next;
|
|
} TimerInfoRec;
|
|
|
|
static TimerInfoRec *m_timer_list;
|
|
static WDL_Mutex m_timermutex;
|
|
|
|
static TimerInfoRec *spare_timers;
|
|
static void free_timer(TimerInfoRec *rec)
|
|
{
|
|
int c = 3; // max spares
|
|
TimerInfoRec *p = spare_timers;
|
|
while (p)
|
|
{
|
|
if (--c <= 0)
|
|
{
|
|
free(rec);
|
|
return;
|
|
}
|
|
p = p->_next;
|
|
}
|
|
rec->_next=spare_timers;
|
|
spare_timers=rec;
|
|
}
|
|
|
|
|
|
void SWELL_RunMessageLoop()
|
|
{
|
|
SWELL_MessageQueue_Flush();
|
|
SWELL_RunEvents();
|
|
|
|
const DWORD now = GetTickCount();
|
|
WDL_MutexLock lock(&m_timermutex);
|
|
TimerInfoRec *rec = m_timer_list;
|
|
while (rec)
|
|
{
|
|
// rec->lastFire might be after now, if the timer was set from another timer
|
|
// we should run this timer if rec->lastFire is in the interval
|
|
// [now-rec->interval-some big range,now-rec->interval]
|
|
if (WDL_TICKS_IN_RANGE_ENDING_AT(rec->lastFire, now - rec->interval, 100000))
|
|
{
|
|
rec->lastFire = GetTickCount();
|
|
++rec->refcnt;
|
|
|
|
HWND h = rec->hwnd;
|
|
TIMERPROC tProc = rec->tProc;
|
|
UINT_PTR tid = rec->timerid;
|
|
m_timermutex.Leave();
|
|
|
|
if (tProc) tProc(h,WM_TIMER,tid,now);
|
|
else if (h) SendMessage(h,WM_TIMER,tid,0);
|
|
|
|
m_timermutex.Enter();
|
|
|
|
if (--rec->refcnt < 0)
|
|
{
|
|
free_timer(rec);
|
|
rec = m_timer_list;
|
|
continue;
|
|
}
|
|
}
|
|
rec=rec->_next;
|
|
}
|
|
}
|
|
|
|
UINT_PTR SetTimer(HWND hwnd, UINT_PTR timerid, UINT rate, TIMERPROC tProc)
|
|
{
|
|
if (WDL_NOT_NORMALLY(!hwnd && !tProc)) return 0; // must have either callback or hwnd
|
|
|
|
if (WDL_NOT_NORMALLY(hwnd && !timerid)) return 0;
|
|
|
|
if (hwnd && hwnd->m_hashaddestroy) return 0;
|
|
|
|
WDL_MutexLock lock(&m_timermutex);
|
|
TimerInfoRec *rec=NULL;
|
|
if (hwnd||timerid)
|
|
{
|
|
rec = m_timer_list;
|
|
while (rec)
|
|
{
|
|
if (rec->timerid == timerid && rec->hwnd == hwnd) // works for both kinds
|
|
break;
|
|
rec=rec->_next;
|
|
}
|
|
}
|
|
|
|
bool recAdd=false;
|
|
if (!rec)
|
|
{
|
|
rec = spare_timers;
|
|
if (!rec) rec = (TimerInfoRec *)malloc(sizeof(TimerInfoRec));
|
|
else spare_timers = rec->_next;
|
|
rec->refcnt = 0;
|
|
recAdd=true;
|
|
}
|
|
|
|
if (!hwnd) timerid = (UINT_PTR)rec;
|
|
|
|
rec->tProc = tProc;
|
|
rec->timerid = timerid;
|
|
rec->hwnd = hwnd;
|
|
rec->interval = rate<1?1: rate;
|
|
rec->lastFire = GetTickCount();
|
|
|
|
if (recAdd)
|
|
{
|
|
rec->_next=m_timer_list;
|
|
m_timer_list=rec;
|
|
}
|
|
|
|
return timerid;
|
|
}
|
|
|
|
BOOL KillTimer(HWND hwnd, UINT_PTR timerid)
|
|
{
|
|
if (WDL_NOT_NORMALLY(!hwnd && !timerid)) return FALSE;
|
|
|
|
WDL_MutexLock lock(&m_timermutex);
|
|
BOOL rv=FALSE;
|
|
|
|
// don't allow removing all global timers
|
|
if (timerid!=(UINT_PTR)-1 || hwnd)
|
|
{
|
|
TimerInfoRec *rec = m_timer_list, *lrec=NULL;
|
|
while (rec)
|
|
{
|
|
if (rec->hwnd == hwnd && (timerid==(UINT_PTR)-1 || rec->timerid == timerid))
|
|
{
|
|
TimerInfoRec *nrec = rec->_next;
|
|
|
|
// remove self from list
|
|
if (lrec) lrec->_next = nrec;
|
|
else m_timer_list = nrec;
|
|
|
|
if (--rec->refcnt < 0)
|
|
free_timer(rec);
|
|
|
|
rv=TRUE;
|
|
if (timerid!=(UINT_PTR)-1) break;
|
|
|
|
rec=nrec;
|
|
}
|
|
else
|
|
{
|
|
lrec=rec;
|
|
rec=rec->_next;
|
|
}
|
|
}
|
|
}
|
|
return rv;
|
|
}
|
|
|
|
BOOL SetDlgItemText(HWND hwnd, int idx, const char *text)
|
|
{
|
|
hwnd =(idx ? GetDlgItem(hwnd,idx) : hwnd);
|
|
if (WDL_NOT_NORMALLY(!hwnd)) return false;
|
|
|
|
if (!text) text="";
|
|
|
|
|
|
if (strcmp(hwnd->m_title.Get(), text))
|
|
{
|
|
hwnd->m_title.Set(text);
|
|
swell_oswindow_update_text(hwnd);
|
|
}
|
|
SendMessage(hwnd,WM_SETTEXT,0,(LPARAM)text);
|
|
return true;
|
|
}
|
|
|
|
int GetWindowTextLength(HWND hwnd)
|
|
{
|
|
return WDL_NORMALLY(hwnd) ? hwnd->m_title.GetLength() : 0;
|
|
}
|
|
|
|
BOOL GetDlgItemText(HWND hwnd, int idx, char *text, int textlen)
|
|
{
|
|
*text=0;
|
|
hwnd = idx?GetDlgItem(hwnd,idx) : hwnd;
|
|
if (WDL_NOT_NORMALLY(!hwnd)) return false;
|
|
|
|
// todo: sendmessage WM_GETTEXT etc? special casing for combo boxes etc
|
|
lstrcpyn_safe(text,hwnd->m_title.Get(), textlen);
|
|
return true;
|
|
}
|
|
|
|
void CheckDlgButton(HWND hwnd, int idx, int check)
|
|
{
|
|
hwnd = GetDlgItem(hwnd,idx);
|
|
if (WDL_NOT_NORMALLY(!hwnd)) return;
|
|
SendMessage(hwnd,BM_SETCHECK,check,0);
|
|
}
|
|
|
|
|
|
int IsDlgButtonChecked(HWND hwnd, int idx)
|
|
{
|
|
hwnd = GetDlgItem(hwnd,idx);
|
|
if (WDL_NOT_NORMALLY(!hwnd)) return 0;
|
|
return SendMessage(hwnd,BM_GETCHECK,0,0);
|
|
}
|
|
|
|
|
|
BOOL SetDlgItemInt(HWND hwnd, int idx, int val, int issigned)
|
|
{
|
|
char buf[128];
|
|
sprintf(buf,issigned?"%d":"%u",val);
|
|
return SetDlgItemText(hwnd,idx,buf);
|
|
}
|
|
|
|
int GetDlgItemInt(HWND hwnd, int idx, BOOL *translated, int issigned)
|
|
{
|
|
char buf[128];
|
|
if (!GetDlgItemText(hwnd,idx,buf,sizeof(buf)))
|
|
{
|
|
if (translated) *translated=0;
|
|
return 0;
|
|
}
|
|
char *p=buf;
|
|
while (*p == ' ' || *p == '\t') p++;
|
|
int a=atoi(p);
|
|
if ((a<0 && !issigned) || (!a && p[0] != '0')) { if (translated) *translated=0; return 0; }
|
|
if (translated) *translated=1;
|
|
return a;
|
|
}
|
|
|
|
void ShowWindow(HWND hwnd, int cmd)
|
|
{
|
|
if (WDL_NOT_NORMALLY(!hwnd)) return;
|
|
|
|
if (cmd==SW_SHOW||cmd==SW_SHOWNA)
|
|
{
|
|
if (hwnd->m_visible) cmd = SW_SHOWNA; // do not take focus if already visible
|
|
hwnd->m_visible=true;
|
|
}
|
|
else if (cmd == SW_RESTORE || cmd == SW_SHOWMAXIMIZED)
|
|
{
|
|
hwnd->m_visible=true;
|
|
}
|
|
else if (cmd==SW_HIDE)
|
|
{
|
|
if (hwnd->m_visible)
|
|
{
|
|
hwnd->m_visible=false;
|
|
if (hwnd->m_parent)
|
|
InvalidateRect(hwnd->m_parent,&hwnd->m_position,FALSE);
|
|
}
|
|
}
|
|
|
|
swell_oswindow_manage(hwnd,cmd==SW_SHOW);
|
|
|
|
if (cmd == SW_SHOW)
|
|
{
|
|
SetForegroundWindow(hwnd);
|
|
}
|
|
else if (cmd == SW_RESTORE || cmd == SW_SHOWMAXIMIZED)
|
|
swell_oswindow_maximize(hwnd,cmd == SW_SHOWMAXIMIZED);
|
|
|
|
InvalidateRect(hwnd,NULL,FALSE);
|
|
|
|
}
|
|
|
|
void *SWELL_ModalWindowStart(HWND hwnd)
|
|
{
|
|
return 0;
|
|
}
|
|
|
|
bool SWELL_ModalWindowRun(void *ctx, int *ret) // returns false and puts retval in *ret when done
|
|
{
|
|
return false;
|
|
}
|
|
|
|
void SWELL_ModalWindowEnd(void *ctx)
|
|
{
|
|
if (ctx)
|
|
{
|
|
}
|
|
}
|
|
|
|
void SWELL_CloseWindow(HWND hwnd)
|
|
{
|
|
DestroyWindow(hwnd);
|
|
}
|
|
|
|
|
|
static void Draw3DBox(HDC hdc, const RECT *r, int bgc, int topc, int botc, bool swap=false)
|
|
{
|
|
RECT tr = *r;
|
|
tr.right--;
|
|
tr.bottom--;
|
|
if (bgc != -1)
|
|
{
|
|
tr.left++;
|
|
tr.top++;
|
|
HBRUSH br = CreateSolidBrush(bgc);
|
|
FillRect(hdc,&tr,br);
|
|
DeleteObject(br);
|
|
tr.left--;
|
|
tr.top--;
|
|
}
|
|
|
|
if (topc == -1 && botc == -1) return;
|
|
|
|
HPEN pen = CreatePen(PS_SOLID,0,swap?botc:topc);
|
|
HPEN pen2 = CreatePen(PS_SOLID,0,swap?topc:botc);
|
|
HGDIOBJ oldpen = SelectObject(hdc,pen);
|
|
MoveToEx(hdc,tr.left,tr.bottom,NULL);
|
|
LineTo(hdc,tr.left,tr.top);
|
|
LineTo(hdc,tr.right,tr.top);
|
|
|
|
SelectObject(hdc,pen2);
|
|
LineTo(hdc,tr.right,tr.bottom);
|
|
LineTo(hdc,tr.left,tr.bottom);
|
|
|
|
SelectObject(hdc,oldpen);
|
|
DeleteObject(pen);
|
|
DeleteObject(pen2);
|
|
}
|
|
|
|
|
|
#include "swell-dlggen.h"
|
|
|
|
static HWND m_make_owner;
|
|
static RECT m_transform;
|
|
static bool m_doautoright;
|
|
static RECT m_lastdoauto;
|
|
static bool m_sizetofits;
|
|
|
|
#define ACTIONTARGET (m_make_owner)
|
|
|
|
void SWELL_MakeSetCurParms(float xscale, float yscale, float xtrans, float ytrans, HWND parent, bool doauto, bool dosizetofit)
|
|
{
|
|
if (g_swell_ui_scale != 256 && xscale != 1.0f && yscale != 1.0f)
|
|
{
|
|
const float m = g_swell_ui_scale/256.0f;
|
|
xscale *= m;
|
|
yscale *= m;
|
|
}
|
|
m_sizetofits=dosizetofit;
|
|
m_lastdoauto.left = 0;
|
|
m_lastdoauto.top = -6553600;
|
|
m_lastdoauto.right = 0;
|
|
m_doautoright=doauto;
|
|
m_transform.left=(int)(xtrans*65536.0);
|
|
m_transform.top=(int)(ytrans*65536.0);
|
|
m_transform.right=(int)(xscale*65536.0);
|
|
m_transform.bottom=(int)(yscale*65536.0);
|
|
m_make_owner=parent;
|
|
}
|
|
|
|
static void UpdateAutoCoords(RECT r)
|
|
{
|
|
m_lastdoauto.right=r.left + r.right - m_lastdoauto.left;
|
|
}
|
|
|
|
|
|
static RECT MakeCoords(int x, int y, int w, int h, bool wantauto)
|
|
{
|
|
if (w<0&&h<0)
|
|
{
|
|
RECT r = { -x, -y, -x-w, -y-h};
|
|
return r;
|
|
}
|
|
|
|
float ysc=m_transform.bottom/65536.0;
|
|
int newx=(int)((x+m_transform.left/65536.0)*m_transform.right/65536.0 + 0.5);
|
|
int newy=(int)(((((double)y+(double)m_transform.top/65536.0) )*ysc) + 0.5);
|
|
|
|
RECT ret= { newx,
|
|
newy,
|
|
(int) (newx + w*(double)m_transform.right/65536.0+0.5),
|
|
(int) (newy + h*fabs(ysc)+0.5)
|
|
};
|
|
|
|
|
|
RECT oret=ret;
|
|
if (wantauto && m_doautoright)
|
|
{
|
|
float dx = ret.left - m_lastdoauto.left;
|
|
if (fabs(dx)<32 && m_lastdoauto.top >= ret.top && m_lastdoauto.top < ret.bottom)
|
|
{
|
|
ret.left += (int) m_lastdoauto.right;
|
|
}
|
|
|
|
m_lastdoauto.left = oret.right;
|
|
m_lastdoauto.top = (ret.top + ret.bottom)*0.5;
|
|
m_lastdoauto.right=0;
|
|
}
|
|
return ret;
|
|
}
|
|
|
|
#define TRANSFORMFONTSIZE ((m_transform.right/65536.0+1.0)*3.7)
|
|
|
|
static void paintDialogBackground(HWND hwnd, const RECT *r, HDC hdc)
|
|
{
|
|
HBRUSH hbrush = (HBRUSH) SendMessage(GetParent(hwnd),WM_CTLCOLORSTATIC,(WPARAM)hdc,(LPARAM)hwnd);
|
|
if (hbrush == (HBRUSH)(INT_PTR)1) return;
|
|
|
|
if (hbrush)
|
|
{
|
|
FillRect(hdc,r,hbrush);
|
|
}
|
|
else
|
|
{
|
|
SWELL_FillDialogBackground(hdc,r,0);
|
|
}
|
|
}
|
|
|
|
static bool fast_has_focus(HWND hwnd)
|
|
{
|
|
if (!hwnd || !SWELL_focused_oswindow || swell_is_app_inactive()>0) return false;
|
|
HWND par;
|
|
while ((par=hwnd->m_parent)!=NULL && par->m_focused_child==hwnd)
|
|
{
|
|
if (par->m_oswindow == SWELL_focused_oswindow) return true;
|
|
hwnd=par;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
static bool draw_focus_indicator(HWND hwnd, HDC hdc, const RECT *drawr)
|
|
{
|
|
if (!fast_has_focus(hwnd)) return false;
|
|
|
|
RECT r,tr;
|
|
const int sz = SWELL_UI_SCALE(3);
|
|
if (drawr) r=*drawr;
|
|
else GetClientRect(hwnd,&r);
|
|
|
|
HBRUSH br = CreateSolidBrushAlpha(g_swell_ctheme.focus_hilight,.75f);
|
|
tr=r; tr.right = tr.left+sz; FillRect(hdc,&tr,br);
|
|
tr=r; tr.left = tr.right-sz; FillRect(hdc,&tr,br);
|
|
tr=r; tr.left+=sz; tr.right-=sz;
|
|
tr.bottom = tr.top+sz; FillRect(hdc,&tr,br);
|
|
tr.bottom = r.bottom; tr.top = tr.bottom-sz; FillRect(hdc,&tr,br);
|
|
|
|
DeleteObject(br);
|
|
return true;
|
|
}
|
|
|
|
|
|
struct buttonWindowState
|
|
{
|
|
buttonWindowState() { bitmap=0; bitmap_mode=0; state=0; }
|
|
~buttonWindowState() { /* if (bitmap) DeleteObject(bitmap); */ }
|
|
|
|
HICON bitmap;
|
|
int bitmap_mode;
|
|
int state;
|
|
};
|
|
|
|
static LRESULT WINAPI buttonWindowProc(HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam)
|
|
{
|
|
switch (msg)
|
|
{
|
|
case WM_NCDESTROY:
|
|
delete (buttonWindowState *)hwnd->m_private_data;
|
|
hwnd->m_private_data=0;
|
|
break;
|
|
case WM_LBUTTONDOWN:
|
|
SetFocus(hwnd);
|
|
SetCapture(hwnd);
|
|
SendMessage(hwnd,WM_USER+100,0,0); // invalidate
|
|
return 0;
|
|
case WM_MOUSEMOVE:
|
|
return 0;
|
|
case WM_KEYDOWN:
|
|
if (wParam == VK_SPACE) goto fakeButtonClick;
|
|
if (wParam == VK_RETURN)
|
|
{
|
|
if (!(hwnd->m_style & 0xf))
|
|
goto fakeButtonClick;
|
|
}
|
|
break;
|
|
case WM_LBUTTONUP:
|
|
if (GetCapture()==hwnd)
|
|
{
|
|
fakeButtonClick:
|
|
buttonWindowState *s = (buttonWindowState*)hwnd->m_private_data;
|
|
ReleaseCapture(); // WM_CAPTURECHANGED will take care of the invalidate
|
|
RECT r;
|
|
GetClientRect(hwnd,&r);
|
|
POINT p={GET_X_LPARAM(lParam),GET_Y_LPARAM(lParam)};
|
|
hwnd->Retain();
|
|
if ((msg==WM_KEYDOWN||PtInRect(&r,p)) && hwnd->m_id && hwnd->m_parent)
|
|
{
|
|
int sf = (hwnd->m_style & 0xf);
|
|
if (sf == BS_AUTO3STATE)
|
|
{
|
|
int a = s->state&3;
|
|
if (a==0) a=1;
|
|
else if (a==1) a=2;
|
|
else a=0;
|
|
s->state = (a) | (s->state&~3);
|
|
}
|
|
else if (sf == BS_AUTOCHECKBOX)
|
|
{
|
|
s->state = (!(s->state&3)) | (s->state&~3);
|
|
}
|
|
else if (sf == BS_AUTORADIOBUTTON)
|
|
{
|
|
int x;
|
|
for (x=0;x<2;x++)
|
|
{
|
|
HWND nw = x ? hwnd->m_next : hwnd->m_prev;
|
|
while (nw)
|
|
{
|
|
if (nw->m_classname && !strcmp(nw->m_classname,"Button"))
|
|
{
|
|
if (x && (nw->m_style & WS_GROUP)) break;
|
|
|
|
if ((nw->m_style & 0xf) == BS_AUTORADIOBUTTON)
|
|
{
|
|
buttonWindowState *nws = (buttonWindowState*)nw->m_private_data;
|
|
if (nws && (nws->state&3))
|
|
{
|
|
nws->state &= ~3;
|
|
InvalidateRect(nw,NULL,FALSE);
|
|
}
|
|
}
|
|
|
|
if (nw->m_style & WS_GROUP) break;
|
|
}
|
|
else
|
|
{
|
|
break;
|
|
}
|
|
|
|
nw=x ? nw->m_next : nw->m_prev;
|
|
}
|
|
}
|
|
|
|
s->state = 1 | (s->state&~3);
|
|
}
|
|
SendMessage(hwnd->m_parent,WM_COMMAND,MAKEWPARAM(hwnd->m_id,BN_CLICKED),(LPARAM)hwnd);
|
|
}
|
|
if (msg == WM_KEYDOWN) InvalidateRect(hwnd,NULL,FALSE);
|
|
hwnd->Release();
|
|
}
|
|
return 0;
|
|
case WM_PAINT:
|
|
{
|
|
PAINTSTRUCT ps;
|
|
if (BeginPaint(hwnd,&ps))
|
|
{
|
|
buttonWindowState *s = (buttonWindowState*)hwnd->m_private_data;
|
|
RECT r;
|
|
GetClientRect(hwnd,&r);
|
|
|
|
bool pressed = GetCapture()==hwnd;
|
|
|
|
SetBkMode(ps.hdc,TRANSPARENT);
|
|
|
|
if (hwnd->m_enabled)
|
|
SetTextColor(ps.hdc, g_swell_ctheme.button_text);
|
|
|
|
paintDialogBackground(hwnd,&r,ps.hdc);
|
|
|
|
if (!hwnd->m_enabled)
|
|
SetTextColor(ps.hdc, g_swell_ctheme.button_text_disabled);
|
|
|
|
int f=DT_VCENTER|DT_SINGLELINE;
|
|
int sf = (hwnd->m_style & 0xf);
|
|
if (sf == BS_OWNERDRAW)
|
|
{
|
|
if (hwnd->m_parent)
|
|
{
|
|
DRAWITEMSTRUCT dis = { ODT_BUTTON, hwnd->m_id, 0, 0, (UINT)(pressed?ODS_SELECTED:0),hwnd,ps.hdc,r,(DWORD_PTR)hwnd->m_userdata };
|
|
SendMessage(hwnd->m_parent,WM_DRAWITEM,(WPARAM)hwnd->m_id,(LPARAM)&dis);
|
|
}
|
|
EndPaint(hwnd,&ps);
|
|
return 0;
|
|
}
|
|
|
|
const bool ischk = sf == BS_AUTO3STATE || sf == BS_AUTOCHECKBOX || sf == BS_AUTORADIOBUTTON;
|
|
if (!ischk)
|
|
{
|
|
Draw3DBox(ps.hdc,&r,g_swell_ctheme.button_bg,
|
|
g_swell_ctheme.button_hilight,
|
|
g_swell_ctheme.button_shadow,pressed);
|
|
|
|
if ((hwnd->m_style & BS_XPOSITION_MASK) == BS_LEFT)
|
|
r.left+=2;
|
|
else
|
|
f|=DT_CENTER;
|
|
if (pressed)
|
|
{
|
|
const int pad = SWELL_UI_SCALE(2);
|
|
r.left+=pad;
|
|
r.top+=pad;
|
|
if (s->bitmap) { r.right+=pad; r.bottom+=pad; }
|
|
}
|
|
}
|
|
|
|
if (draw_focus_indicator(hwnd,ps.hdc,NULL))
|
|
{
|
|
KillTimer(hwnd,1);
|
|
SetTimer(hwnd,1,100,NULL);
|
|
}
|
|
|
|
if (ischk)
|
|
{
|
|
const int chksz = SWELL_UI_SCALE(12), chki = SWELL_UI_SCALE(2);
|
|
RECT tr={r.left+chki,(r.top+r.bottom)/2-chksz/2,r.left+chki+chksz};
|
|
tr.bottom = tr.top+chksz;
|
|
|
|
HPEN pen=CreatePen(PS_SOLID,0,g_swell_ctheme.checkbox_fg);
|
|
HGDIOBJ oldPen = SelectObject(ps.hdc,pen);
|
|
int st = (int)(s->state&3);
|
|
if (sf == BS_AUTOCHECKBOX || sf == BS_AUTO3STATE)
|
|
{
|
|
if (st==3||(st==2 && (hwnd->m_style & 0xf) == BS_AUTOCHECKBOX)) st=1;
|
|
|
|
Draw3DBox(ps.hdc,&tr,
|
|
st==2?g_swell_ctheme.checkbox_inter:
|
|
g_swell_ctheme.checkbox_bg,
|
|
g_swell_ctheme.button_shadow,
|
|
g_swell_ctheme.button_hilight);
|
|
|
|
if (st == 1||pressed)
|
|
{
|
|
RECT ar=tr;
|
|
ar.left+=SWELL_UI_SCALE(2);
|
|
ar.right-=SWELL_UI_SCALE(3);
|
|
ar.top+=SWELL_UI_SCALE(2);
|
|
ar.bottom-=SWELL_UI_SCALE(3);
|
|
if (pressed)
|
|
{
|
|
const int rsz=chksz/4;
|
|
ar.left+=rsz;
|
|
ar.top+=rsz;
|
|
ar.right-=rsz;
|
|
ar.bottom-=rsz;
|
|
}
|
|
MoveToEx(ps.hdc,ar.left,ar.top,NULL);
|
|
LineTo(ps.hdc,ar.right,ar.bottom);
|
|
MoveToEx(ps.hdc,ar.right,ar.top,NULL);
|
|
LineTo(ps.hdc,ar.left,ar.bottom);
|
|
}
|
|
}
|
|
else if (sf == BS_AUTORADIOBUTTON)
|
|
{
|
|
HBRUSH br = CreateSolidBrush(g_swell_ctheme.checkbox_bg);
|
|
HGDIOBJ oldBrush = SelectObject(ps.hdc,br);
|
|
Ellipse(ps.hdc,tr.left+1,tr.top+1,tr.right-1,tr.bottom-1);
|
|
SelectObject(ps.hdc,oldBrush);
|
|
DeleteObject(br);
|
|
if (st)
|
|
{
|
|
const int amt = (tr.right-tr.left)/6 + SWELL_UI_SCALE(2);
|
|
br = CreateSolidBrush(g_swell_ctheme.checkbox_fg);
|
|
oldBrush = SelectObject(ps.hdc,br);
|
|
Ellipse(ps.hdc,tr.left+amt,tr.top+amt,tr.right-amt,tr.bottom-amt);
|
|
SelectObject(ps.hdc,oldBrush);
|
|
DeleteObject(br);
|
|
}
|
|
}
|
|
SelectObject(ps.hdc,oldPen);
|
|
DeleteObject(pen);
|
|
r.left += chksz + SWELL_UI_SCALE(5);
|
|
SetTextColor(ps.hdc,
|
|
hwnd->m_enabled ? g_swell_ctheme.checkbox_text :
|
|
g_swell_ctheme.checkbox_text_disabled);
|
|
}
|
|
|
|
if (s->bitmap)
|
|
{
|
|
BITMAP inf={0,};
|
|
GetObject(s->bitmap,sizeof(BITMAP),&inf);
|
|
RECT cr;
|
|
cr.left = (r.right+r.left - inf.bmWidth)/2;
|
|
cr.top = (r.bottom+r.top - inf.bmHeight)/2;
|
|
cr.right = cr.left+inf.bmWidth;
|
|
cr.bottom = cr.top+inf.bmHeight;
|
|
DrawImageInRect(ps.hdc,s->bitmap,&cr);
|
|
}
|
|
else
|
|
{
|
|
char buf[512];
|
|
buf[0]=0;
|
|
GetWindowText(hwnd,buf,sizeof(buf));
|
|
if (buf[0]) DrawText(ps.hdc,buf,-1,&r,f);
|
|
}
|
|
|
|
EndPaint(hwnd,&ps);
|
|
}
|
|
}
|
|
return 0;
|
|
case WM_TIMER:
|
|
if (wParam==1)
|
|
{
|
|
if (!fast_has_focus(hwnd))
|
|
{
|
|
KillTimer(hwnd,1);
|
|
InvalidateRect(hwnd,NULL,FALSE);
|
|
}
|
|
}
|
|
break;
|
|
case BM_GETCHECK:
|
|
if (hwnd)
|
|
{
|
|
buttonWindowState *s = (buttonWindowState*)hwnd->m_private_data;
|
|
return (s->state&3);
|
|
}
|
|
return 0;
|
|
case BM_GETIMAGE:
|
|
if (wParam != IMAGE_BITMAP && wParam != IMAGE_ICON) return 0; // ignore unknown types
|
|
{
|
|
buttonWindowState *s = (buttonWindowState*)hwnd->m_private_data;
|
|
return (LRESULT) s->bitmap;
|
|
}
|
|
return 0;
|
|
case BM_SETIMAGE:
|
|
if (wParam == IMAGE_BITMAP || wParam == IMAGE_ICON)
|
|
{
|
|
buttonWindowState *s = (buttonWindowState*)hwnd->m_private_data;
|
|
LRESULT res = (LRESULT)s->bitmap;
|
|
s->bitmap = (HICON)lParam;
|
|
s->bitmap_mode = wParam;
|
|
InvalidateRect(hwnd,NULL,FALSE);
|
|
return res;
|
|
}
|
|
return 0;
|
|
case BM_SETCHECK:
|
|
if (hwnd)
|
|
{
|
|
buttonWindowState *s = (buttonWindowState*)hwnd->m_private_data;
|
|
int check = (int)wParam;
|
|
INT_PTR op = s->state;
|
|
s->state=(check > 2 || check<0 ? 1 : (check&3)) | (s->state&~3);
|
|
if (s->state == op) break;
|
|
}
|
|
else
|
|
{
|
|
break;
|
|
}
|
|
// fall through (invalidating)
|
|
case WM_USER+100:
|
|
case WM_CAPTURECHANGED:
|
|
case WM_SETTEXT:
|
|
case WM_SETFOCUS:
|
|
case WM_KILLFOCUS:
|
|
InvalidateRect(hwnd,NULL,FALSE);
|
|
break;
|
|
}
|
|
return DefWindowProc(hwnd,msg,wParam,lParam);
|
|
}
|
|
|
|
static HWND swell_makeButton(HWND owner, int idx, RECT *tr, const char *label, bool vis, int style)
|
|
{
|
|
HWND hwnd = new HWND__(owner,idx,tr,label,vis,buttonWindowProc);
|
|
hwnd->m_private_data = (INT_PTR) new buttonWindowState;
|
|
hwnd->m_classname = "Button";
|
|
hwnd->m_style = style|WS_CHILD;
|
|
hwnd->m_wndproc(hwnd,WM_CREATE,0,0);
|
|
return hwnd;
|
|
}
|
|
|
|
static LRESULT WINAPI groupWindowProc(HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam)
|
|
{
|
|
switch (msg)
|
|
{
|
|
case WM_PAINT:
|
|
{
|
|
PAINTSTRUCT ps;
|
|
if (BeginPaint(hwnd,&ps))
|
|
{
|
|
RECT r;
|
|
GetClientRect(hwnd,&r);
|
|
|
|
const char *buf = hwnd->m_title.Get();
|
|
int th=SWELL_UI_SCALE(20);
|
|
int tw=0;
|
|
int xp=0;
|
|
if (buf && buf[0])
|
|
{
|
|
RECT tr={0,};
|
|
DrawText(ps.hdc,buf,-1,&tr,DT_CALCRECT);
|
|
th=tr.bottom-tr.top;
|
|
tw=tr.right-tr.left;
|
|
}
|
|
if (hwnd->m_style & SS_CENTER)
|
|
{
|
|
xp = r.right/2 - tw/2;
|
|
}
|
|
else if (hwnd->m_style & SS_RIGHT)
|
|
{
|
|
xp = r.right - tw;
|
|
}
|
|
const int sc8 = SWELL_UI_SCALE(8);
|
|
if (xp<sc8)xp=sc8;
|
|
if (xp+tw > r.right-sc8) tw=r.right-sc8-xp;
|
|
|
|
HPEN pen = CreatePen(PS_SOLID,0,g_swell_ctheme.group_hilight);
|
|
HPEN pen2 = CreatePen(PS_SOLID,0,g_swell_ctheme.group_shadow);
|
|
HGDIOBJ oldPen=SelectObject(ps.hdc,pen);
|
|
|
|
MoveToEx(ps.hdc,xp - (tw?sc8/2:0) + 1,th/2+1,NULL);
|
|
LineTo(ps.hdc,1,th/2+1);
|
|
LineTo(ps.hdc,1,r.bottom-1);
|
|
LineTo(ps.hdc,r.right-1,r.bottom-1);
|
|
LineTo(ps.hdc,r.right-1,th/2+1);
|
|
LineTo(ps.hdc,xp+tw + (tw?sc8/2:0),th/2+1);
|
|
|
|
SelectObject(ps.hdc,pen2);
|
|
|
|
MoveToEx(ps.hdc,xp - (tw?sc8/2:0),th/2,NULL);
|
|
LineTo(ps.hdc,0,th/2);
|
|
LineTo(ps.hdc,0,r.bottom-2);
|
|
LineTo(ps.hdc,r.right-2,r.bottom-2);
|
|
LineTo(ps.hdc,r.right-2,th/2);
|
|
LineTo(ps.hdc,xp+tw + (tw?4:0),th/2);
|
|
|
|
|
|
SelectObject(ps.hdc,oldPen);
|
|
DeleteObject(pen);
|
|
DeleteObject(pen2);
|
|
|
|
SetTextColor(ps.hdc,g_swell_ctheme.group_text);
|
|
SetBkMode(ps.hdc,TRANSPARENT);
|
|
r.left = xp;
|
|
r.right = xp+tw;
|
|
r.bottom = th;
|
|
if (buf && buf[0]) DrawText(ps.hdc,buf,-1,&r,DT_LEFT|DT_TOP);
|
|
EndPaint(hwnd,&ps);
|
|
}
|
|
}
|
|
return 0;
|
|
case WM_SETTEXT:
|
|
InvalidateRect(hwnd,NULL,TRUE);
|
|
break;
|
|
case WM_LBUTTONDBLCLK:
|
|
case WM_LBUTTONDOWN:
|
|
case WM_LBUTTONUP:
|
|
case WM_MBUTTONDBLCLK:
|
|
case WM_MBUTTONDOWN:
|
|
case WM_MBUTTONUP:
|
|
case WM_RBUTTONDBLCLK:
|
|
case WM_RBUTTONDOWN:
|
|
case WM_RBUTTONUP:
|
|
case WM_MOUSEMOVE:
|
|
if (GET_Y_LPARAM(lParam) >= SWELL_UI_SCALE(20))
|
|
{
|
|
HWND par = GetParent(hwnd);
|
|
if (par)
|
|
{
|
|
POINT pt = { GET_X_LPARAM(lParam), GET_Y_LPARAM(lParam) };
|
|
ClientToScreen(hwnd,&pt);
|
|
ScreenToClient(par,&pt);
|
|
return SendMessage(par,msg,wParam,MAKELPARAM(pt.x,pt.y));
|
|
}
|
|
}
|
|
break;
|
|
}
|
|
return DefWindowProc(hwnd,msg,wParam,lParam);
|
|
}
|
|
|
|
static void calcScroll(int wh, int totalw, int scroll_x, int *thumbsz, int *thumbpos)
|
|
{
|
|
const double isz = wh / (double) totalw;
|
|
int sz = (int) (wh * isz + 0.5);
|
|
if (sz < g_swell_ctheme.scrollbar_min_thumb_height) sz=g_swell_ctheme.scrollbar_min_thumb_height;
|
|
|
|
*thumbpos = (int) (scroll_x * isz + 0.5);
|
|
if (*thumbpos >= wh-sz) *thumbpos = wh-sz;
|
|
|
|
*thumbsz = sz;
|
|
}
|
|
|
|
static void drawHorizontalScrollbar(HDC hdc, RECT cr, int vieww, int totalw, int scroll_x)
|
|
{
|
|
if (totalw <= vieww) return;
|
|
|
|
int thumbsz, thumbpos;
|
|
calcScroll(vieww,totalw,scroll_x,&thumbsz, &thumbpos);
|
|
|
|
HBRUSH br = CreateSolidBrush(g_swell_ctheme.scrollbar_fg);
|
|
HBRUSH br2 = CreateSolidBrush(g_swell_ctheme.scrollbar_bg);
|
|
RECT fr = { cr.left, cr.bottom - g_swell_ctheme.scrollbar_width, cr.left + thumbpos, cr.bottom };
|
|
if (fr.right>fr.left) FillRect(hdc,&fr,br2);
|
|
|
|
fr.left = fr.right;
|
|
fr.right = fr.left + thumbsz;
|
|
if (fr.right>fr.left) FillRect(hdc,&fr,br);
|
|
|
|
fr.left = fr.right;
|
|
fr.right = cr.right;
|
|
if (fr.right>fr.left) FillRect(hdc,&fr,br2);
|
|
|
|
DeleteObject(br);
|
|
DeleteObject(br2);
|
|
}
|
|
|
|
static void drawVerticalScrollbar(HDC hdc, RECT cr, int totalh, int scroll_y)
|
|
{
|
|
if (totalh <= cr.bottom-cr.top) return;
|
|
|
|
int thumbsz, thumbpos;
|
|
calcScroll(cr.bottom-cr.top,totalh,scroll_y,&thumbsz, &thumbpos);
|
|
|
|
HBRUSH br = CreateSolidBrush(g_swell_ctheme.scrollbar_fg);
|
|
HBRUSH br2 = CreateSolidBrush(g_swell_ctheme.scrollbar_bg);
|
|
RECT fr = { cr.right - g_swell_ctheme.scrollbar_width, cr.top, cr.right,cr.top+thumbpos};
|
|
if (fr.bottom>fr.top) FillRect(hdc,&fr,br2);
|
|
|
|
fr.top = fr.bottom;
|
|
fr.bottom = fr.top + thumbsz;
|
|
if (fr.bottom>fr.top) FillRect(hdc,&fr,br);
|
|
|
|
fr.top = fr.bottom;
|
|
fr.bottom = cr.bottom;
|
|
if (fr.bottom>fr.top)
|
|
{
|
|
FillRect(hdc,&fr,br2);
|
|
|
|
fr.top=fr.bottom-1; //add a little bottom border in case there is a horizontal scrollbar too
|
|
FillRect(hdc,&fr,br2);
|
|
}
|
|
|
|
DeleteObject(br);
|
|
DeleteObject(br2);
|
|
}
|
|
|
|
static int editMeasureLineLength(HDC hdc, const char *str, int str_len)
|
|
{
|
|
RECT tmp = {0,};
|
|
DrawText(hdc,str,str_len,&tmp,DT_NOPREFIX|DT_SINGLELINE|DT_CALCRECT|DT_RIGHT);
|
|
return tmp.right;
|
|
}
|
|
|
|
|
|
int swell_getLineLength(const char *buf, int *post_skip, int wrap_maxwid, HDC hdc)
|
|
{
|
|
int lb=0;
|
|
int ps = 0;
|
|
while (buf[lb] && buf[lb] != '\r' && buf[lb] != '\n') lb++;
|
|
|
|
if (wrap_maxwid > g_swell_ctheme.scrollbar_width && hdc && lb>0)
|
|
{
|
|
wrap_maxwid -= g_swell_ctheme.scrollbar_width;
|
|
|
|
// step through a word at a time and find the most that can fit
|
|
int x=0,best_len=0,sumw=0;
|
|
for (;;)
|
|
{
|
|
while (x < lb && buf[x] > 0 && isspace(buf[x])) x++;
|
|
while (x < lb && (buf[x]<0 || !isspace(buf[x]))) x++;
|
|
const int thisw = editMeasureLineLength(hdc,buf+best_len,x-best_len);
|
|
if (thisw+sumw > wrap_maxwid) break;
|
|
sumw+=thisw;
|
|
best_len=x;
|
|
if (x >= lb) break;
|
|
}
|
|
if (best_len == 0)
|
|
{
|
|
// todo: split individual word (ugh)
|
|
if (x>0) lb = x;
|
|
}
|
|
else
|
|
lb = best_len;
|
|
|
|
while (buf[ps+lb] == '\t' || buf[ps+lb] == ' ') ps++; // skip any trailing whitespace
|
|
}
|
|
if (buf[ps+lb] == '\r') ps++;
|
|
if (buf[ps+lb] == '\n') ps++;
|
|
*post_skip = ps;
|
|
return lb;
|
|
}
|
|
|
|
#define EDIT_ALLOW_MULTILINE_CACHE(wwrap,hwnd,tlen) \
|
|
((wwrap)>0 && ((hwnd)->m_style & (ES_READONLY|ES_MULTILINE)) == (ES_READONLY|ES_MULTILINE) && (tlen)>10000)
|
|
|
|
struct __SWELL_editControlState
|
|
{
|
|
__SWELL_editControlState() : cache_linelen_bytes(8192)
|
|
{
|
|
cursor_timer=0;
|
|
cursor_state=0;
|
|
sel1=sel2=-1;
|
|
cursor_pos=0;
|
|
scroll_x=scroll_y=0;
|
|
max_height=0;
|
|
max_width=0;
|
|
cache_linelen_strlen = cache_linelen_w = 0;
|
|
m_disable_contextmenu = false;
|
|
}
|
|
~__SWELL_editControlState() {}
|
|
|
|
int cursor_pos, sel1,sel2; // in character pos (*not* bytepos)
|
|
int cursor_state;
|
|
int cursor_timer;
|
|
int scroll_x, scroll_y;
|
|
int max_height; // only used in multiline
|
|
int max_width;
|
|
|
|
// used for caching line lengths for multiline word-wrapping edit controls
|
|
int cache_linelen_w, cache_linelen_strlen;
|
|
WDL_TypedBuf<int> cache_linelen_bytes;
|
|
|
|
bool m_disable_contextmenu;
|
|
|
|
bool deleteSelection(WDL_FastString *fs);
|
|
int getSelection(WDL_FastString *fs, const char **ptrOut) const;
|
|
void moveCursor(int cp); // extends selection if shift is held, otherwise clears
|
|
void onMouseDown(int &capmode_state, int last_cursor);
|
|
void onMouseDrag(int &capmode_state, int p);
|
|
|
|
void autoScrollToOffset(HWND hwnd, int charpos, bool is_multiline, bool word_wrap);
|
|
};
|
|
|
|
|
|
static int utf8fs_charpos_to_bytepos(const WDL_FastString *fs, int charpos)
|
|
{
|
|
return charpos < fs->GetLength() ? WDL_utf8_charpos_to_bytepos(fs->Get(),charpos) : fs->GetLength();
|
|
}
|
|
|
|
|
|
|
|
static bool editGetCharPos(HDC hdc, const char *str, int singleline_len, int charpos, int line_h, POINT *pt, int word_wrap,
|
|
__SWELL_editControlState *es, HWND hwnd)
|
|
{
|
|
int bytepos = WDL_utf8_charpos_to_bytepos(str,charpos);
|
|
int ypos = 0;
|
|
if (singleline_len >= 0)
|
|
{
|
|
if (bytepos > singleline_len) return false;
|
|
pt->y=0;
|
|
pt->x=editMeasureLineLength(hdc,str,bytepos);
|
|
return true;
|
|
}
|
|
|
|
|
|
int *use_cache = NULL, use_cache_len = 0;
|
|
int title_len = 0;
|
|
const bool allow_cache = hwnd && es && EDIT_ALLOW_MULTILINE_CACHE(word_wrap,hwnd,title_len = (int)strlen(str));
|
|
if (allow_cache &&
|
|
es->cache_linelen_w == word_wrap &&
|
|
es->cache_linelen_strlen == title_len)
|
|
{
|
|
use_cache = es->cache_linelen_bytes.Get();
|
|
use_cache_len = es->cache_linelen_bytes.GetSize();
|
|
}
|
|
|
|
while (*str)
|
|
{
|
|
int pskip = 0, lb;
|
|
if (!use_cache || use_cache_len < 1)
|
|
{
|
|
lb = swell_getLineLength(str,&pskip,word_wrap,hdc);
|
|
}
|
|
else
|
|
{
|
|
lb = *use_cache++;
|
|
if (WDL_NOT_NORMALLY(lb < 1)) break;
|
|
}
|
|
if (bytepos < lb+pskip)
|
|
{
|
|
pt->x=editMeasureLineLength(hdc,str,bytepos);
|
|
pt->y=ypos;
|
|
return true;
|
|
}
|
|
str += lb+pskip;
|
|
bytepos -= lb+pskip;
|
|
if (*str || (pskip>0 && str[-1] == '\n')) ypos += line_h;
|
|
}
|
|
pt->x=0;
|
|
pt->y=ypos;
|
|
return true;
|
|
}
|
|
|
|
|
|
static int editHitTestLine(HDC hdc, const char *str, int str_len, int xpos, int ypos)
|
|
{
|
|
RECT mr={0,};
|
|
DrawText(hdc,str_len == 0 ? " " : str,wdl_max(str_len,1),&mr,DT_SINGLELINE|DT_NOPREFIX|DT_CALCRECT);
|
|
|
|
if (xpos >= mr.right) return str_len;
|
|
if (xpos < 1) return 0;
|
|
|
|
// could bsearch, but meh
|
|
int x = 0;
|
|
while (x < str_len)
|
|
{
|
|
memset(&mr,0,sizeof(mr));
|
|
const int clen = wdl_utf8_parsechar(str+x,NULL);
|
|
DrawText(hdc,str,x+clen,&mr,DT_SINGLELINE|DT_NOPREFIX|DT_CALCRECT|DT_RIGHT/*swell-only flag*/);
|
|
if (xpos < mr.right) break;
|
|
x += clen;
|
|
}
|
|
return x;
|
|
}
|
|
|
|
static int editHitTest(HDC hdc, const char *str, int singleline_len, int xpos, int ypos, int word_wrap,
|
|
__SWELL_editControlState *es, HWND hwnd)
|
|
{
|
|
if (singleline_len >= 0) return editHitTestLine(hdc,str,singleline_len,xpos,1);
|
|
|
|
const char *buf = str;
|
|
int bytepos = 0;
|
|
RECT tmp={0};
|
|
const int line_h = DrawText(hdc," ",1,&tmp,DT_SINGLELINE|DT_NOPREFIX|DT_CALCRECT);
|
|
|
|
int *use_cache = NULL, use_cache_len = 0;
|
|
int title_len = 0;
|
|
const bool allow_cache = hwnd && es && EDIT_ALLOW_MULTILINE_CACHE(word_wrap,hwnd,title_len = (int)strlen(str));
|
|
if (allow_cache &&
|
|
es->cache_linelen_w == word_wrap &&
|
|
es->cache_linelen_strlen == title_len)
|
|
{
|
|
use_cache = es->cache_linelen_bytes.Get();
|
|
use_cache_len = es->cache_linelen_bytes.GetSize();
|
|
}
|
|
|
|
for (;;)
|
|
{
|
|
int pskip=0;
|
|
int lb;
|
|
|
|
if (!use_cache || use_cache_len < 1)
|
|
{
|
|
lb = swell_getLineLength(buf,&pskip,word_wrap,hdc);
|
|
}
|
|
else
|
|
{
|
|
lb = *use_cache++;
|
|
if (WDL_NOT_NORMALLY(lb < 1)) return bytepos;
|
|
}
|
|
|
|
if (ypos < line_h) return bytepos + editHitTestLine(hdc,buf,lb, xpos,ypos);
|
|
ypos -= line_h;
|
|
|
|
if (!buf[0] || !buf[lb]) return bytepos + lb;
|
|
|
|
bytepos += lb+pskip;
|
|
buf += lb+pskip;
|
|
}
|
|
}
|
|
|
|
|
|
bool __SWELL_editControlState::deleteSelection(WDL_FastString *fs)
|
|
{
|
|
if (sel1>=0 && sel2 > sel1)
|
|
{
|
|
int pos1 = utf8fs_charpos_to_bytepos(fs,sel1);
|
|
int pos2 = utf8fs_charpos_to_bytepos(fs,sel2);
|
|
if (pos2 == pos1) return false;
|
|
|
|
int cp = utf8fs_charpos_to_bytepos(fs,cursor_pos);
|
|
fs->DeleteSub(pos1,pos2-pos1);
|
|
if (cp >= pos2) cp -= pos2-pos1;
|
|
else if (cp >= pos1) cp=pos1;
|
|
cursor_pos = WDL_utf8_bytepos_to_charpos(fs->Get(),cp);
|
|
|
|
sel1=sel2=-1;
|
|
return true;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
int __SWELL_editControlState::getSelection(WDL_FastString *fs, const char **ptrOut) const
|
|
{
|
|
if (sel1>=0 && sel2>sel1)
|
|
{
|
|
int pos1 = utf8fs_charpos_to_bytepos(fs,sel1);
|
|
int pos2 = utf8fs_charpos_to_bytepos(fs,sel2);
|
|
if (ptrOut) *ptrOut = fs->Get()+pos1;
|
|
return pos2-pos1;
|
|
}
|
|
return 0;
|
|
}
|
|
void __SWELL_editControlState::moveCursor(int cp) // extends selection if shift is held, otherwise clears
|
|
{
|
|
if (GetAsyncKeyState(VK_SHIFT)&0x8000)
|
|
{
|
|
if (sel1>=0 && sel2>sel1 && (cursor_pos==sel1 || cursor_pos==sel2))
|
|
{
|
|
if (cursor_pos==sel1) sel1=cp;
|
|
else sel2=cp;
|
|
if (sel2<sel1)
|
|
{
|
|
int a = sel1;
|
|
sel1 = sel2;
|
|
sel2 = a;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
sel1 = wdl_min(cursor_pos,cp);
|
|
sel2 = wdl_max(cursor_pos,cp);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
sel1=sel2=-1;
|
|
}
|
|
cursor_pos = cp;
|
|
}
|
|
|
|
void __SWELL_editControlState::onMouseDown(int &capmode_state, int last_cursor)
|
|
{
|
|
capmode_state = 4;
|
|
|
|
if (GetAsyncKeyState(VK_SHIFT)&0x8000)
|
|
{
|
|
sel1=last_cursor;
|
|
sel2=cursor_pos;
|
|
if (sel1 > sel2)
|
|
{
|
|
sel1=sel2;
|
|
sel2=last_cursor;
|
|
capmode_state = 3;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
sel1=sel2=cursor_pos;
|
|
}
|
|
}
|
|
|
|
void __SWELL_editControlState::onMouseDrag(int &capmode_state, int p)
|
|
{
|
|
if (sel1 == sel2)
|
|
{
|
|
if (p < sel1)
|
|
{
|
|
sel1 = p;
|
|
capmode_state = 3;
|
|
}
|
|
else if (p > sel2)
|
|
{
|
|
sel2 = p;
|
|
capmode_state = 4;
|
|
}
|
|
}
|
|
else if (capmode_state == 3)
|
|
{
|
|
if (p < sel2) sel1 = p;
|
|
else if (p > sel2)
|
|
{
|
|
sel1 = sel2;
|
|
sel2 = p;
|
|
capmode_state=4;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
if (p > sel1) sel2 = p;
|
|
else if (p < sel1)
|
|
{
|
|
sel2 = sel1;
|
|
sel1 = p;
|
|
capmode_state=3;
|
|
}
|
|
}
|
|
}
|
|
|
|
void __SWELL_editControlState::autoScrollToOffset(HWND hwnd, int charpos, bool is_multiline, bool word_wrap)
|
|
{
|
|
if (!hwnd) return;
|
|
HDC hdc = GetDC(hwnd);
|
|
if (!hdc) return;
|
|
RECT tmp={0,};
|
|
const int line_h = DrawText(hdc," ",1,&tmp,DT_CALCRECT|DT_SINGLELINE|DT_NOPREFIX);
|
|
|
|
GetClientRect(hwnd,&tmp);
|
|
if (is_multiline)
|
|
{
|
|
tmp.right -= g_swell_ctheme.scrollbar_width;
|
|
if (!word_wrap) tmp.bottom -= g_swell_ctheme.scrollbar_width;
|
|
}
|
|
|
|
int wwrap = word_wrap?tmp.right:0;
|
|
POINT pt={0,};
|
|
if (editGetCharPos(hdc, hwnd->m_title.Get(),
|
|
is_multiline? -1:hwnd->m_title.GetLength(), charpos, line_h, &pt,
|
|
wwrap, is_multiline?this:NULL,hwnd))
|
|
{
|
|
if (!word_wrap)
|
|
{
|
|
const int padsz = wdl_max(tmp.right - line_h,line_h);
|
|
if (pt.x > scroll_x+padsz) scroll_x = pt.x - padsz;
|
|
if (pt.x < scroll_x) scroll_x=pt.x;
|
|
}
|
|
if (is_multiline)
|
|
{
|
|
if (charpos > hwnd->m_title.GetLength())
|
|
tmp.bottom -= line_h;
|
|
if (pt.y+line_h > scroll_y+tmp.bottom) scroll_y = pt.y - tmp.bottom + line_h;
|
|
if (pt.y < scroll_y) scroll_y=pt.y;
|
|
}
|
|
if (scroll_y < 0) scroll_y=0;
|
|
if (scroll_x < 0) scroll_x=0;
|
|
}
|
|
ReleaseDC(hwnd,hdc);
|
|
}
|
|
|
|
static bool is_word_char(char c)
|
|
{
|
|
return c<0/*all utf-8 chars are word chars*/ || isalnum(c) || c == '_';
|
|
}
|
|
|
|
static int scanWord(const char *buf, int bytepos, int dir)
|
|
{
|
|
if (dir < 0 && !bytepos) return 0;
|
|
if (dir > 0 && !buf[bytepos]) return bytepos;
|
|
|
|
if (!buf[bytepos] && bytepos > 0) bytepos--;
|
|
|
|
const unsigned char *bytebuf = (const unsigned char*) buf;
|
|
if (dir < 0)
|
|
{
|
|
const bool cc = is_word_char(buf[--bytepos]);
|
|
while (bytepos > 0 && is_word_char(buf[bytepos-1]) == cc) bytepos--;
|
|
while (bytepos > 0 && bytebuf[bytepos] >= 0x80 && bytebuf[bytepos] < 0xC0) bytepos--; // skip any UTF-8 continuation bytes
|
|
}
|
|
else
|
|
{
|
|
const bool cc = is_word_char(buf[bytepos]);
|
|
while (buf[bytepos+1] && is_word_char(buf[bytepos+1]) == cc) bytepos++;
|
|
bytepos++;
|
|
while (bytebuf[bytepos] >= 0x80 && bytebuf[bytepos] < 0xC0) bytepos++; // skip any UTF-8 continuation bytes
|
|
}
|
|
|
|
return bytepos;
|
|
}
|
|
|
|
static LRESULT OnEditKeyDown(HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam,
|
|
bool wantReturn, bool isMultiLine, __SWELL_editControlState *es, bool isEditCtl)
|
|
{
|
|
if (lParam & (FCONTROL|FALT|FLWIN))
|
|
{
|
|
if (lParam == (FVIRTKEY | FCONTROL))
|
|
{
|
|
if (wParam == 'C' || wParam == 'X')
|
|
{
|
|
const char *s = NULL;
|
|
int slen = es->getSelection(&hwnd->m_title,&s);
|
|
if (slen > 0 && s)
|
|
{
|
|
if (!isEditCtl || !(hwnd->m_style & ES_PASSWORD))
|
|
{
|
|
OpenClipboard(hwnd);
|
|
HANDLE h = GlobalAlloc(0,slen+1);
|
|
if (h)
|
|
{
|
|
memcpy((char*)h,s,slen);
|
|
((char*)h)[slen] = 0;
|
|
SetClipboardData(CF_TEXT,h);
|
|
}
|
|
CloseClipboard();
|
|
if (h && wParam == 'X' && (!isEditCtl || !(hwnd->m_style & ES_READONLY)))
|
|
{
|
|
es->deleteSelection(&hwnd->m_title);
|
|
return 7;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
else if (wParam == 'V' && !(hwnd->m_style & ES_READONLY))
|
|
{
|
|
OpenClipboard(hwnd);
|
|
HANDLE h = GetClipboardData(CF_TEXT);
|
|
const char *s = NULL;
|
|
if (h)
|
|
{
|
|
s = (const char*)GlobalLock(h);
|
|
if (s)
|
|
{
|
|
es->deleteSelection(&hwnd->m_title);
|
|
int bytepos = utf8fs_charpos_to_bytepos(&hwnd->m_title,es->cursor_pos);
|
|
hwnd->m_title.Insert(s,bytepos);
|
|
if (!(hwnd->m_style&ES_MULTILINE))
|
|
{
|
|
char *p = (char *)hwnd->m_title.Get() + bytepos;
|
|
char *ep = p + strlen(s);
|
|
while (*p && p < ep) { if (*p == '\r' || *p == '\n') *p=' '; p++; }
|
|
}
|
|
es->cursor_pos += WDL_utf8_get_charlen(s);
|
|
GlobalUnlock(h);
|
|
}
|
|
}
|
|
CloseClipboard();
|
|
if (s) return 7;
|
|
}
|
|
else if (wParam == 'A')
|
|
{
|
|
es->sel1 = 0;
|
|
es->cursor_pos = es->sel2 = WDL_utf8_get_charlen(hwnd->m_title.Get());
|
|
return 2;
|
|
}
|
|
}
|
|
if (lParam & (FALT | FLWIN)) return 0;
|
|
}
|
|
else
|
|
{
|
|
if ((lParam & FVIRTKEY) && wParam == VK_TAB) return 0; // pass through to window
|
|
|
|
const bool is_numpad = wParam >= VK_NUMPAD0 && wParam <= VK_DIVIDE;
|
|
if (wParam >= 32 && (!(lParam & FVIRTKEY) || swell_is_virtkey_char((int)wParam) || is_numpad))
|
|
{
|
|
if (lParam & FVIRTKEY)
|
|
{
|
|
if (wParam >= 'A' && wParam <= 'Z')
|
|
{
|
|
if ((lParam&FSHIFT) ^ (swell_is_likely_capslock?0:FSHIFT)) wParam += 'a' - 'A';
|
|
}
|
|
else if (is_numpad)
|
|
{
|
|
if (wParam <= VK_NUMPAD9) wParam += '0' - VK_NUMPAD0;
|
|
else wParam += '*' - VK_MULTIPLY;
|
|
}
|
|
}
|
|
|
|
if (hwnd->m_style & ES_READONLY) return 1;
|
|
|
|
char b[8];
|
|
WDL_MakeUTFChar(b,wParam,sizeof(b));
|
|
es->deleteSelection(&hwnd->m_title);
|
|
int bytepos = utf8fs_charpos_to_bytepos(&hwnd->m_title,es->cursor_pos);
|
|
hwnd->m_title.Insert(b,bytepos);
|
|
es->cursor_pos++;
|
|
return 7;
|
|
}
|
|
}
|
|
|
|
if (es && (lParam & FVIRTKEY)) switch (wParam)
|
|
{
|
|
case VK_NEXT:
|
|
case VK_PRIOR:
|
|
if (!isMultiLine) break;
|
|
case VK_UP:
|
|
case VK_DOWN:
|
|
if (isMultiLine)
|
|
{
|
|
HDC hdc=GetDC(hwnd);
|
|
if (hdc)
|
|
{
|
|
RECT tmp={0};
|
|
const int line_h = DrawText(hdc," ",1,&tmp,DT_SINGLELINE|DT_NOPREFIX|DT_CALCRECT);
|
|
POINT pt;
|
|
GetClientRect(hwnd,&tmp);
|
|
const int wwrap = (hwnd->m_style & ES_AUTOHSCROLL) ? 0 : tmp.right - g_swell_ctheme.scrollbar_width;
|
|
if (editGetCharPos(hdc, hwnd->m_title.Get(), -1, es->cursor_pos, line_h, &pt, wwrap, isEditCtl ? es : NULL, hwnd))
|
|
{
|
|
if (wParam == VK_UP) pt.y -= line_h/2;
|
|
else if (wParam == VK_NEXT)
|
|
{
|
|
int ey = es->scroll_y + tmp.bottom - (wwrap?0:g_swell_ctheme.scrollbar_width) - line_h;
|
|
if (pt.y < ey-line_h) pt.y = ey;
|
|
else pt.y = ey + tmp.bottom - line_h - (wwrap?0:g_swell_ctheme.scrollbar_width);
|
|
}
|
|
else if (wParam == VK_PRIOR)
|
|
{
|
|
if (pt.y > es->scroll_y) pt.y = es->scroll_y;
|
|
else pt.y = es->scroll_y - (tmp.bottom-line_h/2 - g_swell_ctheme.scrollbar_width);
|
|
}
|
|
else pt.y += line_h + line_h/2;
|
|
int nextpos = editHitTest(hdc, hwnd->m_title.Get(), -1,pt.x,pt.y,wwrap,es,hwnd);
|
|
es->moveCursor(WDL_utf8_bytepos_to_charpos(hwnd->m_title.Get(),nextpos));
|
|
}
|
|
ReleaseDC(hwnd,hdc);
|
|
}
|
|
return 3;
|
|
}
|
|
// fall through
|
|
case VK_HOME:
|
|
case VK_END:
|
|
if (isMultiLine)
|
|
{
|
|
const char *buf = hwnd->m_title.Get();
|
|
int lpos = 0, wwrap=0;
|
|
HDC hdc=NULL;
|
|
if (!(hwnd->m_style & ES_AUTOHSCROLL))
|
|
{
|
|
hdc = GetDC(hwnd);
|
|
RECT r;
|
|
GetClientRect(hwnd,&r);
|
|
if (hdc) wwrap = r.right - g_swell_ctheme.scrollbar_width;
|
|
}
|
|
const int cbytepos = WDL_utf8_charpos_to_bytepos(buf,es->cursor_pos);
|
|
for (;;)
|
|
{
|
|
int ps=0, lb = swell_getLineLength(buf+lpos, &ps,wwrap,hdc);
|
|
if (!buf[lpos] || (cbytepos >= lpos && cbytepos < lpos+lb+ps + (buf[lpos+lb+ps]?0:1)))
|
|
{
|
|
if (wParam == VK_HOME) es->moveCursor(WDL_utf8_bytepos_to_charpos(buf,lpos));
|
|
else es->moveCursor(WDL_utf8_bytepos_to_charpos(buf,lpos+lb));
|
|
return 3;
|
|
}
|
|
lpos += lb+ps;
|
|
}
|
|
if (hdc) ReleaseDC(hwnd,hdc);
|
|
}
|
|
|
|
if (wParam == VK_UP || wParam == VK_HOME) es->moveCursor(0);
|
|
else es->moveCursor(WDL_utf8_get_charlen(hwnd->m_title.Get()));
|
|
return 3;
|
|
case VK_LEFT:
|
|
{
|
|
int cp = es->cursor_pos;
|
|
if (cp > 0)
|
|
{
|
|
const char *buf=hwnd->m_title.Get();
|
|
if (lParam & FCONTROL)
|
|
{
|
|
cp = WDL_utf8_bytepos_to_charpos(buf,
|
|
scanWord(buf,WDL_utf8_charpos_to_bytepos(buf,cp),-1));
|
|
}
|
|
else
|
|
{
|
|
const int p = WDL_utf8_charpos_to_bytepos(buf,--cp);
|
|
if (cp > 0 && p > 0 && buf[p] == '\n' && buf[p-1] == '\r') cp--;
|
|
}
|
|
}
|
|
es->moveCursor(cp);
|
|
}
|
|
return 3;
|
|
case VK_RIGHT:
|
|
{
|
|
int cp = es->cursor_pos;
|
|
if (cp < WDL_utf8_get_charlen(hwnd->m_title.Get()))
|
|
{
|
|
const char *buf=hwnd->m_title.Get();
|
|
const int p = WDL_utf8_charpos_to_bytepos(buf,cp);
|
|
|
|
if (lParam & FCONTROL)
|
|
cp = WDL_utf8_bytepos_to_charpos(buf,scanWord(buf,p,1));
|
|
else if (buf[p] == '\r' && buf[p+1] == '\n')
|
|
cp+=2;
|
|
else
|
|
cp++;
|
|
}
|
|
es->moveCursor(cp);
|
|
}
|
|
return 3;
|
|
case VK_DELETE:
|
|
if (hwnd->m_style & ES_READONLY) return 1;
|
|
if (hwnd->m_title.GetLength())
|
|
{
|
|
if (es->deleteSelection(&hwnd->m_title)) return 7;
|
|
|
|
const int bytepos = utf8fs_charpos_to_bytepos(&hwnd->m_title,es->cursor_pos);
|
|
if (bytepos < hwnd->m_title.GetLength())
|
|
{
|
|
const char *rd = hwnd->m_title.Get()+bytepos;
|
|
hwnd->m_title.DeleteSub(bytepos, rd[0] == '\r' && rd[1] == '\n' ? 2 : wdl_utf8_parsechar(rd,NULL));
|
|
return 7;
|
|
}
|
|
}
|
|
return 1;
|
|
|
|
case VK_BACK:
|
|
if (hwnd->m_style & ES_READONLY) return 1;
|
|
if (hwnd->m_title.GetLength())
|
|
{
|
|
if (es->deleteSelection(&hwnd->m_title)) return 7;
|
|
if (es->cursor_pos > 0)
|
|
{
|
|
es->cursor_pos--;
|
|
const char *buf = hwnd->m_title.Get();
|
|
int bytepos = WDL_utf8_charpos_to_bytepos(buf,es->cursor_pos);
|
|
if (bytepos > 0 && buf[bytepos] == '\n' && buf[bytepos-1] == '\r')
|
|
{
|
|
hwnd->m_title.DeleteSub(bytepos-1, 2);
|
|
es->cursor_pos--;
|
|
}
|
|
else hwnd->m_title.DeleteSub(bytepos, wdl_utf8_parsechar(hwnd->m_title.Get()+bytepos,NULL));
|
|
return 7;
|
|
}
|
|
}
|
|
return 1;
|
|
case VK_RETURN:
|
|
if (wantReturn)
|
|
{
|
|
if (hwnd->m_style & ES_READONLY) return 1;
|
|
if (es->deleteSelection(&hwnd->m_title)) return 7;
|
|
int bytepos = utf8fs_charpos_to_bytepos(&hwnd->m_title,es->cursor_pos);
|
|
hwnd->m_title.Insert("\r\n",bytepos);
|
|
es->cursor_pos+=2; // skip \r and \n
|
|
return 7;
|
|
}
|
|
return 0;
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
static int editControlPaintLine(HDC hdc, const char *str, int str_len, int cursor_pos, int sel1, int sel2, const RECT *r, int dtflags)
|
|
{
|
|
// cursor_pos, sel1, sel2 are all byte positions
|
|
int rv = 0;
|
|
if (str_len>0)
|
|
{
|
|
RECT outr = *r;
|
|
if (sel2 < str_len || sel1 > 0)
|
|
{
|
|
RECT tmp={0,};
|
|
DrawText(hdc,str,str_len,&tmp,DT_CALCRECT|DT_SINGLELINE|DT_NOPREFIX);
|
|
rv = tmp.right;
|
|
DrawText(hdc,str,str_len,&outr,dtflags|DT_SINGLELINE|DT_NOPREFIX);
|
|
}
|
|
|
|
const int offs = wdl_max(sel1,0);
|
|
const int endptr = wdl_min(sel2,str_len);
|
|
if (endptr > offs)
|
|
{
|
|
SetBkMode(hdc,OPAQUE);
|
|
SetBkColor(hdc,g_swell_ctheme.edit_bg_sel);
|
|
const int oldc = GetTextColor(hdc);
|
|
SetTextColor(hdc,g_swell_ctheme.edit_text_sel);
|
|
|
|
RECT tmp={0,};
|
|
DrawText(hdc,str,offs,&tmp,DT_CALCRECT|DT_SINGLELINE|DT_NOPREFIX);
|
|
outr.left += tmp.right;
|
|
DrawText(hdc,str+offs,endptr-offs,&outr,dtflags|DT_SINGLELINE|DT_NOPREFIX);
|
|
|
|
SetBkMode(hdc,TRANSPARENT);
|
|
SetTextColor(hdc,oldc);
|
|
}
|
|
}
|
|
|
|
if (cursor_pos >= 0 && cursor_pos <= str_len)
|
|
{
|
|
RECT mr={0,};
|
|
if (cursor_pos>0) DrawText(hdc,str,cursor_pos,&mr,DT_CALCRECT|DT_NOPREFIX|DT_SINGLELINE);
|
|
|
|
int oc = GetTextColor(hdc);
|
|
SetTextColor(hdc,g_swell_ctheme.edit_cursor);
|
|
mr.left = r->left + mr.right - 1;
|
|
mr.right = mr.left+1;
|
|
mr.top = r->top;
|
|
mr.bottom = r->bottom;
|
|
DrawText(hdc,"|",1,&mr,dtflags|DT_SINGLELINE|DT_NOPREFIX|DT_NOCLIP);
|
|
SetTextColor(hdc,oc);
|
|
}
|
|
return rv;
|
|
}
|
|
|
|
static void passwordify(WDL_FastString **s)
|
|
{
|
|
const int l = WDL_utf8_get_charlen((*s)->Get());
|
|
if (l>0)
|
|
{
|
|
static WDL_FastString tmp;
|
|
tmp.SetLen(l,false,'*');
|
|
*s = &tmp;
|
|
}
|
|
}
|
|
|
|
static LRESULT WINAPI editWindowProc(HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam)
|
|
{
|
|
__SWELL_editControlState *es = (__SWELL_editControlState*)hwnd->m_private_data;
|
|
static int s_capmode_state /* 1=vscroll, 2=hscroll, 3=move sel1, 4=move sel2*/, s_capmode_data1;
|
|
switch (msg)
|
|
{
|
|
case WM_NCDESTROY:
|
|
delete es;
|
|
hwnd->m_private_data=0;
|
|
break;
|
|
case WM_CONTEXTMENU:
|
|
{
|
|
if (es->m_disable_contextmenu) break;
|
|
|
|
HMENU menu=CreatePopupMenu();
|
|
MENUITEMINFO mi={sizeof(mi),MIIM_ID|MIIM_TYPE,MFT_STRING, 0,
|
|
(UINT) 100, NULL,NULL,NULL,0,(char*)"Copy"};
|
|
|
|
if (!(hwnd->m_style & ES_PASSWORD))
|
|
InsertMenuItem(menu,0,TRUE,&mi);
|
|
mi.wID++;
|
|
mi.dwTypeData = (char*)"Paste";
|
|
InsertMenuItem(menu,0,TRUE,&mi);
|
|
mi.wID++;
|
|
mi.dwTypeData = (char*)"Select all";
|
|
InsertMenuItem(menu,0,TRUE,&mi);
|
|
POINT p;
|
|
GetCursorPos(&p);
|
|
|
|
const int a = TrackPopupMenu(menu,TPM_NONOTIFY|TPM_RETURNCMD|TPM_LEFTALIGN,p.x,p.y,0,hwnd,0);
|
|
DestroyMenu(menu);
|
|
if (a==100) OnEditKeyDown(hwnd,WM_KEYDOWN,'C',FVIRTKEY|FCONTROL,false,false,es,true);
|
|
else if (a==101)
|
|
{
|
|
OnEditKeyDown(hwnd,WM_KEYDOWN,'V',FVIRTKEY|FCONTROL,false,false,es,true);
|
|
SendMessage(GetParent(hwnd),WM_COMMAND,(EN_CHANGE<<16) | (hwnd->m_id&0xffff),(LPARAM)hwnd);
|
|
}
|
|
else if (a==102) SendMessage(hwnd,EM_SETSEL,0,-1);
|
|
|
|
if (a) InvalidateRect(hwnd,NULL,FALSE);
|
|
|
|
}
|
|
return 1;
|
|
case WM_LBUTTONDOWN:
|
|
case WM_RBUTTONDOWN:
|
|
es->cursor_state=1; // next invalidate draws blinky
|
|
|
|
// todo: if selection active and not focused, do not mouse hit test
|
|
if (msg == WM_LBUTTONDOWN)
|
|
{
|
|
WDL_FastString *title = &hwnd->m_title;
|
|
if (hwnd->m_style & ES_PASSWORD) passwordify(&title);
|
|
const bool multiline = (hwnd->m_style & ES_MULTILINE) != 0;
|
|
RECT r;
|
|
GetClientRect(hwnd,&r);
|
|
if (multiline)
|
|
{
|
|
RECT br = r;
|
|
br.right -= g_swell_ctheme.scrollbar_width;
|
|
|
|
if (es->max_width > br.right && (hwnd->m_style & ES_AUTOHSCROLL))
|
|
{
|
|
if (GET_Y_LPARAM(lParam) >= br.bottom - g_swell_ctheme.scrollbar_width)
|
|
{
|
|
int xp = GET_X_LPARAM(lParam), xpos = xp;
|
|
|
|
int thumbsz, thumbpos;
|
|
calcScroll(br.right, es->max_width,es->scroll_x,&thumbsz,&thumbpos);
|
|
|
|
if (xpos < thumbpos) xp = thumbpos;
|
|
else if (xpos > thumbpos+thumbsz) xp = thumbpos + thumbsz;
|
|
|
|
s_capmode_state = 2;
|
|
s_capmode_data1 = xp;
|
|
SetCapture(hwnd);
|
|
if (xpos < thumbpos || xpos > thumbpos+thumbsz) goto forceMouseMove;
|
|
return 0;
|
|
}
|
|
br.bottom -= g_swell_ctheme.scrollbar_width;
|
|
}
|
|
if (GET_X_LPARAM(lParam)>=br.right && es->max_height > br.bottom)
|
|
{
|
|
int yp = GET_Y_LPARAM(lParam), ypos = yp;
|
|
|
|
int thumbsz, thumbpos;
|
|
calcScroll(br.bottom, es->max_height, es->scroll_y,&thumbsz,&thumbpos);
|
|
|
|
if (ypos < thumbpos) yp = thumbpos;
|
|
else if (ypos > thumbpos+thumbsz) yp = thumbpos + thumbsz;
|
|
|
|
s_capmode_state = 1;
|
|
s_capmode_data1 = yp;
|
|
SetCapture(hwnd);
|
|
if (ypos < thumbpos || ypos > thumbpos+thumbsz) goto forceMouseMove;
|
|
return 0;
|
|
}
|
|
}
|
|
|
|
|
|
int xo=2;
|
|
int yo = multiline ? 2 : 0;
|
|
HDC hdc=GetDC(hwnd);
|
|
const int last_cursor = es->cursor_pos;
|
|
const int wwrap = (hwnd->m_style & (ES_MULTILINE|ES_AUTOHSCROLL)) == ES_MULTILINE ?
|
|
r.right - g_swell_ctheme.scrollbar_width : 0;
|
|
es->cursor_pos = WDL_utf8_bytepos_to_charpos(title->Get(),
|
|
editHitTest(hdc,title->Get(),
|
|
multiline?-1:title->GetLength(),
|
|
GET_X_LPARAM(lParam)-xo + es->scroll_x,
|
|
GET_Y_LPARAM(lParam)-yo + es->scroll_y,
|
|
wwrap,es,hwnd)
|
|
);
|
|
|
|
if (msg == WM_LBUTTONDOWN)
|
|
es->onMouseDown(s_capmode_state,last_cursor);
|
|
|
|
ReleaseDC(hwnd,hdc);
|
|
|
|
}
|
|
SetFocus(hwnd);
|
|
if (msg == WM_LBUTTONDOWN) SetCapture(hwnd);
|
|
|
|
InvalidateRect(hwnd,NULL,FALSE);
|
|
return 0;
|
|
case WM_MOUSEWHEEL:
|
|
if (es && (hwnd->m_style & ES_MULTILINE))
|
|
{
|
|
if ((GetAsyncKeyState(VK_CONTROL)&0x8000) || (GetAsyncKeyState(VK_MENU)&0x8000)) break; // pass modified mousewheel to parent
|
|
|
|
RECT r;
|
|
GetClientRect(hwnd,&r);
|
|
r.right -= g_swell_ctheme.scrollbar_width;
|
|
if (es->max_width > r.right && (hwnd->m_style & ES_AUTOHSCROLL))
|
|
{
|
|
r.bottom -= g_swell_ctheme.scrollbar_width;
|
|
}
|
|
const int viewsz = r.bottom;
|
|
const int totalsz=es->max_height + g_swell_ctheme.scrollbar_width;
|
|
|
|
const int amt = ((short)HIWORD(wParam))/-2;
|
|
|
|
const int oldscroll = es->scroll_y;
|
|
es->scroll_y += amt;
|
|
if (es->scroll_y + viewsz > totalsz) es->scroll_y = totalsz-viewsz;
|
|
if (es->scroll_y < 0) es->scroll_y=0;
|
|
if (es->scroll_y != oldscroll)
|
|
{
|
|
InvalidateRect(hwnd,NULL,FALSE);
|
|
}
|
|
|
|
return 1;
|
|
}
|
|
break;
|
|
case WM_MOUSEMOVE:
|
|
forceMouseMove:
|
|
if (es && GetCapture()==hwnd)
|
|
{
|
|
RECT r;
|
|
GetClientRect(hwnd,&r);
|
|
const bool multiline = (hwnd->m_style & ES_MULTILINE) != 0;
|
|
if (multiline)
|
|
{
|
|
r.right -= g_swell_ctheme.scrollbar_width;
|
|
if (es->max_width > r.right && (hwnd->m_style & ES_AUTOHSCROLL))
|
|
{
|
|
r.bottom -= g_swell_ctheme.scrollbar_width;
|
|
}
|
|
}
|
|
if (s_capmode_state == 1)
|
|
{
|
|
int yv = s_capmode_data1;
|
|
int amt = GET_Y_LPARAM(lParam) - yv;
|
|
|
|
if (amt)
|
|
{
|
|
const int viewsz = r.bottom;
|
|
const int totalsz=es->max_height + g_swell_ctheme.scrollbar_width;
|
|
amt = (int)floor(amt * (double)totalsz / (double)viewsz + 0.5);
|
|
|
|
const int oldscroll = es->scroll_y;
|
|
es->scroll_y += amt;
|
|
if (es->scroll_y + viewsz > totalsz) es->scroll_y = totalsz-viewsz;
|
|
if (es->scroll_y < 0) es->scroll_y=0;
|
|
if (es->scroll_y != oldscroll)
|
|
{
|
|
s_capmode_data1 = GET_Y_LPARAM(lParam);
|
|
InvalidateRect(hwnd,NULL,FALSE);
|
|
}
|
|
}
|
|
}
|
|
else if (s_capmode_state == 2)
|
|
{
|
|
int xv = s_capmode_data1;
|
|
int amt = GET_X_LPARAM(lParam) - xv;
|
|
|
|
if (amt)
|
|
{
|
|
const int viewsz = r.right;
|
|
const int totalsz=es->max_width + g_swell_ctheme.scrollbar_width;
|
|
amt = (int)floor(amt * (double)totalsz / (double)viewsz + 0.5);
|
|
|
|
const int oldscroll = es->scroll_x;
|
|
es->scroll_x += amt;
|
|
if (es->scroll_x + viewsz > totalsz) es->scroll_x = totalsz-viewsz;
|
|
if (es->scroll_x < 0) es->scroll_x=0;
|
|
if (es->scroll_x != oldscroll)
|
|
{
|
|
s_capmode_data1 = GET_X_LPARAM(lParam);
|
|
InvalidateRect(hwnd,NULL,FALSE);
|
|
}
|
|
}
|
|
}
|
|
else if (s_capmode_state == 3 || s_capmode_state == 4)
|
|
{
|
|
int wwrap=0;
|
|
if ((hwnd->m_style & (ES_MULTILINE|ES_AUTOHSCROLL)) == ES_MULTILINE)
|
|
{
|
|
wwrap = r.right; // already has scrollbar size removed
|
|
}
|
|
int xo=2;
|
|
int yo = multiline ? 2 : 0;
|
|
HDC hdc=GetDC(hwnd);
|
|
WDL_FastString *title = &hwnd->m_title;
|
|
if (hwnd->m_style & ES_PASSWORD) passwordify(&title);
|
|
int p = WDL_utf8_bytepos_to_charpos(title->Get(),
|
|
editHitTest(hdc,title->Get(),
|
|
multiline?-1:title->GetLength(),
|
|
GET_X_LPARAM(lParam)-xo + es->scroll_x,
|
|
GET_Y_LPARAM(lParam)-yo + es->scroll_y, wwrap,es,hwnd)
|
|
);
|
|
ReleaseDC(hwnd,hdc);
|
|
|
|
es->onMouseDrag(s_capmode_state,p);
|
|
|
|
es->autoScrollToOffset(hwnd,p,
|
|
(hwnd->m_style & ES_MULTILINE) != 0,
|
|
(hwnd->m_style & (ES_MULTILINE|ES_AUTOHSCROLL)) == ES_MULTILINE);
|
|
|
|
|
|
InvalidateRect(hwnd,NULL,FALSE);
|
|
}
|
|
}
|
|
return 0;
|
|
case WM_LBUTTONUP:
|
|
ReleaseCapture();
|
|
return 0;
|
|
case WM_TIMER:
|
|
if (es && wParam == 100)
|
|
{
|
|
if (++es->cursor_state >= EDIT_CURSOR_CYCLE_INTERVAL) es->cursor_state=0;
|
|
if (GetFocusIncludeMenus()!=hwnd || es->cursor_state<2) InvalidateRect(hwnd,NULL,FALSE);
|
|
}
|
|
return 0;
|
|
case WM_LBUTTONDBLCLK:
|
|
if (es)
|
|
{
|
|
// technically this should select the word rather than all
|
|
es->sel1 = 0;
|
|
es->cursor_pos = es->sel2 = WDL_utf8_get_charlen(hwnd->m_title.Get());
|
|
InvalidateRect(hwnd,NULL,FALSE);
|
|
}
|
|
return 0;
|
|
case WM_KEYDOWN:
|
|
{
|
|
const int osel1 = es && es->sel1 >= 0 && es->sel2 > es->sel1 ? es->sel1 : -1;
|
|
|
|
int f = OnEditKeyDown(hwnd,msg,wParam,lParam,
|
|
(hwnd->m_style&ES_WANTRETURN) && (hwnd->m_style&ES_MULTILINE),
|
|
!!(hwnd->m_style&ES_MULTILINE),
|
|
es,true);
|
|
if (f)
|
|
{
|
|
if (f&4)
|
|
{
|
|
es->cache_linelen_w=0;
|
|
SendMessage(GetParent(hwnd),WM_COMMAND,(EN_CHANGE<<16) | (hwnd->m_id&0xffff),(LPARAM)hwnd);
|
|
}
|
|
if (f&2)
|
|
{
|
|
if ((hwnd->m_style & (ES_MULTILINE|ES_AUTOHSCROLL)) == ES_AUTOHSCROLL &&
|
|
osel1 >= 0 && es->cursor_pos > osel1 && es->sel1 < 0)
|
|
es->autoScrollToOffset(hwnd,osel1,false,false);
|
|
|
|
es->autoScrollToOffset(hwnd,es->cursor_pos,
|
|
(hwnd->m_style & ES_MULTILINE) != 0,
|
|
(hwnd->m_style & (ES_MULTILINE|ES_AUTOHSCROLL)) == ES_MULTILINE
|
|
);
|
|
InvalidateRect(hwnd,NULL,FALSE);
|
|
}
|
|
return 0;
|
|
}
|
|
}
|
|
break;
|
|
case WM_KEYUP:
|
|
return 0;
|
|
case WM_PAINT:
|
|
{
|
|
PAINTSTRUCT ps;
|
|
|
|
const bool focused = GetFocusIncludeMenus()==hwnd;
|
|
if (es)
|
|
{
|
|
if (focused)
|
|
{
|
|
if (!es->cursor_timer) { SetTimer(hwnd,100,EDIT_CURSOR_BLINK_LEN,NULL); es->cursor_timer=1; }
|
|
}
|
|
else
|
|
{
|
|
if (es->cursor_timer) { KillTimer(hwnd,100); es->cursor_timer=0; }
|
|
}
|
|
}
|
|
|
|
if (BeginPaint(hwnd,&ps))
|
|
{
|
|
RECT r;
|
|
GetClientRect(hwnd,&r);
|
|
const RECT orig_r2 = r;
|
|
RECT orig_r = r;
|
|
WDL_FastString *title = &hwnd->m_title;
|
|
if (hwnd->m_style & ES_PASSWORD) passwordify(&title);
|
|
|
|
bool is_secpass = false;
|
|
again:
|
|
Draw3DBox(ps.hdc,&orig_r, hwnd->m_enabled ? g_swell_ctheme.edit_bg : g_swell_ctheme.edit_bg_disabled,-1,-1);
|
|
|
|
SetTextColor(ps.hdc,
|
|
hwnd->m_enabled ?
|
|
//(hwnd->m_style & ES_READONLY) ? g_swell_ctheme.label_text :
|
|
g_swell_ctheme.edit_text :
|
|
g_swell_ctheme.edit_text_disabled
|
|
);
|
|
SetBkMode(ps.hdc,TRANSPARENT);
|
|
if (!focused && es->scroll_x > 0 && es->scroll_x < 4)
|
|
es->scroll_x=0;
|
|
r.left+=2 - es->scroll_x; r.right-=2;
|
|
|
|
const bool do_cursor = es->cursor_state!=0;
|
|
const int cursor_pos = focused ? utf8fs_charpos_to_bytepos(title,es->cursor_pos) : -1;
|
|
const int sel1 = es->sel1>=0 && focused ? utf8fs_charpos_to_bytepos(title,es->sel1) : -1;
|
|
const int sel2 = es->sel2>=0 && focused ? utf8fs_charpos_to_bytepos(title,es->sel2) : -1;
|
|
|
|
const bool multiline = (hwnd->m_style & ES_MULTILINE) != 0;
|
|
|
|
if (multiline)
|
|
{
|
|
r.top+=2 - es->scroll_y;
|
|
const char *buf = title->Get(), *buf_end = buf + title->GetLength();
|
|
int bytepos = 0;
|
|
RECT tmp={0,};
|
|
const int line_h = DrawText(ps.hdc," ",1,&tmp,DT_CALCRECT|DT_SINGLELINE|DT_NOPREFIX);
|
|
const int wwrap = (hwnd->m_style & ES_AUTOHSCROLL) ? 0 : orig_r.right - g_swell_ctheme.scrollbar_width;
|
|
|
|
int *use_cache = NULL, use_cache_len = 0;
|
|
const bool allow_cache = EDIT_ALLOW_MULTILINE_CACHE(wwrap,hwnd,title->GetLength());
|
|
if (allow_cache &&
|
|
es->cache_linelen_w == wwrap &&
|
|
es->cache_linelen_strlen == title->GetLength())
|
|
{
|
|
use_cache = es->cache_linelen_bytes.Get();
|
|
use_cache_len = es->cache_linelen_bytes.GetSize();
|
|
}
|
|
else
|
|
{
|
|
es->cache_linelen_w=allow_cache?wwrap:0;
|
|
es->cache_linelen_strlen=allow_cache?title->GetLength():0;
|
|
es->cache_linelen_bytes.Resize(0,false);
|
|
}
|
|
|
|
for (;;)
|
|
{
|
|
int pskip=0, lb;
|
|
|
|
const bool vis = r.top >= -line_h && r.top < orig_r.bottom;
|
|
|
|
if (vis || !use_cache || use_cache_len < 1)
|
|
{
|
|
lb = swell_getLineLength(buf,&pskip,wwrap,ps.hdc);
|
|
if (!use_cache && allow_cache)
|
|
{
|
|
int s = lb+pskip;
|
|
es->cache_linelen_bytes.Add(&s,1);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
lb = *use_cache;
|
|
if (WDL_NOT_NORMALLY(lb < 1)) break;
|
|
}
|
|
|
|
if (use_cache)
|
|
{
|
|
use_cache++;
|
|
use_cache_len--;
|
|
}
|
|
|
|
if (!*buf && cursor_pos != bytepos) break;
|
|
|
|
if (WDL_NOT_NORMALLY(buf+lb+pskip > buf_end)) break;
|
|
|
|
if (vis)
|
|
{
|
|
int wid = editControlPaintLine(ps.hdc,buf,lb,
|
|
(do_cursor && cursor_pos >= bytepos && cursor_pos <= bytepos + lb) ? cursor_pos - bytepos : -1,
|
|
sel1 >= 0 ? (sel1 - bytepos) : -1,
|
|
sel2 >= 0 ? (sel2 - bytepos) : -1,
|
|
&r, DT_TOP);
|
|
if (wid > es->max_width) es->max_width = wid;
|
|
}
|
|
|
|
r.top += line_h;
|
|
|
|
if (!*buf || !buf[lb]) break;
|
|
|
|
bytepos += lb+pskip;
|
|
buf += lb+pskip;
|
|
}
|
|
r.top += es->scroll_y;
|
|
es->max_height = r.top;
|
|
if (es->max_width > r.right && (hwnd->m_style & ES_AUTOHSCROLL))
|
|
{
|
|
drawHorizontalScrollbar(ps.hdc,orig_r,
|
|
orig_r.right-orig_r.left - g_swell_ctheme.scrollbar_width,
|
|
es->max_width,es->scroll_x);
|
|
orig_r.bottom -= g_swell_ctheme.scrollbar_width;
|
|
r.bottom -= g_swell_ctheme.scrollbar_width;
|
|
}
|
|
if (r.top > r.bottom)
|
|
{
|
|
drawVerticalScrollbar(ps.hdc,orig_r,es->max_height,es->scroll_y);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
es->max_width = editControlPaintLine(ps.hdc, title->Get(), title->GetLength(),
|
|
do_cursor ? cursor_pos : -1, sel1, sel2, &r, DT_VCENTER);
|
|
|
|
if (es->scroll_x > 0 && !is_secpass)
|
|
{
|
|
int max_x = es->max_width - (r.right-2 - (orig_r2.bottom-orig_r2.top)/2);
|
|
if (max_x < 0) max_x = 0;
|
|
if (es->scroll_x > max_x)
|
|
{
|
|
es->scroll_x = max_x;
|
|
r = orig_r = orig_r2;
|
|
is_secpass = true;
|
|
goto again;
|
|
}
|
|
}
|
|
}
|
|
|
|
Draw3DBox(ps.hdc,&orig_r2,-1, g_swell_ctheme.edit_shadow, g_swell_ctheme.edit_hilight);
|
|
EndPaint(hwnd,&ps);
|
|
}
|
|
}
|
|
return 0;
|
|
case WM_SETTEXT:
|
|
if (es)
|
|
{
|
|
es->cursor_pos =
|
|
(hwnd->m_style & (ES_READONLY|ES_MULTILINE)) == (ES_READONLY|ES_MULTILINE) ? 0 :
|
|
WDL_utf8_get_charlen(hwnd->m_title.Get());
|
|
|
|
es->sel1=es->sel2=-1;
|
|
es->cache_linelen_w=es->cache_linelen_strlen=0;
|
|
if ((hwnd->m_style & (ES_MULTILINE|ES_AUTOHSCROLL))==ES_AUTOHSCROLL &&
|
|
GetFocus() != hwnd)
|
|
es->autoScrollToOffset(hwnd,0,false,false);
|
|
}
|
|
InvalidateRect(hwnd,NULL,FALSE);
|
|
if (hwnd->m_id && hwnd->m_parent)
|
|
SendMessage(hwnd->m_parent,WM_COMMAND,(EN_CHANGE<<16)|hwnd->m_id,(LPARAM)hwnd);
|
|
break;
|
|
case EM_REPLACESEL:
|
|
if (lParam && es)
|
|
{
|
|
const char *p = (const char *)lParam;
|
|
int pos = wdl_min(es->sel1,es->sel2);
|
|
es->deleteSelection(&hwnd->m_title);
|
|
if (*p)
|
|
{
|
|
if (pos < 0) pos = es->cursor_pos;
|
|
int bytepos = utf8fs_charpos_to_bytepos(&hwnd->m_title,pos);
|
|
hwnd->m_title.Insert(p,bytepos);
|
|
}
|
|
InvalidateRect(hwnd,NULL,FALSE);
|
|
}
|
|
return 0;
|
|
case EM_GETSEL:
|
|
if (es)
|
|
{
|
|
if (wParam) * (int *)wParam = es->sel1;
|
|
if (lParam) * (int *)lParam = es->sel2;
|
|
if (es->sel1 < 0 || es->sel1 > 65535 ||
|
|
es->sel2 < 0 || es->sel2 > 65535) return -1;
|
|
return MAKELPARAM(es->sel1,es->sel2);
|
|
}
|
|
return 0;
|
|
case EM_SETSEL:
|
|
if (es)
|
|
{
|
|
es->sel1 = (int)wParam;
|
|
es->sel2 = (int)lParam;
|
|
if (!es->sel1 && es->sel2 == -1) es->sel2 = WDL_utf8_get_charlen(hwnd->m_title.Get());
|
|
InvalidateRect(hwnd,NULL,FALSE);
|
|
if (es->sel2>=0)
|
|
es->autoScrollToOffset(hwnd,es->sel2,
|
|
(hwnd->m_style & ES_MULTILINE) != 0,
|
|
(hwnd->m_style & (ES_MULTILINE|ES_AUTOHSCROLL)) == ES_MULTILINE);
|
|
|
|
}
|
|
return 0;
|
|
case EM_SCROLL:
|
|
if (es)
|
|
{
|
|
if (wParam == SB_TOP)
|
|
{
|
|
es->scroll_x=es->scroll_y=0;
|
|
InvalidateRect(hwnd,NULL,FALSE);
|
|
}
|
|
else if (wParam == SB_BOTTOM)
|
|
{
|
|
es->autoScrollToOffset(hwnd,
|
|
hwnd->m_title.GetLength() + 1, // this should be charpos rather than bytepos, but anything longer than m_title.GetLength() ensures we go to the absolute bottom
|
|
(hwnd->m_style & ES_MULTILINE) != 0,
|
|
(hwnd->m_style & (ES_MULTILINE|ES_AUTOHSCROLL)) == ES_MULTILINE);
|
|
InvalidateRect(hwnd,NULL,FALSE);
|
|
}
|
|
}
|
|
return 0;
|
|
case WM_KILLFOCUS:
|
|
SendMessage(GetParent(hwnd),WM_COMMAND,(EN_KILLFOCUS<<16) | (hwnd->m_id&0xffff),(LPARAM)hwnd);
|
|
case WM_SETFOCUS:
|
|
InvalidateRect(hwnd,NULL,FALSE);
|
|
break;
|
|
}
|
|
return DefWindowProc(hwnd,msg,wParam,lParam);
|
|
}
|
|
|
|
static LRESULT WINAPI progressWindowProc(HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam)
|
|
{
|
|
switch (msg)
|
|
{
|
|
case PBM_DELTAPOS:
|
|
if (hwnd->m_private_data) *(int *)hwnd->m_private_data += (int) wParam; // todo: unsigned-ness conversion? unclear
|
|
InvalidateRect(hwnd,NULL,FALSE);
|
|
break;
|
|
case PBM_SETPOS:
|
|
if (hwnd->m_private_data) *(int *)hwnd->m_private_data = (int) wParam;
|
|
InvalidateRect(hwnd,NULL,FALSE);
|
|
break;
|
|
case PBM_SETRANGE:
|
|
if (hwnd->m_private_data) ((int *)hwnd->m_private_data)[1] = (int) lParam;
|
|
InvalidateRect(hwnd,NULL,FALSE);
|
|
break;
|
|
case WM_NCDESTROY:
|
|
free((int *)hwnd->m_private_data);
|
|
hwnd->m_private_data=0;
|
|
break;
|
|
case WM_PAINT:
|
|
{
|
|
PAINTSTRUCT ps;
|
|
if (BeginPaint(hwnd,&ps))
|
|
{
|
|
RECT r;
|
|
GetClientRect(hwnd,&r);
|
|
|
|
paintDialogBackground(hwnd,&r,ps.hdc);
|
|
|
|
if (hwnd->m_private_data)
|
|
{
|
|
int pos = *(int *)hwnd->m_private_data;
|
|
const int range = ((int *)hwnd->m_private_data)[1];
|
|
const int low = LOWORD(range), high=HIWORD(range);
|
|
if (pos > low && high > low)
|
|
{
|
|
if (pos > high) pos=high;
|
|
int dx = ((pos-low)*r.right)/(high-low);
|
|
r.right = dx;
|
|
HBRUSH br = CreateSolidBrush(g_swell_ctheme.progress);
|
|
FillRect(ps.hdc,&r,br);
|
|
DeleteObject(br);
|
|
}
|
|
}
|
|
|
|
EndPaint(hwnd,&ps);
|
|
}
|
|
}
|
|
break;
|
|
}
|
|
|
|
return DefWindowProc(hwnd,msg,wParam,lParam);
|
|
}
|
|
|
|
static LRESULT WINAPI trackbarWindowProc(HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam)
|
|
{
|
|
const int track_h = 10;
|
|
static int s_cap_offs;
|
|
switch (msg)
|
|
{
|
|
case WM_CREATE:
|
|
{
|
|
int *p = (int *)hwnd->m_private_data;
|
|
if (p)
|
|
{
|
|
p[1] = (1000<<16);
|
|
p[2] = -1;
|
|
}
|
|
}
|
|
break;
|
|
case TBM_SETPOS:
|
|
if (hwnd->m_private_data) *(int *)hwnd->m_private_data = (int) lParam;
|
|
if (wParam) InvalidateRect(hwnd,NULL,FALSE);
|
|
break;
|
|
case TBM_GETPOS:
|
|
if (hwnd->m_private_data) return *(int *)hwnd->m_private_data;
|
|
return 0;
|
|
case TBM_SETRANGE:
|
|
if (hwnd->m_private_data) ((int *)hwnd->m_private_data)[1] = (int) lParam;
|
|
if (wParam) InvalidateRect(hwnd,NULL,FALSE);
|
|
break;
|
|
case TBM_SETTIC:
|
|
if (hwnd->m_private_data) ((int *)hwnd->m_private_data)[2] = (int) lParam;
|
|
break;
|
|
case WM_NCDESTROY:
|
|
free((int *)hwnd->m_private_data);
|
|
hwnd->m_private_data=0;
|
|
break;
|
|
case WM_LBUTTONDBLCLK:
|
|
if (hwnd->m_private_data)
|
|
{
|
|
int *state = (int *)hwnd->m_private_data;
|
|
const int range = state[1];
|
|
const int low = LOWORD(range), high=HIWORD(range);
|
|
const int to_val = state[2] >= low && state[2] <= high ? state[2] : (low+high)/2;
|
|
if (state[0] != to_val)
|
|
{
|
|
state[0] = to_val;
|
|
InvalidateRect(hwnd,NULL,FALSE);
|
|
SendMessage(hwnd->m_parent,WM_HSCROLL,SB_ENDSCROLL,(LPARAM)hwnd);
|
|
}
|
|
}
|
|
return 1;
|
|
case WM_LBUTTONDOWN:
|
|
SetFocus(hwnd);
|
|
SetCapture(hwnd);
|
|
|
|
if (hwnd->m_private_data)
|
|
{
|
|
RECT r;
|
|
GetClientRect(hwnd,&r);
|
|
const int rad = wdl_min((r.bottom-r.top)/2-1,track_h);
|
|
int *state = (int *)hwnd->m_private_data;
|
|
const int range = state[1];
|
|
const int low = LOWORD(range), high=HIWORD(range);
|
|
const int dx = ((state[2]-low)*(r.right-2*rad))/(high-low) + rad;
|
|
s_cap_offs=0;
|
|
|
|
if (GET_X_LPARAM(lParam) >= dx-rad && GET_X_LPARAM(lParam)<=dx-rad)
|
|
{
|
|
s_cap_offs = GET_X_LPARAM(lParam)-dx;
|
|
return 1;
|
|
}
|
|
// missed knob, so treat as a move
|
|
}
|
|
|
|
case WM_MOUSEMOVE:
|
|
if (GetCapture()==hwnd && hwnd->m_private_data)
|
|
{
|
|
RECT r;
|
|
GetClientRect(hwnd,&r);
|
|
const int rad = wdl_min((r.bottom-r.top)/2-1,track_h);
|
|
int *state = (int *)hwnd->m_private_data;
|
|
const int range = state[1];
|
|
const int low = LOWORD(range), high=HIWORD(range);
|
|
int xpos = GET_X_LPARAM(lParam) - s_cap_offs;
|
|
int use_range = (r.right-2*rad);
|
|
if (use_range > 0)
|
|
{
|
|
int newval = low + (xpos - rad) * (high-low) / use_range;
|
|
if (newval < low) newval=low;
|
|
else if (newval > high) newval=high;
|
|
if (newval != state[0])
|
|
{
|
|
state[0]=newval;
|
|
InvalidateRect(hwnd,NULL,FALSE);
|
|
SendMessage(hwnd->m_parent,WM_HSCROLL,0,(LPARAM)hwnd);
|
|
}
|
|
}
|
|
}
|
|
return 1;
|
|
case WM_LBUTTONUP:
|
|
if (GetCapture()==hwnd)
|
|
{
|
|
ReleaseCapture();
|
|
SendMessage(hwnd->m_parent,WM_HSCROLL,SB_ENDSCROLL,(LPARAM)hwnd);
|
|
}
|
|
return 1;
|
|
case WM_PAINT:
|
|
{
|
|
PAINTSTRUCT ps;
|
|
if (BeginPaint(hwnd,&ps))
|
|
{
|
|
RECT r;
|
|
GetClientRect(hwnd,&r);
|
|
|
|
HBRUSH hbrush = (HBRUSH) SendMessage(GetParent(hwnd),WM_CTLCOLORSTATIC,(WPARAM)ps.hdc,(LPARAM)hwnd);
|
|
if (hbrush == (HBRUSH)(INT_PTR)1) hbrush = NULL;
|
|
else
|
|
{
|
|
if (hbrush) FillRect(ps.hdc,&r,hbrush);
|
|
else
|
|
{
|
|
SWELL_FillDialogBackground(ps.hdc,&r,3);
|
|
}
|
|
}
|
|
|
|
HBRUSH br = CreateSolidBrush(g_swell_ctheme.trackbar_track);
|
|
const int rad = wdl_min((r.bottom-r.top)/2-1,track_h);
|
|
|
|
RECT sr = r;
|
|
sr.left += rad;
|
|
sr.right -= rad;
|
|
sr.top = (r.top+r.bottom)/2 - rad/2;
|
|
sr.bottom = sr.top + rad;
|
|
FillRect(ps.hdc,&sr,br);
|
|
DeleteObject(br);
|
|
|
|
sr.top = (r.top+r.bottom)/2 - rad;
|
|
sr.bottom = sr.top + rad*2;
|
|
|
|
if (hwnd->m_private_data)
|
|
{
|
|
const int *state = (const int *)hwnd->m_private_data;
|
|
const int range = state[1];
|
|
const int low = LOWORD(range), high=HIWORD(range);
|
|
if (high > low)
|
|
{
|
|
if (state[2] >= low && state[2] <= high)
|
|
{
|
|
const int dx = ((state[2]-low)*(r.right-2*rad))/(high-low) + rad;
|
|
HBRUSH markbr = CreateSolidBrush(g_swell_ctheme.trackbar_mark);
|
|
RECT tr = sr;
|
|
tr.left = dx;
|
|
tr.right = dx+1;
|
|
FillRect(ps.hdc,&tr,markbr);
|
|
DeleteObject(markbr);
|
|
}
|
|
int pos = state[0];
|
|
if (pos < low) pos=low;
|
|
else if (pos > high) pos = high;
|
|
|
|
const int dx = ((pos-low)*(r.right-2*rad))/(high-low) + rad;
|
|
|
|
HBRUSH cbr = CreateSolidBrush(g_swell_ctheme.trackbar_knob);
|
|
HGDIOBJ oldbr = SelectObject(ps.hdc,cbr);
|
|
HGDIOBJ oldpen = SelectObject(ps.hdc,GetStockObject(NULL_PEN));
|
|
Ellipse(ps.hdc, dx-rad, sr.top, dx+rad, sr.bottom);
|
|
SelectObject(ps.hdc,oldbr);
|
|
SelectObject(ps.hdc,oldpen);
|
|
DeleteObject(cbr);
|
|
}
|
|
}
|
|
|
|
EndPaint(hwnd,&ps);
|
|
}
|
|
}
|
|
break;
|
|
case WM_SETFOCUS:
|
|
case WM_KILLFOCUS:
|
|
InvalidateRect(hwnd,NULL,FALSE);
|
|
break;
|
|
}
|
|
|
|
return DefWindowProc(hwnd,msg,wParam,lParam);
|
|
}
|
|
|
|
static LRESULT WINAPI labelWindowProc(HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam)
|
|
{
|
|
switch (msg)
|
|
{
|
|
case WM_PAINT:
|
|
{
|
|
PAINTSTRUCT ps;
|
|
if (BeginPaint(hwnd,&ps))
|
|
{
|
|
RECT r;
|
|
GetClientRect(hwnd,&r);
|
|
|
|
SetTextColor(ps.hdc,
|
|
hwnd->m_enabled ? g_swell_ctheme.label_text :
|
|
g_swell_ctheme.label_text_disabled);
|
|
SetBkMode(ps.hdc,TRANSPARENT);
|
|
|
|
paintDialogBackground(hwnd,&r,ps.hdc);
|
|
|
|
const char *text = hwnd->m_title.Get();
|
|
const int f = (hwnd->m_style & SS_NOPREFIX) ? DT_NOPREFIX : 0;
|
|
switch (hwnd->m_style & SS_TYPEMASK)
|
|
{
|
|
case SS_ETCHEDHORZ:
|
|
case SS_ETCHEDVERT:
|
|
case SS_ETCHEDFRAME:
|
|
{
|
|
HPEN pen = CreatePen(PS_SOLID,0,g_swell_ctheme.group_hilight);
|
|
HPEN pen2 = CreatePen(PS_SOLID,0,g_swell_ctheme.group_shadow);
|
|
HGDIOBJ oldPen=SelectObject(ps.hdc,pen);
|
|
|
|
switch (hwnd->m_style & SS_TYPEMASK)
|
|
{
|
|
case SS_ETCHEDHORZ:
|
|
MoveToEx(ps.hdc,0,1,NULL);
|
|
LineTo(ps.hdc,r.right-1,1);
|
|
SelectObject(ps.hdc,pen2);
|
|
MoveToEx(ps.hdc,0,0,NULL);
|
|
LineTo(ps.hdc,r.right-1,0);
|
|
break;
|
|
case SS_ETCHEDVERT:
|
|
MoveToEx(ps.hdc,1,0,NULL);
|
|
LineTo(ps.hdc,1,r.bottom-1);
|
|
SelectObject(ps.hdc,pen2);
|
|
MoveToEx(ps.hdc,0,0,NULL);
|
|
LineTo(ps.hdc,0,r.bottom-1);
|
|
break;
|
|
case SS_ETCHEDFRAME:
|
|
MoveToEx(ps.hdc,1,1,NULL);
|
|
LineTo(ps.hdc,1,r.bottom-1);
|
|
LineTo(ps.hdc,r.right-1,r.bottom-1);
|
|
LineTo(ps.hdc,r.right-1,1);
|
|
LineTo(ps.hdc,1,1);
|
|
SelectObject(ps.hdc,pen2);
|
|
MoveToEx(ps.hdc,0,0,NULL);
|
|
LineTo(ps.hdc,0,r.bottom-2);
|
|
LineTo(ps.hdc,r.right-2,r.bottom-2);
|
|
LineTo(ps.hdc,r.right-2,0);
|
|
LineTo(ps.hdc,0,0);
|
|
break;
|
|
}
|
|
|
|
SelectObject(ps.hdc,oldPen);
|
|
DeleteObject(pen);
|
|
DeleteObject(pen2);
|
|
|
|
text = "";
|
|
}
|
|
break;
|
|
case SS_LEFT:
|
|
if (text[0])
|
|
{
|
|
RECT tmp={0,};
|
|
const int line_h = DrawText(ps.hdc," ",1,&tmp,DT_SINGLELINE|DT_NOPREFIX|DT_CALCRECT|f);
|
|
if (r.bottom > line_h*5/3)
|
|
{
|
|
int loffs=0;
|
|
while (text[loffs] && r.top < r.bottom)
|
|
{
|
|
int post=0, lb=swell_getLineLength(text+loffs, &post, r.right, ps.hdc);
|
|
if (lb>0)
|
|
DrawText(ps.hdc,text+loffs,lb,&r,DT_TOP|DT_SINGLELINE|DT_LEFT|f);
|
|
r.top += line_h;
|
|
loffs+=lb+post;
|
|
}
|
|
text = "";
|
|
}
|
|
}
|
|
break;
|
|
}
|
|
|
|
if (text[0])
|
|
{
|
|
if (strstr(text,"\n"))
|
|
{
|
|
RECT mr={0,};
|
|
DrawText(ps.hdc,text,-1,&mr,DT_CALCRECT|f);
|
|
const int dsz = r.right-r.left - mr.right;
|
|
if (dsz > 0)
|
|
{
|
|
if (hwnd->m_style & SS_CENTER) r.left += dsz/2;
|
|
else if (hwnd->m_style & SS_RIGHT) r.left += dsz;
|
|
}
|
|
if (r.bottom-r.top > mr.bottom) r.top += (r.bottom-r.top-mr.bottom)/2;
|
|
DrawText(ps.hdc,text,-1,&r,f);
|
|
}
|
|
else
|
|
DrawText(ps.hdc,text,-1,&r,
|
|
((hwnd->m_style & SS_CENTER) ? DT_CENTER :
|
|
(hwnd->m_style & SS_RIGHT) ? DT_RIGHT : 0)|
|
|
(DT_SINGLELINE|DT_VCENTER)|f);
|
|
}
|
|
EndPaint(hwnd,&ps);
|
|
}
|
|
}
|
|
return 0;
|
|
case WM_SETTEXT:
|
|
InvalidateRect(hwnd,NULL,FALSE);
|
|
break;
|
|
case WM_LBUTTONDOWN:
|
|
case WM_LBUTTONDBLCLK:
|
|
if (hwnd->m_style & SS_NOTIFY)
|
|
SendMessage(GetParent(hwnd),WM_COMMAND,
|
|
((msg==WM_LBUTTONDOWN?STN_CLICKED:STN_DBLCLK)<<16)|(hwnd->m_id&0xffff),0);
|
|
return 1;
|
|
}
|
|
return DefWindowProc(hwnd,msg,wParam,lParam);
|
|
}
|
|
|
|
struct __SWELL_ComboBoxInternalState_rec
|
|
{
|
|
__SWELL_ComboBoxInternalState_rec(const char *_desc=NULL, LPARAM _parm=0) { desc=_desc?strdup(_desc):NULL; parm=_parm; }
|
|
~__SWELL_ComboBoxInternalState_rec() { free(desc); }
|
|
char *desc;
|
|
LPARAM parm;
|
|
static int cmp(const __SWELL_ComboBoxInternalState_rec **a, const __SWELL_ComboBoxInternalState_rec **b) { return strcmp((*a)->desc, (*b)->desc); }
|
|
};
|
|
|
|
class __SWELL_ComboBoxInternalState
|
|
{
|
|
public:
|
|
__SWELL_ComboBoxInternalState() { selidx=-1; }
|
|
~__SWELL_ComboBoxInternalState() { }
|
|
|
|
int selidx;
|
|
WDL_PtrList_DeleteOnDestroy<__SWELL_ComboBoxInternalState_rec> items;
|
|
__SWELL_editControlState editstate;
|
|
};
|
|
|
|
static LRESULT WINAPI comboWindowProc(HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam)
|
|
{
|
|
static const int buttonwid = 16; // used in edit combobox
|
|
static int s_capmode_state;
|
|
__SWELL_ComboBoxInternalState *s = (__SWELL_ComboBoxInternalState*)hwnd->m_private_data;
|
|
if (msg >= CB_ADDSTRING && msg <= CB_INITSTORAGE)
|
|
{
|
|
if (s)
|
|
{
|
|
switch (msg)
|
|
{
|
|
case CB_ADDSTRING:
|
|
|
|
if (!(hwnd->m_style & CBS_SORT))
|
|
{
|
|
s->items.Add(new __SWELL_ComboBoxInternalState_rec((const char *)lParam));
|
|
return s->items.GetSize() - 1;
|
|
}
|
|
else
|
|
{
|
|
__SWELL_ComboBoxInternalState_rec *r=new __SWELL_ComboBoxInternalState_rec((const char *)lParam);
|
|
// find position of insert for wParam
|
|
bool m;
|
|
int idx = s->items.LowerBound(r,&m,__SWELL_ComboBoxInternalState_rec::cmp);
|
|
if (s->selidx >= idx) s->selidx++;
|
|
s->items.Insert(idx,r);
|
|
return idx;
|
|
}
|
|
|
|
case CB_INSERTSTRING:
|
|
if (wParam > (WPARAM)s->items.GetSize()) wParam=(WPARAM)s->items.GetSize();
|
|
s->items.Insert(wParam,new __SWELL_ComboBoxInternalState_rec((const char *)lParam));
|
|
if (s->selidx >= (int)wParam) s->selidx++;
|
|
return wParam;
|
|
|
|
case CB_DELETESTRING:
|
|
if (wParam >= (WPARAM)s->items.GetSize()) return CB_ERR;
|
|
|
|
s->items.Delete(wParam,true);
|
|
|
|
if (wParam == (WPARAM)s->selidx || s->selidx >= s->items.GetSize()) { s->selidx=-1; InvalidateRect(hwnd,NULL,FALSE); }
|
|
else if ((int)wParam < s->selidx) s->selidx--;
|
|
|
|
return s->items.GetSize();
|
|
|
|
case CB_GETCOUNT: return s->items.GetSize();
|
|
case CB_GETCURSEL: return s->selidx >=0 && s->selidx < s->items.GetSize() ? s->selidx : -1;
|
|
|
|
case CB_GETLBTEXTLEN:
|
|
case CB_GETLBTEXT:
|
|
if (wParam < (WPARAM)s->items.GetSize())
|
|
{
|
|
__SWELL_ComboBoxInternalState_rec *rec = s->items.Get(wParam);
|
|
if (!rec) return CB_ERR;
|
|
const char *ptr=rec->desc;
|
|
int l = ptr ? strlen(ptr) : 0;
|
|
if (msg == CB_GETLBTEXT && lParam) memcpy((char *)lParam,ptr?ptr:"",l+1);
|
|
return l;
|
|
}
|
|
return CB_ERR;
|
|
case CB_RESETCONTENT:
|
|
s->selidx=-1;
|
|
s->items.Empty(true);
|
|
return 0;
|
|
case CB_SETCURSEL:
|
|
if (wParam >= (WPARAM)s->items.GetSize())
|
|
{
|
|
if (s->selidx!=-1)
|
|
{
|
|
s->selidx = -1;
|
|
SetWindowText(hwnd,"");
|
|
InvalidateRect(hwnd,NULL,FALSE);
|
|
}
|
|
return CB_ERR;
|
|
}
|
|
else
|
|
{
|
|
if (s->selidx != (int)wParam)
|
|
{
|
|
s->selidx=(int)wParam;
|
|
char *ptr=s->items.Get(wParam)->desc;
|
|
SetWindowText(hwnd,ptr);
|
|
InvalidateRect(hwnd,NULL,FALSE);
|
|
}
|
|
}
|
|
return s->selidx;
|
|
|
|
case CB_GETITEMDATA:
|
|
if (wParam < (WPARAM)s->items.GetSize())
|
|
{
|
|
return s->items.Get(wParam)->parm;
|
|
}
|
|
return CB_ERR;
|
|
case CB_SETITEMDATA:
|
|
if (wParam < (WPARAM)s->items.GetSize())
|
|
{
|
|
s->items.Get(wParam)->parm=lParam;
|
|
return 0;
|
|
}
|
|
return CB_ERR;
|
|
case CB_INITSTORAGE:
|
|
return 0;
|
|
|
|
case CB_FINDSTRINGEXACT:
|
|
case CB_FINDSTRING:
|
|
{
|
|
int x;
|
|
int a = (int)wParam;
|
|
a++;
|
|
for (x=a;x<s->items.GetSize();x++)
|
|
if (msg == CB_FINDSTRING ?
|
|
!stricmp(s->items.Get(x)->desc,(char *)lParam) :
|
|
!strcmp(s->items.Get(x)->desc,(char *)lParam)) return x;
|
|
|
|
for (x=0;x<a;x++)
|
|
if (msg == CB_FINDSTRING ?
|
|
!stricmp(s->items.Get(x)->desc,(char *)lParam) :
|
|
!strcmp(s->items.Get(x)->desc,(char *)lParam)) return x;
|
|
}
|
|
return CB_ERR;
|
|
}
|
|
}
|
|
}
|
|
|
|
switch (msg)
|
|
{
|
|
case WM_NCDESTROY:
|
|
hwnd->m_private_data=0;
|
|
delete s;
|
|
break;
|
|
case WM_TIMER:
|
|
if (wParam == 100)
|
|
{
|
|
if (++s->editstate.cursor_state >= EDIT_CURSOR_CYCLE_INTERVAL) s->editstate.cursor_state=0;
|
|
if (GetFocusIncludeMenus()!=hwnd || s->editstate.cursor_state<2) InvalidateRect(hwnd,NULL,FALSE);
|
|
}
|
|
else if (wParam==1)
|
|
{
|
|
if (!fast_has_focus(hwnd))
|
|
{
|
|
KillTimer(hwnd,1);
|
|
InvalidateRect(hwnd,NULL,FALSE);
|
|
}
|
|
}
|
|
return 0;
|
|
|
|
case WM_LBUTTONDOWN:
|
|
{
|
|
RECT r;
|
|
GetClientRect(hwnd,&r);
|
|
if ((hwnd->m_style & CBS_DROPDOWNLIST) == CBS_DROPDOWNLIST || GET_X_LPARAM(lParam) >= r.right-SWELL_UI_SCALE(buttonwid))
|
|
{
|
|
s_capmode_state=5;
|
|
}
|
|
else
|
|
{
|
|
int xo=3;
|
|
HDC hdc=GetDC(hwnd);
|
|
const int last_cursor = s->editstate.cursor_pos;
|
|
s->editstate.cursor_pos =
|
|
WDL_utf8_bytepos_to_charpos(hwnd->m_title.Get(),
|
|
editHitTest(hdc,hwnd->m_title.Get(),
|
|
hwnd->m_title.GetLength(),
|
|
GET_X_LPARAM(lParam)-xo + s->editstate.scroll_x,
|
|
GET_Y_LPARAM(lParam),0,NULL,NULL)
|
|
);
|
|
|
|
s->editstate.onMouseDown(s_capmode_state,last_cursor);
|
|
|
|
ReleaseDC(hwnd,hdc);
|
|
|
|
SetFocus(hwnd);
|
|
}
|
|
SetCapture(hwnd);
|
|
}
|
|
InvalidateRect(hwnd,NULL,FALSE);
|
|
return 0;
|
|
case WM_MOUSEMOVE:
|
|
if (GetCapture()==hwnd)
|
|
{
|
|
if (s_capmode_state == 3 || s_capmode_state == 4)
|
|
{
|
|
const bool multiline = (hwnd->m_style & ES_MULTILINE) != 0;
|
|
int xo=3;
|
|
HDC hdc=GetDC(hwnd);
|
|
int p = WDL_utf8_bytepos_to_charpos(hwnd->m_title.Get(),
|
|
editHitTest(hdc,hwnd->m_title.Get(),
|
|
multiline?-1:hwnd->m_title.GetLength(),
|
|
GET_X_LPARAM(lParam)-xo + s->editstate.scroll_x,
|
|
GET_Y_LPARAM(lParam),0,NULL,NULL)
|
|
);
|
|
ReleaseDC(hwnd,hdc);
|
|
|
|
s->editstate.onMouseDrag(s_capmode_state,p);
|
|
s->editstate.autoScrollToOffset(hwnd,p,false,false);
|
|
|
|
InvalidateRect(hwnd,NULL,FALSE);
|
|
}
|
|
}
|
|
return 0;
|
|
case WM_LBUTTONUP:
|
|
if (GetCapture()==hwnd)
|
|
{
|
|
ReleaseCapture();
|
|
popupMenu:
|
|
if (s && s->items.GetSize() && s_capmode_state == 5)
|
|
{
|
|
int x;
|
|
HMENU menu = CreatePopupMenu();
|
|
for (x=0;x<s->items.GetSize();x++)
|
|
{
|
|
MENUITEMINFO mi={sizeof(mi),MIIM_ID|MIIM_STATE|MIIM_TYPE,MFT_STRING,
|
|
(UINT) (x == s->selidx?MFS_CHECKED:0),
|
|
(UINT) (100+x), NULL,NULL,NULL,0,s->items.Get(x)->desc};
|
|
InsertMenuItem(menu,x,TRUE,&mi);
|
|
}
|
|
|
|
hwnd->Retain();
|
|
RECT r;
|
|
GetWindowRect(hwnd,&r);
|
|
RECT req_r = { 1<<30, 1<<30, 0, s->selidx };
|
|
int a = TrackPopupMenu(menu,TPM_NONOTIFY|TPM_RETURNCMD|TPM_LEFTALIGN,r.left,r.bottom,0,hwnd,&req_r);
|
|
DestroyMenu(menu);
|
|
if (hwnd->m_private_data && a>=100 && a < s->items.GetSize()+100)
|
|
{
|
|
s->selidx = a-100;
|
|
char *ptr=s->items.Get(s->selidx)->desc;
|
|
SetWindowText(hwnd,ptr);
|
|
InvalidateRect(hwnd,NULL,FALSE);
|
|
SendMessage(GetParent(hwnd),WM_COMMAND,(GetWindowLong(hwnd,GWL_ID)&0xffff) | (CBN_SELCHANGE<<16),(LPARAM)hwnd);
|
|
}
|
|
hwnd->Release();
|
|
}
|
|
}
|
|
s_capmode_state=0;
|
|
return 0;
|
|
case WM_LBUTTONDBLCLK:
|
|
if (s)
|
|
{
|
|
// technically this should select the word rather than all
|
|
s->editstate.sel1 = 0;
|
|
s->editstate.cursor_pos = s->editstate.sel2 = WDL_utf8_get_charlen(hwnd->m_title.Get());
|
|
InvalidateRect(hwnd,NULL,FALSE);
|
|
}
|
|
return 0;
|
|
case WM_KEYDOWN:
|
|
if ((lParam&FVIRTKEY) && wParam == VK_DOWN) { s_capmode_state=5; goto popupMenu; }
|
|
if ((hwnd->m_style & CBS_DROPDOWNLIST) != CBS_DROPDOWNLIST &&
|
|
OnEditKeyDown(hwnd,msg,wParam,lParam,false,false,&s->editstate,false))
|
|
{
|
|
if (s) s->selidx=-1; // lookup from text?
|
|
SendMessage(GetParent(hwnd),WM_COMMAND,(CBN_EDITCHANGE<<16) | (hwnd->m_id&0xffff),(LPARAM)hwnd);
|
|
s->editstate.autoScrollToOffset(hwnd,s->editstate.cursor_pos,false,false);
|
|
InvalidateRect(hwnd,NULL,FALSE);
|
|
return 0;
|
|
}
|
|
if (wParam == VK_SPACE) { s_capmode_state=5; goto popupMenu; }
|
|
break;
|
|
case WM_KEYUP:
|
|
return 0;
|
|
case WM_PAINT:
|
|
{
|
|
PAINTSTRUCT ps;
|
|
if (BeginPaint(hwnd,&ps))
|
|
{
|
|
RECT r;
|
|
GetClientRect(hwnd,&r);
|
|
bool pressed = s_capmode_state == 5 && GetCapture()==hwnd;
|
|
|
|
SetTextColor(ps.hdc,
|
|
hwnd->m_enabled ? g_swell_ctheme.combo_text :
|
|
g_swell_ctheme.combo_text_disabled);
|
|
SetBkMode(ps.hdc,TRANSPARENT);
|
|
|
|
Draw3DBox(ps.hdc,&r,g_swell_ctheme.combo_bg,
|
|
g_swell_ctheme.combo_hilight,
|
|
g_swell_ctheme.combo_shadow,pressed);
|
|
|
|
int cursor_pos = -1;
|
|
bool focused = false;
|
|
if ((hwnd->m_style & CBS_DROPDOWNLIST) != CBS_DROPDOWNLIST)
|
|
{
|
|
focused = GetFocusIncludeMenus()==hwnd;
|
|
if (focused)
|
|
{
|
|
if (!s->editstate.cursor_timer) { SetTimer(hwnd,100,EDIT_CURSOR_BLINK_LEN,NULL); s->editstate.cursor_timer=1; }
|
|
}
|
|
else
|
|
{
|
|
if (s->editstate.cursor_timer) { KillTimer(hwnd,100); s->editstate.cursor_timer=0; }
|
|
}
|
|
|
|
HBRUSH br = CreateSolidBrush(g_swell_ctheme.combo_bg2);
|
|
RECT tr=r;
|
|
const int pad = SWELL_UI_SCALE(2);
|
|
tr.left+=pad; tr.top+=pad; tr.bottom-=pad; tr.right -= SWELL_UI_SCALE(buttonwid+2);
|
|
FillRect(ps.hdc,&tr,br);
|
|
DeleteObject(br);
|
|
|
|
if (focused && s->editstate.cursor_state)
|
|
{
|
|
cursor_pos = utf8fs_charpos_to_bytepos(&hwnd->m_title,s->editstate.cursor_pos);
|
|
}
|
|
}
|
|
|
|
HBRUSH br = CreateSolidBrush(pressed?g_swell_ctheme.combo_arrow_press : g_swell_ctheme.combo_arrow);
|
|
HGDIOBJ oldbr=SelectObject(ps.hdc,br);
|
|
HGDIOBJ oldpen=SelectObject(ps.hdc,GetStockObject(NULL_PEN));
|
|
const int dw = SWELL_UI_SCALE(8);
|
|
const int dh = SWELL_UI_SCALE(4);
|
|
const int cx = r.right-dw/2-SWELL_UI_SCALE(5);
|
|
const int cy = (r.bottom+r.top)/2;
|
|
|
|
POINT pts[3] = {
|
|
{ cx-dw/2,cy-dh/2 },
|
|
{ cx,cy+dh/2 },
|
|
{ cx+dw/2,cy-dh/2 }
|
|
};
|
|
Polygon(ps.hdc,pts,3);
|
|
|
|
SelectObject(ps.hdc,oldpen);
|
|
SelectObject(ps.hdc,oldbr);
|
|
DeleteObject(br);
|
|
|
|
|
|
if (pressed)
|
|
{
|
|
r.left+=SWELL_UI_SCALE(2);
|
|
r.top+=SWELL_UI_SCALE(2);
|
|
}
|
|
|
|
r.left+=SWELL_UI_SCALE(3);
|
|
r.right-=SWELL_UI_SCALE(3);
|
|
|
|
if ((hwnd->m_style & CBS_DROPDOWNLIST) != CBS_DROPDOWNLIST)
|
|
{
|
|
r.right -= SWELL_UI_SCALE(buttonwid+2);
|
|
editControlPaintLine(ps.hdc, hwnd->m_title.Get(), hwnd->m_title.GetLength(),
|
|
s->editstate.cursor_state!=0 ? cursor_pos : -1,
|
|
focused ? s->editstate.sel1 : -1, focused ? s->editstate.sel2 : -1, &r, DT_VCENTER);
|
|
}
|
|
else
|
|
{
|
|
char buf[512];
|
|
buf[0]=0;
|
|
GetWindowText(hwnd,buf,sizeof(buf));
|
|
if (buf[0]) DrawText(ps.hdc,buf,-1,&r,DT_VCENTER|DT_SINGLELINE);
|
|
}
|
|
|
|
if (draw_focus_indicator(hwnd,ps.hdc,NULL))
|
|
{
|
|
KillTimer(hwnd,1);
|
|
SetTimer(hwnd,1,100,NULL);
|
|
}
|
|
EndPaint(hwnd,&ps);
|
|
}
|
|
}
|
|
return 0;
|
|
case WM_SETTEXT:
|
|
s->editstate.cursor_pos = WDL_utf8_get_charlen(hwnd->m_title.Get());
|
|
s->editstate.sel1 = s->editstate.sel2 = -1;
|
|
case WM_CAPTURECHANGED:
|
|
InvalidateRect(hwnd,NULL,FALSE);
|
|
break;
|
|
case EM_GETSEL:
|
|
if (s && (hwnd->m_style & CBS_DROPDOWNLIST) != CBS_DROPDOWNLIST)
|
|
{
|
|
if (wParam) * (int *)wParam = s->editstate.sel1;
|
|
if (lParam) * (int *)lParam = s->editstate.sel2;
|
|
if (s->editstate.sel1 < 0 || s->editstate.sel1 > 65535 ||
|
|
s->editstate.sel2 < 0 || s->editstate.sel2 > 65535) return -1;
|
|
return MAKELPARAM(s->editstate.sel1,s->editstate.sel2);
|
|
}
|
|
return 0;
|
|
case EM_SETSEL:
|
|
if (s && (hwnd->m_style & CBS_DROPDOWNLIST) != CBS_DROPDOWNLIST)
|
|
{
|
|
s->editstate.sel1 = (int)wParam;
|
|
s->editstate.sel2 = (int)lParam;
|
|
if (!s->editstate.sel1 && s->editstate.sel2 == -1)
|
|
s->editstate.sel2 = WDL_utf8_get_charlen(hwnd->m_title.Get());
|
|
if (s->editstate.sel2>=0)
|
|
s->editstate.autoScrollToOffset(hwnd,s->editstate.sel2, false, false);
|
|
InvalidateRect(hwnd,NULL,FALSE);
|
|
}
|
|
return 0;
|
|
case WM_MOUSEWHEEL:
|
|
// pass modified mousewheel to parent (not sure if we need to do this but other controls do...)
|
|
if ((GetAsyncKeyState(VK_CONTROL)&0x8000) || (GetAsyncKeyState(VK_MENU)&0x8000)) break;
|
|
|
|
if (s)
|
|
{
|
|
int amt = ((short)HIWORD(wParam));
|
|
if (GetAsyncKeyState(VK_SHIFT)&0x8000)
|
|
{
|
|
if (amt<0) amt=-1;
|
|
else if (amt>0) amt=1;
|
|
}
|
|
else
|
|
amt /= 120;
|
|
|
|
if (amt)
|
|
{
|
|
int nv = s->selidx + amt;
|
|
if (nv>=s->items.GetSize()) nv=s->items.GetSize()-1;
|
|
if (nv < 0) nv=0;
|
|
if (nv != s->selidx && s->items.Get(nv))
|
|
{
|
|
s->selidx=nv;
|
|
SetWindowText(hwnd,s->items.Get(nv)->desc);
|
|
InvalidateRect(hwnd,NULL,FALSE);
|
|
SendMessage(GetParent(hwnd),WM_COMMAND,(GetWindowLong(hwnd,GWL_ID)&0xffff) | (CBN_SELCHANGE<<16),(LPARAM)hwnd);
|
|
}
|
|
}
|
|
}
|
|
|
|
return 0;
|
|
case WM_SETFOCUS:
|
|
case WM_KILLFOCUS:
|
|
InvalidateRect(hwnd,NULL,FALSE);
|
|
break;
|
|
}
|
|
return DefWindowProc(hwnd,msg,wParam,lParam);
|
|
}
|
|
|
|
|
|
/// these are for swell-dlggen.h
|
|
HWND SWELL_MakeButton(int def, const char *label, int idx, int x, int y, int w, int h, int flags)
|
|
{
|
|
UINT_PTR a=(UINT_PTR)label;
|
|
if (a < 65536) label = "ICONTEMP";
|
|
|
|
RECT tr=MakeCoords(x,y,w,h,true);
|
|
HWND hwnd = swell_makeButton(m_make_owner,idx,&tr,label,!(flags&SWELL_NOT_WS_VISIBLE),(def ? BS_DEFPUSHBUTTON : 0) | (flags&BS_XPOSITION_MASK));
|
|
|
|
if (m_doautoright) UpdateAutoCoords(tr);
|
|
if (def) { }
|
|
return hwnd;
|
|
}
|
|
|
|
|
|
HWND SWELL_MakeLabel( int align, const char *label, int idx, int x, int y, int w, int h, int flags)
|
|
{
|
|
RECT tr=MakeCoords(x,y,w,h,true);
|
|
HWND hwnd = new HWND__(m_make_owner,idx,&tr,label, !(flags&SWELL_NOT_WS_VISIBLE),labelWindowProc);
|
|
hwnd->m_classname = "Static";
|
|
if (align > 0) flags |= SS_RIGHT;
|
|
else if (align == 0) flags |= SS_CENTER;
|
|
hwnd->m_style = (flags & ~SWELL_NOT_WS_VISIBLE)|WS_CHILD;
|
|
hwnd->m_wantfocus = false;
|
|
hwnd->m_wndproc(hwnd,WM_CREATE,0,0);
|
|
if (m_doautoright) UpdateAutoCoords(tr);
|
|
return hwnd;
|
|
}
|
|
HWND SWELL_MakeEditField(int idx, int x, int y, int w, int h, int flags)
|
|
{
|
|
RECT tr=MakeCoords(x,y,w,h,true);
|
|
HWND hwnd = new HWND__(m_make_owner,idx,&tr,NULL, !(flags&SWELL_NOT_WS_VISIBLE),editWindowProc);
|
|
hwnd->m_private_data = (INT_PTR) new __SWELL_editControlState;
|
|
hwnd->m_style = WS_CHILD | (flags & ~SWELL_NOT_WS_VISIBLE);
|
|
hwnd->m_classname = "Edit";
|
|
hwnd->m_wndproc(hwnd,WM_CREATE,0,0);
|
|
if (m_doautoright) UpdateAutoCoords(tr);
|
|
return hwnd;
|
|
}
|
|
|
|
|
|
HWND SWELL_MakeCheckBox(const char *name, int idx, int x, int y, int w, int h, int flags=0)
|
|
{
|
|
return SWELL_MakeControl(name,idx,"Button",BS_AUTOCHECKBOX|flags,x,y,w,h,0);
|
|
}
|
|
|
|
struct SWELL_ListView_Col
|
|
{
|
|
char *name;
|
|
int xwid;
|
|
int sortindicator;
|
|
int col_index;
|
|
int fmt; // LVCFMT_
|
|
};
|
|
|
|
enum { LISTVIEW_HDR_YMARGIN = 2 };
|
|
|
|
enum ListViewCapMode
|
|
{
|
|
LISTVIEW_CAP_NONE=0,
|
|
LISTVIEW_CAP_XSCROLL, // data1=xp
|
|
LISTVIEW_CAP_YSCROLL, // data1=yp
|
|
LISTVIEW_CAP_DRAG, // data1=row, data2=displaycolumnindex
|
|
LISTVIEW_CAP_COLRESIZE, // data1 = displaycolumnindex, data2=xoffset
|
|
LISTVIEW_CAP_COLCLICK, // data1 = displaycolumnindex, data2=original lparam, advances to COLREORDER with same parameters
|
|
LISTVIEW_CAP_COLREORDER,
|
|
};
|
|
|
|
struct listViewState
|
|
{
|
|
listViewState(bool ownerData, bool isMultiSel, bool isListBox)
|
|
{
|
|
m_selitem=-1;
|
|
m_is_multisel = isMultiSel;
|
|
m_is_listbox = isListBox;
|
|
m_owner_data_size = ownerData ? 0 : -1;
|
|
m_last_row_height = 0;
|
|
m_scroll_x=m_scroll_y=0;
|
|
m_capmode_state=LISTVIEW_CAP_NONE;
|
|
m_capmode_data1=0;
|
|
m_capmode_data2=0;
|
|
m_status_imagelist = NULL;
|
|
m_status_imagelist_type = 0;
|
|
m_extended_style=0;
|
|
|
|
m_color_bg = g_swell_ctheme.listview_bg;
|
|
m_color_bg_sel = g_swell_ctheme.listview_bg_sel;
|
|
m_color_bg_sel_inactive = g_swell_ctheme.listview_bg_sel_inactive;
|
|
m_color_text = g_swell_ctheme.listview_text;
|
|
m_color_text_sel = g_swell_ctheme.listview_text_sel;
|
|
m_color_text_sel_inactive = g_swell_ctheme.listview_text_sel_inactive;
|
|
m_color_grid = g_swell_ctheme.listview_grid;
|
|
memset(m_color_extras,0xff,sizeof(m_color_extras)); // if !=-1, overrides bg/fg for (focus?0:2)
|
|
m_fastclick_mask = 0;
|
|
}
|
|
~listViewState()
|
|
{
|
|
m_data.Empty(true);
|
|
const int n=m_cols.GetSize();
|
|
for (int x=0;x<n;x++) free(m_cols.Get()[x].name);
|
|
}
|
|
WDL_PtrList<SWELL_ListView_Row> m_data;
|
|
WDL_TypedBuf<SWELL_ListView_Col> m_cols;
|
|
|
|
|
|
int GetColumnIndex(int dispindex) const
|
|
{
|
|
if (m_is_listbox || !m_cols.GetSize())
|
|
{
|
|
WDL_ASSERT(dispindex==0);
|
|
return 0;
|
|
}
|
|
if (WDL_NORMALLY(dispindex>=0 && dispindex < m_cols.GetSize()))
|
|
{
|
|
WDL_ASSERT(m_cols.Get()[dispindex].col_index >= 0);
|
|
WDL_ASSERT(m_cols.Get()[dispindex].col_index < m_cols.GetSize());
|
|
return m_cols.Get()[dispindex].col_index;
|
|
}
|
|
return 0;
|
|
}
|
|
SWELL_ListView_Col *GetColumnByIndex(int idx) const
|
|
{
|
|
SWELL_ListView_Col *c = m_cols.Get();
|
|
const int n = m_cols.GetSize();
|
|
for (int x = 0; x < n; x ++) if (c[x].col_index == idx) return c+x;
|
|
return NULL;
|
|
}
|
|
|
|
int GetNumItems() const { return m_owner_data_size>=0 ? m_owner_data_size : m_data.GetSize(); }
|
|
bool IsOwnerData() const { return m_owner_data_size>=0; }
|
|
bool HasColumnHeaders(HWND hwnd) const
|
|
{
|
|
if (m_is_listbox || !m_cols.GetSize()) return false;
|
|
return !(hwnd->m_style & LVS_NOCOLUMNHEADER) && (hwnd->m_style & LVS_REPORT);
|
|
}
|
|
int GetColumnHeaderHeight(HWND hwnd) const { return HasColumnHeaders(hwnd) ? m_last_row_height+LISTVIEW_HDR_YMARGIN : 0; }
|
|
|
|
int m_owner_data_size; // -1 if m_data valid, otherwise size
|
|
int m_last_row_height;
|
|
int m_selitem; // for single sel, or used for focus for multisel
|
|
|
|
ListViewCapMode m_capmode_state;
|
|
int m_scroll_x,m_scroll_y, m_capmode_data1,m_capmode_data2;
|
|
int m_extended_style;
|
|
|
|
int m_fastclick_mask;
|
|
|
|
int m_color_bg, m_color_bg_sel, m_color_bg_sel_inactive, m_color_text, m_color_text_sel, m_color_text_sel_inactive, m_color_grid;
|
|
int m_color_extras[4];
|
|
|
|
int getTotalWidth() const
|
|
{
|
|
int s = 0;
|
|
const SWELL_ListView_Col *col = m_cols.Get();
|
|
const int n = m_cols.GetSize();
|
|
for (int x=0; x < n; x ++) s += col[x].xwid;
|
|
return s;
|
|
}
|
|
|
|
void sanitizeScroll(HWND h)
|
|
{
|
|
RECT r;
|
|
GetClientRect(h,&r);
|
|
r.right -= g_swell_ctheme.scrollbar_width;
|
|
|
|
const int mx = getTotalWidth() - r.right;
|
|
if (m_scroll_x > mx) m_scroll_x = mx;
|
|
if (m_scroll_x < 0) m_scroll_x = 0;
|
|
|
|
if (m_last_row_height > 0)
|
|
{
|
|
r.bottom -= GetColumnHeaderHeight(h);
|
|
if (mx>0) r.bottom -= g_swell_ctheme.scrollbar_width;
|
|
|
|
const int vh = m_last_row_height * GetNumItems();
|
|
if (m_scroll_y < 0 || vh <= r.bottom) m_scroll_y=0;
|
|
else if (m_scroll_y > vh - r.bottom) m_scroll_y = vh - r.bottom;
|
|
}
|
|
}
|
|
|
|
WDL_TypedBuf<unsigned int> m_owner_multisel_state;
|
|
|
|
bool get_sel(int idx)
|
|
{
|
|
if (!m_is_multisel) return idx>=0 && idx == m_selitem;
|
|
if (m_owner_data_size<0)
|
|
{
|
|
SWELL_ListView_Row *p = m_data.Get(idx);
|
|
return p && (p->m_tmp&1);
|
|
}
|
|
const unsigned int mask = 1<<(idx&31);
|
|
const int szn = idx/32;
|
|
const unsigned int *p=m_owner_multisel_state.Get();
|
|
return p && idx>=0 && szn < m_owner_multisel_state.GetSize() && (p[szn]&mask);
|
|
}
|
|
bool set_sel(int idx, bool v) // returns true if value changed
|
|
{
|
|
if (!m_is_multisel)
|
|
{
|
|
const int oldsel = m_selitem;
|
|
if (v) m_selitem = idx;
|
|
else if (oldsel == idx) m_selitem = -1;
|
|
|
|
return oldsel != m_selitem;
|
|
}
|
|
else if (m_owner_data_size<0)
|
|
{
|
|
SWELL_ListView_Row *p = m_data.Get(idx);
|
|
if (p)
|
|
{
|
|
const int oldtmp = p->m_tmp;
|
|
return (p->m_tmp = (v ? (oldtmp|1) : (oldtmp&~1))) != oldtmp;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
if (idx>=0 && idx < m_owner_data_size)
|
|
{
|
|
const int szn = idx/32;
|
|
const int oldsz=m_owner_multisel_state.GetSize();
|
|
unsigned int *p = m_owner_multisel_state.Get();
|
|
if (oldsz<szn+1)
|
|
{
|
|
p = m_owner_multisel_state.ResizeOK(szn+1,false);
|
|
if (p) memset(p+oldsz,0,(szn+1-oldsz)*sizeof(*p));
|
|
}
|
|
const unsigned int mask = 1<<(idx&31);
|
|
if (p)
|
|
{
|
|
const unsigned int oldval = p[szn];
|
|
return oldval != (p[szn] = v ? (oldval|mask) : (oldval&~mask));
|
|
}
|
|
}
|
|
}
|
|
return false;
|
|
}
|
|
bool clear_sel()
|
|
{
|
|
if (!m_is_multisel)
|
|
{
|
|
if (m_selitem != -1) { m_selitem = -1; return true; }
|
|
return false;
|
|
}
|
|
|
|
if (m_owner_data_size<0)
|
|
{
|
|
bool rv=false;
|
|
const int n=m_data.GetSize();
|
|
for (int x=0;x<n;x++)
|
|
{
|
|
int *a = &m_data.Get(x)->m_tmp;
|
|
if (*a&1)
|
|
{
|
|
*a&=~1;
|
|
rv=true;
|
|
}
|
|
}
|
|
return rv;
|
|
}
|
|
|
|
bool rv=false;
|
|
int n = m_owner_multisel_state.GetSize();
|
|
if (n > m_owner_data_size) n=m_owner_data_size;
|
|
for(int x=0;x<n;x++) if (m_owner_multisel_state.Get()[x]) { rv=true; break; }
|
|
|
|
m_owner_multisel_state.Resize(0,false);
|
|
return rv;
|
|
}
|
|
bool hasStatusImage() const
|
|
{
|
|
return m_status_imagelist && m_status_imagelist_type == LVSIL_STATE;
|
|
}
|
|
bool hasAnyImage() const
|
|
{
|
|
return m_status_imagelist &&
|
|
(m_status_imagelist_type == LVSIL_SMALL || m_status_imagelist_type == LVSIL_STATE);
|
|
}
|
|
|
|
|
|
bool m_is_multisel, m_is_listbox;
|
|
WDL_PtrList<HGDIOBJ__> *m_status_imagelist;
|
|
int m_status_imagelist_type;
|
|
|
|
static int compareRows(const SWELL_ListView_Row **_a, const SWELL_ListView_Row **_b)
|
|
{
|
|
const char *a, *b;
|
|
if (!_a || !(a=(*_a)->get_col_txt(0))) a="";
|
|
if (!_b || !(b=(*_b)->get_col_txt(0))) b="";
|
|
return strcmp(a,b);
|
|
}
|
|
};
|
|
|
|
// returns non-NULL if a searching string occurred
|
|
static const char *stateStringOnKey(UINT uMsg, WPARAM wParam, LPARAM lParam)
|
|
{
|
|
if (lParam & (FCONTROL|FALT|FLWIN)) return NULL;
|
|
if (uMsg != WM_KEYDOWN) return NULL;
|
|
static WDL_FastString str;
|
|
static DWORD last_t;
|
|
DWORD now = GetTickCount();
|
|
if ((now-last_t) > 500) str.Set("");
|
|
last_t = now;
|
|
|
|
const bool is_numpad = wParam >= VK_NUMPAD0 && wParam <= VK_DIVIDE;
|
|
if ((lParam & FVIRTKEY) && wParam == VK_BACK)
|
|
{
|
|
str.SetLen(utf8fs_charpos_to_bytepos(&str,WDL_utf8_get_charlen(str.Get())-1));
|
|
}
|
|
else if (wParam >= 32 && (!(lParam & FVIRTKEY) || swell_is_virtkey_char((int)wParam) || is_numpad))
|
|
{
|
|
if (lParam & FVIRTKEY)
|
|
{
|
|
if (wParam >= 'A' && wParam <= 'Z')
|
|
{
|
|
if ((lParam&FSHIFT) ^ (swell_is_likely_capslock?0:FSHIFT)) wParam += 'a' - 'A';
|
|
}
|
|
else if (is_numpad)
|
|
{
|
|
if (wParam <= VK_NUMPAD9) wParam += '0' - VK_NUMPAD0;
|
|
else wParam += '*' - VK_MULTIPLY;
|
|
}
|
|
}
|
|
|
|
char b[8];
|
|
WDL_MakeUTFChar(b,wParam,sizeof(b));
|
|
str.Append(b);
|
|
return str.Get();
|
|
}
|
|
|
|
return NULL;
|
|
}
|
|
|
|
|
|
static LRESULT listViewWindowProc(HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam)
|
|
{
|
|
enum { col_resize_sz = 3 };
|
|
listViewState *lvs = (listViewState *)hwnd->m_private_data;
|
|
static POINT s_clickpt;
|
|
switch (msg)
|
|
{
|
|
case WM_MOUSEWHEEL:
|
|
if ((GetAsyncKeyState(VK_CONTROL)&0x8000) || (GetAsyncKeyState(VK_MENU)&0x8000)) break; // pass modified mousewheel to parent
|
|
|
|
{
|
|
const int amt = ((short)HIWORD(wParam))/40;
|
|
if (amt && lvs)
|
|
{
|
|
if (GetAsyncKeyState(VK_SHIFT)&0x8000)
|
|
{
|
|
const int oldscroll = lvs->m_scroll_x;
|
|
lvs->m_scroll_x -= amt*4;
|
|
lvs->sanitizeScroll(hwnd);
|
|
if (lvs->m_scroll_x != oldscroll)
|
|
InvalidateRect(hwnd,NULL,FALSE);
|
|
}
|
|
else
|
|
{
|
|
const int oldscroll = lvs->m_scroll_y;
|
|
lvs->m_scroll_y -= amt*lvs->m_last_row_height;
|
|
lvs->sanitizeScroll(hwnd);
|
|
if (lvs->m_scroll_y != oldscroll)
|
|
InvalidateRect(hwnd,NULL,FALSE);
|
|
}
|
|
}
|
|
}
|
|
return 1;
|
|
case WM_RBUTTONDOWN:
|
|
if (lvs && lvs->m_last_row_height>0 && !lvs->m_is_listbox)
|
|
{
|
|
const int hdr_size = lvs->GetColumnHeaderHeight(hwnd);
|
|
if (hdr_size > 0 && GET_Y_LPARAM(lParam) < hdr_size) break; // let it get converted to WM_CONTEXTMENU
|
|
|
|
LVHITTESTINFO inf = { { GET_X_LPARAM(lParam), GET_Y_LPARAM(lParam) }, };
|
|
|
|
const int row = ListView_SubItemHitTest(hwnd, &inf);
|
|
const int n = ListView_GetItemCount(hwnd);
|
|
if (row>=0 && row<n && !ListView_GetItemState(hwnd,row,LVIS_SELECTED))
|
|
{
|
|
for (int x=0;x<n;x++)
|
|
{
|
|
ListView_SetItemState(hwnd,x,(x==row)?(LVIS_SELECTED|LVIS_FOCUSED):0,LVIS_SELECTED|LVIS_FOCUSED);
|
|
}
|
|
}
|
|
}
|
|
return 1;
|
|
case WM_RBUTTONUP:
|
|
if (lvs && lvs->m_last_row_height>0 && !lvs->m_is_listbox)
|
|
{
|
|
const int hdr_size = lvs->GetColumnHeaderHeight(hwnd);
|
|
if (hdr_size > 0 && GET_Y_LPARAM(lParam) < hdr_size) break; // let it get converted to WM_CONTEXTMENU
|
|
|
|
LVHITTESTINFO inf = { { GET_X_LPARAM(lParam), GET_Y_LPARAM(lParam) }, };
|
|
|
|
const int row = ListView_SubItemHitTest(hwnd, &inf);
|
|
NMLISTVIEW nm={{hwnd,hwnd->m_id,NM_RCLICK},row,inf.iSubItem,0,0,0, {inf.pt.x,inf.pt.y}};
|
|
if (SendMessage(GetParent(hwnd),WM_NOTIFY,hwnd->m_id,(LPARAM)&nm))
|
|
return 1;
|
|
}
|
|
break;
|
|
case WM_SETCURSOR:
|
|
if (lvs)
|
|
{
|
|
POINT p;
|
|
GetCursorPos(&p);
|
|
ScreenToClient(hwnd,&p);
|
|
const int hdr_size = lvs->GetColumnHeaderHeight(hwnd);
|
|
if (p.y >= 0 && p.y < hdr_size)
|
|
{
|
|
const SWELL_ListView_Col *col = lvs->m_cols.Get();
|
|
p.x += lvs->m_scroll_x;
|
|
if (lvs->hasStatusImage()) p.x -= lvs->m_last_row_height;
|
|
|
|
for (int x=0; x < lvs->m_cols.GetSize(); x ++)
|
|
{
|
|
const int minw = wdl_max(col_resize_sz+1,col[x].xwid);
|
|
if (p.x >= minw-col_resize_sz && p.x < minw)
|
|
{
|
|
SetCursor(SWELL_LoadCursor(IDC_SIZEWE));
|
|
return 1;
|
|
}
|
|
p.x -= col[x].xwid;
|
|
}
|
|
}
|
|
}
|
|
break;
|
|
case WM_LBUTTONDBLCLK:
|
|
case WM_LBUTTONDOWN:
|
|
SetFocus(hwnd);
|
|
if (msg == WM_LBUTTONDOWN) SetCapture(hwnd);
|
|
else ReleaseCapture();
|
|
|
|
if (lvs && lvs->m_last_row_height>0)
|
|
{
|
|
s_clickpt.x = GET_X_LPARAM(lParam);
|
|
s_clickpt.y = GET_Y_LPARAM(lParam);
|
|
RECT r;
|
|
GetClientRect(hwnd,&r);
|
|
const int hdr_size = lvs->GetColumnHeaderHeight(hwnd);
|
|
const int hdr_size_nomargin = hdr_size>0 ? hdr_size-LISTVIEW_HDR_YMARGIN : 0;
|
|
const int n=lvs->GetNumItems();
|
|
const int row_height = lvs->m_last_row_height;
|
|
const int totalw = lvs->getTotalWidth();
|
|
|
|
if (hdr_size + n * row_height > r.bottom - g_swell_ctheme.scrollbar_width)
|
|
r.right -= g_swell_ctheme.scrollbar_width;
|
|
|
|
if (GET_Y_LPARAM(lParam) >= 0 && GET_Y_LPARAM(lParam) < hdr_size)
|
|
{
|
|
const SWELL_ListView_Col *col = lvs->m_cols.Get();
|
|
int px = GET_X_LPARAM(lParam) + lvs->m_scroll_x;
|
|
if (lvs->hasStatusImage()) px -= lvs->m_last_row_height;
|
|
for (int x=0; x < lvs->m_cols.GetSize(); x ++)
|
|
{
|
|
const int minw = wdl_max(col_resize_sz+1,col[x].xwid);
|
|
if (px >= minw-col_resize_sz && px < minw)
|
|
{
|
|
lvs->m_capmode_state = LISTVIEW_CAP_COLRESIZE;
|
|
lvs->m_capmode_data1 = x;
|
|
lvs->m_capmode_data2 = minw-px;
|
|
return 0;
|
|
}
|
|
|
|
if (px >= 0 && px <col[x].xwid)
|
|
{
|
|
if (msg != WM_LBUTTONDBLCLK)
|
|
{
|
|
lvs->m_capmode_state = LISTVIEW_CAP_COLCLICK;
|
|
lvs->m_capmode_data1 = x;
|
|
lvs->m_capmode_data2 = (int)lParam;
|
|
}
|
|
return 0;
|
|
}
|
|
px -= col[x].xwid;
|
|
}
|
|
}
|
|
else if (totalw > r.right &&
|
|
GET_Y_LPARAM(lParam) >= r.bottom - g_swell_ctheme.scrollbar_width)
|
|
{
|
|
const int xpos = GET_X_LPARAM(lParam);
|
|
int xp = xpos;
|
|
|
|
int thumbsz, thumbpos;
|
|
calcScroll(r.right,totalw,lvs->m_scroll_x,&thumbsz,&thumbpos);
|
|
|
|
if (xpos < thumbpos) xp = thumbpos; // jump on first mouse move
|
|
else if (xpos > thumbpos+thumbsz) xp = thumbpos + thumbsz;
|
|
|
|
lvs->m_capmode_state = LISTVIEW_CAP_XSCROLL;
|
|
lvs->m_capmode_data1 = xp;
|
|
if (xpos < thumbpos || xpos > thumbpos+thumbsz) goto forceMouseMove;
|
|
return 0;
|
|
}
|
|
|
|
lvs->m_capmode_state=LISTVIEW_CAP_NONE;
|
|
const int ypos = GET_Y_LPARAM(lParam) - hdr_size_nomargin;
|
|
|
|
if (totalw > r.right) r.bottom -= g_swell_ctheme.scrollbar_width;
|
|
if (n * row_height > r.bottom - hdr_size_nomargin &&
|
|
GET_X_LPARAM(lParam) >= r.right)
|
|
{
|
|
int yp = GET_Y_LPARAM(lParam);
|
|
|
|
int thumbsz, thumbpos;
|
|
calcScroll(r.bottom - hdr_size_nomargin, n*row_height,lvs->m_scroll_y,&thumbsz,&thumbpos);
|
|
|
|
if (ypos < thumbpos) yp = thumbpos + hdr_size_nomargin; // jump on first mouse move
|
|
else if (ypos > thumbpos+thumbsz) yp = thumbpos + hdr_size_nomargin + thumbsz;
|
|
|
|
lvs->m_capmode_state = LISTVIEW_CAP_YSCROLL;
|
|
lvs->m_capmode_data1 = yp;
|
|
if (ypos < thumbpos || ypos > thumbpos+thumbsz) goto forceMouseMove;
|
|
return 0;
|
|
}
|
|
|
|
const int hit = ypos >= 0 ? ((ypos+lvs->m_scroll_y) / row_height) : -1;
|
|
if (hit < 0) return 1;
|
|
|
|
int subitem_col = 0;
|
|
int subitem = 0; // display index
|
|
|
|
{
|
|
const int ncol=lvs->m_cols.GetSize();
|
|
const bool has_image = lvs->hasStatusImage();
|
|
int xpos=0, xpt = GET_X_LPARAM(lParam) + lvs->m_scroll_x;
|
|
if (has_image) xpos += lvs->m_last_row_height;
|
|
for (int x=0;x<ncol;x++)
|
|
{
|
|
const int xwid = lvs->m_cols.Get()[x].xwid;
|
|
if (xpt >= xpos && xpt < xpos+xwid)
|
|
{
|
|
subitem_col = lvs->m_cols.Get()[x].col_index;
|
|
subitem = x;
|
|
break;
|
|
}
|
|
xpos += xwid;
|
|
}
|
|
}
|
|
|
|
if ((subitem_col >= 0 && subitem_col < 32 && (lvs->m_fastclick_mask & (1u<<subitem_col))) || // fastclick mode
|
|
(lvs->m_status_imagelist && GET_X_LPARAM(lParam) + lvs->m_scroll_x < lvs->m_last_row_height) // image click
|
|
)
|
|
{
|
|
NMLISTVIEW nm={{hwnd,hwnd->m_id,msg == WM_LBUTTONDBLCLK ? NM_DBLCLK : NM_CLICK},hit,lvs->GetColumnIndex(subitem),0,0,0, {s_clickpt.x, s_clickpt.y }};
|
|
SendMessage(GetParent(hwnd),WM_NOTIFY,hwnd->m_id,(LPARAM)&nm);
|
|
return 0;
|
|
}
|
|
|
|
if (!lvs->m_is_multisel)
|
|
{
|
|
const int oldsel = lvs->m_selitem;
|
|
if (hit >= 0 && hit < n) lvs->m_selitem = hit;
|
|
else lvs->m_selitem = -1;
|
|
|
|
if (lvs->m_is_listbox)
|
|
{
|
|
//if (oldsel != lvs->m_selitem)
|
|
SendMessage(GetParent(hwnd),WM_COMMAND,(LBN_SELCHANGE<<16) | (hwnd->m_id&0xffff),(LPARAM)hwnd);
|
|
if (msg == WM_LBUTTONDBLCLK)
|
|
SendMessage(GetParent(hwnd),WM_COMMAND,(LBN_DBLCLK<<16) | (hwnd->m_id&0xffff),(LPARAM)hwnd);
|
|
}
|
|
else
|
|
{
|
|
if (hit >= 0)
|
|
{
|
|
lvs->m_capmode_state = LISTVIEW_CAP_DRAG;
|
|
lvs->m_capmode_data1 = hit;
|
|
lvs->m_capmode_data2 = subitem;
|
|
}
|
|
|
|
if(hit < n)
|
|
{
|
|
NMLISTVIEW nm={{hwnd,hwnd->m_id,msg == WM_LBUTTONDBLCLK ? NM_DBLCLK : NM_CLICK},hit,lvs->GetColumnIndex(subitem),0,0,0, {s_clickpt.x, s_clickpt.y }};
|
|
SendMessage(GetParent(hwnd),WM_NOTIFY,hwnd->m_id,(LPARAM)&nm);
|
|
}
|
|
if (oldsel != lvs->m_selitem)
|
|
{
|
|
NMLISTVIEW nm={{hwnd,hwnd->m_id,LVN_ITEMCHANGED},lvs->m_selitem,1,LVIS_SELECTED,};
|
|
SendMessage(GetParent(hwnd),WM_NOTIFY,hwnd->m_id,(LPARAM)&nm);
|
|
}
|
|
}
|
|
InvalidateRect(hwnd,NULL,FALSE);
|
|
}
|
|
else
|
|
{
|
|
bool changed = false;
|
|
if (hit >= n)
|
|
{
|
|
changed |= lvs->clear_sel();
|
|
lvs->m_selitem = -1;
|
|
}
|
|
else
|
|
{
|
|
const bool ctrl = (GetAsyncKeyState(VK_CONTROL)&0x8000)!=0;
|
|
if (!ctrl)
|
|
{
|
|
if (!lvs->get_sel(hit))
|
|
changed |= lvs->clear_sel();
|
|
}
|
|
if ((GetAsyncKeyState(VK_SHIFT)&0x8000) && lvs->m_selitem >= 0)
|
|
{
|
|
int a=lvs->m_selitem;
|
|
int b = hit;
|
|
if (a>b) { b=a; a=hit; }
|
|
while (a<=b) changed |= lvs->set_sel(a++,true);
|
|
}
|
|
else
|
|
{
|
|
lvs->m_selitem = hit;
|
|
changed |= lvs->set_sel(hit,!ctrl || !lvs->get_sel(hit));
|
|
}
|
|
}
|
|
|
|
if (lvs->m_is_listbox)
|
|
{
|
|
if (changed) SendMessage(GetParent(hwnd),WM_COMMAND,(LBN_SELCHANGE<<16) | (hwnd->m_id&0xffff),(LPARAM)hwnd);
|
|
if (msg == WM_LBUTTONDBLCLK)
|
|
SendMessage(GetParent(hwnd),WM_COMMAND,(LBN_DBLCLK<<16) | (hwnd->m_id&0xffff),(LPARAM)hwnd);
|
|
}
|
|
else
|
|
{
|
|
if (hit >=0 && hit < n)
|
|
{
|
|
lvs->m_capmode_state = LISTVIEW_CAP_DRAG;
|
|
lvs->m_capmode_data1 = hit;
|
|
lvs->m_capmode_data2 = subitem;
|
|
NMLISTVIEW nm={{hwnd,hwnd->m_id,msg == WM_LBUTTONDBLCLK ? NM_DBLCLK : NM_CLICK},hit,lvs->GetColumnIndex(subitem),LVIS_SELECTED,};
|
|
SendMessage(GetParent(hwnd),WM_NOTIFY,hwnd->m_id,(LPARAM)&nm);
|
|
}
|
|
if (changed)
|
|
{
|
|
NMLISTVIEW nm={{hwnd,hwnd->m_id,LVN_ITEMCHANGED},hit,0,LVIS_SELECTED,};
|
|
if (nm.iItem < 0 || nm.iItem >= n) nm.iItem=0;
|
|
SendMessage(GetParent(hwnd),WM_NOTIFY,hwnd->m_id,(LPARAM)&nm);
|
|
}
|
|
}
|
|
|
|
InvalidateRect(hwnd,NULL,FALSE);
|
|
}
|
|
}
|
|
return 1;
|
|
case WM_MOUSEMOVE:
|
|
if (GetCapture()==hwnd && lvs)
|
|
{
|
|
forceMouseMove:
|
|
RECT r;
|
|
GetClientRect(hwnd,&r);
|
|
r.right -= g_swell_ctheme.scrollbar_width;
|
|
switch (lvs->m_capmode_state)
|
|
{
|
|
case LISTVIEW_CAP_NONE:
|
|
break;
|
|
case LISTVIEW_CAP_COLRESIZE:
|
|
{
|
|
int x = lvs->m_capmode_data1;
|
|
int xp = GET_X_LPARAM(lParam) + lvs->m_scroll_x + lvs->m_capmode_data2;
|
|
if (lvs->hasStatusImage()) xp -= lvs->m_last_row_height;
|
|
|
|
SWELL_ListView_Col *col = lvs->m_cols.Get();
|
|
if (x < lvs->m_cols.GetSize())
|
|
{
|
|
for (int i = 0; i < x; i ++) xp -= col[i].xwid;
|
|
if (xp<0) xp=0;
|
|
if (col[x].xwid != xp)
|
|
{
|
|
col[x].xwid = xp;
|
|
if (lvs->m_scroll_x > 0 && GET_X_LPARAM(lParam) < 0)
|
|
lvs->m_scroll_x--;
|
|
else if (GET_X_LPARAM(lParam) > r.right+12)
|
|
lvs->m_scroll_x+=16; // additional check might not be necessary?
|
|
|
|
InvalidateRect(hwnd,NULL,FALSE);
|
|
}
|
|
}
|
|
}
|
|
break;
|
|
case LISTVIEW_CAP_DRAG:
|
|
if (!lvs->m_is_listbox)
|
|
{
|
|
const int dx = GET_X_LPARAM(lParam) - s_clickpt.x, dy = GET_Y_LPARAM(lParam) - s_clickpt.y;
|
|
if (dx*dx+dy*dy > 32)
|
|
{
|
|
NMLISTVIEW nm={{hwnd,hwnd->m_id,LVN_BEGINDRAG},lvs->m_capmode_data1,lvs->GetColumnIndex(lvs->m_capmode_data2)};
|
|
lvs->m_capmode_state=LISTVIEW_CAP_NONE;
|
|
SendMessage(GetParent(hwnd),WM_NOTIFY,hwnd->m_id,(LPARAM)&nm);
|
|
}
|
|
}
|
|
break;
|
|
case LISTVIEW_CAP_YSCROLL:
|
|
{
|
|
int yv = lvs->m_capmode_data1;
|
|
int amt = GET_Y_LPARAM(lParam) - yv;
|
|
|
|
if (amt)
|
|
{
|
|
const int totalw = lvs->getTotalWidth();
|
|
if (totalw > r.right) r.bottom -= lvs->m_last_row_height;
|
|
|
|
|
|
const int viewsz = r.bottom-lvs->GetColumnHeaderHeight(hwnd);
|
|
const double totalsz=(double)lvs->GetNumItems() * (double)lvs->m_last_row_height;
|
|
amt = (int)floor(amt * totalsz / (double)viewsz + 0.5);
|
|
|
|
const int oldscroll = lvs->m_scroll_y;
|
|
lvs->m_scroll_y += amt;
|
|
lvs->sanitizeScroll(hwnd);
|
|
if (lvs->m_scroll_y != oldscroll)
|
|
{
|
|
lvs->m_capmode_data1 = GET_Y_LPARAM(lParam);
|
|
InvalidateRect(hwnd,NULL,FALSE);
|
|
}
|
|
}
|
|
}
|
|
break;
|
|
case LISTVIEW_CAP_XSCROLL:
|
|
{
|
|
int xv = lvs->m_capmode_data1;
|
|
int amt = GET_X_LPARAM(lParam) - xv;
|
|
|
|
if (amt)
|
|
{
|
|
const int viewsz = r.right;
|
|
const double totalsz=(double)lvs->getTotalWidth();
|
|
amt = (int)floor(amt * totalsz / (double)viewsz + 0.5);
|
|
|
|
const int oldscroll = lvs->m_scroll_x;
|
|
lvs->m_scroll_x += amt;
|
|
lvs->sanitizeScroll(hwnd);
|
|
if (lvs->m_scroll_x != oldscroll)
|
|
{
|
|
lvs->m_capmode_data1 = GET_X_LPARAM(lParam);
|
|
InvalidateRect(hwnd,NULL,FALSE);
|
|
}
|
|
}
|
|
}
|
|
break;
|
|
case LISTVIEW_CAP_COLCLICK:
|
|
if (lvs->m_extended_style & LVS_EX_HEADERDRAGDROP)
|
|
{
|
|
const int xd = GET_X_LPARAM(lParam) - GET_X_LPARAM((LPARAM)lvs->m_capmode_data2),
|
|
yd = GET_Y_LPARAM(lParam) - GET_Y_LPARAM((LPARAM)lvs->m_capmode_data2);
|
|
if (xd*xd + yd*yd < 4*4) break;
|
|
lvs->m_capmode_state = LISTVIEW_CAP_COLREORDER;
|
|
}
|
|
case LISTVIEW_CAP_COLREORDER:
|
|
InvalidateRect(hwnd,NULL,FALSE);
|
|
break;
|
|
}
|
|
}
|
|
return 1;
|
|
case WM_LBUTTONUP:
|
|
if (GetCapture()==hwnd)
|
|
{
|
|
if (lvs->m_capmode_state == LISTVIEW_CAP_COLCLICK)
|
|
{
|
|
HWND par = hwnd->m_parent;
|
|
if (par)
|
|
{
|
|
NMLISTVIEW hdr={{hwnd,(UINT_PTR)hwnd->m_id,LVN_COLUMNCLICK},-1, lvs->GetColumnIndex(lvs->m_capmode_data1) };
|
|
if (par->m_wndproc&&!par->m_hashaddestroy) par->m_wndproc(par,WM_NOTIFY,hwnd->m_id, (LPARAM) &hdr);
|
|
}
|
|
}
|
|
else if (lvs->m_capmode_state == LISTVIEW_CAP_COLREORDER)
|
|
{
|
|
const int oldidx = lvs->m_capmode_data1;
|
|
const int ncols=lvs->m_cols.GetSize();
|
|
if (oldidx >= 0 && oldidx < ncols)
|
|
{
|
|
POINT p;
|
|
GetCursorPos(&p);
|
|
ScreenToClient(hwnd,&p);
|
|
int xpos=-lvs->m_scroll_x;
|
|
SWELL_ListView_Col *cols = lvs->m_cols.Get();
|
|
int x;
|
|
for (x = 0; x < ncols; x ++)
|
|
{
|
|
int xwid = cols[x].xwid;
|
|
if (!x && lvs->hasStatusImage()) xwid += lvs->m_last_row_height;
|
|
if (p.x < xpos + xwid/2) break;
|
|
xpos += xwid;
|
|
}
|
|
|
|
if (x != oldidx && x != oldidx+1)
|
|
{
|
|
SWELL_ListView_Col c = cols[oldidx];
|
|
lvs->m_cols.Delete(oldidx);
|
|
|
|
if (x > oldidx) x--;
|
|
lvs->m_cols.Insert(c,x);
|
|
|
|
}
|
|
}
|
|
}
|
|
ReleaseCapture();
|
|
InvalidateRect(hwnd,NULL,FALSE);
|
|
}
|
|
return 1;
|
|
case WM_KEYDOWN:
|
|
if (lvs)
|
|
{
|
|
const char *s = stateStringOnKey(msg,wParam,lParam);
|
|
if (s)
|
|
{
|
|
int col = 0; // column index (not display)
|
|
if (!lvs->m_is_listbox)
|
|
{
|
|
for (int x=0;x<lvs->m_cols.GetSize();x++)
|
|
{
|
|
if (lvs->m_cols.Get()[x].sortindicator)
|
|
{
|
|
col = lvs->m_cols.Get()[x].col_index;
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
const int n = lvs->GetNumItems();
|
|
int selitem = lvs->m_selitem;
|
|
if (selitem < 0 || selitem >= n) selitem=0;
|
|
for (int x=0;x<n;x++)
|
|
{
|
|
int offs = (selitem + x) % n;
|
|
if (offs < 0) offs+=n;
|
|
|
|
const char *v=NULL;
|
|
char buf[1024];
|
|
if (!lvs->IsOwnerData())
|
|
{
|
|
SWELL_ListView_Row *row = lvs->m_data.Get(offs);
|
|
if (row) v = row->get_col_txt(col);
|
|
}
|
|
else
|
|
{
|
|
buf[0]=0;
|
|
NMLVDISPINFO nm={{hwnd,hwnd->m_id,LVN_GETDISPINFO},{LVIF_TEXT, offs,col, 0,0, buf, sizeof(buf), -1 }};
|
|
SendMessage(GetParent(hwnd),WM_NOTIFY,hwnd->m_id,(LPARAM)&nm);
|
|
v = nm.item.pszText;
|
|
}
|
|
|
|
if (v && !strnicmp(v,s,strlen(s)))
|
|
{
|
|
if (!lvs->m_is_multisel)
|
|
{
|
|
const int oldsel = lvs->m_selitem;
|
|
lvs->m_selitem = offs;
|
|
|
|
if (lvs->m_is_listbox)
|
|
{
|
|
SendMessage(GetParent(hwnd),WM_COMMAND,(LBN_SELCHANGE<<16) | (hwnd->m_id&0xffff),(LPARAM)hwnd);
|
|
}
|
|
else
|
|
{
|
|
if (oldsel != lvs->m_selitem)
|
|
{
|
|
NMLISTVIEW nm={{hwnd,hwnd->m_id,LVN_ITEMCHANGED},lvs->m_selitem,1,LVIS_SELECTED,};
|
|
SendMessage(GetParent(hwnd),WM_NOTIFY,hwnd->m_id,(LPARAM)&nm);
|
|
}
|
|
}
|
|
}
|
|
else
|
|
{
|
|
bool changed = lvs->clear_sel() | lvs->set_sel(offs,true);
|
|
lvs->m_selitem = offs;
|
|
|
|
if (lvs->m_is_listbox)
|
|
{
|
|
if (changed) SendMessage(GetParent(hwnd),WM_COMMAND,(LBN_SELCHANGE<<16) | (hwnd->m_id&0xffff),(LPARAM)hwnd);
|
|
}
|
|
else
|
|
{
|
|
if (changed)
|
|
{
|
|
NMLISTVIEW nm={{hwnd,hwnd->m_id,LVN_ITEMCHANGED},offs,0,LVIS_SELECTED,};
|
|
if (nm.iItem < 0 || nm.iItem >= n) nm.iItem=0;
|
|
SendMessage(GetParent(hwnd),WM_NOTIFY,hwnd->m_id,(LPARAM)&nm);
|
|
}
|
|
}
|
|
}
|
|
|
|
ListView_EnsureVisible(hwnd,offs,FALSE);
|
|
InvalidateRect(hwnd,NULL,FALSE);
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
if (lvs && (lParam & FVIRTKEY))
|
|
{
|
|
int flag=0;
|
|
int ni;
|
|
const int oldsel = lvs->m_selitem;
|
|
|
|
switch (wParam)
|
|
{
|
|
case VK_NEXT:
|
|
case VK_PRIOR:
|
|
case VK_UP:
|
|
case VK_DOWN:
|
|
{
|
|
RECT r;
|
|
GetClientRect(hwnd,&r);
|
|
const int page = lvs->m_last_row_height ?
|
|
(r.bottom - g_swell_ctheme.scrollbar_width)/lvs->m_last_row_height : 4;
|
|
const int cnt = lvs->GetNumItems();
|
|
|
|
ni = lvs->m_selitem + (wParam == VK_UP ? -1 :
|
|
wParam == VK_PRIOR ? 2-wdl_max(page,3) :
|
|
wParam == VK_NEXT ? wdl_max(page,3)-2 :
|
|
1);
|
|
|
|
if (ni<0) ni=0;
|
|
if (ni>cnt-1) ni=cnt-1;
|
|
const bool shift = (GetAsyncKeyState(VK_SHIFT)&0x8000)!=0;
|
|
|
|
if (ni>=0 && (ni!=lvs->m_selitem || !shift))
|
|
{
|
|
if (lvs->m_is_multisel)
|
|
{
|
|
if (!shift)
|
|
{
|
|
lvs->clear_sel();
|
|
lvs->set_sel(ni,true);
|
|
}
|
|
else
|
|
{
|
|
if (lvs->get_sel(ni)) lvs->set_sel(lvs->m_selitem,false);
|
|
else lvs->set_sel(ni,true);
|
|
}
|
|
}
|
|
lvs->m_selitem=ni;
|
|
flag|=3;
|
|
}
|
|
}
|
|
flag|=4;
|
|
break;
|
|
case VK_HOME:
|
|
case VK_END:
|
|
ni = wParam == VK_HOME ? 0 : lvs->GetNumItems()-1;
|
|
if (ni != lvs->m_selitem)
|
|
{
|
|
if (lvs->m_is_multisel)
|
|
{
|
|
if (!(GetAsyncKeyState(VK_SHIFT)&0x8000))
|
|
{
|
|
lvs->clear_sel();
|
|
lvs->set_sel(ni,true);
|
|
}
|
|
else
|
|
{
|
|
if (wParam == VK_HOME)
|
|
{
|
|
for (ni = lvs->m_selitem; ni >= 0; ni--)
|
|
lvs->set_sel(ni,true);
|
|
}
|
|
else
|
|
{
|
|
for (int x=wdl_max(0,lvs->m_selitem); x <= ni; x++)
|
|
lvs->set_sel(x,true);
|
|
}
|
|
}
|
|
}
|
|
lvs->m_selitem=ni;
|
|
flag|=3;
|
|
}
|
|
flag|=4;
|
|
break;
|
|
}
|
|
if (flag)
|
|
{
|
|
if (flag & 2)
|
|
{
|
|
if (lvs->m_is_listbox)
|
|
{
|
|
if (oldsel != lvs->m_selitem)
|
|
SendMessage(GetParent(hwnd),WM_COMMAND,(LBN_SELCHANGE<<16) | (hwnd->m_id&0xffff),(LPARAM)hwnd);
|
|
}
|
|
else
|
|
{
|
|
NMLISTVIEW nm={{hwnd,hwnd->m_id,LVN_ITEMCHANGED},lvs->m_selitem,0,LVIS_SELECTED,};
|
|
SendMessage(GetParent(hwnd),WM_NOTIFY,hwnd->m_id,(LPARAM)&nm);
|
|
}
|
|
ListView_EnsureVisible(hwnd,lvs->m_selitem,FALSE);
|
|
}
|
|
if (flag&1) InvalidateRect(hwnd,NULL,FALSE);
|
|
|
|
return 0;
|
|
}
|
|
}
|
|
break;
|
|
case WM_PAINT:
|
|
{
|
|
PAINTSTRUCT ps;
|
|
if (BeginPaint(hwnd,&ps))
|
|
{
|
|
RECT cr;
|
|
GetClientRect(hwnd,&cr);
|
|
RECT rr = cr;
|
|
HBRUSH bgbr = CreateSolidBrush(lvs->m_color_bg);
|
|
FillRect(ps.hdc,&cr,bgbr);
|
|
DeleteObject(bgbr);
|
|
const bool focused = GetFocus() == hwnd;
|
|
const int bgsel = lvs->m_color_extras[focused ? 0 : 2 ];
|
|
bgbr=CreateSolidBrush(bgsel == -1 ? focused ? lvs->m_color_bg_sel : lvs->m_color_bg_sel_inactive : bgsel);
|
|
if (lvs)
|
|
{
|
|
TEXTMETRIC tm;
|
|
GetTextMetrics(ps.hdc,&tm);
|
|
const int row_height = tm.tmHeight + 4;
|
|
lvs->m_last_row_height = row_height;
|
|
lvs->sanitizeScroll(hwnd);
|
|
|
|
const bool owner_data = lvs->IsOwnerData();
|
|
const int nrows = owner_data ? lvs->m_owner_data_size : lvs->m_data.GetSize();
|
|
const int hdr_size = lvs->GetColumnHeaderHeight(hwnd);
|
|
const int hdr_size_nomargin = hdr_size>0 ? hdr_size-LISTVIEW_HDR_YMARGIN : 0;
|
|
int ypos = hdr_size - lvs->m_scroll_y;
|
|
|
|
SetBkMode(ps.hdc,TRANSPARENT);
|
|
const int ncols = lvs->m_cols.GetSize();
|
|
const int nc = wdl_max(ncols,1);
|
|
const SWELL_ListView_Col *cols = lvs->m_cols.Get();
|
|
|
|
const bool has_image = lvs->hasAnyImage();
|
|
const bool has_status_image = lvs->hasStatusImage();
|
|
const bool has_subitem_image = (lvs->m_extended_style & LVS_EX_SUBITEMIMAGES)!=0;
|
|
const int xo = lvs->m_scroll_x;
|
|
|
|
const int totalw = lvs->getTotalWidth();
|
|
|
|
const bool vscroll_area = hdr_size + nrows * row_height > cr.bottom - g_swell_ctheme.scrollbar_width;
|
|
const bool hscroll = totalw > cr.right - (vscroll_area ? g_swell_ctheme.scrollbar_width : 0);
|
|
if (hscroll)
|
|
cr.bottom -= g_swell_ctheme.scrollbar_width;
|
|
|
|
HPEN gridpen = NULL;
|
|
HGDIOBJ oldpen = NULL;
|
|
if (!lvs->m_is_listbox && (hwnd->m_style & LVS_REPORT) && (lvs->m_extended_style&LVS_EX_GRIDLINES))
|
|
{
|
|
gridpen = CreatePen(PS_SOLID,0,lvs->m_color_grid);
|
|
oldpen = SelectObject(ps.hdc,gridpen);
|
|
}
|
|
|
|
HWND par = GetParent(hwnd);
|
|
NMLVCUSTOMDRAW nmlvcd={ { {hwnd,(UINT_PTR)hwnd->m_id, NM_CUSTOMDRAW}, CDDS_ITEMPREPAINT,ps.hdc, } };
|
|
for (int rowidx = 0; rowidx < nrows && ypos < cr.bottom; rowidx ++)
|
|
{
|
|
const char *str = NULL;
|
|
char buf[4096];
|
|
|
|
bool sel;
|
|
{
|
|
RECT tr={cr.left,ypos,cr.right,ypos + row_height};
|
|
if (tr.bottom < hdr_size)
|
|
{
|
|
ypos += row_height;
|
|
continue;
|
|
}
|
|
|
|
sel = lvs->get_sel(rowidx);
|
|
if (sel)
|
|
{
|
|
FillRect(ps.hdc,&tr,bgbr);
|
|
}
|
|
}
|
|
|
|
COLORREF text_c;
|
|
if (sel)
|
|
{
|
|
int c = lvs->m_color_extras[focused ? 1 : 3 ];
|
|
text_c = c == -1 ? focused ? lvs->m_color_text_sel : lvs->m_color_text_sel_inactive : c;
|
|
}
|
|
else
|
|
text_c = lvs->m_color_text;
|
|
|
|
nmlvcd.nmcd.dwItemSpec = (DWORD)rowidx;
|
|
SWELL_ListView_Row *row = lvs->m_data.Get(rowidx);
|
|
int xpos=-xo;
|
|
for (int col = 0; col < nc && xpos < cr.right; col ++)
|
|
{
|
|
const int col_idx = lvs->GetColumnIndex(col);
|
|
|
|
nmlvcd.iSubItem = col_idx;
|
|
nmlvcd.clrText = text_c;
|
|
SendMessage(par,WM_NOTIFY,hwnd->m_id&0xffff,(LPARAM)&nmlvcd);
|
|
SetTextColor(ps.hdc, nmlvcd.clrText);
|
|
|
|
int image_idx = 0;
|
|
if (owner_data)
|
|
{
|
|
NMLVDISPINFO nm={{hwnd,hwnd->m_id,LVN_GETDISPINFO},{LVIF_TEXT, rowidx,col_idx, 0,0, buf, sizeof(buf), -1 }};
|
|
if ((!col || has_subitem_image) && has_image)
|
|
{
|
|
if (lvs->m_status_imagelist_type == LVSIL_STATE) nm.item.mask |= LVIF_STATE;
|
|
else if (lvs->m_status_imagelist_type == LVSIL_SMALL) nm.item.mask |= LVIF_IMAGE;
|
|
}
|
|
buf[0]=0;
|
|
SendMessage(par,WM_NOTIFY,hwnd->m_id,(LPARAM)&nm);
|
|
str=nm.item.pszText;
|
|
if ((!col || has_subitem_image) && has_image)
|
|
{
|
|
if (lvs->m_status_imagelist_type == LVSIL_STATE) image_idx=STATEIMAGEMASKTOINDEX(nm.item.state);
|
|
else if (lvs->m_status_imagelist_type == LVSIL_SMALL) image_idx = nm.item.iImage + 1;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
if (!col && has_image) image_idx = row->get_img_idx(0);
|
|
if (row) str = row->get_col_txt(col_idx);
|
|
}
|
|
|
|
RECT ar = { xpos,ypos, cr.right, ypos + row_height };
|
|
if ((!col || has_subitem_image) && has_image)
|
|
{
|
|
if (image_idx>0)
|
|
{
|
|
HICON icon = lvs->m_status_imagelist->Get(image_idx-1);
|
|
if (icon)
|
|
{
|
|
if (has_status_image || col >= ncols)
|
|
ar.right = ar.left + row_height;
|
|
else
|
|
ar.right = ar.left + wdl_min(row_height,cols[col].xwid);
|
|
DrawImageInRect(ps.hdc,icon,&ar);
|
|
}
|
|
}
|
|
if (has_status_image)
|
|
{
|
|
xpos += row_height;
|
|
ar.left += row_height;
|
|
}
|
|
else if (image_idx > 0)
|
|
{
|
|
ar.left += row_height;
|
|
}
|
|
}
|
|
|
|
if (lvs->m_is_listbox && (hwnd->m_style & LBS_OWNERDRAWFIXED))
|
|
{
|
|
if (hwnd->m_parent)
|
|
{
|
|
DRAWITEMSTRUCT dis = { ODT_LISTBOX, hwnd->m_id, (UINT)rowidx, 0,
|
|
(UINT)(sel?ODS_SELECTED:0),hwnd,ps.hdc,ar,row?(DWORD_PTR)row->m_param:0 };
|
|
dis.rcItem.left++;
|
|
if (cr.bottom-cr.top < nrows*row_height)
|
|
dis.rcItem.right -= g_swell_ctheme.scrollbar_width;
|
|
SendMessage(hwnd->m_parent,WM_DRAWITEM,(WPARAM)hwnd->m_id,(LPARAM)&dis);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
if (ncols > 0)
|
|
{
|
|
ar.right = xpos + cols[col].xwid - SWELL_UI_SCALE(3);
|
|
xpos += cols[col].xwid;
|
|
}
|
|
else ar.right = cr.right;
|
|
|
|
if (ar.right > ar.left && str)
|
|
{
|
|
const int adj = (ar.right-ar.left)/16;
|
|
const int maxadj = SWELL_UI_SCALE(4);
|
|
int fmt = ncols > 0 ? cols[col].fmt & 3 : LVCFMT_LEFT;
|
|
if (fmt != LVCFMT_LEFT)
|
|
{
|
|
RECT mr={0,};
|
|
DrawText(ps.hdc,str,-1,&mr,DT_CALCRECT|DT_SINGLELINE|DT_NOPREFIX);
|
|
if (mr.right-mr.left > ar.right-ar.left-maxadj) fmt = LVCFMT_LEFT;
|
|
}
|
|
|
|
if (fmt == LVCFMT_CENTER)
|
|
{
|
|
ar.left += maxadj/2;
|
|
ar.right -= maxadj/2;
|
|
DrawText(ps.hdc,str,-1,&ar,DT_CENTER|DT_VCENTER|DT_SINGLELINE|DT_NOPREFIX);
|
|
}
|
|
else if (fmt == LVCFMT_RIGHT)
|
|
{
|
|
ar.right -= wdl_min(adj,maxadj);
|
|
DrawText(ps.hdc,str,-1,&ar,DT_RIGHT|DT_VCENTER|DT_SINGLELINE|DT_NOPREFIX);
|
|
}
|
|
else
|
|
{
|
|
ar.left += wdl_min(adj,maxadj);
|
|
DrawText(ps.hdc,str,-1,&ar,DT_LEFT|DT_VCENTER|DT_SINGLELINE|DT_NOPREFIX);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
ypos += row_height;
|
|
if (gridpen)
|
|
{
|
|
MoveToEx(ps.hdc,0,ypos-1,NULL);
|
|
LineTo(ps.hdc,cr.right,ypos-1);
|
|
}
|
|
}
|
|
if (gridpen)
|
|
{
|
|
if (row_height>0) for (;;)
|
|
{
|
|
ypos += row_height;
|
|
if (ypos >= cr.bottom) break;
|
|
MoveToEx(ps.hdc,0,ypos-1,NULL);
|
|
LineTo(ps.hdc,cr.right,ypos-1);
|
|
}
|
|
int xpos=(has_status_image ? row_height : 0) - xo;
|
|
for (int col=0; col < ncols; col ++)
|
|
{
|
|
xpos += cols[col].xwid;
|
|
if (xpos > cr.right) break;
|
|
MoveToEx(ps.hdc,xpos-1,hdr_size_nomargin,NULL);
|
|
LineTo(ps.hdc,xpos-1,cr.bottom);
|
|
}
|
|
}
|
|
if (hdr_size_nomargin>0)
|
|
{
|
|
HBRUSH br = CreateSolidBrush(g_swell_ctheme.listview_hdr_bg);
|
|
int xpos=(has_status_image ? row_height : 0) - xo;
|
|
ypos=0;
|
|
SetTextColor(ps.hdc,g_swell_ctheme.listview_hdr_text);
|
|
|
|
if (xpos>0)
|
|
{
|
|
RECT tr={0,ypos,xpos,ypos+hdr_size_nomargin };
|
|
FillRect(ps.hdc,&tr,br);
|
|
}
|
|
for (int col=0; col < ncols; col ++)
|
|
{
|
|
RECT tr={xpos,ypos,0,ypos + hdr_size_nomargin };
|
|
xpos += cols[col].xwid;
|
|
tr.right = xpos;
|
|
|
|
if (tr.right > tr.left)
|
|
{
|
|
Draw3DBox(ps.hdc,&tr,
|
|
g_swell_ctheme.listview_hdr_bg,
|
|
g_swell_ctheme.listview_hdr_hilight,
|
|
g_swell_ctheme.listview_hdr_shadow);
|
|
|
|
if (cols[col].sortindicator != 0)
|
|
{
|
|
const int tsz = (tr.bottom-tr.top)/4;
|
|
if (tr.right > tr.left + 2*tsz)
|
|
{
|
|
const int x1 = tr.left + 2;
|
|
int y2 = (tr.bottom+tr.top)/2 - tsz/2 - tsz/4;
|
|
int y1 = y2 + tsz;
|
|
if (cols[col].sortindicator >= 0)
|
|
{
|
|
int tmp=y1;
|
|
y1=y2;
|
|
y2=tmp;
|
|
}
|
|
HBRUSH hdrbr = CreateSolidBrush(g_swell_ctheme.listview_hdr_arrow);
|
|
HGDIOBJ oldBrush = SelectObject(ps.hdc,hdrbr);
|
|
HGDIOBJ oldPen = SelectObject(ps.hdc,GetStockObject(NULL_PEN));
|
|
|
|
POINT pts[3] = {{x1,y1}, {x1+tsz*2,y1}, {x1+tsz,y2}};
|
|
Polygon(ps.hdc,pts,3);
|
|
SelectObject(ps.hdc,oldBrush);
|
|
SelectObject(ps.hdc,oldPen);
|
|
DeleteObject(hdrbr);
|
|
tr.left = x1 + tsz*2;
|
|
}
|
|
}
|
|
|
|
if (cols[col].name)
|
|
{
|
|
const int maxadj = SWELL_UI_SCALE(4);
|
|
int fmt = cols[col].fmt & 3;
|
|
if (fmt != LVCFMT_LEFT)
|
|
{
|
|
RECT mr={0,};
|
|
DrawText(ps.hdc,cols[col].name,-1,&mr,DT_SINGLELINE|DT_NOPREFIX|DT_CALCRECT);
|
|
if (mr.right - mr.left > tr.right-tr.left-maxadj) fmt = LVCFMT_LEFT;
|
|
}
|
|
if (fmt == LVCFMT_CENTER)
|
|
{
|
|
tr.left += maxadj/2;
|
|
tr.right -= maxadj/2;
|
|
DrawText(ps.hdc,cols[col].name,-1,&tr,DT_CENTER|DT_VCENTER|DT_SINGLELINE|DT_NOPREFIX);
|
|
}
|
|
else if (fmt == LVCFMT_RIGHT)
|
|
{
|
|
tr.right -= wdl_min((tr.right-tr.left)/4,maxadj);
|
|
DrawText(ps.hdc,cols[col].name,-1,&tr,DT_RIGHT|DT_VCENTER|DT_SINGLELINE|DT_NOPREFIX);
|
|
}
|
|
else
|
|
{
|
|
tr.left += wdl_min((tr.right-tr.left)/4,maxadj);
|
|
DrawText(ps.hdc,cols[col].name,-1,&tr,DT_LEFT|DT_VCENTER|DT_SINGLELINE|DT_NOPREFIX);
|
|
}
|
|
}
|
|
}
|
|
if (xpos >= cr.right) break;
|
|
}
|
|
if (xpos < cr.right)
|
|
{
|
|
RECT tr={xpos,ypos,cr.right,ypos+hdr_size_nomargin };
|
|
FillRect(ps.hdc,&tr,br);
|
|
}
|
|
DeleteObject(br);
|
|
if (lvs->m_capmode_state == LISTVIEW_CAP_COLREORDER && GetCapture()==hwnd)
|
|
{
|
|
const int oldidx = lvs->m_capmode_data1;
|
|
if (oldidx >= 0 && oldidx < ncols)
|
|
{
|
|
POINT p;
|
|
GetCursorPos(&p);
|
|
ScreenToClient(hwnd,&p);
|
|
xpos=-lvs->m_scroll_x;
|
|
int x;
|
|
for (x = 0; x < ncols; x ++)
|
|
{
|
|
int xwid = cols[x].xwid;
|
|
if (!x && lvs->hasStatusImage()) xwid += lvs->m_last_row_height;
|
|
if (p.x < xpos + xwid/2) break;
|
|
xpos += xwid;
|
|
}
|
|
|
|
if (x != oldidx && x != oldidx+1)
|
|
{
|
|
const char *s = cols[lvs->m_capmode_data1].name;
|
|
int sz = cols[lvs->m_capmode_data1].xwid;
|
|
if (sz < hdr_size_nomargin) sz = hdr_size_nomargin;
|
|
br = CreateSolidBrushAlpha(g_swell_ctheme.focusrect,0.75f);
|
|
RECT r = {xpos, ypos, xpos + sz, ypos + hdr_size_nomargin };
|
|
FillRect(ps.hdc, &r, br);
|
|
if (s) DrawText(ps.hdc,s,-1,&r,DT_SINGLELINE|DT_LEFT|DT_VCENTER|DT_NOPREFIX);
|
|
DeleteObject(br);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
if (gridpen)
|
|
{
|
|
SelectObject(ps.hdc,oldpen);
|
|
DeleteObject(gridpen);
|
|
}
|
|
|
|
cr.top += hdr_size_nomargin;
|
|
drawVerticalScrollbar(ps.hdc,cr,nrows*row_height,lvs->m_scroll_y);
|
|
|
|
if (hscroll)
|
|
{
|
|
cr.bottom += g_swell_ctheme.scrollbar_width;
|
|
drawHorizontalScrollbar(ps.hdc,cr,
|
|
cr.right-cr.left - (vscroll_area ? g_swell_ctheme.scrollbar_width : 0),
|
|
totalw,lvs->m_scroll_x);
|
|
}
|
|
}
|
|
Draw3DBox(ps.hdc,&rr,-1,
|
|
g_swell_ctheme.listview_hilight,
|
|
g_swell_ctheme.listview_shadow);
|
|
DeleteObject(bgbr);
|
|
|
|
EndPaint(hwnd,&ps);
|
|
}
|
|
}
|
|
return 0;
|
|
case WM_NCDESTROY:
|
|
hwnd->m_private_data = 0;
|
|
delete lvs;
|
|
break;
|
|
case LB_ADDSTRING:
|
|
case LB_INSERTSTRING:
|
|
if (lvs && !lvs->IsOwnerData())
|
|
{
|
|
SWELL_ListView_Row *row=new SWELL_ListView_Row;
|
|
row->add_col((const char *)lParam);
|
|
int idx;
|
|
if (msg == LB_ADDSTRING && hwnd->m_style & LBS_SORT)
|
|
{
|
|
bool isMatch;
|
|
idx=lvs->m_data.LowerBound(row,&isMatch,listViewState::compareRows);
|
|
}
|
|
else if (msg == LB_INSERTSTRING)
|
|
{
|
|
idx=(int)wParam;
|
|
if (idx<0 || idx>lvs->m_data.GetSize()) idx=lvs->m_data.GetSize();
|
|
}
|
|
else
|
|
idx=lvs->m_data.GetSize();
|
|
lvs->m_data.Insert(idx,row);
|
|
InvalidateRect(hwnd,NULL,FALSE);
|
|
return idx;
|
|
}
|
|
return LB_ERR;
|
|
case LB_DELETESTRING:
|
|
if (lvs && !lvs->IsOwnerData())
|
|
{
|
|
int idx = (int) wParam;
|
|
if (idx<0 || idx>=lvs->m_data.GetSize()) return LB_ERR;
|
|
lvs->m_data.Delete(idx,true);
|
|
InvalidateRect(hwnd,NULL,FALSE);
|
|
return lvs->m_data.GetSize();
|
|
}
|
|
return LB_ERR;
|
|
case LB_GETTEXT:
|
|
if (!lParam) return LB_ERR;
|
|
*(char *)lParam = 0;
|
|
if (lvs && !lvs->IsOwnerData())
|
|
{
|
|
SWELL_ListView_Row *row = lvs->m_data.Get(wParam);
|
|
const char *p;
|
|
if (row && (p=row->get_col_txt(0)))
|
|
{
|
|
strcpy((char *)lParam, p);
|
|
return (LRESULT)strlen(p);
|
|
}
|
|
}
|
|
return LB_ERR;
|
|
case LB_GETTEXTLEN:
|
|
{
|
|
SWELL_ListView_Row *row=lvs->m_data.Get(wParam);
|
|
if (row)
|
|
{
|
|
const char *p=row->get_col_txt(0);
|
|
return p?strlen(p):0;
|
|
}
|
|
}
|
|
return LB_ERR;
|
|
case LB_FINDSTRINGEXACT:
|
|
if (lParam)
|
|
{
|
|
int x = (int) wParam + 1;
|
|
if (x < 0) x=0;
|
|
const int n = lvs->m_data.GetSize();
|
|
for (int i = 0; i < n; i ++)
|
|
{
|
|
SWELL_ListView_Row *row=lvs->m_data.Get(x);
|
|
if (row)
|
|
{
|
|
const char *p = row->get_col_txt(0);
|
|
if (p && !stricmp(p,(const char *)lParam)) return x;
|
|
}
|
|
if (++x >= n) x=0;
|
|
}
|
|
}
|
|
return LB_ERR;
|
|
case LB_RESETCONTENT:
|
|
if (lvs && !lvs->IsOwnerData())
|
|
{
|
|
lvs->m_data.Empty(true,free);
|
|
}
|
|
InvalidateRect(hwnd,NULL,FALSE);
|
|
return 0;
|
|
case LB_SETSEL:
|
|
if (lvs && lvs->m_is_multisel)
|
|
{
|
|
if (lvs->IsOwnerData())
|
|
{
|
|
}
|
|
else
|
|
{
|
|
if ((int)lParam == -1)
|
|
{
|
|
int x;
|
|
const int n=lvs->m_data.GetSize();
|
|
for(x=0;x<n;x++)
|
|
{
|
|
SWELL_ListView_Row *row=lvs->m_data.Get(x);
|
|
if (row) row->m_tmp = (row->m_tmp&~1) | (wParam?1:0);
|
|
}
|
|
InvalidateRect(hwnd,NULL,FALSE);
|
|
}
|
|
else
|
|
{
|
|
SWELL_ListView_Row *row=lvs->m_data.Get((int)lParam);
|
|
if (!row) return LB_ERR;
|
|
const int otmp = row->m_tmp;
|
|
row->m_tmp = (row->m_tmp&~1) | (wParam?1:0);
|
|
if (row->m_tmp != otmp) InvalidateRect(hwnd,NULL,FALSE);
|
|
return 0;
|
|
}
|
|
}
|
|
}
|
|
return LB_ERR;
|
|
case LB_SETCURSEL:
|
|
if (lvs && !lvs->IsOwnerData() && !lvs->m_is_multisel)
|
|
{
|
|
lvs->m_selitem = (int)wParam;
|
|
InvalidateRect(hwnd,NULL,FALSE);
|
|
}
|
|
return LB_ERR;
|
|
case LB_GETSEL:
|
|
if (lvs && lvs->m_is_multisel)
|
|
{
|
|
if (lvs->IsOwnerData())
|
|
{
|
|
}
|
|
else
|
|
{
|
|
SWELL_ListView_Row *row=lvs->m_data.Get((int)wParam);
|
|
if (!row) return LB_ERR;
|
|
return row->m_tmp&1;
|
|
}
|
|
}
|
|
return LB_ERR;
|
|
case LB_GETCURSEL:
|
|
if (lvs)
|
|
{
|
|
return (LRESULT)lvs->m_selitem;
|
|
}
|
|
return LB_ERR;
|
|
case LB_GETCOUNT:
|
|
if (lvs) return lvs->GetNumItems();
|
|
return LB_ERR;
|
|
case LB_GETSELCOUNT:
|
|
if (lvs && lvs->m_is_multisel)
|
|
{
|
|
int cnt=0;
|
|
if (lvs->IsOwnerData())
|
|
{
|
|
}
|
|
else
|
|
{
|
|
int x;
|
|
const int n=lvs->m_data.GetSize();
|
|
for(x=0;x<n;x++)
|
|
{
|
|
SWELL_ListView_Row *row=lvs->m_data.Get(x);
|
|
if (row && (row->m_tmp&1)) cnt++;
|
|
}
|
|
}
|
|
return cnt;
|
|
}
|
|
return LB_ERR;
|
|
case LB_GETITEMDATA:
|
|
if (lvs && !lvs->IsOwnerData())
|
|
{
|
|
SWELL_ListView_Row *row = lvs->m_data.Get(wParam);
|
|
return row ? row->m_param : LB_ERR;
|
|
}
|
|
return LB_ERR;
|
|
case LB_SETITEMDATA:
|
|
if (lvs && !lvs->IsOwnerData())
|
|
{
|
|
SWELL_ListView_Row *row = lvs->m_data.Get(wParam);
|
|
if (row) row->m_param = lParam;
|
|
return row ? 0 : LB_ERR;
|
|
}
|
|
return LB_ERR;
|
|
case WM_SETFOCUS:
|
|
case WM_KILLFOCUS:
|
|
InvalidateRect(hwnd,NULL,FALSE);
|
|
break;
|
|
}
|
|
return DefWindowProc(hwnd,msg,wParam,lParam);
|
|
}
|
|
|
|
struct treeViewState
|
|
{
|
|
treeViewState()
|
|
{
|
|
m_sel=NULL;
|
|
m_last_row_height=0;
|
|
m_scroll_x=m_scroll_y=m_capmode=0;
|
|
m_root.m_state = TVIS_EXPANDED;
|
|
m_root.m_haschildren=true;
|
|
}
|
|
~treeViewState()
|
|
{
|
|
}
|
|
bool findItem(HTREEITEM item, HTREEITEM *parOut, int *idxOut)
|
|
{
|
|
if (!m_root.FindItem(item,parOut,idxOut)) return false;
|
|
if (parOut && *parOut == &m_root) *parOut = NULL;
|
|
return true;
|
|
}
|
|
|
|
int navigateSel(HWND hwnd, int key, int pagesize) // returns 2 force invalidate, 1 if ate key
|
|
{
|
|
HTREEITEM par=NULL;
|
|
int idx=0,tmp=1;
|
|
|
|
switch (key)
|
|
{
|
|
case VK_LEFT:
|
|
if (m_sel && findItem(m_sel,&par,NULL))
|
|
{
|
|
if (m_sel->m_haschildren && (m_sel->m_state & TVIS_EXPANDED))
|
|
{
|
|
NMTREEVIEW nmhdr={{hwnd,(UINT_PTR)hwnd->m_id,TVN_ITEMEXPANDING},};
|
|
nmhdr.action = TVE_COLLAPSE;
|
|
nmhdr.itemNew.hItem=m_sel;
|
|
nmhdr.itemNew.lParam=m_sel->m_param;
|
|
if (!SendMessage(GetParent(hwnd),WM_NOTIFY,hwnd->m_id,(LPARAM)&nmhdr))
|
|
{
|
|
m_sel->m_state &= ~TVIS_EXPANDED;
|
|
return 2;
|
|
}
|
|
else
|
|
{
|
|
return 1;
|
|
}
|
|
}
|
|
if (par) m_sel=par;
|
|
}
|
|
return 1;
|
|
case VK_HOME:
|
|
m_sel=m_root.m_children.Get(0);
|
|
return 1;
|
|
case VK_END:
|
|
par = &m_root;
|
|
while (par && par->m_haschildren &&
|
|
par->m_children.GetSize() && (par->m_state & TVIS_EXPANDED))
|
|
par = par->m_children.Get(par->m_children.GetSize()-1);
|
|
if (par && par != &m_root) m_sel=par;
|
|
return 1;
|
|
case VK_RIGHT:
|
|
if (m_sel && findItem(m_sel,NULL,NULL) && m_sel->m_haschildren)
|
|
{
|
|
if (!(m_sel->m_state&TVIS_EXPANDED))
|
|
{
|
|
NMTREEVIEW nmhdr={{hwnd,(UINT_PTR)hwnd->m_id,TVN_ITEMEXPANDING},};
|
|
nmhdr.action = TVE_EXPAND;
|
|
nmhdr.itemNew.hItem=m_sel;
|
|
nmhdr.itemNew.lParam=m_sel->m_param;
|
|
if (!SendMessage(GetParent(hwnd),WM_NOTIFY,hwnd->m_id,(LPARAM)&nmhdr))
|
|
{
|
|
m_sel->m_state |= TVIS_EXPANDED;
|
|
return 2;
|
|
}
|
|
else
|
|
{
|
|
return 1;
|
|
}
|
|
}
|
|
par = m_sel->m_children.Get(0);
|
|
if (par) m_sel=par;
|
|
}
|
|
return 1;
|
|
case VK_PRIOR:
|
|
tmp = wdl_max(pagesize,2) - 1;
|
|
case VK_UP:
|
|
while (tmp-- > 0)
|
|
{
|
|
if (m_sel && findItem(m_sel,&par,&idx))
|
|
{
|
|
if (idx>0)
|
|
{
|
|
par = (par?par:&m_root)->m_children.Get(idx-1);
|
|
while (par && (par->m_state & TVIS_EXPANDED) &&
|
|
par->m_haschildren && par->m_children.GetSize())
|
|
par = par->m_children.Get(par->m_children.GetSize()-1);
|
|
}
|
|
if (par) m_sel=par;
|
|
}
|
|
}
|
|
return 1;
|
|
case VK_NEXT:
|
|
tmp = wdl_max(pagesize,2) - 1;
|
|
case VK_DOWN:
|
|
while (tmp-- > 0)
|
|
{
|
|
if (m_sel && findItem(m_sel,&par,&idx))
|
|
{
|
|
if (m_sel->m_haschildren &&
|
|
m_sel->m_children.GetSize() &&
|
|
(m_sel->m_state & TVIS_EXPANDED))
|
|
{
|
|
par = m_sel->m_children.Get(0);
|
|
if (par) m_sel=par;
|
|
continue;
|
|
}
|
|
|
|
next_item_in_parent:
|
|
if (par)
|
|
{
|
|
if (idx+1 < par->m_children.GetSize())
|
|
{
|
|
par = par->m_children.Get(idx+1);
|
|
if (par) m_sel=par;
|
|
}
|
|
else if (findItem(par,&par,&idx)) goto next_item_in_parent;
|
|
}
|
|
else
|
|
{
|
|
par = m_root.m_children.Get(idx+1);
|
|
if (par) m_sel=par;
|
|
}
|
|
}
|
|
}
|
|
return 1;
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
void doDrawItem(HTREEITEM item, HDC hdc, RECT *rect, bool focus) // draws any subitems too, updates rect->top
|
|
{
|
|
#ifdef SWELL_LICE_GDI
|
|
if (!item) return;
|
|
|
|
if (item != &m_root)
|
|
{
|
|
const int ob = rect->bottom;
|
|
rect->bottom = rect->top + m_last_row_height;
|
|
if (rect->right > rect->left)
|
|
{
|
|
int oc=0;
|
|
if (item == m_sel)
|
|
{
|
|
SetBkMode(hdc,OPAQUE);
|
|
SetBkColor(hdc,focus ? g_swell_ctheme.treeview_bg_sel : g_swell_ctheme.treeview_bg_sel_inactive);
|
|
oc = GetTextColor(hdc);
|
|
SetTextColor(hdc,focus ? g_swell_ctheme.treeview_text_sel : g_swell_ctheme.treeview_text_sel_inactive);
|
|
}
|
|
|
|
RECT dr = *rect;
|
|
const int sz = m_last_row_height/4;
|
|
if (item->m_haschildren)
|
|
{
|
|
bool exp = (item->m_state&TVIS_EXPANDED);
|
|
POINT pts[3];
|
|
if (exp)
|
|
{
|
|
const int yo = dr.top + sz+sz/2,xo=dr.left+1;
|
|
pts[0].x=xo; pts[0].y=yo;
|
|
pts[1].x=xo+sz*2; pts[1].y=yo;
|
|
pts[2].x=xo+sz; pts[2].y=yo+sz;
|
|
}
|
|
else
|
|
{
|
|
const int yo = dr.top + sz, xo = dr.left+sz*3/4+1;
|
|
pts[0].x=xo; pts[0].y=yo;
|
|
pts[1].x=xo+sz; pts[1].y=yo+sz;
|
|
pts[2].x=xo; pts[2].y=yo+sz*2;
|
|
}
|
|
Polygon(hdc,pts,3);
|
|
}
|
|
dr.left += sz*2+3;
|
|
|
|
DrawText(hdc,item->m_value ? item->m_value : "",-1,&dr,DT_LEFT|DT_VCENTER|DT_SINGLELINE|DT_NOPREFIX);
|
|
if (item == m_sel)
|
|
{
|
|
SetBkMode(hdc,TRANSPARENT);
|
|
SetTextColor(hdc,oc);
|
|
}
|
|
}
|
|
rect->top = rect->bottom;
|
|
rect->bottom = ob;
|
|
}
|
|
|
|
if ((item->m_state & TVIS_EXPANDED) && item->m_haschildren && item->m_children.GetSize())
|
|
{
|
|
const int n = item->m_children.GetSize();
|
|
rect->left += m_last_row_height;
|
|
for (int x=0;x<n && rect->top < rect->bottom;x++)
|
|
{
|
|
doDrawItem(item->m_children.Get(x),hdc,rect,focus);
|
|
}
|
|
rect->left -= m_last_row_height;
|
|
}
|
|
#endif
|
|
}
|
|
HTREEITEM hitTestItem(HTREEITEM item, int *y, int *xo)
|
|
{
|
|
*y -= m_last_row_height;
|
|
if (*y < 0) return item;
|
|
if ((item->m_state & TVIS_EXPANDED) && item->m_haschildren && item->m_children.GetSize())
|
|
{
|
|
int x;
|
|
const int n = item->m_children.GetSize();
|
|
for (x=0;x<n;x++)
|
|
{
|
|
HTREEITEM t=hitTestItem(item->m_children.Get(x),y,xo);
|
|
if (t)
|
|
{
|
|
if (xo) *xo += m_last_row_height;
|
|
return t;
|
|
}
|
|
}
|
|
}
|
|
return NULL;
|
|
}
|
|
int CalculateItemHeight(HTREEITEM__ *item, HTREEITEM stopAt, bool *done)
|
|
{
|
|
int h = m_last_row_height;
|
|
if (item == stopAt) { *done=true; return 0; }
|
|
|
|
if ((item->m_state & TVIS_EXPANDED) &&
|
|
item->m_haschildren &&
|
|
item->m_children.GetSize())
|
|
{
|
|
const int n = item->m_children.GetSize();
|
|
for (int x=0;x<n;x++)
|
|
{
|
|
h += CalculateItemHeight(item->m_children.Get(x),stopAt,done);
|
|
if (*done) break;
|
|
}
|
|
}
|
|
return h;
|
|
}
|
|
|
|
int calculateContentsHeight(HTREEITEM item=NULL)
|
|
{
|
|
bool done=false;
|
|
const int rv = CalculateItemHeight(&m_root,item,&done);
|
|
if (item && !done) return 0;
|
|
return rv - m_last_row_height;
|
|
}
|
|
|
|
int sanitizeScroll(HWND h)
|
|
{
|
|
RECT r;
|
|
GetClientRect(h,&r);
|
|
if (m_last_row_height > 0)
|
|
{
|
|
const int vh = calculateContentsHeight();
|
|
if (m_scroll_y < 0 || vh <= r.bottom) m_scroll_y=0;
|
|
else if (m_scroll_y > vh - r.bottom) m_scroll_y = vh - r.bottom;
|
|
return vh;
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
void ensureItemVisible(HWND hwnd, HTREEITEM item)
|
|
{
|
|
if (m_last_row_height<1) return;
|
|
const int x = item ? calculateContentsHeight(item) : 0;
|
|
RECT r;
|
|
GetClientRect(hwnd,&r);
|
|
if (x < m_scroll_y) m_scroll_y = x;
|
|
else if (x+m_last_row_height > m_scroll_y+r.bottom)
|
|
m_scroll_y = x+m_last_row_height - r.bottom;
|
|
}
|
|
|
|
HTREEITEM__ m_root;
|
|
HTREEITEM m_sel;
|
|
int m_last_row_height;
|
|
|
|
int m_scroll_x,m_scroll_y;
|
|
|
|
int m_capmode; // HIWORD is 0 for normal (then LOWORD gets 1 set if drag began), 1 for scroll (LOWORD has ypos)
|
|
};
|
|
|
|
static LRESULT treeViewWindowProc(HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam)
|
|
{
|
|
static POINT s_clickpt;
|
|
treeViewState *tvs = (treeViewState *)hwnd->m_private_data;
|
|
switch (msg)
|
|
{
|
|
case WM_MOUSEWHEEL:
|
|
if ((GetAsyncKeyState(VK_CONTROL)&0x8000) || (GetAsyncKeyState(VK_MENU)&0x8000)) break; // pass modified mousewheel to parent
|
|
|
|
{
|
|
const int amt = ((short)HIWORD(wParam))/40;
|
|
if (amt && tvs)
|
|
{
|
|
const int oldscroll = tvs->m_scroll_y;
|
|
tvs->m_scroll_y -= amt*tvs->m_last_row_height;
|
|
tvs->sanitizeScroll(hwnd);
|
|
if (tvs->m_scroll_y != oldscroll)
|
|
InvalidateRect(hwnd,NULL,FALSE);
|
|
|
|
}
|
|
}
|
|
return 1;
|
|
case WM_KEYDOWN:
|
|
if (tvs && (lParam & FVIRTKEY))
|
|
{
|
|
HTREEITEM oldSel = tvs->m_sel;
|
|
RECT r;
|
|
GetClientRect(hwnd,&r);
|
|
int flag = tvs->navigateSel(hwnd,(int)wParam,tvs->m_last_row_height ? r.bottom / tvs->m_last_row_height : 4);
|
|
if (oldSel != tvs->m_sel)
|
|
{
|
|
if (tvs->m_sel) tvs->ensureItemVisible(hwnd,tvs->m_sel);
|
|
InvalidateRect(hwnd,NULL,FALSE);
|
|
NMTREEVIEW nm={{(HWND)hwnd,(UINT_PTR)hwnd->m_id,TVN_SELCHANGED},};
|
|
nm.itemNew.hItem = tvs->m_sel;
|
|
nm.itemNew.lParam = tvs->m_sel ? tvs->m_sel->m_param : 0;
|
|
SendMessage(GetParent(hwnd),WM_NOTIFY,nm.hdr.idFrom,(LPARAM)&nm);
|
|
}
|
|
else if (flag&2) InvalidateRect(hwnd,NULL,FALSE);
|
|
|
|
if (flag) return 0;
|
|
}
|
|
break;
|
|
case WM_LBUTTONDOWN:
|
|
SetFocus(hwnd);
|
|
SetCapture(hwnd);
|
|
if (tvs)
|
|
{
|
|
tvs->m_capmode=0;
|
|
s_clickpt.x = GET_X_LPARAM(lParam);
|
|
s_clickpt.y = GET_Y_LPARAM(lParam);
|
|
RECT cr;
|
|
GetClientRect(hwnd,&cr);
|
|
int total_h;
|
|
if (GET_X_LPARAM(lParam) >= cr.right-g_swell_ctheme.scrollbar_width &&
|
|
(total_h=tvs->sanitizeScroll(hwnd)) > cr.bottom)
|
|
{
|
|
int ypos = GET_Y_LPARAM(lParam);
|
|
int yp = ypos;
|
|
|
|
int thumbsz, thumbpos;
|
|
calcScroll(cr.bottom, total_h,tvs->m_scroll_y,&thumbsz,&thumbpos);
|
|
|
|
if (ypos < thumbpos) yp = thumbpos; // jump on first mouse move
|
|
else if (ypos > thumbpos+thumbsz) yp = thumbpos + thumbsz;
|
|
|
|
tvs->m_capmode = (1<<16) | (yp&0xffff);
|
|
if (ypos < thumbpos || ypos > thumbpos+thumbsz) goto forceMouseMove;
|
|
return 0;
|
|
}
|
|
|
|
if (tvs->m_last_row_height)
|
|
{
|
|
int y = GET_Y_LPARAM(lParam) + tvs->m_scroll_y + tvs->m_last_row_height;
|
|
int xo=-tvs->m_last_row_height;
|
|
HTREEITEM hit = tvs->hitTestItem(&tvs->m_root,&y,&xo);
|
|
if (hit && GET_X_LPARAM(lParam) >= xo)
|
|
{
|
|
if (hit->m_haschildren)
|
|
{
|
|
if (GET_X_LPARAM(lParam) < xo + (tvs->m_last_row_height/4)*2+3)
|
|
{
|
|
NMTREEVIEW nmhdr={{hwnd,(UINT_PTR)hwnd->m_id,TVN_ITEMEXPANDING},};
|
|
nmhdr.action = (hit->m_state&TVIS_EXPANDED)!=TVIS_EXPANDED ? TVE_EXPAND : TVE_COLLAPSE;
|
|
nmhdr.itemNew.hItem=hit;
|
|
nmhdr.itemNew.lParam=hit->m_param;
|
|
if (!SendMessage(GetParent(hwnd),WM_NOTIFY,hwnd->m_id,(LPARAM)&nmhdr))
|
|
{
|
|
hit->m_state ^= TVIS_EXPANDED;
|
|
InvalidateRect(hwnd,NULL,FALSE);
|
|
}
|
|
return 0;
|
|
}
|
|
}
|
|
if (tvs->m_sel != hit)
|
|
{
|
|
tvs->m_sel = hit;
|
|
InvalidateRect(hwnd,NULL,FALSE);
|
|
NMTREEVIEW nm={{(HWND)hwnd,(UINT_PTR)hwnd->m_id,TVN_SELCHANGED},};
|
|
nm.itemNew.hItem = hit;
|
|
nm.itemNew.lParam = hit ? hit->m_param : 0;
|
|
SendMessage(GetParent(hwnd),WM_NOTIFY,nm.hdr.idFrom,(LPARAM)&nm);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
return 0;
|
|
case WM_MOUSEMOVE:
|
|
if (GetCapture()==hwnd && tvs)
|
|
{
|
|
forceMouseMove:
|
|
switch (HIWORD(tvs->m_capmode))
|
|
{
|
|
case 0:
|
|
if (!(tvs->m_capmode & 1))
|
|
{
|
|
const int dx = GET_X_LPARAM(lParam) - s_clickpt.x, dy = GET_Y_LPARAM(lParam) - s_clickpt.y;
|
|
if (dx*dx+dy*dy > 32)
|
|
{
|
|
tvs->m_capmode|=1;
|
|
HTREEITEM item = TreeView_GetSelection(hwnd);
|
|
NMTREEVIEW nm={{(HWND)hwnd,(UINT_PTR)hwnd->m_id,TVN_BEGINDRAG},};
|
|
nm.itemNew.hItem = item;
|
|
nm.itemNew.lParam = item ? item->m_param : 0;
|
|
SendMessage(GetParent(hwnd),WM_NOTIFY,nm.hdr.idFrom,(LPARAM)&nm);
|
|
}
|
|
}
|
|
break;
|
|
case 1:
|
|
{
|
|
int yv = (short)LOWORD(tvs->m_capmode);
|
|
int amt = GET_Y_LPARAM(lParam) - yv;
|
|
|
|
if (amt)
|
|
{
|
|
RECT r;
|
|
GetClientRect(hwnd,&r);
|
|
|
|
const int viewsz = r.bottom;
|
|
const double totalsz=tvs->calculateContentsHeight();
|
|
amt = (int)floor(amt * totalsz / (double)viewsz + 0.5);
|
|
|
|
const int oldscroll = tvs->m_scroll_y;
|
|
tvs->m_scroll_y += amt;
|
|
tvs->sanitizeScroll(hwnd);
|
|
if (tvs->m_scroll_y != oldscroll)
|
|
{
|
|
tvs->m_capmode = (GET_Y_LPARAM(lParam)&0xffff) | (1<<16);
|
|
InvalidateRect(hwnd,NULL,FALSE);
|
|
}
|
|
}
|
|
}
|
|
break;
|
|
}
|
|
}
|
|
return 1;
|
|
case WM_LBUTTONUP:
|
|
if (GetCapture() == hwnd)
|
|
{
|
|
ReleaseCapture();
|
|
}
|
|
return 1;
|
|
case WM_RBUTTONUP:
|
|
if (tvs && tvs->m_last_row_height>0)
|
|
{
|
|
NMLISTVIEW nm={{hwnd,hwnd->m_id,NM_RCLICK},0,0,0,};
|
|
if (SendMessage(GetParent(hwnd),WM_NOTIFY,hwnd->m_id,(LPARAM)&nm))
|
|
return 1;
|
|
}
|
|
break;
|
|
case WM_PAINT:
|
|
{
|
|
PAINTSTRUCT ps;
|
|
if (BeginPaint(hwnd,&ps))
|
|
{
|
|
RECT r;
|
|
GetClientRect(hwnd,&r);
|
|
{
|
|
HBRUSH br = CreateSolidBrush(g_swell_ctheme.treeview_bg);
|
|
FillRect(ps.hdc,&r,br);
|
|
DeleteObject(br);
|
|
}
|
|
if (tvs)
|
|
{
|
|
RECT cr=r;
|
|
SetTextColor(ps.hdc,g_swell_ctheme.treeview_text);
|
|
|
|
const int lrh = tvs->m_last_row_height;
|
|
TEXTMETRIC tm;
|
|
GetTextMetrics(ps.hdc,&tm);
|
|
const int row_height = tm.tmHeight;
|
|
tvs->m_last_row_height = row_height;
|
|
const int total_h = tvs->sanitizeScroll(hwnd);
|
|
if (!lrh && tvs->m_sel)
|
|
tvs->ensureItemVisible(hwnd,tvs->m_sel);
|
|
|
|
SetBkMode(ps.hdc,TRANSPARENT);
|
|
|
|
r.top -= tvs->m_scroll_y;
|
|
|
|
HBRUSH br = CreateSolidBrush(g_swell_ctheme.treeview_arrow);
|
|
HGDIOBJ oldpen = SelectObject(ps.hdc,GetStockObject(NULL_PEN));
|
|
HGDIOBJ oldbr = SelectObject(ps.hdc,br);
|
|
|
|
r.left -= tvs->m_last_row_height;
|
|
tvs->doDrawItem(&tvs->m_root,ps.hdc,&r, GetFocus()==hwnd);
|
|
|
|
SelectObject(ps.hdc,oldbr);
|
|
SelectObject(ps.hdc,oldpen);
|
|
DeleteObject(br);
|
|
|
|
drawVerticalScrollbar(ps.hdc,cr,total_h,tvs->m_scroll_y);
|
|
Draw3DBox(ps.hdc,&cr,-1,
|
|
g_swell_ctheme.treeview_hilight,
|
|
g_swell_ctheme.treeview_shadow);
|
|
}
|
|
|
|
EndPaint(hwnd,&ps);
|
|
}
|
|
}
|
|
return 0;
|
|
case WM_NCDESTROY:
|
|
hwnd->m_private_data = 0;
|
|
delete tvs;
|
|
break;
|
|
case WM_SETFOCUS:
|
|
case WM_KILLFOCUS:
|
|
InvalidateRect(hwnd,NULL,FALSE);
|
|
break;
|
|
}
|
|
return DefWindowProc(hwnd,msg,wParam,lParam);
|
|
}
|
|
|
|
struct tabControlState
|
|
{
|
|
tabControlState() { m_curtab=0; }
|
|
~tabControlState() { m_tabs.Empty(true,free); }
|
|
int m_curtab;
|
|
WDL_PtrList<char> m_tabs;
|
|
};
|
|
|
|
#define TABCONTROL_HEIGHT SWELL_UI_SCALE(20)
|
|
|
|
static LRESULT tabControlWindowProc(HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam)
|
|
{
|
|
const int xdiv = 6,xpad=4;
|
|
|
|
tabControlState *s = (tabControlState *)hwnd->m_private_data;
|
|
switch (msg)
|
|
{
|
|
case WM_NCDESTROY:
|
|
hwnd->m_private_data = 0;
|
|
delete s;
|
|
break;
|
|
case WM_TIMER:
|
|
if (wParam==1)
|
|
{
|
|
if (!fast_has_focus(hwnd))
|
|
{
|
|
KillTimer(hwnd,1);
|
|
InvalidateRect(hwnd,NULL,FALSE);
|
|
}
|
|
}
|
|
break;
|
|
case WM_LBUTTONUP:
|
|
if (GET_Y_LPARAM(lParam) < TABCONTROL_HEIGHT)
|
|
{
|
|
return 1;
|
|
}
|
|
break;
|
|
case WM_KEYDOWN:
|
|
if ((lParam&0xff)==FVIRTKEY &&
|
|
(wParam==VK_LEFT ||
|
|
wParam==VK_RIGHT ||
|
|
wParam==VK_HOME ||
|
|
wParam == VK_END))
|
|
{
|
|
int ct = s->m_curtab;
|
|
if (wParam==VK_LEFT)ct--;
|
|
else ct++;
|
|
if (ct>=s->m_tabs.GetSize()||wParam == VK_END) ct=s->m_tabs.GetSize()-1;
|
|
if (ct<0||wParam==VK_HOME) ct=0;
|
|
if (ct != s->m_curtab)
|
|
{
|
|
s->m_curtab = ct;
|
|
NMHDR nm={hwnd,(UINT_PTR)hwnd->m_id,TCN_SELCHANGE};
|
|
InvalidateRect(hwnd,NULL,FALSE);
|
|
SendMessage(GetParent(hwnd),WM_NOTIFY,nm.idFrom,(LPARAM)&nm);
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
break;
|
|
case WM_LBUTTONDOWN:
|
|
if (GET_Y_LPARAM(lParam) < TABCONTROL_HEIGHT)
|
|
{
|
|
SetFocus(hwnd);
|
|
int xp=GET_X_LPARAM(lParam),tab;
|
|
HDC dc = GetDC(hwnd);
|
|
int tabchg = -1;
|
|
for (tab = 0; tab < s->m_tabs.GetSize(); tab ++)
|
|
{
|
|
const char *buf = s->m_tabs.Get(tab);
|
|
RECT tr={0,};
|
|
DrawText(dc,buf,-1,&tr,DT_CALCRECT|DT_NOPREFIX|DT_SINGLELINE);
|
|
xp -= tr.right - tr.left + 2*SWELL_UI_SCALE(xpad) + SWELL_UI_SCALE(xdiv);
|
|
if (xp < 0)
|
|
{
|
|
if (s->m_curtab != tab)
|
|
{
|
|
tabchg = tab;
|
|
}
|
|
break;
|
|
}
|
|
}
|
|
if (tabchg >=0)
|
|
{
|
|
s->m_curtab = tabchg;
|
|
NMHDR nm={hwnd,(UINT_PTR)hwnd->m_id,TCN_SELCHANGE};
|
|
InvalidateRect(hwnd,NULL,FALSE);
|
|
SendMessage(GetParent(hwnd),WM_NOTIFY,nm.idFrom,(LPARAM)&nm);
|
|
}
|
|
else
|
|
InvalidateRect(hwnd,NULL,FALSE);
|
|
|
|
ReleaseDC(hwnd,dc);
|
|
return 1;
|
|
}
|
|
break;
|
|
case WM_PAINT:
|
|
{
|
|
PAINTSTRUCT ps;
|
|
if (BeginPaint(hwnd,&ps))
|
|
{
|
|
RECT r;
|
|
GetClientRect(hwnd,&r);
|
|
|
|
int tab;
|
|
int xp=0;
|
|
HPEN pen = CreatePen(PS_SOLID,0,g_swell_ctheme.tab_hilight);
|
|
HPEN pen2 = CreatePen(PS_SOLID,0,g_swell_ctheme.tab_shadow);
|
|
|
|
SetBkMode(ps.hdc,TRANSPARENT);
|
|
SetTextColor(ps.hdc,g_swell_ctheme.tab_text);
|
|
HGDIOBJ oldPen=SelectObject(ps.hdc,pen);
|
|
const int th = TABCONTROL_HEIGHT;
|
|
|
|
{
|
|
RECT bgr={0,0,r.right,th};
|
|
IntersectRect(&bgr,&bgr,&ps.rcPaint);
|
|
HBRUSH hbrush = (HBRUSH) SendMessage(hwnd,WM_CTLCOLORDLG,(WPARAM)ps.hdc,(LPARAM)hwnd);
|
|
if (hbrush && hbrush != (HBRUSH)1) FillRect(ps.hdc,&bgr,hbrush);
|
|
else SWELL_FillDialogBackground(ps.hdc,&bgr,0);
|
|
}
|
|
|
|
int lx=0;
|
|
RECT fr={0,};
|
|
for (tab = 0; tab < s->m_tabs.GetSize() && xp < r.right; tab ++)
|
|
{
|
|
const char *buf = s->m_tabs.Get(tab);
|
|
RECT tr={0,};
|
|
DrawText(ps.hdc,buf,-1,&tr,DT_CALCRECT|DT_NOPREFIX|DT_SINGLELINE);
|
|
int tw=tr.right-tr.left + 2*SWELL_UI_SCALE(xpad);
|
|
|
|
const int olx=lx;
|
|
lx=xp + tw+SWELL_UI_SCALE(xdiv);
|
|
|
|
MoveToEx(ps.hdc,xp,th-1,NULL);
|
|
LineTo(ps.hdc,xp,0);
|
|
LineTo(ps.hdc,xp+tw,0);
|
|
SelectObject(ps.hdc,pen2);
|
|
LineTo(ps.hdc,xp+tw,th-1);
|
|
|
|
if (tab==s->m_curtab)
|
|
{
|
|
fr.left=xp;
|
|
fr.right=xp+tw;
|
|
fr.top=0;
|
|
fr.bottom=th-2;
|
|
}
|
|
|
|
MoveToEx(ps.hdc, tab == s->m_curtab ? lx-SWELL_UI_SCALE(xdiv) : olx,th-1,NULL);
|
|
LineTo(ps.hdc,lx,th-1);
|
|
|
|
SelectObject(ps.hdc,pen);
|
|
|
|
tr.left = xp+SWELL_UI_SCALE(xpad);
|
|
tr.top=0;
|
|
tr.right = xp+tw-SWELL_UI_SCALE(xpad);
|
|
tr.bottom = th;
|
|
|
|
DrawText(ps.hdc,buf,-1,&tr,DT_LEFT|DT_VCENTER|DT_SINGLELINE|DT_NOPREFIX);
|
|
xp = lx;
|
|
}
|
|
if (draw_focus_indicator(hwnd,ps.hdc,&fr))
|
|
{
|
|
KillTimer(hwnd,1);
|
|
SetTimer(hwnd,1,100,NULL);
|
|
}
|
|
SelectObject(ps.hdc,pen2);
|
|
MoveToEx(ps.hdc,lx,th-1,NULL);
|
|
LineTo(ps.hdc,r.right,th-1);
|
|
|
|
SelectObject(ps.hdc,oldPen);
|
|
|
|
EndPaint(hwnd,&ps);
|
|
DeleteObject(pen);
|
|
DeleteObject(pen2);
|
|
}
|
|
}
|
|
return 0;
|
|
case WM_SETFOCUS:
|
|
case WM_KILLFOCUS:
|
|
InvalidateRect(hwnd,NULL,FALSE);
|
|
break;
|
|
}
|
|
return DefWindowProc(hwnd,msg,wParam,lParam);
|
|
}
|
|
|
|
|
|
|
|
HWND SWELL_MakeListBox(int idx, int x, int y, int w, int h, int styles)
|
|
{
|
|
RECT tr=MakeCoords(x,y,w,h,true);
|
|
HWND hwnd = new HWND__(m_make_owner,idx,&tr,NULL, !(styles&SWELL_NOT_WS_VISIBLE), listViewWindowProc);
|
|
hwnd->m_style = WS_CHILD | (styles & ~SWELL_NOT_WS_VISIBLE);
|
|
hwnd->m_classname = "ListBox";
|
|
hwnd->m_private_data = (INT_PTR) new listViewState(false, !!(styles & LBS_EXTENDEDSEL), true);
|
|
hwnd->m_wndproc(hwnd,WM_CREATE,0,0);
|
|
if (m_doautoright) UpdateAutoCoords(tr);
|
|
return hwnd;
|
|
}
|
|
|
|
typedef struct ccprocrec
|
|
{
|
|
SWELL_ControlCreatorProc proc;
|
|
int cnt;
|
|
struct ccprocrec *next;
|
|
} ccprocrec;
|
|
|
|
static ccprocrec *m_ccprocs;
|
|
|
|
void SWELL_RegisterCustomControlCreator(SWELL_ControlCreatorProc proc)
|
|
{
|
|
if (WDL_NOT_NORMALLY(!proc)) return;
|
|
|
|
ccprocrec *p=m_ccprocs;
|
|
if (p) for (;;)
|
|
{
|
|
if (p->proc == proc)
|
|
{
|
|
p->cnt++;
|
|
return;
|
|
}
|
|
if (!p->next) break;
|
|
p=p->next;
|
|
}
|
|
ccprocrec *ent = (ccprocrec*)malloc(sizeof(ccprocrec));
|
|
ent->proc=proc;
|
|
ent->cnt=1;
|
|
ent->next=NULL;
|
|
|
|
if (p) p->next=ent;
|
|
else m_ccprocs=ent;
|
|
}
|
|
|
|
void SWELL_UnregisterCustomControlCreator(SWELL_ControlCreatorProc proc)
|
|
{
|
|
if (WDL_NOT_NORMALLY(!proc)) return;
|
|
|
|
ccprocrec *lp=NULL;
|
|
ccprocrec *p=m_ccprocs;
|
|
while (p)
|
|
{
|
|
if (p->proc == proc)
|
|
{
|
|
if (--p->cnt <= 0)
|
|
{
|
|
if (lp) lp->next=p->next;
|
|
else m_ccprocs=p->next;
|
|
free(p);
|
|
}
|
|
return;
|
|
}
|
|
lp=p;
|
|
p=p->next;
|
|
}
|
|
WDL_ASSERT(false);
|
|
}
|
|
|
|
|
|
|
|
HWND SWELL_MakeControl(const char *cname, int idx, const char *classname, int style, int x, int y, int w, int h, int exstyle)
|
|
{
|
|
if (m_ccprocs)
|
|
{
|
|
RECT poo=MakeCoords(x,y,w,h,false);
|
|
ccprocrec *p=m_ccprocs;
|
|
while (p)
|
|
{
|
|
HWND hhh=p->proc((HWND)m_make_owner,cname,idx,classname,style,poo.left,poo.top,poo.right-poo.left,poo.bottom-poo.top);
|
|
if (hhh)
|
|
{
|
|
if (exstyle) SetWindowLong(hhh,GWL_EXSTYLE,exstyle);
|
|
return hhh;
|
|
}
|
|
p=p->next;
|
|
}
|
|
}
|
|
if (!stricmp(classname,"SysTabControl32"))
|
|
{
|
|
RECT tr=MakeCoords(x,y,w,h,false);
|
|
HWND hwnd = new HWND__(m_make_owner,idx,&tr,NULL, !(style&SWELL_NOT_WS_VISIBLE), tabControlWindowProc);
|
|
hwnd->m_style = WS_CHILD | (style & ~SWELL_NOT_WS_VISIBLE);
|
|
hwnd->m_classname = "SysTabControl32";
|
|
hwnd->m_private_data = (INT_PTR) new tabControlState;
|
|
hwnd->m_wndproc(hwnd,WM_CREATE,0,0);
|
|
SetWindowPos(hwnd,HWND_BOTTOM,0,0,0,0,SWP_NOMOVE|SWP_NOSIZE|SWP_NOACTIVATE);
|
|
return hwnd;
|
|
}
|
|
else if (!stricmp(classname, "SysListView32")||!stricmp(classname, "SysListView32_LB"))
|
|
{
|
|
RECT tr=MakeCoords(x,y,w,h,false);
|
|
HWND hwnd = new HWND__(m_make_owner,idx,&tr,NULL, !(style&SWELL_NOT_WS_VISIBLE), listViewWindowProc);
|
|
hwnd->m_style = WS_CHILD | (style & ~SWELL_NOT_WS_VISIBLE);
|
|
hwnd->m_classname = "SysListView32";
|
|
if (!stricmp(classname, "SysListView32"))
|
|
hwnd->m_private_data = (INT_PTR) new listViewState(!!(style & LVS_OWNERDATA), !(style & LVS_SINGLESEL), false);
|
|
else
|
|
hwnd->m_private_data = (INT_PTR) new listViewState(false,false, true);
|
|
|
|
hwnd->m_wndproc(hwnd,WM_CREATE,0,0);
|
|
return hwnd;
|
|
}
|
|
else if (!stricmp(classname, "SysTreeView32"))
|
|
{
|
|
RECT tr=MakeCoords(x,y,w,h,false);
|
|
HWND hwnd = new HWND__(m_make_owner,idx,&tr,NULL, !(style&SWELL_NOT_WS_VISIBLE), treeViewWindowProc);
|
|
hwnd->m_style = WS_CHILD | (style & ~SWELL_NOT_WS_VISIBLE);
|
|
hwnd->m_classname = "SysTreeView32";
|
|
hwnd->m_private_data = (INT_PTR) new treeViewState;
|
|
hwnd->m_wndproc(hwnd,WM_CREATE,0,0);
|
|
return hwnd;
|
|
}
|
|
else if (!stricmp(classname, "msctls_progress32"))
|
|
{
|
|
RECT tr=MakeCoords(x,y,w,h,false);
|
|
HWND hwnd = new HWND__(m_make_owner,idx,&tr,NULL, !(style&SWELL_NOT_WS_VISIBLE), progressWindowProc);
|
|
hwnd->m_wantfocus = false;
|
|
hwnd->m_style = WS_CHILD | (style & ~SWELL_NOT_WS_VISIBLE);
|
|
hwnd->m_classname = "msctls_progress32";
|
|
int *state = (int *)calloc(2,sizeof(int)); // pos, range
|
|
if (state) state[1] = 100<<16;
|
|
hwnd->m_private_data = (INT_PTR) state;
|
|
hwnd->m_wndproc(hwnd,WM_CREATE,0,0);
|
|
return hwnd;
|
|
}
|
|
else if (!stricmp(classname,"Edit"))
|
|
{
|
|
return SWELL_MakeEditField(idx,x,y,w,h,style);
|
|
}
|
|
else if (!stricmp(classname, "Static"))
|
|
{
|
|
RECT tr=MakeCoords(x,y,w,h,false);
|
|
HWND hwnd = new HWND__(m_make_owner,idx,&tr,cname, !(style&SWELL_NOT_WS_VISIBLE),labelWindowProc);
|
|
hwnd->m_wantfocus = false;
|
|
hwnd->m_style = WS_CHILD | (style & ~SWELL_NOT_WS_VISIBLE);
|
|
hwnd->m_classname = "Static";
|
|
hwnd->m_wndproc(hwnd,WM_CREATE,0,0);
|
|
if (m_doautoright) UpdateAutoCoords(tr);
|
|
return hwnd;
|
|
}
|
|
else if (!stricmp(classname,"Button"))
|
|
{
|
|
RECT tr=MakeCoords(x,y,w,h,true);
|
|
HWND hwnd = swell_makeButton(m_make_owner,idx,&tr,cname,!(style&SWELL_NOT_WS_VISIBLE),(style&~SWELL_NOT_WS_VISIBLE)|WS_CHILD);
|
|
if (m_doautoright) UpdateAutoCoords(tr);
|
|
return hwnd;
|
|
}
|
|
else if (!stricmp(classname,"REAPERhfader")||!stricmp(classname,"msctls_trackbar32"))
|
|
{
|
|
RECT tr=MakeCoords(x,y,w,h,true);
|
|
HWND hwnd = new HWND__(m_make_owner,idx,&tr,cname, !(style&SWELL_NOT_WS_VISIBLE),trackbarWindowProc);
|
|
hwnd->m_style = WS_CHILD | (style & ~SWELL_NOT_WS_VISIBLE);
|
|
hwnd->m_classname = !stricmp(classname,"REAPERhfader") ? "REAPERhfader" : "msctls_trackbar32";
|
|
hwnd->m_private_data = (INT_PTR) calloc(3,sizeof(int)); // pos, range, tic
|
|
hwnd->m_wndproc(hwnd,WM_CREATE,0,0);
|
|
return hwnd;
|
|
}
|
|
else if (!stricmp(classname,"COMBOBOX"))
|
|
{
|
|
return SWELL_MakeCombo(idx, x, y, w, h, style);
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
HWND SWELL_MakeCombo(int idx, int x, int y, int w, int h, int flags)
|
|
{
|
|
RECT tr=MakeCoords(x,y,w,h,true);
|
|
const int maxh = g_swell_ctheme.combo_height;
|
|
if (tr.bottom > tr.top + maxh) tr.bottom=tr.top+maxh;
|
|
HWND hwnd = new HWND__(m_make_owner,idx,&tr,NULL, !(flags&SWELL_NOT_WS_VISIBLE),comboWindowProc);
|
|
hwnd->m_private_data = (INT_PTR) new __SWELL_ComboBoxInternalState;
|
|
hwnd->m_style = (flags & ~SWELL_NOT_WS_VISIBLE)|WS_CHILD;
|
|
hwnd->m_classname = "combobox";
|
|
hwnd->m_wndproc(hwnd,WM_CREATE,0,0);
|
|
if (m_doautoright) UpdateAutoCoords(tr);
|
|
return hwnd;
|
|
}
|
|
|
|
HWND SWELL_MakeGroupBox(const char *name, int idx, int x, int y, int w, int h, int style)
|
|
{
|
|
RECT tr=MakeCoords(x,y,w,h,false);
|
|
HWND hwnd = new HWND__(m_make_owner,idx,&tr,name, !(style&SWELL_NOT_WS_VISIBLE),groupWindowProc);
|
|
hwnd->m_wantfocus = false;
|
|
hwnd->m_style = WS_CHILD | (style & ~SWELL_NOT_WS_VISIBLE) | BS_GROUPBOX;
|
|
hwnd->m_classname = "Button";
|
|
hwnd->m_wndproc(hwnd,WM_CREATE,0,0);
|
|
SetWindowPos(hwnd,HWND_BOTTOM,0,0,0,0,SWP_NOMOVE|SWP_NOSIZE|SWP_NOACTIVATE);
|
|
return hwnd;
|
|
}
|
|
|
|
|
|
int TabCtrl_GetItemCount(HWND hwnd)
|
|
{
|
|
tabControlState *s = hwnd ? (tabControlState*) hwnd->m_private_data : NULL;
|
|
return WDL_NORMALLY(s) ? s->m_tabs.GetSize() : 0;
|
|
}
|
|
|
|
BOOL TabCtrl_AdjustRect(HWND hwnd, BOOL fLarger, RECT *r)
|
|
{
|
|
if (WDL_NOT_NORMALLY(!r || !hwnd)) return FALSE;
|
|
|
|
r->top += TABCONTROL_HEIGHT;
|
|
|
|
return TRUE;
|
|
}
|
|
|
|
|
|
BOOL TabCtrl_DeleteItem(HWND hwnd, int idx)
|
|
{
|
|
tabControlState *s = hwnd ? (tabControlState*) hwnd->m_private_data : NULL;
|
|
if (WDL_NOT_NORMALLY(!s || !s->m_tabs.Get(idx))) return FALSE;
|
|
|
|
s->m_tabs.Delete(idx,true);
|
|
if (s->m_curtab>0) s->m_curtab--;
|
|
InvalidateRect(hwnd,NULL,FALSE);
|
|
// todo: send notification?
|
|
|
|
return TRUE;
|
|
}
|
|
|
|
int TabCtrl_InsertItem(HWND hwnd, int idx, TCITEM *item)
|
|
{
|
|
tabControlState *s = WDL_NORMALLY(hwnd) ? (tabControlState*) hwnd->m_private_data : NULL;
|
|
if (WDL_NOT_NORMALLY(!item || !s)) return -1;
|
|
if (WDL_NOT_NORMALLY(!(item->mask & TCIF_TEXT) || !item->pszText)) return -1;
|
|
|
|
s->m_tabs.Insert(idx, strdup(item->pszText));
|
|
|
|
InvalidateRect(hwnd,NULL,FALSE);
|
|
// todo: send notification if s->m_tabs.GetSize()==1 ?
|
|
|
|
return TRUE;
|
|
}
|
|
|
|
int TabCtrl_SetCurSel(HWND hwnd, int idx)
|
|
{
|
|
tabControlState *s = WDL_NORMALLY(hwnd) ? (tabControlState*) hwnd->m_private_data : NULL;
|
|
if (!s || !s->m_tabs.Get(idx)) return -1;
|
|
const int lt =s->m_curtab;
|
|
s->m_curtab = idx;
|
|
InvalidateRect(hwnd,NULL,FALSE);
|
|
|
|
return lt;
|
|
}
|
|
|
|
int TabCtrl_GetCurSel(HWND hwnd)
|
|
{
|
|
tabControlState *s = WDL_NORMALLY(hwnd) ? (tabControlState*) hwnd->m_private_data : NULL;
|
|
return s ? s->m_curtab : -1;
|
|
}
|
|
|
|
void ListView_SetExtendedListViewStyleEx(HWND h, int flag, int mask)
|
|
{
|
|
listViewState *lvs = h ? (listViewState *)h->m_private_data : NULL;
|
|
if (WDL_NOT_NORMALLY(!lvs)) return;
|
|
lvs->m_extended_style = (lvs->m_extended_style & ~mask) | (flag&mask);
|
|
}
|
|
|
|
void SWELL_SetListViewFastClickMask(HWND hList, int mask)
|
|
{
|
|
listViewState *lvs = hList ? (listViewState *)hList->m_private_data : NULL;
|
|
if (WDL_NOT_NORMALLY(!lvs)) return;
|
|
lvs->m_fastclick_mask = mask;
|
|
}
|
|
|
|
void ListView_SetImageList(HWND h, HIMAGELIST imagelist, int which)
|
|
{
|
|
listViewState *lvs = h ? (listViewState *)h->m_private_data : NULL;
|
|
if (WDL_NOT_NORMALLY(!lvs)) return;
|
|
lvs->m_status_imagelist= (WDL_PtrList<HGDIOBJ__> *)imagelist;
|
|
lvs->m_status_imagelist_type = which;
|
|
}
|
|
|
|
int ListView_GetColumnWidth(HWND h, int pos)
|
|
{
|
|
listViewState *lvs = h ? (listViewState *)h->m_private_data : NULL;
|
|
if (WDL_NOT_NORMALLY(!lvs)) return 0;
|
|
SWELL_ListView_Col *c = lvs->GetColumnByIndex(pos);
|
|
return WDL_NORMALLY(c) ? c->xwid : 0;
|
|
}
|
|
|
|
void ListView_InsertColumn(HWND h, int pos, const LVCOLUMN *lvc)
|
|
{
|
|
listViewState *lvs = h ? (listViewState *)h->m_private_data : NULL;
|
|
if (WDL_NOT_NORMALLY(!lvs || !lvc)) return;
|
|
SWELL_ListView_Col col = { 0, 100 };
|
|
if (lvc->mask & LVCF_WIDTH) col.xwid = lvc->cx;
|
|
if (lvc->mask & LVCF_TEXT) col.name = lvc->pszText ? strdup(lvc->pszText) : NULL;
|
|
if (lvc->mask & LVCF_FMT) col.fmt = lvc->fmt;
|
|
|
|
for (int x = 0; x < lvs->m_cols.GetSize(); x++)
|
|
if (lvs->m_cols.Get()[x].col_index>=pos)
|
|
lvs->m_cols.Get()[x].col_index++;
|
|
|
|
if (pos<0)pos=0;
|
|
else if (pos>lvs->m_cols.GetSize()) pos=lvs->m_cols.GetSize();
|
|
col.col_index = pos;
|
|
|
|
lvs->m_cols.Insert(col,pos);
|
|
}
|
|
|
|
void ListView_GetColumn(HWND h, int pos, LVCOLUMN *lvc)
|
|
{
|
|
listViewState *lvs = h ? (listViewState *)h->m_private_data : NULL;
|
|
if (WDL_NOT_NORMALLY(!lvs || !lvc)) return;
|
|
SWELL_ListView_Col *col = lvs->GetColumnByIndex(pos);
|
|
if (WDL_NOT_NORMALLY(!col)) return;
|
|
if (lvc->mask & LVCF_WIDTH) lvc->cx = col->xwid;
|
|
if (lvc->mask & LVCF_TEXT)
|
|
{
|
|
if (WDL_NORMALLY(lvc->pszText && lvc->cchTextMax>0))
|
|
lstrcpyn_safe(lvc->pszText, col->name?col->name:"", lvc->cchTextMax);
|
|
}
|
|
if (lvc->mask & LVCF_FMT) lvc->fmt = col->fmt;
|
|
}
|
|
|
|
void ListView_SetColumn(HWND h, int pos, const LVCOLUMN *lvc)
|
|
{
|
|
listViewState *lvs = h ? (listViewState *)h->m_private_data : NULL;
|
|
if (WDL_NOT_NORMALLY(!lvs || !lvc)) return;
|
|
SWELL_ListView_Col *col = lvs->GetColumnByIndex(pos);
|
|
if (WDL_NOT_NORMALLY(!col)) return;
|
|
if (lvc->mask & LVCF_WIDTH) col->xwid = lvc->cx;
|
|
if (lvc->mask & LVCF_TEXT)
|
|
{
|
|
free(col->name);
|
|
col->name = lvc->pszText ? strdup(lvc->pszText) : NULL;
|
|
}
|
|
if (lvc->mask & LVCF_FMT) col->fmt = lvc->fmt;
|
|
}
|
|
|
|
void ListView_GetItemText(HWND hwnd, int item, int subitem, char *text, int textmax)
|
|
{
|
|
LVITEM it={LVIF_TEXT,item,subitem,0,0,text,textmax,};
|
|
ListView_GetItem(hwnd,&it);
|
|
}
|
|
|
|
int ListView_InsertItem(HWND h, const LVITEM *item)
|
|
{
|
|
listViewState *lvs = h ? (listViewState *)h->m_private_data : NULL;
|
|
if (WDL_NOT_NORMALLY(!lvs || lvs->IsOwnerData() || !item || item->iSubItem)) return 0;
|
|
|
|
int idx = (int) item->iItem;
|
|
if (idx<0 || idx>lvs->m_data.GetSize()) idx=lvs->m_data.GetSize();
|
|
SWELL_ListView_Row *row=new SWELL_ListView_Row;
|
|
row->add_col((item->mask&LVIF_TEXT) ? item->pszText : NULL);
|
|
row->m_param = (item->mask&LVIF_PARAM) ? item->lParam : 0;
|
|
row->m_tmp = ((item->mask & LVIF_STATE) && (item->state & LVIS_SELECTED)) ? 1:0;
|
|
lvs->m_data.Insert(idx,row);
|
|
if (item->mask&LVIF_STATE)
|
|
{
|
|
if (item->stateMask & LVIS_STATEIMAGEMASK) row->set_img_idx(0,STATEIMAGEMASKTOINDEX(item->state));
|
|
if (item->stateMask & LVIS_SELECTED) lvs->set_sel(idx,!!(item->state&LVIS_SELECTED));
|
|
}
|
|
InvalidateRect(h,NULL,FALSE);
|
|
return idx;
|
|
}
|
|
|
|
void ListView_SetItemText(HWND h, int ipos, int cpos, const char *txt)
|
|
{
|
|
listViewState *lvs = h ? (listViewState *)h->m_private_data : NULL;
|
|
if (WDL_NOT_NORMALLY(!lvs || lvs->IsOwnerData() || cpos < 0)) return;
|
|
const int ncol = wdl_max(lvs->m_cols.GetSize(),1);
|
|
if (WDL_NOT_NORMALLY(cpos >= ncol)) return;
|
|
|
|
SWELL_ListView_Row *row=lvs->m_data.Get(ipos);
|
|
if (!row) return;
|
|
while (row->get_num_cols()<=cpos) row->add_col(NULL);
|
|
row->set_col_txt(cpos,txt);
|
|
InvalidateRect(h,NULL,FALSE);
|
|
}
|
|
|
|
int ListView_GetNextItem(HWND h, int istart, int flags)
|
|
{
|
|
listViewState *lvs = h ? (listViewState *)h->m_private_data : NULL;
|
|
if (WDL_NOT_NORMALLY(!lvs)) return -1;
|
|
const int n = lvs->GetNumItems();
|
|
for (int x = wdl_max(0,istart+1); x < n; x ++)
|
|
{
|
|
if (flags&LVNI_SELECTED) if (lvs->get_sel(x)) return x;
|
|
if (flags&LVNI_FOCUSED) if (lvs->m_selitem==x) return x;
|
|
}
|
|
return -1;
|
|
}
|
|
|
|
bool ListView_SetItem(HWND h, LVITEM *item)
|
|
{
|
|
listViewState *lvs = h ? (listViewState *)h->m_private_data : NULL;
|
|
if (WDL_NOT_NORMALLY(!lvs || !item)) return false;
|
|
|
|
const bool ownerData = lvs->IsOwnerData();
|
|
if (!ownerData)
|
|
{
|
|
SWELL_ListView_Row *row=lvs->m_data.Get(item->iItem);
|
|
if (!row) return false;
|
|
|
|
const int ncol = wdl_max(lvs->m_cols.GetSize(),1);
|
|
if (item->iSubItem >= 0 && item->iSubItem < ncol)
|
|
{
|
|
while (row->get_num_cols()<=item->iSubItem) row->add_col(NULL);
|
|
if (item->mask&LVIF_TEXT)
|
|
{
|
|
row->set_col_txt(item->iSubItem,item->pszText);
|
|
}
|
|
}
|
|
if (item->mask & LVIF_PARAM)
|
|
{
|
|
row->m_param = item->lParam;
|
|
}
|
|
if (item->mask&LVIF_IMAGE)
|
|
{
|
|
row->set_img_idx(item->iSubItem,item->iImage+1);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
if (item->iItem < 0 || item->iItem >= lvs->GetNumItems()) return false;
|
|
}
|
|
if (item->mask & LVIF_STATE)
|
|
{
|
|
ListView_SetItemState(h,item->iItem,item->state,item->stateMask);
|
|
}
|
|
|
|
InvalidateRect(h,NULL,FALSE);
|
|
|
|
return true;
|
|
}
|
|
|
|
bool ListView_GetItem(HWND h, LVITEM *item)
|
|
{
|
|
listViewState *lvs = h ? (listViewState *)h->m_private_data : NULL;
|
|
if (WDL_NOT_NORMALLY(!lvs || !item)) return false;
|
|
if (!lvs->IsOwnerData())
|
|
{
|
|
SWELL_ListView_Row *row=lvs->m_data.Get(item->iItem);
|
|
if (!row) return false;
|
|
if ((item->mask&LVIF_TEXT)&&item->pszText && item->cchTextMax > 0)
|
|
{
|
|
const char *v=row->get_col_txt(item->iSubItem);
|
|
lstrcpyn_safe(item->pszText, v?v:"",item->cchTextMax);
|
|
}
|
|
if (item->mask & LVIF_PARAM) item->lParam = row->m_param;
|
|
}
|
|
else
|
|
{
|
|
if (item->iItem < 0 || item->iItem >= lvs->GetNumItems()) return false;
|
|
|
|
int mask = item->mask & (LVIF_PARAM|LVIF_TEXT);
|
|
if (mask & LVIF_TEXT)
|
|
{
|
|
if (!item->pszText || item->cchTextMax < 1) mask &= ~LVIF_TEXT;
|
|
else *item->pszText = 0;
|
|
}
|
|
if (mask)
|
|
{
|
|
NMLVDISPINFO nm={{h, (UINT_PTR)GetWindowLong(h,GWL_ID), LVN_GETDISPINFO}};
|
|
nm.item.mask = mask;
|
|
nm.item.iItem = item->iItem;
|
|
nm.item.iSubItem = item->iSubItem;
|
|
nm.item.pszText = item->pszText;
|
|
nm.item.cchTextMax = item->cchTextMax;
|
|
SendMessage(GetParent(h),WM_NOTIFY,nm.hdr.idFrom,(LPARAM)&nm);
|
|
if ((mask & LVIF_TEXT) && nm.item.pszText != item->pszText)
|
|
lstrcpyn_safe(item->pszText, nm.item.pszText ? nm.item.pszText : "", item->cchTextMax);
|
|
if (mask & LVIF_PARAM) item->lParam = nm.item.lParam;
|
|
}
|
|
}
|
|
|
|
if (item->mask & LVIF_STATE)
|
|
{
|
|
item->state = 0;
|
|
if ((item->stateMask & LVIS_SELECTED) && lvs->get_sel(item->iItem)) item->state |= LVIS_SELECTED;
|
|
if ((item->stateMask & LVIS_FOCUSED) && lvs->m_selitem == item->iItem) item->state |= LVIS_FOCUSED;
|
|
if (item->stateMask & 0xff0000)
|
|
{
|
|
SWELL_ListView_Row *row = lvs->m_data.Get(item->iItem);
|
|
if (row)
|
|
item->state |= INDEXTOSTATEIMAGEMASK(row->get_img_idx(0));
|
|
}
|
|
}
|
|
|
|
return true;
|
|
}
|
|
int ListView_GetItemState(HWND h, int ipos, UINT mask)
|
|
{
|
|
listViewState *lvs = h ? (listViewState *)h->m_private_data : NULL;
|
|
if (WDL_NOT_NORMALLY(!lvs)) return 0;
|
|
int ret = 0;
|
|
if (mask & LVIS_SELECTED) ret |= (lvs->get_sel(ipos) ? LVIS_SELECTED : 0 );
|
|
if ((mask & LVIS_FOCUSED) && lvs->m_selitem == ipos) ret |= LVIS_FOCUSED;
|
|
if ((mask & LVIS_STATEIMAGEMASK) && lvs->m_status_imagelist_type == LVSIL_STATE)
|
|
{
|
|
SWELL_ListView_Row *row = lvs->m_data.Get(ipos);
|
|
if (row)
|
|
ret |= INDEXTOSTATEIMAGEMASK(row->get_img_idx(0));
|
|
}
|
|
return ret;
|
|
}
|
|
|
|
bool ListView_SetItemState(HWND h, int ipos, UINT state, UINT statemask)
|
|
{
|
|
listViewState *lvs = h ? (listViewState *)h->m_private_data : NULL;
|
|
if (WDL_NOT_NORMALLY(!lvs)) return false;
|
|
|
|
static int _is_doing_all;
|
|
|
|
if (ipos == -1)
|
|
{
|
|
int x;
|
|
int n=ListView_GetItemCount(h);
|
|
_is_doing_all++;
|
|
if ((statemask & LVIS_SELECTED) && (state & LVIS_SELECTED) && !lvs->m_is_multisel)
|
|
statemask &= ~LVIS_SELECTED;
|
|
for (x = 0; x < n; x ++)
|
|
ListView_SetItemState(h,x,state,statemask);
|
|
_is_doing_all--;
|
|
ListView_RedrawItems(h,0,n-1);
|
|
return true;
|
|
}
|
|
bool changed=false;
|
|
|
|
if (statemask & LVIS_SELECTED) changed |= lvs->set_sel(ipos,!!(state&LVIS_SELECTED));
|
|
if (statemask & LVIS_FOCUSED)
|
|
{
|
|
if (state&LVIS_FOCUSED)
|
|
{
|
|
if (lvs->m_selitem != ipos)
|
|
{
|
|
changed=true;
|
|
lvs->m_selitem = ipos;
|
|
}
|
|
}
|
|
}
|
|
if ((statemask & LVIS_STATEIMAGEMASK) && lvs->m_status_imagelist_type == LVSIL_STATE)
|
|
{
|
|
SWELL_ListView_Row *row = lvs->m_data.Get(ipos);
|
|
if (row)
|
|
{
|
|
const int idx= row->get_img_idx(0);
|
|
row->set_img_idx(0,STATEIMAGEMASKTOINDEX(state));
|
|
if (!changed && idx != row->get_img_idx(0)) ListView_RedrawItems(h,ipos,ipos);
|
|
}
|
|
}
|
|
|
|
if (changed)
|
|
{
|
|
static int __rent;
|
|
if (!__rent)
|
|
{
|
|
__rent++;
|
|
NMLISTVIEW nm={{(HWND)h,(unsigned short)h->m_id,LVN_ITEMCHANGED},ipos,0,state,};
|
|
SendMessage(GetParent(h),WM_NOTIFY,h->m_id,(LPARAM)&nm);
|
|
__rent--;
|
|
}
|
|
if (!_is_doing_all) ListView_RedrawItems(h,ipos,ipos);
|
|
}
|
|
return true;
|
|
}
|
|
void ListView_RedrawItems(HWND h, int startitem, int enditem)
|
|
{
|
|
if (WDL_NOT_NORMALLY(!h)) return;
|
|
InvalidateRect(h,NULL,FALSE);
|
|
}
|
|
|
|
void ListView_DeleteItem(HWND h, int ipos)
|
|
{
|
|
listViewState *lvs = h ? (listViewState *)h->m_private_data : NULL;
|
|
if (WDL_NOT_NORMALLY(!lvs || lvs->IsOwnerData())) return;
|
|
lvs->m_data.Delete(ipos,true);
|
|
InvalidateRect(h,NULL,FALSE);
|
|
}
|
|
|
|
void ListView_DeleteAllItems(HWND h)
|
|
{
|
|
listViewState *lvs = h ? (listViewState *)h->m_private_data : NULL;
|
|
if (WDL_NOT_NORMALLY(!lvs || lvs->IsOwnerData())) return;
|
|
lvs->m_data.Empty(true);
|
|
InvalidateRect(h,NULL,FALSE);
|
|
}
|
|
|
|
int ListView_GetSelectedCount(HWND h)
|
|
{
|
|
listViewState *lvs = h ? (listViewState *)h->m_private_data : NULL;
|
|
if (WDL_NOT_NORMALLY(!lvs)) return 0;
|
|
const int n = lvs->GetNumItems();
|
|
int sum=0,x;
|
|
for (x=0;x<n;x++) if (lvs->get_sel(x)) sum++;
|
|
return sum;
|
|
}
|
|
|
|
int ListView_GetItemCount(HWND h)
|
|
{
|
|
listViewState *lvs = h ? (listViewState *)h->m_private_data : NULL;
|
|
if (WDL_NOT_NORMALLY(!lvs)) return 0;
|
|
return lvs->GetNumItems();
|
|
}
|
|
|
|
int ListView_GetSelectionMark(HWND h)
|
|
{
|
|
listViewState *lvs = h ? (listViewState *)h->m_private_data : NULL;
|
|
if (WDL_NOT_NORMALLY(!lvs)) return 0;
|
|
const int n = lvs->GetNumItems();
|
|
int x;
|
|
for (x=0;x<n;x++) if (lvs->get_sel(x)) return x;
|
|
return -1;
|
|
}
|
|
int SWELL_GetListViewHeaderHeight(HWND h)
|
|
{
|
|
listViewState *lvs = h ? (listViewState *)h->m_private_data : NULL;
|
|
return WDL_NORMALLY(lvs) ? lvs->GetColumnHeaderHeight(h) : 0;
|
|
}
|
|
|
|
void ListView_SetColumnWidth(HWND h, int pos, int wid)
|
|
{
|
|
listViewState *lvs = h ? (listViewState *)h->m_private_data : NULL;
|
|
if (WDL_NOT_NORMALLY(!lvs)) return;
|
|
SWELL_ListView_Col *col = lvs->GetColumnByIndex(pos);
|
|
WDL_ASSERT(col || !pos);
|
|
if (col)
|
|
{
|
|
col->xwid = wid;
|
|
InvalidateRect(h,NULL,FALSE);
|
|
}
|
|
}
|
|
|
|
int ListView_HitTest(HWND h, LVHITTESTINFO *pinf)
|
|
{
|
|
listViewState *lvs = h ? (listViewState *)h->m_private_data : NULL;
|
|
if (WDL_NOT_NORMALLY(!lvs || !pinf)) return -1;
|
|
|
|
pinf->flags=0;
|
|
pinf->iItem=-1;
|
|
|
|
int x=pinf->pt.x;
|
|
int y=pinf->pt.y;
|
|
|
|
RECT r;
|
|
GetClientRect(h,&r);
|
|
|
|
if (x < 0) pinf->flags |= LVHT_TOLEFT;
|
|
if (x >= r.right) pinf->flags |= LVHT_TORIGHT;
|
|
if (y < 0) pinf->flags |= LVHT_ABOVE;
|
|
if (y >= r.bottom) pinf->flags |= LVHT_BELOW;
|
|
|
|
if (!pinf->flags && lvs->m_last_row_height)
|
|
{
|
|
const int ypos = y - lvs->GetColumnHeaderHeight(h);
|
|
const int hit = ypos >= 0 ? ((ypos + lvs->m_scroll_y) / lvs->m_last_row_height) : -1;
|
|
if (hit < 0) pinf->flags |= LVHT_ABOVE;
|
|
pinf->iItem=hit < 0 || hit >= lvs->GetNumItems() ? -1 : hit;
|
|
if (pinf->iItem >= 0)
|
|
{
|
|
if (lvs->m_status_imagelist && x < lvs->m_last_row_height)
|
|
{
|
|
pinf->flags=LVHT_ONITEMSTATEICON;
|
|
}
|
|
else
|
|
{
|
|
pinf->flags=LVHT_ONITEMLABEL;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
pinf->flags=LVHT_NOWHERE;
|
|
}
|
|
}
|
|
|
|
return pinf->iItem;
|
|
}
|
|
int ListView_SubItemHitTest(HWND h, LVHITTESTINFO *pinf)
|
|
{
|
|
listViewState *lvs = h ? (listViewState *)h->m_private_data : NULL;
|
|
if (WDL_NOT_NORMALLY(!lvs || !pinf)) return -1;
|
|
|
|
const int row = ListView_HitTest(h, pinf);
|
|
int x,xpos=-lvs->m_scroll_x,idx=0;
|
|
const int n=lvs->m_cols.GetSize();
|
|
const bool has_image = lvs->hasStatusImage();
|
|
if (has_image) xpos += lvs->m_last_row_height;
|
|
for (x=0;x<n;x++)
|
|
{
|
|
const int xwid = lvs->m_cols.Get()[x].xwid;
|
|
if (pinf->pt.x >= xpos && pinf->pt.x < xpos+xwid) { idx = lvs->m_cols.Get()[x].col_index; break; }
|
|
xpos += xwid;
|
|
}
|
|
pinf->iSubItem = idx;
|
|
return row;
|
|
}
|
|
|
|
void ListView_SetItemCount(HWND h, int cnt)
|
|
{
|
|
listViewState *lvs = h ? (listViewState *)h->m_private_data : NULL;
|
|
if (WDL_NOT_NORMALLY(!lvs) || !lvs->IsOwnerData()) return;
|
|
lvs->m_owner_data_size = cnt > 0 ? cnt : 0;
|
|
if (lvs->m_owner_multisel_state.GetSize() > lvs->m_owner_data_size) lvs->m_owner_multisel_state.Resize(lvs->m_owner_data_size);
|
|
if (lvs->m_selitem >= lvs->m_owner_data_size) lvs->m_selitem = -1;
|
|
}
|
|
|
|
void ListView_EnsureVisible(HWND h, int i, BOOL pok)
|
|
{
|
|
listViewState *lvs = h ? (listViewState *)h->m_private_data : NULL;
|
|
if (WDL_NOT_NORMALLY(!lvs) || !lvs->m_last_row_height) return;
|
|
const int n = lvs->GetNumItems();
|
|
if (i>=0 && i < n)
|
|
{
|
|
const int row_height = lvs->m_last_row_height;
|
|
RECT r;
|
|
GetClientRect(h,&r);
|
|
r.bottom -= lvs->GetColumnHeaderHeight(h);
|
|
if (lvs->getTotalWidth() > r.right) r.bottom -= row_height;
|
|
|
|
const int oldy = lvs->m_scroll_y;
|
|
if (i*row_height < lvs->m_scroll_y) lvs->m_scroll_y = i*row_height;
|
|
else if ((i+1)*row_height > lvs->m_scroll_y + r.bottom) lvs->m_scroll_y = (i+1)*row_height-r.bottom;
|
|
lvs->sanitizeScroll(h);
|
|
if (oldy != lvs->m_scroll_y)
|
|
{
|
|
InvalidateRect(h,NULL,FALSE);
|
|
}
|
|
}
|
|
|
|
}
|
|
bool ListView_GetSubItemRect(HWND h, int item, int subitem, int code, RECT *r)
|
|
{
|
|
listViewState *lvs = h ? (listViewState *)h->m_private_data : NULL;
|
|
if (WDL_NOT_NORMALLY(!lvs || !r)) return false;
|
|
|
|
r->top = lvs->m_last_row_height * item - lvs->m_scroll_y;
|
|
r->top += lvs->GetColumnHeaderHeight(h);
|
|
RECT cr;
|
|
GetClientRect(h,&cr);
|
|
r->left=cr.left;
|
|
r->right=cr.right;
|
|
|
|
if (subitem>0)
|
|
{
|
|
int x,xpos=-lvs->m_scroll_x;
|
|
const int n=lvs->m_cols.GetSize();
|
|
for (x = 0; x < n; x ++)
|
|
{
|
|
int xwid = lvs->m_cols.Get()[x].xwid;
|
|
if (!x && lvs->hasStatusImage()) xwid += lvs->m_last_row_height;
|
|
if (lvs->m_cols.Get()[x].col_index == subitem)
|
|
{
|
|
r->left=xpos;
|
|
r->right=xpos+xwid;
|
|
break;
|
|
}
|
|
xpos += xwid;
|
|
}
|
|
}
|
|
|
|
if (r->top < -64-lvs->m_last_row_height) r->top = -64 - lvs->m_last_row_height;
|
|
if (r->top > cr.bottom+64) r->top = cr.bottom+64;
|
|
|
|
r->bottom = r->top + lvs->m_last_row_height;
|
|
|
|
return true;
|
|
}
|
|
|
|
bool ListView_GetItemRect(HWND h, int item, RECT *r, int code)
|
|
{
|
|
return ListView_GetSubItemRect(h, item, -1, code, r);
|
|
}
|
|
|
|
bool ListView_Scroll(HWND h, int xscroll, int yscroll)
|
|
{
|
|
listViewState *lvs = h ? (listViewState *)h->m_private_data : NULL;
|
|
if (WDL_NOT_NORMALLY(!lvs) || !lvs->m_last_row_height) return false;
|
|
const int oldy = lvs->m_scroll_y, oldx = lvs->m_scroll_x;
|
|
lvs->m_scroll_x += xscroll;
|
|
lvs->m_scroll_y += yscroll;
|
|
lvs->sanitizeScroll(h);
|
|
if (oldy != lvs->m_scroll_y || oldx != lvs->m_scroll_x)
|
|
InvalidateRect(h,NULL,FALSE);
|
|
return true;
|
|
}
|
|
|
|
void ListView_SortItems(HWND hwnd, PFNLVCOMPARE compf, LPARAM parm)
|
|
{
|
|
listViewState *lvs = hwnd ? (listViewState *)hwnd->m_private_data : NULL;
|
|
if (WDL_NOT_NORMALLY(!lvs ||
|
|
lvs->m_is_listbox ||
|
|
lvs->m_owner_data_size >= 0 || !compf)) return;
|
|
|
|
WDL_HeapBuf tmp;
|
|
char *b = (char*)tmp.ResizeOK(lvs->m_data.GetSize()*sizeof(void *));
|
|
if (b)
|
|
__listview_mergesort_internal(lvs->m_data.GetList(),lvs->m_data.GetSize(),
|
|
sizeof(void *),compf,parm,(char*)b);
|
|
InvalidateRect(hwnd,NULL,FALSE);
|
|
}
|
|
|
|
bool ListView_DeleteColumn(HWND h, int pos)
|
|
{
|
|
listViewState *lvs = h ? (listViewState *)h->m_private_data : NULL;
|
|
if (WDL_NOT_NORMALLY(!lvs)) return false;
|
|
SWELL_ListView_Col *c = lvs->GetColumnByIndex(pos);
|
|
if (!c) return false;
|
|
|
|
const int cidx = c->col_index;
|
|
free(c->name);
|
|
lvs->m_cols.Delete((int) (c - lvs->m_cols.Get()));
|
|
|
|
for (int x = 0; x < lvs->m_cols.GetSize(); x ++)
|
|
{
|
|
c = lvs->m_cols.Get()+x;
|
|
WDL_ASSERT(c->col_index != cidx);
|
|
if (c->col_index > cidx) c->col_index--;
|
|
}
|
|
InvalidateRect(h,NULL,FALSE);
|
|
return true;
|
|
}
|
|
|
|
int ListView_GetCountPerPage(HWND h)
|
|
{
|
|
listViewState *lvs = h ? (listViewState *)h->m_private_data : NULL;
|
|
if (WDL_NOT_NORMALLY(!lvs) ||
|
|
!lvs->m_last_row_height) return 0;
|
|
|
|
RECT cr;
|
|
GetClientRect(h,&cr);
|
|
cr.bottom -= lvs->GetColumnHeaderHeight(h);
|
|
return (cr.bottom-cr.top) / lvs->m_last_row_height;
|
|
}
|
|
|
|
HWND ChildWindowFromPoint(HWND h, POINT p)
|
|
{
|
|
if (WDL_NOT_NORMALLY(!h)) return 0;
|
|
|
|
RECT r={0,};
|
|
|
|
for(;;)
|
|
{
|
|
HWND h2=h->m_children;
|
|
RECT sr;
|
|
|
|
NCCALCSIZE_PARAMS tr={{h->m_position,},};
|
|
if (h->m_wndproc) h->m_wndproc(h,WM_NCCALCSIZE,0,(LPARAM)&tr);
|
|
r.left += tr.rgrc[0].left - h->m_position.left;
|
|
r.top += tr.rgrc[0].top - h->m_position.top;
|
|
|
|
HWND best=NULL;
|
|
RECT bestsr = { 0, };
|
|
while (h2)
|
|
{
|
|
sr = h2->m_position;
|
|
sr.left += r.left;
|
|
sr.right += r.left;
|
|
sr.top += r.top;
|
|
sr.bottom += r.top;
|
|
|
|
if (h2->m_visible && PtInRect(&sr,p))
|
|
{
|
|
bestsr = sr;
|
|
best=h2;
|
|
}
|
|
|
|
h2 = h2->m_next;
|
|
}
|
|
if (!best) break; // h is the window we care about
|
|
|
|
h=best; // descend to best
|
|
r=bestsr;
|
|
}
|
|
|
|
return h;
|
|
}
|
|
|
|
static bool isHitTestOpaque(HWND h, const POINT p)
|
|
{
|
|
return h->m_visible && HTTRANSPARENT != SendMessage(h, WM_NCHITTEST, 0, MAKELPARAM(p.x, p.y));
|
|
}
|
|
|
|
static HWND recurseOwnedWindowHitTest(HWND h, POINT p, int maxdepth)
|
|
{
|
|
RECT r;
|
|
GetWindowContentViewRect(h,&r);
|
|
if (!PtInRect(&r,p)) return NULL;
|
|
|
|
// check any owned windows first, as they are always above our window
|
|
if (h->m_owned_list && maxdepth > 0)
|
|
{
|
|
HWND owned = h->m_owned_list;
|
|
while (owned)
|
|
{
|
|
if (isHitTestOpaque(owned, p))
|
|
{
|
|
HWND hit = recurseOwnedWindowHitTest(owned,p,maxdepth-1);
|
|
if (hit) return hit;
|
|
}
|
|
owned = owned->m_owned_next;
|
|
}
|
|
}
|
|
p.x -= r.left;
|
|
p.y -= r.top;
|
|
return ChildWindowFromPoint(h,p);
|
|
}
|
|
|
|
HWND WindowFromPoint(POINT p)
|
|
{
|
|
HWND h = SWELL_topwindows;
|
|
while (h)
|
|
{
|
|
if (isHitTestOpaque(h, p))
|
|
{
|
|
HWND hit = recurseOwnedWindowHitTest(h,p,20);
|
|
if (hit) return hit;
|
|
}
|
|
h = h->m_next;
|
|
}
|
|
return NULL;
|
|
}
|
|
|
|
#ifdef _DEBUG
|
|
int g_swell_in_paint;
|
|
#endif
|
|
|
|
BOOL InvalidateRect(HWND hwnd, const RECT *r, int eraseBk)
|
|
{
|
|
#ifdef _DEBUG
|
|
if (g_swell_in_paint)
|
|
{
|
|
printf("swell-generic: calling InvalidateRect() from within paint, this is allowed but bad form.\n");
|
|
// WDL_ASSERT(false);
|
|
}
|
|
#endif
|
|
|
|
if (WDL_NOT_NORMALLY(!hwnd) || hwnd->m_hashaddestroy) return FALSE;
|
|
|
|
#ifdef SWELL_LICE_GDI
|
|
RECT rect;
|
|
if (r)
|
|
{
|
|
rect = *r;
|
|
}
|
|
else
|
|
{
|
|
rect = hwnd->m_position;
|
|
WinOffsetRect(&rect, -rect.left, -rect.top);
|
|
}
|
|
|
|
// rect is in client coordinates of h
|
|
HWND h = hwnd;
|
|
for (;;)
|
|
{
|
|
if (!h->m_visible || h->m_hashaddestroy) return FALSE;
|
|
|
|
RECT ncrect = h->m_position;
|
|
if (h->m_oswindow) WinOffsetRect(&ncrect, -ncrect.left, -ncrect.top);
|
|
|
|
NCCALCSIZE_PARAMS tr;
|
|
memset(&tr,0,sizeof(tr));
|
|
tr.rgrc[0] = ncrect;
|
|
if (h->m_wndproc) h->m_wndproc(h,WM_NCCALCSIZE,0,(LPARAM)&tr);
|
|
|
|
WinOffsetRect(&rect,tr.rgrc[0].left, tr.rgrc[0].top);
|
|
|
|
if (!IntersectRect(&rect,&rect,&ncrect)) return FALSE;
|
|
|
|
if (h->m_oswindow) break;
|
|
|
|
h=h->m_parent;
|
|
if (!h) return FALSE;
|
|
}
|
|
|
|
{
|
|
hwnd->m_invalidated=true;
|
|
HWND t=hwnd->m_parent;
|
|
if (t && (t->m_style & WS_CLIPSIBLINGS))
|
|
{
|
|
// child window, invalidate any later children that intersect us (redraw them on top of us)
|
|
HWND nw = hwnd->m_next;
|
|
while (nw)
|
|
{
|
|
RECT tmp;
|
|
if (nw->m_visible && !nw->m_invalidated && WinIntersectRect(&tmp,&hwnd->m_position,&nw->m_position))
|
|
nw->m_invalidated=true;
|
|
nw=nw->m_next;
|
|
}
|
|
}
|
|
|
|
while (t)
|
|
{
|
|
if (eraseBk)
|
|
{
|
|
t->m_invalidated=true;
|
|
eraseBk--;
|
|
}
|
|
t->m_child_invalidated=true;
|
|
t=t->m_parent;
|
|
}
|
|
}
|
|
swell_oswindow_invalidate(h, (hwnd!=h || r) ? &rect : NULL);
|
|
#endif
|
|
return TRUE;
|
|
}
|
|
|
|
|
|
HWND GetCapture()
|
|
{
|
|
return swell_captured_window;
|
|
}
|
|
|
|
HWND SetCapture(HWND hwnd)
|
|
{
|
|
WDL_ASSERT(hwnd != NULL);
|
|
HWND oc = swell_captured_window;
|
|
if (oc != hwnd)
|
|
{
|
|
swell_captured_window=hwnd;
|
|
if (oc) SendMessage(oc,WM_CAPTURECHANGED,0,(LPARAM)hwnd);
|
|
}
|
|
return oc;
|
|
}
|
|
|
|
void ReleaseCapture()
|
|
{
|
|
if (swell_captured_window)
|
|
{
|
|
SendMessage(swell_captured_window,WM_CAPTURECHANGED,0,0);
|
|
swell_captured_window=0;
|
|
}
|
|
}
|
|
|
|
static HWND getNextFocusWindow(HWND hwnd, bool rev, HWND foc_child)
|
|
{
|
|
HWND ch = NULL;
|
|
if (foc_child)
|
|
{
|
|
ch = hwnd->m_children;
|
|
while (ch && ch != foc_child) ch = ch->m_next;
|
|
}
|
|
|
|
int pass=0;
|
|
if (ch)
|
|
{
|
|
ch = rev ? ch->m_prev : ch->m_next;
|
|
}
|
|
else
|
|
{
|
|
ch = hwnd->m_children;
|
|
if (ch && rev) while (ch->m_next) ch=ch->m_next;
|
|
pass++;
|
|
}
|
|
|
|
for (;;)
|
|
{
|
|
while (ch)
|
|
{
|
|
// scan to find next matching control
|
|
if (ch->m_wantfocus && ch->m_visible && ch->m_enabled) break;
|
|
ch = rev ? ch->m_prev : ch->m_next;
|
|
}
|
|
if (ch || ++pass>1 || hwnd->m_parent) break;
|
|
|
|
// continue searching
|
|
ch = hwnd->m_children;
|
|
if (ch && rev) while (ch->m_next) ch=ch->m_next;
|
|
}
|
|
if (ch && ch->m_children)
|
|
{
|
|
HWND sub = getNextFocusWindow(ch,rev,NULL);
|
|
if (sub) return sub;
|
|
}
|
|
return ch;
|
|
}
|
|
|
|
|
|
LRESULT SwellDialogDefaultWindowProc(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam)
|
|
{
|
|
DLGPROC d=(DLGPROC)GetWindowLong(hwnd,DWL_DLGPROC);
|
|
if (d)
|
|
{
|
|
if (uMsg == WM_PAINT)
|
|
{
|
|
PAINTSTRUCT ps;
|
|
if (BeginPaint(hwnd,&ps))
|
|
{
|
|
HBRUSH hbrush = (HBRUSH) d(hwnd,WM_CTLCOLORDLG,(WPARAM)ps.hdc,(LPARAM)hwnd);
|
|
if (hbrush && hbrush != (HBRUSH)1)
|
|
{
|
|
FillRect(ps.hdc,&ps.rcPaint,hbrush);
|
|
}
|
|
else if (1)
|
|
{
|
|
SWELL_FillDialogBackground(ps.hdc,&ps.rcPaint,0);
|
|
}
|
|
|
|
EndPaint(hwnd,&ps);
|
|
}
|
|
}
|
|
|
|
LRESULT r=(LRESULT) d(hwnd,uMsg,wParam,lParam);
|
|
|
|
|
|
if (r) return r;
|
|
|
|
if (uMsg == WM_KEYDOWN)
|
|
{
|
|
if (!hwnd->m_parent)
|
|
{
|
|
if (wParam == VK_ESCAPE)
|
|
{
|
|
if (IsWindowEnabled(hwnd) && !SendMessage(hwnd,WM_CLOSE,0,0))
|
|
SendMessage(hwnd,WM_COMMAND,IDCANCEL,0);
|
|
return 0;
|
|
}
|
|
else if (wParam == VK_RETURN)
|
|
{
|
|
HWND c = GetWindow(hwnd,GW_CHILD);
|
|
while (c)
|
|
{
|
|
if (c->m_id && (c->m_style&BS_DEFPUSHBUTTON) &&
|
|
c->m_classname && !strcmp(c->m_classname,"Button"))
|
|
{
|
|
SendMessage(hwnd,WM_COMMAND,c->m_id,0);
|
|
return 0;
|
|
}
|
|
c = GetWindow(c,GW_HWNDNEXT);
|
|
}
|
|
c = GetDlgItem(hwnd,IDOK);
|
|
if (c) SendMessage(hwnd,WM_COMMAND,IDOK,0);
|
|
return 0;
|
|
}
|
|
}
|
|
int navdir = 0;
|
|
|
|
if (wParam == VK_TAB && (lParam&~FSHIFT) == FVIRTKEY) navdir = (lParam & FSHIFT) ? -1 : 1;
|
|
else if ((lParam&0xff) == FVIRTKEY)
|
|
{
|
|
if (wParam == VK_LEFT || wParam == VK_UP) navdir = -1;
|
|
else if (wParam == VK_RIGHT || wParam == VK_DOWN) navdir = 1;
|
|
}
|
|
|
|
if (navdir)
|
|
{
|
|
HWND ch = getNextFocusWindow(hwnd,navdir<0,hwnd->m_focused_child);
|
|
if (ch)
|
|
{
|
|
HWND oldfoc = GetFocus();
|
|
SetFocus(ch);
|
|
if (oldfoc != ch) SWELL_OnNavigationFocus(ch);
|
|
InvalidateRect(ch,NULL,FALSE);
|
|
return 0;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
return DefWindowProc(hwnd,uMsg,wParam,lParam);
|
|
}
|
|
|
|
BOOL EndPaint(HWND hwnd, PAINTSTRUCT *ps)
|
|
{
|
|
WDL_ASSERT(hwnd != NULL);
|
|
return TRUE;
|
|
}
|
|
|
|
|
|
static HFONT menubar_font;
|
|
|
|
static bool wantRightAlignedMenuBarItem(const char *p)
|
|
{
|
|
char c = *p;
|
|
return c > 0 && c != '&' && !isalnum(c);
|
|
}
|
|
|
|
#define MENUBAR_SELECTED_ITEM_XPAD \
|
|
wdl_min(g_swell_ctheme.menubar_spacing_width,g_swell_ctheme.menubar_margin_width)
|
|
|
|
static int menuBarHitTest(HWND hwnd, int mousex, int mousey, RECT *rOut, int forceItem, int expandedItem)
|
|
{
|
|
int rv=-1;
|
|
RECT r;
|
|
GetWindowContentViewRect(hwnd,&r);
|
|
if (forceItem >= 0 || (mousey>=r.top && mousey < r.top+g_swell_ctheme.menubar_height))
|
|
{
|
|
HDC dc = GetWindowDC(hwnd);
|
|
|
|
const int xselpad = MENUBAR_SELECTED_ITEM_XPAD, xpad=g_swell_ctheme.menubar_spacing_width;
|
|
int x,xpos=r.left + g_swell_ctheme.menubar_margin_width;
|
|
HMENU__ *menu = (HMENU__*)hwnd->m_menu;
|
|
HGDIOBJ oldfont = dc ? SelectObject(dc,menubar_font) : NULL;
|
|
const int n=menu->items.GetSize();
|
|
for(x=0;x<n;x++)
|
|
{
|
|
MENUITEMINFO *inf = menu->items.Get(x);
|
|
if (inf->fType == MFT_STRING && inf->dwTypeData)
|
|
{
|
|
bool dis = !!(inf->fState & MF_GRAYED);
|
|
RECT cr={0,};
|
|
DrawText(dc,inf->dwTypeData,-1,&cr,DT_CALCRECT);
|
|
if (x == n-1 && wantRightAlignedMenuBarItem(inf->dwTypeData))
|
|
{
|
|
xpos = wdl_max(xpos,r.right - g_swell_ctheme.menubar_margin_width - cr.right);
|
|
cr.right = r.right - g_swell_ctheme.menubar_margin_width - xpos;
|
|
}
|
|
|
|
if (forceItem >=0 ? forceItem == x :
|
|
expandedItem < 0 ? (mousex >= xpos - xpad && mousex < xpos + cr.right + (xpad+1)*3/4) :
|
|
x == expandedItem ? (mousex >= xpos - xselpad && mousex < xpos + cr.right + xselpad) :
|
|
mousex >= xpos && mousex < xpos + cr.right + xpad - (x == expandedItem-1 ? xselpad : 0)
|
|
)
|
|
{
|
|
if (!dis)
|
|
{
|
|
rOut->left = xpos - xselpad;
|
|
rOut->right = xpos + cr.right;
|
|
rOut->top = r.top;
|
|
rOut->bottom = r.top + g_swell_ctheme.menubar_height;
|
|
rv=x;
|
|
}
|
|
break;
|
|
}
|
|
|
|
xpos+=cr.right+xpad;
|
|
}
|
|
}
|
|
|
|
if (dc)
|
|
{
|
|
SelectObject(dc,oldfont);
|
|
ReleaseDC(hwnd,dc);
|
|
}
|
|
}
|
|
return rv;
|
|
}
|
|
|
|
static RECT g_menubar_lastrect;
|
|
static HWND g_menubar_active;
|
|
static bool g_menubar_active_drag;
|
|
|
|
HWND swell_window_wants_all_input()
|
|
{
|
|
return g_menubar_active_drag ? g_menubar_active : NULL;
|
|
}
|
|
|
|
int menuBarNavigate(int dir) // -1 if no menu bar active, 0 if did nothing, 1 if navigated
|
|
{
|
|
if (!g_menubar_active || !g_menubar_active->m_menu) return -1;
|
|
HMENU__ *menu = (HMENU__*)g_menubar_active->m_menu;
|
|
RECT r;
|
|
const int x = menuBarHitTest(g_menubar_active,0,0,&r,menu->sel_vis + dir, -1);
|
|
if (x>=0)
|
|
{
|
|
MENUITEMINFO *inf = menu->items.Get(x);
|
|
if (inf && inf->hSubMenu)
|
|
{
|
|
menu->sel_vis = x;
|
|
g_menubar_lastrect = r;
|
|
|
|
DestroyPopupMenus();
|
|
return 1;
|
|
}
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
RECT g_trackpopup_yroot;
|
|
static void runMenuBar(HWND hwnd, HMENU__ *menu, int x, const RECT *use_r, int flag)
|
|
{
|
|
menu->Retain();
|
|
MENUITEMINFO *inf = menu->items.Get(x);
|
|
RECT r = *use_r;
|
|
g_trackpopup_yroot = r;
|
|
|
|
RECT mbr;
|
|
GetWindowContentViewRect(hwnd,&mbr);
|
|
mbr.right -= mbr.left;
|
|
mbr.left=0;
|
|
mbr.bottom = 0;
|
|
mbr.top = -g_swell_ctheme.menubar_height;
|
|
menu->sel_vis = x;
|
|
g_menubar_active = hwnd;
|
|
g_menubar_active_drag=true;
|
|
for (;;)
|
|
{
|
|
InvalidateRect(hwnd,&mbr,FALSE);
|
|
if (TrackPopupMenu(inf->hSubMenu,0,r.left,r.bottom,flag,hwnd,NULL) || menu->sel_vis == x) break;
|
|
|
|
x = menu->sel_vis;
|
|
inf = menu->items.Get(x);
|
|
if (!inf || !inf->hSubMenu) break;
|
|
|
|
r = g_menubar_lastrect;
|
|
}
|
|
menu->sel_vis=-1;
|
|
InvalidateRect(hwnd,&mbr,FALSE);
|
|
g_menubar_active = NULL;
|
|
g_trackpopup_yroot.top = g_trackpopup_yroot.bottom = 0;
|
|
menu->Release();
|
|
}
|
|
|
|
LRESULT DefWindowProc(HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam)
|
|
{
|
|
switch (msg)
|
|
{
|
|
case WM_DESTROY:
|
|
if (g_menubar_active == hwnd) g_menubar_active=NULL;
|
|
break;
|
|
case WM_NCMOUSEMOVE:
|
|
if (g_menubar_active == hwnd && hwnd->m_menu)
|
|
{
|
|
swell_delegate_menu_message(hwnd,lParam,WM_MOUSEMOVE,true);
|
|
|
|
HMENU__ *menu = (HMENU__*)hwnd->m_menu;
|
|
RECT r;
|
|
const int x = menuBarHitTest(hwnd,GET_X_LPARAM(lParam),GET_Y_LPARAM(lParam),&r,-1, menu->sel_vis);
|
|
if (x>=0 && x != menu->sel_vis)
|
|
{
|
|
MENUITEMINFO *inf = menu->items.Get(x);
|
|
if (inf && inf->hSubMenu)
|
|
{
|
|
menu->sel_vis = x;
|
|
g_menubar_lastrect = r;
|
|
|
|
DestroyPopupMenus(); // cause new menu to be popped up
|
|
}
|
|
}
|
|
}
|
|
break;
|
|
|
|
case WM_NCCALCSIZE:
|
|
if (!hwnd->m_parent && hwnd->m_menu)
|
|
{
|
|
RECT *r = (RECT*)lParam;
|
|
r->top += g_swell_ctheme.menubar_height;
|
|
}
|
|
break;
|
|
case WM_NCPAINT:
|
|
if (!hwnd->m_parent && hwnd->m_menu)
|
|
{
|
|
HDC dc = GetWindowDC(hwnd);
|
|
if (dc)
|
|
{
|
|
if (!menubar_font)
|
|
menubar_font = CreateFont(g_swell_ctheme.menubar_font_size,0,0,0,FW_NORMAL,0,0,0,0,0,0,0,0,g_swell_deffont_face);
|
|
|
|
RECT r;
|
|
GetWindowContentViewRect(hwnd,&r);
|
|
r.right -= r.left; r.left=0;
|
|
r.bottom -= r.top; r.top=0;
|
|
if (r.bottom>g_swell_ctheme.menubar_height) r.bottom=g_swell_ctheme.menubar_height;
|
|
|
|
bool isactive = false;
|
|
if (swell_is_app_inactive() == 0) // draw as inactive if maybe inactive
|
|
{
|
|
HWND foc = GetFocus();
|
|
if (foc && (foc == hwnd || IsChild(hwnd,foc))) isactive = true;
|
|
}
|
|
|
|
{
|
|
HBRUSH br=CreateSolidBrush(isactive ? g_swell_ctheme.menubar_bg : g_swell_ctheme.menubar_bg_inactive);
|
|
FillRect(dc,&r,br);
|
|
DeleteObject(br);
|
|
}
|
|
|
|
HGDIOBJ oldfont = SelectObject(dc,menubar_font);
|
|
SetBkMode(dc,TRANSPARENT);
|
|
|
|
int x,xpos=g_swell_ctheme.menubar_margin_width;
|
|
HMENU__ *menu = (HMENU__*)hwnd->m_menu;
|
|
const int n = menu->items.GetSize();
|
|
for(x=0;x<n;x++)
|
|
{
|
|
MENUITEMINFO *inf = menu->items.Get(x);
|
|
if (inf->fType == MFT_STRING && inf->dwTypeData)
|
|
{
|
|
bool dis = !!(inf->fState & MF_GRAYED);
|
|
RECT cr={0};
|
|
DrawText(dc,inf->dwTypeData,-1,&cr,DT_CALCRECT|DT_SINGLELINE);
|
|
|
|
if (x == n-1 && wantRightAlignedMenuBarItem(inf->dwTypeData))
|
|
{
|
|
cr.left = wdl_max(xpos,r.right - g_swell_ctheme.menubar_margin_width - cr.right);
|
|
cr.right = r.right - g_swell_ctheme.menubar_margin_width;
|
|
}
|
|
else
|
|
{
|
|
cr.left = xpos;
|
|
cr.right += xpos;
|
|
}
|
|
cr.top = r.top;
|
|
cr.bottom = r.bottom;
|
|
if (!dis && menu->sel_vis == x)
|
|
{
|
|
RECT crb = cr;
|
|
crb.left -= MENUBAR_SELECTED_ITEM_XPAD;
|
|
crb.right += MENUBAR_SELECTED_ITEM_XPAD;
|
|
HBRUSH br = CreateSolidBrush(g_swell_ctheme.menubar_bg_sel);
|
|
FillRect(dc,&crb,br);
|
|
DeleteObject(br);
|
|
SetTextColor(dc,g_swell_ctheme.menubar_text_sel);
|
|
}
|
|
else SetTextColor(dc,
|
|
!isactive ? g_swell_ctheme.menubar_text_inactive :
|
|
dis ? g_swell_ctheme.menubar_text_disabled :
|
|
g_swell_ctheme.menubar_text);
|
|
|
|
DrawText(dc,inf->dwTypeData,-1,&cr,DT_VCENTER|DT_LEFT|DT_SINGLELINE);
|
|
xpos=cr.right+g_swell_ctheme.menubar_spacing_width;
|
|
}
|
|
}
|
|
|
|
SelectObject(dc,oldfont);
|
|
ReleaseDC(hwnd,dc);
|
|
}
|
|
}
|
|
break;
|
|
case WM_RBUTTONUP:
|
|
case WM_NCRBUTTONUP:
|
|
{
|
|
POINT p={GET_X_LPARAM(lParam),GET_Y_LPARAM(lParam)};
|
|
HWND hwndDest=hwnd;
|
|
if (msg==WM_RBUTTONUP)
|
|
{
|
|
ClientToScreen(hwnd,&p);
|
|
HWND h=WindowFromPoint(p);
|
|
if (h && IsChild(hwnd,h)) hwndDest=h;
|
|
}
|
|
if (hwnd == s_last_rbuttondown)
|
|
SendMessage(hwnd,WM_CONTEXTMENU,(WPARAM)hwndDest,(p.x&0xffff)|(p.y<<16));
|
|
}
|
|
return 1;
|
|
case WM_NCLBUTTONDOWN:
|
|
case WM_NCLBUTTONUP:
|
|
if (!hwnd->m_parent && hwnd->m_menu)
|
|
{
|
|
if (msg == WM_NCLBUTTONUP && g_menubar_active_drag)
|
|
{
|
|
g_menubar_active_drag=false;
|
|
if (swell_delegate_menu_message(hwnd,lParam,WM_LBUTTONUP,true)) return 0;
|
|
|
|
POINT pt;
|
|
GetCursorPos(&pt);
|
|
|
|
RECT r;
|
|
GetWindowContentViewRect(hwnd,&r);
|
|
r.bottom = r.top + g_swell_ctheme.menubar_height;
|
|
|
|
if (!PtInRect(&r,pt))
|
|
{
|
|
DestroyPopupMenus();
|
|
return 0;
|
|
}
|
|
}
|
|
RECT r;
|
|
const int x = menuBarHitTest(hwnd,GET_X_LPARAM(lParam),GET_Y_LPARAM(lParam),&r,-1,-1);
|
|
if (x>=0)
|
|
{
|
|
HMENU__ *menu = (HMENU__*)hwnd->m_menu;
|
|
MENUITEMINFO *inf = menu->items.Get(x);
|
|
if (inf)
|
|
{
|
|
if (inf->hSubMenu)
|
|
{
|
|
if (msg == WM_NCLBUTTONDOWN)
|
|
{
|
|
runMenuBar(hwnd,menu,x,&r,0xbeef);
|
|
}
|
|
}
|
|
else if (msg == WM_NCLBUTTONUP)
|
|
{
|
|
if (inf->wID) SendMessage(hwnd,WM_COMMAND,inf->wID,0);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
break;
|
|
case WM_NCHITTEST:
|
|
if (!hwnd->m_parent && hwnd->m_menu)
|
|
{
|
|
RECT r;
|
|
GetWindowContentViewRect(hwnd,&r);
|
|
if (GET_Y_LPARAM(lParam)>=r.top && GET_Y_LPARAM(lParam) < r.top+g_swell_ctheme.menubar_height) return HTMENU;
|
|
}
|
|
// todo: WM_NCCALCSIZE etc
|
|
return HTCLIENT;
|
|
case WM_KEYDOWN:
|
|
case WM_KEYUP:
|
|
if (hwnd->m_parent) return SendMessage(hwnd->m_parent,msg,wParam,lParam);
|
|
|
|
if (msg == WM_KEYDOWN && hwnd->m_menu &&
|
|
lParam == (FVIRTKEY | FALT) && (
|
|
(wParam >= 'A' && wParam <= 'Z') ||
|
|
(wParam >= '0' && wParam <= '9')
|
|
)
|
|
)
|
|
{
|
|
HMENU__ *menu = (HMENU__*)hwnd->m_menu;
|
|
const int n=menu->items.GetSize();
|
|
for(int x=0;x<n;x++)
|
|
{
|
|
MENUITEMINFO *inf = menu->items.Get(x);
|
|
if (inf->fType == MFT_STRING &&
|
|
!(inf->fState & MF_GRAYED) &&
|
|
inf->dwTypeData)
|
|
{
|
|
const char *p = inf->dwTypeData;
|
|
while (*p)
|
|
{
|
|
if (*p++ == '&')
|
|
{
|
|
if (*p != '&') break;
|
|
p++;
|
|
}
|
|
}
|
|
if (*p > 0 && (WPARAM)toupper(*p) == wParam)
|
|
{
|
|
if (inf->hSubMenu)
|
|
{
|
|
RECT r;
|
|
if (menuBarHitTest(hwnd,0,0,&r,x,-1)>=0)
|
|
{
|
|
runMenuBar(hwnd,menu,x,&r,0xbeee);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
if (inf->wID) SendMessage(hwnd,WM_COMMAND,inf->wID,0);
|
|
}
|
|
|
|
return 1;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
return 69;
|
|
|
|
case WM_CONTEXTMENU:
|
|
case WM_MOUSEWHEEL:
|
|
case WM_MOUSEHWHEEL:
|
|
case WM_SETCURSOR:
|
|
return hwnd->m_parent ? SendMessage(hwnd->m_parent,msg,wParam,lParam) : 0;
|
|
|
|
case WM_SETFONT:
|
|
hwnd->m_font = (HFONT)wParam;
|
|
return 0;
|
|
|
|
case WM_GETFONT:
|
|
return (LRESULT)(hwnd->m_font ? hwnd->m_font : SWELL_GetDefaultFont());
|
|
|
|
case WM_DROPFILES:
|
|
if (hwnd->m_parent && wParam && !(GetWindowLong(hwnd,GWL_EXSTYLE)&WS_EX_ACCEPTFILES))
|
|
{
|
|
DROPFILES *df=(DROPFILES*)wParam;
|
|
ClientToScreen(hwnd,&df->pt);
|
|
ScreenToClient(hwnd->m_parent,&df->pt);
|
|
|
|
return SendMessage(hwnd->m_parent,msg,wParam,lParam);
|
|
}
|
|
return 0;
|
|
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
///////////////// clipboard compatability (NOT THREAD SAFE CURRENTLY)
|
|
|
|
|
|
BOOL DragQueryPoint(HDROP hDrop,LPPOINT pt)
|
|
{
|
|
if (!hDrop) return 0;
|
|
DROPFILES *df=(DROPFILES*)GlobalLock(hDrop);
|
|
BOOL rv=!df->fNC;
|
|
*pt=df->pt;
|
|
GlobalUnlock(hDrop);
|
|
return rv;
|
|
}
|
|
|
|
void DragFinish(HDROP hDrop)
|
|
{
|
|
//do nothing for now (caller will free hdrops)
|
|
}
|
|
|
|
UINT DragQueryFile(HDROP hDrop, UINT wf, char *buf, UINT bufsz)
|
|
{
|
|
if (!hDrop) return 0;
|
|
DROPFILES *df=(DROPFILES*)GlobalLock(hDrop);
|
|
|
|
UINT rv=0;
|
|
char *p=(char*)df + df->pFiles;
|
|
if (wf == 0xFFFFFFFF)
|
|
{
|
|
while (*p)
|
|
{
|
|
rv++;
|
|
p+=strlen(p)+1;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
while (*p)
|
|
{
|
|
if (!wf--)
|
|
{
|
|
if (buf)
|
|
{
|
|
lstrcpyn_safe(buf,p,bufsz);
|
|
rv=strlen(buf);
|
|
}
|
|
else rv=strlen(p);
|
|
|
|
break;
|
|
}
|
|
p+=strlen(p)+1;
|
|
}
|
|
}
|
|
GlobalUnlock(hDrop);
|
|
return rv;
|
|
}
|
|
|
|
|
|
///////// PostMessage emulation
|
|
|
|
BOOL PostMessage(HWND hwnd, UINT message, WPARAM wParam, LPARAM lParam)
|
|
{
|
|
WDL_ASSERT(hwnd != NULL);
|
|
return SWELL_Internal_PostMessage(hwnd,message,wParam,lParam);
|
|
}
|
|
|
|
void SWELL_MessageQueue_Clear(HWND h)
|
|
{
|
|
SWELL_Internal_PMQ_ClearAllMessages(h);
|
|
}
|
|
|
|
|
|
|
|
// implementation of postmessage stuff
|
|
|
|
|
|
|
|
|
|
typedef struct PMQ_rec
|
|
{
|
|
HWND hwnd;
|
|
UINT msg;
|
|
WPARAM wParam;
|
|
LPARAM lParam;
|
|
|
|
struct PMQ_rec *next;
|
|
} PMQ_rec;
|
|
|
|
static WDL_Mutex m_pmq_mutex;
|
|
static PMQ_rec *m_pmq, *m_pmq_empty, *m_pmq_tail;
|
|
static int m_pmq_size;
|
|
|
|
#define MAX_POSTMESSAGE_SIZE 1024
|
|
|
|
|
|
void SWELL_Internal_PostMessage_Init()
|
|
{
|
|
}
|
|
|
|
void SWELL_MessageQueue_Flush()
|
|
{
|
|
m_pmq_mutex.Enter();
|
|
int max_amt = m_pmq_size;
|
|
PMQ_rec *p=m_pmq;
|
|
if (p)
|
|
{
|
|
m_pmq = p->next;
|
|
if (m_pmq_tail == p) m_pmq_tail=NULL;
|
|
m_pmq_size--;
|
|
}
|
|
m_pmq_mutex.Leave();
|
|
|
|
// process out up to max_amt of queue
|
|
while (p)
|
|
{
|
|
SendMessage(p->hwnd,p->msg,p->wParam,p->lParam);
|
|
|
|
m_pmq_mutex.Enter();
|
|
// move this message to empty list
|
|
p->next=m_pmq_empty;
|
|
m_pmq_empty = p;
|
|
|
|
// get next queued message (if within limits)
|
|
p = (--max_amt > 0) ? m_pmq : NULL;
|
|
if (p)
|
|
{
|
|
m_pmq = p->next;
|
|
if (m_pmq_tail == p) m_pmq_tail=NULL;
|
|
m_pmq_size--;
|
|
}
|
|
m_pmq_mutex.Leave();
|
|
}
|
|
}
|
|
|
|
void SWELL_Internal_PMQ_ClearAllMessages(HWND hwnd)
|
|
{
|
|
m_pmq_mutex.Enter();
|
|
PMQ_rec *p=m_pmq;
|
|
PMQ_rec *lastrec=NULL;
|
|
while (p)
|
|
{
|
|
if (hwnd && p->hwnd != hwnd) { lastrec=p; p=p->next; }
|
|
else
|
|
{
|
|
PMQ_rec *next=p->next;
|
|
|
|
p->next=m_pmq_empty; // add p to empty list
|
|
m_pmq_empty=p;
|
|
m_pmq_size--;
|
|
|
|
|
|
if (p==m_pmq_tail) m_pmq_tail=lastrec; // update tail
|
|
|
|
if (lastrec) p = lastrec->next = next;
|
|
else p = m_pmq = next;
|
|
}
|
|
}
|
|
m_pmq_mutex.Leave();
|
|
}
|
|
|
|
BOOL SWELL_Internal_PostMessage(HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam)
|
|
{
|
|
if (WDL_NOT_NORMALLY(!hwnd) || hwnd->m_hashaddestroy) return FALSE;
|
|
|
|
BOOL ret=FALSE;
|
|
m_pmq_mutex.Enter();
|
|
|
|
if (m_pmq_empty||m_pmq_size<MAX_POSTMESSAGE_SIZE)
|
|
{
|
|
PMQ_rec *rec=m_pmq_empty;
|
|
if (rec) m_pmq_empty=rec->next;
|
|
else rec=(PMQ_rec*)malloc(sizeof(PMQ_rec));
|
|
rec->next=0;
|
|
rec->hwnd=hwnd;
|
|
rec->msg=msg;
|
|
rec->wParam=wParam;
|
|
rec->lParam=lParam;
|
|
|
|
if (m_pmq_tail) m_pmq_tail->next=rec;
|
|
else
|
|
{
|
|
PMQ_rec *p=m_pmq;
|
|
while (p && p->next) p=p->next; // shouldnt happen unless m_pmq is NULL As well but why not for safety
|
|
if (p) p->next=rec;
|
|
else m_pmq=rec;
|
|
}
|
|
m_pmq_tail=rec;
|
|
m_pmq_size++;
|
|
|
|
ret=TRUE;
|
|
}
|
|
|
|
m_pmq_mutex.Leave();
|
|
|
|
return ret;
|
|
}
|
|
|
|
|
|
int EnumPropsEx(HWND hwnd, PROPENUMPROCEX proc, LPARAM lParam)
|
|
{
|
|
if (WDL_NOT_NORMALLY(!hwnd)) return -1;
|
|
int x;
|
|
for (x =0 ; x < hwnd->m_props.GetSize(); x ++)
|
|
{
|
|
const char *k="";
|
|
void *p = hwnd->m_props.Enumerate(x,&k);
|
|
if (!proc(hwnd,k,p,lParam)) return 0;
|
|
}
|
|
return 1;
|
|
}
|
|
|
|
HANDLE GetProp(HWND hwnd, const char *name)
|
|
{
|
|
if (WDL_NOT_NORMALLY(!hwnd)) return NULL;
|
|
return hwnd->m_props.Get(name);
|
|
}
|
|
|
|
BOOL SetProp(HWND hwnd, const char *name, HANDLE val)
|
|
{
|
|
if (WDL_NOT_NORMALLY(!hwnd)) return false;
|
|
hwnd->m_props.Insert(name,(void *)val);
|
|
return TRUE;
|
|
}
|
|
|
|
HANDLE RemoveProp(HWND hwnd, const char *name)
|
|
{
|
|
if (WDL_NOT_NORMALLY(!hwnd)) return NULL;
|
|
HANDLE h =GetProp(hwnd,name);
|
|
hwnd->m_props.Delete(name);
|
|
return h;
|
|
}
|
|
|
|
|
|
int GetSystemMetrics(int p)
|
|
{
|
|
switch (p)
|
|
{
|
|
case SM_CXSCREEN:
|
|
case SM_CYSCREEN:
|
|
{
|
|
RECT r;
|
|
SWELL_GetViewPort(&r, NULL, false);
|
|
return p==SM_CXSCREEN ? r.right-r.left : r.bottom-r.top;
|
|
}
|
|
case SM_CXHSCROLL:
|
|
case SM_CYHSCROLL:
|
|
case SM_CXVSCROLL:
|
|
case SM_CYVSCROLL: return g_swell_ctheme.smscrollbar_width;
|
|
case SM_CYMENU: return g_swell_ctheme.menubar_height;
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
BOOL ScrollWindow(HWND hwnd, int xamt, int yamt, const RECT *lpRect, const RECT *lpClipRect)
|
|
{
|
|
if (WDL_NOT_NORMALLY(!hwnd) || (!xamt && !yamt)) return FALSE;
|
|
InvalidateRect(hwnd,NULL,FALSE);
|
|
|
|
// move child windows only
|
|
hwnd = hwnd->m_children;
|
|
while (hwnd)
|
|
{
|
|
hwnd->m_position.left += xamt;
|
|
hwnd->m_position.right += xamt;
|
|
hwnd->m_position.top += yamt;
|
|
hwnd->m_position.bottom += yamt;
|
|
|
|
hwnd=hwnd->m_next;
|
|
}
|
|
return TRUE;
|
|
}
|
|
|
|
HWND FindWindowEx(HWND par, HWND lastw, const char *classname, const char *title)
|
|
{
|
|
HWND h=lastw?GetWindow(lastw,GW_HWNDNEXT):par?GetWindow(par,GW_CHILD):SWELL_topwindows;
|
|
while (h)
|
|
{
|
|
bool isOk=true;
|
|
if (title && strcmp(title,h->m_title.Get())) isOk=false;
|
|
else if (classname)
|
|
{
|
|
if (!h->m_classname || strcmp(classname,h->m_classname)) isOk=false;
|
|
}
|
|
|
|
if (isOk) return h;
|
|
h=GetWindow(h,GW_HWNDNEXT);
|
|
}
|
|
return NULL;
|
|
}
|
|
|
|
|
|
HTREEITEM TreeView_InsertItem(HWND hwnd, TV_INSERTSTRUCT *ins)
|
|
{
|
|
treeViewState *tvs = hwnd ? (treeViewState *)hwnd->m_private_data : NULL;
|
|
if (WDL_NOT_NORMALLY(!tvs || !ins)) return NULL;
|
|
|
|
HTREEITEM__ *par=NULL;
|
|
int inspos=0;
|
|
|
|
if (ins->hParent && ins->hParent != TVI_ROOT && ins->hParent != TVI_FIRST && ins->hParent != TVI_LAST && ins->hParent != TVI_SORT)
|
|
{
|
|
if (tvs->findItem(ins->hParent,&par,&inspos))
|
|
{
|
|
par = ins->hParent;
|
|
}
|
|
else return 0;
|
|
}
|
|
|
|
if (ins->hInsertAfter == TVI_FIRST) inspos=0;
|
|
else if (ins->hInsertAfter == TVI_LAST || ins->hInsertAfter == TVI_SORT || !ins->hInsertAfter)
|
|
inspos=(par ? par : &tvs->m_root)->m_children.GetSize();
|
|
else inspos = (par ? par : &tvs->m_root)->m_children.Find(ins->hInsertAfter)+1;
|
|
|
|
HTREEITEM__ *item=new HTREEITEM__;
|
|
if (ins->item.mask & TVIF_CHILDREN) item->m_haschildren = !!ins->item.cChildren;
|
|
if (ins->item.mask & TVIF_PARAM) item->m_param = ins->item.lParam;
|
|
if (ins->item.mask & TVIF_TEXT) item->m_value = strdup(ins->item.pszText);
|
|
|
|
(par ? par : &tvs->m_root)->m_children.Insert(inspos,item);
|
|
|
|
InvalidateRect(hwnd,NULL,FALSE);
|
|
return item;
|
|
}
|
|
|
|
BOOL TreeView_Expand(HWND hwnd, HTREEITEM item, UINT flag)
|
|
{
|
|
treeViewState *tvs = hwnd ? (treeViewState *)hwnd->m_private_data : NULL;
|
|
if (WDL_NOT_NORMALLY(!tvs || !tvs->findItem(item,NULL,NULL))) return FALSE;
|
|
|
|
if (flag == TVE_EXPAND && (item->m_state & TVIS_EXPANDED)) return TRUE;
|
|
if (flag == TVE_COLLAPSE && !(item->m_state & TVIS_EXPANDED)) return TRUE;
|
|
|
|
NMTREEVIEW nmhdr={{hwnd,(UINT_PTR)hwnd->m_id,TVN_ITEMEXPANDING},};
|
|
nmhdr.action = flag;
|
|
nmhdr.itemNew.hItem=item;
|
|
nmhdr.itemNew.lParam=item->m_param;
|
|
if (SendMessage(GetParent(hwnd),WM_NOTIFY,hwnd->m_id,(LPARAM)&nmhdr)) return TRUE;
|
|
|
|
if (flag == TVE_EXPAND) item->m_state |= TVIS_EXPANDED;
|
|
else if (flag == TVE_COLLAPSE) item->m_state &= ~TVIS_EXPANDED;
|
|
else if (flag == TVE_TOGGLE) item->m_state ^= TVIS_EXPANDED;
|
|
|
|
InvalidateRect(hwnd,NULL,FALSE);
|
|
return TRUE;
|
|
}
|
|
|
|
HTREEITEM TreeView_GetSelection(HWND hwnd)
|
|
{
|
|
treeViewState *tvs = hwnd ? (treeViewState *)hwnd->m_private_data : NULL;
|
|
if (WDL_NOT_NORMALLY(!tvs) ||
|
|
!tvs->m_sel || !tvs->findItem(tvs->m_sel,NULL,NULL)) return NULL;
|
|
return tvs->m_sel;
|
|
}
|
|
|
|
void TreeView_DeleteItem(HWND hwnd, HTREEITEM item)
|
|
{
|
|
treeViewState *tvs = hwnd ? (treeViewState *)hwnd->m_private_data : NULL;
|
|
if (WDL_NOT_NORMALLY(!tvs)) return;
|
|
HTREEITEM par=NULL;
|
|
int idx=0;
|
|
if (!tvs->findItem(item,&par,&idx)) return;
|
|
|
|
if (tvs->m_sel && (item == tvs->m_sel || item->FindItem(tvs->m_sel,NULL,NULL))) tvs->m_sel=par;
|
|
|
|
(par ? par : &tvs->m_root)->m_children.Delete(idx,true);
|
|
InvalidateRect(hwnd,NULL,FALSE);
|
|
}
|
|
|
|
void TreeView_DeleteAllItems(HWND hwnd)
|
|
{
|
|
treeViewState *tvs = hwnd ? (treeViewState *)hwnd->m_private_data : NULL;
|
|
if (WDL_NOT_NORMALLY(!tvs)) return;
|
|
tvs->m_root.m_children.Empty(true);
|
|
tvs->m_sel=NULL;
|
|
InvalidateRect(hwnd,NULL,FALSE);
|
|
}
|
|
|
|
void TreeView_EnsureVisible(HWND hwnd, HTREEITEM item)
|
|
{
|
|
treeViewState *tvs = hwnd ? (treeViewState *)hwnd->m_private_data : NULL;
|
|
if (WDL_NOT_NORMALLY(!tvs)) return;
|
|
if (!item || !tvs->findItem(item,NULL,NULL)) return;
|
|
tvs->ensureItemVisible(hwnd,item);
|
|
InvalidateRect(hwnd,NULL,FALSE);
|
|
}
|
|
|
|
void TreeView_SelectItem(HWND hwnd, HTREEITEM item)
|
|
{
|
|
treeViewState *tvs = hwnd ? (treeViewState *)hwnd->m_private_data : NULL;
|
|
if (WDL_NOT_NORMALLY(!tvs)) return;
|
|
|
|
if (tvs->m_sel == item || (item && !tvs->findItem(item,NULL,NULL))) return;
|
|
|
|
tvs->m_sel = item;
|
|
|
|
static int __rent;
|
|
if (!__rent)
|
|
{
|
|
__rent++;
|
|
NMTREEVIEW nm={{(HWND)hwnd,(UINT_PTR)hwnd->m_id,TVN_SELCHANGED},};
|
|
nm.itemNew.hItem = item;
|
|
nm.itemNew.lParam = item ? item->m_param : 0;
|
|
SendMessage(GetParent(hwnd),WM_NOTIFY,nm.hdr.idFrom,(LPARAM)&nm);
|
|
__rent--;
|
|
}
|
|
tvs->ensureItemVisible(hwnd,tvs->m_sel);
|
|
InvalidateRect(hwnd,NULL,FALSE);
|
|
}
|
|
|
|
BOOL TreeView_GetItem(HWND hwnd, LPTVITEM pitem)
|
|
{
|
|
treeViewState *tvs = hwnd ? (treeViewState *)hwnd->m_private_data : NULL;
|
|
if (WDL_NOT_NORMALLY(!tvs || !pitem) ||
|
|
!(pitem->mask & TVIF_HANDLE) || !(pitem->hItem)) return FALSE;
|
|
|
|
HTREEITEM ti = pitem->hItem;
|
|
pitem->cChildren = ti->m_haschildren ? 1:0;
|
|
pitem->lParam = ti->m_param;
|
|
if ((pitem->mask&TVIF_TEXT)&&pitem->pszText&&pitem->cchTextMax>0)
|
|
{
|
|
lstrcpyn_safe(pitem->pszText,ti->m_value?ti->m_value:"",pitem->cchTextMax);
|
|
}
|
|
pitem->state=(ti == tvs->m_sel ? TVIS_SELECTED : 0) | (ti->m_state & TVIS_EXPANDED);
|
|
|
|
return TRUE;
|
|
}
|
|
|
|
BOOL TreeView_SetItem(HWND hwnd, LPTVITEM pitem)
|
|
{
|
|
treeViewState *tvs = hwnd ? (treeViewState *)hwnd->m_private_data : NULL;
|
|
if (WDL_NOT_NORMALLY(!tvs || !pitem) ||
|
|
!(pitem->mask & TVIF_HANDLE) || !(pitem->hItem)) return FALSE;
|
|
|
|
if (!tvs->findItem(pitem->hItem,NULL,NULL)) return FALSE;
|
|
|
|
HTREEITEM__ *ti = (HTREEITEM__*)pitem->hItem;
|
|
|
|
if (pitem->mask & TVIF_CHILDREN) ti->m_haschildren = pitem->cChildren?1:0;
|
|
if (pitem->mask & TVIF_PARAM) ti->m_param = pitem->lParam;
|
|
|
|
if ((pitem->mask&TVIF_TEXT)&&pitem->pszText)
|
|
{
|
|
free(ti->m_value);
|
|
ti->m_value=strdup(pitem->pszText);
|
|
InvalidateRect(hwnd, 0, FALSE);
|
|
}
|
|
|
|
ti->m_state = (ti->m_state & ~pitem->stateMask) | (pitem->state & pitem->stateMask &~ TVIS_SELECTED);
|
|
|
|
if (pitem->stateMask & pitem->state & TVIS_SELECTED)
|
|
{
|
|
tvs->m_sel = ti;
|
|
static int __rent;
|
|
if (!__rent)
|
|
{
|
|
__rent++;
|
|
NMTREEVIEW nm={{hwnd,(UINT_PTR)hwnd->m_id,TVN_SELCHANGED},};
|
|
nm.itemNew.hItem = ti;
|
|
nm.itemNew.lParam = ti ? ti->m_param : 0;
|
|
SendMessage(GetParent(hwnd),WM_NOTIFY,nm.hdr.idFrom,(LPARAM)&nm);
|
|
__rent--;
|
|
}
|
|
}
|
|
|
|
InvalidateRect(hwnd,NULL,FALSE);
|
|
|
|
return TRUE;
|
|
}
|
|
|
|
HTREEITEM TreeView_HitTest(HWND hwnd, TVHITTESTINFO *hti)
|
|
{
|
|
treeViewState *tvs = hwnd ? (treeViewState *)hwnd->m_private_data : NULL;
|
|
if (WDL_NOT_NORMALLY(!tvs || !hti) ||
|
|
!tvs->m_last_row_height) return NULL;
|
|
|
|
RECT r;
|
|
GetClientRect(hwnd,&r);
|
|
if (!PtInRect(&r,hti->pt)) return NULL;
|
|
|
|
int y = hti->pt.y + tvs->m_scroll_y + tvs->m_last_row_height;
|
|
HTREEITEM item = tvs->hitTestItem(&tvs->m_root,&y,NULL);
|
|
if (!item)
|
|
{
|
|
hti->flags |= TVHT_BELOW;
|
|
}
|
|
return item;
|
|
}
|
|
|
|
HTREEITEM TreeView_GetRoot(HWND hwnd)
|
|
{
|
|
treeViewState *tvs = hwnd ? (treeViewState *)hwnd->m_private_data : NULL;
|
|
if (WDL_NOT_NORMALLY(!tvs)) return NULL;
|
|
return tvs->m_root.m_children.Get(0);
|
|
}
|
|
|
|
HTREEITEM TreeView_GetParent(HWND hwnd, HTREEITEM item)
|
|
{
|
|
if (!item) return TreeView_GetRoot(hwnd);
|
|
treeViewState *tvs = hwnd ? (treeViewState *)hwnd->m_private_data : NULL;
|
|
|
|
HTREEITEM par=NULL;
|
|
int idx=0;
|
|
if (WDL_NOT_NORMALLY(!tvs || !tvs->findItem(item,&par,&idx))) return NULL;
|
|
return par;
|
|
}
|
|
|
|
HTREEITEM TreeView_GetChild(HWND hwnd, HTREEITEM item)
|
|
{
|
|
treeViewState *tvs = hwnd ? (treeViewState *)hwnd->m_private_data : NULL;
|
|
if (WDL_NOT_NORMALLY(!tvs)) return NULL;
|
|
return (item && item != TVI_ROOT ? item : &tvs->m_root)->m_children.Get(0);
|
|
}
|
|
|
|
HTREEITEM TreeView_GetNextSibling(HWND hwnd, HTREEITEM item)
|
|
{
|
|
treeViewState *tvs = hwnd ? (treeViewState *)hwnd->m_private_data : NULL;
|
|
|
|
HTREEITEM par=NULL;
|
|
int idx=0;
|
|
if (WDL_NOT_NORMALLY(!tvs || !tvs->findItem(item,&par,&idx))) return NULL;
|
|
|
|
return (par ? par : &tvs->m_root)->m_children.Get(idx+1);
|
|
}
|
|
BOOL TreeView_SetIndent(HWND hwnd, int indent)
|
|
{
|
|
return FALSE;
|
|
}
|
|
|
|
void TreeView_SetBkColor(HWND hwnd, int color)
|
|
{
|
|
// todo implement treeview colors
|
|
}
|
|
void TreeView_SetTextColor(HWND hwnd, int color)
|
|
{
|
|
// todo implement treeview colors
|
|
}
|
|
|
|
#define IS_CLASSNAME_LISTVIEW_LISTBOX(x) ((x) && (!strcmp(x,"SysListView32") || !strcmp(x,"ListBox")))
|
|
|
|
void ListView_SetBkColor(HWND h, int color)
|
|
{
|
|
if (WDL_NORMALLY(h && h->m_private_data && IS_CLASSNAME_LISTVIEW_LISTBOX(h->m_classname)))
|
|
{
|
|
listViewState *lvs = (listViewState *)h->m_private_data;
|
|
if (lvs) lvs->m_color_bg = color;
|
|
}
|
|
}
|
|
void ListView_SetTextBkColor(HWND h, int color)
|
|
{
|
|
}
|
|
void ListView_SetTextColor(HWND h, int color)
|
|
{
|
|
if (WDL_NORMALLY(h && h->m_private_data && IS_CLASSNAME_LISTVIEW_LISTBOX(h->m_classname)))
|
|
{
|
|
listViewState *lvs = (listViewState *)h->m_private_data;
|
|
lvs->m_color_text = color;
|
|
}
|
|
}
|
|
void ListView_SetGridColor(HWND h, int color)
|
|
{
|
|
if (WDL_NORMALLY(h && h->m_private_data && IS_CLASSNAME_LISTVIEW_LISTBOX(h->m_classname)))
|
|
{
|
|
listViewState *lvs = (listViewState *)h->m_private_data;
|
|
lvs->m_color_grid = color;
|
|
}
|
|
}
|
|
void ListView_SetSelColors(HWND h, int *colors, int ncolors)
|
|
{
|
|
if (WDL_NORMALLY(h && h->m_private_data && h->m_classname))
|
|
{
|
|
if (IS_CLASSNAME_LISTVIEW_LISTBOX(h->m_classname))
|
|
{
|
|
listViewState *lvs = (listViewState *)h->m_private_data;
|
|
if (colors && ncolors > 0)
|
|
memcpy(lvs->m_color_extras,colors,wdl_min(ncolors*sizeof(int),sizeof(lvs->m_color_extras)));
|
|
}
|
|
else if (!strcmp(h->m_classname,"SysTreeView32"))
|
|
{
|
|
// todo implement treeview colors
|
|
}
|
|
else
|
|
{
|
|
WDL_ASSERT(false);
|
|
}
|
|
}
|
|
}
|
|
int ListView_GetTopIndex(HWND h)
|
|
{
|
|
listViewState *lvs = h ? (listViewState *)h->m_private_data : NULL;
|
|
if (WDL_NOT_NORMALLY(!lvs) || !lvs->m_last_row_height) return 0;
|
|
return lvs->m_scroll_y / lvs->m_last_row_height;
|
|
}
|
|
BOOL ListView_GetColumnOrderArray(HWND h, int cnt, int* arr)
|
|
{
|
|
if (WDL_NOT_NORMALLY(!arr)) return FALSE;
|
|
listViewState *lvs = h ? (listViewState *)h->m_private_data : NULL;
|
|
if (WDL_NOT_NORMALLY(!lvs || !lvs->HasColumnHeaders(h))) return FALSE;
|
|
|
|
for (int x=0;x<cnt;x++)
|
|
arr[x]=x < lvs->m_cols.GetSize() ? lvs->m_cols.Get()[x].col_index : x;
|
|
return TRUE;
|
|
}
|
|
BOOL ListView_SetColumnOrderArray(HWND h, int cnt, int* arr)
|
|
{
|
|
if (!arr) return FALSE;
|
|
listViewState *lvs = h ? (listViewState *)h->m_private_data : NULL;
|
|
if (WDL_NOT_NORMALLY(!lvs || !lvs->HasColumnHeaders(h))) return FALSE;
|
|
|
|
WDL_TypedBuf<SWELL_ListView_Col> tmp;
|
|
|
|
int x;
|
|
// O(N^2) but even with 1000 columns who cares?
|
|
for (x = 0; x < cnt; x ++)
|
|
{
|
|
SWELL_ListView_Col *c = lvs->GetColumnByIndex(arr[x]);
|
|
if (WDL_NORMALLY(c != NULL))
|
|
{
|
|
tmp.Add(*c);
|
|
lvs->m_cols.Delete((int) (c - lvs->m_cols.Get()));
|
|
}
|
|
}
|
|
WDL_ASSERT(lvs->m_cols.GetSize()==0);
|
|
for (x = 0; x < tmp.GetSize(); x ++)
|
|
{
|
|
lvs->m_cols.Add(tmp.Get()+x,1);
|
|
}
|
|
|
|
return TRUE;
|
|
}
|
|
HWND ListView_GetHeader(HWND h)
|
|
{
|
|
return h;
|
|
}
|
|
|
|
int Header_GetItemCount(HWND h)
|
|
{
|
|
listViewState *lvs = h ? (listViewState *)h->m_private_data : NULL;
|
|
return WDL_NORMALLY(lvs) ? lvs->m_cols.GetSize() : 0;
|
|
}
|
|
|
|
BOOL Header_GetItem(HWND h, int col, HDITEM* hi)
|
|
{
|
|
listViewState *lvs = h ? (listViewState *)h->m_private_data : NULL;
|
|
if (WDL_NOT_NORMALLY(!lvs)) return FALSE;
|
|
const SWELL_ListView_Col *c = lvs->GetColumnByIndex(col);
|
|
if (!c) return FALSE;
|
|
|
|
if (hi->mask&HDI_FORMAT)
|
|
{
|
|
if (c->sortindicator<0) hi->fmt = HDF_SORTUP;
|
|
else if (c->sortindicator>0) hi->fmt = HDF_SORTDOWN;
|
|
else hi->fmt=0;
|
|
}
|
|
|
|
return TRUE;
|
|
}
|
|
|
|
BOOL Header_SetItem(HWND h, int col, HDITEM* hi)
|
|
{
|
|
listViewState *lvs = h ? (listViewState *)h->m_private_data : NULL;
|
|
if (WDL_NOT_NORMALLY(!lvs)) return FALSE;
|
|
SWELL_ListView_Col *c = lvs->GetColumnByIndex(col);
|
|
if (!c) return FALSE;
|
|
|
|
if (hi->mask&HDI_FORMAT)
|
|
{
|
|
if (hi->fmt & HDF_SORTUP) c->sortindicator=-1;
|
|
else if (hi->fmt & HDF_SORTDOWN) c->sortindicator=1;
|
|
else c->sortindicator=0;
|
|
}
|
|
|
|
return TRUE;
|
|
}
|
|
|
|
|
|
BOOL EnumChildWindows(HWND hwnd, BOOL (*cwEnumFunc)(HWND,LPARAM),LPARAM lParam)
|
|
{
|
|
if (WDL_NORMALLY(hwnd) && hwnd->m_children)
|
|
{
|
|
HWND n=hwnd->m_children;
|
|
while (n)
|
|
{
|
|
if (!cwEnumFunc(n,lParam) || !EnumChildWindows(n,cwEnumFunc,lParam)) return FALSE;
|
|
n = n->m_next;
|
|
}
|
|
}
|
|
return TRUE;
|
|
}
|
|
void SWELL_GetDesiredControlSize(HWND hwnd, RECT *r)
|
|
{
|
|
}
|
|
|
|
BOOL SWELL_IsGroupBox(HWND hwnd)
|
|
{
|
|
return WDL_NORMALLY(hwnd) && hwnd->m_classname && !stricmp(hwnd->m_classname,"Button") && (hwnd->m_style & BS_GROUPBOX);
|
|
}
|
|
BOOL SWELL_IsButton(HWND hwnd)
|
|
{
|
|
return WDL_NORMALLY(hwnd) && hwnd->m_classname && !stricmp(hwnd->m_classname,"Button") && !(hwnd->m_style & BS_GROUPBOX);
|
|
}
|
|
BOOL SWELL_IsStaticText(HWND hwnd)
|
|
{
|
|
return WDL_NORMALLY(hwnd) && hwnd->m_classname && !stricmp(hwnd->m_classname,"Static");
|
|
}
|
|
|
|
|
|
BOOL ShellExecute(HWND hwndDlg, const char *action, const char *content1, const char *content2, const char *content3, int blah)
|
|
{
|
|
const char *xdg = "/usr/bin/xdg-open";
|
|
const char *argv[3] = { NULL };
|
|
char *tmp=NULL;
|
|
|
|
if (!content1 || !*content1) return FALSE;
|
|
|
|
if (!strnicmp(content1,"http://",7) || !strnicmp(content1,"https://",8))
|
|
{
|
|
argv[0] = xdg;
|
|
argv[1] = content1;
|
|
}
|
|
else if (!stricmp(content1,"explorer.exe"))
|
|
{
|
|
const char *fn = content2;
|
|
if (fn && !strnicmp(fn, "/select,\"", 9))
|
|
{
|
|
tmp = strdup(fn+9);
|
|
if (*tmp && tmp[strlen(tmp)-1]=='\"') tmp[strlen(tmp)-1]='\0';
|
|
WDL_remove_filepart(tmp);
|
|
fn = tmp;
|
|
}
|
|
if (!fn || !*fn) return FALSE;
|
|
|
|
argv[0] = xdg;
|
|
argv[1] = fn;
|
|
}
|
|
else if (!stricmp(content1,"notepad.exe")||!stricmp(content1,"notepad"))
|
|
{
|
|
if (!content2 || !*content2) return FALSE;
|
|
argv[0] = xdg;
|
|
argv[1] = content2;
|
|
}
|
|
else
|
|
{
|
|
if (content2 && *content2)
|
|
{
|
|
argv[0] = content1;
|
|
argv[1] = content2;
|
|
}
|
|
else
|
|
{
|
|
argv[0] = xdg;
|
|
argv[1] = content1; // default to xdg-open for whatever else
|
|
}
|
|
}
|
|
|
|
const pid_t pid = fork();
|
|
if (pid == 0)
|
|
{
|
|
for (int x=0;argv[x];x++) argv[x] = strdup(argv[x]);
|
|
execv(argv[0],(char *const*)argv);
|
|
exit(0); // if execv fails for some reason
|
|
}
|
|
free(tmp);
|
|
return pid>0;
|
|
}
|
|
|
|
|
|
|
|
static LRESULT WINAPI focusRectWndProc(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam)
|
|
{
|
|
switch (uMsg)
|
|
{
|
|
case WM_PAINT:
|
|
{
|
|
PAINTSTRUCT ps;
|
|
if (BeginPaint(hwnd,&ps))
|
|
{
|
|
RECT r;
|
|
GetClientRect(hwnd,&r);
|
|
HBRUSH br = CreateSolidBrushAlpha(g_swell_ctheme.focusrect,0.5f);
|
|
HPEN pen = CreatePen(0,PS_SOLID,g_swell_ctheme.focusrect);
|
|
HGDIOBJ oldbr = SelectObject(ps.hdc,br);
|
|
HGDIOBJ oldpen = SelectObject(ps.hdc,pen);
|
|
Rectangle(ps.hdc,0,0,r.right,r.bottom);
|
|
SelectObject(ps.hdc,oldbr);
|
|
SelectObject(ps.hdc,oldpen);
|
|
DeleteObject(br);
|
|
DeleteObject(pen);
|
|
EndPaint(hwnd,&ps);
|
|
}
|
|
}
|
|
return 0;
|
|
|
|
}
|
|
return DefWindowProc(hwnd,uMsg,wParam,lParam);
|
|
}
|
|
|
|
// r=NULL to "free" handle
|
|
// otherwise r is in hwndPar coordinates
|
|
void SWELL_DrawFocusRect(HWND hwndPar, RECT *rct, void **handle)
|
|
{
|
|
if (WDL_NOT_NORMALLY(!handle)) return;
|
|
|
|
HWND h = (HWND) *handle;
|
|
if (h && (!rct || h->m_parent != hwndPar))
|
|
{
|
|
DestroyWindow(h);
|
|
h->Release();
|
|
*handle = NULL;
|
|
h = NULL;
|
|
}
|
|
|
|
if (rct)
|
|
{
|
|
if (!h)
|
|
{
|
|
h = new HWND__(hwndPar,0,rct,"",false,focusRectWndProc);
|
|
h->m_style = WS_CHILD; // using this for top-level will also keep it out of the window list
|
|
h->Retain();
|
|
*handle = h;
|
|
ShowWindow(h,SW_SHOWNA);
|
|
}
|
|
SetWindowPos(h,HWND_TOP,rct->left,rct->top,rct->right-rct->left,rct->bottom-rct->top,SWP_NOACTIVATE);
|
|
InvalidateRect(h,NULL,FALSE);
|
|
}
|
|
|
|
if (hwndPar)
|
|
{
|
|
InvalidateRect(hwndPar,NULL,FALSE);
|
|
UpdateWindow(hwndPar);
|
|
}
|
|
}
|
|
|
|
void SWELL_BroadcastMessage(UINT uMsg, WPARAM wParam, LPARAM lParam)
|
|
{
|
|
HWND h = SWELL_topwindows;
|
|
while (h)
|
|
{
|
|
SendMessage(h,uMsg,wParam,lParam);
|
|
if (uMsg == WM_DISPLAYCHANGE)
|
|
InvalidateRect(h,NULL,FALSE);
|
|
h = h->m_next;
|
|
}
|
|
}
|
|
|
|
void SetOpaque(HWND h, bool opaque)
|
|
{
|
|
}
|
|
void SetAllowNoMiddleManRendering(HWND h, bool allow)
|
|
{
|
|
}
|
|
int SWELL_GetDefaultButtonID(HWND hwndDlg, bool onlyIfEnabled)
|
|
{
|
|
return 0;
|
|
}
|
|
|
|
void SWELL_HideApp()
|
|
{
|
|
}
|
|
|
|
BOOL SWELL_GetGestureInfo(LPARAM lParam, GESTUREINFO* gi)
|
|
{
|
|
return FALSE;
|
|
}
|
|
|
|
void SWELL_SetWindowWantRaiseAmt(HWND h, int amt)
|
|
{
|
|
}
|
|
int SWELL_GetWindowWantRaiseAmt(HWND h)
|
|
{
|
|
return 0;
|
|
}
|
|
|
|
// copied from swell-wnd.mm, can maybe have a common impl instead
|
|
void SWELL_GenerateDialogFromList(const void *_list, int listsz)
|
|
{
|
|
#define SIXFROMLIST list->p1,list->p2,list->p3, list->p4, list->p5, list->p6
|
|
SWELL_DlgResourceEntry *list = (SWELL_DlgResourceEntry*)_list;
|
|
while (listsz>0)
|
|
{
|
|
if (!strcmp(list->str1,"__SWELL_BUTTON"))
|
|
{
|
|
SWELL_MakeButton(list->flag1,list->str2, SIXFROMLIST);
|
|
}
|
|
else if (!strcmp(list->str1,"__SWELL_EDIT"))
|
|
{
|
|
SWELL_MakeEditField(SIXFROMLIST);
|
|
}
|
|
else if (!strcmp(list->str1,"__SWELL_COMBO"))
|
|
{
|
|
SWELL_MakeCombo(SIXFROMLIST);
|
|
}
|
|
else if (!strcmp(list->str1,"__SWELL_LISTBOX"))
|
|
{
|
|
SWELL_MakeListBox(SIXFROMLIST);
|
|
}
|
|
else if (!strcmp(list->str1,"__SWELL_GROUP"))
|
|
{
|
|
SWELL_MakeGroupBox(list->str2,SIXFROMLIST);
|
|
}
|
|
else if (!strcmp(list->str1,"__SWELL_CHECKBOX"))
|
|
{
|
|
SWELL_MakeCheckBox(list->str2,SIXFROMLIST);
|
|
}
|
|
else if (!strcmp(list->str1,"__SWELL_LABEL"))
|
|
{
|
|
SWELL_MakeLabel(list->flag1, list->str2, SIXFROMLIST);
|
|
}
|
|
else if (!strcmp(list->str1,"__SWELL_ICON"))
|
|
{
|
|
// todo (str2 is likely a (const char *)(INT_PTR)resid
|
|
}
|
|
else if (*list->str2)
|
|
{
|
|
SWELL_MakeControl(list->str1, list->flag1, list->str2, SIXFROMLIST);
|
|
}
|
|
listsz--;
|
|
list++;
|
|
}
|
|
}
|
|
|
|
void SWELL_SetClassName(HWND hwnd, const char *p)
|
|
{
|
|
if (WDL_NORMALLY(hwnd))
|
|
hwnd->m_classname=p;
|
|
}
|
|
|
|
int GetClassName(HWND hwnd, char *buf, int bufsz)
|
|
{
|
|
if (WDL_NOT_NORMALLY(!hwnd || !hwnd->m_classname || !buf || bufsz<1)) return 0;
|
|
lstrcpyn_safe(buf,hwnd->m_classname,bufsz);
|
|
return (int)strlen(buf);
|
|
}
|
|
|
|
void SWELL_DisableContextMenu(HWND hwnd, bool dis)
|
|
{
|
|
if (WDL_NORMALLY(hwnd))
|
|
{
|
|
if (!strcmp(hwnd->m_classname,"Edit"))
|
|
{
|
|
__SWELL_editControlState *es = (__SWELL_editControlState*)hwnd->m_private_data;
|
|
if (es) es->m_disable_contextmenu = dis;
|
|
}
|
|
}
|
|
}
|
|
|
|
#ifdef _DEBUG
|
|
void VALIDATE_HWND_LIST(HWND listHead, HWND par)
|
|
{
|
|
if (!listHead) return;
|
|
WDL_ASSERT(!listHead->m_prev);
|
|
WDL_ASSERT(listHead->m_parent == par);
|
|
WDL_ASSERT(listHead->m_next != listHead);
|
|
HWND last = listHead;
|
|
HWND list = listHead->m_next;
|
|
while (list)
|
|
{
|
|
WDL_ASSERT(list->m_prev == last);
|
|
WDL_ASSERT(list->m_parent == par);
|
|
last = list;
|
|
list = list->m_next;
|
|
}
|
|
}
|
|
#endif
|
|
|
|
|
|
LRESULT SWELL_SendMouseMessage(HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam)
|
|
{
|
|
if (!hwnd || !hwnd->m_wndproc) return -1;
|
|
if (!IsWindowEnabled(hwnd))
|
|
{
|
|
if (msg == WM_LBUTTONDOWN || msg == WM_RBUTTONDOWN || msg == WM_MBUTTONDOWN ||
|
|
msg == WM_LBUTTONDBLCLK || msg == WM_RBUTTONDBLCLK || msg == WM_MBUTTONDBLCLK)
|
|
{
|
|
HWND h = DialogBoxIsActive();
|
|
if (h) SetForegroundWindow(h);
|
|
}
|
|
return -1;
|
|
}
|
|
|
|
if (msg == WM_RBUTTONDOWN) s_last_rbuttondown = hwnd;
|
|
|
|
LRESULT htc=0;
|
|
if (msg != WM_MOUSEWHEEL && !GetCapture())
|
|
{
|
|
DWORD p=GetMessagePos();
|
|
|
|
htc=hwnd->m_wndproc(hwnd,WM_NCHITTEST,0,p);
|
|
if (hwnd->m_hashaddestroy||!hwnd->m_wndproc)
|
|
{
|
|
return -1; // if somehow WM_NCHITTEST destroyed us, bail
|
|
}
|
|
|
|
if (htc!=HTCLIENT || swell_window_wants_all_input() == hwnd)
|
|
{
|
|
if (msg==WM_MOUSEMOVE) return hwnd->m_wndproc(hwnd,WM_NCMOUSEMOVE,htc,p);
|
|
// if (msg==WM_MOUSEWHEEL) return hwnd->m_wndproc(hwnd,WM_NCMOUSEWHEEL,htc,p);
|
|
// if (msg==WM_MOUSEHWHEEL) return hwnd->m_wndproc(hwnd,WM_NCMOUSEHWHEEL,htc,p);
|
|
if (msg==WM_LBUTTONUP) return hwnd->m_wndproc(hwnd,WM_NCLBUTTONUP,htc,p);
|
|
if (msg==WM_LBUTTONDOWN) return hwnd->m_wndproc(hwnd,WM_NCLBUTTONDOWN,htc,p);
|
|
if (msg==WM_LBUTTONDBLCLK) return hwnd->m_wndproc(hwnd,WM_NCLBUTTONDBLCLK,htc,p);
|
|
if (msg==WM_RBUTTONUP) return hwnd->m_wndproc(hwnd,WM_NCRBUTTONUP,htc,p);
|
|
if (msg==WM_RBUTTONDOWN) return hwnd->m_wndproc(hwnd,WM_NCRBUTTONDOWN,htc,p);
|
|
if (msg==WM_RBUTTONDBLCLK) return hwnd->m_wndproc(hwnd,WM_NCRBUTTONDBLCLK,htc,p);
|
|
if (msg==WM_MBUTTONUP) return hwnd->m_wndproc(hwnd,WM_NCMBUTTONUP,htc,p);
|
|
if (msg==WM_MBUTTONDOWN) return hwnd->m_wndproc(hwnd,WM_NCMBUTTONDOWN,htc,p);
|
|
if (msg==WM_MBUTTONDBLCLK) return hwnd->m_wndproc(hwnd,WM_NCMBUTTONDBLCLK,htc,p);
|
|
}
|
|
}
|
|
|
|
|
|
LRESULT ret=hwnd->m_wndproc(hwnd,msg,wParam,lParam);
|
|
|
|
if (msg==WM_LBUTTONUP || msg==WM_RBUTTONUP || msg==WM_MOUSEMOVE || msg==WM_MBUTTONUP)
|
|
{
|
|
if (!GetCapture() && (hwnd->m_hashaddestroy || !hwnd->m_wndproc || !hwnd->m_wndproc(hwnd,WM_SETCURSOR,(WPARAM)hwnd,htc | (msg<<16))))
|
|
{
|
|
SetCursor(SWELL_LoadCursor(IDC_ARROW));
|
|
}
|
|
}
|
|
|
|
return ret;
|
|
}
|
|
|
|
#endif
|