
/*
 * See the files COPYING and README for copyright information and how to reach 
 * the author.
 *
 *  $Id: menuitems.c,v 1.4 2004/12/05 18:50:34 lordjaxom Exp $
 */

#include "common.h"
#include "menuitems.h"
#include "menuburn.h"
#include "jobs.h"
#include "i18n.h"
#include <vdr/remote.h>
#include <vdr/status.h>
#include <ctype.h>

#define SHIFTTIMEOUT 2

// --- cBurnMenuItem ----------------------------------------------------------

cBurnMenuItem::cBurnMenuItem(eBurnItemType Type, bool Selectable)
{
    mType = Type;
    mSelectable = Selectable;
#if VDRVERSNUM < 10307
    if (!Selectable) {
        SetColor(clrWhite, clrBackground);
    }
#else
    SetSelectable(Selectable);
#endif
}

// --- cBurnMenuTextItem ------------------------------------------------------

cBurnMenuTextItem::cBurnMenuTextItem(const char *Text, bool Selectable):
    cBurnMenuItem(bitText, Selectable)
{
    SetText(Text);
}

// --- cBurnMenuJobItem -------------------------------------------------------

cBurnMenuJobItem::cBurnMenuJobItem(cBurnJob * Job, int Number, bool Done):
    cBurnMenuItem(bitJob, true)
{
    char *text;
    mJob = Job;
    if (!Done) {
        if (mJob->HasArchiveMark()) {
            asprintf(&text, "%d. %s (Archive: %04d)", Number, mJob->Title(),
                    mJob->GetArchiveMark());
        } else {
            asprintf(&text, "%d. %s", Number, mJob->Title());
        }
    } else {
        asprintf(&text, "%d. %s (%s)", Number, mJob->Title(),
                mJob->Status() == rsNone ? tr("Cancelled") : tr("Error"));
    }
    SetText(text, false);
}

// --- cBurnMenuEditItem ------------------------------------------------------

cBurnMenuEditItem::cBurnMenuEditItem(eBurnItemType Type, const char *Name):
    cBurnMenuItem(Type, true)
{
    mName = strdup(Name);
    mValue = NULL;
    mReadOnly = false;
}

cBurnMenuEditItem::~cBurnMenuEditItem()
{
    free(mName);
    free(mValue);
}

void cBurnMenuEditItem::SetValue(const char *Value)
{
    char *buffer;
    free(mValue);
    mValue = strdup(Value);
    asprintf(&buffer, "%s\t%s", mName, mValue);
    SetText(buffer, false);
    FRESHEN();
}

void cBurnMenuEditItem::SetReadOnly(bool ReadOnly)
{
    mReadOnly = ReadOnly;
}

// --- cBurnMenuEditIntItem ---------------------------------------------------

cBurnMenuEditIntItem::cBurnMenuEditIntItem(const char *Name, int *Value,
        int Min, int Max):
    cBurnMenuEditItem(bitEditInt, Name)
{
    mValue = Value;
    mMin = Min;
    mMax = Max;
    Set();
}

cBurnMenuEditIntItem::cBurnMenuEditIntItem(eBurnItemType Type,
        const char *Name, int *Value, int Min, int Max):
    cBurnMenuEditItem(Type, Name)
{
    mValue = Value;
    mMin = Min;
    mMax = Max;
    Set();
}

void cBurnMenuEditIntItem::Set(void)
{
    char buf[16];
    snprintf(buf, sizeof(buf), "%d", *mValue);
    SetValue(buf);
}

eOSState cBurnMenuEditIntItem::ProcessKey(eKeys Key)
{
    eOSState state = cBurnMenuEditItem::ProcessKey(Key);
    if (mReadOnly) {
        return osUnknown;
    }
    if (state == osUnknown) {
        int newValue = *mValue;
        Key = NORMALKEY(Key);
        switch (Key) {
            case kNone:
                break;

            case k0...k9:
                if (fresh) {
                    *mValue = 0;
                    fresh = false;
                }
                newValue = *mValue * 10 + (Key - k0);
                break;

            case kLeft:        // TODO might want to increase the delta if repeated quickly?
                newValue = *mValue - 1;
                fresh = true;
                break;

            case kRight:
                newValue = *mValue + 1;
                fresh = true;
                break;

            default:
                if (*mValue < mMin) {
                    *mValue = mMin;
                    Set();
                }
                if (*mValue > mMax) {
                    *mValue = mMax;
                    Set();
                }
                return state;
        }
        if ((!fresh || mMin <= newValue) && newValue <= mMax) {
            *mValue = newValue;
            Set();
        }
        state = osContinue;
    }
    return state;
}

// --- cBurnMenuEditStrItem ---------------------------------------------------

