Filewatcher File Search
FTP Search
  
Directory 
  
Content Search 
   
pkg://jmr-0.7.23-1.src.rpm:177179/jmr-0.7.23.src.tar.gz  info  downloads

jmrsrc-0.7.23/ 40755    764    764           0  6623366026  12452 5ustar  jvuokkojvuokkojmrsrc-0.7.23/src/ 40775    764    764           0  6623366026  13243 5ustar  jvuokkojvuokkojmrsrc-0.7.23/src/filelist.cc100664    764    764       37750  6623366026  15516 0ustar  jvuokkojvuokko// 	$Id: filelist.cc,v 1.14 1998/11/14 20:40:56 jvuokko Exp $	

/****************************************************************************
 * *
 * *  MODULE : filelist.cc
 * *
 * *  Copyright (c) 1997 Jukka Vuokko
 * *  See file COPYING for more information about copyrights.
 * *
 ****************************************************************************
 * *
 * *
 * *      This module contains methods for handling file lists
 * *      (expand wildcards, select file etc...)
 * *
 * *
 * *
 ***************************************************************************/

#include <stdio.h> // sprintf()
#ifdef __EMX__
#   include <sys/types.h>
#endif 
#ifdef __WATCOMC__
#   include <direct.h>
#else
#   include <dirent.h>
#endif
#include "jmr.hh"
#include "filelist.hh"
#include "../utilib/String.hh"
#include "../utilib/List.hh"
#include "mail.hh"

extern Terminal_screen Screen;
extern settings_t Settings;
extern Window* Wscript;

static const menuinfo filelist_menu_array[] = {
        {"[jmr]", 0 },
        {"Exit from menu", 0},
        {"Quit jmr", FORCED_QUIT_CMD},
        {"@NEXT", 0 },
        {"@END", 0 }
};



Filelist::Filelist()
{
        menubar.init( filelist_menu_array );
        menubar.set_text( MENUBAR_TEXT );
        for( int i = Screen.get_xmax(); i >= 0; i--) {
                dashline += '-';
        }
}

/***************************************************************************
 * FUNCTION: compare_name
 ***************************************************************************
 *
 * DESCRIPTION: compares two strings. This is for qsort().
 *
 * EXTERNAL REFERENCES
 * IN : 
 * OUT: 
 *
 * RETURN: <0, 0 or >0
 ***************************************************************************/
int compare_name (const void *a, const void *b)
{
        String **aa, **bb;
        aa = (String**) a;
        bb = (String**) b;
        return (*aa)->compare (*(*bb));
}


Mail*
Filelist::open_file(const String& bbsid)
{
        Mail *mail;
        open_new = false;
        int c;
		if (bbsid.is_empty()) {
        for (;;) {
                mail = 0;
                if (open_new == true) {
                        read_files (Settings.qwkpath);
                        pattern = Settings.searchpattern;
                } else {
                        read_files (Settings.datadir);
                        pattern = "*.[gG][dD][bB]";
                }
                
                // keep only entries, that matches with pattern
                search_matches();
                
                c = select ();
                Screen.bottom();
                Screen.clr_eol();
                //Screen.clear();
                if (c == QUIT_CMD || c == FORCED_QUIT_CMD) {
                        break;
                } else if (c == LOAD_CMD) {
                        open_new = open_new == true ? false : true;
                } else if (c == SELECT_CMD) {
                        mail = Mail::create (filelist.get()->get(), open_new);
                        break;
                }
        }
		} else {
			mail = 0;
			read_files(Settings.qwkpath);
			pattern = bbsid + ".[qQ][wW][kK]";
			if (search_matches()) {
				mail = Mail::create (filelist.get()->get(), true);
			} else {
				filelist.destroy();
				read_files (Settings.datadir);
				pattern = bbsid + ".[gG][dD][bB]";
				if (search_matches()) {
					mail = Mail::create(filelist.get()->get(), false);
				}
			}
		}
        filelist.destroy();
        return mail;
}


//**************************************************************************/
//  CLASS: Filelist
//  MEMBER FUNCTION: read_files
// 
// 
//  DESCRIPTION: Reads all names of files from directory path
// 
//  EXTERNAL REFERENCES
//  IN :  
//  OUT: 
// 
//**************************************************************************/
void
Filelist::read_files (const String& path)
{
        DIR *dir;
        struct dirent *ent;
        String *str;

        filelist.destroy();
        if ( (dir = opendir ( path.get() )) == NULL) {
                String tmp = "Unable to open mail directory: " + path;
                handle_error (tmp.get(), HALT_FL);
        }
        while ((ent = readdir(dir)) != NULL) {
                if (ent->d_name[0] != '.') {
                        str = new String;
                        str->put(ent->d_name);
                        filelist.add (str);
                }
        }

        filelist.sort (compare_name);

        if (closedir(dir) != 0)
                handle_error("Unable to close mail directory", HALT_FL);

}

//**************************************************************************/
//  CLASS: Filelist
//  MEMBER FUNCTION: search_matches
// 
// 
//  DESCRIPTION: Removes all files that does not match with pattern from list
// 
// 
//  EXTERNAL REFERENCES
//  IN :  
//  OUT: 
// 
//  RETURNS: false if list is empty
// 
//**************************************************************************/
bool
Filelist::search_matches()
{
        bool rc;
        String *str;
                
        if (filelist.first() == false) {
                return false;
        }
        
        do {
                str = filelist.get();
                if (check_matches (str->get(), pattern.get()) == false) {
                        rc = filelist.remove();
                } else {
                        rc = filelist.next();
                }
        } while (rc == true);

        return filelist.first();
}


//**************************************************************************/
//  CLASS: Filelist
//  MEMBER FUNCTION: select
// 
// 
//  DESCRIPTION: 'User interface'. 
// 
// 
//  EXTERNAL REFERENCES
//  IN :  
//  OUT: 
// 
//  RETURNS: selected event.
// 
//**************************************************************************/
int
Filelist::select()
{
        int c;
        first = 0;
        current = 0;
        ypos = ymin = 6;
        ymax = Screen.get_ymax() - 5;
        lines = ymax - ymin;
        ++lines;
        //Screen.clear();
        sline.init( Screen.get_xmax() + 1);

        if (open_new == true) {
                iline.set_default( PACKET_LIST_KEYS_MSG );
        } else {
                iline.set_default( DATABASE_LIST_KEYS_MSG );
        }
        
        display();
        iline.show_default();
        
        while(1) {
                Screen.refresh();
                c = Screen.get_ch();
                iline.reset();
                if (c == ESCAPE ) {
                        iline.set( MENU_MSG );
                        iline.show();
                        c = menubar.select();
                        display();
                        iline.reset();
                }
                switch (c) {
                case MESSAGEWIN_CMD:
                        Wscript->enable();
                        iline.set ( MESSAGES_MSG );
                        iline.show();
                        Screen.get_ch();
                        iline.reset();
                        Wscript->disable();
                        break;
                case CODE_UP:
                case PREV_CMD:
                        prev();
                        break;
                case CODE_DOWN:
                case NEXT_CMD:
                        next();
                        break;
                case CODE_NPAGE:
                case PAGE_DOWN_CMD:
                        page_down();
                        break;
                case CODE_PPAGE:
                case PAGE_UP_CMD:
                        page_up();
                        break;
                        //case CODE_CTRL_L:
                        //display();
                        //break;
                case CODE_HOME:
                        first = 0;
                        current = 0;
                        ypos = ymin;
                        display();
                        break;
                case CODE_END:
                        current = filelist.count_items()-1;
                        // next line is not a bug! It's the only
                        // way to calc first index of page...
                        first = (current / lines) * lines ;
                        ypos = ymin + (current - first);
                        display();
                        break;
                case SELECT_CMD:
                case CODE_RIGHT:
                        if (filelist.check() == true) {
                                return SELECT_CMD;
                        }
                        break;
                case FORCED_QUIT_CMD:
#ifndef NO_EXCEPTIONS
                        throw quit_exception();
#endif
                case QUIT_CMD:
                        return QUIT_CMD;
                case REFRESH_CMD:
                        return REFRESH_CMD;
                case LOAD_CMD:
                        return LOAD_CMD;
                case HELP_CMD:
                        show_help();
                        display();
                        break;
                case 0:
                        break;
                default:
                        iline.set( INVALID_COMMAND_MSG );
                };
                iline.show();
        }
}


//**************************************************************************/
//  CLASS: Filelist
//  MEMBER FUNCTION: display
// 
// 
//  DESCRIPTION: display files.
// 
// 
//  EXTERNAL REFERENCES
//  IN :  
//  OUT: 
// 
//  RETURNS: 
// 
//**************************************************************************/
void
Filelist::display()
{
        int i = 0;
        int y;
        Screen.erase();
        menubar.show();
        display_header();
        if (open_new == true) {
                sline.set_infotext( "List of QWK packets" );
        } else {
                sline.set_infotext ("List of databases" );
        }
        if (filelist.first() == false) {
                Screen.gotoxy (0, ymin);
                if (open_new == true) {
                        Screen.print( "No files matches with pattern \'%s\' "
                                      "in a directory : \'%s\'",
                                      pattern.c_str(),
                                      Settings.qwkpath.c_str() );
                } else {
                        Screen.print( "No message databases available." );
                }
                return;
        }
        
        while (i < first) {
                filelist.next();
                ++i;
        }

        i = first;
        y = ymin;

        do {
                        
                Screen.gotoxy (0,y);
                if (i == current) {
                        Screen.inverse();
                        ypos = y;

                } 
                draw_current();
                Screen.reset_attr();
                ++y;
                ++i;
        } while (filelist.next() == true && y <= ymax);


        if (filelist.count_items() <= ymax - ymin) {
                sline.set_percent( ALL_PERCENT );
        } else if (0 == first) {
                sline.set_percent( TOP_PERCENT );
        } else {
                sline.set_percent( (100*i)/filelist.count_items() );
        }
        sline.show();

        filelist.first();
        i = current;
        while (i) {
                filelist.next();
                --i;
        }
        iline.show( REFRESH_ALL_FL );
        Screen.gotoxy (0,ypos);
}

//**************************************************************************/
//  CLASS: Filelist
//  MEMBER FUNCTION: draw_current
// 
// 
//  DESCRIPTION: Displays name of the current file.
// 
// 
//  EXTERNAL REFERENCES
//  IN :  
//  OUT: 
// 
//  RETURNS: 
// 
//**************************************************************************/
void
Filelist::draw_current()
{
        Screen.empty_line();
        Screen.goto_x( 8 );
        Screen.print( "%s", filelist.get()->c_str() );
}

//**************************************************************************/
//  CLASS: Filelist
//  MEMBER FUNCTION: display_header
// 
// 
//  DESCRIPTION: Displays header of screen...
// 
// 
//  EXTERNAL REFERENCES
//  IN :  
//  OUT: 
// 
//  RETURNS: 
// 
//**************************************************************************/
void
Filelist::display_header()
{
        Screen.reset_attr();
        Screen.gotoxy( 0, 1 );
        Screen.textcolor( Settings.header_subject_color );
        Screen.print( "%s -- A QWK compatible offline mail reader",
                      Settings.version_str.c_str() );
        Screen.textcolor( Settings.header_author_color );
        Screen.print("\nCopyright (c) 1997,1998 Jukka Vuokko. All rights reserved.");
        Screen.reset_attr();
        Screen.print( "\nThis version is compiled at %s" , __DATE__ );
        Screen.print( "\nOfficial jmr homepage: http://iki.fi/"
                      "jvuokko/jmr/");
        Screen.gotoxy( 0, 5 );
        set_dashline_color();
        Screen.print( dashline.c_str() );
        Screen.gotoxy( 7, 5 );
        if (open_new == true) {
                Screen.print( " packets: " );
        } else {
                Screen.print( " databases: " );
        }
        Screen.reset_attr ();

}
        
//**************************************************************************/
//  CLASS: Filelist
//  MEMBER FUNCTION: next
// 
// 
//  DESCRIPTION: Moves to next file.
// 
// 
//  EXTERNAL REFERENCES
//  IN :  
//  OUT: 
// 
//  RETURNS: 
// 
//**************************************************************************/
void
Filelist::next()
{
        if (current < (filelist.count_items()-1)) {
                if (ypos == ymax) {
                        first = ++current;
                        display();
                } else {
                        Screen.gotoxy( 0, ypos );
                        Screen.empty_line();
                        draw_current();
                        Screen.gotoxy( 0, ypos+1 );
                        filelist.next();
                        Screen.inverse();
                        draw_current ();
                        Screen.reset_attr();
                        ++current;
                        ++ypos;
                }
        } else {
                iline.set( AT_TOP_MSG );
        }

}

//**************************************************************************/
//  CLASS: Filelist
//  MEMBER FUNCTION: prev
// 
// 
//  DESCRIPTION: Moves to previous file.
// 
// 
//  EXTERNAL REFERENCES
//  IN :  
//  OUT: 
// 
//  RETURNS: 
// 
//**************************************************************************/
void
Filelist::prev()
{
        if (current > 0) {
                if (ypos == ymin && first > 0) {
                        --current;
                        first -= lines;
                        display();
                } else {
                        Screen.gotoxy( 0, ypos );
                        Screen.empty_line();
                        draw_current ();
                        Screen.gotoxy( 0, ypos-1);
                        Screen.inverse();
                        filelist.prev();
                        draw_current ();
                        Screen.reset_attr();
                        --current;
                        --ypos;
                }
        } else {
                iline.set( AT_BOTTOM_MSG );
        }
}

void
Filelist::page_down()
{
        int max = filelist.count_items()-1;
        if (current >= max) {
                iline.set( AT_LAST_PAGE_MSG );
                return;
        }
        if (ypos == ymax) {
                first = ++current;
                for (int i=1; i < lines && current < max; i++, current++);
                ypos = ymin + (current - first);
        } else {
                while (current < max && ypos < ymax) {
                        ++current;
                        ++ypos;
                }
        }
        display();
}

void
Filelist::page_up()
{
        int i = lines;
        if (current == 0) {
                iline.set( AT_FIRST_PAGE_MSG );
                return;
        }
        if (ypos == ymin) {
                while (i && first > 0) {
                        --first;
                        --current;
                        --i;
                }
        } else {
                while (ypos > ymin) {
                        --ypos;
                        --current;
                }
        }
        display();
}



jmrsrc-0.7.23/src/groupselect.cc100664    764    764       12710  6623366026  16224 0ustar  jvuokkojvuokko// $Id: groupselect.cc,v 1.4 1997/11/17 17:16:59 jvuokko Exp $

/*****************************************************************************
 * *
 * *      MODULE:     groupselect.cc
 * *                  --------------
 * ***************************************************************************
 * *
 * *
 * *      COPYRIGHT (C) 1997 JUKKA VUOKKO. ALL RIGHTS RESERVED
 * ***************************************************************************
 * *
 * *      This module contains functions for selecting group from
 * *      list of all groups.
 * *
 *****************************************************************************/


#include "groupselect.hh"
#include "mail.hh"

extern Terminal_screen Screen;
extern Mail *mail;
extern settings_t Settings;

Groupselection::Groupselection()
{
        grpnode *gnode;
        int     savegrp;
        win = Screen.newwin( 0, 0, Screen.get_xmax()+1, Screen.get_ymax(),
                             WIN_SLINE | WIN_TITLEBAR);
        win->set_sline_text("* Available groups *");
        if (Screen.is_color_term() == true ) {
                win->set_border_colors( Settings.sline_color,
                                        Settings.slineback_color);
        } else {
                win->set_border_colors( INVERSE, INVERSE );
        }
        win->disable();
        savegrp = mail->get_group_number();
        mail->first_group();

        do {
                if (mail->get_group_number() >= BASEGROUP_NUMBER) {
                        continue;
                }
                gnode = new grpnode;
                gnode->number = mail->get_group_number();
                gnode->name   = mail->get_group_name();
                itemlist.add( gnode );
                DEBUG("Added to grouplist: " << gnode->name );
        } while (mail->next_group() == true );

        mail->go_group_number( savegrp );
}

Groupselection::~Groupselection()
{
        Screen.endwin( win );
}

grpnode
Groupselection::select()
{
        bool quit = false;
        grpnode selected;
        selected.number = mail->get_group_number();
        selected.name   = mail->get_group_name();
        int savedgrp = selected.number;
        ymin = 0;
        ymax = win->get_ymax();
        current_item = 0;
        pagesize = ymax - ymin + 1;
        maxitem = itemlist.count_items();
        pages = 1+ (maxitem / pagesize);
        --maxitem;

        // go to current group
        itemlist.first();
        do {
                if ( itemlist.get()->number == selected.number ) {
                        break;
                }
        } while( itemlist.next() == true && ++current_item );

        current_page = current_item / pagesize;
        first_of_page = current_page * pagesize;
        ypos = ymin + (current_item - first_of_page );

        win->enable();
        iline.set_default("@ENTER@=select [@n@]ext [@p@]rev [@q@]uit" ); 
        iline.show_default();

        win->clear();
        draw_page();
        do {
                int c = Screen.get_ch();
                iline.reset();
                switch( c ) {
                case PAGE_UP_CMD:
                case CODE_PPAGE:
                        if ( false == ppage() ) {
                                iline.set( AT_FIRST_PAGE_MSG );
                        }
                        break;
                case PAGE_DOWN_CMD:
                case CODE_NPAGE:
                        if( false == npage() ) {
                                iline.set( AT_LAST_PAGE_MSG );
                        }
                        break;
                case CODE_HOME:
                        first();
                        break;
                case CODE_END:
                        last();
                        break;
                case NEXT_CMD:
                case CODE_DOWN:
                        if( false == down() ) {
                                iline.set( AT_BOTTOM_MSG );
                        }
                        break;
                case PREV_CMD:
                case CODE_UP:
                        if( false == up() ) {
                                iline.set( AT_TOP_MSG );
                        }
                        break;
                case FORCED_QUIT_CMD:
#ifndef NO_EXCEPTIONS
                        throw quit_exception();
#else
                        Quit_flag = true;
                        // no break here!!!
#endif
                case QUIT_CMD:
                        quit = true;
                        break;
                case SELECT_CMD:
                        selected = *itemlist.get();
                        quit = true;
                        break;
                default:
                        iline.set( INVALID_COMMAND_MSG );
                        break;
                }
                iline.show();
        } while ( quit == false );

        mail->go_group_number( savedgrp );
        win->disable();
        return selected;
}


void
Groupselection::draw_item( int flag )
{
        String tmp;
        grpnode *grp = itemlist.get();
        win->reset_attr();
        if (flag & EMPHASIZED_FL) {
                win->inverse();
        }
        
        win->clr_line();
        
        win->goto_x (1);
        win->print( "%.3d", current_item );
        
        win->goto_x (10);
        win->print( "Group no: %d", grp->number );
        
        win->goto_x (28);
        if (!(flag & EMPHASIZED_FL)) {
                win->textcolor (Settings.subject_color);
        }
        win->addstr( grp->name.c_str() );
        
        win->reset_attr();

}



jmrsrc-0.7.23/src/jmr.cc100664    764    764      201164  6623366026  14503 0ustar  jvuokkojvuokko// 	$Id: jmr.cc,v 1.43 1998/11/14 20:42:39 jvuokko Exp $	

/****************************************************************************
 * *
 * *  MODULE : jmr.cc
 * *
 * **************************************************************************
 * *
 * *    This file is part of jmr offline mail reader.
 * *    Copyright (C) 1997,1998 Jukka Vuokko <jvuokko@iki.fi>
 * *
 * *    This program is free software; you can redistribute it and/or modify
 * *    it under the terms of the GNU General Public License as published by
 * *    the Free Software Foundation; either version 2 of the License, or
 * *    (at your option) any later version.
 * *
 * *    This program is distributed in the hope that it will be useful,
 * *    but WITHOUT ANY WARRANTY; without even the implied warranty of
 * *    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * *    GNU General Public License for more details.
 * *
 * *    You should have received a copy of the GNU General Public License
 * *    along with this program; if not, write to the Free Software
 * *    Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
 * *
 * **************************************************************************
 * *
 * *  Main program and signal handling
 * *
 ***************************************************************************/

#include <new.h>        // set_new_handler()
#include <stdio.h>      // sprintf(), rand(), srand()

#ifdef __WATCOMC__
#include <io.h>         // access()
#endif

#include <stdlib.h>     // EXIT_FAILURE, EXIT_SUCCESS
#include <signal.h>
#include <time.h>       // time()

#ifdef __WATCOMC__
#   include <direct.h>
#elif defined (__EMX__)
#   include <unistd.h>   // F_OK
#   include <sys/types.h>
#   include <dirent.h>
#else
#   include <unistd.h>   // F_OK
#   include <dirent.h>
#endif


#include "jmr.hh"
#include "menu.hh"
#include "mail.hh"
#include "filelist.hh"
#include "tree.hh"


/*
 * Defaults for a resources
 */
const char DEFAULT_PACK_CMD[] = "zip -jkq";
const char DEFAULT_UNPACK_CMD[] = "unzip -oLqq";
const char DEFAULT_QUOTEPREFIX[] = "> ";
const char DEFAULT_WILDCARD[] = "*.[qQ]*";
const char DEFAULT_PTAG[] = "";
const char DEFAULT_TAG[] = "";
const char DEFAULT_FULL_SYSNAME[] = "no";
const bool DEFAULT_CONFIRM_RETURN = true;
const int  DEFAULT_REPLYLOGSIZE = 200;
const int  DEFAULT_DATABASESIZE = 5000;
const int  DEFAULT_QUOTECOLOR = GREEN;
const int  DEFAULT_BODYCOLOR = CYAN;
const int  DEFAULT_MENU_COLOR = WHITE | BOLD;
const int  DEFAULT_MENU_BCOLOR = BLUE | BOLD;
const int  DEFAULT_SLINE_COLOR = YELLOW | BOLD;
const int  DEFAULT_SLINE_BCOLOR = BLUE | BOLD;
const int  DEFAULT_DASHLINE_COLOR = DEFAULT_FCOLOR;
const int  DEFAULT_DASHLINE_BCOLOR = DEFAULT_BCOLOR;
const int  DEFAULT_BASEGROUP_COLOR = GREEN | BOLD;
const int  DEFAULT_SUBJECT_COLOR = GREEN;
const int  DEFAULT_AUTHOR_COLOR = CYAN;
const int  DEFAULT_ILINE_COLOR = DEFAULT_FCOLOR;
const int  DEFAULT_ILINE_ERRCOLOR = RED | BOLD;
const int  DEFAULT_HEADER_SUBJECT_COLOR = BOLD;
const int  DEFAULT_HEADER_AUTHOR_COLOR = GREEN;
const int  DEFAULT_PERSONAL_COLOR = YELLOW | BOLD;
const char DEFAULT_FOLLOWUPSTR[] = "In article <%m> %n wrote:";
const char DEFAULT_REPLYSTR[] = "On a message dated %d you wrote:";
const char DEFAULT_DATESTR[] = "%D.%M.%Y";
const char DEFAULT_WRAPIND = '>';
const char DEFAULT_RECEIVER[] = "";
const char DEFAULT_FOLDER[] = "";
const char DEFAULT_TAGLINE_PROGRAM[] = "";
const char DEFAULT_SIGNATUREFILE[] = "";
const bool DEFAULT_RMOLDPACKET = RM_OLD_NO;
#ifdef F_TO_ALL
const String DEFAULT_FOLLOWUPTO = FUT_WRITER;
#endif

const char DEFAULT_QUOTECHARS[] = ":>%&!/#"; 
const int DEFAULT_QUOTEDEPTH = 4;

#ifdef __unix__
const char DEFAULT_EDITOR[] = "/usr/bin/jed";
#else
const char DEFAULT_EDITOR[] = "jed.exe";
#endif




//**************************************************************************/
//
// CLASS: Jmrmain
//
//
// DESCRIPTION: Methods for handling mainloop of program.
//
//**************************************************************************/
class Jmrmain {
public:
        Jmrmain (int ac, char **av);
        ~Jmrmain(){};
private:        
        void init();
        void init_paths();
        void init_defaults();
        void init_settings();
        void parse_arguments ();
        void init_extractdir();
        void get_jmrdir();
        void check_jmrdir();
        void create_rc_file (char *file);
        void read_lines ();
        bool parse_rcline (String&, int);
        void mainloop();
        bool check_argument (int&);
        void show_help();
        void set_textcolor_value (int& c, String& value);
           //void set_backcolor_value (int&, String& );

        bool init_all;      // if this is true, then write new rc-file
        int argc;           // number of items in commandline
        char **argv;        // command parameters
        Filelist filelist;    // qwk-packet selection level
        Groupmenu *groupmenu; // group selection level
        
        String resource_file;  // name of resource file
        key_tree keywords;     // Syntax tree for resources   

           // Extra variables by Yka
        String open_jmr;	// Name of packet or database to open.
                                // If empty assume normal jmr behaviour
};


/************************ GLOBAL VARIABLES ***********************************/
Terminal_screen Screen;
settings_t Settings;
Mail* mail;
Window *Wscript;
#ifdef NO_EXCEPTIONS
bool  Quit_flag = false;
#endif

// This array defines rules for keywords in a resource file.
// "*" means that the keyword must have a value.
// ""             a value of the keyword can be empty.
// "$"            a value must be a number.
//
// Format of an array:
//   Keyword, rule1, rule2, ruleN,
//   "@NEXT",
//   .
//   .
//   .
//   "@NEXT", "@END"
static const char *rc_values[] = {
        RC_MAILPATH,"*",
        "@NEXT",
        RC_REPLYPATH,"*",
        "@NEXT",
        RC_QUOTEPREFIX,"*",
        "@NEXT",
        RC_EDITOR, "*",
        "@NEXT",
        RC_UNPACK, "*",
        "@NEXT",
        RC_PACK, "*",
        "@NEXT",
        RC_SYSCHARSET, "latin1", "ibmpc", "7bit",
        "@NEXT",
        RC_BBSCHARSET, "latin1", "ibmpc", "7bit",
        "@NEXT",
        RC_WILDCARD, "*",
        "@NEXT",
        RC_PTAG, "",
        "@NEXT",
        RC_EXTRACTDIR, "",
        "@NEXT",
        RC_REPLYLOGSIZE, "$",
        "@NEXT",
        RC_DATABASESIZE, "$",
        "@NEXT",
        RC_QUOTECOLOR, COLOR_VALUES,
        "@NEXT",
        RC_BODYCOLOR, COLOR_VALUES,
        "@NEXT",
        RC_WRAPMODE, "yes", "no",
        "@NEXT",
        RC_FOLLOWUPSTR, "",
        "@NEXT",
        RC_REPLYSTR, "",
        "@NEXT",
        RC_TAG, "",
        "@NEXT",
        RC_FULL_SYSNAME, "yes", "no",
        "@NEXT",
        RC_CONFIRM_RETURN, "yes", "no",
        "@NEXT",
        RC_DATESTR, "*",
        "@NEXT",
        RC_MENU_COLOR, COLOR_VALUES,
        "@NEXT",
        RC_MENU_BCOLOR, COLOR_VALUES,
        "@NEXT",
        RC_SLINE_COLOR, COLOR_VALUES,
        "@NEXT",
        RC_SLINE_BCOLOR, COLOR_VALUES,
        "@NEXT",
        RC_DASHLINE_COLOR, COLOR_VALUES,
        "@NEXT",
        RC_DASHLINE_BCOLOR, COLOR_VALUES,
        "@NEXT",
        RC_SUBJECT_COLOR, COLOR_VALUES,
        "@NEXT",
        RC_AUTHOR_COLOR, COLOR_VALUES,
        "@NEXT",
        RC_ILINE_COLOR, COLOR_VALUES,        
        "@NEXT",
        RC_ILINE_ERROR_COLOR, COLOR_VALUES,
        "@NEXT",
        RC_HEADER_SUBJECT_COLOR, COLOR_VALUES,
        "@NEXT",
        RC_BASEGROUP_COLOR, COLOR_VALUES,
        "@NEXT",
        RC_HEADER_AUTHOR_COLOR, COLOR_VALUES,
        "@NEXT",
        RC_PERSONAL_COLOR, COLOR_VALUES,
        "@NEXT",
        RC_DEFAULT_RECEIVER,"",
        "@NEXT",
        RC_DEFAULT_FOLDER,"",
        "@NEXT",
        RC_TAGLINE_PROGRAM,"",
        "@NEXT",
        RC_SIGNATUREFILE,"",
        "@NEXT",
        RC_RMOLDPACKET, "yes", "no","ask",
        "@NEXT",

	// IV
	RC_QUOTECHARS, "",
	"@NEXT",
	RC_QUOTEDEPTH, "$",
	"@NEXT",

#ifdef F_TO_ALL
        RC_FOLLOWUPTO, "*",
        "@NEXT",
#endif
        "@END"
};

/************* PROTOTYPES FOR FUNCTIONS IN THIS MODULE ***********************/

void memory_error_handler();


/***************************************************************************
 * FUNCTION: main
 ***************************************************************************
 *
 * DESCRIPTION: main function of the jmr.
 *
 * EXTERNAL REFERENCES
 * IN : 
 * OUT: Settings.currentdir, Screen
 *
 * RETURN : EXIT_SUCCESS
 ***************************************************************************/
