cursor rectangle was 1px too big
[presencevnc] / libvnc / libvncserver / selbox.c
1 #include <ctype.h>
2 #include <rfb/rfb.h>
3 #include <rfb/keysym.h>
4
5 typedef struct {
6   rfbScreenInfoPtr screen;
7   rfbFontDataPtr font;
8   char** list;
9   int listSize;
10   int selected;
11   int displayStart;
12   int x1,y1,x2,y2,textH,pageH;
13   int xhot,yhot;
14   int buttonWidth,okBX,cancelBX,okX,cancelX,okY;
15   rfbBool okInverted,cancelInverted;
16   int lastButtons;
17   rfbPixel colour,backColour;
18   SelectionChangedHookPtr selChangedHook;
19   enum { SELECTING, OK, CANCEL } state;
20 } rfbSelectData;
21
22 static const char* okStr="OK";
23 static const char* cancelStr="Cancel";
24
25 static void selPaintButtons(rfbSelectData* m,rfbBool invertOk,rfbBool invertCancel)
26 {
27   rfbScreenInfoPtr s = m->screen;
28   rfbPixel bcolour = m->backColour;
29   rfbPixel colour = m->colour;
30
31   rfbFillRect(s,m->x1,m->okY-m->textH,m->x2,m->okY,bcolour);
32
33   if(invertOk) {
34     rfbFillRect(s,m->okBX,m->okY-m->textH,m->okBX+m->buttonWidth,m->okY,colour);
35     rfbDrawStringWithClip(s,m->font,m->okX+m->xhot,m->okY-1+m->yhot,okStr,
36                           m->x1,m->okY-m->textH,m->x2,m->okY,
37                           bcolour,colour);
38   } else
39     rfbDrawString(s,m->font,m->okX+m->xhot,m->okY-1+m->yhot,okStr,colour);
40     
41   if(invertCancel) {
42     rfbFillRect(s,m->cancelBX,m->okY-m->textH,
43              m->cancelBX+m->buttonWidth,m->okY,colour);
44     rfbDrawStringWithClip(s,m->font,m->cancelX+m->xhot,m->okY-1+m->yhot,
45                           cancelStr,m->x1,m->okY-m->textH,m->x2,m->okY,
46                           bcolour,colour);
47   } else
48     rfbDrawString(s,m->font,m->cancelX+m->xhot,m->okY-1+m->yhot,cancelStr,colour);
49
50   m->okInverted = invertOk;
51   m->cancelInverted = invertCancel;
52 }
53
54 /* line is relative to displayStart */
55 static void selPaintLine(rfbSelectData* m,int line,rfbBool invert)
56 {
57   int y1 = m->y1+line*m->textH, y2 = y1+m->textH;
58   if(y2>m->y2)
59     y2=m->y2;
60   rfbFillRect(m->screen,m->x1,y1,m->x2,y2,invert?m->colour:m->backColour);
61   if(m->displayStart+line<m->listSize)
62     rfbDrawStringWithClip(m->screen,m->font,m->x1+m->xhot,y2-1+m->yhot,
63                           m->list[m->displayStart+line],
64                           m->x1,y1,m->x2,y2,
65                           invert?m->backColour:m->colour,
66                           invert?m->backColour:m->colour);
67 }
68
69 static void selSelect(rfbSelectData* m,int _index)
70 {
71   int delta;
72
73   if(_index==m->selected || _index<0 || _index>=m->listSize)
74     return;
75
76   if(m->selected>=0)
77     selPaintLine(m,m->selected-m->displayStart,FALSE);
78
79   if(_index<m->displayStart || _index>=m->displayStart+m->pageH) {
80     /* targetLine is the screen line in which the selected line will
81        be displayed.
82        targetLine = m->pageH/2 doesn't look so nice */
83     int targetLine = m->selected-m->displayStart;
84     int lineStart,lineEnd;
85
86     /* scroll */
87     if(_index<targetLine)
88       targetLine = _index;
89     else if(_index+m->pageH-targetLine>=m->listSize)
90       targetLine = _index+m->pageH-m->listSize;
91     delta = _index-(m->displayStart+targetLine);
92
93     if(delta>-m->pageH && delta<m->pageH) {
94       if(delta>0) {
95         lineStart = m->pageH-delta;
96         lineEnd = m->pageH;
97         rfbDoCopyRect(m->screen,m->x1,m->y1,m->x2,m->y1+lineStart*m->textH,
98                       0,-delta*m->textH);
99       } else {
100         lineStart = 0;
101         lineEnd = -delta;
102         rfbDoCopyRect(m->screen,
103                       m->x1,m->y1+lineEnd*m->textH,m->x2,m->y2,
104                       0,-delta*m->textH);
105       }
106     } else {
107       lineStart = 0;
108       lineEnd = m->pageH;
109     }
110     m->displayStart += delta;
111     for(delta=lineStart;delta<lineEnd;delta++)
112       if(delta!=_index)
113         selPaintLine(m,delta,FALSE);
114   }
115
116   m->selected = _index;
117   selPaintLine(m,m->selected-m->displayStart,TRUE);
118
119   if(m->selChangedHook)
120     m->selChangedHook(_index);
121
122   /* todo: scrollbars */
123 }
124
125 static void selKbdAddEvent(rfbBool down,rfbKeySym keySym,rfbClientPtr cl)
126 {
127   if(down) {
128     if(keySym>' ' && keySym<0xff) {
129       int i;
130       rfbSelectData* m = (rfbSelectData*)cl->screen->screenData;
131       char c = tolower(keySym);
132
133       for(i=m->selected+1;m->list[i] && tolower(m->list[i][0])!=c;i++);
134       if(!m->list[i])
135         for(i=0;i<m->selected && tolower(m->list[i][0])!=c;i++);
136       selSelect(m,i);
137     } else if(keySym==XK_Escape) {
138       rfbSelectData* m = (rfbSelectData*)cl->screen->screenData;
139       m->state = CANCEL;
140     } else if(keySym==XK_Return) {
141       rfbSelectData* m = (rfbSelectData*)cl->screen->screenData;
142       m->state = OK;
143     } else {
144       rfbSelectData* m = (rfbSelectData*)cl->screen->screenData;
145       int curSel=m->selected;
146       if(keySym==XK_Up) {
147         if(curSel>0)
148           selSelect(m,curSel-1);
149       } else if(keySym==XK_Down) {
150         if(curSel+1<m->listSize)
151           selSelect(m,curSel+1);
152       } else {
153         if(keySym==XK_Page_Down) {
154           if(curSel+m->pageH<m->listSize)
155             selSelect(m,curSel+m->pageH);
156           else
157             selSelect(m,m->listSize-1);
158         } else if(keySym==XK_Page_Up) {
159           if(curSel-m->pageH>=0)
160             selSelect(m,curSel-m->pageH);
161           else
162             selSelect(m,0);
163         }
164       }
165     }
166   }
167 }
168
169 static void selPtrAddEvent(int buttonMask,int x,int y,rfbClientPtr cl)
170 {
171   rfbSelectData* m = (rfbSelectData*)cl->screen->screenData;
172   if(y<m->okY && y>=m->okY-m->textH) {
173     if(x>=m->okBX && x<m->okBX+m->buttonWidth) {
174       if(!m->okInverted)
175         selPaintButtons(m,TRUE,FALSE);
176       if(buttonMask)
177         m->state = OK;
178     } else if(x>=m->cancelBX && x<m->cancelBX+m->buttonWidth) {
179       if(!m->cancelInverted)
180         selPaintButtons(m,FALSE,TRUE);
181       if(buttonMask)
182         m->state = CANCEL;
183     } else if(m->okInverted || m->cancelInverted)
184       selPaintButtons(m,FALSE,FALSE);
185   } else {
186     if(m->okInverted || m->cancelInverted)
187       selPaintButtons(m,FALSE,FALSE);
188     if(!m->lastButtons && buttonMask) {
189       if(x>=m->x1 && x<m->x2 && y>=m->y1 && y<m->y2)
190         selSelect(m,m->displayStart+(y-m->y1)/m->textH);
191     }
192   }
193   m->lastButtons = buttonMask;
194
195   /* todo: scrollbars */
196 }
197
198 static rfbCursorPtr selGetCursorPtr(rfbClientPtr cl)
199 {
200   return NULL;
201 }
202
203 int rfbSelectBox(rfbScreenInfoPtr rfbScreen,rfbFontDataPtr font,
204                  char** list,
205                  int x1,int y1,int x2,int y2,
206                  rfbPixel colour,rfbPixel backColour,
207                  int border,SelectionChangedHookPtr selChangedHook)
208 {
209    int bpp = rfbScreen->bitsPerPixel/8;
210    char* frameBufferBackup;
211    void* screenDataBackup = rfbScreen->screenData;
212    rfbKbdAddEventProcPtr kbdAddEventBackup = rfbScreen->kbdAddEvent;
213    rfbPtrAddEventProcPtr ptrAddEventBackup = rfbScreen->ptrAddEvent;
214    rfbGetCursorProcPtr getCursorPtrBackup = rfbScreen->getCursorPtr;
215    rfbDisplayHookPtr displayHookBackup = rfbScreen->displayHook;
216    rfbSelectData selData;
217    int i,j,k;
218    int fx1,fy1,fx2,fy2; /* for font bbox */
219
220    if(list==0 || *list==0)
221      return(-1);
222    
223    rfbWholeFontBBox(font, &fx1, &fy1, &fx2, &fy2);
224    selData.textH = fy2-fy1;
225    /* I need at least one line for the choice and one for the buttons */
226    if(y2-y1<selData.textH*2+3*border)
227      return(-1);
228    selData.xhot = -fx1;
229    selData.yhot = -fy2;
230    selData.x1 = x1+border;
231    selData.y1 = y1+border;
232    selData.y2 = y2-selData.textH-3*border;
233    selData.x2 = x2-2*border;
234    selData.pageH = (selData.y2-selData.y1)/selData.textH;
235
236    i = rfbWidthOfString(font,okStr);
237    j = rfbWidthOfString(font,cancelStr);
238    selData.buttonWidth= k = 4*border+(i<j)?j:i;
239    selData.okBX = x1+(x2-x1-2*k)/3;
240    if(selData.okBX<x1+border) /* too narrow! */
241      return(-1);
242    selData.cancelBX = x1+k+(x2-x1-2*k)*2/3;
243    selData.okX = selData.okBX+(k-i)/2;
244    selData.cancelX = selData.cancelBX+(k-j)/2;
245    selData.okY = y2-border;
246
247    frameBufferBackup = (char*)malloc(bpp*(x2-x1)*(y2-y1));
248
249    selData.state = SELECTING;
250    selData.screen = rfbScreen;
251    selData.font = font;
252    selData.list = list;
253    selData.colour = colour;
254    selData.backColour = backColour;
255    for(i=0;list[i];i++);
256    selData.selected = i;
257    selData.listSize = i;
258    selData.displayStart = i;
259    selData.lastButtons = 0;
260    selData.selChangedHook = selChangedHook;
261    
262    rfbScreen->screenData = &selData;
263    rfbScreen->kbdAddEvent = selKbdAddEvent;
264    rfbScreen->ptrAddEvent = selPtrAddEvent;
265    rfbScreen->getCursorPtr = selGetCursorPtr;
266    rfbScreen->displayHook = NULL;
267    
268    /* backup screen */
269    for(j=0;j<y2-y1;j++)
270      memcpy(frameBufferBackup+j*(x2-x1)*bpp,
271             rfbScreen->frameBuffer+j*rfbScreen->paddedWidthInBytes+x1*bpp,
272             (x2-x1)*bpp);
273
274    /* paint list and buttons */
275    rfbFillRect(rfbScreen,x1,y1,x2,y2,colour);
276    selPaintButtons(&selData,FALSE,FALSE);
277    selSelect(&selData,0);
278
279    /* modal loop */
280    while(selData.state == SELECTING)
281      rfbProcessEvents(rfbScreen,20000);
282
283    /* copy back screen data */
284    for(j=0;j<y2-y1;j++)
285      memcpy(rfbScreen->frameBuffer+j*rfbScreen->paddedWidthInBytes+x1*bpp,
286             frameBufferBackup+j*(x2-x1)*bpp,
287             (x2-x1)*bpp);
288    free(frameBufferBackup);
289    rfbMarkRectAsModified(rfbScreen,x1,y1,x2,y2);
290    rfbScreen->screenData = screenDataBackup;
291    rfbScreen->kbdAddEvent = kbdAddEventBackup;
292    rfbScreen->ptrAddEvent = ptrAddEventBackup;
293    rfbScreen->getCursorPtr = getCursorPtrBackup;
294    rfbScreen->displayHook = displayHookBackup;
295
296    if(selData.state==CANCEL)
297      selData.selected=-1;
298    return(selData.selected);
299 }
300