comparison console/libgnt/gntmain.c @ 14900:065e7ac30338

[gaim-migrate @ 17672] Rearranging a bunch of stuff. Users shouldn't notice any change, apart from the added ability to bind keys for the window-manager. I will update the manual in a while. I need to know how to revert a commit in case things go terribly wrong. ... I am going to remind everyone that Dido is AWESOME! committer: Tailor Script <tailor@pidgin.im>
author Sadrul Habib Chowdhury <imadil@gmail.com>
date Sun, 05 Nov 2006 17:28:33 +0000
parents 5228f8cf2a6a
children 1bd0456fe1c3
comparison
equal deleted inserted replaced
14899:a8f92a837590 14900:065e7ac30338
36 * scr_dump, scr_init, scr_restore: for workspaces 36 * scr_dump, scr_init, scr_restore: for workspaces
37 * 37 *
38 * Need to wattrset for colors to use with PDCurses. 38 * Need to wattrset for colors to use with PDCurses.
39 */ 39 */
40 40
41 /** 41 static GIOChannel *channel = NULL;
42 * There can be at most one menu at a time on the screen.
43 * If there is a menu being displayed, then all the keystrokes will be sent to
44 * the menu until it is closed, either when the user activates a menuitem, or
45 * presses Escape to cancel the menu.
46 */
47 static GntMenu *menu;
48
49 static int lock_focus_list;
50 static GList *focus_list;
51 static GList *ordered;
52
53 static int X_MIN;
54 static int X_MAX;
55 static int Y_MIN;
56 static int Y_MAX;
57 42
58 static gboolean ascii_only; 43 static gboolean ascii_only;
59 static gboolean mouse_enabled; 44 static gboolean mouse_enabled;
60 45
61 /** 46 static void setup_io();
62 * 'event_stack' will be set to TRUE when a user-event, ie. a mouse-click
63 * or a key-press is being processed. This variable will be used to determine
64 * whether to give focus to a new window.
65 */
66 static gboolean event_stack;
67
68 static GMainLoop *loop;
69
70 static struct
71 {
72 GntWidget *window;
73 GntWidget *tree;
74 } _list, *window_list, *action_list;
75
76 typedef struct
77 {
78 GntWidget *me;
79
80 PANEL *panel;
81 } GntNode;
82
83 typedef enum
84 {
85 GNT_KP_MODE_NORMAL,
86 GNT_KP_MODE_RESIZE,
87 GNT_KP_MODE_MOVE,
88 GNT_KP_MODE_MENU,
89 GNT_KP_MODE_WINDOW_LIST
90 } GntKeyPressMode;
91
92 static GHashTable *nodes;
93
94 static void free_node(gpointer data);
95 static void draw_taskbar(gboolean reposition);
96 static void bring_on_top(GntWidget *widget);
97 47
98 static gboolean refresh_screen(); 48 static gboolean refresh_screen();
99 static const GList *list_all_windows(); 49
100 50 GntWM *wm;
101 static void show_actions_list();
102
103 static GntWM wm =
104 {
105 NULL, /* new_window */
106 NULL, /* close_window */
107 NULL, /* window_resize_confirm */
108 NULL, /* window_resized */
109 NULL, /* window_move_confirm */
110 NULL, /* window_moved */
111 NULL, /* window_update */
112 NULL, /* key_pressed */
113 NULL, /* mouse clicked */
114 bring_on_top, /* give_focus */
115 NULL, /* uninit */
116 list_all_windows, /* window_list */
117 };
118
119 static const GList *list_all_windows()
120 {
121 return focus_list;
122 }
123
124 static GList *
125 g_list_bring_to_front(GList *list, gpointer data)
126 {
127 list = g_list_remove(list, data);
128 list = g_list_prepend(list, data);
129 return list;
130 }
131
132 static gboolean
133 update_screen(gpointer null)
134 {
135 if (menu) {
136 GntMenu *top = menu;
137 while (top) {
138 GntNode *node = g_hash_table_lookup(nodes, top);
139 if (node)
140 top_panel(node->panel);
141 top = top->submenu;
142 }
143 }
144 update_panels();
145 doupdate();
146 return TRUE;
147 }
148
149 void gnt_screen_take_focus(GntWidget *widget)
150 {
151 GntWidget *w = NULL;
152
153 if (lock_focus_list)
154 return;
155 if (g_list_find(focus_list, widget))
156 return;
157
158 if (ordered)
159 w = ordered->data;
160
161 focus_list = g_list_append(focus_list, widget);
162
163 if (event_stack) {
164 ordered = g_list_prepend(ordered, widget);
165 g_object_set_data(G_OBJECT(widget), "give_focus", GINT_TO_POINTER(event_stack));
166 } else
167 ordered = g_list_append(ordered, widget);
168
169 gnt_widget_set_focus(widget, TRUE);
170 if (w)
171 gnt_widget_set_focus(w, FALSE);
172 draw_taskbar(FALSE);
173 }
174
175 void gnt_screen_remove_widget(GntWidget *widget)
176 {
177 int pos = g_list_index(focus_list, widget);
178
179 if (lock_focus_list)
180 return;
181
182 if (pos == -1)
183 return;
184
185 focus_list = g_list_remove(focus_list, widget);
186 ordered = g_list_remove(ordered, widget);
187
188 if (ordered)
189 {
190 wm.give_focus(ordered->data);
191 }
192 draw_taskbar(FALSE);
193 }
194
195 static void
196 bring_on_top(GntWidget *widget)
197 {
198 GntNode *node = g_hash_table_lookup(nodes, widget);
199
200 if (!node)
201 return;
202
203 if (ordered->data != widget) {
204 GntWidget *w = ordered->data;
205 ordered = g_list_bring_to_front(ordered, widget);
206 gnt_widget_set_focus(w, FALSE);
207 }
208
209 gnt_widget_set_focus(widget, TRUE);
210 gnt_widget_draw(widget);
211 top_panel(node->panel);
212
213 if (_list.window)
214 {
215 GntNode *nd = g_hash_table_lookup(nodes, _list.window);
216 top_panel(nd->panel);
217 }
218 update_screen(NULL);
219 draw_taskbar(FALSE);
220 }
221
222 static void
223 update_window_in_list(GntWidget *wid)
224 {
225 GntTextFormatFlags flag = 0;
226
227 if (window_list == NULL)
228 return;
229
230 if (wid == ordered->data)
231 flag |= GNT_TEXT_FLAG_DIM;
232 else if (GNT_WIDGET_IS_FLAG_SET(wid, GNT_WIDGET_URGENT))
233 flag |= GNT_TEXT_FLAG_BOLD;
234
235 gnt_tree_set_row_flags(GNT_TREE(window_list->tree), wid, flag);
236 }
237
238 static void
239 draw_taskbar(gboolean reposition)
240 {
241 static WINDOW *taskbar = NULL;
242 GList *iter;
243 int n, width = 0;
244 int i;
245
246 if (taskbar == NULL)
247 {
248 taskbar = newwin(1, getmaxx(stdscr), getmaxy(stdscr) - 1, 0);
249 }
250 else if (reposition)
251 {
252 mvwin(taskbar, Y_MAX, 0);
253 }
254
255 wbkgdset(taskbar, '\0' | COLOR_PAIR(GNT_COLOR_NORMAL));
256 werase(taskbar);
257
258 n = g_list_length(focus_list);
259 if (n)
260 width = getmaxx(stdscr) / n;
261
262 for (i = 0, iter = focus_list; iter; iter = iter->next, i++)
263 {
264 GntWidget *w = iter->data;
265 int color;
266 const char *title;
267
268 if (w == ordered->data) {
269 /* This is the current window in focus */
270 color = GNT_COLOR_TITLE;
271 GNT_WIDGET_UNSET_FLAGS(w, GNT_WIDGET_URGENT);
272 if (wm.window_update) {
273 GntNode *node = g_hash_table_lookup(nodes, w);
274 wm.window_update(node ? node->panel : NULL, w);
275 }
276 } else if (GNT_WIDGET_IS_FLAG_SET(w, GNT_WIDGET_URGENT)) {
277 /* This is a window with the URGENT hint set */
278 color = GNT_COLOR_URGENT;
279 } else {
280 color = GNT_COLOR_NORMAL;
281 }
282 wbkgdset(taskbar, '\0' | COLOR_PAIR(color));
283 mvwhline(taskbar, 0, width * i, ' ' | COLOR_PAIR(color), width);
284 title = GNT_BOX(w)->title;
285 mvwprintw(taskbar, 0, width * i, "%s", title ? title : "<gnt>");
286 if (i)
287 mvwaddch(taskbar, 0, width *i - 1, ACS_VLINE | A_STANDOUT | COLOR_PAIR(GNT_COLOR_NORMAL));
288
289 update_window_in_list(w);
290 }
291
292 wrefresh(taskbar);
293 }
294
295 static void
296 switch_window(int direction)
297 {
298 GntWidget *w = NULL, *wid = NULL;
299 int pos;
300
301 if (!ordered || !ordered->next)
302 return;
303
304 w = ordered->data;
305 pos = g_list_index(focus_list, w);
306 pos += direction;
307
308 if (pos < 0)
309 wid = g_list_last(focus_list)->data;
310 else if (pos >= g_list_length(focus_list))
311 wid = focus_list->data;
312 else if (pos >= 0)
313 wid = g_list_nth_data(focus_list, pos);
314
315 ordered = g_list_bring_to_front(ordered, wid);
316
317 wm.give_focus(ordered->data);
318
319 if (w != wid)
320 {
321 gnt_widget_set_focus(w, FALSE);
322 }
323 }
324
325 static void
326 switch_window_n(int n)
327 {
328 GntWidget *w = NULL;
329 GList *l;
330
331 if (!ordered)
332 return;
333
334 w = ordered->data;
335
336 if ((l = g_list_nth(focus_list, n)) != NULL)
337 {
338 ordered = g_list_bring_to_front(ordered, l->data);
339 wm.give_focus(ordered->data);
340 }
341
342 if (l && w != l->data)
343 {
344 gnt_widget_set_focus(w, FALSE);
345 }
346 }
347
348 static void
349 window_list_activate(GntTree *tree, gpointer null)
350 {
351 GntWidget *widget = gnt_tree_get_selection_data(GNT_TREE(tree));
352 GntWidget *old = NULL;
353
354 if (!ordered || !widget)
355 return;
356
357 old = ordered->data;
358 ordered = g_list_bring_to_front(ordered, widget);
359 wm.give_focus(widget);
360
361 if (old != widget)
362 {
363 gnt_widget_set_focus(old, FALSE);
364 }
365 }
366
367 static void
368 setup__list()
369 {
370 GntWidget *tree, *win;
371 win = _list.window = gnt_box_new(FALSE, FALSE);
372 gnt_box_set_toplevel(GNT_BOX(win), TRUE);
373 gnt_box_set_pad(GNT_BOX(win), 0);
374
375 tree = _list.tree = gnt_tree_new();
376 gnt_box_add_widget(GNT_BOX(win), tree);
377 }
378
379 static void
380 show_window_list()
381 {
382 GntWidget *tree, *win;
383 GList *iter;
384
385 if (window_list)
386 return;
387
388 setup__list();
389
390 window_list = &_list;
391
392 win = window_list->window;
393 tree = window_list->tree;
394
395 gnt_box_set_title(GNT_BOX(win), "Window List");
396
397 for (iter = focus_list; iter; iter = iter->next)
398 {
399 GntBox *box = GNT_BOX(iter->data);
400
401 gnt_tree_add_row_last(GNT_TREE(tree), box,
402 gnt_tree_create_row(GNT_TREE(tree), box->title), NULL);
403 update_window_in_list(GNT_WIDGET(box));
404 }
405
406 gnt_tree_set_selected(GNT_TREE(tree), ordered->data);
407 g_signal_connect(G_OBJECT(tree), "activate", G_CALLBACK(window_list_activate), NULL);
408
409 gnt_tree_set_col_width(GNT_TREE(tree), 0, getmaxx(stdscr) / 3);
410 gnt_widget_set_size(tree, 0, getmaxy(stdscr) / 2);
411 gnt_widget_set_position(win, getmaxx(stdscr) / 3, getmaxy(stdscr) / 4);
412
413 lock_focus_list = 1;
414 gnt_widget_show(win);
415 lock_focus_list = 0;
416 }
417
418 static void
419 shift_window(GntWidget *widget, int dir)
420 {
421 GList *all = focus_list;
422 GList *list = g_list_find(all, widget);
423 int length, pos;
424 if (!list)
425 return;
426
427 length = g_list_length(all);
428 pos = g_list_position(all, list);
429
430 pos += dir;
431 if (dir > 0)
432 pos++;
433
434 if (pos < 0)
435 pos = length;
436 else if (pos > length)
437 pos = 0;
438
439 all = g_list_insert(all, widget, pos);
440 all = g_list_delete_link(all, list);
441 focus_list = all;
442 draw_taskbar(FALSE);
443 }
444
445 static void
446 dump_screen()
447 {
448 int x, y;
449 chtype old = 0, now = 0;
450 FILE *file = fopen("dump.html", "w");
451
452 fprintf(file, "<pre>");
453 for (y = 0; y < getmaxy(stdscr); y++)
454 {
455 for (x = 0; x < getmaxx(stdscr); x++)
456 {
457 char ch;
458 now = mvwinch(curscr, y, x);
459 ch = now & A_CHARTEXT;
460 now ^= ch;
461
462 #define CHECK(attr, start, end) \
463 do \
464 { \
465 if (now & attr) \
466 { \
467 if (!(old & attr)) \
468 fprintf(file, start); \
469 } \
470 else if (old & attr) \
471 { \
472 fprintf(file, end); \
473 } \
474 } while (0)
475
476 CHECK(A_BOLD, "<b>", "</b>");
477 CHECK(A_UNDERLINE, "<u>", "</u>");
478 CHECK(A_BLINK, "<blink>", "</blink>");
479
480 if ((now & A_COLOR) != (old & A_COLOR) ||
481 (now & A_REVERSE) != (old & A_REVERSE))
482 {
483 int ret;
484 short fgp, bgp, r, g, b;
485 struct
486 {
487 int r, g, b;
488 } fg, bg;
489
490 ret = pair_content(PAIR_NUMBER(now & A_COLOR), &fgp, &bgp);
491 if (fgp == -1)
492 fgp = COLOR_BLACK;
493 if (bgp == -1)
494 bgp = COLOR_WHITE;
495 if (now & A_REVERSE)
496 fgp ^= bgp ^= fgp ^= bgp; /* *wink* */
497 ret = color_content(fgp, &r, &g, &b);
498 fg.r = r; fg.b = b; fg.g = g;
499 ret = color_content(bgp, &r, &g, &b);
500 bg.r = r; bg.b = b; bg.g = g;
501 #define ADJUST(x) (x = x * 255 / 1000)
502 ADJUST(fg.r);
503 ADJUST(fg.g);
504 ADJUST(fg.b);
505 ADJUST(bg.r);
506 ADJUST(bg.b);
507 ADJUST(bg.g);
508
509 if (x) fprintf(file, "</span>");
510 fprintf(file, "<span style=\"background:#%02x%02x%02x;color:#%02x%02x%02x\">",
511 bg.r, bg.g, bg.b, fg.r, fg.g, fg.b);
512 }
513 if (now & A_ALTCHARSET)
514 {
515 switch (ch)
516 {
517 case 'q':
518 ch = '-'; break;
519 case 't':
520 case 'u':
521 case 'x':
522 ch = '|'; break;
523 case 'v':
524 case 'w':
525 case 'l':
526 case 'm':
527 case 'k':
528 case 'j':
529 case 'n':
530 ch = '+'; break;
531 case '-':
532 ch = '^'; break;
533 case '.':
534 ch = 'v'; break;
535 case 'a':
536 ch = '#'; break;
537 default:
538 ch = ' '; break;
539 }
540 }
541 if (ch == '&')
542 fprintf(file, "&amp;");
543 else if (ch == '<')
544 fprintf(file, "&lt;");
545 else if (ch == '>')
546 fprintf(file, "&gt;");
547 else
548 fprintf(file, "%c", ch);
549 old = now;
550 }
551 fprintf(file, "</span>\n");
552 old = 0;
553 }
554 fprintf(file, "</pre>");
555 fclose(file);
556 }
557
558 static void
559 refresh_node(GntWidget *widget, GntNode *node, gpointer null)
560 {
561 int x, y, w, h;
562 int nw, nh;
563
564 gnt_widget_get_position(widget, &x, &y);
565 gnt_widget_get_size(widget, &w, &h);
566
567 if (x + w >= X_MAX)
568 x = MAX(0, X_MAX - w);
569 if (y + h >= Y_MAX)
570 y = MAX(0, Y_MAX - h);
571 gnt_screen_move_widget(widget, x, y);
572
573 nw = MIN(w, X_MAX);
574 nh = MIN(h, Y_MAX);
575 if (nw != w || nh != h)
576 gnt_screen_resize_widget(widget, nw, nh);
577 }
578 51
579 /** 52 /**
580 * Mouse support: 53 * Mouse support:
581 * - bring a window on top if you click on its taskbar 54 * - bring a window on top if you click on its taskbar
582 * - click on the top-bar of the active window and drag+drop to move a window 55 * - click on the top-bar of the active window and drag+drop to move a window
600 static int offset = 0; 73 static int offset = 0;
601 GntMouseEvent event; 74 GntMouseEvent event;
602 GntWidget *widget = NULL; 75 GntWidget *widget = NULL;
603 PANEL *p = NULL; 76 PANEL *p = NULL;
604 77
605 if (!ordered || buffer[0] != 27) 78 if (!wm->ordered || buffer[0] != 27)
606 return FALSE; 79 return FALSE;
607 80
608 buffer++; 81 buffer++;
609 if (strlen(buffer) < 5) 82 if (strlen(buffer) < 5)
610 return FALSE; 83 return FALSE;
651 /* button up */ 124 /* button up */
652 event = GNT_MOUSE_UP; 125 event = GNT_MOUSE_UP;
653 } else 126 } else
654 return FALSE; 127 return FALSE;
655 128
656 if (wm.mouse_clicked && wm.mouse_clicked(event, x, y, widget)) 129 if (gnt_wm_process_click(wm, event, x, y, widget))
657 return TRUE; 130 return TRUE;
658 131
659 if (event == GNT_LEFT_MOUSE_DOWN && widget && widget != _list.window && 132 if (event == GNT_LEFT_MOUSE_DOWN && widget && widget != wm->_list.window &&
660 !GNT_WIDGET_IS_FLAG_SET(widget, GNT_WIDGET_TRANSIENT)) { 133 !GNT_WIDGET_IS_FLAG_SET(widget, GNT_WIDGET_TRANSIENT)) {
661 if (widget != ordered->data) { 134 if (widget != wm->ordered->data) {
662 GntWidget *w = ordered->data; 135 gnt_wm_raise_window(wm, widget);
663 ordered = g_list_bring_to_front(ordered, widget);
664 wm.give_focus(ordered->data);
665 gnt_widget_set_focus(w, FALSE);
666 } 136 }
667 if (y == widget->priv.y) { 137 if (y == widget->priv.y) {
668 offset = x - widget->priv.x; 138 offset = x - widget->priv.x;
669 remember = widget; 139 remember = widget;
670 button = MOUSE_LEFT; 140 button = MOUSE_LEFT;
671 } 141 }
672 } else if (event == GNT_MOUSE_UP) { 142 } else if (event == GNT_MOUSE_UP) {
673 if (button == MOUSE_NONE && y == getmaxy(stdscr) - 1) { 143 if (button == MOUSE_NONE && y == getmaxy(stdscr) - 1) {
674 /* Clicked on the taskbar */ 144 /* Clicked on the taskbar */
675 int n = g_list_length(focus_list); 145 int n = g_list_length(wm->list);
676 if (n) { 146 if (n) {
677 int width = getmaxx(stdscr) / n; 147 int width = getmaxx(stdscr) / n;
678 switch_window_n(x / width); 148 gnt_bindable_perform_action_named(GNT_BINDABLE(wm), "switch-window-n", x/width, NULL);
679 } 149 }
680 } else if (button == MOUSE_LEFT && remember) { 150 } else if (button == MOUSE_LEFT && remember) {
681 x -= offset; 151 x -= offset;
682 if (x < 0) x = 0; 152 if (x < 0) x = 0;
683 if (y < 0) y = 0; 153 if (y < 0) y = 0;
684 gnt_screen_move_widget(remember, x, y); 154 gnt_screen_move_widget(remember, x, y);
685 refresh_node(remember, NULL, NULL);
686 } 155 }
687 button = MOUSE_NONE; 156 button = MOUSE_NONE;
688 remember = NULL; 157 remember = NULL;
689 offset = 0; 158 offset = 0;
690 } 159 }
691 160
692 gnt_widget_clicked(widget, event, x, y); 161 gnt_widget_clicked(widget, event, x, y);
693 return TRUE; /* XXX: this should be TRUE */ 162 return TRUE;
694 } 163 }
695 164
696 #ifndef NO_WIDECHAR 165 static gboolean
697 static int 166 io_invoke_error(GIOChannel *source, GIOCondition cond, gpointer data)
698 widestringwidth(wchar_t *wide) 167 {
699 { 168 int id = GPOINTER_TO_INT(data);
700 int len, ret; 169 g_source_remove(id);
701 char *string; 170 g_io_channel_unref(source);
702 171
703 len = wcstombs(NULL, wide, 0) + 1; 172 channel = NULL;
704 string = g_new0(char, len); 173 setup_io();
705 wcstombs(string, wide, len); 174 return TRUE;
706 ret = gnt_util_onscreen_width(string, NULL);
707 g_free(string);
708 return ret;
709 }
710 #endif
711
712 /* Returns the onscreen width of the character at the position */
713 static int
714 reverse_char(WINDOW *d, int y, int x, gboolean set)
715 {
716 #define DECIDE(ch) (set ? ((ch) | A_REVERSE) : ((ch) & ~A_REVERSE))
717
718 #ifdef NO_WIDECHAR
719 chtype ch;
720 ch = mvwinch(d, y, x);
721 mvwaddch(d, y, x, DECIDE(ch));
722 return 1;
723 #else
724 cchar_t ch;
725 int wc = 1;
726 if (mvwin_wch(d, y, x, &ch) == OK) {
727 wc = widestringwidth(ch.chars);
728 ch.attr = DECIDE(ch.attr);
729 ch.attr &= WA_ATTRIBUTES; /* XXX: This is a workaround for a bug */
730 mvwadd_wch(d, y, x, &ch);
731 }
732
733 return wc;
734 #endif
735 }
736
737 static void
738 window_reverse(GntWidget *win, gboolean set)
739 {
740 int i;
741 int w, h;
742 WINDOW *d;
743
744 if (GNT_WIDGET_IS_FLAG_SET(win, GNT_WIDGET_NO_BORDER))
745 return;
746
747 d = win->window;
748 gnt_widget_get_size(win, &w, &h);
749
750 if (gnt_widget_has_shadow(win)) {
751 --w;
752 --h;
753 }
754
755 /* the top and bottom */
756 for (i = 0; i < w; i += reverse_char(d, 0, i, set));
757 for (i = 0; i < w; i += reverse_char(d, h-1, i, set));
758
759 /* the left and right */
760 for (i = 0; i < h; i += reverse_char(d, i, 0, set));
761 for (i = 0; i < h; i += reverse_char(d, i, w-1, set));
762
763 wrefresh(win->window);
764 } 175 }
765 176
766 static gboolean 177 static gboolean
767 io_invoke(GIOChannel *source, GIOCondition cond, gpointer null) 178 io_invoke(GIOChannel *source, GIOCondition cond, gpointer null)
768 { 179 {
769 char keys[256]; 180 char keys[256];
770 gboolean ret = FALSE;
771 static GntKeyPressMode mode = GNT_KP_MODE_NORMAL;
772 const char *buffer;
773
774 int rd = read(STDIN_FILENO, keys, sizeof(keys) - 1); 181 int rd = read(STDIN_FILENO, keys, sizeof(keys) - 1);
775 if (rd < 0) 182 if (rd < 0)
776 { 183 {
777 int ch = getch(); /* This should return ERR, but let's see what it really returns */ 184 int ch = getch(); /* This should return ERR, but let's see what it really returns */
778 endwin(); 185 endwin();
785 endwin(); 192 endwin();
786 printf("EOF\n"); 193 printf("EOF\n");
787 raise(SIGABRT); 194 raise(SIGABRT);
788 } 195 }
789 196
790 event_stack = TRUE;
791 keys[rd] = 0; 197 keys[rd] = 0;
792
793 if (keys[0] == 27 && keys[1] == 'd' && keys[2] == 0)
794 {
795 /* This dumps the screen contents in an html file */
796 dump_screen();
797 }
798
799 gnt_keys_refine(keys); 198 gnt_keys_refine(keys);
800 199
801 if (mouse_enabled && detect_mouse_action(keys)) 200 if (mouse_enabled && detect_mouse_action(keys))
802 return TRUE; 201 return TRUE;
803 202
804 if (wm.key_pressed) { 203 gnt_wm_process_input(wm, keys);
805 buffer = wm.key_pressed(keys); 204
806 if (buffer == NULL) {
807 event_stack = FALSE;
808 return TRUE;
809 }
810 } else
811 buffer = keys;
812
813 if (mode == GNT_KP_MODE_NORMAL)
814 {
815 if (menu) {
816 ret = gnt_widget_key_pressed(GNT_WIDGET(menu), buffer);
817 } else if (ordered) {
818 ret = gnt_widget_key_pressed(ordered->data, buffer);
819 }
820
821 if (!ret)
822 {
823 if (buffer[0] == 27)
824 {
825 /* Some special key has been pressed */
826 if (strcmp(buffer, GNT_KEY_POPUP) == 0)
827 {}
828 else if (strcmp(buffer + 1, "c") == 0)
829 {
830 /* Alt + c was pressed. I am going to use it to close a window. */
831 if (ordered)
832 {
833 gnt_widget_destroy(ordered->data);
834 }
835 }
836 else if (strcmp(buffer + 1, "q") == 0)
837 {
838 /* I am going to use Alt + q to quit. */
839 g_main_loop_quit(loop);
840 }
841 else if (strcmp(buffer + 1, "n") == 0)
842 {
843 /* Alt + n to go to the next window */
844 switch_window(1);
845 }
846 else if (strcmp(buffer + 1, "p") == 0)
847 {
848 /* Alt + p to go to the previous window */
849 switch_window(-1);
850 }
851 else if (strcmp(buffer + 1, "m") == 0 && focus_list)
852 {
853 /* Move a window */
854 mode = GNT_KP_MODE_MOVE;
855 window_reverse(ordered->data, TRUE);
856 }
857 else if (strcmp(buffer + 1, "w") == 0 && focus_list)
858 {
859 /* Window list */
860 mode = GNT_KP_MODE_WINDOW_LIST;
861 show_window_list();
862 }
863 else if (strcmp(buffer + 1, "a") == 0)
864 {
865 mode = GNT_KP_MODE_WINDOW_LIST;
866 show_actions_list();
867 }
868 else if (strcmp(buffer + 1, "r") == 0 && focus_list)
869 {
870 /* Resize window */
871 mode = GNT_KP_MODE_RESIZE;
872 window_reverse(ordered->data, TRUE);
873 }
874 else if (strcmp(buffer + 1, ",") == 0 && focus_list)
875 {
876 /* Re-order the list of windows */
877 shift_window(ordered->data, -1);
878 }
879 else if (strcmp(buffer + 1, ".") == 0 && focus_list)
880 {
881 shift_window(ordered->data, 1);
882 }
883 else if (strcmp(buffer + 1, "l") == 0)
884 {
885 refresh_screen();
886 }
887 else if (strlen(buffer) == 2 && isdigit(*(buffer + 1)))
888 {
889 int n = *(buffer + 1) - '0';
890
891 if (n == 0)
892 n = 10;
893
894 switch_window_n(n - 1);
895 }
896 }
897 }
898 }
899 else if (mode == GNT_KP_MODE_MOVE && focus_list)
900 {
901 if (buffer[0] == 27)
902 {
903 gboolean changed = FALSE;
904 int x, y, w, h;
905 GntWidget *widget = GNT_WIDGET(ordered->data);
906
907 gnt_widget_get_position(widget, &x, &y);
908 gnt_widget_get_size(widget, &w, &h);
909
910 if (strcmp(buffer, GNT_KEY_LEFT) == 0)
911 {
912 if (x > X_MIN)
913 {
914 x--;
915 changed = TRUE;
916 }
917 }
918 else if (strcmp(buffer, GNT_KEY_RIGHT) == 0)
919 {
920 if (x + w < X_MAX)
921 {
922 x++;
923 changed = TRUE;
924 }
925 }
926 else if (strcmp(buffer, GNT_KEY_UP) == 0)
927 {
928 if (y > Y_MIN)
929 {
930 y--;
931 changed = TRUE;
932 }
933 }
934 else if (strcmp(buffer, GNT_KEY_DOWN) == 0)
935 {
936 if (y + h < Y_MAX)
937 {
938 y++;
939 changed = TRUE;
940 }
941 }
942 else if (buffer[1] == 0)
943 {
944 mode = GNT_KP_MODE_NORMAL;
945 window_reverse(widget, FALSE);
946 }
947
948 if (changed)
949 {
950 gnt_screen_move_widget(widget, x, y);
951 }
952 }
953 else if (*buffer == '\r')
954 {
955 mode = GNT_KP_MODE_NORMAL;
956 window_reverse(ordered->data, FALSE);
957 }
958 }
959 else if (mode == GNT_KP_MODE_WINDOW_LIST && _list.window)
960 {
961 gnt_widget_key_pressed(_list.window, buffer);
962
963 if (buffer[0] == '\r' || (buffer[0] == 27 && buffer[1] == 0))
964 {
965 mode = GNT_KP_MODE_NORMAL;
966 lock_focus_list = 1;
967 gnt_widget_destroy(_list.window);
968 _list.window = NULL;
969 _list.tree = NULL;
970 lock_focus_list = 0;
971 window_list = NULL;
972 action_list = NULL;
973 }
974 }
975 else if (mode == GNT_KP_MODE_RESIZE)
976 {
977 if (buffer[0] == '\r' || (buffer[0] == 27 && buffer[1] == 0)) {
978 mode = GNT_KP_MODE_NORMAL;
979 window_reverse(ordered->data, FALSE);
980 } else if (buffer[0] == 27) {
981 GntWidget *widget = ordered->data;
982 gboolean changed = FALSE;
983 int width, height;
984
985 gnt_widget_get_size(widget, &width, &height);
986
987 if (strcmp(buffer, GNT_KEY_DOWN) == 0)
988 {
989 if (widget->priv.y + height < Y_MAX)
990 {
991 height++;
992 changed = TRUE;
993 }
994 }
995 else if (strcmp(buffer, GNT_KEY_UP) == 0)
996 {
997 height--;
998 changed = TRUE;
999 }
1000 else if (strcmp(buffer, GNT_KEY_LEFT) == 0)
1001 {
1002 width--;
1003 changed = TRUE;
1004 }
1005 else if (strcmp(buffer, GNT_KEY_RIGHT) == 0)
1006 {
1007 if (widget->priv.x + width < X_MAX)
1008 {
1009 width++;
1010 changed = TRUE;
1011 }
1012 }
1013
1014 if (changed)
1015 {
1016 gnt_screen_resize_widget(widget, width, height);
1017 window_reverse(widget, TRUE);
1018 }
1019 }
1020 }
1021
1022 event_stack = FALSE;
1023 return TRUE; 205 return TRUE;
206 }
207
208 static void
209 setup_io()
210 {
211 int result;
212 channel = g_io_channel_unix_new(STDIN_FILENO);
213
214 g_io_channel_set_encoding(channel, NULL, NULL);
215 g_io_channel_set_buffered(channel, FALSE);
216 #if 0
217 g_io_channel_set_flags(channel, G_IO_FLAG_NONBLOCK, NULL );
218 #endif
219
220 result = g_io_add_watch_full(channel, G_PRIORITY_HIGH,
221 (G_IO_IN | G_IO_HUP | G_IO_ERR | G_IO_PRI),
222 io_invoke, NULL, NULL);
223
224 g_io_add_watch_full(channel, G_PRIORITY_HIGH,
225 (G_IO_NVAL),
226 io_invoke_error, GINT_TO_POINTER(result), NULL);
227
228 g_io_channel_unref(channel); /* Apparently this caused crashes for some people.
229 But irssi does this, so I am going to assume the
230 crashes were caused by some other stuff. */
231
232 g_printerr("gntmain: setting up IO\n");
1024 } 233 }
1025 234
1026 static gboolean 235 static gboolean
1027 refresh_screen() 236 refresh_screen()
1028 { 237 {
1029 endwin(); 238 gnt_bindable_perform_action_named(GNT_BINDABLE(wm), "refresh-screen", NULL);
1030 refresh();
1031
1032 X_MAX = getmaxx(stdscr);
1033 Y_MAX = getmaxy(stdscr) - 1;
1034
1035 g_hash_table_foreach(nodes, (GHFunc)refresh_node, NULL);
1036 update_screen(NULL);
1037 draw_taskbar(TRUE);
1038
1039 return FALSE; 239 return FALSE;
1040 } 240 }
1041 241
1042 /* Xerox */ 242 /* Xerox */
1043 static void 243 static void
1080 init_wm() 280 init_wm()
1081 { 281 {
1082 const char *name = gnt_style_get(GNT_STYLE_WM); 282 const char *name = gnt_style_get(GNT_STYLE_WM);
1083 gpointer handle; 283 gpointer handle;
1084 284
1085 if (!name || !*name) 285 if (name && *name) {
1086 return; 286 handle = g_module_open(name, G_MODULE_BIND_LAZY);
1087 287 if (handle) {
1088 handle = g_module_open(name, G_MODULE_BIND_LAZY); 288 gboolean (*init)(GntWM **);
1089 if (handle) { 289 if (g_module_symbol(handle, "gntwm_init", (gpointer)&init)) {
1090 gboolean (*init)(GntWM *); 290 init(&wm);
1091 if (g_module_symbol(handle, "gntwm_init", (gpointer)&init)) { 291 }
1092 init(&wm);
1093 } 292 }
1094 } 293 }
294 if (wm == NULL)
295 wm = g_object_new(GNT_TYPE_WM, NULL);
1095 } 296 }
1096 297
1097 void gnt_init() 298 void gnt_init()
1098 { 299 {
1099 static GIOChannel *channel = NULL;
1100 char *filename; 300 char *filename;
1101 int result;
1102 const char *locale; 301 const char *locale;
1103 302
1104 if (channel) 303 if (channel)
1105 return; 304 return;
1106 305
1107 channel = g_io_channel_unix_new(STDIN_FILENO); 306 setup_io();
1108
1109 g_io_channel_set_encoding(channel, NULL, NULL);
1110 g_io_channel_set_buffered(channel, FALSE);
1111 #if 0
1112 g_io_channel_set_flags(channel, G_IO_FLAG_NONBLOCK, NULL );
1113 #endif
1114
1115 result = g_io_add_watch_full(channel, G_PRIORITY_HIGH,
1116 (G_IO_IN | G_IO_HUP | G_IO_ERR | G_IO_PRI | G_IO_NVAL),
1117 io_invoke, NULL, NULL);
1118
1119 g_io_channel_unref(channel); /* Apparently this caused crashes for some people.
1120 But irssi does this, so I am going to assume the
1121 crashes were caused by some other stuff. */
1122 307
1123 locale = setlocale(LC_ALL, ""); 308 locale = setlocale(LC_ALL, "");
1124 309
1125 if (locale && (strstr(locale, "UTF") || strstr(locale, "utf"))) 310 if (locale && (strstr(locale, "UTF") || strstr(locale, "utf")))
1126 ascii_only = FALSE; 311 ascii_only = FALSE;
1137 filename = g_build_filename(g_get_home_dir(), ".gntrc", NULL); 322 filename = g_build_filename(g_get_home_dir(), ".gntrc", NULL);
1138 gnt_style_read_configure_file(filename); 323 gnt_style_read_configure_file(filename);
1139 g_free(filename); 324 g_free(filename);
1140 325
1141 gnt_init_colors(); 326 gnt_init_colors();
1142 X_MIN = 0;
1143 Y_MIN = 0;
1144 X_MAX = getmaxx(stdscr);
1145 Y_MAX = getmaxy(stdscr) - 1;
1146
1147 nodes = g_hash_table_new_full(g_direct_hash, g_direct_equal, NULL, free_node);
1148 327
1149 wbkgdset(stdscr, '\0' | COLOR_PAIR(GNT_COLOR_NORMAL)); 328 wbkgdset(stdscr, '\0' | COLOR_PAIR(GNT_COLOR_NORMAL));
1150 refresh(); 329 refresh();
1151 330
1152 #ifdef ALL_MOUSE_EVENTS 331 #ifdef ALL_MOUSE_EVENTS
1169 init_wm(); 348 init_wm();
1170 } 349 }
1171 350
1172 void gnt_main() 351 void gnt_main()
1173 { 352 {
1174 loop = g_main_loop_new(NULL, FALSE); 353 wm->loop = g_main_loop_new(NULL, FALSE);
1175 g_main_loop_run(loop); 354 g_main_loop_run(wm->loop);
1176 } 355 }
1177 356
1178 /********************************* 357 /*********************************
1179 * Stuff for 'window management' * 358 * Stuff for 'window management' *
1180 *********************************/ 359 *********************************/
1181 360
1182 static void
1183 free_node(gpointer data)
1184 {
1185 GntNode *node = data;
1186 hide_panel(node->panel);
1187 del_panel(node->panel);
1188 g_free(node);
1189 }
1190
1191 void gnt_screen_occupy(GntWidget *widget) 361 void gnt_screen_occupy(GntWidget *widget)
1192 { 362 {
1193 GntNode *node; 363 gnt_wm_new_window(wm, widget);
1194
1195 while (widget->parent)
1196 widget = widget->parent;
1197
1198 if (g_hash_table_lookup(nodes, widget))
1199 return; /* XXX: perhaps _update instead? */
1200
1201 node = g_new0(GntNode, 1);
1202 node->me = widget;
1203
1204 g_hash_table_replace(nodes, widget, node);
1205
1206 refresh_node(widget, node, NULL);
1207
1208 if (window_list)
1209 {
1210 if ((GNT_IS_BOX(widget) && GNT_BOX(widget)->title) && window_list->window != widget
1211 && GNT_WIDGET_IS_FLAG_SET(widget, GNT_WIDGET_CAN_TAKE_FOCUS))
1212 {
1213 gnt_tree_add_row_last(GNT_TREE(window_list->tree), widget,
1214 gnt_tree_create_row(GNT_TREE(window_list->tree), GNT_BOX(widget)->title),
1215 NULL);
1216 update_window_in_list(widget);
1217 }
1218 }
1219
1220 update_screen(NULL);
1221 } 364 }
1222 365
1223 void gnt_screen_release(GntWidget *widget) 366 void gnt_screen_release(GntWidget *widget)
1224 { 367 {
1225 GntNode *node; 368 gnt_wm_window_close(wm, widget);
1226
1227 gnt_screen_remove_widget(widget);
1228 node = g_hash_table_lookup(nodes, widget);
1229
1230 if (node == NULL) /* Yay! Nothing to do. */
1231 return;
1232
1233 if (GNT_WIDGET_IS_FLAG_SET(widget, GNT_WIDGET_DESTROYING) && wm.close_window)
1234 wm.close_window(widget);
1235
1236 g_hash_table_remove(nodes, widget);
1237
1238 if (window_list)
1239 {
1240 gnt_tree_remove(GNT_TREE(window_list->tree), widget);
1241 }
1242
1243 update_screen(NULL);
1244 } 369 }
1245 370
1246 void gnt_screen_update(GntWidget *widget) 371 void gnt_screen_update(GntWidget *widget)
1247 { 372 {
1248 GntNode *node; 373 gnt_wm_update_window(wm, widget);
1249
1250 while (widget->parent)
1251 widget = widget->parent;
1252 if (!GNT_IS_MENU(widget))
1253 gnt_box_sync_children(GNT_BOX(widget));
1254 node = g_hash_table_lookup(nodes, widget);
1255 if (node && !node->panel)
1256 {
1257 if (wm.new_window && node->me != _list.window)
1258 node->panel = wm.new_window(node->me);
1259 else
1260 node->panel = new_panel(node->me->window);
1261 set_panel_userptr(node->panel, node);
1262 if (!GNT_WIDGET_IS_FLAG_SET(node->me, GNT_WIDGET_TRANSIENT)) {
1263 if (!g_object_get_data(G_OBJECT(node->me), "give_focus")) {
1264 bottom_panel(node->panel); /* New windows should not grab focus */
1265 gnt_widget_set_urgent(node->me);
1266 }
1267 else {
1268 bring_on_top(node->me);
1269 }
1270 }
1271 }
1272
1273 if (_list.window)
1274 {
1275 GntNode *nd = g_hash_table_lookup(nodes, _list.window);
1276 top_panel(nd->panel);
1277 }
1278
1279 update_screen(NULL);
1280 } 374 }
1281 375
1282 gboolean gnt_widget_has_focus(GntWidget *widget) 376 gboolean gnt_widget_has_focus(GntWidget *widget)
1283 { 377 {
1284 GntWidget *w; 378 GntWidget *w;
1291 w = widget; 385 w = widget;
1292 386
1293 while (widget->parent) 387 while (widget->parent)
1294 widget = widget->parent; 388 widget = widget->parent;
1295 389
1296 if (widget == _list.window) 390 if (widget == wm->_list.window)
1297 return TRUE; 391 return TRUE;
1298 392 if (wm->ordered && wm->ordered->data == widget) {
1299 if (ordered && ordered->data == widget)
1300 {
1301 if (GNT_IS_BOX(widget) && 393 if (GNT_IS_BOX(widget) &&
1302 (GNT_BOX(widget)->active == w || widget == w)) 394 (GNT_BOX(widget)->active == w || widget == w))
1303 return TRUE; 395 return TRUE;
1304 } 396 }
1305
1306 return FALSE; 397 return FALSE;
1307 } 398 }
1308 399
1309 void gnt_widget_set_urgent(GntWidget *widget) 400 void gnt_widget_set_urgent(GntWidget *widget)
1310 { 401 {
1311 while (widget->parent) 402 while (widget->parent)
1312 widget = widget->parent; 403 widget = widget->parent;
1313 404
1314 if (ordered && ordered->data == widget) 405 if (wm->ordered && wm->ordered->data == widget)
1315 return; 406 return;
1316 407
1317 GNT_WIDGET_SET_FLAGS(widget, GNT_WIDGET_URGENT); 408 GNT_WIDGET_SET_FLAGS(widget, GNT_WIDGET_URGENT);
1318 409
1319 if (wm.window_update) { 410 gnt_wm_update_window(wm, widget);
1320 GntNode *node = g_hash_table_lookup(nodes, widget);
1321 wm.window_update(node ? node->panel : NULL, widget);
1322 }
1323
1324 draw_taskbar(FALSE);
1325 } 411 }
1326 412
1327 void gnt_quit() 413 void gnt_quit()
1328 { 414 {
415 g_hash_table_destroy(wm->nodes); /* XXX: */
416 update_panels();
417 doupdate();
1329 gnt_uninit_colors(); 418 gnt_uninit_colors();
1330 gnt_uninit_styles(); 419 gnt_uninit_styles();
1331 endwin(); 420 endwin();
1332 } 421 }
1333 422
1336 return ascii_only; 425 return ascii_only;
1337 } 426 }
1338 427
1339 void gnt_screen_resize_widget(GntWidget *widget, int width, int height) 428 void gnt_screen_resize_widget(GntWidget *widget, int width, int height)
1340 { 429 {
1341 if (widget->parent == NULL) 430 gnt_wm_resize_window(wm, widget, width, height);
1342 {
1343 GntNode *node = g_hash_table_lookup(nodes, widget);
1344 if (!node)
1345 return;
1346
1347 if (wm.window_resize_confirm && !wm.window_resize_confirm(widget, &width, &height))
1348 return;
1349
1350 hide_panel(node->panel);
1351 gnt_widget_set_size(widget, width, height);
1352 gnt_widget_draw(widget);
1353 if (wm.window_resized)
1354 node->panel = wm.window_resized(node->panel, widget);
1355 else
1356 replace_panel(node->panel, widget->window);
1357 show_panel(node->panel);
1358 update_screen(NULL);
1359 }
1360 } 431 }
1361 432
1362 void gnt_screen_move_widget(GntWidget *widget, int x, int y) 433 void gnt_screen_move_widget(GntWidget *widget, int x, int y)
1363 { 434 {
1364 GntNode *node = g_hash_table_lookup(nodes, widget); 435 gnt_wm_move_window(wm, widget, x, y);
1365
1366 if (wm.window_move_confirm && !wm.window_move_confirm(widget, &x, &y))
1367 return;
1368
1369 gnt_widget_set_position(widget, x, y);
1370 move_panel(node->panel, y, x);
1371
1372 if (wm.window_moved)
1373 wm.window_moved(node->panel, widget);
1374
1375 update_screen(NULL);
1376 } 436 }
1377 437
1378 void gnt_screen_rename_widget(GntWidget *widget, const char *text) 438 void gnt_screen_rename_widget(GntWidget *widget, const char *text)
1379 { 439 {
1380 gnt_box_set_title(GNT_BOX(widget), text); 440 gnt_box_set_title(GNT_BOX(widget), text);
1381 gnt_widget_draw(widget); 441 gnt_widget_draw(widget);
1382 442 gnt_wm_update_window(wm, widget);
1383 if (wm.window_update) { 443 }
1384 GntNode *node = g_hash_table_lookup(nodes, widget);
1385 wm.window_update(node ? node->panel : NULL, widget);
1386 }
1387
1388 draw_taskbar(FALSE);
1389 }
1390
1391 /**
1392 * An application can register actions which will show up in a 'start-menu' like popup
1393 */
1394 typedef struct _GnAction
1395 {
1396 const char *label;
1397 void (*callback)();
1398 } GntAction;
1399
1400 static GList *actions;
1401 444
1402 void gnt_register_action(const char *label, void (*callback)()) 445 void gnt_register_action(const char *label, void (*callback)())
1403 { 446 {
1404 GntAction *action = g_new0(GntAction, 1); 447 GntAction *action = g_new0(GntAction, 1);
1405 action->label = g_strdup(label); 448 action->label = g_strdup(label);
1406 action->callback = callback; 449 action->callback = callback;
1407 450
1408 actions = g_list_append(actions, action); 451 wm->acts = g_list_append(wm->acts, action);
1409 }
1410
1411 static void
1412 action_list_activate(GntTree *tree, gpointer null)
1413 {
1414 GntAction *action = gnt_tree_get_selection_data(tree);
1415 action->callback();
1416 }
1417
1418 static int
1419 compare_action(gconstpointer p1, gconstpointer p2)
1420 {
1421 const GntAction *a1 = p1;
1422 const GntAction *a2 = p2;
1423
1424 return g_utf8_collate(a1->label, a2->label);
1425 }
1426
1427 static void
1428 show_actions_list()
1429 {
1430 GntWidget *tree, *win;
1431 GList *iter;
1432 int h;
1433
1434 if (action_list)
1435 return;
1436
1437 setup__list();
1438 action_list = &_list;
1439 win = action_list->window;
1440 tree = action_list->tree;
1441
1442 gnt_box_set_title(GNT_BOX(win), "Actions");
1443 GNT_WIDGET_SET_FLAGS(tree, GNT_WIDGET_NO_BORDER);
1444 /* XXX: Do we really want this? */
1445 gnt_tree_set_compare_func(GNT_TREE(tree), compare_action);
1446
1447 for (iter = actions; iter; iter = iter->next) {
1448 GntAction *action = iter->data;
1449 gnt_tree_add_row_last(GNT_TREE(tree), action,
1450 gnt_tree_create_row(GNT_TREE(tree), action->label), NULL);
1451 }
1452 g_signal_connect(G_OBJECT(tree), "activate", G_CALLBACK(action_list_activate), NULL);
1453 gnt_widget_set_size(tree, 0, g_list_length(actions));
1454 gnt_widget_get_size(win, NULL, &h);
1455 gnt_widget_set_position(win, 0, getmaxy(stdscr) - 1 - h);
1456
1457 lock_focus_list = 1;
1458 gnt_widget_show(win);
1459 lock_focus_list = 0;
1460 } 452 }
1461 453
1462 static void 454 static void
1463 reset_menu(GntWidget *widget, gpointer null) 455 reset_menu(GntWidget *widget, gpointer null)
1464 { 456 {
1465 menu = NULL; 457 wm->menu = NULL;
1466 } 458 }
1467 459
1468 gboolean gnt_screen_menu_show(gpointer newmenu) 460 gboolean gnt_screen_menu_show(gpointer newmenu)
1469 { 461 {
1470 if (menu) { 462 if (wm->menu) {
1471 /* For now, if a menu is being displayed, then another menu 463 /* For now, if a menu is being displayed, then another menu
1472 * can NOT take over. */ 464 * can NOT take over. */
1473 return FALSE; 465 return FALSE;
1474 } 466 }
1475 467
1476 menu = newmenu; 468 wm->menu = newmenu;
1477 gnt_widget_draw(GNT_WIDGET(menu)); 469 gnt_widget_draw(GNT_WIDGET(wm->menu));
1478 470
1479 g_signal_connect(G_OBJECT(menu), "hide", G_CALLBACK(reset_menu), NULL); 471 g_signal_connect(G_OBJECT(wm->menu), "hide", G_CALLBACK(reset_menu), NULL);
1480 472
1481 return TRUE; 473 return TRUE;
1482 } 474 }
1483 475