int main(int argc, char** argv)
{
        signal (SIGINT, die);
        signal (SIGTERM, die);
#ifndef __WATCOMC__
        signal (SIGKILL, die);
#endif

#ifdef __unix__
        signal (SIGTSTP, suspend);
#endif
        
        set_new_handler (memory_error_handler);
        set_misc_lib_error_handler( fatal_error );
        
        Settings.currentdir[0] = '\0';
        
        // initialize random generator
        srand( (unsigned int) time( NULL ) );
        
        int h = Screen.get_ymax();
        if ( h < 19 || Screen.get_xmax() < 79 ) {
                cerr << "Required size of the terminal is 20x80.\n";
                cerr << "Your terminal has " << h+1 << " lines and "
                     << Screen.get_xmax()+1 << " columns." << endl;

                exit( EXIT_FAILURE );
        }
        
        Wscript = Screen.newwin( 0, 1, Screen.get_xmax()+1, h - 1,
                                 WIN_SLINE);
        Wscript->set_border_colors( DEFAULT_FCOLOR| INVERSE,
                                    DEFAULT_BCOLOR);
        Wscript->set_sline_text("* Message Buffer *");

        
        Jmrmain jmrmain (argc, argv);

        return EXIT_SUCCESS;
}



/************************************************************************
 * FUNCTION : die
 ************************************************************************
 *
 * If SIGTERM, SIGKILL or SIGINT signal received, this procedure tries to
 * save all necessary data before exit.
 *
 * EXTERNAL VARIABLE REFERENCES:
 *  IN : mail
 *  OUT:
 *
 * Parameters:
 *    int sig    signal 
 *
 ***********************************************************************/
void
die(int sig)
{
        //Screen.bottom();
        Screen.enable();
        Wscript->enable();
        
        Wscript->print( "\n\nTerminate signal (%d) received.\n", sig);
	Screen.set_origmode();
        if (mail == 0) {
                exit (EXIT_FAILURE);
        }
        if (mail->have_replies() == true) {
                Wscript->print( "Emergency exit. Storing new replies...\n");
                Screen.refresh();
                mail->emergency_exit();
        }
        exit (EXIT_FAILURE);
}


/***************************************************************************
 * FUNCTION: memory_error_handler
 ***************************************************************************
 *
 * Function for handling memory errors. The new[]-operator calls this if
 * memory is exceed.
 *
 * EXTERNAL VARIABLE REFERENCES:
 *   IN : 
 *   OUT: 
 *
 * RETURN: 
 ***************************************************************************/
void
memory_error_handler()
{
        handle_error (NO_MEMORY_ERROR, HALT_FL);
}

/***************************************************************************
 * FUNCTION: suspend
 ***************************************************************************
 *
 * DESCRIPTION: Caughts SIGTSTP-signal (^Z).
 *
 * EXTERNAL REFERENCES
 * IN : Screen
 * OUT:
 *
 ***************************************************************************/
#ifdef __unix__
void
suspend(int )
{
        sigset_t mask;
        sigemptyset( &mask );
        sigaddset( &mask, SIGTSTP );

        Screen.set_origmode();

        sigprocmask( SIG_UNBLOCK, &mask, NULL );
        signal (SIGTSTP, SIG_DFL);
        kill( getpid(), SIGTSTP );
        signal( SIGTSTP, suspend );
        Screen.set_rawmode();
        Screen.refresh( REDRAW_ALL );

}
#endif


//**************************************************************************/
//  CLASS: Jmrmain
//  MEMBER FUNCTION: Jmrmain
// 
//  DESCRIPTION: Main funtion of class.
// 
// 
//  EXTERNAL REFERENCES
//  IN :  Settings, Screen
//  OUT:  Quit_flag 
// 
//  RETURNS: 
//**************************************************************************/
Jmrmain::Jmrmain(int ac, char** av)
{
        argc = ac;
        argv = av;
        init_all = false;
        open_jmr = "";
#ifdef NO_EXCEPTIONS
        Quit_flag = false;
#endif
        parse_arguments();
        Screen.enable();
        Wscript->enable();
        init();

        if (Screen.is_color_term()) {
                Wscript->set_border_colors( Settings.sline_color,
                                            Settings.slineback_color );
        } 

           // change to working directory
        if (change_dir (Settings.workdir.get())) {
                char buf[1024];
                sprintf (buf, "Cannot change to direcetory : %s",
                         Settings.workdir.get());
                handle_error (buf, HALT_FL);
        }

        mainloop();
        Wscript->enable();
           // back to original directory
        change_dir (Settings.currentdir);

}

//**************************************************************************/
//  CLASS: Jmrmain
//  MEMBER FUNCTION: mainloop
// 
//  DESCRIPTION: Main loop of program
// 
// 
//  EXTERNAL REFERENCES
//  IN :  Settings, mail, Quit_flag
//  OUT:  mail
// 
//**************************************************************************/
void
Jmrmain::mainloop()
{
        bool open_new;
	String tmppath;

        
  loop:
        open_new = false;
        mail = 0;
        if (ACCESS (DEADJMR_FILENAME, F_OK) == 0) {
                Wscript->enable();
                Wscript->print( "\nPrevious session was killed and replies was"
                                "wrote to recovery file.\n" );
                Wscript->print( "Continue killed session with these "
                                "replies? (y/n)? y" );
                Screen.refresh();
                if (get_yesno('y') == 'n') {
                        remove (DEADJMR_FILENAME);
                } else if (0 == (mail = Mail::open_dead())) {
                        handle_error("Cannot open recovery file.",
                                     REPORT_FL);
                }
        }
        if (mail == 0) {
                if (open_jmr.is_empty()) {
                        Wscript->disable();
                        mail = filelist.open_file();
                        if (mail == 0) {
                                return;
                        }
                } else {
                        Wscript->disable();
                        mail = filelist.open_file(open_jmr); 
                           // skip interactive mode
                        if (!mail) {
                                return;
                        }
                       

                }

        }
        Wscript->disable();
#ifdef NO_EXCEPTIONS
        groupmenu = new Groupmenu();
#else
        try {
                groupmenu = new Groupmenu();
        }
        catch( quit_exception ) {
                Wscript->enable();
                Wscript->reset_attr();
                Wscript->textcolor( BOLD );
                Wscript->print("\n'Quit immediately' command received!");
                Wscript->reset_attr();
                Screen.refresh();
                delete mail;
                Wscript->print( "\nCleaning working directory..." );
                Screen.refresh();
                clear_workdir();
                Wscript->print("OK");
                return;
        }
#endif
	Wscript->enable();

	switch (Settings.rmoldpacket) 
	  {
	
	  case RM_OLD_YES : 
	    if ( ! mail->get_packet_name().is_empty() ) 
	      {
		Screen.refresh();
		tmppath = Settings.qwkpath;
		correct_path( tmppath );
		tmppath += mail->get_packet_name();
		Wscript->print( "\nRemoving old packet..." );
		Wscript->print( mail->get_packet_name() );
		rm_files( tmppath ); 
		Wscript->print(" OK");
	      }
	    break;


	  case RM_OLD_NO : break;


	  case RM_OLD_ASK : 
	    if ( ! mail->get_packet_name().is_empty() )
	      {
		tmppath = Settings.qwkpath;
		correct_path( tmppath );
		
		Wscript->print( "\nDo you want to delete packet ");
		Wscript->print( mail->get_packet_name() );
		Wscript->print( " (y/n)? n" );
		Screen.refresh();
		if (get_yesno('n') == 'y') {
		  Wscript->print( "\nRemoving old packet..." );
		  tmppath += mail->get_packet_name();
		  rm_files( tmppath );
		  Wscript->print("OK");
		} 
	      }
	    break;
	  }
	
        delete groupmenu;
        delete mail;
        
        Wscript->enable();
        Wscript->print( "\nCleaning working directory..." );
        Screen.refresh();
        clear_workdir();
        Wscript->print("OK");
	


#ifdef NO_EXCEPTIONS
        if (Quit_flag == true || !open_jmr.is_empty()) {    // --open-jmr means immediate quit after reading.
			
					
                return;
        }
#endif
        goto loop;
}

//**************************************************************************/
//  CLASS: Jmrmain
//  MEMBER FUNCTION: init
// 
//  DESCRIPTION: Reads contents of a resource file and initializes Settings.
// 
// 
//  EXTERNAL REFERENCES
//  IN : rc_values[]
//  OUT: Settings
// 
//  RETURNS: 
//**************************************************************************/
void Jmrmain::init()
{
        Wscript->enable();
        Wscript->print( "\n*** jmr %s (alpha) ***", VERSION );
        Wscript->print( " (compiled@ %s )\n", __DATE__ );
        Screen.refresh();

#ifdef __unix__
           // check for color capable terminal
        char *cterm;
        if ( (cterm = getenv( "COLORTERM" )) ) {
                Screen.force_colormode();
                Wscript->print( "\n\nColor capable terminal '%s' detected!\n",
                                cterm );
                Screen.refresh();
        }
#endif /* __unix__ */

        keywords.init( rc_values );

        init_paths();

        read_lines();
        init_defaults();
        init_settings();
        init_extractdir();

        if( Settings.full_sysname == "no" ) {
                Settings.system = get_system_info();
        } else {
                Settings.system = get_system_info( ALL_FL );
        }

        Settings.version_str = "jmr " VERSION " ";
        Settings.version_str += Settings.system;

        Settings.tag = "* " + Settings.version_str + " * ";

        // set up wrap indicator 
        switch ( Settings.syscharset ) {
        case ISO_LATIN_CHARSET:
                Settings.wrap_indicator = 0xbb;
                break;
        case IBM_PC_CHARSET:
                Settings.wrap_indicator = 175;
                break;
        default:
                Settings.wrap_indicator = DEFAULT_WRAPIND;
        };
                        
        Wscript->enable();
        Wscript->print( "Resource file is OK" );
        Screen.refresh();
        
        DEBUG("---------- SETTINGS ---------");
        DEBUG("zipcmd ="<<Settings.zipcmd);
        DEBUG("unzipcmd ="<<Settings.unzipcmd);
        DEBUG("qwkpath ="<<Settings.qwkpath);
        DEBUG("replypath ="<<Settings.replypath);
        DEBUG("quoteprefix ="<<Settings.quoteprefix);
        DEBUG("searchpattern ="<<Settings.searchpattern);
        DEBUG("ptagline ="<<Settings.ptagline);
        DEBUG("tagline ="<<Settings.tagline);
        DEBUG("replylogsize ="<<Settings.replylogsize);
        DEBUG("datebasesize ="<<Settings.databasesize);
        DEBUG("extractdir ="<<Settings.extractdir);
        DEBUG("quotecolor ="<<Settings.quote_color);
        DEBUG("textcolor ="<<Settings.article_color);
        DEBUG("followup ="<<Settings.followup);
        DEBUG("reply ="<<Settings.reply);
        DEBUG("syscharset ="<<Settings.syscharset);
        DEBUG("bbscharset ="<<Settings.bbscharset);
        DEBUG("menucolor =" <<Settings.menu_color);
        DEBUG("menuback = "<<Settings.menuback_color);
        DEBUG("slinecolor = "<<Settings.sline_color);
        DEBUG("slineback = "<<Settings.slineback_color);
        DEBUG("dashcolor = "<<Settings.dash_color);
        DEBUG("dashback = "<<Settings.dashback_color);
        DEBUG("subjectcolor = "<<Settings.subject_color);
        DEBUG("ilinecolor = "<<Settings.iline_color);
        DEBUG("default_receiver = "<<Settings.default_receiver);
        DEBUG("signaturefile = " << Settings.signaturefile );
        DEBUG("taglinre = " << Settings.tagliner );
        DEBUG("rmoldpacket = " << (Settings.rmoldpacket ? Settings.rmoldpacket == RM_OLD_YES ? "yes" : "ask" : "no") );
#ifdef F_TO_ALL
        DEBUG("followupto = " << Settings.followupto );
#endif

	DEBUG("quotechars = " << Settings.quotechars );
	DEBUG("quotedepth = " << Settings.quotedepth );
        DEBUG("-------- END OF SETTINGS ----");
}

//**************************************************************************/
// CLASS: jmrmain
// MEMBER FUNCTION: init_defaults
//**************************************************************************/ 
//
// This function initializes contents of Settings structure with a 
// default values.
//
// NOTE: All paths must be initialised (init_paths()) before using this.
//
// EXTERNAL VARIABLE REFERENCES
//   IN : Settings.currentdir
//   OUT: Settings
// 
// PARAMETERS:
//
// RETURN: 
//**************************************************************************/
void Jmrmain::init_defaults()
{
        Settings.zipcmd = DEFAULT_PACK_CMD;
        Settings.unzipcmd = DEFAULT_UNPACK_CMD;
        Settings.qwkpath = Settings.currentdir;
        correct_path( Settings.qwkpath );
        Settings.replypath = Settings.currentdir;
        correct_path( Settings.replypath );
        Settings.quoteprefix = DEFAULT_QUOTEPREFIX;
        Settings.searchpattern = DEFAULT_WILDCARD;
        Settings.ptagline = DEFAULT_PTAG;
        Settings.tagline = DEFAULT_TAG;
        Settings.full_sysname = DEFAULT_FULL_SYSNAME;
        Settings.confirm_return = DEFAULT_CONFIRM_RETURN;
        Settings.replylogsize = DEFAULT_REPLYLOGSIZE;
        Settings.databasesize = DEFAULT_DATABASESIZE;
        Settings.extractdir = ""; // this is initialized later
        Settings.max_subject_len = MAX_SUBJECT_LEN;
        Settings.max_bbs_subject_len = MAX_SUBJECT_LEN;
        Settings.is_pcboard = false;
        Settings.quote_color = DEFAULT_QUOTECOLOR;
        Settings.article_color  = DEFAULT_BODYCOLOR;
        Settings.followup = DEFAULT_FOLLOWUPSTR;
        Settings.reply = DEFAULT_REPLYSTR;
        Settings.date_fmt = DEFAULT_DATESTR;
        Settings.editor = DEFAULT_EDITOR;
        Settings.menu_color = DEFAULT_MENU_COLOR;
        Settings.menuback_color = DEFAULT_MENU_BCOLOR;
        Settings.sline_color = DEFAULT_SLINE_COLOR;
        Settings.slineback_color = DEFAULT_SLINE_BCOLOR;
        Settings.dash_color = DEFAULT_DASHLINE_COLOR;
        Settings.dashback_color = DEFAULT_DASHLINE_BCOLOR;
        Settings.subject_color = DEFAULT_SUBJECT_COLOR;
        Settings.author_color = DEFAULT_AUTHOR_COLOR;
        Settings.iline_color = DEFAULT_ILINE_COLOR;
        Settings.iline_err_color = DEFAULT_ILINE_ERRCOLOR;
        Settings.header_subject_color = DEFAULT_HEADER_SUBJECT_COLOR;
        Settings.basegroup_color = DEFAULT_BASEGROUP_COLOR;
        Settings.personal_color = DEFAULT_PERSONAL_COLOR;
        Settings.header_author_color = DEFAULT_HEADER_AUTHOR_COLOR;
        Settings.is_wrapmode = false;
	Settings.default_receiver = DEFAULT_RECEIVER;
	Settings.default_folder = DEFAULT_FOLDER;
        Settings.tagliner = DEFAULT_TAGLINE_PROGRAM;
        Settings.signaturefile = DEFAULT_SIGNATUREFILE;
        Settings.rmoldpacket = DEFAULT_RMOLDPACKET;
#ifdef F_TO_ALL
        Settings.followupto = DEFAULT_FOLLOWUPTO;
#endif
	Settings.quotechars = DEFAULT_QUOTECHARS;
	Settings.quotedepth = DEFAULT_QUOTEDEPTH;
}

//**************************************************************************/
//  CLASS: Jmrmain
//  MEMBER FUNCTION: init_settings
// 
//  DESCRIPTION: Initializes settings with a values readed from
//               a resource file.
// 
// 
//  EXTERNAL REFERENCES
//  IN :  
//  OUT: Settings
// 
//  RETURNS: 
//**************************************************************************/
void Jmrmain::init_settings()
{
        String value;
        String tmp;
        int errors = 0;

           // first init required resources
        
        value = keywords.get_value( RC_SYSCHARSET );
        if (value.is_empty()) {
                ++errors;
                tmp = "Resource \'";
                tmp += RC_SYSCHARSET;
                tmp += "\' not found";
                handle_error(tmp.get(), REPORT_FL );
        } else {
                if (value == "ibmpc") {
                        Settings.syscharset = IBM_PC_CHARSET;
                } else if (value == "latin1") {
                        Settings.syscharset = ISO_LATIN_CHARSET;
                } else {
                        Settings.syscharset = ASCII_7BIT_CHARSET;
                }
        }


        value = keywords.get_value( RC_BBSCHARSET );
        if (value.is_empty()) {
                ++errors;
                tmp = "Resource \'";
                tmp += RC_BBSCHARSET;
                tmp += "\' not found";
                handle_error( tmp.get(), REPORT_FL );
        } else {
                if (value == "ibmpc") {
                        Settings.bbscharset = IBM_PC_CHARSET;
                } else if (value == "latin1") {
                        Settings.bbscharset = ISO_LATIN_CHARSET;
                } else {
                        Settings.bbscharset = ASCII_7BIT_CHARSET;
                }
        }
#ifdef F_TO_ALL
        value = keywords.get_value( RC_FOLLOWUPTO );
        if ( value.is_empty() == false ) {
                Settings.followupto = value;
        } else {
                ++errors;
                tmp = "Resource \'";
                tmp += RC_FOLLOWUPTO;
                tmp += "\' not found";
                handle_error( tmp.get(), REPORT_FL );
        }
#endif

        if (errors) {
                handle_error("All required resources are not defined!",
                             HALT_FL);
        }


           // if resource is defined in file, then override default

        value = keywords.get_value( RC_EDITOR );
        if (value.is_empty() == false) {
                Settings.editor = value;
        }
        
        value = keywords.get_value( RC_PACK );
        if (value.is_empty() == false) {
                Settings.zipcmd = value;
        }

        value = keywords.get_value( RC_UNPACK );
        if (value.is_empty() == false) {
                Settings.unzipcmd = value;
        }
        
        value = keywords.get_value( RC_MAILPATH );
        if (value.is_empty() == false) {
                Settings.qwkpath = value;
                correct_path( Settings.qwkpath );
        }
        
        value = keywords.get_value( RC_REPLYPATH );
        if (value.is_empty() == false) {
                Settings.replypath = value;
                correct_path( Settings.replypath );
        }
        
        value = keywords.get_value( RC_QUOTEPREFIX );
        if (value.is_empty() == false) {
                value.replace_ch( '_', ' ' );
                Settings.quoteprefix = value;
        }
        
        value = keywords.get_value( RC_WILDCARD );
        if (value.is_empty() == false) {
                Settings.searchpattern = value;
        }

        value = keywords.get_value( RC_WRAPMODE );
        if ( value.is_empty() == false ) {
                Settings.is_wrapmode = value == "yes" ? true : false;
        }

        value = keywords.get_value( RC_PTAG );
        if (value.is_empty() == false) {
                Settings.ptagline = value;
        }
        
        value = keywords.get_value( RC_TAG );
        if (value.is_empty() == false) {
                Settings.tagline = value;
        }

        value = keywords.get_value( RC_FULL_SYSNAME );
        if (value.is_empty() == false) {
                Settings.full_sysname = value;
        }

        value = keywords.get_value( RC_CONFIRM_RETURN );
        if (value.is_empty() == false) {
//                Settings.confirm_return = value == "yes" ? true : false;
                Settings.confirm_return = (value == "yes");
        }
        
        value = keywords.get_value( RC_REPLYLOGSIZE );
        if (value.is_empty() == false) {
                Settings.replylogsize = atoi( value.get() );
        }

        value = keywords.get_value( RC_DATABASESIZE );
        if (value.is_empty() == false) {
                Settings.databasesize = atoi( value.get() );
        }

        value = keywords.get_value( RC_EXTRACTDIR );
        if (value.is_empty() == false) {
                correct_path( value );
                Settings.extractdir = value;
        }
                
        value = keywords.get_value( RC_QUOTECOLOR );
        set_textcolor_value (Settings.quote_color, value);

        value = keywords.get_value( RC_BODYCOLOR );
        set_textcolor_value (Settings.article_color, value);
        
        value = keywords.get_value( RC_FOLLOWUPSTR );
        if (value.is_empty() == false) {
                Settings.followup = value;
        }
        
        value = keywords.get_value( RC_REPLYSTR );
        if (value.is_empty() == false) {
                Settings.reply = value;
        }

        value = keywords.get_value( RC_DATESTR );
        if (value.is_empty() == false) {
                Settings.date_fmt = value;
        }
        value = keywords.get_value( RC_MENU_COLOR );
        set_textcolor_value (Settings.menu_color, value);
                
        value = keywords.get_value( RC_MENU_BCOLOR );
        set_textcolor_value( Settings.menuback_color, value );
        
        value = keywords.get_value( RC_SLINE_COLOR );
        set_textcolor_value (Settings.sline_color, value);
                
        value = keywords.get_value( RC_SLINE_BCOLOR );
        set_textcolor_value( Settings.slineback_color, value );
        
        value = keywords.get_value( RC_DASHLINE_COLOR );
        set_textcolor_value (Settings.dash_color, value);
        
        value = keywords.get_value( RC_DASHLINE_BCOLOR );
        set_textcolor_value( Settings.dashback_color, value );
        
        value = keywords.get_value( RC_SUBJECT_COLOR );
        set_textcolor_value (Settings.subject_color, value);

        value = keywords.get_value( RC_AUTHOR_COLOR );
        set_textcolor_value (Settings.author_color, value);
        
        value = keywords.get_value( RC_ILINE_COLOR );
        set_textcolor_value (Settings.iline_color, value);

        value = keywords.get_value( RC_ILINE_ERROR_COLOR );
        set_textcolor_value (Settings.iline_err_color, value);

        value = keywords.get_value( RC_BASEGROUP_COLOR );
        set_textcolor_value (Settings.basegroup_color, value);

        value = keywords.get_value( RC_HEADER_SUBJECT_COLOR );
        set_textcolor_value (Settings.header_subject_color, value);

        value = keywords.get_value( RC_HEADER_AUTHOR_COLOR );
        set_textcolor_value (Settings.header_author_color, value);

        value = keywords.get_value( RC_PERSONAL_COLOR );
        set_textcolor_value (Settings.personal_color, value);

        value = keywords.get_value( RC_DEFAULT_RECEIVER );
        if (value.is_empty() == false) {
                Settings.default_receiver = value;
        }

        value = keywords.get_value( RC_DEFAULT_FOLDER );
        if (value.is_empty() == false) {
                Settings.default_folder = value;
        }

        value = keywords.get_value( RC_SIGNATUREFILE );
        if ( value.is_empty() == false ) {
                Settings.signaturefile = value;
                expand_path( Settings.signaturefile );
        }

        value = keywords.get_value( RC_TAGLINE_PROGRAM );
        if ( value.is_empty() == false ) {
                Settings.tagliner = value;
                expand_path( Settings.tagliner );
        }
        value = keywords.get_value( RC_RMOLDPACKET );
        if ( value.is_empty() == false ) {
                if (value == "yes")
                        Settings.rmoldpacket = RM_OLD_YES;
                else if (value == "no")
                        Settings.rmoldpacket = RM_OLD_NO;
                else if (value == "ask")
                        Settings.rmoldpacket = RM_OLD_ASK;
                else {
                        tmp = "Resource \'";
                        tmp += RC_RMOLDPACKET;
                        tmp += "\' is terribly wrong";
                        handle_error( tmp.get(), HALT_FL );
                }
        }

	value = keywords.get_value( RC_QUOTECHARS );
	if ( ! value.is_empty() )
		Settings.quotechars = value;
		
	value = keywords.get_value( RC_QUOTEDEPTH );
	if ( ! value.is_empty() )
		Settings.quotedepth = atoi(value.get());

}


void
Jmrmain::set_textcolor_value (int& c, String& value)
{
        if (value.is_empty() == false) {
                c = atoi( value.get() );
                if( c > 7 ) {
                        c -= 7;
                        c |= BOLD;
                }
        }
}

        
//**************************************************************************/
//  CLASS: Jmrmain
//  MEMBER FUNCTION: read_lines
// 
//  This function reads contents of a resource file to a tree structure.
//  If syntax of the resource file is not correct, then program is halted.
// 
//  EXTERNAL REFERENCES
//  IN :  Settigs.jmrdir
//  OUT: 
// 
//  RETURNS: 
//**************************************************************************/
void
Jmrmain::read_lines ()
{
        String tmp;
        char line[MAX_LINE_LEN+1];
        int linenum = 1;
        bool rc = true;
        tmp = Settings.jmrdir + resource_file;

        ifstream rcfile( tmp.get(), ios::in );
        if (rcfile.fail()) {
                String err_str = "Cannot open resource file : " + tmp;
                handle_error (err_str.get(), HALT_FL);
        }
        
        Wscript->print( "\nParsing resource file \'%s\'....", tmp.c_str() );
        Screen.refresh();

        do {
                rcfile.getline (line, MAX_LINE_LEN);
                tmp = line;

                // remove preceeding spaces
                tmp.cut_first();

                // if line is not empty and it is not commented,
                // then check syntax of line and get value
                // of keyword.
                if (tmp.is_empty() == false && tmp[0] != '#') {
                        // remove trailing spaces
                        tmp.cut_tail();

                        // parse the line
                        if (parse_rcline( tmp, linenum ) == false) {
                                rc = false;
                        }
                }
                ++linenum;
        } while (!rcfile.eof());
        rcfile.close();

        if (rc == false) {
                handle_error("Fatal errors in resource file.",
                             HALT_FL);
        }
}

//**************************************************************************/
//  CLASS: Jmrmain
//  MEMBER FUNCTION: parse_rcline
// 
//  Checks that syntax of a line is right, and initializes keyword that
//  is defined in a line with a value given to it.
//  If syntax error occures, then false is returned.
// 
//  EXTERNAL REFERENCES
//  IN :  
//  OUT: 
// 
//  RETURNS: true if success.
//**************************************************************************/
bool
Jmrmain::parse_rcline (String& line, int num)
{
        int n=0;
        String tmp;

        // check that assigment operator is in it's place
        n = line.findch ('=');
        if (n == NOTHING) {
                String error_str;
                error_str = "Assigment operator is missing in line ";
                error_str += num;
                handle_error( error_str.get(), REPORT_FL);
                return false;
        }

        // Then get word before an assigment operator and check
        // if it matches with some of keywords in a trie
        tmp.copy( line, 0, n);
        tmp.cut_tail();
        if (false == keywords.goto_keyword( tmp )) {
                String error_str = "At line ";
                error_str += num;
                error_str += ": Illegal keyword : \'" + tmp + "\'";
                handle_error( error_str.get(), REPORT_FL);
                return false;
        }

        // Check that given value is legal, and store it
        // in a trie.
        tmp.copy( line, n + 1 );
        tmp.cut_first();
        tmp.cut_tail();
        if (false == keywords.set_value( tmp )) {
                String error_str = "Invalid value at line ";
                error_str += num;
                error_str += ": \'" + tmp + "\'";
                handle_error( error_str.get(), REPORT_FL );
                return false;
        }

        return true;
}


//**************************************************************************/
//  CLASS: Jmrmain
//  MEMBER FUNCTION: init_paths
// 
//  DESCRIPTION: Initializes directories for use.
// 
// 
//  EXTERNAL REFERENCES
//  IN :  Settings
//  OUT:  Settings.workdir
// 
//  RETURNS: 
//**************************************************************************/
void
Jmrmain::init_paths()
{
        String cmd;

        // initialize jmr's directory
        get_jmrdir();

        // Create directories for working files and for data files if
        // them not exist.
        Settings.datadir = Settings.jmrdir + DATA_DIRECTORY;
        Settings.workdir = Settings.jmrdir + WORK_DIRECTORY;

        if (ACCESS (Settings.datadir.get(), F_OK)) {
                   // FIXME: use mknod() or similar function instead
                cmd = CREATE_DIRECTORY_COMMAND;
                cmd += Settings.datadir;
                jmrsystem (cmd.get());
        }
        
        if (ACCESS (Settings.workdir.get(), F_OK)) {
                   // FIXME: use mknod() or similar function instead
                cmd = CREATE_DIRECTORY_COMMAND;
                cmd += Settings.workdir;
                jmrsystem (cmd.get());
        }
        // append path separator ('\' or '/')
        correct_path (Settings.datadir);
        correct_path (Settings.workdir);

        // store current directory
        getcwd (Settings.currentdir, MAXPATH);
}

//**************************************************************************/
//  CLASS: Jmrmain
//  MEMBER FUNCTION: get_jmrdir
// 
//  DESCRIPTION:  Initializes jmr's directory.
// 
// 
//  EXTERNAL REFERENCES
//  IN :  
//  OUT: Settings.jmrdir
// 
//  RETURNS: 
//**************************************************************************/
void
Jmrmain::get_jmrdir()
{
        char *dir;
        // get jmr's directory (where logs, resources etc. are)
        dir = getenv (DIR_ENVIRONMENT);
        if (dir == NULL) {
                // if environment variable is not set, then read variable
                // HOME, and use directory HOME/.jmr
                dir = getenv ("HOME");
                if (dir == NULL) {
                        handle_error ("Environment variable JMRDIR or HOME is not set!", HALT_FL);
                }
                Settings.jmrdir = dir;
                correct_path (Settings.jmrdir);
                Settings.jmrdir += DEFAULT_DIRECTORY;
        } else {
                Settings.jmrdir = dir;
        }
        correct_path (Settings.jmrdir);
        check_jmrdir();

}

