static char rcsid[] = "@(#)$Id: thread.c,v 1.10 2006/04/16 21:01:35 hurtta Exp $"; /****************************************************************************** * The Elm (ME+) Mail System - $Revision: 1.10 $ $State: Exp $ * * Author: Kari Hurtta * or Kari Hurtta *****************************************************************************/ #include "def_elm.h" #include "s_me.h" DEBUG_VAR(Debug,__FILE__,"mail"); static void first_item P_((void)); static void first_item() { lib_error(CATGETS(elm_msg_cat, MeSet, MeNoMoreThreadsAbove, "No more threads above.")); } static void last_item P_((void)); static void last_item() { lib_error(CATGETS(elm_msg_cat, MeSet, MeNoMoreThreadsBelow, "No more threads below.")); } static struct move_messages M = { first_item, last_item }; #if ANSI_C #define S_(x) static x; #else #define S_(x) #endif struct sort_list { char status_letter; int idx; }; struct menu_anon_param { struct MailboxView * mailbox; struct sort_list * sort_list; int sort_list_len; }; struct sort_data1 { struct MailboxView *mailbox; int index; }; static int compare_threads_1 P_((const struct thread_info *X1, const struct thread_info *X2)); static int compare_threads_1(X1,X2) CONST struct thread_info *X1; CONST struct thread_info *X2; { long diff = 0; int ret; switch (give_dt_sort_as_int(&thread_sortby)) { case SENT_DATE: /* as THREAD sorting */ diff = X1->time_sent_first - X2->time_sent_first; if (diff < 0) return -1; if (diff > 0) return 1; break; case REVERSE SENT_DATE: if (unstable_reverse_thread) { /* Move thread new location when new mail arrives, therfore use newest sent time */ diff = X1->time_sent_last - X2->time_sent_last; } else diff = X1->time_sent_first - X2->time_sent_first; if (diff < 0) return 1; if (diff > 0) return -1; break; case SUBJECT: if (! X1->thread_subject && ! X2->thread_subject) return 0; if (! X1->thread_subject) return -1; if (! X2->thread_subject) return 1; ret = string_cmp(X1->thread_subject,X2->thread_subject, 0 /* == Values not comparable */ ); return ret; case REVERSE SUBJECT: if (! X1->thread_subject && ! X2->thread_subject) return 0; if (! X1->thread_subject) return -1; if (! X2->thread_subject) return 1; ret = string_cmp(X1->thread_subject,X2->thread_subject, 0 /* == Values not comparable */ ); return ret; } return 0; } static int compare_threads P_((struct sort_data1 *p1, struct sort_data1 *p2)); static int compare_threads(p1,p2) struct sort_data1 *p1; struct sort_data1 *p2; { int ret; /* give_thread_info checks that index is correct range */ CONST struct thread_info *X1 = give_thread_info(p1->mailbox, p1->index); CONST struct thread_info *X2 = give_thread_info(p2->mailbox, p2->index); if (!X1 && !X2) { return p2->index - p1->index; } if (!X1) return -1; if (!X2) return 1; ret = compare_threads_1(X1,X2); if (ret != 0) return ret; return p2->index - p1->index; } static void sort_threads P_((struct MailboxView *mailbox, struct menu_anon_param *A, struct menu_context *page)); static void sort_threads(mailbox,A,page) struct MailboxView *mailbox; struct menu_anon_param *A; struct menu_context *page; { /* Be sure that we have threads available */ int num_threads = get_thread_count(mailbox,0); int old_len = A->sort_list_len; int cur = menu_header_get(page,header_current); int top = menu_header_get(page,header_top_line); int real_cur = 0; int i; struct sort_data1 * array; /* Little dirty ... */ typedef int (*compar) P_((const void *, const void *)); compar X = (compar) compare_threads; int li,co; menu_get_sizes(page, &li, &co); if (num_threads < 1) { A->sort_list_len = 0; menu_header_change(page, header_top_line,0); menu_header_change(page,header_current,0); return; } if (cur >= 0 && cur < A->sort_list_len) real_cur = A->sort_list[cur].idx; A->sort_list = safe_realloc(A->sort_list, num_threads * sizeof (A->sort_list[0])); for (i = old_len; i < num_threads; i++) { A->sort_list[i].idx = i; A->sort_list[i].status_letter = '\0'; } A->sort_list_len = num_threads; array = safe_malloc(num_threads * sizeof(array[0])); for (i = 0; i < num_threads; i++) { array[i].index = A->sort_list[i].idx; array[i].mailbox = mailbox; } qsort(array,num_threads,sizeof (array[0]), X); for (i = 0; i < num_threads; i++) { if (array[i].index != A->sort_list[i].idx) { A->sort_list[i].idx = array[i].index; A->sort_list[i].status_letter = '\0'; } } free(array); /* FIXME -- not very effective */ for (i = 0; i < A->sort_list_len; i++) { if (real_cur == A->sort_list[i].idx) { menu_header_change(page,header_current,i); if (i < top || i >= top+li) menu_header_change(page,header_current,i); break; } } menu_trigger_redraw(page); } static int calculate_status P_((struct menu_anon_param *A, int index)); /* return 1 if changed */ static int calculate_status(A,index) struct menu_anon_param *A; int index; { int * vector = NULL; int len; char new_val; if (index < 0 || index > A->sort_list_len) panic("THREAD PANIC",__FILE__,__LINE__,"calculate_status", "Bad index",0); vector = give_thread_message_list(A->mailbox,A->sort_list[index].idx, &len); if (vector) { int i; int all_deleted = 1; int all_expired = 1; new_val = ' '; for (i = 0; i < len; i++) { if (!ison_status_message(A->mailbox,vector[i],status_basic, DELETED)) all_deleted = 0; if (!ison_status_message(A->mailbox,vector[i],status_basic, EXPIRED)) all_expired = 0; if (' ' == new_val && ison_status_message(A->mailbox,vector[i],status_basic,UNREAD)) new_val = 'O'; if ((' ' == new_val || 'O' == new_val) && ison_status_message(A->mailbox,vector[i],status_basic,NEW)) new_val = 'N'; } if (all_expired) new_val = 'E'; if (all_deleted) new_val = 'D'; free(vector); } else new_val = 'Z'; if (A->sort_list[index].status_letter != new_val) { A->sort_list[index].status_letter = new_val; return 1; } return 0; } enum { thread_mp_mailbox, thread_mp_param, thread_mp_COUNT }; S_(header_line_redraw thread_show_header) static void thread_show_header P_((struct menu_context *ptr, struct menu_param *list, int line_number, int index, int is_current)); static void thread_show_header(ptr,list,line_number,index,is_current) struct menu_context *ptr; struct menu_param *list; int line_number; int index; int is_current; { struct menu_anon_param *A = mp_lookup_anon(list,thread_mp_param); int LINES, COLUMNS; menu_get_sizes(ptr, &LINES, &COLUMNS); menu_ClearLine(ptr,line_number); menu_MoveCursor(ptr,line_number,0); if (is_current) { if (has_highlighting && ! arrow_cursor) { menu_StartXX(ptr,pg_INVERSE); menu_PutLine0(ptr,line_number,0," "); } else { menu_PutLine0(ptr,line_number,0,"-> "); } } else menu_PutLine0(ptr,line_number,0," "); if (index >= 0 && index < A->sort_list_len) { CONST struct thread_info *X; if (!A->sort_list[index].status_letter) calculate_status(A,index); /* give_thread_info checks that index is correct range */ X = give_thread_info(A->mailbox,A->sort_list[index].idx); if (X) { /* Message menu is show time as sending timezone, but on thread view we use localtime */ char buffer[7]; struct tm * tmres = localtime(& X->time_sent_first); struct string * S = NULL; int l; S = format_string(FRM("%c %3d %3.3s %-2d "), A->sort_list[index].status_letter ? A->sort_list[index].status_letter : '?', index+1, arpa_monname[tmres->tm_mon], tmres->tm_mday); l = string_len(S); if (COLUMNS > l+3) { struct string *S1; if (X->time_sent_first != X->time_sent_last) { tmres = localtime(& X->time_sent_last); S1 = format_string(FRM("- %3.3s %-2d %04d "), arpa_monname[tmres->tm_mon], tmres->tm_mday,tmres->tm_year+1900); } else { S1 = format_string(FRM("%04d "), tmres->tm_year+1900); } append_string(&S,S1); free_string(&S1); l = string_len(S); } if (COLUMNS > l+3) { struct string *S1; if (X->num_messages != 1) { S1 = format_string(FRM("[%3d] "), X->num_messages); } else { S1 = format_string(FRM(" ")); } append_string(&S,S1); free_string(&S1); l = string_len(S); } menu_PutLineX(ptr,line_number,3,FRM("%S"),S); free_string(&S); if (X->thread_subject && COLUMNS > l+3) { struct string * buffer2 = NULL; int X1 = 0; int subj_width = COLUMNS-l-3; int was_len; /* clip_from_string updates X1 */ buffer2 = curses_printable_clip(X->thread_subject,&X1, subj_width, &was_len,subj_width); if (buffer2) { menu_PutLineX(ptr,line_number,l+3,FRM("%S"), buffer2); free_string(&buffer2); } if (is_current && !arrow_cursor) { while (was_len < subj_width) { menu_Writechar(ptr,' '); was_len++; } } } } } if (is_current) { if (has_highlighting && ! arrow_cursor) { menu_EndXX(ptr,pg_INVERSE); } } menu_Writechar(ptr,'\r'); menu_Writechar(ptr,'\n'); } S_(header_line_redraw thread_show_current) static void thread_show_current P_((struct menu_context *ptr, struct menu_param *list, int line_number, int index, int is_current)); static void thread_show_current(ptr,list,line_number,index,is_current) struct menu_context *ptr; struct menu_param *list; int line_number; int index; int is_current; { if (has_highlighting && ! arrow_cursor) { thread_show_header(ptr,list,line_number,index,is_current); } else { if (!is_current) menu_PutLine0(ptr,line_number,0," "); /* remove old pointer... */ else menu_PutLine0(ptr,line_number,0,"->"); } } S_(header_line_redraw thread_show_status) static void thread_show_status P_((struct menu_context *ptr, struct menu_param *list, int line_number, int index, int is_current)); static void thread_show_status(ptr,list,line_number,index,is_current) struct menu_context *ptr; struct menu_param *list; int line_number; int index; int is_current; { struct menu_anon_param *A = mp_lookup_anon(list,thread_mp_param); if (has_highlighting && ! arrow_cursor) { thread_show_header(ptr,list,line_number,index,is_current); } else { if (!is_current) menu_PutLine0(ptr,line_number,0," "); /* remove old pointer... */ else menu_PutLine0(ptr,line_number,0,"->"); if (index >= 0 && index < A->sort_list_len) { CONST struct thread_info *X; /* give_thread_info checks that index is correct range */ X = give_thread_info(A->mailbox,A->sort_list[index].idx); if (X) { menu_PutLineX(ptr,line_number,3, FRM("%c"), A->sort_list[index].status_letter ? A->sort_list[index].status_letter : '?'); } } } } S_(subpage_simple_redraw sb_update_thread_menu) static int sb_update_thread_menu P_((struct menu_context *ptr, struct menu_param *list)); static int sb_update_thread_menu(ptr,list) struct menu_context *ptr; struct menu_param *list; { menu_ClearScreen(ptr); menu_print_format_center(ptr,0, CATGETS(elm_msg_cat, MeSet, MeThreadMenuLine1, "To select thread, press . j = move down, k = move up, q = quit menu")); menu_print_format_center(ptr,1, CATGETS(elm_msg_cat, MeSet, MeThreadMenuLine2, "Or m)ail message, d)elete or u)ndelete messages of thread")); return 1; } S_(subpage_simple_redraw sb_update_threadtitle) static int sb_update_threadtitle P_((struct menu_context *ptr, struct menu_param *list)); static int sb_update_threadtitle(ptr,list) struct menu_context *ptr; struct menu_param *list; { struct menu_common *mptr = mp_lookup_mcommon(list,thread_mp_mailbox); struct string * f1 = mcommon_title(mptr); struct menu_anon_param *A = mp_lookup_anon(list,thread_mp_param); int num_threads = get_thread_count(A->mailbox,0); struct string * buffer = NULL; struct string * buffer2 = NULL; int LINES, COLUMNS; int l1,l2,l; menu_ClearScreen(ptr); menu_get_sizes(ptr, &LINES, &COLUMNS); if (1 == num_threads) buffer = format_string(CATGETS(elm_msg_cat, MeSet, MeShownWithThread, "%S with 1 thread"), f1); else buffer = format_string(CATGETS(elm_msg_cat, MeSet, MeShownWithThreads, "%S with %d threads"), f1,num_threads); l1 = string_len(buffer); buffer2 = format_string(FRM("[ELM %s]"), version_buff); l2 = string_len(buffer2); l = l1 + l2 + 1; /* Assumed */ if (l > COLUMNS) { if (l2 < COLUMNS) menu_PutLineX(ptr,2,(COLUMNS - l2)/2,FRM("%S"),buffer2); if (l1 > COLUMNS) menu_PutLineX(ptr,1,1,FRM("%S"),buffer); else menu_PutLineX(ptr,1,(COLUMNS - l1)/2,FRM("%S"),buffer); } else { menu_PutLineX(ptr,1,(COLUMNS - l)/2,FRM("%S %S"),buffer,buffer2); } free_string(&buffer2); free_string(&buffer); free_string(&f1); return 1; /* subpage area updated completely */ } static void set_thread_screen P_((struct menu_context *page, struct screen_parts *LOC, struct menu_param *LIST)); static void set_thread_screen(page,LOC,LIST) struct menu_context *page; struct screen_parts *LOC; struct menu_param *LIST; { int LINES, COLUMNS; int headers_per_page; menu_get_sizes(page,&LINES, &COLUMNS); /* 1) Title part of screen */ if (! LOC->title_page) LOC->title_page = new_menu_subpage(page,0,4,sb_update_threadtitle, LIST); else menu_subpage_relocate(LOC->title_page,page,0,4); /* 3) menu part */ if (LOC->menu_page && LINES < 14) erase_menu_context (&(LOC->menu_page)); else if (LOC->menu_page) menu_subpage_relocate(LOC->menu_page,page,LINES-7,3); else if (mini_menu && LINES > 14) LOC->menu_page = new_menu_subpage(page,LINES-7,3, sb_update_thread_menu,LIST); /* 4) prompt part */ if (LOC->prompt_page) menu_subpage_relocate(LOC->prompt_page,page,LINES-4,4); else LOC->prompt_page = new_menu_subpage(page,LINES-4,4, subpage_simple_noredraw,LIST); /* 2) thread part */ headers_per_page = LOC->menu_page ? LINES-12 : LINES-9; if (headers_per_page < 1) { headers_per_page = 1; } if (! LOC->header_page) LOC->header_page = new_menu_header(page,4, headers_per_page, thread_show_header, thread_show_current, null_header_param_changed, thread_show_status, LIST); else menu_header_relocate(LOC->header_page,page, 4,headers_per_page); } static void check_thread_screen P_((struct screen_parts *LOC, struct menu_param *list)); static void check_thread_screen(LOC,list) struct screen_parts *LOC; struct menu_param *list; { /* 1) title page */ if (menu_resized(LOC->title_page)) { DPRINT(Debug,1, (&Debug, "title page resized\n")); } if (menu_need_redraw(LOC->title_page)) { DPRINT(Debug,1, (&Debug, "title page redraw???\n")); sb_update_threadtitle(LOC->title_page,list); } /* 2) headers part */ if (menu_resized(LOC->header_page)) { DPRINT(Debug,1, (&Debug, "header page resized\n")); } if (menu_need_redraw(LOC->header_page)) { DPRINT(Debug,1, (&Debug, "header page redraw\n")); menu_ClearScreen(LOC->header_page); } if (LOC->menu_page) { /* 3) menu page */ if (menu_resized(LOC->menu_page)) { DPRINT(Debug,1, (&Debug, "menu page resized\n")); } if (menu_need_redraw(LOC->menu_page)) { DPRINT(Debug,1, (&Debug, "menu page redraw\n")); sb_update_thread_menu(LOC->menu_page,list); } } /* 4) prompt part */ if (menu_resized(LOC->prompt_page)) { DPRINT(Debug,1, (&Debug, "prompt page resized\n")); } if (menu_need_redraw(LOC->prompt_page)) { DPRINT(Debug,7, (&Debug, "prompt page redraw\n")); menu_ClearScreen(LOC->prompt_page); show_last_error(); /* for those operations that have to * clear the footer except for a message. */ } } void ViewThreads(mailbox,aview, parent_page) struct MailboxView *mailbox; struct AliasView *aview; struct menu_context *parent_page; { struct menu_context * page; struct screen_parts LOC = { NULL, NULL, NULL, NULL }; struct menu_common MENU; int mailbox_sort_needed = 0; struct menu_anon_param A; struct menu_param PARAM[thread_mp_COUNT+1] = { { mp_menu_common, 0 }, { mp_anon_param,0 }, { mp_END,0 } }; int ch; int update = 1; int c; /* we need update thread list, becuase it may be changed meanwhile */ update_mailbox_threads(mailbox); set_mcommon_from_mbxview(&MENU,mailbox); mp_list_set_mcommon(PARAM,thread_mp_mailbox,&MENU); A.mailbox = mailbox; A.sort_list = NULL; A.sort_list_len = 0; mp_list_set_anon(PARAM,thread_mp_param,&A); page = new_menu_context(); set_thread_screen(page,&LOC,PARAM); sort_threads(mailbox,&A,LOC.header_page); c = get_current(mailbox); if (c > 0) { int i; struct folder_view INDEX; int li,co; INDEX.thread_number = -1; give_index_number(mailbox,c-1,&INDEX); menu_get_sizes(LOC.header_page, &li, &co); /* FIXME -- not very effective */ for (i = 0; i < A.sort_list_len; i++) { if (INDEX.thread_number == A.sort_list[i].idx) { menu_header_change(LOC.header_page,header_current,i); if (i > li) menu_header_change(LOC.header_page, header_top_line,i); } } } for (;;) { if (menu_resized(page)) { set_thread_screen(page,&LOC,PARAM); update = 1; } new_mail_check(mailbox, page, &LOC); if (update_view(mailbox)) { update = 1; mailbox_sort_needed = 1; /* We do not sort mailbox, because we do not show message list */ update_mailbox_threads(mailbox); sort_threads(mailbox,&A,LOC.header_page); } if (update || menu_need_redraw(page)) { menu_ClearScreen(page); /* Call refresh routines of children */ menu_redraw_children(page); update = 0; show_last_error(); } check_thread_screen(&LOC, PARAM); { int lin,col; menu_ClearLine(LOC.prompt_page,0); menu_PutLineX (LOC.prompt_page,0, 0, CATGETS(elm_msg_cat, MeSet, MeThreadMenuPrompt, "Thread command: ")); menu_GetXYLocation(LOC.prompt_page,&lin,&col); menu_CleartoEOS(LOC.prompt_page); show_last_error(); menu_MoveCursor(LOC.prompt_page,lin,col); ch = menu_ReadCh(LOC.prompt_page, REDRAW_MARK|READCH_CURSOR|READCH_resize| READCH_sig_char); menu_CleartoEOS(LOC.prompt_page); set_error(""); /* clear error buffer */ } ch = do_movement1(LOC.header_page,ch,A.sort_list_len,&M); switch (ch) { int cur; int top; int li,co; case RESIZE_MARK: DPRINT(Debug,4, (&Debug, " ... resizing\n")); continue; case ctrl('L'): case REDRAW_MARK: update = 1; break; case DOWN_MARK : case 'j' : cur = menu_header_get(LOC.header_page,header_current); cur++; while (0 <= cur && cur < A.sort_list_len) { if (A.sort_list[cur].status_letter != 'D' && A.sort_list[cur].status_letter != 'Z') { goto found1; } cur++; } lib_error(CATGETS(elm_msg_cat, MeSet, MeNoMoreUndThreadsBelow, "No more undeleted threads below.")); break; found1: top = menu_header_get(LOC.header_page,header_top_line); menu_header_change(LOC.header_page, header_current,cur); menu_get_sizes(LOC.header_page, &li, &co); if (top+li <= cur) menu_header_change(LOC.header_page, header_top_line,cur); break; case UP_MARK : case 'k' : cur = menu_header_get(LOC.header_page,header_current); cur--; while (0 <= cur && cur < A.sort_list_len) { if (A.sort_list[cur].status_letter != 'D' && A.sort_list[cur].status_letter != 'Z') { goto found2; } cur--; } lib_error(CATGETS(elm_msg_cat, MeSet, MeNoMoreUndThreadsAbove, "No more undeleted threads above.")); break; found2: top = menu_header_get(LOC.header_page,header_top_line); menu_header_change(LOC.header_page, header_current,cur); menu_get_sizes(LOC.header_page, &li, &co); if (top > cur) { top = cur-li+1; if (top < 0) top = 0; menu_header_change(LOC.header_page, header_top_line,top); } break; case 'd': cur = menu_header_get(LOC.header_page,header_current); if (0 <= cur && cur < A.sort_list_len) { int len; int * vector = give_thread_message_list(mailbox,A.sort_list[cur].idx, &len); if (vector) { int i; for (i = 0; i < len; i++) { setf_status_message(mailbox,vector[i],status_basic, DELETED); } free(vector); } if (calculate_status(&A,cur)) { menu_header_status_update(LOC.header_page,cur); } } if (resolve_mode) { cur = menu_header_get(LOC.header_page,header_current); cur++; while (0 <= cur && cur < A.sort_list_len) { if (A.sort_list[cur].status_letter != 'D' && A.sort_list[cur].status_letter != 'Z') { goto found1; } cur++; } } break; case 'u': cur = menu_header_get(LOC.header_page,header_current); if (0 <= cur && cur < A.sort_list_len) { int len; int * vector = give_thread_message_list(mailbox,A.sort_list[cur].idx, &len); if (vector) { int i; for (i = 0; i < len; i++) { clearf_status_message(mailbox,vector[i],status_basic, DELETED); } free(vector); } if (calculate_status(&A,cur)) { menu_header_status_update(LOC.header_page,cur); } } if (resolve_mode) { cur = menu_header_get(LOC.header_page,header_current); cur++; while (0 <= cur && cur < A.sort_list_len) { if (A.sort_list[cur].status_letter != 'Z') { goto found1; } cur++; } } break; case '\n': cur = menu_header_get(LOC.header_page,header_current); if (0 <= cur && cur < A.sort_list_len) { int need_resort = 0; ViewThread1(mailbox,aview,A.sort_list[cur].idx,page, &need_resort); if (calculate_status(&A,cur)) { menu_header_status_update(LOC.header_page,cur); } /* New mails may be arrived meanwhile */ update = 1; if (need_resort) mailbox_sort_needed = 1; /* We do not sort mailbox, because we do not show message list */ update_mailbox_threads(mailbox); sort_threads(mailbox,&A,LOC.header_page); } break; case 'm': menu_Write_to_screen(LOC.prompt_page, CATGETS(elm_msg_cat, MeSet, MeMail, "Mail")); FlushBuffer(); send_msg_l(-1, NULL, NULL, NULL, MAIL_EDIT_MSG,allow_forms, mailbox, aview,page, LOC.prompt_page); break; case 'i': case 'q': case 'x': case TERMCH_interrupt_char: case EOF: goto OUT; case 0: break; default: if (ch > '0' && ch <= '9') { int value; struct string * str = format_string(CATGETS(elm_msg_cat, MeSet, MeThreadItem, "thread")); menu_Write_to_screen(LOC.prompt_page, CATGETS(elm_msg_cat, MeSet, MeNewCurrentThread, "New current thread")); cur = menu_header_get(LOC.header_page,header_current); value = read_number(ch, str, cur+1, page, LOC.prompt_page); free_string(&str); if (value > A.sort_list_len) { lib_error(CATGETS(elm_msg_cat, MeSet, MeNotThatManyThread, "Not that many threads.")); } else if (value > 0) { cur = value-1; top = menu_header_get(LOC.header_page,header_top_line); menu_header_change(LOC.header_page, header_current,cur); menu_get_sizes(LOC.header_page, &li, &co); if (top+li <= cur) menu_header_change(LOC.header_page, header_top_line,cur); else if (top > cur) { top = cur-li+1; if (top < 0) top = 0; menu_header_change(LOC.header_page, header_top_line,top); } } } else { if (isascii(ch) && isprint(ch)) lib_error(CATGETS(elm_msg_cat, MeSet, MeThreadUnknownCommand, "Unknown command: %c"), ch); else lib_error(CATGETS(elm_msg_cat, MeSet, MeUnknownCommand2, "Unknown command.")); } } } OUT: free_mailbox_screen(&LOC); erase_menu_context(&page); if (mailbox_sort_needed) resort_mailbox(mailbox,1); menu_trigger_redraw(parent_page); /* Force default return to parent page ... */ menu_set_default(parent_page); } /* * Local Variables: * mode:c * c-basic-offset:4 * buffer-file-coding-system: iso-8859-1 * End: */