cBurnMenuEditStrItem::cBurnMenuEditStrItem(const char *Name,
        char *Value, int Length, const char *Allowed):
    cBurnMenuEditItem(bitEditStr, Name)
{
    length = Length;
    allowed = strdup(Allowed);
    pos = -1;
    insert = false;
    uppercase = false;
    newchar = true;
    ieCurChr = 0;
    lastKey = kNone;
    lastActivity = time(NULL);
    value = Value;
    Set();
}

cBurnMenuEditStrItem::cBurnMenuEditStrItem(eBurnItemType Type,
        const char *Name, char *Value, int Length, const char *Allowed):
    cBurnMenuEditItem(Type, Name)
{
    length = Length;
    allowed = strdup(Allowed);
    pos = -1;
    insert = false;
    uppercase = false;
    newchar = true;
    ieCurChr = 0;
    lastKey = kNone;
    lastActivity = time(NULL);
    value = Value;
    Set();
}

cBurnMenuEditStrItem::~cBurnMenuEditStrItem()
{
    free(allowed);
}

void cBurnMenuEditStrItem::SetHelpKeys(void)
{
    if (pos >= 0) {
        SETHELP(tr("ABC/abc"), tr(insert ? "Overwrite" : "Insert"),
                tr("Delete"));
    } else {
        SETHELP(NULL);
    }
}