//**************************************************************************/
//  CLASS: Jmrmain
//  MEMBER FUNCTION: check_jmrdir
// 
// DESCRIPTION: Checks if directory exist. If not, then creates it.
//              If directory does not contain file jmrrc, then create file
//              and tell user that it's necessary to modify it.
//  
// EXTERNAL REFERENCES
// IN : Settings.jmrdir
// OUT:
// 
//  RETURNS: 
//**************************************************************************/
void
Jmrmain::check_jmrdir()
{
        String cmd;
        String rcfile;
        int c = 0;
        // check if directory exists. If not, then create it
        if (ACCESS (Settings.jmrdir.get(), F_OK)) {
                Wscript->enable();
                Wscript->print( "\nDirectory \'%s\' does not exist.",
                                Settings.jmrdir.c_str() );
                Wscript->print( "\n\nShall I create it ? (y/n) y" );
                Screen.refresh();
                c = get_yesno('y');
                if (c == 'n') {
                        Wscript->print( "\nExiting...." );
                        exit(EXIT_SUCCESS);
                }
                
                cmd = CREATE_DIRECTORY_COMMAND;
                cmd += Settings.jmrdir;
                if (jmrsystem (cmd.get())) {
                        handle_error("Directory creating failed.",HALT_FL);
                }
        }
        rcfile = Settings.jmrdir;
        rcfile += RESOURCE_FILE_NAME;
        // check if resource-file exists. If not, create it.
        if (ACCESS (rcfile.get(), F_OK) || init_all == true) {
                create_rc_file(rcfile.get());
        }
}

//**************************************************************************/
//  CLASS: Jmrmain
//  MEMBER FUNCTION: init_extractdir
// 
//  DESCRIPTION: Gets directory for storing articles.
//
//  EXTERNAL REFERENCES
//  IN :
//  OUT: Settings.extractdir
//
//  RETURN :
//**************************************************************************/
void
Jmrmain::init_extractdir()
{
        String cmd;
        if ( Settings.extractdir == "" ) {
                char *path = getenv ("HOME");
                if (path == NULL) {
                        handle_error ("Either key 'extractdir' must defined in resource file\nor environment variable HOME must set.", HALT_FL);
                }
                Settings.extractdir = path;
                correct_path(Settings.extractdir);

                Settings.extractdir += EXTRACT_DIRECTORY;
        }
        if (ACCESS (Settings.extractdir.get(), F_OK)) {
                   // FIXME: Should use mknod() or similar function...
                cmd = CREATE_DIRECTORY_COMMAND;
                cmd += Settings.extractdir;
                DEBUG( "Exractdir = '" << Settings.extractdir.c_str() << "'" );
                jmrsystem (cmd.get());
        }
        correct_path( Settings.extractdir );
}

//**************************************************************************/
//  CLASS: Jmrmain
//  MEMBER FUNCTION: create_rc_file
// 
//  DESCRIPTION: Writes new resource file
// 
// 
//  EXTERNAL REFERENCES
//  IN :  
//  OUT: 
// 
//  RETURNS: 
//**************************************************************************/
void
Jmrmain::create_rc_file (char *file)
{
        ofstream fout (file);
        if (fout.fail()) {
                handle_error ("Cannot create default rc-file.",HALT_FL);
        }
        fout <<"\n########################################################################";
        fout <<"\n#";
        fout <<"\n#";
        fout <<"\n# Configuration file for jmr QWK offline mail reader. This file sets";
        fout <<"\n# up the runtime parameters and options.";
        fout <<"\n#";
        fout <<"\n# Parameters defined here are global for each bbs, but you can override";
        fout <<"\n# some of these using local, bbs related configuration files.";
        fout <<"\n# Sample for the local configuration file should be found at";
        fout <<"\n# /usr/local/lib/jmr/sample.rc, if jmr is properly installed.";
        fout <<"\n#";
        fout <<"\n#";
        fout <<"\n# Lines that start with a '#' are ignored.";
        fout <<"\n#";
        fout <<"\n# NOTE: Name of this file should be 'jmrrc' unless you specify name using";
        fout <<"\n#       parameter -f at commandline.";
        fout <<"\n#       This file must allways be in the directory, that ";
        fout <<"\n#       environment variable JMRDIR points or in directory";
        fout <<"\n#       $HOME/.jmr/";
        fout <<"\n#";
        fout <<"\n# NOTE: All paths must be absolute, and if your system uses drive-letters";
        fout <<"\n#       (like MS-DOG), path must start with drive-letter.";
        fout <<"\n#       e.g. in Unix systems replypath might be something like:";
        fout <<"\n#            /spool/send/";
        fout <<"\n#";
        fout <<"\n#            And in DOS-familian systems it might be:";
        fout <<"\n#            c:\\work\\uploads\\";
        fout <<"\n#";
        fout <<"\n#";
        fout <<"\n# Resources are divited to these sections:";
        fout <<"\n#    Required settings  -- Without these jmr wont run";
        fout <<"\n#    Basic settings     -- Defines paths, editor, zip command etc.";
        fout <<"\n#    Replying & quoting -- Sets up quoteprefix, tagline etc.";
        fout <<"\n#    Database           -- Define size of database and replylog";
        fout <<"\n#    Colors             -- Set up colors ";
        fout <<"\n#";
        fout <<"\n########################################################################";
        fout <<"\n";
        fout <<"\n";
        fout <<"\n";
        fout <<"\n";
        fout <<"\n########################################################################";
        fout <<"\n#";
        fout <<"\n#                         REQUIRED SETTINGS";
        fout <<"\n#";
        fout <<"\n# You must set up at least these two resources in order to run jmr.";
        fout <<"\n#";
        fout <<"\n########################################################################";
        fout <<"\n";
        fout <<"\n";
        fout <<"\n   # Define your systems characterset here. Possible values are:";
        fout <<"\n   # latin1     for ISO-LATIN1 character set. (e.g. Linux uses it)";
        fout <<"\n   # ibmpc      for PC 437 codepage. (e.g. OS/2, MS-DOG)";
        fout <<"\n   # 7bit       for 7bit ascii character set. Some old machines (?)";
        fout <<"\n   #";
        fout <<"\n   # E.g. systemcharset = latin1";
        fout <<"\nsystemcharset = ";
        fout <<"\n";
        fout <<"\n";
        fout <<"\n   #";
        fout <<"\n   # Character set that bbs is using.";
        fout <<"\n   #";
        fout <<"\nbbscharset = ";
        fout <<"\n";
        fout <<"\n";
        fout <<"\n########################################################################";
        fout <<"\n#";
        fout <<"\n#                          BASIC SETTINGS";
        fout <<"\n#";
        fout <<"\n########################################################################";
        fout <<"\n";
        fout <<"\n";
        fout <<"\n   # Shell command to unzip qwk-packet. Normally you don't need to change ";
        fout <<"\n   # this, unless you are using MS-DOG and you want to use pkzip/pkunzip.";
        fout <<"\n   # NOTE: Use unzip-program with option, that overwrites existing files!";
        fout <<"\n   # Default command is 'unzip -oLqq'";
        fout <<"\n#unzipcmd = pkunzip";
        fout <<"\n";
        fout <<"\n";
        fout <<"\n   # Shell command to pack replies. This should also work in your ";
        fout <<"\n   # system without changes.";
        fout <<"\n   # Default command is 'zip -jkq'";
        fout <<"\n#zipcmd = pkzip";
        fout <<"\n";
        fout <<"\n";
        fout <<"\n   # Set up path to directory, that holds your QWK mail packets.";
        fout <<"\n   # Default value is './'";
        fout <<"\n#qwkpath = c:\\work\\download";
        fout <<"\n#qwkpath = ~/work/download";
        fout <<"\n";
        fout <<"\n";
        fout <<"\n   # Set up path to directory, where replies goes. Usually it should be";
        fout <<"\n   # same directory, that you use for uploads in your terminal software.";
        fout <<"\n   # Default is './'";
        fout <<"\n#replypath = ~/work/upload";
        fout <<"\n";
        fout <<"\n";
        fout <<"\n   # This is the wildcard file specification to use when searching for";
        fout <<"\n   # packets to read. Accepts *, ? and list [].";
        fout <<"\n   # Default pattern is '*.[qQ]*'";
        fout <<"\n   # NOTE: Pattern is case-sensitive even in Os/2!";
        fout <<"\n#searchpattern = *.qwk";
        fout <<"\n";
        fout <<"\n";
        fout <<"\n   # Define the editor use for replies.";
        fout <<"\n   # Default is '/usr/bin/jed' in unix and 'jed.exe' in DOS-style";
        fout <<"\n   # operating systems.";
        fout <<"\n#editor = ";
        fout <<"\n";
        fout <<"\n";
        fout <<"\n   # Set up path to directory for saved messages.";
        fout <<"\n   #";
        fout <<"\n   # Normally program reads environment variable HOME, and extracts saved";
        fout <<"\n   # messages to subdirectory 'Messages' of directory that HOME points";
        fout <<"\n   # ($HOME/Messages/).";
        fout <<"\n   # ";
        fout <<"\n   # If You want to override HOME-environment variable, uncomment next line";
        fout <<"\n#extractdir = ~/work/articles";
        fout <<"\n";
        fout <<"\n";
        fout <<"\n   # Default name for file to save articles. ";
        fout <<"\n   # This name can be simple name like `saved_articles', when jmr creates";
        fout <<"\n   # it under directory defined by key `extractdir' or $HOME/Messages.";
        fout <<"\n   # Name of folder can also be relative like `../saved_articles',";
        fout <<"\n   # absolute like `/home/my_name/saved_articles' or `~/texts/saved_artcles";
        fout <<"\n   # ";
        fout <<"\n#default_folder = saved_articles";
        fout <<"\n";
        fout <<"\n";
        fout <<"\n   # This format string defines how date should be displayed. Default format";
        fout <<"\n   # for date is dd.mm.yyyy (%D.%M.%Y as a format string).";
        fout <<"\n   # Format specifiers are: %M for month, %D for day and %Y for year.";
        fout <<"\n#dateformat = %M/%D/%Y";
        fout <<"\n";
        fout <<"\n";
        fout <<"\n   # With this option set, lines are word wrapped at the end of screen,";
        fout <<"\n   # avoiding truncated messages.";
        fout <<"\n   # Note that wrap mode can also be changed in jmr using key 'ESC-w'.";
        fout <<"\n   # Default value is 'no'";
        fout <<"\n#wrapmode = yes";
        fout <<"\n";
        fout <<"\n";
        fout <<"\n   # If this is set to 'yes', then jmr asks confirm when returning to";
        fout <<"\n   # packet selection level from group selection level.";
        fout <<"\n   # Default value is 'yes'";
        fout <<"\n#confirm_return = no";
        fout <<"\n";
        fout <<"\n";
        fout <<"\n########################################################################";
        fout <<"\n#";
        fout <<"\n#                  SETTINGS FOR REPLYING & QUOTING";
        fout <<"\n#";
        fout <<"\n########################################################################";
        fout <<"\n";
        fout <<"\n";
        fout <<"\n   # Default value for name of receiver when composing new article.";
        fout <<"\n#default_receiver = All";
        fout <<"\n";
        fout <<"\n";
        fout <<"\n   # This describes how jmr will quote the lines of the source message when";
        fout <<"\n   # creating a new reply file. '_' indicates space.";
        fout <<"\n   # Default prefix is '> '";
        fout <<"\n#quoteprefix = :_";
        fout <<"\n";
        fout <<"\n";
        fout <<"\n   # This specifies all characters that might indicate that the line is";
        fout <<"\n   # quoted from someone else. Default is \":>%&!/#\".";
        fout <<"\n#quote_chars = >";
        fout <<"\n";
        fout <<"\n";
	fout <<"\n   # This tells jmr how deep from the line it should search for";
	fout <<"\n   # quote prefix characters. Default is four";
	fout <<"\n#quote_depth = 4";	
	fout <<"\n";
	fout <<"\n";
        fout <<"\n   # This sets up follow-up string. The following format specifiers are";
        fout <<"\n   # recognized:";
        fout <<"\n   # %d  = date of quoted article";
        fout <<"\n   # %t  = time";
        fout <<"\n   # %n  = name of author";
        fout <<"\n   # %s  = subject of article";
        fout <<"\n   # %m  = reference message id";
        fout <<"\n   # %M  = number of month";
        fout <<"\n   # %D  = number of day";
        fout <<"\n   # %Y  = year";
        fout <<"\n   # %%  = percent";
        fout <<"\n   #";
        fout <<"\n   # E.g. next string 'In article dated %d %n wrote:'";
        fout <<"\n   # Expands to something like: ";
        fout <<"\n   #   'In article dated 03-31-1997 John Smith wrote:'";
        fout <<"\n   #";
        fout <<"\n   # Default string is 'In article <%m> %n wrote:'";
        fout <<"\n#followupstring =";
        fout <<"\n";
        fout <<"\n";
        fout <<"\n   # Set up reply string. Syntax is same as in follow-up string, but";
        fout <<"\n   # this is used only in a private replies.";
        fout <<"\n   #";
        fout <<"\n   # Default string is 'On a message dated %d you wrote:'";
        fout <<"\n#replystring = ";
        fout <<"\n";
        fout <<"\n";
        fout <<"\n   # When this is set to 'no' then only name of the operating system";
        fout <<"\n   # is displayed at the tagline. Default is 'no'.";
        fout <<"\n   # With value 'yes', tagline looks like:";
        fout <<"\n   #         * jmr r.x.y.z [machine/system] *";
        fout <<"\n   # And with 'no', tag is:";
        fout <<"\n   #         * jmr r.x.y.z [system] *";
        fout <<"\n   #";
        fout <<"\n   # Note that this settings works only in Unix-like systems.";
        fout <<"\n#tag_full_sysname = yes";
        fout <<"\n";
        fout <<"\n";
        fout <<"\n   # Write your permanent tagline here.";
        fout <<"\n   # This line will come right after jmr's own tag.";
        fout <<"\n   # Usually you might want leave this empty...";
        fout <<"\n   #      Tagline should not be longer than this!!!";
        fout <<"\n   #      (---------------------------------------------------)";
        fout <<"\n#ptagline = ";
        fout <<"\n";
        fout <<"\n";
        fout <<"\n   # This sets the default tagline for articles.";
        fout <<"\n   # This string is placed at bottom of each reply, and it will start with";
        fout <<"\n   # three point ( e.g. '...this is the tagline')";
        fout <<"\n   #";
        fout <<"\n   # You can also specify file of taglines by using character '@'";
        fout <<"\n   # at start of tagline. Example: if tagline is:";
        fout <<"\n   #          @my_taglines";
        fout <<"\n   # Then jmr will gets random line from file my_taglines as a tagline.";
        fout <<"\n   #";
        fout <<"\n   # If file name is given without path, then jmr seeks file from jmr's";
        fout <<"\n   # directory ($JMRDIR or ~/.jmr) and if file not found, then jmr seeks";
        fout <<"\n   # from directory /usr/local/lib/jmr ";
        fout <<"\n   #";
        fout <<"\n   # NOTE: if you specify file with path, then you should use full path. e.g:";
        fout <<"\n   # @/home/my_name/sruff/tagline_file";
        fout <<"\n   # @C:\\misc\\taglines.txt";
        fout <<"\n#tagline = This message has been UNIXized for your protection.";
        fout <<"\n";
        fout <<"\n";
        fout <<"\n   # Use external program to generate tagline.";
        fout <<"\n   # This overrides both taglines, 'tagline' and 'ptagline'";
        fout <<"\n#tagline_program = /usr/games/fortune";
        fout <<"\n";
        fout <<"\n";
        fout <<"\n   # Set up signature file.";
        fout <<"\n   # If this is set, then jmr reads singature from the given";
        fout <<"\n   # file. Note that this overrides all tagline settings.";
        fout <<"\n#signaturefile = ~/.signature";
        fout <<"\n";
        fout <<"\n";
        fout <<"\n";
        fout <<"\n########################################################################";
        fout <<"\n#";
        fout <<"\n#                         DATABASE SETTINGS";
        fout <<"\n#";
        fout <<"\n########################################################################";
        fout <<"\n";
        fout <<"\n";
        fout <<"\n   # Maximum count of replies to store in log.";
        fout <<"\n   # Default value is 200.";
        fout <<"\n#replylogsize = 1500";
        fout <<"\n";
        fout <<"\n";
        fout <<"\n   # Maximum count of articles to keep in database";
        fout <<"\n   # NOTE: Database for single BBS with 10000 messages requires about";
        fout <<"\n   #       8-10MB free disk space in jmr's home directory, and 8-10MB";
        fout <<"\n   #       free disk space in temporary directory.";
        fout <<"\n   # Default size is 5000";
        fout <<"\n#databasesize = 10000";
        fout <<"\n";
        fout <<"\n";
        fout <<"\n#########################################################################";
        fout <<"\n#";
		fout <<"\n#                    Experimental code and keywords";
		fout <<"\n#";
		fout <<"\n#########################################################################";
		fout <<"\n";
		fout <<"\n";
		fout <<"\n    # If set to 'yes' jmr will automagically remove read qwk packet.";
		fout <<"\n    # Setting it to 'ask', will make jmr to ask permission to delete.";
		fout <<"\n    # Be careful! Not Tested and probably dangerous piece of code!";
                fout <<"\n    # The default value is 'no'.";
		fout <<"\n";
		fout <<"\n#rmoldpacket = ask";
		fout <<"\n";
#ifdef F_TO_ALL
		fout <<"\n    # To whom follow up should be addressed to ?";
		fout <<"\n    # Domain : all, writer, John Doe, ...";
		fout <<"\n    # Setting to writer assumes normal jmr behaviour.";
		fout <<"\n";
		fout <<"\nfollowupto = all";
		fout <<"\n";
#endif
		fout <<"\n";
        fout <<"\n#########################################################################";
        fout <<"\n#";
        fout <<"\n#                              COLORS";
        fout <<"\n#";
        fout <<"\n#########################################################################";
        fout <<"\n";
        fout <<"\n";
        fout <<"\n   # Set up color for quoted part of article";
        fout <<"\n   #";
        fout <<"\n   #    Colors:";
        fout <<"\n   #";
        fout <<"\n   #    0  use default screen color";
        fout <<"\n   #    1  red";
        fout <<"\n   #    2  green";
        fout <<"\n   #    3  brown";
        fout <<"\n   #    4  blue";
        fout <<"\n   #    5  magenta";
        fout <<"\n   #    6  cyan";
        fout <<"\n   #    7  lightgray";
        fout <<"\n   #    8  brightred";
        fout <<"\n   #    9  brightgreen";
        fout <<"\n   #    10 yellow";
        fout <<"\n   #    11 brightblue";
        fout <<"\n   #    12 brightmagenta";
        fout <<"\n   #    13 brightcyan";
        fout <<"\n   #    14 white";
        fout <<"\n   #";
        fout <<"\n   # For non-color terminals, colors are shown : 1 - 7 normal, 8 - 14 bold.";
        fout <<"\n   #";
        fout <<"\n   # Default color is green ";
        fout <<"\n#quote_color = ";
        fout <<"\n";
        fout <<"\n";
        fout <<"\n   # Set up color for normal text of article.";
        fout <<"\n   # Default is cyan ";
        fout <<"\n#article_color = ";
        fout <<"\n";
        fout <<"\n";
        fout <<"\n   # Foreground color for menubar.";
        fout <<"\n   # Default is white ";
        fout <<"\n#menubar_color = ";
        fout <<"\n";
        fout <<"\n";
        fout <<"\n   # Background color for menubar.";
        fout <<"\n   # Default is brightblue ";
        fout <<"\n#menubar_back = ";
        fout <<"\n";
        fout <<"\n";
        fout <<"\n   # Foreground color for statusline.";
        fout <<"\n   # Default is yellow ";
        fout <<"\n#statusline_color = ";
        fout <<"\n";
        fout <<"\n";
        fout <<"\n   # Background color for statusline.";
        fout <<"\n   # Default is brightblue ";
        fout <<"\n#statusline_back = ";
        fout <<"\n";
        fout <<"\n";
        fout <<"\n   # Foreground color for dashed line that splits screen.";
        fout <<"\n   # Default is normal text color";
        fout <<"\n#dashline_color = ";
        fout <<"\n";
        fout <<"\n";
        fout <<"\n   # Background color for dashed line that splits screen.";
        fout <<"\n   # Default is normal text color";
        fout <<"\n#dashline_back = ";
        fout <<"\n";
        fout <<"\n";
        fout <<"\n   # Color for text of infoline (that is line at bottom of the screen)";
        fout <<"\n   # Default is normal text color";
        fout <<"\n#infoline_color = ";
        fout <<"\n";
        fout <<"\n";
        fout <<"\n   # Color for error message that is displayed at the infoline";
        fout <<"\n   # Default is brightred";
        fout <<"\n#infoline_error_color = ";
        fout <<"\n";
        fout <<"\n";
        fout <<"\n   # Color for subject that is displayed at the header part of the screen";
        fout <<"\n   # Default is normal color, but bold text";
        fout <<"\n#header_subject_color = ";
        fout <<"\n";
        fout <<"\n";
        fout <<"\n   # Color for author that is displayed at the header part of the screen";
        fout <<"\n   # Default is color is green";
        fout <<"\n#header_author_color = ";
        fout <<"\n";
        fout <<"\n";
        fout <<"\n   # Color for subject that is displayed at the lists (grouplist, threadlist)";
        fout <<"\n   # Default is color is green";
        fout <<"\n#subject_color = ";
        fout <<"\n";
        fout <<"\n";
        fout <<"\n   # Color for name of the author at the thread list and color for the number";
        fout <<"\n   # of unread articles that is displayed at the group list";
        fout <<"\n   # Default is color is cyan";
        fout <<"\n#author_color = ";
        fout <<"\n";
        fout <<"\n";
        fout <<"\n   # Color for highlighting your name at the personal messages. ";
        fout <<"\n   # Default color is bright yellow";
        fout <<"\n#personal_color = ";
        fout <<"\n";
        fout <<"\n";
        fout <<"\n   # Color for name of the jmrs basegroups at the group list.";
        fout <<"\n   # Default is color is bright green";
        fout <<"\n#basegroup_color = ";
        fout <<"\n";
        fout.close();
        Wscript->enable();
        Wscript->reset_attr();
        Wscript->bold();
        Wscript->print( "\nResource file (%s) created with default settings.\nEdit file and set up required values.", file);

        exit (EXIT_SUCCESS);
}

//**************************************************************************/
//  CLASS: Jmrmain
//  MEMBER FUNCTION: parse_arguments
// 
//  DESCRIPTION: Checks correctness of command line arguments 
//
//  FIXME: Replace by using getopt_long someday... 
// 
// 
//  RETURNS: 
//**************************************************************************/
void Jmrmain::parse_arguments ()
{
        int i;
        resource_file = RESOURCE_FILE_NAME;
        if (argc == 1) {
                return;
        }
        for (i = 1; i < argc; i++) {
                if (check_argument (i) == false) {
                        cerr << "\njmr: illegal option -- "<<argv[i]<<"\n";
                        cerr << "try 'jmr --help' for more information.";
                        cerr << endl;
                        exit(EXIT_FAILURE);
                }
        }
}
//**************************************************************************/
//  CLASS: Jmrmain
//  MEMBER FUNCTION: check_argument
// 
//  DESCRIPTION: Checks if option <n> is legal.
// 
//  RETURNS: true if option is ok. Otherwise false.
//**************************************************************************/
bool Jmrmain::check_argument (int& n)
{
        if (strcmp (argv[n], "-f") == 0 ||
            strcmp (argv[n], "--rcfile") == 0) {
                if (argc > n+1) {
                        resource_file = argv[++n];
                        return true;
                } else {
                        return false;
                }
        }
        if (strcmp (argv[n], "-c") == 0 || strcmp (argv[n], "--color") == 0) {
                Screen.force_colormode();
                return true;
        }
        if (strcmp (argv[n], "-h") == 0 || strcmp (argv[n], "--help") == 0) {
                show_help();
                exit (EXIT_SUCCESS);
        }
        if (strcmp (argv[n], "--init") == 0) {
                init_all = true;
                return true;
        }
        if (strcmp (argv[n], "--open-jmr") == 0) {
                if ( argc > n+1 ) {
                        open_jmr = argv[++n];
                        return true;
                } else
                        return false;
        }
        return false;
}

//**************************************************************************/
//  CLASS: Jmrmain
//  MEMBER FUNCTION: show_help
// 
//  DESCRIPTION: Displays help for options of jmr.
// 
//**************************************************************************/
void Jmrmain::show_help()
{
        cerr << "jmr " << VERSION <<" -- A QWK offline mail reader";
        cerr << "\nCopyright (c) 1997,1998 Jukka Vuokko\n\n" ;
        cerr <<"Usage: jmr [-chl][-f file][--help][--color][--rcfile file][--init]\n\n";
        cerr <<"-h, --help                Show this help and exit.\n";
        cerr <<"-f file, --rcfile file    Use resource file 'file' instead of jmrrc\n";
        cerr <<"-c, --color               Force ANSI-color mode on. Useful in color-xterm\n";
        cerr <<"--init                    Write new jmrrc file\n";
        cerr <<"\nAdvanced option:\n";
        cerr <<"--open-jmr <bbsid>        Opens bbsid.qwk if its is there, otherwise \n";
        cerr <<"                          bbsid dabase is opened.\n";
}


jmrsrc-0.7.23/src/jmrmisc.cc100664    764    764       67740  6623366026  15351 0ustar  jvuokkojvuokko// $Id: jmrmisc.cc,v 1.3 1998/05/25 20:06:04 jvuokko Exp $
/*****************************************************************************
 * *
 * *      MODULE:     jmrmisc.cc
 * *                  ----------
 * *
 * *
 * * Revision  : $Revision: 1.3 $
 * * Date(UTC) : $Date: 1998/05/25 20:06:04 $
 * * Source    : $Source: /usr/local/cvsroot/jmr/src/jmrmisc.cc,v $
 * * Author    : $Author: jvuokko $
 * *
 * ***************************************************************************
 * *    This file is part of jmr offline mail reader.
 * *
 * *    Copyright (C) 1998 Jukka Vuokko <jvuokko@iki.fi>
 * *
 * *    This program is free software; you can redistribute it and/or modify
 * *    it under the terms of the GNU General Public License as published by
 * *    the Free Software Foundation; either version 2 of the License, or
 * *    (at your option) any later version.
 * *
 * *    This program is distributed in the hope that it will be useful,
 * *    but WITHOUT ANY WARRANTY; without even the implied warranty of
 * *    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * *    GNU General Public License for more details.
 * *
 * *    You should have received a copy of the GNU General Public License
 * *    along with this program; if not, write to the Free Software
 * *    Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
 * ***************************************************************************
 * *
 * *      This module contains miscallenous helper functions for jmr
 * *
 *****************************************************************************/

#include <stdio.h>      // sprintf(), rand(), srand(), tmpnam()
#include <time.h>       // time()
#include <stdlib.h>     // EXIT_FAILURE, EXIT_SUCCESS
#include <signal.h>
#include <string.h>     // strerror
#include <errno.h>

#ifdef __WATCOMC__      // Watcom stuff
#   include <io.h>         // access()
#   include <direct.h>
#elif defined (__EMX__)  // EMX-stuff
#   include <unistd.h>   // F_OK
#   include <sys/types.h>
#   include <dirent.h>
#else                    // UNIX
#   include <sys/types.h>
#   include <sys/wait.h>    // wait()
#   include <sys/utsname.h> // uname()
#   include <unistd.h>   // F_OK
#   include <dirent.h>
#   include <fcntl.h>    // open, write, creat, read
#endif

#include "constants.h"
#include "jmr.hh"
#include "time.hh"
#include "terminal_io.hh"
#include "linereader.hh"

//-------------------------------
// for set current diskdrive and copying file
#ifdef OS2
//#   define INCL_DOSERRORS
#   define INCL_ERRORS
#   define INCL_DOSFILEMGR
#   include <os2.h>
#endif

#if defined (WIN32) || defined (DOS)
#   include <dos.h>
#endif

#ifdef __unix__
#   include <unistd.h>  // getcwd, chdir
#endif

//****************** GLOBAL VARIABLES *************************************/
extern settings_t Settings;
extern Terminal_screen Screen;
extern Window *Wscript;

