~ubuntu-branches/ubuntu/gutsy/flwm/gutsy

1 by Tommi Virtanen
Import upstream version 1.00
1
// Menu.cxx
2
3
#include "config.h"
4
#include "Frame.H"
5
#if DESKTOPS
6
#include "Desktop.H"
7
#endif
8
#include <FL/Fl_Box.H>
9
#include <FL/Fl_Return_Button.H>
10
#include <FL/Fl_Input.H>
11
#include <FL/Fl_Menu_Item.H>
12
#include <FL/fl_draw.H>
13
#include <stdio.h>
14
#include <stdlib.h>
15
#include <string.h>
16
#include <ctype.h>
17
#include "FrameWindow.H"
18
19
#include <sys/types.h>
20
#include <dirent.h>
21
#include <sys/stat.h>
22
23
// it is possible for the window to be deleted or withdrawn while
24
// the menu is up.  This will detect that case (with reasonable
25
// reliability):
26
static int
27
window_deleted(Frame* c)
28
{
29
  return c->state() != NORMAL
30
    && c->state() != ICONIC
31
    && c->state() != OTHER_DESKTOP;
32
}
33
34
static void
35
frame_callback(Fl_Widget*, void*d)
36
{
37
  Frame* c = (Frame*)d;
38
  if (window_deleted(c)) return;
39
  c->raise();
40
  c->activate(2);
41
}
42
43
#if DESKTOPS
44
// raise it but also put it on the current desktop:
45
static void
46
move_frame_callback(Fl_Widget*, void*d)
47
{
48
  Frame* c = (Frame*)d;
49
  if (window_deleted(c)) return;
50
  c->desktop(Desktop::current());
51
  c->raise();
52
  c->activate(2);
53
}
54
#endif
55
56
#define SCREEN_DX 1	// offset to corner of contents area
57
#define SCREEN_W (MENU_ICON_W-2)	// size of area to draw contents in
58
#define SCREEN_H (MENU_ICON_H-2)	// size of area to draw contents in
59
60
#define	MAX_NESTING_DEPTH	32
61
62
extern Fl_Window* Root;
63
64
static void
65
frame_label_draw(const Fl_Label* o, int X, int Y, int W, int H, Fl_Align align)
66
{
67
  Frame* f = (Frame*)(o->value);
68
  if (window_deleted(f)) return;
69
  fl_draw_box(FL_THIN_DOWN_BOX, X, Y, MENU_ICON_W, MENU_ICON_H, FL_GRAY);
70
  for (Frame* c = Frame::first; c; c = c->next) {
71
    if (c->state() != UNMAPPED && (c==f || c->is_transient_for(f))) {
72
      int x = ((c->x()-Root->x())*SCREEN_W+Root->w()/2)/Root->w();
73
      int w = (c->w()*SCREEN_W+Root->w()-1)/Root->w();
74
      if (w > SCREEN_W) w = SCREEN_W;
75
      if (w < 3) w = 3;
76
      if (x+w > SCREEN_W) x = SCREEN_W-w;
77
      if (x < 0) x = 0;
78
      int y = ((c->y()-Root->y())*SCREEN_H+Root->h()/2)/Root->h();
79
      int h = (c->h()*SCREEN_H+Root->h()-1)/Root->h();
80
      if (h > SCREEN_H) h = SCREEN_H;
81
      if (h < 3) h = 3;
82
      if (y+h > SCREEN_H) y = SCREEN_H-h;
83
      if (y < 0) y = 0;
84
      fl_color(FL_BLACK);
85
      if (c->state() == ICONIC)
86
	fl_rect(X+x+SCREEN_DX, Y+y+SCREEN_DX, w, h);
87
      else
88
	fl_rectf(X+x+SCREEN_DX, Y+y+SCREEN_DX, w, h);
89
    }
90
  }
91
  fl_font(o->font, o->size);
92
  fl_color((Fl_Color)o->color);
93
  const char* l = f->label(); if (!l) l = "unnamed";
94
  fl_draw(l, X+MENU_ICON_W+3, Y, W-MENU_ICON_W-3, H, align);
95
}
96
97
static void
98
frame_label_measure(const Fl_Label* o, int& W, int& H)
99
{
100
  Frame* f = (Frame*)(o->value);
101
  if (window_deleted(f)) {W = MENU_ICON_W+3; H = MENU_ICON_H; return;}
102
  const char* l = f->label(); if (!l) l = "unnamed";
103
  fl_font(o->font, o->size);
104
  fl_measure(l, W, H);
105
  W += MENU_ICON_W+3;
106
  if (W > MAX_MENU_WIDTH) W = MAX_MENU_WIDTH;
107
  if (H < MENU_ICON_H) H = MENU_ICON_H;
108
}
109
110
// This labeltype is used for non-frame items so the text can line
111
// up with the icons:
112
113
static void
114
label_draw(const Fl_Label* o, int X, int Y, int W, int H, Fl_Align align)
115
{
116
  fl_font(o->font, o->size);
117
  fl_color((Fl_Color)o->color);
118
  fl_draw(o->value, X+MENU_ICON_W+3, Y, W-MENU_ICON_W-3, H, align);
119
}
120
121
static void
122
label_measure(const Fl_Label* o, int& W, int& H)
123
{
124
  fl_font(o->font, o->size);
125
  fl_measure(o->value, W, H);
126
  W += MENU_ICON_W+3;
127
  if (W > MAX_MENU_WIDTH) W = MAX_MENU_WIDTH;
128
  if (H < MENU_ICON_H) H = MENU_ICON_H;
129
}
130
131
#define FRAME_LABEL FL_FREE_LABELTYPE
132
#define TEXT_LABEL Fl_Labeltype(FL_FREE_LABELTYPE+1)
133
134
////////////////////////////////////////////////////////////////
135
136
static void
137
cancel_cb(Fl_Widget* w, void*)
138
{
139
  w->window()->hide();
140
}
141
142
#if DESKTOPS
143
144
static void
145
desktop_cb(Fl_Widget*, void* v)
146
{
147
  Desktop::current((Desktop*)v);
148
}
149
150
static void
151
delete_desktop_cb(Fl_Widget*, void* v)
152
{
153
  delete (Desktop*)v;
154
}
155
156
#if ASK_FOR_NEW_DESKTOP_NAME
157
158
static Fl_Input* new_desktop_input;
159
160
static void
161
new_desktop_ok_cb(Fl_Widget* w, void*)
162
{
163
  w->window()->hide();
164
  Desktop::current(new Desktop(new_desktop_input->value(), Desktop::available_number()));
165
}
166
167
static void
168
new_desktop_cb(Fl_Widget*, void*)
169
{
170
  if (!new_desktop_input) {
171
    FrameWindow* w = new FrameWindow(190,90);
172
    new_desktop_input = new Fl_Input(10,30,170,25,"New desktop name:");
173
    new_desktop_input->align(FL_ALIGN_TOP_LEFT);
174
    new_desktop_input->labelfont(FL_BOLD);
175
    Fl_Return_Button* b = new Fl_Return_Button(100,60,80,20,"OK");
176
    b->callback(new_desktop_ok_cb);
177
    Fl_Button* b2 = new Fl_Button(10,60,80,20,"Cancel");
178
    b2->callback(cancel_cb);
179
    w->set_non_modal();
180
    w->end();
181
  }
182
  char buf[120];
183
  sprintf(buf, "Desktop %d", Desktop::available_number());
184
  new_desktop_input->value(buf);
185
  new_desktop_input->window()->hotspot(new_desktop_input);
186
  new_desktop_input->window()->show();
187
}
188
189
#else // !ASK_FOR_NEW_DESKTOP_NAME
190
191
static void
192
new_desktop_cb(Fl_Widget*, void*)
193
{
194
  char buf[120];
195
  int i = Desktop::available_number();
196
  sprintf(buf, "Desktop %d", i);
197
  Desktop::current(new Desktop(buf, i));
198
}
199
200
#endif
201
202
#endif
203
////////////////////////////////////////////////////////////////
204
205
static void
206
exit_cb(Fl_Widget*, void*)
207
{
208
  Frame::save_protocol();
209
  exit(0);
210
}
211
212
static void
213
logout_cb(Fl_Widget*, void*)
214
{
215
  static FrameWindow* w;
216
  if (!w) {
217
    w = new FrameWindow(190,90);
218
    Fl_Box* l = new Fl_Box(0, 0, 190, 60, "Really log out?");
219
    l->labelfont(FL_BOLD);
220
    Fl_Return_Button* b = new Fl_Return_Button(100,60,80,20,"OK");
221
    b->callback(exit_cb);
222
    Fl_Button* b2 = new Fl_Button(10,60,80,20,"Cancel");
223
    b2->callback(cancel_cb);
224
    w->set_non_modal();
225
    w->end();
226
  }
227
  w->hotspot(w);
228
  w->show();
229
}
230
231
////////////////////////////////////////////////////////////////
232
233
#include <unistd.h>
234
#include <sys/wait.h>
235
#include <errno.h>
236
237
#if XTERM_MENU_ITEM || WMX_MENU_ITEMS
238
239
static const char* xtermname = "xterm";
240
241
static void
242
spawn_cb(Fl_Widget*, void*n)
243
{
244
  char* name = (char*)n;
245
  // strange code thieved from 9wm to avoid leaving zombies
246
  if (fork() == 0) {
247
    if (fork() == 0) {
248
      close(ConnectionNumber(fl_display));
249
      if (name == xtermname) execlp(name, name, "-ut", 0);
250
      else execl(name, name, 0);
251
      fprintf(stderr, "flwm: can't run %s, %s\n", name, strerror(errno));
252
      XBell(fl_display, 70);
253
      exit(1);
254
    }
255
    exit(0);
256
  }
257
  wait((int *) 0);
258
}
259
260
#endif
261
262
static Fl_Menu_Item other_menu_items[] = {
263
#if XTERM_MENU_ITEM
264
  {"New xterm", 0, spawn_cb, (void*)xtermname, 0, 0, 0, MENU_FONT_SIZE},
265
#endif
266
#if DESKTOPS
267
  {"New desktop", 0, new_desktop_cb, 0, 0, 0, 0, MENU_FONT_SIZE},
268
#endif
269
  {"Logout", 0, logout_cb, 0, 0, 0, 0, MENU_FONT_SIZE},
270
  {0}};
