1
0
mirror of https://github.com/gryf/wmaker.git synced 2025-12-23 06:38:05 +01:00
Files
wmaker/src/winmenu.c
Brad Jorsch 79e1bb53d3 Menu positioning bug
I noticed a bug today in menu workspace positioning (in the next tree):
if I right-click and hold the button down at the left edge of the
screen, the menu appears (correctly) half off the edge but will *not*
slide back onto the screen. Compare the behavior to right-clicking at
the right edge of the screen: the menu appears half off the edge, but
sliding the mouse to the edge causes the menu to slide until it is fully
visible.

Also, opening a submenu in this state positions the submenu as if the
menu were fully on the screen, leaving a gap between the menu and the
submenu.

If the menu happens to also go off the bottom of the screen, moving the
mouse to the bottom edge causes the issue to be magically fixed as soon
as the sliding upwards begins.

I also note inconsistent behavior when simply right-clicking (without
holding) to bring up the menu: at the right or bottom edge the menu
appears in the correct partially off the edge position, but at the left
edge it immediately jumps to be fully on-screen, almost as if
WrapMenus = YES were set for the left edge only.

I bisected it to d316260395. As far as I
can tell the "fix" there was to position the menu at the correct
negative X position but then lie to wmaker so it thought the menu was at
X=0. Presumably the "WrapMenus" behavior was the intended result.

Since (AFAIK) the window menu is the only one with this problem, why
don't we just check if x is too negative in OpenWindowMenu()?
2010-04-08 15:53:50 +02:00

667 lines
17 KiB
C