static const char* help_text[] = {
        "@COMMON MOVING COMMANDS:",
        "",
        "  HOME, ESC <    Beginning of list/article",
        "  END, ESC >     End of list/article",
        "  PGUP, Ctrl-v   Page up",
        "  PGDN, ESC v    Page down",
        "  UP ARROW       Line up",
        "  DOWN ARROW     Line down",
        "  LEFT ARROW     Back to previous level",
        "  RIGHT ARROW    Enter to next level",
        "",
        "@COMMON EDITING COMMANDS:",
        "",
        "  Ctrl-a, Ctrl-e Move to beginning or end of the line",
        "  Ctrl-k         Delete all characters, from cursor to end of line",
        "  Ctrl-h         Delete the previous character",
        "  Ctrl-b, Ctrl-f Move backward of forward",
        "",
        "@MAIL PACKET AND DATABASE SELECTION:",
        "",
        "  r              Refresh file or database list",
        "  n              Next packet or database",
        "  p              Previous packet or database",
        "  ENTER          Open packet or database",
        "  l              Switch to list of available databases or list of",
        "                   available mail packets",
        "  q, Q           Quit jmr",
        "",
        "@GROUP SELECTION:",
        "",
        "  n              Next group",
        "  p              Previous group",
        "  ENTER, ->      Enter to group",
        "  TAB            Enter to next group with unread articles",
        "  w              Write an article to current group",
        "  y              Switch between all groups and groups with unread articles",
        "  c              Catchup. Mark all articles in current group as read",
        "  C              Catchup without questions",
        "  t              Tag/untag current group",
        "  T              Tag/untag all groups",
        "  /              Search from current, tagged or all groups",
        "  q              Return to packet selection level",
        "",
        "@THREAD SELECTION:",
        "",
        "  n              Next thread",
        "  p              Previous thread",
        "  TAB            View next unread article within all threads",
        "  ENTER, ->      View first article of current thread",
        "  w              Write an article to current group",
        "  f              Write followup to current article or first article",
        "                   of current thread, if reading mode is by threads",
        "  r              Write private reply to current article (or first",
        "                   article of current thread)",
        "  y              Show all threads or only threads with unread articles",
        "  t              Tag/untag current article (does not work in thread mode)",
        "  T              Tag/untag all articles (not in thread mode)",
        "  s              Sort articles",
        "  c              Catchup. Mark all articles within current group as",
        "                   a read",
        "  C              Forced catchup",
        "  e              Edit current reply. Works only in group 'New Replies'",
        "  E              Edit headers of a current reply.",
        "  k              Kill/unkill article. Works only in groups Replylog,",
        "                   New Replies and Tagged Articles",
        "  q, <-          Return to group selection level",
        "",
        "@MESSAGE VIEWER:",
        "",
        "  TAB, ENTER     View next unread article within current group",
        "  n              View next article",
        "  p              View previous article",
        "  <, Ctrl-a      View first article of a current thread",
        "  >, Ctrl-e      View last article of a current thread",
        "  N              Next article in history",
        "  P              Previous article in history",
        "  f              Write followup to current article",
        "  r              Write private reply to current article",
        "  e              Edit current reply. Works only in group 'New Replies'",
        "  E              Edit headers of a current reply.",
        "  t              Tag/untag current article",
        "  k              Kill/unkill current article. Only in groups:",
        "                   Replylog, New Replies and Tagged Articles",
        "  s              Save current article or thread to file",
        "  SPC            Page down, or if end of current article, then",
        "                   view next unread article",
        "  ESC-r          View article using Rot13",
        "  ESC-w          Toggle line wrapping mode on/off",
        "  q, LEFT ARROW  Return to thread selection level",
        "",
        "@MISC",
        "",
        "  Ctrl-l         Redraw screen",
        "  m              Show message buffer",
        "  Q              Quit jmr",
        "@END" // This must be last item of the array!
};



//*************************************************************************/
// FUNCTION: read_tagline
//*************************************************************************/
//
// Function reads random line from given file.
//
//
// RETURN: line as String object.
//*************************************************************************/
String read_tagline (String const &file)
{
        List<String> *taglines;
        Linereader l( file );

        // read lines from file of taglines to linked list
        taglines = l.read_all_lines();

        // get random line
        String tmp;
        DEBUG( "read_tagline: Lines = " << taglines->count_items());
        int index = rand() % taglines->count_items();
        DEBUG( "read_tagline: random is: " << index );
        if ( taglines->first() == true ) {
                while ( index ) {
                        taglines->next();
                        --index;
                }
                tmp = *taglines->get();
        }
        delete taglines;
        return tmp;
}


//*************************************************************************/
// FUNCTION: get_tagline
//*************************************************************************/
//
// Function gets tagline from file of taglines, or from resource file.
//
// EXTERNAL VARIABLE REFERENCES:
//   IN : Settings.jmrdir, Settings.tagline
//   OUT: 
//
// RETURN: tagline as String
//*************************************************************************/
String
get_tagline ()
{
        // default tagline
        String tag = Settings.tagline;
        tag.cut_tail();
        // read random tagline from file.
        if (Settings.tagline.nget (0) == '@') {
                String file;
                String tmp;
                tmp.copy (Settings.tagline, 1);
                correct_path (tmp);  // expand possible ~
                tmp.ncut_end (1);    // cut trailing '/' or '\'
                file = tmp;
                if (ACCESS (file.get(), F_OK) == 0) {
                        tag = read_tagline (file);
                } else {
                        file = "/usr/local/lib/jmr/" + tmp;
                        if (ACCESS (file.get(), F_OK) == 0) {
                                tag = read_tagline (file);
                        } else {
                                file = Settings.jmrdir + tmp;
                                if (ACCESS (file.get(), F_OK) == 0) {
                                        tag = read_tagline (file);
                                } else {
                                        handle_error ("Cannot access file of taglines.");
                                        tag = "";
                                }
                        }
                }
        }

           // Remove possible ^M from the tagline. Seems like that
           // usually tagline file is in MS-DOG format.... 
           // (where is good old dos2unix?)
        tag.strip_ch( '\r' );
        return tag;
}

/***************************************************************************
 * FUNCTION: clear_workdir
 ***************************************************************************
 *
 * DESCRIPTION: Removes all files from working directory
 *
 * EXTERNAL REFERENCES
 * IN : Settings.workdir
 * OUT:
 *
 ***************************************************************************/
void
clear_workdir()
{
        String path;
        String file;
        String tmp;
        
        path = Settings.workdir;
        correct_path (path);
        
        file = path + "*.[nN][dD][xX]";
        rm_files( file );

        file = path + "*.[dD][aA][tT]";
        rm_files( file );

        file = path + "*.[iI][dD]";
        rm_files( file );

        file = path + "*.[mM][sS][gG]";
        rm_files( file );
        
        // remove files with exact name.
        // Under Os/2, readdir() returns uppercase name, if file is
        // stored to FAT file system. That's why in Os/2 it is needed
        // also check for uppercase names...
        tmp = DEADJMR_FILENAME;
        file = path + tmp;
        rm_files( file );

#ifdef OS2
        file = path + tmp.upcase();
        rm_files( file );
#endif
        
        tmp = DEADJMR_ID_FILENAME;
        file = path + tmp;
        rm_files( file );
#ifdef OS2
        file = path + tmp.upcase();
        rm_files( file );
#endif

        tmp = REPLY_TEMP_FILENAME;
        file = path + tmp;
        rm_files( file );
#ifdef OS2
        file = path + tmp.upcase();
        rm_files( file );
#endif  
}

/***************************************************************************
 * FUNCTION: handle_error
 ***************************************************************************
 *
 * DESCRIPTION: Shows error message, and if flag is HALT_FL, then program 
 *              is halted. 
 *
 *              flag:
 *              DEFAULT_FL     Show error message and wait for key
 *              REPORT_FL      Print error message to stderr, and continue
 *              HALT_FL        Print error message to stderr and halt.
 *
 * EXTERNAL REFERENCES
 * IN : Settings.currentdir
 * OUT:
 *
 ***************************************************************************/
void
handle_error (const char *msg, int flag)
{
        Screen.enable();
        Wscript->reset_attr();
        Wscript->textcolor (BOLD | RED);
        Wscript->enable();
        Wscript->print("\n*** ERROR: ");
        Wscript->reset_attr();
        Wscript->print("%s", msg );
        if (flag != HALT_FL && flag != REPORT_FL) {
                Wscript->print( "\nPress any key to continue.." );
                Screen.get_ch();
                Wscript->disable();
                Wscript->print("\n -----------------------------");
        } else if ( flag == HALT_FL) {
                Wscript->print("\n*** Program halted.\n" );
                Screen.refresh();
                if (Settings.currentdir[0]) {
                        change_dir (Settings.currentdir);
                }
                exit (EXIT_FAILURE);
        }
        Screen.refresh();
}

void
system_error ( const char *msg, int flag )
{
        String str = msg;
        str += " -- ";
        str += strerror( errno );
        handle_error( str.c_str(), flag );
}

void
fatal_error( const char *msg )
{
        handle_error( msg, HALT_FL );
}

/***************************************************************************
 * FUNCTION: convert_to_qwk_date
 ***************************************************************************
 *
 * DESCRIPTION: Converts date from format mm-dd-yyyy to QWK style mm-dd-yy
 *              (and wait year 2000 happy!)
 *
 *
 * RETURN : Pointer to c-string that contains date. Copy it immediately to
 *          the final location
 ***************************************************************************/
char*
convert_to_qwk_date (char *s)
{
        static char buf[12];
        strncpy (buf, s, 6);
        buf[6] = s[8];
        buf[7] = s[9];
        buf[8] = '\0';
        return buf;
}

/***************************************************************************
 * FUNCTION: convert_to_jmr_date
 ***************************************************************************
 *
 * DESCRIPTION: Converts date from stubid QWK format to better mm-dd-yyyy
 *              format
 *
 *
 * RETURN : pointer to static c-string that contains new date.
 ***************************************************************************/
char *
convert_to_jmr_date (char *s)
{
        Time_handler Time;
        static char buf[12];
        char *tmp;
        tmp = Time.get_date();
        strcpy (buf, s);
        buf[6] = tmp[6];
        buf[7] = tmp[7];
        buf[8] = s[6];
        buf[9] = s[7];
        buf[10] = '\0';
        return buf;
}
//*************************************************************************/
// FUNCTION: get_month
//*************************************************************************/
//
// Returns number of month from date that is in jmr's format (mm-dd-yyyy)
//
//*************************************************************************/
int
get_month (char *s)
{
        return (int) (10 * ((*s) - 48) + *(s+1) - 48);
}
//*************************************************************************/
// FUNCTION: get_day
//*************************************************************************/
//
// Returns number of day from date string that is in jmr's date format.
//
//*************************************************************************/
int
get_day (char *s)
{
        return (int) (10 * (*(s+3) - 48) + *(s+4) -48);
}
//*************************************************************************/
// FUNCTION: get_year
//*************************************************************************/
//
// Returns number of year from date string 
//
//*************************************************************************/
int
get_year (char *s)
{
        return atoi (s+6);
}


/***************************************************************************
 * FUNCTION: strip_re_prefix
 ***************************************************************************
 *
 * DESCRIPTION: Checks if prefix 're:' exist. If so, then remove prefix.
 *
 *
 * RETURN : true, if prefix 're:' found.
 ***************************************************************************/
bool
strip_re_prefix (String& str)
{
        bool rc = false;
        String tmp = str;
        
        tmp.cut_first();
        if (tmp.ifind ("re:") == 0) {
                rc = true;
                str.copy (tmp, 3);
        }

        str.cut_first();
        str.cut_tail();
        return rc;
}


/***************************************************************************
 * FUNCTION: get_yesno
 ***************************************************************************
 *
 * DESCRIPTION: Reads answer to question y/n. If user just press enter, then
 *              default_value is token as answer.
 *
 *
 * RETURN : ascii code of answer.
 ***************************************************************************/
int
get_yesno (int default_value)
{
        int c;
        do {
                c = Screen.get_ch();
        } while (c != 'y' && c != SELECT_CMD && c != 'n');
        if (c == SELECT_CMD) {
                c = default_value;
        }
        return c;
}

//*************************************************************************/
// FUNCTION: show_help
//*************************************************************************/
//
// This function shows help.
//
// EXTERNAL VARIABLE REFERENCES:
//   IN : Screen
//   OUT: 
//
// RETURN: 
//*************************************************************************/
void show_help()
{
        int y;
        int ymax = Screen.get_ymax() - 3;
        int index;
  loop:
        index = 0;
        int c;
        y = 0;
        Screen.clear();
        do {
                if (*help_text[index] == '@') {
                        Screen.textcolor( BOLD );
                        Screen.print( "%s\n", help_text[index]+1 );
                        //cout << help_text[index]+1 << "\n";
                        Screen.reset_attr();
                } else {
                        Screen.print( "%s\n", help_text[index] );
                        //cout << help_text[index] << "\n";
                }
                ++index;
                ++y;
                if (y == ymax) {
                        Screen.gotoxy( 0, ymax + 3);
                        Screen.print( "q to quit, ? to start over or any other key to continue" );
                        c = Screen.get_ch();
                        if (c == QUIT_CMD) {
                                return;
                        }
                        if (c == HELP_CMD) {
                                goto loop;
                        }
                        Screen.clear();
                        y = 0;
                        
                }
        } while ( strcmp( help_text[index], "@END" ) );
        Screen.gotoxy(0, ymax + 3);
        Screen.print( "? to start over, any other to exit from help" );
        c = Screen.get_ch();
        if (c == HELP_CMD) {
                goto loop;
        }
}

//**************************************************************************/
// CLASS: Infoline
// MEMBER FUNCTION: reset
//**************************************************************************/ 
//
// 
// 
// EXTERNAL VARIABLE REFERENCES
//   IN :  
//   OUT: 
// 
// PARAMETERS:
//
// RETURN: 
//**************************************************************************/

void
Infoline::reset()
{
        if (true == active) {
                Screen.bottom();
                _msg = "";
                show_option_str( default_msg.get(), Settings.iline_color );
                Screen.clr_eol();
                active = false;
        }
}

//**************************************************************************/
// CLASS: Infoline
// MEMBER FUNCTION: show
//**************************************************************************/ 
//
// 
// 
// EXTERNAL VARIABLE REFERENCES
//   IN :  
//   OUT: 
// 
// PARAMETERS:
//
// RETURN: 
//**************************************************************************/

void
Infoline::show( int flag)
{
        if (true == active || flag & REFRESH_ALL_FL) {
                int color = is_error ? Settings.iline_err_color
                        : Settings.iline_color;
                Screen.bottom();

                if (_msg.is_empty() == false) {
                        show_option_str( _msg.c_str(), color );
                } else {
                        show_option_str( default_msg.c_str(), color );
                }
                Screen.clr_eol();
        }
}
//**************************************************************************/
// CLASS: Infoline
// MEMBER FUNCTION: show_default
//**************************************************************************/ 
//
// 
// 
// EXTERNAL VARIABLE REFERENCES
//   IN :  
//   OUT: 
// 
// PARAMETERS:
//
// RETURN: 
//**************************************************************************/
void
Infoline::show_default()
{
        Screen.bottom();
        show_option_str( default_msg.c_str(), Settings.iline_color | BOLD,
                         Settings.iline_color);
        Screen.clr_eol();
}


//**************************************************************************/
// CLASS: Infoline
// MEMBER FUNCTION: set
//**************************************************************************/ 
//
// 
// 
// EXTERNAL VARIABLE REFERENCES
//   IN :  
//   OUT: 
// 
// PARAMETERS:
//
// RETURN: 
//**************************************************************************/
void
Infoline::set( const char* msg, int fl )
{
        active = true;
        if (fl & ERROR_FL ) {
                _msg = "*** ";
                is_error = true;
        } else {
                _msg = "";
                is_error = false;
        }
        _msg += msg;

}

void
Sline::show()
{
        Screen.gotoxy( 0, Screen.get_ymax() - 1 );
        Screen.reset_attr();
        if( Screen.is_color_term() == true ) {
                Screen.textcolor( Settings.sline_color );
                Screen.back_color( Settings.slineback_color );
        } else {
                Screen.inverse();
        }
        Screen.addstr( get_sline() );
        Screen.reset_attr();
}



//*************************************************************************/
// FUNCTION: set_dashline_color
//*************************************************************************/
//
// Sets colors of dashed line on.
//
// EXTERNAL VARIABLE REFERENCES:
//   IN : Settings.dash_color, Settings_dashback_color
//   OUT: Screen
//
// RETURN: 
//*************************************************************************/
void
set_dashline_color()
{
        Screen.reset_attr();
        if (Settings.dash_color != DEFAULT_FCOLOR) {
                Screen.textcolor( Settings.dash_color );
        }
        if (Settings.dashback_color != DEFAULT_BCOLOR) {
                Screen.back_color( Settings.dashback_color );
        } 
}
//*************************************************************************/
// FUNCTION: show_option_str
//*************************************************************************/
//
// Displays given string using given 'bold_color' in all text that are
// between '@' marks. Color for other text is 'color'.
//
// e.g. If msg is "Press @Q@ for quit", is letter Q highlighted using
// color 'bold_color' and other text is drawed using color 'color'.
//
// EXTERNAL VARIABLE REFERENCES:
//   IN : -
//   OUT: -
//
// RETURN: -
//*************************************************************************/
void
show_option_str( const char *msg, int bold_color, int color)
{
        bool bold = false;
        const char *ptr = msg;
        Screen.reset_attr();
        if (color != DEFAULT_FCOLOR) {
                DEBUG("Settings option color : " << color );
                Screen.textcolor( color );
        }
        while (*ptr) {
                if ( *ptr == '@' ) {
                        bold = bold == false ? true : false;
                        if (bold == true ) {
                                if (bold_color == DEFAULT_FCOLOR) {
                                        Screen.bold();
                                } else {
                                        Screen.textcolor( bold_color );
                                }
                        } else {
                                Screen.reset_attr();
                                if (color != DEFAULT_FCOLOR ) {
                                        Screen.textcolor( color );
                                }
                        }
                        if (! *++ptr ) {
                                break;
                        }
                }
                Screen.addch( *ptr );
                ++ptr;
        }
        Screen.reset_attr();
}

//*************************************************************************/
// FUNCTION: get_system_info
//*************************************************************************/
//
// Gets name of system, in long or short form. If flag is ALL_FL, then
// long form is used, i.e. '[processor/system_name]'. Short format is
// '[system_name]'.
//
// EXTERNAL VARIABLE REFERENCES:
//   IN : -
//   OUT: -
//
// RETURN: System name in string, between square brackets.
//*************************************************************************/
String
get_system_info( int flag )
{
        String str;
#ifdef DO_NOT_USE_UNAME
        if (flag == ALL_FL ) {
                str =  LONG_SYSTEM ;
        } else {
                str =  SYSTEM ;
        }
#else
        struct utsname sysdata;
        if ( uname( &sysdata ) <= 0 ) {
                str =  SYSTEM ;
        } else {
                str =  "[";
                if( flag == ALL_FL ) {
                        str += sysdata.machine;
                        str += "/";
                }
                str += sysdata.sysname;
                str +="]";
        }
#endif
        return str;
}

int jmrsystem( const char* cmd )
{
        Screen.set_origmode();
#ifdef __unix__
        struct sigaction act, save;
        act.sa_flags = 0;
        act.sa_handler = SIG_DFL;
        sigemptyset( &act.sa_mask );

        sigaction( SIGTSTP, &act, &save );
        int i = _system( cmd );
        sigaction( SIGTSTP, &save, NULL );
#else
        int i = _system( cmd );
#endif
        Screen.set_rawmode();
        return i;
}

/***************************************************************************
 * FUNCTION : get_integer
 ***************************************************************************
 *
 * description : Gets integer value from string in byte_t array.
 *
 * Example: Array = { 1,5,7,9 } ---> returned value is 1579
 *
 * return : value readed from array.
 ***************************************************************************/
int
get_integer (
            byte_t *str,   // pointer to first number
            int len)     // amount of numbers
{
        int i;
        char *tmp = new char[len+1];

        for (i=0; i < len; i++) {
                tmp[i] = (char) *(str+i);
        }
        tmp[i] = 0;
        i = atoi (tmp);
        delete[] tmp;        
        return i;
}

/***************************************************************************
 * FUNCTION : get_ushort
 ***************************************************************************
 *
 * description : returns value of two byte little-endian number
 *
 ***************************************************************************/
int
get_ushort (
           byte_t *str) // array of two bytes.
{
        int i = (int) str[0] + 256*(int) str[1];
        return i;
}
jmrsrc-0.7.23/src/linereader.cc100664    764    764        2311  6623366026  15756 0ustar  jvuokkojvuokko
#include "../utilib/my_types.h"
#include "linereader.hh"
#include "../utilib/String.hh"
#include "../utilib/List.hh"

Linereader::Linereader( const String &fname )
{
        fin.open( fname.c_str(), ios::in );
        failed = false;
        if (fin.fail()) {
                failed = true;
        }
        DEBUG(" ==== Linereader initialized" );
}

Linereader::~Linereader()
{
        fin.close();
        DEBUG(" ==== Linereader destroyed");
}

String
Linereader::read_line()
{
        char tmp[MAX_LINE_LEN];
        String line;
        if (true == failed) {
                DEBUG(" Linereader: read_line failed! ");
                return line;
        }
        fin.getline( tmp, MAX_LINE_LEN );
        if ( fin.fail() ) {
                failed = true;
        }
        line = tmp;
        return line;
}


List<String>*
Linereader::read_all_lines()
{
        List<String> *lines = new List<String>;
        
        String tmp;
        DEBUG( "Linereader: Reading contents of file" );
        for (;;) {
                tmp = read_line();
                if ( failed ) {
                        break;
                }
                lines->add( new String( tmp ) );
        }
        
        return lines;
}
jmrsrc-0.7.23/src/mail.cc100664    764    764      211353  6623366026  14636 0ustar  jvuokkojvuokko// $Id: mail.cc,v 1.25 1998/11/14 20:41:15 jvuokko Exp $
/*****************************************************************************
 * *
 * *      MODULE:     mail.cc
 * *                  ---------
 * ***************************************************************************
 * *
 * *
 * *      COPYRIGHT (C) 1997 JUKKA VUOKKO. ALL RIGHTS RESERVED
 * ***************************************************************************
 * *
 * *      This module contains functions of classes Group and Mail.
 * *
 *****************************************************************************/

#include <fstream.h>
#include <stdio.h>  // remove()
#ifdef __WATCOMC__
#   include <io.h>  // access()
#else
#   include <unistd.h> // F_OK, rename()
#endif
#include <time.h>
#include "mail.hh"
#include "qwk.hh"
#include "jmr.hh"


/************************ GLOBAL VARIABLES *********************************/
extern settings_t Settings;
extern Terminal_screen Screen;
extern Window* Wscript;


// This array defines rules for keywords in a resource file.
// "*" means that the keyword must have a value.
// ""             a value of the keyword can be empty.
// "$"            a value must be a number.
//
// Format of an array:
//   Keyword, rule1, rule2, ruleN,
//   "@NEXT",
//   .
//   .
//   .
//   "@NEXT", "@END"
static const char *bbs_setting_array[] = {
        RC_QUOTEPREFIX,"*",
        "@NEXT",
        RC_BBSCHARSET, "latin1", "ibmpc", "7bit",
        "@NEXT",
        RC_PTAG, "",
        "@NEXT",
        RC_EXTRACTDIR, "",
        "@NEXT",
        RC_REPLYLOGSIZE, "$",
        "@NEXT",
        RC_DATABASESIZE, "$",
        "@NEXT",
        RC_FOLLOWUPSTR, "",
        "@NEXT",
        RC_REPLYSTR, "",
        "@NEXT",
        RC_TAG, "",
        "@NEXT",
        "@END"
};



/***************************************************************************
 * FUNCTION : cmp_date
 ***************************************************************************
 *
 * description : Compares date and time fields of a two Message class
 *
 * return : <0, 0 or >0
 ***************************************************************************/
int cmp_date (const void *a, const void *b)
{
        Message **aa, **bb;

        aa = (Message**) a;
        bb = (Message**) b;

        return strcmp ((*aa)->get_cmpdate(), (*bb)->get_cmpdate());
}


/***************************************************************************
 * FUNCTION : cmp_subject
 ***************************************************************************
 *
 * description : Function for compare two subject field of a Message_t
 *               structure.
 *
 * return : <0, if first is less, 0 if equal, >0 if last is less than first.
 ***************************************************************************/
int cmp_subject (const void *a, const void *b)
{
        int rc;
        Message **aa, **bb;
        Message *cc, *dd;
        const String *s1, *s2;
        aa = (Message**) a;
        bb = (Message**) b;
        cc = aa[0];
        dd = bb[0];
        s1 = &cc->get_subject();
        s2 = &dd->get_subject();
        if ((rc = s1->compare (*s2)) == 0) {
                rc = cmp_date (a, b);
        }
        return rc;
}
/***************************************************************************
 * FUNCTION : cmp_thread
 ***************************************************************************
 *
 * description : Function for compare two stripped subject field of a
 *               Message_t structure.
 *
 *
 * return : <0, 0 or >0
 ***************************************************************************/
int cmp_thread (const void *a, const void *b)
{
        int rc;
        Message **aa, **bb;
        Message *cc, *dd;
        aa = (Message**) a;
        bb = (Message**) b;
        cc = aa[0];
        dd = bb[0];
        
        rc = cc->get_stripped_subject().compare( dd->get_stripped_subject() );
        if ( !rc ) {
                rc = cmp_date (a, b);
        }
        return rc;
}


/***************************************************************************
 * FUNCTION : cmp_writer
 ***************************************************************************
 *
 * description : compares writer-fields of a two Message_t structure
 *
 * return : guess what :-)
 ***************************************************************************/
int cmp_writer (const void *a, const void *b)
{
        int rc;
        Message **aa, **bb;
        Message *cc, *dd;
        const String *s1, *s2;
        aa = (Message**) a;
        bb = (Message**) b;
        cc = aa[0];
        dd = bb[0];
        s1 = &cc->get_writer();
        s2 = &dd->get_writer();
        if ((rc = s1->compare (*s2)) == 0) {
                rc = cmp_date (a, b);
        }
        return rc;
}

/***************************************************************************
 * FUNCTION : cmp_receiver
 ***************************************************************************
 *
 * description : compares receiver-fields
 *
 * return : <0, 0 or >0
 ***************************************************************************/
int cmp_receiver (const void *a, const void *b)
{
        int rc;
        Message **aa, **bb;
        Message *cc, *dd;
        const String *s1, *s2;
        aa = (Message**) a;
        bb = (Message**) b;
        cc = aa[0];
        dd = bb[0];
        s1 = &cc->get_receiver();
        s2 = &dd->get_receiver();
        if ((rc = s1->compare (*s2)) == 0) {
                rc = cmp_date (a, b);
        }
        return rc;
}


//**************************************************************************/
// CLASS: Group
// MEMBER FUNCTION: Group
//**************************************************************************/ 
//
// Constructor for class.
// 
//**************************************************************************/
Group::Group (
              const int& g_num,     // number of group
              const String& g_name) // name of group
{
        is_mirror = false;
        number = g_num;
        unread_msgs = 0;
        total_msgs = 0;
        status_ = DEFAULT_FL;
        name = g_name;
        numberstr.put (number, 3);
}

//**************************************************************************/
// CLASS: Group
// MEMBER FUNCTION: ~Group
//**************************************************************************/ 
//
// Destructor for class. Removes contents of all articles of group, but
// if group is marked as mirrored, then removes only list of messages,
// not contents of that list...
// 
//**************************************************************************/
Group::~Group()
{
        DEBUG("Deleting group: "<<number<<"   "<<name);
        if (is_mirror == true) {
                DEBUG("Mirrored!");
                messages.leave();
        } else {
                DEBUG("Normal");
                messages.destroy();
        }
        DEBUG("OK");
}
//**************************************************************************/
// CLASS: Group
// MEMBER FUNCTION: add
//**************************************************************************/ 
//
// Adds new article to end of exist articles.
// 
// EXTERNAL VARIABLE REFERENCES
//   IN :  
//   OUT: 
// 
// RETURN: true if success.
//**************************************************************************/
bool
Group::add (const Message *msg)
{
        ++total_msgs;
        if (! (msg->get_status() & READ_ST)) {
                ++unread_msgs;
        }
        messages.last();
        return messages.add (msg);
}
//**************************************************************************/
// CLASS: Group
// MEMBER FUNCTION: remove_article
//**************************************************************************/ 
//
// Removes current article. If group is mirrored, then does not free
// memory of listnodes.
// 
// EXTERNAL VARIABLE REFERENCES
//   IN :  
//   OUT: 
// 
// RETURN: 
//**************************************************************************/
void
Group::remove_article()
{
        Message *msg;

        msg = messages.get();

        if (! (msg->get_status() & READ_ST) ) {
                --unread_msgs;
        }
        --total_msgs;
        
        if (is_mirror == false) {
                messages.remove();
        } else {
                messages.leavenode();
        }
}

//**************************************************************************/
// CLASS: Group
// MEMBER FUNCTION: sort
//**************************************************************************/ 
//
// Sorts contents of group to using given order
// 
// EXTERNAL VARIABLE REFERENCES
//   IN :  
//   OUT: 
// 
// RETURN: 
//**************************************************************************/
void
Group::sort (const int order)
{
        switch (order) {
        case SUBJECT_SORTED:
                messages.sort(cmp_subject);
                break;
        case DATE_SORTED:
                messages.sort(cmp_date);
                break;
        case WRITER_SORTED:
                messages.sort(cmp_writer);
                break;
        case RECEIVER_SORTED:
                messages.sort(cmp_receiver);
                break;
        case THREAD_SORTED:
                messages.sort(cmp_thread);
                break;
        }

        sort_order = order;
}

