1#include <ctype.h>
2#include <rfb/rfb.h>
3#include <rfb/keysym.h>
4
5typedef 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
22static const char* okStr="OK";
23static const char* cancelStr="Cancel";
24
25static 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 */
55static 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
69static 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
125static 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
169static 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
198static rfbCursorPtr selGetCursorPtr(rfbClientPtr cl)
199{
200  return NULL;
201}
202
203int 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
301