[comp.sources.x] v09i086: xrolo -- an XView rolodex, Part03/03

luis@rice.edu (Luis Soltero) (10/11/90)

Submitted-by: Luis Soltero <luis@rice.edu>
Posting-number: Volume 9, Issue 86
Archive-name: xrolo/part03

#! /bin/sh
# This is a shell archive.  Remove anything before this line, then unpack
# it by saving it into a file and typing "sh file".  To overwrite existing
# files, type "sh file -c".  You can also feed this as standard input via
# unshar, or by typing "sh <file", e.g..  If this archive is complete, you
# will see the following message at the end:
#		"End of archive 3 (of 3)."
# Contents:  panel.c
# Wrapped by luis@oort on Wed Oct 10 15:56:19 1990
PATH=/bin:/usr/bin:/usr/ucb ; export PATH
if test -f 'panel.c' -a "${1}" != "-c" ; then 
  echo shar: Will not clobber existing file \"'panel.c'\"
else
echo shar: Extracting \"'panel.c'\" \(37272 characters\)
sed "s/^X//" >'panel.c' <<'END_OF_FILE'
X#ifndef lint
Xstatic char sccsid[] = "@(#)panel.c	2.3 8/16/88";
X#endif
X
X/*
X *	Stuff dealing with the panel
X */
X
X/*
X * -------------------------------------------------------------------------
X *	ROLO - A Sun Tool to implement a Rolodex-style list of notes
X *
X *	This code manipulates "cards" in a visual manner approximating
X *	a rolodex file.  All the cards are stored in one real file, the
X *	cards are seperated by a ^L (form-feed).  The default path
X *	name is $HOME/.rolo.  A different pathname may be specified at
X *	startup on the command line.  The pathname is relative to the
X *	user's home directory.
X *
X *	Due to bugs in the 3.0 distribution, especially with text subwindows,
X *	this code is only guaranteed to compile and run properly with 3.2
X *	or greater.
X *
X *	This code is public domain, anyone and everyone is welcome to it.
X *	All I ask is that my name and this notice remain on it.  If Sun would
X *	like to bundle it with their product they are welcome to do so,
X *	I only ask that the sources be included in the binary distribution.
X *
X *	Please return any fixes, improvements, gripes, etc to me.
X *
X *	Ron Hitchens		ronbo@vixen.uucp
X *	March 1987 (V1.0)	hitchens@cs.utexas.edu
X *	August 1988 (V2.0)
X * -------------------------------------------------------------------------
X */
X
X
X#include <stdio.h>
X#include <xview/xview.h>
X#include <xview/panel.h>
X#include <xview/textsw.h>
X#include <xview/seln.h>
X#include <sys/param.h>
X#include <ctype.h>
X
X#include "defs.h"
X#include "help.h"
X
X
X
X/* ------------------------------ Exports ---------------------------------- */
X
Xvoid			show_card (), set_slider_max ();
X
XNotify_value		rolo_destroy(), catch_resize();
X
X
X/* ------------------------------ Imports ---------------------------------- */
X
Xextern Textsw		rolocard;
X
Xextern struct card	*first, *last, *current;
X
Xextern int		need_save;
X
Xextern char		*rolofile;
X
Xextern struct card	*make_card (), *insert_card (), *undelete_card (),
X			*pop_card ();
X
Xextern Menu		gen_undelete (), gen_undelete_before();
X
Xextern Menu_item	check_stack ();
X
Xextern void		delete_card (), dispose_card (),
X			push_card (), dump_rolo (),
X			nuke_active_cards (), init_rolo (),
X			sort_cards (), set_stripe (),
X			read_rolo (), write_rolo ();
X
Xextern char		*first_char (), *index (), *re_comp (), *strcpy (),
X			*strncpy (), *strcat (), *sprintf (), *getenv ();
X
Xextern caddr_t		undel_menu_card ();
X
X
X/* ------------------------------ Locals ----------------------------------- */
X
Xstatic Panel_item	regex_item, slider_item;
X
Xstatic int		panel_height, panel_width;
X
Xstatic int		mask_from_menu_value (), value_from_mask (),
X			filename_ok ();
X
Xstatic void		next_button (), next_button_next(),  next_button_S_next(),
X  prev_button (), prev_button_prev(), prev_button_S_prev(),
X  new_button (), new_card_after(), new_card_before(),
X  delete_button (), delete_button_delete(), delete_button_undelete(),
X  delete_button_undelete_before(), 
X  file_button (), file_button_save(), file_button_reload(), file_button_sort(),
X  file_button_sort_backwards(), file_button_load(),
X  file_button_save_to_file(),
X
X  done_button (), done_n_save(), done_n_save_exit(), done_n_exit(),
X  find_button (), find_button_forward(), find_button_reverse(),
X
Xlist_button (), help_button (),
X  slider_proc (), button_event(), goto_card (),
X  no_comprendo ();
X
Xstatic char		*get_selection ();
X
X
X/* prev new next delete */
Xstatic u_short		buttons1_image [] = {
X#include "buttons1.icon"
X};
X
X/* drawer (file), "?" (help), flag (finished) and list */
Xstatic u_short		buttons2_image [] = {
X#include "buttons2.icon"
X};
X
X#undef pr_region
XServer_image pr_region(i_image, i_w, x, y, w, h)
Xchar *i_image;
X{
X	int i;
X	int wb = w/8;
X	int xb = x/8;
X    int i_wb = i_w/8;
X	char *image          = (char *)malloc(h*wb);
X	Server_image retval;
X
X	/* build the image */
X	for( i = 0; i < h; i++ ) {
X		bcopy(i_image + y*i_wb + xb + i*i_wb, image + i*wb, wb);
X	}
X	
X	retval = (Server_image)xv_create(NULL, SERVER_IMAGE, 
X									 XV_WIDTH, 32,
X									 XV_HEIGHT, 32,
X									 SERVER_IMAGE_BITS, image,
X									 NULL
X									 );
X
X	return(retval);
X}
X
X
X/* ------------------------------------------------------------------------- */
X
X
X
X/*
X *	Actually create the panel subwindow and all the items in it.
X */
Xstatic Panel	panel;
Xstatic Frame    frame;
XPanel init_panel (_frame)
X	Frame	_frame;
X{
X	int	panel_columns;
X	Menu tmpmenu;
X
X	frame = _frame;
X	panel = xv_create (frame, PANEL,
X		PANEL_LAYOUT,		PANEL_HORIZONTAL,
X/*		PANEL_EVENT_PROC,	button_event, */
X		PANEL_ITEM_X_GAP,	10,
X		PANEL_ITEM_Y_GAP,	9,
X		0);
X
X	/* 1st row */
X	/* next prev new delete list file help done */
X	tmpmenu = menu_create (
X						   MENU_ACTION_ITEM, "    Next Card  ", next_button_next,
X						   MENU_ACTION_ITEM, "(S) Last Card  ", next_button_S_next, 
X						   NULL, NULL);
X
X	(void) xv_create (panel, PANEL_BUTTON,
X		PANEL_LABEL_IMAGE,	pr_region(buttons1_image, 64, 0, 32, 32, 32),
X		PANEL_ITEM_MENU,	tmpmenu,
X		PANEL_NOTIFY_PROC,	next_button, 
X		XV_X, xv_col(panel, 0),
X        XV_Y, xv_row(panel, 0),
X		0);
X
X	tmpmenu = menu_create (
X						   MENU_ACTION_ITEM,"    Previous Card", prev_button_prev,
X						   MENU_ACTION_ITEM, "(S) First Card   ", prev_button_S_prev,
X						   NULL, NULL);
X	(void) xv_create (panel, PANEL_BUTTON,
X		PANEL_LABEL_IMAGE,	pr_region(buttons1_image, 64, 0, 0, 32, 32),
X		PANEL_ITEM_MENU,	tmpmenu,
X		PANEL_NOTIFY_PROC,	prev_button,
X		0);
X
X	tmpmenu = menu_create (
X						   MENU_ACTION_ITEM, "    New Card After this One ", new_card_after,
X						   MENU_ACTION_ITEM, "(S) New Card Before this One", new_card_before,
X						   NULL, NULL);
X	(void) xv_create (panel, PANEL_BUTTON,
X		PANEL_LABEL_IMAGE,	pr_region (buttons1_image, 64, 32, 0, 32, 32),
X		PANEL_ITEM_MENU,	tmpmenu,
X		PANEL_NOTIFY_PROC,	new_button,
X		0);
X
X	tmpmenu =  xv_create (NULL, MENU,
X				MENU_NOTIFY_PROC, delete_button_delete,
X				MENU_ITEM,
X					MENU_STRING,
X						"    Delete this card        ",
X					MENU_VALUE,		1,
X					MENU_NOTIFY_PROC, delete_button_delete,
X					0,
X				MENU_ITEM,
X					MENU_STRING,
X						"(S) UnDelete a Card (after) ",
X					MENU_NOTIFY_PROC, delete_button_undelete,
X					MENU_GEN_PROC,		check_stack,
X					MENU_GEN_PULLRIGHT,	gen_undelete, 
X					MENU_CLIENT_DATA,	FALSE,
X					0,
X				MENU_ITEM,
X						  MENU_STRING,
X						  "(C) UnDelete a Card (before)",
X						  MENU_NOTIFY_PROC, delete_button_undelete_before,
X						  MENU_GEN_PROC,		check_stack,
X						  MENU_GEN_PULLRIGHT,	gen_undelete_before, 
X						  MENU_CLIENT_DATA,	TRUE,
X						  NULL,
X			   NULL);
X
X	(void) xv_create (panel, PANEL_BUTTON,
X		PANEL_LABEL_IMAGE,	pr_region (buttons1_image, 64, 32, 32, 32, 32),
X		PANEL_ITEM_MENU, tmpmenu,
X		PANEL_NOTIFY_PROC,	delete_button,
X		0);
X
X	tmpmenu = menu_create (
X						   MENU_ACTION_ITEM, "Show Index List of Cards",  list_button,
X						   NULL);
X	(void) xv_create (panel, PANEL_BUTTON,
X		PANEL_LABEL_IMAGE,	pr_region(buttons2_image, 64, 32, 32, 32, 32),
X		PANEL_ITEM_MENU,	tmpmenu,
X		0);
X
X	tmpmenu = menu_create (
X						   MENU_ACTION_ITEM,
X						   "      Save Cards to Disk  ",
X						   file_button_save,
X
X						   MENU_ACTION_ITEM,
X						   "  (S) Reload From Disk    ",
X						   file_button_reload,
X
X						   MENU_ACTION_ITEM,
X						   "  (C) Sort Cards          ",
X						   file_button_sort,
X
X						   MENU_ACTION_ITEM,
X						   "(S+C) Sort Backwards      ",
X						   file_button_sort_backwards,
X
X						   MENU_ACTION_ITEM,
X						   "      Load From Named File",
X						   file_button_load,
X
X						   MENU_ACTION_ITEM,
X						   "      Save To Named File  ",
X						   file_button_save_to_file,
X						   NULL);
X
X	(void) xv_create (panel, PANEL_BUTTON,
X		PANEL_LABEL_IMAGE,	pr_region(buttons2_image, 64, 0, 0, 32, 32),
X		PANEL_NOTIFY_PROC,	file_button,
X		PANEL_ITEM_MENU, tmpmenu,
X		0);
X
X	tmpmenu = menu_create (
X						   MENU_ACTION_ITEM, "Display Help Message", help_button, 
X						   NULL);
X
X	(void) xv_create (panel, PANEL_BUTTON,
X		PANEL_LABEL_IMAGE,	pr_region(buttons2_image, 64, 32, 0, 32, 32),
X		PANEL_ITEM_MENU,	tmpmenu,
X		0);
X
X	tmpmenu = menu_create (
X						   MENU_ACTION_ITEM, 
X						   "   Save Changes and Close  ", 
X						   done_n_save,
X
X						   MENU_ACTION_ITEM,
X						   "(S) Exit Rolo, Save Changes ",
X						   done_n_save_exit,
X						   
X						   MENU_ACTION_ITEM,
X						   "(C) Exit, Don't Save Changes", 
X						   done_n_exit,
X						   NULL);
X	(void) xv_create (panel, PANEL_BUTTON,
X		PANEL_LABEL_IMAGE,	pr_region(buttons2_image, 64, 0, 32, 32, 32),
X		PANEL_NOTIFY_PROC,	done_button,
X		PANEL_ITEM_MENU,	tmpmenu,
X		0);
X
X	/*
X	 * Tighten up the window around the buttons in the first row, this
X	 * will be the width of the panel window, so we save that size for
X	 * later reference.  We also ask for the width of the panel in
X	 * columns for computing the width of the find pattern text item.
X	 */
X	window_fit_width (panel); 
X	panel_width = (int) xv_get (panel, XV_WIDTH);
X	panel_columns = (int) xv_get (panel, WIN_COLUMNS);
X
X	/*
X	 * Begin second row, set the inter-item gap so the Find button and
X	 * the text item following it are placed nicely.
X	 */
X	xv_set (panel, PANEL_ITEM_X_GAP, 8, 0);
X
X	tmpmenu = menu_create (
X						   MENU_ACTION_ITEM,
X						   "    Find Regular Expression, Forward",
X						   find_button_forward,
X						   
X						   MENU_ACTION_ITEM,
X						   "(S) Find Regular Expression, Reverse", 
X						   find_button_reverse, 
X						   NULL);
X
X	(void) xv_create (panel, PANEL_BUTTON,
X		PANEL_LABEL_STRING, "Find", 
X		PANEL_ITEM_MENU,	tmpmenu, 
X		PANEL_NOTIFY_PROC,	find_button,
X		0);
X
X	regex_item = xv_create (panel, PANEL_TEXT,
X		PANEL_BLINK_CARET,		TRUE,
X		PANEL_LABEL_STRING,		"",
X		PANEL_VALUE_DISPLAY_LENGTH,	panel_columns - 10,
X		PANEL_VALUE_STORED_LENGTH,	80,
X		PANEL_NOTIFY_PROC,	find_button,
X		0);
X
X	/*
X	 * Begin the third row, squeeze the inter-item gap back down so that
X	 * the slider value is displayed close to the slider bar.
X	 */
X	xv_set (panel, PANEL_ITEM_X_GAP, 4, 0);
X
X	tmpmenu = menu_create (
X				MENU_ITEM,
X					MENU_STRING,	"Pick a card, any card",
X					MENU_VALUE,	0,
X					0,
X				0);
X
X	slider_item = xv_create (panel, PANEL_SLIDER,
X		PANEL_MIN_VALUE,	0,
X		PANEL_MAX_VALUE,	1,
X		PANEL_VALUE,		1,
X		/* This slider width is temp, recomputed later */
X		PANEL_SLIDER_WIDTH, 500,
X		PANEL_SHOW_RANGE,	FALSE,
X		PANEL_SHOW_VALUE,	TRUE,
X		PANEL_NOTIFY_LEVEL,	PANEL_DONE,
X		PANEL_NOTIFY_PROC,	slider_proc,
X		PANEL_ITEM_MENU, tmpmenu,
X		0);
X
X	/*
X	 * Adjust the position of the text item slightly for better aesthetic
X	 * placement relative to the label in the Find button.  Do it after
X	 * creating the slider so that the slider is placed relative to the
X	 * original position of the text item, not the final position.
X	 */
X	xv_set (regex_item,
X		XV_Y,	xv_get (regex_item, XV_Y) + 4,
X		0);
X	/*
X	 * Tighten up the panel window in both directions around the final
X	 * layout.  Save off the resulting height for later use.
X	 */
X	window_fit_height (panel); 
X	panel_height = (int) xv_get (panel, XV_HEIGHT);
X
X	return (panel);
X}
X
X/* ------------------------------------------------------------------------- */
X
X
X/*		Panel item notification handlers		*/
X
X/*
X *	Notification proc for the "next" button.  If the button is pressed
X *	with no shift, the card is advanced to the next one.  If pressed
X *	with the shift key, then jump to the last card.
X */
X
Xdo_bozo(panel,p)
XPanel panel;
Xstruct card *p;
X{
X	static int	bozo = 0;	/* speak second time end is reached */
X	if (p == NULL_CARD) {		/* did we step off end of the list? */ 
X		if (panel == NULL || bozo != 0) {	/* have we already beeped once? */
X			msg ("This is the last card");
X		} else {
X			window_bell (panel);
X		}
X		bozo = (bozo+1)&1;
X		return(1);
X	}
X	return(0);
X}
X
Xstatic int in_next_panel;
Xstatic void next_button_next(item, event)
XPanel_item item;
XEvent *event;
X{
X	struct card	*p;
X	if ( in_next_panel ) {
X		in_next_panel = 0;
X		return;
X	}
X	save_card (current);
X	p = current->c_next;
X	if ( !do_bozo(panel,p) )
X	  show_card (p);		/* all is well, display the new card */
X}
X
Xstatic void next_button_S_next(item, event)
XPanel_item item;
XEvent *event;
X{
X	if ( in_next_panel ) {
X		in_next_panel = 0;
X		return;
X	}
X	save_card (current);
X	show_card (last);		/* all is well, display the new card */
X}
X
X/*ARGSUSED*/
Xstatic
Xvoid
Xnext_button (item, event)
X	Panel_item	item;
X	Event		*event;
X{
X	if ( event_action(event) == ACTION_MENU )
X	  return;
X	switch (value_from_mask (event)) {
X	  case SHIFT_CLICK:		/* click plus shift (menu item 2) */
X		next_button_S_next(item, event);
X		break;
X
X	  default:			/* any other shift mask (or none) */
X		next_button_next(item, event);
X		break;
X	}
X	in_next_panel = 1; 
X}
X
X
X/*
X *	Notification proc for the "Previous" button.  Similar to the proc
X *	above for next card.
X */
Xstatic in_prev_panel;
X
Xstatic void prev_button_prev(item,event)
XPanel_item item;
XEvent *event;
X{
X	struct card	*p;
X	if ( in_prev_panel ) {
X		in_prev_panel = 0;
X		return;
X	}
X	save_card (current);
X	p = current->c_prev;	/* move backwards one card */
X	if ( !do_bozo(panel,p) )
X	  show_card(p);
X}
X
Xstatic void prev_button_S_prev(item,event)
XPanel_item item;
XEvent *event;
X{
X	if ( in_prev_panel ) {
X		in_prev_panel = 0;
X		return;
X	}
X	save_card (current);
X	show_card (first);
X}
X
X/*ARGSUSED*/
Xstatic void prev_button (item, event)
X	Panel_item	item;
X	Event		*event;
X{
X
X	if ( event_action(event) == ACTION_MENU )
X	  return;
X
X	switch (value_from_mask (event)) {
X	  case SHIFT_CLICK:
X		prev_button_S_prev(item, event);
X		break;
X
X	default:
X		prev_button_prev(item, event);
X		break;
X	}
X	in_prev_panel = 1;
X}
X
X
X/*
X *	Notification for the "New Card" button.  The new card is inserted
X *	after the currently displayed card, unless the shift key is down,
X *	in which case it is inserted before the current card.
X */
X
Xstatic in_new_button;
X
Xstatic void new_card_after (item, event)
XPanel_item	item;
XEvent		*event;
X{
X	struct	card	*c, *p;
X	if ( in_new_button ) {
X		in_new_button = 0;
X		return;
X	}
X	save_card (current);
X	c = make_card (NULL);
X	if (c == NULL_CARD) {
X		msg ("Can't allocate space for a new card");
X		return;
X	}
X	p = current;			/* insert after current */
X	p = insert_card (c, p);			/* insert c after p */
X	need_save = TRUE;			/* Things have changed */
X	set_slider_max (renumber (first));	/* update slider range */
X	show_card (p);	
X}
X
Xstatic void new_card_before(item, event)
X	Panel_item	item;
X	Event		*event;
X{
X	struct	card	*c, *p;	
X	if ( in_new_button ) {
X		in_new_button = 0;
X		return;
X	}
X	save_card (current);
X	c = make_card (NULL);
X	if (c == NULL_CARD) {
X		msg ("Can't allocate space for a new card");
X		return;
X	}
X	p = current->c_prev;		/* c_prev may be NULL */
X	p = insert_card (c, p);			/* insert c after p */
X	need_save = TRUE;			/* Things have changed */
X	set_slider_max (renumber (first));	/* update slider range */
X	show_card (p);	
X}
X
X/*ARGSUSED*/
Xstatic void new_button (item, event)
X	Panel_item	item;
X	Event		*event;
X{
X
X	if ( event_action(event) == ACTION_MENU )
X	  return;
X	switch (value_from_mask (event)) {
X	  case SHIFT_CLICK:			/* click+shift, insert before */
X		new_card_before(item, event);
X		break;
X
X	  default:
X		new_card_after(item, event);
X		break;
X	}
X	in_new_button = 1;
X}
X
X
X/*
X *	Notification proc for the "Delete" button.  A plain click deletes
X *	the current card and pushes it on the deleted stack.  Shift and
X *	Control key modifiers undelete the card on the top of the stack.
X *	The undelete operations via the menu are special for the delete
X *	button.  Picking either undelete operation from the menu does not
X *	wind up as a call to this function.  Those operations are entirely
X *	handled by the menu code via action and gen procs.  See the menu
X *	procs in cards.c.  
X */
X
Xstatic int in_delete_button;
Xstatic void delete_button_delete (item, event)
XPanel_item	item;
XEvent		*event;
X{
X	struct card	*p;
X	if ( in_delete_button ) {
X		in_delete_button = 0;
X		return;
X	}
X	if (first == last) {
X		/* if we're deleting the last card, add a dummy */
X		(void) insert_card (make_card (NULL), NULL_CARD);
X	}
X	
X	/* and the new current card will be... */
X	save_card(current);
X	p = (current == last) ? current->c_prev : current->c_next;
X	delete_card (current);
X	need_save = TRUE;
X	set_slider_max( renumber(first));
X	if ( !do_bozo(panel,p) )
X	  show_card(p);
X}
X
Xstatic void delete_button_undelete_before (item, event)
XPanel_item	item;
XEvent		*event;
X{
X	struct card *p;
X	if ( in_delete_button ) {
X		in_delete_button = 0;
X		return;
X	}
X	save_card (current);
X	p = undelete_card (current->c_prev);	/* can be nil */
X	need_save = TRUE;
X	set_slider_max (renumber (first));
X	if ( !do_bozo(panel,p) )
X	  show_card(p);
X}
X
Xstatic void delete_button_undelete (item, event)
XPanel_item	item;
XEvent		*event;
X{
X	struct card *p;
X	if ( in_delete_button ) {
X		in_delete_button = 0;
X		return;
X	}
X	save_card (current);
X	p = undelete_card (current);
X	need_save = TRUE;
X	set_slider_max (renumber (first));
X	if ( !do_bozo(panel,p) )
X	  show_card(p);
X}
X
X/*ARGSUSED*/
Xstatic void delete_button (item, event)
XPanel_item	item;
XEvent		*event;
X{
X	if ( event_action(event) == ACTION_MENU )
X	  return;
X
X
X	switch (value_from_mask (event)) {
X	  case PLAIN_CLICK:
X		delete_button_delete(item, event);
X		break;
X
X	  case SHIFT_CLICK:
X		delete_button_undelete(item, event);
X		break;
X
X	  case CTRL_CLICK:
X		delete_button_undelete_before(item, event);
X		break;
X		
X	  default:
X		no_comprendo (event, "delete_button");
X		return;
X	}
X	in_delete_button = 1;
X}
X
X/*
X *	Notification proc for the "File" button.  
X */
X
Xstatic int in_file_button;
X
Xstatic void file_button_save (item, event)
XPanel_item	item;
XEvent		*event;
X{
X	if ( in_file_button ) {
X		in_file_button = 0;
X		return;
X	}
X	save_card (current);
X	dump_rolo (first, rolofile);
X}
X
Xstatic void file_button_reload (item, event)
XPanel_item	item;
XEvent		*event;
X{
X	if ( in_file_button ) {
X		in_file_button = 0;
X		return;
X	}
X	save_card (current);
X	if ((need_save == TRUE) && (verify_no_save () == FALSE)) {
X		return;
X	}
X	nuke_active_cards ();
X	init_rolo (rolofile);
X}
X
Xstatic void file_button_sort (item, event)
XPanel_item	item;
XEvent		*event;
X{
X	if ( in_file_button ) {
X		in_file_button = 0;
X		return;
X	}
X	save_card (current);
X	sort_cards (FALSE);
X	need_save = TRUE;
X}
X
Xstatic void file_button_sort_backwards (item, event)
XPanel_item	item;
XEvent		*event;
X{
X	if ( in_file_button ) {
X		in_file_button = 0;
X		return;
X	}
X	save_card (current);
X	sort_cards (TRUE);
X	need_save = TRUE;
X}
X
Xstatic void file_button_load (item, event)
XPanel_item	item;
XEvent		*event;
X{
X	char *filename;
X	if ( in_file_button ) {
X		in_file_button = 0;
X		return;
X	}
X	save_card (current);
X	filename = get_selection ();
X	if (filename == NULL) {
X		msg ("No active selection, need a filename to load from");
X		return;
X	}
X	if (filename_ok (filename) == FALSE) {
X		return;
X	}
X	if ((need_save == TRUE) && (verify_no_save () == FALSE)) {
X		return;
X	}
X	read_rolo (filename);
X}
X
Xstatic void file_button_save_to_file (item, event)
XPanel_item	item;
XEvent		*event;
X{
X	char *filename;
X	if ( in_file_button ) {
X		in_file_button = 0;
X		return;
X	}
X	save_card (current);
X	filename = get_selection ();
X	if (filename == NULL) {
X		msg ("No active selection, need a filename to save to");
X		return;
X	}
X	if (filename_ok (filename) == FALSE) {
X		return;
X	}
X	write_rolo (filename);
X}
X
X/*ARGSUSED*/
Xstatic void file_button (item, event)
XPanel_item	item;
XEvent		*event;
X{
X	char		*filename;
X
X	if ( event_action(event) == ACTION_MENU )
X	  return;
X
X	switch (value_from_mask (event)) {
X	  case PLAIN_CLICK:			/* Plain save */
X		file_button_save(item, event);
X		break;
X
X	case SHIFT_CLICK:			/* reload, no save first */
X		file_button_reload(item, event);
X		break;
X
X	case CTRL_CLICK:			/* sort ascending */
X		file_button_sort(item, event);
X		break;
X
X	case CTRL_SHIFT_CLICK:			/* sort descending */
X		file_button_sort_backwards(item, event);
X		break;
X
X	case META_CLICK:			/* load named file */
X		file_button_load(item, event);
X		break;
X
X	case META_SHIFT_CLICK:			/* store to named file */
X		file_button_save_to_file(item, event);
X		break;
X
X	default:				/* say what? */
X		no_comprendo (event, "file_button");
X		return;
X	}
X	in_file_button = 1;
X}
X
X
X/*
X *	Notification proc for the "Done" button.  This covers both closing
X *	the tool and exiting.  (The checkered flag button is supposed to
X *	convey the idea of "finished".  Yeah, I know, you got a better idea?)
X */
X
Xstatic int in_done_button;
Xstatic void done_n_save (item, event)
XPanel_item	item;
XEvent		*event;
X{
X	if (in_done_button) {
X		in_done_button = 0;
X		return;
X	}
X	save_card (current);
X	if (need_save) {
X		dump_rolo (first, rolofile);
X	}
X	xv_set (frame, FRAME_CLOSED, TRUE, 0);
X}
X
Xstatic void done_n_save_exit (item, event)
XPanel_item	item;
XEvent		*event;
X{
X	if (in_done_button) {
X		in_done_button = 0;
X		return;
X	}
X	save_card (current);
X	if (need_save) {
X		dump_rolo (first, rolofile);
X	}
X	xv_destroy_safe (frame);
X}
X
Xstatic void done_n_exit (item, event)
XPanel_item	item;
XEvent		*event;
X{
X	if (in_done_button) {
X		in_done_button = 0;
X		return;
X	}
X	save_card (current);
X	if (need_save && (verify_no_save () == FALSE)) {
X		return;
X	}
X	need_save = FALSE;
X	xv_destroy_safe (frame);
X}
X
Xstatic void done_button (item, event)
X	Panel_item	item;
X	Event		*event;
X{
X	if ( event_action(event) == ACTION_MENU )
X	  return;
X
X	switch (value_from_mask (event)) {
X	  case SHIFT_CLICK:			/* save cards and exit */
X		done_n_save_exit(item,event);
X		break;
X
X	case CTRL_CLICK:			/* don't save and exit */
X		done_n_exit(item,event);
X		break;
X
X	default:				/* save and go iconic */
X		done_n_save(item,event);
X		return;
X	}
X
X	in_done_button = 1;
X	xv_destroy_safe (frame);
X}
X
X
X/*
X *	Notification proc for both the "Find" button and text item for
X *	the search pattern.  This proc will be called from the pattern item
X *	if you type return.  If this proc is called, and there is an active
X *	selection, that will be used as the search pattern.  The selection
X *	will also be inserted into the pattern item if it is currently empty.
X */
X
Xstatic  int in_find_button;
Xstatic	char	*e, regbuf [MAX_SELN_LEN];
X
Xinit_find_button()
X{
X	static		int bozo = 0;
X	save_card (current);
X
X	/* if selection active, use it */
X	if ((e = get_selection ()) != NULL) {
X		char	*pe = (char *) xv_get(regex_item, PANEL_VALUE);
X
X		(void) strcpy (regbuf, e);
X		/* if panel item is empty, copy selection into it */
X/*		if ((pe == NULL) || (strlen (pe) == 0)) { /* */
X			xv_set (regex_item, PANEL_VALUE, regbuf, 0);
X/*		} /* */
X	} else {
X		/* else use panel value */
X		(void) strcpy (regbuf, (char *) xv_get(regex_item, PANEL_VALUE));
X	}
X
X	if (strlen (regbuf) == 0) {
X		if (bozo) {
X			msg ("Enter an expression to search for");
X		} else {
X			window_bell (panel);
X			bozo++;
X		}
X		return(0);
X	}
X
X	bozo = 0;
X	e = re_comp (regbuf);
X	if (e != NULL) {
X		msg ("Regular Expression error: %s", e);
X		return(0);
X	}
X	return(1);
X}
X
Xstatic void find_button_forward (item, event)
XPanel_item	item;
XEvent		*event;
X{
X	struct card *p;
X	if ( in_find_button ) {
X		in_find_button = 0;
X		return;
X	}
X	if ( !init_find_button() )
X	  return;
X	p = (current == last) ? first : current->c_next;
X	while (p != current) {
X		if (re_exec (p->c_text) == 1) {
X			show_card (p);
X			return;
X		}
X		p = (p == last) ? first : p->c_next;
X	}
X	if (re_exec (p->c_text) != 1)		/* wrapped back to current */
X	  window_bell (panel);
X}
X
Xstatic void find_button_reverse (item, event)
XPanel_item	item;
XEvent		*event;
X{
X	struct card *p;
X	if ( in_find_button ) {
X		in_find_button = 0;
X		return;
X	}
X	if ( !init_find_button() )
X	  return;
X	p = (current == first) ? last : current->c_prev;
X	while (p != current) {
X		if (re_exec (p->c_text) == 1) {
X			show_card (p);
X			return;
X		}
X		p = (p == first) ? last : p->c_prev;
X	}
X	if (re_exec (p->c_text) != 1)		/* wrapped back to current */
X		window_bell (panel);
X}
X
X/*ARGSUSED*/
Xstatic void find_button (item, event)
XPanel_item	item;
XEvent		*event;
X{
X	if ( event_action(event) == ACTION_MENU )
X	  return;
X
X	if (value_from_mask(event) == SHIFT_CLICK) {		/* search backwards */
X		find_button_reverse(item,event);
X	} else {
X		find_button_forward(item,event);
X	}
X	in_find_button = 1;
X}
X
X
X/*
X *	Notification proc for the "List" button.  The text window is used
X *	to display the first non-blank line of each card.
X */
X
X/*ARGSUSED*/
Xstatic
Xvoid
Xlist_button (item, event)
X	Panel_item	item;
X	Event		*event;
X{
X	struct card 	*p;
X
X	save_card (current);			/* save off pending changes */
X
X	textsw_reset (rolocard, 0, 0);		/* clear the text window */
X
X	for (p = first; p != NULL_CARD; p = p->c_next) {
X		char	*nl;
X		char	line_buf [MAX_INDEX_LINE + 2];
X
X		(void) sprintf (line_buf, "%d: ", p->c_num);/* prepend number */
X		textsw_insert (rolocard, line_buf, strlen (line_buf));
X
X		(void) strncpy (line_buf, first_char (p->c_text),
X			MAX_INDEX_LINE);
X		line_buf [MAX_INDEX_LINE] = 0;	/* make sure it's terminated */
X
X		(void) strcat (line_buf, "\n");	/* make sure of newline */
X		nl = index (line_buf, '\n');
X		*++nl = '\0';			/* chop at first line break */
X		textsw_insert (rolocard, line_buf, strlen (line_buf));
X	}
X
X	xv_set (rolocard, TEXTSW_INSERTION_POINT, 0, 0);	/* rewind */
X	textsw_normalize_view (rolocard, 0);
X
X	current = NULL_CARD;			/* indicate no card displayed */
X	update_num_display (LISTALLCARDS);
X}
X
X
X
X/*
X *	Notification proc for the "Help" button.  The text window is used to
X *	display a help message.  The text of the help message is defined
X *	in help.h as an array of string pointers (there is too much text to
X *	be parsed as one long string token).
X */
X
X/*ARGSUSED*/
Xstatic
Xvoid
Xhelp_button (item, event)
X	Panel_item	item;
X	Event		*event;
X{
X	int		i;
X
X	save_card (current);			/* capture any pending mods */
X
X	textsw_reset (rolocard, 0, 0);		/* empty the window */
X
X	/* insert the help strings into the window in order */
X	for (i = 0; i < sizeof (help_msg) / sizeof (char *); i++) {
X  		textsw_insert (rolocard, help_msg [i], strlen (help_msg [i]));
X	}
X
X  	xv_set (rolocard, TEXTSW_INSERTION_POINT, 0, 0);	/* rewind */
X  	textsw_normalize_view (rolocard, 0);
X
X  	current = NULL_CARD;			/* indicate no card displayed */
X  	update_num_display (HELPDISPLAYED);	/* set title bar */
X}
X
X
X/*
X *	Notification proc for the slider item on the panel.  This one's easy.
X */
X
X/*ARGSUSED*/
Xstatic
Xvoid
Xslider_proc (item, value, event)
X	Panel_item	item;
X	int		value;
X	Event		*event;
X{
X	save_card (current);
X	goto_card (value);
X}
X
X/* ----------------------------------------------------------------------- */
X
X/*		Utility procs called by the event handlers above	*/
X
X
X/*
X *	Set the range on the slider.  Lower bound is always 1, upper bound
X *	is set to the argument i.  The slider size is adjusted according to
X *	how many digits it takes to represent the value.  This looks a bit
X *	goofy because of the way the ATTR_COLS() macro works.  These macros
X *	do NOT return an integer which represents a number of pixels, they
X *	encode a value in the high bits which is interpreted later inside
X *	the library code.  This means you cannot say:
X *		x = panel_width - ATTR_COLS(n);
X *	See the macro definitions in <sunwindow/attr.h>
X */
X
Xvoid
Xset_slider_max (i)
X	int	i;
X{
X	int	delta = 6;		/* space for 3 digits */
X
X	if (i < 100) {
X		delta = 5;		/* space for 2 digits */
X	}
X
X	if (i < 10) {
X		delta = 4;		/* space for 1 digit */
X	}
X
X	xv_set (slider_item,
X		PANEL_MIN_VALUE,	1,
X		PANEL_MAX_VALUE,	i,
X/*		PANEL_SLIDER_WIDTH,	panel_width - 15 + xv_cols (-delta), */
X		0);
X}
X
X
X/*
X *	Display the card pointed to by p.  Reset the text window, which
X *	clears it.  Insert the text of the card into the window, then
X *	roll it back to the beginning.  After displaying the text of
X *	the card, update the display items on the panel and set the global
X *	current pointer to point at this card.
X */
X
Xvoid
Xshow_card (p)
X	struct	card	*p;
X{
X	textsw_reset (rolocard, 0, 0);
X	textsw_insert (rolocard, p->c_text, strlen (p->c_text));
X	xv_set (rolocard, TEXTSW_INSERTION_POINT, 0, 0);
X	textsw_normalize_view (rolocard, 0);
X	update_num_display (p->c_num);
X	current = p;
X}
X
X
X/*
X *	Change to the card with the given index number.  The number is
X *	range checked, then searched for.  When found, the pointer to the
X *	card struct is passed to the function which actually changes
X *	to the new card.
X */
X
Xstatic
Xvoid
Xgoto_card (i)
X	int		i;
X{
X	struct	card	*p, *q;
X
X	if ((i < 1) || (i > last->c_num)) {
X		msg ("Sorry, don't have a card #%d", i);
X		show_card (first);	/* show something we're sure of */
X		return;			/* unlikely to happen */
X	}
X
X	/* reduce the search space a bit */
X	if ((current != NULL_CARD) && (i >= current->c_num)) {
X		q = current;
X	} else {
X		q = first;
X	}
X
X	for (p = q; p != NULL_CARD; p = p->c_next) {
X		if (p->c_num == i) {
X			show_card (p);
X			return;
X		}
X	}
X
X	msg ("Unexpected inconsistency, couldn't find card #%d", i);
X}
X
X
X/*
X *	Save off the contents of the card being displayed in the text window.
X *	If the data in the window has been modified by the user, we need to
X *	replace the data in the card struct with the contents of the window.
X *	Since there isn't a way to load some data into the window as an
X *	initial value and mark it as "clean", we need to use a brute force
X *	method to determine if any changes have been made.  We copy out
X *	the contents of the window and compare it to the data we inserted
X *	in the first place.  If they are the same, no change was made and
X *	there is nothing to do.  If they are different, we throw away the
X *	old contents and stash the pointer to the new in the card struct.
X */
X
Xsave_card (p)
X	struct	card 	*p;
X{
X	int	red, len;
X	char	*c;
X
X	if (p == NULL_CARD) {
X		/*
X		 * If nil, the text window is being used for an index list
X		 * or help message.  If so, we know there is no card data
X		 * to be saved, and that some button has been clicked.  So
X		 * at this point we'll redisplay the last card that was
X		 * displayed.  The slider item should still remember which
X		 * one it was.
X		 */
X		goto_card ((int) xv_get (slider_item, PANEL_VALUE));
X		return;
X	}
X
X	len = (int) xv_get (rolocard, TEXTSW_LENGTH);
X	c = malloc (len + 1);
X	red = (int) xv_get (rolocard, TEXTSW_CONTENTS, 0, c, len);
X	if (red != len) {
X		fprintf (stderr, "rolo: fetch error: red=%d, len=%d\n",
X				red, len);
X		return;
X	}
X
X	c [len] = '\0';
X	if (strcmp (c, p->c_text) == 0) {
X		free (c);			/* didn't change */
X	} else {
X		free (p->c_text);		/* changed and must save */
X		p->c_text = c;
X		need_save = TRUE;
X	}
X}
X
X
X/*
X *	Update the displayed information which indicates which card is
X *	currently being displayed.  Provisions are made for the special
X *	cases where the help message or index list is being displayed.
X *	The tool name stripe and the slider value are set to indicate
X *	the number of the current card.
X */
X
Xupdate_num_display (i)
X	int	i;
X{
X	char	buf [MAXPATHLEN + 20];		/* worst case */
X
X	switch (i) {
X	case LISTALLCARDS:
X		(void) sprintf (buf, "%s - %s  (First lines of all %d cards)",
X				NAME, rolofile, last->c_num);
X		break;
X
X	case HELPDISPLAYED:
X		(void) sprintf (buf, "%s - %s  (Help Message)",
X				NAME, rolofile);
X		break;
X
X	default:
X		xv_set (slider_item, PANEL_VALUE, i, 0);
X		(void) sprintf (buf, "%s - %s  (Card %d of %d)",
X				NAME, rolofile, i, last->c_num);
X		break;
X	}
X
X	set_stripe (buf);
X}
X
X
X/*
X *	Ask the user if they're really sure they don't want to save the
X *	changes they've made.  This function only really exists because
X *	the same question is asked in two places (I really hate duplicating
X *	code).
X */
X
Xverify_no_save ()
X{
X	return (confirm (
X 		"You have made changes which will be lost.  Are you sure?"));
X}
X
X
X/*
X *	Complain about an unknown shiftmask in a click event.
X */
X
Xstatic
Xvoid
Xno_comprendo (event, p)
X	Event	*event;
X	char	*p;
X{
X	char	buf [100];
X
X	(void) sprintf (buf, " %s: Huh?  Don't understand click value %d",
X		p, value_from_mask (event));
X	msg ("%s", buf);
X}
X
X
X/*
X *	Make sure a filename is reasonable.  This is important because the
X *	filename is provided via the selection mechanism, which could contain
X *	all manner of gibberish.
X */
X
Xstatic
Xint
Xfilename_ok (p)
X	char	*p;
X{
X	char	*q, *bad_chars = "!^&*()|~`{}[]:;\\\"'<>?";
X
X	for (q = p; *q != '\0'; q++) {
X		if (( ! isgraph (*q)) || (index (bad_chars, *q) != NULL)) {
X			msg ("Sorry, that looks like a bad filename to me");
X			return (FALSE);
X		}
X	}
X
X	return (TRUE);
X}
X
X
X/*
X *	Get the primary selection, copy it into a static buffer, up to a
X *	maximum, and return a pointer to it.  Return NULL if there is no
X *	current primary selection to get.
X */
X
Xstatic
Xchar *
Xget_selection()
X{
X	Seln_holder	holder;
X	Seln_request	*buffer;
X	static char 	sel_text [MAX_SELN_LEN + 1];
X	static notfirsttime = 0;
X	Xv_server server = (Xv_server)xv_get(xv_get(frame, XV_SCREEN), SCREEN_SERVER);
X	
X	holder = selection_inquire (server, SELN_PRIMARY);
X	buffer = selection_ask (server, &holder, SELN_REQ_CONTENTS_ASCII, NULL, NULL);
X
X	(void) strcpy (sel_text, buffer->data + sizeof (SELN_REQ_CONTENTS_ASCII));
X
X	if (strlen (sel_text) == 0 || !notfirsttime++) {
X		/* empty string is no sel. */
X		return (NULL);
X	}
X
X	return (sel_text);
X}
X
X/* ----------------------------------------------------------------------- */
X
X/*		Panel event dispatcher, trap menu button events		*/
X
X
X/*
X *	Convert a value representing a menu item into the corresponding
X *	event shift mask for faking a button click.  The menu value, which
X *	is 1-relative, is decremented by one, then the lowest three bits
X *	are examined and the corresponding shiftmask bits are set.
X */
X
X/*
X *	Given an event, translate it into an integer number representing
X *	which logical choice it is.  The value returned will be in the
X *	range 0-7, made up of the three possible bits representing the
X *	states of the SHIFT, CONTROL and META shift keys.  The effect is
X *	that the number returned by this function will be one less than
X *	the value initially passed into mask_from_menu_value().
X */
X
Xstatic
Xint
Xvalue_from_mask (event)
X	Event	*event;
X{
X	int	value = 0;
X
X	/*
X	 * These macros don't return TRUE or FALSE, exactly.  They return
X	 * the corresponding bits from the shiftmask.  For example, the
X	 * value of event_ctrl_is_down() is 0x30 if the control key was
X	 * down.  That means you can't compare the result to TRUE.
X	 */
X
X	if (event_shift_is_down (event) != 0) {
X		value |= 1;
X	}
X
X	if (event_ctrl_is_down (event) != 0) {
X		value |= 2;
X	}
X
X	if (event_meta_is_down (event) != 0) {
X		value |= 4;
X	}
X
X	return (value);
X}
X
X/* ----------------------------------------------------------------------- */
X
X/*		Interposer functions watching for frame events		*/
X
X
X/*
X *	Interposer proc for catching window resize events.  We're a little
X *	bit fascist here and insist that the frame remain at least big
X *	enough to display the whole panel and at least three lines of the
X *	text window.
X */
X
X#define MIN_CARD_ROWS	3
X
XNotify_value
Xcatch_resize (frame, event, arg, type)
X	Frame			frame;
X	Event			*event;
X	Notify_arg		arg;
X	Notify_event_type	type;
X{
X	Panel			panel;
X	int			width;
X	int			height;
X	int			card_height;
X	int			frame_height;
X	Notify_value		value;
X
X	value = notify_next_event_func (frame, event, arg, type);
X
X	if (event_id (event) != WIN_RESIZE) {
X		return (value);
X	}
X
X	if ((int) xv_get (frame, FRAME_CLOSED) == TRUE) {
X		return (value);
X	}
X
X	panel = (Panel) xv_get (frame, FRAME_NTH_SUBWINDOW, 1);
X
X
X	width = (int) xv_get (panel, XV_WIDTH);
X
X	if (width < panel_width) {
X		xv_set (panel, XV_WIDTH, panel_width, 0);
X		window_fit_width (frame);
X	}
X
X
X	height = (int) xv_get (panel, XV_HEIGHT);
X
X	if (height < panel_height) {
X		xv_set (panel, XV_HEIGHT, panel_height, 0);
X		window_fit_height (frame);
X	}
X
X	card_height = (int) xv_get (rolocard, WIN_ROWS);
X	if (card_height < MIN_CARD_ROWS) {
X		xv_set (rolocard, WIN_ROWS, MIN_CARD_ROWS, 0);
X		window_fit_height (frame);
X	}
X
X	/*
X	 * This catches cases where the subwindows are completely clipped
X	 * and don't shrink as far as the frame is concerned.
X	 */
X	card_height = (int) xv_get (rolocard, XV_HEIGHT);
X	frame_height = (int) xv_get (frame, XV_HEIGHT);
X	if (frame_height < (panel_height + card_height)) {
X		window_fit_height (frame);
X	}
X
X	return (value);
X}
X
X
X/*
X *	Interposer function to catch destroy events.  We want to know when
X *	the tool about to be destroyed so that we can save any changes to the
X *	cards back out to disk.  This gets a little tricky because the
X *	text edit window will veto a tool destroy (selecting Quit from
X *	the tool menu) if there is any text in the window.  This is because
X *	from its point of view the text buffer has been modified because we
X *	inserted the initial contents of the card.  The way we get around
X *	this is by saving the contents of the window, resetting it, calling
X *	the rest of the notification chain, then restoring the contents of
X *	the window.  The destroy proc for the text window is called in that
X *	notification chain and it will see an empty window and not object to
X *	the tool being destroyed.  If this function is called again with
X *	a notification flag other than DESTROY_CHECKING, that means the tool
X *	is really going away (either the user OKed a quit or suntools is
X *	shutting down).  In that case we write the cards back out if they
X *	have been changed, without any fancy footwork.
X */
X
XNotify_value
Xrolo_destroy (frame, status)
X	Frame	frame;
X	Destroy_status status;
X{
X	if (status == DESTROY_CHECKING) {
X		Notify_value	s;
X
X		save_card (current);
X		textsw_reset (rolocard, 0, 0);		/* fake out textedit */
X		s = notify_next_destroy_func (frame, status);
X		show_card (current);
X		return (s);
X	}
X
X	save_card (current);
X	if (need_save) {
X		dump_rolo (first, rolofile);
X	}
X
X	return (notify_next_destroy_func (frame, status));
X}
X
END_OF_FILE
if test 37272 -ne `wc -c <'panel.c'`; then
    echo shar: \"'panel.c'\" unpacked with wrong size!
fi
# end of 'panel.c'
fi
echo shar: End of archive 3 \(of 3\).
cp /dev/null ark3isdone
MISSING=""
for I in 1 2 3 ; do
    if test ! -f ark${I}isdone ; then
	MISSING="${MISSING} ${I}"
    fi
done
if test "${MISSING}" = "" ; then
    echo You have unpacked all 3 archives.
    rm -f ark[1-9]isdone
else
    echo You still need to unpack the following archives:
    echo "        " ${MISSING}
fi
##  End of shell archive.
exit 0

dan
----------------------------------------------------
O'Reilly && Associates   argv@sun.com / argv@ora.com
Opinions expressed reflect those of the author only.