| #include <ctype.h> |
| #include <rfb/rfb.h> |
| #include <rfb/keysym.h> |
| |
| typedef struct { |
| rfbScreenInfoPtr screen; |
| rfbFontDataPtr font; |
| char** list; |
| int listSize; |
| int selected; |
| int displayStart; |
| int x1,y1,x2,y2,textH,pageH; |
| int xhot,yhot; |
| int buttonWidth,okBX,cancelBX,okX,cancelX,okY; |
| rfbBool okInverted,cancelInverted; |
| int lastButtons; |
| rfbPixel colour,backColour; |
| SelectionChangedHookPtr selChangedHook; |
| enum { SELECTING, OK, CANCEL } state; |
| } rfbSelectData; |
| |
| static const char* okStr="OK"; |
| static const char* cancelStr="Cancel"; |
| |
| static void selPaintButtons(rfbSelectData* m,rfbBool invertOk,rfbBool invertCancel) |
| { |
| rfbScreenInfoPtr s = m->screen; |
| rfbPixel bcolour = m->backColour; |
| rfbPixel colour = m->colour; |
| |
| rfbFillRect(s,m->x1,m->okY-m->textH,m->x2,m->okY,bcolour); |
| |
| if(invertOk) { |
| rfbFillRect(s,m->okBX,m->okY-m->textH,m->okBX+m->buttonWidth,m->okY,colour); |
| rfbDrawStringWithClip(s,m->font,m->okX+m->xhot,m->okY-1+m->yhot,okStr, |
| m->x1,m->okY-m->textH,m->x2,m->okY, |
| bcolour,colour); |
| } else |
| rfbDrawString(s,m->font,m->okX+m->xhot,m->okY-1+m->yhot,okStr,colour); |
| |
| if(invertCancel) { |
| rfbFillRect(s,m->cancelBX,m->okY-m->textH, |
| m->cancelBX+m->buttonWidth,m->okY,colour); |
| rfbDrawStringWithClip(s,m->font,m->cancelX+m->xhot,m->okY-1+m->yhot, |
| cancelStr,m->x1,m->okY-m->textH,m->x2,m->okY, |
| bcolour,colour); |
| } else |
| rfbDrawString(s,m->font,m->cancelX+m->xhot,m->okY-1+m->yhot,cancelStr,colour); |
| |
| m->okInverted = invertOk; |
| m->cancelInverted = invertCancel; |
| } |
| |
| /* line is relative to displayStart */ |
| static void selPaintLine(rfbSelectData* m,int line,rfbBool invert) |
| { |
| int y1 = m->y1+line*m->textH, y2 = y1+m->textH; |
| if(y2>m->y2) |
| y2=m->y2; |
| rfbFillRect(m->screen,m->x1,y1,m->x2,y2,invert?m->colour:m->backColour); |
| if(m->displayStart+line<m->listSize) |
| rfbDrawStringWithClip(m->screen,m->font,m->x1+m->xhot,y2-1+m->yhot, |
| m->list[m->displayStart+line], |
| m->x1,y1,m->x2,y2, |
| invert?m->backColour:m->colour, |
| invert?m->backColour:m->colour); |
| } |
| |
| static void selSelect(rfbSelectData* m,int _index) |
| { |
| int delta; |
| |
| if(_index==m->selected || _index<0 || _index>=m->listSize) |
| return; |
| |
| if(m->selected>=0) |
| selPaintLine(m,m->selected-m->displayStart,FALSE); |
| |
| if(_index<m->displayStart || _index>=m->displayStart+m->pageH) { |
| /* targetLine is the screen line in which the selected line will |
| be displayed. |
| targetLine = m->pageH/2 doesn't look so nice */ |
| int targetLine = m->selected-m->displayStart; |
| int lineStart,lineEnd; |
| |
| /* scroll */ |
| if(_index<targetLine) |
| targetLine = _index; |
| else if(_index+m->pageH-targetLine>=m->listSize) |
| targetLine = _index+m->pageH-m->listSize; |
| delta = _index-(m->displayStart+targetLine); |
| |
| if(delta>-m->pageH && delta<m->pageH) { |
| if(delta>0) { |
| lineStart = m->pageH-delta; |
| lineEnd = m->pageH; |
| rfbDoCopyRect(m->screen,m->x1,m->y1,m->x2,m->y1+lineStart*m->textH, |
| 0,-delta*m->textH); |
| } else { |
| lineStart = 0; |
| lineEnd = -delta; |
| rfbDoCopyRect(m->screen, |
| m->x1,m->y1+lineEnd*m->textH,m->x2,m->y2, |
| 0,-delta*m->textH); |
| } |
| } else { |
| lineStart = 0; |
| lineEnd = m->pageH; |
| } |
| m->displayStart += delta; |
| for(delta=lineStart;delta<lineEnd;delta++) |
| if(delta!=_index) |
| selPaintLine(m,delta,FALSE); |
| } |
| |
| m->selected = _index; |
| selPaintLine(m,m->selected-m->displayStart,TRUE); |
| |
| if(m->selChangedHook) |
| m->selChangedHook(_index); |
| |
| /* todo: scrollbars */ |
| } |
| |
| static void selKbdAddEvent(rfbBool down,rfbKeySym keySym,rfbClientPtr cl) |
| { |
| if(down) { |
| if(keySym>' ' && keySym<0xff) { |
| int i; |
| rfbSelectData* m = (rfbSelectData*)cl->screen->screenData; |
| char c = tolower(keySym); |
| |
| for(i=m->selected+1;m->list[i] && tolower(m->list[i][0])!=c;i++); |
| if(!m->list[i]) |
| for(i=0;i<m->selected && tolower(m->list[i][0])!=c;i++); |
| selSelect(m,i); |
| } else if(keySym==XK_Escape) { |
| rfbSelectData* m = (rfbSelectData*)cl->screen->screenData; |
| m->state = CANCEL; |
| } else if(keySym==XK_Return) { |
| rfbSelectData* m = (rfbSelectData*)cl->screen->screenData; |
| m->state = OK; |
| } else { |
| rfbSelectData* m = (rfbSelectData*)cl->screen->screenData; |
| int curSel=m->selected; |
| if(keySym==XK_Up) { |
| if(curSel>0) |
| selSelect(m,curSel-1); |
| } else if(keySym==XK_Down) { |
| if(curSel+1<m->listSize) |
| selSelect(m,curSel+1); |
| } else { |
| if(keySym==XK_Page_Down) { |
| if(curSel+m->pageH<m->listSize) |
| selSelect(m,curSel+m->pageH); |
| else |
| selSelect(m,m->listSize-1); |
| } else if(keySym==XK_Page_Up) { |
| if(curSel-m->pageH>=0) |
| selSelect(m,curSel-m->pageH); |
| else |
| selSelect(m,0); |
| } |
| } |
| } |
| } |
| } |
| |
| static void selPtrAddEvent(int buttonMask,int x,int y,rfbClientPtr cl) |
| { |
| rfbSelectData* m = (rfbSelectData*)cl->screen->screenData; |
| if(y<m->okY && y>=m->okY-m->textH) { |
| if(x>=m->okBX && x<m->okBX+m->buttonWidth) { |
| if(!m->okInverted) |
| selPaintButtons(m,TRUE,FALSE); |
| if(buttonMask) |
| m->state = OK; |
| } else if(x>=m->cancelBX && x<m->cancelBX+m->buttonWidth) { |
| if(!m->cancelInverted) |
| selPaintButtons(m,FALSE,TRUE); |
| if(buttonMask) |
| m->state = CANCEL; |
| } else if(m->okInverted || m->cancelInverted) |
| selPaintButtons(m,FALSE,FALSE); |
| } else { |
| if(m->okInverted || m->cancelInverted) |
| selPaintButtons(m,FALSE,FALSE); |
| if(!m->lastButtons && buttonMask) { |
| if(x>=m->x1 && x<m->x2 && y>=m->y1 && y<m->y2) |
| selSelect(m,m->displayStart+(y-m->y1)/m->textH); |
| } |
| } |
| m->lastButtons = buttonMask; |
| |
| /* todo: scrollbars */ |
| } |
| |
| static rfbCursorPtr selGetCursorPtr(rfbClientPtr cl) |
| { |
| return NULL; |
| } |
| |
| int rfbSelectBox(rfbScreenInfoPtr rfbScreen,rfbFontDataPtr font, |
| char** list, |
| int x1,int y1,int x2,int y2, |
| rfbPixel colour,rfbPixel backColour, |
| int border,SelectionChangedHookPtr selChangedHook) |
| { |
| int bpp = rfbScreen->bitsPerPixel/8; |
| char* frameBufferBackup; |
| void* screenDataBackup = rfbScreen->screenData; |
| rfbKbdAddEventProcPtr kbdAddEventBackup = rfbScreen->kbdAddEvent; |
| rfbPtrAddEventProcPtr ptrAddEventBackup = rfbScreen->ptrAddEvent; |
| rfbGetCursorProcPtr getCursorPtrBackup = rfbScreen->getCursorPtr; |
| rfbDisplayHookPtr displayHookBackup = rfbScreen->displayHook; |
| rfbSelectData selData; |
| int i,j,k; |
| int fx1,fy1,fx2,fy2; /* for font bbox */ |
| |
| if(list==0 || *list==0) |
| return(-1); |
| |
| rfbWholeFontBBox(font, &fx1, &fy1, &fx2, &fy2); |
| selData.textH = fy2-fy1; |
| /* I need at least one line for the choice and one for the buttons */ |
| if(y2-y1<selData.textH*2+3*border) |
| return(-1); |
| selData.xhot = -fx1; |
| selData.yhot = -fy2; |
| selData.x1 = x1+border; |
| selData.y1 = y1+border; |
| selData.y2 = y2-selData.textH-3*border; |
| selData.x2 = x2-2*border; |
| selData.pageH = (selData.y2-selData.y1)/selData.textH; |
| |
| i = rfbWidthOfString(font,okStr); |
| j = rfbWidthOfString(font,cancelStr); |
| selData.buttonWidth= k = 4*border+(i<j?j:i); |
| selData.okBX = x1+(x2-x1-2*k)/3; |
| if(selData.okBX<x1+border) /* too narrow! */ |
| return(-1); |
| selData.cancelBX = x1+k+(x2-x1-2*k)*2/3; |
| selData.okX = selData.okBX+(k-i)/2; |
| selData.cancelX = selData.cancelBX+(k-j)/2; |
| selData.okY = y2-border; |
| |
| frameBufferBackup = (char*)malloc(bpp*(x2-x1)*(y2-y1)); |
| |
| selData.state = SELECTING; |
| selData.screen = rfbScreen; |
| selData.font = font; |
| selData.list = list; |
| selData.colour = colour; |
| selData.backColour = backColour; |
| for(i=0;list[i];i++); |
| selData.selected = i; |
| selData.listSize = i; |
| selData.displayStart = i; |
| selData.lastButtons = 0; |
| selData.selChangedHook = selChangedHook; |
| |
| rfbScreen->screenData = &selData; |
| rfbScreen->kbdAddEvent = selKbdAddEvent; |
| rfbScreen->ptrAddEvent = selPtrAddEvent; |
| rfbScreen->getCursorPtr = selGetCursorPtr; |
| rfbScreen->displayHook = NULL; |
| |
| /* backup screen */ |
| for(j=0;j<y2-y1;j++) |
| memcpy(frameBufferBackup+j*(x2-x1)*bpp, |
| rfbScreen->frameBuffer+j*rfbScreen->paddedWidthInBytes+x1*bpp, |
| (x2-x1)*bpp); |
| |
| /* paint list and buttons */ |
| rfbFillRect(rfbScreen,x1,y1,x2,y2,colour); |
| selPaintButtons(&selData,FALSE,FALSE); |
| selSelect(&selData,0); |
| |
| /* modal loop */ |
| while(selData.state == SELECTING) |
| rfbProcessEvents(rfbScreen,20000); |
| |
| /* copy back screen data */ |
| for(j=0;j<y2-y1;j++) |
| memcpy(rfbScreen->frameBuffer+j*rfbScreen->paddedWidthInBytes+x1*bpp, |
| frameBufferBackup+j*(x2-x1)*bpp, |
| (x2-x1)*bpp); |
| free(frameBufferBackup); |
| rfbMarkRectAsModified(rfbScreen,x1,y1,x2,y2); |
| rfbScreen->screenData = screenDataBackup; |
| rfbScreen->kbdAddEvent = kbdAddEventBackup; |
| rfbScreen->ptrAddEvent = ptrAddEventBackup; |
| rfbScreen->getCursorPtr = getCursorPtrBackup; |
| rfbScreen->displayHook = displayHookBackup; |
| |
| if(selData.state==CANCEL) |
| selData.selected=-1; |
| return(selData.selected); |
| } |
| |