/*
 * MultiMail offline mail reader
 * message display

 Copyright 1996 Kolossvary Tamas <thomas@tvnet.hu>
 Copyright 1997 John Zero <john@graphisoft.hu>
 Copyright 1997-2019 William McBrine <wmcbrine@gmail.com>
 Distributed under the GNU General Public License, version 3 or later. */

#include "interfac.h"

LetterWindow::Line::Line()
{
    next = 0;
    text = 0;
    attr = Normal;
}

void LetterWindow::Line::out(FILE *fd)
{
    for (unsigned i = 0; i < length; i++)
         fputc(text[i], fd);
    fputc('\n', fd);
}

LetterWindow::LetterWindow()
{
    linelist = 0;
    NM.isSet = hidden = rot13 = false;
    NumOfLines = 0;
    tagline1[0] = '\0';
    To = 0;

    beepPers = mm.resourceObject->getInt(BeepOnPers);
    lynxNav = mm.resourceObject->getInt(UseLynxNav);
}

LetterWindow::~LetterWindow()
{
    delete[] To;
}

net_address &LetterWindow::PickNetAddr()
{
    Line *line;
    static net_address result;
    int i;

    for (i = 0; i < NumOfLines; i++)
        if (Origin == linelist[i]->attr)
            break;

    if (i != NumOfLines) {
        line = linelist[i];
        i = line->length;

        char *end = (char *) (line->text) + i;

        while (((line->text[i - 1]) != '(') && i > 0)
            i--;
        // we have the opening bracket

        while ((i < (int) line->length) &&
              ((line->text[i] < '0') || (line->text[i] > '9')))
            i++;
        // we have the begining of the address

        char c = *end;
        *end = '\0';

        result = &line->text[i];

        *end = c;
    } else
        result = mm.letterList->getNetAddr();

    return result;
}

void LetterWindow::ReDraw()
{
    headbar->wtouch();
    header->wtouch();
    text->wtouch();
    statbar->cursor_on();  // to get it out of the way
    statbar->wtouch();
    statbar->cursor_off();
}

void LetterWindow::DestroyChain()
{
    if (linelist) {
        while (NumOfLines)
            delete linelist[--NumOfLines];
        delete[] linelist;
        linelist = 0;
    }
    letter_in_chain = -1;
}

