1 /**
2  * @file popup.c
3  * @author Joe Wingbermuehle
4  * @date 2004-2006
5  *
6  * @brief Functions for displaying popup windows.
7  *
8  */
9 
10 #include "jwm.h"
11 #include "popup.h"
12 #include "main.h"
13 #include "color.h"
14 #include "font.h"
15 #include "screen.h"
16 #include "cursor.h"
17 #include "timing.h"
18 #include "misc.h"
19 #include "settings.h"
20 #include "event.h"
21 #include "hint.h"
22 
23 typedef struct PopupType {
24    int x, y;   /* The coordinates of the upper-left corner of the popup. */
25    int mx, my; /* The mouse position when the popup was created. */
26    Window mw;
27    int width, height;
28    char *text;    /* The raw popup text. */
29    char *lines;   /* Popup text split into NUL-separated lines. */
30    int lineCount; /* The number of lines. */
31    Window window;
32    Pixmap pmap;
33 } PopupType;
34 
35 static PopupType popup;
36 
37 static void MeasurePopupText();
38 static void SignalPopup(const TimeType *now, int x, int y, Window w,
39                         void *data);
40 
41 /** Startup popups. */
StartupPopup(void)42 void StartupPopup(void)
43 {
44    popup.text = NULL;
45    popup.window = None;
46    RegisterCallback(100, SignalPopup, NULL);
47 }
48 
49 /** Shutdown popups. */
ShutdownPopup(void)50 void ShutdownPopup(void)
51 {
52    UnregisterCallback(SignalPopup, NULL);
53    if(popup.text) {
54       Release(popup.text);
55       Release(popup.lines);
56       popup.text = NULL;
57    }
58    if(popup.window != None) {
59       JXDestroyWindow(display, popup.window);
60       JXFreePixmap(display, popup.pmap);
61       popup.window = None;
62    }
63 }
64 
65 /** Calculate dimensions of a popup window given the popup text. */
MeasurePopupText()66 void MeasurePopupText()
67 {
68    const int textHeight = GetStringHeight(FONT_POPUP) + 1;
69    char *ptr;
70 
71    popup.lines = CopyString(popup.text);
72    ptr = popup.lines;
73 
74    popup.width       = 0;
75    popup.height      = 1;
76    popup.lineCount   = 0;
77    for(;;) {
78       char *end = strchr(ptr, '\n');
79       int currentWidth;
80       if(end) {
81          *end = 0;
82       }
83       currentWidth = GetStringWidth(FONT_POPUP, ptr) + 9;
84       popup.width = Max(popup.width, currentWidth);
85       popup.height += textHeight;
86       popup.lineCount += 1;
87       if(end) {
88          ptr = end + 1;
89       } else {
90          break;
91       }
92    }
93 }
94 
95 /** Show a popup window. */
ShowPopup(int x,int y,const char * text,const PopupMaskType context)96 void ShowPopup(int x, int y, const char *text,
97                const PopupMaskType context)
98 {
99    const ScreenType *sp;
100    char *ptr;
101    int textHeight;
102    int i;
103 
104    if(!(settings.popupMask & context)) {
105       return;
106    }
107 
108    if(popup.text) {
109       if(x == popup.x && y == popup.y && !strcmp(popup.text, text)) {
110          /* This popup is already shown. */
111          return;
112       }
113       Release(popup.text);
114       Release(popup.lines);
115       popup.text = NULL;
116    }
117 
118    if(text[0] == 0) {
119       return;
120    }
121 
122    GetMousePosition(&popup.mx, &popup.my, &popup.mw);
123    popup.text = CopyString(text);
124 
125    MeasurePopupText();
126    sp = GetCurrentScreen(x, y);
127    if(popup.width > sp->width) {
128       popup.width = sp->width;
129    }
130 
131    popup.x = x;
132    if(y + 2 * popup.height + 2 >= sp->height) {
133       popup.y = y - popup.height - 2;
134    } else {
135       popup.y = y + GetStringHeight(FONT_POPUP) + 2;
136    }
137 
138    if(popup.width + popup.x > sp->x + sp->width) {
139       popup.x = sp->x + sp->width - popup.width - 2;
140    }
141    if(popup.height + popup.y > sp->y + sp->height) {
142       popup.y = sp->y + sp->height - popup.height - 2;
143    }
144    if(popup.x < 2) {
145       popup.x = 2;
146    }
147    if(popup.y < 2) {
148       popup.y = 2;
149    }
150 
151    if(popup.window == None) {
152 
153       XSetWindowAttributes attr;
154       unsigned long attrMask = 0;
155 
156       attrMask |= CWEventMask;
157       attr.event_mask = ExposureMask
158                       | PointerMotionMask | PointerMotionHintMask;
159 
160       attrMask |= CWSaveUnder;
161       attr.save_under = True;
162 
163       attrMask |= CWDontPropagate;
164       attr.do_not_propagate_mask = PointerMotionMask
165                                  | ButtonPressMask
166                                  | ButtonReleaseMask;
167 
168       popup.window = JXCreateWindow(display, rootWindow, popup.x, popup.y,
169                                     popup.width, popup.height, 0,
170                                     CopyFromParent, InputOutput,
171                                     CopyFromParent, attrMask, &attr);
172       SetAtomAtom(popup.window, ATOM_NET_WM_WINDOW_TYPE,
173                   ATOM_NET_WM_WINDOW_TYPE_NOTIFICATION);
174       JXMapRaised(display, popup.window);
175 
176    } else {
177 
178       JXMoveResizeWindow(display, popup.window, popup.x, popup.y,
179                          popup.width, popup.height);
180       JXFreePixmap(display, popup.pmap);
181 
182    }
183 
184    popup.pmap = JXCreatePixmap(display, popup.window,
185                                popup.width, popup.height,
186                                rootDepth);
187 
188    JXSetForeground(display, rootGC, colors[COLOR_POPUP_BG]);
189    JXFillRectangle(display, popup.pmap, rootGC, 0, 0,
190                    popup.width - 1, popup.height - 1);
191    JXSetForeground(display, rootGC, colors[COLOR_POPUP_OUTLINE]);
192    JXDrawRectangle(display, popup.pmap, rootGC, 0, 0,
193                    popup.width - 1, popup.height - 1);
194    ptr = popup.lines;
195    textHeight = GetStringHeight(FONT_POPUP) + 1;
196    for(i = 0; i < popup.lineCount; i++) {
197       RenderString(popup.pmap, FONT_POPUP, COLOR_POPUP_FG, 4,
198                    textHeight * i + 1, popup.width, ptr);
199       ptr += strlen(ptr) + 1;
200    }
201    JXCopyArea(display, popup.pmap, popup.window, rootGC,
202               0, 0, popup.width, popup.height, 0, 0);
203 
204 }
205 
206 /** Signal popup (this is used to hide popups after awhile). */
SignalPopup(const TimeType * now,int x,int y,Window w,void * data)207 void SignalPopup(const TimeType *now, int x, int y, Window w, void *data)
208 {
209    if(popup.window != None) {
210       if(popup.mw != w ||
211          abs(popup.mx - x) > 0 || abs(popup.my - y) > 0) {
212          JXDestroyWindow(display, popup.window);
213          JXFreePixmap(display, popup.pmap);
214          popup.window = None;
215       }
216    }
217 }
218 
219 /** Process an event on a popup window. */
ProcessPopupEvent(const XEvent * event)220 char ProcessPopupEvent(const XEvent *event)
221 {
222    if(popup.window != None && event->xany.window == popup.window) {
223       if(event->type == Expose && event->xexpose.count == 0) {
224          JXCopyArea(display, popup.pmap, popup.window, rootGC,
225                     0, 0, popup.width, popup.height, 0, 0);
226       } else if(event->type == MotionNotify) {
227          JXDestroyWindow(display, popup.window);
228          JXFreePixmap(display, popup.pmap);
229          popup.window = None;
230       }
231       return 1;
232    }
233    return 0;
234 }
235