-
Notifications
You must be signed in to change notification settings - Fork 9
/
Frame.C
2078 lines (1891 loc) · 61.9 KB
/
Frame.C
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
925
926
927
928
929
930
931
932
933
934
935
936
937
938
939
940
941
942
943
944
945
946
947
948
949
950
951
952
953
954
955
956
957
958
959
960
961
962
963
964
965
966
967
968
969
970
971
972
973
974
975
976
977
978
979
980
981
982
983
984
985
986
987
988
989
990
991
992
993
994
995
996
997
998
999
1000
// Frame.C
//
// CHANGES
// 20090927: Some modifications by Michael A. Losh, tagged "ML" below,
// or bracketed by TOPSIDE manifest constant
// 20160402: some tests clarified (more parentheses ...); dentonlt
// 20190303: Added: DoNotWarp variable to overide mouse cursor warping if desired.
// Added: Double click titlebar to toggle window max size and normal size.
// Compiler Warnings: Moved break statements to their own lines. Rich
#define FL_INTERNALS 1
#include "config.h"
#include "Frame.H"
#include "Desktop.H"
#include <string.h>
#include <stdio.h>
#include <stdlib.h>
#include <FL/fl_draw.H>
#ifndef HAVE_XFT
#include "Rotated.H" // text rotation code; not supported by non-Xft FLTK 1.3.x
#endif
// From Hotkeys.C
extern void ToggleWinMax(void);
static Atom wm_state = 0;
static Atom wm_change_state;
static Atom wm_protocols;
static Atom wm_delete_window;
static Atom wm_take_focus;
static Atom wm_save_yourself;
static Atom wm_colormap_windows;
static Atom _motif_wm_hints;
static Atom kwm_win_decoration;
#if DESKTOPS
static Atom kwm_win_desktop;
static Atom kwm_win_sticky;
#endif
//static Atom wm_client_leader;
static Atom _wm_quit_app;
// these are set by initialize in main.C:
Atom _win_hints;
Atom _win_state;
#if DESKTOPS
extern Atom _win_workspace;
#endif
#ifdef SHOW_CLOCK
extern char clock_buf[];
extern int clock_alarm_on;
#endif
// Set by main in main.C:
extern int DoNotWarp; // Used to override mouse pointer warping if environmental variable NOWARP exists.
static const int XEventMask =
ExposureMask|StructureNotifyMask
|KeyPressMask|KeyReleaseMask|KeymapStateMask|FocusChangeMask
|ButtonPressMask|ButtonReleaseMask
|EnterWindowMask|LeaveWindowMask
|PointerMotionMask|SubstructureRedirectMask|SubstructureNotifyMask;
extern Fl_Window* Root;
Frame* Frame::active_;
Frame* Frame::first;
static inline int max(int a, int b) {return a > b ? a : b;}
static inline int min(int a, int b) {return a < b ? a : b;}
////////////////////////////////////////////////////////////////
// The constructor is by far the most complex part, as it collects
// all the scattered pieces of information about the window that
// X has and uses them to initialize the structure, position the
// window, and then finally create it.
int dont_set_event_mask = 0; // used by FrameWindow
// "existing" is a pointer to an XWindowAttributes structure that is
// passed for an already-existing window when the window manager is
// starting up. If so we don't want to alter the state, size, or
// position. If null than this is a MapRequest of a new window.
Frame::Frame(XWindow window, XWindowAttributes* existing) :
Fl_Window(0,0),
window_(window),
state_flags_(0),
flags_(0),
transient_for_xid(None),
transient_for_(0),
revert_to(active_),
colormapWinCount(0),
close_button(BUTTON_LEFT,BUTTON_TOP,BUTTON_W,BUTTON_H,"X"),
iconize_button(BUTTON_LEFT,BUTTON_TOP,BUTTON_W,BUTTON_H,"i"),
max_h_button(BUTTON_LEFT,BUTTON_TOP+3*BUTTON_H,BUTTON_W,BUTTON_H,"h"),
max_w_button(BUTTON_LEFT,BUTTON_TOP+BUTTON_H,BUTTON_W,BUTTON_H,"w"),
min_w_button(BUTTON_LEFT,BUTTON_TOP+2*BUTTON_H,BUTTON_W,BUTTON_H,"W")
{
close_button.callback(button_cb_static);
iconize_button.callback(button_cb_static);
max_h_button.type(FL_TOGGLE_BUTTON);
max_h_button.callback(button_cb_static);
max_w_button.type(FL_TOGGLE_BUTTON);
max_w_button.callback(button_cb_static);
min_w_button.type(FL_TOGGLE_BUTTON);
min_w_button.callback(button_cb_static);
end();
box(FL_NO_BOX); // relies on background color erasing interior
// ML -------------------------------
#ifdef ML_TITLEBAR_COLOR
char * titlebar_color_str = getenv("FLWM_TITLEBAR_COLOR");
int r = 0x90, g = 0x90, b = 0x90;
int fields = 0;
if (titlebar_color_str) {
fields = sscanf(titlebar_color_str, "%02X:%02X:%02X", &r, &g, &b);
}
if (titlebar_color_str && (fields == 3)) {
Fl::set_color(FL_BACKGROUND2_COLOR, r, g, b);
}
else {
Fl::set_color(FL_BACKGROUND2_COLOR, 0x90, 0x90, 0x90);
}
#else
Fl::set_color(FL_BACKGROUND2_COLOR, 0x90, 0x90, 0x90);
#endif
// --------------------------------ML
labelcolor(FL_FOREGROUND_COLOR);
next = first;
first = this;
// do this asap so we don't miss any events...
if (!dont_set_event_mask)
XSelectInput(fl_display, window_,
ColormapChangeMask | PropertyChangeMask | FocusChangeMask
);
if (!wm_state) {
// allocate all the atoms if this is the first time
wm_state = XInternAtom(fl_display, "WM_STATE", 0);
wm_change_state = XInternAtom(fl_display, "WM_CHANGE_STATE", 0);
wm_protocols = XInternAtom(fl_display, "WM_PROTOCOLS", 0);
wm_delete_window = XInternAtom(fl_display, "WM_DELETE_WINDOW", 0);
wm_take_focus = XInternAtom(fl_display, "WM_TAKE_FOCUS", 0);
wm_save_yourself = XInternAtom(fl_display, "WM_SAVE_YOURSELF", 0);
wm_colormap_windows = XInternAtom(fl_display, "WM_COLORMAP_WINDOWS",0);
_motif_wm_hints = XInternAtom(fl_display, "_MOTIF_WM_HINTS", 0);
kwm_win_decoration = XInternAtom(fl_display, "KWM_WIN_DECORATION", 0);
#if DESKTOPS
kwm_win_desktop = XInternAtom(fl_display, "KWM_WIN_DESKTOP", 0);
kwm_win_sticky = XInternAtom(fl_display, "KWM_WIN_STICKY", 0);
#endif
// wm_client_leader = XInternAtom(fl_display, "WM_CLIENT_LEADER", 0);
_wm_quit_app = XInternAtom(fl_display, "_WM_QUIT_APP", 0);
}
#ifdef TOPSIDE
label_x = label_h = label_w = 0;
#else
label_y = label_h = label_w = 0;
#endif
getLabel();
// getIconLabel();
{XWindowAttributes attr;
if (existing) attr = *existing;
else {
// put in some legal values in case XGetWindowAttributes fails:
attr.x = attr.y = 0; attr.width = attr.height = 100;
attr.colormap = fl_colormap;
attr.border_width = 0;
XGetWindowAttributes(fl_display, window, &attr);
}
left = top = dwidth = dheight = 0; // pretend border is zero-width for now
app_border_width = attr.border_width;
x(attr.x+app_border_width); restore_x = x();
y(attr.y+app_border_width); restore_y = y();
w(attr.width); restore_w = w();
h(attr.height); restore_h = h();
colormap = attr.colormap;}
getColormaps();
//group_ = 0;
{XWMHints* hints = XGetWMHints(fl_display, window_);
if (hints) {
if ((hints->flags & InputHint) && !hints->input) set_flag(NO_FOCUS);
//if (hints && hints->flags&WindowGroupHint) group_ = hints->window_group;
}
switch (getIntProperty(wm_state, wm_state, 0)) {
case NormalState:
state_ = NORMAL; break;
case IconicState:
state_ = ICONIC; break;
// X also defines obsolete values ZoomState and InactiveState
default:
if ((hints && (hints->flags&StateHint)) && (hints->initial_state==IconicState))
state_ = ICONIC;
else
state_ = NORMAL;
}
if (hints) XFree(hints);}
// Maya sets this, seems to mean the same as group:
// if (!group_) group_ = getIntProperty(wm_client_leader, XA_WINDOW);
XGetTransientForHint(fl_display, window_, &transient_for_xid);
getProtocols();
getMotifHints();
// get Gnome hints:
int p = getIntProperty(_win_hints, XA_CARDINAL);
if (p&1) set_flag(NO_FOCUS); // WIN_HINTS_SKIP_FOCUS
// if (p&2) // WIN_HINTS_SKIP_WINLIST
// if (p&4) // WIN_HINTS_SKIP_TASKBAR
// if (p&8) ... // WIN_HINTS_GROUP_TRANSIENT
if (p&16) set_flag(CLICK_TO_FOCUS); // WIN_HINTS_FOCUS_ON_CLICK
// get KDE hints:
p = getIntProperty(kwm_win_decoration, kwm_win_decoration, 1);
if (!(p&3)) set_flag(NO_BORDER);
else if (p & 2) set_flag(THIN_BORDER);
if (p & 256) set_flag(NO_FOCUS);
fix_transient_for();
if (transient_for()) {
if (state_ == NORMAL) state_ = transient_for()->state_;
#if DESKTOPS
desktop_ = transient_for()->desktop_;
#endif
}
#if DESKTOPS
// see if anybody thinks window is "sticky:"
else if ((getIntProperty(_win_state, XA_CARDINAL) & 1) // WIN_STATE_STICKY
|| getIntProperty(kwm_win_sticky, kwm_win_sticky)) {
desktop_ = 0;
} else {
// get the desktop from either Gnome or KDE (Gnome takes precedence):
p = getIntProperty(_win_workspace, XA_CARDINAL, -1) + 1; // Gnome desktop
if (p <= 0) p = getIntProperty(kwm_win_desktop, kwm_win_desktop);
if ((p > 0) && (p < 25))
desktop_ = Desktop::number(p, 1);
else
desktop_ = Desktop::current();
}
if (desktop_ && (desktop_ != Desktop::current()))
if (state_ == NORMAL) state_ = OTHER_DESKTOP;
#endif
int autoplace = getSizes();
// some Motif programs assumme this will force the size to conform :-(
if (w() < min_w || h() < min_h) {
if (w() < min_w) w(min_w);
if (h() < min_h) h(min_h);
XResizeWindow(fl_display, window_, w(), h());
}
// try to detect programs that think "transient_for" means "no border":
if (transient_for_xid && !label() && !flag(NO_BORDER)) {
set_flag(THIN_BORDER);
}
updateBorder();
show_hide_buttons();
if (autoplace && !existing && !(transient_for() && (x() || y()))) {
place_window();
}
// move window so contents and border are visible:
x(force_x_onscreen(x(), w()));
y(force_y_onscreen(y(), h()));
// guess some values for the "restore" fields, if already maximized:
if (max_w_button.value()) {
restore_w = min_w + ((w()-dwidth-min_w)/2/inc_w) * inc_w;
restore_x = x()+left + (w()-dwidth-restore_w)/2;
}
if (max_h_button.value()) {
restore_h = min_h + ((h()-dheight-min_h)/2/inc_h) * inc_h;
restore_y = y()+top + (h()-dheight-restore_h)/2;
}
const int mask = CWBorderPixel | CWColormap | CWEventMask | CWBitGravity
| CWBackPixel | CWOverrideRedirect;
XSetWindowAttributes sattr;
sattr.event_mask = XEventMask;
sattr.colormap = fl_colormap;
sattr.border_pixel = fl_xpixel(FL_GRAY0);
sattr.bit_gravity = NorthWestGravity;
sattr.override_redirect = 1;
sattr.background_pixel = fl_xpixel(FL_GRAY);
Fl_X::set_xid(this, XCreateWindow(fl_display,
RootWindow(fl_display,fl_screen),
x(), y(), w(), h(), 0,
fl_visual->depth,
InputOutput,
fl_visual->visual,
mask, &sattr));
setStateProperty();
if (!dont_set_event_mask) XAddToSaveSet(fl_display, window_);
if (existing) set_state_flag(IGNORE_UNMAP);
XReparentWindow(fl_display, window_, fl_xid(this), left, top);
XSetWindowBorderWidth(fl_display, window_, 0);
if (state_ == NORMAL) XMapWindow(fl_display, window_);
sendConfigureNotify(); // many apps expect this even if window size unchanged
#if CLICK_RAISES || CLICK_TO_TYPE
if (!dont_set_event_mask)
XGrabButton(fl_display, AnyButton, AnyModifier, window, False,
ButtonPressMask, GrabModeSync, GrabModeAsync, None, None);
#endif
if (state_ == NORMAL) {
XMapWindow(fl_display, fl_xid(this));
if (!existing) activate_if_transient();
}
set_visible();
}
#if SMART_PLACEMENT
// Helper functions for "smart" window placement.
int overlap1(int p1, int l1, int p2, int l2) {
int ret = 0;
if(p1 <= p2 && p2 <= p1 + l1) {
ret = min(p1 + l1 - p2, l2);
} else if (p2 <= p1 && p1 <= p2 + l2) {
ret = min(p2 + l2 - p1, l1);
}
return ret;
}
int overlap(int x1, int y1, int w1, int h1, int x2, int y2, int w2, int h2) {
return (overlap1(x1, w1, x2, w2) * overlap1(y1, h1, y2, h2));
}
// Compute the overlap with existing windows.
// For normal windows the overlapping area is taken into account plus a
// constant value for every overlapping window.
// The active window counts twice.
// For iconic windows half the overlapping area is taken into account.
int getOverlap(int x, int y, int w, int h, Frame *first, Frame *self) {
int ret = 0;
short state;
for (Frame* f = first; f; f = f->next) {
if (f != self) {
state = f->state();
if (state == NORMAL || state == ICONIC) {
int o = overlap(x, y, w, h, f->x(), f->y(), f->w(), f->h());
if (state == NORMAL) {
ret = ret + o + (o>0?40000:0) + (o * f->active());
} else if (state == ICONIC) {
ret = ret + o/2;
}
}
}
}
return ret;
}
// autoplacement (brute force version for now)
void Frame::place_window() {
int min_overlap = -1;
int tmp_x, tmp_y, tmp_o;
int best_x = 0;
int best_y = 0;
int _w = w();
int _h = h();
int max_x = Root->x() + Root->w();
int max_y = Root->y() + Root->h();
Frame *f1 = Frame::first;
for(int i=0;; i++) {
if (i==0) {
tmp_x = 0;
} else if (i==1) {
tmp_x = max_x - _w;
} else {
if (f1 == this) {
f1 = f1->next;
}
if (!f1) {
break;
}
tmp_x = f1->x() + f1->w();
f1 = f1->next;
}
Frame *f2 = Frame::first;
for(int j=0;; j++) {
if (j==0) {
tmp_y = 0;
} else if (j==1) {
tmp_y = max_y - _h;
} else {
if (f2 == this) {
f2 = f2->next;
}
if (!f2) {
break;
}
tmp_y = f2->y() + f2->h();
f2 = f2->next;
}
if ((tmp_x + _w <= max_x) && (tmp_y + _h <= max_y)) {
tmp_o = getOverlap(tmp_x, tmp_y, _w, _h, Frame::first, this);
if(tmp_o < min_overlap || min_overlap < 0) {
best_x = tmp_x;
best_y = tmp_y;
min_overlap = tmp_o;
if (min_overlap == 0) {
break;
}
}
}
}
if (min_overlap == 0) {
break;
}
}
x(best_x);
y(best_y);
}
#else
// autoplacement (stupid version for now)
void Frame::place_window() {
x(Root->x()+(Root->w()-w())/2);
y(Root->y()+(Root->h()-h())/2);
// move it until it does not hide any existing windows:
const int delta = TITLE_WIDTH+LEFT;
for (Frame* f = next; f; f = f->next) {
if (f->x()+delta > x() && f->y()+delta > y() &&
f->x()+f->w()-delta < x()+w() && f->y()+f->h()-delta < y()+h()) {
x(max(x(),f->x()+delta));
y(max(y(),f->y()+delta));
f = this;
}
}
}
#endif
// modify the passed X & W to a legal horizontal window position
int Frame::force_x_onscreen(int X, int W) {
// force all except the black border on-screen:
X = min(X, Root->x()+Root->w()+1-W);
X = max(X, Root->x()-1);
// force the contents on-screen:
X = min(X, Root->x()+Root->w()-W+dwidth-left);
if (W-dwidth > Root->w() || h()-dheight > Root->h())
// windows bigger than the screen need title bar so they can move
X = max(X, Root->x()-LEFT);
else
X = max(X, Root->x()-left);
return X;
}
// modify the passed Y & H to a legal vertical window position:
int Frame::force_y_onscreen(int Y, int H) {
// force border (except black edge) to be on-screen:
Y = min(Y, Root->y()+Root->h()+1-H);
Y = max(Y, Root->y()-1);
// force contents to be on-screen:
Y = min(Y, Root->y()+Root->h()-H+dheight-top);
Y = max(Y, Root->y()-top);
return Y;
}
////////////////////////////////////////////////////////////////
// destructor
// The destructor is called on DestroyNotify, so I don't have to do anything
// to the contained window, which is already been destroyed.
Frame::~Frame() {
// It is possible for the frame to be destroyed while the menu is
// popped-up, and the menu will still contain a pointer to it. To
// fix this the menu checks the state_ location for a legal and
// non-withdrawn state value before doing anything. This should
// be reliable unless something reallocates the memory and writes
// a legal state value to this location:
state_ = UNMAPPED;
// remove any pointers to this:
Frame** cp; for (cp = &first; *cp; cp = &((*cp)->next))
if (*cp == this) {*cp = next; break;}
for (Frame* f = first; f; f = f->next) {
if (f->transient_for_ == this) f->transient_for_ = transient_for_;
if (f->revert_to == this) f->revert_to = revert_to;
}
throw_focus(1);
if (colormapWinCount) {
XFree((char *)colormapWindows);
delete[] window_Colormaps;
}
//if (iconlabel()) XFree((char*)iconlabel());
if (label()) XFree((char*)label());
}
////////////////////////////////////////////////////////////////
void Frame::getLabel(int del) {
char* old = (char*)label();
char* nu = del ? 0 : (char*)getProperty(XA_WM_NAME);
if (nu) {
// since many window managers print a default label when none is
// given, many programs send spaces to make a blank label. Detect
// this and make it really be blank:
char* c = nu; while (*c == ' ') c++;
if (!*c) {XFree(nu); nu = 0;}
}
if (old) {
if (nu && !strcmp(old,nu)) {XFree(nu); return;}
XFree(old);
} else {
if (!nu) return;
}
#ifdef TOPSIDE
Fl_Widget::label(nu);
if (nu) {
fl_font(TITLE_FONT_SLOT, TITLE_FONT_SIZE);
label_h = int(fl_size())+6;
} else
label_h = 0;
if (shown())// && label_w > 3 && top > 3)
XClearArea(fl_display, fl_xid(this), label_x, BUTTON_TOP, label_w, label_h, 1);
#else
Fl_Widget::label(nu);
if (nu) {
fl_font(TITLE_FONT_SLOT, TITLE_FONT_SIZE);
label_w = int(fl_width(nu))+6;
} else
label_w = 0;
if (shown() && label_h > 3 && left > 3)
XClearArea(fl_display, fl_xid(this), 1, label_y+3, left-1, label_h-3, 1);
#endif
}
////////////////////////////////////////////////////////////////
int Frame::getGnomeState(int &) {
// values for _WIN_STATE property are from Gnome WM compliance docs:
#define WIN_STATE_STICKY (1<<0) /*everyone knows sticky*/
#define WIN_STATE_MINIMIZED (1<<1) /*Reserved - definition is unclear*/
#define WIN_STATE_MAXIMIZED_VERT (1<<2) /*window in maximized V state*/
#define WIN_STATE_MAXIMIZED_HORIZ (1<<3) /*window in maximized H state*/
#define WIN_STATE_HIDDEN (1<<4) /*not on taskbar but window visible*/
#define WIN_STATE_SHADED (1<<5) /*shaded (MacOS / Afterstep style)*/
#define WIN_STATE_HID_WORKSPACE (1<<6) /*not on current desktop*/
#define WIN_STATE_HID_TRANSIENT (1<<7) /*owner of transient is hidden*/
#define WIN_STATE_FIXED_POSITION (1<<8) /*window is fixed in position even*/
#define WIN_STATE_ARRANGE_IGNORE (1<<9) /*ignore for auto arranging*/
// nyi
return 0;
}
////////////////////////////////////////////////////////////////
// Read the sizeHints, and try to remove the vast number of mistakes
// that some applications seem to do writing them.
// Returns true if autoplace should be done.
int Frame::getSizes() {
XSizeHints sizeHints;
long junk;
if (!XGetWMNormalHints(fl_display, window_, &sizeHints, &junk))
sizeHints.flags = 0;
// get the increment, use 1 if none or illegal values:
if (sizeHints.flags & PResizeInc) {
inc_w = sizeHints.width_inc; if (inc_w < 1) inc_w = 1;
inc_h = sizeHints.height_inc; if (inc_h < 1) inc_h = 1;
} else {
inc_w = inc_h = 1;
}
// get the current size of the window:
int W = w()-dwidth;
int H = h()-dheight;
// I try a lot of places to get a good minimum size value. Lots of
// programs set illegal or junk values, so getting this correct is
// difficult:
min_w = W;
min_h = H;
// guess a value for minimum size in case it is not set anywhere:
min_w = min(min_w, 4*BUTTON_H);
min_w = ((min_w+inc_w-1)/inc_w) * inc_w;
min_h = min(min_h, 4*BUTTON_H);
min_h = ((min_h+inc_h-1)/inc_h) * inc_h;
// some programs put the minimum size here:
if (sizeHints.flags & PBaseSize) {
junk = sizeHints.base_width; if (junk > 0) min_w = junk;
junk = sizeHints.base_height; if (junk > 0) min_h = junk;
}
// finally, try the actual place the minimum size should be:
if (sizeHints.flags & PMinSize) {
junk = sizeHints.min_width; if (junk > 0) min_w = junk;
junk = sizeHints.min_height; if (junk > 0) min_h = junk;
}
max_w = max_h = 0; // default maximum size is "infinity"
if (sizeHints.flags & PMaxSize) {
// Though not defined by ICCCM standard, I interpret any maximum
// size that is less than the minimum to mean "infinity". This
// allows the maximum to be set in one direction only:
junk = sizeHints.max_width;
if (junk >= min_w && junk <= W) max_w = junk;
junk = sizeHints.max_height;
if (junk >= min_h && junk <= H) max_h = junk;
}
// set the maximize buttons according to current size:
max_w_button.value(W == maximize_width());
max_h_button.value(H == maximize_height());
// Currently only 1x1 aspect works:
if (sizeHints.flags & PAspect
&& sizeHints.min_aspect.x == sizeHints.min_aspect.y)
set_flag(KEEP_ASPECT);
// another fix for gimp, which sets PPosition to 0,0:
if (x() <= 0 && y() <= 0) sizeHints.flags &= ~PPosition;
return !(sizeHints.flags & (USPosition|PPosition));
}
int max_w_switch;
// return width of contents when maximize button pressed:
int Frame::maximize_width() {
int W = max_w_switch; if (!W) W = Root->w();
#ifdef TOPSIDE
return ((W-min_w)/inc_w) * inc_w + min_w;
#else
return ((W-TITLE_WIDTH-min_w)/inc_w) * inc_w + min_w;
#endif
}
int max_h_switch;
int Frame::maximize_height() {
int H = max_h_switch; if (!H) H = Root->h();
#ifdef TOPSIDE
return ((H-TITLE_HEIGHT-min_h)/inc_h) * inc_h + min_h;
#else
return ((H-min_h)/inc_h) * inc_h + min_h;
#endif
}
////////////////////////////////////////////////////////////////
void Frame::getProtocols() {
int n; Atom* p = (Atom*)getProperty(wm_protocols, XA_ATOM, &n);
if (p) {
clear_flag(DELETE_WINDOW_PROTOCOL|TAKE_FOCUS_PROTOCOL|QUIT_PROTOCOL);
for (int i = 0; i < n; ++i) {
if (p[i] == wm_delete_window) {
set_flag(DELETE_WINDOW_PROTOCOL);
} else if (p[i] == wm_take_focus) {
set_flag(TAKE_FOCUS_PROTOCOL);
} else if (p[i] == wm_save_yourself) {
set_flag(SAVE_PROTOCOL);
} else if (p[i] == _wm_quit_app) {
set_flag(QUIT_PROTOCOL);
}
}
}
XFree((char*)p);
}
////////////////////////////////////////////////////////////////
int Frame::getMotifHints() {
long* prop = (long*)getProperty(_motif_wm_hints, _motif_wm_hints);
if (!prop) return 0;
// see /usr/include/X11/Xm/MwmUtil.h for meaning of these bits...
// prop[0] = flags (what props are specified)
// prop[1] = functions (all, resize, move, minimize, maximize, close, quit)
// prop[2] = decorations (all, border, resize, title, menu, minimize,
// maximize)
// prop[3] = input_mode (modeless, primary application modal, system modal,
// full application modal)
// prop[4] = status (tear-off window)
// Fill in the default value for missing fields:
if (!(prop[0]&1)) prop[1] = 1;
if (!(prop[0]&2)) prop[2] = 1;
// The low bit means "turn the marked items off", invert this.
// Transient windows already have size & iconize buttons turned off:
if (prop[1]&1) prop[1] = ~prop[1] & (transient_for_xid ? ~0x58 : -1);
if (prop[2]&1) prop[2] = ~prop[2] & (transient_for_xid ? ~0x60 : -1);
int old_flags = flags();
// see if they are trying to turn off border:
if (!(prop[2])) set_flag(NO_BORDER); else clear_flag(NO_BORDER);
// see if they are trying to turn off title & close box:
if (!(prop[2]&0x18)) set_flag(THIN_BORDER); else clear_flag(THIN_BORDER);
// some Motif programs use this to disable resize :-(
// and some programs change this after the window is shown (*&%$#%)
if (!(prop[1]&2) || !(prop[2]&4))
set_flag(NO_RESIZE); else clear_flag(NO_RESIZE);
// and some use this to disable the Close function. The commented
// out test is it trying to turn off the mwm menu button: it appears
// programs that do that still expect Alt+F4 to close them, so I
// leave the close on then:
if (!(prop[1]&0x20) /*|| !(prop[2]&0x10)*/)
set_flag(NO_CLOSE); else clear_flag(NO_CLOSE);
// see if they set "input hint" to non-zero:
// prop[3] should be nonzero but the only example of this I have
// found is Netscape 3.0 and it sets it to zero...
if (!shown() && (prop[0]&4) /*&& prop[3]*/) set_flag(::MODAL);
// see if it is forcing the iconize button back on. This makes
// transient_for act like group instead...
if ((prop[1]&0x8) || (prop[2]&0x20)) set_flag(ICONIZE);
// Silly 'ol Amazon paint ignores WM_DELETE_WINDOW and expects to
// get the SGI-specific "_WM_QUIT_APP". It indicates this by trying
// to turn off the close box. SIGH!!!
if (flag(QUIT_PROTOCOL) && !(prop[1]&0x20))
clear_flag(DELETE_WINDOW_PROTOCOL);
XFree((char*)prop);
return (flags() ^ old_flags);
}
////////////////////////////////////////////////////////////////
void Frame::getColormaps(void) {
if (colormapWinCount) {
XFree((char *)colormapWindows);
delete[] window_Colormaps;
}
int n;
XWindow* cw = (XWindow*)getProperty(wm_colormap_windows, XA_WINDOW, &n);
if (cw) {
colormapWinCount = n;
colormapWindows = cw;
window_Colormaps = new Colormap[n];
for (int i = 0; i < n; ++i) {
if (cw[i] == window_) {
window_Colormaps[i] = colormap;
} else {
XWindowAttributes attr;
XSelectInput(fl_display, cw[i], ColormapChangeMask);
XGetWindowAttributes(fl_display, cw[i], &attr);
window_Colormaps[i] = attr.colormap;
}
}
} else {
colormapWinCount = 0;
}
}
void Frame::installColormap() const {
for (int i = colormapWinCount; i--;)
if (colormapWindows[i] != window_ && window_Colormaps[i])
XInstallColormap(fl_display, window_Colormaps[i]);
if (colormap)
XInstallColormap(fl_display, colormap);
}
////////////////////////////////////////////////////////////////
// figure out transient_for(), based on the windows that exist, the
// transient_for and group attributes, etc:
void Frame::fix_transient_for() {
Frame* p = 0;
if (transient_for_xid && !flag(ICONIZE)) {
for (Frame* f = first; f; f = f->next) {
if (f != this && f->window_ == transient_for_xid) {p = f; break;}
}
// loops are illegal:
for (Frame* q = p; q; q = q->transient_for_) if (q == this) {p = 0; break;}
}
transient_for_ = p;
}
int Frame::is_transient_for(const Frame* f) const {
if (f)
for (Frame* p = transient_for(); p; p = p->transient_for())
if (p == f) return 1;
return 0;
}
// When a program maps or raises a window, this is called. It guesses
// if this window is in fact a modal window for the currently active
// window and if so transfers the active state to this:
// This also activates new main windows automatically
int Frame::activate_if_transient() {
if (!Fl::pushed())
if (!transient_for() || is_transient_for(active_)) return activate(1);
return 0;
}
////////////////////////////////////////////////////////////////
int Frame::activate(int warp) {
// see if a modal & newer window is up:
for (Frame* c = first; c && c != this; c = c->next)
if (c->flag(::MODAL) && c->transient_for() == this)
if (c->activate(warp)) return 1;
// ignore invisible windows:
if (state() != NORMAL || w() <= dwidth) return 0;
// always put in the colormap:
installColormap();
// move the pointer if desired:
// (note that moving the pointer is pretty much required for point-to-type
// unless you know the pointer is already in the window):
if (!warp || Fl::event_state() & (FL_BUTTON1|FL_BUTTON2|FL_BUTTON3)) {
;
} else if (warp==2 && !DoNotWarp) {
// warp to point at title:
#ifdef TOPSIDE
XWarpPointer(fl_display, None, fl_xid(this), 0,0,0,0, left/2+1,
min(label_x+label_w/2+1, label_h/2));
#else
XWarpPointer(fl_display, None, fl_xid(this), 0,0,0,0, left/2+1,
min(label_y+label_w/2+1,h()/2));
#endif
} else {
warp_pointer();
}
// skip windows that don't want focus:
if (flag(NO_FOCUS)) return 0;
// set this even if we think it already has it, this seems to fix
// bugs with Motif popups:
XSetInputFocus(fl_display, window_, RevertToPointerRoot, fl_event_time);
if (active_ != this) {
if (active_) active_->deactivate();
active_ = this;
//#ifdef ACTIVE_COLOR
// XSetWindowAttributes a;
// a.background_pixel = fl_xpixel(FL_SELECTION_COLOR);
// XChangeWindowAttributes(fl_display, fl_xid(this), CWBackPixel, &a);
// labelcolor(fl_contrast(FL_FOREGROUND_COLOR, FL_BACKGROUND_COLOR)); // ML changed color comparison
// XClearArea(fl_display, fl_xid(this), 2, 2, w()-4, h()-4, 1);
//#else
XSetWindowAttributes a;
a.background_pixel = fl_xpixel(FL_BACKGROUND2_COLOR);
XChangeWindowAttributes(fl_display, fl_xid(this), CWBackPixel, &a);
labelcolor(fl_contrast(FL_FOREGROUND_COLOR, FL_BACKGROUND2_COLOR)); // ML changed color comparison
XClearArea(fl_display, fl_xid(this), 2, 2, w()-4, h()-4, 1);
#ifdef SHOW_CLOCK
redraw();
#endif
//#endif
if (flag(TAKE_FOCUS_PROTOCOL))
sendMessage(wm_protocols, wm_take_focus);
}
return 1;
}
// this private function should only be called by constructor and if
// the window is active():
void Frame::deactivate() {
#ifdef ACTIVE_COLOR
XSetWindowAttributes a;
a.background_pixel = fl_xpixel(FL_GRAY);
XChangeWindowAttributes(fl_display, fl_xid(this), CWBackPixel, &a);
labelcolor(FL_FOREGROUND_COLOR);
XClearArea(fl_display, fl_xid(this), 2, 2, w()-4, h()-4, 1);
#else
#ifdef SHOW_CLOCK
redraw();
#endif
#endif
}
#if CLICK_RAISES || CLICK_TO_TYPE
// After the XGrabButton, the main loop will get the mouse clicks, and
// it will call here when it gets them:
void click_raise(Frame* f) {
f->activate();
#if CLICK_RAISES
if (fl_xevent->xbutton.button <= 1) f->raise();
#endif
XAllowEvents(fl_display, ReplayPointer, CurrentTime);
}
#endif
// get rid of the focus by giving it to somebody, if possible:
void Frame::throw_focus(int destructor) {
if (!active()) return;
if (!destructor) deactivate();
active_ = 0;
if (revert_to && revert_to->activate()) return;
for (Frame* f = first; f; f = f->next)
if (f != this && f->activate()) return;
}
////////////////////////////////////////////////////////////////
// change the state of the window (this is a private function and
// it ignores the transient-for or desktop information):
void Frame::state(short newstate) {
short oldstate = state();
if (newstate == oldstate) return;
state_ = newstate;
switch (newstate) {
case UNMAPPED:
throw_focus();
XUnmapWindow(fl_display, fl_xid(this));
//set_state_flag(IGNORE_UNMAP);
//XUnmapWindow(fl_display, window_);
XRemoveFromSaveSet(fl_display, window_);
break;
case NORMAL:
if (oldstate == UNMAPPED) XAddToSaveSet(fl_display, window_);
if (w() > dwidth) XMapWindow(fl_display, window_);
XMapWindow(fl_display, fl_xid(this));
clear_state_flag(IGNORE_UNMAP);
break;
default:
if (oldstate == UNMAPPED) {
XAddToSaveSet(fl_display, window_);
} else if (oldstate == NORMAL) {
throw_focus();
XUnmapWindow(fl_display, fl_xid(this));
//set_state_flag(IGNORE_UNMAP);
//XUnmapWindow(fl_display, window_);
} else {
return; // don't setStateProperty IconicState multiple times
}
break;
}
setStateProperty();
}
void Frame::setStateProperty() const {
long data[2];
switch (state()) {
case UNMAPPED :
data[0] = WithdrawnState; break;
case NORMAL :
case OTHER_DESKTOP :
data[0] = NormalState; break;
default :
data[0] = IconicState; break;
}
data[1] = (long)None;
XChangeProperty(fl_display, window_, wm_state, wm_state,
32, PropModeReplace, (unsigned char *)data, 2);
}
////////////////////////////////////////////////////////////////
// Public state modifiers that move all transient_for(this) children
// with the frame and do the desktops right:
void Frame::raise() {
Frame* newtop = 0;
Frame* previous = 0;
int previous_state = state_;
Frame** p;
// Find all the transient-for windows and this one, and raise them,
// preserving stacking order:
for (p = &first; *p;) {
Frame* f = *p;
if ((f == this) || (f->is_transient_for(this) && (f->state() != UNMAPPED))) {
*p = f->next; // remove it from list
if (previous) {
XWindowChanges w;
w.sibling = fl_xid(previous);
w.stack_mode = Below;
XConfigureWindow(fl_display, fl_xid(f), CWSibling|CWStackMode, &w);
previous->next = f;
} else {
XRaiseWindow(fl_display, fl_xid(f));
newtop = f;
}
#if DESKTOPS
if (f->desktop_ && f->desktop_ != Desktop::current())
f->state(OTHER_DESKTOP);
else
#endif
f->state(NORMAL);
previous = f;
} else {
p = &((*p)->next);
}
}
previous->next = first;
first = newtop;
#if DESKTOPS
if (!transient_for() && desktop_ && desktop_ != Desktop::current()) {
// for main windows we also must move to the current desktop