/* Take a message as returned by getBody() -- the whole thing as one C
   string, with paragraphs separated by '\n' -- and turn it into a linked
   list with an array index, wrapping long paragraphs if needed. Also
   flags hidden and quoted lines.
*/
void LetterWindow::MakeChain(int columns, bool rejoin)
{
    static const char *seenby = "SEEN-BY:";
    const int tabwidth = 8;

    letter_body *msgBody;
    Line head, *curr;

    int x, orgarea = -1;
    if (mm.areaList->isCollection() && !mm.areaList->isReplyArea()) {
        orgarea = mm.areaList->getAreaNo();
        mm.areaList->gotoArea(mm.letterList->getAreaID());
    }
    bool inet = !(!(mm.areaList->getType() & INTERNET));

    DestroyChain();

    letter_in_chain = mm.letterList->getCurrent();
    curr = &head;

    bool qpenc = mm.letterList->isQP();
    if (hidden)
        mm.letterList->setQP(false);

    msgBody = mm.letterList->getBody();

    if (hidden)
        mm.letterList->setQP(qpenc);

    bool insig = false, skipSeenBy = false;

    while (msgBody) {
        unsigned char each;
#ifdef BOGUS_WARNING
        char *src = 0,
#else
        char *src,
#endif
            *dest, *begin;

        int len, maxcol;
        lineattr tmpattr;

        bool wrapped = false;
        bool end = false, allHidden = msgBody->isHidden();
//#define XFACE
#ifdef XFACE
        // Quick X-Face hack
        if (allHidden) {
            src = msgBody->getText();
            const char *c = searchstr(src, "x-face: ");
            if (c) {
                char tmp[255];
                char *xname = mytmpnam();
                FILE *f = fopen(xname, "w");
                c += 8;
                bool end = false;
                while (!end) {
                    fputc(*c++, f);
                    if (!(*c) || (('\n' == *c) && !((' ' == c[1])
                        || ('\t' == c[1]))))

                        end = true;
                }
                fputc('\n', f);
                fclose(f);
                sprintf(tmp, "(uncompface -X %s | xv -; rm %s) &",
                        xname, xname);
                mysystem(tmp);
                delete[] xname;
            }
        }
#endif
        if (!hidden && allHidden)
            end = true;
        else {
            src = msgBody->getText();
            letterconv_in(src);
        }

        while (!end) {
            if (!hidden)  // skip ^A lines
                while ((*src == 1) || (skipSeenBy &&
                    !strncmp(src, seenby, 8))) {

                    do
                        src++;
                    while (*src && (*src != '\n'));
                    while (*src == '\n')
                        src++;
                }

            if (*src) {
                curr->next = new Line;
                curr = curr->next;
                tmpattr = allHidden ? Hidden : Normal;

                NumOfLines++;

                if (*src == 1) {  // ^A is hidden line marker
                    src++;
                    tmpattr = Hidden;
                } else
                    if (skipSeenBy && !strncmp(src, seenby, 8))
                        tmpattr = Hidden;

                begin = 0;
                curr->text = dest = src;
                len = 0;

                maxcol = columns;

                while (!end && ((len < maxcol) || (rejoin &&
                      (Quoted == tmpattr) && (len < 80)))) {

                    each = *src;

                    if (each == '\n') {
                        if (wrapped) {
                            wrapped = false;

                            // Strip any trailing spaces (1):

                            while (len && (dest[-1] == ' ')) {
                                len--;
                                dest--;
                            }

                            // If the next character indicates a new
                            // paragraph, quote or hidden line, this
                            // is the end of the line; otherwise make
                            // it a space:

                            each = src[1];
                            if (!each || (each == '\n') || (each == ' ')
                                || (each == '\t') || (each == '>')
                                || (each == 1) || skipSeenBy) {

                                src++;
                                break;
                            } else
                                if (!len)
                                    src++;
                                else
                                    each = ' ';
                        } else {  // if wrapped is false, EOL
                            src++;
                            break;
                        }
                    }

                    switch (each) {
                    case '\t':
                        maxcol -= (tabwidth - (len % tabwidth));
                    case ' ':
                        // begin == start of last word (for wrapping):

                        begin = 0;
                        if (Normal == tmpattr)
                            if (len >= (maxcol - 1))
                                wrapped = true;
                        break;
                    case '>':  // Quoted line
                        if ((len < 5) && (Normal == tmpattr))
                            tmpattr = Quoted;
                        break;
                    case ':':
                        if ((src[1] == '-') || (src[1] == ')') ||
                            (src[1] == '('))

                            break;
                    case '|':
                        if ((len == 0) && (Normal == tmpattr))
                            tmpattr = Quoted;
                        break;
                    default:
                        if (rot13) {
                            if (each >= 'A' && each <= 'Z')
                                each = (each - 'A' + 13) % 26 + 'A';
                            else if (each >= 'a' && each <= 'z')
                                each = (each - 'a' + 13) % 26 + 'a';
                        }
                    }
                    if ((each != ' ') && (each != '\t') && !begin)
                        begin = src;

                    if (each) {
                        *dest++ = each;
                        src++;
                        len++;
                    }

                    end = !each;  // a 0-terminated string
                }

                // Start a new line on a word boundary (if needed):

                if ((len >= maxcol) && begin && ((src - begin) < maxcol)
                    && !(rejoin && (Quoted == tmpattr))) {

                    x = src - begin;
                    len -= x;
                    while (x--)
                        *--src = *--dest;
                    wrapped = (Hidden != tmpattr);
                }

                // Check for sigs:

                const char *ct = curr->text;

                if (inet && !insig && (ct[0] == '-') && (ct[1] == '-') &&
                    (((ct[2] == ' ') && (len == 3)) || (len == 2)))

                    insig = true;

                if (insig)
                    tmpattr = Sigline;

                // Strip any trailing spaces (2):

                while (len && ((ct[len - 1] == ' ') || (ct[len - 1] == '\t')))
                    len--;

                curr->length = len;

                // Check for taglines, tearlines, and origin lines:

                if (!inet && (Normal == tmpattr)) {
                    each = *ct;
                    if ((ct[1] == each) && (ct[2] == each) &&
                        ((ct[3] == ' ') || (len == 3)))

                        switch (each) {
                        case '.':
                            if (len > 4)
                                tmpattr = Tagline;
                            break;
                        case '-':
                        case '~':
                            tmpattr = Tearline;
                        }
                    else
                        if (!strncmp(ct, " * Origin:", 10)) {
                            tmpattr = Origin;

                            // SEEN-BY: should only appear after Origin:
                            skipSeenBy = true;
                        }
                }

                curr->attr = tmpattr;

            } else
                end = true;
        }

        msgBody = msgBody->next;
    }

    linelist = new Line *[NumOfLines];
    curr = head.next;
    x = 0;
    while (curr) {
        linelist[x++] = curr;
        curr = curr->next;
    }

    if (orgarea != -1)
        mm.areaList->gotoArea(orgarea);
}

