Little simplification in makeIcon()
[wmaker-crm.git] / WPrefs.app / KeyboardShortcuts.c
blob4fd5305cb266ce00aeb21aa5cc9ce5fad9990616
1 /* KeyboardShortcuts.c- keyboard shortcut bindings
3 * WPrefs - Window Maker Preferences Program
5 * Copyright (c) 1998-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.
22 #include "config.h" /* for HAVE_XCONVERTCASE */
24 #include "WPrefs.h"
25 #include <ctype.h>
27 #include <X11/keysym.h>
29 typedef struct _Panel {
30 WMBox *box;
32 char *sectionName;
34 char *description;
36 CallbackRec callbacks;
38 WMWidget *parent;
40 WMLabel *actL;
41 WMList *actLs;
43 WMFrame *shoF;
44 WMTextField *shoT;
45 WMButton *cleB;
46 WMButton *defB;
48 WMLabel *instructionsL;
50 WMColor *white;
51 WMColor *black;
52 WMColor *gray;
53 WMFont *font;
55 Bool capturing;
56 char **shortcuts;
57 int actionCount;
58 } _Panel;
60 #define ICON_FILE "keyshortcuts"
63 * Must be in the same order as the corresponding items in
64 * "actions list" in createPanel()
66 static char *keyOptions[] = {
67 "RootMenuKey",
68 "WindowListKey",
69 "WindowMenuKey",
70 "HideKey",
71 "HideOthersKey",
72 "MiniaturizeKey",
73 "MinimizeAllKey",
74 "CloseKey",
75 "MaximizeKey",
76 "VMaximizeKey",
77 "HMaximizeKey",
78 "LHMaximizeKey",
79 "RHMaximizeKey",
80 "MaximusKey",
81 "RaiseKey",
82 "LowerKey",
83 "RaiseLowerKey",
84 "ShadeKey",
85 "MoveResizeKey",
86 "SelectKey",
87 "FocusNextKey",
88 "FocusPrevKey",
89 "GroupNextKey",
90 "GroupPrevKey",
91 "NextWorkspaceKey",
92 "PrevWorkspaceKey",
93 "NextWorkspaceLayerKey",
94 "PrevWorkspaceLayerKey",
95 "Workspace1Key",
96 "Workspace2Key",
97 "Workspace3Key",
98 "Workspace4Key",
99 "Workspace5Key",
100 "Workspace6Key",
101 "Workspace7Key",
102 "Workspace8Key",
103 "Workspace9Key",
104 "Workspace10Key",
105 "WindowShortcut1Key",
106 "WindowShortcut2Key",
107 "WindowShortcut3Key",
108 "WindowShortcut4Key",
109 "WindowShortcut5Key",
110 "WindowShortcut6Key",
111 "WindowShortcut7Key",
112 "WindowShortcut8Key",
113 "WindowShortcut9Key",
114 "WindowShortcut10Key",
115 "ScreenSwitchKey",
116 "DockRaiseLowerKey",
117 #ifndef XKB_MODELOCK
118 "ClipRaiseLowerKey"
119 #else
120 "ClipRaiseLowerKey",
121 "ToggleKbdModeKey"
122 #endif /* XKB_MODELOCK */
125 #ifndef HAVE_XCONVERTCASE
126 /* from Xlib */
128 static void XConvertCase(register KeySym sym, KeySym * lower, KeySym * upper)
130 *lower = sym;
131 *upper = sym;
132 switch (sym >> 8) {
133 case 0: /* Latin 1 */
134 if ((sym >= XK_A) && (sym <= XK_Z))
135 *lower += (XK_a - XK_A);
136 else if ((sym >= XK_a) && (sym <= XK_z))
137 *upper -= (XK_a - XK_A);
138 else if ((sym >= XK_Agrave) && (sym <= XK_Odiaeresis))
139 *lower += (XK_agrave - XK_Agrave);
140 else if ((sym >= XK_agrave) && (sym <= XK_odiaeresis))
141 *upper -= (XK_agrave - XK_Agrave);
142 else if ((sym >= XK_Ooblique) && (sym <= XK_Thorn))
143 *lower += (XK_oslash - XK_Ooblique);
144 else if ((sym >= XK_oslash) && (sym <= XK_thorn))
145 *upper -= (XK_oslash - XK_Ooblique);
146 break;
147 case 1: /* Latin 2 */
148 /* Assume the KeySym is a legal value (ignore discontinuities) */
149 if (sym == XK_Aogonek)
150 *lower = XK_aogonek;
151 else if (sym >= XK_Lstroke && sym <= XK_Sacute)
152 *lower += (XK_lstroke - XK_Lstroke);
153 else if (sym >= XK_Scaron && sym <= XK_Zacute)
154 *lower += (XK_scaron - XK_Scaron);
155 else if (sym >= XK_Zcaron && sym <= XK_Zabovedot)
156 *lower += (XK_zcaron - XK_Zcaron);
157 else if (sym == XK_aogonek)
158 *upper = XK_Aogonek;
159 else if (sym >= XK_lstroke && sym <= XK_sacute)
160 *upper -= (XK_lstroke - XK_Lstroke);
161 else if (sym >= XK_scaron && sym <= XK_zacute)
162 *upper -= (XK_scaron - XK_Scaron);
163 else if (sym >= XK_zcaron && sym <= XK_zabovedot)
164 *upper -= (XK_zcaron - XK_Zcaron);
165 else if (sym >= XK_Racute && sym <= XK_Tcedilla)
166 *lower += (XK_racute - XK_Racute);
167 else if (sym >= XK_racute && sym <= XK_tcedilla)
168 *upper -= (XK_racute - XK_Racute);
169 break;
170 case 2: /* Latin 3 */
171 /* Assume the KeySym is a legal value (ignore discontinuities) */
172 if (sym >= XK_Hstroke && sym <= XK_Hcircumflex)
173 *lower += (XK_hstroke - XK_Hstroke);
174 else if (sym >= XK_Gbreve && sym <= XK_Jcircumflex)
175 *lower += (XK_gbreve - XK_Gbreve);
176 else if (sym >= XK_hstroke && sym <= XK_hcircumflex)
177 *upper -= (XK_hstroke - XK_Hstroke);
178 else if (sym >= XK_gbreve && sym <= XK_jcircumflex)
179 *upper -= (XK_gbreve - XK_Gbreve);
180 else if (sym >= XK_Cabovedot && sym <= XK_Scircumflex)
181 *lower += (XK_cabovedot - XK_Cabovedot);
182 else if (sym >= XK_cabovedot && sym <= XK_scircumflex)
183 *upper -= (XK_cabovedot - XK_Cabovedot);
184 break;
185 case 3: /* Latin 4 */
186 /* Assume the KeySym is a legal value (ignore discontinuities) */
187 if (sym >= XK_Rcedilla && sym <= XK_Tslash)
188 *lower += (XK_rcedilla - XK_Rcedilla);
189 else if (sym >= XK_rcedilla && sym <= XK_tslash)
190 *upper -= (XK_rcedilla - XK_Rcedilla);
191 else if (sym == XK_ENG)
192 *lower = XK_eng;
193 else if (sym == XK_eng)
194 *upper = XK_ENG;
195 else if (sym >= XK_Amacron && sym <= XK_Umacron)
196 *lower += (XK_amacron - XK_Amacron);
197 else if (sym >= XK_amacron && sym <= XK_umacron)
198 *upper -= (XK_amacron - XK_Amacron);
199 break;
200 case 6: /* Cyrillic */
201 /* Assume the KeySym is a legal value (ignore discontinuities) */
202 if (sym >= XK_Serbian_DJE && sym <= XK_Serbian_DZE)
203 *lower -= (XK_Serbian_DJE - XK_Serbian_dje);
204 else if (sym >= XK_Serbian_dje && sym <= XK_Serbian_dze)
205 *upper += (XK_Serbian_DJE - XK_Serbian_dje);
206 else if (sym >= XK_Cyrillic_YU && sym <= XK_Cyrillic_HARDSIGN)
207 *lower -= (XK_Cyrillic_YU - XK_Cyrillic_yu);
208 else if (sym >= XK_Cyrillic_yu && sym <= XK_Cyrillic_hardsign)
209 *upper += (XK_Cyrillic_YU - XK_Cyrillic_yu);
210 break;
211 case 7: /* Greek */
212 /* Assume the KeySym is a legal value (ignore discontinuities) */
213 if (sym >= XK_Greek_ALPHAaccent && sym <= XK_Greek_OMEGAaccent)
214 *lower += (XK_Greek_alphaaccent - XK_Greek_ALPHAaccent);
215 else if (sym >= XK_Greek_alphaaccent && sym <= XK_Greek_omegaaccent &&
216 sym != XK_Greek_iotaaccentdieresis && sym != XK_Greek_upsilonaccentdieresis)
217 *upper -= (XK_Greek_alphaaccent - XK_Greek_ALPHAaccent);
218 else if (sym >= XK_Greek_ALPHA && sym <= XK_Greek_OMEGA)
219 *lower += (XK_Greek_alpha - XK_Greek_ALPHA);
220 else if (sym >= XK_Greek_alpha && sym <= XK_Greek_omega && sym != XK_Greek_finalsmallsigma)
221 *upper -= (XK_Greek_alpha - XK_Greek_ALPHA);
222 break;
223 case 0x14: /* Armenian */
224 if (sym >= XK_Armenian_AYB && sym <= XK_Armenian_fe) {
225 *lower = sym | 1;
226 *upper = sym & ~1;
228 break;
231 #endif
233 char *capture_shortcut(Display *dpy, Bool *capturing, Bool convert_case)
235 XEvent ev;
236 KeySym ksym, lksym, uksym;
237 char buffer[64];
238 char *key = NULL;
240 while (*capturing) {
241 XAllowEvents(dpy, AsyncKeyboard, CurrentTime);
242 WMNextEvent(dpy, &ev);
243 if (ev.type == KeyPress && ev.xkey.keycode != 0) {
244 ksym = XKeycodeToKeysym(dpy, ev.xkey.keycode, 0);
245 if (!IsModifierKey(ksym)) {
246 if (convert_case) {
247 XConvertCase(ksym, &lksym, &uksym);
248 key = XKeysymToString(uksym);
249 } else {
250 key = XKeysymToString(ksym);
253 *capturing = 0;
254 break;
257 WMHandleEvent(&ev);
260 if (!key)
261 return NULL;
263 buffer[0] = 0;
265 if (ev.xkey.state & ControlMask) {
266 strcat(buffer, "Control+");
268 if (ev.xkey.state & ShiftMask) {
269 strcat(buffer, "Shift+");
271 if (ev.xkey.state & Mod1Mask) {
272 strcat(buffer, "Mod1+");
274 if (ev.xkey.state & Mod2Mask) {
275 strcat(buffer, "Mod2+");
277 if (ev.xkey.state & Mod3Mask) {
278 strcat(buffer, "Mod3+");
280 if (ev.xkey.state & Mod4Mask) {
281 strcat(buffer, "Mod4+");
283 if (ev.xkey.state & Mod5Mask) {
284 strcat(buffer, "Mod5+");
286 strcat(buffer, key);
288 return wstrdup(buffer);
291 static void captureClick(WMWidget * w, void *data)
293 _Panel *panel = (_Panel *) data;
294 Display *dpy = WMScreenDisplay(WMWidgetScreen(panel->parent));
295 char *shortcut;
297 if (!panel->capturing) {
298 panel->capturing = 1;
299 WMSetButtonText(w, _("Cancel"));
300 WMSetLabelText(panel->instructionsL,
301 _("Press the desired shortcut key(s) or click Cancel to stop capturing."));
302 XGrabKeyboard(dpy, WMWidgetXID(panel->parent), True, GrabModeAsync, GrabModeAsync, CurrentTime);
303 shortcut = capture_shortcut(dpy, &panel->capturing, 1);
304 if (shortcut) {
305 int row = WMGetListSelectedItemRow(panel->actLs);
307 WMSetTextFieldText(panel->shoT, shortcut);
308 if (row >= 0) {
309 if (panel->shortcuts[row])
310 wfree(panel->shortcuts[row]);
311 panel->shortcuts[row] = shortcut;
313 WMRedisplayWidget(panel->actLs);
314 } else {
315 wfree(shortcut);
319 panel->capturing = 0;
320 WMSetButtonText(w, _("Capture"));
321 WMSetLabelText(panel->instructionsL, _("Click on Capture to interactively define the shortcut key."));
322 XUngrabKeyboard(dpy, CurrentTime);
325 static void clearShortcut(WMWidget * w, void *data)
327 _Panel *panel = (_Panel *) data;
328 int row = WMGetListSelectedItemRow(panel->actLs);
330 WMSetTextFieldText(panel->shoT, NULL);
332 if (row >= 0) {
333 if (panel->shortcuts[row])
334 wfree(panel->shortcuts[row]);
335 panel->shortcuts[row] = NULL;
336 WMRedisplayWidget(panel->actLs);
340 static void typedKeys(void *observerData, WMNotification * notification)
342 _Panel *panel = (_Panel *) observerData;
343 int row = WMGetListSelectedItemRow(panel->actLs);
345 if (row < 0)
346 return;
348 if (panel->shortcuts[row])
349 wfree(panel->shortcuts[row]);
350 panel->shortcuts[row] = WMGetTextFieldText(panel->shoT);
351 if (strlen(panel->shortcuts[row]) == 0) {
352 wfree(panel->shortcuts[row]);
353 panel->shortcuts[row] = NULL;
355 WMRedisplayWidget(panel->actLs);
358 static void listClick(WMWidget * w, void *data)
360 _Panel *panel = (_Panel *) data;
361 int row = WMGetListSelectedItemRow(w);
363 WMSetTextFieldText(panel->shoT, panel->shortcuts[row]);
366 static void showData(_Panel * panel)
368 char *str;
369 int i;
371 for (i = 0; i < panel->actionCount; i++) {
373 str = GetStringForKey(keyOptions[i]);
374 if (panel->shortcuts[i])
375 wfree(panel->shortcuts[i]);
376 if (str)
377 panel->shortcuts[i] = wtrimspace(str);
378 else
379 panel->shortcuts[i] = NULL;
381 if (panel->shortcuts[i] &&
382 (strcasecmp(panel->shortcuts[i], "none") == 0 || strlen(panel->shortcuts[i]) == 0)) {
383 wfree(panel->shortcuts[i]);
384 panel->shortcuts[i] = NULL;
387 WMRedisplayWidget(panel->actLs);
390 static void paintItem(WMList * lPtr, int index, Drawable d, char *text, int state, WMRect * rect)
392 int width, height, x, y;
393 _Panel *panel = (_Panel *) WMGetHangedData(lPtr);
394 WMScreen *scr = WMWidgetScreen(lPtr);
395 Display *dpy = WMScreenDisplay(scr);
396 WMColor *backColor = (state & WLDSSelected) ? panel->white : panel->gray;
398 width = rect->size.width;
399 height = rect->size.height;
400 x = rect->pos.x;
401 y = rect->pos.y;
403 XFillRectangle(dpy, d, WMColorGC(backColor), x, y, width, height);
405 if (panel->shortcuts[index]) {
406 WMPixmap *pix = WMGetSystemPixmap(scr, WSICheckMark);
407 WMSize size = WMGetPixmapSize(pix);
409 WMDrawPixmap(pix, d, x + (20 - size.width) / 2, (height - size.height) / 2 + y);
410 WMReleasePixmap(pix);
413 WMDrawString(scr, d, panel->black, panel->font, x + 20, y, text, strlen(text));
416 static void createPanel(Panel * p)
418 _Panel *panel = (_Panel *) p;
419 WMScreen *scr = WMWidgetScreen(panel->parent);
420 WMColor *color;
421 WMFont *boldFont;
423 panel->capturing = 0;
425 panel->white = WMWhiteColor(scr);
426 panel->black = WMBlackColor(scr);
427 panel->gray = WMGrayColor(scr);
428 panel->font = WMSystemFontOfSize(scr, 12);
430 panel->box = WMCreateBox(panel->parent);
431 WMSetViewExpandsToParent(WMWidgetView(panel->box), 2, 2, 2, 2);
433 boldFont = WMBoldSystemFontOfSize(scr, 12);
435 /* **************** Actions **************** */
436 panel->actL = WMCreateLabel(panel->box);
437 WMResizeWidget(panel->actL, 280, 20);
438 WMMoveWidget(panel->actL, 20, 10);
439 WMSetLabelFont(panel->actL, boldFont);
440 WMSetLabelText(panel->actL, _("Actions"));
441 WMSetLabelRelief(panel->actL, WRSunken);
442 WMSetLabelTextAlignment(panel->actL, WACenter);
443 color = WMDarkGrayColor(scr);
444 WMSetWidgetBackgroundColor(panel->actL, color);
445 WMReleaseColor(color);
446 WMSetLabelTextColor(panel->actL, panel->white);
448 panel->actLs = WMCreateList(panel->box);
449 WMResizeWidget(panel->actLs, 280, 190);
450 WMMoveWidget(panel->actLs, 20, 32);
451 WMSetListUserDrawProc(panel->actLs, paintItem);
452 WMHangData(panel->actLs, panel);
454 WMAddListItem(panel->actLs, _("Open applications menu"));
455 WMAddListItem(panel->actLs, _("Open window list menu"));
456 WMAddListItem(panel->actLs, _("Open window commands menu"));
457 WMAddListItem(panel->actLs, _("Hide active application"));
458 WMAddListItem(panel->actLs, _("Hide other applications"));
459 WMAddListItem(panel->actLs, _("Miniaturize active window"));
460 WMAddListItem(panel->actLs, _("Miniaturize all windows"));
461 WMAddListItem(panel->actLs, _("Close active window"));
462 WMAddListItem(panel->actLs, _("Maximize active window"));
463 WMAddListItem(panel->actLs, _("Maximize active window vertically"));
464 WMAddListItem(panel->actLs, _("Maximize active window horizontally"));
465 WMAddListItem(panel->actLs, _("Maximize active window left half"));
466 WMAddListItem(panel->actLs, _("Maximize active window right half"));
467 WMAddListItem(panel->actLs, _("Maximus: Tiled maximization "));
468 WMAddListItem(panel->actLs, _("Raise active window"));
469 WMAddListItem(panel->actLs, _("Lower active window"));
470 WMAddListItem(panel->actLs, _("Raise/Lower window under mouse pointer"));
471 WMAddListItem(panel->actLs, _("Shade active window"));
472 WMAddListItem(panel->actLs, _("Move/Resize active window"));
473 WMAddListItem(panel->actLs, _("Select active window"));
474 WMAddListItem(panel->actLs, _("Focus next window"));
475 WMAddListItem(panel->actLs, _("Focus previous window"));
476 WMAddListItem(panel->actLs, _("Focus next group window"));
477 WMAddListItem(panel->actLs, _("Focus previous group window"));
478 WMAddListItem(panel->actLs, _("Switch to next workspace"));
479 WMAddListItem(panel->actLs, _("Switch to previous workspace"));
480 WMAddListItem(panel->actLs, _("Switch to next ten workspaces"));
481 WMAddListItem(panel->actLs, _("Switch to previous ten workspaces"));
482 WMAddListItem(panel->actLs, _("Switch to workspace 1"));
483 WMAddListItem(panel->actLs, _("Switch to workspace 2"));
484 WMAddListItem(panel->actLs, _("Switch to workspace 3"));
485 WMAddListItem(panel->actLs, _("Switch to workspace 4"));
486 WMAddListItem(panel->actLs, _("Switch to workspace 5"));
487 WMAddListItem(panel->actLs, _("Switch to workspace 6"));
488 WMAddListItem(panel->actLs, _("Switch to workspace 7"));
489 WMAddListItem(panel->actLs, _("Switch to workspace 8"));
490 WMAddListItem(panel->actLs, _("Switch to workspace 9"));
491 WMAddListItem(panel->actLs, _("Switch to workspace 10"));
492 WMAddListItem(panel->actLs, _("Shortcut for window 1"));
493 WMAddListItem(panel->actLs, _("Shortcut for window 2"));
494 WMAddListItem(panel->actLs, _("Shortcut for window 3"));
495 WMAddListItem(panel->actLs, _("Shortcut for window 4"));
496 WMAddListItem(panel->actLs, _("Shortcut for window 5"));
497 WMAddListItem(panel->actLs, _("Shortcut for window 6"));
498 WMAddListItem(panel->actLs, _("Shortcut for window 7"));
499 WMAddListItem(panel->actLs, _("Shortcut for window 8"));
500 WMAddListItem(panel->actLs, _("Shortcut for window 9"));
501 WMAddListItem(panel->actLs, _("Shortcut for window 10"));
502 WMAddListItem(panel->actLs, _("Switch to Next Screen/Monitor"));
503 WMAddListItem(panel->actLs, _("Raise/Lower Dock"));
504 WMAddListItem(panel->actLs, _("Raise/Lower Clip"));
505 #ifdef XKB_MODELOCK
506 WMAddListItem(panel->actLs, _("Toggle keyboard language"));
507 #endif /* XKB_MODELOCK */
509 WMSetListAction(panel->actLs, listClick, panel);
511 panel->actionCount = WMGetListNumberOfRows(panel->actLs);
512 panel->shortcuts = wmalloc(sizeof(char *) * panel->actionCount);
513 memset(panel->shortcuts, 0, sizeof(char *) * panel->actionCount);
515 /***************** Shortcut ****************/
517 panel->shoF = WMCreateFrame(panel->box);
518 WMResizeWidget(panel->shoF, 190, 210);
519 WMMoveWidget(panel->shoF, 315, 10);
520 WMSetFrameTitle(panel->shoF, _("Shortcut"));
522 panel->shoT = WMCreateTextField(panel->shoF);
523 WMResizeWidget(panel->shoT, 160, 20);
524 WMMoveWidget(panel->shoT, 15, 65);
525 WMAddNotificationObserver(typedKeys, panel, WMTextDidChangeNotification, panel->shoT);
527 panel->cleB = WMCreateCommandButton(panel->shoF);
528 WMResizeWidget(panel->cleB, 75, 24);
529 WMMoveWidget(panel->cleB, 15, 95);
530 WMSetButtonText(panel->cleB, _("Clear"));
531 WMSetButtonAction(panel->cleB, clearShortcut, panel);
533 panel->defB = WMCreateCommandButton(panel->shoF);
534 WMResizeWidget(panel->defB, 75, 24);
535 WMMoveWidget(panel->defB, 100, 95);
536 WMSetButtonText(panel->defB, _("Capture"));
537 WMSetButtonAction(panel->defB, captureClick, panel);
539 panel->instructionsL = WMCreateLabel(panel->shoF);
540 WMResizeWidget(panel->instructionsL, 160, 55);
541 WMMoveWidget(panel->instructionsL, 15, 140);
542 WMSetLabelTextAlignment(panel->instructionsL, WACenter);
543 WMSetLabelWraps(panel->instructionsL, True);
544 WMSetLabelText(panel->instructionsL, _("Click on Capture to interactively define the shortcut key."));
546 WMMapSubwidgets(panel->shoF);
548 WMReleaseFont(boldFont);
550 WMRealizeWidget(panel->box);
551 WMMapSubwidgets(panel->box);
553 showData(panel);
556 static void storeData(_Panel * panel)
558 int i;
559 char *str;
561 for (i = 0; i < panel->actionCount; i++) {
562 str = NULL;
563 if (panel->shortcuts[i]) {
564 str = wtrimspace(panel->shortcuts[i]);
565 if (strlen(str) == 0) {
566 wfree(str);
567 str = NULL;
570 if (str) {
571 SetStringForKey(str, keyOptions[i]);
572 wfree(str);
573 } else {
574 SetStringForKey("None", keyOptions[i]);
579 Panel *InitKeyboardShortcuts(WMScreen * scr, WMWidget * parent)
581 _Panel *panel;
583 panel = wmalloc(sizeof(_Panel));
584 memset(panel, 0, sizeof(_Panel));
586 panel->sectionName = _("Keyboard Shortcut Preferences");
588 panel->description = _("Change the keyboard shortcuts for actions such\n"
589 "as changing workspaces and opening menus.");
591 panel->parent = parent;
593 panel->callbacks.createWidgets = createPanel;
594 panel->callbacks.updateDomain = storeData;
596 AddSection(panel, ICON_FILE);
598 return panel;