826 lines
30 KiB
D
826 lines
30 KiB
D
/* MinWin Window class
|
|
*
|
|
* An AbstractWindow is a top-level window. Concrete subclasses
|
|
* are Window and Dialog.
|
|
*
|
|
* Written by Ben Hinkle and released to the public domain, as
|
|
* explained at http://creativecommons.org/licenses/publicdomain
|
|
* Report comments and bugs at dsource: http://www.dsource.org/projects/minwin
|
|
*/
|
|
|
|
module minwin.window;
|
|
|
|
private {
|
|
import std.string;
|
|
import std.c.string;
|
|
import minwin.peerimpl;
|
|
import minwin.font;
|
|
import minwin.event;
|
|
import minwin.component;
|
|
import minwin.menu;
|
|
import minwin.app;
|
|
import minwin.paint;
|
|
import minwin.logging;
|
|
import minwin.icon;
|
|
import minwin.image;
|
|
}
|
|
|
|
int DefaultWindowWidth = 400;
|
|
int DefaultWindowHeight = 300;
|
|
Icon DefaultWindowIcon;
|
|
Icon DefaultWindowSmallIcon;
|
|
|
|
version (MinWin32) {
|
|
public import std.utf : toUTF16z, toUTF16, toUTF8;
|
|
}
|
|
|
|
template CommonEventSourceImpl() {
|
|
version (MinWin32) {
|
|
HBRUSH backgroundPeer;
|
|
void repaintNow() {
|
|
UpdateWindow(peer);
|
|
}
|
|
|
|
GContext getGContext() {
|
|
GContext gc = newGContext();
|
|
gc.peer = GetDC(peer);
|
|
sysAssert(gc.peer !is null, "Failed to get DC in getGContext");
|
|
gc.hasPeer = OWNS_PEER;
|
|
gc.release = true;
|
|
gc.hwnd = peer;
|
|
return gc;
|
|
}
|
|
|
|
Image getCompatibleImage(int width, int height) {
|
|
HDC dc = GetDC(peer);
|
|
sysAssert(dc !is null, "Failed to get DC in getCompatibleImage");
|
|
ImagePeer bm = CreateCompatibleBitmap(dc,width,height);
|
|
sysAssert(bm !is null, "Failed to create compatible bitmap");
|
|
version(LOG) log.writefln("create bitmap %x",cast(int)bm);
|
|
Image res = new Image(bm);
|
|
res.width = width;
|
|
res.height = height;
|
|
res.hasPeer = OWNS_PEER;
|
|
ReleaseDC(peer,dc);
|
|
return res;
|
|
}
|
|
|
|
Image loadCompatibleImage(char[] imageKey, char[] fmt = "bmp") {
|
|
// TODO: use fmt
|
|
ImagePeer bm;
|
|
if (useWfuncs)
|
|
bm = LoadImageW(gApp.hInstance,toUTF16z(imageKey),IMAGE_BITMAP,0,0,0);
|
|
else
|
|
bm = LoadImageA(gApp.hInstance,toMBSz(imageKey),IMAGE_BITMAP,0,0,0);
|
|
sysAssert(bm !is null, format("Failed to load image %s",imageKey));
|
|
BITMAP bm_data;
|
|
int ok = GetObjectA(bm,bm_data.sizeof,&bm_data);
|
|
sysAssert(ok != false, "Failed to get bitmap data in loadCompatibleImage");
|
|
version(LOG) log.writefln("create bitmap %x",cast(int)bm);
|
|
Image res = new Image(bm);
|
|
res.width = bm_data.bmWidth;
|
|
res.height = bm_data.bmHeight;
|
|
res.hasPeer = OWNS_PEER;
|
|
return res;
|
|
}
|
|
|
|
} else version (GTK) {
|
|
void repaintNow(Event* paintEvent = null) {
|
|
layout(true);
|
|
if (!paintDelegate.isEmpty) {
|
|
GContext gc = getGContext(paintEvent);
|
|
paintDelegate(this,gc);
|
|
gc.dispose();
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
template CommonWindowImpl() {
|
|
static AbstractWindow[AbstractWindow] WindowList;
|
|
bool quitOnDestroy;
|
|
|
|
void doCommand(int cmd) {
|
|
commandDelegate(this,cmd);
|
|
}
|
|
|
|
MultiDelegate!(Component, GContext) paintDelegate;
|
|
MultiDelegate!(Component, GXContext) paintXDelegate;
|
|
MultiDelegate!(Component, int) commandDelegate;
|
|
MultiBoolDelegate!(Component) cancelCloseDelegate; // don't quit if bool is true
|
|
|
|
// events
|
|
MultiDelegate!(Component, KeyEvent*) keyDelegate;
|
|
MultiDelegate!(Component, MouseEvent*) mouseDelegate;
|
|
MultiDelegate!(Component, WindowEvent*) windowDelegate;
|
|
|
|
Point preferredSize() {
|
|
Point s;
|
|
if (layoutMgr) {
|
|
s = layoutMgr.preferredSize(this);
|
|
} else {
|
|
s.x = DefaultWindowWidth;
|
|
s.y = DefaultWindowHeight;
|
|
}
|
|
if (userPreferredWidth > 0)
|
|
s.x = userPreferredWidth;
|
|
if (userPreferredHeight > 0)
|
|
s.y = userPreferredHeight;
|
|
return s;
|
|
}
|
|
}
|
|
|
|
version (MinWin32) {
|
|
|
|
private import minwin.mswindows;
|
|
|
|
alias HWND WindowPeer;
|
|
|
|
const int MinWinWindowStyle = WS_OVERLAPPEDWINDOW;
|
|
|
|
// Get the Window object associated with the given peer.
|
|
// The peer is accessed from the Window using the peer property.
|
|
AbstractWindow peerToWindow(WindowPeer peer) {
|
|
return cast(AbstractWindow)cast(void*)GetWindowLongA(peer,GWL_USERDATA);
|
|
}
|
|
// Associate c with the peer. This overwrites the window UserData.
|
|
void setWindowPeer(AbstractWindow c, WindowPeer peer, int peerState) {
|
|
SetWindowLongA(peer,GWL_USERDATA,cast(int)cast(void*)c);
|
|
c.hasPeer = peerState;
|
|
}
|
|
class AbstractWindow : Component {
|
|
WindowPeer peer;
|
|
MenuBar menubar;
|
|
|
|
mixin CommonWindowImpl!();
|
|
|
|
void title(char[] str) {
|
|
BOOL ok;
|
|
if (useWfuncs)
|
|
ok = SetWindowTextW(peer,toUTF16z(str));
|
|
else
|
|
ok = SetWindowTextA(peer,toMBSz(str));
|
|
sysAssert(ok != false, "Failed to set window title");
|
|
}
|
|
|
|
char[] title() {
|
|
if (useWfuncs) {
|
|
wchar[64] res;
|
|
int len = GetWindowTextW(peer,res.ptr,res.length);
|
|
return toUTF8(res[0..len]);
|
|
}
|
|
else {
|
|
char[64] res;
|
|
int len = GetWindowTextA(peer,res.ptr,res.length);
|
|
return fromMBSz(res[0..len].ptr);
|
|
}
|
|
}
|
|
|
|
PeerForAdd getPeerForAdd() { return peer; }
|
|
|
|
void disposePeer() {
|
|
if (hasPeer == OWNS_PEER) {
|
|
BOOL ok = DestroyWindow(peer);
|
|
sysAssert(ok != false, "Failed to destroy window");
|
|
}
|
|
hasPeer = NO_PEER;
|
|
//delete WindowList[this]; // remove global reference
|
|
WindowList.remove(this); // remove global reference
|
|
}
|
|
|
|
void close() {
|
|
SendMessageA(peer,WM_CLOSE,0,0);
|
|
}
|
|
|
|
void visible(bool vis) {
|
|
ShowWindow(peer,vis ? SW_SHOW : SW_HIDE); // ignore bool result
|
|
}
|
|
|
|
bool visible() {
|
|
return IsWindowVisible(peer) != 0;
|
|
}
|
|
|
|
void enabled(bool ena) {
|
|
EnableWindow(peer, ena);
|
|
}
|
|
bool enabled() {
|
|
return IsWindowEnabled(peer) == TRUE;
|
|
}
|
|
|
|
void backgroundColor(Color c) {
|
|
version(LOG) log.writefln("making background %d %d %d",
|
|
cast(int)c.red,
|
|
cast(int)c.green,
|
|
cast(int)c.blue);
|
|
if (backgroundPeer)
|
|
DeleteObject(backgroundPeer);
|
|
backgroundPeer = CreateSolidBrush(c.native);
|
|
// use GetSysColorBrush?
|
|
}
|
|
|
|
// returns client bounds in screen coordinates
|
|
void getBounds(inout Rect r) {
|
|
BOOL ok = GetClientRect(peer,&r.native);
|
|
sysAssert(ok != false, "Failed to get client rect");
|
|
Point pt = XY(r.left,r.top);
|
|
ClientToScreen(peer,&pt.native);
|
|
int x,y;
|
|
if (parent) parent.getPeerOffset(x,y);
|
|
r.LTWH(pt.x+x,pt.y+y,r.width,r.height);
|
|
}
|
|
|
|
// should be the same as AdjustWindowRect but it seems like
|
|
// that doesn't account for the title bar.
|
|
protected final void adjustBounds(inout Rect r) {
|
|
BOOL ok = AdjustWindowRect(&r.native,MinWinWindowStyle,menubar !is null);
|
|
sysAssert(ok != false, "Failed to adjust window bounds");
|
|
}
|
|
|
|
void setBounds(inout Rect r) {
|
|
Rect r2 = r;
|
|
adjustBounds(r2);
|
|
int x,y;
|
|
if (parent) parent.getPeerOffset(x,y);
|
|
BOOL ok = MoveWindow(peer,r2.left+x,r2.top+y,r2.width,r2.height,true);
|
|
sysAssert(ok != false, "Failed to move window in setBounds");
|
|
childLayoutDirty = true;
|
|
}
|
|
|
|
alias Component.size size;
|
|
void size(Point s) {
|
|
Rect r;
|
|
getBounds(r);
|
|
r.width = s.x;
|
|
r.height = s.y;
|
|
adjustBounds(r);
|
|
BOOL ok = MoveWindow(peer,r.left,r.top,r.width,r.height,true);
|
|
sysAssert(ok != false, "Failed to move window in size");
|
|
childLayoutDirty = true;
|
|
}
|
|
|
|
void toFront() {
|
|
BOOL ok = BringWindowToTop(peer);
|
|
sysAssert(ok != false, "Failed to bring window to front");
|
|
}
|
|
|
|
// mark as needing a repaint and post event to event queue
|
|
void repaint() {
|
|
InvalidateRect(peer,null,clearBackgroundOnPaint);
|
|
super.repaint();
|
|
}
|
|
|
|
void repaint(inout Rect r) {
|
|
InvalidateRect(peer,&r.native,clearBackgroundOnPaint);
|
|
}
|
|
|
|
mixin CommonEventSourceImpl!();
|
|
|
|
int WindowProc(HWND hWnd, uint uMsg, WPARAM wParam, LPARAM lParam) {
|
|
//version (LOG) log.printf(" in windowproc msg %x LowW %x HiW %x L %x\n",
|
|
//uMsg,cast(int)LOWORD(wParam),cast(int)HIWORD(wParam),lParam);
|
|
bool doDefault = true;
|
|
if (uMsg <= WM_KEYLAST && uMsg >= WM_KEYFIRST) {
|
|
// version(LOG) log.writefln("got key event %d %d",uMsg,wParam);
|
|
keyDelegate(this, cast(KeyEvent*)&gApp.event);
|
|
} else if (uMsg <= WM_MOUSELAST && uMsg >= WM_MOUSEFIRST) {
|
|
mouseDelegate(this, cast(MouseEvent*)&gApp.event);
|
|
} else {
|
|
switch (uMsg) {
|
|
case WM_COMMAND:
|
|
// version (LOG) log.printf(" got command %x %x from %x\n",
|
|
// HIWORD(wParam),LOWORD(wParam),lParam);
|
|
Component c = peerToWindowChild(cast(HWND)lParam);
|
|
// version (LOG) log.printf(" got pointer %p\n",c);
|
|
if (c !is null) {
|
|
c.doCommand(wParam);
|
|
} else {
|
|
doCommand(LOWORD(wParam));
|
|
}
|
|
break;
|
|
case WM_PAINT:
|
|
// version (LOG) log.writefln(" got paint");
|
|
layout(true);
|
|
GContext gc = newGContext();
|
|
gc.peer = BeginPaint(peer, &gc.paintstruct);
|
|
sysAssert(gc.peer !is null, "Failed to get DC in repaintNow");
|
|
// version(LOG)log.writefln("Paint rect: %s",toRect(gc.paintstruct.rcPaint).toString);
|
|
gc.hasPeer = FOREIGN_PEER; // don't call DisposeDC
|
|
if (clearBackgroundOnPaint && backgroundPeer) {
|
|
Point s = super.size;
|
|
Rect r = LTWH(0,0,s.x,s.y);
|
|
// version(LOG)log.writefln("painting background %d %d",width,height);
|
|
FillRect(gc.peer,&r.native,backgroundPeer);
|
|
}
|
|
if (!paintDelegate.isEmpty) {
|
|
paintDelegate(this,gc);
|
|
}
|
|
EndPaint(peer, &gc.paintstruct);
|
|
gc.dispose();
|
|
break;
|
|
/*
|
|
case CBN_DROPDOWN:
|
|
// TODO: doesn't work!
|
|
Rect r;
|
|
HWND ctrl = cast(HWND)lParam;
|
|
BOOL ok = GetWindowRect(ctrl,&r.native);
|
|
// sysAssert(ok != false, "Failed to get drop down bounds");
|
|
|
|
// BOOL ok = SendMessageA(ctrl,CB_GETDROPPEDCONTROLRECT,0,cast(LPARAM)&r);
|
|
// sysAssert(ok != false, "Failed to get drop down bounds");
|
|
int height1 = SendMessageA(ctrl,CB_GETITEMHEIGHT,cast(WPARAM)-1,0);
|
|
int n = SendMessageA(ctrl,CB_GETITEMHEIGHT,cast(WPARAM)0,0);
|
|
version(LOG)log.writefln("got drop down command %d %d",height1,n);
|
|
// MoveWindow(peer,rectL(r),rectR(r),rectW(r),height1+n,false);
|
|
// MoveWindow(ctrl,rectL(r),rectR(r),rectW(r),rectH(r),false);
|
|
break;
|
|
*/
|
|
case WM_SIZE:
|
|
case WM_SIZING:
|
|
// version (LOG) log.writefln(" got size");
|
|
childLayoutDirty = true;
|
|
if (!windowDelegate.isEmpty) {
|
|
WindowEvent event;
|
|
event.native.hwnd = hWnd;
|
|
event.native.wParam = wParam;
|
|
event.native.lParam = lParam;
|
|
event.native.message = uMsg;
|
|
windowDelegate(this, &event);
|
|
}
|
|
break;
|
|
case WM_MOVE:
|
|
case WM_MOVING:
|
|
// version (LOG) log.writefln(" got move");
|
|
if (!windowDelegate.isEmpty) {
|
|
WindowEvent event;
|
|
event.native.hwnd = hWnd;
|
|
event.native.wParam = wParam;
|
|
event.native.lParam = lParam;
|
|
event.native.message = uMsg;
|
|
windowDelegate(this, &event);
|
|
}
|
|
break;
|
|
case WM_CLOSE:
|
|
version (LOG) log.writefln(" got close");
|
|
doDefault = !cancelCloseDelegate(this);
|
|
break;
|
|
case WM_DESTROY:
|
|
version (LOG) log.writefln(" got destroy");
|
|
hasPeer = NO_PEER;
|
|
if (quitOnDestroy)
|
|
gApp.exitEventLoop();
|
|
break;
|
|
default:
|
|
// version (LOG) log.writefln(" got default");
|
|
break;
|
|
}
|
|
}
|
|
return doDefault;
|
|
}
|
|
}
|
|
|
|
class Window : AbstractWindow {
|
|
|
|
this(char[] title = "", char[] name = "") {
|
|
peer = CreateWindowX("MinWinWindow", title,
|
|
MinWinWindowStyle,
|
|
CW_USEDEFAULT, CW_USEDEFAULT,
|
|
DefaultWindowWidth, DefaultWindowHeight,
|
|
HWND_DESKTOP,
|
|
cast(HMENU) null, gApp.hInstance, null);
|
|
sysAssert(peer !is null, "Failed to create peer Window");
|
|
this.name = name;
|
|
setWindowPeer(this,peer,OWNS_PEER);
|
|
backgroundPeer = GetStockObject(WHITE_BRUSH);
|
|
WindowList[this] = this; // prevent garbage collection
|
|
}
|
|
|
|
this(WindowPeer peer) {
|
|
setWindowPeer(this,peer,FOREIGN_PEER);
|
|
WindowList[this] = this; // prevent garbage collection
|
|
}
|
|
}
|
|
extern(Windows)
|
|
int MinWinWindowProc(HWND hWnd, uint uMsg, WPARAM wParam, LPARAM lParam) {
|
|
// version(LOG) log.printf("in minwin window proc hwnd %p\n",hWnd);
|
|
bool doDefault = true;
|
|
AbstractWindow win = cast(AbstractWindow)peerToWindow(hWnd);
|
|
// version(LOG) log.printf("in minwin window proc win %p\n",win);
|
|
if (win !is null)
|
|
doDefault = win.WindowProc(hWnd,uMsg,wParam,lParam) != 0;
|
|
if (doDefault)
|
|
return DefWindowProcA(hWnd, uMsg, wParam, lParam);
|
|
else
|
|
return 0;
|
|
}
|
|
static this() {
|
|
if (DefaultWindowIcon is null) {
|
|
IconPeer ip = LoadIconA(cast(HINSTANCE) null, IDI_APPLICATION);
|
|
DefaultWindowIcon = DefaultWindowSmallIcon = new Icon(ip);
|
|
}
|
|
HINSTANCE hInst = GetModuleHandleA(null);
|
|
WNDCLASSA wc;
|
|
wc.lpszClassName = "MinWinWindow";
|
|
wc.style = CS_OWNDC | CS_HREDRAW | CS_VREDRAW;
|
|
wc.lpfnWndProc = &MinWinWindowProc;
|
|
wc.hInstance = hInst;
|
|
wc.hIcon = DefaultWindowIcon.peer;
|
|
// wc.hIconSm = DefaultWindowSmallIcon.peer;
|
|
wc.hCursor = LoadCursorA(cast(HINSTANCE) null, IDC_ARROW);
|
|
wc.hbrBackground = null;
|
|
// wc.hbrBackground = cast(HBRUSH) (COLOR_WINDOW + 1); // not +1 for default
|
|
wc.lpszMenuName = null;
|
|
wc.cbClsExtra = 0;
|
|
wc.cbWndExtra = 0;
|
|
RegisterClassA(&wc);
|
|
}
|
|
|
|
} else version (GTK) {
|
|
|
|
private import minwin.gtk;
|
|
private import minwin.gtk_peers;
|
|
|
|
alias GtkWindow* WindowPeer;
|
|
|
|
class AbstractWindow : Component {
|
|
WindowPeer peer;
|
|
MinWinGtkPeer* content;
|
|
MenuBar menubar;
|
|
|
|
mixin CommonWindowImpl!();
|
|
|
|
void title(char[] str) {
|
|
gtk_window_set_title(peer,toStringz(str));
|
|
}
|
|
|
|
char[] title() {
|
|
char* cstr = gtk_window_get_title(peer);
|
|
if (cstr is null)
|
|
return "";
|
|
return cstr[0..strlen(cstr)].dup;
|
|
}
|
|
|
|
// TODO
|
|
void backgroundColor(Color c) {
|
|
GtkWidget* wid = cast(GtkWidget*)peer;
|
|
GdkColor* nc = toNativeColor(c);
|
|
GtkStyle* style = gtk_widget_get_style(wid);
|
|
style.bg[GtkStateType.GTK_STATE_NORMAL] = *nc;
|
|
gtk_widget_set_style(wid,style);
|
|
}
|
|
|
|
// callback from the GTK peer asking for preferred size
|
|
private void gtkRequest(GtkWidget *w, GtkRequisition* req) {
|
|
version(LOG)log.writefln("gtkRequest for toplevel window");
|
|
if (layoutMgr) {
|
|
Point s = layoutMgr.preferredSize(this);
|
|
req.width = s.x;
|
|
req.height = s.y;
|
|
} else {
|
|
req.width = 0;
|
|
req.height = 0;
|
|
}
|
|
version(LOG)log.writefln("done gtkRequest for toplevel window");
|
|
}
|
|
|
|
// callback from GTK peer telling us our position
|
|
private void gtkAllocate(GtkWidget *w, GtkAllocation* req) {
|
|
version(LOG)log.writefln("gtkAllocate for toplevel window");
|
|
Rect r = toRect(*req);
|
|
this.size = XY(r.width,r.height);
|
|
if (layoutMgr) {
|
|
layoutMgr.layout(this);
|
|
}
|
|
childLayoutDirty = false;
|
|
version(LOG)log.writefln("done gtkAllocate for toplevel window");
|
|
}
|
|
|
|
PeerForAdd getPeerForAdd() { return cast(GtkWidget*)content; }
|
|
|
|
void disposePeer() {
|
|
if (hasPeer == OWNS_PEER) {
|
|
gtk_widget_destroy(cast(GtkWidget*)peer);
|
|
}
|
|
hasPeer = NO_PEER;
|
|
//delete WindowList[this]; // remove global reference
|
|
WindowList.remove(this); // remove global reference
|
|
}
|
|
|
|
void close() {
|
|
if (!cancelCloseDelegate(this)) {
|
|
gtk_widget_destroy(cast(GtkWidget*)peer);
|
|
}
|
|
}
|
|
|
|
void resizable(bool vis) {
|
|
gtk_window_set_resizable(peer,vis);
|
|
}
|
|
|
|
bool resizable() {
|
|
return gtk_window_get_resizable(peer) != 0;
|
|
}
|
|
|
|
void visible(bool vis) {
|
|
if (vis) {
|
|
gtk_widget_show(cast(GtkWidget*)peer);
|
|
} else {
|
|
gtk_widget_hide(cast(GtkWidget*)peer);
|
|
}
|
|
}
|
|
|
|
bool visible() {
|
|
GtkObject* obj = cast(GtkObject*)peer;
|
|
return ((obj.flags & GtkWidgetFlags.GTK_VISIBLE) != 0);
|
|
}
|
|
|
|
void enabled(bool b) {
|
|
gtk_widget_set_sensitive(cast(GtkWidget*)peer,cast(int)b);
|
|
}
|
|
bool enabled() {
|
|
GtkObject* obj = cast(GtkObject*)peer;
|
|
return ((obj.flags & GtkWidgetFlags.GTK_SENSITIVE) != 0);
|
|
}
|
|
|
|
void getPeerOffset(inout int x, inout int y) {
|
|
x = 0;
|
|
y = 0;
|
|
if (menubar !is null) {
|
|
GtkRequisition req;
|
|
gtk_widget_size_request(menubar.peer,&req);
|
|
y = req.height;
|
|
}
|
|
}
|
|
|
|
override void layout(bool validateParent) {
|
|
if (parent && validateParent)
|
|
parent.layout(true);
|
|
if (menubar !is null) {
|
|
positionMenuBar();
|
|
}
|
|
if (childLayoutDirty) {
|
|
if (layoutMgr)
|
|
layoutMgr.layout(this);
|
|
childLayoutDirty = false;
|
|
}
|
|
foreach( Component ch; this) {
|
|
ch.layout(false);
|
|
}
|
|
}
|
|
|
|
void positionMenuBar() {
|
|
Rect r;
|
|
getBounds(r);
|
|
MenuBarPeer w = menubar.peer;
|
|
GtkRequisition req;
|
|
gtk_widget_size_request(menubar.peer,&req);
|
|
r.height = req.height;
|
|
gtk_widget_size_allocate(w,&r.native);
|
|
}
|
|
|
|
void getBounds(inout Rect r) {
|
|
int x,y;
|
|
r = toRect((cast(GtkWidget*)peer).allocation);
|
|
if (parent) parent.getPeerOffset(x,y);
|
|
r.left = r.left+x;
|
|
r.top = r.top+y;
|
|
}
|
|
|
|
void setBounds(inout Rect r) {
|
|
int x,y;
|
|
if (parent) parent.getPeerOffset(x,y);
|
|
gtk_window_move(peer,r.left+x,r.top+y);
|
|
childLayoutDirty = true;
|
|
gtk_window_resize(peer,r.width,r.height);
|
|
}
|
|
|
|
alias Component.size size;
|
|
void size(Point s) {
|
|
gtk_window_resize(peer,s.x,s.y);
|
|
}
|
|
|
|
void toFront() {
|
|
gdk_window_raise((cast(GtkWidget*)peer).window);
|
|
}
|
|
|
|
// mark as needing a repaint and post event to event queue
|
|
void repaint() {
|
|
Rect r;
|
|
GdkWindow* win = (cast(GtkWidget*)peer).window;
|
|
getBounds(r);
|
|
gdk_window_invalidate_rect(win,&r.native,1);
|
|
}
|
|
|
|
void repaint(inout Rect r) {
|
|
GdkWindow* win = (cast(GtkWidget*)peer).window;
|
|
gdk_window_invalidate_rect(win,&r.native,1);
|
|
}
|
|
|
|
mixin CommonEventSourceImpl!();
|
|
|
|
GContext getGContext(Event* paintEvent = null) {
|
|
GContext gc = newGContext();
|
|
GtkWidget* widget = cast(GtkWidget*)content;
|
|
GdkWindow* gwin = widget.window;
|
|
gc.drawable = cast(GdkDrawable*)gwin;
|
|
gc.layout = gtk_widget_create_pango_layout(widget,"");
|
|
gc.peer = gdk_gc_new(gc.drawable);
|
|
gc.hasPeer = OWNS_PEER;
|
|
gc.paintEvent = cast(GdkEventExpose*)paintEvent;
|
|
return gc;
|
|
}
|
|
|
|
Image getCompatibleImage(int width, int height) {
|
|
GtkWidget* widget = cast(GtkWidget*)content;
|
|
GdkDrawable* gwin = cast(GdkDrawable*)widget.window;
|
|
int depth = gdk_drawable_get_depth(gwin);
|
|
ImagePeer bm = gdk_pixmap_new(gwin,width,height,depth);
|
|
version(LOG) log.writefln("create bitmap %x",cast(int)bm);
|
|
Image res = new Image(bm);
|
|
res.width = width;
|
|
res.height = height;
|
|
res.hasPeer = OWNS_PEER;
|
|
return res;
|
|
}
|
|
|
|
Image loadCompatibleImage(char[] imageKey, char[] fmt = "bmp") {
|
|
GtkWidget* widget = cast(GtkWidget*)content;
|
|
GdkDrawable* gwin = cast(GdkDrawable*)widget.window;
|
|
GError* err;
|
|
char[] fname = imageKey;
|
|
if (gApp.resourcePath.length > 0) {
|
|
fname = gApp.resourcePath ~ "/" ~ fname;
|
|
}
|
|
fname = fname ~ "." ~ fmt;
|
|
GdkPixbuf* pbuf = gdk_pixbuf_new_from_file(toStringz(fname),&err);
|
|
int width = gdk_pixbuf_get_width(pbuf);
|
|
int height = gdk_pixbuf_get_height(pbuf);
|
|
int depth = gdk_drawable_get_depth(gwin);
|
|
GdkColormap* cmap = gdk_drawable_get_colormap(gwin);
|
|
ImagePeer bm = gdk_pixmap_new(gwin,width,height,depth);
|
|
gdk_pixbuf_render_pixmap_and_mask_for_colormap(pbuf,cmap,&bm,null,0);
|
|
Image res = new Image(bm);
|
|
res.width = width;
|
|
res.height = height;
|
|
res.hasPeer = OWNS_PEER;
|
|
return res;
|
|
}
|
|
}
|
|
|
|
class Window : AbstractWindow {
|
|
this(char[] title = "", char[] name = "") {
|
|
char* str = toStringz(title);
|
|
// TODO: application name vs window name??
|
|
peer = cast(GtkWindow*)gtk_window_new(GtkWindowType.GTK_WINDOW_TOPLEVEL);
|
|
gtk_window_set_title(peer,str);
|
|
gtk_window_set_default_size(peer,DefaultWindowWidth,DefaultWindowHeight);
|
|
backgroundColor = RGB(255,255,255);
|
|
this.name = name;
|
|
gtk_widget_add_events(cast(GtkWidget*)peer,GdkEventMask.GDK_KEY_PRESS_MASK |
|
|
GdkEventMask.GDK_KEY_RELEASE_MASK |
|
|
GdkEventMask.GDK_EXPOSURE_MASK |
|
|
GdkEventMask.GDK_STRUCTURE_MASK |
|
|
GdkEventMask.GDK_POINTER_MOTION_MASK |
|
|
GdkEventMask.GDK_BUTTON_PRESS_MASK |
|
|
GdkEventMask.GDK_BUTTON_RELEASE_MASK);
|
|
g_signal_connect_data(peer,"destroy",
|
|
cast(GCallback)&mw_destroy_callback,
|
|
cast(gpointer)this,
|
|
null,cast(GConnectFlags)0);
|
|
g_signal_connect_data(peer,"delete-event",
|
|
cast(GCallback)&mw_close_callback,
|
|
cast(gpointer)this,
|
|
null,cast(GConnectFlags)0);
|
|
g_signal_connect_data(peer,"configure-event",
|
|
cast(GCallback)&mw_notify_callback,
|
|
cast(gpointer)this,
|
|
null,cast(GConnectFlags)0);
|
|
setWindowPeer(this,peer,OWNS_PEER);
|
|
WindowList[this] = this; // prevent garbage collection
|
|
|
|
GtkWidget* wcontent = MinWinGtkPeer_new();
|
|
content = cast(MinWinGtkPeer*)wcontent;
|
|
gtk_widget_set_sensitive(wcontent,true);
|
|
content.sizeRequest = >kRequest;
|
|
content.sizeAllocate = >kAllocate;
|
|
gtk_container_add(cast(GtkContainer*)peer,wcontent);
|
|
g_signal_connect_data(peer,"expose-event",
|
|
cast(GCallback)&mw_expose_callback,
|
|
cast(gpointer)this,
|
|
null,GConnectFlags.G_CONNECT_AFTER);
|
|
g_signal_connect_data(peer,"key-press-event",
|
|
cast(GCallback)&mw_key_callback,
|
|
cast(gpointer)this,
|
|
null,GConnectFlags.G_CONNECT_AFTER);
|
|
g_signal_connect_data(peer,"key-release-event",
|
|
cast(GCallback)&mw_key_callback,
|
|
cast(gpointer)this,
|
|
null,GConnectFlags.G_CONNECT_AFTER);
|
|
g_signal_connect_data(peer,"motion-notify-event",
|
|
cast(GCallback)&mw_win_mouse_callback,
|
|
cast(gpointer)this,
|
|
null,GConnectFlags.G_CONNECT_AFTER);
|
|
g_signal_connect_data(peer,"button-press-event",
|
|
cast(GCallback)&mw_win_button_callback,
|
|
cast(gpointer)this,
|
|
null,GConnectFlags.G_CONNECT_AFTER);
|
|
g_signal_connect_data(peer,"button-release-event",
|
|
cast(GCallback)&mw_win_button_callback,
|
|
cast(gpointer)this,
|
|
null,GConnectFlags.G_CONNECT_AFTER);
|
|
gtk_widget_realize(wcontent);
|
|
gtk_widget_show(wcontent);
|
|
}
|
|
|
|
this(WindowPeer peer) {
|
|
setWindowPeer(this,peer,FOREIGN_PEER);
|
|
WindowList[this] = this; // prevent garbage collection
|
|
}
|
|
|
|
}
|
|
|
|
|
|
// Get the Window object associated with the given peer.
|
|
// The peer is accessed from the Window using the peer property.
|
|
AbstractWindow peerToWindow(WindowPeer peer) {
|
|
gpointer ptr = g_object_get_data(cast(GObject*)peer,"MinWinUserData");
|
|
return cast(AbstractWindow)ptr;
|
|
}
|
|
|
|
// Associate c with the peer. This overwrites the window UserData.
|
|
void setWindowPeer(AbstractWindow c, WindowPeer peer, int peerState) {
|
|
g_object_set_data(cast(GObject*)peer,"MinWinUserData",cast(gpointer)peer);
|
|
c.hasPeer = peerState;
|
|
}
|
|
|
|
extern (C) gboolean mw_expose_callback(GtkWidget* w,
|
|
GdkEventExpose* event,
|
|
gpointer ud) {
|
|
Window win = cast(Window) ud;
|
|
if (win) {
|
|
win.repaintNow(cast(Event*)event);
|
|
}
|
|
return false;
|
|
}
|
|
|
|
extern (C) gboolean mw_key_callback(GtkWidget* w,
|
|
GdkEventKey* event,
|
|
gpointer ud) {
|
|
Window win = cast(Window) ud;
|
|
if (win && !win.keyDelegate.isEmpty) {
|
|
win.keyDelegate(win,cast(KeyEvent*)event);
|
|
}
|
|
return false;
|
|
}
|
|
|
|
extern (C) gboolean mw_win_mouse_callback(GtkWidget* w,
|
|
GdkEventMotion* event,
|
|
gpointer ud) {
|
|
version(LOG)log.writefln("got mouse motion event");
|
|
Window win = cast(Window) ud;
|
|
if (win && !win.mouseDelegate.isEmpty) {
|
|
// TODO this should be made into a GdkEventButton
|
|
win.mouseDelegate(win,cast(MouseEvent*)event);
|
|
}
|
|
return false;
|
|
}
|
|
|
|
extern (C) gboolean mw_win_button_callback(GtkWidget* w,
|
|
GdkEventButton* event,
|
|
gpointer ud) {
|
|
Window win = cast(Window) ud;
|
|
if (win && !win.mouseDelegate.isEmpty) {
|
|
win.mouseDelegate(win,cast(MouseEvent*)event);
|
|
}
|
|
return false;
|
|
}
|
|
|
|
extern (C) void mw_destroy_callback(GtkObject* w, gpointer ud) {
|
|
Window win = cast(Window) ud;
|
|
if (win) {
|
|
version(LOG)log.printf("mw_destroy_callback\n");
|
|
win.hasPeer = NO_PEER;
|
|
if (win.quitOnDestroy)
|
|
gApp.exitEventLoop();
|
|
if (win in Window.WindowList) {
|
|
//delete Window.WindowList[win];
|
|
Window.WindowList.remove(win);
|
|
}
|
|
}
|
|
}
|
|
extern (C) void mw_close_callback(GtkWidget* w, GdkEvent* ev, gpointer ud) {
|
|
Window win = cast(Window) ud;
|
|
version(LOG)log.printf("mw_close_callback\n");
|
|
if (win)
|
|
win.close();
|
|
}
|
|
|
|
extern (C) void mw_notify_callback(GtkWidget* w, GdkEvent* ev, gpointer ud) {
|
|
Window win = cast(Window) ud;
|
|
if (win && ev.type == GdkEventType.GDK_CONFIGURE) {
|
|
if (!win.windowDelegate.isEmpty) {
|
|
win.windowDelegate(win, cast(WindowEvent*)ev);
|
|
}
|
|
}
|
|
}
|
|
|
|
}
|
|
|