void LetterWindow::StatToggle(int mask)
{
    int stat = mm.letterList->getStatus();
    stat ^= mask;
    mm.letterList->setStatus(stat);
    ui->setAnyRead();
    DrawFlags();
}

void LetterWindow::MakeChainFixPos()
{
    MakeChain(COLS);
    if (position >= NumOfLines)
        position = (NumOfLines > y) ? NumOfLines - y : 0;
}

void LetterWindow::Draw(bool redo)
{
    if (redo) {
        rot13 = false;
        position = 0;
        tagline1[0] = '\0';
        if (!ui->dontRead()) {
            if (!mm.letterList->getRead()) {
                mm.letterList->setRead();  // nem ide kene? de.
                ui->setAnyRead();
            }
            if (beepPers && mm.letterList->isPersonal())
                beep();
        }
    }

    if (letter_in_chain != mm.letterList->getCurrent())
        MakeChainFixPos();

    DrawHeader();
    DrawBody();
    DrawStat();
}

char *LetterWindow::netAdd(char *tmp)
{
    net_address &na = mm.letterList->getNetAddr();
    if (na.isSet) {
        const char *p = na;
        if (*p)
            tmp += sprintf(tmp, (na.isInternet ? " <%s>" : " @ %s"), p);
    }
    return tmp;
}

#define flagattr(x) header->attrib((x) ? C_LHFLAGSHI : C_LHFLAGS)

void LetterWindow::DrawFlags()
{
    int stat = mm.letterList->getStatus();

    flagattr(mm.letterList->getPrivate());
    header->put(2, COLS - 27, "Pvt ");
    flagattr(stat & MS_READ);
    header->put(2, COLS - 23, "Read ");
    flagattr(stat & MS_REPLIED);
    header->put(2, COLS - 18, "Repl ");
    flagattr(stat & MS_MARKED);
    header->put(2, COLS - 13, "Mark ");
    flagattr(stat & MS_SAVED);
    header->put(2, COLS - 8, "Save  ");

    header->delay_update();
}

void LetterWindow::lineCount()
{
    char tmp[40];
    int percent = ((position + y) > NumOfLines) ? 100 :
                  (((long) position + y) * 100 / NumOfLines);

    header->attrib(C_LHMSGNUM);
    sprintf(tmp, "%9d/%-10d%3d%%    ", position + 1, NumOfLines, percent);
    header->put(1, COLS - 28, tmp);
    header->delay_update();
}

