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>
36 #define WEDLN_BRUSH(X) ((X)->input.brush)
39 /*{{{ Drawing primitives */
42 static int calc_text_y(WEdln *wedln, const WRectangle *geom)
46 grbrush_get_font_extents(WEDLN_BRUSH(wedln), &fnte);
48 return geom->y+geom->h/2-fnte.max_height/2+fnte.baseline;
52 static int wedln_draw_strsect(WEdln *wedln, const WRectangle *geom,
53 int x, int y, const char *str, int len,
59 grbrush_set_attr(WEDLN_BRUSH(wedln), a);
60 grbrush_draw_string(WEDLN_BRUSH(wedln), x, y, str, len, TRUE);
61 grbrush_unset_attr(WEDLN_BRUSH(wedln), a);
63 return grbrush_get_text_width(WEDLN_BRUSH(wedln), str, len);
67 static void dispu(const char* s, int l)
70 int c=(unsigned char)*s;
71 fprintf(stderr, "%d[%c]", c, *s);
75 fprintf(stderr, "\n");
79 #define DSTRSECT(LEN, A) \
81 tx+=wedln_draw_strsect(wedln, geom, geom->x+tx, ty, \
82 str, LEN, GR_ATTR(A)); \
90 GR_DEFATTR(selection);
95 static void init_attr()
99 GR_ALLOCATTR(inactive);
100 GR_ALLOCATTR(normal);
101 GR_ALLOCATTR(selection);
102 GR_ALLOCATTR(cursor);
103 GR_ALLOCATTR(prompt);
109 static void wedln_do_draw_str_box(WEdln *wedln, const WRectangle *geom,
110 const char *str, int cursor,
113 int len=strlen(str), ll=0, ty=0;
115 ty=calc_text_y(wedln, geom);
119 DSTRSECT(mark, normal);
120 DSTRSECT(cursor-mark, selection);
122 DSTRSECT(cursor, normal);
125 tx+=wedln_draw_strsect(wedln, geom, geom->x+tx, ty,
126 " ", 1, GR_ATTR(cursor));
128 ll=str_nextoff(str, 0);
129 DSTRSECT(ll, cursor);
132 DSTRSECT(cursor, normal);
133 ll=str_nextoff(str, 0);
134 DSTRSECT(ll, cursor);
135 DSTRSECT(mark-cursor-ll, selection);
137 DSTRSECT(len, normal);
143 grbrush_clear_area(WEDLN_BRUSH(wedln), &g);
148 static void wedln_draw_str_box(WEdln *wedln, const WRectangle *geom,
149 int vstart, const char *str,
150 int dstart, int point, int mark)
154 /* Some fonts and Xmb/utf8 routines don't work well with dstart!=0. */
163 point-=vstart+dstart;
166 tx=grbrush_get_text_width(WEDLN_BRUSH(wedln), str+vstart, dstart);
168 grbrush_begin(WEDLN_BRUSH(wedln), geom,
169 GRBRUSH_AMEND|GRBRUSH_KEEP_ATTR|GRBRUSH_NEED_CLIP);
171 wedln_do_draw_str_box(wedln, geom, str+vstart+dstart, point, mark, tx);
173 grbrush_end(WEDLN_BRUSH(wedln));
177 static bool wedln_update_cursor(WEdln *wedln, int iw)
180 int vstart=wedln->vstart;
181 int point=wedln->edln.point;
182 int len=wedln->edln.psize;
183 int mark=wedln->edln.mark;
184 const char *str=wedln->edln.p;
187 if(point<wedln->vstart)
190 if(wedln->vstart==point)
195 cx=grbrush_get_text_width(WEDLN_BRUSH(wedln), str+vstart,
197 cx+=grbrush_get_text_width(WEDLN_BRUSH(wedln), " ", 1);
199 int nxt=str_nextoff(str, point);
200 cx=grbrush_get_text_width(WEDLN_BRUSH(wedln), str+vstart,
207 l=str_nextoff(str, vstart);
213 ret=(wedln->vstart!=vstart);
214 wedln->vstart=vstart;
223 /*{{{ Size/location calc */
226 static int get_textarea_height(WEdln *wedln, bool with_spacing)
231 grbrush_get_border_widths(WEDLN_BRUSH(wedln), &bdw);
232 grbrush_get_font_extents(WEDLN_BRUSH(wedln), &fnte);
234 return (fnte.max_height+bdw.top+bdw.bottom+
235 (with_spacing ? bdw.spacing : 0));
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);
364 void wedln_draw_completions(WEdln *wedln, bool complete)
368 if(wedln->compl_list.strs!=NULL && WEDLN_BRUSH(wedln)!=NULL){
369 get_completions_geom(wedln, G_CURRENT, &geom);
371 draw_listing(WEDLN_BRUSH(wedln), &geom, &(wedln->compl_list),
372 complete, GR_ATTR(selection));
377 void wedln_draw_textarea(WEdln *wedln)
382 if(WEDLN_BRUSH(wedln)==NULL)
385 get_outer_geom(wedln, G_CURRENT, &geom);
387 /*grbrush_begin(WEDLN_BRUSH(wedln), &geom, GRBRUSH_AMEND);*/
389 grbrush_draw_border(WEDLN_BRUSH(wedln), &geom);
391 get_inner_geom(wedln, G_CURRENT, &geom);
393 ty=calc_text_y(wedln, &geom);
395 grbrush_set_attr(WEDLN_BRUSH(wedln), GR_ATTR(prompt));
397 if(wedln->prompt!=NULL){
398 grbrush_draw_string(WEDLN_BRUSH(wedln), geom.x, ty,
399 wedln->prompt, wedln->prompt_len, TRUE);
402 if(wedln->info!=NULL){
403 int x=geom.x+geom.w-wedln->info_w;
405 grbrush_set_attr(WEDLN_BRUSH(wedln), GR_ATTR(info));
406 grbrush_draw_string(WEDLN_BRUSH(wedln), x, ty,
407 wedln->info, wedln->info_len, TRUE);
408 grbrush_unset_attr(WEDLN_BRUSH(wedln), GR_ATTR(info));
411 grbrush_unset_attr(WEDLN_BRUSH(wedln), GR_ATTR(prompt));
413 get_textarea_geom(wedln, G_CURRENT, &geom);
415 wedln_draw_str_box(wedln, &geom, wedln->vstart, wedln->edln.p, 0,
416 wedln->edln.point, wedln->edln.mark);
418 /*grbrush_end(WEDLN_BRUSH(wedln));*/
422 static void wedln_draw_(WEdln *wedln, bool complete, bool completions)
425 int f=(complete ? 0 : GRBRUSH_NO_CLEAR_OK);
427 if(WEDLN_BRUSH(wedln)==NULL)
430 get_geom(wedln, G_CURRENT, &g);
432 grbrush_begin(WEDLN_BRUSH(wedln), &g, f);
434 grbrush_set_attr(WEDLN_BRUSH(wedln), REGION_IS_ACTIVE(wedln)
436 : GR_ATTR(inactive));
439 wedln_draw_completions(wedln, FALSE);
441 wedln_draw_textarea(wedln);
443 grbrush_end(WEDLN_BRUSH(wedln));
447 void wedln_draw(WEdln *wedln, bool complete)
449 wedln_draw_(wedln, complete, TRUE);
458 static void wedln_set_info(WEdln *wedln, const char *info)
463 if(wedln->info!=NULL){
471 wedln->info=scat3(" [", info, "]");
472 if(wedln->info!=NULL){
473 wedln->info_len=strlen(wedln->info);
474 if(WEDLN_BRUSH(wedln)!=NULL){
475 wedln->info_w=grbrush_get_text_width(WEDLN_BRUSH(wedln),
482 get_textarea_geom(wedln, G_CURRENT, &tageom);
483 wedln_update_cursor(wedln, tageom.w);
485 wedln_draw_(wedln, FALSE, FALSE);
495 static void wedln_show_completions(WEdln *wedln, char **strs, int nstrs,
498 int w=REGION_GEOM(wedln).w;
499 int h=REGION_GEOM(wedln).h;
501 if(WEDLN_BRUSH(wedln)==NULL)
504 setup_listing(&(wedln->compl_list), strs, nstrs, FALSE);
505 wedln->compl_list.selected_str=selected;
507 input_refit((WInput*)wedln);
508 if(w==REGION_GEOM(wedln).w && h==REGION_GEOM(wedln).h)
509 wedln_draw_completions(wedln, TRUE);
513 void wedln_hide_completions(WEdln *wedln)
515 if(wedln->compl_list.strs!=NULL){
516 deinit_listing(&(wedln->compl_list));
517 input_refit((WInput*)wedln);
522 void wedln_scrollup_completions(WEdln *wedln)
524 if(wedln->compl_list.strs==NULL)
526 if(scrollup_listing(&(wedln->compl_list)))
527 wedln_draw_completions(wedln, TRUE);
531 void wedln_scrolldown_completions(WEdln *wedln)
533 if(wedln->compl_list.strs==NULL)
535 if(scrolldown_listing(&(wedln->compl_list)))
536 wedln_draw_completions(wedln, TRUE);
540 static int update_nocompl=0;
543 static void free_completions(char **ptr, int i)
554 bool wedln_do_set_completions(WEdln *wedln, char **ptr, int n,
555 char *beg, char *end, int cycle,
560 if(wedln->compl_beg!=NULL)
561 free(wedln->compl_beg);
563 if(wedln->compl_end!=NULL)
564 free(wedln->compl_end);
566 wedln->compl_beg=beg;
567 wedln->compl_end=end;
568 wedln->compl_current_id=-1;
570 n=edln_do_completions(&(wedln->edln), ptr, n, beg, end,
571 !mod_query_config.autoshowcompl, nosort);
573 if(mod_query_config.autoshowcompl && n>0 && cycle!=0){
575 sel=(cycle>0 ? 0 : n-1);
576 edln_set_completion(&(wedln->edln), ptr[sel], beg, end);
580 if(n>1 || (mod_query_config.autoshowcompl && n>0)){
581 wedln_show_completions(wedln, ptr, n, sel);
585 free_completions(ptr, n);
591 void wedln_set_completions(WEdln *wedln, ExtlTab completions, int cycle)
594 char **ptr=NULL, *beg=NULL, *end=NULL, *p=NULL;
596 n=extl_table_get_n(completions);
599 wedln_hide_completions(wedln);
603 ptr=ALLOC_N(char*, n);
608 if(!extl_table_geti_s(completions, i+1, &p)){
614 extl_table_gets_s(completions, "common_beg", &beg);
615 extl_table_gets_s(completions, "common_end", &end);
617 if(!wedln_do_set_completions(wedln, ptr, n, beg, end, cycle, FALSE))
618 wedln_hide_completions(wedln);
623 wedln_hide_completions(wedln);
624 free_completions(ptr, i);
628 static void wedln_do_select_completion(WEdln *wedln, int n)
630 bool complredraw=listing_select(&(wedln->compl_list), n);
631 wedln_draw_completions(wedln, complredraw);
634 edln_set_completion(&(wedln->edln), wedln->compl_list.strs[n],
635 wedln->compl_beg, wedln->compl_end);
641 static ExtlExportedFn *sc_safe_fns[]={
642 (ExtlExportedFn*)&complproxy_set_completions,
647 static ExtlSafelist sc_safelist=EXTL_SAFELIST_INIT(sc_safe_fns);
650 static int wedln_alloc_compl_id(WEdln *wedln)
652 int id=wedln->compl_waiting_id+1;
653 wedln->compl_waiting_id=maxof(0, wedln->compl_waiting_id+1);
657 static bool wedln_do_call_completor(WEdln *wedln, int id, int cycle)
659 if(wedln->compl_history_mode){
663 wedln->compl_waiting_id=id;
665 n=edln_history_matches(&wedln->edln, &h);
668 wedln_hide_completions(wedln);
672 if(wedln_do_set_completions(wedln, h, n, NULL, NULL, cycle, TRUE)){
673 wedln->compl_current_id=id;
679 const char *p=wedln->edln.p;
680 int point=wedln->edln.point;
681 WComplProxy *proxy=create_complproxy(wedln, id, cycle);
686 /* Set Lua-side to own the proxy: it gets freed by Lua's GC */
687 ((Obj*)proxy)->flags|=OBJ_EXTL_OWNED;
694 extl_protect(&sc_safelist);
695 extl_call(wedln->completor, "osi", NULL, proxy, p, point);
696 extl_unprotect(&sc_safelist);
703 static void timed_complete(WTimer *tmr, Obj *obj)
705 WEdln *wedln=(WEdln*)obj;
708 int id=wedln->compl_timed_id;
709 wedln->compl_timed_id=-1;
710 if(id==wedln->compl_waiting_id && id>=0)
711 wedln_do_call_completor((WEdln*)obj, id, FALSE);
717 * Select next completion.
720 bool wedln_next_completion(WEdln *wedln)
724 if(wedln->compl_current_id!=wedln->compl_waiting_id)
727 if(wedln->compl_list.nstrs<=0)
730 if(wedln->compl_list.selected_str<0 ||
731 wedln->compl_list.selected_str+1>=wedln->compl_list.nstrs){
734 n=wedln->compl_list.selected_str+1;
737 if(n!=wedln->compl_list.selected_str)
738 wedln_do_select_completion(wedln, n);
745 * Select previous completion.
748 bool wedln_prev_completion(WEdln *wedln)
752 if(wedln->compl_current_id!=wedln->compl_waiting_id)
755 if(wedln->compl_list.nstrs<=0)
758 if(wedln->compl_list.selected_str<=0){
759 n=wedln->compl_list.nstrs-1;
761 n=wedln->compl_list.selected_str-1;
764 if(n!=wedln->compl_list.selected_str)
765 wedln_do_select_completion(wedln, n);
772 * Call completion handler with the text between the beginning of line and
773 * current cursor position, or select next/previous completion from list if in
774 * auto-show-completions mode and \var{cycle} is set to ``next'' or ``prev'',
775 * respectively. The \var{mode} may be ``history'' or ``normal''. If it is
776 * not set, the previous mode is used. Normally next entry is not cycled to
777 * despite the setting of \var{cycle} if mode switch occurs. To override
778 * this, use ``next-always'' and ``prev-always'' for \var{cycle}.
781 void wedln_complete(WEdln *wedln, const char *cycle, const char *mode)
787 if(strcmp(mode, "history")==0){
788 valid=wedln->compl_history_mode;
789 wedln->compl_history_mode=TRUE;
790 }else if(strcmp(mode, "normal")==0){
791 valid=!wedln->compl_history_mode;
792 wedln->compl_history_mode=FALSE;
795 wedln_set_info(wedln,
796 (wedln->compl_history_mode
803 if((valid && strcmp(cycle, "next")==0) ||
804 strcmp(cycle, "next-always")==0){
806 }else if((valid && strcmp(cycle, "prev")==0) ||
807 strcmp(cycle, "prev-always")==0){
812 if(valid && cyclei!=0 && mod_query_config.autoshowcompl &&
813 wedln->compl_list.nstrs>0){
815 wedln_next_completion(wedln);
817 wedln_prev_completion(wedln);
819 int oldid=wedln->compl_waiting_id;
821 if(!wedln_do_call_completor(wedln, wedln_alloc_compl_id(wedln),
823 wedln->compl_waiting_id=oldid;
830 * Get history completion mode.
833 bool wedln_is_histcompl(WEdln *wedln)
835 return wedln->compl_history_mode;
842 /*{{{ Update handler */
845 static void wedln_update_handler(WEdln *wedln, int from, int flags)
849 if(WEDLN_BRUSH(wedln)==NULL)
852 get_textarea_geom(wedln, G_CURRENT, &geom);
854 if(flags&EDLN_UPDATE_NEW)
857 if(flags&EDLN_UPDATE_MOVED){
858 if(wedln_update_cursor(wedln, geom.w))
862 from=maxof(0, from-wedln->vstart);
864 wedln_draw_str_box(wedln, &geom, wedln->vstart, wedln->edln.p, from,
865 wedln->edln.point, wedln->edln.mark);
867 if(update_nocompl==0 &&
868 mod_query_config.autoshowcompl &&
869 flags&EDLN_UPDATE_CHANGED){
870 wedln->compl_current_id=-1; /* invalidate */
871 if(wedln->autoshowcompl_timer==NULL)
872 wedln->autoshowcompl_timer=create_timer();
873 if(wedln->autoshowcompl_timer!=NULL){
874 wedln->compl_timed_id=wedln_alloc_compl_id(wedln);
875 timer_set(wedln->autoshowcompl_timer,
876 mod_query_config.autoshowcompl_delay,
877 timed_complete, (Obj*)wedln);
886 /*{{{ Init, deinit and config update */
889 static bool wedln_init_prompt(WEdln *wedln, const char *prompt)
900 wedln->prompt_len=strlen(p);
911 static bool wedln_init(WEdln *wedln, WWindow *par, const WFitParams *fp,
912 WEdlnCreateParams *params)
918 if(!wedln_init_prompt(wedln, params->prompt))
921 if(!edln_init(&(wedln->edln), params->dflt)){
926 wedln->handler=extl_fn_none();
927 wedln->completor=extl_fn_none();
929 wedln->edln.uiptr=wedln;
930 wedln->edln.ui_update=(EdlnUpdateHandler*)wedln_update_handler;
932 wedln->autoshowcompl_timer=NULL;
934 init_listing(&(wedln->compl_list));
936 wedln->compl_waiting_id=-1;
937 wedln->compl_current_id=-1;
938 wedln->compl_timed_id=-1;
939 wedln->compl_beg=NULL;
940 wedln->compl_end=NULL;
941 wedln->compl_tab=FALSE;
942 wedln->compl_history_mode=FALSE;
948 wedln->cycle_bindmap=NULL;
950 if(!input_init((WInput*)wedln, par, fp)){
951 edln_deinit(&(wedln->edln));
956 window_create_xic(&wedln->input.win);
958 wedln->handler=extl_ref_fn(params->handler);
959 wedln->completor=extl_ref_fn(params->completor);
961 region_add_bindmap((WRegion*)wedln, mod_query_wedln_bindmap);
967 WEdln *create_wedln(WWindow *par, const WFitParams *fp,
968 WEdlnCreateParams *params)
970 CREATEOBJ_IMPL(WEdln, wedln, (p, par, fp, params));
974 static void wedln_deinit(WEdln *wedln)
976 if(wedln->prompt!=NULL)
979 if(wedln->info!=NULL)
982 if(wedln->compl_beg!=NULL)
983 free(wedln->compl_beg);
985 if(wedln->compl_end!=NULL)
986 free(wedln->compl_end);
988 if(wedln->compl_list.strs!=NULL)
989 deinit_listing(&(wedln->compl_list));
991 if(wedln->autoshowcompl_timer!=NULL)
992 destroy_obj((Obj*)wedln->autoshowcompl_timer);
994 if(wedln->cycle_bindmap!=NULL)
995 bindmap_destroy(wedln->cycle_bindmap);
997 extl_unref_fn(wedln->completor);
998 extl_unref_fn(wedln->handler);
1000 edln_deinit(&(wedln->edln));
1001 input_deinit((WInput*)wedln);
1005 static void wedln_do_finish(WEdln *wedln)
1010 handler=wedln->handler;
1011 wedln->handler=extl_fn_none();
1012 p=edln_finish(&(wedln->edln));
1014 if(region_manager_allows_destroying((WRegion*)wedln))
1015 destroy_obj((Obj*)wedln);
1018 extl_call(handler, "s", NULL, p);
1021 extl_unref_fn(handler);
1026 * Close \var{wedln} and call any handlers.
1029 void wedln_finish(WEdln *wedln)
1031 mainloop_defer_action((Obj*)wedln, (WDeferredAction*)wedln_do_finish);
1042 * Request selection from application holding such.
1044 * Note that this function is asynchronous; the selection will not
1045 * actually be inserted before Ion receives it. This will be no
1046 * earlier than Ion return to its main loop.
1049 void wedln_paste(WEdln *wedln)
1051 ioncore_request_selection_for(wedln->input.win.win);
1055 void wedln_insstr(WEdln *wedln, const char *buf, size_t n)
1057 edln_insstr_n(&(wedln->edln), buf, n, TRUE, TRUE);
1061 static const char *wedln_style(WEdln *wedln)
1063 return "input-edln";
1070 /*{{{ Dynamic function table and class implementation */
1073 static DynFunTab wedln_dynfuntab[]={
1074 {window_draw, wedln_draw},
1075 {input_calc_size, wedln_calc_size},
1076 {input_scrollup, wedln_scrollup_completions},
1077 {input_scrolldown, wedln_scrolldown_completions},
1078 {window_insstr, wedln_insstr},
1079 {(DynFun*)input_style, (DynFun*)wedln_style},
1085 IMPLCLASS(WEdln, WInput, wedln_deinit, wedln_dynfuntab);