1 /**
2  * \file ui-equip-cmp.c
3  * \brief Supply a "resistance grid for home" in the knowledge menu
4  *
5  * This work is free software; you can redistribute it and/or modify it
6  * under the terms of either:
7  *
8  * a) the GNU General Public License as published by the Free Software
9  *    Foundation, version 2, or
10  *
11  * b) the "Angband licence":
12  *    This software may be copied and distributed for educational, research,
13  *    and not for profit purposes provided that this copyright and statement
14  *    are included in all such copies.  Other copyrights may also apply.
15  */
16 
17 #include "cave.h"
18 #include "game-input.h"
19 #include "init.h"
20 #include "object.h"
21 #include "obj-desc.h"
22 #include "obj-gear.h"
23 #include "obj-ignore.h"
24 #include "obj-info.h"
25 #include "obj-tval.h"
26 #include "player.h"
27 #include "store.h"
28 #include "ui-entry.h"
29 #include "ui-entry-renderers.h"
30 #include "ui-equip-cmp.h"
31 #include "ui-event.h"
32 #include "ui-input.h"
33 #include "ui-object.h"
34 #include "ui-output.h"
35 #include "ui-term.h"
36 #include "z-color.h"
37 #include "z-file.h"
38 #include "z-textblock.h"
39 #include "z-util.h"
40 #include "z-virt.h"
41 
42 enum equippable_source {
43 	EQUIP_SOURCE_WORN,
44 	EQUIP_SOURCE_PACK,
45 	EQUIP_SOURCE_FLOOR,
46 	EQUIP_SOURCE_HOME,
47 	EQUIP_SOURCE_STORE
48 };
49 
50 enum equippable_quality {
51 	EQUIP_QUAL_ARTIFACT,
52 	EQUIP_QUAL_EGO,
53 	EQUIP_QUAL_GOOD,
54 	EQUIP_QUAL_AVERAGE,
55 	EQUIP_QUAL_BAD
56 };
57 
58 struct equippable {
59 	char *short_name;
60 	int *vals;
61 	int *auxvals;
62 	const struct object *obj;
63 	enum equippable_source src;
64 	enum equippable_quality qual;
65 	int slot;
66 	int nmlen;
67 	wchar_t ch;
68 	byte at;
69 };
70 
71 union equippable_selfunc_extra {
72 	enum equippable_source src;
73 	enum equippable_quality qual;
74 	int slot;
75 	int propind;
76 };
77 typedef bool (*equippable_selfunc)(const struct equippable *eq,
78 	const union equippable_selfunc_extra *ex);
79 struct equippable_selector {
80 	equippable_selfunc func;
81 	union equippable_selfunc_extra ex;
82 };
83 enum equippable_expr_class {
84 	EQUIP_EXPR_SELECTOR,
85 	EQUIP_EXPR_AND,
86 	EQUIP_EXPR_OR,
87 	EQUIP_EXPR_TERMINATOR
88 };
89 struct equippable_expr {
90 	struct equippable_selector s;
91 	enum equippable_expr_class c;
92 };
93 struct equippable_filter {
94 	/*
95 	 * Has nv + 1 used elements with the last a sentinel (c ==
96 	 * EQUIP_EXPR_TERMINATOR).
97 	 */
98 	struct equippable_expr *v;
99 	/*
100 	 * Use EQUIP_EXPR_SELECTOR to indicate the simple evaluation is not
101 	 * possible.  Use EQUIP_EXPR_TERMINATOR when nv is zero:  there's no
102 	 * filtering.  The others indicate the way the terms are combined
103 	 * during simple evaluation.
104 	 */
105 	enum equippable_expr_class simple;
106 	int nv;
107 	int nalloc;
108 };
109 
110 typedef int (*equippable_cmpfunc)(const struct equippable *left,
111 	const struct equippable *right, int propind);
112 struct equippable_arranger {
113 	equippable_cmpfunc func;
114 	int propind;
115 };
116 struct equippable_sorter {
117 	/* Has nv + 1 used elements with the last a sentinel (funct == 0). */
118 	struct equippable_arranger *v;
119 	int nv;
120 	int nalloc;
121 };
122 
123 enum store_inclusion {
124 	EQUIPPABLE_NO_STORE,
125 	EQUIPPABLE_ONLY_STORE,
126 	EQUIPPABLE_YES_STORE,
127 	EQUIPPABLE_ONLY_CARRIED,
128 };
129 
130 struct prop_category {
131 	struct ui_entry **entries;
132 	wchar_t **labels;
133 	wchar_t *label_buffer;
134 	int n, off, nvw[3], ivw[3];
135 };
136 
137 struct equippable_summary {
138 	/* Has space for nalloc items; nitems are currently used */
139 	struct equippable *items;
140 	/*
141 	 * Has space for nalloc + 1 items; nfilt + 1 are currently used with
142 	 * the last a sentinel (-1).
143 	 */
144 	int *sorted_indices;
145 	int *p_and_eq_vals;
146 	int *p_and_eq_auxvals;
147 	const char *dlg_trans_msg;
148 	struct prop_category propcats[5];
149 	struct equippable_filter easy_filt;
150 	/*
151 	 * Was intended to be an alternative to easy_filt where the player
152 	 * could configure a filter on more than one attribute.  Backend
153 	 * support is, in principle there, but there's currently in place to
154 	 * allow the player to set it up.  Leaving it here for now just in
155 	 * case.  config_filt was to be what the player specified compiled to
156 	 * what the backend wants.  config_mod_filt would be config_filt
157 	 * modified to handle the filtering of items from the stores as set
158 	 * by 'c'.
159 	 */
160 	struct equippable_filter config_filt;
161 	struct equippable_filter config_mod_filt;
162 	struct equippable_sorter default_sort;
163 	/*
164 	 * Was intended to be an alternative to the default sort that the
165 	 * player could configure.  While backend support should mostly be
166 	 * there, there's nothing that allows the player to do the
167 	 * configuration.  Leaving it here for now, just in case.
168 	 */
169 	struct equippable_sorter config_sort;
170 	enum store_inclusion stores;
171 	/* Is the index, in sorted_indices, for first shown on page. */
172 	int ifirst;
173 	int indinc;
174 	int iview;
175 	/* Are indices into items for selection. */
176 	int isel0, isel1;
177 	/* Is the index, into sorted_indices, for choosing selection. */
178 	int work_sel;
179 	/* Is the number shown on current page. */
180 	int npage;
181 	int nfilt;
182 	int nitems;
183 	int nalloc;
184 	int nprop;
185 	/* Is the maximum number that can be shown on any page. */
186 	int maxpage;
187 	/* Is the maximum number of characters for a short object name. */
188 	int nshortnm;
189 	/* Is the number of character for propery labels. */
190 	int nproplab;
191 	/*
192 	 * Is the number of views to use for displaying the properties, either
193 	 * two or three.
194 	 */
195 	int nview;
196 	/*
197 	 * Is the row used for display of the combined player and current
198 	 * equipment properties.
199 	 */
200 	int irow_combined_equip;
201 	/* Is the column where the object names start. */
202 	int icol_name;
203 	/*
204 	 * These two are the terminal dimensions used when configuring the
205 	 * layout.
206 	 */
207 	int term_ncol, term_nrow;
208 	bool config_filt_is_on;
209 	bool config_sort_is_on;
210 };
211 
212 struct indirect_sort_data {
213 	const struct equippable *items;
214 	const struct equippable_arranger *arr;
215 };
216 
217 
218 /* These have to match up with the state array in equip_cmp_display(). */
219 enum {
220 	EQUIP_CMP_MENU_DONE,
221 	EQUIP_CMP_MENU_BAIL,
222 	EQUIP_CMP_MENU_NEW_PAGE,
223 	EQUIP_CMP_MENU_SAME_PAGE,
224 	EQUIP_CMP_MENU_SEL0,
225 	EQUIP_CMP_MENU_SEL1,
226 };
227 struct menu_display_state {
228 	const char *prompt;
229 	int (*keyfunc)(struct keypress ch, int istate,
230 		struct equippable_summary *s, struct player *p);
231 	bool clear;
232 	bool refresh;
233 };
234 
235 
236 static int initialize_summary(struct player *p,
237 	struct equippable_summary **s);
238 static void cleanup_summary(struct equippable_summary *s);
239 static void cleanup_summary_items(struct equippable_summary *s);
240 static int display_page(struct equippable_summary *s, const struct player *p,
241 	bool allow_reconfig);
242 static void display_equip_cmp_help(void);
243 static void display_equip_cmp_sel_help(void);
244 static int get_expected_easy_filter_count(enum store_inclusion stores);
245 static int handle_key_bail(struct keypress ch, int istate,
246 	struct equippable_summary *s, struct player *p);
247 static int handle_key_equip_cmp_general(struct keypress ch, int istate,
248 	struct equippable_summary *s, struct player *p);
249 static int handle_key_equip_cmp_select(struct keypress ch, int istate,
250 	struct equippable_summary *s, struct player *p);
251 static int prompt_for_easy_filter(struct equippable_summary *s, bool apply_not);
252 static void display_object_comparison(const struct equippable_summary *s);
253 static bool dump_to_file(const char *path);
254 static void append_to_file(ang_file *fff);
255 static void filter_items(struct equippable_summary *s);
256 /*
257  * Not used at the moment; left here in case more configurable filtering
258  * is implemented.
259  */
260 #if 0
261 static bool sel_better_than(const struct equippable *eq,
262 	const union equippable_selfunc_extra *ex);
263 #endif
264 static bool sel_at_least_resists(const struct equippable *eq,
265 	const union equippable_selfunc_extra *ex);
266 static bool sel_does_not_resist(const struct equippable *eq,
267 	const union equippable_selfunc_extra *ex);
268 static bool sel_has_flag(const struct equippable *eq,
269 	const union equippable_selfunc_extra *ex);
270 static bool sel_does_not_have_flag(const struct equippable *eq,
271 	const union equippable_selfunc_extra *ex);
272 static bool sel_has_pos_mod(const struct equippable *eq,
273 	const union equippable_selfunc_extra *ex);
274 static bool sel_has_nonpos_mod(const struct equippable *eq,
275 	const union equippable_selfunc_extra *ex);
276 /*
277  * Not used at the moment; left here in case more configurable filtering
278  * is implemented.
279  */
280 #if 0
281 static bool sel_exclude_slot(const struct equippable *eq,
282 	const union equippable_selfunc_extra *ex);
283 static bool sel_only_slot(const struct equippable *eq,
284 	const union equippable_selfunc_extra *ex);
285 #endif
286 static bool sel_exclude_src(const struct equippable *eq,
287 	const union equippable_selfunc_extra *ex);
288 static bool sel_only_src(const struct equippable *eq,
289 	const union equippable_selfunc_extra *ex);
290 static void sort_items(struct equippable_summary *s);
291 static wchar_t source_to_char(enum equippable_source src);
292 
293 
294 static struct equippable_summary *the_summary = NULL;
295 /* Used by append_to_file() (and therefore for dump_to_file()) */
296 static struct equippable_summary *s_for_file = NULL;
297 static struct indirect_sort_data sort_dat;
298 
299 
300 
equip_cmp_display(void)301 void equip_cmp_display(void)
302 {
303 	const struct menu_display_state states[] = {
304 		/* EQUIP_CMP_MENU_DONE */
305 		{ "", 0, false, false },
306 		/* EQUIP_CMP_MENU_BAIL */
307 		{ "Sorry, could not display.  Press any key.",
308 			handle_key_bail, true, false },
309 		/* EQUIP_CMP_MENU_NEW_PAGE */
310 		{ "[Up/Down arrow, p/PgUp, n/PgDn to move; ? for help; ESC to exit]", handle_key_equip_cmp_general, true, true },
311 		/* EQUIP_CMP_MENU_SAME_PAGE */
312 		{ "[Up/Down arrow, p/PgUp, n/PgDn to move; ? for help; ESC to exit]", handle_key_equip_cmp_general, false, false },
313 		/* EQUIP_CMP_MENU_SEL0 */
314 		{ "[Up/Down arrow, p/PgUp, n/PgDn to move; return to accept]", handle_key_equip_cmp_select, true, true },
315 		/* EQUIP_CMP_MENU_SEL1 */
316 		{ "[Up/Down arrow, p/PgUp, n/PgDn to move; return to accept]", handle_key_equip_cmp_select, true, true },
317 	};
318 	int istate;
319 
320 	screen_save();
321 
322 	if (initialize_summary(player, &the_summary)) {
323 		istate = EQUIP_CMP_MENU_BAIL;
324 	} else {
325 		istate = EQUIP_CMP_MENU_NEW_PAGE;
326 	}
327 
328 	while (istate != EQUIP_CMP_MENU_DONE) {
329 		struct keypress ch;
330 		int wid, hgt;
331 
332 		assert(istate >= 0 && istate < (int)N_ELEMENTS(states));
333 		if (states[istate].clear) {
334 			Term_clear();
335 		}
336 		if (states[istate].refresh) {
337 			if (display_page(the_summary, player, true)) {
338 				istate = EQUIP_CMP_MENU_BAIL;
339 			}
340 		}
341 
342 		/*
343 		 * Use row 0 for state transition or other informational
344 		 * messages
345 		 */
346 		if (the_summary->dlg_trans_msg != NULL &&
347 			the_summary->dlg_trans_msg[0] != '\0') {
348 			prt(the_summary->dlg_trans_msg, 0, 0);
349 			the_summary->dlg_trans_msg = NULL;
350 		} else {
351 			if (the_summary->nfilt == 0) {
352 				prt("No items; use q, !, c, or R to change filter", 0, 0);
353 			} else if (! states[istate].clear) {
354 				prt("", 0, 0);
355 			}
356 		}
357 
358 		/* Use last row for prompt. */
359 		Term_get_size(&wid, &hgt);
360 		prt(states[istate].prompt, hgt - 1, 0);
361 
362 		ch = inkey();
363 		istate = (*states[istate].keyfunc)(ch, istate, the_summary,
364 			player);
365 	}
366 
367 	screen_load();
368 }
369 
370 
display_equip_cmp_help(void)371 static void display_equip_cmp_help(void)
372 {
373 	int irow, wid, hgt;
374 
375 	Term_clear();
376 	irow = 1;
377 	prt("Movement/scrolling ---------------------------------", irow, 0);
378 	++irow;
379 	prt("Down arrow  one line down    Up arrow    one line up", irow, 0);
380 	++irow;
381 	prt("n, PgDn     one page down    p, PgUp     one page up", irow, 0);
382 	++irow;
383 	prt("space       one page down", irow, 0);
384 	++irow;
385 	prt("Filtering/searching/sorting ------------------------", irow, 0);
386 	++irow;
387 	prt("q           quick filter     !           use opposite quick", irow, 0);
388 	++irow;
389 	prt("c           cycle through sources of items", irow, 0);
390 	++irow;
391 	prt("r           reverse", irow, 0);
392 	++irow;
393 	prt("Information ----------------------------------------", irow, 0);
394 	++irow;
395 	prt("v           cycle through attribute views", irow, 0);
396 	++irow;
397 	prt("I, x        select one or two items for details", irow, 0);
398 	++irow;
399 	prt("Other ----------------------------------------------", irow, 0);
400 	++irow;
401 	prt("d           dump to file     R           reset display", irow, 0);
402 	++irow;
403 	prt("ESC         exit", irow, 0);
404 	++irow;
405 
406 	Term_get_size(&wid, &hgt);
407 	prt("Press any key to continue", hgt - 1, 0);
408 	(void) inkey();
409 }
410 
411 
get_expected_easy_filter_count(enum store_inclusion stores)412 static int get_expected_easy_filter_count(enum store_inclusion stores)
413 {
414 	switch (stores) {
415 	case EQUIPPABLE_NO_STORE:
416 	case EQUIPPABLE_ONLY_STORE:
417 		return 1;
418 
419 	case EQUIPPABLE_YES_STORE:
420 		return 0;
421 
422 	case EQUIPPABLE_ONLY_CARRIED:
423 		return 3;
424 
425 	default:
426 		assert(0);
427 		return 0;
428 	}
429 }
430 
431 
handle_key_bail(struct keypress ch,int istate,struct equippable_summary * s,struct player * p)432 static int handle_key_bail(struct keypress ch, int istate,
433 	struct equippable_summary *s, struct player *p)
434 {
435 	return EQUIP_CMP_MENU_DONE;
436 }
437 
438 
handle_key_equip_cmp_general(struct keypress ch,int istate,struct equippable_summary * s,struct player * p)439 static int handle_key_equip_cmp_general(struct keypress ch, int istate,
440 	struct equippable_summary *s, struct player *p)
441 {
442 	static const char *trans_msg_unknown_key =
443 		"Unknown key pressed; ? will list available keys";
444 	static const char *trans_msg_view =
445 		"Showing alternate attributes; press v to cycle";
446 	static const char *trans_msg_onlystore =
447 		"Only showing goods from stores; press c to change";
448 	static const char *trans_msg_withstore =
449 		"Showing possessions and goods from stores; press c to change";
450 	static const char *trans_msg_carried =
451 		"Only showing carried items; press c to change";
452 	static const char *trans_msg_save_ok = "Successfully saved to file";
453 	static const char *trans_msg_save_bad = "Failed to save to file!";
454 	static const char *trans_msg_sel0 = "Select first item to examine";
455 	int result;
456 	int ilast;
457 	int nfilt;
458 
459 	switch (ch.code) {
460 	case 'n':
461 	case ' ':
462 	case KC_PGDOWN:
463 		if (s->npage == s->maxpage) {
464 			ilast = s->ifirst + 2 * s->indinc * s->npage;
465 			if (ilast > s->nfilt) {
466 				assert(s->indinc > 0);
467 				s->ifirst = s->nfilt + 1 - s->npage;
468 				--s->npage;
469 			} else if (ilast < 0) {
470 				assert(s->indinc < 0);
471 				s->ifirst = s->npage - 2;
472 				--s->npage;
473 			} else {
474 				s->ifirst += s->indinc * s->npage;
475 			}
476 			result = EQUIP_CMP_MENU_NEW_PAGE;
477 		} else {
478 			/* Page already includes the end, so don't move. */
479 			result = EQUIP_CMP_MENU_SAME_PAGE;
480 		}
481 		break;
482 
483 	case 'p':
484 	case KC_PGUP:
485 		if (s->indinc > 0) {
486 			if (s->ifirst > 0) {
487 				s->ifirst = (s->ifirst < s->maxpage) ?
488 					0 : s->ifirst - s->maxpage;
489 				s->npage = (s->ifirst + s->maxpage <= s->nfilt) ?
490 					s->maxpage : s->nfilt - s->ifirst;
491 				result = EQUIP_CMP_MENU_NEW_PAGE;
492 			} else {
493 				result = EQUIP_CMP_MENU_SAME_PAGE;
494 			}
495 		} else {
496 			if (s->ifirst < s->nfilt - 1) {
497 				s->ifirst = (s->ifirst >= s->nfilt - s->maxpage) ?
498 					s->nfilt - 1 : s->ifirst + s->maxpage;
499 				s->npage = (s->ifirst - s->maxpage >= -1) ?
500 					s->maxpage : s->ifirst + 1;
501 				result = EQUIP_CMP_MENU_NEW_PAGE;
502 			} else {
503 				result = EQUIP_CMP_MENU_SAME_PAGE;
504 			}
505 		}
506 		break;
507 
508 	case ARROW_DOWN:
509 		if (s->npage == s->maxpage) {
510 			s->ifirst += s->indinc;
511 			ilast = s->ifirst + s->npage * s->indinc;
512 			if (ilast < -1 || ilast > s->nfilt) {
513 				--s->npage;
514 			}
515 			result = EQUIP_CMP_MENU_NEW_PAGE;
516 		} else {
517 			/* Page already includes the end, so don't move. */
518 			result = EQUIP_CMP_MENU_SAME_PAGE;
519 		}
520 		break;
521 
522 	case ARROW_UP:
523 		if (s->indinc > 0) {
524 			if (s->ifirst > 0) {
525 				--s->ifirst;
526 				if (s->npage < s->maxpage &&
527 					s->ifirst + s->npage < s->nfilt) {
528 					++s->npage;
529 				}
530 				result = EQUIP_CMP_MENU_NEW_PAGE;
531 			} else {
532 				result = EQUIP_CMP_MENU_SAME_PAGE;
533 			}
534 		} else {
535 			if (s->ifirst < s->nfilt - 1) {
536 				++s->ifirst;
537 				if (s->npage < s->maxpage &&
538 					s->ifirst - s->npage > -1) {
539 					++s->npage;
540 				}
541 				result = EQUIP_CMP_MENU_NEW_PAGE;
542 			} else {
543 				result = EQUIP_CMP_MENU_SAME_PAGE;
544 			}
545 		}
546 		break;
547 
548 	case 'c':
549 		/*
550 		 * Cycle through no goods from stores (default), only goods
551 		 * from stores, both possessions and gopds from stores,
552 		 * and only what's carried (either equipped or in pack).
553 		 */
554 		switch (s->stores) {
555 		case EQUIPPABLE_NO_STORE:
556 			assert(s->easy_filt.simple == EQUIP_EXPR_AND &&
557 				(s->easy_filt.nv == 1 || s->easy_filt.nv == 2) &&
558 				s->easy_filt.v[0].s.func == sel_exclude_src &&
559 				s->easy_filt.v[0].s.ex.src == EQUIP_SOURCE_STORE);
560 			s->easy_filt.v[0].s.func = sel_only_src;
561 			s->stores = EQUIPPABLE_ONLY_STORE;
562 			s->dlg_trans_msg = trans_msg_onlystore;
563 			break;
564 
565 		case EQUIPPABLE_ONLY_STORE:
566 			assert(s->easy_filt.simple == EQUIP_EXPR_AND &&
567 				(s->easy_filt.nv == 1 || s->easy_filt.nv == 2) &&
568 				s->easy_filt.v[0].s.func == sel_only_src &&
569 				s->easy_filt.v[0].s.ex.src == EQUIP_SOURCE_STORE);
570 			s->easy_filt.v[0] = s->easy_filt.v[1];
571 			if (s->easy_filt.nv == 2) {
572 				s->easy_filt.v[1] = s->easy_filt.v[2];
573 			} else {
574 				s->easy_filt.simple = EQUIP_EXPR_TERMINATOR;
575 			}
576 			--s->easy_filt.nv;
577 			s->stores = EQUIPPABLE_YES_STORE;
578 			s->dlg_trans_msg = trans_msg_withstore;
579 			break;
580 
581 		case EQUIPPABLE_YES_STORE:
582 			assert(s->easy_filt.nv == 0 || s->easy_filt.nv == 1);
583 			s->easy_filt.v[3] = s->easy_filt.v[0];
584 			s->easy_filt.simple = EQUIP_EXPR_AND;
585 			s->easy_filt.v[0].s.func = sel_exclude_src;
586 			s->easy_filt.v[0].s.ex.src = EQUIP_SOURCE_STORE;
587 			s->easy_filt.v[0].c = EQUIP_EXPR_SELECTOR;
588 			s->easy_filt.v[1].s.func = sel_exclude_src;
589 			s->easy_filt.v[1].s.ex.src = EQUIP_SOURCE_HOME;
590 			s->easy_filt.v[1].c = EQUIP_EXPR_SELECTOR;
591 			s->easy_filt.v[2].s.func = sel_exclude_src;
592 			s->easy_filt.v[2].s.ex.src = EQUIP_SOURCE_FLOOR;
593 			s->easy_filt.v[2].c = EQUIP_EXPR_SELECTOR;
594 			s->easy_filt.nv += 3;
595 			s->stores = EQUIPPABLE_ONLY_CARRIED;
596 			s->dlg_trans_msg = trans_msg_carried;
597 			break;
598 
599 		case EQUIPPABLE_ONLY_CARRIED:
600 			assert(s->easy_filt.simple = EQUIP_EXPR_AND &&
601 				(s->easy_filt.nv == 3 || s->easy_filt.nv == 4) &&
602 				s->easy_filt.v[0].s.func == sel_exclude_src &&
603 				s->easy_filt.v[0].s.ex.src == EQUIP_SOURCE_STORE &&
604 				s->easy_filt.v[1].s.func == sel_exclude_src &&
605 				s->easy_filt.v[1].s.ex.src == EQUIP_SOURCE_HOME &&
606 				s->easy_filt.v[2].s.func == sel_exclude_src &&
607 				s->easy_filt.v[2].s.ex.src == EQUIP_SOURCE_FLOOR);
608 			s->easy_filt.v[1] = s->easy_filt.v[s->easy_filt.nv - 1];
609 			s->easy_filt.v[0].s.func = sel_exclude_src;
610 			s->easy_filt.v[0].s.ex.src = EQUIP_SOURCE_STORE;
611 			s->easy_filt.v[0].c = EQUIP_EXPR_SELECTOR;
612 			s->easy_filt.v[2].c = EQUIP_EXPR_TERMINATOR;
613 			s->easy_filt.v[3].c = EQUIP_EXPR_TERMINATOR;
614 			s->easy_filt.nv -= 2;
615 			s->stores = EQUIPPABLE_NO_STORE;
616 			break;
617 		}
618 		filter_items(s);
619 		sort_items(s);
620 		result = EQUIP_CMP_MENU_NEW_PAGE;
621 		break;
622 
623 	case 'd':
624 		/* Dump to a file. */
625 		{
626 			char buf[1024];
627 			char fname[80];
628 
629 			player_safe_name(fname, sizeof(fname),
630 				p->full_name, false);
631 			my_strcat(fname, "_equip.txt", sizeof(fname));
632 			if (get_file(fname, buf, sizeof(buf))) {
633 				s_for_file = s;
634 				if (dump_to_file(buf)) {
635 					s->dlg_trans_msg = trans_msg_save_ok;
636 				} else {
637 					s->dlg_trans_msg = trans_msg_save_bad;
638 				}
639 			}
640 		}
641 		result = EQUIP_CMP_MENU_NEW_PAGE;
642 		break;
643 
644 	case 'q':
645 		/* Choose a quick filter - one based on a single attribute. */
646 		result = prompt_for_easy_filter(s, false);
647 		break;
648 
649 	case 'r':
650 		/* Reverse the order of display; keep in the same page. */
651 		if (s->npage > 0) {
652 			s->ifirst += (s->npage - 1) * s->indinc;
653 		}
654 		s->indinc *= -1;
655 		result = EQUIP_CMP_MENU_NEW_PAGE;
656 		break;
657 
658 	case 'v':
659 		/* Cycle through which attributes are shown. */
660 		++s->iview;
661 		if (s->iview >= s->nview) {
662 			s->iview = 0;
663 		} else {
664 			s->dlg_trans_msg = trans_msg_view;
665 		}
666 		result = EQUIP_CMP_MENU_NEW_PAGE;
667 		break;
668 
669 	case 'x':
670 	case 'I':
671 		/* Select an item or two to examine. */
672 		s->work_sel = s->ifirst;
673 		s->dlg_trans_msg = trans_msg_sel0;
674 		result = EQUIP_CMP_MENU_SEL0;
675 		break;
676 
677 	case 'R':
678 		/* Reset view to defaults. */
679 		s->indinc = 1;
680 		s->iview = 0;
681 		s->config_filt_is_on = false;
682 		s->stores = EQUIPPABLE_NO_STORE;
683 		s->easy_filt.simple = EQUIP_EXPR_AND;
684 		s->easy_filt.nv = 1;
685 		s->easy_filt.v[0].s.func = sel_exclude_src;
686 		s->easy_filt.v[0].s.ex.src = EQUIP_SOURCE_STORE;
687 		s->easy_filt.v[0].c = EQUIP_EXPR_SELECTOR;
688 		s->easy_filt.v[1].c = EQUIP_EXPR_TERMINATOR;
689 		filter_items(s);
690 		s->config_sort_is_on = false;
691 		sort_items(s);
692 		result = EQUIP_CMP_MENU_NEW_PAGE;
693 		break;
694 
695 	case '!':
696 		/*
697 		 * If using a quick filter, use not for the criteria.
698 		 * Otherwise, set up a quick filter where not is applied.
699 		 */
700 		nfilt = get_expected_easy_filter_count(s->stores);
701 		if (! s->config_filt_is_on && s->easy_filt.nv > nfilt) {
702 			assert(s->easy_filt.v[nfilt].c == EQUIP_EXPR_SELECTOR);
703 			if (s->easy_filt.v[nfilt].s.func ==
704 					sel_at_least_resists) {
705 				s->easy_filt.v[nfilt].s.func =
706 					sel_does_not_resist;
707 			} else if (s->easy_filt.v[nfilt].s.func ==
708 					sel_has_flag) {
709 				s->easy_filt.v[nfilt].s.func =
710 					sel_does_not_have_flag;
711 			} else if (s->easy_filt.v[nfilt].s.func ==
712 					sel_has_pos_mod) {
713 				s->easy_filt.v[nfilt].s.func =
714 					sel_has_nonpos_mod;
715 			} else if (s->easy_filt.v[nfilt].s.func ==
716 					sel_does_not_resist) {
717 				s->easy_filt.v[nfilt].s.func =
718 					sel_at_least_resists;
719 			} else if (s->easy_filt.v[nfilt].s.func ==
720 					sel_does_not_have_flag) {
721 				s->easy_filt.v[nfilt].s.func = sel_has_flag;
722 			} else if (s->easy_filt.v[nfilt].s.func ==
723 					sel_has_nonpos_mod) {
724 				s->easy_filt.v[nfilt].s.func = sel_has_pos_mod;
725 			} else {
726 				assert(0);
727 			}
728 			filter_items(s);
729 			sort_items(s);
730 			result = EQUIP_CMP_MENU_NEW_PAGE;
731 		} else {
732 			result = prompt_for_easy_filter(s, true);
733 		}
734 		break;
735 
736 	case '?':
737 		display_equip_cmp_help();
738 		result = EQUIP_CMP_MENU_NEW_PAGE;
739 		break;
740 
741 	case ESCAPE:
742 		result = EQUIP_CMP_MENU_DONE;
743 		break;
744 
745 	default:
746 		s->dlg_trans_msg = trans_msg_unknown_key;
747 		result = EQUIP_CMP_MENU_SAME_PAGE;
748 		break;
749 	}
750 
751 	return result;
752 }
753 
754 
display_equip_cmp_sel_help(void)755 static void display_equip_cmp_sel_help(void)
756 {
757 	int irow, wid, hgt;
758 
759 	Term_clear();
760 	irow = 1;
761 	prt("Down arrow  move selection one line down", irow, 0);
762 	++irow;
763 	prt("Up arrow    move selection one line up", irow, 0);
764 	++irow;
765 	prt("n, PgDn     move selection one page up", irow, 0);
766 	++irow;
767 	prt("p, PgUp     move selection one page up", irow, 0);
768 	++irow;
769 	prt("x           stop selection; if first item, escapes", irow, 0);
770 	++irow;
771 	prt("return      select current item", irow, 0);
772 	++irow;
773 	prt("ESC         leave selection process", irow, 0);
774 	++irow;
775 
776 	Term_get_size(&wid, &hgt);
777 	prt("Press any key to continue", hgt - 1, 0);
778 	(void) inkey();
779 }
780 
781 
handle_key_equip_cmp_select(struct keypress ch,int istate,struct equippable_summary * s,struct player * p)782 static int handle_key_equip_cmp_select(struct keypress ch, int istate,
783 	struct equippable_summary *s, struct player *p)
784 {
785 	static const char *trans_msg_unknown_key =
786 		"Unknown key pressed; ? will list available keys";
787 	static const char *trans_msg_sel1 = "Select second item; x to skip";
788 	int result;
789 	int ilast;
790 
791 	switch (ch.code) {
792 	case 'n':
793 	case KC_PGDOWN:
794 		/* Move selection by one page. */
795 		s->work_sel += s->indinc * s->npage;
796 		if (s->work_sel < 0) {
797 			s->work_sel = 0;
798 		} else if (s->work_sel >= s->nfilt) {
799 			s->work_sel = s->nfilt - 1;
800 		}
801 		/* Shift view. */
802 		s->ifirst += s->indinc * s->npage;
803 		if (s->indinc > 0) {
804 			if (s->ifirst >= s->nfilt + 1 - s->maxpage) {
805 				s->ifirst = s->nfilt + 1 - s->maxpage;
806 				if (s->ifirst < 0) {
807 					s->ifirst = 0;
808 					s->npage = s->nfilt;
809 				} else {
810 					s->npage = s->maxpage - 1;
811 				}
812 			}
813 		} else {
814 			if (s->ifirst < s->maxpage - 2) {
815 				s->ifirst = s->maxpage - 2;
816 				if (s->ifirst >= s->nfilt) {
817 					s->ifirst = s->nfilt - 1;
818 					s->npage = s->nfilt;
819 				} else {
820 					s->npage = s->maxpage - 1;
821 				}
822 				--s->npage;
823 			}
824 		}
825 		result = istate;
826 		break;
827 
828 	case 'p':
829 	case KC_PGUP:
830 		/* Move selection by one page. */
831 		s->work_sel -= s->indinc * s->npage;
832 		if (s->work_sel < 0) {
833 			s->work_sel = 0;
834 		} else if (s->work_sel >= s->nfilt) {
835 			s->work_sel = s->nfilt - 1;
836 		}
837 		/* Shift view. */
838 		s->ifirst -= s->indinc * s->npage;
839 		if (s->ifirst < 0) {
840 			s->ifirst = 0;
841 		} else if (s->ifirst >= s->nfilt) {
842 			s->ifirst = s->nfilt - 1;
843 		}
844 		ilast = s->ifirst + s->indinc * s->maxpage;
845 		if (s->indinc > 0) {
846 			s->npage = (ilast <= s->nfilt) ?
847 				s->maxpage : s->nfilt - s->ifirst;
848 		} else {
849 			s->npage = (ilast >= -1) ?
850 				s->maxpage : s->ifirst + 1;
851 		}
852 		result = istate;
853 		break;
854 
855 	case ARROW_DOWN:
856 		/* Move selection by one line. */
857 		s->work_sel += s->indinc;
858 		if ((s->work_sel - s->ifirst) * s->indinc >= s->npage) {
859 			if (s->work_sel < 0) {
860 				s->work_sel = 0;
861 			} else if (s->work_sel >= s->nfilt) {
862 				s->work_sel = s->nfilt - 1;
863 			} else {
864 				/* Shift view. */
865 				s->ifirst = s->work_sel - s->indinc;
866 				if (s->indinc > 0) {
867 					if (s->ifirst + s->npage > s->nfilt) {
868 						s->ifirst = s->nfilt + 1 -
869 							s->npage;
870 						--s->npage;
871 					}
872 				} else {
873 					if (s->ifirst - s->npage < -1) {
874 						s->ifirst = s->npage - 2;
875 						--s->npage;
876 					}
877 				}
878 			}
879 		}
880 		result = istate;
881 		break;
882 
883 	case ARROW_UP:
884 		/* Move selection by one line. */
885 		s->work_sel -= s->indinc;
886 		if ((s->work_sel - s->ifirst) * s->indinc < 0) {
887 			if (s->work_sel < 0) {
888 				s->work_sel = 0;
889 			} else if (s->work_sel >= s->nfilt) {
890 				s->work_sel = s->nfilt - 1;
891 			} else {
892 				/* Shift view. */
893 				s->ifirst = s->work_sel + s->indinc *
894 					(2 - s->npage);
895 				if (s->indinc > 0) {
896 					if (s->ifirst < 0) {
897 						s->ifirst = 0;
898 					}
899 					ilast = s->ifirst + s->maxpage;
900 					if (ilast <= s->nfilt) {
901 						s->npage = s->maxpage;
902 					} else {
903 						s->npage =
904 							s->nfilt - s->ifirst;
905 					}
906 				} else {
907 					if (s->ifirst >= s->nfilt) {
908 						s->ifirst = s->nfilt - 1;
909 					}
910 					ilast = s->ifirst - s->maxpage;
911 					if (ilast >= -1) {
912 						s->npage = s->maxpage;
913 					} else {
914 						s->npage = s->ifirst + 1;
915 					}
916 				}
917 			}
918 		}
919 		result = istate;
920 		break;
921 
922 	case 'x':
923 		/* Skip the selection. For the first, acts like ESC. */
924 		if (istate == EQUIP_CMP_MENU_SEL1) {
925 			display_object_comparison(s);
926 		}
927 		s->isel0 = -1;
928 		s->isel1 = -1;
929 		s->work_sel = -1;
930 		result = EQUIP_CMP_MENU_NEW_PAGE;
931 		break;
932 
933 	case KC_ENTER:
934 		assert(s->work_sel >= 0 && s->work_sel < s->nfilt);
935 		if (istate == EQUIP_CMP_MENU_SEL0) {
936 			s->isel0 = s->sorted_indices[s->work_sel];
937 			s->dlg_trans_msg = trans_msg_sel1;
938 			result = EQUIP_CMP_MENU_SEL1;
939 		} else {
940 			s->isel1 = s->sorted_indices[s->work_sel];
941 			display_object_comparison(s);
942 			s->isel0 = -1;
943 			s->isel1 = -1;
944 			s->work_sel = -1;
945 			result = EQUIP_CMP_MENU_NEW_PAGE;
946 		}
947 		break;
948 
949 	case '?':
950 		display_equip_cmp_sel_help();
951 		result = istate;
952 		break;
953 
954 	case ESCAPE:
955 		s->isel0 = -1;
956 		s->isel1 = -1;
957 		s->work_sel = -1;
958 		result = EQUIP_CMP_MENU_NEW_PAGE;
959 		break;
960 
961 	default:
962 		s->dlg_trans_msg = trans_msg_unknown_key;
963 		result = istate;
964 		break;
965 	}
966 
967 	return result;
968 }
969 
970 
prompt_for_easy_filter(struct equippable_summary * s,bool apply_not)971 static int prompt_for_easy_filter(struct equippable_summary *s, bool apply_not)
972 {
973 	static const char *no_matching_attribute_msg =
974 		"Did not find attribute with that name; filter unchanged";
975 	char c[4] = "";
976 	int itry;
977 	bool threec;
978 
979 	if (! get_string("Enter 2 or 3 (for stat) character code and return or return to clear ", c,
980 		N_ELEMENTS(c))) {
981 		return EQUIP_CMP_MENU_NEW_PAGE;
982 	}
983 
984 	/* Clear the current filter. */
985 	if (c[0] == '\0') {
986 		s->config_filt_is_on = false;
987 		if (s->easy_filt.nv >
988 				get_expected_easy_filter_count(s->stores)) {
989 			s->easy_filt.v[s->easy_filt.nv - 1] =
990 				s->easy_filt.v[s->easy_filt.nv];
991 			--s->easy_filt.nv;
992 			filter_items(s);
993 			sort_items(s);
994 		}
995 		return EQUIP_CMP_MENU_NEW_PAGE;
996 	}
997 
998 	/*
999 	 * Try different combinations of capitalization to match the
1000 	 * entered string to one of the column labels.
1001 	 */
1002 	itry = 0;
1003 	threec = false;
1004 	while (1) {
1005 		char ctry[4];
1006 		wchar_t wc[4];
1007 
1008 		if (itry >= 4 || (threec && itry >= 3)) {
1009 			s->dlg_trans_msg = no_matching_attribute_msg;
1010 			break;
1011 		}
1012 		switch (itry) {
1013 		case 0:
1014 			ctry[0] = toupper(c[0]);
1015 			if (c[1] != '\0') {
1016 				ctry[1] = tolower(c[1]);
1017 				if (c[2] != '\0') {
1018 					ctry[2] = tolower(c[2]);
1019 					ctry[3] = '\0';
1020 					threec = true;
1021 				} else {
1022 					ctry[2] = '\0';
1023 				}
1024 			} else {
1025 				ctry[1] = '\0';
1026 			}
1027 			break;
1028 
1029 		case 1:
1030 			ctry[0] = toupper(c[0]);
1031 			if (c[1] != '\0') {
1032 				ctry[1] = toupper(c[1]);
1033 				if (c[2] != '\0') {
1034 					ctry[2] = toupper(c[2]);
1035 					ctry[3] = '\0';
1036 					threec = true;
1037 				} else {
1038 					ctry[2] = '\0';
1039 				}
1040 			} else {
1041 				ctry[1] = '\0';
1042 			}
1043 			break;
1044 
1045 		case 2:
1046 			ctry[0] = tolower(c[0]);
1047 			if (c[1] != '\0') {
1048 				ctry[1] = tolower(c[1]);
1049 				if (c[2] != '\0') {
1050 					ctry[2] = tolower(c[2]);
1051 					ctry[3] = '\0';
1052 					threec = true;
1053 				} else {
1054 					ctry[2] = '\0';
1055 				}
1056 			} else {
1057 				ctry[1] = '\0';
1058 			}
1059 			break;
1060 
1061 		case 3:
1062 			ctry[0] = tolower(c[0]);
1063 			if (c[1] != '\0') {
1064 				ctry[1] = toupper(c[1]);
1065 				ctry[2] = '\0';
1066 			} else {
1067 				ctry[1] = '\0';
1068 			}
1069 			break;
1070 		}
1071 
1072 		if (text_mbstowcs(wc, ctry, N_ELEMENTS(wc)) != (size_t)-1) {
1073 			int j = 0, k = 0;
1074 			bool search = true;
1075 
1076 			while (search) {
1077 				if (j < (int)N_ELEMENTS(s->propcats)) {
1078 					if (k < s->propcats[j].n) {
1079 						if (threec) {
1080 							wchar_t wce[4];
1081 
1082 							get_ui_entry_label(s->propcats[j].entries[k], 4, false, wce);
1083 							if (wc[0] == wce[0] &&
1084 							    wc[1] == wce[1] &&
1085 							    wc[2] == wce[2]) {
1086 								search = false;
1087 							} else {
1088 								++k;
1089 							}
1090 						} else if (wc[0] == s->propcats[j].labels[k][0] &&
1091 							wc[1] == s->propcats[j].labels[k][1]) {
1092 							search = false;
1093 						} else {
1094 							++k;
1095 						}
1096 					} else {
1097 						k = 0;
1098 						++j;
1099 					}
1100 				} else {
1101 					search = false;
1102 				}
1103 			}
1104 
1105 			/* Configure the new filter and apply it. */
1106 			if (j < (int)N_ELEMENTS(s->propcats)) {
1107 				int ind;
1108 
1109 				s->config_filt_is_on = false;
1110 				if (s->easy_filt.nv ==
1111 						get_expected_easy_filter_count(s->stores)) {
1112 					s->easy_filt.v[s->easy_filt.nv].c =
1113 						EQUIP_EXPR_SELECTOR;
1114 					++s->easy_filt.nv;
1115 				}
1116 				ind = s->easy_filt.nv - 1;
1117 				s->easy_filt.v[ind].s.ex.propind =
1118 					s->propcats[j].off + k;
1119 				switch (j) {
1120 				case 0:
1121 					/* Resistance */
1122 					s->easy_filt.v[ind].s.func =
1123 						(apply_not) ?
1124 						sel_does_not_resist :
1125 						sel_at_least_resists;
1126 					break;
1127 
1128 				case 1:
1129 					/*
1130 					 * A boolean flag where on is usually
1131 					 * preferred.
1132 					 */
1133 					s->easy_filt.v[ind].s.func =
1134 						(apply_not) ?
1135 						sel_does_not_have_flag :
1136 						sel_has_flag;
1137 					break;
1138 
1139 				case 2:
1140 					/*
1141 					 * A boolean flag where off is usually
1142 					 * preferred.
1143 					 */
1144 					s->easy_filt.v[ind].s.func =
1145 						(apply_not) ?
1146 						sel_has_flag :
1147 						sel_does_not_have_flag;
1148 					break;
1149 
1150 				case 3:
1151 				case 4:
1152 					/* Integer modifier. */
1153 					s->easy_filt.v[ind].s.func =
1154 						(apply_not) ?
1155 						sel_has_nonpos_mod :
1156 						sel_has_pos_mod;
1157 					break;
1158 
1159 				default:
1160 					assert(0);
1161 				}
1162 
1163 				filter_items(s);
1164 				sort_items(s);
1165 				break;
1166 			}
1167 		}
1168 
1169 		++itry;
1170 	}
1171 	return EQUIP_CMP_MENU_NEW_PAGE;
1172 }
1173 
1174 
display_object_comparison(const struct equippable_summary * s)1175 static void display_object_comparison(const struct equippable_summary *s)
1176 {
1177 	char hbuf[120];
1178 	textblock *tb0;
1179 	region local_area = { 0, 0, 0, 0 };
1180 
1181 	assert(s->isel0 >= 0 && s->isel0 < s->nitems);
1182 	tb0 = object_info(s->items[s->isel0].obj, OINFO_NONE);
1183 	object_desc(hbuf, sizeof(hbuf), s->items[s->isel0].obj,
1184 		ODESC_PREFIX | ODESC_FULL | ODESC_CAPITAL);
1185 	if (s->isel1 != -1 && s->isel1 != s->isel0) {
1186 		textblock *tb1 = textblock_new();
1187 		textblock *tb2;
1188 
1189 		assert(s->isel1 >= 0 && s->isel1 < s->nitems);
1190 		textblock_append(tb1, "%s\n", hbuf);
1191 		textblock_append_textblock(tb1, tb0);
1192 		object_desc(hbuf, sizeof(hbuf), s->items[s->isel1].obj,
1193 			ODESC_PREFIX | ODESC_FULL | ODESC_CAPITAL);
1194 		textblock_append(tb1, "\n%s\n", hbuf);
1195 		tb2 = object_info(s->items[s->isel1].obj, OINFO_NONE);
1196 		textblock_append_textblock(tb1, tb2);
1197 		textblock_free(tb2);
1198 
1199 		textui_textblock_show(tb1, local_area, "Object comparison");
1200 		textblock_free(tb1);
1201 	} else {
1202 		textui_textblock_show(tb0, local_area, hbuf);
1203 	}
1204 	textblock_free(tb0);
1205 }
1206 
1207 
dump_to_file(const char * path)1208 static bool dump_to_file(const char *path)
1209 {
1210 	return (text_lines_to_file(path, append_to_file)) ? false : true;
1211 }
1212 
1213 
1214 /**
1215  * Write all the pages with the current selection of items and properties to
1216  * display.
1217  */
append_to_file(ang_file * fff)1218 static void append_to_file(ang_file *fff)
1219 {
1220 	int cached_ifirst, cached_npage;
1221 	int wid, hgt, y;
1222 	char *buf;
1223 
1224 	if (!s_for_file) {
1225 		if (initialize_summary(player, &the_summary)) {
1226 			return;
1227 		}
1228 		s_for_file = the_summary;
1229 	}
1230 
1231 	cached_ifirst = s_for_file->ifirst;
1232 	cached_npage = s_for_file->npage;
1233 	s_for_file->ifirst =
1234 		(s_for_file->indinc > 0 || s_for_file->nfilt == 0) ?
1235 		0 : s_for_file->nfilt - 1;
1236 	s_for_file->npage =
1237 		(s_for_file->maxpage <= s_for_file->nfilt) ?
1238 		s_for_file->maxpage : s_for_file->nfilt;
1239 
1240 	Term_get_size(&wid, &hgt);
1241 	buf = mem_alloc(text_wcsz() * wid + 1);
1242 
1243 	(void) display_page(s_for_file, player, false);
1244 
1245 	/* Dump part of the screen. */
1246 	for (y = s_for_file->irow_combined_equip - s_for_file->nproplab;
1247 		y < s_for_file->irow_combined_equip + 1 + s_for_file->npage;
1248 		++y) {
1249 		char *p = buf;
1250 		int x, a, n;
1251 		wchar_t c;
1252 
1253 		/* Dump a row. */
1254 		for (x = 0; x < wid; ++x) {
1255 			(void) Term_what(x, y, &a, &c);
1256 			n = text_wctomb(p, c);
1257 			if (n > 0) {
1258 				p += n;
1259 			} else {
1260 				*p++ = ' ';
1261 			}
1262 		}
1263 		/* Back up over spaces */
1264 		while ((p > buf) && (p[-1] == ' ')) {
1265 			--p;
1266 		}
1267 		/* Terminate */
1268 		*p = '\0';
1269 
1270 		file_putf(fff, "%s\n", buf);
1271 	}
1272 
1273 	while (1) {
1274 		s_for_file->ifirst += s_for_file->npage * s_for_file->indinc;
1275 		if (s_for_file->indinc > 0) {
1276 			if (s_for_file->ifirst >= s_for_file->nfilt) {
1277 				break;
1278 			}
1279 			if (s_for_file->ifirst + s_for_file->npage >
1280 				s_for_file->nfilt) {
1281 				s_for_file->npage = s_for_file->nfilt -
1282 					s_for_file->ifirst;
1283 			}
1284 		} else {
1285 			if (s_for_file->ifirst < 0) {
1286 				break;
1287 			}
1288 			if (s_for_file->ifirst - s_for_file->npage < -1) {
1289 				s_for_file->npage = s_for_file->ifirst + 1;
1290 			}
1291 		}
1292 
1293 		(void) display_page(s_for_file, player, false);
1294 
1295 		/*
1296 		 * Dump part of the screen.  Exclude the header that appeared
1297 		 * on the first page.
1298 		 */
1299 		for (y = s_for_file->irow_combined_equip + 1;
1300 			y < s_for_file->irow_combined_equip + 1 +
1301 			s_for_file->npage; ++y) {
1302 			char *p = buf;
1303 			int x, a, n;
1304 			wchar_t c;
1305 
1306 			/* Dump a row. */
1307 			for (x = 0; x < wid; ++x) {
1308 				(void) Term_what(x, y, &a, &c);
1309 				n = text_wctomb(p, c);
1310 				if (n > 0) {
1311 					p += n;
1312 				} else {
1313 					*p++ = ' ';
1314 				}
1315 			}
1316 			/* Back up over spaces */
1317 			while ((p > buf) && (p[-1] == ' ')) {
1318 				--p;
1319 			}
1320 			/* Terminate */
1321 			*p = '\0';
1322 
1323 			file_putf(fff, "%s\n", buf);
1324 		}
1325 	}
1326 
1327 	mem_free(buf);
1328 
1329 	s_for_file->ifirst = cached_ifirst;
1330 	s_for_file->npage = cached_npage;
1331 	s_for_file = NULL;
1332 }
1333 
1334 
set_short_name(const struct object * obj,size_t length)1335 static char *set_short_name(const struct object *obj, size_t length)
1336 {
1337 	char buf[80];
1338 	const char *nmsrc;
1339 	size_t nmlen;
1340 	bool tail;
1341 	char *result;
1342 
1343 	if (obj->known && obj->known->artifact) {
1344 		nmsrc = obj->known->artifact->name;
1345 		tail = true;
1346 	} else if (obj->known && obj->known->ego) {
1347 		nmsrc = obj->known->ego->name;
1348 		tail = true;
1349 	} else {
1350 		object_desc(buf, N_ELEMENTS(buf), obj, ODESC_COMBAT |
1351 			ODESC_SINGULAR | ODESC_TERSE);
1352 		nmsrc = buf;
1353 		tail = false;
1354 	}
1355 	nmlen = strlen(nmsrc);
1356 	if (nmlen <= length) {
1357 		result = string_make(nmsrc);
1358 	} else if (tail) {
1359 		result = string_make(nmsrc + nmlen - length);
1360 	} else {
1361 		result = string_make(format("%.*s", (int) length, nmsrc));
1362 	}
1363 	return result;
1364 }
1365 
1366 
1367 #if 0
1368 static bool sel_better_than(const struct equippable *eq,
1369 	const union equippable_selfunc_extra *ex)
1370 {
1371 	return eq->qual < ex->qual;
1372 }
1373 #endif
1374 
1375 
sel_at_least_resists(const struct equippable * eq,const union equippable_selfunc_extra * ex)1376 static bool sel_at_least_resists(const struct equippable *eq,
1377 	const union equippable_selfunc_extra *ex)
1378 {
1379 	return eq->vals[ex->propind] >= 1;
1380 }
1381 
1382 
sel_does_not_resist(const struct equippable * eq,const union equippable_selfunc_extra * ex)1383 static bool sel_does_not_resist(const struct equippable *eq,
1384 	const union equippable_selfunc_extra *ex)
1385 {
1386 	return eq->vals[ex->propind] < 1;
1387 }
1388 
1389 
sel_has_flag(const struct equippable * eq,const union equippable_selfunc_extra * ex)1390 static bool sel_has_flag(const struct equippable *eq,
1391 	const union equippable_selfunc_extra *ex)
1392 {
1393 	return eq->vals[ex->propind] != 0;
1394 }
1395 
1396 
sel_does_not_have_flag(const struct equippable * eq,const union equippable_selfunc_extra * ex)1397 static bool sel_does_not_have_flag(const struct equippable *eq,
1398 	const union equippable_selfunc_extra *ex)
1399 {
1400 	return eq->vals[ex->propind] == 0;
1401 }
1402 
1403 
sel_has_pos_mod(const struct equippable * eq,const union equippable_selfunc_extra * ex)1404 static bool sel_has_pos_mod(const struct equippable *eq,
1405 	const union equippable_selfunc_extra *ex)
1406 {
1407 	return eq->vals[ex->propind] > 0;
1408 }
1409 
1410 
sel_has_nonpos_mod(const struct equippable * eq,const union equippable_selfunc_extra * ex)1411 static bool sel_has_nonpos_mod(const struct equippable *eq,
1412 	const union equippable_selfunc_extra *ex)
1413 {
1414 	return eq->vals[ex->propind] <= 0;
1415 }
1416 
1417 
1418 #if 0
1419 static bool sel_exclude_slot(const struct equippable *eq,
1420 	const union equippable_selfunc_extra *ex)
1421 {
1422 	return eq->slot != ex->slot;
1423 }
1424 
1425 
1426 static bool sel_only_slot(const struct equippable *eq,
1427 	const union equippable_selfunc_extra *ex)
1428 {
1429 	return eq->slot == ex->slot;
1430 }
1431 #endif
1432 
1433 
sel_exclude_src(const struct equippable * eq,const union equippable_selfunc_extra * ex)1434 static bool sel_exclude_src(const struct equippable *eq,
1435 	const union equippable_selfunc_extra *ex)
1436 {
1437 	return eq->src != ex->src;
1438 }
1439 
1440 
sel_only_src(const struct equippable * eq,const union equippable_selfunc_extra * ex)1441 static bool sel_only_src(const struct equippable *eq,
1442 	const union equippable_selfunc_extra *ex)
1443 {
1444 	return eq->src == ex->src;
1445 }
1446 
1447 
apply_simple_filter(const struct equippable_filter * f,struct equippable_summary * s)1448 static void apply_simple_filter(const struct equippable_filter *f,
1449 	struct equippable_summary *s)
1450 {
1451 	int i;
1452 
1453 	s->nfilt = 0;
1454 	switch (f->simple) {
1455 	case EQUIP_EXPR_AND:
1456 		for (i = 0; i < s->nitems; ++i) {
1457 			int j = 0;
1458 
1459 			while (1) {
1460 				if (j >= f->nv) {
1461 					assert(s->nfilt < s->nitems &&
1462 						s->nfilt < s->nalloc);
1463 					s->sorted_indices[s->nfilt] = i;
1464 					++s->nfilt;
1465 					break;
1466 				}
1467 				assert(f->v[j].c == EQUIP_EXPR_SELECTOR);
1468 				if (! (*f->v[j].s.func)(s->items + i,
1469 					&f->v[j].s.ex)) {
1470 					break;
1471 				}
1472 				++j;
1473 			}
1474 		}
1475 		break;
1476 
1477 	case EQUIP_EXPR_OR:
1478 		for (i = 0; i < s->nitems; ++i) {
1479 			int j = 0;
1480 
1481 			while (1) {
1482 				if (j >= f->nv) {
1483 					break;
1484 				}
1485 				assert(f->v[j].c == EQUIP_EXPR_SELECTOR);
1486 				if ((*f->v[j].s.func)(s->items + i,
1487 					&f->v[j].s.ex)) {
1488 					assert(s->nfilt < s->nitems &&
1489 						s->nfilt < s->nalloc);
1490 					s->sorted_indices[s->nfilt] = i;
1491 					++s->nfilt;
1492 					break;
1493 				}
1494 				++j;
1495 			}
1496 		}
1497 		break;
1498 
1499 	default:
1500 		assert(0);
1501 	}
1502 
1503 	s->sorted_indices[s->nfilt] = -1;
1504 }
1505 
1506 
apply_complex_filter(const struct equippable_filter * f,struct equippable_summary * s)1507 static void apply_complex_filter(const struct equippable_filter *f,
1508 	struct equippable_summary *s)
1509 {
1510 	bool *stack;
1511 	int nst, i;
1512 
1513 	assert(f->nv > 0);
1514 	stack = mem_alloc(f->nv * sizeof(*stack));
1515 	nst = 0;
1516 	s->nfilt = 0;
1517 	for (i = 0; i < s->nitems; ++i) {
1518 		int j = 0;
1519 
1520 		while (1) {
1521 			assert(j <= f->nv);
1522 
1523 			if (f->v[j].c == EQUIP_EXPR_TERMINATOR) {
1524 				assert(nst == 1);
1525 				if (stack[0]) {
1526 					assert(s->nfilt < s->nitems &&
1527 						s->nfilt < s->nalloc);
1528 					s->sorted_indices[s->nfilt] = i;
1529 					++s->nfilt;
1530 				}
1531 				break;
1532 			}
1533 
1534 			switch (f->v[j].c) {
1535 			case EQUIP_EXPR_SELECTOR:
1536 				assert(nst < f->nv);
1537 				stack[nst] = (*f->v[j].s.func)(s->items + i,
1538 					&f->v[j].s.ex);
1539 				++nst;
1540 				break;
1541 
1542 			case EQUIP_EXPR_AND:
1543 				assert(nst >= 2);
1544 				stack[nst - 2] =
1545 					(stack[nst - 1] && stack[nst - 2]);
1546 				--nst;
1547 				break;
1548 
1549 			case EQUIP_EXPR_OR:
1550 				assert(nst >= 2);
1551 				stack[nst - 2] =
1552 					(stack[nst - 1] || stack[nst - 2]);
1553 				--nst;
1554 				break;
1555 
1556 			case EQUIP_EXPR_TERMINATOR:
1557 				assert(0);
1558 			}
1559 			++j;
1560 		}
1561 	}
1562 
1563 	s->sorted_indices[s->nfilt] = -1;
1564 
1565 	mem_free(stack);
1566 }
1567 
1568 
filter_items(struct equippable_summary * s)1569 static void filter_items(struct equippable_summary *s)
1570 {
1571 	if (s->config_filt_is_on) {
1572 		if (s->config_mod_filt.simple == EQUIP_EXPR_SELECTOR) {
1573 			apply_complex_filter(&s->config_mod_filt, s);
1574 		} else {
1575 			apply_simple_filter(&s->config_mod_filt, s);
1576 		}
1577 	} else if (s->easy_filt.simple == EQUIP_EXPR_SELECTOR) {
1578 		apply_complex_filter(&s->easy_filt, s);
1579 	} else if (s->easy_filt.simple != EQUIP_EXPR_TERMINATOR) {
1580 		apply_simple_filter(&s->easy_filt, s);
1581 	} else {
1582 		/*
1583 		 * Tnere's no filtering; set up the sorted indices to include
1584 		 * everything.
1585 		 */
1586 		int i;
1587 
1588 		s->nfilt = s->nitems;
1589 		for (i = 0; i < s->nitems; ++i) {
1590 			s->sorted_indices[i] = i;
1591 		}
1592 		s->sorted_indices[s->nitems] = -1;
1593 	}
1594 
1595 	/* Reset the first item shown and the number on that page. */
1596 	s->ifirst = (s->indinc > 0) ? 0 : s->nfilt - 1;
1597 	s->npage = (s->maxpage <= s->nfilt) ? s->maxpage : s->nfilt;
1598 }
1599 
1600 
cmp_by_location(const struct equippable * left,const struct equippable * right,int propind)1601 static int cmp_by_location(const struct equippable *left,
1602 	const struct equippable *right, int propind)
1603 {
1604 	return (left->src < right->src) ?
1605 		-1 : ((left->src > right->src) ? 1 : 0);
1606 }
1607 
1608 
cmp_by_quality(const struct equippable * left,const struct equippable * right,int propind)1609 static int cmp_by_quality(const struct equippable *left,
1610 	const struct equippable *right, int propind)
1611 {
1612 	return (left->qual < right->qual) ?
1613 		-1 : ((left->qual > right->qual) ? 1 : 0);
1614 }
1615 
1616 
cmp_by_short_name(const struct equippable * left,const struct equippable * right,int propind)1617 static int cmp_by_short_name(const struct equippable *left,
1618 	const struct equippable *right, int propind)
1619 {
1620 
1621 	return strcmp(left->short_name, right->short_name);
1622 }
1623 
1624 
cmp_by_slot(const struct equippable * left,const struct equippable * right,int propind)1625 static int cmp_by_slot(const struct equippable *left,
1626 	const struct equippable *right, int propind)
1627 {
1628 	return (left->slot < right->slot) ?
1629 		-1 : ((left->slot > right->slot) ? 1 : 0);
1630 }
1631 
1632 
cmp_for_sort_items(const void * left,const void * right)1633 static int cmp_for_sort_items(const void *left, const void *right)
1634 {
1635 	int ileft = *((int*) left);
1636 	int iright = *((int*) right);
1637 	int i = 0;
1638 
1639 	while (1) {
1640 		int cres;
1641 
1642 		if (sort_dat.arr[i].func == 0) {
1643 			return 0;
1644 		}
1645 		cres = (*sort_dat.arr[i].func)(sort_dat.items + ileft,
1646 			sort_dat.items + iright, sort_dat.arr[i].propind);
1647 		if (cres != 0) {
1648 			return cres;
1649 		}
1650 		++i;
1651 	}
1652 }
1653 
1654 
sort_items(struct equippable_summary * s)1655 static void sort_items(struct equippable_summary *s)
1656 {
1657 	sort_dat.items = s->items;
1658 	sort_dat.arr = (s->config_sort_is_on) ?
1659 		s->config_sort.v : s->default_sort.v;
1660 	sort(s->sorted_indices, s->nfilt, sizeof(*s->sorted_indices),
1661 		cmp_for_sort_items);
1662 }
1663 
1664 
1665 typedef bool (*objselfunc)(const struct object *obj, const void *closure);
1666 typedef void (*objusefunc)(const struct object *obj, void *closure);
1667 struct obj_visitor_data {
1668 	objselfunc selfunc;
1669 	objusefunc usefunc;
1670 	const void *selfunc_closure;
1671 	void *usefunc_closure;
1672 };
1673 
1674 
1675 /**
1676  * Intended for use with apply_visitor_to_pile() or
1677  * apply_visitor_to_equipped().
1678  */
select_any(const struct object * obj,const void * closure)1679 static bool select_any(const struct object *obj, const void *closure)
1680 {
1681 	return true;
1682 }
1683 
1684 
1685 /**
1686  * Test for wearable objects in the pack.  Intended for use with
1687  * apply_visitor_to_pile().  Assumes obj is in the player's gear.
1688  */
select_nonequipped_wearable(const struct object * obj,const void * closure)1689 static bool select_nonequipped_wearable(const struct object *obj,
1690 	const void *closure)
1691 {
1692 	const struct player *p = closure;
1693 
1694 	return tval_is_wearable(obj) && !object_is_equipped(p->body, obj);
1695 }
1696 
1697 
1698 /**
1699  * Test for wearable objects.  Intended for use with apply_visitor_to_pile().
1700  */
select_wearable(const struct object * obj,const void * closure)1701 static bool select_wearable(const struct object *obj, const void *closure)
1702 {
1703 	return tval_is_wearable(obj);
1704 }
1705 
1706 
1707 /**
1708  * Test for wearable objects that are not ignored and have been seen (have a
1709  * known object).  Intended for use with apply_visitor_to_pile().
1710  */
select_seen_wearable(const struct object * obj,const void * closure)1711 static bool select_seen_wearable(const struct object *obj, const void *closure)
1712 {
1713 	return tval_is_wearable(obj) && obj->known && !ignore_item_ok(obj);
1714 }
1715 
1716 
1717 /**
1718  * Increment a counter; intended for use with apply_visitor_to_pile() or
1719  * apply_visitor_to_equipped().
1720  */
count_objects(const struct object * obj,void * closure)1721 static void count_objects(const struct object *obj, void *closure)
1722 {
1723 	int *n = closure;
1724 
1725 	++*n;
1726 }
1727 
1728 
1729 /**
1730  * Add an object to the summary of equippable items; intended for use with
1731  * apply_visitor_to_pile() or apply_visitor_to_equipped().
1732  */
1733 struct add_obj_to_summary_closure {
1734 	const struct player *p;
1735 	struct equippable_summary *summary;
1736 	enum equippable_source src;
1737 };
add_obj_to_summary(const struct object * obj,void * closure)1738 static void add_obj_to_summary(const struct object *obj, void *closure)
1739 {
1740 	struct add_obj_to_summary_closure *c = closure;
1741 	struct equippable *e;
1742 	struct cached_object_data *cache;
1743 	int i;
1744 
1745 	assert(c->summary->nitems < c->summary->nalloc);
1746 	e = c->summary->items + c->summary->nitems;
1747 	++c->summary->nitems;
1748 
1749 	if (!e->vals) {
1750 		e->vals = mem_alloc(c->summary->nprop * sizeof(*e->vals));
1751 	}
1752 	if (!e->auxvals) {
1753 		e->auxvals = mem_alloc(c->summary->nprop *
1754 			sizeof(*e->auxvals));
1755 	}
1756 	cache = NULL;
1757 	for (i = 0; i < (int)N_ELEMENTS(c->summary->propcats); ++i) {
1758 		int j;
1759 
1760 		for (j = 0; j < c->summary->propcats[i].n; ++j) {
1761 			compute_ui_entry_values_for_object(
1762 				c->summary->propcats[i].entries[j], obj,
1763 				c->p, &cache,
1764 				e->vals + j + c->summary->propcats[i].off,
1765 				e->auxvals + j + c->summary->propcats[i].off);
1766 		}
1767 	}
1768 	release_cached_object_data(cache);
1769 
1770 	if (c->summary->nshortnm > 0) {
1771 		string_free(e->short_name);
1772 		e->short_name = set_short_name(obj, c->summary->nshortnm);
1773 		e->nmlen = (int)strlen(e->short_name);
1774 	}
1775 
1776 	e->obj = obj;
1777 	e->src = c->src;
1778 
1779 	switch (ignore_level_of(obj)) {
1780 	case IGNORE_GOOD:
1781 		e->qual = EQUIP_QUAL_GOOD;
1782 		break;
1783 
1784 	case IGNORE_AVERAGE:
1785 		e->qual = EQUIP_QUAL_AVERAGE;
1786 		break;
1787 
1788 	case IGNORE_BAD:
1789 		e->qual = EQUIP_QUAL_BAD;
1790 		break;
1791 
1792 	default:
1793 		/* Try to get some finer distinctions. */
1794 		if (obj->known && obj->known->artifact) {
1795 			e->qual = EQUIP_QUAL_ARTIFACT;
1796 		} else if (obj->known && obj->known->ego) {
1797 			e->qual = EQUIP_QUAL_EGO;
1798 		} else {
1799 			/* Treat unknown items as average. */
1800 			e->qual = EQUIP_QUAL_AVERAGE;
1801 		}
1802 		break;
1803 	}
1804 
1805 	e->slot = wield_slot(obj);
1806 	e->ch = object_char(obj);
1807 	e->at = object_attr(obj);
1808 }
1809 
1810 
1811 /**
1812  * Given the function pointers in visitor, apply them to each object in the
1813  * pile pointed to by obj.  Assumes that the functions do not modify the
1814  * structure of the pile.
1815  */
apply_visitor_to_pile(const struct object * obj,struct obj_visitor_data * visitor)1816 static void apply_visitor_to_pile(const struct object *obj,
1817 	struct obj_visitor_data *visitor)
1818 {
1819 	while (obj) {
1820 		if ((*visitor->selfunc)(obj, visitor->selfunc_closure)) {
1821 			(*visitor->usefunc)(obj, visitor->usefunc_closure);
1822 		}
1823 		obj = obj->next;
1824 	}
1825 }
1826 
1827 
1828 /**
1829  * Given the function pointers in visitor, apply them to each object in the
1830  * given player's inventory.
1831  */
apply_visitor_to_equipped(struct player * p,struct obj_visitor_data * visitor)1832 static void apply_visitor_to_equipped(struct player *p,
1833 	struct obj_visitor_data *visitor)
1834 {
1835 	int i;
1836 
1837 	for (i = 0; i < p->body.count; ++i) {
1838 		const struct object *obj = slot_object(p, i);
1839 
1840 		if (obj && (*visitor->selfunc)(obj,
1841 			visitor->selfunc_closure)) {
1842 			(*visitor->usefunc)(obj, visitor->usefunc_closure);
1843 		}
1844 	}
1845 }
1846 
1847 
reconfigure_for_term_if_necessary(bool update_names,struct equippable_summary * s)1848 static int reconfigure_for_term_if_necessary(bool update_names,
1849 	struct equippable_summary *s)
1850 {
1851 	int result = 0;
1852 	int min_length = 16;
1853 	int ncol, nrow, length, i;
1854 
1855 	Term_get_size(&ncol, &nrow);
1856 	if (s->term_ncol == ncol && s->term_nrow == nrow) {
1857 		return result;
1858 	}
1859 
1860 	/*
1861 	 * Have s->irow_combined_equip + 1 rows of a header and one row, for
1862 	 * prompts, of a footer.
1863 	 */
1864 	s->maxpage = nrow - s->irow_combined_equip - 2;
1865 	if (s->maxpage < 1) {
1866 		result = 1;
1867 	} else if (s->npage > s->maxpage) {
1868 		s->npage = s->maxpage;
1869 		if (s->work_sel != -1 &&
1870 			(s->work_sel - s->ifirst) * s->indinc >= s->npage) {
1871 			s->work_sel = s->ifirst + (s->npage - 1) * s->indinc;
1872 		}
1873 	}
1874 
1875 	/*
1876 	 * Leave a space between the name and the properties.  Don't include
1877 	 * the core stat modifiers in the first view.
1878 	 */
1879 	length = ncol - s->nprop + s->propcats[N_ELEMENTS(s->propcats) - 1].n -
1880 		1 - s->icol_name;
1881 	if (length < min_length) {
1882 		/* Try shifting the other modifiers to the second view. */
1883 		length += s->propcats[N_ELEMENTS(s->propcats) - 2].n;
1884 		if (length < min_length) {
1885 			/* Try a three view layout. */
1886 			length += s->propcats[N_ELEMENTS(s->propcats) - 3].n;
1887 			if (length < min_length) {
1888 				/* Give up. */
1889 				result = 1;
1890 			}
1891 			s->nview = 3;
1892 			for (i = 0; i < (int)N_ELEMENTS(s->propcats) - 3; ++i) {
1893 				s->propcats[i].nvw[0] = s->propcats[i].n;
1894 				s->propcats[i].nvw[1] = 0;
1895 				s->propcats[i].nvw[2] = 0;
1896 				s->propcats[i].ivw[0] = 0;
1897 				s->propcats[i].ivw[1] = 0;
1898 				s->propcats[i].ivw[2] = 0;
1899 			}
1900 			for (i = (int)N_ELEMENTS(s->propcats) - 3;
1901 				i < (int)N_ELEMENTS(s->propcats) - 1; ++i) {
1902 				s->propcats[i].nvw[0] = 0;
1903 				s->propcats[i].nvw[1] = s->propcats[i].n;
1904 				s->propcats[i].nvw[2] = 0;
1905 				s->propcats[i].ivw[0] = 0;
1906 				s->propcats[i].ivw[1] = 0;
1907 				s->propcats[i].ivw[2] = 0;
1908 			}
1909 			s->propcats[N_ELEMENTS(s->propcats) - 1].nvw[0] = 0;
1910 			s->propcats[N_ELEMENTS(s->propcats) - 1].nvw[1] = 0;
1911 			s->propcats[N_ELEMENTS(s->propcats) - 1].nvw[2] =
1912 				s->propcats[N_ELEMENTS(s->propcats) - 1].n;
1913 			s->propcats[N_ELEMENTS(s->propcats) - 1].ivw[0] = 0;
1914 			s->propcats[N_ELEMENTS(s->propcats) - 1].ivw[1] = 0;
1915 			s->propcats[N_ELEMENTS(s->propcats) - 1].ivw[2] = 0;
1916 		} else {
1917 			s->nview = 2;
1918 			for (i = 0; i < (int)N_ELEMENTS(s->propcats) - 2; ++i) {
1919 				s->propcats[i].nvw[0] = s->propcats[i].n;
1920 				s->propcats[i].nvw[1] = 0;
1921 				s->propcats[i].nvw[2] = 0;
1922 				s->propcats[i].ivw[0] = 0;
1923 				s->propcats[i].ivw[1] = 0;
1924 				s->propcats[i].ivw[2] = 0;
1925 			}
1926 			for (i = (int)N_ELEMENTS(s->propcats) - 2;
1927 				i < (int)N_ELEMENTS(s->propcats); ++i) {
1928 				s->propcats[i].nvw[0] = 0;
1929 				s->propcats[i].nvw[1] = s->propcats[i].n;
1930 				s->propcats[i].nvw[2] = 0;
1931 				s->propcats[i].ivw[0] = 0;
1932 				s->propcats[i].ivw[1] = 0;
1933 				s->propcats[i].ivw[2] = 0;
1934 			}
1935 		}
1936 	} else {
1937 		s->nview = 2;
1938 		for (i = 0; i < (int)N_ELEMENTS(s->propcats) - 1; ++i) {
1939 			s->propcats[i].nvw[0] = s->propcats[i].n;
1940 			s->propcats[i].nvw[1] = 0;
1941 			s->propcats[i].nvw[2] = 0;
1942 			s->propcats[i].ivw[0] = 0;
1943 			s->propcats[i].ivw[1] = 0;
1944 			s->propcats[i].ivw[2] = 0;
1945 		}
1946 		s->propcats[N_ELEMENTS(s->propcats) - 1].nvw[0] = 0;
1947 		s->propcats[N_ELEMENTS(s->propcats) - 1].nvw[1] =
1948 			s->propcats[N_ELEMENTS(s->propcats) - 1].n;
1949 		s->propcats[N_ELEMENTS(s->propcats) - 1].nvw[2] = 0;
1950 		s->propcats[N_ELEMENTS(s->propcats) - 1].ivw[0] = 0;
1951 		s->propcats[N_ELEMENTS(s->propcats) - 1].ivw[1] = 0;
1952 		s->propcats[N_ELEMENTS(s->propcats) - 1].ivw[2] = 0;
1953 	}
1954 	if (s->iview >= s->nview) {
1955 		s->iview = s->nview - 1;
1956 	}
1957 	if (length > 20) {
1958 		length = 20;
1959 	}
1960 	if (length != s->nshortnm && update_names) {
1961 		for (i = 0; i < s->nitems; ++i) {
1962 			string_free(s->items[i].short_name);
1963 			s->items[i].short_name =
1964 				set_short_name(s->items[i].obj, length);
1965 			s->items[i].nmlen =
1966 				(int)strlen(s->items[i].short_name);
1967 		}
1968 	}
1969 	s->nshortnm = length;
1970 
1971 	s->term_ncol = ncol;
1972 	s->term_nrow = nrow;
1973 
1974 	return result;
1975 }
1976 
1977 
compute_player_and_equipment_values(struct player * p,struct equippable_summary * s)1978 static void compute_player_and_equipment_values(struct player *p,
1979 	struct equippable_summary *s)
1980 {
1981 	struct cached_player_data *pcache;
1982 	struct ui_entry_combiner_state *cstates;
1983 	struct ui_entry_combiner_funcs cfuncs;
1984 	int i;
1985 
1986 	if (! s->p_and_eq_vals) {
1987 		s->p_and_eq_vals = mem_alloc(s->nprop *
1988 			sizeof(*s->p_and_eq_vals));
1989 	}
1990 	if (! s->p_and_eq_auxvals) {
1991 		s->p_and_eq_auxvals = mem_alloc(s->nprop *
1992 			sizeof(*s->p_and_eq_auxvals));
1993 	}
1994 
1995 	pcache = NULL;
1996 	cstates = mem_alloc(s->nprop * sizeof(*cstates));
1997 	for (i = 0; i < (int)N_ELEMENTS(s->propcats); ++i) {
1998 		int j;
1999 
2000 		for (j = 0; j < s->propcats[i].n; ++j) {
2001 			const struct ui_entry *entry =
2002 				s->propcats[i].entries[j];
2003 			int rendind = get_ui_entry_renderer_index(entry);
2004 			int combind =
2005 				ui_entry_renderer_query_combiner(rendind);
2006 			int v, a;
2007 
2008 			assert(combind > 0);
2009 			(void) ui_entry_combiner_get_funcs(combind, &cfuncs);
2010 			compute_ui_entry_values_for_player(
2011 				entry, p, &pcache, &v, &a);
2012 			(*cfuncs.init_func)(v, a,
2013 				cstates + j + s->propcats[i].off);
2014 		}
2015 	}
2016 	release_cached_player_data(pcache);
2017 
2018 	/* Combine with the values from the equipment. */
2019 	for (i = 0; i < p->body.count; ++i) {
2020 		const struct object *obj = slot_object(p, i);
2021 		struct cached_object_data *cache = NULL;
2022 		int j;
2023 
2024 		if (!obj) {
2025 			continue;
2026 		}
2027 		for (j = 0; j < (int)N_ELEMENTS(s->propcats); ++j) {
2028 			int k;
2029 
2030 			for (k = 0; k < s->propcats[j].n; ++k) {
2031 				const struct ui_entry *entry =
2032 					s->propcats[j].entries[k];
2033 				int rendind = get_ui_entry_renderer_index(entry);
2034 				int combind = ui_entry_renderer_query_combiner(rendind);
2035 				int v, a;
2036 
2037 				assert(combind > 0);
2038 				(void) ui_entry_combiner_get_funcs(
2039 					combind, &cfuncs);
2040 				compute_ui_entry_values_for_object(
2041 					s->propcats[j].entries[k], obj, p,
2042 					&cache, &v, &a);
2043 				(*cfuncs.accum_func)(v, a,
2044 					cstates + k + s->propcats[j].off);
2045 			}
2046 		}
2047 		release_cached_object_data(cache);
2048 	}
2049 
2050 	for (i = 0; i < (int)N_ELEMENTS(s->propcats); ++i) {
2051 		int j;
2052 
2053 		for (j = 0; j < s->propcats[i].n; ++j) {
2054 			const struct ui_entry *entry =
2055 				s->propcats[i].entries[j];
2056 			int rendind = get_ui_entry_renderer_index(entry);
2057 			int combind =
2058 				ui_entry_renderer_query_combiner(rendind);
2059 
2060 			assert(combind > 0);
2061 			(void) ui_entry_combiner_get_funcs(combind, &cfuncs);
2062 			(*cfuncs.finish_func)(
2063 				cstates + j + s->propcats[i].off);
2064 			s->p_and_eq_vals[j + s->propcats[i].off] =
2065 				cstates[j + s->propcats[i].off].accum;
2066 			s->p_and_eq_auxvals[j + s->propcats[i].off] =
2067 				cstates[j + s->propcats[i].off].accum_aux;
2068 		}
2069 	}
2070 
2071 	mem_free(cstates);
2072 }
2073 
2074 
check_for_two_categories(const struct ui_entry * entry,void * closure)2075 static bool check_for_two_categories(const struct ui_entry *entry,
2076 	void *closure)
2077 {
2078 	char **categories = closure;
2079 
2080 	return ui_entry_has_category(entry, categories[0]) &&
2081 		ui_entry_has_category(entry, categories[1]);
2082 }
2083 
2084 
initialize_summary(struct player * p,struct equippable_summary ** s)2085 static int initialize_summary(struct player *p,
2086 	struct equippable_summary **s)
2087 {
2088 	struct obj_visitor_data visitor;
2089 	struct add_obj_to_summary_closure add_obj_data;
2090 	int count, i;
2091 
2092 	if (*s == NULL) {
2093 		char *categories[] = {
2094 			"resistances",
2095 			"abilities",
2096 			"hindrances",
2097 			"modifiers",
2098 			"stat_modifiers"
2099 		};
2100 		char *test_categories[2];
2101 
2102 		*s = mem_alloc(sizeof(**s));
2103 		(*s)->items = NULL;
2104 		(*s)->sorted_indices = NULL;
2105 		(*s)->p_and_eq_vals = NULL;
2106 		(*s)->p_and_eq_auxvals = NULL;
2107 		(*s)->dlg_trans_msg = NULL;
2108 		(*s)->nitems = 0;
2109 		(*s)->nalloc = 0;
2110 		(*s)->stores = EQUIPPABLE_NO_STORE;
2111 		(*s)->ifirst = 0;
2112 		(*s)->indinc = 1;
2113 		(*s)->iview = 0;
2114 		(*s)->npage = 0;
2115 		(*s)->nshortnm = 0;
2116 		(*s)->term_ncol = -1;
2117 		(*s)->term_nrow = -1;
2118 
2119 		/* These are currently hardwired layout choices. */
2120 		(*s)->nproplab = 2;
2121 		(*s)->irow_combined_equip = (*s)->nproplab + 1;
2122 		/*
2123 		 * Leave room for a character (item character), a space,
2124 		 * a character (item location code), and a space.
2125 		 */
2126 		(*s)->icol_name = 4;
2127 
2128 		/*
2129 		 * These need to be done once after the game configuration is
2130 		 * read.
2131 		 */
2132 		(*s)->nprop = 0;
2133 		test_categories[0] = "EQUIPCMP_SCREEN";
2134 		for (i = 0; i < (int)N_ELEMENTS((*s)->propcats); ++i) {
2135 			struct ui_entry_iterator *ui_iter;
2136 			int n, j;
2137 
2138 			test_categories[1] = categories[i];
2139 			ui_iter = initialize_ui_entry_iterator(
2140 				check_for_two_categories, test_categories,
2141 				test_categories[1]);
2142 			n = count_ui_entry_iterator(ui_iter);
2143 			(*s)->nprop += n;
2144 			(*s)->propcats[i].n = n;
2145 			(*s)->propcats[i].off = (i == 0) ?
2146 				0 : (*s)->propcats[i - 1].off +
2147 				(*s)->propcats[i - 1].n;
2148 			(*s)->propcats[i].entries = mem_alloc(n *
2149 				sizeof(*(*s)->propcats[i].entries));
2150 			(*s)->propcats[i].labels = mem_alloc(n *
2151 				sizeof(*(*s)->propcats[i].labels));
2152 			(*s)->propcats[i].label_buffer = mem_alloc(n *
2153 				((*s)->nproplab + 1) *
2154 				sizeof(*(*s)->propcats[i].label_buffer));
2155 			for (j = 0; j < n; ++j) {
2156 				struct ui_entry *entry =
2157 					advance_ui_entry_iterator(ui_iter);
2158 
2159 				(*s)->propcats[i].entries[j] = entry;
2160 				(*s)->propcats[i].labels[j] =
2161 					(*s)->propcats[i].label_buffer +
2162 					j * ((*s)->nproplab + 1);
2163 				get_ui_entry_label(entry,
2164 					(*s)->nproplab + 1, true,
2165 					(*s)->propcats[i].labels[j]);
2166 			}
2167 			release_ui_entry_iterator(ui_iter);
2168 		}
2169 
2170 		/*
2171 		 * Start with nothing for the easy filter but set up space
2172 		 * so it is trivial to add one term filtering on an attribute
2173 		 * and up to three others that can filter on the source of the
2174 		 * item.
2175 		 */
2176 		(*s)->easy_filt.nalloc = 5;
2177 		(*s)->easy_filt.v = mem_alloc((*s)->easy_filt.nalloc *
2178 			sizeof(*(*s)->easy_filt.v));
2179 		switch ((*s)->stores) {
2180 		case EQUIPPABLE_NO_STORE:
2181 			(*s)->easy_filt.simple = EQUIP_EXPR_AND;
2182 			(*s)->easy_filt.nv = 1;
2183 			(*s)->easy_filt.v[0].s.func = sel_exclude_src;
2184 			(*s)->easy_filt.v[0].s.ex.src = EQUIP_SOURCE_STORE;
2185 			(*s)->easy_filt.v[0].c = EQUIP_EXPR_SELECTOR;
2186 			break;
2187 
2188 		case EQUIPPABLE_ONLY_STORE:
2189 			(*s)->easy_filt.simple = EQUIP_EXPR_AND;
2190 			(*s)->easy_filt.nv = 1;
2191 			(*s)->easy_filt.v[0].s.func = sel_only_src;
2192 			(*s)->easy_filt.v[0].s.ex.src = EQUIP_SOURCE_STORE;
2193 			(*s)->easy_filt.v[0].c = EQUIP_EXPR_SELECTOR;
2194 			break;
2195 
2196 		case EQUIPPABLE_YES_STORE:
2197 			/* There's no filtering to be done. */
2198 			(*s)->easy_filt.simple = EQUIP_EXPR_TERMINATOR;
2199 			(*s)->easy_filt.nv = 0;
2200 			break;
2201 
2202 		case EQUIPPABLE_ONLY_CARRIED:
2203 			(*s)->easy_filt.simple = EQUIP_EXPR_AND;
2204 			(*s)->easy_filt.nv = 3;
2205 			(*s)->easy_filt.v[0].s.func = sel_exclude_src;
2206 			(*s)->easy_filt.v[0].s.ex.src = EQUIP_SOURCE_STORE;
2207 			(*s)->easy_filt.v[0].c = EQUIP_EXPR_SELECTOR;
2208 			(*s)->easy_filt.v[1].s.func = sel_exclude_src;
2209 			(*s)->easy_filt.v[1].s.ex.src = EQUIP_SOURCE_HOME;
2210 			(*s)->easy_filt.v[1].c = EQUIP_EXPR_SELECTOR;
2211 			(*s)->easy_filt.v[2].s.func = sel_exclude_src;
2212 			(*s)->easy_filt.v[2].s.ex.src = EQUIP_SOURCE_FLOOR;
2213 			(*s)->easy_filt.v[2].c = EQUIP_EXPR_SELECTOR;
2214 		}
2215 
2216 		for (i = (*s)->easy_filt.nv; i < (*s)->easy_filt.nalloc; ++i) {
2217 			(*s)->easy_filt.v[i].c = EQUIP_EXPR_TERMINATOR;
2218 		}
2219 
2220 		/* Start with nothing for the specially configured filter. */
2221 		(*s)->config_filt.v = NULL;
2222 		(*s)->config_filt.simple = EQUIP_EXPR_TERMINATOR;
2223 		(*s)->config_filt.nv = 0;
2224 		(*s)->config_filt.nalloc = 0;
2225 		(*s)->config_mod_filt.v = NULL;
2226 		(*s)->config_mod_filt.simple = EQUIP_EXPR_TERMINATOR;
2227 		(*s)->config_mod_filt.nv = 0;
2228 		(*s)->config_mod_filt.nalloc = 0;
2229 		(*s)->config_filt_is_on = false;
2230 
2231 		/*
2232 		 * The default sort is by equipment slot.  Any ties are
2233 		 * resolved first by the location of the object, rough object
2234 		 * quality, and then alphabetical order.
2235 		 */
2236 		(*s)->default_sort.nalloc = 5;
2237 		(*s)->default_sort.v = mem_alloc((*s)->default_sort.nalloc *
2238 			sizeof(*(*s)->default_sort.v));
2239 		(*s)->default_sort.v[0].func = cmp_by_slot;
2240 		(*s)->default_sort.v[0].propind = 0;
2241 		(*s)->default_sort.v[1].func = cmp_by_location;
2242 		(*s)->default_sort.v[1].propind = 0;
2243 		(*s)->default_sort.v[2].func = cmp_by_quality;
2244 		(*s)->default_sort.v[2].propind = 0;
2245 		(*s)->default_sort.v[3].func = cmp_by_short_name;
2246 		(*s)->default_sort.v[3].propind = 0;
2247 		(*s)->default_sort.v[4].func = 0;
2248 		(*s)->default_sort.v[4].propind = 0;
2249 		(*s)->default_sort.nv = 4;
2250 
2251 		/* Start with nothing for the specially configured sort. */
2252 		(*s)->config_sort.v = NULL;
2253 		(*s)->config_sort.nv = 0;
2254 		(*s)->config_sort.nalloc = 0;
2255 		(*s)->config_sort_is_on = false;
2256 	}
2257 
2258 	/* These need to be redone on a change to the terminal size. */
2259 	if (reconfigure_for_term_if_necessary(false, *s)) {
2260 		return 1;
2261 	}
2262 
2263 	/*
2264 	 * These need to be redone for any change in the equipped items, pack,
2265 	 * home, or stores.
2266 	 */
2267 
2268 	/* Count the available items to include. */
2269 	count = 0;
2270 	visitor.usefunc = count_objects;
2271 	visitor.usefunc_closure = &count;
2272 	visitor.selfunc = select_any;
2273 	visitor.selfunc_closure = NULL;
2274 	apply_visitor_to_equipped(p, &visitor);
2275 	visitor.selfunc = select_nonequipped_wearable;
2276 	visitor.selfunc_closure = p;
2277 	apply_visitor_to_pile(p->gear, &visitor);
2278 	if (cave) {
2279 		visitor.selfunc = select_seen_wearable;
2280 		visitor.selfunc_closure = NULL;
2281 		apply_visitor_to_pile(square_object(cave, p->grid), &visitor);
2282 	}
2283 	visitor.selfunc = select_wearable;
2284 	visitor.selfunc_closure = NULL;
2285 	apply_visitor_to_pile(stores[STORE_HOME].stock, &visitor);
2286 	for (i = 0; i < MAX_STORES; ++i) {
2287 		if (i == STORE_HOME) {
2288 			continue;
2289 		}
2290 		apply_visitor_to_pile(stores[i].stock, &visitor);
2291 	}
2292 
2293 	/* Allocate storage and add the available items. */
2294 	if (count > (*s)->nalloc) {
2295 		mem_free((*s)->sorted_indices);
2296 		cleanup_summary_items(*s);
2297 		mem_free((*s)->items);
2298 		(*s)->items = mem_zalloc(count * sizeof(*(*s)->items));
2299 		(*s)->sorted_indices =
2300 			mem_alloc((count + 1) * sizeof(*(*s)->sorted_indices));
2301 		(*s)->nalloc = count;
2302 	}
2303 	(*s)->nitems = 0;
2304 	visitor.usefunc = add_obj_to_summary;
2305 	visitor.usefunc_closure = &add_obj_data;
2306 	add_obj_data.p = p;
2307 	add_obj_data.summary = *s;
2308 	add_obj_data.src = EQUIP_SOURCE_WORN;
2309 	visitor.selfunc = select_any;
2310 	visitor.selfunc_closure = NULL;
2311 	apply_visitor_to_equipped(p, &visitor);
2312 	add_obj_data.src = EQUIP_SOURCE_PACK;
2313 	visitor.selfunc = select_nonequipped_wearable;
2314 	visitor.selfunc_closure = p;
2315 	apply_visitor_to_pile(p->gear, &visitor);
2316 	if (cave) {
2317 		add_obj_data.src = EQUIP_SOURCE_FLOOR;
2318 		visitor.selfunc = select_seen_wearable;
2319 		visitor.selfunc_closure = NULL;
2320 		apply_visitor_to_pile(square_object(cave, p->grid), &visitor);
2321 	}
2322 	add_obj_data.src = EQUIP_SOURCE_HOME;
2323 	visitor.selfunc = select_wearable;
2324 	visitor.selfunc_closure = NULL;
2325 	apply_visitor_to_pile(stores[STORE_HOME].stock, &visitor);
2326 	add_obj_data.src = EQUIP_SOURCE_STORE;
2327 	for (i = 0; i < MAX_STORES; ++i) {
2328 		if (i == STORE_HOME) {
2329 			continue;
2330 		}
2331 		apply_visitor_to_pile(stores[i].stock, &visitor);
2332 	}
2333 
2334 	compute_player_and_equipment_values(p, *s);
2335 
2336 	/*
2337 	 * Do an initial filtering and sorting of the items so they're ready
2338 	 * for display.
2339 	 */
2340 	filter_items(*s);
2341 	sort_items(*s);
2342 
2343 	/* Reset selection state. */
2344 	(*s)->isel0 = -1;
2345 	(*s)->isel1 = -1;
2346 	(*s)->work_sel = -1;
2347 
2348 	return 0;
2349 }
2350 
2351 
cleanup_summary(struct equippable_summary * s)2352 static void cleanup_summary(struct equippable_summary *s)
2353 {
2354 	int i;
2355 
2356 	if (s == NULL) {
2357 		return;
2358 	}
2359 	cleanup_summary_items(s);
2360 	mem_free(s->p_and_eq_auxvals);
2361 	mem_free(s->p_and_eq_vals);
2362 	mem_free(s->sorted_indices);
2363 	mem_free(s->items);
2364 	mem_free(s->config_sort.v);
2365 	mem_free(s->default_sort.v);
2366 	mem_free(s->config_mod_filt.v);
2367 	mem_free(s->config_filt.v);
2368 	mem_free(s->easy_filt.v);
2369 	for (i = 0; i < (int)N_ELEMENTS(s->propcats); ++i) {
2370 		mem_free(s->propcats[i].label_buffer);
2371 		mem_free(s->propcats[i].labels);
2372 		mem_free(s->propcats[i].entries);
2373 	}
2374 	mem_free(s);
2375 }
2376 
2377 
cleanup_summary_items(struct equippable_summary * s)2378 static void cleanup_summary_items(struct equippable_summary *s)
2379 {
2380 	int i;
2381 
2382 	for (i = 0; i < s->nalloc; ++i) {
2383 		string_free(s->items[i].short_name);
2384 		mem_free(s->items[i].auxvals);
2385 		mem_free(s->items[i].vals);
2386 	}
2387 }
2388 
2389 
source_to_char(enum equippable_source src)2390 static wchar_t source_to_char(enum equippable_source src)
2391 {
2392 	static bool first_call = true;
2393 	static wchar_t wchars[7];
2394 	wchar_t result;
2395 
2396 	if (first_call) {
2397 		if (text_mbstowcs(wchars, "epfhs ", N_ELEMENTS(wchars)) == (size_t)-1) {
2398 			quit("Invalid encoding for item location codes");
2399 		}
2400 		first_call = false;
2401 	}
2402 
2403 	switch (src) {
2404 	case EQUIP_SOURCE_WORN:
2405 		result = wchars[0];
2406 		break;
2407 
2408 	case EQUIP_SOURCE_PACK:
2409 		result = wchars[1];
2410 		break;
2411 
2412 	case EQUIP_SOURCE_FLOOR:
2413 		result = wchars[2];
2414 		break;
2415 
2416 	case EQUIP_SOURCE_HOME:
2417 		result = wchars[3];
2418 		break;
2419 
2420 	case EQUIP_SOURCE_STORE:
2421 		result = wchars[4];
2422 		break;
2423 
2424 	default:
2425 		result = wchars[5];
2426 		break;
2427 	}
2428 
2429 	return result;
2430 }
2431 
2432 
display_page(struct equippable_summary * s,const struct player * p,bool allow_reconfig)2433 static int display_page(struct equippable_summary *s, const struct player *p,
2434 	bool allow_reconfig)
2435 {
2436 	struct ui_entry_details rdetails;
2437 	int color = (COLOUR_WHITE);
2438 	int i;
2439 
2440 	/* Try to handle terminal size changes while displaying the summary. */
2441 	if (allow_reconfig) {
2442 		if (reconfigure_for_term_if_necessary(true, s)) {
2443 			return 1;
2444 		}
2445 	}
2446 
2447 	/*
2448 	 * Display the column labels and the combined values for @ and the
2449 	 * the current equipment.  The display of the labels bypasses what's
2450 	 * done by ui_entry_renderer_apply() so the color of the label can
2451 	 * alternate between columns.
2452 	 */
2453 	rdetails.label_position.x = s->icol_name + s->nshortnm + 1;
2454 	rdetails.label_position.y = s->irow_combined_equip - s->nproplab;
2455 	rdetails.value_position.x = rdetails.label_position.x;
2456 	rdetails.value_position.y = s->irow_combined_equip;
2457 	rdetails.position_step = loc(1, 0);
2458 	rdetails.combined_position = loc(0, 0);
2459 	rdetails.vertical_label = false;
2460 	rdetails.alternate_color_first = false;
2461 	rdetails.show_combined = false;
2462 	Term_putch(s->icol_name - 4, rdetails.value_position.y, color, L'@');
2463 	for (i = 0; i < (int)N_ELEMENTS(s->propcats); ++i) {
2464 		int j;
2465 
2466 		if (!s->propcats[i].nvw[s->iview]) {
2467 			continue;
2468 		}
2469 		for (j = 0; j < s->propcats[i].nvw[s->iview]; ++j) {
2470 			int joff = j + s->propcats[i].ivw[s->iview];
2471 			/*
2472 			 * As a hack, label colors are hardwired; it would be
2473 			 * better if they configurable so they'd be consistent
2474 			 * with the scheme for the symbol colors.
2475 			 */
2476 			int label_color = (j % 2 == 0) ?
2477 				COLOUR_WHITE : COLOUR_L_WHITE;
2478 			int k;
2479 
2480 			for (k = 0; k < s->nproplab; ++k) {
2481 				Term_putch(rdetails.label_position.x,
2482 					rdetails.label_position.y + k,
2483 					label_color,
2484 					s->propcats[i].labels[joff][k]);
2485 			}
2486 			rdetails.known_rune = is_ui_entry_for_known_rune(
2487 				s->propcats[i].entries[joff], p);
2488 			ui_entry_renderer_apply(get_ui_entry_renderer_index(
2489 				s->propcats[i].entries[joff]), NULL, 0,
2490 				s->p_and_eq_vals + joff + s->propcats[i].off,
2491 				s->p_and_eq_auxvals + joff +
2492 				s->propcats[i].off, 1, &rdetails);
2493 			++rdetails.label_position.x;
2494 			++rdetails.value_position.x;
2495 			rdetails.alternate_color_first =
2496 				!rdetails.alternate_color_first;
2497 		}
2498 	}
2499 
2500 	/* Display the items available on the current page. */
2501 	++rdetails.value_position.y;
2502 	for (i = 0; i < s->npage; ++i) {
2503 		const struct equippable *e;
2504 		int icnv, isort, j, nmcolor;
2505 
2506 		icnv = s->ifirst + s->indinc * i;
2507 		assert(icnv >= 0 && icnv < s->nfilt);
2508 		isort = s->sorted_indices[icnv];
2509 		assert(isort >= 0 && isort < s->nitems);
2510 		e = s->items + isort;
2511 
2512 		Term_putch(s->icol_name - 4, rdetails.value_position.y,
2513 			e->at, e->ch);
2514 		Term_putch(s->icol_name - 2, rdetails.value_position.y, color,
2515 			source_to_char(e->src));
2516 		if (isort == s->isel0 || isort == s->isel1 ||
2517 			icnv == s->work_sel) {
2518 			nmcolor = (COLOUR_L_BLUE);
2519 		} else {
2520 			nmcolor = color;
2521 		}
2522 		Term_putstr(s->icol_name, rdetails.value_position.y,
2523 			e->nmlen, nmcolor, e->short_name);
2524 		rdetails.value_position.x = s->icol_name + s->nshortnm + 1;
2525 		rdetails.alternate_color_first = false;
2526 		for (j = 0; j < (int)N_ELEMENTS(s->propcats); ++j) {
2527 			int k;
2528 
2529 			if (!s->propcats[j].nvw[s->iview]) {
2530 				continue;
2531 			}
2532 			for (k = 0; k < s->propcats[j].nvw[s->iview]; ++k) {
2533 				int koff = k + s->propcats[j].ivw[s->iview];
2534 
2535 				ui_entry_renderer_apply(
2536 					get_ui_entry_renderer_index(
2537 					s->propcats[j].entries[koff]), NULL, 0,
2538 					e->vals + koff + s->propcats[j].off,
2539 					e->auxvals + koff + s->propcats[j].off,
2540 					1, &rdetails);
2541 				++rdetails.value_position.x;
2542 				rdetails.alternate_color_first =
2543 					!rdetails.alternate_color_first;
2544 			}
2545 		}
2546 		++rdetails.value_position.y;
2547 	}
2548 
2549 	return 0;
2550 }
2551 
2552 
init_ui_equip_cmp(void)2553 static void init_ui_equip_cmp(void)
2554 {
2555 	/* There's nothing to do; initialize lazily. */
2556 }
2557 
2558 
cleanup_ui_equip_cmp(void)2559 static void cleanup_ui_equip_cmp(void)
2560 {
2561 	cleanup_summary(the_summary);
2562 	the_summary = NULL;
2563 }
2564 
2565 
2566 struct init_module ui_equip_cmp_module = {
2567 	.name = "ui-equip-cmp",
2568 	.init = init_ui_equip_cmp,
2569 	.cleanup = cleanup_ui_equip_cmp
2570 };
2571