1 #include "elm_defs.h"
2 #include "elm_globals.h"
3 #include "sndparts.h"
4 #include "s_attach.h"
5 #include <assert.h>
6 
7 #define AT_DUMMYMODE	(user_level == 0)
8 #define AT_MAX_ATTACH	64
9 
10 /* WARNING - side effects */
11 #define PutRight(line, str) PutLine0((line), COLS-(strlen(str)+2), (str))
12 
13 #define S_(sel, str)	catgets(elm_msg_cat, AttachSet, (sel), (str))
14 
15 /*
16  * Locations within the screen display.
17  */
18 #define ATLINE_TITLE		0		/* page title		*/
19 #define ATLINE_LTOP		3		/* top line of att list	*/
20 #define ATLINE_LBOT		(LINES-14)	/* bot line of att list	*/
21 #define ATLINE_CURR_TITLE	(LINES-12)	/* current sel title	*/
22 #define ATLINE_CURR_FILE	(LINES-11)	/* curr filename	*/
23 #define ATLINE_CURR_TYPE	(LINES-10)	/* curr Content-Type:	*/
24 #define ATLINE_CURR_ENCOD	(LINES-9)	/* curr C-T-Encoding:	*/
25 #define ATLINE_CURR_DESCR	(LINES-8)	/* curr C-Description:	*/
26 #define ATLINE_CURR_DISP	(LINES-7)	/* curr C-Disposition:	*/
27 #define ATLINE_INSTR		(LINES-4)	/* instructions display	*/
28 #define ATLINE_PROMPT		(LINES-2)	/* user data entry line	*/
29 
30 #define ATCOL_CURR_COLON	24	/* end of curr selection title	*/
31 #define ATCOL_CURR_DATA		26	/* start of curr selection data	*/
32 
33 /*
34  * Display redraw requests.
35  */
36 #define ATDRAW_NONE		(0)	/* nothing to redisplay		*/
37 #define ATDRAW_HEADER		(1<<0)	/* redisplay the screen header	*/
38 #define ATDRAW_LIST		(1<<1)	/* redisplay list of attachments*/
39 #define ATDRAW_LIST_SEL		(1<<2)	/*  just sel change of _LIST	*/
40 #define ATDRAW_CURR_FILE	(1<<3)	/* redisplay curr filename	*/
41 #define ATDRAW_CURR_TYPE	(1<<4)	/* redisplay curr Content-Type:	*/
42 #define ATDRAW_CURR_ENCOD	(1<<5)	/* redisplay curr C-T-Encoding:	*/
43 #define ATDRAW_CURR_DESCR	(1<<6)	/* redisplay curr C-Descrip:	*/
44 #define ATDRAW_CURR_DISP	(1<<7)	/* redisplay curr C-Disposition:*/
45 #define ATDRAW_CURR_ALL		(ATDRAW_CURR_FILE|ATDRAW_CURR_TYPE|ATDRAW_CURR_ENCOD|ATDRAW_CURR_DESCR|ATDRAW_CURR_DISP)
46 #define ATDRAW_INSTR		(1<<8)
47 #define ATDRAW_PROMPT		(1<<9)
48 #define ATDRAW_ALL		(~0)	/* redisplay everything		*/
49 
50 
51 /*
52  * Entry selection/sizing/placment values.
53  */
54 #define AT_lines_per_page	((ATLINE_LBOT-ATLINE_LTOP) + 1)
55 #define AT_pagenum(sel)		((sel) / AT_lines_per_page)
56 #define AT_line(sel)		((sel) % AT_lines_per_page)
57 #define AT_first_sel_of_page	(AT_pagenum(curr_sel)*AT_lines_per_page)
58 #define AT_last_sel_of_page	((AT_pagenum(curr_sel)+1)*AT_lines_per_page - 1)
59 #define AT_first_sel_of_list	(0)
60 #define AT_last_sel_of_list	(at_attachmenu_count - 1)
61 #define AT_num_pages		(AT_last_sel_of_list/AT_lines_per_page + 1)
62 
63 #define AT_FL_NOTOUCH	(1<<0)
64 #define AT_FL_DUMMY	(1<<1)
65 #define AT_FL_TAGGED	(1<<2)
66 
67 struct at_attachmenu_entry {
68     SEND_BODYPART *att;
69     int flags;
70 };
71 
72 static struct at_attachmenu_entry at_attachmenu_list[AT_MAX_ATTACH];
73 static int at_attachmenu_count;	/* num entries in at_attachmenu_list[]	*/
74 static SEND_BODYPART at_bogus;	/* marker used in assertions		*/
75 
76 static char at_acursor_on[]  = "->";	/* arrow cursor on		*/
77 static char at_acursor_off[] = "  ";	/* arrow cursor blanked		*/
78 
79 static void at_disp_entry P_((int, int, int));
80 static void at_disp_currline P_((int, const char *, const char *, int));
81 static void at_disp_instr P_((const char *, const char *, const char *, int));
82 static SEND_BODYPART *at_do_change_file P_((SEND_BODYPART *, int *, int *));
83 static int at_do_change_type P_((SEND_BODYPART *, int *));
84 static int at_do_change_encoding P_((SEND_BODYPART *, int *));
85 static int at_do_change_descrip P_((SEND_BODYPART *, int *));
86 static int at_do_change_disposition P_((SEND_BODYPART *, int *));
87 
88 static char *strtruncate P_((char *, int));
89 
90 /* manipulation of the internal attachment list */
91 static void atlist_initialize P_((void));
92 static int atlist_full P_((void));
93 static void atlist_insert P_((int, SEND_BODYPART *, int));
94 static void atlist_remove P_((int));
95 static void atlist_replace P_((int, SEND_BODYPART *, int));
96 static SEND_BODYPART *atlist_getbodypart P_((int));
97 static int atlist_getflags P_((int));
98 static int atlist_tagged_clrcnt P_((int));
99 static int atlist_tagged_move P_((int));
100 
attachment_menu(user_attachments_p)101 PUBLIC int attachment_menu(user_attachments_p)
102 SEND_MULTIPART **user_attachments_p;
103 {
104     SEND_MULTIPART *mp;
105     SEND_BODYPART *att;
106     int done;			/* TRUE when it's time to exit		*/
107     int inp_line, inp_col;	/* cursor position for user input	*/
108     int do_redraw;		/* what parts of screen need redrawing	*/
109     int bad_cmd;		/* TRUE if last command was bad		*/
110     int cmd;			/* command from user			*/
111     int next_cmd;		/* force a "cmd" next time through loop	*/
112     int curr_sel;		/* currently selected entry		*/
113     int prev_sel;		/* selection at previous iteration	*/
114     int mssg_ok;
115     char *mssg_what;
116     int n, i;
117     char tmp_buf[SLEN], *s;
118 
119     /* initialize our list of attachments */
120     atlist_initialize();
121     atlist_insert(0, bodypart_new(S_(AttachMainMessage,
122 		"[main message]"), BP_IS_DUMMY), AT_FL_NOTOUCH|AT_FL_DUMMY);
123 
124     /* slurp all of the existing attachments from an (SEND_BODYPART *) */
125     if (*user_attachments_p != NULL) {
126 	for (;;) {
127 	    mp = multipart_next(*user_attachments_p, (SEND_MULTIPART *) NULL);
128 	    if (mp == NULL)
129 		break;
130 	    if (atlist_full()) {
131 		error(S_(AttachTooManyAttachments,
132 			    "Too many attachments for menu to handle!"));
133 		return 0;
134 	    }
135 	    att = multipart_deletepart(*user_attachments_p, mp);
136 	    atlist_insert(at_attachmenu_count, att, 0);
137 	}
138 	multipart_destroy(*user_attachments_p);
139 	*user_attachments_p = NULL;
140     }
141 
142     done = FALSE;
143     do_redraw = ATDRAW_ALL;
144     bad_cmd = FALSE;
145     next_cmd = '\0';
146     prev_sel = curr_sel = 0;
147 
148     while (!done) {
149 
150 	/* complain if last entry was bad */
151 	if (bad_cmd) {
152 	    Beep();
153 	    bad_cmd = FALSE;
154 	}
155 
156 	/* see if the selection moved */
157 	if (prev_sel != curr_sel) {
158 	    if (curr_sel < AT_first_sel_of_list) {
159 		/* adjust for movement beyond start of list */
160 		if ((curr_sel = AT_first_sel_of_list) == prev_sel) {
161 		    bad_cmd = TRUE;
162 		    continue;
163 		}
164 	    }
165 	    if (curr_sel > AT_last_sel_of_list) {
166 		/* adjust for movement beyond end of list */
167 		if ((curr_sel = AT_last_sel_of_list) == prev_sel) {
168 		    bad_cmd = TRUE;
169 		    continue;
170 		}
171 	    }
172 	    if (AT_pagenum(curr_sel) != AT_pagenum(prev_sel)) {
173 		/* page changed */
174 		do_redraw = ATDRAW_ALL;
175 	    } else {
176 		/* moved to a selection on this page */
177 		do_redraw |= (ATDRAW_LIST_SEL|ATDRAW_CURR_ALL);
178 	    }
179 	}
180 
181 	/* do screen updates */
182 	if (do_redraw != ATDRAW_NONE) {
183 
184 	    if (do_redraw == ATDRAW_ALL)
185 		ClearScreen();
186 
187 	    /* redraw the title and selection header lines */
188 	    if (do_redraw & ATDRAW_HEADER) {
189 		if (do_redraw != ATDRAW_ALL)
190 		    ClearLine(ATLINE_TITLE);
191 		CenterLine(ATLINE_TITLE, S_(AttachScreenTitle,
192 			    "Message Attachments Screen"));
193 		sprintf(tmp_buf, S_(AttachScreenPage, "[page %d/%d]"),
194 			    AT_pagenum(curr_sel)+1, AT_num_pages);
195 		PutLine0(ATLINE_TITLE, COLS-(strlen(tmp_buf)+2), tmp_buf);
196 	    }
197 
198 	    /* redraw the entire list of entries */
199 	    if (do_redraw & ATDRAW_LIST) {
200 		n = AT_first_sel_of_page;
201 		for (i = 0 ; i < AT_lines_per_page ; ++i) {
202 		    if (n <= AT_last_sel_of_list) {
203 			at_disp_entry(ATLINE_LTOP+i, n, (n == curr_sel));
204 		    } else if (do_redraw != ATDRAW_ALL) {
205 			ClearLine(ATLINE_LTOP+i);
206 		    }
207 		    ++n;
208 		}
209 	    }
210 
211 	    /* redraw just the entries with selection changes */
212 	    if ((do_redraw & ATDRAW_LIST_SEL) && !(do_redraw & ATDRAW_LIST)) {
213 		if (prev_sel >= AT_first_sel_of_page
214 			&& prev_sel <= AT_last_sel_of_page) {
215 		    if (arrow_cursor) {
216 			PutLine0(ATLINE_LTOP+AT_line(prev_sel), 0,
217 				    at_acursor_off);
218 		    } else {
219 			at_disp_entry(ATLINE_LTOP+AT_line(prev_sel),
220 				    prev_sel, FALSE);
221 		    }
222 		}
223 		if (arrow_cursor) {
224 		    PutLine0(ATLINE_LTOP+AT_line(curr_sel), 0, at_acursor_on);
225 		} else {
226 		    at_disp_entry(ATLINE_LTOP+AT_line(curr_sel),
227 				curr_sel, TRUE);
228 		}
229 	    }
230 
231 	    /* display details on the selected attachment */
232 	    if (do_redraw == ATDRAW_ALL) {
233 		CenterLine(ATLINE_CURR_TITLE, S_(AttachCurrTitle,
234 			    "---------- Current Attachment ----------"));
235 	    }
236 	    att = atlist_getbodypart(curr_sel);
237 	    if (do_redraw & ATDRAW_CURR_FILE)
238 		at_disp_currline(ATLINE_CURR_FILE,
239 			S_(AttachCurrFileName, /*(*/ "F)ile Name"),
240 			bodypart_get_filename(att),
241 			(do_redraw != ATDRAW_ALL));
242 	    if (do_redraw & ATDRAW_CURR_TYPE)
243 		at_disp_currline(ATLINE_CURR_TYPE,
244 			S_(AttachCurrContType, /*(*/ "C)ontent Type"),
245 			bodypart_get_content(att, BP_CONT_TYPE),
246 			(do_redraw != ATDRAW_ALL));
247 	    if (do_redraw & ATDRAW_CURR_ENCOD)
248 		at_disp_currline(ATLINE_CURR_ENCOD,
249 			S_(AttachCurrContEncoding, /*(*/ "Content E)ncoding"),
250 			bodypart_get_content(att, BP_CONT_ENCODING),
251 			(do_redraw != ATDRAW_ALL));
252 	    if (do_redraw & ATDRAW_CURR_DESCR)
253 		at_disp_currline(ATLINE_CURR_DESCR,
254 			S_(AttachCurrContDescription, "Content De(s)cription"),
255 			bodypart_get_content(att, BP_CONT_DESCRIPTION),
256 			(do_redraw != ATDRAW_ALL));
257 	    if (do_redraw & ATDRAW_CURR_DISP)
258 		at_disp_currline(ATLINE_CURR_DISP,
259 			S_(AttachCurrContDisposition, "Content Dis(p)osition"),
260 			bodypart_get_content(att, BP_CONT_DISPOSITION),
261 			(do_redraw != ATDRAW_ALL));
262 
263 	    if (do_redraw & ATDRAW_INSTR) {
264 		at_disp_instr(
265 			    S_(AttachMainInstrNorm,
266 /*(((*/ "Use \"jk+-\" to move; \"?\" = help; a)dd, d)elete, or q)uit."),
267 			    S_(AttachMainInstrDummy1,
268 "Make selection:  j/k = down/up, +/- = down/up page, or enter selection num."),
269 			    S_(AttachMainInstrDummy2, /*(((*/
270 "Then a)dd after or d)elete the selection.  ? = help.  Then q)uit when done."),
271 			    (do_redraw != ATDRAW_ALL));
272 	    }
273 
274 	    if (do_redraw & ATDRAW_PROMPT) {
275 		PutLine0(ATLINE_PROMPT, 0, S_(AttachMainPrompt, "Command: "));
276 		GetCursorPos(&inp_line, &inp_col);
277 		if (do_redraw != ATDRAW_ALL) {
278 		    /* erase down to (but EXCLUDING) the error line */
279 		    for (i = ATLINE_PROMPT+1 ; i < LINES ; ++i)
280 			ClearLine(i);
281 		}
282 	    }
283 
284 	    if (do_redraw == ATDRAW_ALL)
285 		show_last_error();
286 	    do_redraw = ATDRAW_NONE;
287 
288 	}
289 
290 	prev_sel = curr_sel;
291 
292 	/* prompt for command */
293 	if (next_cmd != '\0') {
294 	    cmd = next_cmd;
295 	    next_cmd = '\0';
296 	} else {
297 	    MoveCursor(inp_line, inp_col);
298 	    CleartoEOLN();
299 	    PutLine0(-1, -1, "q\b");
300 	    if ((cmd = GetKey(0)) == KEY_REDRAW) {
301 		do_redraw = ATDRAW_ALL;
302 		continue;
303 	    }
304 	    if (clear_error())
305 		MoveCursor(inp_line, inp_col);
306 	}
307 
308 	switch (cmd) {
309 
310 	case '?':			/* help */
311 	    display_helpfile("attach");
312 	    do_redraw = ATDRAW_ALL;
313 	    break;
314 
315 	case ctrl('L'):			/* redraw display */
316 	    do_redraw = ATDRAW_ALL;
317 	    break;
318 
319 #ifdef ALLOW_SUBSHELL
320 	case '!':			/* subshell */
321 	    at_disp_instr((char *)NULL, (char *)NULL, (char *)NULL, TRUE);
322 	    ClearLine(ATLINE_PROMPT);
323 	    do_redraw = (subshell()
324 			? ATDRAW_ALL : (ATDRAW_INSTR|ATDRAW_PROMPT));
325 	    break;
326 #endif
327 
328 	case 'q':			/* quit menu */
329 	case '\r':
330 	case '\n':
331 	    done = TRUE;
332 	    break;
333 
334 	case KEY_DOWN:			/* down entry */
335 	case 'j':
336 	    ++curr_sel;
337 	    break;
338 
339 	case KEY_UP:			/* up entry */
340 	case 'k':
341 	    --curr_sel;
342 	    break;
343 
344 	case KEY_NPAGE:			/* down page */
345 	case '+':
346 	    curr_sel += AT_lines_per_page;
347 	    break;
348 
349 	case KEY_PPAGE:			/* up page */
350 	case '-':
351 	    curr_sel -= AT_lines_per_page;
352 	    break;
353 
354 	case KEY_HOME:			/* first entry */
355 	    curr_sel = 0;
356 	    break;
357 
358 	case KEY_END:			/* last entry */
359 	case '*':
360 	    curr_sel = AT_last_sel_of_list;
361 	    break;
362 
363 	case '1': case '2': case '3':	/* numeric selection */
364 	case '4': case '5': case '6':
365 	case '7': case '8': case '9':
366 	    UnreadCh(cmd);
367 	    i = enter_number(ATLINE_PROMPT, curr_sel+1,
368 			S_(AttachAttachment, "attachment")) - 1;
369 	    if (i < AT_first_sel_of_list || i > AT_last_sel_of_list) {
370 		error(S_(AttachNoAttachmentThere,
371 			    "There isn't an attachment there!"));
372 	    } else if (i == curr_sel) {
373 		error(S_(AttachSelectionNotChanged,
374 			    "Selection not changed."));
375 	    } else {
376 		curr_sel = i;
377 	    }
378 	    do_redraw = ATDRAW_PROMPT;
379 	    break;
380 
381 	case 'a':			/* add attachment */
382 	    if (atlist_full()) {
383 		error(S_(AttachNoRoom,
384 			    "Sorry - no room for any more attachments."));
385 		break;
386 	    }
387 	    att = bodypart_new(S_(AttachNewAttachment, "[new attachment]"),
388 			BP_IS_DUMMY);
389 	    atlist_insert(++curr_sel, att, AT_FL_DUMMY);
390 	    do_redraw = ATDRAW_LIST;
391 	    /* we need to do a display update ... then continue with add */
392 	    next_cmd = (0x100|'a');
393 	    break;
394 
395 	case (0x100|'a'):		/* add attachment ... continued */
396 	    att = at_do_change_file((SEND_BODYPART *)NULL, &do_redraw, &mssg_ok);
397 	    if (att == NULL) {
398 		atlist_remove(curr_sel--);
399 		if (mssg_ok) {
400 			error(S_(AttachAttachmentNotAdded,
401 				    "Attachment not added."));
402 		}
403 		do_redraw |= ATDRAW_LIST;
404 		break;
405 	    }
406 	    atlist_replace(curr_sel, att, 0);
407 	    if (mssg_ok) {
408 		error(S_(AttachAttachmentHasBeenAdded,
409 			    "Attachment has been added to this message."));
410 	    }
411 	    do_redraw |= ATDRAW_LIST_SEL;
412 	    break;
413 
414 	case 'd':			/* delete attachment */
415 	    if (atlist_getflags(curr_sel) & AT_FL_NOTOUCH) {
416 		error(S_(AttachYouCantDelete, "Hey!  You can't delete that!"));
417 		break;
418 	    }
419 	    do_redraw = (ATDRAW_INSTR|ATDRAW_PROMPT);
420 	    if (!enter_yn(S_(AttachReallyDelete, "Really delete attachment?"),
421 			FALSE, ATLINE_PROMPT, FALSE)) {
422 		error(S_(AttachAttachmentNotDeleted,
423 			    "Attachment not deleted."));
424 		break;
425 	    }
426 	    atlist_remove(curr_sel);
427 	    if (curr_sel > AT_last_sel_of_list)
428 		--curr_sel;
429 	    error(S_(AttachAttachmentHasBeenDeleted,
430 			"Attachment has been deleted from this message."));
431 	    do_redraw |= (ATDRAW_LIST|ATDRAW_CURR_ALL);
432 	    break;
433 
434 	case 'f':			/* change "f)ile name" */
435 	    if (atlist_getflags(curr_sel) & AT_FL_NOTOUCH) {
436 		error(S_(AttachYouCantChange, "Hey!  You can't change that!"));
437 		break;
438 	    }
439 	    att = at_do_change_file(atlist_getbodypart(curr_sel),
440 			&do_redraw, &mssg_ok);
441 	    if (att == NULL) {
442 		if (mssg_ok)
443 			error(S_(AttachNotChanged, "Attachment not changed."));
444 	    } else {
445 		atlist_replace(curr_sel, att, ~0);
446 		if (mssg_ok) {
447 		    error(S_(AttachAttachmentFileChanged,
448 				"Attachment file has been changed."));
449 		}
450 		do_redraw |= (ATDRAW_LIST|ATDRAW_CURR_ALL);
451 	    }
452 	    break;
453 
454 
455 	case 'c':			/* change "c)ontent type" */
456 	    if (atlist_getflags(curr_sel) & AT_FL_NOTOUCH) {
457 		error(S_(AttachYouCantChange, "Hey!  You can't change that!"));
458 		break;
459 	    }
460 	    if (!at_do_change_type(atlist_getbodypart(curr_sel), &do_redraw))
461 		error(S_(AttachNotChanged, "Attachment not changed."));
462 	    else {
463 		error(S_(AttachAttachmentContTypeChanged,
464 			"Attachment content type has been changed."));
465 		do_redraw |= (ATDRAW_LIST_SEL|ATDRAW_CURR_ALL);
466 	    }
467 	    break;
468 
469 	case 'e':			/* change "content e)ncoding" */
470 	    if (atlist_getflags(curr_sel) & AT_FL_NOTOUCH) {
471 		error(S_(AttachYouCantChange, "Hey!  You can't change that!"));
472 		break;
473 	    }
474 	    if (!at_do_change_encoding(atlist_getbodypart(curr_sel), &do_redraw))
475 		error(S_(AttachNotChanged, "Attachment not changed."));
476 	    else {
477 		error(S_(AttachAttachmentContEncodingChanged,
478 			    "Attachment content encoding has been changed."));
479 		do_redraw |= (ATDRAW_LIST_SEL|ATDRAW_CURR_ALL);
480 	    }
481 	    break;
482 
483 	case 's':			/* change "content de(s)cription" */
484 	    if (atlist_getflags(curr_sel) & AT_FL_NOTOUCH) {
485 		error(S_(AttachYouCantChange, "Hey!  You can't change that!"));
486 		break;
487 	    }
488 	    if (!at_do_change_descrip(atlist_getbodypart(curr_sel), &do_redraw))
489 		error(S_(AttachNotChanged, "Attachment not changed."));
490 	    else {
491 		error(S_(AttachAttachmentContDescriptionChanged,
492 			"Attachment content description has been changed."));
493 		do_redraw |= (ATDRAW_LIST_SEL|ATDRAW_CURR_ALL);
494 	    }
495 	    break;
496 
497 	case 'p':			/* change "content dis(p)osition" */
498 	    if (atlist_getflags(curr_sel) & AT_FL_NOTOUCH) {
499 		error(S_(AttachYouCantChange, "Hey!  You can't change that!"));
500 		break;
501 	    }
502 	    if (!at_do_change_disposition(atlist_getbodypart(curr_sel), &do_redraw))
503 		error(S_(AttachNotChanged, "Attachment not changed."));
504 	    else {
505 		error(S_(AttachAttachmentContDispositionChanged,
506 			"Attachment content disposition has been changed."));
507 		do_redraw |= (ATDRAW_LIST_SEL|ATDRAW_CURR_ALL);
508 	    }
509 	    break;
510 
511 	case 't':			/* tag attachment */
512 	    if (atlist_getflags(curr_sel) & AT_FL_NOTOUCH) {
513 		error(S_(AttachYouCantTag, "Hey!  You can't tag that!"));
514 		break;
515 	    }
516 	    i = (atlist_getflags(curr_sel) ^ AT_FL_TAGGED);
517 	    atlist_replace(curr_sel, (SEND_BODYPART *)NULL, i);
518 	    if (i & AT_FL_TAGGED) {
519 		error(S_(AttachAttachmentHasBeenTagged,
520 			    "Current attachment has been tagged."));
521 	    } else {
522 		error(S_(AttachTagHasBeenRemoved,
523 			    "Tag has been removed from current attachment."));
524 	    }
525 	    do_redraw |= ATDRAW_LIST_SEL;
526 	    break;
527 
528 	case ctrl('T'):			/* remove all tags */
529 	    if (atlist_tagged_clrcnt(FALSE) == 0) {
530 		error(S_(AttachTagsAlreadyClear,
531 			    "All tags already are cleared."));
532 		break;
533 	    }
534 	    do_redraw = ATDRAW_PROMPT;
535 	    if (!enter_yn(S_(AttachReallyClearTags, "Really clear all tags?"),
536 			FALSE, ATLINE_PROMPT, FALSE)) {
537 		error(S_(AttachTagsNotChanged, "Tags not changed."));
538 		break;
539 	    }
540 	    atlist_tagged_clrcnt(TRUE);
541 	    error(S_(AttachTagsHaveBeenCleared, "All tags have been cleared."));
542 	    do_redraw |= ATDRAW_LIST;
543 	    break;
544 
545 	case 'm':			/* move tagged attachments */
546 	    if ((i = atlist_tagged_clrcnt(FALSE)) == 0) {
547 		error(S_(AttachMustTagAttachments,
548 			    "You must tag the attachments you want to move."));
549 		break;
550 	    }
551 	    do_redraw = ATDRAW_PROMPT;
552 	    s = (i > 1
553 			? S_(AttachMoveTaggedAttachments,
554 			    "Move tagged attachments after selection?")
555 			: S_(AttachMoveTaggedAttachment,
556 			    "Move tagged attachment after selection?"));
557 	    if (!enter_yn(s, FALSE, ATLINE_PROMPT, FALSE)) {
558 		if (i > 1) {
559 		    error(S_(AttachAttachmentsNotMoved,
560 				"Attachments not moved."));
561 		} else {
562 		    error(S_(AttachAttachmentNotMoved,
563 				"Attachment not moved."));
564 		}
565 		break;
566 	    }
567 	    curr_sel = atlist_tagged_move(curr_sel);
568 	    if (i > 1) {
569 		error(S_(AttachAttachmentsHaveBeenMoved,
570 			    "Attachments have been moved."));
571 	    } else {
572 		error(S_(AttachAttachmentHaveBeenMoved,
573 			    "Attachment has been moved."));
574 	    }
575 	    do_redraw |= ATDRAW_LIST;
576 	    break;
577 
578 	default:
579 	    bad_cmd = TRUE;
580 	    break;
581 
582 	}
583 
584     }
585 
586     /* clear out entire bottom but the error line */
587     for (i = ATLINE_INSTR-1 ; i < LINES ; ++i)
588 	ClearLine(i);
589 
590     /* rebuild the user attachments list */
591     assert(*user_attachments_p == NULL);
592     for (i = 0 ; i < at_attachmenu_count ; ++i) {
593 	if (*user_attachments_p == NULL)
594 	    *user_attachments_p = multipart_new((SEND_BODYPART *)NULL, 0L);
595 	if ((atlist_getflags(i) & AT_FL_DUMMY)) {
596 	    bodypart_destroy(atlist_getbodypart(i));
597 	} else {
598 	    multipart_appendpart(*user_attachments_p,
599 			MULTIPART_TAIL(*user_attachments_p),
600 			atlist_getbodypart(i), MP_ID_ATTACHMENT);
601 	}
602     }
603 
604     return 0;
605 }
606 
607 
at_disp_entry(line,n,selected)608 static void at_disp_entry(line, n, selected)
609 int line, n, selected;
610 {
611     int flags, len, i;
612     char out_buf[SLEN], trunc_buf[SLEN], *bp;
613     SEND_BODYPART *att;
614 
615     att = atlist_getbodypart(n);
616     flags = atlist_getflags(n);
617 
618     /* truncate parameters off displayed Content-Type */
619     (void) strfcpy(trunc_buf, bodypart_get_content(att, BP_CONT_TYPE), sizeof(trunc_buf));
620     for (bp = trunc_buf ; *bp != '\0' && *bp != ';' && !isspace(*bp) ; ++bp)
621 	;
622     *bp = '\0';
623 
624     /* format up the line (sans Content-Description) */
625     (void) sprintf(out_buf, "%2.2s%1.1s%2d  %-16.16s  %-6.6s  %-14.14s  ",
626 		(arrow_cursor && selected ? at_acursor_on : at_acursor_off),
627 		((flags & AT_FL_TAGGED) ? "+" : " "),
628 		n+1,
629 		basename(bodypart_get_filename(att)),
630 		bodypart_get_content(att, BP_CONT_ENCODING),
631 		trunc_buf);
632 
633     /* truncate the Content-Description to fit the remainder of the line */
634     (void) strfcpy(trunc_buf, bodypart_get_content(att, BP_CONT_DESCRIPTION), sizeof(trunc_buf));
635     len = strlen(out_buf);
636     (void) strcpy(out_buf+len, strtruncate(trunc_buf, COLS - (len+2)));
637 
638     MoveCursor(line, 0);
639     if (selected && !arrow_cursor)
640 	StartStandout();
641     PutLine0(-1, -1, out_buf);
642     if (selected && !arrow_cursor) {
643 	for (i = (COLS-2) - strlen(out_buf) ; i > 0 ; --i)
644 	    WriteChar(' ');
645 	EndStandout();
646     } else {
647 	CleartoEOLN();
648     }
649 }
650 
651 
at_disp_currline(line,title,value,do_erase)652 static void at_disp_currline(line, title, value, do_erase)
653 int line;
654 const char *title, *value;
655 int do_erase;
656 {
657     char trunc_buf[SLEN];
658 
659     if (do_erase)
660 	ClearLine(line);
661 
662     PutLine0(line, ATCOL_CURR_COLON-(strlen(title)+1), title);
663     MoveCursor(line, ATCOL_CURR_COLON);
664     WriteChar(':');
665     (void) strfcpy(trunc_buf, value, sizeof(trunc_buf));
666     PutLine0(line, ATCOL_CURR_DATA,
667 		    strtruncate(trunc_buf, COLS-(ATCOL_CURR_DATA+2)));
668 }
669 
670 
at_disp_instr(instr_normal,instr_dummy1,instr_dummy2,do_erase)671 static void at_disp_instr(instr_normal, instr_dummy1, instr_dummy2, do_erase)
672 const char *instr_normal, *instr_dummy1, *instr_dummy2;
673 int do_erase;
674 {
675     if (AT_DUMMYMODE) {
676 	if (do_erase)
677 	    ClearLine(ATLINE_INSTR-1);
678 	if (instr_dummy1 != NULL && *instr_dummy1)
679 	    CenterLine(ATLINE_INSTR-1, instr_dummy1);
680 	if (do_erase)
681 	    ClearLine(ATLINE_INSTR);
682 	if (instr_dummy2 != NULL && *instr_dummy2)
683 	    CenterLine(ATLINE_INSTR, instr_dummy2);
684     } else {
685 	if (do_erase) {
686 	    ClearLine(ATLINE_INSTR-1);
687 	    ClearLine(ATLINE_INSTR);
688 	}
689 	if (instr_normal != NULL && *instr_normal)
690 	    CenterLine(ATLINE_INSTR, instr_normal);
691     }
692 }
693 
694 
695 /* set "att" NULL to create a new attachment */
at_do_change_file(att,do_redraw_p,mssg_ok_p)696 static SEND_BODYPART *at_do_change_file(att, do_redraw_p, mssg_ok_p)
697 SEND_BODYPART *att;
698 int *do_redraw_p, *mssg_ok_p;
699 {
700     SEND_BODYPART *att_new;
701     char fname[SLEN], fb_dir[SLEN], fb_pat[SLEN], *s;
702     char cont_descrip[SLEN], cont_type[SLEN], cont_encoding[SLEN];
703     int do_browser, i;
704     static char save_fname[SLEN];
705 
706     if (att == NULL)
707 	fname[0] = '\0';
708     else
709 	strfcpy(fname, bodypart_get_filename(att), sizeof(fname));
710 
711     /*
712      * We may leave this routine with important messages on the display
713      * (e.g. from encoding failure).  This flag tells the calling routine
714      * it is ok to write a message.
715      */
716     *mssg_ok_p = TRUE;
717 
718     *do_redraw_p |= (ATDRAW_INSTR|ATDRAW_PROMPT|ATDRAW_CURR_FILE);
719     at_disp_instr(
720 		S_(AttachChgFileInstrNorm,
721 "Enter attachment filename.  \"~\", \"=\", and patterns ok, CTRL/D to abort."),
722 		S_(AttachChgFileInstrDummy1,
723 "Enter name of file to attach to your mail message, or CTRL/D to abort entry."),
724 		S_(AttachChgFileInstrDummy2,
725 "Say \".\" to select a file in the current directory."),
726 		TRUE);
727     ClearLine(ATLINE_PROMPT);
728 
729     for (att_new = NULL ; att_new == NULL ; ) {
730 
731 	if (*do_redraw_p == ATDRAW_ALL) {
732 	    /*
733 	     * We are in a bit of a jam here.  We've been through this loop
734 	     * once before, and the display is garbage (probably from the
735 	     * browser).  About all we can do is abort the add.  There
736 	     * should be a message on the display explaining the problem,
737 	     * so be sure not to trounce it.
738 	     */
739 	    *mssg_ok_p = FALSE;
740 	    return (SEND_BODYPART *) NULL;
741 	}
742 
743 	if (enter_string(fname, sizeof(fname),
744 		    ATLINE_CURR_FILE, ATCOL_CURR_DATA, ESTR_UPDATE) < 0)
745 	    return (SEND_BODYPART *) NULL;
746 	clear_error();
747 
748 	/* FOO - I'd rather have "^R)ecall last" and ^B)rowse options above */
749 	if (fname[0] == '\0') {
750 	    if (att != NULL)
751 		(void) strfcpy(fname,
752 			    bodypart_get_filename(att), sizeof(fname));
753 	    else if (save_fname[0] != '\0')
754 		(void) strfcpy(fname, save_fname, sizeof(fname));
755 	    if ((s = strrchr(fname, '/')) != NULL)
756 		s[1] = '\0';
757 	}
758 
759 	if (fname[0] == '\0') {
760 	    error("Please enter a filename for the attachment.");
761 	    continue;
762 	}
763 
764 	if (!expand_filename(fname))
765 	    continue;
766 
767 	if (att != NULL && streq(fname, bodypart_get_filename(att)))
768 	    return (SEND_BODYPART *) NULL;
769 
770 	if (fbrowser_analyze_spec(fname, fb_dir, fb_pat)) {
771 	    *do_redraw_p = ATDRAW_ALL;
772 	    if (!fbrowser(fname, sizeof(fname), fb_dir, fb_pat,
773 			FB_READ, "Select File to Attach")) {
774 		return (SEND_BODYPART *) NULL;
775 	    }
776 	}
777 
778 	if ((att_new = newpart_mimepart(fname)) != NULL) {
779 	    for (i = 0 ; i < BP_NUM_CONT_HEADERS ; ++i)
780 		bodypart_guess_content(att_new, i);
781 	}
782 
783     }
784 
785     (void) strfcpy(save_fname, fname, sizeof(save_fname));
786     *do_redraw_p |= ATDRAW_CURR_ALL;
787     return att_new;
788 }
789 
790 
at_do_change_type(att,do_redraw_p)791 static int at_do_change_type(att, do_redraw_p)
792 SEND_BODYPART *att;
793 int *do_redraw_p;
794 {
795     char cont_type[SLEN];
796 
797     (void) strfcpy(cont_type, bodypart_get_content(att, BP_CONT_TYPE),
798 		sizeof(cont_type));
799     *do_redraw_p |= (ATDRAW_INSTR|ATDRAW_PROMPT|ATDRAW_CURR_TYPE);
800     at_disp_instr(
801 		S_(AttachChgTypeInstrNorm,
802 "Enter attachment content type, CTRL/D to abort."),
803 		S_(AttachChgTypeInstrDummy1,
804 "Enter the \"content type\" of this file, or CTRL/D to abort entry."),
805 		S_(AttachChgTypeInstrDummy2,
806 "You DO know what a Content-Type is...don't you???"), /*FOO*/
807 		TRUE);
808     ClearLine(ATLINE_PROMPT);
809 
810     for (;;) {
811 	if (enter_string(cont_type, sizeof(cont_type),
812 		    ATLINE_CURR_TYPE, ATCOL_CURR_DATA, ESTR_UPDATE) < 0)
813 	    return FALSE;
814 	if (streq(cont_type, bodypart_get_content(att, BP_CONT_TYPE)))
815 	    return FALSE;
816 	clear_error();
817 	if (cont_type[0] == '\0') {
818 	    error(S_(AttachChgTypeEnterType, "Please enter a content type."));
819 	    continue;
820 	}
821 	bodypart_set_content(att, BP_CONT_TYPE, cont_type);
822 	return TRUE;
823     }
824     /*NOTREACHED*/
825 }
826 
827 
at_do_change_encoding(att,do_redraw_p)828 static int at_do_change_encoding(att, do_redraw_p)
829 SEND_BODYPART *att;
830 int *do_redraw_p;
831 {
832     char cont_encoding[SLEN];
833 
834     (void) strfcpy(cont_encoding, bodypart_get_content(att, BP_CONT_ENCODING),
835 		sizeof(cont_encoding));
836     *do_redraw_p |= (ATDRAW_INSTR|ATDRAW_PROMPT|ATDRAW_CURR_ENCOD);
837     at_disp_instr(
838 		S_(AttachChgEncodingInstrNorm,
839 "Enter attachment content encoding, CTRL/D to abort."),
840 		S_(AttachChgEncodingInstrDummy1,
841 "Enter the \"content encoding\" of this file, or CTRL/D to abort entry."),
842 		S_(AttachChgEncodingInstrDummy2,
843 "Common values are \"7bit\", \"8bit\", \"base64\", and \"x-uuencode\"."),
844 		TRUE);
845     ClearLine(ATLINE_PROMPT);
846 
847     for (;;) {
848 	if (enter_string(cont_encoding, sizeof(cont_encoding),
849 		    ATLINE_CURR_ENCOD, ATCOL_CURR_DATA, ESTR_UPDATE) < 0)
850 	    return FALSE;
851 	if (streq(cont_encoding, bodypart_get_content(att, BP_CONT_ENCODING)))
852 	    return FALSE;
853 	clear_error();
854 	if (!encoding_is_reasonable(cont_encoding))
855 	    continue;
856 	bodypart_set_content(att, BP_CONT_ENCODING, cont_encoding);
857 	return TRUE;
858     }
859     /*NOTREACHED*/
860 }
861 
862 
at_do_change_descrip(att,do_redraw_p)863 static int at_do_change_descrip(att, do_redraw_p)
864 SEND_BODYPART *att;
865 int *do_redraw_p;
866 {
867     char cont_descrip[SLEN];
868 
869     (void) strfcpy(cont_descrip, bodypart_get_content(att, BP_CONT_DESCRIPTION),
870 		sizeof(cont_descrip));
871     *do_redraw_p |= (ATDRAW_INSTR|ATDRAW_PROMPT|ATDRAW_CURR_DESCR);
872     at_disp_instr(
873 		S_(AttachChgDescripInstrNorm,
874 "Enter attachment content description, CTRL/D to abort."),
875 		S_(AttachChgDescripInstrDummy1,
876 "Enter the \"content description\", or CTRL/D to abort entry."),
877 		S_(AttachChgDescripInstrDummy2,
878 "This is just a brief comment to tell the recipient about the attachment."),
879 		TRUE);
880     ClearLine(ATLINE_PROMPT);
881 
882     for (;;) {
883 	if (enter_string(cont_descrip, sizeof(cont_descrip),
884 		    ATLINE_CURR_DESCR, ATCOL_CURR_DATA, ESTR_UPDATE) < 0)
885 	    return FALSE;
886 	if (streq(cont_descrip, bodypart_get_content(att, BP_CONT_DESCRIPTION)))
887 	    return FALSE;
888 	clear_error();
889 	bodypart_set_content(att, BP_CONT_DESCRIPTION, cont_descrip);
890 	return TRUE;
891     }
892     /*NOTREACHED*/
893 }
894 
895 
at_do_change_disposition(att,do_redraw_p)896 static int at_do_change_disposition(att, do_redraw_p)
897 SEND_BODYPART *att;
898 int *do_redraw_p;
899 {
900     const char *cp;
901     char orig_fname[SLEN], new_fname[SLEN], *s;
902     int len;
903 
904     *do_redraw_p |= (ATDRAW_INSTR|ATDRAW_PROMPT|ATDRAW_CURR_DISP);
905     at_disp_instr(
906 		S_(AttachChgDispositionInstrNorm,
907 "Enter suggested filename (empty OK), CTRL/D to abort."),
908 		S_(AttachChgDispositionInstrDummy1,
909 "Enter a suggested filename under which the recipient might want to save"),
910 		S_(AttachChgDispositionInstrDummy2,
911 "this attachment (empty filename is OK), or CTRL/D to abort entry."),
912 		TRUE);
913     ClearLine(ATLINE_PROMPT);
914 
915     /*
916      * The Content-Disposition header is in a format:
917      *     attachment; filename="/suggested/attachment/filename"
918      * The user will be editing just the filename.  Here, we parse
919      * the filename out of the existing header.
920      */
921     orig_fname[0] = '\0';
922     cp = bodypart_get_content(att, BP_CONT_DISPOSITION);
923     while ((cp = strchr(cp, ';')) != NULL) {
924 	++cp;					/* skip semicolon	*/
925 	while (isspace(*cp))			/* advance to name	*/
926 	    ++cp;
927 	if (!strbegConst(cp, "filename="))	/* "filename=" param?	*/
928 	    continue;
929 	cp += (sizeof("filename=")-1);		/* point to arg		*/
930 	if (*cp == '"') {
931 	    /* filename is in quotes */
932 	    len = len_next_part(cp) - 2;
933 	    if (len >= sizeof(orig_fname))
934 		len = sizeof(orig_fname)-1;
935 	    (void) strfcpy(orig_fname, cp+1, len+1);
936 	} else {
937 	    /* filename is unquoted */
938 	    (void) strfcpy(orig_fname, cp, sizeof(orig_fname));
939 	    for (s = orig_fname ; *s != '\0' && !isspace(*s) ; ++s)
940 		;
941 	    *s = '\0';
942 	}
943     }
944     (void) strcpy(new_fname, orig_fname);
945 
946     for (;;) {
947 	PutLine0(ATLINE_CURR_DISP, ATCOL_CURR_DATA, "attachment; filename=");
948 	if (enter_string(new_fname, sizeof(new_fname), -1, -1, ESTR_UPDATE) < 0)
949 	    return FALSE;
950 	if (streq(new_fname, orig_fname))
951 	    return FALSE;
952 	clear_error();
953 	if (new_fname[0] != '\0') {
954 	    sprintf(orig_fname, "attachment; filename=\"%s\"", new_fname);
955 	    bodypart_set_content(att, BP_CONT_DISPOSITION, orig_fname);
956 	} else {
957 	    bodypart_set_content(att, BP_CONT_DISPOSITION, "attachment");
958 	}
959 	return TRUE;
960     }
961     /*NOTREACHED*/
962 }
963 
964 
strtruncate(str,len)965 static char *strtruncate(str, len)
966 char *str;
967 int len;
968 {
969     int i;
970 
971     if (strlen(str) <= len)
972 	return str;
973 
974     while (len > 0 && isspace(str[len-1]))
975 	--len;
976 
977     /* see if there is a word boundary near the end */
978     for (i = 4 ; i < 10 ; ++i) {
979 	if (isspace(str[len-i])) {
980 		len = len-i+1;
981 		while (len > 0 && isspace(str[len-1]))
982 		    --len;
983 		(void) strcpy(str+len, " ...");
984 		return str;
985 	}
986     }
987 
988     /* nope - just chop off the tail */
989     (void) strcpy(str+len-4, " ...");
990     return str;
991 }
992 
993 
atlist_initialize()994 static void atlist_initialize()
995 {
996     int i;
997 
998     for (i = 0 ; i < AT_MAX_ATTACH ; ++i) {
999 	at_attachmenu_list[i].att = &at_bogus;
1000 	at_attachmenu_list[i].flags = (~0);
1001     }
1002     at_attachmenu_count = 0;
1003 }
1004 
1005 
atlist_full()1006 static int atlist_full()
1007 {
1008     return (at_attachmenu_count >= AT_MAX_ATTACH);
1009 }
1010 
1011 
atlist_insert(sel,att,flags)1012 static void atlist_insert(sel, att, flags)
1013 int sel;
1014 SEND_BODYPART *att;
1015 int flags;
1016 {
1017     int i;
1018 
1019     assert(at_attachmenu_count >= 0 && at_attachmenu_count < AT_MAX_ATTACH);
1020     assert(sel >= 0 && sel <= at_attachmenu_count);
1021 
1022     for (i = at_attachmenu_count ; i > sel ; --i) {
1023 	assert(at_attachmenu_list[i-1].att != &at_bogus);
1024 	at_attachmenu_list[i].att = at_attachmenu_list[i-1].att;
1025 	at_attachmenu_list[i].flags = at_attachmenu_list[i-1].flags;
1026     }
1027     at_attachmenu_list[sel].att = att;
1028     at_attachmenu_list[sel].flags = flags;
1029     ++at_attachmenu_count;
1030 }
1031 
1032 
atlist_remove(sel)1033 static void atlist_remove(sel)
1034 int sel;
1035 {
1036     int i;
1037 
1038     assert(at_attachmenu_count > 0 && at_attachmenu_count <= AT_MAX_ATTACH);
1039     assert(sel >= 0 && sel < at_attachmenu_count);
1040     assert(at_attachmenu_list[sel].att != &at_bogus);
1041 
1042     bodypart_destroy(at_attachmenu_list[sel].att);
1043 
1044     for (i = sel+1 ; i < at_attachmenu_count ; ++i) {
1045 	assert(at_attachmenu_list[i].att != &at_bogus);
1046 	at_attachmenu_list[i-1].att = at_attachmenu_list[i].att;
1047 	at_attachmenu_list[i-1].flags = at_attachmenu_list[i].flags;
1048     }
1049     at_attachmenu_list[at_attachmenu_count].att = &at_bogus;
1050     at_attachmenu_list[at_attachmenu_count].flags = (~0);
1051     --at_attachmenu_count;
1052 }
1053 
1054 
atlist_replace(sel,att,flags)1055 static void atlist_replace(sel, att, flags)
1056 int sel;
1057 SEND_BODYPART *att;
1058 int flags;
1059 {
1060     assert(at_attachmenu_count > 0 && at_attachmenu_count <= AT_MAX_ATTACH);
1061     assert(sel >= 0 && sel < at_attachmenu_count);
1062     assert(at_attachmenu_list[sel].att != &at_bogus);
1063 
1064     if (att != NULL && att != at_attachmenu_list[sel].att) {
1065 	bodypart_destroy(at_attachmenu_list[sel].att);
1066 	at_attachmenu_list[sel].att = att;
1067     }
1068     if (flags != ~0)
1069 	at_attachmenu_list[sel].flags = flags;
1070 }
1071 
1072 
atlist_getbodypart(sel)1073 static SEND_BODYPART *atlist_getbodypart(sel)
1074 int sel;
1075 {
1076     assert(at_attachmenu_count > 0 && at_attachmenu_count <= AT_MAX_ATTACH);
1077     assert(sel >= 0 && sel < at_attachmenu_count);
1078     assert(at_attachmenu_list[sel].att != &at_bogus);
1079 
1080     return at_attachmenu_list[sel].att;
1081 }
1082 
1083 
atlist_getflags(sel)1084 static int atlist_getflags(sel)
1085 int sel;
1086 {
1087     assert(at_attachmenu_count > 0 && at_attachmenu_count <= AT_MAX_ATTACH);
1088     assert(sel >= 0 && sel < at_attachmenu_count);
1089     assert(at_attachmenu_list[sel].att != &at_bogus);
1090 
1091     return at_attachmenu_list[sel].flags;
1092 }
1093 
1094 
atlist_tagged_clrcnt(do_clear)1095 static int atlist_tagged_clrcnt(do_clear)
1096 int do_clear;	/* if TRUE clear all tags, if FALSE just return count */
1097 {
1098     int count, i;
1099 
1100     assert(at_attachmenu_count > 0 && at_attachmenu_count <= AT_MAX_ATTACH);
1101 
1102     count = 0;
1103     for (i = 0 ; i < at_attachmenu_count ; ++i) {
1104 	if (!(at_attachmenu_list[i].flags & AT_FL_NOTOUCH)
1105 		    && (at_attachmenu_list[i].flags & AT_FL_TAGGED)) {
1106 	    if (do_clear)
1107 		at_attachmenu_list[i].flags &= ~AT_FL_TAGGED;
1108 	    ++count;
1109 	}
1110     }
1111     return count;
1112 }
1113 
1114 
atlist_tagged_move(sel)1115 static int atlist_tagged_move(sel)
1116 int sel;
1117 {
1118     int nmoved, i, j;
1119     struct at_attachmenu_entry tmp_att;
1120 
1121     assert(at_attachmenu_count > 0 && at_attachmenu_count <= AT_MAX_ATTACH);
1122     assert(sel >= 0 && sel < at_attachmenu_count);
1123 
1124     nmoved = 0;
1125 
1126     /*
1127      * move attachments left of selection:
1128      *
1129      * before:		_a_ _b_ _c_ _d_ _e_ _f_ _g_ _h_
1130      *		                TAG         SEL
1131      *			         i
1132      *
1133      * after:		_a_ _b_ _d_ _e_ _f_ _c_ _g_ _h_
1134      *		                        SEL TAG
1135      *			         i
1136      *
1137      * Several important effects:
1138      *  - The move modifies the index of the selected attachment,
1139      *    so the "sel" value must be adjusted.
1140      *  - Adjustment is required or else the (++i) for the next
1141      *    loop iteration will skip "_d_".
1142      *  - The target of the next move should NOT be after "sel"
1143      *    but rather "sel+1".  Thus we need to track "nmoved".
1144      */
1145 
1146     /* move attachments left of selection (note this modifies the sel value) */
1147     for (i = 0 ; i < sel ; ++i) {
1148 	if (at_attachmenu_list[i].flags & AT_FL_NOTOUCH)
1149 	    continue;
1150 	if (!(at_attachmenu_list[i].flags & AT_FL_TAGGED))
1151 	    continue;
1152 	tmp_att.att = at_attachmenu_list[i].att;
1153 	tmp_att.flags = at_attachmenu_list[i].flags;
1154 	for (j = i ; j < sel+nmoved ; ++j) {
1155 	    at_attachmenu_list[j].att = at_attachmenu_list[j+1].att;
1156 	    at_attachmenu_list[j].flags = at_attachmenu_list[j+1].flags;
1157 	}
1158 	at_attachmenu_list[sel+nmoved].att = tmp_att.att;
1159 	at_attachmenu_list[sel+nmoved].flags = tmp_att.flags;
1160 	--sel;
1161 	--i;
1162 	++nmoved;
1163     }
1164 
1165     /*
1166      * move attachments right of selection:
1167      *
1168      * before:		_a_ _b_ _c_ _d_ _e_ _f_ _g_ _h_
1169      *		                SEL         TAG
1170      *			                     i
1171      *
1172      * after:		_a_ _b_ _c_ _f_ _d_ _e_ _g_ _h_
1173      *		                SEL TAG
1174      *			                     i
1175      *
1176      * This is much simpler than the previous case.  The only
1177      * side effect of concern is that making sure the target
1178      * index for subsequent moves is adjusted by "nmoved".
1179      */
1180 
1181     for (i = sel+nmoved+1 ; i < at_attachmenu_count ; ++i) {
1182 	if (at_attachmenu_list[i].flags & AT_FL_NOTOUCH)
1183 	    continue;
1184 	if (!(at_attachmenu_list[i].flags & AT_FL_TAGGED))
1185 	    continue;
1186 	tmp_att.att = at_attachmenu_list[i].att;
1187 	tmp_att.flags = at_attachmenu_list[i].flags;
1188 	for (j = i ; j > sel+nmoved+1 ; --j) {
1189 	    at_attachmenu_list[j].att = at_attachmenu_list[j-1].att;
1190 	    at_attachmenu_list[j].flags = at_attachmenu_list[j-1].flags;
1191 	}
1192 	at_attachmenu_list[sel+nmoved+1].att = tmp_att.att;
1193 	at_attachmenu_list[sel+nmoved+1].flags = tmp_att.flags;
1194 	++nmoved;
1195     }
1196 
1197     return sel;
1198 }
1199 
1200