/************************************************************ Vnews Started by: Stephen Vermeulen 1987. 26-Aug-1987 This version has been Manxified. Also some workbench support has been implemented. This program is designed to be a simple news file reader. The user invokes it with the name of the directory containing the news files he wants to read. The program then allows him to read these files easily. Syntax from CLI: vnews [dir_name [first_file]] vnews dir_name will display the contents of all the files in the directory named dir_name. vnews will display the contents of all the files in the current directory. vnews dir_name first_file will allow you to read all the files within the directory "dir_name" and will start you at file "first_file" Syntax from Workbench: Just point and click. To specify the directory to read the news files from enter the following line into the TOOL TYPES field: DIR=MyDirectoryName The default width that tabs are to be expanded to is specified by the following line: TAB=n where n is a small positive number. The reading can start with any particular file, just include a line like: FIRST=first_file_name NOTE: some aspects of the code are clearly examples of how not to code in C! Ie: I have made extensive and unforgivable use of the GOTO statement... ************************************************************/ #include #include #include #include #include #include #define PGUP 1 #define PGDN 2 #define NEXT 3 #define PREV 4 #define HELP 5 #define FILE_GAD 6 struct IntuitionBase *IntuitionBase; /* to use intuition functions */ struct GfxBase *GfxBase; /* to use graphics functions */ extern long IconBase; extern struct WBStartup *WBenchMsg; #define MAX_FILES 1000 /************************************** Yuck, static list size in the Amiga? Poor programming, bad boy, naughty... Seriously though, since good old Ami needs at least 2 blocks per file and there are only 1800 blocks on a floppy this should suffice for all but nutty hard disk owners... ***************************************/ char *file_list[MAX_FILES]; /************************************************************ The following stuff sets up the window to be used to display the text along with its gadgets that advance you through the text of a single message, of from message to message. The following gadgets are provided: PgUp - display next page of the current message PgDn - displays the previous page of the current message Next - displays the next message Prev - displays the previous message These all live in the title bar, along with the name of the file that is being displayed. ************************************************************/ #define LEFT_OFFSET 300 struct IntuiText pgup_text = { 1, 0, JAM2, 0, 0, NULL, (UBYTE *) "Prev", NULL }; struct IntuiText pgdn_text = { 1, 0, JAM2, 0, 0, NULL, (UBYTE *) "back", NULL }; struct IntuiText next_text = { 1, 0, JAM2, 0, 0, NULL, (UBYTE *) "fore", NULL }; struct IntuiText prev_text = { 1, 0, JAM2, 0, 0, NULL, (UBYTE *) "Next", NULL }; struct IntuiText help_text = { 1, 0, JAM2, 0, 0, NULL, (UBYTE *) "?", NULL }; struct Gadget pgup_gad = { NULL, LEFT_OFFSET, 0, 32, 10, GADGHCOMP, RELVERIFY | TOPBORDER, BOOLGADGET, NULL, NULL, &pgup_text, NULL, NULL, PREV, NULL }; struct Gadget pgdn_gad = { &pgup_gad, LEFT_OFFSET+48, 0, 32, 10, GADGHCOMP, RELVERIFY | TOPBORDER, BOOLGADGET, NULL, NULL, &pgdn_text, NULL, NULL, PGDN, NULL }; struct Gadget next_gad = { &pgdn_gad, LEFT_OFFSET+96, 0, 32, 10, GADGHCOMP, RELVERIFY | TOPBORDER, BOOLGADGET, NULL, NULL, &next_text, NULL, NULL, PGUP, NULL }; struct Gadget prev_gad = { &next_gad, LEFT_OFFSET+144, 0, 32, 10, GADGHCOMP, RELVERIFY | TOPBORDER, BOOLGADGET, NULL, NULL, &prev_text, NULL, NULL, NEXT, NULL }; struct Gadget help_gad = { &prev_gad, LEFT_OFFSET+192, 0, 8, 10, GADGHCOMP, RELVERIFY | TOPBORDER, BOOLGADGET, NULL, NULL, &help_text, NULL, NULL, HELP, NULL }; char file_string[32] = ""; struct StringInfo file_info = { (UBYTE *) &file_string, NULL, 0, 30, 0, 0, 0, 0, 0, 0, NULL, 0, NULL }; struct Gadget file_gad = { &help_gad, LEFT_OFFSET+208, 0, 78, 10, GADGHCOMP, RELVERIFY | TOPBORDER, STRGADGET, NULL, NULL, NULL, NULL, (APTR) &file_info, FILE_GAD, NULL }; struct NewWindow nw = { 0, 3, 640, 200-3, /* we should go for the max in case morerows was used */ -1, -1, GADGETUP | CLOSEWINDOW, WINDOWDEPTH | WINDOWCLOSE | SMART_REFRESH | BORDERLESS | ACTIVATE, &file_gad, NULL, NULL, NULL, /* default screen */ NULL, /* no special bm */ 0, 0, -1, -1, WBENCHSCREEN }; struct NewWindow new_help = { 168, 47, 304, 110, -1, -1, CLOSEWINDOW, WINDOWDEPTH | WINDOWCLOSE | SMART_REFRESH, NULL, NULL, (UBYTE *) "Vnews Help", NULL, /* default screen */ NULL, /* no special bm */ 0, 0, -1, -1, WBENCHSCREEN }; /************************************************************ This array is used to hold offsets from the start of the file to the nth page in it, so the PgDn function works, as the file is paged through by hitting PgUp the position of each page start is entered into the array, then when PgDn is hit you can easily backup. ************************************************************/ unsigned long int page_top[MAX_FILES]; short current_page; /* index for the page_top array */ short current_file; /* index for the file name array */ struct Window *w; FILE *in; /************************************************************ The display_page() function displays a page of text from the file on the screen. It first clears the window, then it does a line by line display of the file, it reads from the line one line of text at a time, printing each line of text to the window and then advancing to the next line on the window, when it has printed 23 lines of text the process stops. ************************************************************/ char temp_buf[200]; /************************************************************ The clean_string function removes CR's from the text string and expands tabs out to tabwidth spaces each. Note the value for tabwidth can only be specified if the program is started from the workbench. ************************************************************/ short tabwidth; void clean_string(s) register char *s; { register int n; register int i, j, k; n = strlen(s); if (n) { if (index(s, 10) || index(s, 13) || index(s, 9)) { for (i = 0; i < n; ++i) { switch(s[i]) { case 10: /* line feed -- replace with space */ case 13: /* carriage return -- replace with space */ s[i] = ' '; break; case 9: /* tab -- expand to tabwidth spaces */ s[i] = ' '; if (tabwidth > 1) { /***************************** First figure out how far to the next tab stop. ******************************/ k = tabwidth - (i % tabwidth); if (k > 1) { movmem(s + i + 1, s + i + k, n - i); for (j = 1; j < k; ++j) s[i+j] = ' '; n += k; } } break; } } } } } void display_page() { int dy; SetAPen(w->RPort, 0L); RectFill(w->RPort, 0L, (long) w->BorderTop + 1, (long) w->Width - 1, (long) w->Height - w->BorderBottom); SetAPen(w->RPort, 1L); dy = w->RPort->TxHeight; Move(w->RPort, 0L, (long) w->BorderTop + dy); while((w->RPort->cp_y + dy) < w->Height) /* enough room */ { if (fgets(temp_buf, 80, in)) { temp_buf[80] = 0; /* always force an end of string here as fgets does not put one in if 80 characters were read before any end of line type character was hit */ clean_string(temp_buf); /********************************************* This is not the best way of doing it, because lines that are longer than about 80 characters get truncated. One should really break long lines appropriately so that all the text does get displayed. This problem has been partially eliminated by reading in only 80 character chunks from the file by the fgets() function. This is not the true solution because it depends on the font that is being used. *********************************************/ Text(w->RPort, temp_buf, (long) strlen(temp_buf)); w->RPort->cp_x = 0; w->RPort->cp_y += dy; } else /* we have reached EOF */ { clean_string(temp_buf); Text(w->RPort, temp_buf, (long) strlen(temp_buf)); goto eof_stop; } } eof_stop: return; } /************************************************************ The next_page() function has three modes depending on the value of n: -1 - Move to the previous page and display one page of the file. 0 - Display the file from the very first line. +1 - Move the file to the next page and display it. ************************************************************/ void next_page(n) short n; { unsigned long int pos; if (!in) return; switch(n) { case -1: if (current_page) --current_page; fseek(in, page_top[current_page], 0); display_page(); break; case 0: current_page = 0; page_top[0] = 0; display_page(); break; case 1: pos = ftell(in); if (pos != EOF) { if (current_page < MAX_FILES) ++current_page; page_top[current_page] = pos; display_page(); } break; } } /************************************************************ The next file function has three modes depending on the value of n: -1 - Move to the previous file in the file list. 0 - Open the very first file in the file list. +1 - Move to the next file in the file list. *************************************************************/ void next_file(n) short n; { switch(n) { case -1: if (current_file) --current_file; if (in) fclose(in); in = fopen(file_list[current_file], "r"); break; case 0: current_file = 0; in = fopen(file_list[current_file], "r"); break; case 1: if (current_file < MAX_FILES) ++current_file; if (in) fclose(in); in = fopen(file_list[current_file], "r"); break; } if (in) { SetWindowTitles(w, file_list[current_file], -1L); } else { SetWindowTitles(w, "No more news!", -1L); } } extern char *scdir(); extern char *malloc(); /****************************** Function to compare strings ******************************/ compare(a, b) char **a, **b; { return(strcmp(*a, *b)); } /*************************************** this function scans the current directory, using the "pattern" string, placing pointers to the file names in the file_names array, it allocates the nessecary string space, up to a maximum of nmax strings. Oh yes, it sorts the strings with the function comp too. *****************************************/ get_dir(pattern, file_names, nmax, comp) char *pattern, *file_names[]; short nmax; short (*comp)(); { char *s; int i, j; /********************************* Paranoia time, zero all pointers manually... **********************************/ for (i = 0; i < nmax; ++i) file_names[i] = 0; i = 0; while((s = scdir(pattern)) && (i < nmax)) { if (file_names[i] = malloc(strlen(s) + 1)) { strcpy(file_names[i], s); ++i; } } /****************************************** Now sort the file lists that have been built up. ******************************************/ qsort(file_names, i, 4, comp); } /********************************************** this function frees up the space taken by the copys of the strings. ***********************************************/ free_strings(file_names, nmax) char *file_names[]; short nmax; { short j; j = 0; while (file_names[j]) { free(file_names[j]); ++j; } } /************************************************ This is a function that gets a value from a tool type field. You give it a toolarry pointer a tooltype string and a conversion string and a pointer of where to put the result. *************************************************/ getToolValue(ta, tt, cs, x) char **ta; char *tt, *cs, *x; { char *s; if (s = FindToolType(ta, tt)) sscanf(s, cs, x); } /************************************************ The following is a function that returns TRUE is a certain tool type mode is set. *************************************************/ isToolModeSet(ta, tt, tm) char **ta, *tt, *tm; { char *s; if (s = FindToolType(ta, tt)) return((short) MatchToolValue(s, tm)); return(0); } char start_name[90], directory[90], temp[90], dir_path[90]; void main(argc, argv) int argc; char *argv[]; { int flag; int func, class, i; struct IntuiMessage *mess; struct Screen wb_screen; struct Window *hw; char **toolarray; struct WBArg *arg; struct DiskObject *diskobj; IntuitionBase = (struct IntuitionBase *) OpenLibrary("intuition.library", 0L); if (!IntuitionBase) goto no_intuition; GfxBase = (struct GfxBase *) OpenLibrary("graphics.library", 0L); if (!GfxBase) goto no_graphics; IconBase = (long) OpenLibrary("icon.library", 0L); if (!IconBase) goto no_icons; tabwidth = 2; start_name[0] = 0; dir_path[0] = 0; /************************** Are we running from CLI or the Workbench? **************************/ if (argc) { /************************ Running from the CLI ************************/ if (argc > 1) { strcpy(start_name, argv[1]); strcpy(dir_path, argv[1]); if (start_name[strlen(start_name) - 1] == ':') strcat(start_name, "*"); else { strcat(start_name, "/*"); strcat(dir_path, "/"); } } else strcpy(start_name, "*"); get_dir(start_name, file_list, MAX_FILES, compare); if (argc > 2) { strcpy(start_name, argv[1]); if (start_name[strlen(start_name) - 1] == ':') strcat(start_name, argv[2]); else { strcat(start_name, "/"); strcat(start_name, argv[2]); } } else start_name[0] = 0; } else { /**************************** Running from the Workbench Arg will always point to the tool's icon. So what we must do is scan this tool type array to read in the name of the directory to use and the name of the first file to start with and the tab size. *****************************/ arg = WBenchMsg->sm_ArgList; /************************************** First we change our current directory to the location of the program. This is needed in case the user just specifies a relative directory path name. ***************************************/ (void) CurrentDir(arg->wa_Lock); diskobj = GetDiskObject(arg->wa_Name); /********************************* now parse the tooltypes array **********************************/ directory[0] = 0; temp[0] = 0; if (diskobj) { toolarray = diskobj->do_ToolTypes; getToolValue(toolarray, "TAB", "%d", &tabwidth); getToolValue(toolarray, "DIR", "%s", directory); getToolValue(toolarray, "FIRST", "%s", temp); FreeDiskObject(diskobj); } if (temp[0]) { strcpy(start_name, directory); strcpy(dir_path, directory); if (start_name[strlen(start_name) - 1] == ':') strcat(start_name, temp); else { strcat(start_name, "/"); strcat(dir_path, "/"); strcat(start_name, temp); } } if (directory[strlen(directory) - 1] == ':') strcat(directory, "*"); else strcat(directory, "/*"); get_dir(directory, file_list, MAX_FILES, compare); } /**************************************** The directory has now been read and sorted it is time to walk through the files. Lets open the window so that it makes full use of the workbench screen. ****************************************/ GetScreenData(&wb_screen, (long) sizeof(struct Screen), WBENCHSCREEN, NULL); nw.Width = wb_screen.Width; nw.Height = wb_screen.Height - 3; w = (struct Window *) OpenWindow(&nw); if (!w) goto no_window; /* can't open window error */ if (start_name[0]) /* start at this file */ { current_file = 0; while (strcmp(start_name, file_list[current_file]) && file_list[current_file]) ++current_file; --current_file; next_file(1); next_page(0); } else { next_file(0); /* open a new file (the first) for display */ next_page(0); /* display the next page in this file */ } flag = TRUE; while(flag) { Wait(1L << w->UserPort->mp_SigBit); while(mess = (struct IntuiMessage *) GetMsg(w->UserPort)) { func = ((struct Gadget *) mess->IAddress)->GadgetID; class = mess->Class; ReplyMsg(mess); if (class == CLOSEWINDOW) flag = FALSE; switch(func) { case PGUP: if (feof(in)) { next_file(1); next_page(0); } else next_page(1); break; case PGDN: if (current_page == 0) { next_file(-1); next_page(0); } else next_page(-1); break; case NEXT: next_file(1); next_page(0); break; case PREV: next_file(-1); next_page(0); break; case HELP: if (hw = OpenWindow(&new_help)) { Move(hw->RPort, 4L, 18L); Text(hw->RPort, "------------ Vnews 1.1 -----------", 36L); Move(hw->RPort, 4L, 26L); Text(hw->RPort, "By: Stephen Vermeulen (403) 282-7990", 36L); Move(hw->RPort, 4L, 34L); Text(hw->RPort, " Copyright 1987 ", 36L); Move(hw->RPort, 4L, 42L); Text(hw->RPort, "This program is in the public domain", 36L); Move(hw->RPort, 4L, 50L); Text(hw->RPort, "except it may not be sold or packaged", 37L); Move(hw->RPort, 4L, 58L); Text(hw->RPort, "with a commercial product. ", 36L); Move(hw->RPort, 4L, 66L); Text(hw->RPort, "To use just click on the title bar ", 36L); Move(hw->RPort, 4L, 74L); Text(hw->RPort, "icons:", 6L); Move(hw->RPort, 4L, 82L); Text(hw->RPort, "Prev gets you to the previous file ", 36L); Move(hw->RPort, 4L, 90L); Text(hw->RPort, "Back moves backwards one page ", 36L); Move(hw->RPort, 4L, 98L); Text(hw->RPort, "Fore moves foward one page ", 36L); Move(hw->RPort, 4L, 106L); Text(hw->RPort, "Next gets you to the next file ", 36L); Wait(1L << hw->UserPort->mp_SigBit); while(mess = (struct IntuiMessage *) GetMsg(hw->UserPort)) ReplyMsg(mess); CloseWindow(hw); } break; case FILE_GAD: /************************************** Get a filename to go to from the user The user has already typed it in, so process it. Note we will have to preference it with the directory path! ***************************************/ strcpy(start_name, dir_path); strcat(start_name, file_string); if (start_name[0]) /* start at this file */ { current_file = 0; while (strcmp(start_name, file_list[current_file]) && file_list[current_file]) ++current_file; --current_file; next_file(1); next_page(0); } break; } } /* end of while more messages */ } /* end of while(flag) */ CloseWindow(w); no_window: dir_error: free_strings(file_list, MAX_FILES); no_files: CloseLibrary(IconBase); no_icons: CloseLibrary(GfxBase); no_graphics: CloseLibrary(IntuitionBase); no_intuition: return; }