//**************************************************************************/
// CLASS: Group
// MEMBER FUNCTION: update
//**************************************************************************/ 
//
// Updates statistics. Counts amount of unread articles.
// 
// EXTERNAL VARIABLE REFERENCES
//   IN :  
//   OUT: 
// 
// RETURN: 
//**************************************************************************/
void
Group::update()
{
        Message *msg;
        messages.save_position();
        unread_msgs = 0;
        total_msgs = 0;
        if (messages.first() == true) {
                do {
                        msg = messages.get();
                        if (! (msg->get_status() & READ_ST)) {
                                ++unread_msgs;
                        }
                        ++total_msgs;
                } while (messages.next() == true);
        }
        messages.restore_position();
}

//**************************************************************************/
// CLASS: Group
// MEMBER FUNCTION: mark_all_read
//**************************************************************************/ 
//
// Marks all articles as read.
// 
// EXTERNAL VARIABLE REFERENCES
//   IN :  
//   OUT: 
// 
// RETURN: 
//**************************************************************************/
void
Group::mark_all_read()
{
        Message *msg;
        messages.save_position();
        if (messages.first() == true) {
                do {
                        msg = messages.get();
                        msg->set_status_bits (READ_ST);
                } while (messages.next() == true);
        }
        unread_msgs = 0;
        messages.restore_position();
}
                
//**************************************************************************/
// CLASS: Group
// MEMBER FUNCTION: unload
//**************************************************************************/ 
//
// Removes lines (from memory) of those articles, that have valid handle 
// to a file, where lines are stored.
// 
// EXTERNAL VARIABLE REFERENCES
//   IN :  
//   OUT: 
// 
// RETURN: 
//**************************************************************************/
void
Group::unload()
{
        if (messages.first() == true) {
                do {
                        messages.get()->unload_lines();
                } while (messages.next() == true);
                messages.first();
        }
}




//**************************************************************************/
// CLASS: Mail
// MEMBER FUNCTION: Mail
//**************************************************************************/ 
//
// Constructor for class. This should be called in first constructor of 
// derived classes.
// 
// EXTERNAL VARIABLE REFERENCES
//   IN :  
//   OUT: 
//
// PARAMETERS:
//      char *packet_name  Name of mail packet (QWK etc). Name must in
//                         format <bbsid>.XXX.
//                         
// RETURN: 
//**************************************************************************/
Mail::Mail (const char *packet_name)
{
	DEBUG("Entering Mail::Mail");
        tmp_db_file = NULL;
        tmp_log_file = NULL;

        num_tagged_groups = 0;
        
        // get id of bbs from name of packet.
        bbsid = packet_name;
        int i = bbsid.findch ('.');
        if (i != NOTHING) {
                bbsid.ncut_end (bbsid.length() - i);
        }
        bbsid.s_downcase();

        have_new_replies = false;
        new_articles = 0;
        articles = 0;
        dead_flag = false;
        replyhandler = new Replyhandler;

        // read bbs related settings from resource file, if it exist.
        local_settings.set( bbsid );
        
        // read contents of message database and replylog
        Wscript->enable();
        Wscript->print( "\nReading message database..." );
        Screen.refresh();
        read_message_database();

        Screen.refresh();
        // if previous session was killed, then read replies from
        // emergency file
        if (ACCESS (DEADJMR_FILENAME, F_OK) == 0) {
                read_deadjmr();
        }
}

//**************************************************************************/
// CLASS: Mail
// MEMBER FUNCTION: create
//**************************************************************************/ 
//
// Creates new Mail-object, using child class, that matches with type
// of given mail packet.
//
// This function extracts contents of zipped mail packet, and solves
// type of packet by checking extracted files.
//
// If open_new_packet is false, then opens only exist message database, and
// does not open new mail packet.
//
// EXTERNAL VARIABLE REFERENCES
//   IN :  Settings.unzipcmd
//   OUT: 
// 
// RETURN: Pointer to created object, or 0 if failed.
//**************************************************************************/
Mail*
Mail::create (
              const char* packet_name,
              bool open_new_packet)
{
	DEBUG("Enterting Mail::create");
        String tmp;
        Mail *mail = 0;
        

        DEBUG("Clearing working directory");
        clear_workdir();
        Wscript->enable();
        Wscript->print( "\n----------------------------------------------\n");
        Wscript->print( "Opening mail packet/database: %s", packet_name );
        // extract mail packet
        if (open_new_packet == true) {
                tmp = Settings.unzipcmd + " " + Settings.qwkpath + packet_name;
                if ( jmrsystem (tmp.get()) ) {
                        char buf[256];
                        sprintf( buf, "Can't exec '%s' for unpacking mail packet.", 
                                 Settings.unzipcmd.c_str() );
                        handle_error( buf, HALT_FL );
                }
        }

        // check if type of packet is QWK.
        if (open_new_packet == false || ACCESS ("control.dat", F_OK) == 0) {
                // QWK-packet
		DEBUG("Creating Qwk object");
                mail = new Qwk (packet_name, open_new_packet);
        } else {
                handle_error ("Not a QWK packet", HALT_FL);
        }
#ifdef USE_DEBUG
        DEBUG( "GROUPS:" );
        mail->grouplist.first();
        do {
                DEBUG( mail->grouplist.get()->get_numstr() << " -- '"
                       << mail->grouplist.get()->get_name() << "'" );
        } while ( mail->grouplist.next() );
        mail->grouplist.first();
#endif
        return mail;
}

//**************************************************************************/
// CLASS: Mail
// MEMBER FUNCTION: open_dead
//**************************************************************************/ 
//
// Creates new Mail object using killed session.
// 
// EXTERNAL VARIABLE REFERENCES
//   IN :  
//   OUT: 
// 
// RETURN: 0 if failed, otherwise returns pointer to created object
//**************************************************************************/
Mail*
Mail::open_dead()
{
        Mail *mail = 0;
        char line[81];
        if (ACCESS (DEADJMR_FILENAME, F_OK) != 0) {
                return 0;
        }

        // read bbsid of killed session
        ifstream fin (DEADJMR_ID_FILENAME);
        if (fin.fail()) {
                return 0;
        }
        fin.getline (line, 80);
        fin.close();

        // check type of killed session
        if (ACCESS ("control.dat", F_OK) == 0) {
                // Type was QWK
                mail = new Qwk (line, true);
        } else {
                // if type of killed session is unknown, then read only
                // contents of message database and keep new replies
                // of killed session
                mail = new Qwk (line, false);
        }
        return mail;
}

//**************************************************************************/
// CLASS: Mail
// MEMBER FUNCTION: init
//**************************************************************************/ 
//
// Moves messages from list 'packet_articles' to message database, and
// sets up all necessary values. Function also removes deadjmr-files.
//
// NOTE: this function must be called before returned from constructor
//       of derived class!
// 
// EXTERNAL VARIABLE REFERENCES
//   IN :  
//   OUT:  Settings.is_pcboard, Settings.max_subject_len
// 
// RETURN: 
//**************************************************************************/
void
Mail::init()
{
        Wscript->enable();
        DEBUG("Moving messages to database");
        move_messages_to_base();
        DEBUG("OK");
        DEBUG("Setting up personal mail");
        set_personal_mail();
        DEBUG("OK");
        Wscript->print( "\nCreating article threads..." );
        Screen.refresh();

        clock_t start_time = clock();

        thread_sort_articles();

        clock_t end_time = clock();

        Wscript->print("OK (sorted in %.2f seconds)",
                       float((end_time - start_time) /(float) CLOCKS_PER_SEC));
        
        go_group_number (NEW_REPLIES_NUMBER);
        if (count_articles() > 0) {
                have_new_replies = true;
        }

        // check if bbs is useing PCBOARD-software, that allows
        // long subjects with QWK-packets.
        if (door.ifind ("pcboard") != NOTHING) {
                Settings.is_pcboard = true;
                Settings.max_subject_len = JMR_MAX_SUBJECT_LEN;
        } else {
                Settings.is_pcboard = false;
                Settings.max_subject_len = MAX_SUBJECT_LEN;
        }
        remove (DEADJMR_FILENAME);
        remove (DEADJMR_ID_FILENAME);
        write_recovery_file();
}

//**************************************************************************/
// CLASS: Mail
// MEMBER FUNCTION: emergency_exit
//**************************************************************************/ 
//
// Writes new replies to file 'deadjmr.msg' and sets flag
// dead_flag to true. Also writes ID of bbs to file 'deadjmr.id'.
//
// This function should be called if program is terminated abnormally.
//
// EXTERNAL VARIABLE REFERENCES
//   IN :  -
//   OUT:  -
// 
// RETURN: -
//**************************************************************************/
void
Mail::emergency_exit()
{
        dead_flag = true;
        write_recovery_file();
}
//**************************************************************************/
// CLASS: Mail
// MEMBER FUNCTION: write_recovery_file
//**************************************************************************/ 
//
// Writes new replies to recovery file 'deadjmr.msg' and writes ID of the
// bbs to file 'deadjmr.id'.
//
// NOTES: This saves and restores postion in list, so do not call this
//        when you have saved group position.
//
// EXTERNAL VARIABLE REFERENCES
//   IN :  -
//   OUT:  -
// 
// PARAMETERS: -
//
// RETURN: -
//**************************************************************************/
void
Mail::write_recovery_file( void )
{
        DEBUG("Creating recovery files");
        String fname = DEADJMR_ID_FILENAME;

        // write bbsid to file 'deadjmr.id'
        ofstream fout (fname.get());
        if (fout.fail()) {
                system_error ("Cannot write recovery file.. Sad but true.",
                              HALT_FL);
        }
        fout << bbsid;
        fout.close();

        // then write new replies to file 'deadjmr.msg'.
        fname = DEADJMR_FILENAME;
        
        if (false == dead_flag) {
                grouplist.save_position();
        }
        go_group_number( NEW_REPLIES_NUMBER );
        write_log( fname, NEW_REPLIES_NUMBER, count_articles() +1, true );
        if (false == dead_flag) {
                grouplist.restore_position();
        }
}

//**************************************************************************/
// CLASS: Mail
// MEMBER FUNCTION: ~Mail
//**************************************************************************/ 
//
// Destructor for class. Writes message database, replylog (if session
// was not killed).
// Removes temporary files.
// 
// EXTERNAL VARIABLE REFERENCES
//   IN :  
//   OUT: 
// 
// RETURN: 
//**************************************************************************/
Mail::~Mail()
{
        Wscript->enable();
        if (dead_flag == false) {

                Wscript->print( "\nWriting message database..." );
                Screen.refresh();
                write_message_database();
                Wscript->print("OK");
                Screen.refresh();
        }
        delete replyhandler;
        DEBUG("Removing database");
        grouplist.destroy();
        DEBUG("Database removed!");
        DEBUG("Closing files");
        if (tmp_db_file != 0) {
                tmp_db_file->close();
                remove (tmp_db_name.get());
                delete tmp_db_file;
        }
        if (tmp_log_file != 0) {
                tmp_log_file->close();
                remove (tmp_log_name.get());
                delete tmp_log_file;
        }

        // restore original settings
        local_settings.restore();
}

//**************************************************************************/
// CLASS: Mail
// MEMBER FUNCTION: move_messages_to_base
//**************************************************************************/ 
//
// Moves contents of packet_articles list to message database. If some of
// articles exist already in database, then ask from user, if it is ok to
// replace all exiest articles with newer.
//
// After function, list 'packet_articles' will be empty.
// 
// EXTERNAL VARIABLE REFERENCES
//   IN :  
//   OUT: 
// 
// RETURN: 
//**************************************************************************/
void
Mail::move_messages_to_base()
{
        Group *grp, *tmp_grp;
        Message *msg, *tmp_msg;
        int number;
        bool replace = false;
        bool load = true;
        new_articles = 0;
        
        if (packet_articles->first() == false) {
                DEBUG( "** No new articles from packet! ** ");
                return;
        }
        
        // go through every group readed from packet
        do {
                grp = packet_articles->get();
                assert (grp != 0);

                if (false == go_group_number (grp->get_number())) {
                        tmp_grp = new Group (grp->get_number(),
                                             grp->get_name());
                        grouplist.add (tmp_grp);
                } else {
                        tmp_grp = grouplist.get();
                        if ( tmp_grp->get_name() != grp->get_name() ) {
                                tmp_grp->set_name( grp->get_name() );
                        }
                }
                
                assert (tmp_grp != 0);
                if (grp->first_article() == false) {
                        continue;
                }
                // go through every article within a single group from packet
                do {
                        msg = grp->get_article();

                        assert (msg != 0);
                        
                        number = msg->get_number();

                        if (REPLYLOG_NUMBER == tmp_grp->get_number()
                            || NEW_REPLIES_NUMBER==tmp_grp->get_number()) {
                                tmp_grp->add (msg);
                                continue;
                        } else if (false == tmp_grp->first_article()) {
                                tmp_grp->add (msg);
                                ++new_articles;
                                continue;
                        } else if (false == load) {
                                continue;
                        }

                        do {
                                tmp_msg = tmp_grp->get_article();
                                if (tmp_msg->get_number() == number) {
                                        if (false == replace) {
                                                load = ask_replace();
                                                if (false == load) {
                                                        break;
                                                }
                                                replace = true;
                                        }
                                        // remove old one, if exist.
                                        tmp_grp->remove_article();
                                        break;
                                }
                        } while (true == tmp_grp->next_article());
                        // add new article to group
                        if (true == load) {
                                tmp_grp->add (msg);
                                ++new_articles;
                        }
                } while (true == grp->next_article());
                
        } while (true == packet_articles->next());
        

        packet_articles->leave();
}

//**************************************************************************/
// CLASS: Mail
// MEMBER FUNCTION: ask_replace
//**************************************************************************/ 
// 
// Asks if user wants to load contents of mail packet, even if articles
// are already in message database.
// 
// EXTERNAL VARIABLE REFERENCES
//   IN :  
//   OUT: 
// 
// RETURN: true if it's ok to load packet.
//**************************************************************************/
bool
Mail::ask_replace() const
{
        Wscript->enable();
        Wscript->print( "\n\nArticle is already in database. " );
        Wscript->print( "\nReload contents of mail packet to database (y/n) y" );
        int c = get_yesno ('y');
        if (c == 'y') {
                Wscript->print( "\bYes" );
                Screen.refresh();
                return true;
        }
        Wscript->print( "\bNo!" );
        Screen.refresh();
        return false;
}
//**************************************************************************/
// CLASS: Mail
// MEMBER FUNCTION: read_message_database
//**************************************************************************/ 
//
// Reads contents of message database and replylog. Does not read lines
// of articles to memory, reads only headers at this point. Lines are
// readed only when it is necessary (using Message::first_line() method).
//
// Before reading, original database and replylog is copied to temporaly
// files.
//
// EXTERNAL VARIABLE REFERENCES
//   IN :  
//   OUT: 
// 
// RETURN: 
//**************************************************************************/
void
Mail::read_message_database()
{
        Group *grp;
        String file = Settings.datadir + bbsid + ".gdb";
        String mfile = Settings.datadir + bbsid + ".mdb";
        String logfile = Settings.datadir + bbsid + ".log";
        
        // remove exist articles.
        // List should already be empty before this but...
        grouplist.destroy();

        // add basegroups to begin of list.
        grp = new Group (PERSONAL_NUMBER, PERSONAL_TEXT);
        grp->set_mirror();
        grouplist.add (grp);
        grp = new Group (REPLYLOG_NUMBER, REPLYLOG_TEXT);
        grouplist.add (grp);
        grp = new Group (NEW_REPLIES_NUMBER, NEW_REPLIES_TEXT);
        grouplist.add (grp);
        grp = new Group (TAGGED_ARTICLES_NUMBER, TAGGED_ARTICLES_TEXT);
        grp->set_mirror();
        grouplist.add (grp);
        grp = new Group (SEARCH_RESULTS_NUMBER, SEARCH_RESULTS_TEXT);
        grp->set_mirror();
        grouplist.add (grp);
        
        DEBUG("Reading message database and replylog");
        if (ACCESS (file.get(), F_OK) == 0 &&
            ACCESS (mfile.get(), F_OK) == 0) {
                DEBUG("Database exist!");
                tmp_db_name = get_tmpname( "jmr" );
                if (copy_file (mfile.get(), tmp_db_name.get()) == false) {
                        char buffer[256];
                        sprintf (buffer, "Cannot create temp file : %s\n",
                                 tmp_db_name.get());
                        system_error(buffer, HALT_FL );
                }
                read_groups_from_db();
                read_articles_from_db();
                Wscript->print("OK");
        } 

        if (ACCESS (logfile.get(), F_OK) == 0) {
                tmp_log_name = get_tmpname( "jmr" );
                if (copy_file (logfile.get(), tmp_log_name.get()) == false) {
                        char buffer[256];
                        sprintf (buffer, "Cannot create temp file : %s\n",
                                 tmp_db_name.get());
                        system_error (buffer, HALT_FL);
                }
                read_replylog();
        }
}
//**************************************************************************/
// CLASS: Mail
// MEMBER FUNCTION: read_groups_from_db
//**************************************************************************/ 
//
// Reads data of bbs, names and numbers of groups from group database
// (<bbsid>.gdb).
// 
// EXTERNAL VARIABLE REFERENCES
//   IN :  
//   OUT: 
// 
// RETURN: 
//**************************************************************************/
void
Mail::read_groups_from_db()
{
        int pos = 0;
        int num;
        String name;
        String file;
        Group *grp;
        char *buffer;
        int size = 0;

        assert (bbsid.is_empty() == false);
        file = Settings.datadir + bbsid + ".gdb";
        
        DEBUG("Reading groups : "<< file.get());
        fstream fin (file.get(), ios::in
#ifndef __unix__
                     | ios::binary
#endif
                     );
        if (fin.fail()) {
                system_error ("Cannot open message database", HALT_FL);
        }
        fin.seekg (0, ios::end);
        size = fin.tellg();
        fin.seekg (0);
        
        // read whole file to buffer
        buffer = new char [size + 1];
        fin.read (buffer, size);
        fin.close();

        // get information about bbs from buffer
        pos += bbsname.put (buffer) + 1;
        DEBUG( "Readed from gdb: '" << bbsname << "'" );
        pos += city.put (buffer + pos) +1;
        DEBUG( "Readed from gdb: '" << city << "'" );
        pos += phone.put (buffer + pos) +1;
        DEBUG( "Readed from gdb: '" << phone << "'" );
        pos += sysop.put (buffer + pos) +1;
        DEBUG( "Readed from gdb: '" << sysop << "'" );
        pos += door.put (buffer + pos) +1;
        pos += time.put (buffer + pos) +1;
        pos += username.put (buffer + pos) +1;
        username.convert_charset (LOG_CHARSET, Settings.syscharset);
        username.s_capitalize();
        Settings.username = username;

        if ( door.ifind( "pcboard" ) != NOTHING ) {
                Settings.is_pcboard = true;
                Settings.max_subject_len = JMR_MAX_SUBJECT_LEN;
        }

        
        // get groups from buffer
        while (pos < size) {
                num = atoi (buffer + pos);
                pos += strlen (buffer + pos) + 1;
                pos += name.put (buffer + pos) +1;
                name.convert_charset (LOG_CHARSET, Settings.syscharset);
                DEBUG( "Read group from db: '" << name << "'" );
                grp = new Group (num, name);
                DEBUG( "Adding group from db: '" << name << "'" );
                grouplist.add (grp);
        }
        
        delete[] buffer;
}

//**************************************************************************/
// CLASS: Mail
// MEMBER FUNCTION: read_articles_from_db
//**************************************************************************/ 
//
// Read articles from temporaly copy of message database. Does not
// read lines of articles to memory, they should be read only when
// it is necessary. This program is not memory monster like Microsoft...
//
// File opened here must be keep opened until the destructor closes it.
// 
// EXTERNAL VARIABLE REFERENCES
//   IN :  Settings.syscharset
//   OUT: 
// 
// RETURN: 
//**************************************************************************/
void
Mail::read_articles_from_db()
{
        Message *msg;
        Group *grp;
        Group *tag;
        char idcode[4];
        
        go_group_number (TAGGED_ARTICLES_NUMBER);
        tag = grouplist.get();

        assert (bbsid.is_empty() == false);
        assert (tmp_db_file == NULL);


        DEBUG("Reading articles : "<< tmp_db_name.get());
        tmp_db_file = new fstream (tmp_db_name.get(), ios::in
#ifndef __unix__
                                   | ios::binary
#endif
                                   );
        if (tmp_db_file->fail()) {
                system_error ("Cannot open message database", HALT_FL);
        }
        tmp_db_file->read ((char*) idcode, 4); // skip over id code
        
        while (NULL != (msg=read_msg_from_log(*tmp_db_file, HEADER_ONLY_FL))) {
                if (go_group_number (msg->get_group_number()) == true) {
                        grp = grouplist.get();
                } else {
                        grp = new Group (msg->get_group_number(), "Unknown");
                        grouplist.add (grp);
                }
                msg->set_charset (LOG_CHARSET);
                msg->translate (Settings.syscharset);
                grp->add (msg);
                if (msg->get_status() & TAGGED_ST) {
                        tag->add (msg);
                }
#ifdef HASH_HACK
                char a[30];
                char b[15];
//                unsigned int hkey = ((unsigned) msg->get_group_number()) << 17;
//                hkey |= msg->get_number();
                msg->get_subject().get_at( a, 10 );
                msg->get_writer().get_at( b, 10 );
                strcat( a, b ); 
                unsigned int hkey = msg->get_group_number() * msg->get_number();
/*                unsigned int foo = 1;
                for ( int x = 0; a[x]; ++x ) {
                        foo = a[x] + 31 * foo;
                }
                hkey *= foo; */
                cerr << "Hashkey: " << hkey << endl;
#endif
        }
}

//**************************************************************************/
// CLASS: Mail
// MEMBER FUNCTION: read_deadjmr
//**************************************************************************/ 
//
// Read replies of killed session.
// NOTE: contents of message database must be read before this!
// 
// EXTERNAL VARIABLE REFERENCES
//   IN :  
//   OUT: 
// 
// RETURN: 
//**************************************************************************/
void
Mail::read_deadjmr()
{

        fstream fin;
        Message *msgdata;
        Group *grp;
        char idcode[4];

        fin.open (DEADJMR_FILENAME, ios::in
#ifndef __unix__
                  | ios::binary
#endif
                  );
        if (fin.fail()) {
                return;
        }
        
        // skip over idcode 
        fin.read ((char*) idcode, 4);

        go_group_number (NEW_REPLIES_NUMBER);
        grp = grouplist.get();
        
        msgdata = read_msg_from_log (fin);
        while (msgdata != NULL) {
                msgdata->set_charset (LOG_CHARSET);
                msgdata->translate (Settings.syscharset);
                grp->add (msgdata);
                msgdata = read_msg_from_log (fin);
        }
        fin.close();
}

//**************************************************************************/
// CLASS: Mail
// MEMBER FUNCTION: write_message_database
//**************************************************************************/ 
//
// Writes new message database and replylog
// 
// EXTERNAL VARIABLE REFERENCES
//   IN :  
//   OUT: 
// 
// RETURN: 
//**************************************************************************/
void
Mail::write_message_database()
{
        write_groups_to_db();
        write_articles_to_db();
        write_replylog();
}

//**************************************************************************/
// CLASS: Mail
// MEMBER FUNCTION: write_groups_to_db
//**************************************************************************/ 
//
// Writes information of bbs and numbers and names of each group to
// database file <bbsid>.gdb
// 
// EXTERNAL VARIABLE REFERENCES
//   IN :  
//   OUT: 
// 
// RETURN: true
//**************************************************************************/
bool
Mail::write_groups_to_db()
{
        Group *grp;
        String str;
        String file;
        
        file = Settings.datadir + bbsid + ".gdb";

        fstream fout (file.get(), ios::out | ios::trunc
#ifndef __unix__
                       | ios::binary
#endif
                       );
        if (fout.fail()) {
                system_error ("Cannot write message database", HALT_FL);
        }

        // Write information about bbs to file
        DEBUG( "writing to gdb: '" << bbsname.get() << "'" );
        fout.write (bbsname.get(), bbsname.length()+1);
        DEBUG( "writing to gdb: '" << city.get() << "'" );
        fout.write (city.get(), city.length()+1);
        DEBUG( "writing to gdb: '" << phone.get() << "'" );
        fout.write (phone.get(), phone.length()+1);
        DEBUG( "writing to gdb: '" << sysop.get() << "'" );
        fout.write (sysop.get(), sysop.length()+1);
        fout.write (door.get(), door.length()+1);
        fout.write (time.get(), time.length()+1);
        username.convert_charset (Settings.syscharset, LOG_CHARSET);
        fout.write (username.get(), username.length()+1);

        // write number and name of each group
        grouplist.first();
        do {
                grp = grouplist.get();
                if (grp->get_number() < BASEGROUP_NUMBER) {
                        fout.write (grp->get_numstr().get(),
                                    grp->get_numstr().length()+1);
                        str = grp->get_name();
                        str.convert_charset (Settings.syscharset, LOG_CHARSET);
                        fout.write (str.get(), str.length()+1);
                        DEBUG( "Writing group to gdb: '"<<str<<"'");
                }
        } while (grouplist.next() == true);

        
        fout.close();

        return true;
}

//**************************************************************************/
// CLASS: Mail
// MEMBER FUNCTION: write_articles_to_db
//**************************************************************************/ 
//
// Writes articles to message database.
//
// Before writing, all articles are copied to one list and sorted in order
// of date. Only Settings.databasesize newest articles are writed to file.
// 
// EXTERNAL VARIABLE REFERENCES
//   IN :  Settings.databasesize, Settings.datadir
//   OUT: 
// 
// RETURN: 
//**************************************************************************/
bool
Mail::write_articles_to_db()
{
        Group *grp;
        Message *msg;
        List<Message> msglist;
        String file;
        char idcode[4];

        DEBUG("Writing message database");
        
        file = Settings.datadir + bbsid + ".mdb";
        fstream fout (file.get(), ios::out | ios::trunc
#ifndef __unix__
                      | ios::binary
#endif
                      );
        if (fout.fail()) {
                system_error ("Cannot write message database", HALT_FL);
        }

        put_le_int32 ((byte_t*) idcode, LOG_IDCODE);
        fout.write (idcode, 4);

        // add all articles to single list
        grouplist.first();
        do {
                grp = grouplist.get();
                if (grp->get_number() < BASEGROUP_NUMBER
                    && grp->first_article() == true) {
                        do {
                                msg = grp->get_article();
                                msglist.add (msg);
                        } while (grp->next_article() == true);
                }
        } while (grouplist.next() == true);
        
        // sort articles by date
	DEBUG("Sorting articles by date...");
#ifdef USE_DEBUG
        clock_t start_time = clock();
#endif
	msglist.sort( cmp_date );
#ifdef USE_DEBUG
        clock_t end_time = clock();
        cerr <<"Sorted in "<< float((end_time - start_time) / (float) CLK_TCK)
	  << " seconds";
#endif
	DEBUG("OK");
        
        // Check if size of database is larger than limit.
        msglist.first();
        int i = msglist.count_items() - Settings.databasesize;
        DEBUG("Trashing "<<i<<" oldest articles");

        // If size is too big, then mark oldest i articles
        // with killed flag.
        if (i > 0 ) {
                do {
                        msg = msglist.get();
                        // If article is not tagged and is not marked as 
                        // killed, then mark it killed now.
                        if (! (msg->get_status() & TAGGED_ST) &&
                            !msg->is_killed()) {
                                msg->invert_kill_flag();
                                --i;
                        }
                } while (i > 0 && msglist.next());
        }

        // Write database, skip killed articles.
        msglist.first();
        do {
                msg = msglist.get();
		if (msg->is_killed() == true &&
		    (msg->get_status() & TAGGED_ST) ) {
			msg->reset_status_bits( TAGGED_ST);
		} else if (msg->is_killed() ) {
                        continue;
                }
                msg->first_line();
                msg->translate (LOG_CHARSET);
                write_msg_to_log (fout, *msg);
                // remove lines of article from memory
                msg->unload_lines();
        } while (msglist.next() == true);
        
        msglist.leave();
        fout.close();

        DEBUG("OK");
        return true;
}

//**************************************************************************/
// CLASS: Mail
// MEMBER FUNCTION: read_replylog
//**************************************************************************/ 
//
// Reads old replies of current bbs from temporaly replylog file.
// Does not read lines of articles to memory.
//
// NOTE: Variable tmp_log_name must be set before use this!
// 
// EXTERNAL VARIABLE REFERENCES
//   IN :  Settings.datadir, Settings.syscharset
//   OUT: 
// 
//**************************************************************************/
void
Mail::read_replylog()
{
        Message *msgdata;
        Group *grp;
        byte_t idcode[4];
        Message* (*read_func)(fstream&, int);

        assert (tmp_log_file == NULL);
        assert (tmp_log_name.is_empty() == false);

        DEBUG("Reading replylog : "<<tmp_log_name.get());
        tmp_log_file = new fstream (tmp_log_name.get(), ios::in
#ifndef __unix__
                                    | ios::binary
#endif
                                    );
        if (tmp_log_file->fail()) {
                return;
        }
        Wscript->enable();
        Wscript->print( "\nReading replylog..." );
        Screen.refresh();
        // select right function for reading data from log
        tmp_log_file->read ((char*) idcode, 4);
        if (get_le_int32 (idcode) != LOG_IDCODE) {
                Wscript->print( "\nObsolete log format detected. Converting...");
                Screen.refresh();
                read_func = read_msg_from_obsolete_log;
        } else {
                read_func = read_msg_from_log;
        }

        go_group_number (REPLYLOG_NUMBER);
        grp = grouplist.get();
        
        msgdata = read_func (*tmp_log_file, HEADER_ONLY_FL);
        while (msgdata != NULL) {
                msgdata->set_charset (LOG_CHARSET);
                msgdata->translate (Settings.syscharset);
                grp->add (msgdata);
                msgdata = read_func (*tmp_log_file, HEADER_ONLY_FL);
        }
        Wscript->print( "OK" );
        Screen.refresh();
}

