summaryrefslogtreecommitdiff
path: root/popover
diff options
context:
space:
mode:
Diffstat (limited to 'popover')
-rw-r--r--popover/main_windows.c245
-rw-r--r--popover/popover.c174
-rw-r--r--popover/popover.h30
3 files changed, 449 insertions, 0 deletions
diff --git a/popover/main_windows.c b/popover/main_windows.c
new file mode 100644
index 0000000..0bf368f
--- /dev/null
+++ b/popover/main_windows.c
@@ -0,0 +1,245 @@
+// 9 october 2014
+#define UNICODE
+#define _UNICODE
+#define STRICT
+#define STRICT_TYPED_ITEMIDS
+// get Windows version right; right now Windows XP
+#define WINVER 0x0501
+#define _WIN32_WINNT 0x0501
+#define _WIN32_WINDOWS 0x0501 /* according to Microsoft's winperf.h */
+#define _WIN32_IE 0x0600 /* according to Microsoft's sdkddkver.h */
+#define NTDDI_VERSION 0x05010000 /* according to Microsoft's sdkddkver.h */
+#include <windows.h>
+#include <commctrl.h>
+#include <stdint.h>
+#include <uxtheme.h>
+#include <string.h>
+#include <wchar.h>
+#include <windowsx.h>
+#include <vsstyle.h>
+#include <vssym32.h>
+#include "popover.h"
+
+// #qo LIBS: user32 kernel32 gdi32
+
+// TODO
+// - should the parent window appear deactivated?
+
+HWND popoverWindow;
+
+void xpanic(char *msg, DWORD err)
+{
+ printf("%d | %s\n", err, msg);
+ abort();
+}
+
+popover *p;
+
+HRGN makePopoverRegion(HDC dc, LONG width, LONG height)
+{
+ popoverPoint ppt[20];
+ POINT pt[20];
+ int i, n;
+ HRGN region;
+
+ n = popoverMakeFramePoints(p, (intptr_t) width, (intptr_t) height, ppt);
+ for (i = 0; i < n; i++) {
+ pt[i].x = (LONG) (ppt[i].x);
+ pt[i].y = (LONG) (ppt[i].y);
+ }
+
+ if (BeginPath(dc) == 0)
+ xpanic("error beginning path for Popover shape", GetLastError());
+ if (Polyline(dc, pt, n) == 0)
+ xpanic("error drawing lines in Popover shape", GetLastError());
+ if (EndPath(dc) == 0)
+ xpanic("error ending path for Popover shape", GetLastError());
+ region = PathToRegion(dc);
+ if (region == NULL)
+ xpanic("error converting Popover shape path to region", GetLastError());
+ return region;
+}
+
+#define msgPopoverPrepareLeftRight (WM_APP+50)
+#define msgPopoverPrepareTopBottom (WM_APP+51)
+
+LRESULT CALLBACK popoverproc(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam)
+{
+ PAINTSTRUCT ps;
+ HDC dc;
+ HRGN region;
+ RECT r;
+ LONG width;
+ LONG height;
+ WINDOWPOS *wp;
+ HBRUSH brush;
+
+ switch (uMsg) {
+ case WM_NCPAINT:
+ if (GetWindowRect(hwnd, &r) == 0)
+ xpanic("error getting Popover window rect for shape redraw", GetLastError());
+ width = r.right - r.left;
+ height = r.bottom - r.top;
+ dc = GetWindowDC(hwnd);
+ if (dc == NULL)
+ xpanic("error getting Popover window DC for drawing border", GetLastError());
+ region = makePopoverRegion(dc, width, height);
+ // don't call FillRgn(); WM_ERASEBKGND seems to do this to the non-client area for us already :S (TODO confirm)
+ // TODO arrow is black in wine
+ brush = (HBRUSH) GetStockObject(BLACK_BRUSH);
+ if (brush == NULL)
+ xpanic("error getting Popover border brush", GetLastError());
+ if (FrameRgn(dc, region, brush, 1, 1) == 0)
+ xpanic("error drawing Popover border", GetLastError());
+ if (DeleteObject(region) == 0)
+ xpanic("error deleting Popover shape region", GetLastError());
+ if (ReleaseDC(hwnd, dc) == 0)
+ xpanic("error releasing Popover window DC for shape drawing", GetLastError());
+ return 0;
+ case WM_WINDOWPOSCHANGED:
+ // this must be here; if it's in WM_NCPAINT weird things happen (see http://stackoverflow.com/questions/26288303/why-is-my-client-rectangle-drawing-behaving-bizarrely-pictures-provided-if-i-t)
+ wp = (WINDOWPOS *) lParam;
+ if ((wp->flags & SWP_NOSIZE) == 0) {
+ dc = GetWindowDC(hwnd);
+ if (dc == NULL)
+ xpanic("error getting Popover window DC for reshaping", GetLastError());
+ region = makePopoverRegion(dc, wp->cx, wp->cy);
+ if (SetWindowRgn(hwnd, region, TRUE) == 0)
+ xpanic("error setting Popover shape", GetLastError());
+ // don't delete the region; the window manager owns it now
+ if (ReleaseDC(hwnd, dc) == 0)
+ xpanic("error releasing Popover window DC for reshaping", GetLastError());
+ }
+ break; // defer to DefWindowProc()
+ case WM_NCCALCSIZE:
+ {
+ RECT *r = (RECT *) lParam;
+ NCCALCSIZE_PARAMS *np = (NCCALCSIZE_PARAMS *) lParam;
+ popoverRect pr;
+
+ if (wParam != FALSE)
+ r = &np->rgrc[0];
+ pr.left = (intptr_t) (r->left);
+ pr.top = (intptr_t) (r->top);
+ pr.right = (intptr_t) (r->right);
+ pr.bottom = (intptr_t) (r->bottom);
+ popoverWindowSizeToClientSize(p, &pr);
+ r->left = (LONG) (pr.left);
+ r->top = (LONG) (pr.top);
+ r->right = (LONG) (pr.right);
+ r->bottom = (LONG) (pr.bottom);
+ return 0;
+ }
+ case WM_PAINT:
+ dc = BeginPaint(hwnd, &ps);
+ GetClientRect(hwnd, &r);
+ FillRect(dc, &r, GetSysColorBrush(COLOR_ACTIVECAPTION));
+ FrameRect(dc, &r, GetStockPen(WHITE_BRUSH));
+ EndPaint(hwnd, &ps);
+ return 0;
+ case msgPopoverPrepareLeftRight:
+ case msgPopoverPrepareTopBottom:
+ // TODO window edge detection
+ {
+ RECT r;
+ LONG width = 200, height = 200;
+ popoverRect control;
+ uintptr_t side;
+ popoverRect out;
+
+ if (GetWindowRect((HWND) wParam, &r) == 0)
+ xpanic("error getting window rect of Popover target", GetLastError());
+ control.left = (intptr_t) (r.left);
+ control.top = (intptr_t) (r.top);
+ control.right = (intptr_t) (r.right);
+ control.bottom = (intptr_t) (r.bottom);
+ switch (uMsg) {
+ case msgPopoverPrepareLeftRight:
+ side = popoverPointLeft;
+ break;
+ case msgPopoverPrepareTopBottom:
+ side = popoverPointTop;
+ break;
+ }
+ out = popoverPointAt(p, control, (intptr_t) width, (intptr_t) height, side);
+ if (MoveWindow(hwnd, out.left, out.top, out.right - out.left, out.bottom - out.top, TRUE) == 0)
+ xpanic("error repositioning Popover", GetLastError());
+ }
+ return 0;
+ }
+ return DefWindowProcW(hwnd, uMsg, wParam, lParam);
+}
+
+HWND button;
+
+LRESULT CALLBACK wndproc(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam)
+{
+ switch (uMsg) {
+ case WM_COMMAND:
+ if (HIWORD(wParam) == BN_CLICKED && LOWORD(wParam) == 100) {
+ SendMessageW(popoverWindow, msgPopoverPrepareLeftRight, (WPARAM) button, 0);
+ ShowWindow(popoverWindow, SW_SHOW);
+ UpdateWindow(popoverWindow);
+ return 0;
+ }
+ break;
+ case WM_CLOSE:
+ PostQuitMessage(0);
+ return 0;
+ }
+ return DefWindowProcW(hwnd, uMsg, wParam, lParam);
+}
+
+int main(int argc, char *argv[])
+{
+ WNDCLASSW wc;
+ HWND mainwin;
+ MSG msg;
+
+ p = popoverDataNew(NULL);
+ // TODO null check
+
+ ZeroMemory(&wc, sizeof (WNDCLASSW));
+ wc.lpszClassName = L"popover";
+ wc.lpfnWndProc = popoverproc;
+ wc.hbrBackground = (HBRUSH) (COLOR_BTNFACE + 1);
+ wc.style = CS_DROPSHADOW | CS_NOCLOSE;
+ if (RegisterClassW(&wc) == 0)
+ abort();
+ popoverWindow = CreateWindowExW(WS_EX_TOPMOST,
+ L"popover", L"",
+ WS_POPUP,
+ 0, 0, 150, 100,
+ NULL, NULL, NULL, NULL);
+ if (popoverWindow == NULL)
+ abort();
+
+ ZeroMemory(&wc, sizeof (WNDCLASSW));
+ wc.lpszClassName = L"mainwin";
+ wc.lpfnWndProc = wndproc;
+ wc.hbrBackground = (HBRUSH) (COLOR_BTNFACE + 1);
+ if (RegisterClassW(&wc) == 0)
+ abort();
+ mainwin = CreateWindowExW(0,
+ L"mainwin", L"Main Window",
+ WS_OVERLAPPEDWINDOW,
+ 0, 0, 150, 100,
+ NULL, NULL, NULL, NULL);
+ if (mainwin == NULL)
+ abort();
+ button = CreateWindowExW(0,
+ L"button", L"Click Me",
+ BS_PUSHBUTTON | WS_CHILD | WS_VISIBLE,
+ 20, 20, 100, 40,
+ mainwin, (HMENU) 100, NULL, NULL);
+ if (button == NULL)
+ abort();
+ ShowWindow(mainwin, SW_SHOWDEFAULT);
+ if (UpdateWindow(mainwin) == 0)
+ abort();
+ while (GetMessageW(&msg, NULL, 0, 0) > 0) {
+ TranslateMessage(&msg);
+ DispatchMessageW(&msg);
+ }
+ return 0;
+}
diff --git a/popover/popover.c b/popover/popover.c
new file mode 100644
index 0000000..2c2bf66
--- /dev/null
+++ b/popover/popover.c
@@ -0,0 +1,174 @@
+// 9 october 2014
+#include <stdlib.h>
+#include <stdint.h>
+#include "popover.h"
+
+#define ARROWHEIGHT 8
+#define ARROWWIDTH 8 /* should be the same for smooth lines on Windows (TODO is there a better/nicer looking way?) */
+
+struct popover {
+ void *gopopover;
+
+ // a nice consequence of this design is that it allows four arrowheads to jut out at once; in practice only one will ever be used, but hey — simple implementation!
+ intptr_t arrowLeft;
+ intptr_t arrowTop;
+ intptr_t arrowRight;
+ intptr_t arrowBottom;
+};
+
+popover *popoverDataNew(void *gopopover)
+{
+ popover *p;
+
+ p = (popover *) malloc(sizeof (popover));
+ if (p != NULL) {
+ p->gopopover = gopopover;
+ p->arrowLeft = -1;
+ p->arrowTop = 20;//TODO-1;
+ p->arrowRight = -1;
+ p->arrowBottom = -1;
+ }
+ return p;
+}
+
+int popoverMakeFramePoints(popover *p, intptr_t width, intptr_t height, popoverPoint pt[20])
+{
+ int n;
+ intptr_t xmax, ymax;
+
+ n = 0;
+
+ // figure out the xmax and ymax of the box
+ xmax = width;
+ if (p->arrowRight >= 0)
+ xmax -= ARROWWIDTH;
+ ymax = height;
+ if (p->arrowBottom >= 0)
+ ymax -= ARROWHEIGHT;
+
+ // the first point is either at (0,0), (0,arrowHeight), (arrowWidth,0), or (arrowWidth,arrowHeight)
+ pt[n].x = 0;
+ if (p->arrowLeft >= 0)
+ pt[n].x = ARROWWIDTH;
+ pt[n].y = 0;
+ if (p->arrowTop >= 0)
+ pt[n].y = ARROWHEIGHT;
+ n++;
+
+ // the left side
+ pt[n].x = pt[n - 1].x;
+ if (p->arrowLeft >= 0) {
+ pt[n].y = pt[n - 1].y + p->arrowLeft;
+ n++;
+ pt[n].x = pt[n - 1].x - ARROWWIDTH;
+ pt[n].y = pt[n - 1].y + ARROWHEIGHT;
+ n++;
+ pt[n].x = pt[n - 1].x + ARROWWIDTH;
+ pt[n].y = pt[n - 1].y + ARROWHEIGHT;
+ n++;
+ pt[n].x = pt[n - 1].x;
+ }
+ pt[n].y = ymax;
+ n++;
+
+ // the bottom side
+ pt[n].y = pt[n - 1].y;
+ if (p->arrowBottom >= 0) {
+ pt[n].x = pt[n - 1].x + p->arrowBottom;
+ n++;
+ pt[n].x = pt[n - 1].x + ARROWWIDTH;
+ pt[n].y = pt[n - 1].y + ARROWHEIGHT;
+ n++;
+ pt[n].x = pt[n - 1].x + ARROWWIDTH;
+ pt[n].y = pt[n - 1].y - ARROWHEIGHT;
+ n++;
+ pt[n].y = pt[n - 1].y;
+ }
+ pt[n].x = xmax;
+ n++;
+
+ // the right side
+ pt[n].x = pt[n - 1].x;
+ if (p->arrowRight >= 0) {
+ pt[n].y = pt[0].y + p->arrowRight + (ARROWHEIGHT * 2);
+ n++;
+ pt[n].x = pt[n - 1].x + ARROWWIDTH;
+ pt[n].y = pt[n - 1].y - ARROWHEIGHT;
+ n++;
+ pt[n].x = pt[n - 1].x - ARROWWIDTH;
+ pt[n].y = pt[n - 1].y - ARROWHEIGHT;
+ n++;
+ pt[n].x = pt[n - 1].x;
+ }
+ pt[n].y = pt[0].y;
+ n++;
+
+ // the top side
+ pt[n].y = pt[n - 1].y;
+ if (p->arrowTop >= 0) {
+ pt[n].x = pt[0].x + p->arrowTop + (ARROWWIDTH * 2);
+ n++;
+ pt[n].x = pt[n - 1].x - ARROWWIDTH;
+ pt[n].y = pt[n - 1].y - ARROWHEIGHT;
+ n++;
+ pt[n].x = pt[n - 1].x - ARROWWIDTH;
+ pt[n].y = pt[n - 1].y + ARROWHEIGHT;
+ n++;
+ pt[n].y = pt[n - 1].y;
+ }
+ pt[n].x = pt[0].x;
+ n++;
+
+ return n;
+}
+
+void popoverWindowSizeToClientSize(popover *p, popoverRect *r)
+{
+ r->left++;
+ r->top++;
+ r->right--;
+ r->bottom--;
+ if (p->arrowLeft >= 0)
+ r->left += ARROWWIDTH;
+ if (p->arrowRight >= 0)
+ r->right -= ARROWWIDTH;
+ if (p->arrowTop >= 0)
+ r->top += ARROWHEIGHT;
+ if (p->arrowBottom >= 0)
+ r->bottom -= ARROWHEIGHT;
+}
+
+// TODO window edge detection
+popoverRect popoverPointAt(popover *p, popoverRect control, intptr_t width, intptr_t height, unsigned int side)
+{
+ intptr_t x, y;
+ popoverRect out;
+
+ // account for border
+ width += 2;
+ height += 2;
+ p->arrowLeft = -1;
+ p->arrowRight = -1;
+ p->arrowTop = -1;
+ p->arrowBottom = -1;
+ // TODO right and bottom
+ switch (side) {
+ case popoverPointLeft:
+ width += ARROWWIDTH;
+ p->arrowLeft = height / 2 - ARROWHEIGHT;
+ x = control.right;
+ y = control.top - ((height - (control.bottom - control.top)) / 2);
+ break;
+ case popoverPointTop:
+ height += ARROWHEIGHT;
+ p->arrowTop = width / 2 - ARROWWIDTH;
+ x = control.left - ((width - (control.right - control.left)) / 2);
+ y = control.bottom;
+ break;
+ }
+ out.left = x;
+ out.top = y;
+ out.right = x + width;
+ out.bottom = y + height;
+ return out;
+}
diff --git a/popover/popover.h b/popover/popover.h
new file mode 100644
index 0000000..28220e4
--- /dev/null
+++ b/popover/popover.h
@@ -0,0 +1,30 @@
+// 11 october 2014
+
+typedef struct popover popover;
+typedef struct popoverPoint popoverPoint;
+typedef struct popoverRect popoverRect;
+
+struct popoverPoint {
+ intptr_t x;
+ intptr_t y;
+};
+
+struct popoverRect {
+ intptr_t left;
+ intptr_t top;
+ intptr_t right;
+ intptr_t bottom;
+};
+
+// note the order: flipping sides is as easy as side ^ 1
+enum {
+ popoverPointLeft,
+ popoverPointRight,
+ popoverPointTop,
+ popoverPointBottom,
+};
+
+popover *popoverDataNew(void *);
+int popoverMakeFramePoints(popover *, intptr_t, intptr_t, popoverPoint[20]);
+void popoverWindowSizeToClientSize(popover *, popoverRect *);
+popoverRect popoverPointAt(popover *, popoverRect, intptr_t, intptr_t, unsigned int);