mirror of
https://github.com/Alcaro/Flips.git
synced 2026-09-07 10:06:09 -05:00
1397 lines
35 KiB
C++
1397 lines
35 KiB
C++
#include "window.h"
|
|
#ifdef ARGUI_WINDOWS
|
|
#undef bind
|
|
#include <windows.h>
|
|
#define bind bind_func
|
|
#include <windowsx.h>
|
|
#include <commctrl.h>
|
|
#include <shellapi.h>
|
|
#include <stdlib.h>
|
|
|
|
//controls HIG and screenshots of them http://msdn.microsoft.com/en-us/library/dn742399.aspx
|
|
//controls docs http://msdn.microsoft.com/en-us/library/windows/desktop/bb773169%28v=vs.85%29.aspx
|
|
//alternatively http://msdn.microsoft.com/en-us/library/aa368039%28v=vs.85%29.aspx for some widgets
|
|
|
|
//NOTE: Widgets must respond to measure() before init() is called. Do not measure them in init.
|
|
|
|
#define dpi_vert 96
|
|
#define dpi_horz 96
|
|
|
|
#define btn_width 75
|
|
#define btn_height 23
|
|
|
|
#define checkbox_width 17
|
|
#define checkbox_height 17
|
|
|
|
#define radio_width 17
|
|
#define radio_height 17
|
|
|
|
#define frame_top 16
|
|
#define frame_left 4
|
|
#define frame_right 4
|
|
#define frame_bottom 4
|
|
|
|
#define g_padding 3
|
|
|
|
#define TIMER_MOUSEHIDE 1
|
|
|
|
//Keep noninteractive at 0 and defbutton at button+1.
|
|
//Otherwise, no rules for which values mean what; the IDs only mean anything inside this file.
|
|
#define CTID_NONINTERACTIVE 0
|
|
#define CTID_BUTTON 1
|
|
#define CTID_DEFBUTTON 2
|
|
#define CTID_CHECK 3
|
|
#define CTID_RADIO 4
|
|
#define CTID_LISTVIEW 5
|
|
#define CTID_TEXTBOX 6
|
|
|
|
static HFONT dlgfont;
|
|
static unsigned int xwidth;
|
|
|
|
static HBRUSH bg_invalid;
|
|
|
|
//static bool recursive=false;
|
|
|
|
static HFONT try_create_font(const char * name, int size)
|
|
{
|
|
return CreateFont(-MulDiv(size, dpi_vert, 72), 0, 0, 0, FW_NORMAL,
|
|
FALSE, FALSE, FALSE, DEFAULT_CHARSET,
|
|
OUT_DEFAULT_PRECIS, CLIP_DEFAULT_PRECIS, DEFAULT_QUALITY, DEFAULT_PITCH|FF_DONTCARE,
|
|
name);
|
|
}
|
|
|
|
static void measure_text(const char * text, unsigned int * width, unsigned int * height)
|
|
{
|
|
HDC hdc=GetDC(NULL);
|
|
SelectObject(hdc, dlgfont);
|
|
RECT rc={0, 0, 0, 0};
|
|
DrawText(hdc, text, -1, &rc, DT_CALCRECT);
|
|
ReleaseDC(NULL, hdc);
|
|
if (width) *width=rc.right;
|
|
if (height) *height=rc.bottom;
|
|
}
|
|
|
|
void _window_init_inner()
|
|
{
|
|
//HDC hdc=GetDC(NULL);
|
|
//dpiheight=GetDeviceCaps(hdc, LOGPIXELSY);
|
|
|
|
dlgfont=try_create_font("Segoe UI", 9);
|
|
if (!dlgfont) dlgfont=try_create_font("MS Shell Dlg 2", 8);//I'm just gonna expect this one not to fail.
|
|
//If it does, we'll get the bad font; no real catastrophe.
|
|
|
|
// HGDIOBJ prevfont=SelectObject(hdc, dlgfont);
|
|
// TEXTMETRIC metrics;
|
|
// if (GetTextMetrics(hdc, &metrics))
|
|
// {
|
|
////for dialogs: GetDialogBaseUnits http://msdn.microsoft.com/en-us/library/ms645475%28VS.85%29.aspx
|
|
////button height is 14/8 times the font height
|
|
////button width is 50/4 times the average character width
|
|
////can be extracted from http://msdn.microsoft.com/en-us/library/dd144941%28v=vs.85%29.aspx tmHeight and tmAveCharWidth
|
|
// dlgbuttonwidth=MulDiv(metrics.tmAveCharWidth, 50, 4);
|
|
// dlgbuttonheight=MulDiv(metrics.tmHeight, 14, 8);
|
|
// }
|
|
// else
|
|
// {
|
|
// dlgbuttonwidth=80;
|
|
// dlgbuttonheight=20;
|
|
// }
|
|
//"tmAveCharWidth is not precise". fuck that shit, I do the right thing if it's not a pain.
|
|
//actually, the right thing would probably be "fuck Microsoft, more Linux" and delete this file,
|
|
// because that's even less of a pain.
|
|
|
|
//SelectObject(hdc, prevfont);
|
|
//ReleaseDC(NULL, hdc);
|
|
|
|
measure_text("XXXXXXXXXXXX", &xwidth, NULL);
|
|
|
|
INITCOMMONCONTROLSEX initctrls;
|
|
initctrls.dwSize=sizeof(initctrls);
|
|
initctrls.dwICC=ICC_LISTVIEW_CLASSES|ICC_STANDARD_CLASSES;
|
|
InitCommonControlsEx(&initctrls);
|
|
|
|
bg_invalid=CreateSolidBrush(RGB(0xFF,0x66,0x66));
|
|
}
|
|
|
|
static void place_window(HWND hwnd, void* resizeinf, unsigned int x, unsigned int y, unsigned int width, unsigned int height)
|
|
{
|
|
if (resizeinf)
|
|
{
|
|
HDWP* hdwp = (HDWP*)resizeinf;
|
|
*hdwp = DeferWindowPos(*hdwp, hwnd, NULL, x, y, width, height, SWP_NOACTIVATE|SWP_NOCOPYBITS|SWP_NOOWNERZORDER|SWP_NOZORDER);
|
|
}
|
|
else
|
|
{
|
|
SetWindowPos(hwnd, NULL, x, y, width, height, SWP_NOACTIVATE|SWP_NOCOPYBITS|SWP_NOOWNERZORDER|SWP_NOZORDER);
|
|
}
|
|
}
|
|
|
|
|
|
|
|
struct widget_label::impl {
|
|
window* parent;
|
|
HWND hwnd;
|
|
uint8_t state;
|
|
//char padding[7];
|
|
};
|
|
|
|
widget_label::widget_label(const char * text) : m(new impl)
|
|
{
|
|
this->widthprio=1;
|
|
this->heightprio=1;
|
|
|
|
m->hwnd=(HWND)strdup(text);
|
|
measure_text(text, &this->width, &this->height);
|
|
this->width+=g_padding*2; this->height+=g_padding*2;
|
|
m->state=0;
|
|
}
|
|
|
|
unsigned int widget_label::init(window* parent, uintptr_t parenthandle)
|
|
{
|
|
m->parent=parent;
|
|
char* text=(char*)m->hwnd;
|
|
m->hwnd=CreateWindow(WC_STATIC, text, WS_CHILD|WS_VISIBLE|SS_NOPREFIX,
|
|
0, 0, 16, 16, // just some random sizes, we'll resize it in _place()
|
|
(HWND)parenthandle, NULL, GetModuleHandle(NULL), NULL);
|
|
free(text);
|
|
if (m->state==1) EnableWindow(m->hwnd, false);
|
|
m->state=2;
|
|
SendMessage(m->hwnd, WM_SETFONT, (WPARAM)dlgfont, FALSE);
|
|
return 1;
|
|
}
|
|
|
|
void widget_label::measure() {}
|
|
|
|
void widget_label::place(void* resizeinf, unsigned int x, unsigned int y, unsigned int width, unsigned int height)
|
|
{
|
|
x+=g_padding; y+=g_padding; width-=g_padding*2; /*height-=g_padding*2;*/
|
|
place_window(m->hwnd, resizeinf, x,y+(height-this->height)/2, width,this->height);
|
|
}
|
|
|
|
widget_label::~widget_label()
|
|
{
|
|
delete m;
|
|
}
|
|
|
|
widget_label* widget_label::set_enabled(bool enable)
|
|
{
|
|
if (m->state==2) EnableWindow(m->hwnd, enable);
|
|
else m->state=!enable;
|
|
return this;
|
|
}
|
|
|
|
widget_label* widget_label::set_text(const char * text)
|
|
{
|
|
measure_text(text, &this->width, &this->height);
|
|
this->width+=g_padding*2; this->height+=g_padding*2;
|
|
m->parent->_reflow();
|
|
return this;
|
|
}
|
|
|
|
widget_label* widget_label::set_ellipsize(bool ellipsize)
|
|
{
|
|
//puts("FIXME: label_set_ellipsize");
|
|
return this;
|
|
}
|
|
|
|
widget_label* widget_label::set_alignment(int alignment)
|
|
{
|
|
//puts("FIXME: label_set_alignment");
|
|
return this;
|
|
}
|
|
|
|
|
|
|
|
struct widget_button::impl {
|
|
//window* parent;
|
|
HWND hwnd;
|
|
|
|
function<void()> onclick;
|
|
|
|
uint8_t state;
|
|
//char padding[7];
|
|
};
|
|
|
|
widget_button::widget_button(const char * text) : m(new impl)
|
|
{
|
|
this->widthprio=1;
|
|
this->heightprio=1;
|
|
|
|
this->width=btn_width+g_padding*2;
|
|
this->height=btn_height+g_padding*2;
|
|
|
|
m->onclick=NULL;
|
|
m->state=0;
|
|
m->hwnd=(HWND)strdup(text);
|
|
}
|
|
|
|
unsigned int widget_button::init(window* parent, uintptr_t parenthandle)
|
|
{
|
|
char* text=(char*)m->hwnd;
|
|
m->hwnd=CreateWindow(WC_BUTTON, text, WS_CHILD|WS_VISIBLE|WS_TABSTOP, 0, 0, 16, 16,
|
|
(HWND)parenthandle, (HMENU)CTID_BUTTON, GetModuleHandle(NULL), NULL);
|
|
free(text);
|
|
if (m->state==1) EnableWindow(m->hwnd, false);
|
|
m->state=2;
|
|
SetWindowLongPtr(m->hwnd, GWLP_USERDATA, (LONG_PTR)this);
|
|
SendMessage(m->hwnd, WM_SETFONT, (WPARAM)dlgfont, FALSE);
|
|
return 1;
|
|
}
|
|
|
|
void widget_button::measure() {}
|
|
|
|
void widget_button::place(void* resizeinf, unsigned int x, unsigned int y, unsigned int width, unsigned int height)
|
|
{
|
|
x+=g_padding; y+=g_padding; width-=g_padding*2; height-=g_padding*2;
|
|
place_window(m->hwnd, resizeinf, x, y, width, height);
|
|
}
|
|
|
|
widget_button::~widget_button()
|
|
{
|
|
delete m;
|
|
}
|
|
|
|
widget_button* widget_button::set_enabled(bool enable)
|
|
{
|
|
if (m->state&2) EnableWindow(m->hwnd, enable);
|
|
else m->state=!enable;
|
|
return this;
|
|
}
|
|
|
|
widget_button* widget_button::set_text(const char * text)
|
|
{
|
|
SetWindowText(m->hwnd, text);
|
|
return this;
|
|
}
|
|
|
|
widget_button* widget_button::set_onclick(function<void()> onclick)
|
|
{
|
|
m->onclick=onclick;
|
|
return this;
|
|
}
|
|
|
|
|
|
|
|
struct widget_checkbox::impl {
|
|
window* parent;
|
|
HWND hwnd;
|
|
|
|
function<void(bool checked)> onclick;
|
|
|
|
uint8_t state;
|
|
//char padding[7];
|
|
};
|
|
|
|
widget_checkbox::widget_checkbox(const char * text) : m(new impl)
|
|
{
|
|
this->widthprio=1;
|
|
this->heightprio=1;
|
|
|
|
this->width=btn_width+g_padding*2;
|
|
this->height=btn_height+g_padding*2;
|
|
|
|
m->onclick=NULL;
|
|
m->state=0;
|
|
m->hwnd=(HWND)strdup(text);
|
|
}
|
|
|
|
unsigned int widget_checkbox::init(window* parent, uintptr_t parenthandle)
|
|
{
|
|
m->parent=parent;
|
|
char* text=(char*)m->hwnd;
|
|
m->hwnd=CreateWindow(WC_BUTTON, "", WS_CHILD|WS_VISIBLE|WS_TABSTOP|BS_CHECKBOX, 0, 0, 16, 16,
|
|
(HWND)parenthandle, (HMENU)CTID_CHECK, GetModuleHandle(NULL), NULL);
|
|
if (m->state&2) EnableWindow(m->hwnd, false);
|
|
if (m->state&4) Button_SetCheck(m->hwnd, BST_CHECKED);
|
|
m->state=0x01;
|
|
SetWindowLongPtr(m->hwnd, GWLP_USERDATA, (LONG_PTR)this);
|
|
SendMessage(m->hwnd, WM_SETFONT, (WPARAM)dlgfont, FALSE);
|
|
|
|
this->set_text(text);
|
|
free(text);
|
|
return 1;
|
|
}
|
|
|
|
void widget_checkbox::measure() {}
|
|
|
|
void widget_checkbox::place(void* resizeinf, unsigned int x, unsigned int y, unsigned int width, unsigned int height)
|
|
{
|
|
x+=g_padding; y+=g_padding; width-=g_padding*2; height-=g_padding*2;
|
|
place_window(m->hwnd, resizeinf, x, y, width, height);
|
|
}
|
|
|
|
widget_checkbox::~widget_checkbox()
|
|
{
|
|
delete m;
|
|
}
|
|
|
|
widget_checkbox* widget_checkbox::set_enabled(bool enable)
|
|
{
|
|
if (m->state==1) EnableWindow(m->hwnd, enable);
|
|
else m->state=((m->state&0xFD)|(!enable<<1));
|
|
return this;
|
|
}
|
|
|
|
widget_checkbox* widget_checkbox::set_text(const char * text)
|
|
{
|
|
if (m->state==1)
|
|
{
|
|
measure_text(text, &this->width, NULL);
|
|
this->width+=checkbox_width + g_padding*2;
|
|
this->height=checkbox_height + g_padding*2;
|
|
|
|
SetWindowText(m->hwnd, text);
|
|
m->parent->_reflow();
|
|
}
|
|
else
|
|
{
|
|
free((char*)m->hwnd);
|
|
m->hwnd=(HWND)strdup(text);
|
|
}
|
|
return this;
|
|
}
|
|
|
|
bool widget_checkbox::get_state()
|
|
{
|
|
if (m->state==1) return (Button_GetCheck(m->hwnd)==BST_CHECKED);
|
|
else return (m->state&2);
|
|
}
|
|
|
|
widget_checkbox* widget_checkbox::set_state(bool checked)
|
|
{
|
|
if (m->state==1) Button_SetCheck(m->hwnd, checked ? BST_CHECKED : BST_UNCHECKED);
|
|
else m->state=((m->state&0xFB)|(checked<<2));
|
|
return this;
|
|
}
|
|
|
|
widget_checkbox* widget_checkbox::set_onclick(function<void(bool checked)> onclick)
|
|
{
|
|
m->onclick=onclick;
|
|
return this;
|
|
}
|
|
|
|
|
|
|
|
struct widget_radio::impl {
|
|
union {
|
|
struct { //active if !initialized (if init() has not been called)
|
|
char* text;//strdup'd
|
|
bool disabled;
|
|
};
|
|
struct { //active otherwise
|
|
window* parent;
|
|
HWND hwnd;
|
|
};
|
|
};
|
|
|
|
bool initialized;
|
|
//char padding[3];
|
|
|
|
//this can point to itself
|
|
unsigned int id;
|
|
widget_radio* leader;
|
|
|
|
widget_radio* * group;
|
|
|
|
function<void(unsigned int state)> onclick;//can't be put in the union because it has a constructor
|
|
union {
|
|
//which one is active depends on 'id'; if it's nonzero, 'last' is valid
|
|
struct { //active if id==0
|
|
unsigned int active;
|
|
};
|
|
struct { //active otherwise
|
|
bool last;//the 'first' flag is known by checking for id==0
|
|
//char padding[3];
|
|
};
|
|
};
|
|
//char padding[4];
|
|
};
|
|
|
|
widget_radio::widget_radio(const char * text) : m(new impl)
|
|
{
|
|
this->widthprio=1;
|
|
this->heightprio=1;
|
|
|
|
measure_text(text, &this->width, &this->height);
|
|
this->width+=this->height;
|
|
this->width+=g_padding*2;
|
|
|
|
m->leader=NULL;
|
|
m->onclick=NULL;
|
|
m->group=NULL;
|
|
m->active=0;
|
|
|
|
m->initialized=false;
|
|
m->disabled=false;
|
|
m->text=strdup(text);
|
|
}
|
|
|
|
unsigned int widget_radio::init(window* parent, uintptr_t parenthandle)
|
|
{
|
|
bool disabled=m->disabled;
|
|
char* text=m->text;
|
|
|
|
m->parent=parent;
|
|
m->hwnd=CreateWindow(WC_BUTTON, text, WS_CHILD|WS_VISIBLE|WS_TABSTOP|BS_RADIOBUTTON, 0, 0, 16, 16,
|
|
(HWND)parenthandle, (HMENU)CTID_RADIO, GetModuleHandle(NULL), NULL);
|
|
SetWindowLongPtr(m->hwnd, GWLP_USERDATA, (LONG_PTR)this);
|
|
SendMessage(m->hwnd, WM_SETFONT, (WPARAM)dlgfont, FALSE);
|
|
if (m->leader && m->id==m->leader->m->active) Button_SetCheck(m->hwnd, BST_CHECKED);
|
|
if (disabled) EnableWindow(m->hwnd, false);
|
|
|
|
m->initialized=true;
|
|
this->set_text(text);
|
|
free(text);
|
|
|
|
return 1;
|
|
}
|
|
|
|
void widget_radio::measure() {}
|
|
|
|
void widget_radio::place(void* resizeinf, unsigned int x, unsigned int y, unsigned int width, unsigned int height)
|
|
{
|
|
x+=g_padding; width-=g_padding*2;
|
|
if (m->id==0)
|
|
{
|
|
y+=g_padding;
|
|
height-=g_padding;
|
|
}
|
|
else if (m->last)
|
|
{
|
|
height-=g_padding;
|
|
}
|
|
place_window(m->hwnd, resizeinf, x, y, width, height);
|
|
}
|
|
|
|
widget_radio::~widget_radio()
|
|
{
|
|
free(m->group);//this frees NULL on anything except the leader
|
|
delete m;
|
|
}
|
|
|
|
widget_radio* widget_radio::set_enabled(bool enable)
|
|
{
|
|
if (m->initialized) EnableWindow(m->hwnd, enable);
|
|
else m->disabled=!enable;
|
|
return this;
|
|
}
|
|
|
|
widget_radio* widget_radio::set_text(const char * text)
|
|
{
|
|
if (m->initialized)
|
|
{
|
|
measure_text(text, &this->width, NULL);
|
|
this->width+=checkbox_width + g_padding*2;
|
|
this->height=checkbox_height;
|
|
|
|
if (m->id==0 || m->last) this->height+=g_padding;
|
|
|
|
SetWindowText(m->hwnd, text);
|
|
m->parent->_reflow();
|
|
}
|
|
else
|
|
{
|
|
free(m->text);
|
|
m->text=strdup(text);
|
|
}
|
|
return this;
|
|
}
|
|
|
|
widget_radio* widget_radio::group(unsigned int numitems, widget_radio* * group)
|
|
{
|
|
for (unsigned int i=0;i<numitems;i++)
|
|
{
|
|
group[i]->m->leader=this;
|
|
group[i]->m->id=i;
|
|
if (i)
|
|
{
|
|
group[i]->m->last=false;
|
|
if (i==numitems-1)
|
|
{
|
|
group[i]->m->last=true;
|
|
group[i]->height+=g_padding;
|
|
}
|
|
}
|
|
}
|
|
|
|
m->group=malloc(sizeof(widget_radio*)*numitems);
|
|
memcpy(m->group, group, sizeof(widget_radio*)*numitems);
|
|
if (m->initialized) Button_SetCheck(m->hwnd, BST_CHECKED);
|
|
this->height+=g_padding;
|
|
if (m->initialized && m->parent) m->parent->_reflow();
|
|
|
|
return this;
|
|
}
|
|
|
|
unsigned int widget_radio::get_state()
|
|
{
|
|
return m->active;
|
|
}
|
|
|
|
widget_radio* widget_radio::set_state(unsigned int state)
|
|
{
|
|
Button_SetCheck(m->group[m->active]->m->hwnd, BST_UNCHECKED);
|
|
Button_SetCheck(m->group[state]->m->hwnd, BST_CHECKED);
|
|
m->active=state;
|
|
return this;
|
|
}
|
|
|
|
widget_radio* widget_radio::set_onclick(function<void(unsigned int state)> onclick)
|
|
{
|
|
m->onclick=onclick;
|
|
return this;
|
|
}
|
|
|
|
|
|
|
|
struct widget_textbox::impl {
|
|
union {
|
|
struct { // if initialized
|
|
window* parent;
|
|
HWND hwnd;
|
|
};
|
|
struct { // if !initialized
|
|
unsigned int maxchars;
|
|
|
|
bool disabled;
|
|
bool focus;
|
|
//char padding[10];
|
|
};
|
|
};
|
|
|
|
char * text;
|
|
|
|
bool initialized;
|
|
bool invalid;
|
|
//char padding[6];
|
|
|
|
function<void(const char * text)> onchange;
|
|
function<void(const char * text)> onactivate;
|
|
};
|
|
|
|
static LRESULT CALLBACK textbox_SubclassProc(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam,
|
|
UINT_PTR uIdSubclass, DWORD_PTR dwRefData);
|
|
|
|
widget_textbox::widget_textbox() : m(new impl)
|
|
{
|
|
m->initialized=false;
|
|
m->disabled=false;
|
|
m->focus=false;
|
|
m->maxchars=0;
|
|
|
|
this->widthprio=3;
|
|
this->heightprio=1;
|
|
|
|
m->text=NULL;
|
|
m->onchange=NULL;
|
|
m->onactivate=NULL;
|
|
m->invalid=false;
|
|
|
|
measure_text("XXXXXXXXXXXX", NULL, &this->height);
|
|
this->width = 5*xwidth/12 + 6 + g_padding*2;
|
|
this->height += 6 + g_padding*2;
|
|
}
|
|
|
|
unsigned int widget_textbox::init(window* parent, uintptr_t parenthandle)
|
|
{
|
|
HWND hwnd = CreateWindow(WC_EDIT, "", WS_CHILD|WS_VISIBLE|WS_TABSTOP|WS_BORDER|ES_AUTOHSCROLL, 0, 0, 16, 16,
|
|
(HWND)parenthandle, (HMENU)CTID_TEXTBOX, GetModuleHandle(NULL), NULL);
|
|
SetWindowLongPtr(hwnd, GWLP_USERDATA, (LONG_PTR)this);
|
|
SendMessage(hwnd, WM_SETFONT, (WPARAM)dlgfont, FALSE);
|
|
SetWindowSubclass(hwnd, textbox_SubclassProc, 0, 0);
|
|
if (m->disabled) EnableWindow(hwnd, false);
|
|
if (m->focus) SetFocus(hwnd);
|
|
if (m->text) SetWindowText(hwnd, m->text);
|
|
|
|
this->set_length(m->maxchars);
|
|
this->set_invalid(m->invalid);
|
|
|
|
m->initialized=true;
|
|
|
|
m->parent=parent;
|
|
m->hwnd=hwnd;
|
|
return 1;
|
|
}
|
|
|
|
void widget_textbox::measure() {}
|
|
|
|
void widget_textbox::place(void* resizeinf, unsigned int x, unsigned int y, unsigned int width, unsigned int height)
|
|
{
|
|
x+=g_padding; y+=g_padding; width-=g_padding*2; height-=g_padding*2;
|
|
place_window(m->hwnd, resizeinf, x+1,y+1, width-2,height-2);//this math is to get the border to the inside of the widget
|
|
}
|
|
|
|
widget_textbox::~widget_textbox()
|
|
{
|
|
free(m->text);
|
|
delete m;
|
|
}
|
|
|
|
widget_textbox* widget_textbox::set_enabled(bool enable)
|
|
{
|
|
if (m->initialized) EnableWindow(m->hwnd, enable);
|
|
else m->disabled=!enable;
|
|
return this;
|
|
}
|
|
|
|
widget_textbox* widget_textbox::focus()
|
|
{
|
|
SetFocus(m->hwnd);
|
|
return this;
|
|
}
|
|
|
|
const char * widget_textbox::get_text()
|
|
{
|
|
free(m->text);
|
|
unsigned int len=GetWindowTextLength(m->hwnd);
|
|
m->text=malloc(len+1);
|
|
GetWindowText(m->hwnd, m->text, len+1);
|
|
return m->text;
|
|
}
|
|
|
|
widget_textbox* widget_textbox::set_text(const char * text)
|
|
{
|
|
free(m->text);
|
|
m->text=strdup(text);
|
|
if (m->initialized) SetWindowText(m->hwnd, m->text);
|
|
return this;
|
|
}
|
|
|
|
widget_textbox* widget_textbox::set_length(unsigned int maxlen)
|
|
{
|
|
Edit_LimitText(m->hwnd, maxlen);//conveniently, we both chose 0 to mean unlimited
|
|
return this;
|
|
}
|
|
|
|
widget_textbox* widget_textbox::set_width(unsigned int xs)
|
|
{
|
|
this->width = xs*xwidth/12 + 6 + g_padding*2;
|
|
m->parent->_reflow();
|
|
return this;
|
|
}
|
|
|
|
widget_textbox* widget_textbox::set_invalid(bool invalid)
|
|
{
|
|
m->invalid=invalid;
|
|
if (m->initialized)
|
|
{
|
|
InvalidateRect(m->hwnd, NULL, FALSE);
|
|
if (invalid) SetFocus(m->hwnd);
|
|
}
|
|
return this;
|
|
}
|
|
|
|
widget_textbox* widget_textbox::set_onchange(function<void(const char * text)> onchange)
|
|
{
|
|
m->onchange=onchange;
|
|
return this;
|
|
}
|
|
|
|
widget_textbox* widget_textbox::set_onactivate(function<void(const char * text)> onactivate)
|
|
{
|
|
m->onactivate=onactivate;
|
|
return this;
|
|
}
|
|
|
|
|
|
static LRESULT CALLBACK textbox_SubclassProc(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam,
|
|
UINT_PTR uIdSubclass, DWORD_PTR dwRefData)
|
|
{
|
|
widget_textbox* obj=(widget_textbox*)GetWindowLongPtr(hwnd, GWLP_USERDATA);
|
|
switch (uMsg)
|
|
{
|
|
case WM_KEYDOWN:
|
|
if (wParam==VK_RETURN && !(lParam & 0x40000000))
|
|
{
|
|
obj->m->onactivate(obj->get_text());
|
|
return 0;
|
|
}
|
|
break;
|
|
case WM_NCDESTROY:
|
|
RemoveWindowSubclass(hwnd, textbox_SubclassProc, 0);
|
|
break;
|
|
}
|
|
return DefSubclassProc(hwnd, uMsg, wParam, lParam);
|
|
}
|
|
|
|
|
|
|
|
struct widget_canvas_win32;
|
|
|
|
|
|
|
|
struct widget_viewport::impl {
|
|
window* parent;
|
|
HWND hwnd;
|
|
|
|
//bool hide_cursor_user;
|
|
//bool hide_cursor_timer;
|
|
//LPARAM lastmousepos;
|
|
|
|
//function<void(const char * const * filenames)> on_file_drop;
|
|
|
|
unsigned int lastx;
|
|
unsigned int lasty;
|
|
unsigned int lastwidth;
|
|
unsigned int lastheight;
|
|
|
|
function<void(size_t width, size_t height)> onresize;
|
|
function<void()> ondestroy;
|
|
};
|
|
|
|
|
|
|
|
unsigned int widget_viewport::init(window* parent, uintptr_t parenthandle)
|
|
{
|
|
m->parent = parent;
|
|
m->hwnd = NULL;
|
|
//CreateWindow("arlib_viewport", "", WS_CHILD|WS_VISIBLE, 0, 0, 16, 16, // TODO: figure out why this isn't resized properly
|
|
// (HWND)parenthandle, NULL, GetModuleHandle(NULL), NULL); // probably a missed reflow
|
|
SetWindowLongPtr(m->hwnd, GWLP_USERDATA, (LONG_PTR)this);
|
|
return 0;
|
|
}
|
|
|
|
widget_viewport::widget_viewport(unsigned int width, unsigned int height) : m(new impl)
|
|
{
|
|
this->widthprio=0;
|
|
this->heightprio=0;
|
|
|
|
this->width = width;
|
|
this->height = height;
|
|
//no padding here
|
|
|
|
//m->hide_cursor_user=false;
|
|
//m->hide_cursor_timer=true;
|
|
//m->lastmousepos=-1;//random value, just so Valgrind-like tools won't throw.
|
|
//m->on_file_drop=NULL;
|
|
}
|
|
|
|
void widget_viewport::measure() {}
|
|
|
|
void widget_viewport::place(void* resizeinf, unsigned int x, unsigned int y, unsigned int width, unsigned int height)
|
|
{
|
|
m->lastx = x;
|
|
m->lasty = y;
|
|
if (m->hwnd)
|
|
{
|
|
//intentionally doesn't pass resizeinf, onresize() requires the widget resized before being called
|
|
//not sure if I need DeferWindowPos at all, doubt it saves any time on modern systems
|
|
place_window(m->hwnd, NULL, x, y, width, height);
|
|
}
|
|
if (width != m->lastwidth || height != m->lastheight)
|
|
{
|
|
m->lastwidth = width;
|
|
m->lastheight = height;
|
|
m->onresize(width, height);
|
|
}
|
|
}
|
|
|
|
widget_viewport::~widget_viewport()
|
|
{
|
|
m->ondestroy();
|
|
delete m;
|
|
}
|
|
|
|
widget_viewport* widget_viewport::resize(unsigned int width, unsigned int height)
|
|
{
|
|
this->width = width;
|
|
this->height = height;
|
|
if (m->parent->is_visible())
|
|
{
|
|
m->parent->_reflow();
|
|
}
|
|
else
|
|
{
|
|
//video drivers may be sensitive to size of hidden windows, so make sure it's resized
|
|
place(NULL, 0, 0, width, height);
|
|
}
|
|
return this;
|
|
}
|
|
|
|
uintptr_t widget_viewport::get_parent()
|
|
{
|
|
return m->parent->_get_handle();
|
|
}
|
|
|
|
void widget_viewport::set_child(uintptr_t windowhandle, function<void(size_t width, size_t height)> onresize, function<void()> ondestroy)
|
|
{
|
|
m->hwnd = (HWND)windowhandle;
|
|
m->onresize = onresize;
|
|
m->ondestroy = ondestroy;
|
|
|
|
m->lastwidth = -1; // force onresize() to be called
|
|
place(NULL, m->lastx, m->lasty, this->width, this->height);
|
|
}
|
|
|
|
//widget_viewport* widget_viewport::set_hide_cursor(bool hide)
|
|
//{
|
|
// m->hide_cursor_user=hide;
|
|
// if (m->hide_cursor_user && m->hide_cursor_timer) SetCursor(NULL);
|
|
// else SetCursor(LoadCursor(NULL, IDC_ARROW));
|
|
//
|
|
// return this;
|
|
//}
|
|
//
|
|
//widget_viewport* widget_viewport::set_support_drop(function<void(const char * const * filenames)> on_file_drop)
|
|
//{
|
|
// DragAcceptFiles(m->hwnd, TRUE);
|
|
// m->on_file_drop=on_file_drop;
|
|
//
|
|
// return this;
|
|
//}
|
|
|
|
//static LRESULT CALLBACK viewport_WindowProc(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam)
|
|
//{
|
|
// widget_viewport* obj=(widget_viewport*)GetWindowLongPtr(hwnd, GWLP_USERDATA);
|
|
// switch (uMsg)
|
|
// {
|
|
// case WM_MOUSEMOVE:
|
|
// {
|
|
// //windows, what exactly is the point behind the bogus mouse move messages
|
|
// //you clearly know they exist (http://blogs.msdn.com/b/oldnewthing/archive/2009/06/17/9763416.aspx), why not block them?
|
|
// if (lParam==obj->m->lastmousepos) break;
|
|
// obj->m->lastmousepos=lParam;
|
|
//
|
|
// SetTimer(obj->m->hwnd, TIMER_MOUSEHIDE, 1000, NULL);
|
|
// TRACKMOUSEEVENT tme={ sizeof(tme), TME_LEAVE, obj->m->hwnd, HOVER_DEFAULT };
|
|
// TrackMouseEvent(&tme);
|
|
// obj->m->hide_cursor_timer=false;
|
|
// }
|
|
// break;
|
|
// case WM_MOUSELEAVE:
|
|
// case WM_NCMOUSEMOVE:
|
|
// {
|
|
// KillTimer(obj->m->hwnd, TIMER_MOUSEHIDE);
|
|
// obj->m->hide_cursor_timer=false;
|
|
// }
|
|
// break;
|
|
// case WM_TIMER:
|
|
// {
|
|
// if (wParam==TIMER_MOUSEHIDE)
|
|
// {
|
|
// obj->m->hide_cursor_timer=true;
|
|
// if (obj->m->hide_cursor_user) SetCursor(NULL);
|
|
// KillTimer(obj->m->hwnd, TIMER_MOUSEHIDE);
|
|
// }
|
|
// }
|
|
// break;
|
|
// case WM_SETCURSOR:
|
|
// {
|
|
// if (obj->m->hide_cursor_user && obj->m->hide_cursor_timer) SetCursor(NULL);
|
|
// else goto _default;
|
|
// }
|
|
// break;
|
|
// case WM_DROPFILES:
|
|
// {
|
|
// HDROP hdrop=(HDROP)wParam;
|
|
// UINT numfiles=DragQueryFile(hdrop, 0xFFFFFFFF, NULL, 0);//but what if I drop four billion files?
|
|
// char * * filenames=malloc(sizeof(char*)*(numfiles+1));
|
|
// for (UINT i=0;i<numfiles;i++)
|
|
// {
|
|
// UINT len=DragQueryFile(hdrop, i, NULL, 0);
|
|
// filenames[i]=malloc(len+1);
|
|
// DragQueryFile(hdrop, i, filenames[i], len+1);
|
|
// for (unsigned int j=0;filenames[i][j];j++)
|
|
// {
|
|
// if (filenames[i][j]=='\\') filenames[i][j]='/';
|
|
// }
|
|
// }
|
|
// filenames[numfiles]=NULL;
|
|
// DragFinish(hdrop);
|
|
// obj->m->on_file_drop((const char * const *)filenames);
|
|
// for (UINT i=0;i<numfiles;i++) free(filenames[i]);
|
|
// free(filenames);
|
|
// }
|
|
// break;
|
|
// _default:
|
|
// default:
|
|
// return DefWindowProc(hwnd, uMsg, wParam, lParam);
|
|
// }
|
|
// return 0;
|
|
//}
|
|
|
|
|
|
|
|
//http://msdn.microsoft.com/en-us/library/windows/desktop/bb774737(v=vs.85).aspx
|
|
//http://www.codeproject.com/Articles/7891/Using-virtual-lists - codeproject being useful for once
|
|
|
|
#ifndef LVCFMT_FIXED_WIDTH
|
|
#define LVCFMT_FIXED_WIDTH 0x100
|
|
#endif
|
|
|
|
struct widget_listbox_virtual::impl {
|
|
window* parent;
|
|
HWND hwnd;
|
|
|
|
unsigned int rows;
|
|
unsigned int columns;
|
|
|
|
unsigned int columnwidthsum;
|
|
unsigned int * columnwidths;
|
|
|
|
bool initialized;
|
|
bool disabled;
|
|
|
|
bool checkboxes;//oddly placed due to padding
|
|
//char padding[1];
|
|
|
|
function<const char * (size_t row, int column)> get_cell;
|
|
function<size_t(const char * prefix, size_t start, bool up)> search;
|
|
|
|
function<void(size_t row)> onfocuschange;
|
|
function<void(size_t row)> onactivate;
|
|
|
|
function<void(size_t row)> ontoggle;
|
|
};
|
|
|
|
void widget_listbox_virtual::construct(unsigned int numcolumns, const char * * columns)
|
|
{
|
|
m=new impl;
|
|
|
|
this->widthprio=3;
|
|
this->heightprio=3;
|
|
|
|
m->columnwidths=malloc(sizeof(unsigned int)*numcolumns);
|
|
m->columns=numcolumns;
|
|
m->rows=0;
|
|
m->disabled=false;
|
|
m->onfocuschange=NULL;
|
|
m->onactivate=NULL;
|
|
m->checkboxes=NULL;
|
|
|
|
this->width=1+19+1+m->columns*20;
|
|
this->height=1+25+19*0+2;
|
|
|
|
m->hwnd=malloc(sizeof(const char*)*numcolumns);
|
|
memcpy(m->hwnd, columns, sizeof(const char*)*numcolumns);
|
|
|
|
m->initialized=false;
|
|
}
|
|
|
|
unsigned int widget_listbox_virtual::init(window* parent, uintptr_t parenthandle)
|
|
{
|
|
m->parent=parent;
|
|
const char * * columns=(const char**)m->hwnd;
|
|
m->hwnd=CreateWindowEx(WS_EX_CLIENTEDGE, WC_LISTVIEW, "", WS_CHILD|WS_VISIBLE|WS_TABSTOP|LVS_REPORT|LVS_OWNERDATA,
|
|
0, 0, 16, 16, (HWND)parenthandle, (HMENU)CTID_LISTVIEW, GetModuleHandle(NULL), NULL);
|
|
|
|
SetWindowLongPtr(m->hwnd, GWLP_USERDATA, (LONG_PTR)this);
|
|
SendMessage(m->hwnd, WM_SETFONT, (WPARAM)dlgfont, FALSE);
|
|
|
|
LVCOLUMN col;
|
|
col.mask=LVCF_FMT|LVCF_WIDTH|LVCF_TEXT;
|
|
col.fmt=LVCFMT_LEFT|LVCFMT_FIXED_WIDTH;
|
|
col.cx=20;
|
|
for (unsigned int i=0;i<m->columns;i++)
|
|
{
|
|
//measure_text(columns[i], (unsigned int*)&col.cx, NULL);
|
|
//col.cx+=12;
|
|
col.pszText=(char*)columns[i];
|
|
(void)ListView_InsertColumn(m->hwnd, i, &col);
|
|
m->columnwidths[i]=1;
|
|
//this->width+=col.cx;
|
|
}
|
|
m->columnwidthsum=m->columns;
|
|
|
|
if (m->checkboxes) (void)ListView_SetExtendedListViewStyleEx(m->hwnd, LVS_EX_CHECKBOXES, LVS_EX_CHECKBOXES);
|
|
if (m->disabled) EnableWindow(m->hwnd, false);
|
|
ListView_SetItemCountEx(m->hwnd, m->rows, 0);//not redrawing because the window isn't even visible at this point
|
|
|
|
free(columns);
|
|
|
|
m->initialized=true;
|
|
|
|
//pretty sure the listbox has children, but I can't ask how many of those there are. Probably varies if I add checkboxes, too.
|
|
//But I'm not the one resizing them, so I can't do anything with that even if I knew the number.
|
|
return 1;
|
|
}
|
|
|
|
void widget_listbox_virtual::measure() {}
|
|
|
|
static void listbox_resize_column(HWND hwnd, int col, unsigned int width)
|
|
{
|
|
//microsoft, which drugs did you take this time? LVCFMT_FIXED_WIDTH blocks me from resizing from code too!
|
|
LVCOLUMN mkrz;
|
|
mkrz.mask=LVCF_FMT;
|
|
mkrz.fmt=LVCFMT_LEFT;
|
|
LVCOLUMN mknrz;
|
|
mknrz.mask=LVCF_FMT;
|
|
mknrz.fmt=LVCFMT_LEFT|LVCFMT_FIXED_WIDTH;
|
|
|
|
(void)ListView_SetColumn(hwnd, col, &mkrz);
|
|
(void)ListView_SetColumnWidth(hwnd, col, width);
|
|
(void)ListView_SetColumn(hwnd, col, &mknrz);
|
|
}
|
|
|
|
void widget_listbox_virtual::place(void* resizeinf, unsigned int x, unsigned int y, unsigned int width, unsigned int height)
|
|
{
|
|
x+=g_padding; y+=g_padding; width-=g_padding*2; height-=g_padding*2;
|
|
place_window(m->hwnd, resizeinf, x, y, width, height);
|
|
|
|
width-=GetSystemMetrics(SM_CXVSCROLL)+4;
|
|
unsigned char width_div=m->columnwidthsum;
|
|
unsigned int width_frac=0;
|
|
for (unsigned int i=0;i<m->columns;i++)
|
|
{
|
|
width_frac+=m->columnwidths[i]*width;
|
|
unsigned int yourwidth=width_frac/width_div;
|
|
width_frac%=width_div;
|
|
listbox_resize_column(m->hwnd, i, yourwidth);
|
|
}
|
|
}
|
|
|
|
widget_listbox_virtual::~widget_listbox_virtual()
|
|
{
|
|
free(m->columnwidths);
|
|
delete m;
|
|
}
|
|
|
|
widget_listbox_virtual* widget_listbox_virtual::set_enabled(bool enable)
|
|
{
|
|
if (m->initialized) EnableWindow(m->hwnd, enable);
|
|
else m->disabled=!enable;
|
|
return this;
|
|
}
|
|
|
|
widget_listbox_virtual* widget_listbox_virtual::set_num_rows(size_t rows)
|
|
{
|
|
if (rows > 100000000) rows=100000000;//Windows "only" supports 100 million (0x05F5E100); more than that and it gets empty.
|
|
m->rows=rows;
|
|
if (m->initialized)
|
|
{
|
|
ListView_SetItemCountEx(m->hwnd, rows, 0);
|
|
(void)ListView_RedrawItems(m->hwnd, 0, rows-1);
|
|
}
|
|
return this;
|
|
}
|
|
|
|
widget_listbox_virtual* widget_listbox_virtual::set_contents(function<const char * (size_t row, int column)> get_cell,
|
|
function<size_t(const char * prefix, size_t start, bool up)> search)
|
|
{
|
|
m->get_cell = get_cell;
|
|
m->search = search;
|
|
return this;
|
|
}
|
|
|
|
widget_listbox_virtual* widget_listbox_virtual::refresh()
|
|
{
|
|
if (m->initialized)
|
|
{
|
|
(void)ListView_RedrawItems(m->hwnd, 0, m->rows-1);
|
|
}
|
|
return this;
|
|
}
|
|
|
|
#if 0
|
|
widget_listbox_virtual* widget_listbox_virtual::set_size(unsigned int height, const unsigned int * widths, int expand)
|
|
{
|
|
int widthpx=-1;
|
|
int heightpx=1+25+height*19+2;
|
|
|
|
if (widths)
|
|
{
|
|
widthpx=1+19+1;
|
|
m->columnwidthsum=0;
|
|
for (unsigned int i=0;i<m->columns;i++)
|
|
{
|
|
listbox_resize_column(m->hwnd, i, widths[i]*xwidth/12+12+5+(i==0));//why are these columns not resized to the sizes I tell them to resize to
|
|
widthpx+=widths[i]*xwidth/12+12+5;
|
|
m->columnwidths[i]=widths[i];
|
|
m->columnwidthsum+=widths[i];
|
|
}
|
|
}
|
|
|
|
if (expand!=-1)
|
|
{
|
|
LVCOLUMN mkrz;
|
|
mkrz.mask=LVCF_FMT;
|
|
mkrz.fmt=LVCFMT_LEFT;
|
|
(void)ListView_SetColumn(m->hwnd, expand, &mkrz);
|
|
}
|
|
|
|
DWORD widthheight=ListView_ApproximateViewRect(m->hwnd, widthpx, heightpx, height);
|
|
this->width=LOWORD(widthheight);
|
|
this->height=HIWORD(widthheight)-GetSystemMetrics(SM_CYHSCROLL)+2;//microsoft really isn't making this easy for me
|
|
this->width+=g_padding*2; this->height+=g_padding*2;
|
|
//printf("%u->%u %u->%u\n",widthpx,this->i._base.width,heightpx,this->i._base.height);
|
|
|
|
m->parent->_reflow();
|
|
|
|
return this;
|
|
}
|
|
#endif
|
|
|
|
size_t widget_listbox_virtual::get_active_row()
|
|
//TODO: check retval from ListView_GetSelectionMark if there is no selection
|
|
{
|
|
return ListView_GetSelectionMark(m->hwnd);
|
|
}
|
|
|
|
widget_listbox_virtual* widget_listbox_virtual::set_on_focus_change(function<void(size_t row)> onchange)
|
|
{
|
|
m->onfocuschange=onchange;
|
|
return this;
|
|
}
|
|
|
|
widget_listbox_virtual* widget_listbox_virtual::set_onactivate(function<void(size_t row)> onactivate)
|
|
{
|
|
m->onactivate=onactivate;
|
|
return this;
|
|
}
|
|
|
|
widget_listbox_virtual* widget_listbox_virtual::add_checkboxes(function<void(size_t row)> ontoggle)
|
|
{
|
|
m->ontoggle=ontoggle;
|
|
m->checkboxes=true;
|
|
if (m->initialized) (void)ListView_SetExtendedListViewStyleEx(m->hwnd, LVS_EX_CHECKBOXES, LVS_EX_CHECKBOXES);
|
|
return this;
|
|
}
|
|
|
|
static uintptr_t listbox_notify(NMHDR* nmhdr)
|
|
{
|
|
widget_listbox_virtual* obj = (widget_listbox_virtual*)GetWindowLongPtr(nmhdr->hwndFrom, GWLP_USERDATA);
|
|
if (nmhdr->code == LVN_GETDISPINFO)
|
|
{
|
|
LV_DISPINFO* info = (LV_DISPINFO*)nmhdr;
|
|
|
|
unsigned int row = info->item.iItem;
|
|
unsigned int column = info->item.iSubItem;
|
|
|
|
if (info->item.mask & LVIF_TEXT)
|
|
{
|
|
const char * str = obj->m->get_cell(row, column);
|
|
unsigned int len = strlen(str);
|
|
if (len > (unsigned int)info->item.cchTextMax-1) len = info->item.cchTextMax-1;
|
|
memcpy(info->item.pszText, str, len);
|
|
info->item.pszText[len] = '\0';
|
|
}
|
|
|
|
if (info->item.mask & LVIF_IMAGE)
|
|
{
|
|
info->item.iImage = 0;
|
|
info->item.mask |= LVIF_STATE;
|
|
info->item.stateMask = LVIS_STATEIMAGEMASK;
|
|
info->item.state = INDEXTOSTATEIMAGEMASK(obj->m->checkboxes ? obj->m->get_cell(row, -1) ? 2 : 1 : 0);
|
|
}
|
|
}
|
|
if (nmhdr->code==LVN_ODCACHEHINT)
|
|
{
|
|
//ignore
|
|
}
|
|
if (nmhdr->code==LVN_ODFINDITEM)
|
|
{
|
|
NMLVFINDITEM* find=(NMLVFINDITEM*)nmhdr;
|
|
if (!(find->lvfi.flags&LVFI_STRING)) return 0;
|
|
|
|
if (obj->m->search)
|
|
{
|
|
size_t ret=obj->m->search(find->lvfi.psz, find->iStart, false);
|
|
if (ret >= obj->m->rows) ret=(size_t)-1;
|
|
return (uintptr_t)ret;
|
|
}
|
|
else
|
|
{
|
|
return _widget_listbox_search(obj->m->get_cell, obj->m->rows, find->lvfi.psz, find->iStart, false);
|
|
}
|
|
}
|
|
if (nmhdr->code==LVN_KEYDOWN)
|
|
{
|
|
NMLVKEYDOWN* keydown=(NMLVKEYDOWN*)nmhdr;
|
|
if (keydown->wVKey==VK_SPACE && obj->m->checkboxes)
|
|
{
|
|
int row=ListView_GetSelectionMark(obj->m->hwnd);
|
|
if (row!=-1)
|
|
{
|
|
obj->m->ontoggle(row);
|
|
(void)ListView_RedrawItems(obj->m->hwnd, row, row);
|
|
}
|
|
}
|
|
if (keydown->wVKey==VK_RETURN)
|
|
{
|
|
int row=ListView_GetSelectionMark(obj->m->hwnd);
|
|
if (row!=-1) obj->m->onactivate(row);
|
|
}
|
|
}
|
|
if (nmhdr->code==NM_CLICK)
|
|
{
|
|
NMITEMACTIVATE* click=(NMITEMACTIVATE*)nmhdr;
|
|
LVHITTESTINFO hitinfo;
|
|
hitinfo.pt=click->ptAction;
|
|
int row=ListView_HitTest(obj->m->hwnd, &hitinfo);
|
|
if (row!=-1 && (hitinfo.flags & LVHT_ONITEMSTATEICON))
|
|
{
|
|
obj->m->ontoggle(row);
|
|
(void)ListView_RedrawItems(obj->m->hwnd, row, row);
|
|
}
|
|
}
|
|
if (nmhdr->code==NM_DBLCLK)
|
|
{
|
|
NMITEMACTIVATE* click=(NMITEMACTIVATE*)nmhdr;
|
|
LVHITTESTINFO hitinfo;
|
|
hitinfo.pt=click->ptAction;
|
|
int row=ListView_HitTest(obj->m->hwnd, &hitinfo);
|
|
if (row!=-1 && (hitinfo.flags & LVHT_ONITEMLABEL))
|
|
{
|
|
obj->m->onactivate(row);
|
|
}
|
|
}
|
|
if (nmhdr->code==LVN_ITEMCHANGED)
|
|
{
|
|
NMLISTVIEW* change=(NMLISTVIEW*)nmhdr;
|
|
if (!(change->uOldState&LVIS_FOCUSED) && (change->uNewState&LVIS_FOCUSED) && change->iItem>=0)
|
|
{
|
|
obj->m->onfocuschange(change->iItem);
|
|
}
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
|
|
|
|
struct widget_frame::impl {
|
|
bool initialized;
|
|
//char padding[7];
|
|
HWND hwnd;
|
|
widget_base* child;
|
|
};
|
|
|
|
widget_frame::widget_frame(const char * text, widget_base* contents) : m(new impl)
|
|
{
|
|
m->initialized=false;
|
|
m->child=contents;
|
|
m->hwnd=(HWND)strdup(text);
|
|
}
|
|
|
|
unsigned int widget_frame::init(window* parent, uintptr_t parenthandle)
|
|
{
|
|
//this->parent=parent;//this one can't do anything that changes its size
|
|
char * text=(char*)m->hwnd;
|
|
m->hwnd=CreateWindow(WC_BUTTON, text, WS_CHILD|WS_VISIBLE|WS_GROUP|WS_DISABLED|BS_GROUPBOX, 0, 0, 16, 16,
|
|
(HWND)parenthandle, (HMENU)CTID_NONINTERACTIVE, GetModuleHandle(NULL), NULL);
|
|
//SetWindowLongPtr(m->hwnd, GWLP_USERDATA, (LONG_PTR)this);//this one sends no notifications
|
|
SendMessage(m->hwnd, WM_SETFONT, (WPARAM)dlgfont, FALSE);
|
|
free(text);
|
|
m->initialized=true;
|
|
return 1 + m->child->init(parent, parenthandle);
|
|
}
|
|
|
|
void widget_frame::measure()
|
|
{
|
|
m->child->measure();
|
|
this->width=m->child->width + frame_left+frame_right + g_padding*2;
|
|
this->height=m->child->height + frame_top+frame_bottom + g_padding*2;
|
|
this->widthprio=m->child->widthprio;
|
|
this->heightprio=m->child->heightprio;
|
|
}
|
|
|
|
void widget_frame::place(void* resizeinf, unsigned int x, unsigned int y, unsigned int width, unsigned int height)
|
|
{
|
|
x+=g_padding; y+=g_padding; width-=g_padding*2; height-=g_padding*2;
|
|
m->child->place(resizeinf, x+frame_left, y+frame_top, width-frame_left-frame_right, height-frame_top-frame_bottom);
|
|
place_window(m->hwnd, resizeinf, x, y, width, height);
|
|
}
|
|
|
|
widget_frame::~widget_frame()
|
|
{
|
|
delete m->child;
|
|
delete m;
|
|
}
|
|
|
|
widget_frame* widget_frame::set_text(const char * text)
|
|
{
|
|
if (m->initialized) SetWindowText(m->hwnd, text);
|
|
else
|
|
{
|
|
free((char*)m->hwnd);
|
|
m->hwnd=(HWND)strdup(text);
|
|
}
|
|
return this;
|
|
}
|
|
|
|
|
|
|
|
uintptr_t _window_notify_inner(void* notification)
|
|
{
|
|
NMHDR* nmhdr=(NMHDR*)notification;
|
|
switch (nmhdr->idFrom)
|
|
{
|
|
case CTID_NONINTERACTIVE: break;
|
|
case CTID_BUTTON:
|
|
case CTID_DEFBUTTON:
|
|
{
|
|
if (nmhdr->code==BN_CLICKED)
|
|
{
|
|
widget_button* obj=(widget_button*)GetWindowLongPtr(nmhdr->hwndFrom, GWLP_USERDATA);
|
|
obj->m->onclick();
|
|
}
|
|
break;
|
|
}
|
|
case CTID_CHECK:
|
|
{
|
|
if (nmhdr->code==BN_CLICKED)
|
|
{
|
|
widget_checkbox * obj=(widget_checkbox*)GetWindowLongPtr(nmhdr->hwndFrom, GWLP_USERDATA);
|
|
bool state=obj->get_state();
|
|
state=!state;
|
|
obj->set_state(state);
|
|
obj->m->onclick(state);
|
|
}
|
|
break;
|
|
}
|
|
case CTID_RADIO:
|
|
{
|
|
if (nmhdr->code==BN_CLICKED)
|
|
{
|
|
widget_radio* obj=(widget_radio*)GetWindowLongPtr(nmhdr->hwndFrom, GWLP_USERDATA);
|
|
widget_radio* leader=obj->m->leader;
|
|
leader->set_state(obj->m->id);
|
|
|
|
leader->m->onclick(obj->m->id);
|
|
}
|
|
break;
|
|
}
|
|
case CTID_TEXTBOX:
|
|
{
|
|
if (nmhdr->code==EN_CHANGE)
|
|
{
|
|
widget_textbox* obj=(widget_textbox*)GetWindowLongPtr(nmhdr->hwndFrom, GWLP_USERDATA);
|
|
if (obj->m->invalid)
|
|
{
|
|
obj->m->invalid=false;
|
|
InvalidateRect(obj->m->hwnd, NULL, FALSE);
|
|
}
|
|
if (obj->m->onchange)
|
|
{
|
|
obj->m->onchange(obj->get_text());
|
|
}
|
|
}
|
|
break;
|
|
}
|
|
case CTID_LISTVIEW:
|
|
{
|
|
return listbox_notify(nmhdr);
|
|
break;
|
|
}
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
uintptr_t _window_get_widget_color(unsigned int type, void* handle, void* draw, void* parent)
|
|
{
|
|
switch (GetDlgCtrlID((HWND)handle))
|
|
{
|
|
case CTID_TEXTBOX:
|
|
{
|
|
widget_textbox* obj=(widget_textbox*)GetWindowLongPtr((HWND)handle, GWLP_USERDATA);
|
|
if (obj->m->invalid)
|
|
{
|
|
SetBkMode((HDC)draw, TRANSPARENT);
|
|
return (LRESULT)bg_invalid;
|
|
}
|
|
break;
|
|
}
|
|
}
|
|
return DefWindowProcA((HWND)parent, (UINT)type, (WPARAM)draw, (LPARAM)handle);
|
|
}
|
|
#endif
|