//**************************************************************************/
//  CLASS: Mail
//  MEMBER FUNCTION: write_replylog
// 
// 
//  Writes replylog of current bbs to file <bbsid>.log.
// 
// 
//  EXTERNAL REFERENCES:
//  IN :  Settings.datadir, Settings.replylogsize
//  OUT: 
//**************************************************************************/
void
Mail::write_replylog()
{
        String fname = Settings.datadir + bbsid + ".log";
        write_log (fname, REPLYLOG_NUMBER, Settings.replylogsize);
}

//**************************************************************************/
// CLASS: Mail
// MEMBER FUNCTION: write_log
//**************************************************************************/ 
// Writes contents of given group to given file. If group has more articles
// than given maximum count is, then writes only newest <maxsize> articles.
// 
// EXTERNAL VARIABLE REFERENCES
//   IN :  
//   OUT: 
// 
//**************************************************************************/
void
Mail::write_log (const String &fname,   // name of file
                 const int group_number,// number of group
                 const int maxsize,     // max. number of articles
                 bool  keep_charset)   // should charset restored after writing
{
        int size,i;
        Message *msg;
        char idcode[4];

        go_group_number (group_number);
        // check size of log. Return immediately if there's no replies
        size = count_articles();
        if (size == 0) {
                return;
        }

        DEBUG("Writing replies to log");
        first_article();
        DEBUG("Size of logfile is : "<<size);
        DEBUG("Max. size is : "<<maxsize);
        // skip over oldest articles, if count of articles > maxsize
        if (size > maxsize) {
                sort (DATE_SORTED);
                i = size - maxsize;
                DEBUG("Trashing "<<i<<" articles...");
                while (i) {
                        next_article();
                        i--;
                }
        }

        
        fstream file (fname.get(), ios::out | ios::trunc
#ifndef __unix__
                      | ios::binary
#endif
                      );
        if (file.fail()) {
                system_error ("Cannot write log");
                return;
        }

        // write contents of log
        put_le_int32 ((byte_t*)idcode, LOG_IDCODE);
        file.write (idcode, 4); // tunniste
        do {
                msg = get_article();
                if (msg->is_killed() == false) {
                        // mark article as unread
                        // msg->reset_status_bits (READ_ST);
                        msg->first_line();
                        msg->translate (LOG_CHARSET);
                        write_msg_to_log (file, *msg);
                        if ( keep_charset == true ) {
                                msg->translate( Settings.syscharset );
                        }
                        msg->unload_lines();
                }
        } while (next_article() == true);
        file.close();
}



//**************************************************************************/
// CLASS: Mail
// MEMBER FUNCTION: set_personal_mail
//**************************************************************************/ 
//
// Copies pointer of each article, which's receiver matches with
// Settings.username to group 'Personal Mail'. (Personal articles are mirrored
// to group 'Personal Mail').
// 
// EXTERNAL VARIABLE REFERENCES
//   IN :  Settings.username
//   OUT: 
// 
// RETURN: 
//**************************************************************************/
void
Mail::set_personal_mail()
{
        Group *personal, *grp;
        Message *msg;
        go_group_number (PERSONAL_NUMBER);
        personal = grouplist.get();

        assert (personal != 0);
        
        grouplist.first();

        do {
                grp = grouplist.get();
                if (grp->get_number() >= BASEGROUP_NUMBER) {
                        continue;
                }
                if (grp->first_article() == false) {
                        continue;
                }
                
                do {
                        msg = grp->get_article();

                        assert (msg != NULL);
                        
                        if (Settings.username == msg->get_receiver()) {
                                personal->add (msg);
                        }
                } while (grp->next_article() == true);

        } while (grouplist.next() == true);

}

//**************************************************************************/
// CLASS: Mail
// MEMBER FUNCTION: thread_sort_articles
//**************************************************************************/ 
//
// Sorts contents of basegroups by subject, and other groups by threads.
// 
//**************************************************************************/
void
Mail::thread_sort_articles ()
{
        // contents first 4 groups is sorted by subject (basegroups)
        grouplist.first();
        assert (grouplist.first() == true);

        for (int i = 0; i < FIRST_GROUP_FROM_PACKET; ++i) {
                sort (SUBJECT_SORTED);
                grouplist.next();
        }

        // and contents of rest groups is sorted by threads
        do {
                sort (THREAD_SORTED);
                articles += count_articles();
        } while (true == grouplist.next());
        grouplist.first();
}

//**************************************************************************/
// CLASS: Mail
// MEMBER FUNCTION: sort
//**************************************************************************/ 
//
// Sorts messages of current group to given order. After this function,
// current position within group will be first article.
// 
// EXTERNAL VARIABLE REFERENCES
//   IN :  
//   OUT: 
// 
//**************************************************************************/
void
Mail::sort (const int order)
{
        Group *tmp;
        int num;

        tmp = grouplist.get();
        assert (tmp != 0);
        DEBUG("Sorting group: '" << grouplist.get()->get_name() <<"'");
        tmp->sort (order);

        num = tmp->get_number();
        if (num == PERSONAL_NUMBER) {
                return;
        }
        tmp->first_article();
}


//**************************************************************************/
// CLASS: Mail
// MEMBER FUNCTION: operator []
//**************************************************************************/ 
//
// Moves position in list of groups to given index and returns reference
// to that group. If index is out of range, then returned group will be
// last group of list.
// 
// EXTERNAL VARIABLE REFERENCES
//   IN :  
//   OUT: 
// 
// RETURN: reference to group
//**************************************************************************/
const Group&
Mail::operator [] (int index)
{
        grouplist.first();
        
        for (int i = 0; i < index; i++) {
                grouplist.next();
        }
        
        assert (grouplist.get() != 0);
        
        return *(grouplist.get());
}

//**************************************************************************/
// CLASS: Mail
// MEMBER FUNCTION: go_group_index
//**************************************************************************/ 
//
// Goes to group of given index.
// 
// EXTERNAL VARIABLE REFERENCES
//   IN :  
//   OUT: 
// 
// RETURN: false if given index is out of range
//**************************************************************************/
bool
Mail::go_group_index (const int n)
{
        if (n >= grouplist.count_items()) {
                return false;
        }
        (*this)[n];
        return true;
}
        

//**************************************************************************/
// CLASS: Mail
// MEMBER FUNCTION: go_group_number
//**************************************************************************/ 
//
// Moves position in list of groups to group, that have number matches
// with given value. If group not found, then position will be last group
// of list.
// 
// EXTERNAL VARIABLE REFERENCES
//   IN :  
//   OUT: 
// 
// RETURN: true if group exist. false if not.
//**************************************************************************/
bool
Mail::go_group_number(const int num)
{
        Group *tmp;
        grouplist.first();
        do {
                tmp = grouplist.get();
                if (tmp->get_number() == num)
                        return true;
        } while (grouplist.next() == true);
        
        return false;
}



//**************************************************************************/
// CLASS: Mail
// MEMBER FUNCTION: add_reply
//**************************************************************************/ 
//
// Adds new reply to end of list of new replies.
// 
// EXTERNAL VARIABLE REFERENCES
//   IN :  
//   OUT: 
// 
//**************************************************************************/
void
Mail::add_reply (Message *reply)
{
        grouplist.save_position();
        go_group_number (NEW_REPLIES_NUMBER);
        grouplist.get()->add (reply);
        grouplist.restore_position();
        have_new_replies = true;
}

//**************************************************************************/
// CLASS: Mail
// MEMBER FUNCTION: kill_reply
//**************************************************************************/ 
//
// Removes given reply from list of new replies.
// 
// EXTERNAL VARIABLE REFERENCES
//   IN :  
//   OUT: 
//**************************************************************************/
void
Mail::kill_reply (const Message *msg)
{
        Message *tmp;
        Group *grp;

        grouplist.save_position();
        go_group_number (NEW_REPLIES_NUMBER);
        grp = grouplist.get();
        grouplist.restore_position();
        
        grp->first_article();
        do {
                tmp = grp->get_article();
                if (msg == tmp) {
                        grp->remove_article();
                        break;
                }
        } while (grp->next_article() == true);
}

//**************************************************************************/
// CLASS: Mail
// MEMBER FUNCTION: tag_article
//**************************************************************************/ 
//
// Marks current message as tagged, or untags message if it's
// already tagged.
// 
// EXTERNAL VARIABLE REFERENCES
//   IN :  
//   OUT: 
//**************************************************************************/
void
Mail::tag_article (Message *msg)
{
        Message *tmp;
        Group *grp;
        int number;

        if (!msg) {
                return;
        }
        
        msg->inv_status_bits (TAGGED_ST);
        
        grouplist.save_position();
        go_group_number (TAGGED_ARTICLES_NUMBER);
        grp = grouplist.get();
        // add message to tagged list list
        if (msg->get_status() & TAGGED_ST) {
                grp->add (msg);
        } else {
                // remove message from tagged article list
                grp->first_article();
                number = msg->get_number();
                do {
                        tmp = grp->get_article();
                        if (tmp->get_number() == number) {
                                grp->remove_article();
                                break;
                        }
                } while (grp->next_article() == true);
        }
        grouplist.restore_position();
}


int
Mail::search (char* pattern, int mode)
{
        int count = 0;
        
        assert( mode & ALL_FL || mode & TAGGED_FL || mode & CURRENT_FL );
        
        // search
        if (mode & ALL_FL) {
                count = search_all( pattern, mode );
        } else if (mode & TAGGED_FL) {
                count = search_tagged( pattern, mode );
        } else {
                count = search_current( pattern, mode );
        }
                
        return count;
}


int
Mail::search_all (char* pattern, int mode)
{
        Group *grp;
        Group *search_grp;
        Message *msg;
        int count = 0;
        int searched_articles = 0;
        float percent = 0;

        save_group_pos();
        
        // remove results of previous search
        go_group_number (SEARCH_RESULTS_NUMBER);
        search_grp = grouplist.get();
        if (search_grp->first_article() == true) {
                do {
                        search_grp->remove_article();
                } while (search_grp->first_article() == true);
        }
        

        go_group_index(FIRST_GROUP_FROM_PACKET);
        do {
                Screen.bottom();
                Screen.print( "%d%% of articles searched", (int) percent );
                Screen.refresh();
                grp = grouplist.get();
                if (grp->first_article() == false) {
                        continue;
                }
                do {
                        msg = grp->get_article();
                        if (msg->search( pattern, mode ) == true) {
                                ++count;
                                search_grp->add (msg);
                        }
                        ++searched_articles;
                } while (grp->next_article() == true);
                percent = (float) searched_articles / (float) articles;
                percent *= 100;
        } while (grouplist.next() == true);

        restore_group_pos();
        return count;

}

int
Mail::search_current (char* pattern, int mode)
{
        Group *grp;
        Group *search_grp;
        Message *msg;
        int count = 0;
        
        save_group_pos();
        
        // remove results of previous search
        go_group_number (SEARCH_RESULTS_NUMBER);
        search_grp = grouplist.get();
        if (search_grp->first_article() == true) {
                do {
                        search_grp->remove_article();
                } while (search_grp->first_article() == true);
        }
        
        restore_group_pos();

        grp = grouplist.get();
        if (grp->first_article() == false) {
                return 0;
        }

        do {
                msg = grp->get_article();
                if (msg->search( pattern, mode ) == true) {
                        ++count;
                        search_grp->add( msg );
                }
        } while (grp->next_article() == true);

        return count;
}


int
Mail::search_tagged (char* pattern, int mode)
{
        int count = 0;
        Group *grp;
        Group *search_grp;
        Message *msg;
        int searched_articles = 0;
        int all_articles = 0;
        float percent = 0;

        save_group_pos();
        
        // remove results of previous search
        go_group_number (SEARCH_RESULTS_NUMBER);
        search_grp = grouplist.get();
        if (search_grp->first_article() == true) {
                do {
                        search_grp->remove_article();
                } while (search_grp->first_article() == true);
        }
        
        go_group_index(FIRST_GROUP_FROM_PACKET);
        // count amount of articles to search
        do {
                grp = grouplist.get();
                if (grp->is_tagged() == false) {
                        continue;
                }
                if (grp->first_article() == false) {
                        continue;
                }
                all_articles += grp->count_articles();
        } while (grouplist.next() == true);

        // search
        go_group_index( FIRST_GROUP_FROM_PACKET);
        do {
                Screen.bottom();
                Screen.print("%d%% of articles searched", (int) percent );
                Screen.refresh();
                grp = grouplist.get();
                if (grp->is_tagged() == false) {
                        continue;
                }
                if (grp->first_article() == false) {
                        continue;
                }
                do {
                        msg = grp->get_article();
                        if (msg->search( pattern, mode ) == true) {
                                ++count;
                                search_grp->add (msg);
                        }
                        ++searched_articles;
                } while (grp->next_article() == true);
                percent = (float) searched_articles / (float) all_articles;
                percent *= 100;
        } while (grouplist.next() == true);

        restore_group_pos();
        return count;
}


void
Mail::tag_group()
{
        if (grouplist.get()->get_number() >= BASEGROUP_NUMBER) {
                return;
        }
        grouplist.get()->tag();
        if (grouplist.get()->is_tagged() == true) {
                ++num_tagged_groups;
        } else {
                --num_tagged_groups;
        }
}

int
Mail::count_tagged_groups() const
{
        return num_tagged_groups;
}




//**************************************************************************/
// CLASS: bbs_settings
// MEMBER FUNCTION: set
//**************************************************************************/ 
//
// This reads settings defined in file <bbsid>.rc, and replaces
// original settings using them.
// Contents of original Settings-structure are stored and them
// can be restored when these settings are no longer needed.
//
// EXTERNAL VARIABLE REFERENCES
//   IN :  Settings.jmrdir
//   OUT:  Settings
// 
// PARAMETERS: id of the current bbs.
//
// RETURN: true if new settings was defined, false if not
//**************************************************************************/
bool
bbs_settings::set (String& bbsid)
{
        rcfname = Settings.jmrdir + bbsid + ".rc";
        if ( ACCESS( rcfname.get(), F_OK) ) {
                save_flag = false;
                return false;
        }
        if (read_resources() == false) {
                save_flag = false;
                return false;
        }

        String value;

        saved = Settings;
        save_flag = true;
        
        DEBUG("Setting up bbs related resources");
        value = trie.get_value( RC_BBSCHARSET );
        if ( value.is_empty() == false ) {
                if (value == "ibmpc") {
                        Settings.bbscharset = IBM_PC_CHARSET;
                } else if (value == "latin1") {
                        Settings.bbscharset = ISO_LATIN_CHARSET;
                } else {
                        Settings.bbscharset = ASCII_7BIT_CHARSET;
                }
        }

        value = trie.get_value( RC_PTAG );
        if (value.is_empty() == false) {
                Settings.ptagline = value;
        }

        value = trie.get_value( RC_TAG );
        if (value.is_empty() == false) {
                Settings.tagline = value;
        }

        value = trie.get_value( RC_QUOTEPREFIX );
        if (value.is_empty() == false) {
                value.replace_ch( '_', ' ' );
                Settings.quoteprefix = value;
        }

        value = trie.get_value( RC_REPLYLOGSIZE );
        if (value.is_empty() == false) {
                Settings.replylogsize = atoi( value.get() );
        }

        value = trie.get_value( RC_DATABASESIZE );
        if (value.is_empty() == false) {
                Settings.databasesize = atoi( value.get() );
        }

        value = trie.get_value( RC_EXTRACTDIR );
        if (value.is_empty() == false) {
                correct_path( value );
                Settings.extractdir = value;
        }

        value = trie.get_value( RC_FOLLOWUPSTR );
        if (value.is_empty() == false) {
                Settings.followup = value;
        }
        
        value = trie.get_value( RC_REPLYSTR );
        if (value.is_empty() == false) {
                Settings.reply = value;
        }
        return true;
}
//**************************************************************************/
// CLASS: bbs_settings
// MEMBER FUNCTION: read_resources
//**************************************************************************/ 
//
//  This function reads contents of a resource file to a tree structure.
//  If syntax of the resource file is not correct, then program is halted.
// 
// EXTERNAL VARIABLE REFERENCES
//   IN :  
//   OUT: 
// 
// PARAMETERS:
//
// RETURN: true if syntax of the file was right.
//**************************************************************************/
bool
bbs_settings::read_resources()
{
        String tmp;
        char line[MAX_LINE_LEN+1];
        int linenum = 1;
        bool rc = true;
        
        ifstream rcfile( rcfname.get(), ios::in );
        if (rcfile.fail()) {
                String err_str = "Cannot read resource file : " + rcfname;
                system_error (err_str.get());
                return false;
        }
        Wscript->enable();
        Wscript->print("\nParsing resource file: \'%s\'....", rcfname.c_str());
        Screen.refresh();
        do {
                rcfile.getline (line, MAX_LINE_LEN);
                tmp = line;

                // remove preceeding spaces
                tmp.cut_first();

                // if line is not empty and it is not commented,
                // then check syntax of line and get value
                // of keyword.
                if (tmp.is_empty() == false && tmp[0] != '#') {
                        // remove trailing spaces
                        tmp.cut_tail();

                        // parse the line
                        if (parse_rcline( tmp, linenum ) == false) {
                                rc = false;
                        }
                }
                ++linenum;
        } while (!rcfile.eof());
        rcfile.close();

        if (rc == false) {
                handle_error("Fatal errors in bbs related resource file. Game over - Insert a coin.",
                             HALT_FL);
        }
        Wscript->print( "OK" );
        Screen.refresh();
        return true;
}
//**************************************************************************/
// CLASS: bbs_settings
// MEMBER FUNCTION: parse_rcline
//**************************************************************************/ 
//
//  Checks that syntax of a line is right, and initializes keyword that
//  is defined in a line with a value given to it.
//  If syntax error occures, then false is returned.
// 
// EXTERNAL VARIABLE REFERENCES
//   IN :  
//   OUT: 
// 
// PARAMETERS: line  Contents of the current line
//             num   Number of the line
//
// RETURN: true if syntax of the line was right, false if not.
//**************************************************************************/
bool
bbs_settings::parse_rcline (String& line, int num)
{
        int n=0;
        String tmp;

        // check that assigment operator is in it's place
        n = line.findch ('=');
        if (n == NOTHING) {
                String error_str ;
                error_str = "Assigment operator is missing at line ";
                error_str += num;
                handle_error( error_str.get(), REPORT_FL);
                return false;
        }

        // Then get word before an assigment operator and check
        // if it matches with some of keywords in a trie
        tmp.copy( line, 0, n);
        tmp.cut_tail();
        if (false == trie.goto_keyword( tmp )) {
                String error_str = "At line ";
                error_str += num;
                error_str += ": Illegal keyword : \'" + tmp + "\'";
                handle_error( error_str.get(), REPORT_FL);
                return false;
        }

        // Check that given value is legal, and store it
        // in a trie.
        tmp.copy( line, n + 1 );
        tmp.cut_first();
        tmp.cut_tail();
        if (false == trie.set_value( tmp )) {
                String error_str = "Invalid value at line ";
                error_str += num;
                error_str += ": \'" + tmp + "\'";
                handle_error( error_str.get(), REPORT_FL );
                return false;
        }

        return true;
}

//**************************************************************************/
// CLASS: bbs_settings
// MEMBER FUNCTION: restore
//**************************************************************************/ 
//
// This function restores original settings.
// 
// EXTERNAL VARIABLE REFERENCES
//   IN :  
//   OUT: Settings
// 
// PARAMETERS:
//
// RETURN: always true
//**************************************************************************/
bool
bbs_settings::restore ()
{
        if (save_flag == true) {
                DEBUG("Restoring original settings.");
                Settings = saved;
                save_flag = false;
        }
        return true;
}

//**************************************************************************/
// CLASS: bbs_settings
// MEMBER FUNCTION: bbs_settings
//**************************************************************************/ 
//
// Constructor for class. Initializes syntax trie using values defined
// in static array bbs_setting_array
//
// EXTERNAL VARIABLE REFERENCES
//   IN :  bbs_setting_array
//   OUT: 
// 
// PARAMETERS:
//
// RETURN: 
//**************************************************************************/
bbs_settings::bbs_settings()
{
        save_flag = false;
        trie.init( bbs_setting_array );
}








jmrsrc-0.7.23/src/menu.cc100664    764    764       64535  6623366026  14650 0ustar  jvuokkojvuokko// 	$Id: menu.cc,v 1.16 1997/08/28 18:21:34 jvuokko Exp $	

/****************************************************************************
 * *
 * *  MODULE : menu.cc
 * *
 * *  Copyright (c) 1997 Jukka Vuokko
 * *  See file COPYING for more information about copyrights.
 * *
 ****************************************************************************
 * *
 * * Functions for handling group selection menu.
 * *
 * *
 * *
 * *
 * *
 ***************************************************************************/
#include <time.h> // testiä varten
#include <iostream.h>
#include "jmr.hh"
#include "menu.hh"
#include "mail.hh"
#include "replies.hh"
#include "menubar.hh"

extern Terminal_screen Screen;
extern Window* Wscript;
extern settings_t Settings;
extern Mail* mail;

static const menuinfo group_menu_array[] = {
        {"[jmr]", 0 },
        {"Exit from menu", 0},
        {"Back to mail packet selection", QUIT_CMD},
        {"Quit jmr", FORCED_QUIT_CMD},
        {"@NEXT", 0 },
        {"[Tagging]", 0 },
        {"Tag/untag group", TAG_CMD },
        {"Tag/untag all", TAG_ALL_CMD },
        {"@NEXT", 0 },
        {"Write", WRITE_CMD },
        {"@NEXT", 0 },
        {"Yank in/out", YANK_IN_OUT_CMD },
        {"@NEXT", 0 },
        {"Search", SEARCH_CMD },
        {"@NEXT", 0 },
        {"@END", 0 }
};


//**************************************************************************/
//
// CLASS:  Groupmenu
// MEMBER FUNCTION: Groupmenu
//
// description: Constructor. This is main function of class.
//              NOTE: Mail *mail must be initialized before use this!!
//
//  in: Screen, Quit_flag
// out: Quit_flag
//
//**************************************************************************/
Groupmenu::Groupmenu()
{
        int c;
        int tmp = 0;
        Msgmenu msgmenu;      // thread selection level        
        bool quit = false;
        show_all_groups = false;
        pages = 0;
        ymax = Screen.get_ymax() - 2;
        ymin = 6;
        ypos = ymin;
        groups_per_page = ymax - ymin +1;
        current_page = 0;
        first_of_page = 0;
        current_group = 0;
        headerline = "------------ Group  Name of the Group ----------- Unread   Total ";
        for (c = Screen.get_xmax() - headerline.length(); c >= 0; c--) {
                headerline += "-";
        }
        
        menubar.init( group_menu_array );
        menubar.set_text( MENUBAR_TEXT );
        sline.init( Screen.get_xmax() + 1);
        update_all ();
        iline.set_default ( GROUP_MENU_KEYS_MSG );
        iline.show_default();

        do {
                c = Screen.get_ch();
                iline.reset();
                if (c == ESCAPE) {
                        iline.set( MENU_MSG );
                        iline.show();
                        c = menubar.select();
                        update_all();
                        iline.reset();
                }
                switch (c) {
                case MESSAGEWIN_CMD:
                        Wscript->enable();
                        iline.set ( MESSAGES_MSG );
                        iline.show();
                        Screen.get_ch();
                        iline.reset();
                        Wscript->disable();
                        break;
                case PREV_CMD:
                case CODE_UP:
                        if (false == up()) {
                                iline.set( AT_TOP_MSG );
                        }
                        break;
                case NEXT_CMD:
                case CODE_DOWN:
                        if (false == down()) {
                                iline.set( AT_BOTTOM_MSG );
                        }
                        break;
                case CODE_NPAGE:
                case PAGE_DOWN_CMD:
                        if (false == page_down()) {
                                iline.set( AT_LAST_PAGE_MSG );
                        }
                        break;
                case CODE_PPAGE:
                case PAGE_UP_CMD:
                        if (false == page_up()) {
                                iline.set( AT_FIRST_PAGE_MSG );
                        }
                        break;
                case CODE_HOME:
                        //pages = 0;
                        current_group=0;
                        update_list();
                        break;
                case CODE_END:
                        last_group();
                        update_list();
                        break;
                case YANK_IN_OUT_CMD:
                        show_all_groups = show_all_groups == true ?
                                false : true;
                        if (show_all_groups == true ) {
                                sline.set_infotext( "List Of All Groups" );
                        } else {
                                sline.set_infotext( "List Of Unread Groups" );
                        }
                        pages = 0;
                        update_all ();
                        break;
                case NEXT_UNREAD_CMD:
                        if (true == next_unread()) {
                                msgmenu.enter();
                                update_all();
                        } else {
                                iline.set( NO_UNREAD_MSG);
                        }
                        break;
                case CODE_RIGHT:
                case SELECT_CMD:
                        msgmenu.enter();
                        update_all();
                        break;
                case WRITE_CMD:
                        if (*groupnumbers.get() < BASEGROUP_NUMBER) {
                                mail->write();
                                pages = 0;
                                update_all();
                        }
                        break;
                case CATCHUP_CMD:
                        if (true == catchup()) {
                                update_all();
                                iline.set("Articles marked as read");
                        } else {
                                iline.show(REFRESH_ALL_FL);
                        }
                        break;
                case FORCED_CATCHUP_CMD:
                        mail->catchup();
                        update_all();
                        break;
                case SEARCH_CMD:
			tmp = search();
                        if (tmp > 0) {
                                pages = 0;
                        }
                        update_all();
                        if (tmp == 0) {
                                iline.set("No matching articles found!");
                        } else if (tmp) {
                                String _tmp = "Expression found from ";
                                _tmp += tmp;
                                _tmp += " articles";
                                iline.set(_tmp.get());
                        }
                        break;
                case TAG_CMD:
                        mail->tag_group();
                        Screen.gotoxy( 0, ypos );
                        draw_group_data( EMPHASIZED_FL );
                        break;
                case TAG_ALL_CMD:
                        if (true == tag_all()) {
                                update_all();
                                iline.set("All groups tagged");
                        } else {
                                update_all();
                                iline.set("All tags removed");
                        }
                        break;
                case HELP_CMD:
                        show_help();
                        update_all();
                        break;
                case QUIT_CMD:
                        quit = confirm_quit();
                        break;
                case FORCED_QUIT_CMD:
#ifndef NO_EXCEPTIONS
                        throw quit_exception();
#else
                        Quit_flag = true;
                        break;
#endif
                case 0:
                        break;
                default:
                        iline.set( INVALID_COMMAND_MSG );
                        break;
                }
                iline.show();
        } while (quit == false
#ifdef NO_EXCEPTIONS
                 && Quit_flag == false
#endif
                 );
        Screen.bottom();
        Screen.clr_eol();
}

bool
Groupmenu::confirm_quit()
{
        if (Settings.confirm_return == false) {
                return true;
        }
        bool rc = true;
        Screen.bottom();
        show_option_str( "Really return to the packet selection level"
                         " (y/n) @y@" );
        Screen.clr_eol();
        if (get_yesno( 'y' ) == 'n' ) {
                iline.show( REFRESH_ALL_FL );
                rc = false;
        } 
        return rc;
}

// true jos kaikki merkitty, false jos merkit poistettu
bool
Groupmenu::tag_all()
{
        static int count;
        mail->save_group_pos();
        ++count;
        mail->first_group();
        if (count % 2) {
                do {
                        if (mail->is_group_tagged() == false) {
                                mail->tag_group();
                        }
                } while (mail->next_group() == true);
        } else {
                do {
                        if (mail->is_group_tagged() == true) {
                                mail->tag_group();
                        }
                } while (mail->next_group() == true);
        }
        mail->restore_group_pos();

        if (count % 2) {
                return true;
        }
        return false;
}


int
Groupmenu::get_search_groups()
{
        Screen.bottom();
        show_option_str("Search from [@a@]ll groups [@c@]urrent group [@t@]agged groups or [@q@]uit? @c@");
        Screen.clr_eol();
        int c;
        while ((c = Screen.get_ch()) != 0) {
                switch (c) {
                case 'a':
                        Screen.print( "\ba" );
                        Screen.refresh();
                        return ALL_FL;
                case 'c':
                case SELECT_CMD:
                        return CURRENT_FL;
                case 't':
                        Screen.print( "\bt" );
                        Screen.refresh();
                        return TAGGED_FL;
                case 'q':
                        return QUIT_FL;
                }
        }
        return DEFAULT_FL;
}