void LetterWindow::UpdateHeader()
{
    char tmp[256], *p;

    int orgarea = -1;
    if (mm.areaList->isCollection() && !mm.areaList->isReplyArea()) {
        orgarea = mm.areaList->getAreaNo();
        mm.areaList->gotoArea(mm.letterList->getAreaID());
    }

    int maxToFromWidth = COLS - 42;
    if (maxToFromWidth > 255)
        maxToFromWidth = 255;

    int maxSubjWidth = COLS - 9;
    if (maxSubjWidth > 255)
        maxSubjWidth = 255;

    if (orgarea != -1)
        mm.areaList->gotoArea(orgarea);

    header->attrib(C_LHMSGNUM);
    sprintf(tmp, "%ld (%d of %d)", mm.letterList->getMsgNum(),
            mm.letterList->getCurrent() + 1, mm.areaList->getNoOfLetters());
    header->put(0, 8, tmp);

    if (orgarea != -1)
        mm.areaList->gotoArea(mm.letterList->getAreaID());

    header->attrib(C_LHFROM);
    p = tmp + sprintf(tmp, "%.*s", maxToFromWidth, mm.letterList->getFrom());
    if (mm.areaList->isEmail())
        netAdd(p);
    letterconv_in(tmp);
    header->put(1, 8, tmp);

    header->attrib(C_LHTO);
    if (mm.areaList->hasTo()) {
        p = (char *) mm.letterList->getTo();
        if (*p) {
            p = tmp + sprintf(tmp, "%.*s", maxToFromWidth, p);
            if (mm.areaList->isReplyArea())
                if (p != tmp)
                    netAdd(p);
        }
    } else
        sprintf(tmp, "%.*s", maxToFromWidth,
                (const char *) mm.letterList->getNetAddr());

    letterconv_in(tmp);
    header->put(2, 8, tmp);

    header->attrib(C_LHSUBJ);
    int i = sprintf(tmp, "%.*s", maxSubjWidth, mm.letterList->getSubject());
    letterconv_in(tmp);
    header->put(3, 8, tmp);
    header->clreol(3, i + 8);

    header->attrib(C_LHDATE);
    sprintf(tmp, "%.30s", mm.letterList->getDate());

    // Truncate the date to fit, if needed:
    p = tmp;
    while (p && (strlen(tmp) > 26)) {
        p = strrchr(tmp, ' ');
        if (p)
            *p = '\0';
    }

    letterconv_in(tmp);
    header->put(0, COLS - 27, tmp);

    if (orgarea != -1)
        mm.areaList->gotoArea(orgarea);

    DrawFlags();
}

void LetterWindow::DrawHeader()
{
    header->Clear(C_LHEADTEXT);

    header->put(0, 2, "Msg#:");
    header->put(0, COLS - 33, "Date:");

    header->put(1, 2, "From:");
    header->put(1, COLS - 33, "Line:");

    if (mm.areaList->hasTo())
        header->put(2, 2, "  To:");
    else
        header->put(2, 2, "Addr:");
    header->put(2, COLS - 33, "Stat: ");
    header->put(3, 2, "Subj:");

    UpdateHeader();
}

void LetterWindow::oneLine(int i)
{
    static const coltype useatt[] = {
        C_LHIDDEN, C_LORIGIN, C_LTEAR, C_LTAGLINE, C_LTAGLINE,
        C_LQTEXT, C_LTEXT
    };
    int length, z = position + i;

    if (z < NumOfLines) {
        Line *curr = linelist[z];

        text->attrib(useatt[curr->attr]);

        if (Tagline == curr->attr) {
            length = curr->length - 4;
            if (length > TAGLINE_LENGTH)
                length = TAGLINE_LENGTH;
            strncpy(tagline1, &curr->text[4], TAGLINE_LENGTH);
            tagline1[length] = '\0';
        }
        length = text->put(i, 0, curr->text, curr->length);
    } else {
        text->attrib(C_LTEXT);
        length = 0;
    }
    text->clreol(i, length);
}

void LetterWindow::DrawBody()
{
    lineCount();

    for (int i = 0; i < y; i++)
        oneLine(i);
    text->delay_update();
}

void LetterWindow::DrawStat()
{
    static const char *helpmsg = " F1 or ? - Help ";
    char format[40], *tmp = new char[COLS + 1];

    bool expert = mm.resourceObject->getInt(ExpertMode);
    const char *pn = mm.resourceObject->get(PacketName);

    int pnlen = strlen(pn);
    if (pnlen > 20)
        pnlen = 20;
    int maxw = COLS - ((expert ? 4 : 20) + pnlen);

    bool collflag = false;
    if (mm.areaList->isCollection()) {
        if (mm.areaList->isReplyArea()) {
            maxw -= 10;
            sprintf(format, " %%.*s | REPLY in: %%-*.*s%%s");
        } else {
            maxw -= 13;
            sprintf(format, " %%.*s | PERSONAL in: %%-*.*s%%s");
        }
        mm.areaList->gotoArea(mm.letterList->getAreaID());
        collflag = true;
    } else
        sprintf(format, " %%.*s | %%-*.*s%%s");

    const char *s = mm.letterList->getNewsgrps();
    sprintf(tmp, format, pnlen, pn, maxw, maxw, s ? s :
            mm.areaList->getDescription(), expert ? "" : helpmsg);
    areaconv_in(tmp);

    if (s && ((int) strlen(s) > maxw))
        memcpy(tmp + maxw + pnlen + 1, "...", 3);

    if (collflag)
        ui->areas.Select();

    statbar->cursor_on();
    statbar->put(0, 0, tmp);
    statbar->delay_update();
    statbar->cursor_off();

    delete[] tmp;
}

