1 /* Copyright (c) 2010
2 * Juergen Weigert (jnweiger@immd4.informatik.uni-erlangen.de)
3 * Sadrul Habib Chowdhury (sadrul@users.sourceforge.net)
4 *
5 * This program is free software; you can redistribute it and/or modify
6 * it under the terms of the GNU General Public License as published by
7 * the Free Software Foundation; either version 3, or (at your option)
8 * any later version.
9 *
10 * This program is distributed in the hope that it will be useful,
11 * but WITHOUT ANY WARRANTY; without even the implied warranty of
12 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 * GNU General Public License for more details.
14 *
15 * You should have received a copy of the GNU General Public License
16 * along with this program (see the file COPYING); if not, see
17 * https://www.gnu.org/licenses/, or contact Free Software Foundation, Inc.,
18 * 51 Franklin Street, Fifth Floor, Boston, MA 02111-1301 USA
19 *
20 ****************************************************************
21 */
22
23 /* Deals with the list of windows */
24
25 /* NOTE: A 'struct win *' is used as the 'data' for each row. It might make more sense
26 * to use 'struct win* ->w_number' as the 'data', instead, because that way, we can
27 * verify that the window does exist (by looking at wtab[]).
28 */
29
30 #include "config.h"
31 #include "screen.h"
32 #include "layer.h"
33 #include "extern.h"
34 #include "list_generic.h"
35
36 extern struct layer *flayer;
37 extern struct display *display, *displays;
38
39 extern char *wlisttit;
40 extern char *wliststr;
41
42 extern struct mchar mchar_blank, mchar_so;
43 extern int renditions[];
44
45 extern struct win **wtab, *windows, *fore;
46 extern int maxwin;
47
48 extern char *noargs[];
49
50 static char ListID[] = "window";
51
52 struct gl_Window_Data
53 {
54 struct win *group; /* Set only for a W_TYPE_GROUP window */
55 int order; /* MRU? NUM? */
56 int onblank;
57 int nested;
58 struct win *fore; /* The foreground window we had. */
59 };
60
61 /* Is this wdata for a group window? */
62 #define WLIST_FOR_GROUP(wdate) ((wdata)->group && !(wdata)->onblank && Layer2Window(flayer) && Layer2Window(flayer)->w_type == W_TYPE_GROUP)
63
64 /* This macro should not be used if 'fn' is expected to update the window list */
65 #define FOR_EACH_WINDOW(_wdata, _w, fn) do { \
66 if ((_wdata)->order == WLIST_MRU) \
67 { \
68 struct win *_ww; \
69 for (_ww = windows; _ww; _ww = _ww->w_next) \
70 { \
71 _w = _ww; \
72 fn \
73 } \
74 } \
75 else \
76 { \
77 struct win **_ww, *_witer; \
78 for (_ww = wtab, _witer = windows; _witer && _ww - wtab < maxwin; _ww++) \
79 { \
80 if (!(_w = *_ww)) continue; \
81 fn \
82 _witer = _witer->w_next; \
83 } \
84 } \
85 } while (0)
86
87 /* Is 'a' an ancestor of 'd'? */
88 static int
window_ancestor(struct win * a,struct win * d)89 window_ancestor(struct win *a, struct win *d)
90 {
91 if (!a)
92 return 1; /* Every window is a descendant of the 'null' group */
93 for (; d; d = d->w_group)
94 if (d->w_group == a)
95 return 1;
96 return 0;
97 }
98
99 static void
window_kill_confirm(char * buf,int len,char * data)100 window_kill_confirm(char *buf, int len, char *data)
101 {
102 struct win *w = windows;
103 struct action act;
104
105 if (len || (*buf != 'y' && *buf != 'Y'))
106 {
107 memset(buf, 0, len);
108 return;
109 }
110
111 /* Loop over the windows to make sure that the window actually still exists. */
112 for (; w; w = w->w_next)
113 if (w == (struct win *)data)
114 break;
115
116 if (!w)
117 return;
118
119 /* Pretend the selected window is the foreground window. Then trigger a non-interactive 'kill' */
120 fore = w;
121 act.nr = RC_KILL;
122 act.args = noargs;
123 act.argl = 0;
124 act.quiet = 0;
125 DoAction(&act, -1);
126 }
127
128 static struct ListRow *
gl_Window_add_group(struct ListData * ldata,struct ListRow * row)129 gl_Window_add_group(struct ListData *ldata, struct ListRow *row)
130 {
131 /* Right now, 'row' doesn't have any child. */
132 struct gl_Window_Data *wdata = ldata->data;
133 struct win *group = row->data, *w;
134 struct ListRow *cur = row;
135
136 ASSERT(wdata->nested);
137
138 FOR_EACH_WINDOW(wdata, w,
139 if (w->w_group != group)
140 continue;
141
142 cur = glist_add_row(ldata, w, cur);
143 if (w == wdata->fore)
144 ldata->selected = cur;
145
146 if (w->w_type == W_TYPE_GROUP)
147 cur = gl_Window_add_group(ldata, cur);
148 );
149
150 return cur;
151 }
152
153 static void
gl_Window_rebuild(struct ListData * ldata)154 gl_Window_rebuild(struct ListData *ldata)
155 {
156 struct ListRow *row = NULL;
157 struct gl_Window_Data *wdata = ldata->data;
158 struct win *w;
159
160 FOR_EACH_WINDOW(wdata, w,
161 if (w->w_group != wdata->group)
162 continue;
163 row = glist_add_row(ldata, w, row);
164 if (w == wdata->fore)
165 ldata->selected = row;
166 if (w->w_type == W_TYPE_GROUP && wdata->nested)
167 row = gl_Window_add_group(ldata, row);
168 );
169 glist_display_all(ldata);
170 }
171
172 static struct ListRow *
gl_Window_findrow(struct ListData * ldata,struct win * p)173 gl_Window_findrow(struct ListData *ldata, struct win *p)
174 {
175 struct ListRow *row = ldata->root;
176 for (; row; row = row->next)
177 {
178 if (row->data == p)
179 break;
180 }
181 return row;
182 }
183
184 static int
gl_Window_remove(struct ListData * ldata,struct win * p)185 gl_Window_remove(struct ListData *ldata, struct win *p)
186 {
187 struct ListRow *row = gl_Window_findrow(ldata, p);
188 if (!row)
189 return 0;
190
191 /* Remove 'row'. Update 'selected', 'top', 'root' if necessary. */
192 if (row->next)
193 row->next->prev = row->prev;
194 if (row->prev)
195 row->prev->next = row->next;
196
197 if (ldata->selected == row)
198 ldata->selected = row->prev ? row->prev : row->next;
199 if (ldata->top == row)
200 ldata->top = row->prev ? row->prev : row->next;
201 if (ldata->root == row)
202 ldata->root = row->next;
203
204 ldata->list_fn->gl_freerow(ldata, row);
205 free(row);
206
207 return 1;
208 }
209
210 static int
gl_Window_header(struct ListData * ldata)211 gl_Window_header(struct ListData *ldata)
212 {
213 char *str;
214 struct gl_Window_Data *wdata = ldata->data;
215 int g;
216
217 if ((g = (wdata->group != NULL)))
218 {
219 LPutWinMsg(flayer, "Group: ", 7, &mchar_blank, 0, 0);
220 LPutWinMsg(flayer, wdata->group->w_title, strlen(wdata->group->w_title), &mchar_blank, 7, 0);
221 }
222
223 str = MakeWinMsgEv(wlisttit, (struct win *)0, '%', flayer->l_width, (struct event *)0, 0);
224
225 LPutWinMsg(flayer, str, strlen(str), &mchar_blank, 0, g);
226 return 2 + g;
227 }
228
229 static int
gl_Window_footer(struct ListData * ldata)230 gl_Window_footer(struct ListData *ldata)
231 {
232 return 0;
233 }
234
235 static int
gl_Window_row(struct ListData * ldata,struct ListRow * lrow)236 gl_Window_row(struct ListData *ldata, struct ListRow *lrow)
237 {
238 char *str;
239 struct win *w, *g;
240 int xoff;
241 struct mchar *mchar;
242 struct mchar mchar_rend = mchar_blank;
243 struct gl_Window_Data *wdata = ldata->data;
244
245 w = lrow->data;
246
247 /* First, make sure we want to display this window in the list.
248 * If we are showing a list for a group, and not on blank, then we must
249 * only show the windows directly belonging to that group.
250 * Otherwise, do some more checks. */
251
252 for (xoff = 0, g = w->w_group; g != wdata->group; g = g->w_group)
253 xoff += 2;
254 str = MakeWinMsgEv(wliststr, w, '%', flayer->l_width - xoff, NULL, 0);
255 if (ldata->selected == lrow)
256 mchar = &mchar_so;
257 else if (w->w_monitor == MON_DONE && renditions[REND_MONITOR] != -1)
258 {
259 mchar = &mchar_rend;
260 ApplyAttrColor(renditions[REND_MONITOR], mchar);
261 }
262 else if ((w->w_bell == BELL_DONE || w->w_bell == BELL_FOUND) && renditions[REND_BELL] != -1)
263 {
264 mchar = &mchar_rend;
265 ApplyAttrColor(renditions[REND_BELL], mchar);
266 }
267 else if ((w->w_silence == SILENCE_FOUND || w->w_silence == SILENCE_DONE) && renditions[REND_SILENCE] != -1)
268 {
269 mchar = &mchar_rend;
270 ApplyAttrColor(renditions[REND_SILENCE], mchar);
271 }
272 else
273 mchar = &mchar_blank;
274
275 LPutWinMsg(flayer, str, flayer->l_width, mchar, xoff, lrow->y);
276 if (xoff)
277 LPutWinMsg(flayer, "", xoff, mchar, 0, lrow->y);
278
279 return 1;
280 }
281
282 static int
gl_Window_input(struct ListData * ldata,char ** inp,int * len)283 gl_Window_input(struct ListData *ldata, char **inp, int *len)
284 {
285 struct win *win;
286 unsigned char ch;
287 struct display *cd = display;
288 struct gl_Window_Data *wdata = ldata->data;
289
290 if (!ldata->selected)
291 return 0;
292
293 ch = (unsigned char) **inp;
294 ++*inp;
295 --*len;
296
297 win = ldata->selected->data;
298 switch (ch)
299 {
300 case ' ':
301 case '\n':
302 case '\r':
303 if (!win)
304 break;
305 #ifdef MULTIUSER
306 if (display && AclCheckPermWin(D_user, ACL_READ, win))
307 return 0; /* Not allowed to switch to this window. */
308 #endif
309 if (WLIST_FOR_GROUP(wdata))
310 SwitchWindow(win->w_number);
311 else
312 {
313 /* Abort list only when not in a group window. */
314 glist_abort();
315 display = cd;
316 if (D_fore != win)
317 SwitchWindow(win->w_number);
318 }
319 *len = 0;
320 break;
321
322 case 'm':
323 /* Toggle MRU-ness */
324 wdata->order = wdata->order == WLIST_MRU ? WLIST_NUM : WLIST_MRU;
325 glist_remove_rows(ldata);
326 gl_Window_rebuild(ldata);
327 break;
328
329 case 'g':
330 /* Toggle nestedness */
331 wdata->nested = !wdata->nested;
332 glist_remove_rows(ldata);
333 gl_Window_rebuild(ldata);
334 break;
335
336 case 'a':
337 /* All-window view */
338 if (wdata->group)
339 {
340 int order = wdata->order | (wdata->nested ? WLIST_NESTED : 0);
341 glist_abort();
342 display = cd;
343 display_windows(1, order, NULL);
344 *len = 0;
345 }
346 else if (!wdata->nested)
347 {
348 wdata->nested = 1;
349 glist_remove_rows(ldata);
350 gl_Window_rebuild(ldata);
351 }
352 break;
353
354 case 010: /* ^H */
355 case 0177: /* Backspace */
356 if (!wdata->group)
357 break;
358 if (wdata->group->w_group)
359 {
360 /* The parent is another group window. So switch to that window. */
361 struct win *g = wdata->group->w_group;
362 glist_abort();
363 display = cd;
364 SetForeWindow(g);
365 *len = 0;
366 }
367 else
368 {
369 /* We were in a group view. Now we are moving to an all-window view.
370 * So treat it as 'windowlist on blank'. */
371 int order = wdata->order | (wdata->nested ? WLIST_NESTED : 0);
372 glist_abort();
373 display = cd;
374 display_windows(1, order, NULL);
375 *len = 0;
376 }
377 break;
378
379 case ',': /* Switch numbers with the previous window. */
380 if (wdata->order == WLIST_NUM && ldata->selected->prev)
381 {
382 struct win *pw = ldata->selected->prev->data;
383 if (win->w_group != pw->w_group)
384 break; /* Do not allow switching with the parent group */
385
386 /* When a windows's number is successfully changed, it triggers a WListUpdatecv
387 * with NULL window. So that causes a redraw of the entire list. So reset the
388 * 'selected' after that. */
389 wdata->fore = win;
390 WindowChangeNumber(win->w_number, pw->w_number);
391 }
392 break;
393
394 case '.': /* Switch numbers with the next window. */
395 if (wdata->order == WLIST_NUM && ldata->selected->next)
396 {
397 struct win *nw = ldata->selected->next->data;
398 if (win->w_group != nw->w_group)
399 break; /* Do not allow switching with the parent group */
400
401 wdata->fore = win;
402 WindowChangeNumber(win->w_number, nw->w_number);
403 }
404 break;
405
406 case 'K': /* Kill a window */
407 {
408 char str[MAXSTR];
409 snprintf(str, sizeof(str) - 1, "Really kill window %d (%s) [y/n]",
410 win->w_number, win->w_title);
411 Input(str, 1, INP_RAW, window_kill_confirm, (char *)win, 0);
412 }
413 break;
414
415 case 033: /* escape */
416 case 007: /* ^G */
417 if (!WLIST_FOR_GROUP(wdata))
418 {
419 int fnumber = wdata->onblank ? wdata->fore->w_number : -1;
420 glist_abort();
421 display = cd;
422 if (fnumber >= 0)
423 SwitchWindow(fnumber);
424 *len = 0;
425 }
426 break;
427 default:
428 if (ch >= '0' && ch <= '9')
429 {
430 struct ListRow *row = ldata->root;
431 for (; row; row = row->next)
432 {
433 struct win *w = row->data;
434 if (w->w_number == ch - '0')
435 {
436 struct ListRow *old = ldata->selected;
437 if (old == row)
438 break;
439 ldata->selected = row;
440 if (ldata->selected->y == -1)
441 {
442 /* We need to list all the rows, since we are scrolling down. But first,
443 * find the top of the visible list. */
444 ldata->top = row;
445 glist_display_all(ldata);
446 }
447 else
448 {
449 /* just redisplay the two lines. */
450 ldata->list_fn->gl_printrow(ldata, old);
451 ldata->list_fn->gl_printrow(ldata, ldata->selected);
452 flayer->l_y = ldata->selected->y;
453 LaySetCursor();
454 }
455 break;
456 }
457 }
458 break;
459 }
460 --*inp;
461 ++*len;
462 return 0;
463 }
464 return 1;
465 }
466
467 static int
gl_Window_freerow(struct ListData * ldata,struct ListRow * row)468 gl_Window_freerow(struct ListData *ldata, struct ListRow *row)
469 {
470 return 0;
471 }
472
473 static int
gl_Window_free(struct ListData * ldata)474 gl_Window_free(struct ListData *ldata)
475 {
476 Free(ldata->data);
477 return 0;
478 }
479
480 static int
gl_Window_match(struct ListData * ldata,struct ListRow * row,const char * needle)481 gl_Window_match(struct ListData *ldata, struct ListRow *row, const char *needle)
482 {
483 struct win *w = row->data;
484 if (InStr(w->w_title, needle))
485 return 1;
486 return 0;
487 }
488
489 static struct GenericList gl_Window =
490 {
491 gl_Window_header,
492 gl_Window_footer,
493 gl_Window_row,
494 gl_Window_input,
495 gl_Window_freerow,
496 gl_Window_free,
497 gl_Window_match
498 };
499
500 void
display_windows(int onblank,int order,struct win * group)501 display_windows(int onblank, int order, struct win *group)
502 {
503 struct win *p;
504 struct ListData *ldata;
505 struct gl_Window_Data *wdata;
506
507 if (flayer->l_width < 10 || flayer->l_height < 6)
508 {
509 LMsg(0, "Window size too small for window list page");
510 return;
511 }
512
513 if (group)
514 onblank = 0; /* When drawing a group window, ignore 'onblank' */
515
516 if (onblank)
517 {
518 debug3("flayer %x %d %x\n", flayer, flayer->l_width, flayer->l_height);
519 if (!display)
520 {
521 LMsg(0, "windowlist -b: display required");
522 return;
523 }
524 p = D_fore;
525 if (p)
526 {
527 SetForeWindow((struct win *)0);
528 if (p->w_group)
529 {
530 D_fore = p->w_group;
531 flayer->l_data = (char *)p->w_group;
532 }
533 Activate(0);
534 }
535 if (flayer->l_width < 10 || flayer->l_height < 6)
536 {
537 LMsg(0, "Window size too small for window list page");
538 return;
539 }
540 }
541 else
542 p = Layer2Window(flayer);
543 if (!group && p)
544 group = p->w_group;
545
546 ldata = glist_display(&gl_Window, ListID);
547 if (!ldata)
548 {
549 if (onblank && p)
550 {
551 /* Could not display the list. So restore the window. */
552 SetForeWindow(p);
553 Activate(1);
554 }
555 return;
556 }
557
558 wdata = calloc(1, sizeof(struct gl_Window_Data));
559 wdata->group = group;
560 wdata->order = (order & ~WLIST_NESTED);
561 wdata->nested = !!(order & WLIST_NESTED);
562 wdata->onblank = onblank;
563
564 /* Set the most recent window as selected. */
565 wdata->fore = windows;
566 while (wdata->fore && wdata->fore->w_group != group)
567 wdata->fore = wdata->fore->w_next;
568
569 ldata->data = wdata;
570
571 gl_Window_rebuild(ldata);
572 }
573
574 static void
WListUpdate(struct win * p,struct ListData * ldata)575 WListUpdate(struct win *p, struct ListData *ldata)
576 {
577 struct gl_Window_Data *wdata = ldata->data;
578 struct ListRow *row, *rbefore;
579 struct win *before;
580 int d = 0, sel = 0;
581
582 if (!p)
583 {
584 if (ldata->selected)
585 wdata->fore = ldata->selected->data; /* Try to retain the current selection */
586 glist_remove_rows(ldata);
587 gl_Window_rebuild(ldata);
588 return;
589 }
590
591 /* First decide if this window should be displayed at all. */
592 d = 1;
593 if (wdata->order == WLIST_NUM || wdata->order == WLIST_MRU)
594 {
595 if (p->w_group != wdata->group)
596 {
597 if (!wdata->nested)
598 d = 0;
599 else
600 d = window_ancestor(wdata->group, p);
601 }
602 }
603
604 if (!d)
605 {
606 if (gl_Window_remove(ldata, p))
607 glist_display_all(ldata);
608 return;
609 }
610
611 /* OK, so we keep the window in the list. Update the ordering.
612 * First, find the row where this window should go to. Then, either create
613 * a new row for that window, or move the exising row for the window to the
614 * correct place. */
615 before = NULL;
616 if (wdata->order == WLIST_MRU)
617 {
618 if (windows != p)
619 for (before = windows; before; before = before->w_next)
620 if (before->w_next == p)
621 break;
622 }
623 else if (wdata->order == WLIST_NUM)
624 {
625 if (p->w_number != 0)
626 {
627 struct win **w = wtab + p->w_number - 1;
628 for (; w >= wtab; w--)
629 {
630 if (*w && (*w)->w_group == wdata->group)
631 {
632 before = *w;
633 break;
634 }
635 }
636 }
637 }
638
639 /* Now, find the row belonging to 'before' */
640 if (before)
641 rbefore = gl_Window_findrow(ldata, before);
642 else if (wdata->nested && p->w_group) /* There's no 'before'. So find the group window */
643 rbefore = gl_Window_findrow(ldata, p->w_group);
644 else
645 rbefore = NULL;
646
647 /* For now, just remove the row containing 'p' if it is not already in the right place . */
648 row = gl_Window_findrow(ldata, p);
649 if (row)
650 {
651 if (row->prev != rbefore)
652 {
653 sel = ldata->selected->data == p;
654 gl_Window_remove(ldata, p);
655 }
656 else
657 p = NULL; /* the window is in the correct place */
658 }
659 if (p)
660 {
661 row = glist_add_row(ldata, p, rbefore);
662 if (sel)
663 ldata->selected = row;
664 }
665 glist_display_all(ldata);
666 }
667
668 void
WListUpdatecv(cv,p)669 WListUpdatecv(cv, p)
670 struct canvas *cv;
671 struct win *p;
672 {
673 struct ListData *ldata;
674 struct gl_Window_Data *wdata;
675
676 if (cv->c_layer->l_layfn != &ListLf)
677 return;
678 ldata = cv->c_layer->l_data;
679 if (ldata->name != ListID)
680 return;
681 wdata = ldata->data;
682 CV_CALL(cv, WListUpdate(p, ldata));
683 }
684
685 void
WListLinkChanged()686 WListLinkChanged()
687 {
688 struct display *olddisplay = display;
689 struct canvas *cv;
690 struct ListData *ldata;
691 struct gl_Window_Data *wdata;
692
693 for (display = displays; display; display = display->d_next)
694 for (cv = D_cvlist; cv; cv = cv->c_next)
695 {
696 if (!cv->c_layer || cv->c_layer->l_layfn != &ListLf)
697 continue;
698 ldata = cv->c_layer->l_data;
699 if (ldata->name != ListID)
700 continue;
701 wdata = ldata->data;
702 if (!(wdata->order & WLIST_MRU))
703 continue;
704 CV_CALL(cv, WListUpdate(0, ldata));
705 }
706 display = olddisplay;
707 }
708
709