/* -*-c-*- */ /* * This file is partly derived from FvwmForm.c, this is the original * copyright: * * FvwmForm is original work of Thomas Zuwei Feng. * * Copyright Feb 1995, Thomas Zuwei Feng. No guarantees or warantees are * provided or implied in any way whatsoever. Use this program at your own * risk. Permission to use, modify, and redistribute this program is hereby * given, provided that this copyright is kept intact. * * And the same goes for me, Dan Espen, March 10, 1999. */ /* This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA */ /* Modification History */ /* Changed on 03/19/99 by DanEspen (dje): */ /* - paste tab as space if there is one input field. */ /* Changed on 03/10/99 by DanEspen (dje): */ /* - Make button 2 paste work. */ /* Changed on 02/27/99 by DanEspen (dje): */ /* - Add logic to allow international characters, bug id 179 */ /* - Split into separate file. */ #include "config.h" #include "libs/fvwmlib.h" #include "libs/Colorset.h" #include #include #include #include #include #include #define XK_MISCELLANY #include #include /* for XA_CUT_BUFFER0 */ #include static void process_regular_char_input(unsigned char *buf); static int process_tabtypes(unsigned char * buf); static void process_history(int direction); static void process_paste_request (XEvent *event, Item *item); static void ToggleChoice (Item *item); static void ResizeFrame (void); /* read an X event */ void ReadXServer (void) { static XEvent event; int old_cursor = 0, keypress; Item *item, *old_item; KeySym ks; char *sp, *dp; static unsigned char buf[10]; /* unsigned for international */ static int n; while (FEventsQueued(dpy, QueuedAfterReading)) { FNextEvent(dpy, &event); if (event.xany.window == CF.frame) { switch (event.type) { case ClientMessage: { if(event.xclient.format == 32 && event.xclient.data.l[0] == wm_del_win) { exit(0); } } break; case ConfigureNotify: /* has window be reconfigured */ { XEvent tmpe; while (FCheckTypedWindowEvent( dpy, CF.frame, ConfigureNotify, &tmpe)) { if (!tmpe.xconfigure.send_event) continue; event.xconfigure.x = tmpe.xconfigure.x; event.xconfigure.y = tmpe.xconfigure.y; event.xconfigure.send_event = True; } if (CF.max_width != event.xconfigure.width || CF.total_height != event.xconfigure.height) { /* adjust yourself... do noting */ ResizeFrame(); CF.max_width = event.xconfigure.width; CF.total_height = event.xconfigure.height; UpdateRootTransapency(False, True); if (!CSET_IS_TRANSPARENT(colorset)) { RedrawFrame(NULL); } } else if (event.xconfigure.send_event) { UpdateRootTransapency(False, True); } } break; #if 0 case SelectionClear: selection_clear (); break; case SelectionNotify: selection_paste (); break; case SelectionRequest: selection_send (); break; #endif case Expose: { int ex = event.xexpose.x; int ey = event.xexpose.y; int ex2 = event.xexpose.x + event.xexpose.width; int ey2 = event.xexpose.y + event.xexpose.height; while (FCheckTypedWindowEvent(dpy, CF.frame, Expose, &event)) { ex = min(ex, event.xexpose.x); ey = min(ey, event.xexpose.y); ex2 = max(ex2, event.xexpose.x + event.xexpose.width); ey2 = max(ey2 , event.xexpose.y + event.xexpose.height); } event.xexpose.x = ex; event.xexpose.y = ey; event.xexpose.width = ex2 - ex; event.xexpose.height = ey2 - ey; RedrawFrame(&event); if (CF.grab_server && !CF.server_grabbed) { if (GrabSuccess == XGrabPointer(dpy, CF.frame, True, 0, GrabModeAsync, GrabModeAsync, None, None, CurrentTime)) CF.server_grabbed = 1; } } break; case VisibilityNotify: if (CF.server_grabbed && event.xvisibility.state != VisibilityUnobscured) { /* raise our window to the top */ XRaiseWindow(dpy, CF.frame); XSync(dpy, 0); } break; case KeyPress: /* we do text input here */ n = XLookupString(&event.xkey, (char *)buf, sizeof(buf), &ks, NULL); keypress = buf[0]; myfprintf((stderr, "Keypress [%s]\n", buf)); if (n == 0) { /* not a regular key, translate it into one */ switch (ks) { case XK_Home: case XK_Begin: buf[0] = '\001'; /* ^A */ break; case XK_End: buf[0] = '\005'; /* ^E */ break; case XK_Left: buf[0] = '\002'; /* ^B */ break; case XK_Right: buf[0] = '\006'; /* ^F */ break; case XK_Up: buf[0] = '\020'; /* ^P */ break; case XK_Down: buf[0] = '\016'; /* ^N */ break; default: if (ks >= XK_F1 && ks <= XK_F35) { buf[0] = '\0'; keypress = 257 + ks - XK_F1; } else goto no_redraw; /* no action for this event */ } } switch (ks) { /* regular key, may need adjustment */ case XK_Tab: #ifdef XK_XKB_KEYS case XK_ISO_Left_Tab: #endif if (event.xkey.state & ShiftMask) { /* shifted key */ buf[0] = '\020'; /* chg shift tab to ^P */ } break; case '>': if (event.xkey.state & Mod1Mask) { /* Meta, shift > */ process_history(1); goto redraw_newcursor; } break; case '<': if (event.xkey.state & Mod1Mask) { /* Meta, shift < */ process_history(-1); goto redraw_newcursor; } break; } if (!CF.cur_input) { /* no text input fields */ for (item = root_item_ptr; item != 0; item = item->header.next) {/* all items */ if (item->type == I_BUTTON && item->button.keypress == keypress) { RedrawItem(item, 1, NULL); usleep(MICRO_S_FOR_10MS); RedrawItem(item, 0, NULL); DoCommand(item); goto no_redraw; } } break; } else if (CF.cur_input == CF.cur_input->input.next_input) { /* 1 ip field */ switch (buf[0]) { case '\020': /* ^P previous field */ process_history(-1); goto redraw_newcursor; break; case '\016': /* ^N next field */ process_history(1); goto redraw_newcursor; break; } /* end switch */ } /* end one input field */ switch (buf[0]) { case '\001': /* ^A */ old_cursor = CF.abs_cursor; CF.rel_cursor = 0; CF.abs_cursor = 0; CF.cur_input->input.left = 0; goto redraw_newcursor; break; case '\005': /* ^E */ old_cursor = CF.abs_cursor; CF.rel_cursor = CF.cur_input->input.n; if ((CF.cur_input->input.left = CF.rel_cursor - CF.cur_input->input.size) < 0) CF.cur_input->input.left = 0; CF.abs_cursor = CF.rel_cursor - CF.cur_input->input.left; goto redraw_newcursor; break; case '\002': /* ^B */ old_cursor = CF.abs_cursor; if (CF.rel_cursor > 0) { CF.rel_cursor--; CF.abs_cursor--; if (CF.abs_cursor <= 0 && CF.rel_cursor > 0) { CF.abs_cursor++; CF.cur_input->input.left--; } } goto redraw_newcursor; break; case '\006': /* ^F */ old_cursor = CF.abs_cursor; if (CF.rel_cursor < CF.cur_input->input.n) { CF.rel_cursor++; CF.abs_cursor++; if (CF.abs_cursor >= CF.cur_input->input.size && CF.rel_cursor < CF.cur_input->input.n) { CF.abs_cursor--; CF.cur_input->input.left++; } } goto redraw_newcursor; break; case '\010': /* ^H */ old_cursor = CF.abs_cursor; if (CF.rel_cursor > 0) { sp = CF.cur_input->input.value + CF.rel_cursor; dp = sp - 1; for (; *dp = *sp, *sp != '\0'; dp++, sp++); CF.cur_input->input.n--; CF.rel_cursor--; if (CF.rel_cursor < CF.abs_cursor) { CF.abs_cursor--; if (CF.abs_cursor <= 0 && CF.rel_cursor > 0) { CF.abs_cursor++; CF.cur_input->input.left--; } } else CF.cur_input->input.left--; } goto redraw_newcursor; break; case '\177': /* DEL */ case '\004': /* ^D */ if (CF.rel_cursor < CF.cur_input->input.n) { sp = CF.cur_input->input.value + CF.rel_cursor + 1; dp = sp - 1; for (; *dp = *sp, *sp != '\0'; dp++, sp++); CF.cur_input->input.n--; goto redraw_newcursor; } break; case '\013': /* ^K */ CF.cur_input->input.value[CF.rel_cursor] = '\0'; CF.cur_input->input.n = CF.rel_cursor; goto redraw_newcursor; case '\025': /* ^U */ CF.cur_input->input.value[0] = '\0'; CF.cur_input->input.n = CF.cur_input->input.left = 0; CF.rel_cursor = CF.abs_cursor = 0; goto redraw_newcursor; case '\020': /* ^P previous field */ old_item = CF.cur_input; CF.cur_input = old_item->input.prev_input; /* new current input fld */ RedrawItem(old_item, 1, NULL); CF.rel_cursor = CF.abs_cursor = 0; /* home cursor in new input field */ goto redraw; break; case '\t': case '\n': case '\015': case '\016': /* LINEFEED, TAB, RETURN, ^N, jump to the next field */ switch (process_tabtypes(&buf[0])) { case 0: goto no_redraw;break; case 1: goto redraw;break; } break; default: old_cursor = CF.abs_cursor; if((buf[0] >= ' ' && buf[0] < '\177') || (buf[0] >= 160)) { /* regular or intl char */ process_regular_char_input(&buf[0]); /* insert into input field */ goto redraw_newcursor; } /* unrecognized key press, check for buttons */ for (item = root_item_ptr; item != 0; item = item->header.next) { /* all items */ myfprintf((stderr, "Button: keypress==%d\n", item->button.keypress)); if (item->type == I_BUTTON && item->button.keypress == keypress) { RedrawItem(item, 1, NULL); usleep(MICRO_S_FOR_10MS); /* .1 seconds */ RedrawItem(item, 0, NULL); DoCommand(item); goto no_redraw; } } break; } redraw_newcursor: { XSetForeground(dpy, CF.cur_input->header.dt_ptr->dt_item_GC, CF.cur_input->header.dt_ptr->dt_colors[c_item_bg]); /* Since DrawString is being used, I changed this to clear the entire input field. dje 10/24/99. */ XClearArea(dpy, CF.cur_input->header.win, BOX_SPC + TEXT_SPC - 1, BOX_SPC, CF.cur_input->header.size_x - (2 * BOX_SPC) - 2 - TEXT_SPC, (CF.cur_input->header.size_y - 1) - 2 * BOX_SPC + 1, False); } redraw: { int len, x, dy; len = CF.cur_input->input.n - CF.cur_input->input.left; XSetForeground(dpy, CF.cur_input->header.dt_ptr->dt_item_GC, CF.cur_input->header.dt_ptr->dt_colors[c_item_fg]); if (len > CF.cur_input->input.size) len = CF.cur_input->input.size; CF.cur_input->header.dt_ptr->dt_Fstr->win = CF.cur_input->header.win; CF.cur_input->header.dt_ptr->dt_Fstr->gc = CF.cur_input->header.dt_ptr->dt_item_GC; CF.cur_input->header.dt_ptr->dt_Fstr->flags.has_colorset = False; if (itemcolorset >= 0) { CF.cur_input->header.dt_ptr->dt_Fstr->colorset = &Colorset[itemcolorset]; CF.cur_input->header.dt_ptr->dt_Fstr->flags.has_colorset = True; } CF.cur_input->header.dt_ptr->dt_Fstr->str = CF.cur_input->input.value; CF.cur_input->header.dt_ptr->dt_Fstr->x = BOX_SPC + TEXT_SPC; CF.cur_input->header.dt_ptr->dt_Fstr->y = BOX_SPC + TEXT_SPC + CF.cur_input->header.dt_ptr->dt_Ffont->ascent; CF.cur_input->header.dt_ptr->dt_Fstr->len = len; FlocaleDrawString(dpy, CF.cur_input->header.dt_ptr->dt_Ffont, CF.cur_input->header.dt_ptr->dt_Fstr, FWS_HAVE_LENGTH); x = BOX_SPC + TEXT_SPC + FlocaleTextWidth(CF.cur_input->header.dt_ptr->dt_Ffont, CF.cur_input->input.value,CF.abs_cursor) - 1; dy = CF.cur_input->header.size_y - 1; XDrawLine(dpy, CF.cur_input->header.win, CF.cur_input->header.dt_ptr->dt_item_GC, x, BOX_SPC, x, dy - BOX_SPC); myfprintf((stderr,"Line %d/%d - %d/%d (char)\n", x, BOX_SPC, x, dy - BOX_SPC)); } no_redraw: break; /* end of case KeyPress */ } /* end of switch (event.type) */ continue; } /* end of if (event.xany.window == CF.frame) */ for (item = root_item_ptr; item != 0; item = item->header.next) { /* all items */ if (event.xany.window == item->header.win) { switch (event.type) { case Expose: { int ex = event.xexpose.x; int ey = event.xexpose.y; int ex2 = event.xexpose.x + event.xexpose.width; int ey2 = event.xexpose.y + event.xexpose.height; while (FCheckTypedWindowEvent( dpy, item->header.win, Expose, &event)) { ex = min(ex, event.xexpose.x); ey = min(ey, event.xexpose.y); ex2 = max(ex2, event.xexpose.x + event.xexpose.width); ey2 = max(ey2 , event.xexpose.y + event.xexpose.height); } event.xexpose.x = ex; event.xexpose.y = ey; event.xexpose.width = ex2 - ex; event.xexpose.height = ey2 - ey; RedrawItem(item, 0, &event); } break; case ButtonPress: if (item->type == I_INPUT) { old_item = CF.cur_input; CF.cur_input = item; RedrawItem(old_item, 1, NULL); { Bool done = False; CF.abs_cursor = 0; while(CF.abs_cursor <= item->input.size && !done) { if (FlocaleTextWidth(item->header.dt_ptr->dt_Ffont, item->input.value, CF.abs_cursor) >= event.xbutton.x - BOX_SPC - TEXT_SPC) { done = True; CF.abs_cursor--; } else { CF.abs_cursor++; } } } if (CF.abs_cursor < 0) CF.abs_cursor = 0; if (CF.abs_cursor > item->input.size) CF.abs_cursor = item->input.size; CF.rel_cursor = CF.abs_cursor + item->input.left; if (CF.rel_cursor < 0) CF.rel_cursor = 0; if (CF.rel_cursor > item->input.n) CF.rel_cursor = item->input.n; if (CF.rel_cursor > 0 && CF.rel_cursor == item->input.left) item->input.left--; if (CF.rel_cursor < item->input.n && CF.rel_cursor == item->input.left + item->input.size) item->input.left++; CF.abs_cursor = CF.rel_cursor - item->input.left; if (event.xbutton.button == Button2) { /* if paste request */ process_paste_request (&event, item); } RedrawItem(item, 0, NULL); } if (item->type == I_CHOICE) ToggleChoice(item); if (item->type == I_BUTTON) { RedrawItem(item, 1, NULL); /* push button in */ if (CF.activate_on_press) { usleep(MICRO_S_FOR_10MS); /* make sure its visible */ RedrawItem(item, 0, NULL); /* pop button out */ DoCommand(item); /* execute the button command */ } else { XGrabPointer(dpy, item->header.win, False, /* owner of events */ ButtonReleaseMask, /* events to report */ GrabModeAsync, /* keyboard mode */ GrabModeAsync, /* pointer mode */ None, /* confine to */ /* I sort of like this, the hand points in the other direction and the color is reversed. I don't know what other GUIs do, Java doesn't do anything, neither does anything else I can find...dje */ CF.pointer[button_in_pointer], /* cursor */ CurrentTime); } /* end activate on press */ } break; case ButtonRelease: if (!CF.activate_on_press) { RedrawItem(item, 0, NULL); if (CF.grab_server && CF.server_grabbed) { /* You have to regrab the pointer, or focus can go to another window. grab... */ XGrabPointer(dpy, CF.frame, True, 0, GrabModeAsync, GrabModeAsync, None, None, CurrentTime); XFlush(dpy); } else { XUngrabPointer(dpy, CurrentTime); XFlush(dpy); } if (event.xbutton.x >= 0 && event.xbutton.x < item->header.size_x && event.xbutton.y >= 0 && event.xbutton.y < item->header.size_y) { DoCommand(item); } } break; } } } /* end of for (i = 0) */ } /* while loop */ } /* Each input field has a history, depending on the passed direction, get the desired history item into the input field. After "Restart" the yank point is one entry beyond the last inserted item. Normal yanks, going backward, go down the array decrementing yank count before extracting. Forward yanks increment before extracting. */ static void process_history(int direction) { int count; if (!CF.cur_input /* no input fields */ || !CF.cur_input->input.value_history_ptr) { /* or no history */ return; /* bail out */ } /* yankat is always one beyond slot to yank from. */ count = CF.cur_input->input.value_history_yankat + direction; if (count < 0 || count >= VH_SIZE || CF.cur_input->input.value_history_ptr[count] == 0 ) { if (direction <= 0) { /* going down */ for (count = VH_SIZE - 1; CF.cur_input->input.value_history_ptr[count] == 0; --count); /* find last used slot */ } else { /* going up */ count = 0; /* up is the bottom */ } } CF.cur_input->input.value = safestrdup(CF.cur_input->input.value_history_ptr[count]); CF.cur_input->input.n = strlen(CF.cur_input->input.value); CF.cur_input->input.value_history_yankat = count; /* new yank point */ CF.cur_input->input.buf = CF.cur_input->input.n; CF.rel_cursor = 0; CF.abs_cursor = 0; CF.cur_input->input.left = 0; return; } /* Note that tab, return, linefeed, ^n, all do the same thing except when it comes to matching a command button hotkey. Return 1 to redraw, 0 for no redraw */ static int process_tabtypes(unsigned char * buf) { Item *item, *old_item; /* Note: the input field ring used with ^P above could probably make this a lot simpler. dje 12/20/98 */ /* Code tracks cursor. */ item = root_item_ptr; /* init item ptr */ if (CF.cur_input != 0) { /* if in text */ item = CF.cur_input->header.next; /* move to next item */ } for ( ; item != 0; item = item->header.next) {/* find next input item */ if (item->type == I_INPUT) { old_item = CF.cur_input; CF.cur_input = item; RedrawItem(old_item, 1, NULL); CF.rel_cursor = CF.abs_cursor = 0; /* home cursor in new input field */ return (1); /* cause redraw */ } } /* end of all text input fields, check for buttons */ for (item = root_item_ptr; item != 0; item = item->header.next) {/* all items */ myfprintf((stderr, "Button: keypress==%d vs buf %d\n", item->button.keypress, buf[0])); if (item->type == I_BUTTON && item->button.keypress == buf[0]) { RedrawItem(item, 1, NULL); usleep(MICRO_S_FOR_10MS); RedrawItem(item, 0, NULL); DoCommand(item); return (0); /* cause no_redraw */ } } /* goto the first text input field */ for (item = root_item_ptr; item != 0; item = item->header.next) {/* all items */ if (item->type == I_INPUT) { old_item = CF.cur_input; CF.cur_input = item; RedrawItem(old_item, 1, NULL); CF.abs_cursor = CF.rel_cursor - item->input.left; return (1); /* goto redraw */ } } return (-1); } static void process_regular_char_input(unsigned char *buf) { char *sp, *dp, *ep; /* n is the space actually used. buf is the size of the buffer size is the size of the field on the screen size is used as the increment, arbitrarily. */ if (++(CF.cur_input->input.n) >= CF.cur_input->input.buf) { CF.cur_input->input.buf += CF.cur_input->input.size; CF.cur_input->input.value = (char *)saferealloc(CF.cur_input->input.value, CF.cur_input->input.buf); } dp = CF.cur_input->input.value + CF.cur_input->input.n; sp = dp - 1; ep = CF.cur_input->input.value + CF.rel_cursor; for (; *dp = *sp, sp != ep; sp--, dp--); *ep = buf[0]; CF.rel_cursor++; CF.abs_cursor++; if (CF.abs_cursor >= CF.cur_input->input.size) { if (CF.rel_cursor < CF.cur_input->input.n) CF.abs_cursor = CF.cur_input->input.size - 1; else CF.abs_cursor = CF.cur_input->input.size; CF.cur_input->input.left = CF.rel_cursor - CF.abs_cursor; } } /* Process a paste. This can be any size. Right now, the input loop can't just be fed characters. Send plain text and newlines to the 2 subroutines that want them. */ static void process_paste_request (XEvent *event, Item *item) { Atom actual_type; int actual_format; unsigned long nitems, bytes_after, nread; unsigned char *data, *h, buf[256]; unsigned char *c; nread = 0; /* init read offset */ h = buf; /* starting point */ do { if (XGetWindowProperty (dpy, DefaultRootWindow (dpy), XA_CUT_BUFFER0, nread/4, 1024L, /* offset, length */ False, /* delete */ AnyPropertyType, /* request type */ &actual_type, &actual_format, &nitems, &bytes_after, (unsigned char **)&data) != Success) { return; /* didn't work, give up */ } /* end didn't work */ if (actual_type != XA_STRING) { /* if something other than text */ return; /* give up */ } for (c = data; c != data + nitems; c++) { /* each char */ switch (*c) { case '\t': /* TAB */ if (CF.cur_input == CF.cur_input->input.next_input) { /* 1 ip field */ process_regular_char_input((unsigned char *)" "); /* paste space */ } else { process_tabtypes(c); /* jump to the next field */ } case '\015': /* LINEFEED */ case '\016': /* ^N */ process_tabtypes(c); /* jump to the next field */ break; case '\n': /* change \n to \r for pasting */ process_tabtypes((unsigned char *)"\r"); break; default: process_regular_char_input(c); break; } /* end swtich on char type */ } /* end each char */ myfprintf((stderr,"See paste data, %s, nread %d, nitems %d\n", data, (int)nread, (int)nitems)); nread += nitems; XFree (data); } while (bytes_after > 0); } static void ToggleChoice (Item *item) { int i; Item *sel = item->choice.sel; if (sel->selection.key == IS_SINGLE) { if (!item->choice.on) { for (i = 0; i < sel->selection.n; i++) { if (sel->selection.choices[i]->choice.on) { sel->selection.choices[i]->choice.on = 0; RedrawItem(sel->selection.choices[i], 0, NULL); } } item->choice.on = 1; RedrawItem(item, 0, NULL); } } else { /* IS_MULTIPLE */ item->choice.on = !item->choice.on; RedrawItem(item, 0, NULL); } } static void ResizeFrame (void) { #if 0 /* unfinished dje. */ /* If you ever finish this, please make sure to check the return code of * XGetGeometry below. dv */ Window root; XEvent dummy; int x, y; unsigned int border, depth, width, height; /* get anything queued */ while (FCheckTypedWindowEvent(dpy, CF.frame, ConfigureNotify, &dummy)) ; XGetGeometry(dpy, CF.frame, &root, &x, &y, &width, &height, &border, &depth); if (width != CF.max_width) { if (CF.last_error != 0) { /* if form has message area */ fprintf(stderr, "Frame was %d, is %d\n",CF.max_width,width); /* RedrawText sets x = item->header.pos_x + TEXT_SPC; MassageConfig does : line->size_x += (line->n + 1) * ITEM_HSPC; =(10)= if (line->size_x > CF.max_width) CF.max_width = line->size_x; (70 + 1) * 10 = 700?? */ int delta; int curr_x; delta = width - CF.max_width; /* new width - curr width */ curr_x = CF.last_error->header.pos_x + TEXT_SPC; curr_end = curr_x + CF.last_error->size_x; } } #endif }