First Commit
This commit is contained in:
commit
ae0fab9400
424 files changed
+71006
No files matched your search
@@ -0,0 +1,369 @@
|
||||
// 24 april 2015
|
||||
#include "uipriv_windows.hpp"
|
||||
|
||||
// LONGTERM migrate to std::vector
|
||||
|
||||
static uiMenu **menus = NULL;
|
||||
static size_t len = 0;
|
||||
static size_t cap = 0;
|
||||
static BOOL menusFinalized = FALSE;
|
||||
static WORD curID = 100; // start somewhere safe
|
||||
static BOOL hasQuit = FALSE;
|
||||
static BOOL hasPreferences = FALSE;
|
||||
static BOOL hasAbout = FALSE;
|
||||
|
||||
struct uiMenu {
|
||||
WCHAR *name;
|
||||
uiMenuItem **items;
|
||||
size_t len;
|
||||
size_t cap;
|
||||
};
|
||||
|
||||
struct uiMenuItem {
|
||||
WCHAR *name;
|
||||
int type;
|
||||
WORD id;
|
||||
void (*onClicked)(uiMenuItem *, uiWindow *, void *);
|
||||
void *onClickedData;
|
||||
BOOL disabled; // template for new instances; kept in sync with everything else
|
||||
BOOL checked;
|
||||
HMENU *hmenus;
|
||||
size_t len;
|
||||
size_t cap;
|
||||
};
|
||||
|
||||
enum {
|
||||
typeRegular,
|
||||
typeCheckbox,
|
||||
typeQuit,
|
||||
typePreferences,
|
||||
typeAbout,
|
||||
typeSeparator,
|
||||
};
|
||||
|
||||
#define grow 32
|
||||
|
||||
static void sync(uiMenuItem *item)
|
||||
{
|
||||
size_t i;
|
||||
MENUITEMINFOW mi;
|
||||
|
||||
ZeroMemory(&mi, sizeof (MENUITEMINFOW));
|
||||
mi.cbSize = sizeof (MENUITEMINFOW);
|
||||
mi.fMask = MIIM_STATE;
|
||||
if (item->disabled)
|
||||
mi.fState |= MFS_DISABLED;
|
||||
if (item->checked)
|
||||
mi.fState |= MFS_CHECKED;
|
||||
|
||||
for (i = 0; i < item->len; i++)
|
||||
if (SetMenuItemInfo(item->hmenus[i], item->id, FALSE, &mi) == 0)
|
||||
logLastError(L"error synchronizing menu items");
|
||||
}
|
||||
|
||||
static void defaultOnClicked(uiMenuItem *item, uiWindow *w, void *data)
|
||||
{
|
||||
// do nothing
|
||||
}
|
||||
|
||||
static void onQuitClicked(uiMenuItem *item, uiWindow *w, void *data)
|
||||
{
|
||||
if (uiprivShouldQuit())
|
||||
uiQuit();
|
||||
}
|
||||
|
||||
void uiMenuItemEnable(uiMenuItem *i)
|
||||
{
|
||||
i->disabled = FALSE;
|
||||
sync(i);
|
||||
}
|
||||
|
||||
void uiMenuItemDisable(uiMenuItem *i)
|
||||
{
|
||||
i->disabled = TRUE;
|
||||
sync(i);
|
||||
}
|
||||
|
||||
void uiMenuItemOnClicked(uiMenuItem *i, void (*f)(uiMenuItem *, uiWindow *, void *), void *data)
|
||||
{
|
||||
if (i->type == typeQuit)
|
||||
uiprivUserBug("You can not call uiMenuItemOnClicked() on a Quit item; use uiOnShouldQuit() instead.");
|
||||
i->onClicked = f;
|
||||
i->onClickedData = data;
|
||||
}
|
||||
|
||||
int uiMenuItemChecked(uiMenuItem *i)
|
||||
{
|
||||
return i->checked != FALSE;
|
||||
}
|
||||
|
||||
void uiMenuItemSetChecked(uiMenuItem *i, int checked)
|
||||
{
|
||||
// use explicit values
|
||||
i->checked = FALSE;
|
||||
if (checked)
|
||||
i->checked = TRUE;
|
||||
sync(i);
|
||||
}
|
||||
|
||||
static uiMenuItem *newItem(uiMenu *m, int type, const char *name)
|
||||
{
|
||||
uiMenuItem *item;
|
||||
|
||||
if (menusFinalized)
|
||||
uiprivUserBug("You can not create a new menu item after menus have been finalized.");
|
||||
|
||||
if (m->len >= m->cap) {
|
||||
m->cap += grow;
|
||||
m->items = (uiMenuItem **) uiprivRealloc(m->items, m->cap * sizeof (uiMenuItem *), "uiMenuitem *[]");
|
||||
}
|
||||
|
||||
item = uiprivNew(uiMenuItem);
|
||||
|
||||
m->items[m->len] = item;
|
||||
m->len++;
|
||||
|
||||
item->type = type;
|
||||
switch (item->type) {
|
||||
case typeQuit:
|
||||
item->name = toUTF16("Quit");
|
||||
break;
|
||||
case typePreferences:
|
||||
item->name = toUTF16("Preferences...");
|
||||
break;
|
||||
case typeAbout:
|
||||
item->name = toUTF16("About");
|
||||
break;
|
||||
case typeSeparator:
|
||||
break;
|
||||
default:
|
||||
item->name = toUTF16(name);
|
||||
break;
|
||||
}
|
||||
|
||||
if (item->type != typeSeparator) {
|
||||
item->id = curID;
|
||||
curID++;
|
||||
}
|
||||
|
||||
if (item->type == typeQuit) {
|
||||
// can't call uiMenuItemOnClicked() here
|
||||
item->onClicked = onQuitClicked;
|
||||
item->onClickedData = NULL;
|
||||
} else
|
||||
uiMenuItemOnClicked(item, defaultOnClicked, NULL);
|
||||
|
||||
return item;
|
||||
}
|
||||
|
||||
uiMenuItem *uiMenuAppendItem(uiMenu *m, const char *name)
|
||||
{
|
||||
return newItem(m, typeRegular, name);
|
||||
}
|
||||
|
||||
uiMenuItem *uiMenuAppendCheckItem(uiMenu *m, const char *name)
|
||||
{
|
||||
return newItem(m, typeCheckbox, name);
|
||||
}
|
||||
|
||||
uiMenuItem *uiMenuAppendQuitItem(uiMenu *m)
|
||||
{
|
||||
if (hasQuit)
|
||||
uiprivUserBug("You can not have multiple Quit menu items in a program.");
|
||||
hasQuit = TRUE;
|
||||
newItem(m, typeSeparator, NULL);
|
||||
return newItem(m, typeQuit, NULL);
|
||||
}
|
||||
|
||||
uiMenuItem *uiMenuAppendPreferencesItem(uiMenu *m)
|
||||
{
|
||||
if (hasPreferences)
|
||||
uiprivUserBug("You can not have multiple Preferences menu items in a program.");
|
||||
hasPreferences = TRUE;
|
||||
newItem(m, typeSeparator, NULL);
|
||||
return newItem(m, typePreferences, NULL);
|
||||
}
|
||||
|
||||
uiMenuItem *uiMenuAppendAboutItem(uiMenu *m)
|
||||
{
|
||||
if (hasAbout)
|
||||
// TODO place these uiprivImplBug() and uiprivUserBug() strings in a header
|
||||
uiprivUserBug("You can not have multiple About menu items in a program.");
|
||||
hasAbout = TRUE;
|
||||
newItem(m, typeSeparator, NULL);
|
||||
return newItem(m, typeAbout, NULL);
|
||||
}
|
||||
|
||||
void uiMenuAppendSeparator(uiMenu *m)
|
||||
{
|
||||
newItem(m, typeSeparator, NULL);
|
||||
}
|
||||
|
||||
uiMenu *uiNewMenu(const char *name)
|
||||
{
|
||||
uiMenu *m;
|
||||
|
||||
if (menusFinalized)
|
||||
uiprivUserBug("You can not create a new menu after menus have been finalized.");
|
||||
if (len >= cap) {
|
||||
cap += grow;
|
||||
menus = (uiMenu **) uiprivRealloc(menus, cap * sizeof (uiMenu *), "uiMenu *[]");
|
||||
}
|
||||
|
||||
m = uiprivNew(uiMenu);
|
||||
|
||||
menus[len] = m;
|
||||
len++;
|
||||
|
||||
m->name = toUTF16(name);
|
||||
|
||||
return m;
|
||||
}
|
||||
|
||||
static void appendMenuItem(HMENU menu, uiMenuItem *item)
|
||||
{
|
||||
UINT uFlags;
|
||||
|
||||
uFlags = MF_SEPARATOR;
|
||||
if (item->type != typeSeparator) {
|
||||
uFlags = MF_STRING;
|
||||
if (item->disabled)
|
||||
uFlags |= MF_DISABLED | MF_GRAYED;
|
||||
if (item->checked)
|
||||
uFlags |= MF_CHECKED;
|
||||
}
|
||||
if (AppendMenuW(menu, uFlags, item->id, item->name) == 0)
|
||||
logLastError(L"error appending menu item");
|
||||
|
||||
if (item->len >= item->cap) {
|
||||
item->cap += grow;
|
||||
item->hmenus = (HMENU *) uiprivRealloc(item->hmenus, item->cap * sizeof (HMENU), "HMENU[]");
|
||||
}
|
||||
item->hmenus[item->len] = menu;
|
||||
item->len++;
|
||||
}
|
||||
|
||||
static HMENU makeMenu(uiMenu *m)
|
||||
{
|
||||
HMENU menu;
|
||||
size_t i;
|
||||
|
||||
menu = CreatePopupMenu();
|
||||
if (menu == NULL)
|
||||
logLastError(L"error creating menu");
|
||||
for (i = 0; i < m->len; i++)
|
||||
appendMenuItem(menu, m->items[i]);
|
||||
return menu;
|
||||
}
|
||||
|
||||
HMENU makeMenubar(void)
|
||||
{
|
||||
HMENU menubar;
|
||||
HMENU menu;
|
||||
size_t i;
|
||||
|
||||
menusFinalized = TRUE;
|
||||
|
||||
menubar = CreateMenu();
|
||||
if (menubar == NULL)
|
||||
logLastError(L"error creating menubar");
|
||||
|
||||
for (i = 0; i < len; i++) {
|
||||
menu = makeMenu(menus[i]);
|
||||
if (AppendMenuW(menubar, MF_POPUP | MF_STRING, (UINT_PTR) menu, menus[i]->name) == 0)
|
||||
logLastError(L"error appending menu to menubar");
|
||||
}
|
||||
|
||||
return menubar;
|
||||
}
|
||||
|
||||
void runMenuEvent(WORD id, uiWindow *w)
|
||||
{
|
||||
uiMenu *m;
|
||||
uiMenuItem *item;
|
||||
size_t i, j;
|
||||
|
||||
// this isn't optimal, but it works, and it should be just fine for most cases
|
||||
for (i = 0; i < len; i++) {
|
||||
m = menus[i];
|
||||
for (j = 0; j < m->len; j++) {
|
||||
item = m->items[j];
|
||||
if (item->id == id)
|
||||
goto found;
|
||||
}
|
||||
}
|
||||
// no match
|
||||
uiprivImplBug("unknown menu ID %hu in runMenuEvent()", id);
|
||||
|
||||
found:
|
||||
// first toggle checkboxes, if any
|
||||
if (item->type == typeCheckbox)
|
||||
uiMenuItemSetChecked(item, !uiMenuItemChecked(item));
|
||||
|
||||
// then run the event
|
||||
(*(item->onClicked))(item, w, item->onClickedData);
|
||||
}
|
||||
|
||||
static void freeMenu(uiMenu *m, HMENU submenu)
|
||||
{
|
||||
size_t i;
|
||||
uiMenuItem *item;
|
||||
size_t j;
|
||||
|
||||
for (i = 0; i < m->len; i++) {
|
||||
item = m->items[i];
|
||||
for (j = 0; j < item->len; j++)
|
||||
if (item->hmenus[j] == submenu)
|
||||
break;
|
||||
if (j >= item->len)
|
||||
uiprivImplBug("submenu handle %p not found in freeMenu()", submenu);
|
||||
for (; j < item->len - 1; j++)
|
||||
item->hmenus[j] = item->hmenus[j + 1];
|
||||
item->hmenus[j] = NULL;
|
||||
item->len--;
|
||||
}
|
||||
}
|
||||
|
||||
void freeMenubar(HMENU menubar)
|
||||
{
|
||||
size_t i;
|
||||
MENUITEMINFOW mi;
|
||||
|
||||
for (i = 0; i < len; i++) {
|
||||
ZeroMemory(&mi, sizeof (MENUITEMINFOW));
|
||||
mi.cbSize = sizeof (MENUITEMINFOW);
|
||||
mi.fMask = MIIM_SUBMENU;
|
||||
if (GetMenuItemInfoW(menubar, i, TRUE, &mi) == 0)
|
||||
logLastError(L"error getting menu to delete item references from");
|
||||
freeMenu(menus[i], mi.hSubMenu);
|
||||
}
|
||||
// no need to worry about destroying any menus; destruction of the window they're in will do it for us
|
||||
}
|
||||
|
||||
void uninitMenus(void)
|
||||
{
|
||||
uiMenu *m;
|
||||
uiMenuItem *item;
|
||||
size_t i, j;
|
||||
|
||||
for (i = 0; i < len; i++) {
|
||||
m = menus[i];
|
||||
uiprivFree(m->name);
|
||||
for (j = 0; j < m->len; j++) {
|
||||
item = m->items[j];
|
||||
if (item->len != 0)
|
||||
// LONGTERM uiprivUserBug()?
|
||||
uiprivImplBug("menu item %p (%ws) still has uiWindows attached; did you forget to destroy some windows?", item, item->name);
|
||||
if (item->name != NULL)
|
||||
uiprivFree(item->name);
|
||||
if (item->hmenus != NULL)
|
||||
uiprivFree(item->hmenus);
|
||||
uiprivFree(item);
|
||||
}
|
||||
if (m->items != NULL)
|
||||
uiprivFree(m->items);
|
||||
uiprivFree(m);
|
||||
}
|
||||
if (menus != NULL)
|
||||
uiprivFree(menus);
|
||||
}
|
||||
Reference in new issue
Block a user