e4256764c9652a7081cd65c6e2d987ff21cbf62b
[wmaker-crm.git] / src / workspace.c
blobe4256764c9652a7081cd65c6e2d987ff21cbf62b
1 /* workspace.c- Workspace management
3 * Window Maker window manager
5 * Copyright (c) 1997-2003 Alfredo K. Kojima
7 * This program is free software; you can redistribute it and/or modify
8 * it under the terms of the GNU General Public License as published by
9 * the Free Software Foundation; either version 2 of the License, or
10 * (at your option) any later version.
12 * This program is distributed in the hope that it will be useful,
13 * but WITHOUT ANY WARRANTY; without even the implied warranty of
14 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
15 * GNU General Public License for more details.
17 * You should have received a copy of the GNU General Public License along
18 * with this program; if not, write to the Free Software Foundation, Inc.,
19 * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
21 #include "wconfig.h"
23 #include <X11/Xlib.h>
24 #include <X11/Xutil.h>
25 #ifdef SHAPE
26 #include <X11/extensions/shape.h>
27 #endif
29 #include <stdlib.h>
30 #include <stdio.h>
31 #include <stdint.h>
32 #include <unistd.h>
33 #include <ctype.h>
34 #include <string.h>
35 #include <time.h>
36 #include <sys/time.h>
38 #include "WindowMaker.h"
39 #include "framewin.h"
40 #include "window.h"
41 #include "icon.h"
42 #include "misc.h"
43 #include "menu.h"
44 #include "application.h"
45 #include "dock.h"
46 #include "actions.h"
47 #include "workspace.h"
48 #include "appicon.h"
49 #include "wmspec.h"
50 #include "xinerama.h"
51 #include "event.h"
53 #define MC_NEW 0
54 #define MC_DESTROY_LAST 1
55 #define MC_LAST_USED 2
56 /* index of the first workspace menu entry */
57 #define MC_WORKSPACE1 3
59 #define MAX_SHORTCUT_LENGTH 32
60 #define WORKSPACE_NAME_DISPLAY_PADDING 32
62 extern int ignore_wks_change;
63 extern WPreferences wPreferences;
64 extern XContext wVEdgeContext;
65 extern WShortKey wKeyBindings[WKBD_LAST];
67 static WMPropList *dWorkspaces = NULL;
68 static WMPropList *dClip, *dName;
70 static void make_keys(void)
72 if (dWorkspaces != NULL)
73 return;
75 dWorkspaces = WMCreatePLString("Workspaces");
76 dName = WMCreatePLString("Name");
77 dClip = WMCreatePLString("Clip");
80 void wWorkspaceMake(WScreen * scr, int count)
82 while (count > 0) {
83 wWorkspaceNew(scr);
84 count--;
88 int wWorkspaceNew(WScreen *scr)
90 WWorkspace *wspace, **list;
91 int i;
93 if (scr->workspace_count < MAX_WORKSPACES) {
94 scr->workspace_count++;
96 wspace = wmalloc(sizeof(WWorkspace));
97 wspace->name = NULL;
98 wspace->clip = NULL;
100 if (!wspace->name) {
101 wspace->name = wmalloc(strlen(_("Workspace %i")) + 8);
102 sprintf(wspace->name, _("Workspace %i"), scr->workspace_count);
105 if (!wPreferences.flags.noclip)
106 wspace->clip = wDockCreate(scr, WM_CLIP, NULL);
108 list = wmalloc(sizeof(WWorkspace *) * scr->workspace_count);
110 for (i = 0; i < scr->workspace_count - 1; i++)
111 list[i] = scr->workspaces[i];
113 list[i] = wspace;
114 if (scr->workspaces)
115 wfree(scr->workspaces);
117 scr->workspaces = list;
119 wWorkspaceMenuUpdate(scr, scr->workspace_menu);
120 wWorkspaceMenuUpdate(scr, scr->clip_ws_menu);
121 wNETWMUpdateDesktop(scr);
122 WMPostNotificationName(WMNWorkspaceCreated, scr, (void *)(uintptr_t) (scr->workspace_count - 1));
123 XFlush(dpy);
125 return scr->workspace_count - 1;
128 return -1;
131 Bool wWorkspaceDelete(WScreen * scr, int workspace)
133 WWindow *tmp;
134 WWorkspace **list;
135 int i, j;
137 if (workspace <= 0)
138 return False;
140 /* verify if workspace is in use by some window */
141 tmp = scr->focused_window;
142 while (tmp) {
143 if (!IS_OMNIPRESENT(tmp) && tmp->frame->workspace == workspace)
144 return False;
145 tmp = tmp->prev;
148 if (!wPreferences.flags.noclip) {
149 wDockDestroy(scr->workspaces[workspace]->clip);
150 scr->workspaces[workspace]->clip = NULL;
153 list = wmalloc(sizeof(WWorkspace *) * (scr->workspace_count - 1));
154 j = 0;
155 for (i = 0; i < scr->workspace_count; i++) {
156 if (i != workspace) {
157 list[j++] = scr->workspaces[i];
158 } else {
159 if (scr->workspaces[i]->name)
160 wfree(scr->workspaces[i]->name);
161 wfree(scr->workspaces[i]);
164 wfree(scr->workspaces);
165 scr->workspaces = list;
167 scr->workspace_count--;
169 /* update menu */
170 wWorkspaceMenuUpdate(scr, scr->workspace_menu);
171 /* clip workspace menu */
172 wWorkspaceMenuUpdate(scr, scr->clip_ws_menu);
174 /* update also window menu */
175 if (scr->workspace_submenu) {
176 WMenu *menu = scr->workspace_submenu;
178 i = menu->entry_no;
179 while (i > scr->workspace_count)
180 wMenuRemoveItem(menu, --i);
181 wMenuRealize(menu);
183 /* and clip menu */
184 if (scr->clip_submenu) {
185 WMenu *menu = scr->clip_submenu;
187 i = menu->entry_no;
188 while (i > scr->workspace_count)
189 wMenuRemoveItem(menu, --i);
190 wMenuRealize(menu);
192 wNETWMUpdateDesktop(scr);
193 WMPostNotificationName(WMNWorkspaceDestroyed, scr, (void *)(uintptr_t) (scr->workspace_count - 1));
195 if (scr->current_workspace >= scr->workspace_count)
196 wWorkspaceChange(scr, scr->workspace_count - 1);
197 if (scr->last_workspace >= scr->workspace_count)
198 scr->last_workspace = 0;
200 return True;
203 typedef struct WorkspaceNameData {
204 int count;
205 RImage *back;
206 RImage *text;
207 time_t timeout;
208 } WorkspaceNameData;
210 static void hideWorkspaceName(void *data)
212 WScreen *scr = (WScreen *) data;
214 if (!scr->workspace_name_data || scr->workspace_name_data->count == 0
215 || time(NULL) > scr->workspace_name_data->timeout) {
216 XUnmapWindow(dpy, scr->workspace_name);
218 if (scr->workspace_name_data) {
219 RReleaseImage(scr->workspace_name_data->back);
220 RReleaseImage(scr->workspace_name_data->text);
221 wfree(scr->workspace_name_data);
223 scr->workspace_name_data = NULL;
225 scr->workspace_name_timer = NULL;
226 } else {
227 RImage *img = RCloneImage(scr->workspace_name_data->back);
228 Pixmap pix;
230 scr->workspace_name_timer = WMAddTimerHandler(WORKSPACE_NAME_FADE_DELAY, hideWorkspaceName, scr);
232 RCombineImagesWithOpaqueness(img, scr->workspace_name_data->text,
233 scr->workspace_name_data->count * 255 / 10);
235 RConvertImage(scr->rcontext, img, &pix);
237 RReleaseImage(img);
239 XSetWindowBackgroundPixmap(dpy, scr->workspace_name, pix);
240 XClearWindow(dpy, scr->workspace_name);
241 XFreePixmap(dpy, pix);
242 XFlush(dpy);
244 scr->workspace_name_data->count--;
248 static void showWorkspaceName(WScreen * scr, int workspace)
250 WorkspaceNameData *data;
251 RXImage *ximg;
252 Pixmap text, mask;
253 int w, h;
254 int px, py;
255 char *name = scr->workspaces[workspace]->name;
256 int len = strlen(name);
257 int x, y;
258 #ifdef XINERAMA
259 int head;
260 WMRect rect;
261 int xx, yy;
262 #endif
264 if (wPreferences.workspace_name_display_position == WD_NONE || scr->workspace_count < 2) {
265 return;
268 if (scr->workspace_name_timer) {
269 WMDeleteTimerHandler(scr->workspace_name_timer);
270 XUnmapWindow(dpy, scr->workspace_name);
271 XFlush(dpy);
273 scr->workspace_name_timer = WMAddTimerHandler(WORKSPACE_NAME_DELAY, hideWorkspaceName, scr);
275 if (scr->workspace_name_data) {
276 RReleaseImage(scr->workspace_name_data->back);
277 RReleaseImage(scr->workspace_name_data->text);
278 wfree(scr->workspace_name_data);
281 data = wmalloc(sizeof(WorkspaceNameData));
282 data->back = NULL;
284 w = WMWidthOfString(scr->workspace_name_font, name, len);
285 h = WMFontHeight(scr->workspace_name_font);
287 #ifdef XINERAMA
288 head = wGetHeadForPointerLocation(scr);
289 rect = wGetRectForHead(scr, head);
290 if (scr->xine_info.count) {
291 xx = rect.pos.x + (scr->xine_info.screens[head].size.width - (w + 4)) / 2;
292 yy = rect.pos.y + (scr->xine_info.screens[head].size.height - (h + 4)) / 2;
294 else {
295 xx = (scr->scr_width - (w + 4)) / 2;
296 yy = (scr->scr_height - (h + 4)) / 2;
298 #endif
300 switch (wPreferences.workspace_name_display_position) {
301 case WD_TOP:
302 #ifdef XINERAMA
303 px = xx;
304 #else
305 px = (scr->scr_width - (w + 4)) / 2;
306 #endif
307 py = WORKSPACE_NAME_DISPLAY_PADDING;
308 break;
309 case WD_BOTTOM:
310 #ifdef XINERAMA
311 px = xx;
312 #else
313 px = (scr->scr_width - (w + 4)) / 2;
314 #endif
315 py = scr->scr_height - (h + 4 + WORKSPACE_NAME_DISPLAY_PADDING);
316 break;
317 case WD_TOPLEFT:
318 px = WORKSPACE_NAME_DISPLAY_PADDING;
319 py = WORKSPACE_NAME_DISPLAY_PADDING;
320 break;
321 case WD_TOPRIGHT:
322 px = scr->scr_width - (w + 4 + WORKSPACE_NAME_DISPLAY_PADDING);
323 py = WORKSPACE_NAME_DISPLAY_PADDING;
324 break;
325 case WD_BOTTOMLEFT:
326 px = WORKSPACE_NAME_DISPLAY_PADDING;
327 py = scr->scr_height - (h + 4 + WORKSPACE_NAME_DISPLAY_PADDING);
328 break;
329 case WD_BOTTOMRIGHT:
330 px = scr->scr_width - (w + 4 + WORKSPACE_NAME_DISPLAY_PADDING);
331 py = scr->scr_height - (h + 4 + WORKSPACE_NAME_DISPLAY_PADDING);
332 break;
333 case WD_CENTER:
334 default:
335 #ifdef XINERAMA
336 px = xx;
337 py = yy;
338 #else
339 px = (scr->scr_width - (w + 4)) / 2;
340 py = (scr->scr_height - (h + 4)) / 2;
341 #endif
342 break;
344 XResizeWindow(dpy, scr->workspace_name, w + 4, h + 4);
345 XMoveWindow(dpy, scr->workspace_name, px, py);
347 text = XCreatePixmap(dpy, scr->w_win, w + 4, h + 4, scr->w_depth);
348 mask = XCreatePixmap(dpy, scr->w_win, w + 4, h + 4, 1);
350 /*XSetForeground(dpy, scr->mono_gc, 0);
351 XFillRectangle(dpy, mask, scr->mono_gc, 0, 0, w+4, h+4); */
353 XFillRectangle(dpy, text, WMColorGC(scr->black), 0, 0, w + 4, h + 4);
355 for (x = 0; x <= 4; x++) {
356 for (y = 0; y <= 4; y++) {
357 WMDrawString(scr->wmscreen, text, scr->white, scr->workspace_name_font, x, y, name, len);
361 XSetForeground(dpy, scr->mono_gc, 1);
362 XSetBackground(dpy, scr->mono_gc, 0);
364 XCopyPlane(dpy, text, mask, scr->mono_gc, 0, 0, w + 4, h + 4, 0, 0, 1 << (scr->w_depth - 1));
366 /*XSetForeground(dpy, scr->mono_gc, 1); */
367 XSetBackground(dpy, scr->mono_gc, 1);
369 XFillRectangle(dpy, text, WMColorGC(scr->black), 0, 0, w + 4, h + 4);
371 WMDrawString(scr->wmscreen, text, scr->white, scr->workspace_name_font, 2, 2, name, len);
373 #ifdef SHAPE
374 XShapeCombineMask(dpy, scr->workspace_name, ShapeBounding, 0, 0, mask, ShapeSet);
375 #endif
376 XSetWindowBackgroundPixmap(dpy, scr->workspace_name, text);
377 XClearWindow(dpy, scr->workspace_name);
379 data->text = RCreateImageFromDrawable(scr->rcontext, text, None);
381 XFreePixmap(dpy, text);
382 XFreePixmap(dpy, mask);
384 if (!data->text) {
385 XMapRaised(dpy, scr->workspace_name);
386 XFlush(dpy);
388 goto erro;
391 ximg = RGetXImage(scr->rcontext, scr->root_win, px, py, data->text->width, data->text->height);
393 if (!ximg || !ximg->image) {
394 goto erro;
397 XMapRaised(dpy, scr->workspace_name);
398 XFlush(dpy);
400 data->back = RCreateImageFromXImage(scr->rcontext, ximg->image, NULL);
401 RDestroyXImage(scr->rcontext, ximg);
403 if (!data->back) {
404 goto erro;
407 data->count = 10;
409 /* set a timeout for the effect */
410 data->timeout = time(NULL) + 2 + (WORKSPACE_NAME_DELAY + WORKSPACE_NAME_FADE_DELAY * data->count) / 1000;
412 scr->workspace_name_data = data;
414 return;
416 erro:
417 if (scr->workspace_name_timer)
418 WMDeleteTimerHandler(scr->workspace_name_timer);
420 if (data->text)
421 RReleaseImage(data->text);
422 if (data->back)
423 RReleaseImage(data->back);
424 wfree(data);
426 scr->workspace_name_data = NULL;
428 scr->workspace_name_timer = WMAddTimerHandler(WORKSPACE_NAME_DELAY +
429 10 * WORKSPACE_NAME_FADE_DELAY, hideWorkspaceName, scr);
432 void wWorkspaceChange(WScreen *scr, int workspace)
434 if (scr->flags.startup || scr->flags.startup2 || scr->flags.ignore_focus_events)
435 return;
437 if (workspace != scr->current_workspace)
438 wWorkspaceForceChange(scr, workspace);
441 void wWorkspaceRelativeChange(WScreen * scr, int amount)
443 int w;
445 /* While the deiconify animation is going on the window is
446 * still "flying" to its final position and we don't want to
447 * change workspace before the animation finishes, otherwise
448 * the window will land in the new workspace */
449 if (ignore_wks_change)
450 return;
452 w = scr->current_workspace + amount;
454 if (amount < 0) {
455 if (w >= 0) {
456 wWorkspaceChange(scr, w);
457 } else if (wPreferences.ws_cycle) {
458 wWorkspaceChange(scr, scr->workspace_count + w);
460 } else if (amount > 0) {
461 if (w < scr->workspace_count) {
462 wWorkspaceChange(scr, w);
463 } else if (wPreferences.ws_advance) {
464 wWorkspaceChange(scr, WMIN(w, MAX_WORKSPACES - 1));
465 } else if (wPreferences.ws_cycle) {
466 wWorkspaceChange(scr, w % scr->workspace_count);
471 void wWorkspaceForceChange(WScreen * scr, int workspace)
473 WWindow *tmp, *foc = NULL, *foc2 = NULL;
474 WWindow **toUnmap;
475 int toUnmapSize, toUnmapCount;
477 if (workspace >= MAX_WORKSPACES || workspace < 0)
478 return;
480 SendHelperMessage(scr, 'C', workspace + 1, NULL);
482 if (workspace > scr->workspace_count - 1) {
483 wWorkspaceMake(scr, workspace - scr->workspace_count + 1);
486 wClipUpdateForWorkspaceChange(scr, workspace);
488 scr->last_workspace = scr->current_workspace;
489 scr->current_workspace = workspace;
491 wWorkspaceMenuUpdate(scr, scr->workspace_menu);
493 wWorkspaceMenuUpdate(scr, scr->clip_ws_menu);
495 toUnmapSize = 16;
496 toUnmapCount = 0;
497 toUnmap = wmalloc(toUnmapSize * sizeof(WWindow *));
499 if ((tmp = scr->focused_window) != NULL) {
500 if ((IS_OMNIPRESENT(tmp) && (tmp->flags.mapped || tmp->flags.shaded) &&
501 !WFLAGP(tmp, no_focusable)) || tmp->flags.changing_workspace) {
502 foc = tmp;
505 /* foc2 = tmp; will fix annoyance with gnome panel
506 * but will create annoyance for every other application
508 while (tmp) {
509 if (tmp->frame->workspace != workspace && !tmp->flags.selected) {
510 /* unmap windows not on this workspace */
511 if ((tmp->flags.mapped || tmp->flags.shaded) &&
512 !IS_OMNIPRESENT(tmp) && !tmp->flags.changing_workspace) {
513 if (toUnmapCount == toUnmapSize)
515 toUnmapSize *= 2;
516 toUnmap = wrealloc(toUnmap, toUnmapSize * sizeof(WWindow *));
518 toUnmap[toUnmapCount++] = tmp;
520 /* also unmap miniwindows not on this workspace */
521 if (!wPreferences.sticky_icons && tmp->flags.miniaturized &&
522 tmp->icon && !IS_OMNIPRESENT(tmp)) {
523 XUnmapWindow(dpy, tmp->icon->core->window);
524 tmp->icon->mapped = 0;
526 /* update current workspace of omnipresent windows */
527 if (IS_OMNIPRESENT(tmp)) {
528 WApplication *wapp = wApplicationOf(tmp->main_window);
530 tmp->frame->workspace = workspace;
532 if (wapp) {
533 wapp->last_workspace = workspace;
535 if (!foc2 && (tmp->flags.mapped || tmp->flags.shaded)) {
536 foc2 = tmp;
539 } else {
540 /* change selected windows' workspace */
541 if (tmp->flags.selected) {
542 wWindowChangeWorkspace(tmp, workspace);
543 if (!tmp->flags.miniaturized && !foc) {
544 foc = tmp;
546 } else {
547 if (!tmp->flags.hidden) {
548 if (!(tmp->flags.mapped || tmp->flags.miniaturized)) {
549 /* remap windows that are on this workspace */
550 wWindowMap(tmp);
551 if (!foc && !WFLAGP(tmp, no_focusable)) {
552 foc = tmp;
555 /* Also map miniwindow if not omnipresent */
556 if (!wPreferences.sticky_icons &&
557 tmp->flags.miniaturized && !IS_OMNIPRESENT(tmp) && tmp->icon) {
558 tmp->icon->mapped = 1;
559 XMapWindow(dpy, tmp->icon->core->window);
564 tmp = tmp->prev;
567 while (toUnmapCount > 0)
569 wWindowUnmap(toUnmap[--toUnmapCount]);
571 wfree(toUnmap);
573 /* Gobble up events unleashed by our mapping & unmapping.
574 * These may trigger various grab-initiated focus &
575 * crossing events. However, we don't care about them,
576 * and ignore their focus implications altogether to avoid
577 * flicker.
579 scr->flags.ignore_focus_events = 1;
580 ProcessPendingEvents();
581 scr->flags.ignore_focus_events = 0;
583 if (!foc)
584 foc = foc2;
586 if (scr->focused_window->flags.mapped && !foc) {
587 foc = scr->focused_window;
589 if (wPreferences.focus_mode == WKF_CLICK) {
590 wSetFocusTo(scr, foc);
591 } else {
592 unsigned int mask;
593 int foo;
594 Window bar, win;
595 WWindow *tmp;
597 tmp = NULL;
598 if (XQueryPointer(dpy, scr->root_win, &bar, &win, &foo, &foo, &foo, &foo, &mask)) {
599 tmp = wWindowFor(win);
602 /* If there's a window under the pointer, focus it.
603 * (we ate all other focus events above, so it's
604 * certainly not focused). Otherwise focus last
605 * focused, or the root (depending on sloppiness)
607 if (!tmp && wPreferences.focus_mode == WKF_SLOPPY) {
608 wSetFocusTo(scr, foc);
609 } else {
610 wSetFocusTo(scr, tmp);
615 /* We need to always arrange icons when changing workspace, even if
616 * no autoarrange icons, because else the icons in different workspaces
617 * can be superposed.
618 * This can be avoided if appicons are also workspace specific.
620 if (!wPreferences.sticky_icons)
621 wArrangeIcons(scr, False);
623 if (scr->dock)
624 wAppIconPaint(scr->dock->icon_array[0]);
626 if (!wPreferences.flags.noclip && (scr->workspaces[workspace]->clip->auto_collapse ||
627 scr->workspaces[workspace]->clip->auto_raise_lower)) {
628 /* to handle enter notify. This will also */
629 XUnmapWindow(dpy, scr->clip_icon->icon->core->window);
630 XMapWindow(dpy, scr->clip_icon->icon->core->window);
632 else if (scr->clip_icon != NULL) {
633 wClipIconPaint(scr->clip_icon);
635 wScreenUpdateUsableArea(scr);
636 wNETWMUpdateDesktop(scr);
637 showWorkspaceName(scr, workspace);
639 WMPostNotificationName(WMNWorkspaceChanged, scr, (void *)(uintptr_t) workspace);
641 /* XSync(dpy, False); */
644 static void switchWSCommand(WMenu * menu, WMenuEntry * entry)
646 wWorkspaceChange(menu->frame->screen_ptr, (long)entry->clientdata);
649 static void lastWSCommand(WMenu * menu, WMenuEntry * entry)
651 wWorkspaceChange(menu->frame->screen_ptr, menu->frame->screen_ptr->last_workspace);
654 static void deleteWSCommand(WMenu * menu, WMenuEntry * entry)
656 wWorkspaceDelete(menu->frame->screen_ptr, menu->frame->screen_ptr->workspace_count - 1);
659 static void newWSCommand(WMenu *menu, WMenuEntry *foo)
661 int ws;
663 ws = wWorkspaceNew(menu->frame->screen_ptr);
665 /* autochange workspace */
666 if (ws >= 0)
667 wWorkspaceChange(menu->frame->screen_ptr, ws);
670 void wWorkspaceRename(WScreen *scr, int workspace, const char *name)
672 char buf[MAX_WORKSPACENAME_WIDTH + 1];
673 char *tmp;
675 if (workspace >= scr->workspace_count)
676 return;
678 /* trim white spaces */
679 tmp = wtrimspace(name);
681 if (strlen(tmp) == 0) {
682 snprintf(buf, sizeof(buf), _("Workspace %i"), workspace + 1);
683 } else {
684 strncpy(buf, tmp, MAX_WORKSPACENAME_WIDTH);
686 buf[MAX_WORKSPACENAME_WIDTH] = 0;
687 wfree(tmp);
689 /* update workspace */
690 wfree(scr->workspaces[workspace]->name);
691 scr->workspaces[workspace]->name = wstrdup(buf);
693 if (scr->clip_ws_menu) {
694 if (strcmp(scr->clip_ws_menu->entries[workspace + MC_WORKSPACE1]->text, buf) != 0) {
695 wfree(scr->clip_ws_menu->entries[workspace + MC_WORKSPACE1]->text);
696 scr->clip_ws_menu->entries[workspace + MC_WORKSPACE1]->text = wstrdup(buf);
697 wMenuRealize(scr->clip_ws_menu);
700 if (scr->workspace_menu) {
701 if (strcmp(scr->workspace_menu->entries[workspace + MC_WORKSPACE1]->text, buf) != 0) {
702 wfree(scr->workspace_menu->entries[workspace + MC_WORKSPACE1]->text);
703 scr->workspace_menu->entries[workspace + MC_WORKSPACE1]->text = wstrdup(buf);
704 wMenuRealize(scr->workspace_menu);
708 if (scr->clip_icon)
709 wClipIconPaint(scr->clip_icon);
711 WMPostNotificationName(WMNWorkspaceNameChanged, scr, (void *)(uintptr_t) workspace);
714 /* callback for when menu entry is edited */
715 static void onMenuEntryEdited(WMenu * menu, WMenuEntry * entry)
717 char *tmp;
719 tmp = entry->text;
720 wWorkspaceRename(menu->frame->screen_ptr, (long)entry->clientdata, tmp);
723 WMenu *wWorkspaceMenuMake(WScreen * scr, Bool titled)
725 WMenu *wsmenu;
726 WMenuEntry *entry;
728 wsmenu = wMenuCreate(scr, titled ? _("Workspaces") : NULL, False);
729 if (!wsmenu) {
730 wwarning(_("could not create Workspace menu"));
731 return NULL;
734 /* callback to be called when an entry is edited */
735 wsmenu->on_edit = onMenuEntryEdited;
737 wMenuAddCallback(wsmenu, _("New"), newWSCommand, NULL);
738 wMenuAddCallback(wsmenu, _("Destroy Last"), deleteWSCommand, NULL);
740 entry = wMenuAddCallback(wsmenu, _("Last Used"), lastWSCommand, NULL);
741 entry->rtext = GetShortcutKey(wKeyBindings[WKBD_LASTWORKSPACE]);
743 return wsmenu;
746 void wWorkspaceMenuUpdate(WScreen * scr, WMenu * menu)
748 int i;
749 long ws;
750 char title[MAX_WORKSPACENAME_WIDTH + 1];
751 WMenuEntry *entry;
752 int tmp;
754 if (!menu)
755 return;
757 if (menu->entry_no < scr->workspace_count + MC_WORKSPACE1) {
758 /* new workspace(s) added */
759 i = scr->workspace_count - (menu->entry_no - MC_WORKSPACE1);
760 ws = menu->entry_no - MC_WORKSPACE1;
761 while (i > 0) {
762 wstrlcpy(title, scr->workspaces[ws]->name, MAX_WORKSPACENAME_WIDTH);
764 entry = wMenuAddCallback(menu, title, switchWSCommand, (void *)ws);
765 entry->flags.indicator = 1;
766 entry->flags.editable = 1;
768 i--;
769 ws++;
771 } else if (menu->entry_no > scr->workspace_count + MC_WORKSPACE1) {
772 /* removed workspace(s) */
773 for (i = menu->entry_no - 1; i >= scr->workspace_count + MC_WORKSPACE1; i--) {
774 wMenuRemoveItem(menu, i);
778 for (i = 0; i < scr->workspace_count; i++) {
779 /* workspace shortcut labels */
780 if (i / 10 == scr->current_workspace / 10)
781 menu->entries[i + MC_WORKSPACE1]->rtext = GetShortcutKey(wKeyBindings[WKBD_WORKSPACE1 + (i % 10)]);
782 else
783 menu->entries[i + MC_WORKSPACE1]->rtext = NULL;
785 menu->entries[i + MC_WORKSPACE1]->flags.indicator_on = 0;
787 menu->entries[scr->current_workspace + MC_WORKSPACE1]->flags.indicator_on = 1;
788 wMenuRealize(menu);
790 /* don't let user destroy current workspace */
791 if (scr->current_workspace == scr->workspace_count - 1) {
792 wMenuSetEnabled(menu, MC_DESTROY_LAST, False);
793 } else {
794 wMenuSetEnabled(menu, MC_DESTROY_LAST, True);
797 /* back to last workspace */
798 if (scr->workspace_count && scr->last_workspace != scr->current_workspace)
799 wMenuSetEnabled(menu, MC_LAST_USED, True);
800 else
801 wMenuSetEnabled(menu, MC_LAST_USED, False);
803 tmp = menu->frame->top_width + 5;
804 /* if menu got unreachable, bring it to a visible place */
805 if (menu->frame_x < tmp - (int)menu->frame->core->width)
806 wMenuMove(menu, tmp - (int)menu->frame->core->width, menu->frame_y, False);
808 wMenuPaint(menu);
811 void wWorkspaceSaveState(WScreen * scr, WMPropList * old_state)
813 WMPropList *parr, *pstr, *wks_state, *old_wks_state, *foo, *bar;
814 int i;
816 make_keys();
818 old_wks_state = WMGetFromPLDictionary(old_state, dWorkspaces);
819 parr = WMCreatePLArray(NULL);
820 for (i = 0; i < scr->workspace_count; i++) {
821 pstr = WMCreatePLString(scr->workspaces[i]->name);
822 wks_state = WMCreatePLDictionary(dName, pstr, NULL);
823 WMReleasePropList(pstr);
824 if (!wPreferences.flags.noclip) {
825 pstr = wClipSaveWorkspaceState(scr, i);
826 WMPutInPLDictionary(wks_state, dClip, pstr);
827 WMReleasePropList(pstr);
828 } else if (old_wks_state != NULL) {
829 if ((foo = WMGetFromPLArray(old_wks_state, i)) != NULL) {
830 if ((bar = WMGetFromPLDictionary(foo, dClip)) != NULL) {
831 WMPutInPLDictionary(wks_state, dClip, bar);
835 WMAddToPLArray(parr, wks_state);
836 WMReleasePropList(wks_state);
838 WMPutInPLDictionary(scr->session_state, dWorkspaces, parr);
839 WMReleasePropList(parr);
842 void wWorkspaceRestoreState(WScreen *scr)
844 WMPropList *parr, *pstr, *wks_state, *clip_state;
845 int i, j;
847 make_keys();
849 if (scr->session_state == NULL)
850 return;
852 parr = WMGetFromPLDictionary(scr->session_state, dWorkspaces);
854 if (!parr)
855 return;
857 for (i = 0; i < WMIN(WMGetPropListItemCount(parr), MAX_WORKSPACES); i++) {
858 wks_state = WMGetFromPLArray(parr, i);
859 if (WMIsPLDictionary(wks_state))
860 pstr = WMGetFromPLDictionary(wks_state, dName);
861 else
862 pstr = wks_state;
864 if (i >= scr->workspace_count)
865 wWorkspaceNew(scr);
867 if (scr->workspace_menu) {
868 wfree(scr->workspace_menu->entries[i + MC_WORKSPACE1]->text);
869 scr->workspace_menu->entries[i + MC_WORKSPACE1]->text = wstrdup(WMGetFromPLString(pstr));
870 scr->workspace_menu->flags.realized = 0;
873 wfree(scr->workspaces[i]->name);
874 scr->workspaces[i]->name = wstrdup(WMGetFromPLString(pstr));
875 if (!wPreferences.flags.noclip) {
876 int added_omnipresent_icons = 0;
878 clip_state = WMGetFromPLDictionary(wks_state, dClip);
879 if (scr->workspaces[i]->clip)
880 wDockDestroy(scr->workspaces[i]->clip);
882 scr->workspaces[i]->clip = wDockRestoreState(scr, clip_state, WM_CLIP);
883 if (i > 0)
884 wDockHideIcons(scr->workspaces[i]->clip);
886 /* We set the global icons here, because scr->workspaces[i]->clip
887 * was not valid in wDockRestoreState().
888 * There we only set icon->omnipresent to know which icons we
889 * need to set here.
891 for (j = 0; j < scr->workspaces[i]->clip->max_icons; j++) {
892 WAppIcon *aicon = scr->workspaces[i]->clip->icon_array[j];
893 int k;
895 if (!aicon || !aicon->omnipresent)
896 continue;
897 aicon->omnipresent = 0;
898 if (wClipMakeIconOmnipresent(aicon, True) != WO_SUCCESS)
899 continue;
900 if (i == 0)
901 continue;
903 /* Move this appicon from workspace i to workspace 0 */
904 scr->workspaces[i]->clip->icon_array[j] = NULL;
905 scr->workspaces[i]->clip->icon_count--;
907 added_omnipresent_icons++;
908 /* If there are too many omnipresent appicons, we are in trouble */
909 assert(scr->workspaces[0]->clip->icon_count + added_omnipresent_icons
910 <= scr->workspaces[0]->clip->max_icons);
911 /* Find first free spot on workspace 0 */
912 for (k = 0; k < scr->workspaces[0]->clip->max_icons; k++)
913 if (scr->workspaces[0]->clip->icon_array[k] == NULL)
914 break;
915 scr->workspaces[0]->clip->icon_array[k] = aicon;
916 aicon->dock = scr->workspaces[0]->clip;
918 scr->workspaces[0]->clip->icon_count += added_omnipresent_icons;
921 WMPostNotificationName(WMNWorkspaceNameChanged, scr, (void *)(uintptr_t) i);
925 /* Returns the workspace number for a given workspace name */
926 int wGetWorkspaceNumber(WScreen *scr, const char *value)
928 int w, i;
930 if (sscanf(value, "%i", &w) != 1) {
931 w = -1;
932 for (i = 0; i < scr->workspace_count; i++) {
933 if (strcmp(scr->workspaces[i]->name, value) == 0) {
934 w = i;
935 break;
938 } else {
939 w--;
942 return w;