2 * ion/mod_query/wedln.c
4 * Copyright (c) Tuomo Valkonen 1999-2009.
6 * See the included file LICENSE for details.
11 #include <libtu/objp.h>
12 #include <libtu/minmax.h>
13 #include <libtu/setparam.h>
14 #include <libextl/extl.h>
15 #include <libmainloop/defer.h>
16 #include <libmainloop/signal.h>
18 #include <ioncore/common.h>
19 #include <ioncore/global.h>
20 #include <ioncore/strings.h>
21 #include <ioncore/xic.h>
22 #include <ioncore/selection.h>
23 #include <ioncore/event.h>
24 #include <ioncore/regbind.h>
25 #include <ioncore/gr-util.h>
26 #include <ioncore/sizehint.h>
27 #include <ioncore/resize.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_set_attr(WEDLN_BRUSH(wedln), a);
59 grbrush_draw_string(WEDLN_BRUSH(wedln), x, y, str, len, TRUE);
60 grbrush_unset_attr(WEDLN_BRUSH(wedln), a);
62 return grbrush_get_text_width(WEDLN_BRUSH(wedln), str, len);
66 static void dispu(const char* s, int l)
69 int c=(unsigned char)*s;
70 fprintf(stderr, "%d[%c]", c, *s);
74 fprintf(stderr, "\n");
78 #define DSTRSECT(LEN, A) \
80 tx+=wedln_draw_strsect(wedln, geom, geom->x+tx, ty, \
81 str, LEN, GR_ATTR(A)); \
89 GR_DEFATTR(selection);
94 static void init_attr()
98 GR_ALLOCATTR(inactive);
100 GR_ALLOCATTR(selection);
101 GR_ALLOCATTR(cursor);
102 GR_ALLOCATTR(prompt);
108 static void wedln_do_draw_str_box(WEdln *wedln, const WRectangle *geom,
109 const char *str, int cursor,
112 int len=strlen(str), ll=0, ty=0;
114 ty=calc_text_y(wedln, geom);
118 DSTRSECT(mark, normal);
119 DSTRSECT(cursor-mark, selection);
121 DSTRSECT(cursor, normal);
124 tx+=wedln_draw_strsect(wedln, geom, geom->x+tx, ty,
125 " ", 1, GR_ATTR(cursor));
127 ll=str_nextoff(str, 0);
128 DSTRSECT(ll, cursor);
131 DSTRSECT(cursor, normal);
132 ll=str_nextoff(str, 0);
133 DSTRSECT(ll, cursor);
134 DSTRSECT(mark-cursor-ll, selection);
136 DSTRSECT(len, normal);
142 grbrush_clear_area(WEDLN_BRUSH(wedln), &g);
147 static void wedln_draw_str_box(WEdln *wedln, const WRectangle *geom,
148 int vstart, const char *str,
149 int dstart, int point, int mark)
153 /* Some fonts and Xmb/utf8 routines don't work well with dstart!=0. */
162 point-=vstart+dstart;
165 tx=grbrush_get_text_width(WEDLN_BRUSH(wedln), str+vstart, dstart);
167 grbrush_begin(WEDLN_BRUSH(wedln), geom,
168 GRBRUSH_AMEND|GRBRUSH_KEEP_ATTR|GRBRUSH_NEED_CLIP);
170 wedln_do_draw_str_box(wedln, geom, str+vstart+dstart, point, mark, tx);
172 grbrush_end(WEDLN_BRUSH(wedln));
176 static bool wedln_update_cursor(WEdln *wedln, int iw)
179 int vstart=wedln->vstart;
180 int point=wedln->edln.point;
181 int len=wedln->edln.psize;
182 int mark=wedln->edln.mark;
183 const char *str=wedln->edln.p;
186 if(point<wedln->vstart)
189 if(wedln->vstart==point)
194 cx=grbrush_get_text_width(WEDLN_BRUSH(wedln), str+vstart,
196 cx+=grbrush_get_text_width(WEDLN_BRUSH(wedln), " ", 1);
198 int nxt=str_nextoff(str, point);
199 cx=grbrush_get_text_width(WEDLN_BRUSH(wedln), str+vstart,
206 l=str_nextoff(str, vstart);
212 ret=(wedln->vstart!=vstart);
213 wedln->vstart=vstart;
222 /*{{{ Size/location calc */
225 static int get_textarea_height(WEdln *wedln, bool with_spacing)
229 if(WEDLN_BRUSH(wedln)!=NULL)
230 mod_query_get_minimum_extents(WEDLN_BRUSH(wedln), with_spacing, &w, &h);
236 enum{G_NORESET, G_MAX, G_CURRENT};
239 static void get_geom(WEdln *wedln, int mode, WRectangle *geom)
242 *geom=wedln->input.last_fp.g;
243 else if(mode==G_CURRENT)
244 *geom=REGION_GEOM(wedln);
248 static void get_completions_geom(WEdln *wedln, int mode, WRectangle *geom)
250 get_geom(wedln, mode, geom);
254 geom->h-=get_textarea_height(wedln, TRUE);
260 static void get_outer_geom(WEdln *wedln, int mode, WRectangle *geom)
264 get_geom(wedln, mode, geom);
268 th=get_textarea_height(wedln, FALSE);
275 static void get_inner_geom(WEdln *wedln, int mode, WRectangle *geom)
279 grbrush_get_border_widths(WEDLN_BRUSH(wedln), &bdw);
281 get_outer_geom(wedln, mode, geom);
284 geom->w-=bdw.left+bdw.right;
286 geom->h-=bdw.top+bdw.bottom;
287 geom->w=maxof(0, geom->w);
288 geom->h=maxof(0, geom->h);
292 static void get_textarea_geom(WEdln *wedln, int mode, WRectangle *geom)
294 get_inner_geom(wedln, mode, geom);
295 geom->x+=wedln->prompt_w;
296 geom->w=maxof(0, geom->w - wedln->prompt_w - wedln->info_w);
300 static void wedln_calc_size(WEdln *wedln, WRectangle *geom)
304 WRectangle max_geom=*geom, tageom;
306 if(WEDLN_BRUSH(wedln)==NULL)
309 if(wedln->prompt!=NULL){
310 wedln->prompt_w=grbrush_get_text_width(WEDLN_BRUSH(wedln),
315 if(wedln->info!=NULL){
316 wedln->info_w=grbrush_get_text_width(WEDLN_BRUSH(wedln),
321 th=get_textarea_height(wedln, wedln->compl_list.strs!=NULL);
323 if(wedln->compl_list.strs==NULL){
324 if(max_geom.h<th || !(wedln->input.last_fp.mode®ION_FIT_BOUNDS))
331 get_completions_geom(wedln, G_MAX, &g);
333 fit_listing(WEDLN_BRUSH(wedln), &g, &(wedln->compl_list));
335 grbrush_get_border_widths(WEDLN_BRUSH(wedln), &bdw);
337 h=wedln->compl_list.toth;
338 th+=bdw.top+bdw.bottom;
340 if(h+th>max_geom.h || !(wedln->input.last_fp.mode®ION_FIT_BOUNDS))
346 geom->y=max_geom.y+max_geom.h-geom->h;
350 get_textarea_geom(wedln, G_NORESET, &tageom);
351 wedln_update_cursor(wedln, tageom.w);
355 void wedln_size_hints(WEdln *wedln, WSizeHints *hints_ret)
359 if(WEDLN_BRUSH(wedln)!=NULL){
360 mod_query_get_minimum_extents(WEDLN_BRUSH(wedln), FALSE, &w, &h);
361 w+=wedln->prompt_w+wedln->info_w;
362 w+=grbrush_get_text_width(WEDLN_BRUSH(wedln), "xxxxxxxxxx", 10);
365 hints_ret->min_set=TRUE;
366 hints_ret->min_width=w;
367 hints_ret->min_height=h;
377 void wedln_draw_completions(WEdln *wedln, int mode)
381 if(wedln->compl_list.strs!=NULL && WEDLN_BRUSH(wedln)!=NULL){
382 get_completions_geom(wedln, G_CURRENT, &geom);
384 draw_listing(WEDLN_BRUSH(wedln), &geom, &(wedln->compl_list),
385 mode, GR_ATTR(selection));
390 void wedln_draw_textarea(WEdln *wedln)
395 if(WEDLN_BRUSH(wedln)==NULL)
398 get_outer_geom(wedln, G_CURRENT, &geom);
400 /*grbrush_begin(WEDLN_BRUSH(wedln), &geom, GRBRUSH_AMEND);*/
402 grbrush_draw_border(WEDLN_BRUSH(wedln), &geom);
404 get_inner_geom(wedln, G_CURRENT, &geom);
406 ty=calc_text_y(wedln, &geom);
408 grbrush_set_attr(WEDLN_BRUSH(wedln), GR_ATTR(prompt));
410 if(wedln->prompt!=NULL){
411 grbrush_draw_string(WEDLN_BRUSH(wedln), geom.x, ty,
412 wedln->prompt, wedln->prompt_len, TRUE);
415 if(wedln->info!=NULL){
416 int x=geom.x+geom.w-wedln->info_w;
418 grbrush_set_attr(WEDLN_BRUSH(wedln), GR_ATTR(info));
419 grbrush_draw_string(WEDLN_BRUSH(wedln), x, ty,
420 wedln->info, wedln->info_len, TRUE);
421 grbrush_unset_attr(WEDLN_BRUSH(wedln), GR_ATTR(info));
424 grbrush_unset_attr(WEDLN_BRUSH(wedln), GR_ATTR(prompt));
426 get_textarea_geom(wedln, G_CURRENT, &geom);
428 wedln_draw_str_box(wedln, &geom, wedln->vstart, wedln->edln.p, 0,
429 wedln->edln.point, wedln->edln.mark);
431 /*grbrush_end(WEDLN_BRUSH(wedln));*/
435 static void wedln_draw_(WEdln *wedln, bool complete, bool completions)
438 int f=(complete ? 0 : GRBRUSH_NO_CLEAR_OK);
440 if(WEDLN_BRUSH(wedln)==NULL)
443 get_geom(wedln, G_CURRENT, &g);
445 grbrush_begin(WEDLN_BRUSH(wedln), &g, f);
447 grbrush_set_attr(WEDLN_BRUSH(wedln), REGION_IS_ACTIVE(wedln)
449 : GR_ATTR(inactive));
452 wedln_draw_completions(wedln, LISTING_DRAW_ALL);
454 wedln_draw_textarea(wedln);
456 grbrush_end(WEDLN_BRUSH(wedln));
460 void wedln_draw(WEdln *wedln, bool complete)
462 wedln_draw_(wedln, complete, TRUE);
471 static void wedln_set_info(WEdln *wedln, const char *info)
476 if(wedln->info!=NULL){
484 wedln->info=scat3(" [", info, "]");
485 if(wedln->info!=NULL){
486 wedln->info_len=strlen(wedln->info);
487 if(WEDLN_BRUSH(wedln)!=NULL){
488 wedln->info_w=grbrush_get_text_width(WEDLN_BRUSH(wedln),
495 get_textarea_geom(wedln, G_CURRENT, &tageom);
496 wedln_update_cursor(wedln, tageom.w);
498 wedln_draw_(wedln, FALSE, FALSE);
508 static void wedln_show_completions(WEdln *wedln, char **strs, int nstrs,
511 int w=REGION_GEOM(wedln).w;
512 int h=REGION_GEOM(wedln).h;
514 if(WEDLN_BRUSH(wedln)==NULL)
517 setup_listing(&(wedln->compl_list), strs, nstrs, FALSE);
518 wedln->compl_list.selected_str=selected;
520 input_refit((WInput*)wedln);
521 if(w==REGION_GEOM(wedln).w && h==REGION_GEOM(wedln).h)
522 wedln_draw_completions(wedln, LISTING_DRAW_COMPLETE);
526 void wedln_hide_completions(WEdln *wedln)
528 if(wedln->compl_list.strs!=NULL){
529 deinit_listing(&(wedln->compl_list));
530 input_refit((WInput*)wedln);
535 void wedln_scrollup_completions(WEdln *wedln)
537 if(wedln->compl_list.strs==NULL)
539 if(scrollup_listing(&(wedln->compl_list)))
540 wedln_draw_completions(wedln, LISTING_DRAW_COMPLETE);
544 void wedln_scrolldown_completions(WEdln *wedln)
546 if(wedln->compl_list.strs==NULL)
548 if(scrolldown_listing(&(wedln->compl_list)))
549 wedln_draw_completions(wedln, LISTING_DRAW_COMPLETE);
553 static int update_nocompl=0;
556 static void free_completions(char **ptr, int i)
567 bool wedln_do_set_completions(WEdln *wedln, char **ptr, int n,
568 char *beg, char *end, int cycle,
573 if(wedln->compl_beg!=NULL)
574 free(wedln->compl_beg);
576 if(wedln->compl_end!=NULL)
577 free(wedln->compl_end);
579 wedln->compl_beg=beg;
580 wedln->compl_end=end;
581 wedln->compl_current_id=-1;
583 n=edln_do_completions(&(wedln->edln), ptr, n, beg, end,
584 !mod_query_config.autoshowcompl, nosort);
586 if(mod_query_config.autoshowcompl && n>0 && cycle!=0){
588 sel=(cycle>0 ? 0 : n-1);
589 edln_set_completion(&(wedln->edln), ptr[sel], beg, end);
593 if(n>1 || (mod_query_config.autoshowcompl && n>0)){
594 wedln_show_completions(wedln, ptr, n, sel);
598 free_completions(ptr, n);
604 void wedln_set_completions(WEdln *wedln, ExtlTab completions, int cycle)
607 char **ptr=NULL, *beg=NULL, *end=NULL, *p=NULL;
609 n=extl_table_get_n(completions);
612 wedln_hide_completions(wedln);
616 ptr=ALLOC_N(char*, n);
621 if(!extl_table_geti_s(completions, i+1, &p)){
627 extl_table_gets_s(completions, "common_beg", &beg);
628 extl_table_gets_s(completions, "common_end", &end);
630 if(!wedln_do_set_completions(wedln, ptr, n, beg, end, cycle, FALSE))
631 wedln_hide_completions(wedln);
636 wedln_hide_completions(wedln);
637 free_completions(ptr, i);
641 static void wedln_do_select_completion(WEdln *wedln, int n)
643 bool redraw=listing_select(&(wedln->compl_list), n);
644 wedln_draw_completions(wedln, redraw);
647 edln_set_completion(&(wedln->edln), wedln->compl_list.strs[n],
648 wedln->compl_beg, wedln->compl_end);
654 static ExtlExportedFn *sc_safe_fns[]={
655 (ExtlExportedFn*)&complproxy_set_completions,
660 static ExtlSafelist sc_safelist=EXTL_SAFELIST_INIT(sc_safe_fns);
663 static int wedln_alloc_compl_id(WEdln *wedln)
665 int id=wedln->compl_waiting_id+1;
666 wedln->compl_waiting_id=maxof(0, wedln->compl_waiting_id+1);
670 static bool wedln_do_call_completor(WEdln *wedln, int id, int cycle)
672 if(wedln->compl_history_mode){
676 wedln->compl_waiting_id=id;
678 n=edln_history_matches(&wedln->edln, &h);
681 wedln_hide_completions(wedln);
685 if(wedln_do_set_completions(wedln, h, n, NULL, NULL, cycle, TRUE)){
686 wedln->compl_current_id=id;
692 const char *p=wedln->edln.p;
693 int point=wedln->edln.point;
694 WComplProxy *proxy=create_complproxy(wedln, id, cycle);
699 /* Set Lua-side to own the proxy: it gets freed by Lua's GC */
700 ((Obj*)proxy)->flags|=OBJ_EXTL_OWNED;
707 extl_protect(&sc_safelist);
708 extl_call(wedln->completor, "osi", NULL, proxy, p, point);
709 extl_unprotect(&sc_safelist);
716 static void timed_complete(WTimer *tmr, Obj *obj)
718 WEdln *wedln=(WEdln*)obj;
721 int id=wedln->compl_timed_id;
722 wedln->compl_timed_id=-1;
723 if(id==wedln->compl_waiting_id && id>=0)
724 wedln_do_call_completor((WEdln*)obj, id, FALSE);
730 * Select next completion.
733 bool wedln_next_completion(WEdln *wedln)
737 if(wedln->compl_current_id!=wedln->compl_waiting_id)
740 if(wedln->compl_list.nstrs<=0)
743 if(wedln->compl_list.selected_str<0 ||
744 wedln->compl_list.selected_str+1>=wedln->compl_list.nstrs){
747 n=wedln->compl_list.selected_str+1;
750 if(n!=wedln->compl_list.selected_str)
751 wedln_do_select_completion(wedln, n);
758 * Select previous completion.
761 bool wedln_prev_completion(WEdln *wedln)
765 if(wedln->compl_current_id!=wedln->compl_waiting_id)
768 if(wedln->compl_list.nstrs<=0)
771 if(wedln->compl_list.selected_str<=0){
772 n=wedln->compl_list.nstrs-1;
774 n=wedln->compl_list.selected_str-1;
777 if(n!=wedln->compl_list.selected_str)
778 wedln_do_select_completion(wedln, n);
785 * Call completion handler with the text between the beginning of line and
786 * current cursor position, or select next/previous completion from list if in
787 * auto-show-completions mode and \var{cycle} is set to \codestr{next} or
788 * \codestr{prev}, respectively.
789 * The \var{mode} may be \codestr{history} or \codestr{normal}. If it is
790 * not set, the previous mode is used. Normally next entry is not cycled to
791 * despite the setting of \var{cycle} if mode switch occurs. To override
792 * this, use \codestr{next-always} and \codestr{prev-always} for \var{cycle}.
795 void wedln_complete(WEdln *wedln, const char *cycle, const char *mode)
801 if(strcmp(mode, "history")==0){
802 valid=wedln->compl_history_mode;
803 wedln->compl_history_mode=TRUE;
804 }else if(strcmp(mode, "normal")==0){
805 valid=!wedln->compl_history_mode;
806 wedln->compl_history_mode=FALSE;
809 wedln_set_info(wedln,
810 (wedln->compl_history_mode
817 if((valid && strcmp(cycle, "next")==0) ||
818 strcmp(cycle, "next-always")==0){
820 }else if((valid && strcmp(cycle, "prev")==0) ||
821 strcmp(cycle, "prev-always")==0){
826 if(valid && cyclei!=0 && mod_query_config.autoshowcompl &&
827 wedln->compl_list.nstrs>0){
829 wedln_next_completion(wedln);
831 wedln_prev_completion(wedln);
833 int oldid=wedln->compl_waiting_id;
835 if(!wedln_do_call_completor(wedln, wedln_alloc_compl_id(wedln),
837 wedln->compl_waiting_id=oldid;
844 * Get history completion mode.
847 bool wedln_is_histcompl(WEdln *wedln)
849 return wedln->compl_history_mode;
856 /*{{{ Update handler */
859 static void wedln_update_handler(WEdln *wedln, int from, int flags)
863 if(WEDLN_BRUSH(wedln)==NULL)
866 get_textarea_geom(wedln, G_CURRENT, &geom);
868 if(flags&EDLN_UPDATE_NEW)
871 if(flags&EDLN_UPDATE_MOVED){
872 if(wedln_update_cursor(wedln, geom.w))
876 from=maxof(0, from-wedln->vstart);
878 wedln_draw_str_box(wedln, &geom, wedln->vstart, wedln->edln.p, from,
879 wedln->edln.point, wedln->edln.mark);
881 if(update_nocompl==0 &&
882 mod_query_config.autoshowcompl &&
883 flags&EDLN_UPDATE_CHANGED){
884 wedln->compl_current_id=-1; /* invalidate */
885 if(wedln->autoshowcompl_timer==NULL)
886 wedln->autoshowcompl_timer=create_timer();
887 if(wedln->autoshowcompl_timer!=NULL){
888 wedln->compl_timed_id=wedln_alloc_compl_id(wedln);
889 timer_set(wedln->autoshowcompl_timer,
890 mod_query_config.autoshowcompl_delay,
891 timed_complete, (Obj*)wedln);
900 /*{{{ Init, deinit and config update */
903 static bool wedln_init_prompt(WEdln *wedln, const char *prompt)
914 wedln->prompt_len=strlen(p);
925 static bool wedln_init(WEdln *wedln, WWindow *par, const WFitParams *fp,
926 WEdlnCreateParams *params)
932 if(!wedln_init_prompt(wedln, params->prompt))
935 if(!edln_init(&(wedln->edln), params->dflt)){
940 wedln->handler=extl_fn_none();
941 wedln->completor=extl_fn_none();
943 wedln->edln.uiptr=wedln;
944 wedln->edln.ui_update=(EdlnUpdateHandler*)wedln_update_handler;
946 wedln->autoshowcompl_timer=NULL;
948 init_listing(&(wedln->compl_list));
950 wedln->compl_waiting_id=-1;
951 wedln->compl_current_id=-1;
952 wedln->compl_timed_id=-1;
953 wedln->compl_beg=NULL;
954 wedln->compl_end=NULL;
955 wedln->compl_tab=FALSE;
956 wedln->compl_history_mode=FALSE;
962 wedln->cycle_bindmap=NULL;
964 if(!input_init((WInput*)wedln, par, fp)){
965 edln_deinit(&(wedln->edln));
970 window_create_xic(&wedln->input.win);
972 wedln->handler=extl_ref_fn(params->handler);
973 wedln->completor=extl_ref_fn(params->completor);
975 region_add_bindmap((WRegion*)wedln, mod_query_wedln_bindmap);
981 WEdln *create_wedln(WWindow *par, const WFitParams *fp,
982 WEdlnCreateParams *params)
984 CREATEOBJ_IMPL(WEdln, wedln, (p, par, fp, params));
988 static void wedln_deinit(WEdln *wedln)
990 if(wedln->prompt!=NULL)
993 if(wedln->info!=NULL)
996 if(wedln->compl_beg!=NULL)
997 free(wedln->compl_beg);
999 if(wedln->compl_end!=NULL)
1000 free(wedln->compl_end);
1002 if(wedln->compl_list.strs!=NULL)
1003 deinit_listing(&(wedln->compl_list));
1005 if(wedln->autoshowcompl_timer!=NULL)
1006 destroy_obj((Obj*)wedln->autoshowcompl_timer);
1008 if(wedln->cycle_bindmap!=NULL)
1009 bindmap_destroy(wedln->cycle_bindmap);
1011 extl_unref_fn(wedln->completor);
1012 extl_unref_fn(wedln->handler);
1014 edln_deinit(&(wedln->edln));
1015 input_deinit((WInput*)wedln);
1019 static void wedln_do_finish(WEdln *wedln)
1024 handler=wedln->handler;
1025 wedln->handler=extl_fn_none();
1026 p=edln_finish(&(wedln->edln));
1028 region_rqdispose((WRegion*)wedln);
1031 extl_call(handler, "s", NULL, p);
1034 extl_unref_fn(handler);
1039 * Close \var{wedln} and call any handlers.
1042 void wedln_finish(WEdln *wedln)
1044 mainloop_defer_action((Obj*)wedln, (WDeferredAction*)wedln_do_finish);
1055 * Request selection from application holding such.
1057 * Note that this function is asynchronous; the selection will not
1058 * actually be inserted before Ion receives it. This will be no
1059 * earlier than Ion return to its main loop.
1062 void wedln_paste(WEdln *wedln)
1064 ioncore_request_selection_for(wedln->input.win.win);
1068 void wedln_insstr(WEdln *wedln, const char *buf, size_t n)
1070 edln_insstr_n(&(wedln->edln), buf, n, TRUE, TRUE);
1074 static const char *wedln_style(WEdln *wedln)
1076 return "input-edln";
1083 /*{{{ Dynamic function table and class implementation */
1086 static DynFunTab wedln_dynfuntab[]={
1087 {window_draw, wedln_draw},
1088 {input_calc_size, wedln_calc_size},
1089 {input_scrollup, wedln_scrollup_completions},
1090 {input_scrolldown, wedln_scrolldown_completions},
1091 {window_insstr, wedln_insstr},
1092 {(DynFun*)input_style, (DynFun*)wedln_style},
1093 {region_size_hints, wedln_size_hints},
1099 IMPLCLASS(WEdln, WInput, wedln_deinit, wedln_dynfuntab);