/* * screen.c -- UI stuff * * Copyright (C) 2005-2007 Mikael Berthe <mikael@lilotux.net> * Parts of this file come from the Cabber project <cabber@ajmacias.com> * * 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 */#include<stdio.h>#include<stdlib.h>#include<string.h>#include<time.h>#include<ctype.h>#include<locale.h>#include<langinfo.h>#include<config.h>#include<assert.h>#ifdef HAVE_ASPELL_H# include <aspell.h>#endif#include"screen.h"#include"utf8.h"#include"hbuf.h"#include"commands.h"#include"compl.h"#include"roster.h"#include"histolog.h"#include"settings.h"#include"utils.h"#define get_color(col) (COLOR_PAIR(col)|COLOR_ATTRIB[col])#define DEFAULT_LOG_WIN_HEIGHT (5+2)#define DEFAULT_ROSTER_WIDTH 24#define CHAT_WIN_HEIGHT (maxY-1-Log_Win_Height)char*LocaleCharSet="C";staticunsignedshortintLog_Win_Height;staticunsignedshortintRoster_Width;staticinlinevoidcheck_offset(int);staticvoidscr_cancel_current_completion(void);staticvoidscr_end_current_completion(void);staticvoidscr_insert_text(constchar*);staticvoidscr_handle_tab(void);#ifdef HAVE_ASPELL_Hstaticvoidspellcheck(char*,char*);#endifstaticGHashTable*winbufhash;typedefstruct{GList*hbuf;GList*top;// If top is NULL, we'll display the last linescharcleared;// For ex, user has issued a /clear command...charlock;}buffdata;typedefstruct{WINDOW*win;PANEL*panel;buffdata*bd;}winbuf;structdimensions{intl;intc;};staticWINDOW*rosterWnd,*chatWnd,*activechatWnd,*inputWnd,*logWnd;staticWINDOW*mainstatusWnd,*chatstatusWnd;staticPANEL*rosterPanel,*chatPanel,*activechatPanel,*inputPanel;staticPANEL*mainstatusPanel,*chatstatusPanel;staticPANEL*logPanel;staticintmaxY,maxX;staticintprev_chatwidth;staticwinbuf*statusWindow;staticwinbuf*currentWindow;staticGList*statushbuf;staticintroster_hidden;staticintchatmode;staticintmultimode;staticchar*multiline,*multimode_subj;intupdate_roster;intutf8_mode=0;staticboolCurses;staticboollog_win_on_top;staticboolroster_win_on_right;statictime_tLastActivity;staticcharinputLine[INPUTLINE_LENGTH+1];#ifdef HAVE_ASPELL_HstaticcharmaskLine[INPUTLINE_LENGTH+1];#endifstaticchar*ptr_inputline;staticshortintinputline_offset;staticintcompletion_started;staticGList*cmdhisto;staticGList*cmdhisto_cur;staticguintcmdhisto_nblines;staticcharcmdhisto_backup[INPUTLINE_LENGTH+1];staticintchatstate;/* (0=active, 1=composing, 2=paused) */staticboollock_chatstate;statictime_tchatstate_timestamp;intchatstates_disabled;#define MAX_KEYSEQ_LENGTH 8typedefstruct{char*seqstr;guintmkeycode;gintvalue;}keyseq;GSList*keyseqlist;staticvoidadd_keyseq(char*seqstr,guintmkeycode,gintvalue);voidscr_WriteInWindow(constchar*winId,constchar*text,time_ttimestamp,unsignedintprefix_flags,intforce_show,unsignedmucnicklen);#ifdef HAVE_ASPELL_H#define ASPELLBADCHAR 5AspellConfig*spell_config;AspellSpeller*spell_checker;#endiftypedefstruct{char*status,*wildcard;intcolor;GPatternSpec*compiled;}rostercolor;staticGSList*rostercolrules=NULL;staticGHashTable*muccolors=NULL,*nickcolors=NULL;typedefstruct{boolmanual;//Manually set?intcolor;}nickcolor;staticintnickcolcount=0,*nickcols=NULL;staticmuccoltypeglob_muccol=MC_OFF;/* Functions */staticintcolor_conv_table[]={COLOR_BLACK,COLOR_RED,COLOR_GREEN,COLOR_YELLOW,COLOR_BLUE,COLOR_MAGENTA,COLOR_CYAN,COLOR_WHITE};staticintcolor_conv_table_fg[]={COLOR_BLACK_FG,COLOR_RED_FG,COLOR_GREEN_FG,COLOR_YELLOW_FG,COLOR_BLUE_FG,COLOR_MAGENTA_FG,COLOR_CYAN_FG,COLOR_WHITE_FG};staticintcolor_to_color_fg(intcolor){unsignedi=0;for(;i<sizeofcolor_conv_table/sizeof*color_conv_table;i++)if(color==color_conv_table[i])returncolor_conv_table_fg[i];return-1;}staticintcolor_fg_to_color(intcolor){unsignedi=0;if(color>=COLOR_BLACK_BOLD_FG)color-=COLOR_BLACK_BOLD_FG-COLOR_BLACK_FG;for(;i<sizeofcolor_conv_table_fg/sizeof*color_conv_table_fg;i++)if(color==color_conv_table_fg[i])returncolor_conv_table[i];return-1;}staticintFindColorInternal(constchar*name){if(!strcmp(name,"default"))return-1;if(!strcmp(name,"black"))returnCOLOR_BLACK;if(!strcmp(name,"red"))returnCOLOR_RED;if(!strcmp(name,"green"))returnCOLOR_GREEN;if(!strcmp(name,"yellow"))returnCOLOR_YELLOW;if(!strcmp(name,"blue"))returnCOLOR_BLUE;if(!strcmp(name,"magenta"))returnCOLOR_MAGENTA;if(!strcmp(name,"cyan"))returnCOLOR_CYAN;if(!strcmp(name,"white"))returnCOLOR_WHITE;return-2;}staticintFindColor(constchar*name){intresult=FindColorInternal(name);if(result!=-2)returnresult;scr_LogPrint(LPRINT_LOGNORM,"ERROR: Wrong color: %s",name);return-1;}staticintget_user_color(constchar*color){boolisbright=false;intcl;if(!strncmp(color,"bright",6)){isbright=true;color+=6;}cl=color_to_color_fg(FindColorInternal(color));if(cl<0)returncl;if(isbright)cl+=COLOR_BLACK_BOLD_FG-COLOR_BLACK_FG;returncl;}staticvoidensure_string_htable(GHashTable**table,GDestroyNotifyvalue_destroy_func){if(*table)//Have it alreadyreturn;*table=g_hash_table_new_full(g_str_hash,g_str_equal,g_free,value_destroy_func);}// Sets the coloring mode for given MUC// The MUC room does not need to be in the roster at that time// muc - the JID of room// type - the new typevoidscr_MucColor(constchar*muc,muccoltypetype){gchar*muclow=g_utf8_strdown(muc,-1);if(type==MC_REMOVE){//Remove itif(strcmp(muc,"*")){if(muccolors&&g_hash_table_lookup(muccolors,muclow))g_hash_table_remove(muccolors,muclow);}else{scr_LogPrint(LPRINT_NORMAL,"Can not remove global coloring mode");}g_free(muclow);}else{//Add or overwriteif(strcmp(muc,"*")){ensure_string_htable(&muccolors,g_free);muccoltype*value=g_new(muccoltype,1);*value=type;g_hash_table_replace(muccolors,muclow,value);}else{glob_muccol=type;g_free(muclow);}}//Need to redraw?if(chatmode&&((buddy_search_jid(muc)==current_buddy)||!strcmp(muc,"*")))scr_UpdateBuddyWindow();}// Sets the color for nick in MUC// If color is "-", the color is marked as automaticly assigned and is// not used if the room is in the "preset" modevoidscr_MucNickColor(constchar*nick,constchar*color){char*snick,*mnick;boolneed_update=false;snick=g_strdup_printf("<%s>",nick);mnick=g_strdup_printf("*%s ",nick);if(!strcmp(color,"-")){//Remove the colorif(nickcolors){nickcolor*nc=g_hash_table_lookup(nickcolors,snick);if(nc){//Have this nick alreadync->manual=false;nc=g_hash_table_lookup(nickcolors,mnick);assert(nc);//Must have both at the same timenc->manual=false;}// Else -> no color saved, nothing to delete}g_free(snick);//They are not saved in the hashg_free(mnick);need_update=true;}elseif(!strcmp(color,"!")){if(nickcolors){g_free(g_hash_table_lookup(nickcolors,snick));g_hash_table_remove(nickcolors,snick);g_hash_table_remove(nickcolors,mnick);}g_free(snick);//They are not saved in the hashg_free(mnick);need_update=true;}else{intcl=get_user_color(color);if(cl<0){scr_LogPrint(LPRINT_NORMAL,"No such color name");g_free(snick);g_free(mnick);}else{nickcolor*nc=g_new(nickcolor,1);ensure_string_htable(&nickcolors,NULL);nc->manual=true;nc->color=cl;//Free the struct, if any there alreadyg_free(g_hash_table_lookup(nickcolors,mnick));//Save the new onesg_hash_table_replace(nickcolors,mnick,nc);g_hash_table_replace(nickcolors,snick,nc);need_update=true;}}if(need_update&&chatmode&&(buddy_gettype(BUDDATA(current_buddy))&ROSTER_TYPE_ROOM))scr_UpdateBuddyWindow();}staticvoidfree_rostercolrule(rostercolor*col){g_free(col->status);g_free(col->wildcard);g_pattern_spec_free(col->compiled);g_free(col);}// Removes all roster coloring rulesvoidscr_RosterClearColor(void){GSList*head;for(head=rostercolrules;head;head=g_slist_next(head)){free_rostercolrule(head->data);}g_slist_free(rostercolrules);rostercolrules=NULL;}// Adds, modifies or removes roster coloring rule// color set to "-" removes the rule,// otherwise it is modified (if exists) or added//// Returns weather it was successfull (therefore the roster should be// redrawed) or not. If it failed, for example because of invalid color// name, it also prints the error.boolscr_RosterColor(constchar*status,constchar*wildcard,constchar*color){GSList*head;GSList*found=NULL;for(head=rostercolrules;head;head=g_slist_next(head)){rostercolor*rc=head->data;if((!strcmp(status,rc->status))&&(!strcmp(wildcard,rc->wildcard))){found=head;break;}}if(!strcmp(color,"-")){//Delete the ruleif(found){free_rostercolrule(found->data);rostercolrules=g_slist_delete_link(rostercolrules,found);returnTRUE;}else{scr_LogPrint(LPRINT_NORMAL,"No such color rule, nothing removed");returnFALSE;}}else{intcl=get_user_color(color);if(cl<0){scr_LogPrint(LPRINT_NORMAL,"No such color name");returnFALSE;}if(found){rostercolor*rc=found->data;rc->color=cl;}else{rostercolor*rc=g_new(rostercolor,1);rc->status=g_strdup(status);rc->wildcard=g_strdup(wildcard);rc->compiled=g_pattern_spec_new(wildcard);rc->color=cl;rostercolrules=g_slist_prepend(rostercolrules,rc);}returnTRUE;}}staticvoidParseColors(void){constchar*colors[]={"","","general","msgout","msghl","status","roster","rostersel","rosterselmsg","rosternewmsg","info","msgin",NULL};constchar*color;constchar*background=settings_opt_get("color_background");constchar*backselected=settings_opt_get("color_bgrostersel");constchar*backstatus=settings_opt_get("color_bgstatus");char*tmp;inti;// Initialize color attributesmemset(COLOR_ATTRIB,0,sizeof(COLOR_ATTRIB));// Default valuesif(!background)background="black";if(!backselected)backselected="cyan";if(!backstatus)backstatus="blue";for(i=0;colors[i];i++){tmp=g_strdup_printf("color_%s",colors[i]);color=settings_opt_get(tmp);g_free(tmp);if(color){if(!strncmp(color,"bright",6)){COLOR_ATTRIB[i+1]=A_BOLD;color+=6;}}switch(i+1){case1:init_pair(1,COLOR_BLACK,COLOR_WHITE);break;case2:init_pair(2,COLOR_WHITE,COLOR_BLACK);break;caseCOLOR_GENERAL:init_pair(i+1,((color)?FindColor(color):COLOR_WHITE),FindColor(background));break;caseCOLOR_MSGOUT:init_pair(i+1,((color)?FindColor(color):COLOR_CYAN),FindColor(background));break;caseCOLOR_MSGHL:init_pair(i+1,((color)?FindColor(color):COLOR_YELLOW),FindColor(background));break;caseCOLOR_STATUS:init_pair(i+1,((color)?FindColor(color):COLOR_WHITE),FindColor(backstatus));break;caseCOLOR_ROSTER:init_pair(i+1,((color)?FindColor(color):COLOR_GREEN),FindColor(background));break;caseCOLOR_ROSTERSEL:init_pair(i+1,((color)?FindColor(color):COLOR_BLUE),FindColor(backselected));break;caseCOLOR_ROSTERSELNMSG:init_pair(i+1,((color)?FindColor(color):COLOR_RED),FindColor(backselected));break;caseCOLOR_ROSTERNMSG:init_pair(i+1,((color)?FindColor(color):COLOR_RED),FindColor(background));break;caseCOLOR_INFO:init_pair(i+1,((color)?FindColor(color):COLOR_WHITE),FindColor(background));break;caseCOLOR_MSGIN:init_pair(i+1,((color)?FindColor(color):COLOR_WHITE),FindColor(background));break;}}for(i=COLOR_BLACK_FG;i<COLOR_max;i++){init_pair(i,color_fg_to_color(i),FindColor(background));if(i>=COLOR_BLACK_BOLD_FG)COLOR_ATTRIB[i]=A_BOLD;}if(!nickcols){char*ncolors=g_strdup(settings_opt_get("nick_colors"));if(ncolors){char*ncolor_start,*ncolor_end;ncolor_start=ncolor_end=ncolors;while(*ncolor_end)ncolor_end++;while(ncolors<ncolor_end&&*ncolors){if((*ncolors==' ')||(*ncolors=='\t')){ncolors++;}else{char*end=ncolors;intcl;while(*end&&(*end!=' ')&&(*end!='\t'))end++;*end='\0';cl=get_user_color(ncolors);if(cl<0){scr_LogPrint(LPRINT_NORMAL,"Unknown color %s",ncolors);}else{nickcols=g_realloc(nickcols,(++nickcolcount)*sizeof*nickcols);nickcols[nickcolcount-1]=cl;}ncolors=end+1;}}g_free(ncolor_start);}if(!nickcols){//Fallback to have somethingnickcolcount=1;nickcols=g_new(int,1);*nickcols=COLOR_GENERAL;}}}staticvoidinit_keycodes(void){add_keyseq("O5A",MKEY_EQUIV,521);// Ctrl-Upadd_keyseq("O5B",MKEY_EQUIV,514);// Ctrl-Downadd_keyseq("O5C",MKEY_EQUIV,518);// Ctrl-Rightadd_keyseq("O5D",MKEY_EQUIV,516);// Ctrl-Leftadd_keyseq("O6A",MKEY_EQUIV,520);// Shift-Upadd_keyseq("O6B",MKEY_EQUIV,513);// Shift-Downadd_keyseq("O6C",MKEY_EQUIV,402);// Shift-Rightadd_keyseq("O6D",MKEY_EQUIV,393);// Shift-Leftadd_keyseq("O2A",MKEY_EQUIV,520);// Shift-Upadd_keyseq("O2B",MKEY_EQUIV,513);// Shift-Downadd_keyseq("O2C",MKEY_EQUIV,402);// Shift-Rightadd_keyseq("O2D",MKEY_EQUIV,393);// Shift-Leftadd_keyseq("[5^",MKEY_CTRL_PGUP,0);// Ctrl-PageUpadd_keyseq("[6^",MKEY_CTRL_PGDOWN,0);// Ctrl-PageDownadd_keyseq("[5@",MKEY_CTRL_SHIFT_PGUP,0);// Ctrl-Shift-PageUpadd_keyseq("[6@",MKEY_CTRL_SHIFT_PGDOWN,0);// Ctrl-Shift-PageDownadd_keyseq("[7@",MKEY_CTRL_SHIFT_HOME,0);// Ctrl-Shift-Homeadd_keyseq("[8@",MKEY_CTRL_SHIFT_END,0);// Ctrl-Shift-Endadd_keyseq("[8^",MKEY_CTRL_END,0);// Ctrl-Endadd_keyseq("[7^",MKEY_CTRL_HOME,0);// Ctrl-Homeadd_keyseq("[2^",MKEY_CTRL_INS,0);// Ctrl-Insertadd_keyseq("[3^",MKEY_CTRL_DEL,0);// Ctrl-Delete// Xtermadd_keyseq("[1;5A",MKEY_EQUIV,521);// Ctrl-Upadd_keyseq("[1;5B",MKEY_EQUIV,514);// Ctrl-Downadd_keyseq("[1;5C",MKEY_EQUIV,518);// Ctrl-Rightadd_keyseq("[1;5D",MKEY_EQUIV,516);// Ctrl-Leftadd_keyseq("[1;6A",MKEY_EQUIV,520);// Ctrl-Shift-Upadd_keyseq("[1;6B",MKEY_EQUIV,513);// Ctrl-Shift-Downadd_keyseq("[1;6C",MKEY_EQUIV,402);// Ctrl-Shift-Rightadd_keyseq("[1;6D",MKEY_EQUIV,393);// Ctrl-Shift-Leftadd_keyseq("[1;6H",MKEY_CTRL_SHIFT_HOME,0);// Ctrl-Shift-Homeadd_keyseq("[1;6F",MKEY_CTRL_SHIFT_END,0);// Ctrl-Shift-Endadd_keyseq("[1;2A",MKEY_EQUIV,521);// Shift-Upadd_keyseq("[1;2B",MKEY_EQUIV,514);// Shift-Downadd_keyseq("[5;5~",MKEY_CTRL_PGUP,0);// Ctrl-PageUpadd_keyseq("[6;5~",MKEY_CTRL_PGDOWN,0);// Ctrl-PageDownadd_keyseq("[1;5F",MKEY_CTRL_END,0);// Ctrl-Endadd_keyseq("[1;5H",MKEY_CTRL_HOME,0);// Ctrl-Homeadd_keyseq("[2;5~",MKEY_CTRL_INS,0);// Ctrl-Insertadd_keyseq("[3;5~",MKEY_CTRL_DEL,0);// Ctrl-Delete// PuTTYadd_keyseq("[A",MKEY_EQUIV,521);// Ctrl-Upadd_keyseq("[B",MKEY_EQUIV,514);// Ctrl-Downadd_keyseq("[C",MKEY_EQUIV,518);// Ctrl-Rightadd_keyseq("[D",MKEY_EQUIV,516);// Ctrl-Left// screenadd_keyseq("Oa",MKEY_EQUIV,521);// Ctrl-Upadd_keyseq("Ob",MKEY_EQUIV,514);// Ctrl-Downadd_keyseq("Oc",MKEY_EQUIV,518);// Ctrl-Rightadd_keyseq("Od",MKEY_EQUIV,516);// Ctrl-Leftadd_keyseq("[a",MKEY_EQUIV,520);// Shift-Upadd_keyseq("[b",MKEY_EQUIV,513);// Shift-Downadd_keyseq("[c",MKEY_EQUIV,402);// Shift-Rightadd_keyseq("[d",MKEY_EQUIV,393);// Shift-Leftadd_keyseq("[5$",MKEY_SHIFT_PGUP,0);// Shift-PageUpadd_keyseq("[6$",MKEY_SHIFT_PGDOWN,0);// Shift-PageDown// VT100add_keyseq("[H",MKEY_EQUIV,KEY_HOME);// Homeadd_keyseq("[F",MKEY_EQUIV,KEY_END);// End// Konsole Linuxadd_keyseq("[1~",MKEY_EQUIV,KEY_HOME);// Homeadd_keyseq("[4~",MKEY_EQUIV,KEY_END);// End}// scr_init_bindings()// Create default key bindings// Return 0 if error and 1 if nonevoidscr_init_bindings(void){GString*sbuf=g_string_new("");// Common backspace key codes: 8, 127settings_set(SETTINGS_TYPE_BINDING,"8","iline char_bdel");// Ctrl-hsettings_set(SETTINGS_TYPE_BINDING,"127","iline char_bdel");g_string_printf(sbuf,"%d",KEY_BACKSPACE);settings_set(SETTINGS_TYPE_BINDING,sbuf->str,"iline char_bdel");g_string_printf(sbuf,"%d",KEY_DC);settings_set(SETTINGS_TYPE_BINDING,sbuf->str,"iline char_fdel");g_string_printf(sbuf,"%d",KEY_LEFT);settings_set(SETTINGS_TYPE_BINDING,sbuf->str,"iline bchar");g_string_printf(sbuf,"%d",KEY_RIGHT);settings_set(SETTINGS_TYPE_BINDING,sbuf->str,"iline fchar");settings_set(SETTINGS_TYPE_BINDING,"7","iline compl_cancel");// Ctrl-gg_string_printf(sbuf,"%d",KEY_UP);settings_set(SETTINGS_TYPE_BINDING,sbuf->str,"iline hist_beginning_search_bwd");g_string_printf(sbuf,"%d",KEY_DOWN);settings_set(SETTINGS_TYPE_BINDING,sbuf->str,"iline hist_beginning_search_fwd");g_string_printf(sbuf,"%d",KEY_PPAGE);settings_set(SETTINGS_TYPE_BINDING,sbuf->str,"roster up");g_string_printf(sbuf,"%d",KEY_NPAGE);settings_set(SETTINGS_TYPE_BINDING,sbuf->str,"roster down");g_string_printf(sbuf,"%d",KEY_HOME);settings_set(SETTINGS_TYPE_BINDING,sbuf->str,"iline iline_start");settings_set(SETTINGS_TYPE_BINDING,"1","iline iline_start");// Ctrl-ag_string_printf(sbuf,"%d",KEY_END);settings_set(SETTINGS_TYPE_BINDING,sbuf->str,"iline iline_end");settings_set(SETTINGS_TYPE_BINDING,"5","iline iline_end");// Ctrl-e// Ctrl-o (accept-line-and-down-history):settings_set(SETTINGS_TYPE_BINDING,"15","iline iline_accept_down_hist");settings_set(SETTINGS_TYPE_BINDING,"21","iline iline_bdel");// Ctrl-ug_string_printf(sbuf,"%d",KEY_EOL);settings_set(SETTINGS_TYPE_BINDING,sbuf->str,"iline iline_fdel");settings_set(SETTINGS_TYPE_BINDING,"11","iline iline_fdel");// Ctrl-ksettings_set(SETTINGS_TYPE_BINDING,"16","buffer up");// Ctrl-psettings_set(SETTINGS_TYPE_BINDING,"14","buffer down");// Ctrl-nsettings_set(SETTINGS_TYPE_BINDING,"20","iline char_swap");// Ctrl-tsettings_set(SETTINGS_TYPE_BINDING,"23","iline word_bdel");// Ctrl-wsettings_set(SETTINGS_TYPE_BINDING,"M98","iline bword");// Meta-bsettings_set(SETTINGS_TYPE_BINDING,"M102","iline fword");// Meta-fsettings_set(SETTINGS_TYPE_BINDING,"M100","iline word_fdel");// Meta-d// Ctrl-Left (2 codes):settings_set(SETTINGS_TYPE_BINDING,"515","iline bword");settings_set(SETTINGS_TYPE_BINDING,"516","iline bword");// Ctrl-Right (2 codes):settings_set(SETTINGS_TYPE_BINDING,"517","iline fword");settings_set(SETTINGS_TYPE_BINDING,"518","iline fword");settings_set(SETTINGS_TYPE_BINDING,"12","screen_refresh");// Ctrl-lsettings_set(SETTINGS_TYPE_BINDING,"27","chat_disable");// Escsettings_set(SETTINGS_TYPE_BINDING,"M27","chat_disable");// Esc-Escsettings_set(SETTINGS_TYPE_BINDING,"4","iline send_multiline");// Ctrl-dsettings_set(SETTINGS_TYPE_BINDING,"M117","iline word_upcase");// Meta-usettings_set(SETTINGS_TYPE_BINDING,"M108","iline word_downcase");// Meta-lsettings_set(SETTINGS_TYPE_BINDING,"M99","iline word_capit");// Meta-csettings_set(SETTINGS_TYPE_BINDING,"265","help");// Bind F1 to help...g_string_free(sbuf,TRUE);}// is_speckey(key)// Return TRUE if key is a special code, i.e. no char should be displayed on// the screen. It's not very nice, it's a workaround for the systems where// isprint(KEY_PPAGE) returns TRUE...staticintis_speckey(intkey){switch(key){case127:case393:case402:caseKEY_BACKSPACE:caseKEY_DC:caseKEY_LEFT:caseKEY_RIGHT:caseKEY_UP:caseKEY_DOWN:caseKEY_PPAGE:caseKEY_NPAGE:caseKEY_HOME:caseKEY_END:caseKEY_EOL:returnTRUE;}// Fn keysif(key>=265&&key<265+12)returnTRUE;// Special key combinationsif(key>=513&&key<=521)returnTRUE;returnFALSE;}voidscr_InitLocaleCharSet(void){setlocale(LC_CTYPE,"");LocaleCharSet=nl_langinfo(CODESET);utf8_mode=(strcmp(LocaleCharSet,"UTF-8")==0);}voidscr_InitCurses(void){/* Key sequences initialization */init_keycodes();initscr();raw();noecho();nonl();intrflush(stdscr,FALSE);start_color();use_default_colors();ParseColors();getmaxyx(stdscr,maxY,maxX);Log_Win_Height=DEFAULT_LOG_WIN_HEIGHT;// Note scr_DrawMainWindow() should be called early after scr_InitCurses()// to update Log_Win_Height and set max{X,Y}inputLine[0]=0;ptr_inputline=inputLine;Curses=TRUE;return;}voidscr_TerminateCurses(void){if(!Curses)return;clear();refresh();endwin();Curses=FALSE;return;}inlinevoidscr_Beep(void){beep();}// This and following belongs to dynamic setting of time prefixstaticconstchar*timeprefixes[]={"%m-%d %H:%M ","%H:%M "," "};staticconstchar*spectimeprefixes[]={"%m-%d %H:%M:%S ","%H:%M:%S "," "};staticinttimepreflengths[]={17,11,6};staticconstchar*gettprefix(){returntimeprefixes[settings_opt_get_int("time_prefix")];}staticconstchar*getspectprefix(){returnspectimeprefixes[settings_opt_get_int("time_prefix")];}staticunsignedgetprefixwidth(){returntimepreflengths[settings_opt_get_int("time_prefix")];}// scr_LogPrint(...)// Display a message in the log window.// This function will convert from UTF-8 unless the LPRINT_NOTUTF8 flag is set.voidscr_LogPrint(unsignedintflag,constchar*fmt,...){time_ttimestamp;charstrtimestamp[64];char*buffer,*btext;char*convbuf1=NULL,*convbuf2=NULL;va_listap;if(!(flag&~LPRINT_NOTUTF8))return;// Shouldn't happentimestamp=time(NULL);strftime(strtimestamp,48,"[%H:%M:%S]",localtime(&timestamp));va_start(ap,fmt);btext=g_strdup_vprintf(fmt,ap);va_end(ap);if(flag&LPRINT_NORMAL){char*buffer_locale;char*buf_specialwindow;buffer=g_strdup_printf("%s %s",strtimestamp,btext);// Convert buffer to current locale for wprintw()if(!(flag&LPRINT_NOTUTF8))buffer_locale=convbuf1=from_utf8(buffer);elsebuffer_locale=buffer;if(!buffer_locale){wprintw(logWnd,"\n%s*Error: cannot convert string to locale.",strtimestamp);update_panels();g_free(buffer);g_free(btext);return;}// For the special status buffer, we need utf-8, but without the timestampif(flag&LPRINT_NOTUTF8)buf_specialwindow=convbuf2=to_utf8(btext);elsebuf_specialwindow=btext;if(Curses){wprintw(logWnd,"\n%s",buffer_locale);update_panels();scr_WriteInWindow(NULL,buf_specialwindow,timestamp,HBB_PREFIX_SPECIAL,FALSE,0);}else{printf("%s\n",buffer_locale);// ncurses are not initialized yet, so we call directly hbuf routinehbuf_add_line(&statushbuf,buf_specialwindow,timestamp,HBB_PREFIX_SPECIAL,0,0,0);}g_free(convbuf1);g_free(convbuf2);g_free(buffer);}if(flag&(LPRINT_LOG|LPRINT_DEBUG)){strftime(strtimestamp,23,"[%Y-%m-%d %H:%M:%S]",localtime(&timestamp));buffer=g_strdup_printf("%s %s\n",strtimestamp,btext);ut_WriteLog(flag,buffer);g_free(buffer);}g_free(btext);}staticwinbuf*scr_SearchWindow(constchar*winId,intspecial){char*id;winbuf*wbp;if(special)returnstatusWindow;// Only one special window atm.if(!winId)returnNULL;id=g_strdup(winId);mc_strtolower(id);wbp=g_hash_table_lookup(winbufhash,id);g_free(id);returnwbp;}intscr_BuddyBufferExists(constchar*bjid){return(scr_SearchWindow(bjid,FALSE)!=NULL);}// scr_new_buddy(title, dontshow)// Note: title (aka winId/jid) can be NULL for special buffersstaticwinbuf*scr_new_buddy(constchar*title,intdont_show){winbuf*tmp;tmp=g_new0(winbuf,1);tmp->win=activechatWnd;tmp->panel=activechatPanel;if(!dont_show){currentWindow=tmp;}else{if(currentWindow)top_panel(currentWindow->panel);elsetop_panel(chatPanel);}update_panels();// If title is NULL, this is a special bufferif(title){char*id;id=hlog_get_log_jid(title);if(id){winbuf*wb=scr_SearchWindow(id,FALSE);if(!wb)wb=scr_new_buddy(id,TRUE);tmp->bd=wb->bd;g_free(id);}else{// Load buddy history from file (if enabled)tmp->bd=g_new0(buffdata,1);hlog_read_history(title,&tmp->bd->hbuf,maxX-Roster_Width-getprefixwidth());}id=g_strdup(title);mc_strtolower(id);g_hash_table_insert(winbufhash,id,tmp);}else{tmp->bd=g_new0(buffdata,1);}returntmp;}// scr_UpdateWindow()// (Re-)Display the given chat window.staticvoidscr_UpdateWindow(winbuf*win_entry){intn;intwidth;hbb_line**lines,*line;GList*hbuf_head;chardate[64];intcolor;width=getmaxx(win_entry->win);// Should the window be empty?if(win_entry->bd->cleared){werase(win_entry->win);return;}// win_entry->bd->top is the top message of the screen. If it set to NULL,// we are displaying the last messages.// We will show the last CHAT_WIN_HEIGHT lines.// Let's find out where it begins.if(!win_entry->bd->top||(g_list_position(g_list_first(win_entry->bd->hbuf),win_entry->bd->top)==-1)){// Move up CHAT_WIN_HEIGHT lineswin_entry->bd->hbuf=g_list_last(win_entry->bd->hbuf);hbuf_head=win_entry->bd->hbuf;win_entry->bd->top=NULL;// (Just to make sure)n=0;while(hbuf_head&&(n<CHAT_WIN_HEIGHT-1)&&g_list_previous(hbuf_head)){hbuf_head=g_list_previous(hbuf_head);n++;}// If the buffer is locked, remember current "top" line for the next time.if(win_entry->bd->lock)win_entry->bd->top=hbuf_head;}elsehbuf_head=win_entry->bd->top;// Get the last CHAT_WIN_HEIGHT lines.lines=hbuf_get_lines(hbuf_head,CHAT_WIN_HEIGHT);// Display these linesfor(n=0;n<CHAT_WIN_HEIGHT;n++){wmove(win_entry->win,n,0);line=*(lines+n);if(line){if(line->flags&HBB_PREFIX_HLIGHT_OUT)color=COLOR_MSGOUT;elseif(line->flags&HBB_PREFIX_HLIGHT)color=COLOR_MSGHL;elseif(line->flags&HBB_PREFIX_INFO)color=COLOR_INFO;elseif(line->flags&HBB_PREFIX_IN)color=COLOR_MSGIN;elsecolor=COLOR_GENERAL;if(color!=COLOR_GENERAL)wattrset(win_entry->win,get_color(color));if(line->timestamp&&!(line->flags&(HBB_PREFIX_SPECIAL|HBB_PREFIX_CONT))){strftime(date,30,gettprefix(),localtime(&line->timestamp));}elsestrcpy(date," ");if(!(line->flags&HBB_PREFIX_CONT)){if(line->flags&HBB_PREFIX_INFO){chardir='*';if(line->flags&HBB_PREFIX_IN)dir='<';elseif(line->flags&HBB_PREFIX_OUT)dir='>';wprintw(win_entry->win,"%s*%c* ",date,dir);}elseif(line->flags&HBB_PREFIX_ERR){chardir='#';if(line->flags&HBB_PREFIX_IN)dir='<';elseif(line->flags&HBB_PREFIX_OUT)dir='>';wprintw(win_entry->win,"%s#%c# ",date,dir);}elseif(line->flags&HBB_PREFIX_IN){charcryptflag=line->flags&HBB_PREFIX_PGPCRYPT?'~':'=';wprintw(win_entry->win,"%s<%c= ",date,cryptflag);}elseif(line->flags&HBB_PREFIX_OUT){charcryptflag=line->flags&HBB_PREFIX_PGPCRYPT?'~':'-';wprintw(win_entry->win,"%s-%c> ",date,cryptflag);}elseif(line->flags&HBB_PREFIX_SPECIAL){strftime(date,30,getspectprefix(),localtime(&line->timestamp));wprintw(win_entry->win,"%s ",date);}else{wprintw(win_entry->win,"%s ",date);}}else{wprintw(win_entry->win," ");}// Make sure we are at the right positionwmove(win_entry->win,n,getprefixwidth()-1);//The MUC nick - overwrite with propper colorif(line->mucnicklen){//Store the char after the nickchartmp=line->text[line->mucnicklen];muccoltypetype=glob_muccol,*typetmp;//Terminate the string after the nickline->text[line->mucnicklen]='\0';char*mucjid=g_utf8_strdown(CURRENT_JID,-1);if(muccolors){typetmp=g_hash_table_lookup(muccolors,mucjid);if(typetmp)type=*typetmp;}g_free(mucjid);nickcolor*actual=NULL;// Need to generate some random color?if((type==MC_ALL)&&(!nickcolors||!g_hash_table_lookup(nickcolors,line->text))){ensure_string_htable(&nickcolors,NULL);char*snick=g_strdup(line->text),*mnick=g_strdup(line->text);nickcolor*nc=g_new(nickcolor,1);nc->color=nickcols[random()%nickcolcount];nc->manual=false;*snick='<';snick[strlen(snick)-1]='>';*mnick='*';mnick[strlen(mnick)-1]=' ';//Insert themg_hash_table_insert(nickcolors,snick,nc);g_hash_table_insert(nickcolors,mnick,nc);}if(nickcolors)actual=g_hash_table_lookup(nickcolors,line->text);if(actual&&((type==MC_ALL)||(actual->manual))&&(line->flags&HBB_PREFIX_IN)&&(!(line->flags&HBB_PREFIX_HLIGHT_OUT)))wattrset(win_entry->win,get_color(actual->color));wprintw(win_entry->win,"%s",line->text);//Return the charline->text[line->mucnicklen]=tmp;//Return the color backwattrset(win_entry->win,get_color(color));}// Display text linewprintw(win_entry->win,"%s",line->text+line->mucnicklen);wclrtoeol(win_entry->win);// Return the color backif(color!=COLOR_GENERAL)wattrset(win_entry->win,get_color(COLOR_GENERAL));g_free(line->text);g_free(line);}else{wclrtobot(win_entry->win);break;}}g_free(lines);}staticwinbuf*scr_CreateWindow(constchar*winId,intspecial,intdont_show){if(special){if(!statusWindow){statusWindow=scr_new_buddy(NULL,dont_show);statusWindow->bd->hbuf=statushbuf;}returnstatusWindow;}else{returnscr_new_buddy(winId,dont_show);}}// scr_ShowWindow()// Display the chat window with the given identifier.// "special" must be true if this is a special buffer window.staticvoidscr_ShowWindow(constchar*winId,intspecial){winbuf*win_entry;win_entry=scr_SearchWindow(winId,special);if(!win_entry){win_entry=scr_CreateWindow(winId,special,FALSE);}top_panel(win_entry->panel);currentWindow=win_entry;chatmode=TRUE;if(!win_entry->bd->lock)roster_msg_setflag(winId,special,FALSE);if(!special)roster_setflags(winId,ROSTER_FLAG_LOCK,TRUE);update_roster=TRUE;// Refresh the windowscr_UpdateWindow(win_entry);// Finished :)update_panels();top_panel(inputPanel);}// scr_ShowBuddyWindow()// Display the chat window buffer for the current buddy.voidscr_ShowBuddyWindow(void){constgchar*bjid;if(!current_buddy){bjid=NULL;}else{bjid=CURRENT_JID;if(buddy_gettype(BUDDATA(current_buddy))&ROSTER_TYPE_SPECIAL){scr_ShowWindow(buddy_getname(BUDDATA(current_buddy)),TRUE);return;}}if(!bjid){top_panel(chatPanel);top_panel(inputPanel);currentWindow=NULL;return;}scr_ShowWindow(bjid,FALSE);}// scr_UpdateBuddyWindow()// (Re)Display the current window.// If chatmode is enabled, call scr_ShowBuddyWindow(),// else display the chat window.inlinevoidscr_UpdateBuddyWindow(void){if(chatmode){scr_ShowBuddyWindow();return;}top_panel(chatPanel);top_panel(inputPanel);}// scr_WriteInWindow()// Write some text in the winId window (this usually is a jid).// Use winId == NULL for the special status buffer.// Lines are splitted when they are too long to fit in the chat window.// If this window doesn't exist, it is created.voidscr_WriteInWindow(constchar*winId,constchar*text,time_ttimestamp,unsignedintprefix_flags,intforce_show,unsignedmucnicklen){winbuf*win_entry;char*text_locale;intdont_show=FALSE;intspecial;guintnum_history_blocks;boolsetmsgflg=FALSE;char*nicktmp,*nicklocaltmp;// Look for the window entry.special=(winId==NULL);win_entry=scr_SearchWindow(winId,special);// Do we have to really show the window?if(!chatmode)dont_show=TRUE;elseif((!force_show)&&((!currentWindow||(currentWindow!=win_entry))))dont_show=TRUE;// If the window entry doesn't exist yet, let's create it.if(!win_entry){win_entry=scr_CreateWindow(winId,special,dont_show);}// The message must be displayed -> update top pointerif(win_entry->bd->cleared)win_entry->bd->top=g_list_last(win_entry->bd->hbuf);// Make sure we do not free the buffer while it's locked or when// top is set.if(win_entry->bd->lock||win_entry->bd->top)num_history_blocks=0U;elsenum_history_blocks=get_max_history_blocks();text_locale=from_utf8(text);//Convert the nick alone and compute its lengthif(mucnicklen){nicktmp=g_strndup(text,mucnicklen);nicklocaltmp=from_utf8(nicktmp);mucnicklen=strlen(nicklocaltmp);g_free(nicklocaltmp);g_free(nicktmp);}hbuf_add_line(&win_entry->bd->hbuf,text_locale,timestamp,prefix_flags,maxX-Roster_Width-getprefixwidth(),num_history_blocks,mucnicklen);g_free(text_locale);if(win_entry->bd->cleared){win_entry->bd->cleared=FALSE;if(g_list_next(win_entry->bd->top))win_entry->bd->top=g_list_next(win_entry->bd->top);}// Make sure the last line appears in the window; update top if necessaryif(!win_entry->bd->lock&&win_entry->bd->top){intdist;GList*first=g_list_first(win_entry->bd->hbuf);dist=g_list_position(first,g_list_last(win_entry->bd->hbuf))-g_list_position(first,win_entry->bd->top);if(dist>=CHAT_WIN_HEIGHT)win_entry->bd->top=NULL;}if(!dont_show){if(win_entry->bd->lock)setmsgflg=TRUE;// Show and refresh the windowtop_panel(win_entry->panel);scr_UpdateWindow(win_entry);top_panel(inputPanel);update_panels();}elseif(!(prefix_flags&HBB_PREFIX_NOFLAG)){setmsgflg=TRUE;}if(setmsgflg&&!special){if(special&&!winId)winId=SPECIAL_BUFFER_STATUS_ID;roster_msg_setflag(winId,special,TRUE);update_roster=TRUE;}}// scr_UpdateMainStatus()// Redraw the main (bottom) status line.voidscr_UpdateMainStatus(intforceupdate){char*sm=from_utf8(jb_getstatusmsg());werase(mainstatusWnd);mvwprintw(mainstatusWnd,0,0,"%c[%c] %s",(unread_msg(NULL)?'#':' '),imstatus2char[jb_getstatus()],(sm?sm:""));if(forceupdate){top_panel(inputPanel);update_panels();}g_free(sm);}// scr_DrawMainWindow()// Set fullinit to TRUE to also create panels. Set it to FALSE for a resize.//// I think it could be improved a _lot_ but I'm really not an ncurses// expert... :-\ Mikael.//voidscr_DrawMainWindow(unsignedintfullinit){intrequested_size;gchar*ver,*message;intchat_y_pos,chatstatus_y_pos,log_y_pos;introster_x_pos,chat_x_pos;Log_Win_Height=DEFAULT_LOG_WIN_HEIGHT;requested_size=settings_opt_get_int("log_win_height");if(requested_size>0){if(maxY>requested_size+3)Log_Win_Height=requested_size+2;elseLog_Win_Height=((maxY>5)?(maxY-2):3);}elseif(requested_size<0){Log_Win_Height=3;}if(maxY<Log_Win_Height+2){if(maxY<5){Log_Win_Height=3;maxY=Log_Win_Height+2;}else{Log_Win_Height=maxY-2;}}if(roster_hidden){Roster_Width=0;}else{requested_size=settings_opt_get_int("roster_width");if(requested_size>1)Roster_Width=requested_size;elseif(requested_size==1)Roster_Width=2;elseRoster_Width=DEFAULT_ROSTER_WIDTH;}log_win_on_top=(settings_opt_get_int("log_win_on_top")==1);roster_win_on_right=(settings_opt_get_int("roster_win_on_right")==1);if(log_win_on_top){chat_y_pos=Log_Win_Height-1;log_y_pos=0;chatstatus_y_pos=Log_Win_Height-2;}else{chat_y_pos=0;log_y_pos=CHAT_WIN_HEIGHT+1;chatstatus_y_pos=CHAT_WIN_HEIGHT;}if(roster_win_on_right){roster_x_pos=maxX-Roster_Width;chat_x_pos=0;}else{roster_x_pos=0;chat_x_pos=Roster_Width;}if(fullinit){if(!winbufhash)winbufhash=g_hash_table_new_full(g_str_hash,g_str_equal,g_free,NULL);/* Create windows */rosterWnd=newwin(CHAT_WIN_HEIGHT,Roster_Width,chat_y_pos,roster_x_pos);chatWnd=newwin(CHAT_WIN_HEIGHT,maxX-Roster_Width,chat_y_pos,chat_x_pos);activechatWnd=newwin(CHAT_WIN_HEIGHT,maxX-Roster_Width,chat_y_pos,chat_x_pos);logWnd=newwin(Log_Win_Height-2,maxX,log_y_pos,0);chatstatusWnd=newwin(1,maxX,chatstatus_y_pos,0);mainstatusWnd=newwin(1,maxX,maxY-2,0);inputWnd=newwin(1,maxX,maxY-1,0);if(!rosterWnd||!chatWnd||!logWnd||!inputWnd){scr_TerminateCurses();fprintf(stderr,"Cannot create windows!\n");exit(EXIT_FAILURE);}wbkgd(rosterWnd,get_color(COLOR_GENERAL));wbkgd(chatWnd,get_color(COLOR_GENERAL));wbkgd(activechatWnd,get_color(COLOR_GENERAL));wbkgd(logWnd,get_color(COLOR_GENERAL));wbkgd(chatstatusWnd,get_color(COLOR_STATUS));wbkgd(mainstatusWnd,get_color(COLOR_STATUS));}else{/* Resize/move windows */wresize(rosterWnd,CHAT_WIN_HEIGHT,Roster_Width);wresize(chatWnd,CHAT_WIN_HEIGHT,maxX-Roster_Width);wresize(logWnd,Log_Win_Height-2,maxX);mvwin(chatWnd,chat_y_pos,chat_x_pos);mvwin(rosterWnd,chat_y_pos,roster_x_pos);mvwin(logWnd,log_y_pos,0);// Resize & move chat status windowwresize(chatstatusWnd,1,maxX);mvwin(chatstatusWnd,chatstatus_y_pos,0);// Resize & move main status windowwresize(mainstatusWnd,1,maxX);mvwin(mainstatusWnd,maxY-2,0);// Resize & move input line windowwresize(inputWnd,1,maxX);mvwin(inputWnd,maxY-1,0);werase(chatWnd);}/* Draw/init windows */ver=mcabber_version();message=g_strdup_printf("MCabber version %s.\n",ver);mvwprintw(chatWnd,0,0,message);mvwprintw(chatWnd,1,0,"http://www.lilotux.net/~mikael/mcabber/");g_free(ver);g_free(message);// Auto-scrolling in log windowscrollok(logWnd,TRUE);if(fullinit){// Enable keypad (+ special keys)keypad(inputWnd,TRUE);nodelay(inputWnd,TRUE);// Create panelsrosterPanel=new_panel(rosterWnd);chatPanel=new_panel(chatWnd);activechatPanel=new_panel(activechatWnd);logPanel=new_panel(logWnd);chatstatusPanel=new_panel(chatstatusWnd);mainstatusPanel=new_panel(mainstatusWnd);inputPanel=new_panel(inputWnd);// Build the buddylist at least once, to make sure the special buffer// is addedbuddylist_build();// Init prev_chatwidth; this variable will be used to prevent us// from rewrapping buffers when the width doesn't change.prev_chatwidth=maxX-Roster_Width-getprefixwidth();// Wrap existing status buffer lineshbuf_rebuild(&statushbuf,prev_chatwidth);#ifndef UNICODEif(utf8_mode)scr_LogPrint(LPRINT_NORMAL,"WARNING: Compiled without full UTF-8 support!");#endif}else{// Update panelsreplace_panel(rosterPanel,rosterWnd);replace_panel(chatPanel,chatWnd);replace_panel(logPanel,logWnd);replace_panel(chatstatusPanel,chatstatusWnd);replace_panel(mainstatusPanel,mainstatusWnd);replace_panel(inputPanel,inputWnd);}// We'll need to redraw the rosterupdate_roster=TRUE;return;}staticvoidresize_win_buffer(gpointerkey,gpointervalue,gpointerdata){winbuf*wbp=value;structdimensions*dim=data;intchat_x_pos,chat_y_pos;intnew_chatwidth;if(!(wbp&&wbp->win))return;if(log_win_on_top)chat_y_pos=Log_Win_Height-1;elsechat_y_pos=0;if(roster_win_on_right)chat_x_pos=0;elsechat_x_pos=Roster_Width;// Resize/move buddy windowwresize(wbp->win,dim->l,dim->c);mvwin(wbp->win,chat_y_pos,chat_x_pos);werase(wbp->win);// If a panel exists, replace the old window with the newif(wbp->panel)replace_panel(wbp->panel,wbp->win);// Redo line wrappingwbp->bd->top=hbuf_previous_persistent(wbp->bd->top);new_chatwidth=maxX-Roster_Width-getprefixwidth();if(new_chatwidth!=prev_chatwidth)hbuf_rebuild(&wbp->bd->hbuf,new_chatwidth);}// scr_Resize()// Function called when the window is resized.// - Resize windows// - Rewrap lines in each buddy buffervoidscr_Resize(void){structdimensionsdim;// First, update the global variablesgetmaxyx(stdscr,maxY,maxX);// scr_DrawMainWindow() will take care of maxY and Log_Win_Height// Make sure the cursor stays inside the windowcheck_offset(0);// Resize windows and update panelsscr_DrawMainWindow(FALSE);// Resize all buddy windowsdim.l=CHAT_WIN_HEIGHT;dim.c=maxX-Roster_Width;if(dim.c<1)dim.c=1;// Resize all buffersg_hash_table_foreach(winbufhash,resize_win_buffer,&dim);// Resize/move special status bufferif(statusWindow)resize_win_buffer(NULL,statusWindow,&dim);// Update prev_chatwidth, now that all buffers have been resizedprev_chatwidth=maxX-Roster_Width-getprefixwidth();// Refresh current buddy windowif(chatmode)scr_ShowBuddyWindow();}// scr_UpdateChatStatus(forceupdate)// Redraw the buddy status bar.// Set forceupdate to TRUE if update_panels() must be called.voidscr_UpdateChatStatus(intforceupdate){unsignedshortbtype,isgrp,ismuc,isspe;constchar*fullname;constchar*msg=NULL;charstatus;char*buf,*buf_locale;// Usually we need to update the bottom status line too,// at least to refresh the pending message flag.scr_UpdateMainStatus(FALSE);// Clear the linewerase(chatstatusWnd);if(chatmode)wprintw(chatstatusWnd,"~");if(!current_buddy){if(forceupdate){update_panels();}return;}fullname=buddy_getname(BUDDATA(current_buddy));btype=buddy_gettype(BUDDATA(current_buddy));isgrp=btype&ROSTER_TYPE_GROUP;ismuc=btype&ROSTER_TYPE_ROOM;isspe=btype&ROSTER_TYPE_SPECIAL;if(chatmode&&!isgrp){winbuf*win_entry;win_entry=scr_SearchWindow(buddy_getjid(BUDDATA(current_buddy)),isspe);if(win_entry&&win_entry->bd->lock)mvwprintw(chatstatusWnd,0,0,"*");}if(isgrp||isspe){buf_locale=from_utf8(fullname);if(isgrp)mvwprintw(chatstatusWnd,0,5,"Group: %s",buf_locale);elsemvwprintw(chatstatusWnd,0,5,"Special buffer: %s",buf_locale);g_free(buf_locale);if(forceupdate){update_panels();}return;}status='?';if(ismuc){if(buddy_getinsideroom(BUDDATA(current_buddy)))status='C';elsestatus='x';}elseif(jb_getstatus()!=offline){enumimstatusbudstate;budstate=buddy_getstatus(BUDDATA(current_buddy),NULL);if(budstate<imstatus_size)status=imstatus2char[budstate];}// No status message for groups & MUC roomsif(!isgrp&&!ismuc){GSList*resources,*p_res;resources=buddy_getresources(BUDDATA(current_buddy));msg=buddy_getstatusmsg(BUDDATA(current_buddy),resources?resources->data:"");// Free the resources list datafor(p_res=resources;p_res;p_res=g_slist_next(p_res))g_free(p_res->data);g_slist_free(resources);}elseif(ismuc){msg=buddy_gettopic(BUDDATA(current_buddy));}if(!msg)msg="";buf=g_strdup_printf("[%c] Buddy: %s -- %s",status,fullname,msg);replace_nl_with_dots(buf);buf_locale=from_utf8(buf);mvwprintw(chatstatusWnd,0,1,"%s",buf_locale);g_free(buf_locale);g_free(buf);// Display chatstates of the contact, if available.if(btype&ROSTER_TYPE_USER){chareventchar=0;guintevent;// We do not specify the resource here, so one of the resources with the// highest priority will be used.event=buddy_resource_getevents(BUDDATA(current_buddy),NULL);if(event==ROSTER_EVENT_ACTIVE)eventchar='A';elseif(event==ROSTER_EVENT_COMPOSING)eventchar='C';elseif(event==ROSTER_EVENT_PAUSED)eventchar='P';elseif(event==ROSTER_EVENT_INACTIVE)eventchar='I';elseif(event==ROSTER_EVENT_GONE)eventchar='G';if(eventchar)mvwprintw(chatstatusWnd,0,maxX-3,"[%c]",eventchar);}if(forceupdate){update_panels();}}// scr_DrawRoster()// Display the buddylist (not really the roster) on the screenvoidscr_DrawRoster(void){staticintoffset=0;char*name,*rline;intmaxx,maxy;GList*buddy;inti,n;intrOffset;intcursor_backup;charstatus,pending;enumimstatuscurrentstatus=jb_getstatus();intx_pos;// We can reset update_rosterupdate_roster=FALSE;getmaxyx(rosterWnd,maxy,maxx);maxx--;// Last char is for vertical bordercursor_backup=curs_set(0);if(!buddylist)offset=0;elsescr_UpdateChatStatus(FALSE);// Cleanup of roster windowwerase(rosterWnd);if(Roster_Width){intline_x_pos=roster_win_on_right?0:Roster_Width-1;// Redraw the vertical line (not very good...)wattrset(rosterWnd,get_color(COLOR_GENERAL));for(i=0;i<CHAT_WIN_HEIGHT;i++)mvwaddch(rosterWnd,i,line_x_pos,ACS_VLINE);}// Leave now if buddylist is empty or the roster is hiddenif(!buddylist||!Roster_Width){update_panels();curs_set(cursor_backup);return;}// Update offset if necessary// a) Try to show as many buddylist items as possiblei=g_list_length(buddylist)-maxy;if(i<0)i=0;if(i<offset)offset=i;// b) Make sure the current_buddy is visiblei=g_list_position(buddylist,current_buddy);if(i==-1){// This is badscr_LogPrint(LPRINT_NORMAL,"Doh! Can't find current selected buddy!!");curs_set(cursor_backup);return;}elseif(i<offset){offset=i;}elseif(i+1>offset+maxy){offset=i+1-maxy;}if(roster_win_on_right)x_pos=1;// 1 char offset (vertical line)elsex_pos=0;name=g_new0(char,4*Roster_Width);rline=g_new0(char,4*Roster_Width+1);buddy=buddylist;rOffset=offset;for(i=0;i<maxy&&buddy;buddy=g_list_next(buddy)){unsignedshortbflags,btype,ismsg,isgrp,ismuc,ishid,isspe;gchar*rline_locale;GSList*resources,*p_res;bflags=buddy_getflags(BUDDATA(buddy));btype=buddy_gettype(BUDDATA(buddy));ismsg=bflags&ROSTER_FLAG_MSG;ishid=bflags&ROSTER_FLAG_HIDE;isgrp=btype&ROSTER_TYPE_GROUP;ismuc=btype&ROSTER_TYPE_ROOM;isspe=btype&ROSTER_TYPE_SPECIAL;if(rOffset>0){rOffset--;continue;}status='?';pending=' ';resources=buddy_getresources(BUDDATA(buddy));for(p_res=resources;p_res;p_res=g_slist_next(p_res)){guintevents=buddy_resource_getevents(BUDDATA(buddy),p_res?p_res->data:"");if((events&ROSTER_EVENT_PAUSED)&&pending!='+')pending='.';if(events&ROSTER_EVENT_COMPOSING)pending='+';g_free(p_res->data);}g_slist_free(resources);// Display message notice if there is a message flag, but not// for unfolded groups.if(ismsg&&(!isgrp||ishid)){pending='#';}if(ismuc){if(buddy_getinsideroom(BUDDATA(buddy)))status='C';elsestatus='x';}elseif(currentstatus!=offline){enumimstatusbudstate;budstate=buddy_getstatus(BUDDATA(buddy),NULL);if(budstate<imstatus_size)status=imstatus2char[budstate];}if(buddy==current_buddy){if(pending=='#')wattrset(rosterWnd,get_color(COLOR_ROSTERSELNMSG));elsewattrset(rosterWnd,get_color(COLOR_ROSTERSEL));// The 3 following lines aim at coloring the whole linewmove(rosterWnd,i,x_pos);for(n=0;n<maxx;n++)waddch(rosterWnd,' ');}else{if(pending=='#')wattrset(rosterWnd,get_color(COLOR_ROSTERNMSG));else{intcolor=get_color(COLOR_ROSTER);if((!isspe)&&(!isgrp)){//Look for color rulesGSList*head;constchar*jid=buddy_getjid(BUDDATA(buddy));for(head=rostercolrules;head;head=g_slist_next(head)){rostercolor*rc=head->data;if(g_pattern_match_string(rc->compiled,jid)&&(!strcmp("*",rc->status)||strchr(rc->status,status))){color=get_color(rc->color);break;}}}wattrset(rosterWnd,color);}}if(Roster_Width>7)g_utf8_strncpy(name,buddy_getname(BUDDATA(buddy)),Roster_Width-7);elsename[0]=0;if(isgrp){char*sep;if(ishid)sep="+++";elsesep="---";snprintf(rline,4*Roster_Width," %c%s %s",pending,sep,name);}elseif(isspe){snprintf(rline,4*Roster_Width," %c%s",pending,name);}else{charsepleft='[';charsepright=']';if(btype&ROSTER_TYPE_USER){guintsubtype=buddy_getsubscription(BUDDATA(buddy));if(status=='_'&&!(subtype&sub_to))status='?';if(!(subtype&sub_from)){sepleft='{';sepright='}';}}snprintf(rline,4*Roster_Width," %c%c%c%c %s",pending,sepleft,status,sepright,name);}rline_locale=from_utf8(rline);mvwprintw(rosterWnd,i,x_pos,"%s",rline_locale);g_free(rline_locale);i++;}g_free(rline);g_free(name);top_panel(inputPanel);update_panels();curs_set(cursor_backup);}// scr_RosterVisibility(status)// Set the roster visibility:// status=1 Show roster// status=0 Hide roster// status=-1 Toggle roster statusvoidscr_RosterVisibility(intstatus){intold_roster_status=roster_hidden;if(status>0)roster_hidden=FALSE;elseif(status==0)roster_hidden=TRUE;elseroster_hidden=!roster_hidden;if(roster_hidden!=old_roster_status){if(roster_hidden){// Enter chat modescr_set_chatmode(TRUE);scr_ShowBuddyWindow();}// Recalculate windows size and redrawscr_Resize();redrawwin(stdscr);}}inlinevoidscr_WriteMessage(constchar*bjid,constchar*text,time_ttimestamp,guintprefix_flags,unsignedmucnicklen){char*xtext;if(!timestamp)timestamp=time(NULL);xtext=ut_expand_tabs(text);// Expand tabs and filter out some charsscr_WriteInWindow(bjid,xtext,timestamp,prefix_flags,FALSE,mucnicklen);if(xtext!=(char*)text)g_free(xtext);}// If prefix is NULL, HBB_PREFIX_IN is supposed.voidscr_WriteIncomingMessage(constchar*jidfrom,constchar*text,time_ttimestamp,guintprefix,unsignedmucnicklen){if(!(prefix&~HBB_PREFIX_NOFLAG&~HBB_PREFIX_HLIGHT&~HBB_PREFIX_HLIGHT_OUT&~HBB_PREFIX_PGPCRYPT))prefix|=HBB_PREFIX_IN;scr_WriteMessage(jidfrom,text,timestamp,prefix,mucnicklen);}voidscr_WriteOutgoingMessage(constchar*jidto,constchar*text,guintprefix){GSList*roster_elt;roster_elt=roster_find(jidto,jidsearch,ROSTER_TYPE_USER|ROSTER_TYPE_AGENT|ROSTER_TYPE_ROOM);scr_WriteMessage(jidto,text,0,prefix|HBB_PREFIX_OUT|HBB_PREFIX_HLIGHT_OUT,0);// Show jidto's buffer unless the buddy is not in the buddylistif(roster_elt&&g_list_position(buddylist,roster_elt->data)!=-1)scr_ShowWindow(jidto,FALSE);}staticinlinevoidset_autoaway(boolsetaway){staticenumimstatusoldstatus;staticchar*oldmsg;Autoaway=setaway;if(setaway){constchar*msg,*prevmsg;oldstatus=jb_getstatus();if(oldmsg){g_free(oldmsg);oldmsg=NULL;}prevmsg=jb_getstatusmsg();msg=settings_opt_get("message_autoaway");if(!msg)msg=prevmsg;if(prevmsg)oldmsg=g_strdup(prevmsg);jb_setstatus(away,NULL,msg,FALSE);}else{// Backjb_setstatus(oldstatus,NULL,(oldmsg?oldmsg:""),FALSE);if(oldmsg){g_free(oldmsg);oldmsg=NULL;}}}longintscr_GetAutoAwayTimeout(time_tnow){enumimstatuscur_st;unsignedintautoaway_timeout=settings_opt_get_int("autoaway");if(Autoaway||!autoaway_timeout)return86400;cur_st=jb_getstatus();// Auto-away is disabled for the following statesif((cur_st!=available)&&(cur_st!=freeforchat))return86400;if(now>=LastActivity+(time_t)autoaway_timeout)return0;elsereturnLastActivity+(time_t)autoaway_timeout-now;}// set_chatstate(state)// Set the current chat state (0=active, 1=composing, 2=paused)// If the chat state has changed, call jb_send_chatstate()staticinlinevoidset_chatstate(intstate){#if defined JEP0022 || defined JEP0085if(chatstates_disabled)return;if(!chatmode)state=0;if(state!=chatstate){chatstate=state;if(current_buddy&&buddy_gettype(BUDDATA(current_buddy))==ROSTER_TYPE_USER){guintjep_state;if(chatstate==1)jep_state=ROSTER_EVENT_COMPOSING;elseif(chatstate==2)jep_state=ROSTER_EVENT_PAUSED;elsejep_state=ROSTER_EVENT_ACTIVE;jb_send_chatstate(BUDDATA(current_buddy),jep_state);}if(!chatstate)chatstate_timestamp=0;}#endif}#if defined JEP0022 || defined JEP0085inlinelongintscr_GetChatStatesTimeout(time_tnow){// Check if we're currently composing...if(chatstate!=1||!chatstate_timestamp)return86400;// If the timeout is reached, let's change the state right now.if(now>=chatstate_timestamp+COMPOSING_TIMEOUT){chatstate_timestamp=now;set_chatstate(2);return86400;}returnchatstate_timestamp+COMPOSING_TIMEOUT-now;}#endif// Check if we should enter/leave automatic away statusvoidscr_CheckAutoAway(intactivity){enumimstatuscur_st;unsignedintautoaway_timeout=settings_opt_get_int("autoaway");if(Autoaway&&activity)set_autoaway(FALSE);if(!autoaway_timeout)return;if(!LastActivity||activity)time(&LastActivity);cur_st=jb_getstatus();// Auto-away is disabled for the following statesif((cur_st!=available)&&(cur_st!=freeforchat))return;if(!activity){time_tnow;time(&now);if(!Autoaway&&(now>LastActivity+(time_t)autoaway_timeout))set_autoaway(TRUE);}}// set_current_buddy(newbuddy)// Set the current_buddy to newbuddy (if not NULL)// Lock the newbuddy, and unlock the previous current_buddystaticvoidset_current_buddy(GList*newbuddy){enumimstatusprev_st=imstatus_size;/* prev_st initialized to imstatus_size, which is used as "undef" value. * We are sure prev_st will get a different status value after the * buddy_getstatus() call. */if(!current_buddy||!newbuddy)return;if(newbuddy==current_buddy)return;// We're moving to another buddy. We're thus inactive wrt current_buddy.set_chatstate(0);// We don't want the chatstate to be changed again right now.lock_chatstate=true;prev_st=buddy_getstatus(BUDDATA(current_buddy),NULL);buddy_setflags(BUDDATA(current_buddy),ROSTER_FLAG_LOCK,FALSE);if(chatmode)alternate_buddy=current_buddy;current_buddy=newbuddy;// Lock the buddy in the buddylist if we're in chat modeif(chatmode)buddy_setflags(BUDDATA(current_buddy),ROSTER_FLAG_LOCK,TRUE);// We should rebuild the buddylist but not everytime// Here we check if we were locking a buddy who is actually offline,// and hide_offline_buddies is TRUE. In which case we need to rebuild.if(!(buddylist_get_filter()&1<<prev_st))buddylist_build();update_roster=TRUE;}// scr_RosterTop()// Go to the first buddy in the buddylistvoidscr_RosterTop(void){set_current_buddy(buddylist);if(chatmode)scr_ShowBuddyWindow();}// scr_RosterBottom()// Go to the last buddy in the buddylistvoidscr_RosterBottom(void){set_current_buddy(g_list_last(buddylist));if(chatmode)scr_ShowBuddyWindow();}// scr_RosterUp()// Go to the previous buddy in the buddylistvoidscr_RosterUp(void){set_current_buddy(g_list_previous(current_buddy));if(chatmode)scr_ShowBuddyWindow();}// scr_RosterDown()// Go to the next buddy in the buddylistvoidscr_RosterDown(void){set_current_buddy(g_list_next(current_buddy));if(chatmode)scr_ShowBuddyWindow();}// scr_RosterPrevGroup()// Go to the previous group in the buddylistvoidscr_RosterPrevGroup(void){GList*bud;for(bud=current_buddy;bud;){bud=g_list_previous(bud);if(!bud)break;if(buddy_gettype(BUDDATA(bud))&ROSTER_TYPE_GROUP){set_current_buddy(bud);if(chatmode)scr_ShowBuddyWindow();break;}}}// scr_RosterNextGroup()// Go to the next group in the buddylistvoidscr_RosterNextGroup(void){GList*bud;for(bud=current_buddy;bud;){bud=g_list_next(bud);if(!bud)break;if(buddy_gettype(BUDDATA(bud))&ROSTER_TYPE_GROUP){set_current_buddy(bud);if(chatmode)scr_ShowBuddyWindow();break;}}}// scr_RosterSearch(str)// Look forward for a buddy with jid/name containing str.voidscr_RosterSearch(char*str){set_current_buddy(buddy_search(str));if(chatmode)scr_ShowBuddyWindow();}// scr_RosterJumpJid(bjid)// Jump to buddy bjid.// NOTE: With this function, the buddy is added to the roster if doesn't exist.voidscr_RosterJumpJid(char*barejid){GSList*roster_elt;// Look for an existing buddyroster_elt=roster_find(barejid,jidsearch,ROSTER_TYPE_USER|ROSTER_TYPE_AGENT|ROSTER_TYPE_ROOM);// Create it if necessaryif(!roster_elt)roster_elt=roster_add_user(barejid,NULL,NULL,ROSTER_TYPE_USER,sub_none,-1);// Set a lock to see it in the buddylistbuddy_setflags(BUDDATA(roster_elt),ROSTER_FLAG_LOCK,TRUE);buddylist_build();// Jump to the buddyset_current_buddy(buddy_search_jid(barejid));if(chatmode)scr_ShowBuddyWindow();}// scr_RosterUnreadMessage(next)// Go to a new message. If next is not null, try to go to the next new// message. If it is not possible or if next is NULL, go to the first new// message from unread_list.voidscr_RosterUnreadMessage(intnext){gpointerunread_ptr;gpointerrefbuddata;GList*nbuddy;if(!current_buddy)return;if(next)refbuddata=BUDDATA(current_buddy);elserefbuddata=NULL;unread_ptr=unread_msg(refbuddata);if(!unread_ptr)return;if(!(buddy_gettype(unread_ptr)&ROSTER_TYPE_SPECIAL)){gpointerngroup;// If buddy is in a folded group, we need to expand itngroup=buddy_getgroup(unread_ptr);if(buddy_getflags(ngroup)&ROSTER_FLAG_HIDE){buddy_setflags(ngroup,ROSTER_FLAG_HIDE,FALSE);buddylist_build();}}nbuddy=g_list_find(buddylist,unread_ptr);if(nbuddy){set_current_buddy(nbuddy);if(chatmode)scr_ShowBuddyWindow();}elsescr_LogPrint(LPRINT_LOGNORM,"Error: nbuddy == NULL");// should not happen}// scr_RosterJumpAlternate()// Try to jump to alternate (== previous) buddyvoidscr_RosterJumpAlternate(void){if(!alternate_buddy||g_list_position(buddylist,alternate_buddy)==-1)return;set_current_buddy(alternate_buddy);if(chatmode)scr_ShowBuddyWindow();}// scr_RosterDisplay(filter)// Set the roster filter mask. If filter is null/empty, the current// mask is displayed.voidscr_RosterDisplay(constchar*filter){gucharstatus;enumimstatusbudstate;charstrfilter[imstatus_size+1];char*psfilter;if(filter&&*filter){intshow_all=(*filter=='*');status=0;for(budstate=0;budstate<imstatus_size-1;budstate++)if(strchr(filter,imstatus2char[budstate])||show_all)status|=1<<budstate;buddylist_set_filter(status);buddylist_build();update_roster=TRUE;return;}// Display current filterpsfilter=strfilter;status=buddylist_get_filter();for(budstate=0;budstate<imstatus_size-1;budstate++)if(status&1<<budstate)*psfilter++=imstatus2char[budstate];*psfilter='\0';scr_LogPrint(LPRINT_NORMAL,"Roster status filter: %s",strfilter);}// scr_BufferScrollUpDown()// Scroll up/down the current buddy window,// - half a screen if nblines is 0,// - up if updown == -1, down if updown == 1voidscr_BufferScrollUpDown(intupdown,unsignedintnblines){winbuf*win_entry;intn,nbl;GList*hbuf_top;guintisspe;// Get win_entryif(!current_buddy)return;isspe=buddy_gettype(BUDDATA(current_buddy))&ROSTER_TYPE_SPECIAL;win_entry=scr_SearchWindow(CURRENT_JID,isspe);if(!win_entry)return;if(!nblines){// Scroll half a screen (or less)nbl=CHAT_WIN_HEIGHT/2;}else{nbl=nblines;}hbuf_top=win_entry->bd->top;if(updown==-1){// UPif(!hbuf_top){hbuf_top=g_list_last(win_entry->bd->hbuf);if(!win_entry->bd->cleared){if(!nblines)nbl=nbl*3-1;elsenbl+=CHAT_WIN_HEIGHT-1;}else{win_entry->bd->cleared=FALSE;}}for(n=0;hbuf_top&&n<nbl&&g_list_previous(hbuf_top);n++)hbuf_top=g_list_previous(hbuf_top);win_entry->bd->top=hbuf_top;}else{// DOWNfor(n=0;hbuf_top&&n<nbl;n++)hbuf_top=g_list_next(hbuf_top);win_entry->bd->top=hbuf_top;// Check if we are at the bottomfor(n=0;hbuf_top&&n<CHAT_WIN_HEIGHT-1;n++)hbuf_top=g_list_next(hbuf_top);if(!hbuf_top)win_entry->bd->top=NULL;// End reached}// Refresh the windowscr_UpdateWindow(win_entry);// Finished :)update_panels();}// scr_BufferClear()// Clear the current buddy window (used for the /clear command)voidscr_BufferClear(void){winbuf*win_entry;guintisspe;// Get win_entryif(!current_buddy)return;isspe=buddy_gettype(BUDDATA(current_buddy))&ROSTER_TYPE_SPECIAL;win_entry=scr_SearchWindow(CURRENT_JID,isspe);if(!win_entry)return;win_entry->bd->cleared=TRUE;win_entry->bd->top=NULL;// Refresh the windowscr_UpdateWindow(win_entry);// Finished :)update_panels();}// buffer_purge()// key: winId/jid// value: winbuf structure// data: int, set to 1 if the buffer should be closed.// NOTE: does not work for special buffers.staticvoidbuffer_purge(gpointerkey,gpointervalue,gpointerdata){int*p_closebuf=data;winbuf*win_entry=value;// Delete the current hbufhbuf_free(&win_entry->bd->hbuf);if(*p_closebuf){g_hash_table_remove(winbufhash,key);}else{win_entry->bd->cleared=FALSE;win_entry->bd->top=NULL;}}// scr_BufferPurge(closebuf, jid)// Purge/Drop the current buddy buffer or jid's buffer if jid != NULL.// If closebuf is 1, close the buffer.voidscr_BufferPurge(intclosebuf,constchar*jid){winbuf*win_entry;guintisspe;guint*p_closebuf;constchar*cjid;guinthold_chatmode=FALSE;if(jid){cjid=jid;isspe=FALSE;// If closebuf is TRUE, it's probably better not to leave chat mode// if the change isn't related to the current buffer.if(closebuf&&current_buddy){if(buddy_gettype(BUDDATA(current_buddy))&ROSTER_TYPE_SPECIAL||strcasecmp(jid,CURRENT_JID))hold_chatmode=TRUE;}}else{// Get win_entryif(!current_buddy)return;cjid=CURRENT_JID;isspe=buddy_gettype(BUDDATA(current_buddy))&ROSTER_TYPE_SPECIAL;}win_entry=scr_SearchWindow(cjid,isspe);if(!win_entry)return;if(!isspe){p_closebuf=g_new(guint,1);*p_closebuf=closebuf;buffer_purge((gpointer)cjid,win_entry,p_closebuf);g_free(p_closebuf);if(closebuf&&!hold_chatmode){scr_set_chatmode(FALSE);currentWindow=NULL;}}else{// (Special buffer)// Reset the current hbufhbuf_free(&win_entry->bd->hbuf);// Currently it can only be the status bufferstatushbuf=NULL;win_entry->bd->cleared=FALSE;win_entry->bd->top=NULL;}// Refresh the windowscr_UpdateBuddyWindow();// Finished :)update_panels();}voidscr_BufferPurgeAll(intclosebuf){guint*p_closebuf;p_closebuf=g_new(guint,1);*p_closebuf=closebuf;g_hash_table_foreach(winbufhash,buffer_purge,p_closebuf);g_free(p_closebuf);if(closebuf){scr_set_chatmode(FALSE);currentWindow=NULL;}// Refresh the windowscr_UpdateBuddyWindow();// Finished :)update_panels();}// scr_BufferScrollLock(lock)// Lock/unlock the current buddy buffer// lock = 1 : lock// lock = 0 : unlock// lock = -1: toggle lock statusvoidscr_BufferScrollLock(intlock){winbuf*win_entry;guintisspe;// Get win_entryif(!current_buddy)return;isspe=buddy_gettype(BUDDATA(current_buddy))&ROSTER_TYPE_SPECIAL;win_entry=scr_SearchWindow(CURRENT_JID,isspe);if(!win_entry)return;if(lock==-1)lock=!win_entry->bd->lock;if(lock){win_entry->bd->lock=TRUE;}else{win_entry->bd->lock=FALSE;//win_entry->bd->cleared = FALSE;if(isspe||(buddy_getflags(BUDDATA(current_buddy))&ROSTER_FLAG_MSG))win_entry->bd->top=NULL;}// If chatmode is disabled and we're at the bottom of the buffer,// we need to set the "top" line, so we need to call scr_ShowBuddyWindow()// at least once. (Maybe it will cause a double refresh...)if(!chatmode&&!win_entry->bd->top){chatmode=TRUE;scr_ShowBuddyWindow();chatmode=FALSE;}// Refresh the windowscr_UpdateBuddyWindow();// Finished :)update_panels();}// scr_BufferTopBottom()// Jump to the head/tail of the current buddy window// (top if topbottom == -1, bottom topbottom == 1)voidscr_BufferTopBottom(inttopbottom){winbuf*win_entry;guintisspe;// Get win_entryif(!current_buddy)return;isspe=buddy_gettype(BUDDATA(current_buddy))&ROSTER_TYPE_SPECIAL;win_entry=scr_SearchWindow(CURRENT_JID,isspe);if(!win_entry)return;win_entry->bd->cleared=FALSE;if(topbottom==1)win_entry->bd->top=NULL;elsewin_entry->bd->top=g_list_first(win_entry->bd->hbuf);// Refresh the windowscr_UpdateWindow(win_entry);// Finished :)update_panels();}// scr_BufferSearch(direction, text)// Jump to the next line containing text// (backward search if direction == -1, forward if topbottom == 1)voidscr_BufferSearch(intdirection,constchar*text){winbuf*win_entry;GList*current_line,*search_res;guintisspe;// Get win_entryif(!current_buddy)return;isspe=buddy_gettype(BUDDATA(current_buddy))&ROSTER_TYPE_SPECIAL;win_entry=scr_SearchWindow(CURRENT_JID,isspe);if(!win_entry)return;if(win_entry->bd->top)current_line=win_entry->bd->top;elsecurrent_line=g_list_last(win_entry->bd->hbuf);search_res=hbuf_search(current_line,direction,text);if(search_res){win_entry->bd->cleared=FALSE;win_entry->bd->top=search_res;// Refresh the windowscr_UpdateWindow(win_entry);// Finished :)update_panels();}elsescr_LogPrint(LPRINT_NORMAL,"Search string not found");}// scr_BufferPercent(n)// Jump to the specified position in the buffer, in %voidscr_BufferPercent(intpc){winbuf*win_entry;GList*search_res;guintisspe;// Get win_entryif(!current_buddy)return;isspe=buddy_gettype(BUDDATA(current_buddy))&ROSTER_TYPE_SPECIAL;win_entry=scr_SearchWindow(CURRENT_JID,isspe);if(!win_entry)return;if(pc<0||pc>100){scr_LogPrint(LPRINT_NORMAL,"Bad % value");return;}search_res=hbuf_jump_percent(win_entry->bd->hbuf,pc);win_entry->bd->cleared=FALSE;win_entry->bd->top=search_res;// Refresh the windowscr_UpdateWindow(win_entry);// Finished :)update_panels();}// scr_BufferDate(t)// Jump to the first line after date t in the buffer// t is a date in seconds since `00:00:00 1970-01-01 UTC'voidscr_BufferDate(time_tt){winbuf*win_entry;GList*search_res;guintisspe;// Get win_entryif(!current_buddy)return;isspe=buddy_gettype(BUDDATA(current_buddy))&ROSTER_TYPE_SPECIAL;win_entry=scr_SearchWindow(CURRENT_JID,isspe);if(!win_entry)return;search_res=hbuf_jump_date(win_entry->bd->hbuf,t);win_entry->bd->cleared=FALSE;win_entry->bd->top=search_res;// Refresh the windowscr_UpdateWindow(win_entry);// Finished :)update_panels();}#ifdef DEBUG_ENABLE// buffer_list()// key: winId/jid// value: winbuf structure// data: none.staticvoidbuffer_list(gpointerkey,gpointervalue,gpointerdata){GList*head;winbuf*win_entry=value;head=g_list_first(win_entry->bd->hbuf);scr_LogPrint(LPRINT_NORMAL," %s (%u/%u)",key,g_list_length(head),hbuf_get_blocks_number(head));}voidscr_BufferList(void){scr_LogPrint(LPRINT_NORMAL,"Buffer list:");buffer_list("[status]",statusWindow,NULL);g_hash_table_foreach(winbufhash,buffer_list,NULL);scr_LogPrint(LPRINT_NORMAL,"End of buffer list.");scr_setmsgflag_if_needed(SPECIAL_BUFFER_STATUS_ID,TRUE);update_roster=TRUE;}#endif// scr_set_chatmode()// Public function to (un)set chatmode...inlinevoidscr_set_chatmode(intenable){chatmode=enable;scr_UpdateChatStatus(TRUE);}// scr_get_chatmode()// Public function to get chatmode state.inlineintscr_get_chatmode(void){returnchatmode;}// scr_get_multimode()// Public function to get multimode status...inlineintscr_get_multimode(void){returnmultimode;}// scr_setmsgflag_if_needed(jid)// Set the message flag unless we're already in the jid buffer windowvoidscr_setmsgflag_if_needed(constchar*bjid,intspecial){constchar*current_id;booliscurrentlocked=FALSE;if(!bjid)return;if(current_buddy){if(special)current_id=buddy_getname(BUDDATA(current_buddy));elsecurrent_id=buddy_getjid(BUDDATA(current_buddy));if(current_id){winbuf*win_entry=scr_SearchWindow(current_id,special);if(!win_entry)return;iscurrentlocked=win_entry->bd->lock;}}else{current_id=NULL;}if(!chatmode||!current_id||strcmp(bjid,current_id)||iscurrentlocked)roster_msg_setflag(bjid,special,TRUE);}// scr_set_multimode()// Public function to (un)set multimode...// Convention:// 0 = disabled / 1 = multimode / 2 = multimode verbatim (commands disabled)inlinevoidscr_set_multimode(intenable,char*subject){g_free(multiline);multiline=NULL;g_free(multimode_subj);if(enable&&subject)multimode_subj=g_strdup(subject);elsemultimode_subj=NULL;multimode=enable;}// scr_get_multiline()// Public function to get the current multi-line.inlineconstchar*scr_get_multiline(void){if(multimode&&multiline)returnmultiline;returnNULL;}// scr_get_multimode_subj()// Public function to get the multi-line subject, if any.inlineconstchar*scr_get_multimode_subj(void){if(multimode)returnmultimode_subj;returnNULL;}// scr_append_multiline(line)// Public function to append a line to the current multi-line message.// Skip empty leading lines.voidscr_append_multiline(constchar*line){staticintnum;if(!multimode){scr_LogPrint(LPRINT_NORMAL,"Error: Not in multi-line message mode!");return;}if(multiline){intlen=strlen(multiline)+strlen(line)+2;if(len>=HBB_BLOCKSIZE-1){// We don't handle single messages with size > HBB_BLOCKSIZE// (see hbuf)scr_LogPrint(LPRINT_NORMAL,"Your multi-line message is too big, ""this line has not been added.");scr_LogPrint(LPRINT_NORMAL,"Please send this part now...");return;}if(num>=MULTILINE_MAX_LINE_NUMBER){// We don't allow too many lines; however the maximum is arbitrary// (It should be < 1000 yet)scr_LogPrint(LPRINT_NORMAL,"Your message has too many lines, ""this one has not been added.");scr_LogPrint(LPRINT_NORMAL,"Please send this part now...");return;}multiline=g_renew(char,multiline,len);strcat(multiline,"\n");strcat(multiline,line);num++;}else{// First message line (we skip leading empty lines)num=0;if(line[0]){multiline=g_strdup(line);num++;}elsereturn;}scr_LogPrint(LPRINT_NORMAL|LPRINT_NOTUTF8,"Multi-line mode: line #%d added [%.25s...",num,line);}// scr_cmdhisto_addline()// Add a line to the inputLine historyinlinevoidscr_cmdhisto_addline(char*line){intmax_histo_lines;if(!line||!*line)return;max_histo_lines=settings_opt_get_int("cmdhistory_lines");if(max_histo_lines<0)max_histo_lines=1;if(max_histo_lines)while(cmdhisto_nblines>=(guint)max_histo_lines){if(cmdhisto_cur&&cmdhisto_cur==cmdhisto)break;g_free(cmdhisto->data);cmdhisto=g_list_delete_link(cmdhisto,cmdhisto);cmdhisto_nblines--;}cmdhisto=g_list_append(cmdhisto,g_strdup(line));cmdhisto_nblines++;}// scr_cmdhisto_prev()// Look for previous line beginning w/ the given mask in the inputLine history// Returns NULL if none foundstaticconstchar*scr_cmdhisto_prev(char*mask,guintlen){GList*hl;if(!cmdhisto_cur){hl=g_list_last(cmdhisto);if(hl){// backup current linestrncpy(cmdhisto_backup,mask,INPUTLINE_LENGTH);}}else{hl=g_list_previous(cmdhisto_cur);}while(hl){if(!strncmp((char*)hl->data,mask,len)){// Found a matchcmdhisto_cur=hl;return(constchar*)hl->data;}hl=g_list_previous(hl);}returnNULL;}// scr_cmdhisto_next()// Look for next line beginning w/ the given mask in the inputLine history// Returns NULL if none foundstaticconstchar*scr_cmdhisto_next(char*mask,guintlen){GList*hl;if(!cmdhisto_cur)returnNULL;hl=cmdhisto_cur;while((hl=g_list_next(hl))!=NULL)if(!strncmp((char*)hl->data,mask,len)){// Found a matchcmdhisto_cur=hl;return(constchar*)hl->data;}// If the "backuped" line matches, we'll use itif(strncmp(cmdhisto_backup,mask,len))returnNULL;// No matchcmdhisto_cur=NULL;returncmdhisto_backup;}// readline_transpose_chars()// Drag the character before point forward over the character at// point, moving point forward as well. If point is at the end of// the line, then this transposes the two characters before point.voidreadline_transpose_chars(void){char*c1,*c2;unsigneda,b;if(ptr_inputline==inputLine)return;if(!*ptr_inputline){// We're at EOL// If line is only 1 char long, nothing to do...if(ptr_inputline==prev_char(ptr_inputline,inputLine))return;// Transpose the two previous charactersc2=prev_char(ptr_inputline,inputLine);c1=prev_char(c2,inputLine);a=get_char(c1);b=get_char(c2);put_char(put_char(c1,b),a);}else{// Swap the two characters before the cursor and move right.c2=ptr_inputline;c1=prev_char(c2,inputLine);a=get_char(c1);b=get_char(c2);put_char(put_char(c1,b),a);check_offset(1);}}voidreadline_forward_kill_word(void){char*c,*old=ptr_inputline;intspaceallowed=1;if(!*ptr_inputline)return;for(c=ptr_inputline;*c;c=next_char(c)){if(!iswalnum(get_char(c))){if(iswblank(get_char(c))){if(!spaceallowed)break;}elsespaceallowed=0;}elsespaceallowed=0;}// Modify the linefor(;;){*old=*c++;if(!*old++)break;}}// readline_backward_kill_word()// Kill the word before the cursor, in input linevoidreadline_backward_kill_word(void){char*c,*old=ptr_inputline;intspaceallowed=1;if(ptr_inputline==inputLine)return;c=prev_char(ptr_inputline,inputLine);for(;c>inputLine;c=prev_char(c,inputLine)){if(!iswalnum(get_char(c))){if(iswblank(get_char(c))){if(!spaceallowed)break;}elsespaceallowed=0;}elsespaceallowed=0;}if(c==inputLine&&*c==COMMAND_CHAR&&old!=c+1){c=next_char(c);}elseif(c!=inputLine||iswblank(get_char(c))){if((c<prev_char(ptr_inputline,inputLine))&&(!iswalnum(get_char(c))))c=next_char(c);}// Modify the lineptr_inputline=c;for(;;){*c=*old++;if(!*c++)break;}check_offset(-1);}// readline_backward_word()// Move back to the start of the current or previous wordvoidreadline_backward_word(void){inti=0;if(ptr_inputline==inputLine)return;if(iswalnum(get_char(ptr_inputline))&&!iswalnum(get_char(prev_char(ptr_inputline,inputLine))))i--;for(;ptr_inputline>inputLine;ptr_inputline=prev_char(ptr_inputline,inputLine)){if(!iswalnum(get_char(ptr_inputline))){if(i){ptr_inputline=next_char(ptr_inputline);break;}}elsei++;}check_offset(-1);}// readline_forward_word()// Move forward to the end of the next wordvoidreadline_forward_word(void){intstopsymbol_allowed=1;while(*ptr_inputline){if(!iswalnum(get_char(ptr_inputline))){if(!stopsymbol_allowed)break;}elsestopsymbol_allowed=0;ptr_inputline=next_char(ptr_inputline);}check_offset(1);}voidreadline_updowncase_word(intupcase){intstopsymbol_allowed=1;while(*ptr_inputline){if(!iswalnum(get_char(ptr_inputline))){if(!stopsymbol_allowed)break;}else{stopsymbol_allowed=0;if(upcase)*ptr_inputline=towupper(get_char(ptr_inputline));else*ptr_inputline=towlower(get_char(ptr_inputline));}ptr_inputline=next_char(ptr_inputline);}check_offset(1);}voidreadline_capitalize_word(void){intstopsymbol_allowed=1;intupcased=0;while(*ptr_inputline){if(!iswalnum(get_char(ptr_inputline))){if(!stopsymbol_allowed)break;}else{stopsymbol_allowed=0;if(!upcased){*ptr_inputline=towupper(get_char(ptr_inputline));upcased=1;}else*ptr_inputline=towlower(get_char(ptr_inputline));}ptr_inputline=next_char(ptr_inputline);}check_offset(1);}voidreadline_backward_char(void){if(ptr_inputline==(char*)&inputLine)return;ptr_inputline=prev_char(ptr_inputline,inputLine);check_offset(-1);}voidreadline_forward_char(void){if(!*ptr_inputline)return;ptr_inputline=next_char(ptr_inputline);check_offset(1);}// readline_accept_line(down_history)// Validate current command line.// If down_history is true, load the next history line.intreadline_accept_line(intdown_history){scr_CheckAutoAway(TRUE);if(process_line(inputLine))return255;// Add line to historyscr_cmdhisto_addline(inputLine);// Reset the lineptr_inputline=inputLine;*ptr_inputline=0;inputline_offset=0;if(down_history){// Use next history line instead of a blank lineconstchar*l=scr_cmdhisto_next("",0);if(l)strcpy(inputLine,l);// Reset backup history linecmdhisto_backup[0]=0;}else{// Reset history line pointercmdhisto_cur=NULL;}return0;}voidreadline_cancel_completion(void){scr_cancel_current_completion();scr_end_current_completion();check_offset(-1);}voidreadline_do_completion(void){inti,n;if(scr_get_multimode()!=2){// Not in verbatim multi-line modescr_handle_tab();}else{// Verbatim multi-line mode: expand tabchartabstr[9];n=8-(ptr_inputline-inputLine)%8;for(i=0;i<n;i++)tabstr[i]=' ';tabstr[i]='\0';scr_insert_text(tabstr);}check_offset(0);}voidreadline_refresh_screen(void){scr_CheckAutoAway(TRUE);ParseColors();scr_Resize();redrawwin(stdscr);}voidreadline_disable_chat_mode(void){scr_CheckAutoAway(TRUE);currentWindow=NULL;chatmode=FALSE;if(current_buddy)buddy_setflags(BUDDATA(current_buddy),ROSTER_FLAG_LOCK,FALSE);scr_RosterVisibility(1);scr_UpdateChatStatus(FALSE);top_panel(chatPanel);top_panel(inputPanel);update_panels();}voidreadline_hist_beginning_search_bwd(void){constchar*l=scr_cmdhisto_prev(inputLine,ptr_inputline-inputLine);if(l)strcpy(inputLine,l);}voidreadline_hist_beginning_search_fwd(void){constchar*l=scr_cmdhisto_next(inputLine,ptr_inputline-inputLine);if(l)strcpy(inputLine,l);}voidreadline_hist_prev(void){constchar*l=scr_cmdhisto_prev(inputLine,0);if(l){strcpy(inputLine,l);// Set the pointer at the EOL.// We have to move it to BOL first, because we could be too far already.readline_iline_start();readline_iline_end();}}voidreadline_hist_next(void){constchar*l=scr_cmdhisto_next(inputLine,0);if(l){strcpy(inputLine,l);// Set the pointer at the EOL.// We have to move it to BOL first, because we could be too far already.readline_iline_start();readline_iline_end();}}voidreadline_backward_kill_char(void){char*src,*c;if(ptr_inputline==(char*)&inputLine)return;src=ptr_inputline;c=prev_char(ptr_inputline,inputLine);ptr_inputline=c;for(;*src;)*c++=*src++;*c=0;check_offset(-1);}voidreadline_forward_kill_char(void){if(!*ptr_inputline)return;strcpy(ptr_inputline,next_char(ptr_inputline));}voidreadline_iline_start(void){ptr_inputline=inputLine;inputline_offset=0;}voidreadline_iline_end(void){for(;*ptr_inputline;ptr_inputline++);check_offset(1);}voidreadline_backward_kill_iline(void){strcpy(inputLine,ptr_inputline);ptr_inputline=inputLine;inputline_offset=0;}voidreadline_forward_kill_iline(void){*ptr_inputline=0;}voidreadline_send_multiline(void){// Validate current multi-lineif(scr_get_multimode())process_command(mkcmdstr("msay send"),TRUE);}// which_row()// Tells which row our cursor is in, in the command line.// -2 -> normal text// -1 -> room: nickname completion// 0 -> command// 1 -> parameter 1 (etc.)// If > 0, then *p_row is set to the beginning of the rowstaticintwhich_row(constchar**p_row){introw=-1;char*p;intquote=FALSE;// Not a command?if((ptr_inputline==inputLine)||(inputLine[0]!=COMMAND_CHAR)){if(!current_buddy)return-2;if(buddy_gettype(BUDDATA(current_buddy))==ROSTER_TYPE_ROOM){*p_row=inputLine;return-1;}return-2;}// This is a commandrow=0;for(p=inputLine;p<ptr_inputline;p=next_char(p)){if(quote){if(*p=='"'&&*(p-1)!='\\')quote=FALSE;continue;}if(*p=='"'&&*(p-1)!='\\'){quote=TRUE;}elseif(*p==' '){if(*(p-1)!=' ')row++;*p_row=p+1;}}returnrow;}// scr_insert_text()// Insert the given text at the current cursor position.// The cursor is moved. We don't check if the cursor still is in the screen// after, the caller should do that.staticvoidscr_insert_text(constchar*text){chartmpLine[INPUTLINE_LENGTH+1];intlen=strlen(text);// Check the line isn't too longif(strlen(inputLine)+len>=INPUTLINE_LENGTH){scr_LogPrint(LPRINT_LOGNORM,"Cannot insert text, line too long.");return;}strcpy(tmpLine,ptr_inputline);strcpy(ptr_inputline,text);ptr_inputline+=len;strcpy(ptr_inputline,tmpLine);}staticvoidscr_cancel_current_completion(void);// scr_handle_tab()// Function called when tab is pressed.// Initiate or continue a completion...staticvoidscr_handle_tab(void){intnrow;constchar*row;constchar*cchar;guintcompl_categ;row=inputLine;// (Kills a GCC warning)nrow=which_row(&row);// a) No completion if no leading slash ('cause not a command),// unless this is a room (then, it is a nickname completion)// b) We can't have more than 2 parameters (we use 2 flags)if((nrow==-2)||(nrow==3&&!completion_started)||nrow>3)return;if(nrow==0){// Command completionrow=next_char(inputLine);compl_categ=COMPL_CMD;}elseif(nrow==-1){// Nickname completioncompl_categ=COMPL_RESOURCE;}else{// Other completion, depending on the commandintalias=FALSE;cmd*com;char*xpline=expandalias(inputLine);com=cmd_get(xpline);if(xpline!=inputLine){// This is an alias, so we can't complete rows > 0alias=TRUE;g_free(xpline);}if((!com&&(!alias||!completion_started))||!row){scr_LogPrint(LPRINT_NORMAL,"I cannot complete that...");return;}if(!alias)compl_categ=com->completion_flags[nrow-1];elsecompl_categ=0;}if(!completion_started){guintdynlist;GSList*list=compl_get_category_list(compl_categ,&dynlist);if(list){guintn;char*prefix=g_strndup(row,ptr_inputline-row);// Init completionn=new_completion(prefix,list);g_free(prefix);if(n==0&&nrow==-1){// This is a MUC room and we can't complete from the beginning of the// line. Let's try a bit harder and complete the current word.row=prev_char(ptr_inputline,inputLine);while(row>=inputLine){if(iswspace(get_char(row))||get_char(row)=='('){row=next_char((char*)row);break;}if(row==inputLine)break;row=prev_char((char*)row,inputLine);}// There's no need to try again if row == inputLineif(row>inputLine){prefix=g_strndup(row,ptr_inputline-row);new_completion(prefix,list);g_free(prefix);}}// Free the list if it's a dynamic oneif(dynlist){GSList*slp;for(slp=list;slp;slp=g_slist_next(slp))g_free(slp->data);g_slist_free(list);}// Now completecchar=complete();if(cchar)scr_insert_text(cchar);completion_started=TRUE;}}else{// Completion already initializedscr_cancel_current_completion();// Now complete againcchar=complete();if(cchar)scr_insert_text(cchar);}}staticvoidscr_cancel_current_completion(void){char*c;char*src=ptr_inputline;guintback=cancel_completion();guinti;// Remove $back charsfor(i=0;i<back;i++)ptr_inputline=prev_char(ptr_inputline,inputLine);c=ptr_inputline;for(;*src;)*c++=*src++;*c=0;}staticvoidscr_end_current_completion(void){done_completion();completion_started=FALSE;}// check_offset(int direction)// Check inputline_offset value, and make sure the cursor is inside the// screen.staticinlinevoidcheck_offset(intdirection){inti;char*c=&inputLine[inputline_offset];// Left sideif(inputline_offset&&direction<=0){while(ptr_inputline<=c){for(i=0;i<5;i++)c=prev_char(c,inputLine);if(c==inputLine)break;}}// Right sideif(direction>=0){intdelta=get_char_width(c);while(ptr_inputline>c){c=next_char(c);delta+=get_char_width(c);}c=&inputLine[inputline_offset];while(delta>=maxX){for(i=0;i<5;i++){delta-=get_char_width(c);c=next_char(c);}}}inputline_offset=c-inputLine;}#ifdef HAVE_ASPELL_H// prints inputLine with underlined words when misspelledstaticinlinevoidprint_checked_line(void){char*wprint_char_fmt="%c";intpoint;char*ptrCur=inputLine+inputline_offset;#ifdef UNICODE// We need this to display a single UTF-8 char... Any better solution?if(utf8_mode)wprint_char_fmt="%lc";#endifwmove(inputWnd,0,0);// problem with backspacewhile(*ptrCur){point=ptrCur-inputLine;if(maskLine[point])wattrset(inputWnd,A_UNDERLINE);wprintw(inputWnd,wprint_char_fmt,get_char(ptrCur));wattrset(inputWnd,A_NORMAL);ptrCur=next_char(ptrCur);}}#endifstaticinlinevoidrefresh_inputline(void){#ifdef HAVE_ASPELL_Hif(settings_opt_get_int("aspell_enable")){memset(maskLine,0,INPUTLINE_LENGTH+1);spellcheck(inputLine,maskLine);}print_checked_line();wclrtoeol(inputWnd);if(*ptr_inputline){// hack to set cursor pos. Characters can have different width,// so I know of no better way.charc=*ptr_inputline;*ptr_inputline=0;print_checked_line();*ptr_inputline=c;}#elsemvwprintw(inputWnd,0,0,"%s",inputLine+inputline_offset);wclrtoeol(inputWnd);if(*ptr_inputline){// hack to set cursor pos. Characters can have different width,// so I know of no better way.charc=*ptr_inputline;*ptr_inputline=0;mvwprintw(inputWnd,0,0,"%s",inputLine+inputline_offset);*ptr_inputline=c;}#endif}voidscr_handle_CtrlC(void){if(!Curses)return;// Leave multi-line modeprocess_command(mkcmdstr("msay abort"),TRUE);// Same as Ctrl-g, nowscr_cancel_current_completion();scr_end_current_completion();check_offset(-1);refresh_inputline();}staticvoidadd_keyseq(char*seqstr,guintmkeycode,gintvalue){keyseq*ks;// Let's make sure the length is correctif(strlen(seqstr)>MAX_KEYSEQ_LENGTH){scr_LogPrint(LPRINT_LOGNORM,"add_keyseq(): key sequence is too long!");return;}ks=g_new0(keyseq,1);ks->seqstr=g_strdup(seqstr);ks->mkeycode=mkeycode;ks->value=value;keyseqlist=g_slist_append(keyseqlist,ks);}// match_keyseq(iseq, &ret)// Check if "iseq" is a known key escape sequence.// Return value:// -1 if "seq" matches no known sequence// 0 if "seq" could match 1 or more known sequences// >0 if "seq" matches a key sequence; the mkey code is returned// and *ret is set to the matching keyseq structure.staticinlinegintmatch_keyseq(int*iseq,keyseq**ret){GSList*ksl;keyseq*ksp;char*p,c;int*i;intneedmore=FALSE;for(ksl=keyseqlist;ksl;ksl=g_slist_next(ksl)){ksp=ksl->data;p=ksp->seqstr;i=iseq;while(1){c=(unsignedchar)*i;if(!*p&&!c){// Match(*ret)=ksp;returnksp->mkeycode;}if(!c){// iseq is too shortneedmore=TRUE;break;}elseif(!*p||c!=*p){// This isn't a matchbreak;}p++;i++;}}if(needmore)return0;return-1;}staticinlineintmatch_utf8_keyseq(int*iseq){int*strp=iseq;unsignedc=*strp++;unsignedmask=0x80;intlen=-1;while(c&mask){mask>>=1;len++;}if(len<=0||len>4)return-1;c&=mask-1;while((*strp&0xc0)==0x80){if(len--<=0)// can't happenreturn-1;c=(c<<6)|(*strp++&0x3f);}if(len)return0;returnc;}voidscr_Getch(keycode*kcode){keyseq*mks=NULL;intks[MAX_KEYSEQ_LENGTH+1];inti;memset(kcode,0,sizeof(keycode));memset(ks,0,sizeof(ks));kcode->value=wgetch(inputWnd);if(utf8_mode){boolismeta=(kcode->value==27);if(ismeta)ks[0]=wgetch(inputWnd);elseks[0]=kcode->value;for(i=0;i<MAX_KEYSEQ_LENGTH-1;i++){intmatch=match_utf8_keyseq(ks);if(match==-1)break;if(match>0){kcode->value=match;kcode->utf8=1;if(ismeta)kcode->mcode=MKEY_META;return;}ks[i+1]=wgetch(inputWnd);if(ks[i+1]==ERR)break;}while(i>0)ungetch(ks[i--]);if(ismeta)ungetch(ks[0]);memset(ks,0,sizeof(ks));}if(kcode->value!=27)return;// Check for escape key sequencefor(i=0;i<MAX_KEYSEQ_LENGTH;i++){intmatch;ks[i]=wgetch(inputWnd);if(ks[i]==ERR)break;match=match_keyseq(ks,&mks);if(match==-1){// No such key sequence. Let's increment i as it is a valid key.i++;break;}if(match>0){// We have a matching sequencekcode->mcode=mks->mkeycode;kcode->value=mks->value;return;}}// No match. Let's return a meta-key.if(i>0){kcode->mcode=MKEY_META;kcode->value=ks[0];}if(i>1){// We need to push some keys back to the keyboard bufferwhile(i-->1)ungetch(ks[i]);}return;}inlinevoidscr_DoUpdate(void){doupdate();}staticintbindcommand(keycodekcode){gcharasciikey[16],asciicode[16];constgchar*boundcmd;if(kcode.utf8)g_snprintf(asciicode,15,"U%d",kcode.value);elseg_snprintf(asciicode,15,"%d",kcode.value);if(!kcode.mcode||kcode.mcode==MKEY_EQUIV)g_snprintf(asciikey,15,"%s",asciicode);elseif(kcode.mcode==MKEY_META)g_snprintf(asciikey,15,"M%s",asciicode);elseg_snprintf(asciikey,15,"MK%d",kcode.mcode);boundcmd=settings_get(SETTINGS_TYPE_BINDING,asciikey);if(boundcmd){gchar*cmdline,*boundcmd_locale;boundcmd_locale=from_utf8(boundcmd);cmdline=g_strdup_printf(mkcmdstr("%s"),boundcmd_locale);scr_CheckAutoAway(TRUE);if(process_command(cmdline,TRUE))return255;// Quitg_free(boundcmd_locale);g_free(cmdline);return0;}scr_LogPrint(LPRINT_NORMAL,"Unknown key=%s",asciikey);#ifndef UNICODEif(utf8_mode)scr_LogPrint(LPRINT_NORMAL,"WARNING: Compiled without full UTF-8 support!");#endifreturn-1;}// process_key(key)// Handle the pressed key, in the command line (bottom).intprocess_key(keycodekcode){intkey=kcode.value;intdisplay_char=FALSE;lock_chatstate=false;switch(kcode.mcode){case0:break;caseMKEY_EQUIV:key=kcode.value;break;caseMKEY_META:default:if(bindcommand(kcode)==255)return255;key=ERR;// Do not process any further}if(kcode.utf8){if(key!=ERR&&!kcode.mcode)display_char=TRUE;gotodisplay;}switch(key){case0:caseERR:break;case9:// Tabreadline_do_completion();break;case13:// Enterif(readline_accept_line(FALSE)==255)return255;break;case3:// Ctrl-Cscr_handle_CtrlC();break;caseKEY_RESIZE:scr_Resize();break;default:display_char=TRUE;}// switchdisplay:if(display_char){if(kcode.utf8?iswprint(key):(isprint(key)&&!is_speckey(key))){chartmpLine[INPUTLINE_LENGTH+1];// Check the line isn't too longif(strlen(inputLine)+4>INPUTLINE_LENGTH)return0;// Insert charstrcpy(tmpLine,ptr_inputline);ptr_inputline=put_char(ptr_inputline,key);strcpy(ptr_inputline,tmpLine);check_offset(1);}else{// Look for a key binding.if(!kcode.utf8&&(bindcommand(kcode)==255))return255;}}if(completion_started&&key!=9&&key!=KEY_RESIZE)scr_end_current_completion();refresh_inputline();if(!lock_chatstate){// Set chat state to composing (1) if the user is currently composing,// i.e. not an empty line and not a command line.if(inputLine[0]==0||inputLine[0]==COMMAND_CHAR)set_chatstate(0);elseset_chatstate(1);if(chatstate)time(&chatstate_timestamp);}return0;}#ifdef HAVE_ASPELL_H// Aspell initializationvoidspellcheck_init(void){intaspell_enable=settings_opt_get_int("aspell_enable");constchar*aspell_lang=settings_opt_get("aspell_lang");constchar*aspell_encoding=settings_opt_get("aspell_encoding");AspellCanHaveError*possible_err;if(!aspell_enable)return;if(spell_checker){delete_aspell_speller(spell_checker);delete_aspell_config(spell_config);spell_checker=NULL;spell_config=NULL;}spell_config=new_aspell_config();aspell_config_replace(spell_config,"encoding",aspell_encoding);aspell_config_replace(spell_config,"lang",aspell_lang);possible_err=new_aspell_speller(spell_config);if(aspell_error_number(possible_err)!=0){spell_checker=NULL;delete_aspell_config(spell_config);spell_config=NULL;}else{spell_checker=to_aspell_speller(possible_err);}}// Deinitialization of Aspell spellcheckervoidspellcheck_deinit(void){if(spell_checker){delete_aspell_speller(spell_checker);spell_checker=NULL;}if(spell_config){delete_aspell_config(spell_config);spell_config=NULL;}}#define aspell_isalpha(c) (utf8_mode ? iswalpha(get_char(c)) : isalpha(*c))// Spell checking functionstaticvoidspellcheck(char*line,char*checked){constchar*start,*line_start;if(inputLine[0]==0||inputLine[0]==COMMAND_CHAR)return;line_start=line;while(*line){if(!aspell_isalpha(line)){line=next_char(line);continue;}if(!strncmp(line,"http://",7)){line+=7;// : and / characters are 1 byte long in utf8, right?while(!strchr(" \t\r\n",*line))line=next_char(line);// i think line++ would be fine here?continue;}if(!strncmp(line,"ftp://",6)){line+=6;while(!strchr(" \t\r\n",*line))line=next_char(line);continue;}start=line;while(aspell_isalpha(line))line=next_char(line);if(spell_checker&&aspell_speller_check(spell_checker,start,line-start)==0)memset(&checked[start-line_start],ASPELLBADCHAR,line-start);}}#endif/* vim: set expandtab cindent cinoptions=>2\:2(0: For Vim users... */