void cBurnMenuEditStrItem::Set(void)
{
    char buf[1000];
    const char *fmt = insert && newchar ? "[]%c%s" : "[%c]%s";

    if (pos >= 0) {
        strncpy(buf, value, pos);
        snprintf(buf + pos, sizeof(buf) - pos - 2, fmt, *(value + pos),
                value + pos + 1);
#if VDRVERSNUM < 10307
        int width = Interface->Width() - Interface->GetCols()[0];
        if (cOsd::WidthInCells(buf) <= width) {
#else
        const cFont *font = cFont::GetFont(fontOsd);
        int width = cSkinDisplay::Current()->EditableWidth();
        if (font->Width(buf) <= width) {
#endif
            // the whole buffer fits on the screen
            SetValue(buf);
            return;
        }
#if VDRVERSNUM < 10307
        width *= cOsd::CellWidth();
        width -= cOsd::Width('>');    // assuming '<' and '>' have the same with
#else
        width -= font->Width('>');    // assuming '<' and '>' have the same with
#endif
        int w = 0;
        int i = 0;
        int l = strlen(buf);
        while (i < l && w <= width) {
#if VDRVERSNUM < 10307
            w += cOsd::Width(buf[i++]);
#else
            w += font->Width(buf[i++]);
#endif
        }
        if (i >= pos + 4) {
            // the cursor fits on the screen
            buf[i - 1] = '>';
            buf[i] = 0;
            SetValue(buf);
            return;
        }
        // the cursor doesn't fit on the screen
        w = 0;
        if (buf[i = pos + 3]) {
            buf[i] = '>';
            buf[i + 1] = 0;
        } else {
            i--;
        }
        while (i >= 0 && w <= width) {
#if VDRVERSNUM < 10307
            w += cOsd::Width(buf[i--]);
#else
            w += font->Width(buf[i--]);
#endif
        }
        buf[++i] = '<';
        SetValue(buf + i);
    } else {
        SetValue(value);
    }
}

char cBurnMenuEditStrItem::Inc(char c, bool Up)
{
    const char *p = strchr(allowed, c);
    if (!p) {
        p = allowed;
    }
    if (Up) {
        if (!*++p) {
            p = allowed;
        }
    } else if (--p < allowed) {
        p = allowed + strlen(allowed) - 1;
    }
    return *p;
}

eOSState cBurnMenuEditStrItem::ProcessKey(eKeys Key)
{
    const char c1[] = "-.#~,/_@1";
    const char c2[] = "abcäåá2";
    const char c3[] = "defé3";
    const char c4[] = "ghi4";
    const char c5[] = "jkl5";
    const char c6[] = "mnoöñó6";
    const char c7[] = "pqrs7";
    const char c8[] = "tuvüú8";
    const char c9[] = "wxyz9";
    const char c0[] = " 0";

    if (mReadOnly) {
        return osUnknown;
    }
    switch (Key) {
        case kRed:             // Switch between upper- and lowercase characters
            if (pos >= 0 && (!insert || !newchar)) {
                uppercase = !uppercase;
                value[pos] =
                        uppercase ? toupper(value[pos]) : tolower(value[pos]);
            }
            lastKey = Key;
            break;

        case kGreen:           // Toggle insert/overwrite modes
            if (pos >= 0) {
                insert = !insert;
                newchar = true;
            }
            SetHelpKeys();
            lastKey = Key;
            break;

        case kYellow | k_Repeat:
        case kYellow:          // Remove the character at current position; in insert mode it is the character to the right of cursor
            if (pos >= 0) {
                if (strlen(value) > 1) {
                    if (!insert || pos < int (strlen(value)) - 1) {
                        memmove(value + pos, value + pos + 1,
                                strlen(value) - pos);
                    }
                    // reduce position, if we removed the last character
                    if (pos == int (strlen(value))) {
                        pos--;
                    }
                } else if (strlen(value) == 1) {
                    value[0] = ' ';     // This is the last character in the string, replace it with a blank
                }
                if (isalpha(value[pos])) {
                    uppercase = isupper(value[pos]);
                }
                newchar = true;
            }
            lastKey = Key;
            break;

        case kLeft | k_Repeat:
        case kLeft:
            if (pos > 0) {
                if (!insert || newchar) {
                    pos--;
                }
                newchar = true;
            }
            if (!insert && isalpha(value[pos])) {
                uppercase = isupper(value[pos]);
            }
            lastKey = Key;
            break;

        case kRight | k_Repeat:
        case kRight:
            if (pos < length - 2 && pos < int (strlen(value))) {
                if (++pos >= int (strlen(value))) {
                    if (pos >= 2 && value[pos - 1] == ' ' &&
                            value[pos - 2] == ' ')
                    {
                        pos--;  // allow only two blanks at the end
                    } else {
                        value[pos] = ' ';
                        value[pos + 1] = 0;
                    }
                }
            }
            newchar = true;
            if (!insert && isalpha(value[pos])) {
                uppercase = isupper(value[pos]);
            }
            if (pos == 0) {
                SetHelpKeys();
            }
            lastKey = Key;
            break;

        case kUp | k_Repeat:
        case kUp:
        case kDown | k_Repeat:
        case kDown:
            if (pos >= 0) {
                if (insert && newchar) {
                    // create a new character in insert mode
                    if (int (strlen(value)) < length - 1) {
                        memmove(value + pos + 1, value + pos,
                                strlen(value) - pos + 1);
                        value[pos] = ' ';
                    }
                }
                if (uppercase) {
                    value[pos] = toupper(Inc(tolower(value[pos]),
                            NORMALKEY(Key) == kUp));
                } else {
                    value[pos] = Inc(value[pos], NORMALKEY(Key) == kUp);
                }
                newchar = false;
            } else {
                return cBurnMenuEditItem::ProcessKey(Key);
            }
            lastKey = Key;
            break;

        case k0 | k_Repeat...k9 | k_Repeat:
        case k0...k9:
            if ((Key != lastKey) || ((Key == lastKey) &&
                    (time(NULL) - lastActivity > SHIFTTIMEOUT)))
            {
                ieCurChr = 0;
                if (!newchar) {
                    // kRight
                    if (pos < length - 2 && pos < int (strlen(value))) {
                        if (++pos >= int (strlen(value))) {
                            if (pos >= 2 && value[pos - 1] == ' ' &&
                                    value[pos - 2] == ' ')
                            {
                                pos--;  // allow only two blanks at the end
                            } else {
                                value[pos] = ' ';
                                value[pos + 1] = 0;
                            }
                        }
                    }
                    newchar = true;
                    if (!insert && isalpha(value[pos])) {
                        uppercase = isupper(value[pos]);
                    }
                }
            }
            // kUp/kDown
            if (pos >= 0) {
                if (insert && newchar) {
                    // create a new character in insert mode
                    if (int (strlen(value)) < length - 1) {
                        memmove(value + pos + 1, value + pos,
                                strlen(value) - pos + 1);
                        value[pos] = ' ';
                    }
                }
            } else {
                return cBurnMenuEditItem::ProcessKey(Key);
            }
            switch (Key) {
                case k1:
                    if (uppercase) {
                        value[pos] = toupper(c1[ieCurChr]);
                    } else {
                        value[pos] = c1[ieCurChr];
                    }
                    if (c1[ieCurChr + 1] == 0) {
                        ieCurChr = 0;
                    } else {
                        ieCurChr++;
                    }
                    break;

                case k2:
                    if (uppercase) {
                        value[pos] = toupper(c2[ieCurChr]);
                    } else {
                        value[pos] = c2[ieCurChr];
                    }
                    if (c2[ieCurChr + 1] == 0) {
                        ieCurChr = 0;
                    } else {
                        ieCurChr++;
                    }
                    break;

                case k3:
                    if (uppercase) {
                        value[pos] = toupper(c3[ieCurChr]);
                    } else {
                        value[pos] = c3[ieCurChr];
                    }
                    if (c3[ieCurChr + 1] == 0) {
                        ieCurChr = 0;
                    } else {
                        ieCurChr++;
                    }
                    break;

                case k4:
                    if (uppercase) {
                        value[pos] = toupper(c4[ieCurChr]);
                    } else {
                        value[pos] = c4[ieCurChr];
                    }
                    if (c4[ieCurChr + 1] == 0) {
                        ieCurChr = 0;
                    } else {
                        ieCurChr++;
                    }
                    break;

                case k5:
                    if (uppercase) {
                        value[pos] = toupper(c5[ieCurChr]);
                    } else {
                        value[pos] = c5[ieCurChr];
                    }
                    if (c5[ieCurChr + 1] == 0) {
                        ieCurChr = 0;
                    } else {
                        ieCurChr++;
                    }
                    break;

                case k6:
                    if (uppercase) {
                        value[pos] = toupper(c6[ieCurChr]);
                    } else {
                        value[pos] = c6[ieCurChr];
                    }
                    if (c6[ieCurChr + 1] == 0) {
                        ieCurChr = 0;
                    } else {
                        ieCurChr++;
                    }
                    break;

                case k7:
                    if (uppercase) {
                        value[pos] = toupper(c7[ieCurChr]);
                    } else {
                        value[pos] = c7[ieCurChr];
                    }
                    if (c7[ieCurChr + 1] == 0) {
                        ieCurChr = 0;
                    } else {
                        ieCurChr++;
                    }
                    break;

                case k8:
                    if (uppercase) {
                        value[pos] = toupper(c8[ieCurChr]);
                    } else {
                        value[pos] = c8[ieCurChr];
                    }
                    if (c8[ieCurChr + 1] == 0) {
                        ieCurChr = 0;
                    } else {
                        ieCurChr++;
                    }
                    break;

                case k9:
                    if (uppercase) {
                        value[pos] = toupper(c9[ieCurChr]);
                    } else {
                        value[pos] = c9[ieCurChr];
                    }
                    if (c9[ieCurChr + 1] == 0) {
                        ieCurChr = 0;
                    } else {
                        ieCurChr++;
                    }
                    break;
                case k0:
                    if (uppercase) {
                        value[pos] = toupper(c0[ieCurChr]);
                    } else {
                        value[pos] = c0[ieCurChr];
                    }
                    if (c0[ieCurChr + 1] == 0) {
                        ieCurChr = 0;
                    } else {
                        ieCurChr++;
                    }
                    break;

                default:
                    break;
            }
            lastKey = Key;
            newchar = false;
            lastActivity = time(NULL);
            break;

        case kOk:
            if (pos >= 0) {
                pos = -1;
                lastKey = Key;
                newchar = true;
                stripspace(value);
                SetHelpKeys();
                break;
            }
            // run into default
        default:
            if (pos >= 0 && BASICKEY(Key) == kKbd) {
                int c = KEYKBD(Key);
                if (c <= 0xFF) {
                    const char *p = strchr(allowed, tolower(c));
                    if (p) {
                        int l = strlen(value);
                        if (insert && l < length - 1) {
                            memmove(value + pos + 1, value + pos,
                                    l - pos + 1);
                        }
                        value[pos] = c;
                        if (pos < length - 2) {
                            pos++;
                        }
                        if (pos >= l) {
                            value[pos] = ' ';
                            value[pos + 1] = 0;
                        }
                    } else {
                        switch (c) {
                            case 0x7F: // backspace
                                if (pos > 0) {
                                    pos--;
                                    return ProcessKey(kYellow);
                                }
                                break;
                        }
                    }
                } else {
                    switch (c) {
                        case kfHome:
                            pos = 0;
                            break;

                        case kfEnd:
                            pos = strlen(value) - 1;
                            break;

                        case kfIns:
                            return ProcessKey(kGreen);

                        case kfDel:
                            return ProcessKey(kYellow);
                    }
                }
            } else {
                return cBurnMenuEditItem::ProcessKey(Key);
            }
    }
    Set();
    return osContinue;
}

// --- cBurnMenuRecordingItem -------------------------------------------------

cBurnMenuEditRecordingItem::cBurnMenuEditRecordingItem(cBurnRecording *Recording):
    cBurnMenuEditStrItem(bitEditRecording, Recording->Title(),
            Recording->mName, JOBNAMELEN, TitleChars)
{
    mFileName = strdup(Recording->FileName());
}

cBurnMenuEditRecordingItem::~cBurnMenuEditRecordingItem()
{
    free(mFileName);
}

// --- cBurnMenuEditStraItem --------------------------------------------------

cBurnMenuEditStraItem::cBurnMenuEditStraItem(const char *Name, int *Value,
        int NumStrings, const char **Strings):
    cBurnMenuEditIntItem(bitEditStra, Name, Value, 0, NumStrings - 1)
{
    mStrings = Strings;
    Set();
}

void cBurnMenuEditStraItem::Set(void)
{
    SetValue(tr(mStrings[*mValue]));
}