// palauttaa löydettyjen määrän, tai -1 jos quit.
int
Groupmenu::search()
{
        int c;
        int mode;
        
        mode = get_search_groups();
        if (mode == QUIT_FL) {
                return -1;
        }
        
        Screen.bottom();
        show_option_str( "And from [@s@]ubject [@a@]uthor [@r@]eceiver [@h@]eader [@A@]rticle or [@q@]uit? @h@" );
        Screen.clr_eol();
        while ((c = Screen.get_ch()) != 0) {
                switch (c) {
                case 's':
                        mode |= SUBJECT_FL;
                        goto out_of_loop;
                case 'a':
                        mode |= AUTHOR_FL;
                        goto out_of_loop;
                case 'r':
                        mode |= RECEIVER_FL;
                        goto out_of_loop;
                case SELECT_CMD:
                case 'h':
                        c = 'h';
                        mode |= SUBJECT_FL | AUTHOR_FL | RECEIVER_FL;
                        goto out_of_loop;
                case 'A':
                        mode |= SUBJECT_FL | AUTHOR_FL|RECEIVER_FL|ARTICLE_FL;
                        goto out_of_loop;
                case 'q':
                        return false;
                }
        }
  out_of_loop:
        char *buf = new char [82];
        memset (buf, 0, 82);
        Screen.bottom();
        Screen.print( "Type expression : " );
        Screen.clr_eol();
        Screen.getline (buf, 60, 0);
        if (*buf == 0x00) {
                delete[] buf;
                return false;
        }
        Screen.bottom();
        Screen.clr_eol();
        int n = mail->search (buf, mode);
        delete[] buf;
        return n;
}

bool
Groupmenu::catchup()
{
        Screen.bottom();
        show_option_str( "Really want mark all articles as read (y/n) @y@" );
        Screen.clr_eol();
        if (get_yesno ('y') == 'y') {
                mail->catchup();
                return true;
        }
        return false;
}

//**************************************************************************/
//
// CLASS:  Groupmenu
// MEMBER FUNCTION: count_pages
//
// description: Counts all values that are necessary for corresponding paging.
//              Sets up list contains numbers of all groups to be show.
//
// in:  mail
// out: 
//
//**************************************************************************/
void
Groupmenu::count_pages ()
{
        all_groups = mail->get_num_groups();
        int *i;
        mail->first_group();

        groups = 0;
        groupnumbers.destroy();
        do {
                int n = mail->get_group_number();
                if (show_all_groups == false && mail->count_unread() == 0
                    && n != REPLYLOG_NUMBER && n != NEW_REPLIES_NUMBER) {
                        if (! ((n == SEARCH_RESULTS_NUMBER ||
                                n == TAGGED_ARTICLES_NUMBER) &&
                               mail->count_articles() > 0) ) {
                                continue;
                        }
                }
                i = new int;
                *i = n;
                groupnumbers.add (i);
                ++groups;
        } while (mail->next_group() == true);

        
        pages = groups / groups_per_page;
        pages++;
        while (current_group >= groups) {
                --current_group;
        }
        current_page = current_group / groups_per_page;
        first_of_page = current_page * groups_per_page;
        ypos = ymin + (current_group - first_of_page);
}

//**************************************************************************/
//
// CLASS:  Groupmenu
// MEMBER FUNCTION: update_all
//
// description: Draws everything.
//              
//
// in:  mail, Screen, Quit_flag
// out: 
//
//**************************************************************************/
void
Groupmenu::update_all ()
{
        
        Screen.erase();
#ifdef NO_EXCEPTIONS
        if (Quit_flag == true) {
                return;
        }
#endif
        menubar.show();
        iline.show_default();
        if (pages == 0) {
                count_pages();
        }

        sline.set_infotext( mail->get_bbs_name().get() );
        if (show_all_groups == true) {
                sline.set_extra_info( "All Groups" );
        } else {
                sline.set_extra_info( "Unread Groups" );
        }

        Screen.gotoxy( 0, 1 );
        Screen.print( "Phone : %s  (%s)", mail->get_bbs_phone().c_str(),
                      mail->get_bbs_sysop().c_str());
        Screen.gotoxy(0,2);
        Screen.print( "Packet time : %s", mail->get_packet_time().c_str() );
        Screen.gotoxy(0,3);
        Screen.print( "Total number of the articles in the database: %d",
                      mail->count_all_articles());
        Screen.gotoxy(0,4);
        Screen.print( "New articles from packet: %d",
                      mail->count_new_articles() );
        
        Screen.gotoxy(0,5);
        set_dashline_color();
        Screen.print( headerline.c_str() );
        Screen.reset_attr();
        update_list();

}

void
Groupmenu::update_list()
{
        int i;
        int pros;
        int y = ymin;

        // find first group of current page
        groupnumbers.first();
        i = 0;
        while (i < first_of_page) {
                ++i;
                groupnumbers.next();
        }
        i = current_group;
        current_group = first_of_page;
        // display groups
        do {
                Screen.gotoxy (0,y);

                if (current_group == i) {
                        ypos = y;
                        draw_group_data(EMPHASIZED_FL);
                } else {
                        draw_group_data();
                }
                ++current_group;
                ++y;
        } while (groupnumbers.next() == true && (y <= ymax));
        
        Screen.reset_attr();

        while ( y <= ymax ) {
                Screen.gotoxy(0,y++);
                Screen.empty_line();
        }
        
        if (groups <= groups_per_page ) {
                pros = ALL_PERCENT;
        } else if ( first_of_page == 0 ) {
                pros = TOP_PERCENT;
        } else {
                pros = 100 * current_group / groups;
        }
        sline.set_percent( pros );
        sline.show();

        // Set position in list at current Group
        current_group = i;
        goto_current_group();
}

//**************************************************************************/
//
// CLASS:  Groupmenu
// MEMBER FUNCTION: draw_group_data
//
// description: Displays data of Group
//
//
// in:  mail, Screen, Settings
// out: 
//
//**************************************************************************/
void
Groupmenu::draw_group_data (int flag)
{
        String tmp;

        if (flag & EMPHASIZED_FL) {
                Screen.inverse();
        }

        Screen.empty_line();

        Screen.goto_x (6);
        Screen.print( "%.3d", current_group );

        mail->go_group_number ( *(groupnumbers.get()) );

        Screen.goto_x (14);
        Screen.addstr( mail->get_group_numstr() );

        if (mail->is_group_tagged() == true) {
                Screen.goto_x (18);
                Screen.print( "T" );
        }
        
        Screen.goto_x (20);
        if (!(flag & EMPHASIZED_FL)) {
                if (*(groupnumbers.get()) >= BASEGROUP_NUMBER) {
                        Screen.textcolor (Settings.basegroup_color);
                } else {
                        Screen.textcolor (Settings.subject_color);
                }
        }
        Screen.addstr( mail->get_group_name() );

        if (!(flag & EMPHASIZED_FL)) {
                Screen.textcolor (Settings.author_color);
        }
        Screen.goto_x(50);
        if (mail->count_unread() > 0) {
                Screen.print( "%d", mail->count_unread() );
        } else {
                Screen.addstr( "-" );
        }
        if (!(flag & EMPHASIZED_FL)) {
                Screen.reset_attr();
        }
        Screen.goto_x (59);
        if (mail->count_articles() > 0) {
                Screen.print( "%d", mail->count_articles() );
        } else {
                Screen.addstr( "-" );
        }

        Screen.reset_attr();
}

//**************************************************************************/
//
// CLASS:  Groupmenu
// MEMBER FUNCTION: goto_current_group
//
// description: Moves listpointers to current Group
//
//
// in:  mail
// out: 
//
//**************************************************************************/
void
Groupmenu::goto_current_group()
{
        int i = 0;
        groupnumbers.first();
        while (i < current_group) {
                groupnumbers.next();
                ++i;
        }
        mail->go_group_number (*(groupnumbers.get()));
}
                
//**************************************************************************/
//
// CLASS:  Groupmenu
// MEMBER FUNCTION: previous_group
//
// description: Moves listpointer to previous group
//
// in: mail
// out:
//
// returns: true if previous exist
//
//**************************************************************************/
bool
Groupmenu::previous_group()
{
        bool rc = groupnumbers.prev();
        if (rc == true) {
                --current_group;
                mail->go_group_number ( *(groupnumbers.get()) );
        }
        return rc;
}


//**************************************************************************/
//
// CLASS:  Groupmenu
// MEMBER FUNCTION: next_group
//
// description: moves listpointer to next group
//
// in: mail
// out:
//
// returns: true if next group exist
//
//**************************************************************************/
bool
Groupmenu::next_group()
{
        bool rc = groupnumbers.next();
        if (rc == true) {
                ++current_group;
                mail->go_group_number (*(groupnumbers.get()));
        }
        return rc;
}

//**************************************************************************/
//  CLASS: Groupmenu
//  MEMBER FUNCTION: last_group 
// 
// 
//  DESCRIPTION: Moves pointer to last group
//
//  EXTERNAL VARIABLE REFERENCES
//  IN : mail
//  OUT:
// 
//**************************************************************************/
void
Groupmenu::last_group()
{
        groupnumbers.last();
        mail->go_group_number (*(groupnumbers.get()));
        current_group = groups-1;

        current_page = current_group / groups_per_page;
        first_of_page = current_page * groups_per_page;
        ypos = ymin + (current_group - first_of_page);
}

//**************************************************************************/
//
// CLASS:  Groupmenu
// MEMBER FUNCTION: up
//
// description: Moves cursor up, and updates listpointer
//
// in: Screen
// out:
//
//**************************************************************************/
bool
Groupmenu::up()
{
        if (current_group > 0) {
                if (ypos == ymin) {
                        ypos = ymax;

                        first_of_page = current_group-groups_per_page;
                        --current_group;
                        --current_page;
                        update_list();
                } else {
                        Screen.gotoxy (0, ypos);
                        draw_group_data();
                        Screen.gotoxy (0,--ypos);
                        previous_group();
                        draw_group_data(EMPHASIZED_FL);
                        Screen.reset_attr();
                }
                return true;
        }
        return false;
}

//**************************************************************************/
//
// CLASS:  Groupmenu
// MEMBER FUNCTION: down
//
// description: moves cursor one line down, and moves to next group
//
// in: Screen
// out:
//
//**************************************************************************/
bool
Groupmenu::down()
{
        if (current_group < (groups-1)) {
                if (ypos == ymax) {
                        first_of_page = ++current_group;
                        ++current_page;
                        ypos = ymin;
                        update_list();
                } else {
                        Screen.gotoxy (0,ypos);
                        draw_group_data();
                        
                        Screen.gotoxy (0,++ypos);
                        next_group();
                        draw_group_data (EMPHASIZED_FL);
                        Screen.reset_attr();
                }
                return true;
        }
        return false;
}

//**************************************************************************/
//
// CLASS:  Groupmenu
// MEMBER FUNCTION: page_up
//
// description: Moves cursor one page up
//
//
//**************************************************************************/
bool
Groupmenu::page_up()
{
        int i = groups_per_page;
        if (current_group == 0) {
                return false;
        }
        if (ypos == ymin) {
                while (i && first_of_page > 0) {
                        --first_of_page;
                        --current_group;
                        --i;
                }
        } else {
                while (ypos > ymin) {
                        --ypos;
                        --current_group;
                }
        }
        goto_current_group();

        update_list();

        return true;
}

//**************************************************************************/
//
// CLASS:  Groupmenu
// MEMBER FUNCTION: page_down
//
// description: moves cursor one page down
//
//**************************************************************************/
bool
Groupmenu::page_down()
{

        if (current_group >= groups-1) {
                return false;
        }
        if (ypos == ymax) {
                ++current_page;
                first_of_page = ++current_group;
                for (int i=1; i < groups_per_page && current_group < groups-1;
                     ++i, ++current_group);
                ypos = ymin + (current_group - first_of_page);
        } else {
                while (current_group < groups-1 && ypos < ymax) {
                        ++current_group;
                        ++ypos;
                }
        }
        goto_current_group();

        update_list();

        return true;
}


//**************************************************************************/
//  CLASS: Groupmenu
//  MEMBER FUNCTION: next_unread
// 
//  DESCRIPTION: Selects next group with unread articles. If not found,
//               then seeks for previous group.
//
//  EXTERNAL VARIABLE REFERENCES:
//   IN : mail
//   OUT: 
//
//  RETURNS: true if group found.
//**************************************************************************/

// toimivuus voi olla huono. Saattaa sekoittaa nykyisen sijainnin....
// Kirjoiteltu silm„t ristiss„ klo 00:40...
bool
Groupmenu::next_unread()
{
        bool rc = false;
        int i;
        
        i = current_group;
        mail->go_group_number ( *(groupnumbers.get()) );
        if (mail->count_unread() > 0) {
                rc = true;
        } else {
                groupnumbers.save_position();
                // seek forward for unread articles
                while (groupnumbers.next() == true) {
                        mail->go_group_number ( *(groupnumbers.get()) );
                        ++current_group;
                        if (mail->count_unread() > 0) {
                                rc = true;
                                break;
                        }
                }
                // if unread message not found, try seek from first thread.
                if (rc == false) {
                        groupnumbers.first();
                        current_group = 0;
                        do {
                                mail->go_group_number (*(groupnumbers.get()));
                                if (mail->count_unread() > 0) {
                                        rc = true;
                                        break;
                                }
                                ++current_group;
                        } while (groupnumbers.next() == true
                                 && current_group < i);
                        if (rc == false) {
                                groupnumbers.restore_position();
                                current_group = i;
                        }
                }
        }
        return rc;
}



















jmrsrc-0.7.23/src/menubar.cc100664    764    764       23316  6623366026  15325 0ustar  jvuokkojvuokko// Module id:
// $Id: menubar.cc,v 1.5 1997/07/17 15:30:09 jvuokko Exp $
/*****************************************************************************
 * *
 * *      MODULE:     menubar.cc
 * *                  ----------
 * ***************************************************************************
 * *
 * *
 * *      COPYRIGHT (C) 1997 JUKKA VUOKKO. ALL RIGHTS RESERVED
 * ***************************************************************************
 * *
 * *      Methods for handling simple pull-down menu.
 * *
 *****************************************************************************/



#include "datatypes.hh"
#include "jmr.hh"
#include "menubar.hh"


/************************ GLOBAL VARIABLES ***********************************/

extern Terminal_screen Screen;
extern settings_t Settings;


//**************************************************************************/
// CLASS: Menubar
// MEMBER FUNCTION: 
//**************************************************************************/ 
//
// 
// 
// EXTERNAL VARIABLE REFERENCES
//   IN :  
//   OUT: 
// 
// PARAMETERS:
//
// RETURN: 
//**************************************************************************/
void
Menubar::init( const menuinfo* array )
{
        menu.destroy();

        int i = 0;
        String tmp;
        // loop until item "@END" is reached
        while (strcmp( array[i].item, "@END") != 0) {
                tmp = array[i].item;
                menu.add_menu( tmp, array[i].cmd );
                ++i;
                while (strcmp( array[i].item, "@NEXT" ) != 0) {
                        tmp =  array[i].item ;
                        menu.add_item( tmp, array[i].cmd );
                        ++i;
                }
                ++i;
        }
        text = "";
        xmax = Screen.get_xmax();
        ymax = Screen.get_ymax();
        x = 0;
        y = 0;
}

//**************************************************************************/
// CLASS: Menubar
// MEMBER FUNCTION: 
//**************************************************************************/ 
//
// 
// 
// EXTERNAL VARIABLE REFERENCES
//   IN :  
//   OUT: 
// 
// PARAMETERS:
//
// RETURN: 
//**************************************************************************/
void
Menubar::show ()
{
        Screen.reset_attr();
        if (Screen.is_color_term() == true) {
                Screen.textcolor( Settings.menu_color,
                                  Settings.menuback_color );
        } else {
                Screen.textcolor( INVERSE );
        }
        Screen.gotoxy( 0, 0 );
        Screen.addstr( text );
        Screen.clr_eol();
        Screen.reset_attr();
}


//**************************************************************************/
// CLASS: Menubar
// MEMBER FUNCTION: 
//**************************************************************************/ 
//
// 
// 
// EXTERNAL VARIABLE REFERENCES
//   IN :  
//   OUT: 
// 
// PARAMETERS:
//
// RETURN: 
//**************************************************************************/
int
Menubar::select()
{
        int c;
        int cmd(0);

  refresh_all:
        bool quit = false;
        Screen.gotoxy( 0,0 );
        set_passive_color();

        Screen.empty_line();
        menu.first_menu();
        assert( menu.first_menu() == true );
        do {
                Screen.addstr ( menu.get_menu() );
                Screen.addch (' ');
        } while (menu.next_menu() == true);

        menu.first_menu();
        x = 0;
        if (menu.first_item() == true) {
                y = 1;
        } else {
                y = 0;
        }
        do {
                draw_menu_item( EMPHASIZED_FL );
                c = Screen.get_ch();  
                switch (c) {
                case CODE_CTRL_L:
                        goto refresh_all;
                case QUIT_CMD:
                        cmd = QUIT_CMD;
                        quit = true;
                        break;
                case ESCAPE:
                        cmd = 0;
                        quit = true;
                        break;
                case FORCED_QUIT_CMD:
#ifndef NO_EXCEPTIONS
                        throw quit_exception();
#else
                        cmd = FORCED_QUIT_CMD;
                        quit = true;
                        Quit_flag = true;
                        break;
#endif
                case CODE_RIGHT:
                        draw_menu_item();
                        if (true == menu.next_menu()) {
                                ++x;
                                if (true == menu.first_item()) {
                                        y = 1;
                                } else {
                                        y = 0;
                                }
                        }
                        draw_menu_item( EMPHASIZED_FL );
                        break;
                case CODE_LEFT:
                        draw_menu_item();
                        if (true == menu.prev_menu()) {
                                --x;
                                if (true == menu.first_item()) {
                                        y = 1;
                                } else {
                                        y = 0;
                                }
                        }
                        draw_menu_item( EMPHASIZED_FL );
                        break;
                case CODE_DOWN:
                        draw_menu_item();
                        if (true == menu.next_item()) {
                                ++y;
                        }
                        draw_menu_item( EMPHASIZED_FL );
                        break;
                case CODE_UP:
                        draw_menu_item();
                        if (true == menu.prev_item()) {
                                --y;
                        }
                        draw_menu_item( EMPHASIZED_FL );
                        break;
                case SELECT_CMD:
                        cmd = menu.get_command();
                        quit = true;
                        break;
                           
                };
        } while (false == quit);
                
        Screen.reset_attr();
        return cmd;
}

//**************************************************************************/
// CLASS: Menubar
// MEMBER FUNCTION: 
//**************************************************************************/ 
//
// 
// 
// EXTERNAL VARIABLE REFERENCES
//   IN :  
//   OUT: 
// 
// PARAMETERS:
//
// RETURN: 
//**************************************************************************/
void
Menubar::set_active_color()
{
        Screen.reset_attr();
        if (Screen.is_color_term() == true ) {
                Screen.textcolor (Settings.menuback_color,
                                  Settings.menu_color );
        } else {
                Screen.textcolor (BOLD);
        }
}

//**************************************************************************/
// CLASS: Menubar
// MEMBER FUNCTION: 
//**************************************************************************/ 
//
// 
// 
// EXTERNAL VARIABLE REFERENCES
//   IN :  
//   OUT: 
// 
// PARAMETERS:
//
// RETURN: 
//**************************************************************************/
void
Menubar::set_passive_color()
{
        Screen.reset_attr();
        if (Screen.is_color_term() == true) {
                Screen.textcolor ( Settings.menu_color );
                Screen.back_color( Settings.menuback_color );
        } else {
                Screen.inverse();
        }
}

//**************************************************************************/
// CLASS: Menubar
// MEMBER FUNCTION: 
//**************************************************************************/ 
//
// 
// 
// EXTERNAL VARIABLE REFERENCES
//   IN :  
//   OUT: 
// 
// PARAMETERS:
//
// RETURN: 
//**************************************************************************/
void
Menubar::draw_menu_item( int flag )
{
        Screen.reset_attr();
        int pos;

        if (flag & EMPHASIZED_FL ) {
                set_active_color();
        } else {
                for (pos = 1; pos < 4; pos++) {
                        Screen.gotoxy( 0, pos );
                        Screen.empty_line();
                }
                set_passive_color();
        }
        
        pos = x;
        int xcoord = 0;
        
        menu.first_menu();
        while (pos) {
                xcoord += menu.get_menu().length();
                ++xcoord;
                --pos;
                menu.next_menu();
        }

        Screen.gotoxy( xcoord, 0 );
        Screen.addstr( menu.get_menu() );

        if (flag & EMPHASIZED_FL ) {
                pos = 1;
                if (true == menu.first_item()) {
                        int len;
                        do {
                                Screen.gotoxy( xcoord, pos );
                                len = menu.get_maxlen();
                                if (pos == y) {
                                        set_active_color();
                                        Screen.addch( '>' );
                                } else {
                                        set_passive_color();
                                        Screen.addch( ' ' );
                                }
                                Screen.addstr( menu.get_item() );
                                len -= menu.get_item().length();
                                while (len > 0 ) {
                                        Screen.addch(' ');
                                        --len;
                                }
                                ++pos;
                        } while (menu.next_item() == true );
                }
                menu.first_item();
                for (pos = 1; pos < y; pos++ ) {
                        menu.next_item();
                }
        }
}










jmrsrc-0.7.23/src/message_display.cc100664    764    764      101623  6623366026  17063 0ustar  jvuokkojvuokko// 	$Id: message_display.cc,v 1.26 1998/09/27 09:53:57 jvuokko Exp $	

/****************************************************************************
 * *
 * *  MODULE : message_display.cc
 * *
 * *  Copyright (c) 1997 Jukka Vuokko
 * *  See file COPYING for more information about copyrights.
 * *
 ****************************************************************************
 * *
 * *
 * *  Functions for article viewing level.
 * *  
 * *
 * *
 * *
 ***************************************************************************/

#include <fstream.h>
#include <stdlib.h>   // abs()
#ifdef __WATCOMC__
#   include <io.h>    // F_OK
#else
#   include <unistd.h>
#endif
#include "jmr.hh"
#include "terminal_io.hh"
#include "menu.hh"
#include "mail.hh"
#include "menubar.hh"


static void save_thread_to_file (Msgthread *thread);
static void save_articles (Msgthread *thread, int flag, char *fname,
                           int filemode);


extern Terminal_screen Screen;
extern settings_t Settings;
extern Mail* mail;
extern Window* Wscript;


static const menuinfo article_menu_array[] = {
        {"[jmr]", 0 },
        {"Exit from menu", 0},
        {"Return to thread selection", QUIT_CMD},
        {"Quit jmr", FORCED_QUIT_CMD},
        {"@NEXT", 0 },
        {"[Moving]", 0},
        {"Next Unread", NEXT_UNREAD_CMD },
        {"First of thread", '<' },
        {"Last of thread", '>' },
        {"@NEXT", 0 },
        {"Followup", FOLLOWUP_CMD },
        {"@NEXT", 0 },
        {"Reply private", REPLY_CMD },
        {"@NEXT", 0 },
        {"@END", 0 }
};

/***************************************************************************
 * FUNCTION : save_thread_to_file
 ***************************************************************************
 *
 * description : Saves current thread or article to file.
 *
 *
 * in: Settings.extractdir, Screen
 * out:
 *
 * return :
 ***************************************************************************/
static void
save_thread_to_file (Msgthread *thread)
{
        ofstream file;
        String fullname;
        static char fname[MAXPATH];
        bool quit;
        int c;
        int flag = DEFAULT_FL;
        int filemode = 0;

        if ( !*fname ) {
                strcpy( fname, Settings.default_folder.c_str() );
        }
        Screen.gotoxy (0, Screen.get_ymax() -4 );
        for (int i = 0; i < 4; ++i ) {
                Screen.down();
                Screen.clr_eol();
        }
        if (ACCESS (Settings.extractdir.get(), F_OK)) {
                handle_error ("Cannot access extract directory");
                return;
        }
        Screen.up(1);
        show_option_str( "Save [@t@]hread [@a@]rticle or [@q@]uit ? @t@" );
        quit = false;
        do {
                c = Screen.get_ch();
                switch(c) {
                case 't':
                case SELECT_CMD:
                        flag = THREAD_FL;
                        quit = true;
                        break;
                case 'a':
                        flag = ARTICLE_FL;
                        Screen.addstr ("\ba" );
                        quit = true;
                        break;
                case QUIT_CMD:
                        return;
                }
        } while (quit == false);

        do {
                c = 0;
                Screen.bottom();
                Screen.clr_eol();
                Screen.reset_attr();
                Screen.addstr( "Input file name : " );
                Screen.textcolor(BOLD);
                Screen.getline (fname, 40);
                Screen.reset_attr();
                Screen.bottom();
                Screen.clr_eol();
                fullname = fname;
                if ( have_full_path( fullname ) == false && 
                     expand_path( fullname ) == false ) {
                        fullname = Settings.extractdir.get();
                        fullname += fname;
                } 
                if (  access( fullname.c_str(), R_OK ) == 0 ) {
                        Screen.bottom();
                        show_option_str("File exists. Select: [@o@]verwrite"
                                        " [@n@]ew file [@a@]ppend [@q@]uit ?"
                                        " @a@" );
                        Screen.clr_eol();
                        quit = false;
                        do {
                                c = Screen.get_ch();
                                switch (c) {
                                case 'o':
                                        filemode = ios::trunc;
                                        quit = true;
                                        break;
                                case 'n':
                                case QUIT_CMD:
                                        quit = true;
                                        break;
                                case 'a':
                                case SELECT_CMD:
                                        filemode = ios::app;
                                        quit = true;
                                        break;
                                }
                        } while (quit == false);
                        Screen.bottom();
                        Screen.clr_eol();
                } else {
                        filemode = ios::trunc;
                }
        } while (c == 'n');

        if (c != QUIT_CMD) {
                save_articles (thread, flag, fullname.get(), filemode);
        }
}

/***************************************************************************
 * FUNCTION : save_articles
 ***************************************************************************
 *
 * description : Saves article or thread to given file.
 *
 ***************************************************************************/
static void
save_articles (Msgthread *thread, // thread
              int flag,     // flag. if THREAD_FL, then saves whole thread
              char *fname,  // name of file
              int filemode) // mode. ios::trunc or ios::app
{
        ofstream file;
        Message *msg;
        
        file.open (fname, ios::out | filemode);
        if (file.fail()) {
                String err = "Cannot save message to file `";
                err += fname;
                err += "'";
                system_error ( err.c_str() );
                return;
        }
        
        if (flag & THREAD_FL) {
                thread->save_position();
                thread->first();
        }

        // write contents of thread to file
        do {
                msg = thread->get();
                assert( msg );
                msg->save_to_file (file, mail->get_bbs_name(),
                                   mail->get_group_name(),
                                   mail->get_group_number());

                if (!(flag & THREAD_FL)) {
                        break;
                }
        } while (thread->next() == true);
        
        if (flag & THREAD_FL) {
                thread->restore_position();
        }
        
        file.close();
}


//**************************************************************************/
//
// CLASS: Msgdisplay
// MEMBER FUNCTION: Msgdisplay
//
// description: Constructor for this class.
//
//
//**************************************************************************/
Msgdisplay::Msgdisplay ( Grpthread &groups_threads ) :
        threads( groups_threads), hist_ring(READ_HISTORY_SIZE)
{
        
        orig_article = 0;
        ymax = Screen.get_ymax() - 2;
        ymin = 6;
        ypos = ymin;
        lines_per_page = ymax - ymin;
        pages = 0;
        lines_in_article = 0;
        xmax = Screen.get_xmax();
        modefl = 0;
        if (Settings.is_wrapmode == true ) {
                modefl |= WRAPMODE;
        }
        menubar.init( article_menu_array );
        menubar.set_text( MENUBAR_TEXT );

        for (int i=0; i <= xmax; i++) {
                dashline += '-';
        }
}

