2 * ion/mod_query/wedln.c
4 * Copyright (c) Tuomo Valkonen 1999-2006.
6 * Ion is free software; you can redistribute it and/or modify it under
7 * the terms of the GNU Lesser General Public License as published by
8 * the Free Software Foundation; either version 2.1 of the License, or
9 * (at your option) any later version.
14 #include <libtu/objp.h>
15 #include <libtu/minmax.h>
16 #include <libtu/setparam.h>
17 #include <libextl/extl.h>
18 #include <libmainloop/defer.h>
19 #include <libmainloop/signal.h>
21 #include <ioncore/common.h>
22 #include <ioncore/global.h>
23 #include <ioncore/strings.h>
24 #include <ioncore/xic.h>
25 #include <ioncore/selection.h>
26 #include <ioncore/event.h>
27 #include <ioncore/regbind.h>
35 #define WEDLN_BRUSH(X) ((X)->input.brush)
38 /*{{{ Drawing primitives */
41 static int calc_text_y(WEdln *wedln, const WRectangle *geom)
45 grbrush_get_font_extents(WEDLN_BRUSH(wedln), &fnte);
47 return geom->y+geom->h/2-fnte.max_height/2+fnte.baseline;
51 static int wedln_draw_strsect(WEdln *wedln, const WRectangle *geom,
52 int x, int y, const char *str, int len,
58 grbrush_draw_string(WEDLN_BRUSH(wedln), x, y, str, len, TRUE, attr);
60 return grbrush_get_text_width(WEDLN_BRUSH(wedln), str, len);
64 static void dispu(const char* s, int l)
67 int c=(unsigned char)*s;
68 fprintf(stderr, "%d[%c]", c, *s);
72 fprintf(stderr, "\n");
76 #define DSTRSECT(LEN, INV) \
77 if(LEN>0){tx+=wedln_draw_strsect(wedln, geom, geom->x+tx, ty, str, LEN, INV); \
81 static void wedln_do_draw_str_box(WEdln *wedln, const WRectangle *geom,
82 const char *str, int cursor,
85 int len=strlen(str), ll=0, ty=0;
86 const char *normalstyle=(REGION_IS_ACTIVE(wedln)
87 ? "active-normal" : "inactive-normal");
88 const char *selectionstyle=(REGION_IS_ACTIVE(wedln)
89 ? "active-selection" : "inactive-selection");
90 const char *cursorstyle=(REGION_IS_ACTIVE(wedln)
91 ? "active-cursor" : "inactive-cursor");
97 grbrush_clear_area(WEDLN_BRUSH(wedln), &g);
100 ty=calc_text_y(wedln, geom);
104 DSTRSECT(mark, normalstyle);
105 DSTRSECT(cursor-mark, selectionstyle);
107 DSTRSECT(cursor, normalstyle);
110 tx+=wedln_draw_strsect(wedln, geom, geom->x+tx, ty,
111 " ", 1, cursorstyle);
113 ll=str_nextoff(str, 0);
114 DSTRSECT(ll, cursorstyle);
117 DSTRSECT(cursor, normalstyle);
118 ll=str_nextoff(str, 0);
119 DSTRSECT(ll, cursorstyle);
120 DSTRSECT(mark-cursor-ll, selectionstyle);
122 DSTRSECT(len, normalstyle);
128 grbrush_clear_area(WEDLN_BRUSH(wedln), &g);
133 static void wedln_draw_str_box(WEdln *wedln, const WRectangle *geom,
134 int vstart, const char *str,
135 int dstart, int point, int mark)
139 /* Some fonts and Xmb/utf8 routines don't work well with dstart!=0. */
148 point-=vstart+dstart;
151 tx=grbrush_get_text_width(WEDLN_BRUSH(wedln), str+vstart, dstart);
153 grbrush_begin(WEDLN_BRUSH(wedln), geom, GRBRUSH_AMEND|GRBRUSH_NEED_CLIP);
155 wedln_do_draw_str_box(wedln, geom, str+vstart+dstart, point, mark, tx);
157 grbrush_end(WEDLN_BRUSH(wedln));
161 static bool wedln_update_cursor(WEdln *wedln, int iw)
164 int vstart=wedln->vstart;
165 int point=wedln->edln.point;
166 int len=wedln->edln.psize;
167 int mark=wedln->edln.mark;
168 const char *str=wedln->edln.p;
171 if(point<wedln->vstart)
174 if(wedln->vstart==point)
179 cx=grbrush_get_text_width(WEDLN_BRUSH(wedln), str+vstart,
181 cx+=grbrush_get_text_width(WEDLN_BRUSH(wedln), " ", 1);
183 int nxt=str_nextoff(str, point);
184 cx=grbrush_get_text_width(WEDLN_BRUSH(wedln), str+vstart,
191 l=str_nextoff(str, vstart);
197 ret=(wedln->vstart!=vstart);
198 wedln->vstart=vstart;
207 /*{{{ Size/location calc */
210 static int get_textarea_height(WEdln *wedln, bool with_spacing)
215 grbrush_get_border_widths(WEDLN_BRUSH(wedln), &bdw);
216 grbrush_get_font_extents(WEDLN_BRUSH(wedln), &fnte);
218 return (fnte.max_height+bdw.top+bdw.bottom+
219 (with_spacing ? bdw.spacing : 0));
223 enum{G_NORESET, G_MAX, G_CURRENT};
226 static void get_geom(WEdln *wedln, int mode, WRectangle *geom)
229 *geom=wedln->input.last_fp.g;
230 else if(mode==G_CURRENT)
231 *geom=REGION_GEOM(wedln);
235 static void get_completions_geom(WEdln *wedln, int mode, WRectangle *geom)
237 get_geom(wedln, mode, geom);
241 geom->h-=get_textarea_height(wedln, TRUE);
247 static void get_outer_geom(WEdln *wedln, int mode, WRectangle *geom)
251 get_geom(wedln, mode, geom);
255 th=get_textarea_height(wedln, FALSE);
262 static void get_inner_geom(WEdln *wedln, int mode, WRectangle *geom)
266 grbrush_get_border_widths(WEDLN_BRUSH(wedln), &bdw);
268 get_outer_geom(wedln, mode, geom);
271 geom->w-=bdw.left+bdw.right;
273 geom->h-=bdw.top+bdw.bottom;
274 geom->w=maxof(0, geom->w);
275 geom->h=maxof(0, geom->h);
279 static void get_textarea_geom(WEdln *wedln, int mode, WRectangle *geom)
281 get_inner_geom(wedln, mode, geom);
282 geom->x+=wedln->prompt_w;
283 geom->w=maxof(0, geom->w - wedln->prompt_w - wedln->info_w);
287 static void wedln_calc_size(WEdln *wedln, WRectangle *geom)
291 WRectangle max_geom=*geom, tageom;
293 if(WEDLN_BRUSH(wedln)==NULL)
296 if(wedln->prompt!=NULL){
297 wedln->prompt_w=grbrush_get_text_width(WEDLN_BRUSH(wedln),
302 if(wedln->info!=NULL){
303 wedln->info_w=grbrush_get_text_width(WEDLN_BRUSH(wedln),
308 th=get_textarea_height(wedln, wedln->compl_list.strs!=NULL);
310 if(wedln->compl_list.strs==NULL){
311 if(max_geom.h<th || !(wedln->input.last_fp.mode®ION_FIT_BOUNDS))
318 get_completions_geom(wedln, G_MAX, &g);
320 fit_listing(WEDLN_BRUSH(wedln), &g, &(wedln->compl_list));
322 grbrush_get_border_widths(WEDLN_BRUSH(wedln), &bdw);
324 h=wedln->compl_list.toth;
325 th+=bdw.top+bdw.bottom;
327 if(h+th>max_geom.h || !(wedln->input.last_fp.mode®ION_FIT_BOUNDS))
333 geom->y=max_geom.y+max_geom.h-geom->h;
337 get_textarea_geom(wedln, G_NORESET, &tageom);
338 wedln_update_cursor(wedln, tageom.w);
348 void wedln_draw_completions(WEdln *wedln, bool complete)
352 if(wedln->compl_list.strs!=NULL && WEDLN_BRUSH(wedln)!=NULL){
353 const char *style=(REGION_IS_ACTIVE(wedln)
356 const char *selstyle=(REGION_IS_ACTIVE(wedln)
358 : "inactive-selection");
360 get_completions_geom(wedln, G_CURRENT, &geom);
362 draw_listing(WEDLN_BRUSH(wedln), &geom, &(wedln->compl_list),
363 complete, style, selstyle);
368 void wedln_draw_textarea(WEdln *wedln)
372 const char *style=(REGION_IS_ACTIVE(wedln) ? "active" : "inactive");
374 if(WEDLN_BRUSH(wedln)==NULL)
377 get_outer_geom(wedln, G_CURRENT, &geom);
379 /*grbrush_begin(WEDLN_BRUSH(wedln), &geom, GRBRUSH_AMEND);*/
381 grbrush_draw_border(WEDLN_BRUSH(wedln), &geom, style);
383 get_inner_geom(wedln, G_CURRENT, &geom);
385 ty=calc_text_y(wedln, &geom);
387 if(wedln->prompt!=NULL){
388 const char *promptstyle=(REGION_IS_ACTIVE(wedln)
390 : "inactive-prompt");
391 grbrush_draw_string(WEDLN_BRUSH(wedln), geom.x, ty,
392 wedln->prompt, wedln->prompt_len, TRUE,
396 if(wedln->info!=NULL){
397 int x=geom.x+geom.w-wedln->info_w;
398 const char *promptstyle=(REGION_IS_ACTIVE(wedln)
399 ? "active-prompt-info"
400 : "inactive-prompt-info");
401 grbrush_draw_string(WEDLN_BRUSH(wedln), x, ty,
402 wedln->info, wedln->info_len, TRUE,
406 get_textarea_geom(wedln, G_CURRENT, &geom);
408 wedln_draw_str_box(wedln, &geom, wedln->vstart, wedln->edln.p, 0,
409 wedln->edln.point, wedln->edln.mark);
411 /*grbrush_end(WEDLN_BRUSH(wedln));*/
415 void wedln_draw(WEdln *wedln, bool complete)
418 int f=(complete ? 0 : GRBRUSH_NO_CLEAR_OK);
420 if(WEDLN_BRUSH(wedln)==NULL)
423 get_geom(wedln, G_CURRENT, &g);
425 grbrush_begin(WEDLN_BRUSH(wedln), &g, f);
427 wedln_draw_completions(wedln, FALSE);
428 wedln_draw_textarea(wedln);
430 grbrush_end(WEDLN_BRUSH(wedln));
440 static void wedln_set_info(WEdln *wedln, const char *info)
445 if(wedln->info!=NULL){
453 wedln->info=scat3(" [", info, "]");
454 if(wedln->info!=NULL){
455 wedln->info_len=strlen(wedln->info);
456 if(WEDLN_BRUSH(wedln)!=NULL){
457 wedln->info_w=grbrush_get_text_width(WEDLN_BRUSH(wedln),
464 get_textarea_geom(wedln, G_CURRENT, &tageom);
465 wedln_update_cursor(wedln, tageom.w);
467 wedln_draw_textarea(wedln);
477 static void wedln_show_completions(WEdln *wedln, char **strs, int nstrs,
480 int w=REGION_GEOM(wedln).w;
481 int h=REGION_GEOM(wedln).h;
483 if(WEDLN_BRUSH(wedln)==NULL)
486 setup_listing(&(wedln->compl_list), strs, nstrs, FALSE);
487 wedln->compl_list.selected_str=selected;
489 input_refit((WInput*)wedln);
490 if(w==REGION_GEOM(wedln).w && h==REGION_GEOM(wedln).h)
491 wedln_draw_completions(wedln, TRUE);
495 void wedln_hide_completions(WEdln *wedln)
497 if(wedln->compl_list.strs!=NULL){
498 deinit_listing(&(wedln->compl_list));
499 input_refit((WInput*)wedln);
504 void wedln_scrollup_completions(WEdln *wedln)
506 if(wedln->compl_list.strs==NULL)
508 if(scrollup_listing(&(wedln->compl_list)))
509 wedln_draw_completions(wedln, TRUE);
513 void wedln_scrolldown_completions(WEdln *wedln)
515 if(wedln->compl_list.strs==NULL)
517 if(scrolldown_listing(&(wedln->compl_list)))
518 wedln_draw_completions(wedln, TRUE);
522 static int update_nocompl=0;
525 static void free_completions(char **ptr, int i)
535 bool wedln_do_set_completions(WEdln *wedln, char **ptr, int n,
536 char *beg, char *end, int cycle,
541 if(wedln->compl_beg!=NULL)
542 free(wedln->compl_beg);
544 if(wedln->compl_end!=NULL)
545 free(wedln->compl_end);
547 wedln->compl_beg=beg;
548 wedln->compl_end=end;
549 wedln->compl_current_id=-1;
551 n=edln_do_completions(&(wedln->edln), ptr, n, beg, end,
552 !mod_query_config.autoshowcompl, nosort);
554 if(mod_query_config.autoshowcompl && n>0 && cycle!=0){
556 sel=(cycle>0 ? 0 : n-1);
557 edln_set_completion(&(wedln->edln), ptr[sel], beg, end);
561 if(n>1 || (mod_query_config.autoshowcompl && n>0)){
562 wedln_show_completions(wedln, ptr, n, sel);
566 free_completions(ptr, n);
572 void wedln_set_completions(WEdln *wedln, ExtlTab completions, int cycle)
575 char **ptr=NULL, *beg=NULL, *end=NULL, *p=NULL;
577 n=extl_table_get_n(completions);
580 wedln_hide_completions(wedln);
584 ptr=ALLOC_N(char*, n);
589 if(!extl_table_geti_s(completions, i+1, &p)){
595 extl_table_gets_s(completions, "common_beg", &beg);
596 extl_table_gets_s(completions, "common_end", &end);
598 if(wedln_do_set_completions(wedln, ptr, n, beg, end, cycle, FALSE))
602 wedln_hide_completions(wedln);
603 free_completions(ptr, i);
606 /* edln_do_completions may NULL things */
614 static void wedln_do_select_completion(WEdln *wedln, int n)
616 bool complredraw=listing_select(&(wedln->compl_list), n);
617 wedln_draw_completions(wedln, complredraw);
620 edln_set_completion(&(wedln->edln), wedln->compl_list.strs[n],
621 wedln->compl_beg, wedln->compl_end);
627 static ExtlExportedFn *sc_safe_fns[]={
628 (ExtlExportedFn*)&complproxy_set_completions,
633 static ExtlSafelist sc_safelist=EXTL_SAFELIST_INIT(sc_safe_fns);
636 static int wedln_alloc_compl_id(WEdln *wedln)
638 int id=wedln->compl_waiting_id+1;
639 wedln->compl_waiting_id=maxof(0, wedln->compl_waiting_id+1);
643 static bool wedln_do_call_completor(WEdln *wedln, int id, int cycle)
645 if(wedln->compl_history_mode){
649 wedln->compl_waiting_id=id;
651 n=edln_history_matches(&wedln->edln, &h);
654 wedln_hide_completions(wedln);
658 if(wedln_do_set_completions(wedln, h, n, NULL, NULL, cycle, TRUE)){
659 wedln->compl_current_id=id;
665 const char *p=wedln->edln.p;
666 int point=wedln->edln.point;
667 WComplProxy *proxy=create_complproxy(wedln, id, cycle);
672 /* Set Lua-side to own the proxy: it gets freed by Lua's GC */
673 ((Obj*)proxy)->flags|=OBJ_EXTL_OWNED;
680 extl_protect(&sc_safelist);
681 extl_call(wedln->completor, "osi", NULL, proxy, p, point);
682 extl_unprotect(&sc_safelist);
689 static void timed_complete(WTimer *tmr, Obj *obj)
691 WEdln *wedln=(WEdln*)obj;
694 int id=wedln->compl_timed_id;
695 wedln->compl_timed_id=-1;
696 if(id==wedln->compl_waiting_id && id>=0)
697 wedln_do_call_completor((WEdln*)obj, id, FALSE);
703 * Select next completion.
706 bool wedln_next_completion(WEdln *wedln)
710 if(wedln->compl_current_id!=wedln->compl_waiting_id)
713 if(wedln->compl_list.nstrs<=0)
716 if(wedln->compl_list.selected_str<0 ||
717 wedln->compl_list.selected_str+1>=wedln->compl_list.nstrs){
720 n=wedln->compl_list.selected_str+1;
723 if(n!=wedln->compl_list.selected_str)
724 wedln_do_select_completion(wedln, n);
731 * Select previous completion.
734 bool wedln_prev_completion(WEdln *wedln)
738 if(wedln->compl_current_id!=wedln->compl_waiting_id)
741 if(wedln->compl_list.nstrs<=0)
744 if(wedln->compl_list.selected_str<=0){
745 n=wedln->compl_list.nstrs-1;
747 n=wedln->compl_list.selected_str-1;
750 if(n!=wedln->compl_list.selected_str)
751 wedln_do_select_completion(wedln, n);
758 * Call completion handler with the text between the beginning of line and
759 * current cursor position, or select next/previous completion from list if in
760 * auto-show-completions mode and \var{cycle} is set to ``next'' or ``prev'',
761 * respectively. The \var{mode} may be ``history'' or ``normal''. If it is
762 * not set, the previous mode is used. Normally next entry is not cycled to
763 * despite the setting of \var{cycle} if mode switch occurs. To override
764 * this, use ``next-always'' and ``prev-always'' for \var{cycle}.
767 void wedln_complete(WEdln *wedln, const char *cycle, const char *mode)
773 if(strcmp(mode, "history")==0){
774 valid=wedln->compl_history_mode;
775 wedln->compl_history_mode=TRUE;
776 }else if(strcmp(mode, "normal")==0){
777 valid=!wedln->compl_history_mode;
778 wedln->compl_history_mode=FALSE;
781 wedln_set_info(wedln,
782 (wedln->compl_history_mode
789 if((valid && strcmp(cycle, "next")==0) ||
790 strcmp(cycle, "next-always")==0){
792 }else if((valid && strcmp(cycle, "prev")==0) ||
793 strcmp(cycle, "prev-always")==0){
798 if(valid && cyclei!=0 && mod_query_config.autoshowcompl &&
799 wedln->compl_list.nstrs>0){
801 wedln_next_completion(wedln);
803 wedln_prev_completion(wedln);
805 int oldid=wedln->compl_waiting_id;
807 if(!wedln_do_call_completor(wedln, wedln_alloc_compl_id(wedln),
809 wedln->compl_waiting_id=oldid;
816 * Get history completion mode.
819 bool wedln_is_histcompl(WEdln *wedln)
821 return wedln->compl_history_mode;
828 /*{{{ Update handler */
831 static void wedln_update_handler(WEdln *wedln, int from, int flags)
835 if(WEDLN_BRUSH(wedln)==NULL)
838 get_textarea_geom(wedln, G_CURRENT, &geom);
840 if(flags&EDLN_UPDATE_NEW)
843 if(flags&EDLN_UPDATE_MOVED){
844 if(wedln_update_cursor(wedln, geom.w))
848 from=maxof(0, from-wedln->vstart);
850 wedln_draw_str_box(wedln, &geom, wedln->vstart, wedln->edln.p, from,
851 wedln->edln.point, wedln->edln.mark);
853 if(update_nocompl==0 &&
854 mod_query_config.autoshowcompl &&
855 flags&EDLN_UPDATE_CHANGED){
856 wedln->compl_current_id=-1; /* invalidate */
857 if(wedln->autoshowcompl_timer==NULL)
858 wedln->autoshowcompl_timer=create_timer();
859 if(wedln->autoshowcompl_timer!=NULL){
860 wedln->compl_timed_id=wedln_alloc_compl_id(wedln);
861 timer_set(wedln->autoshowcompl_timer,
862 mod_query_config.autoshowcompl_delay,
863 timed_complete, (Obj*)wedln);
872 /*{{{ Init, deinit and config update */
875 static bool wedln_init_prompt(WEdln *wedln, const char *prompt)
886 wedln->prompt_len=strlen(p);
897 static bool wedln_init(WEdln *wedln, WWindow *par, const WFitParams *fp,
898 WEdlnCreateParams *params)
902 if(!wedln_init_prompt(wedln, params->prompt))
905 if(!edln_init(&(wedln->edln), params->dflt)){
910 wedln->handler=extl_fn_none();
911 wedln->completor=extl_fn_none();
913 wedln->edln.uiptr=wedln;
914 wedln->edln.ui_update=(EdlnUpdateHandler*)wedln_update_handler;
916 wedln->autoshowcompl_timer=NULL;
918 init_listing(&(wedln->compl_list));
920 wedln->compl_waiting_id=-1;
921 wedln->compl_current_id=-1;
922 wedln->compl_timed_id=-1;
923 wedln->compl_beg=NULL;
924 wedln->compl_end=NULL;
925 wedln->compl_tab=FALSE;
926 wedln->compl_history_mode=FALSE;
932 wedln->cycle_bindmap=NULL;
934 if(!input_init((WInput*)wedln, par, fp)){
935 edln_deinit(&(wedln->edln));
940 window_create_xic(&wedln->input.win);
942 wedln->handler=extl_ref_fn(params->handler);
943 wedln->completor=extl_ref_fn(params->completor);
945 region_add_bindmap((WRegion*)wedln, mod_query_wedln_bindmap);
951 WEdln *create_wedln(WWindow *par, const WFitParams *fp,
952 WEdlnCreateParams *params)
954 CREATEOBJ_IMPL(WEdln, wedln, (p, par, fp, params));
958 static void wedln_deinit(WEdln *wedln)
960 if(wedln->prompt!=NULL)
963 if(wedln->info!=NULL)
966 if(wedln->compl_beg!=NULL)
967 free(wedln->compl_beg);
969 if(wedln->compl_end!=NULL)
970 free(wedln->compl_end);
972 if(wedln->compl_list.strs!=NULL)
973 deinit_listing(&(wedln->compl_list));
975 if(wedln->autoshowcompl_timer!=NULL)
976 destroy_obj((Obj*)wedln->autoshowcompl_timer);
978 if(wedln->cycle_bindmap!=NULL)
979 bindmap_destroy(wedln->cycle_bindmap);
981 extl_unref_fn(wedln->completor);
982 extl_unref_fn(wedln->handler);
984 edln_deinit(&(wedln->edln));
985 input_deinit((WInput*)wedln);
989 static void wedln_do_finish(WEdln *wedln)
994 handler=wedln->handler;
995 wedln->handler=extl_fn_none();
996 p=edln_finish(&(wedln->edln));
998 if(region_manager_allows_destroying((WRegion*)wedln))
999 destroy_obj((Obj*)wedln);
1002 extl_call(handler, "s", NULL, p);
1005 extl_unref_fn(handler);
1010 * Close \var{wedln} and call any handlers.
1013 void wedln_finish(WEdln *wedln)
1015 mainloop_defer_action((Obj*)wedln, (WDeferredAction*)wedln_do_finish);
1026 * Request selection from application holding such.
1028 * Note that this function is asynchronous; the selection will not
1029 * actually be inserted before Ion receives it. This will be no
1030 * earlier than Ion return to its main loop.
1033 void wedln_paste(WEdln *wedln)
1035 ioncore_request_selection_for(wedln->input.win.win);
1039 void wedln_insstr(WEdln *wedln, const char *buf, size_t n)
1041 edln_insstr_n(&(wedln->edln), buf, n, TRUE, TRUE);
1045 static const char *wedln_style(WEdln *wedln)
1047 return "input-edln";
1054 /*{{{ Dynamic function table and class implementation */
1057 static DynFunTab wedln_dynfuntab[]={
1058 {window_draw, wedln_draw},
1059 {input_calc_size, wedln_calc_size},
1060 {input_scrollup, wedln_scrollup_completions},
1061 {input_scrolldown, wedln_scrolldown_completions},
1062 {window_insstr, wedln_insstr},
1063 {(DynFun*)input_style, (DynFun*)wedln_style},
1069 IMPLCLASS(WEdln, WInput, wedln_deinit, wedln_dynfuntab);