void LetterWindow::TimeUpdate()
{
    static int mode = mm.resourceObject->getInt(ClockMode);
    char tmp[6];
    time_t now = time(0);

    if (mode && ((now - lasttime) > 59)) {
        if (1 == mode)
            strftime(tmp, 6, "%H:%M", localtime(&now));
        else {
            unsigned long elapsed = (now - starttime) / 60;
            sprintf(tmp, "%02ld:%02ld", (elapsed / 60) % 100, elapsed % 60);
        }
        headbar->put(0, COLS - 6, tmp);
        headbar->delay_update();
        lasttime = now - (now % 60) + ((2 == mode) ? (starttime % 60) : 0);
    }
}

void LetterWindow::MakeActive(bool redo)
{
    DestroyChain();

    y = LINES - 7;

    headbar = new Win(1, COLS, 0, C_LBOTTSTAT);
    header = new Win(5, COLS, 1, C_LHEADTEXT);
    text = new Win(y, COLS, 6, C_LTEXT);
    statbar = new Win(1, COLS, LINES - 1, C_LBOTTSTAT);

    char *tmp = new char[COLS + 1];
    int i = sprintf(tmp, " " MM_TOPHEADER, sysname());
    tmp[i] = '\0';

    headbar->put(0, 0, tmp);
    headbar->clreol(0, i);
    headbar->delay_update();

    delete[] tmp;

    lasttime = 0;
    TimeUpdate();

    Draw(redo);
}

bool LetterWindow::Next()
{
    if (mm.letterList->getActive() < (mm.letterList->noOfActive() - 1)) {
        ui->letters.Move(KEY_DOWN);
        mm.letterList->gotoActive(mm.letterList->getActive() + 1);
        Draw(true);
        return true;
    } else {
        ui->back();
        doupdate();
        ui->back();
        doupdate();
        ui->areas.KeyHandle('+');
    }
    return false;
}

bool LetterWindow::Previous()
{
    if (mm.letterList->getActive() > 0) {
        ui->letters.Move(KEY_UP);
        mm.letterList->gotoActive(mm.letterList->getActive() - 1);
        Draw(true);
        return true;
    } else {
        ui->back();
        doupdate();
        ui->back();
        doupdate();
        ui->areas.KeyHandle('-');
    }
    return false;
}

void LetterWindow::Move(int dir)
{
    switch (dir) {
    case MM_UP:
        if (position > 0) {
            position--;
            text->wscroll(-1);
            oneLine(0);
            text->delay_update();
            lineCount();
        }
        break;
    case MM_DOWN:
        if (position < NumOfLines - y) {
            position++;
            lineCount();
            text->wscroll(1);
            oneLine(y - 1);
            text->delay_update();
        }
        break;
    case MM_HOME:
        position = 0;
        DrawBody();
        break;
    case MM_END:
        if (NumOfLines > y) {
            position = NumOfLines - y;
            DrawBody();
        }
        break;
    case 'B':
    case MM_PPAGE:
        position -= ((y < position) ? y : position);
        DrawBody();
        break;
    case 'F':
    case MM_NPAGE:
        if (position < NumOfLines - y) {
            position += y;
            if (position > NumOfLines - y)
                position = NumOfLines - y;
            DrawBody();
        }
        break;
    }
}

void LetterWindow::NextDown()
{
    position += y;
    if (position < NumOfLines)
        DrawBody();
    else
        Next();
}

void LetterWindow::Delete()
{
    DestroyChain();
    delete headbar;
    delete header;
    delete text;
    delete statbar;
    headbar = header = text = statbar = 0;
}