//**************************************************************************/
//
// CLASS: Msgdisplay
// MEMBER FUNCTION: enter
//
// description: Main function for class. Gets events from user.
//
// in:  Screen, Settings.is_wrapmode
// out: Settings.is_wrapmode
//
//
// returns: DEFAULT_FL or TAGGED_FL.
//
//**************************************************************************/
int
Msgdisplay::enter (
                   // pointer to flag of viewing mode
                   bool *showflag,
                   // if NEXT_UNREAD_FL, then search for next unread.
                   int flag)
{
        int c;
        int rc = DEFAULT_FL;
        bool quit = false;
        bool first_pass = false;
        show_all_articles = showflag;
        is_bottom = false;
        // go to next unread, or first message of thread
        if (flag & NEXT_UNREAD_FL) {
                threads.next_unread();
        } else {
                threads.get_thread()->first();
        }
        sline.init( Screen.get_xmax() + 1);
        
        update ();
        hist_ring.clear();
        hist_ring.add( orig_article );
        display (REFRESH_ALL_FL);
        iline.show_default();
        do {
                c = Screen.get_ch();
                iline.reset();
                switch (c) {
                case ESCAPE:
                        if ( Screen.get_xch() == ESCAPE ) {
                                iline.set( MENU_MSG );
                                iline.show();
                                c = menubar.select();
                                draw_header();
                                iline.reset();
                        } else if ( Screen.get_xch() == 'r' ) {
                                modefl ^= ROT13MODE;
                                display();
                        } else if ( Screen.get_xch() == 'w' ) {
                                modefl ^= WRAPMODE;
                                Settings.is_wrapmode =
                                        Settings.is_wrapmode ? false : true;
                                update();
                                display();
                        }
                        break;
                case MESSAGEWIN_CMD:
                        Wscript->enable();
                        iline.set ( MESSAGES_MSG );
                        iline.show();
                        Screen.get_ch();
                        iline.reset();
                        Wscript->disable();
                        break;
                case QUIT_CMD:
                case CODE_LEFT:
                        quit = true;
                        break;
                case CODE_DOWN:
                        down();
                        break;
                case CODE_NPAGE:
                case PAGE_DOWN_CMD:
                        if (is_bottom == true && first_pass == false) {
                                first_pass = true;
                                iline.set(AT_END_OF_ARTICLE_MSG);
                        } else if (is_bottom == true && first_pass == true) {
                                first_pass = false;
                                if (page_down(NEXT_FL) == false) {
                                        iline.set( NO_UNREAD_MSG, ERROR_FL );
                                }
                        } else {
                                page_down();
                        }
                        break;
                case CODE_PPAGE:
                case PAGE_UP_CMD:
                        page_up();
                        break;
                case CODE_UP:
                        up();
                        break;
                case CODE_HOME:
                        display (REFRESH_ALL_FL);
                        break;
                case CODE_END:
                        go_bottom();
                        break;
                case NEXT_UNREAD_CMD:
                case SELECT_CMD:
                        if (true == threads.next_unread()) {
                                update();
                                hist_ring.add( orig_article );
                                display (REFRESH_ALL_FL);
                        } else {
                                iline.set( NO_UNREAD_MSG, ERROR_FL );
                        }
                        break;
                case NEXT_CMD:
                        //case CODE_RIGHT:
                        if (true == next()) {
                                update();
                                hist_ring.add( orig_article );
                                display (REFRESH_ALL_FL);
                        } else {
                                quit = true;
                        }
                        break;
                case PREV_CMD:
                        if (true == prev()) {
                                update();
                                hist_ring.add( orig_article );
                                display (REFRESH_ALL_FL);
                        }
                        break;
                case EDIT_REPLY_CMD:
                        if(mail->get_group_number() == NEW_REPLIES_NUMBER) {
                                mail->edit( orig_article );
                                update();
                                display( REFRESH_ALL_FL );
                        }
                        break;
                case 'E':
                        if(mail->get_group_number() == NEW_REPLIES_NUMBER) {
                                mail->edit_header( orig_article );
                                update();
                                display( REFRESH_ALL_FL );
                        }
                        break;

                case REPLY_CMD:
                        mail->reply( orig_article );
                        display(REFRESH_ALL_FL);
                        break;
                case FOLLOWUP_CMD:
                        mail->followup( orig_article );
                        display(REFRESH_ALL_FL);
                        break;
                case SAVE_TO_FILE_CMD:
                        save_thread_to_file (threads.get_thread());
                        display(REFRESH_ALL_FL);
                        break;
                case TAG_CMD:
                        if (mail->get_group_number() < BASEGROUP_NUMBER) {
                                mail->tag_article( orig_article );
                                draw_header_status();
                                rc = TAGGED_FL;
                        }
                        break;
                case KILL_CMD:
                        if (mail->get_group_number() > PERSONAL_NUMBER) {
                                orig_article->invert_kill_flag();
                                draw_header_status();
                        }
                        break;
                case FORCED_QUIT_CMD:
#ifndef NO_EXCEPTIONS
                        throw quit_exception();
#else
                        Quit_flag = true;
                        break;
#endif
                case '<':
                case CODE_CTRL_A:
                        if (true == begin_of_thread()) {
                                update();
                                hist_ring.add( orig_article );
                                display( REFRESH_ALL_FL );
                        }
                        break;
                case '>':
                case CODE_CTRL_E:
                        if (true == end_of_thread()) {
                                update();
                                hist_ring.add( orig_article );
                                display( REFRESH_ALL_FL );
                        }
                        break;
                case HELP_CMD:
                        show_help();
                        display( REFRESH_ALL_FL );
                        break;
                case PREV_IN_HIST_CMD:
                        if (true == --hist_ring ) {
                                threads.go_message( hist_ring.get() );
                                update();
                                display( REFRESH_ALL_FL );
                        }
                        break;
                case NEXT_IN_HIST_CMD:
                        if (true == ++hist_ring) {
                                threads.go_message( hist_ring.get() );
                                update();
                                display( REFRESH_ALL_FL );
                        }
                        break;
                case 0:
                        break;
                default:
                        iline.set( INVALID_COMMAND_MSG );
                        break;
                }
                iline.show();
        } while (quit == false
#ifdef NO_EXCEPTIONS
                 && Quit_flag == false
#endif
                 );

        Screen.bottom();
        Screen.clr_eol();
        return rc;
}

//**************************************************************************/
// CLASS: Msgdisplay
// MEMBER FUNCTION: begin_of_thread
//**************************************************************************/ 
//
// Moves current position to first article of current thread.
// 
// EXTERNAL VARIABLE REFERENCES
//   IN :  
//   OUT: 
// 
//
// RETURN: true if success
//**************************************************************************/
bool
Msgdisplay::begin_of_thread()
{
        return threads.get_thread()->first();
}

//**************************************************************************/
// CLASS: Msgdisplay
// MEMBER FUNCTION: end_of_thread
//**************************************************************************/ 
//
// Moves position within current thread to thread's last article
// 
// EXTERNAL VARIABLE REFERENCES
//   IN :  
//   OUT: 
// 
//
// RETURN: true if success
//**************************************************************************/
bool
Msgdisplay::end_of_thread()
{
        return threads.get_thread()->last();
}

//**************************************************************************/
//
// CLASS: Msgdisplay
// MEMBER FUNCTION: next
//
// description: Moves position to next article in a current thread.
//              If thread ends, then switch to next thread.
//
// in:  
// out: 
//
// returns: true if success.
//
//**************************************************************************/
bool
Msgdisplay::next ()
{
        if ( threads.get_thread()->next() == true) {
                return true;
        }
        // uusi kokeilu, vanhassa ei ehkä toiminut threadin vaihto oikein???
        if ( threads.next() == true ) {
                return threads.get_thread()->first();
        }
        return false;
}

//**************************************************************************/
//
// CLASS: Msgdisplay
// MEMBER FUNCTION: prev
//
// description: Moves position to previous message of a current thread.
//              If already at start of thread, then moves to last message of
//              previous thread.
// in:  
// out: 
//
// returns: true if success.
//
//**************************************************************************/
bool
Msgdisplay::prev()
{
        bool rc = threads.get_thread()->prev();
        if (rc == false) {
                if (threads.prev() == true) {
                        rc = threads.get_thread()->last();
                }
        }
        return rc;
}

//**************************************************************************/
//
// CLASS: Msgdisplay
// MEMBER FUNCTION: update
//
// description: Counts some necessary values and makes sure that lines
//              are read from file to memory. (Message::first_line()).
// FIXME: lisää maininnat orig_articlen assosionnista...
//
// NOTE: This should be called only when position in thread is changed!!!!
//
//**************************************************************************/
void
Msgdisplay::update()
{
        xmax = Screen.get_xmax();
        orig_article = threads.get_article();
        orig_article->first_line();
        article = *orig_article;
        if ( modefl & WRAPMODE ) {
                article.wrap( xmax+1 );
        }
        lines_in_article = article.get_num_of_lines();
        first_of_page = 0;
        pages = lines_in_article / lines_per_page + 1;
}

//**************************************************************************/
//  CLASS: Msgdisplay
//  MEMBER FUNCTION: go_bottom
// 
// 
//  DESCRIPTION: Moves position to bottom of article
// 
// 
//**************************************************************************/
void
Msgdisplay::go_bottom()
{
        for (;;) {
                article.save_position();
                int i = lines_per_page - 1;
                while (i) {
                        article.next_line();
                        i--;
                }
                if (article.next_line() == true) {
                        first_of_page += lines_per_page;
                } else {
                        article.restore_position();
                        break;
                }
        }
        display();
}

//**************************************************************************/
//
// CLASS: Msgdisplay
// MEMBER FUNCTION: display
//
// description: Displays everyhting. If flag is REFRESH_ALL_FL, then
//              moves top of article and redraws header and footer.
//
//
// in:  threadlist (number of articles)
// out: 
//
// returns: 
//
//**************************************************************************/
void
Msgdisplay::display (int flag)
{
        String str;
        int i;
        int y;
        int pros;
        int lastline = lines_in_article;
        int width = xmax + 1;
        Screen.reset_attr();
        if (flag & REFRESH_ALL_FL) {
                if (!(orig_article->get_status() & READ_ST) &&
                    mail->get_group_number() != PERSONAL_NUMBER &&
                    mail->get_group_number() != NEW_REPLIES_NUMBER) {
                        // Set articles status to readed
                        orig_article->set_status_bits (READ_ST);
                        threads.get_thread()->dec_unread();
                }
                Screen.erase();
                
                draw_header();
                if (mail->get_group_number() == NEW_REPLIES_NUMBER) {
                        iline.set_default( MSG_DISPLAY_NEW_REPLIES_KEYS_MSG);
                } else if (mail->get_group_number() >= BASEGROUP_NUMBER) {
                        iline.set_default( MSG_DISPLAY_BASEGROUP_KEYS_MSG);
                } else {
                        iline.set_default( MSG_DISPLAY_KEYS_MSG);
                }
                iline.show_default();
                first_of_page = 0;
                article.first_line();
        }
        // show group on status bar. If current group is some of the
        // basegroups, then display original group of the article
        str = "Group: [";
        str += orig_article->get_group_number();
        str += "] ";
        if (mail->get_group_number() >= BASEGROUP_NUMBER) {
                mail->save_group_pos();
                mail->go_group_number( orig_article->get_group_number());
                str +=  mail->get_group_name();
                mail->restore_group_pos();
        } else {
                str += mail->get_group_name();
        }

        if ((modefl & (ROT13MODE | WRAPMODE)) == (ROT13MODE | WRAPMODE)) {
                str += " (mode:ROT13,WRAP)";
        } else if ( modefl & ROT13MODE ) {
                str += " (mode:ROT13)";
        } else if ( modefl & WRAPMODE ) {
                str += " (mode:WRAP)";
        }
        DEBUG( "modefl = " << modefl << " " + str);
        sline.set_infotext( str.get() );
        
        // print message lines
        y = ymin;
        if (article.check_lines() == true) {
                do {
                        Screen.gotoxy (0,y);
                        Screen.clr_eol();
                        if ( article.getline().length() > width ) {
                                str.copy (article.getline(), 0, xmax);
                        } else {
                                str.copy (article.getline(), 0, width);
                        }
//                        if (str.find_any_ch (">", 6) == true) {
//                                Screen.textcolor (Settings.quote_color);
//                        } else 
                        if (str.find_any_ch (Settings.quotechars.get(),
                                   Settings.quotedepth)) {
                                Screen.textcolor (Settings.quote_color);
                        } else {
                                Screen.textcolor (Settings.article_color);
                        }
                        if ( modefl & ROT13MODE ) {
                                str.rot13();
                        }
                        Screen.addstr( str );
                        if (article.getline().length() > width) {
                                Screen.textcolor (RED | BOLD);
                                if ( modefl & WRAPMODE ) {
                                        Screen.addch( Settings.wrap_indicator);
                                } else {
                                        Screen.addch ('$');
                                }
                        }
                        Screen.reset_attr();

                        if (++y > ymax)
                                break;
                } while (article.next_line() == true);
                lastline = y - ymin + first_of_page;
                pros = 100 * lastline;
                pros /= lines_in_article;
        } else {
                pros = 100;
        }
        i = y-ymin-1;

        // clear bottom of screen
        while (y <= ymax) {
                Screen.gotoxy(0,y);
                //Screen.empty_line();
                Screen.clr_eol();
                y++;
        }
        
        // Test, if current line is last line of message
        is_bottom = false;
        if (article.next_line() == true) {
                sline.set_percent( pros );
                article.prev_line();
                str = "L";
                str += lastline;
                str += "/";
                str += lines_in_article;
                sline.set_extra_info( str.get() );
        } else {
                Msgthread *tmp = threads.get_thread();
                is_bottom = true;
                if (tmp->next() == true) {
                        tmp->prev();
                        sline.set_extra_info( "Next Response" );
                } else {
                        sline.set_extra_info("Last Response");
                }
                if (lines_in_article <= ymax - ymin) {
                        sline.set_percent( ALL_PERCENT );
                } else {
                        sline.set_percent( BOT_PERCENT );
                }
        }

        // move pointer to first line of page
        while (i>0) {
                article.prev_line();
                i--;
        }
        sline.show();
}

//**************************************************************************/
//
// CLASS: Msgdisplay
// MEMBER FUNCTION: draw_header
//
// description: Displays some interesting data at top of screen.
//
// NOTE: this displays data of original message 'orig_article' not
//       wrapped one!
//
//**************************************************************************/
void
Msgdisplay::draw_header()
{
        String str;

        menubar.show();
        Screen.reset_attr();
        for (int i=1; i <= 3; i++) {
                Screen.gotoxy( 0, i );
                Screen.empty_line();
        }
        Screen.gotoxy(0,1);
        Screen.print("   Date: %s, %s ", orig_article->get_date().c_str(),
                      orig_article->get_time().c_str() );

        draw_header_status();
        
        Screen.gotoxy (0,2);
        Screen.addstr( "   From: " );
        Screen.textcolor (Settings.header_author_color);
        Screen.addstr( str.copy (orig_article->get_writer(), 0, 40) );
        if (orig_article->get_writer().length() > 40) {
                Screen.addch( '$' );
        }
        Screen.reset_attr();
        Screen.gotoxy( 0, 3 );
        Screen.addstr( "     To: " );
        if (orig_article->get_receiver() == Settings.username) {
                Screen.textcolor (Settings.personal_color | BLINK);
        }
        Screen.addstr( str.copy (orig_article->get_receiver(), 0, 40) );
        if (orig_article->get_receiver().length() > 40) {
                Screen.addch( '$' );
        }
        Screen.reset_attr();
        
        Screen.gotoxy( Screen.get_xmax() - 30,2);
        Screen.print( "Msgno : %d", orig_article->get_number() );
        Screen.print( " Ref: %d", orig_article->get_reference_num() );
        Screen.gotoxy( Screen.get_xmax() - 30, 3);
        Screen.print( "Respno: %d/%d", orig_article->get_number_in_thread(),
                      threads.count_threads_articles() );
        
        Screen.gotoxy( 0, 4 );
        Screen.addstr( "Subject: " );
        if (Screen.is_color_term() == true) {
                Screen.textcolor ( Settings.header_subject_color );
        } else {
                Screen.textcolor ( BOLD );
        }
        Screen.addstr( str.copy (orig_article->get_subject(), 0, 72) );
        if (orig_article->get_subject().length() > 72) {
                Screen.addch( '$' );
        }
        Screen.gotoxy( 0, 5 );

        set_dashline_color();
        Screen.addstr( dashline );
        Screen.reset_attr();
}
//**************************************************************************/
// CLASS: Msgdisplay
// MEMBER FUNCTION: draw_header_status
//**************************************************************************/ 
//
// Display status information of current article.
// This also queries information directly from original message (orig_article)
// not from wrapped message, 'article'.
// 
//**************************************************************************/
void
Msgdisplay::draw_header_status()
{
        Screen.gotoxy( Screen.get_xmax() - 30, 1);
        Screen.reset_attr();
        Screen.clr_eol();
        Screen.addstr( "Status: " );
        if (orig_article->get_status() & REPLIED_ST) {
                Screen.textcolor (CYAN | BOLD | BLINK);
                Screen.addstr( "Replied" );
                Screen.reset_attr();
        } else if (orig_article->get_status() & PRIVATE_ST) {
                Screen.textcolor(MAGENTA);
                Screen.addstr( "Private" );
        } else {
                Screen.addstr( "Public " );
        }
        Screen.gotoxy( Screen.get_xmax() - 14, 1);
        if (orig_article->is_killed() == true) {
                Screen.textcolor (RED | BOLD | BLINK);
                Screen.addstr( "! Killed !" );
        } else if (orig_article->get_status() & TAGGED_ST) {
                Screen.textcolor (RED | BOLD);
                Screen.addstr( "- Tagged -" );
        }
        Screen.reset_attr();
}


//**************************************************************************/
//
// CLASS: Msgdisplay
// MEMBER FUNCTION: down
//
// description: Scrolls one line down
//
//
//**************************************************************************/
void
Msgdisplay::down()
{
        article.save_position();
        int i = lines_per_page - 1;
        while (i) {
                article.next_line();
                i--;
        }
        if (article.next_line() == true) {
                article.restore_position();
                article.next_line();
                first_of_page++;
                display();
        } else {
                article.restore_position();
        }
}

//**************************************************************************/
//
// CLASS: Msgdisplay
// MEMBER FUNCTION: Up
//
// description: Scrolls one line up.
//
//
//**************************************************************************/
void
Msgdisplay::up()
{
        if (first_of_page > 0) {
                first_of_page--;
                article.prev_line();
                display();
        }
}

//**************************************************************************/
//
// CLASS: Msgdisplay
// MEMBER FUNCTION: page_down
//
// description: Moves one page down, or if end of article and flag is
//              NEXT_FL then moves to next article.
//
//
// returns: false if no more articles
//
//**************************************************************************/
bool
Msgdisplay::page_down (int flag)
{
        bool rc = true;
        article.save_position();
        int i = lines_per_page - 1;
        while (i) {
                article.next_line();
                i--;
        }
        if (article.next_line() == true) {
                first_of_page += lines_per_page;
                display();
        } else if (flag & NEXT_FL) {
                //  if position is bottom of article, then switch to next
                //  article
                article.restore_position();
                if (true == (rc = threads.next_unread()) ) {
                        update();
                        display (REFRESH_ALL_FL);
                }
        }
        return rc;
}

//**************************************************************************/
//
// CLASS: Msgdisplay
// MEMBER FUNCTION: page_up
//
// description: Moves one page up within article.
//
//
//**************************************************************************/
void
Msgdisplay::page_up()
{
        int i = lines_per_page;
        while (i && first_of_page > 0) {
                first_of_page--;
                i--;
                article.prev_line();
        }
        display();
}
jmrsrc-0.7.23/src/message_menu.cc100664    764    764      104007  6623366026  16361 0ustar  jvuokkojvuokko// 	$Id: message_menu.cc,v 1.15 1997/07/17 15:35:03 jvuokko Exp $	

/****************************************************************************
 * *
 * *  MODULE : message_menu.cc
 * *
 * *  Copyright (c) 1997 Jukka Vuokko
 * *  See file COPYING for more information about copyrights.
 * *
 ****************************************************************************
 * *
 * *
 * *  Functions for thread selection level.
 * *  
 * *
 * *
 * *
 ***************************************************************************/

#include <fstream.h>
#include <stdlib.h>   // abs()
#ifdef __WATCOMC__
#   include <io.h>    // F_OK
#else
#   include <unistd.h>
#endif
#include "jmr.hh"
#include "terminal_io.hh"
#include "menu.hh"
#include "mail.hh"
#include "menubar.hh"

extern Terminal_screen Screen;
extern settings_t Settings;
extern Mail* mail;
extern Window* Wscript;

static const menuinfo thread_menu_array[] = {
        {"[jmr]", 0 },
        {"Exit from menu", 0},
        {"Return to group selection", QUIT_CMD},
        {"Quit jmr", FORCED_QUIT_CMD},
        {"@NEXT", 0 },
        {"Write", WRITE_CMD },
        {"@NEXT", 0 },
        {"Yank in/out", YANK_IN_OUT_CMD },
        {"@NEXT", 0 },
        {"@END", 0 }
};

//**************************************************************************/
//
// CLASS: Msgmenu
// MEMBER FUNCTION: Msgmenu
//
// description: Constructor
//
//
// in:  Screen
// out: 
//
// returns: 
//
//**************************************************************************/
Msgmenu::Msgmenu ()
{
        show_all_articles = false;
        is_status_msg = false;
        thread_mode = true;
        thread_cnt = 0;

        ymin = 6;
        ymax = Screen.get_ymax() - 2;
        lines_per_page = ymax - ymin + 1;
        pages = 0;
        first_of_page = 0;
        current = 0;
        ypos = ymin;

        menubar.init( thread_menu_array );
        menubar.set_text( MENUBAR_TEXT );

        for (int i=Screen.get_xmax(); i >= 0; i--) {
                dashline += '-';
        }
}


//**************************************************************************/
//
// CLASS: Msgmenu
// MEMBER FUNCTION: enter
//
// description: Main procedure for class. Handles events from input.
//
// NOTE: Position in ListGroup-list must be in right place
//
// in:  mail, Screen, Quit_flag
// out: Quit_flag
//
// returns: 
//
//**************************************************************************/
void
Msgmenu::enter ()
{
        int c;
        int flag;
        bool quit = false;
        bool rc = false;
        
        show_all_articles = false;
        
        c = mail->get_group_number();
        if (mail->count_articles() > 0 && mail->count_unread() == 0) {
                show_all_articles = true;
        }

        mail->first_article();

        threads.update( show_all_articles );

        sline.init( Screen.get_xmax() + 1);

        Msgdisplay msgdisp( threads );
        update();
        display();

        do {
                c = Screen.get_ch();
                flag = DEFAULT_FL;
                iline.reset();
                if (c == ESCAPE) {
                        iline.set( MENU_MSG );
                        iline.show();
                        c = menubar.select();
                        display();
                        iline.reset();
                }
                switch (c) {
                case MESSAGEWIN_CMD:
                        Wscript->enable();
                        iline.set ( MESSAGES_MSG );
                        iline.show();
                        Screen.get_ch();
                        iline.reset();
                        Wscript->disable();
                        break;
                case NEXT_UNREAD_CMD:
                        if (go_next_article() == true) {
                                flag = msgdisp.enter(&show_all_articles,
                                                     NEXT_UNREAD_FL);
                                update_msg_counts();
                                set_selector_to_current_thread();
                                display ();
                        } else {
                                iline.set( NO_UNREAD_MSG, ERROR_FL );
                        }
                        break;
                case SELECT_CMD: 
                case CODE_RIGHT:
                        if (threads.check() == true) {
                                flag = msgdisp.enter(&show_all_articles,
                                                     DEFAULT_FL);
                                update_msg_counts();
                                set_selector_to_current_thread();
                                display ();
                        }
                        break;
                case REPLY_CMD:
                        if (threads.check() == false) {
                                break;
                        }
                        mail->reply(threads.get_article());
                        if (mail->get_group_number() == NEW_REPLIES_NUMBER) {
                                threads.update( show_all_articles );
                                update();
                        }
                        update_msg_counts();
                        display();
                        break;
                case FOLLOWUP_CMD:
                        if (threads.check() == false) {
                                break;
                        }
                        mail->followup(threads.get_article());
                        if (mail->get_group_number() == NEW_REPLIES_NUMBER) {
                                threads.update( show_all_articles );
                                update();
                        }
                        update_msg_counts();
                        display();
                        break;
                case EDIT_REPLY_CMD:
                        if (mail->get_group_number() == NEW_REPLIES_NUMBER) {
                                mail->edit(threads.get_article());
                                threads.update( show_all_articles );
                                update();
                                update_msg_counts();
                                display();
                        }
                        break;
                case 'E':
                        if (mail->get_group_number() == NEW_REPLIES_NUMBER) {
                                mail->edit_header(threads.get_article());
                                threads.update( show_all_articles );
                                update();
                                update_msg_counts();
                                display();
                        }
                        break;
                case KILL_CMD:
                        if (mail->get_group_number() > PERSONAL_NUMBER) {
                                threads.get_article()->invert_kill_flag();
                                Screen.gotoxy (0, ypos);
                                draw_article_info (EMPHASIZED_FL);
                        }
                        break;
                case WRITE_CMD:
                        if (mail->get_group_number() < BASEGROUP_NUMBER) {
                                mail->write();
                                update_msg_counts();
                                display();
                        }
                        break;
                case QUIT_CMD:
                case CODE_LEFT:
                        quit = true;
                        break;
                case CODE_DOWN:
                case NEXT_CMD:
                        if (false == down()) {
                                iline.set( AT_BOTTOM_MSG );
                        }
                        break;
                case CODE_UP:
                case PREV_CMD:
                        if (false == up()) {
                                iline.set( AT_TOP_MSG );
                        }
                        break;
                case CODE_HOME:
                        update();
                        //display();
                        draw_list();
                        break;
                case CODE_END:    
                        go_last_thread();
                        draw_list();
                        break;
                case CODE_PPAGE:  // same as PAGE_UP_CMD
                case PAGE_UP_CMD:
                        if (false ==page_up()) {
                                iline.set( AT_FIRST_PAGE_MSG );
                        }
                        break;
                case CODE_NPAGE:
                case PAGE_DOWN_CMD:
                        if (false ==page_down()) {
                                iline.set( AT_LAST_PAGE_MSG );
                        }
                        break;
                case CATCHUP_CMD:
                        quit = catchup();
                        break;
                case FORCED_CATCHUP_CMD:
                        mail->catchup();
                        quit = true;
                        break;
                case YANK_IN_OUT_CMD:
                        show_all_articles = show_all_articles == false ?
                                true : false;
                        threads.update( show_all_articles );
                        update();
                        display();
                        break;
                case SORT_CMD:
                        if (true == sort()) {
                                update();
                                display();
                                iline.set("Articles sorted!");
                        } else {
                                display();
                                iline.show( REFRESH_ALL_FL );
                        }
                        break;
                case TAG_CMD:
                        if (mail->get_sort_order() == THREAD_SORTED) {
                                iline.set(TAGGING_NOT_ALLOWED_MSG, ERROR_FL);
                                break;
                        }
                        if (mail->get_group_number() != REPLYLOG_NUMBER &&
                            mail->get_group_number() != NEW_REPLIES_NUMBER) {
                                if (!threads.check()) {
                                        iline.set("No articles to tag!",
                                                  ERROR_FL);
                                        break;
                                }
                                mail->tag_article (threads.get_article());
                                flag = TAGGED_FL;
                                Screen.gotoxy (0, ypos);
                                draw_article_info (EMPHASIZED_FL);
                        }
                        break;
                case TAG_ALL_CMD:
                        if (mail->get_sort_order() == THREAD_SORTED) {
                                iline.set( TAGGING_NOT_ALLOWED_MSG, ERROR_FL );
                                break;
                        }
                        if (mail->get_group_number() != REPLYLOG_NUMBER &&
                            mail->get_group_number() != NEW_REPLIES_NUMBER) {
                                rc = threads.tag_all();
                                flag = TAGGED_FL;
                                //update();
                                display();
                                if (rc == true) {
                                        iline.set("All articles tagged!");
                                } else {
                                        iline.set("All tags removed");
                                }
                        }
                        break;
                case FORCED_QUIT_CMD:
#ifndef NO_EXCEPTIONS
                        throw quit_exception();
#else
                        Quit_flag = true;
                        break;
#endif
                case HELP_CMD:
                        show_help();
                        display();
                        break;
                case 0:
                        break;
                default:
                        iline.set( INVALID_COMMAND_MSG );
                        break;
                }; // end of switch-case
                
                if ( mail->get_group_number() == TAGGED_ARTICLES_NUMBER &&
                     ( flag & TAGGED_FL ) ){
                        threads.update( show_all_articles );
                        update();
                        update_msg_counts();
                        display();
                }
                iline.show();
        } while (quit == false
#ifdef NO_EXCEPTIONS
                 && Quit_flag == false
#endif
                 );
        mail->unload_articles_of_group();
        
        // update read-counts in personal mail group
        mail->save_group_pos();
        mail->go_group_number (PERSONAL_NUMBER);
        mail->update_groups_counts();
        mail->restore_group_pos();

        Screen.bottom();
        Screen.clr_eol();
}

//**************************************************************************/
//
// CLASS: Msgmenu
// MEMBER FUNCTION: 
//
// description: Inits some variables.
//
//
//**************************************************************************/
void
Msgmenu::update()
{
        thread_cnt = threads.count_threads();
        pages = thread_cnt / lines_per_page;
        pages++;
        current_page = 0;
        first_of_page = 0;
        current = 0;
        ypos = ymin;
        if (mail->get_sort_order() == THREAD_SORTED) {
                thread_mode = true;
        } else {
                thread_mode = false;
        }
        if (mail->get_group_number() == NEW_REPLIES_NUMBER) {
                iline.set_default(THREAD_MENU_NEW_REPLIES_KEYS_MSG);
        } else if (mail->get_group_number() == REPLYLOG_NUMBER ||
                   mail->get_group_number() == TAGGED_ARTICLES_NUMBER) {
                iline.set_default( THREAD_MENU_BASEGROUP_KEYS_MSG );
        } else {
                iline.set_default( THREAD_MENU_KEYS_MSG );
        }
}
//**************************************************************************/
// CLASS: Msgmenu
// MEMBER FUNCTION: catchup
//**************************************************************************/ 
//
// Mark all articles of current group as read if user answers 'y' to
// question.
// 
// EXTERNAL VARIABLE REFERENCES
//   IN :  Screen, mail
//   OUT:  
// 
// RETURN: true if articles are marked as read
//**************************************************************************/
bool
Msgmenu::catchup()
{
        bool rc = false;
        Screen.reset_attr();
        Screen.
Results 1 - 1
Help - FTP Sites List - Software Dir.
Searching half a billion files worldwide
© 1997-2009 MARUHN Internet Solutions