271
#define num_other_items (sizeof(other_menu_items)/sizeof(Fl_Menu_Item))
272
273
// use this to fill in a menu location:
274
static void
275
init(Fl_Menu_Item& m, const char* data)
276
{
277
#ifdef HAVE_STYLES
278
  m.style = 0;
279
#endif
280
  m.label(data);
281
  m.flags = 0;
282
  m.labeltype(FL_NORMAL_LABEL);
283
  m.shortcut(0);
284
  m.labelfont(MENU_FONT_SLOT);
285
  m.labelsize(MENU_FONT_SIZE);
286
  m.labelcolor(FL_BLACK);
287
}
288
289
#if WMX_MENU_ITEMS
290
291
// wmxlist is an array of char* pointers (for efficient sorting purposes), 
292
// which are stored in wmxbuffer (for memory efficiency and to avoid
293
// freeing and fragmentation)
294
static char** wmxlist = NULL;
295
static int wmxlistsize = 0;
296
// wmx commands are read from ~/.wmx,
297
// they are stored null-separated here:
298
static char* wmxbuffer = NULL;
299
static int wmxbufsize = 0;
300
static int num_wmx = 0;
301
static time_t wmx_time = 0;
302
static int wmx_pathlen = 0;
303
304
static int
305
scan_wmx_dir (char *path, int bufindex, int nest)
306
{
307
  DIR* dir = opendir(path);
308
  struct stat st;
309
  int pathlen = strlen (path);
310
  if (dir) {
311
    struct dirent* ent;
312
    while ((ent=readdir(dir))) {
313
    if (ent->d_name[0] == '.')
314
        continue;
315
      strcpy(path+pathlen, ent->d_name);
316
      if (stat(path, &st) < 0) continue;
317
      int len = pathlen+strlen(ent->d_name);
318
	// worst-case alloc needs
319
      if (bufindex+len+nest+1 > wmxbufsize)
320
	wmxbuffer = (char*)realloc(wmxbuffer, (wmxbufsize+=1024));
321
      for (int i=0; i<nest; i++)
322
	wmxbuffer[bufindex++] = '/'; // extra slash marks menu titles
323
      if (S_ISDIR(st.st_mode) && (st.st_mode & 0555) && nest<MAX_NESTING_DEPTH){
324
	strcpy(wmxbuffer+bufindex, path);
325
        bufindex += len+1;
326
        strcat(path, "/");
327
        bufindex = scan_wmx_dir (path, bufindex, nest+1);
328
	num_wmx++;
329
      } else if (S_ISREG(st.st_mode) && (st.st_mode & 0111)) {
330
	// make sure it exists and is an executable file:
331
	strcpy(wmxbuffer+bufindex, path);
332
	bufindex += len+1;
333
	num_wmx++;
334
      }
335
    }
336
    closedir(dir);
337
  }
338
  return bufindex;
339
}
340
341
// comparison for qsort
342
//	We keep submenus together by noting that they're proper superstrings
343
static int
344
wmxCompare(const void *A, const void *B)
345
{
346
  char	*pA, *pB;
347
  pA = *(char **)A;
348
  pB = *(char **)B;
349
350
  pA += strspn(pA, "/");
351
  pB += strspn(pB, "/");
352
353
  // caseless compare
354
  while (*pA && *pB) {
355
    if (toupper(*pA) > toupper(*pB))
356
      return(1);
357
    if (toupper(*pA) < toupper(*pB))
358
      return(-1);
359
    pA++;
360
    pB++;
361
  }
362
  if (*pA)
363
    return(1);
364
  if (*pB)
365
    return(-1);
366
  return(0);
367
}
368
369
static void
370
load_wmx()
371
{
372
  const char* home=getenv("HOME"); if (!home) home = ".";
373
  char path[1024];
374
  strcpy(path, home);
375
  if (path[strlen(path)-1] != '/') strcat(path, "/");
376
  strcat(path, ".wmx/");
2 by Tommi Virtanen
Added xutils to Build-Deps. Closes: #87825.
377
  struct stat st;
378
  if (stat(path, &st) < 0) {
4 by Bill Allombert
* Rebuild with current g++/libfltk1.1. Closes: #328174.
379
    strcpy(path, "/var/lib/flwm/wmx/");
2 by Tommi Virtanen
Added xutils to Build-Deps. Closes: #87825.
380
    if (stat(path, &st) < 0) return;
381
  }
1 by Tommi Virtanen
Import upstream version 1.00
382
  if (st.st_mtime == wmx_time) return;
383
  wmx_time = st.st_mtime;
384
  num_wmx = 0;
385
  wmx_pathlen = strlen(path);
386
  scan_wmx_dir(path, 0, 0);
387
388
  // Build wmxlist
389
  if (num_wmx > wmxlistsize) {
390
    if (wmxlist)
391
      delete [] wmxlist;
392
    wmxlist = new char *[num_wmx];
393
    wmxlistsize = num_wmx;
394
  }
395
  for (int i=0; i<num_wmx; i++) {
396
    char* cmd = wmxbuffer;
397
398
    for (int j = 0; j < num_wmx; j++) {
399
      wmxlist[j] = cmd;
400
      cmd += strlen(cmd)+1;
401
    }
402
  }
403
404
  qsort(wmxlist, num_wmx, sizeof(char *), wmxCompare);
405
}
406
407
#endif
408
409
////////////////////////////////////////////////////////////////
410
411
int exit_flag; // set by the -x switch
412
413
static int is_active_frame(Frame* c) {
414
  for (Frame* a = Frame::activeFrame(); a; a = a->transient_for())
415
    if (a == c) return 1;
416
  return 0;
417
}
418
419
void
420
ShowTabMenu(int tab)
421
{
422
423
  static char beenhere;
424
  if (!beenhere) {
425
    beenhere = 1;
426
    Fl::set_labeltype(FRAME_LABEL, frame_label_draw, frame_label_measure);
427
    Fl::set_labeltype(TEXT_LABEL, label_draw, label_measure);
428
    if (exit_flag) {
429
      Fl_Menu_Item* m = other_menu_items+num_other_items-2;
430
      m->label("Exit");
431
      m->callback(exit_cb);
432
    }
433
  }
434
435
  static Fl_Menu_Item* menu = 0;
436
  static int arraysize = 0;
437
438
#if DESKTOPS
439
  int one_desktop = !Desktop::first->next;
440
#endif
441
442
  // count up how many items are on the menu:
443
444
  int n = num_other_items;
445
#if WMX_MENU_ITEMS
446
  load_wmx();
447
  if (num_wmx) {
448
    n -= 1; // delete "new xterm"
449
    // add wmx items
450
    int	level = 0;
451
    for (int i=0; i<num_wmx; i++) {
452
      int nextlev = (i==num_wmx-1)?0:strspn(wmxlist[i+1], "/")-1;
453
      if (nextlev < level) {
454
	n += level-nextlev;
455
	level = nextlev;
456
      } else if (nextlev > level)
457
	level++;
458
      n++;
459
    }
460
  }
461
#endif
462
463
#if DESKTOPS
464
  // count number of items per desktop in these variables:
465
  int numsticky = 0;
466
  Desktop* d;
467
  for (d = Desktop::first; d; d = d->next) d->junk = 0;
468
#endif
469
470
  // every frame contributes 1 item:
471
  Frame* c;
472
  for (c = Frame::first; c; c = c->next) {
473
    if (c->state() == UNMAPPED || c->transient_for()) continue;
474
#if DESKTOPS
475
    if (!c->desktop()) {
476
      numsticky++;
477
    } else {
478
      c->desktop()->junk++;
479
    }
480
#endif
481
    n++;
482
  }
483
484
#if DESKTOPS
485
  if (!one_desktop) {
486
    // add the sticky "desktop":
487
    n += 2; if (!numsticky) n++;
488
    if (Desktop::current()) {
489
      n += numsticky;
490
      Desktop::current()->junk += numsticky;
491
    }
492
    // every desktop contributes menu title, null terminator,
493
    // and possible delete:
494
    for (d = Desktop::first; d; d = d->next) {
495
      n += 2; if (!d->junk) n++;
496
    }
497
  }
498
#endif
499
500
  if (n > arraysize) {
501
    delete[] menu;
502
    menu = new Fl_Menu_Item[arraysize = n];
503
  }
504
505
  // build the menu:
506
  n = 0;
507
  const Fl_Menu_Item* preset = 0;
508
  const Fl_Menu_Item* first_on_desk = 0;
509
#if DESKTOPS
510
  if (one_desktop) {
511
#endif
512
    for (c = Frame::first; c; c = c->next) {
513
      if (c->state() == UNMAPPED || c->transient_for()) continue;
514
      init(menu[n],(char*)c);
515
      menu[n].labeltype(FRAME_LABEL);
516
      menu[n].callback(frame_callback, c);
517
      if (is_active_frame(c)) preset = menu+n;
518
      n++;
519
    }
520
    if (n > 0) first_on_desk = menu;
521
#if DESKTOPS
522
  } else for (d = Desktop::first; ; d = d->next) {
523
    // this loop adds the "sticky" desktop last, when d==0
524
    if (d == Desktop::current()) preset = menu+n;
525
    init(menu[n], d ? d->name() : "Sticky");
526
    menu[n].callback(desktop_cb, d);
527
    menu[n].flags = FL_SUBMENU;
528
    n++;
529
    if (d && !d->junk) {
530
      init(menu[n],"delete this desktop");
531
      menu[n].callback(delete_desktop_cb, d);
532
      n++;
533
    } else if (!d && !numsticky) {
534
      init(menu[n],"(empty)");
535
      menu[n].callback_ = 0;
536
      menu[n].deactivate();
537
      n++;
538
    } else {
539
      if (d == Desktop::current()) first_on_desk = menu+n;
540
      for (c = Frame::first; c; c = c->next) {
541
	if (c->state() == UNMAPPED || c->transient_for()) continue;
542
	if (c->desktop() == d || !c->desktop() && d == Desktop::current()) {
543
	  init(menu[n],(char*)c);
544
	  menu[n].labeltype(FRAME_LABEL);
545
	  menu[n].callback(d == Desktop::current() ?
546
			   frame_callback : move_frame_callback, c);
547
	  if (d == Desktop::current() && is_active_frame(c)) preset = menu+n;
548
	  n++;
549
	}
550
      }
551
    }
552
    menu[n].label(0); n++; // terminator for submenu
553
    if (!d) break;
554
  }
555
#endif
556
557
  // For ALT+Tab, move the selection forward or backward:
558
  if (tab > 0 && first_on_desk) {
559
    if (!preset)
560
      preset = first_on_desk;
561
    else {
562
      preset++;
563
      if (!preset->label() || preset->callback_ != frame_callback)
564
	preset = first_on_desk;
565
    }
566
  } else if (tab < 0 && first_on_desk) {
567
    if (preset && preset != first_on_desk)
568
      preset--;
569
    else {
570
      // go to end of menu
571
      preset = first_on_desk;
572
      while (preset[1].label() && preset[1].callback_ == frame_callback)
573
	preset++;
574
    }
575
  }
576
577
#if WMX_MENU_ITEMS
578
  // put wmx-style commands above that:
579
  if (num_wmx > 0) {
580
    char* cmd;
581
    int pathlen[MAX_NESTING_DEPTH];
582
    int level = 0;
583
    pathlen[0] = wmx_pathlen;
584
    for (int i = 0; i < num_wmx; i++) {
585
      cmd = wmxlist[i];
586
      cmd += strspn(cmd, "/")-1;
587
      init(menu[n], cmd+pathlen[level]);
588
#if DESKTOPS
589
      if (one_desktop)
590
#endif
591
	if (!level)
592
	  menu[n].labeltype(TEXT_LABEL);
593
594
      int	nextlev = (i==num_wmx-1)?0:strspn(wmxlist[i+1], "/")-1;
595
      if (nextlev < level) {
596
	menu[n].callback(spawn_cb, cmd);
597
	// Close 'em off
598
	for (; level>nextlev; level--)
599
	  init(menu[++n], 0);
600
      } else if (nextlev > level) {
601
	// This should be made a submenu
602
	pathlen[++level] = strlen(cmd)+1; // extra for next trailing /
603
	menu[n].flags = FL_SUBMENU;
604
	menu[n].callback((Fl_Callback*)0);
605
      } else {
606
	menu[n].callback(spawn_cb, cmd);
607
      }
608
      n++;
609
    }
610
  }
611
612
  // put the fixed menu items at the bottom:
613
#if XTERM_MENU_ITEM
614
  if (num_wmx) // if wmx commands, delete the built-in xterm item:
615
    memcpy(menu+n, other_menu_items+1, sizeof(other_menu_items)-sizeof(Fl_Menu_Item));
616
  else
617
#endif
618
#endif
619
    memcpy(menu+n, other_menu_items, sizeof(other_menu_items));
620
#if DESKTOPS
621
  if (one_desktop)
622
#endif
623
    // fix the menus items so they are indented to align with window names:
624
    while (menu[n].label()) menu[n++].labeltype(TEXT_LABEL);
625
626
  const Fl_Menu_Item* picked =
627
    menu->popup(Fl::event_x(), Fl::event_y(), 0, preset);
628
  if (picked && picked->callback()) picked->do_callback(0);
629
}
630
631
void ShowMenu() {ShowTabMenu(0);}