bool LetterWindow::Save(int stype)
{
    FILE *fd;

    static const char *ntemplate[] = {
        "%.8s.%.03d", "%.8s.all", "%.8s.mkd"
    };

    static char keepname[3][128];
    char filename[128], oldfname[128];

    stype--;

    if (keepname[stype][0])
        strcpy(filename, keepname[stype]);
    else {
        switch (stype) {
        case 2:  // Marked
        case 1:  // All
            sprintf(filename, ntemplate[stype], mm.areaList->getName());
            break;
        case 0:  // This one
            sprintf(filename, ntemplate[0], mm.areaList->getName(),
                    mm.letterList->getCurrent());
        }

        unspace(filename);
    }

    strcpy(oldfname, filename);

    if (ui->savePrompt("Save to file:", filename)) {
        mychdir(mm.resourceObject->get(SaveDir));
        fd = fopen(homify(filename), "at");
        if (fd) {
            int num = mm.letterList->noOfActive();

            switch (stype) {
            case 2:
            case 1:
                for (int i = 0; i < num; i++) {
                    mm.letterList->gotoActive(i);
                    if ((stype == 1) ||
                        (mm.letterList->getStatus() & MS_MARKED))

                        write_to_file(fd);
                }
                break;
            case 0:
                write_to_file(fd);
            }
            fclose(fd);
            if (!stype)
                MakeChain(COLS);

            ui->setAnyRead();
        } else {
            char tmp[142];

            sprintf(tmp, "%s: Save failed", filename);
            ui->nonFatalError(tmp);

            return false;
        }
        if (strcmp(filename, oldfname))
            strcpy(keepname[stype], filename);

        return true;
    } else {
        ui->nonFatalError("Save aborted");

        return false;
    }
}

void LetterWindow::write_header_to_file(FILE *fd)
{
    enum {system, area, newsg, date, from, to, addr, subj, items};
    static const char *names[items] = {"System", "  Area", "Newsgr",
        "  Date", "  From", "    To", "  Addr", "  Subj"};
    char Header[512], *p;
    int j;

    for (j = 0; j < 72; j++)
        fputc('=', fd);
    fputc('\n', fd);

    mm.areaList->gotoArea(mm.letterList->getAreaID());

    const char *head[items] = {
        mm.packet->getBBSName(), mm.areaList->getDescription(),
        mm.letterList->getNewsgrps(), mm.letterList->getDate(),
        mm.letterList->getFrom(), mm.letterList->getTo(),
        mm.letterList->getNetAddr(), mm.letterList->getSubject()
    };

    ui->areas.Select();

    head[to + mm.areaList->hasTo()] = 0;
    if (head[newsg])
        head[area] = 0;

    for (j = 0; j < items; j++)
        if (head[j]) {
            p = Header + sprintf(Header, "%.511s", head[j]);
            if (((j == from) && mm.areaList->isEmail())
                || ((j == to) && mm.areaList->isReplyArea()))

                netAdd(p);
            letterconv_in(Header);
            fprintf(fd, " %s: %s\n", names[j], Header);
        }

    for (j = 0; j < 72; j++)
        fputc('-', fd);
    fputc('\n', fd);
}

void LetterWindow::write_to_file(FILE *fd)
{
    write_header_to_file(fd);

    // write chain to file

    MakeChain(80);
    for (int i = 0; i < NumOfLines; i++)
        linelist[i]->out(fd);
    fputc('\n', fd);

    // set saved, unmarked -- not part of writing to a file, but anyway

    int stat = mm.letterList->getStatus();
    int oldstat = stat;
    stat |= MS_SAVED;
    stat &= ~MS_MARKED;
    mm.letterList->setStatus(stat);
    if (stat != oldstat)
        ui->setAnyRead();
}

// For searches (may want to start at position == -1):
void LetterWindow::setPos(int x)
{
    position = x;
}

int LetterWindow::getPos()
{
    return position;
}

searchret LetterWindow::search(const char *item)
{
    searchret found = False;

    for (int x = position + 1; (x < NumOfLines) && (found == False); x++) {

        if (text->keypressed() == 27) {
            found = Abort;
            break;
        }

        found = searchstr(linelist[x]->text, item,
                          linelist[x]->length) ? True : False;

        if (found == True) {
            position = x;
            if (ui->active() == letter)
                DrawBody();
        }
    }

    return found;
}

