1217309Snwhitehorn/* 2251843Sbapt * $Id: fselect.c,v 1.93 2012/12/30 20:52:25 tom Exp $ 3217309Snwhitehorn * 4220749Snwhitehorn * fselect.c -- implements the file-selector box 5217309Snwhitehorn * 6251843Sbapt * Copyright 2000-2011,2012 Thomas E. Dickey 7217309Snwhitehorn * 8217309Snwhitehorn * This program is free software; you can redistribute it and/or modify 9217309Snwhitehorn * it under the terms of the GNU Lesser General Public License, version 2.1 10217309Snwhitehorn * as published by the Free Software Foundation. 11217309Snwhitehorn * 12217309Snwhitehorn * This program is distributed in the hope that it will be useful, but 13217309Snwhitehorn * WITHOUT ANY WARRANTY; without even the implied warranty of 14217309Snwhitehorn * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 15217309Snwhitehorn * Lesser General Public License for more details. 16217309Snwhitehorn * 17217309Snwhitehorn * You should have received a copy of the GNU Lesser General Public 18217309Snwhitehorn * License along with this program; if not, write to 19217309Snwhitehorn * Free Software Foundation, Inc. 20217309Snwhitehorn * 51 Franklin St., Fifth Floor 21217309Snwhitehorn * Boston, MA 02110, USA. 22217309Snwhitehorn */ 23217309Snwhitehorn 24217309Snwhitehorn#include <dialog.h> 25217309Snwhitehorn#include <dlg_keys.h> 26217309Snwhitehorn 27217309Snwhitehorn#include <sys/types.h> 28217309Snwhitehorn#include <sys/stat.h> 29217309Snwhitehorn 30217309Snwhitehorn#if HAVE_DIRENT_H 31217309Snwhitehorn# include <dirent.h> 32217309Snwhitehorn# define NAMLEN(dirent) strlen((dirent)->d_name) 33217309Snwhitehorn#else 34217309Snwhitehorn# define dirent direct 35217309Snwhitehorn# define NAMLEN(dirent) (dirent)->d_namlen 36217309Snwhitehorn# if HAVE_SYS_NDIR_H 37217309Snwhitehorn# include <sys/ndir.h> 38217309Snwhitehorn# endif 39217309Snwhitehorn# if HAVE_SYS_DIR_H 40217309Snwhitehorn# include <sys/dir.h> 41217309Snwhitehorn# endif 42217309Snwhitehorn# if HAVE_NDIR_H 43217309Snwhitehorn# include <ndir.h> 44217309Snwhitehorn# endif 45217309Snwhitehorn#endif 46217309Snwhitehorn 47217309Snwhitehorn# if defined(_FILE_OFFSET_BITS) && defined(HAVE_STRUCT_DIRENT64) 48217309Snwhitehorn# if !defined(_LP64) && (_FILE_OFFSET_BITS == 64) 49217309Snwhitehorn# define DIRENT struct dirent64 50217309Snwhitehorn# else 51217309Snwhitehorn# define DIRENT struct dirent 52217309Snwhitehorn# endif 53217309Snwhitehorn# else 54217309Snwhitehorn# define DIRENT struct dirent 55217309Snwhitehorn# endif 56217309Snwhitehorn 57217309Snwhitehorn#define EXT_WIDE 1 58217309Snwhitehorn#define HDR_HIGH 1 59217309Snwhitehorn#define BTN_HIGH (1 + 2 * MARGIN) /* Ok/Cancel, also input-box */ 60217309Snwhitehorn#define MIN_HIGH (HDR_HIGH - MARGIN + (BTN_HIGH * 2) + 4 * MARGIN) 61217309Snwhitehorn#define MIN_WIDE (2 * MAX(dlg_count_columns(d_label), dlg_count_columns(f_label)) + 6 * MARGIN + 2 * EXT_WIDE) 62217309Snwhitehorn 63217309Snwhitehorn#define MOUSE_D (KEY_MAX + 0) 64217309Snwhitehorn#define MOUSE_F (KEY_MAX + 10000) 65217309Snwhitehorn#define MOUSE_T (KEY_MAX + 20000) 66217309Snwhitehorn 67217309Snwhitehorntypedef enum { 68217309Snwhitehorn sDIRS = -3 69217309Snwhitehorn ,sFILES = -2 70217309Snwhitehorn ,sTEXT = -1 71217309Snwhitehorn} STATES; 72217309Snwhitehorn 73217309Snwhitehorntypedef struct { 74217309Snwhitehorn WINDOW *par; /* parent window */ 75217309Snwhitehorn WINDOW *win; /* this window */ 76217309Snwhitehorn int length; /* length of the data[] array */ 77217309Snwhitehorn int offset; /* index of first item on screen */ 78217309Snwhitehorn int choice; /* index of the selection */ 79217309Snwhitehorn int mousex; /* base of mouse-code return-values */ 80217309Snwhitehorn unsigned allocd; 81217309Snwhitehorn char **data; 82217309Snwhitehorn} LIST; 83217309Snwhitehorn 84217309Snwhitehorntypedef struct { 85217309Snwhitehorn int length; 86217309Snwhitehorn char **data; 87217309Snwhitehorn} MATCH; 88217309Snwhitehorn 89217309Snwhitehornstatic void 90217309Snwhitehorninit_list(LIST * list, WINDOW *par, WINDOW *win, int mousex) 91217309Snwhitehorn{ 92217309Snwhitehorn list->par = par; 93217309Snwhitehorn list->win = win; 94217309Snwhitehorn list->length = 0; 95217309Snwhitehorn list->offset = 0; 96217309Snwhitehorn list->choice = 0; 97217309Snwhitehorn list->mousex = mousex; 98217309Snwhitehorn list->allocd = 0; 99217309Snwhitehorn list->data = 0; 100217309Snwhitehorn dlg_mouse_mkbigregion(getbegy(win), getbegx(win), 101217309Snwhitehorn getmaxy(win), getmaxx(win), 102217309Snwhitehorn mousex, 1, 1, 1 /* by lines */ ); 103217309Snwhitehorn} 104217309Snwhitehorn 105217309Snwhitehornstatic char * 106217309Snwhitehornleaf_of(char *path) 107217309Snwhitehorn{ 108217309Snwhitehorn char *leaf = strrchr(path, '/'); 109217309Snwhitehorn if (leaf != 0) 110217309Snwhitehorn leaf++; 111217309Snwhitehorn else 112217309Snwhitehorn leaf = path; 113217309Snwhitehorn return leaf; 114217309Snwhitehorn} 115217309Snwhitehorn 116217309Snwhitehornstatic char * 117217309Snwhitehorndata_of(LIST * list) 118217309Snwhitehorn{ 119217309Snwhitehorn if (list != 0 120217309Snwhitehorn && list->data != 0) 121217309Snwhitehorn return list->data[list->choice]; 122217309Snwhitehorn return 0; 123217309Snwhitehorn} 124217309Snwhitehorn 125217309Snwhitehornstatic void 126217309Snwhitehornfree_list(LIST * list, int reinit) 127217309Snwhitehorn{ 128217309Snwhitehorn int n; 129217309Snwhitehorn 130217309Snwhitehorn if (list->data != 0) { 131217309Snwhitehorn for (n = 0; list->data[n] != 0; n++) 132217309Snwhitehorn free(list->data[n]); 133217309Snwhitehorn free(list->data); 134217309Snwhitehorn list->data = 0; 135217309Snwhitehorn } 136217309Snwhitehorn if (reinit) 137217309Snwhitehorn init_list(list, list->par, list->win, list->mousex); 138217309Snwhitehorn} 139217309Snwhitehorn 140217309Snwhitehornstatic void 141217309Snwhitehornadd_to_list(LIST * list, char *text) 142217309Snwhitehorn{ 143217309Snwhitehorn unsigned need; 144217309Snwhitehorn 145217309Snwhitehorn need = (unsigned) (list->length + 1); 146217309Snwhitehorn if (need + 1 > list->allocd) { 147217309Snwhitehorn list->allocd = 2 * (need + 1); 148217309Snwhitehorn if (list->data == 0) { 149217309Snwhitehorn list->data = dlg_malloc(char *, list->allocd); 150217309Snwhitehorn } else { 151217309Snwhitehorn list->data = dlg_realloc(char *, list->allocd, list->data); 152217309Snwhitehorn } 153217309Snwhitehorn assert_ptr(list->data, "add_to_list"); 154217309Snwhitehorn } 155217309Snwhitehorn list->data[list->length++] = dlg_strclone(text); 156217309Snwhitehorn list->data[list->length] = 0; 157217309Snwhitehorn} 158217309Snwhitehorn 159217309Snwhitehornstatic void 160217309Snwhitehornkeep_visible(LIST * list) 161217309Snwhitehorn{ 162217309Snwhitehorn int high = getmaxy(list->win); 163217309Snwhitehorn 164217309Snwhitehorn if (list->choice < list->offset) { 165217309Snwhitehorn list->offset = list->choice; 166217309Snwhitehorn } 167217309Snwhitehorn if (list->choice - list->offset >= high) 168217309Snwhitehorn list->offset = list->choice - high + 1; 169217309Snwhitehorn} 170217309Snwhitehorn 171217309Snwhitehorn#define Value(c) (int)((c) & 0xff) 172217309Snwhitehorn 173217309Snwhitehornstatic int 174217309Snwhitehornfind_choice(char *target, LIST * list) 175217309Snwhitehorn{ 176217309Snwhitehorn int n; 177217309Snwhitehorn int choice = list->choice; 178217309Snwhitehorn int len_1, len_2, cmp_1, cmp_2; 179217309Snwhitehorn 180217309Snwhitehorn if (*target == 0) { 181217309Snwhitehorn list->choice = 0; 182217309Snwhitehorn } else { 183217309Snwhitehorn /* find the match with the longest length. If more than one has the 184217309Snwhitehorn * same length, choose the one with the closest match of the final 185217309Snwhitehorn * character. 186217309Snwhitehorn */ 187217309Snwhitehorn len_1 = 0; 188217309Snwhitehorn cmp_1 = 256; 189217309Snwhitehorn for (n = 0; n < list->length; n++) { 190217309Snwhitehorn char *a = target; 191217309Snwhitehorn char *b = list->data[n]; 192217309Snwhitehorn 193217309Snwhitehorn len_2 = 0; 194217309Snwhitehorn while ((*a != 0) && (*b != 0) && (*a == *b)) { 195217309Snwhitehorn a++; 196217309Snwhitehorn b++; 197217309Snwhitehorn len_2++; 198217309Snwhitehorn } 199217309Snwhitehorn cmp_2 = Value(*a) - Value(*b); 200217309Snwhitehorn if (cmp_2 < 0) 201217309Snwhitehorn cmp_2 = -cmp_2; 202217309Snwhitehorn if ((len_2 > len_1) 203217309Snwhitehorn || (len_1 == len_2 && cmp_2 < cmp_1)) { 204217309Snwhitehorn len_1 = len_2; 205217309Snwhitehorn cmp_1 = cmp_2; 206217309Snwhitehorn list->choice = n; 207217309Snwhitehorn } 208217309Snwhitehorn } 209217309Snwhitehorn } 210217309Snwhitehorn if (choice != list->choice) { 211217309Snwhitehorn keep_visible(list); 212217309Snwhitehorn } 213217309Snwhitehorn return (choice != list->choice); 214217309Snwhitehorn} 215217309Snwhitehorn 216217309Snwhitehornstatic void 217217309Snwhitehorndisplay_list(LIST * list) 218217309Snwhitehorn{ 219217309Snwhitehorn int n; 220217309Snwhitehorn int x; 221217309Snwhitehorn int y; 222217309Snwhitehorn int top; 223217309Snwhitehorn int bottom; 224217309Snwhitehorn 225217309Snwhitehorn if (list->win != 0) { 226217309Snwhitehorn dlg_attr_clear(list->win, getmaxy(list->win), getmaxx(list->win), item_attr); 227217309Snwhitehorn for (n = list->offset; n < list->length && list->data[n]; n++) { 228217309Snwhitehorn y = n - list->offset; 229217309Snwhitehorn if (y >= getmaxy(list->win)) 230217309Snwhitehorn break; 231217309Snwhitehorn (void) wmove(list->win, y, 0); 232217309Snwhitehorn if (n == list->choice) 233251843Sbapt (void) wattrset(list->win, item_selected_attr); 234217309Snwhitehorn (void) waddstr(list->win, list->data[n]); 235251843Sbapt (void) wattrset(list->win, item_attr); 236217309Snwhitehorn } 237251843Sbapt (void) wattrset(list->win, item_attr); 238217309Snwhitehorn 239217309Snwhitehorn getparyx(list->win, y, x); 240217309Snwhitehorn 241217309Snwhitehorn top = y - 1; 242217309Snwhitehorn bottom = y + getmaxy(list->win); 243217309Snwhitehorn dlg_draw_scrollbar(list->par, 244220749Snwhitehorn (long) list->offset, 245220749Snwhitehorn (long) list->offset, 246220749Snwhitehorn (long) (list->offset + getmaxy(list->win)), 247220749Snwhitehorn (long) (list->length), 248217309Snwhitehorn x + 1, 249217309Snwhitehorn x + getmaxx(list->win), 250217309Snwhitehorn top, 251217309Snwhitehorn bottom, 252251843Sbapt menubox_border2_attr, 253217309Snwhitehorn menubox_border_attr); 254217309Snwhitehorn 255217309Snwhitehorn (void) wmove(list->win, list->choice - list->offset, 0); 256217309Snwhitehorn (void) wnoutrefresh(list->win); 257217309Snwhitehorn } 258217309Snwhitehorn} 259217309Snwhitehorn 260217309Snwhitehorn/* FIXME: see arrows.c 261217309Snwhitehorn * This workaround is used to allow two lists to have scroll-tabs at the same 262217309Snwhitehorn * time, by reassigning their return-values to be different. Just for 263217309Snwhitehorn * readability, we use the names of keys with similar connotations, though all 264217309Snwhitehorn * that is really required is that they're distinct, so we can put them in a 265217309Snwhitehorn * switch statement. 266217309Snwhitehorn */ 267217309Snwhitehornstatic void 268217309Snwhitehornfix_arrows(LIST * list) 269217309Snwhitehorn{ 270217309Snwhitehorn int x; 271217309Snwhitehorn int y; 272217309Snwhitehorn int top; 273251843Sbapt int right; 274217309Snwhitehorn int bottom; 275217309Snwhitehorn 276217309Snwhitehorn if (list->win != 0) { 277217309Snwhitehorn getparyx(list->win, y, x); 278217309Snwhitehorn top = y - 1; 279251843Sbapt right = getmaxx(list->win); 280217309Snwhitehorn bottom = y + getmaxy(list->win); 281217309Snwhitehorn 282251843Sbapt mouse_mkbutton(top, x, right, 283217309Snwhitehorn ((list->mousex == MOUSE_D) 284217309Snwhitehorn ? KEY_PREVIOUS 285217309Snwhitehorn : KEY_PPAGE)); 286251843Sbapt mouse_mkbutton(bottom, x, right, 287217309Snwhitehorn ((list->mousex == MOUSE_D) 288217309Snwhitehorn ? KEY_NEXT 289217309Snwhitehorn : KEY_NPAGE)); 290217309Snwhitehorn } 291217309Snwhitehorn} 292217309Snwhitehorn 293217309Snwhitehornstatic int 294220749Snwhitehornshow_list(char *target, LIST * list, int keep) 295217309Snwhitehorn{ 296217309Snwhitehorn int changed = keep || find_choice(target, list); 297217309Snwhitehorn display_list(list); 298217309Snwhitehorn return changed; 299217309Snwhitehorn} 300217309Snwhitehorn 301217309Snwhitehorn/* 302217309Snwhitehorn * Highlight the closest match to 'target' in the given list, setting offset 303217309Snwhitehorn * to match. 304217309Snwhitehorn */ 305217309Snwhitehornstatic int 306220749Snwhitehornshow_both_lists(char *input, LIST * d_list, LIST * f_list, int keep) 307217309Snwhitehorn{ 308217309Snwhitehorn char *leaf = leaf_of(input); 309217309Snwhitehorn 310217309Snwhitehorn return show_list(leaf, d_list, keep) | show_list(leaf, f_list, keep); 311217309Snwhitehorn} 312217309Snwhitehorn 313217309Snwhitehorn/* 314217309Snwhitehorn * Move up/down in the given list 315217309Snwhitehorn */ 316217309Snwhitehornstatic bool 317217309Snwhitehornchange_list(int choice, LIST * list) 318217309Snwhitehorn{ 319217309Snwhitehorn if (data_of(list) != 0) { 320217309Snwhitehorn int last = list->length - 1; 321217309Snwhitehorn 322217309Snwhitehorn choice += list->choice; 323217309Snwhitehorn if (choice < 0) 324217309Snwhitehorn choice = 0; 325217309Snwhitehorn if (choice > last) 326217309Snwhitehorn choice = last; 327217309Snwhitehorn list->choice = choice; 328217309Snwhitehorn keep_visible(list); 329217309Snwhitehorn display_list(list); 330217309Snwhitehorn return TRUE; 331217309Snwhitehorn } 332217309Snwhitehorn return FALSE; 333217309Snwhitehorn} 334217309Snwhitehorn 335217309Snwhitehornstatic void 336217309Snwhitehornscroll_list(int direction, LIST * list) 337217309Snwhitehorn{ 338217309Snwhitehorn if (data_of(list) != 0) { 339217309Snwhitehorn int length = getmaxy(list->win); 340217309Snwhitehorn if (change_list(direction * length, list)) 341217309Snwhitehorn return; 342217309Snwhitehorn } 343217309Snwhitehorn beep(); 344217309Snwhitehorn} 345217309Snwhitehorn 346217309Snwhitehornstatic int 347217309Snwhitehorncompar(const void *a, const void *b) 348217309Snwhitehorn{ 349217309Snwhitehorn return strcmp(*(const char *const *) a, *(const char *const *) b); 350217309Snwhitehorn} 351217309Snwhitehorn 352217309Snwhitehornstatic void 353217309Snwhitehornmatch(char *name, LIST * d_list, LIST * f_list, MATCH * match_list) 354217309Snwhitehorn{ 355217309Snwhitehorn char *test = leaf_of(name); 356217309Snwhitehorn size_t test_len = strlen(test); 357217309Snwhitehorn char **matches = dlg_malloc(char *, (size_t) (d_list->length + f_list->length)); 358217309Snwhitehorn size_t data_len = 0; 359217309Snwhitehorn int i; 360217309Snwhitehorn for (i = 2; i < d_list->length; i++) { 361217309Snwhitehorn if (strncmp(test, d_list->data[i], test_len) == 0) { 362217309Snwhitehorn matches[data_len++] = d_list->data[i]; 363217309Snwhitehorn } 364217309Snwhitehorn } 365217309Snwhitehorn for (i = 0; i < f_list->length; i++) { 366217309Snwhitehorn if (strncmp(test, f_list->data[i], test_len) == 0) { 367217309Snwhitehorn matches[data_len++] = f_list->data[i]; 368217309Snwhitehorn } 369217309Snwhitehorn } 370251843Sbapt matches = dlg_realloc(char *, data_len + 1, matches); 371217309Snwhitehorn match_list->data = matches; 372217309Snwhitehorn match_list->length = (int) data_len; 373217309Snwhitehorn} 374217309Snwhitehorn 375217309Snwhitehornstatic void 376217309Snwhitehornfree_match(MATCH * match_list) 377217309Snwhitehorn{ 378217309Snwhitehorn free(match_list->data); 379217309Snwhitehorn match_list->length = 0; 380217309Snwhitehorn} 381217309Snwhitehorn 382217309Snwhitehornstatic int 383217309Snwhitehorncomplete(char *name, LIST * d_list, LIST * f_list, char **buff_ptr) 384217309Snwhitehorn{ 385217309Snwhitehorn MATCH match_list; 386217309Snwhitehorn char *test; 387217309Snwhitehorn size_t test_len; 388217309Snwhitehorn size_t i; 389217309Snwhitehorn int j; 390217309Snwhitehorn char *buff; 391217309Snwhitehorn 392217309Snwhitehorn match(name, d_list, f_list, &match_list); 393217309Snwhitehorn if (match_list.length == 0) { 394217309Snwhitehorn *buff_ptr = NULL; 395217309Snwhitehorn return 0; 396217309Snwhitehorn } 397217309Snwhitehorn 398217309Snwhitehorn test = match_list.data[0]; 399217309Snwhitehorn test_len = strlen(test); 400217309Snwhitehorn buff = dlg_malloc(char, test_len + 2); 401217309Snwhitehorn if (match_list.length == 1) { 402217309Snwhitehorn strcpy(buff, test); 403217309Snwhitehorn i = test_len; 404217309Snwhitehorn if (test == data_of(d_list)) { 405217309Snwhitehorn buff[test_len] = '/'; 406217309Snwhitehorn i++; 407217309Snwhitehorn } 408217309Snwhitehorn } else { 409217309Snwhitehorn for (i = 0; i < test_len; i++) { 410217309Snwhitehorn char test_char = test[i]; 411217309Snwhitehorn if (test_char == '\0') 412217309Snwhitehorn break; 413217309Snwhitehorn for (j = 0; j < match_list.length; j++) { 414217309Snwhitehorn if (match_list.data[j][i] != test_char) { 415217309Snwhitehorn break; 416217309Snwhitehorn } 417217309Snwhitehorn } 418217309Snwhitehorn if (j == match_list.length) { 419217309Snwhitehorn (buff)[i] = test_char; 420217309Snwhitehorn } else 421217309Snwhitehorn break; 422217309Snwhitehorn } 423217309Snwhitehorn buff = dlg_realloc(char, i + 1, buff); 424217309Snwhitehorn } 425217309Snwhitehorn free_match(&match_list); 426217309Snwhitehorn buff[i] = '\0'; 427217309Snwhitehorn *buff_ptr = buff; 428217309Snwhitehorn return (i != 0); 429217309Snwhitehorn} 430217309Snwhitehorn 431217309Snwhitehornstatic bool 432220749Snwhitehornfill_lists(char *current, char *input, LIST * d_list, LIST * f_list, int keep) 433217309Snwhitehorn{ 434251843Sbapt bool result = TRUE; 435251843Sbapt bool rescan = FALSE; 436217309Snwhitehorn DIR *dp; 437217309Snwhitehorn DIRENT *de; 438217309Snwhitehorn struct stat sb; 439217309Snwhitehorn int n; 440217309Snwhitehorn char path[MAX_LEN + 1]; 441217309Snwhitehorn char *leaf; 442217309Snwhitehorn 443217309Snwhitehorn /* check if we've updated the lists */ 444217309Snwhitehorn for (n = 0; current[n] && input[n]; n++) { 445217309Snwhitehorn if (current[n] != input[n]) 446217309Snwhitehorn break; 447217309Snwhitehorn } 448251843Sbapt 449251843Sbapt if (current[n] == input[n]) { 450251843Sbapt result = FALSE; 451251843Sbapt rescan = (n == 0 && d_list->length == 0); 452251843Sbapt } else if (strchr(current + n, '/') == 0 453251843Sbapt && strchr(input + n, '/') == 0) { 454251843Sbapt result = show_both_lists(input, d_list, f_list, keep); 455251843Sbapt } else { 456251843Sbapt rescan = TRUE; 457244850Snwhitehorn } 458241818Snwhitehorn 459251843Sbapt if (rescan) { 460251843Sbapt size_t have = strlen(input); 461244850Snwhitehorn 462251843Sbapt if (have > MAX_LEN) 463251843Sbapt have = MAX_LEN; 464251843Sbapt memcpy(current, input, have); 465251843Sbapt current[have] = '\0'; 466251843Sbapt 467251843Sbapt /* refill the lists */ 468251843Sbapt free_list(d_list, TRUE); 469251843Sbapt free_list(f_list, TRUE); 470251843Sbapt memcpy(path, current, have); 471251843Sbapt path[have] = '\0'; 472251843Sbapt if ((leaf = strrchr(path, '/')) != 0) { 473251843Sbapt *++leaf = 0; 474251843Sbapt } else { 475251843Sbapt strcpy(path, "./"); 476251843Sbapt leaf = path + strlen(path); 477251843Sbapt } 478251843Sbapt dlg_trace_msg("opendir '%s'\n", path); 479251843Sbapt if ((dp = opendir(path)) != 0) { 480251843Sbapt while ((de = readdir(dp)) != 0) { 481251843Sbapt strncpy(leaf, de->d_name, NAMLEN(de))[NAMLEN(de)] = 0; 482251843Sbapt if (stat(path, &sb) == 0) { 483251843Sbapt if ((sb.st_mode & S_IFMT) == S_IFDIR) 484251843Sbapt add_to_list(d_list, leaf); 485251843Sbapt else if (f_list->win) 486251843Sbapt add_to_list(f_list, leaf); 487251843Sbapt } 488217309Snwhitehorn } 489251843Sbapt (void) closedir(dp); 490251843Sbapt /* sort the lists */ 491251843Sbapt if (d_list->data != 0 && d_list->length > 1) { 492251843Sbapt qsort(d_list->data, 493251843Sbapt (size_t) d_list->length, 494251843Sbapt sizeof(d_list->data[0]), 495251843Sbapt compar); 496251843Sbapt } 497251843Sbapt if (f_list->data != 0 && f_list->length > 1) { 498251843Sbapt qsort(f_list->data, 499251843Sbapt (size_t) f_list->length, 500251843Sbapt sizeof(f_list->data[0]), 501251843Sbapt compar); 502251843Sbapt } 503217309Snwhitehorn } 504251843Sbapt 505251843Sbapt (void) show_both_lists(input, d_list, f_list, FALSE); 506251843Sbapt d_list->offset = d_list->choice; 507251843Sbapt f_list->offset = f_list->choice; 508251843Sbapt result = TRUE; 509244850Snwhitehorn } 510251843Sbapt return result; 511217309Snwhitehorn} 512217309Snwhitehorn 513217309Snwhitehornstatic bool 514217309Snwhitehornusable_state(int state, LIST * dirs, LIST * files) 515217309Snwhitehorn{ 516217309Snwhitehorn bool result; 517217309Snwhitehorn 518217309Snwhitehorn switch (state) { 519217309Snwhitehorn case sDIRS: 520217309Snwhitehorn result = (dirs->win != 0) && (data_of(dirs) != 0); 521217309Snwhitehorn break; 522217309Snwhitehorn case sFILES: 523217309Snwhitehorn result = (files->win != 0) && (data_of(files) != 0); 524217309Snwhitehorn break; 525217309Snwhitehorn default: 526217309Snwhitehorn result = TRUE; 527217309Snwhitehorn break; 528217309Snwhitehorn } 529217309Snwhitehorn return result; 530217309Snwhitehorn} 531217309Snwhitehorn 532217309Snwhitehorn#define which_list() ((state == sFILES) \ 533217309Snwhitehorn ? &f_list \ 534217309Snwhitehorn : ((state == sDIRS) \ 535217309Snwhitehorn ? &d_list \ 536217309Snwhitehorn : 0)) 537217309Snwhitehorn#define NAVIGATE_BINDINGS \ 538217309Snwhitehorn DLG_KEYS_DATA( DLGK_FIELD_NEXT, KEY_RIGHT ), \ 539217309Snwhitehorn DLG_KEYS_DATA( DLGK_FIELD_NEXT, TAB ), \ 540217309Snwhitehorn DLG_KEYS_DATA( DLGK_FIELD_PREV, KEY_BTAB ), \ 541217309Snwhitehorn DLG_KEYS_DATA( DLGK_ITEM_NEXT, KEY_DOWN ), \ 542217309Snwhitehorn DLG_KEYS_DATA( DLGK_ITEM_NEXT, CHR_NEXT ), \ 543217309Snwhitehorn DLG_KEYS_DATA( DLGK_ITEM_NEXT, KEY_NEXT ), \ 544217309Snwhitehorn DLG_KEYS_DATA( DLGK_ITEM_PREV, CHR_PREVIOUS ), \ 545217309Snwhitehorn DLG_KEYS_DATA( DLGK_ITEM_PREV, KEY_UP ), \ 546217309Snwhitehorn DLG_KEYS_DATA( DLGK_PAGE_NEXT, KEY_NPAGE ), \ 547217309Snwhitehorn DLG_KEYS_DATA( DLGK_PAGE_PREV, KEY_PPAGE ) 548217309Snwhitehorn 549217309Snwhitehorn/* 550217309Snwhitehorn * Display a dialog box for entering a filename 551217309Snwhitehorn */ 552217309Snwhitehornstatic int 553220749Snwhitehorndlg_fselect(const char *title, const char *path, int height, int width, int dselect) 554217309Snwhitehorn{ 555217309Snwhitehorn /* *INDENT-OFF* */ 556217309Snwhitehorn static DLG_KEYS_BINDING binding[] = { 557224014Snwhitehorn HELPKEY_BINDINGS, 558217309Snwhitehorn ENTERKEY_BINDINGS, 559217309Snwhitehorn NAVIGATE_BINDINGS, 560217309Snwhitehorn END_KEYS_BINDING 561217309Snwhitehorn }; 562217309Snwhitehorn static DLG_KEYS_BINDING binding2[] = { 563217309Snwhitehorn INPUTSTR_BINDINGS, 564224014Snwhitehorn HELPKEY_BINDINGS, 565217309Snwhitehorn ENTERKEY_BINDINGS, 566217309Snwhitehorn NAVIGATE_BINDINGS, 567217309Snwhitehorn END_KEYS_BINDING 568217309Snwhitehorn }; 569217309Snwhitehorn /* *INDENT-ON* */ 570217309Snwhitehorn 571217309Snwhitehorn#ifdef KEY_RESIZE 572217309Snwhitehorn int old_height = height; 573217309Snwhitehorn int old_width = width; 574217309Snwhitehorn bool resized = FALSE; 575217309Snwhitehorn#endif 576217309Snwhitehorn int tbox_y, tbox_x, tbox_width, tbox_height; 577217309Snwhitehorn int dbox_y, dbox_x, dbox_width, dbox_height; 578217309Snwhitehorn int fbox_y, fbox_x, fbox_width, fbox_height; 579217309Snwhitehorn int show_buttons = TRUE; 580217309Snwhitehorn int offset = 0; 581217309Snwhitehorn int key = 0; 582217309Snwhitehorn int fkey = FALSE; 583217309Snwhitehorn int code; 584217309Snwhitehorn int result = DLG_EXIT_UNKNOWN; 585251843Sbapt int state = dialog_vars.default_button >= 0 ? dlg_default_button() : sTEXT; 586251843Sbapt int button; 587217309Snwhitehorn int first = (state == sTEXT); 588251843Sbapt int first_trace = TRUE; 589217309Snwhitehorn char *input; 590217309Snwhitehorn char *completed; 591217309Snwhitehorn char current[MAX_LEN + 1]; 592217309Snwhitehorn WINDOW *dialog = 0; 593217309Snwhitehorn WINDOW *w_text = 0; 594217309Snwhitehorn WINDOW *w_work = 0; 595217309Snwhitehorn const char **buttons = dlg_ok_labels(); 596220749Snwhitehorn const char *d_label = _("Directories"); 597220749Snwhitehorn const char *f_label = _("Files"); 598251843Sbapt char *partial = 0; 599217309Snwhitehorn int min_wide = MIN_WIDE; 600217309Snwhitehorn int min_items = height ? 0 : 4; 601217309Snwhitehorn LIST d_list, f_list; 602217309Snwhitehorn 603217309Snwhitehorn dlg_does_output(); 604217309Snwhitehorn 605217309Snwhitehorn /* Set up the initial value */ 606217309Snwhitehorn input = dlg_set_result(path); 607217309Snwhitehorn offset = (int) strlen(input); 608217309Snwhitehorn *current = 0; 609217309Snwhitehorn 610217309Snwhitehorn dlg_button_layout(buttons, &min_wide); 611217309Snwhitehorn 612217309Snwhitehorn#ifdef KEY_RESIZE 613217309Snwhitehorn retry: 614217309Snwhitehorn#endif 615217309Snwhitehorn dlg_auto_size(title, (char *) 0, &height, &width, 6, 25); 616217309Snwhitehorn height += MIN_HIGH + min_items; 617217309Snwhitehorn if (width < min_wide) 618217309Snwhitehorn width = min_wide; 619217309Snwhitehorn dlg_print_size(height, width); 620217309Snwhitehorn dlg_ctl_size(height, width); 621217309Snwhitehorn 622217309Snwhitehorn dialog = dlg_new_window(height, width, 623217309Snwhitehorn dlg_box_y_ordinate(height), 624217309Snwhitehorn dlg_box_x_ordinate(width)); 625217309Snwhitehorn dlg_register_window(dialog, "fselect", binding); 626217309Snwhitehorn dlg_register_buttons(dialog, "fselect", buttons); 627217309Snwhitehorn 628217309Snwhitehorn dlg_mouse_setbase(0, 0); 629217309Snwhitehorn 630251843Sbapt dlg_draw_box2(dialog, 0, 0, height, width, dialog_attr, border_attr, border2_attr); 631251843Sbapt dlg_draw_bottom_box2(dialog, border_attr, border2_attr, dialog_attr); 632217309Snwhitehorn dlg_draw_title(dialog, title); 633217309Snwhitehorn 634251843Sbapt (void) wattrset(dialog, dialog_attr); 635217309Snwhitehorn 636217309Snwhitehorn /* Draw the input field box */ 637217309Snwhitehorn tbox_height = 1; 638217309Snwhitehorn tbox_width = width - (4 * MARGIN + 2); 639217309Snwhitehorn tbox_y = height - (BTN_HIGH * 2) + MARGIN; 640217309Snwhitehorn tbox_x = (width - tbox_width) / 2; 641217309Snwhitehorn 642217309Snwhitehorn w_text = derwin(dialog, tbox_height, tbox_width, tbox_y, tbox_x); 643251843Sbapt if (w_text == 0) { 644251843Sbapt result = DLG_EXIT_ERROR; 645251843Sbapt goto finish; 646251843Sbapt } 647217309Snwhitehorn 648217309Snwhitehorn (void) keypad(w_text, TRUE); 649217309Snwhitehorn dlg_draw_box(dialog, tbox_y - MARGIN, tbox_x - MARGIN, 650217309Snwhitehorn (2 * MARGIN + 1), tbox_width + (MARGIN + EXT_WIDE), 651251843Sbapt menubox_border_attr, menubox_border2_attr); 652217309Snwhitehorn dlg_mouse_mkbigregion(getbegy(dialog) + tbox_y - MARGIN, 653217309Snwhitehorn getbegx(dialog) + tbox_x - MARGIN, 654217309Snwhitehorn 1 + (2 * MARGIN), 655217309Snwhitehorn tbox_width + (MARGIN + EXT_WIDE), 656217309Snwhitehorn MOUSE_T, 1, 1, 3 /* doesn't matter */ ); 657217309Snwhitehorn 658251843Sbapt dlg_register_window(w_text, "fselect2", binding2); 659217309Snwhitehorn 660217309Snwhitehorn /* Draw the directory listing box */ 661217309Snwhitehorn if (dselect) 662217309Snwhitehorn dbox_width = (width - (6 * MARGIN)); 663217309Snwhitehorn else 664217309Snwhitehorn dbox_width = (width - (6 * MARGIN + 2 * EXT_WIDE)) / 2; 665217309Snwhitehorn dbox_height = height - MIN_HIGH; 666217309Snwhitehorn dbox_y = (2 * MARGIN + 1); 667217309Snwhitehorn dbox_x = tbox_x; 668217309Snwhitehorn 669217309Snwhitehorn w_work = derwin(dialog, dbox_height, dbox_width, dbox_y, dbox_x); 670251843Sbapt if (w_work == 0) { 671251843Sbapt result = DLG_EXIT_ERROR; 672251843Sbapt goto finish; 673251843Sbapt } 674217309Snwhitehorn 675217309Snwhitehorn (void) keypad(w_work, TRUE); 676251843Sbapt (void) mvwaddstr(dialog, dbox_y - (MARGIN + 1), dbox_x - MARGIN, d_label); 677217309Snwhitehorn dlg_draw_box(dialog, 678217309Snwhitehorn dbox_y - MARGIN, dbox_x - MARGIN, 679217309Snwhitehorn dbox_height + (MARGIN + 1), dbox_width + (MARGIN + 1), 680251843Sbapt menubox_border_attr, menubox_border2_attr); 681217309Snwhitehorn init_list(&d_list, dialog, w_work, MOUSE_D); 682217309Snwhitehorn 683217309Snwhitehorn if (!dselect) { 684217309Snwhitehorn /* Draw the filename listing box */ 685217309Snwhitehorn fbox_height = dbox_height; 686217309Snwhitehorn fbox_width = dbox_width; 687217309Snwhitehorn fbox_y = dbox_y; 688217309Snwhitehorn fbox_x = tbox_x + dbox_width + (2 * MARGIN); 689217309Snwhitehorn 690217309Snwhitehorn w_work = derwin(dialog, fbox_height, fbox_width, fbox_y, fbox_x); 691251843Sbapt if (w_work == 0) { 692251843Sbapt result = DLG_EXIT_ERROR; 693251843Sbapt goto finish; 694251843Sbapt } 695217309Snwhitehorn 696217309Snwhitehorn (void) keypad(w_work, TRUE); 697251843Sbapt (void) mvwaddstr(dialog, fbox_y - (MARGIN + 1), fbox_x - MARGIN, f_label); 698217309Snwhitehorn dlg_draw_box(dialog, 699217309Snwhitehorn fbox_y - MARGIN, fbox_x - MARGIN, 700217309Snwhitehorn fbox_height + (MARGIN + 1), fbox_width + (MARGIN + 1), 701251843Sbapt menubox_border_attr, menubox_border2_attr); 702217309Snwhitehorn init_list(&f_list, dialog, w_work, MOUSE_F); 703217309Snwhitehorn } else { 704217309Snwhitehorn memset(&f_list, 0, sizeof(f_list)); 705217309Snwhitehorn } 706217309Snwhitehorn 707217309Snwhitehorn while (result == DLG_EXIT_UNKNOWN) { 708217309Snwhitehorn 709217309Snwhitehorn if (fill_lists(current, input, &d_list, &f_list, state < sTEXT)) 710217309Snwhitehorn show_buttons = TRUE; 711217309Snwhitehorn 712217309Snwhitehorn#ifdef KEY_RESIZE 713217309Snwhitehorn if (resized) { 714217309Snwhitehorn resized = FALSE; 715217309Snwhitehorn dlg_show_string(w_text, input, offset, inputbox_attr, 716220749Snwhitehorn 0, 0, tbox_width, (bool) 0, (bool) first); 717217309Snwhitehorn } 718217309Snwhitehorn#endif 719217309Snwhitehorn 720217309Snwhitehorn /* 721217309Snwhitehorn * The last field drawn determines where the cursor is shown: 722217309Snwhitehorn */ 723217309Snwhitehorn if (show_buttons) { 724217309Snwhitehorn show_buttons = FALSE; 725217309Snwhitehorn button = (state < 0) ? 0 : state; 726217309Snwhitehorn dlg_draw_buttons(dialog, height - 2, 0, buttons, button, FALSE, width); 727217309Snwhitehorn } 728251843Sbapt 729251843Sbapt if (first_trace) { 730251843Sbapt first_trace = FALSE; 731251843Sbapt dlg_trace_win(dialog); 732251843Sbapt } 733251843Sbapt 734217309Snwhitehorn if (state < 0) { 735217309Snwhitehorn switch (state) { 736217309Snwhitehorn case sTEXT: 737217309Snwhitehorn dlg_set_focus(dialog, w_text); 738217309Snwhitehorn break; 739217309Snwhitehorn case sFILES: 740217309Snwhitehorn dlg_set_focus(dialog, f_list.win); 741217309Snwhitehorn break; 742217309Snwhitehorn case sDIRS: 743217309Snwhitehorn dlg_set_focus(dialog, d_list.win); 744217309Snwhitehorn break; 745217309Snwhitehorn } 746217309Snwhitehorn } 747217309Snwhitehorn 748217309Snwhitehorn if (first) { 749217309Snwhitehorn (void) wrefresh(dialog); 750217309Snwhitehorn } else { 751217309Snwhitehorn fix_arrows(&d_list); 752217309Snwhitehorn fix_arrows(&f_list); 753217309Snwhitehorn key = dlg_mouse_wgetch((state == sTEXT) ? w_text : dialog, &fkey); 754217309Snwhitehorn if (dlg_result_key(key, fkey, &result)) 755217309Snwhitehorn break; 756217309Snwhitehorn } 757217309Snwhitehorn 758217309Snwhitehorn if (!fkey && key == ' ') { 759217309Snwhitehorn key = DLGK_SELECT; 760217309Snwhitehorn fkey = TRUE; 761217309Snwhitehorn } 762217309Snwhitehorn 763217309Snwhitehorn if (fkey) { 764217309Snwhitehorn switch (key) { 765217309Snwhitehorn case DLGK_MOUSE(KEY_PREVIOUS): 766217309Snwhitehorn state = sDIRS; 767217309Snwhitehorn scroll_list(-1, which_list()); 768217309Snwhitehorn continue; 769217309Snwhitehorn case DLGK_MOUSE(KEY_NEXT): 770217309Snwhitehorn state = sDIRS; 771217309Snwhitehorn scroll_list(1, which_list()); 772217309Snwhitehorn continue; 773217309Snwhitehorn case DLGK_MOUSE(KEY_PPAGE): 774217309Snwhitehorn state = sFILES; 775217309Snwhitehorn scroll_list(-1, which_list()); 776217309Snwhitehorn continue; 777217309Snwhitehorn case DLGK_MOUSE(KEY_NPAGE): 778217309Snwhitehorn state = sFILES; 779217309Snwhitehorn scroll_list(1, which_list()); 780217309Snwhitehorn continue; 781217309Snwhitehorn case DLGK_PAGE_PREV: 782217309Snwhitehorn scroll_list(-1, which_list()); 783217309Snwhitehorn continue; 784217309Snwhitehorn case DLGK_PAGE_NEXT: 785217309Snwhitehorn scroll_list(1, which_list()); 786217309Snwhitehorn continue; 787217309Snwhitehorn case DLGK_ITEM_PREV: 788217309Snwhitehorn if (change_list(-1, which_list())) 789217309Snwhitehorn continue; 790217309Snwhitehorn /* FALLTHRU */ 791217309Snwhitehorn case DLGK_FIELD_PREV: 792217309Snwhitehorn show_buttons = TRUE; 793217309Snwhitehorn do { 794217309Snwhitehorn state = dlg_prev_ok_buttonindex(state, sDIRS); 795217309Snwhitehorn } while (!usable_state(state, &d_list, &f_list)); 796217309Snwhitehorn continue; 797217309Snwhitehorn case DLGK_ITEM_NEXT: 798217309Snwhitehorn if (change_list(1, which_list())) 799217309Snwhitehorn continue; 800217309Snwhitehorn /* FALLTHRU */ 801217309Snwhitehorn case DLGK_FIELD_NEXT: 802217309Snwhitehorn show_buttons = TRUE; 803217309Snwhitehorn do { 804217309Snwhitehorn state = dlg_next_ok_buttonindex(state, sDIRS); 805217309Snwhitehorn } while (!usable_state(state, &d_list, &f_list)); 806217309Snwhitehorn continue; 807217309Snwhitehorn case DLGK_SELECT: 808217309Snwhitehorn completed = 0; 809251843Sbapt if (partial != 0) { 810251843Sbapt free(partial); 811251843Sbapt partial = 0; 812251843Sbapt } 813217309Snwhitehorn if (state == sFILES && !dselect) { 814217309Snwhitehorn completed = data_of(&f_list); 815217309Snwhitehorn } else if (state == sDIRS) { 816217309Snwhitehorn completed = data_of(&d_list); 817217309Snwhitehorn } else { 818217309Snwhitehorn if (complete(input, &d_list, &f_list, &partial)) { 819217309Snwhitehorn completed = partial; 820217309Snwhitehorn } 821217309Snwhitehorn } 822217309Snwhitehorn if (completed != 0) { 823217309Snwhitehorn state = sTEXT; 824217309Snwhitehorn show_buttons = TRUE; 825217309Snwhitehorn strcpy(leaf_of(input), completed); 826217309Snwhitehorn offset = (int) strlen(input); 827217309Snwhitehorn dlg_show_string(w_text, input, offset, inputbox_attr, 828217309Snwhitehorn 0, 0, tbox_width, 0, first); 829251843Sbapt if (partial != NULL) { 830217309Snwhitehorn free(partial); 831251843Sbapt partial = 0; 832251843Sbapt } 833217309Snwhitehorn continue; 834217309Snwhitehorn } else { /* if (state < sTEXT) */ 835217309Snwhitehorn (void) beep(); 836217309Snwhitehorn continue; 837217309Snwhitehorn } 838217309Snwhitehorn /* FALLTHRU */ 839217309Snwhitehorn case DLGK_ENTER: 840224014Snwhitehorn result = (state > 0) ? dlg_enter_buttoncode(state) : DLG_EXIT_OK; 841217309Snwhitehorn continue; 842217309Snwhitehorn#ifdef KEY_RESIZE 843217309Snwhitehorn case KEY_RESIZE: 844217309Snwhitehorn /* reset data */ 845217309Snwhitehorn height = old_height; 846217309Snwhitehorn width = old_width; 847217309Snwhitehorn show_buttons = TRUE; 848217309Snwhitehorn *current = 0; 849217309Snwhitehorn resized = TRUE; 850217309Snwhitehorn /* repaint */ 851217309Snwhitehorn dlg_clear(); 852217309Snwhitehorn dlg_del_window(dialog); 853217309Snwhitehorn refresh(); 854217309Snwhitehorn dlg_mouse_free_regions(); 855217309Snwhitehorn goto retry; 856217309Snwhitehorn#endif 857217309Snwhitehorn default: 858217309Snwhitehorn if (key >= DLGK_MOUSE(MOUSE_T)) { 859217309Snwhitehorn state = sTEXT; 860217309Snwhitehorn continue; 861217309Snwhitehorn } else if (key >= DLGK_MOUSE(MOUSE_F)) { 862217309Snwhitehorn if (f_list.win != 0) { 863217309Snwhitehorn state = sFILES; 864217309Snwhitehorn f_list.choice = (key - DLGK_MOUSE(MOUSE_F)) + f_list.offset; 865217309Snwhitehorn display_list(&f_list); 866217309Snwhitehorn } 867217309Snwhitehorn continue; 868217309Snwhitehorn } else if (key >= DLGK_MOUSE(MOUSE_D)) { 869217309Snwhitehorn if (d_list.win != 0) { 870217309Snwhitehorn state = sDIRS; 871217309Snwhitehorn d_list.choice = (key - DLGK_MOUSE(MOUSE_D)) + d_list.offset; 872217309Snwhitehorn display_list(&d_list); 873217309Snwhitehorn } 874217309Snwhitehorn continue; 875217309Snwhitehorn } else if (is_DLGK_MOUSE(key) 876217309Snwhitehorn && (code = dlg_ok_buttoncode(key - M_EVENT)) >= 0) { 877217309Snwhitehorn result = code; 878217309Snwhitehorn continue; 879217309Snwhitehorn } 880217309Snwhitehorn break; 881217309Snwhitehorn } 882217309Snwhitehorn } 883217309Snwhitehorn 884217309Snwhitehorn if (state < 0) { /* Input box selected if we're editing */ 885217309Snwhitehorn int edit = dlg_edit_string(input, &offset, key, fkey, first); 886217309Snwhitehorn 887217309Snwhitehorn if (edit) { 888217309Snwhitehorn dlg_show_string(w_text, input, offset, inputbox_attr, 889217309Snwhitehorn 0, 0, tbox_width, 0, first); 890217309Snwhitehorn first = FALSE; 891217309Snwhitehorn state = sTEXT; 892217309Snwhitehorn } 893217309Snwhitehorn } else if (state >= 0 && 894217309Snwhitehorn (code = dlg_char_to_button(key, buttons)) >= 0) { 895217309Snwhitehorn result = dlg_ok_buttoncode(code); 896217309Snwhitehorn break; 897217309Snwhitehorn } 898217309Snwhitehorn } 899217309Snwhitehorn 900217309Snwhitehorn dlg_unregister_window(w_text); 901217309Snwhitehorn dlg_del_window(dialog); 902217309Snwhitehorn dlg_mouse_free_regions(); 903217309Snwhitehorn free_list(&d_list, FALSE); 904217309Snwhitehorn free_list(&f_list, FALSE); 905251843Sbapt 906251843Sbapt finish: 907251843Sbapt if (partial != 0) 908251843Sbapt free(partial); 909217309Snwhitehorn return result; 910217309Snwhitehorn} 911217309Snwhitehorn 912217309Snwhitehorn/* 913217309Snwhitehorn * Display a dialog box for entering a filename 914217309Snwhitehorn */ 915217309Snwhitehornint 916217309Snwhitehorndialog_fselect(const char *title, const char *path, int height, int width) 917217309Snwhitehorn{ 918217309Snwhitehorn return dlg_fselect(title, path, height, width, FALSE); 919217309Snwhitehorn} 920217309Snwhitehorn 921217309Snwhitehorn/* 922217309Snwhitehorn * Display a dialog box for entering a directory 923217309Snwhitehorn */ 924217309Snwhitehornint 925217309Snwhitehorndialog_dselect(const char *title, const char *path, int height, int width) 926217309Snwhitehorn{ 927217309Snwhitehorn return dlg_fselect(title, path, height, width, TRUE); 928217309Snwhitehorn} 929