2 * ion/mod_query/wedln.c
4 * Copyright (c) Tuomo Valkonen 1999-2007.
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>
28 #include <ioncore/gr-util.h>
29 #include <ioncore/sizehint.h>
30 #include <ioncore/resize.h>
38 #define WEDLN_BRUSH(X) ((X)->input.brush)
41 /*{{{ Drawing primitives */
44 static int calc_text_y(WEdln *wedln, const WRectangle *geom)
48 grbrush_get_font_extents(WEDLN_BRUSH(wedln), &fnte);
50 return geom->y+geom->h/2-fnte.max_height/2+fnte.baseline;
54 static int wedln_draw_strsect(WEdln *wedln, const WRectangle *geom,
55 int x, int y, const char *str, int len,
61 grbrush_set_attr(WEDLN_BRUSH(wedln), a);
62 grbrush_draw_string(WEDLN_BRUSH(wedln), x, y, str, len, TRUE);
63 grbrush_unset_attr(WEDLN_BRUSH(wedln), a);
65 return grbrush_get_text_width(WEDLN_BRUSH(wedln), str, len);
69 static void dispu(const char* s, int l)
72 int c=(unsigned char)*s;
73 fprintf(stderr, "%d[%c]", c, *s);
77 fprintf(stderr, "\n");
81 #define DSTRSECT(LEN, A) \
83 tx+=wedln_draw_strsect(wedln, geom, geom->x+tx, ty, \
84 str, LEN, GR_ATTR(A)); \
92 GR_DEFATTR(selection);
97 static void init_attr()
100 GR_ALLOCATTR(active);
101 GR_ALLOCATTR(inactive);
102 GR_ALLOCATTR(normal);
103 GR_ALLOCATTR(selection);
104 GR_ALLOCATTR(cursor);
105 GR_ALLOCATTR(prompt);
111 static void wedln_do_draw_str_box(WEdln *wedln, const WRectangle *geom,
112 const char *str, int cursor,
115 int len=strlen(str), ll=0, ty=0;
117 ty=calc_text_y(wedln, geom);
121 DSTRSECT(mark, normal);
122 DSTRSECT(cursor-mark, selection);
124 DSTRSECT(cursor, normal);
127 tx+=wedln_draw_strsect(wedln, geom, geom->x+tx, ty,
128 " ", 1, GR_ATTR(cursor));
130 ll=str_nextoff(str, 0);
131 DSTRSECT(ll, cursor);
134 DSTRSECT(cursor, normal);
135 ll=str_nextoff(str, 0);
136 DSTRSECT(ll, cursor);
137 DSTRSECT(mark-cursor-ll, selection);
139 DSTRSECT(len, normal);
145 grbrush_clear_area(WEDLN_BRUSH(wedln), &g);
150 static void wedln_draw_str_box(WEdln *wedln, const WRectangle *geom,
151 int vstart, const char *str,
152 int dstart, int point, int mark)
156 /* Some fonts and Xmb/utf8 routines don't work well with dstart!=0. */
165 point-=vstart+dstart;
168 tx=grbrush_get_text_width(WEDLN_BRUSH(wedln), str+vstart, dstart);
170 grbrush_begin(WEDLN_BRUSH(wedln), geom,
171 GRBRUSH_AMEND|GRBRUSH_KEEP_ATTR|GRBRUSH_NEED_CLIP);
173 wedln_do_draw_str_box(wedln, geom, str+vstart+dstart, point, mark, tx);
175 grbrush_end(WEDLN_BRUSH(wedln));
179 static bool wedln_update_cursor(WEdln *wedln, int iw)
182 int vstart=wedln->vstart;
183 int point=wedln->edln.point;
184 int len=wedln->edln.psize;
185 int mark=wedln->edln.mark;
186 const char *str=wedln->edln.p;
189 if(point<wedln->vstart)
192 if(wedln->vstart==point)
197 cx=grbrush_get_text_width(WEDLN_BRUSH(wedln), str+vstart,
199 cx+=grbrush_get_text_width(WEDLN_BRUSH(wedln), " ", 1);
201 int nxt=str_nextoff(str, point);
202 cx=grbrush_get_text_width(WEDLN_BRUSH(wedln), str+vstart,
209 l=str_nextoff(str, vstart);
215 ret=(wedln->vstart!=vstart);
216 wedln->vstart=vstart;
225 /*{{{ Size/location calc */
228 static int get_textarea_height(WEdln *wedln, bool with_spacing)
232 if(WEDLN_BRUSH(wedln)!=NULL)
233 mod_query_get_minimum_extents(WEDLN_BRUSH(wedln), with_spacing, &w, &h);
239 enum{G_NORESET, G_MAX, G_CURRENT};
242 static void get_geom(WEdln *wedln, int mode, WRectangle *geom)
245 *geom=wedln->input.last_fp.g;
246 else if(mode==G_CURRENT)
247 *geom=REGION_GEOM(wedln);
251 static void get_completions_geom(WEdln *wedln, int mode, WRectangle *geom)
253 get_geom(wedln, mode, geom);
257 geom->h-=get_textarea_height(wedln, TRUE);
263 static void get_outer_geom(WEdln *wedln, int mode, WRectangle *geom)
267 get_geom(wedln, mode, geom);
271 th=get_textarea_height(wedln, FALSE);
278 static void get_inner_geom(WEdln *wedln, int mode, WRectangle *geom)
282 grbrush_get_border_widths(WEDLN_BRUSH(wedln), &bdw);
284 get_outer_geom(wedln, mode, geom);
287 geom->w-=bdw.left+bdw.right;
289 geom->h-=bdw.top+bdw.bottom;
290 geom->w=maxof(0, geom->w);
291 geom->h=maxof(0, geom->h);
295 static void get_textarea_geom(WEdln *wedln, int mode, WRectangle *geom)
297 get_inner_geom(wedln, mode, geom);
298 geom->x+=wedln->prompt_w;
299 geom->w=maxof(0, geom->w - wedln->prompt_w - wedln->info_w);
303 static void wedln_calc_size(WEdln *wedln, WRectangle *geom)
307 WRectangle max_geom=*geom, tageom;
309 if(WEDLN_BRUSH(wedln)==NULL)
312 if(wedln->prompt!=NULL){
313 wedln->prompt_w=grbrush_get_text_width(WEDLN_BRUSH(wedln),
318 if(wedln->info!=NULL){
319 wedln->info_w=grbrush_get_text_width(WEDLN_BRUSH(wedln),
324 th=get_textarea_height(wedln, wedln->compl_list.strs!=NULL);
326 if(wedln->compl_list.strs==NULL){
327 if(max_geom.h<th || !(wedln->input.last_fp.mode®ION_FIT_BOUNDS))
334 get_completions_geom(wedln, G_MAX, &g);
336 fit_listing(WEDLN_BRUSH(wedln), &g, &(wedln->compl_list));
338 grbrush_get_border_widths(WEDLN_BRUSH(wedln), &bdw);
340 h=wedln->compl_list.toth;
341 th+=bdw.top+bdw.bottom;
343 if(h+th>max_geom.h || !(wedln->input.last_fp.mode®ION_FIT_BOUNDS))
349 geom->y=max_geom.y+max_geom.h-geom->h;
353 get_textarea_geom(wedln, G_NORESET, &tageom);
354 wedln_update_cursor(wedln, tageom.w);
358 void wedln_size_hints(WEdln *wedln, WSizeHints *hints_ret)
362 if(WEDLN_BRUSH(wedln)!=NULL){
363 mod_query_get_minimum_extents(WEDLN_BRUSH(wedln), FALSE, &w, &h);
364 w+=wedln->prompt_w+wedln->info_w;
365 w+=grbrush_get_text_width(WEDLN_BRUSH(wedln), "xxxxxxxxxx", 10);
368 hints_ret->min_set=TRUE;
369 hints_ret->min_width=w;
370 hints_ret->min_height=h;
380 void wedln_draw_completions(WEdln *wedln, bool complete)
384 if(wedln->compl_list.strs!=NULL && WEDLN_BRUSH(wedln)!=NULL){
385 get_completions_geom(wedln, G_CURRENT, &geom);
387 draw_listing(WEDLN_BRUSH(wedln), &geom, &(wedln->compl_list),
388 complete, GR_ATTR(selection));
393 void wedln_draw_textarea(WEdln *wedln)
398 if(WEDLN_BRUSH(wedln)==NULL)
401 get_outer_geom(wedln, G_CURRENT, &geom);
403 /*grbrush_begin(WEDLN_BRUSH(wedln), &geom, GRBRUSH_AMEND);*/
405 grbrush_draw_border(WEDLN_BRUSH(wedln), &geom);
407 get_inner_geom(wedln, G_CURRENT, &geom);
409 ty=calc_text_y(wedln, &geom);
411 grbrush_set_attr(WEDLN_BRUSH(wedln), GR_ATTR(prompt));
413 if(wedln->prompt!=NULL){
414 grbrush_draw_string(WEDLN_BRUSH(wedln), geom.x, ty,
415 wedln->prompt, wedln->prompt_len, TRUE);
418 if(wedln->info!=NULL){
419 int x=geom.x+geom.w-wedln->info_w;
421 grbrush_set_attr(WEDLN_BRUSH(wedln), GR_ATTR(info));
422 grbrush_draw_string(WEDLN_BRUSH(wedln), x, ty,
423 wedln->info, wedln->info_len, TRUE);
424 grbrush_unset_attr(WEDLN_BRUSH(wedln), GR_ATTR(info));
427 grbrush_unset_attr(WEDLN_BRUSH(wedln), GR_ATTR(prompt));
429 get_textarea_geom(wedln, G_CURRENT, &geom);
431 wedln_draw_str_box(wedln, &geom, wedln->vstart, wedln->edln.p, 0,
432 wedln->edln.point, wedln->edln.mark);
434 /*grbrush_end(WEDLN_BRUSH(wedln));*/
438 static void wedln_draw_(WEdln *wedln, bool complete, bool completions)
441 int f=(complete ? 0 : GRBRUSH_NO_CLEAR_OK);
443 if(WEDLN_BRUSH(wedln)==NULL)
446 get_geom(wedln, G_CURRENT, &g);
448 grbrush_begin(WEDLN_BRUSH(wedln), &g, f);
450 grbrush_set_attr(WEDLN_BRUSH(wedln), REGION_IS_ACTIVE(wedln)
452 : GR_ATTR(inactive));
455 wedln_draw_completions(wedln, FALSE);
457 wedln_draw_textarea(wedln);
459 grbrush_end(WEDLN_BRUSH(wedln));
463 void wedln_draw(WEdln *wedln, bool complete)
465 wedln_draw_(wedln, complete, TRUE);
474 static void wedln_set_info(WEdln *wedln, const char *info)
479 if(wedln->info!=NULL){
487 wedln->info=scat3(" [", info, "]");
488 if(wedln->info!=NULL){
489 wedln->info_len=strlen(wedln->info);
490 if(WEDLN_BRUSH(wedln)!=NULL){
491 wedln->info_w=grbrush_get_text_width(WEDLN_BRUSH(wedln),
498 get_textarea_geom(wedln, G_CURRENT, &tageom);
499 wedln_update_cursor(wedln, tageom.w);
501 wedln_draw_(wedln, FALSE, FALSE);
511 static void wedln_show_completions(WEdln *wedln, char **strs, int nstrs,
514 int w=REGION_GEOM(wedln).w;
515 int h=REGION_GEOM(wedln).h;
517 if(WEDLN_BRUSH(wedln)==NULL)
520 setup_listing(&(wedln->compl_list), strs, nstrs, FALSE);
521 wedln->compl_list.selected_str=selected;
523 input_refit((WInput*)wedln);
524 if(w==REGION_GEOM(wedln).w && h==REGION_GEOM(wedln).h)
525 wedln_draw_completions(wedln, TRUE);
529 void wedln_hide_completions(WEdln *wedln)
531 if(wedln->compl_list.strs!=NULL){
532 deinit_listing(&(wedln->compl_list));
533 input_refit((WInput*)wedln);
538 void wedln_scrollup_completions(WEdln *wedln)
540 if(wedln->compl_list.strs==NULL)
542 if(scrollup_listing(&(wedln->compl_list)))
543 wedln_draw_completions(wedln, TRUE);
547 void wedln_scrolldown_completions(WEdln *wedln)
549 if(wedln->compl_list.strs==NULL)
551 if(scrolldown_listing(&(wedln->compl_list)))
552 wedln_draw_completions(wedln, TRUE);
556 static int update_nocompl=0;
559 static void free_completions(char **ptr, int i)
570 bool wedln_do_set_completions(WEdln *wedln, char **ptr, int n,
571 char *beg, char *end, int cycle,
576 if(wedln->compl_beg!=NULL)
577 free(wedln->compl_beg);
579 if(wedln->compl_end!=NULL)
580 free(wedln->compl_end);
582 wedln->compl_beg=beg;
583 wedln->compl_end=end;
584 wedln->compl_current_id=-1;
586 n=edln_do_completions(&(wedln->edln), ptr, n, beg, end,
587 !mod_query_config.autoshowcompl, nosort);
589 if(mod_query_config.autoshowcompl && n>0 && cycle!=0){
591 sel=(cycle>0 ? 0 : n-1);
592 edln_set_completion(&(wedln->edln), ptr[sel], beg, end);
596 if(n>1 || (mod_query_config.autoshowcompl && n>0)){
597 wedln_show_completions(wedln, ptr, n, sel);
601 free_completions(ptr, n);
607 void wedln_set_completions(WEdln *wedln, ExtlTab completions, int cycle)
610 char **ptr=NULL, *beg=NULL, *end=NULL, *p=NULL;
612 n=extl_table_get_n(completions);
615 wedln_hide_completions(wedln);
619 ptr=ALLOC_N(char*, n);
624 if(!extl_table_geti_s(completions, i+1, &p)){
630 extl_table_gets_s(completions, "common_beg", &beg);
631 extl_table_gets_s(completions, "common_end", &end);
633 if(!wedln_do_set_completions(wedln, ptr, n, beg, end, cycle, FALSE))
634 wedln_hide_completions(wedln);
639 wedln_hide_completions(wedln);
640 free_completions(ptr, i);
644 static void wedln_do_select_completion(WEdln *wedln, int n)
646 bool complredraw=listing_select(&(wedln->compl_list), n);
647 wedln_draw_completions(wedln, complredraw);
650 edln_set_completion(&(wedln->edln), wedln->compl_list.strs[n],
651 wedln->compl_beg, wedln->compl_end);
657 static ExtlExportedFn *sc_safe_fns[]={
658 (ExtlExportedFn*)&complproxy_set_completions,
663 static ExtlSafelist sc_safelist=EXTL_SAFELIST_INIT(sc_safe_fns);
666 static int wedln_alloc_compl_id(WEdln *wedln)
668 int id=wedln->compl_waiting_id+1;
669 wedln->compl_waiting_id=maxof(0, wedln->compl_waiting_id+1);
673 static bool wedln_do_call_completor(WEdln *wedln, int id, int cycle)
675 if(wedln->compl_history_mode){
679 wedln->compl_waiting_id=id;
681 n=edln_history_matches(&wedln->edln, &h);
684 wedln_hide_completions(wedln);
688 if(wedln_do_set_completions(wedln, h, n, NULL, NULL, cycle, TRUE)){
689 wedln->compl_current_id=id;
695 const char *p=wedln->edln.p;
696 int point=wedln->edln.point;
697 WComplProxy *proxy=create_complproxy(wedln, id, cycle);
702 /* Set Lua-side to own the proxy: it gets freed by Lua's GC */
703 ((Obj*)proxy)->flags|=OBJ_EXTL_OWNED;
710 extl_protect(&sc_safelist);
711 extl_call(wedln->completor, "osi", NULL, proxy, p, point);
712 extl_unprotect(&sc_safelist);
719 static void timed_complete(WTimer *tmr, Obj *obj)
721 WEdln *wedln=(WEdln*)obj;
724 int id=wedln->compl_timed_id;
725 wedln->compl_timed_id=-1;
726 if(id==wedln->compl_waiting_id && id>=0)
727 wedln_do_call_completor((WEdln*)obj, id, FALSE);
733 * Select next completion.
736 bool wedln_next_completion(WEdln *wedln)
740 if(wedln->compl_current_id!=wedln->compl_waiting_id)
743 if(wedln->compl_list.nstrs<=0)
746 if(wedln->compl_list.selected_str<0 ||
747 wedln->compl_list.selected_str+1>=wedln->compl_list.nstrs){
750 n=wedln->compl_list.selected_str+1;
753 if(n!=wedln->compl_list.selected_str)
754 wedln_do_select_completion(wedln, n);
761 * Select previous completion.
764 bool wedln_prev_completion(WEdln *wedln)
768 if(wedln->compl_current_id!=wedln->compl_waiting_id)
771 if(wedln->compl_list.nstrs<=0)
774 if(wedln->compl_list.selected_str<=0){
775 n=wedln->compl_list.nstrs-1;
777 n=wedln->compl_list.selected_str-1;
780 if(n!=wedln->compl_list.selected_str)
781 wedln_do_select_completion(wedln, n);
788 * Call completion handler with the text between the beginning of line and
789 * current cursor position, or select next/previous completion from list if in
790 * auto-show-completions mode and \var{cycle} is set to ``next'' or ``prev'',
791 * respectively. The \var{mode} may be ``history'' or ``normal''. If it is
792 * not set, the previous mode is used. Normally next entry is not cycled to
793 * despite the setting of \var{cycle} if mode switch occurs. To override
794 * this, use ``next-always'' and ``prev-always'' for \var{cycle}.
797 void wedln_complete(WEdln *wedln, const char *cycle, const char *mode)
803 if(strcmp(mode, "history")==0){
804 valid=wedln->compl_history_mode;
805 wedln->compl_history_mode=TRUE;
806 }else if(strcmp(mode, "normal")==0){
807 valid=!wedln->compl_history_mode;
808 wedln->compl_history_mode=FALSE;
811 wedln_set_info(wedln,
812 (wedln->compl_history_mode
819 if((valid && strcmp(cycle, "next")==0) ||
820 strcmp(cycle, "next-always")==0){
822 }else if((valid && strcmp(cycle, "prev")==0) ||
823 strcmp(cycle, "prev-always")==0){
828 if(valid && cyclei!=0 && mod_query_config.autoshowcompl &&
829 wedln->compl_list.nstrs>0){
831 wedln_next_completion(wedln);
833 wedln_prev_completion(wedln);
835 int oldid=wedln->compl_waiting_id;
837 if(!wedln_do_call_completor(wedln, wedln_alloc_compl_id(wedln),
839 wedln->compl_waiting_id=oldid;
846 * Get history completion mode.
849 bool wedln_is_histcompl(WEdln *wedln)
851 return wedln->compl_history_mode;
858 /*{{{ Update handler */
861 static void wedln_update_handler(WEdln *wedln, int from, int flags)
865 if(WEDLN_BRUSH(wedln)==NULL)
868 get_textarea_geom(wedln, G_CURRENT, &geom);
870 if(flags&EDLN_UPDATE_NEW)
873 if(flags&EDLN_UPDATE_MOVED){
874 if(wedln_update_cursor(wedln, geom.w))
878 from=maxof(0, from-wedln->vstart);
880 wedln_draw_str_box(wedln, &geom, wedln->vstart, wedln->edln.p, from,
881 wedln->edln.point, wedln->edln.mark);
883 if(update_nocompl==0 &&
884 mod_query_config.autoshowcompl &&
885 flags&EDLN_UPDATE_CHANGED){
886 wedln->compl_current_id=-1; /* invalidate */
887 if(wedln->autoshowcompl_timer==NULL)
888 wedln->autoshowcompl_timer=create_timer();
889 if(wedln->autoshowcompl_timer!=NULL){
890 wedln->compl_timed_id=wedln_alloc_compl_id(wedln);
891 timer_set(wedln->autoshowcompl_timer,
892 mod_query_config.autoshowcompl_delay,
893 timed_complete, (Obj*)wedln);
902 /*{{{ Init, deinit and config update */
905 static bool wedln_init_prompt(WEdln *wedln, const char *prompt)
916 wedln->prompt_len=strlen(p);
927 static bool wedln_init(WEdln *wedln, WWindow *par, const WFitParams *fp,
928 WEdlnCreateParams *params)
934 if(!wedln_init_prompt(wedln, params->prompt))
937 if(!edln_init(&(wedln->edln), params->dflt)){
942 wedln->handler=extl_fn_none();
943 wedln->completor=extl_fn_none();
945 wedln->edln.uiptr=wedln;
946 wedln->edln.ui_update=(EdlnUpdateHandler*)wedln_update_handler;
948 wedln->autoshowcompl_timer=NULL;
950 init_listing(&(wedln->compl_list));
952 wedln->compl_waiting_id=-1;
953 wedln->compl_current_id=-1;
954 wedln->compl_timed_id=-1;
955 wedln->compl_beg=NULL;
956 wedln->compl_end=NULL;
957 wedln->compl_tab=FALSE;
958 wedln->compl_history_mode=FALSE;
964 wedln->cycle_bindmap=NULL;
966 if(!input_init((WInput*)wedln, par, fp)){
967 edln_deinit(&(wedln->edln));
972 window_create_xic(&wedln->input.win);
974 wedln->handler=extl_ref_fn(params->handler);
975 wedln->completor=extl_ref_fn(params->completor);
977 region_add_bindmap((WRegion*)wedln, mod_query_wedln_bindmap);
983 WEdln *create_wedln(WWindow *par, const WFitParams *fp,
984 WEdlnCreateParams *params)
986 CREATEOBJ_IMPL(WEdln, wedln, (p, par, fp, params));
990 static void wedln_deinit(WEdln *wedln)
992 if(wedln->prompt!=NULL)
995 if(wedln->info!=NULL)
998 if(wedln->compl_beg!=NULL)
999 free(wedln->compl_beg);
1001 if(wedln->compl_end!=NULL)
1002 free(wedln->compl_end);
1004 if(wedln->compl_list.strs!=NULL)
1005 deinit_listing(&(wedln->compl_list));
1007 if(wedln->autoshowcompl_timer!=NULL)
1008 destroy_obj((Obj*)wedln->autoshowcompl_timer);
1010 if(wedln->cycle_bindmap!=NULL)
1011 bindmap_destroy(wedln->cycle_bindmap);
1013 extl_unref_fn(wedln->completor);
1014 extl_unref_fn(wedln->handler);
1016 edln_deinit(&(wedln->edln));
1017 input_deinit((WInput*)wedln);
1021 static void wedln_do_finish(WEdln *wedln)
1026 handler=wedln->handler;
1027 wedln->handler=extl_fn_none();
1028 p=edln_finish(&(wedln->edln));
1030 region_rqdispose((WRegion*)wedln);
1033 extl_call(handler, "s", NULL, p);
1036 extl_unref_fn(handler);
1041 * Close \var{wedln} and call any handlers.
1044 void wedln_finish(WEdln *wedln)
1046 mainloop_defer_action((Obj*)wedln, (WDeferredAction*)wedln_do_finish);
1057 * Request selection from application holding such.
1059 * Note that this function is asynchronous; the selection will not
1060 * actually be inserted before Ion receives it. This will be no
1061 * earlier than Ion return to its main loop.
1064 void wedln_paste(WEdln *wedln)
1066 ioncore_request_selection_for(wedln->input.win.win);
1070 void wedln_insstr(WEdln *wedln, const char *buf, size_t n)
1072 edln_insstr_n(&(wedln->edln), buf, n, TRUE, TRUE);
1076 static const char *wedln_style(WEdln *wedln)
1078 return "input-edln";
1085 /*{{{ Dynamic function table and class implementation */
1088 static DynFunTab wedln_dynfuntab[]={
1089 {window_draw, wedln_draw},
1090 {input_calc_size, wedln_calc_size},
1091 {input_scrollup, wedln_scrollup_completions},
1092 {input_scrolldown, wedln_scrolldown_completions},
1093 {window_insstr, wedln_insstr},
1094 {(DynFun*)input_style, (DynFun*)wedln_style},
1095 {region_size_hints, wedln_size_hints},
1101 IMPLCLASS(WEdln, WInput, wedln_deinit, wedln_dynfuntab);