/* winmenu.c - command menu for windows
*
* Window Maker window manager
*
* Copyright (c) 1997-2003 Alfredo K. Kojima
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307,
* USA.
*/
#include "wconfig.h"
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
#include <X11/Xlib.h>
#include <X11/Xutil.h>
#include "WindowMaker.h"
#include "actions.h"
#include "menu.h"
#include "funcs.h"
#include "window.h"
#include "client.h"
#include "application.h"
#include "keybind.h"
#include "framewin.h"
#include "workspace.h"
#include "winspector.h"
#include "dialog.h"
#include "stacking.h"
#include "icon.h"
#include "xinerama.h"
#define MC_MAXIMIZE 0
#define MC_MINIATURIZE 1
#define MC_SHADE 2
#define MC_HIDE 3
#define MC_MOVERESIZE 4
#define MC_SELECT 5
#define MC_DUMMY_MOVETO 6
#define MC_PROPERTIES 7
#define MC_OPTIONS 8
#define MC_SHORTCUT 8
#define MC_CLOSE 9
#define MC_KILL 10
#define WO_KEEP_ON_TOP 0
#define WO_KEEP_AT_BOTTOM 1
#define WO_OMNIPRESENT 2
#define WO_ENTRIES 3
/**** Global data ***/
extern Time LastTimestamp;
extern Atom _XA_WM_DELETE_WINDOW;
extern Atom _XA_GNUSTEP_WM_MINIATURIZE_WINDOW;
extern WShortKey wKeyBindings[WKBD_LAST];
extern WPreferences wPreferences;
static void updateOptionsMenu(WMenu * menu, WWindow * wwin);
static void execWindowOptionCommand(WMenu * menu, WMenuEntry * entry)
{
WWindow *wwin = (WWindow *) entry->clientdata;
switch (entry->order) {
case WO_KEEP_ON_TOP:
if (wwin->frame->core->stacking->window_level != WMFloatingLevel)
ChangeStackingLevel(wwin->frame->core, WMFloatingLevel);
else
ChangeStackingLevel(wwin->frame->core, WMNormalLevel);
break;
case WO_KEEP_AT_BOTTOM:
if (wwin->frame->core->stacking->window_level != WMSunkenLevel)
ChangeStackingLevel(wwin->frame->core, WMSunkenLevel);
else
ChangeStackingLevel(wwin->frame->core, WMNormalLevel);
break;
case WO_OMNIPRESENT:
wWindowSetOmnipresent(wwin, !wwin->flags.omnipresent);
break;
}
}
static void execMenuCommand(WMenu * menu, WMenuEntry * entry)
{
WWindow *wwin = (WWindow *) entry->clientdata;
WApplication *wapp;
CloseWindowMenu(menu->frame->screen_ptr);
switch (entry->order) {
case MC_CLOSE:
/* send delete message */
wClientSendProtocol(wwin, _XA_WM_DELETE_WINDOW, LastTimestamp);
break;
case MC_KILL:
wretain(wwin);
if (wPreferences.dont_confirm_kill
|| wMessageDialog(menu->frame->screen_ptr, _("Kill Application"),
_
("This will kill the application.\nAny unsaved changes will be lost.\nPlease confirm."),
_("Yes"), _("No"), NULL) == WAPRDefault) {
if (!wwin->flags.destroyed)
wClientKill(wwin);
}
wrelease(wwin);
break;
case MC_MINIATURIZE:
if (wwin->flags.miniaturized) {
wDeiconifyWindow(wwin);
} else {
if (wwin->protocols.MINIATURIZE_WINDOW) {
wClientSendProtocol(wwin, _XA_GNUSTEP_WM_MINIATURIZE_WINDOW, LastTimestamp);
} else {
wIconifyWindow(wwin);
}
}
break;
case MC_MAXIMIZE:
if (wwin->flags.maximized)
wUnmaximizeWindow(wwin);
else
wMaximizeWindow(wwin, MAX_VERTICAL | MAX_HORIZONTAL);
break;
case MC_SHADE:
if (wwin->flags.shaded)
wUnshadeWindow(wwin);
else
wShadeWindow(wwin);
break;
case MC_SELECT:
if (!wwin->flags.miniaturized)
wSelectWindow(wwin, !wwin->flags.selected);
else
wIconSelect(wwin->icon);
break;
case MC_MOVERESIZE:
wKeyboardMoveResizeWindow(wwin);
break;
case MC_PROPERTIES:
wShowInspectorForWindow(wwin);
break;
case MC_HIDE:
wapp = wApplicationOf(wwin->main_window);
wHideApplication(wapp);
break;
}
}
static void switchWSCommand(WMenu * menu, WMenuEntry * entry)
{
WWindow *wwin = (WWindow *) entry->clientdata;
wSelectWindow(wwin, False);
wWindowChangeWorkspace(wwin, entry->order);
}
static void makeShortcutCommand(WMenu * menu, WMenuEntry * entry)
{
WWindow *wwin = (WWindow *) entry->clientdata;
WScreen *scr = wwin->screen_ptr;
int index = entry->order - WO_ENTRIES;
if (scr->shortcutWindows[index]) {
WMFreeArray(scr->shortcutWindows[index]);
scr->shortcutWindows[index] = NULL;
}
if (wwin->flags.selected && scr->selected_windows) {
scr->shortcutWindows[index] = WMDuplicateArray(scr->selected_windows);
/*WMRemoveFromArray(scr->shortcutWindows[index], wwin);
WMInsertInArray(scr->shortcutWindows[index], 0, wwin); */
} else {
scr->shortcutWindows[index] = WMCreateArray(4);
WMAddToArray(scr->shortcutWindows[index], wwin);
}
wSelectWindow(wwin, !wwin->flags.selected);
XFlush(dpy);
wusleep(3000);
wSelectWindow(wwin, !wwin->flags.selected);
XFlush(dpy);
}
static void updateWorkspaceMenu(WMenu * menu)
{
WScreen *scr = menu->frame->screen_ptr;
char title[MAX_WORKSPACENAME_WIDTH + 1];
int i;
for (i = 0; i < scr->workspace_count; i++) {
if (i < menu->entry_no) {
if (strcmp(menu->entries[i]->text, scr->workspaces[i]->name) != 0) {
wfree(menu->entries[i]->text);
strncpy(title, scr->workspaces[i]->name, MAX_WORKSPACENAME_WIDTH);
title[MAX_WORKSPACENAME_WIDTH] = 0;
menu->entries[i]->text = wstrdup(title);
menu->flags.realized = 0;
}
} else {
strncpy(title, scr->workspaces[i]->name, MAX_WORKSPACENAME_WIDTH);
title[MAX_WORKSPACENAME_WIDTH] = 0;
wMenuAddCallback(menu, title, switchWSCommand, NULL);
menu->flags.realized = 0;
}
}
if (!menu->flags.realized)
wMenuRealize(menu);
}
static void updateMakeShortcutMenu(WMenu * menu, WWindow * wwin)
{
WMenu *smenu = menu->cascades[menu->entries[MC_SHORTCUT]->cascade];
int i;
char *buffer;
int buflen;
KeyCode kcode;
if (!smenu)
return;
buflen = strlen(_("Set Shortcut")) + 16;
buffer = wmalloc(buflen);
for (i = WO_ENTRIES; i < smenu->entry_no; i++) {
char *tmp;
int shortcutNo = i - WO_ENTRIES;
WMenuEntry *entry = smenu->entries[i];
WMArray *shortSelWindows = wwin->screen_ptr->shortcutWindows[shortcutNo];
snprintf(buffer, buflen, "%s %i", _("Set Shortcut"), shortcutNo + 1);
if (!shortSelWindows) {
entry->flags.indicator_on = 0;
} else {
entry->flags.indicator_on = 1;
if (WMCountInArray(shortSelWindows, wwin))
entry->flags.indicator_type = MI_DIAMOND;
else
entry->flags.indicator_type = MI_CHECK;
}
if (strcmp(buffer, entry->text) != 0) {
wfree(entry->text);
entry->text = wstrdup(buffer);
smenu->flags.realized = 0;
}
kcode = wKeyBindings[WKBD_WINDOW1 + shortcutNo].keycode;
if (kcode) {
if ((tmp = XKeysymToString(XKeycodeToKeysym(dpy, kcode, 0)))
&& (!entry->rtext || strcmp(tmp, entry->rtext) != 0)) {
if (entry->rtext)
wfree(entry->rtext);
entry->rtext = wstrdup(tmp);
smenu->flags.realized = 0;
}
wMenuSetEnabled(smenu, i, True);
} else {
wMenuSetEnabled(smenu, i, False);
if (entry->rtext) {
wfree(entry->rtext);
entry->rtext = NULL;
smenu->flags.realized = 0;
}
}
entry->clientdata = wwin;
}
wfree(buffer);
if (!smenu->flags.realized)
wMenuRealize(smenu);
}
static void updateOptionsMenu(WMenu * menu, WWindow * wwin)
{
WMenu *smenu = menu->cascades[menu->entries[MC_OPTIONS]->cascade];
/* keep on top check */
smenu->entries[WO_KEEP_ON_TOP]->clientdata = wwin;
smenu->entries[WO_KEEP_ON_TOP]->flags.indicator_on =
(wwin->frame->core->stacking->window_level == WMFloatingLevel) ? 1 : 0;
wMenuSetEnabled(smenu, WO_KEEP_ON_TOP, !wwin->flags.miniaturized);
/* keep at bottom check */
smenu->entries[WO_KEEP_AT_BOTTOM]->clientdata = wwin;
smenu->entries[WO_KEEP_AT_BOTTOM]->flags.indicator_on =
(wwin->frame->core->stacking->window_level == WMSunkenLevel) ? 1 : 0;
wMenuSetEnabled(smenu, WO_KEEP_AT_BOTTOM, !wwin->flags.miniaturized);
/* omnipresent check */
smenu->entries[WO_OMNIPRESENT]->clientdata = wwin;
smenu->entries[WO_OMNIPRESENT]->flags.indicator_on = IS_OMNIPRESENT(wwin);
smenu->flags.realized = 0;
wMenuRealize(smenu);
}
static WMenu *makeWorkspaceMenu(WScreen * scr)
{
WMenu *menu;
menu = wMenuCreate(scr, NULL, False);
if (!menu) {
wwarning(_("could not create submenu for window menu"));
return NULL;
}
updateWorkspaceMenu(menu);
return menu;
}
static WMenu *makeMakeShortcutMenu(WScreen * scr, WMenu * menu)
{
/*
WMenu *menu;
*/
int i;
/*
menu = wMenuCreate(scr, NULL, False);
if (!menu) {
wwarning(_("could not create submenu for window menu"));
return NULL;
}
*/
for (i = 0; i < MAX_WINDOW_SHORTCUTS; i++) {
WMenuEntry *entry;
entry = wMenuAddCallback(menu, "", makeShortcutCommand, NULL);
entry->flags.indicator = 1;
}
return menu;
}
static WMenu *makeOptionsMenu(WScreen * scr)
{
WMenu *menu;
WMenuEntry *entry;
menu = wMenuCreate(scr, NULL, False);
if (!menu) {
wwarning(_("could not create submenu for window menu"));
return NULL;
}
entry = wMenuAddCallback(menu, _("Keep on top"), execWindowOptionCommand, NULL);
entry->flags.indicator = 1;
entry->flags.indicator_type = MI_CHECK;
entry = wMenuAddCallback(menu, _("Keep at bottom"), execWindowOptionCommand, NULL);
entry->flags.indicator = 1;
entry->flags.indicator_type = MI_CHECK;
entry = wMenuAddCallback(menu, _("Omnipresent"), execWindowOptionCommand, NULL);
entry->flags.indicator = 1;
entry->flags.indicator_type = MI_CHECK;
return menu;
}
static WMenu *createWindowMenu(WScreen * scr)
{
WMenu *menu;
KeyCode kcode;
WMenuEntry *entry;
char *tmp;
menu = wMenuCreate(scr, NULL, False);
/*
* Warning: If you make some change that affects the order of the
* entries, you must update the command #defines in the top of
* this file.
*/
entry = wMenuAddCallback(menu, _("Maximize"), execMenuCommand, NULL);
if (wKeyBindings[WKBD_MAXIMIZE].keycode != 0) {
kcode = wKeyBindings[WKBD_MAXIMIZE].keycode;
if (kcode && (tmp = XKeysymToString(XKeycodeToKeysym(dpy, kcode, 0))))
entry->rtext = wstrdup(tmp);
}
entry = wMenuAddCallback(menu, _("Miniaturize"), execMenuCommand, NULL);
if (wKeyBindings[WKBD_MINIATURIZE].keycode != 0) {
kcode = wKeyBindings[WKBD_MINIATURIZE].keycode;
if (kcode && (tmp = XKeysymToString(XKeycodeToKeysym(dpy, kcode, 0))))
entry->rtext = wstrdup(tmp);
}
entry = wMenuAddCallback(menu, _("Shade"), execMenuCommand, NULL);
if (wKeyBindings[WKBD_SHADE].keycode != 0) {
kcode = wKeyBindings[WKBD_SHADE].keycode;
if (kcode && (tmp = XKeysymToString(XKeycodeToKeysym(dpy, kcode, 0))))
entry->rtext = wstrdup(tmp);
}
entry = wMenuAddCallback(menu, _("Hide"), execMenuCommand, NULL);
if (wKeyBindings[WKBD_HIDE].keycode != 0) {
kcode = wKeyBindings[WKBD_HIDE].keycode;
if (kcode && (tmp = XKeysymToString(XKeycodeToKeysym(dpy, kcode, 0))))
entry->rtext = wstrdup(tmp);
}
entry = wMenuAddCallback(menu, _("Resize/Move"), execMenuCommand, NULL);
if (wKeyBindings[WKBD_MOVERESIZE].keycode != 0) {
kcode = wKeyBindings[WKBD_MOVERESIZE].keycode;
if (kcode && (tmp = XKeysymToString(XKeycodeToKeysym(dpy, kcode, 0))))
entry->rtext = wstrdup(tmp);
}
entry = wMenuAddCallback(menu, _("Select"), execMenuCommand, NULL);
if (wKeyBindings[WKBD_SELECT].keycode != 0) {
kcode = wKeyBindings[WKBD_SELECT].keycode;
if (kcode && (tmp = XKeysymToString(XKeycodeToKeysym(dpy, kcode, 0))))
entry->rtext = wstrdup(tmp);
}
entry = wMenuAddCallback(menu, _("Move To"), NULL, NULL);
scr->workspace_submenu = makeWorkspaceMenu(scr);
if (scr->workspace_submenu)
wMenuEntrySetCascade(menu, entry, scr->workspace_submenu);
entry = wMenuAddCallback(menu, _("Attributes..."), execMenuCommand, NULL);
entry = wMenuAddCallback(menu, _("Options"), NULL, NULL);
wMenuEntrySetCascade(menu, entry, makeMakeShortcutMenu(scr, makeOptionsMenu(scr)));
/*
entry = wMenuAddCallback(menu, _("Select Shortcut"), NULL, NULL);
wMenuEntrySetCascade(menu, entry, makeMakeShortcutMenu(scr));
*/
entry = wMenuAddCallback(menu, _("Close"), execMenuCommand, NULL);
if (wKeyBindings[WKBD_CLOSE].keycode != 0) {
kcode = wKeyBindings[WKBD_CLOSE].keycode;
if (kcode && (tmp = XKeysymToString(XKeycodeToKeysym(dpy, kcode, 0))))
entry->rtext = wstrdup(tmp);
}
entry = wMenuAddCallback(menu, _("Kill"), execMenuCommand, NULL);
return menu;
}
void CloseWindowMenu(WScreen * scr)
{
if (scr->window_menu) {
if (scr->window_menu->flags.mapped)
wMenuUnmap(scr->window_menu);
if (scr->window_menu->entries[0]->clientdata) {
WWindow *wwin = (WWindow *) scr->window_menu->entries[0]->clientdata;
wwin->flags.menu_open_for_me = 0;
}
scr->window_menu->entries[0]->clientdata = NULL;
}
}
static void updateMenuForWindow(WMenu * menu, WWindow * wwin)
{
WApplication *wapp = wApplicationOf(wwin->main_window);
WScreen *scr = wwin->screen_ptr;
int i;
updateOptionsMenu(menu, wwin);
updateMakeShortcutMenu(menu, wwin);
wMenuSetEnabled(menu, MC_HIDE, wapp != NULL && !WFLAGP(wapp->main_window_desc, no_appicon));
wMenuSetEnabled(menu, MC_CLOSE, (wwin->protocols.DELETE_WINDOW && !WFLAGP(wwin, no_closable)));
if (wwin->flags.miniaturized) {
static char *text = NULL;
if (!text)
text = _("Deminiaturize");
menu->entries[MC_MINIATURIZE]->text = text;
} else {
static char *text = NULL;
if (!text)
text = _("Miniaturize");
menu->entries[MC_MINIATURIZE]->text = text;
}
wMenuSetEnabled(menu, MC_MINIATURIZE, !WFLAGP(wwin, no_miniaturizable));
if (wwin->flags.maximized) {
static char *text = NULL;
if (!text)
text = _("Unmaximize");
menu->entries[MC_MAXIMIZE]->text = text;
} else {
static char *text = NULL;
if (!text)
text = _("Maximize");
menu->entries[MC_MAXIMIZE]->text = text;
}
wMenuSetEnabled(menu, MC_MAXIMIZE, IS_RESIZABLE(wwin));
wMenuSetEnabled(menu, MC_MOVERESIZE, IS_RESIZABLE(wwin)
&& !wwin->flags.miniaturized);
if (wwin->flags.shaded) {
static char *text = NULL;
if (!text)
text = _("Unshade");
menu->entries[MC_SHADE]->text = text;
} else {
static char *text = NULL;
if (!text)
text = _("Shade");
menu->entries[MC_SHADE]->text = text;
}
wMenuSetEnabled(menu, MC_SHADE, !WFLAGP(wwin, no_shadeable)
&& !wwin->flags.miniaturized);
wMenuSetEnabled(menu, MC_DUMMY_MOVETO, !IS_OMNIPRESENT(wwin));
if (!wwin->flags.inspector_open) {
wMenuSetEnabled(menu, MC_PROPERTIES, True);
} else {
wMenuSetEnabled(menu, MC_PROPERTIES, False);
}
/* set the client data of the entries to the window */
for (i = 0; i < menu->entry_no; i++) {
menu->entries[i]->clientdata = wwin;
}
for (i = 0; i < scr->workspace_submenu->entry_no; i++) {
scr->workspace_submenu->entries[i]->clientdata = wwin;
if (i == scr->current_workspace) {
wMenuSetEnabled(scr->workspace_submenu, i, False);
} else {
wMenuSetEnabled(scr->workspace_submenu, i, True);
}
}
menu->flags.realized = 0;
wMenuRealize(menu);
}
void OpenWindowMenu(WWindow * wwin, int x, int y, int keyboard)
{
WMenu *menu;
WScreen *scr = wwin->screen_ptr;
WMRect rect;
wwin->flags.menu_open_for_me = 1;
if (!scr->window_menu) {
scr->window_menu = createWindowMenu(scr);
/* hack to save some memory allocation/deallocation */
wfree(scr->window_menu->entries[MC_MINIATURIZE]->text);
wfree(scr->window_menu->entries[MC_MAXIMIZE]->text);
wfree(scr->window_menu->entries[MC_SHADE]->text);
} else {
updateWorkspaceMenu(scr->workspace_submenu);
}
menu = scr->window_menu;
if (menu->flags.mapped) {
wMenuUnmap(menu);
if (menu->entries[0]->clientdata == wwin) {
return;
}
}
updateMenuForWindow(menu, wwin);
x -= menu->frame->core->width / 2;
if (x + menu->frame->core->width > wwin->frame_x + wwin->frame->core->width)
x = wwin->frame_x + wwin->frame->core->width - menu->frame->core->width;
if (x < wwin->frame_x)
x = wwin->frame_x;
rect = wGetRectForHead(menu->frame->screen_ptr,
wGetHeadForPointerLocation(menu->frame->screen_ptr));
if (x < rect.pos.x - menu->frame->core->width / 2)
x = rect.pos.x - menu->frame->core->width / 2;
if (y < rect.pos.y)
y = rect.pos.y;
if (!wwin->flags.internal_window)
wMenuMapAt(menu, x, y, keyboard);
}
void OpenMiniwindowMenu(WWindow * wwin, int x, int y)
{
WMenu *menu;
WScreen *scr = wwin->screen_ptr;
wwin->flags.menu_open_for_me = 1;
if (!scr->window_menu) {
scr->window_menu = createWindowMenu(scr);
/* hack to save some memory allocation/deallocation */
wfree(scr->window_menu->entries[MC_MINIATURIZE]->text);
wfree(scr->window_menu->entries[MC_MAXIMIZE]->text);
wfree(scr->window_menu->entries[MC_SHADE]->text);
} else {
updateWorkspaceMenu(scr->workspace_submenu);
}
menu = scr->window_menu;
if (menu->flags.mapped) {
wMenuUnmap(menu);
if (menu->entries[0]->clientdata == wwin) {
return;
}
}
updateMenuForWindow(menu, wwin);
x -= menu->frame->core->width / 2;
wMenuMapAt(menu, x, y, False);
}