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