void LetterWindow::KeyHandle(int key)
{
    int t_area;

    switch (key) {
    case ERR:                           // no key pressed
        if (ui->active() == letter)
            TimeUpdate();
        break;
#ifdef USE_MOUSE
    case MM_MOUSE:
        if (0 == mm_mouse_event.y)
            Move(KEY_UP);
        else
            if ((LINES - 1) == mm_mouse_event.y)
                Move(KEY_DOWN);
            else
                if (mm_mouse_event.y > 5) {
                    if (mm_mouse_event.y > (LINES >> 1))
                        NextDown();
                    else
                        Move(KEY_PPAGE);
                } else
                    if (3 == mm_mouse_event.y) {
                        if (mm_mouse_event.x >= (COLS - 8))
                            KeyHandle('S');
                        else
                            if (mm_mouse_event.x >= (COLS - 13))
                                KeyHandle('M');
                            else
                                if (mm_mouse_event.x >= (COLS - 18))
                                    KeyHandle('R');
                                else
                                    if (mm_mouse_event.x >= (COLS - 23))
                                        KeyHandle('U');
                                    else
                                        if (mm_mouse_event.x >= (COLS - 27))
                                            KeyHandle('N');
                    }
        break;
#endif
    case 'D':
        rot13 = !rot13;
        MakeChainFixPos();
        DrawBody();
        break;
    case 'X':
        hidden = !hidden;
        MakeChainFixPos();
        DrawBody();
        break;
    case 'I':
        {
            bool stripCR = mm.resourceObject->getInt(StripSoftCR);
            mm.resourceObject->set(StripSoftCR, !stripCR);
        }
        MakeChainFixPos();
        DrawBody();
        break;
    case 'S':
        Save(1);
        DrawFlags();
        ReDraw();
        break;
    case '?':
    case MM_F1:
        ui->changestate(letter_help);
        break;
    case 'V':
    case 1:                             // Ctrl-A
    case 11:                            // Ctrl-V
        {
            int nextAns;
            bool cont = false;
            do {
                Delete();
                nextAns = ui->ansiLoop(mm.letterList->getBody(),
                                       mm.letterList->getSubject(),
                                       mm.letterList->isLatin());
                if (nextAns == 1)
                    cont = Next();
                else if (nextAns == -1)
                    cont = Previous();
            } while (nextAns && cont);
        }
        break;
    case MM_RIGHT:
        if (lynxNav)
            break;
    case MM_PLUS:
        Next();
        break;
    case MM_LEFT:
        if (lynxNav) {
            ui->back();
            break;
        }
    case MM_MINUS:
        Previous();
        break;
    case ' ':
        NextDown();
        break;
    case 6:                             // Ctrl-F
        EditLetter(true);
        ui->redraw();
        break;
    default:
        if (mm.areaList->isReplyArea()) {
            switch(key) {
            case 'R':
            case 'E':
                EditLetter(false);
                ui->redraw();
                break;
            case MM_DEL:
            case 'K':
                ui->kill_letter();
                break;
            case 2:                     // Ctrl-B
                SplitLetter();
                ui->redraw();
                break;
            default:
                Move(key);
            }
        } else {
            switch (key) {
            case '\t':
                ui->letters.Next();
                Draw(true);
                break;
            case 'M':
            case 'U':
                StatToggle((key == 'M') ? MS_MARKED : MS_READ);
                break;
            case 'R':                   // Allow re-editing from here:
            case 'O':
                if (mm.letterList->getStatus() & MS_REPLIED)
                    if (EditOriginal()) {
                        ui->redraw();
                        break;
                    }
            case 5:
            case 'E':
                t_area = ui->areaMenu();
                if (t_area != -1) {
                    mm.areaList->gotoArea(t_area);

                    if ((5 == key) || mm.areaList->isEmail()) {
                        if ((5 == key) || ('E' == key))
                            ui->addressbook();
                        else {
                            net_address nm = PickNetAddr();
                            set_Letter_Params(nm, 0);
                        }
                    }
                    EnterLetter(t_area, (5 == key) ? 'E' : key);
                }
                break;
            case 'N':
                t_area = (mm.areaList->getType() & INTERNET) ?
                         mm.areaList->findInternet() :
                         mm.areaList->findNetmail();
                if (t_area != -1) {
                    net_address nm = PickNetAddr();
                    if (nm.isSet) {
                        set_Letter_Params(nm, 0);
                        EnterLetter(t_area, 'N');
                    } else
                        ui->nonFatalError("No reply address");
                } else
                    ui->nonFatalError("Netmail is not available");
                break;
            case 'T':
                GetTagline();
                break;
            default:
                Move(key);
            }
        }
    }
}
