/* * GraphApp - Cross-Platform Graphics Programming Library. * * File: controls.c -- manipulating scrollbars, buttons, etc. * Platform: Windows Version: 2.40 Date: 1998/04/04 * * Version: 1.00 Changes: Original version by Lachlan Patrick. * Version: 1.60 Changes: Added clear(), draw(), flashcontrol(). * Version: 2.00 Changes: New object class system. * Version: 2.15 Changes: Transparent backgrounds added. * Version: 2.20 Changes: Non-native buttons supported. * Version: 2.35 Changes: New reference count technique. * Version: 2.40 Changes: Support for new controls. */ /* Copyright (C) 1993-1998 Lachlan Patrick This file is part of GraphApp, a cross-platform C graphics library. GraphApp is free software; you can redistribute it and/or modify it under the terms of the GNU Library General Public License. GraphApp is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY. See the file COPYLIB.TXT for details. */ /* Copyright (C) 2004 The R Foundation Copyright (C) 2022 The R Core Team Changes for R: sort out resize (confused screen and client coords) add printer and metafile handling Remove assumption of current->dest being non-NULL Improve caret handling (destroy when not in focus, do not hide/show non-existent caret, prevent against corruption via recursive redraw while not in focus, preserve coordinates when not in focus, ensure caret exists when a new window first gets focus) */ #include "internal.h" #include # define alloca(x) __builtin_alloca((x)) /* * Setting control call-backs. */ void setaction(control obj, actionfn fn) { if (obj) obj->action = fn; } void sethit(control obj, intfn fn) { if (obj) obj->hit = fn; } void setdel(control obj, actionfn fn) { if (obj) if (obj->call) obj->call->die = fn; } void setclose(control obj, actionfn fn) { if (obj) if (obj->call) obj->call->close = fn; } void setredraw(control obj, drawfn fn) { if (obj) if (obj->call) obj->call->redraw = fn; } void setresize(control obj, drawfn fn) { if (obj) if (obj->call) obj->call->resize = fn; } void setkeydown(control obj, keyfn fn) { if (obj) if (obj->call) obj->call->keydown = fn; } void setkeyaction(control obj, keyfn fn) { if (obj) if (obj->call) obj->call->keyaction = fn; } void setmousedown(control obj, mousefn fn) { if (obj) if (obj->call) obj->call->mousedown = fn; } void setmouseup(control obj, mousefn fn) { if (obj) if (obj->call) obj->call->mouseup = fn; } void setmousemove(control obj, mousefn fn) { if (obj) if (obj->call) obj->call->mousemove = fn; } void setmousedrag(control obj, mousefn fn) { if (obj) if (obj->call) obj->call->mousedrag = fn; } void setmouserepeat(control obj, mousefn fn) { if (obj) if (obj->call) obj->call->mouserepeat = fn; } void setdrop(control obj, dropfn fn) { if (obj) if (obj->call) { DragAcceptFiles(obj->handle, TRUE); obj->call->drop = fn; } } void setonfocus(control obj, actionfn fn) { if (obj) obj->call->focus = fn; } void setim(control obj, imfn fn) { if (obj) obj->call->im = fn; } /* * Drawing controls and windows. */ void clear(control obj) { drawing prev; rgb old; if (! obj) return; if (! isvisible(obj)) return; if (! isvisible(parentwindow(obj))) return; if (obj->bg == Transparent) return; prev = current->dest; drawto(obj); old = currentrgb(); setrgb(obj->bg); fillrect(getrect(obj)); if (prev) { setrgb(old); drawto(prev); } } void draw(control obj) { drawing prev; drawstate old = NULL; if (! obj) return; if (obj->kind == MenubarObject) { DrawMenuBar(obj->parent->handle); return; } if (! isvisible(obj)) return; if (! isvisible(parentwindow(obj))) return; if ((obj->call == NULL) || (obj->call->redraw == NULL)) return; prev = current->dest; drawto(obj); if (prev) old = copydrawstate(); moveto(pt(0,0)); obj->call->redraw(obj, getrect(obj)); if (prev) { restoredrawstate(old); drawto(prev); } } void redraw(control obj) { clear(obj); draw(obj); } /* void getscreenrect(control obj, rect *r) */ /* { */ /* RECT W; */ /* GetWindowRect(obj->handle, &W); */ /* r->x = W.left; */ /* r->y = W.top; */ /* r->width = W.right - W.left; */ /* r->height = W.bottom - W.top; */ /* } */ /* The original here used GetWindowRect (which used screen coordinates) and MoveWindow (which uses client coordinates) so got the positioning hopelessly wrong. This version works for WindowObjects, but I would be suspicious of it for other cases. BDR 2000/04/05 */ void resize(control obj, rect r) { RECT R; WINDOWPLACEMENT W; int dw, dh, dx, dy; if (! obj) return; r = rcanon(r); if (obj->kind == WindowObject) { W.length = sizeof(WINDOWPLACEMENT); r.x = obj->rect.x; r.y = obj->rect.y; if (!equalr(r, obj->rect)) { GetWindowPlacement(obj->handle, &W); if (!isvisible(obj)) W.showCmd = SW_HIDE; /* stops the resize from revealing the window */ dx = r.x - obj->rect.x; dy = r.y - obj->rect.y; /* don't believe current sizes! dw = r.width - obj->rect.width; dh = r.height - obj->rect.height; Rprintf("dw %d dh %d\n", dw, dh); */ GetClientRect(obj->handle, &R); dw = r.width - (R.right - R.left); dh = r.height - (R.bottom - R.top); W.rcNormalPosition.left += dx; W.rcNormalPosition.top += dy; W.rcNormalPosition.right += dx + dw; W.rcNormalPosition.bottom += dy + dh; SetWindowPlacement(obj->handle, &W); } } else { if (! equalr(r, obj->rect)) MoveWindow(obj->handle, r.x, r.y, r.width, r.height, 1); obj->rect.x = r.x; obj->rect.y = r.y; } obj->rect.width = r.width; obj->rect.height = r.height; } /* * Showing and hiding controls and windows. */ void show(control obj) { if (! obj) return; switch (obj->kind) { case CursorObject: case FontObject: case BitmapObject: case MenubarObject: case MenuObject: case MenuitemObject: break; case WindowObject: obj->state |= GA_Visible; show_window(obj); break; default: ShowWindow(obj->handle, SW_SHOWNORMAL); SetFocus(obj->handle); UpdateWindow(obj->handle); } obj->state |= GA_Visible; } void hide(control obj) { if (! obj) return; switch (obj->kind) { case CursorObject: case FontObject: case BitmapObject: case MenubarObject: case MenuObject: case MenuitemObject: break; case WindowObject: hide_window(obj); break; default: ShowWindow(obj->handle, SW_HIDE); } obj->state &= ~GA_Visible; } int isvisible(control obj) { if (! obj) return 0; return (obj->state & GA_Visible) ? 1 : 0; } /* * Enabling and disabling controls and windows. */ void enable(control obj) { if (! obj) return; switch (obj->kind) { case CursorObject: case FontObject: case BitmapObject: case MenubarObject: break; case MenuObject: case MenuitemObject: EnableMenuItem(obj->parent->handle, obj->id, MF_ENABLED | MF_BYCOMMAND); break; case FieldObject: case TextboxObject: sendmessage(obj->handle, EM_SETREADONLY, FALSE, 0L); break; default: if (! isenabled(obj)) { EnableWindow(obj->handle, 1); obj->state |= GA_Enabled; draw(obj); } } obj->state |= GA_Enabled; } void disable(control obj) { if (! obj) return; switch (obj->kind) { case CursorObject: case FontObject: case BitmapObject: case MenubarObject: case MenuObject: break; case MenuitemObject: EnableMenuItem(obj->parent->handle, obj->id, MF_GRAYED | MF_BYCOMMAND); break; case FieldObject: case TextboxObject: sendmessage(obj->handle, EM_SETREADONLY, TRUE, 0L); break; default: if (isenabled(obj)) { EnableWindow(obj->handle, 0); obj->state &= ~GA_Enabled; draw(obj); } } obj->state &= ~GA_Enabled; } int isenabled(control obj) { if (! obj) return 0; return (obj->state & GA_Enabled) ? 1 : 0; } /* * Checking and unchecking controls. */ void check(control obj) { if (! obj) return; switch (obj->kind) { case CursorObject: case FontObject: case BitmapObject: case MenubarObject: case MenuObject: break; case MenuitemObject: CheckMenuItem(obj->parent->handle, obj->id, MF_CHECKED | MF_BYCOMMAND); break; #if USE_NATIVE_BUTTONS case ButtonObject: sendmessage(obj->handle, BM_SETCHECK, 1, 0L); break; #endif #if USE_NATIVE_TOGGLES case CheckboxObject: case RadioObject: sendmessage(obj->handle, BM_SETCHECK, 1, 0L); break; #endif default: if (! ischecked(obj)) { obj->state |= GA_Checked; draw(obj); } } obj->state |= GA_Checked; } void uncheck(control obj) { if (! obj) return; switch (obj->kind) { case CursorObject: case FontObject: case BitmapObject: case MenubarObject: case MenuObject: break; case MenuitemObject: CheckMenuItem(obj->parent->handle, obj->id, MF_UNCHECKED | MF_BYCOMMAND); break; #if USE_NATIVE_BUTTONS case ButtonObject: sendmessage(obj->handle, BM_SETCHECK, 0, 0L); break; #endif #if USE_NATIVE_TOGGLES case CheckboxObject: case RadioObject: sendmessage(obj->handle, BM_SETCHECK, 0, 0L); break; #endif default: if (ischecked(obj)) { obj->state &= ~GA_Checked; draw(obj); } } obj->state &= ~GA_Checked; } int ischecked(control obj) { if (! obj) return 0; return (obj->state & GA_Checked) ? 1 : 0; } /* * Highlighting and unhighlighting controls. */ void highlight(control obj) { if (! obj) return; switch (obj->kind) { case CursorObject: case FontObject: case BitmapObject: case MenubarObject: case MenuObject: case MenuitemObject: break; #if USE_NATIVE_BUTTONS case ButtonObject: sendmessage(obj->handle, BM_SETSTATE, 1, 0L); break; #endif #if USE_NATIVE_TOGGLES case CheckboxObject: case RadioObject: sendmessage(obj->handle, BM_SETSTATE, 1, 0L); break; #endif case FieldObject: case TextboxObject: sendmessage(obj->handle, EM_SETSEL, 0, MAKELONG(0,-1)); break; default: if (! ishighlighted(obj)) { obj->state |= GA_Highlighted; draw(obj); } } obj->state |= GA_Highlighted; } void unhighlight(control obj) { if (! obj) return; switch (obj->kind) { case CursorObject: case FontObject: case BitmapObject: case MenubarObject: case MenuObject: case MenuitemObject: break; #if USE_NATIVE_BUTTONS case ButtonObject: sendmessage(obj->handle, BM_SETSTATE, 0, 0L); break; #endif #if USE_NATIVE_TOGGLES case CheckboxObject: case RadioObject: sendmessage(obj->handle, BM_SETSTATE, 0, 0L); break; #endif case FieldObject: case TextboxObject: sendmessage(obj->handle, EM_SETSEL, 0, MAKELONG(0,0)); break; default: if (ishighlighted(obj)) { obj->state &= ~GA_Highlighted; draw(obj); } } obj->state &= ~GA_Highlighted; } int ishighlighted(control obj) { if (! obj) return 0; return (obj->state & GA_Highlighted) ? 1 : 0; } /* * The flashcontrol function highlights a control as if the user * just used it, e.g. a button will become pressed down for a * moment. Used in conjunction with activatecontrol() below, * it can simulate a mouse-click on a button. */ void flashcontrol(control obj) { highlight(obj); delay(150); unhighlight(obj); } /* * The activatecontrol function is really useful. It causes * a variety of controls to call their 'action functions.' * * There are two types of action functions which a control can use. * The first (obj->action) takes one argument, the control itself, * and is used in buttons, checkboxes and the like as a response * to a user clicking on these controls. The second function, * (obj->hit) function, takes two arguments: the control and its * current 'value'. A scrollbar's value changes as the user scrolls, * and so its hit function is called whenever that happens. * * The activatecontrol function tries to call the action function, * and if that doesn't exist, calls the hit function, passing the * object's value to it. */ void activatecontrol(control obj) { if (! obj) return; drawto(obj); if (obj->action != NULL) obj->action(obj); else if (obj->hit != NULL) obj->hit(obj, obj->value); } /* * Changing and determining the state of an object. * These functions do not automatically redraw the object. */ #if 0 void setstate(control obj, long state) { if (obj) obj->state = state; } long getstate(control obj) { if (obj) return obj->state; else return 0; } #endif void setvalue(control obj, int value) { if (obj) obj->value = value; } int getvalue(control obj) { if (obj) return obj->value; else return 0; } void setforeground(control obj, rgb fg) { if (! obj) return; obj->fg = fg; if (obj->kind == TextboxObject) { if (obj->handle) { CHARFORMAT format; COLORREF wincolour = RGB((fg&gaRed)>>16,(fg&gaGreen)>>8,(fg&gaBlue)); format.cbSize = sizeof(format); format.dwMask = CFM_COLOR; format.dwEffects = 0; format.crTextColor = wincolour; sendmessage(obj->handle, EM_SETCHARFORMAT, 0, (LPARAM)&format); } } else { InvalidateRect(obj->handle, NULL, TRUE); redraw(obj); } } rgb getforeground(control obj) { if (obj) return obj->fg; else return Black; } void setbackground(control obj, rgb bg) { COLORREF wincolour = RGB((bg&gaRed)>>16,(bg&gaGreen)>>8,(bg&gaBlue)); if (! obj) return; obj->bg = bg; if (obj->kind == TextboxObject) { if (obj->handle) sendmessage(obj->handle, EM_SETBKGNDCOLOR, 0, wincolour); } else { if (obj->bgbrush) DeleteObject(obj->bgbrush); obj->bgbrush = CreateSolidBrush(wincolour); InvalidateRect(obj->handle, NULL, TRUE); redraw(obj); } } rgb getbackground(control obj) { if (obj) return obj->bg; else return Transparent; } void setdata(control obj, void *data) { if (obj) obj->data = data; } void *getdata(control obj) { if (obj) return obj->data; else return NULL; } /* These two are in none of the headers */ #ifdef UNUSED void _setextradata(control obj, void *data) { if (obj) obj->extra = data; } void *_getextradata(control obj) { if (obj) return obj->extra; else return NULL; } #endif /* * Set the text of an object. This will set the names appearing * in a window's title bar, a button, checkbox or radio button, * or the value in a textbox or a text field. */ void settext(control obj, const char *text) { char *old_text; if (! obj) return; if (! text) text = ""; old_text = GA_gettext(obj); if (old_text && strcmp(old_text, text) == 0) return; /* no changes to be made */ if (obj->text) { /* discard prior information */ discard(obj->text); obj->text = NULL; } /* Set the new text. */ obj->text = new_string(text); if (text) { if (obj->kind & ControlObject) { text = to_dos_string(text); if(localeCP > 0 && (localeCP != GetACP())) { /* This seems not actually to work */ wchar_t *wc; int nc = strlen(text) + 1; wc = (wchar_t*) alloca(nc*sizeof(wchar_t)); mbstowcs(wc, text, nc); SetWindowTextW(obj->handle, wc); } else SetWindowText(obj->handle, text); discard(text); } if (obj->kind == MenuitemObject) { if(localeCP > 0 && (localeCP != GetACP())) { /* But this does */ wchar_t wc[1000]; mbstowcs(wc, text, 1000); ModifyMenuW(obj->parent->handle, obj->id, MF_BYCOMMAND|MF_STRING, obj->id, wc); } else ModifyMenu(obj->parent->handle, obj->id, MF_BYCOMMAND|MF_STRING, obj->id, text); } } /* Redraw it if it's a redrawable object. */ if (obj->call && obj->call->redraw) redraw(obj); } /* * Get the text string from a window's title bar or from a * control. This may be a button's name, for example, or * the value inside a text field. */ char *GA_gettext(control obj) { static char *empty = ""; char *text; int length, index; HWND hwnd; UINT len_msg, gettext_msg; WPARAM arg1, arg2; if (! obj) return empty; if ((obj->kind & ControlObject) == 0) return obj->text ? obj->text : empty; hwnd = obj->handle; switch (obj->kind) { case ListboxObject: case MultilistObject: len_msg = LB_GETTEXTLEN; gettext_msg = LB_GETTEXT; index = getlistitem(obj); if (index < 0) return empty; arg1 = arg2 = index; break; case DroplistObject: len_msg = CB_GETLBTEXTLEN; gettext_msg = CB_GETLBTEXT; index = getlistitem(obj); if (index < 0) return empty; arg1 = arg2 = index; break; case DropfieldObject: // use default mechanism default: len_msg = WM_GETTEXTLENGTH; gettext_msg = WM_GETTEXT; arg1 = 0; /* FIXME: INT in wine/riched20, unsigned types in other */ arg2 = (LRESULT) sendmessage(hwnd, len_msg, 0, 0L)+1; break; } /* Free any previous information. */ if (obj->text) discard(obj->text); /* Find the length of the string. */ /* FIXME: properly cast the result */ length = (LRESULT) sendmessage(obj->handle, len_msg, arg1, 0L); if (length == 0) return (obj->text = new_string(NULL)); /* Copy the text from the object. */ text = array(length+2, char); sendmessage(obj->handle, gettext_msg, arg2, (LPCSTR) text); obj->text = to_c_string(text); discard(text); /* Return the resultant string. */ if (! obj->text) obj->text = new_string(NULL); return obj->text; } /* * Set the font used by a control. */ void settextfont(object obj, font f) { if (! obj) return; if (! f) f = SystemFont; if (obj->drawstate) { decrease_refcount(obj->drawstate->fnt); obj->drawstate->fnt = f; increase_refcount(f); } else { sendmessage(obj->handle, WM_SETFONT, f->handle, 0L); } } /* * Get the font used by a control. */ font gettextfont(object obj) { font f = NULL; if (obj) { if (obj->drawstate) f = obj->drawstate->fnt; } if (! f) f = SystemFont; return f; } /* * Control and window functions. * Parentwindow of a window is itself. */ window parentwindow(control obj) { while (obj) { if (obj->kind == WindowObject) break; obj = obj->parent; } return (window) obj; } /* * Polymorphic functions: */ rect objrect(object obj) { rect r; image img; if (! obj) return rect(0,0,0,0); switch (obj->kind) { case Image8: case Image32: img = (image) obj; r = rect(0,0,img->width,img->height); break; case BitmapObject: case FontObject: case CursorObject: case PrinterObject: r = obj->rect; break; case MetafileObject: r = obj->rect; break; default: GetClientRect(obj->handle, rect2RECT(&r)); break; } return r; } int objwidth(object obj) { rect r = objrect(obj); return r.width; } int objheight(object obj) { rect r = objrect(obj); return r.height; } int objdepth(object obj) { HDC screendc; int depth; if (! obj) return 0; switch (obj->kind) { case Image8: case Image32: depth = ((image)obj)->depth; break; case BitmapObject: case FontObject: case CursorObject: depth = obj->depth; break; default: screendc = GetDC(NULL); depth = GetDeviceCaps(screendc, BITSPIXEL) * GetDeviceCaps(screendc, PLANES); ReleaseDC(NULL, screendc); break; } return depth; } void delobj(object obj) { if (! obj) return; switch (obj->kind) { case Image8: case Image32: delimage((image)obj); break; default: /* if (obj->refcount == 1) why would this test be here?? */ decrease_refcount(obj); break; } } void setcaret(object obj, int x, int y, int width, int height) { if (! obj) return; if (width > 0 && !(obj->state & GA_Focus)) { /* prevent against accidental corruption of caret data while not in focus, such as during a screen redraw triggered by disabling the window when using the menu (e.g. when accessing GUI preferences from the console) */ return; } if (width != obj->caretwidth || height != obj->caretheight) { if (obj->caretwidth > 0) { if (obj->caretshowing) /* preserve caretshowing */ HideCaret(obj->handle); /* we destroy the WinAPI caret also when loosing focus, as suggested */ /* in Microsoft documentation */ DestroyCaret(); obj->caretexists = 0; } obj->caretwidth = width; obj->caretheight = height; if (width > 0) { if (obj->state & GA_Focus) { CreateCaret(obj->handle, (HBITMAP) NULL, width, height); obj->caretexists = 1; if (obj->caretshowing) /* match preserved caretshowing */ ShowCaret(obj->handle); } } } if (obj->state & GA_Focus) { SetCaretPos(x, y); obj->caretx = x; obj->carety = y; } } void showcaret(object obj, int showing) { if (! obj || ! obj->caretexists || showing == obj->caretshowing) return; obj->caretshowing = showing; if (showing) ShowCaret(obj->handle); else HideCaret(obj->handle); }