// Copyright (c) Warwick Allison, 1999.
// Qt4 conversion copyright (c) Ray Chason, 2012-2014.
// NetHack may be freely redistributed.  See license for details.

// qt_stat.cpp -- status window, upper right portion of the overall window
//
// The Qt status window consists of many lines:
//
//      hitpoint bar (when enabled)
//      Title (plname the Rank or plname the MonsterSpecies)
//      Dungeon location (branch and level)
//      separator line
//      six icons (special 40x40 tiles, paired with...)
//      six characteristic texts ("Str:18/03", "Dex:15", &c)
//      separator line
//      five status fields without icons (some containing two values:
//        HP/HPmax, Energy/Enmax, AC, XpLevel/ExpPoints or HD, [blank], Gold)
//      separator line
//      line with two optional text fields (Time:1234, Score:89), maybe blank
//      varying number of icons (one or more, each paired with...)
//      corresponding text (Alignment plus zero or more status conditions
//        including Hunger if not "normal" and encumbrance if not "normal")
//      and version description (text only) right justified (when enabled)
//
// The hitpoint bar spans the width of the status window when enabled.
// Title and location are centered.
// The icons and text for the six characteristics are evenly spaced;
//   this pair of lines is sometimes referred to as "row 1" below.
// The five main stats or slash-separated stat pairs are padded with an
//   empty slot between Xp and Gold; adding the sixth makes that row
//   line up with the characteristics; this line is sometimes referred
//   to as "row 2".
// Time and Score are spaced as if each were three fields wide; their
//   line is "row 3" relative to statuslines:2 vs statuslines:3.
// Icons and texts for alignment and conditions are left justified.
// The separator lines are thin and don't take up much vertical space.
// When enabled, the hitpoint bar bisects the margin above Title,
//   increasing the overall status height by 9 pixels; when disabled,
//   the status shifts up by those 9 pixels.
// When row 3 (Time, Score) is blank, it still takes up the vertical
//   space that would be used to show those values.
//
// The above is for statuslines:3, which used to be the default.  For
//   statuslines:2, rows 1 and 2 are extended from six to seven fields
//   and row 3 (optional Time, Score) is eliminated.  Alignment is
//   moved from the beginning of the Conditions pair (icon over text)
//   of lines up to the end of row 1, the Characteristics pair of lines,
//   with a separator between Cha:NN and it.  Time, when active, is
//   placed after Gold.  Score, if enabled and active, is shown in the
//   filler slot before Gold.  When there are no Conditions to display,
//   there is an invisible fake one (blank icon over blank text)
//   rendered in order to preserve the vertical space they need.
//
// FIXME:
//  When hitpoint bar is shown, attempting to resize horizontally won't
//    do anything.  Toggling it off, then resizing, and back On works.
//    (Caused by specifying min-width and max-width constraints in the
//    style sheets used to control color, but removing those constraints
//    causes the bar display to get screwed up.)
//  There are separate icons for Satiated and Hungry, but Weak, Fainting,
//    and Fainted all share the Hungry one.  Weak should have its own,
//    Fainting+Fainted should have another.  The current two depict
//    plates with cutlery which is a bit of an anachronism.  Satiated
//    could be replaced by a figure in profile with a bulging belly,
//    Hungry similar but with a slightly concave belly, Weak either a
//    collapsing figure or a much larger concavity or both, Fainting/
//    Fainted a fully collapsed figure.
//  If 'version' is being shown but gets squeezed by the cumulative width
//    of conditions, the default clipping shows the center portion of the
//    text string with beginning and end omitted.  We want to force the
//    end of the string to be shown with only the beginning omitted.
//
// TODO:
//  If/when status conditions become too wide for the status window, scale
//    down their icons and switch their text to a smaller font to match.
//  Title and Location are explicitly rendered with a bigger font than
//    the rest of status.  That takes up more space, which is ok, but it
//    also increases the vertical margin in between them by more than is
//    necessary.  Should squeeze some of that excess blank space out.
//

extern "C" {
#include "hack.h"

extern const char *const enc_stat[]; /* from botl.c */
extern const char *const hu_stat[]; /* from eat.c */
}

#include "qt_pre.h"
#include <QtGui/QtGui>
#if QT_VERSION >= 0x050000
#include <QtWidgets/QtWidgets>
#endif
#include "qt_post.h"
#include "qt_stat.h"
#include "qt_stat.moc"
#include "qt_set.h"
#include "qt_str.h"
#include "qt_xpms.h"

extern int qt_compact_mode;

namespace nethack_qt_ {

NetHackQtStatusWindow::NetHackQtStatusWindow() :
    /* first three rows:  hitpoint bar, title (plname the Rank), location */
    hpbar_health(this),
    hpbar_injury(this),
    name(this,"(name)"),
    dlevel(this,"(dlevel)"),
    /* next two rows:  icon over text label for the six characteristics */
    str(this, "Str"),
    dex(this, "Dex"),
    con(this, "Con"),
    intel(this, "Int"),
    wis(this, "Wis"),
    cha(this, "Cha"),
    /* sixth row, text only:  some contain two slash-separated values */
    hp(this,"Hit Points"),
    power(this,"Power"),
    ac(this,"Armor Class"),
    level(this,"Level"), // Xp level, with "/"+Exp points optionally appended
    blank1(this, ""),    // used for padding to align columns (was once 'exp')
    gold(this,"Gold"),   // gold used to be this row's first column, now last
    /* seventh row:  two optionally displayed values (just text, no icons) */
    time(this,"Time"),   // if 'time' option On
    score(this,"Score"), // if SCORE_ON_BOTL defined and 'showscore' option On
    /* last two rows:  alignment followed by conditions (icons over text) */
    align(this,"Alignment"),
    blank2(this, " "),   // used to prevent Conditions row from being empty
    hunger(this,""),
    encumber(this,""),
    stoned(this,"Stone"),     // major conditions
    slimed(this,"Slime"),
    strngld(this,"Strngl"),
    sick_fp(this,"FoodPois"),
    sick_il(this,"TermIll"),
    stunned(this,"Stun"),     // minor conditions
    confused(this,"Conf"),
    hallu(this,"Hallu"),
    blind(this,"Blind"),
    deaf(this,"Deaf"),
    lev(this,"Lev"),          // 'other' conditions
    fly(this,"Fly"),
    ride(this,"Ride"),
    vers(this,""),            // optional, right justified after 'conditions'
    hline1(this),             // separators
    hline2(this),
    hline3(this),
    vline1(this), // vertical separator between Characteristics and Alignment
    vline2(this), // padding for row 2 to match row 1's separator; not shown
    /* miscellaneous; not display fields */
    cursy(0),
    first_set(true),
    alreadyfullhp(false),
    was_polyd(false),
    had_exp(false),
    had_score(false),
    prev_versinfo(0U)
{
    if (!qt_compact_mode) {
        int w = NetHackQtBind::mainWidget()->width();
        setMaximumWidth(w / 2);
    }
    // for tool tips; they mostly work without this but sometimes changes
    // to hunger or encumbrance seemed to cause tip display to stop
    setMouseTracking(true);

    p_str = QPixmap(str_xpm);
    p_str = QPixmap(str_xpm);
    p_dex = QPixmap(dex_xpm);
    p_con = QPixmap(cns_xpm);
    p_int = QPixmap(int_xpm);
    p_wis = QPixmap(wis_xpm);
    p_cha = QPixmap(cha_xpm);

    p_chaotic = QPixmap(chaotic_xpm);
    p_neutral = QPixmap(neutral_xpm);
    p_lawful = QPixmap(lawful_xpm);
    p_blank2 = QPixmap(blank_xpm);

    p_satiated = QPixmap(satiated_xpm);
    p_hungry = QPixmap(hungry_xpm);

    p_encumber[0] = QPixmap(slt_enc_xpm);
    p_encumber[1] = QPixmap(mod_enc_xpm);
    p_encumber[2] = QPixmap(hvy_enc_xpm);
    p_encumber[3] = QPixmap(ext_enc_xpm);
    p_encumber[4] = QPixmap(ovr_enc_xpm);

    p_stoned = QPixmap(stone_xpm);
    p_slimed = QPixmap(slime_xpm);
    p_strngld = QPixmap(strngl_xpm);
    p_sick_fp = QPixmap(sick_fp_xpm);
    p_sick_il = QPixmap(sick_il_xpm);
    p_stunned = QPixmap(stunned_xpm);
    p_confused = QPixmap(confused_xpm);
    p_hallu = QPixmap(hallu_xpm);
    p_blind = QPixmap(blind_xpm);
    p_deaf = QPixmap(deaf_xpm);
    p_lev = QPixmap(lev_xpm);
    p_fly = QPixmap(fly_xpm);
    p_ride = QPixmap(ride_xpm);

    p_vers = QPixmap(blank_xpm); // same all-background pixmap as blank2

    str.setIcon(p_str, "strength");
    dex.setIcon(p_dex, "dexterity");
    con.setIcon(p_con, "constitution");
    intel.setIcon(p_int, "intelligence");
    wis.setIcon(p_wis, "wisdom");
    cha.setIcon(p_cha, "charisma");

    align.setIcon(p_neutral);
    blank2.setIcon(p_blank2); // used for spacing when Conditions row is empty
    hunger.setIcon(p_hungry);
    encumber.setIcon(p_encumber[0]);

    stoned.setIcon(p_stoned, "turning to stone");
    slimed.setIcon(p_slimed, "turning into slime");
    strngld.setIcon(p_strngld, "being strangled");
    sick_fp.setIcon(p_sick_fp, "severe food poisoning");
    sick_il.setIcon(p_sick_il, "terminal illness");
    stunned.setIcon(p_stunned, "stunned");
    confused.setIcon(p_confused, "confused");
    hallu.setIcon(p_hallu, "hallucinating");
    blind.setIcon(p_blind, "cannot see");
    deaf.setIcon(p_deaf, "cannot hear");
    lev.setIcon(p_lev, "levitating");
    fly.setIcon(p_fly, "flying");
    ride.setIcon(p_ride, "riding");

    vers.setIcon(p_vers); // used to align text-only version with conditions

    // separator lines
#if __cplusplus >= 202002L
    hline1.setFrameStyle(static_cast<int>(QFrame::HLine)
                             | static_cast<int>(QFrame::Sunken));
    hline2.setFrameStyle(static_cast<int>(QFrame::HLine)
                             | static_cast<int>(QFrame::Sunken));
    hline3.setFrameStyle(static_cast<int>(QFrame::HLine)
                             | static_cast<int>(QFrame::Sunken));
#else
    hline1.setFrameStyle(QFrame::HLine | QFrame::Sunken);
    hline2.setFrameStyle(QFrame::HLine | QFrame::Sunken);
    hline3.setFrameStyle(QFrame::HLine | QFrame::Sunken);
#endif
    hline1.setLineWidth(1);
    hline2.setLineWidth(1);
    hline3.setLineWidth(1);
    // vertical separators for condensed layout (statuslines:2)
#if __cplusplus >= 202002L
    vline1.setFrameStyle(static_cast<int>(QFrame::VLine)
                             | static_cast<int>(QFrame::Sunken));
    vline2.setFrameStyle(static_cast<int>(QFrame::VLine)
                             | static_cast<int>(QFrame::Sunken));
#else
    vline1.setFrameStyle(QFrame::VLine | QFrame::Sunken);
    vline2.setFrameStyle(QFrame::VLine | QFrame::Sunken);
#endif
    vline1.setLineWidth(1); // separates Alignment from Charisma
    vline2.setLineWidth(1);
    vline2.hide(); // padding to keep row 2 aligned with row 1, never shown

    // when 'hitpointbar' is On, the bar gets drawn above name/title
    QHBoxLayout *hpbar = InitHitpointBar();

    // 'statuslines' takes a value of 2 or 3; we use 3 as a request to put
    // Alignment in front of status conditions so that line is never empty
    // and to show Time and/or Score on their own line which might be empty
    boolean spreadout = (::iflags.wc2_statuslines != 2);

#if 1 //RLC
    name.setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Minimum);
    dlevel.setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Minimum);
    QVBoxLayout *vbox = new QVBoxLayout();
    vbox->setSpacing(0);
    vbox->addLayout(hpbar); // when 'hitpointbar' is enabled, it comes first
    vbox->addWidget(&name);
    vbox->addWidget(&dlevel);
    vbox->addWidget(&hline1);
    QHBoxLayout *charbox = new QHBoxLayout(); // Characteristics
        charbox->addWidget(&str);
        charbox->addWidget(&dex);
        charbox->addWidget(&con);
        charbox->addWidget(&intel);
        charbox->addWidget(&wis);
        charbox->addWidget(&cha);
        if (!spreadout) {
            // when condensed, include Alignment with Characteristics
            charbox->addWidget(&vline1); // show a short vertical separator
            charbox->addWidget(&align);
        }
    vbox->addLayout(charbox);
    vbox->addWidget(&hline2);
    QHBoxLayout *statbox = new QHBoxLayout(); // core status fields
        statbox->addWidget(&hp);
        statbox->addWidget(&power);
        statbox->addWidget(&ac);
        statbox->addWidget(&level);
        if (spreadout) {
            // when not condensed, put a blank field in front of Gold;
            // Time and Score will be shown on their own separate line
            statbox->addWidget(&blank1); // empty column #5 of 6
            statbox->addWidget(&gold);
        } else {
            // when condensed, display Time and Score on HP,...,Gold row
#ifndef SCORE_ON_BOTL
            statbox->addWidget(&blank1); // empty column #5 of 7
#else
            statbox->addWidget(&score);  // usually empty column #5
#endif
            statbox->addWidget(&gold);   // columns 6 and maybe empty 7
            statbox->addWidget(&vline2); // padding between 6 and 7; not shown
            statbox->addWidget(&time);
        }
    vbox->addLayout(statbox);
    vbox->addWidget(&hline3); // separator before Time+Score or Conditions
    if (spreadout) {
        // when not condensed, put Time and Score on an extra row; since
        // they're both optionally displayed, their row might be empty
        // TODO? when neither will be shown, set their heights smaller
        // and if either gets toggled On, set height back to normal
        QHBoxLayout *timebox = new QHBoxLayout();
            timebox->addWidget(&time);
            timebox->addWidget(&score);
        vbox->addLayout(timebox);
    }
    QHBoxLayout *condbox = new QHBoxLayout(); // Conditions
        if (spreadout) {
            // when not condensed, include Alignment with Conditions to
            // spread things out and also so that their row is never empty
            condbox->addWidget(&align);
        } else {
            // otherwise place a padding widget on this row; it will be
            // hidden if any Conditions are shown, or shown (with blank
            // icon and empty text) when there aren't any, reserving
            // space (the height of the row) for later conditions
            condbox->addWidget(&blank2);
        }
        condbox->addWidget(&hunger);
        condbox->addWidget(&encumber);
        condbox->addWidget(&stoned);
        condbox->addWidget(&slimed);
        condbox->addWidget(&strngld);
        condbox->addWidget(&sick_fp);
        condbox->addWidget(&sick_il);
        condbox->addWidget(&stunned);
        condbox->addWidget(&confused);
        condbox->addWidget(&hallu);
        condbox->addWidget(&blind);
        condbox->addWidget(&deaf);
        condbox->addWidget(&lev);
        condbox->addWidget(&fly);
        condbox->addWidget(&ride);
        condbox->setAlignment(Qt::AlignLeft | Qt::AlignVCenter);
        // left justify conditions, whether 'vers' is present or absent
        int stretch = 0;
        condbox->addStretch(++stretch);       // stretch==1
        // to right justify 'vers', use bigger stretch than condbox separator
        condbox->addWidget(&vers, ++stretch,  // stretch==2
                // text is below a blank icon, so bottom-aligned with the rest
                // of the line with its text centered within that bottom area
                           Qt::AlignRight | Qt::AlignVCenter);
    vbox->addLayout(condbox);
    setLayout(vbox);
#endif

    connect(qt_settings, SIGNAL(fontChanged()), this, SLOT(doUpdate()));
    doUpdate();
}

void NetHackQtStatusWindow::doUpdate()
{
    const QFont& large=qt_settings->largeFont();
    name.setFont(large);
    dlevel.setFont(large);

    const QFont& normal=qt_settings->normalFont();
    str.setFont(normal);
    dex.setFont(normal);
    con.setFont(normal);
    intel.setFont(normal);
    wis.setFont(normal);
    cha.setFont(normal);
    hp.setFont(normal);
    power.setFont(normal);
    ac.setFont(normal);
    level.setFont(normal);
    blank1.setFont(normal); // padding
    gold.setFont(normal);
    time.setFont(normal);
    score.setFont(normal);
    align.setFont(normal);
    // blank2 is used as a dummy condition when Alignment has been moved
    // elsewhere (statuslines:2) and no other conditions currently apply;
    // it has a blank icon with a label of a single space (if the label
    // is completely empty, the rest of status shifts down a little when
    // one or more real conditions replace it and shifts up again when
    // all conditions are removed and this one is reinstated--as if "" is
    // slightly taller than " ")
    blank2.setFont(normal);
    hunger.setFont(normal);
    encumber.setFont(normal);
    stoned.setFont(normal);
    slimed.setFont(normal);
    strngld.setFont(normal);
    sick_fp.setFont(normal);
    sick_il.setFont(normal);
    stunned.setFont(normal);
    confused.setFont(normal);
    hallu.setFont(normal);
    blind.setFont(normal);
    deaf.setFont(normal);
    lev.setFont(normal);
    fly.setFont(normal);
    ride.setFont(normal);
    vers.setFont(normal); // shouldn't need small font

    updateStats();
}

QWidget* NetHackQtStatusWindow::Widget() { return this; }

void NetHackQtStatusWindow::Clear()
{
}
void NetHackQtStatusWindow::Display(bool block UNUSED)
{
}
void NetHackQtStatusWindow::CursorTo(int,int y)
{
    cursy=y;
}
void NetHackQtStatusWindow::PutStr(int attr UNUSED, const QString& text UNUSED)
{
    // do a complete update when line 0 is done (as per X11 fancy status)
    if (cursy==0) updateStats();
}

#if 0 // RLC
void NetHackQtStatusWindow::resizeEvent(QResizeEvent*)
{
#if 0
    const float SP_name=0.13; //     <Name> the <Class> (large)
    const float SP_dlev=0.13; //   Level 3 in The Dungeons of Doom (large)
    const float SP_atr1=0.25; //  STR   DEX   CON   INT   WIS   CHA
    const float SP_hln1=0.02; // ---
    const float SP_atr2=0.09; //  Au    HP    PW    AC    LVL   EXP
    const float SP_hln2=0.02; // ---
    const float SP_time=0.09; //      time    score
    const float SP_hln3=0.02; // ---
    const float SP_stat=0.25; // Alignment, Poisoned, Hungry, Sick, etc.

    int h=height();
    int x=0,y=0;

    int iw; // Width of an item across line
    int lh; // Height of a line of values

    lh=int(h*SP_name);
    name.setGeometry(0,0,width(),lh); y+=lh;
    lh=int(h*SP_dlev);
    dlevel.setGeometry(0,y,width(),lh); y+=lh;

    lh=int(h*SP_hln1);
    hline1.setGeometry(0,y,width(),lh); y+=lh;

    lh=int(h*SP_atr1);
    iw=width()/6;
    str.setGeometry(x,y,iw,lh); x+=iw;
    dex.setGeometry(x,y,iw,lh); x+=iw;
    con.setGeometry(x,y,iw,lh); x+=iw;
    intel.setGeometry(x,y,iw,lh); x+=iw;
    wis.setGeometry(x,y,iw,lh); x+=iw;
    cha.setGeometry(x,y,iw,lh); x+=iw;
    x=0; y+=lh;

    lh=int(h*SP_hln2);
    hline2.setGeometry(0,y,width(),lh); y+=lh;

    lh=int(h*SP_atr2);
    iw=width()/6;
    gold.setGeometry(x,y,iw,lh); x+=iw;
    hp.setGeometry(x,y,iw,lh); x+=iw;
    power.setGeometry(x,y,iw,lh); x+=iw;
    ac.setGeometry(x,y,iw,lh); x+=iw;
    level.setGeometry(x,y,iw,lh); x+=iw;
    //exp.setGeometry(x,y,iw,lh); x+=iw;
    x=0; y+=lh;

    lh=int(h*SP_hln3);
    hline3.setGeometry(0,y,width(),lh); y+=lh;

    lh=int(h*SP_time);
    iw=width()/3; x+=iw/2;
    time.setGeometry(x,y,iw,lh); x+=iw;
    score.setGeometry(x,y,iw,lh); x+=iw;
    x=0; y+=lh;

    lh=int(h*SP_stat);
    iw=width()/9;
    align.setGeometry(x,y,iw,lh); x+=iw;
    hunger.setGeometry(x,y,iw,lh); x+=iw;
    encumber.setGeometry(x,y,iw,lh); x+=iw;
    stoned.setGeometry(x,y,iw,lh); x+=iw;
    slimed.setGeometry(x,y,iw,lh); x+=iw;
    strngld.setGeometry(x,y,iw,lh); x+=iw;
    sick_fp.setGeometry(x,y,iw,lh); x+=iw;
    sick_il.setGeometry(x,y,iw,lh); x+=iw;
    stunned.setGeometry(x,y,iw,lh); x+=iw;
    confused.setGeometry(x,y,iw,lh); x+=iw;
    hallu.setGeometry(x,y,iw,lh); x+=iw;
    blind.setGeometry(x,y,iw,lh); x+=iw;
    deaf.setGeometry(x,y,iw,lh); x+=iw;
    lev.setGeometry(x,y,iw,lh); x+=iw;
    fly.setGeometry(x,y,iw,lh); x+=iw;
    ride.setGeometry(x,y,iw,lh); x+=iw;
#if 0
    // [not fully implemented; this big chunk of code is no longer used]
    //X = [get version, set font, measure width of version string]
    vers.setGeometry(width() - X, y, width(), lh); x=width();
#endif
    x=0; y+=lh;
#else
    // This is clumsy.  But QLayout objects are proving balky.

    int row[10];

    row[0] = name.sizeHint().height();
    row[1] = dlevel.sizeHint().height();
    row[2] = h.sizeHint().height();
#endif
}
#endif


/*
 * Set all widget values to a null string.  This is used after all spacings
 * have been calculated so that when the window is popped up we don't get all
 * kinds of funny values being displayed.  [Actually it isn't used at all.]
 */
void NetHackQtStatusWindow::nullOut()
{
}

void NetHackQtStatusWindow::fadeHighlighting()
{
    name.dissipateHighlight();
    dlevel.dissipateHighlight();

    str.dissipateHighlight();
    dex.dissipateHighlight();
    con.dissipateHighlight();
    intel.dissipateHighlight();
    wis.dissipateHighlight();
    cha.dissipateHighlight();

    gold.dissipateHighlight();
    hp.dissipateHighlight();
    power.dissipateHighlight();
    ac.dissipateHighlight();
    level.dissipateHighlight();
    align.dissipateHighlight();

    //time.dissipateHighlight();
    score.dissipateHighlight();
    //vers.dissipateHighlight(); // never gets highlighted

    hunger.dissipateHighlight();
    encumber.dissipateHighlight();
    stoned.dissipateHighlight();
    slimed.dissipateHighlight();
    strngld.dissipateHighlight();
    sick_fp.dissipateHighlight();
    sick_il.dissipateHighlight();
    stunned.dissipateHighlight();
    confused.dissipateHighlight();
    hallu.dissipateHighlight();
    blind.dissipateHighlight();
    deaf.dissipateHighlight();
    lev.dissipateHighlight();
    fly.dissipateHighlight();
    ride.dissipateHighlight();
}

// hitpointbar: two panels: left==current health, right==missing max health
QHBoxLayout *NetHackQtStatusWindow::InitHitpointBar()
{
    hpbar_health.setFrameStyle(QFrame::NoFrame);
    hpbar_health.setMaximumHeight(9);
    hpbar_health.setAutoFillBackground(true);
    if (!iflags.wc2_hitpointbar)
        hpbar_health.hide();

    hpbar_injury.setFrameStyle(QFrame::NoFrame);
    /* health portion has thickness 9, injury portion just 3 */
    hpbar_injury.setMaximumHeight(3);
    hpbar_injury.setContentsMargins(0, 3, 0, 3); // left,top,right,bottom
    hpbar_injury.setAutoFillBackground(true);
    hpbar_injury.hide(); // only shown when hitpointbar is On and uhp < uhpmax

    QHBoxLayout *hpbar = new QHBoxLayout;
    hpbar->setSpacing(0);
#if QT_VERSION < 0x060000
    hpbar->setMargin(0);
#endif
    hpbar->addWidget(&hpbar_health);
    hpbar->setAlignment(&hpbar_health, Qt::AlignLeft);
    hpbar->addWidget(&hpbar_injury);
    hpbar->setAlignment(&hpbar_injury, Qt::AlignRight);
    return hpbar; // caller will add our result to vbox layout
}

DISABLE_WARNING_FORMAT_NONLITERAL

// when hitpoint bar is enabled, calculate and draw it, otherwise remove it
void NetHackQtStatusWindow::HitpointBar()
{
    // a style sheet is used to specify color for otherwise blank labels;
    // barcolors[][*]: column [0=left] is current health, [1=right] is injury
    static const char
    *styleformat = "QLabel { background-color : %s ; color : transparent ;"
                           " min-width : %d ; max-width %d }",
    *barcolors[6][2] = {
        { "black",   "black"     },  // 100%   /* second black never shown */
        { "blue",    "darkBlue"  },  //75..99
        /* gray is darker than darkGray for some reason (at least on OSX);
           default green is too dark compared to blue, yellow, orange,
           and red so is changed here to green.lighter(150) */
        { "#00c000", "gray"      },  //50..74  /* "green"=="#008000" */
        { "yellow",  "darkGray"  },  //25..49
        { "orange",  "lightGray" },  //10..24
        { "red",     "white"     },  // 0..9
    };

    /*
     * tty and curses use inverse video characters in the left portion
     * of the name+rank string to reflect hero's health.  We draw a
     * separate line above the name+rank field instead.  The left side
     * of the line indicates current health.  The right side is only
     * shown when injured and indicates missing amount of maximum health.
     */
    if (iflags.wc2_hitpointbar) {
        int colorindx, w,
            ihp = Upolyd ? u.mh : u.uhp,
            ihpmax = Upolyd ? u.mhmax : u.uhpmax;
        ihp = std::max(std::min(ihp, ihpmax), 0);
        int pct = 100 * ihp / ihpmax,
            lox = hline1.x(),
            hix = lox + hline1.width() - 1;
        QRect geoH = hpbar_health.geometry(),
              geoI = hpbar_injury.geometry();
        QString styleH, styleI;

        if (ihp < ihpmax) {
            // health is less than full;
            // use red for extreme low health even if the percentage is
            // above the usual threshold (which will happen when maximum
            // health is very low); do a similar threshold override for
            // orange even though it can be distracting for low level hero
            colorindx = (pct < 10 || ihp < 5) ? 5       // red    | white
                        : (pct < 25 || ihp < 10 ) ? 4   // orange | lightGray
                          : (pct < 50) ? 3              // yellow | darkGray*
                            : (pct < 75) ? 2            // green  | gray*
                              : 1;                      // blue   | darkBlue

            int pxl_health = (hix - lox + 1) * ihp / ihpmax;
            geoH.setRight(std::min(lox + pxl_health - 1, hix));
            hpbar_health.setGeometry(geoH);
            w = geoH.right() - geoH.left() + 1; // might yield 0 (ie, if dead)
            styleH = nh_qsprintf(styleformat, barcolors[colorindx][0], w, w);
            hpbar_health.setStyleSheet(styleH);
            // when healing, having the old injury-side shown while the new
            // health-side expands pushes the injury farther right and it's
            // momentarily visible there before it gets recalculated+redrawn
            hpbar_injury.hide(); // will re-show below
            hpbar_health.show(); // don't need to hide() if/when width is 0

            int oldleft = geoI.left();
            geoI.setLeft(geoH.right() + 1);
            geoI.setRight(hix);
            hpbar_injury.setGeometry(geoI);
            w = geoI.right() - geoI.left() + 1;
            styleI = nh_qsprintf(styleformat, barcolors[colorindx][1], w, w);
            hpbar_injury.setStyleSheet(styleI);
            if (geoI.left() != oldleft)
                hpbar_injury.move(geoI.left(), geoI.top());
            hpbar_injury.show();

            alreadyfullhp = false;
        } else if (!alreadyfullhp) { // skip if unchanged
            // health is full
            colorindx = 0; // black | (not used)

            hpbar_injury.hide();
            geoI.setLeft(hix); // hix + 1
            hpbar_injury.setGeometry(geoI);

            geoH.setRight(hix);
            hpbar_health.setGeometry(geoH);
            w = geoH.right() - geoH.left() + 1;
            styleH = nh_qsprintf(styleformat, barcolors[colorindx][0], w, w);
            hpbar_health.setStyleSheet(styleH);
            hpbar_health.show();

            alreadyfullhp = true;
        }
    } else {
        // hitpoint bar is disabled
        hpbar_health.hide();
        hpbar_injury.hide();
        alreadyfullhp = false;
    }
}

RESTORE_WARNING_FORMAT_NONLITERAL

/*
 * Update the displayed status.  The current code in botl.c updates
 * two lines of information.  Both lines are always updated one after
 * the other.  So only do our update when we update the second line.
 *
 * Information on the first line:
 *    name, Str/Dex/&c characteristics, alignment, score
 *
 * Information on the second line:
 *    dlvl, gold, hp, power, ac, {level & exp or HD **}
 *    status (hunger, encumbrance, sick, stun, conf, halu, blind), time
 *
 * [**] HD is shown instead of level and exp when hero is polymorphed.
 */
void NetHackQtStatusWindow::updateStats()
{
    if (!parentWidget()) return;
    if (cursy != 0) return; /* do a complete update when line 0 is done */

    QString buf;

    if (first_set) {
        // set toggle-detection flags for optional fields
        was_polyd = Upolyd ? true : false;
        had_exp = ::flags.showexp ? true : false;
        // not conditionalized upon '#ifdef SCORE_ON_BOTL' here
        had_score = ::flags.showscore ? true : false; // false when disabled
        score.setLabel(""); // init if enabled, one-time set if disabled
    }
    // display hitpoint bar if it is active; it isn't subject to field
    // highlighting so we don't track whether it has just been toggled On|Off
    HitpointBar();

    int st = ACURR(A_STR);
    if (st > STR18(100)) {
        buf = nh_qsprintf("Str:%d", st - 100);        // 19..25
    } else if (st == STR18(100)) {
        buf = nh_qsprintf("Str:18/**");               // 18/100
    } else if (st > 18) {
        buf = nh_qsprintf("Str:18/%02d", st - 18);    // 18/01..18/99
    } else {
        buf = nh_qsprintf("Str:%d", st);              //  3..18
    }
    str.setLabel(buf, NetHackQtLabelledIcon::NoNum, (long) st);
    dex.setLabel("Dex:", (long) ACURR(A_DEX));
    con.setLabel("Con:", (long) ACURR(A_CON));
    intel.setLabel("Int:", (long) ACURR(A_INT));
    wis.setLabel("Wis:", (long) ACURR(A_WIS));
    cha.setLabel("Cha:", (long) ACURR(A_CHA));

    boolean spreadout = (::iflags.wc2_statuslines != 2);
    int k = 0; // number of conditions shown

    long qt_uhs = 0L;
    const char *hung = hu_stat[u.uhs];
    QString qhung = QString(hung).trimmed();
    if (hung[0]==' ') {
        if (!hunger.isHidden()) {
            hunger.setLabel("", NetHackQtLabelledIcon::NoNum, qt_uhs);
            hunger.hide();
        }
    } else {
        // satiated is worse (due to risk of death from overeating)
        // than not-hungry and we'll treat it as also worse than hungry,
        // but better than weak or fainting; the u.uhs enum values
        // order them differently so we jump through a hoop
        switch (u.uhs) {
        case NOT_HUNGRY: qt_uhs = 0L; break;
        case HUNGRY:     qt_uhs = 1L; break;
        case SATIATED:   qt_uhs = 2L; break;
        case WEAK:       qt_uhs = 3L; break;
        case FAINTING:   qt_uhs = 4L; break;
        default:         qt_uhs = 5L; break; // fainted, starved
        }
        hunger.setIcon(u.uhs ? p_hungry : p_satiated, qhung.toLower());
        hunger.setLabel(qhung, NetHackQtLabelledIcon::NoNum, qt_uhs);
        hunger.ForceResize();
	++k, hunger.show();
    }
    long encindx = (long) near_capacity();
    const char *enc = enc_stat[encindx];
    if (enc[0]==' ' || !enc[0]) {
        if (!encumber.isHidden()) {
            encumber.setLabel("", NetHackQtLabelledIcon::NoNum, encindx);
            encumber.hide();
        }
    } else {
        encumber.setIcon(p_encumber[encindx - 1], QString(enc).toLower());
        encumber.setLabel(enc, NetHackQtLabelledIcon::NoNum, encindx);
        encumber.ForceResize();
	++k, encumber.show();
    }

    if (Stoned) ++k, stoned.show(); else stoned.hide();
    if (Slimed) ++k, slimed.show(); else slimed.hide();
    if (Strangled) ++k, strngld.show(); else strngld.hide();
    if (Sick) {
        /* FoodPois or TermIll or both */
	if (u.usick_type & SICK_VOMITABLE) { /* food poisoning */
	    ++k, sick_fp.show();
	} else {
	    sick_fp.hide();
	}
	if (u.usick_type & SICK_NONVOMITABLE) { /* terminally ill */
	    ++k, sick_il.show();
	} else {
	    sick_il.hide();
	}
    } else {
	sick_fp.hide();
	sick_il.hide();
    }
    if (Stunned) ++k, stunned.show(); else stunned.hide();
    if (Confusion) ++k, confused.show(); else confused.hide();
    if (Hallucination) ++k, hallu.show(); else hallu.hide();
    if (Blind) ++k, blind.show(); else blind.hide();
    if (Deaf) ++k, deaf.show(); else deaf.hide();

    // flying is blocked when levitating, so Lev and Fly are mutually exclusive
    if (Levitation) ++k, lev.show(); else lev.hide();
    if (Flying) ++k, fly.show(); else fly.hide();
    if (u.usteed) ++k, ride.show(); else ride.hide();

    // version is optional; displayed to the right of conditions when present
    if (::flags.showvers) {
        // the value only changes if user modifies the 'versinfo' option
        if (::flags.versinfo != prev_versinfo) {
            char vbuf[80], *cvers = status_version(vbuf, sizeof vbuf, FALSE);
            vers.setLabel(QString(cvers));
            // FIXME: this shouldn't be necessary but without hide() before
            // forthcoming show(), the new value isn't appearing
            if (!vers.isHidden()) vers.hide();
            prev_versinfo = ::flags.versinfo;
        }
        ++k, vers.show();
    } else
        vers.hide();

    if (Upolyd) {
	buf = nh_capitalize_words(pmname(&mons[u.umonnum],
                                  ::flags.female ? FEMALE : MALE));
    } else {
	buf = rank_of(u.ulevel, svp.pl_character[0], ::flags.female);
    }
    QString buf2;
    char buf3[BUFSZ];
    buf2 = nh_qsprintf("%s the %s", upstart(strcpy(buf3, svp.plname)),
                       buf.toLatin1().constData());
    name.setLabel(buf2, NetHackQtLabelledIcon::NoNum, u.ulevel);

    if (!describe_level(buf3, 0)) {
	Sprintf(buf3, "%s, level %d",
                svd.dungeons[u.uz.dnum].dname, ::depth(&u.uz));
    }
    dlevel.setLabel(buf3);

    int poly_toggled = !was_polyd ^ !Upolyd;
    int exp_toggled = !had_exp ^ !::flags.showexp;
    if (poly_toggled)
        // for this update, changed values aren't better|worse, just different
        hp.setCompareMode(NeitherIsBetter);
    if (poly_toggled || exp_toggled)
        level.setCompareMode(NeitherIsBetter);
    if (Upolyd) {
        // You're a monster!
        buf = nh_qsprintf("/%d", u.mhmax);
        hp.setLabel("HP:", std::max((long) u.mh, 0L), buf);
        level.setLabel("HD:", (long) mons[u.umonnum].mlevel); // hit dice
        // Exp points are not shown when HD is displayed instead of Xp level
    } else {
        // You're normal.
        buf = nh_qsprintf("/%d", u.uhpmax);
        hp.setLabel("HP:", std::max((long) u.uhp, 0L), buf);
        // if Exp points are to be displayed, append them to Xp level;
        // up/down highlighting becomes tricky--don't try very hard;
        // depending upon font size and status layout, "Level:NN/nnnnnnnn"
        // might be too wide to fit
        static const char *const lvllbl[3] = { "Level:", "Lvl:", "L:" };
        QFontMetrics fm(level.label->font());
        for (int i = ::flags.showexp ? 0 : 3; i < 4; ++i) {
            // passes 0,1,2 are with Exp, 3 is without Exp and always fits
            if (i < 3) {
                buf = nh_qsprintf("%s%ld/%ld", lvllbl[i], (long) u.ulevel,
                                  u.uexp);
            } else {
                buf = nh_qsprintf("%s%ld", lvllbl[i - 3], (long) u.ulevel);
            }
            // +2: allow a couple of pixels at either end to be clipped off
            if (fm.size(0, buf).width() <= (2 + level.label->width() + 2))
                break;
        }
        level.setLabel(buf, NetHackQtLabelledIcon::NoNum,
                       // if we intended to show Exp but must settle
                       // for Xp due to width, we still want to use
                       // Exp for setLabel()'s Up|Down highlighting
                       ::flags.showexp ? u.uexp : (long) u.ulevel);
    }
    if (poly_toggled)
        // for next update, changed values will be better|worse as usual
        hp.setCompareMode(BiggerIsBetter);
    if (poly_toggled || exp_toggled)
        level.setCompareMode(BiggerIsBetter);
    was_polyd = Upolyd ? true : false;
    had_exp = (::flags.showexp && !was_polyd) ? true : false;

    buf = nh_qsprintf("/%d", u.uenmax);
    power.setLabel("Pow:", (long) u.uen, buf);
    ac.setLabel("AC:", (long) u.uac);
    // gold prefix used to be "Au:", tty uses "$:"; never too wide to fit;
    // practical limit due to carrying capacity limit is less than 300K
    long goldamt = money_cnt(gi.invent);
    goldamt = std::max(goldamt, 0L); // sanity; core's botl() does likewise
    goldamt = std::min(goldamt, 99999999L); // ditto
    gold.setLabel("Gold:", goldamt);

    const char *text;
    QString qtext;
    QPixmap *pxmp;
    if (u.ualign.type == A_LAWFUL) {
        pxmp = &p_lawful;
        text = "Lawful";
    } else if (u.ualign.type == A_NEUTRAL) {
        pxmp = &p_neutral;
        text = "Neutral";
    } else {
        pxmp = &p_chaotic;
        // Unaligned should never happen
        text = (u.ualign.type == A_CHAOTIC) ? "Chaotic"
               : (u.ualign.type == A_NONE) ? "unaligned"
                 : "other?";
    }
    qtext = nh_qsprintf("%sly aligned", text);
    align.setIcon(*pxmp, qtext.toLower());
    align.setLabel(QString(text));
    // without this, the ankh pixmap shifts from centered to left
    // justified relative to the label text for some unknown reason...
    align.ForceResize();
    if (spreadout)
        ++k; // when not condensed, Alignment is shown on the Conditions row

    if (!k) {
        blank2.show(); // for vertical spacing: force the row to be non-empty
    } else
        blank2.hide();

    // Time isn't highlighted (due to constantly changing) so we don't keep
    // track of whether it has just been toggled On or Off
    if (::flags.time) {
        // hypothetically Time could grow to enough digits to have trouble
        // fitting, but it's not worth worrying about
        time.setLabel("Time:", (long) svm.moves);
    } else {
        time.setLabel("");
    }

#ifdef SCORE_ON_BOTL
    int score_toggled = !had_score ^ !::flags.showscore;
    if (::flags.showscore) {
        if (score_toggled) // toggled On
            score.setCompareMode(NeitherIsBetter);
        long pts = botl_score();
        if (spreadout) {
            // plenty of room; Time and Score both have the width of 3 fields
            score.setLabel("Score:", pts);
        } else {
            // depending upon font size and status layout, "Score:nnnnnnnn"
            // might be too wide to fit (simpler version of Level:NN/nnnnnnnn)
            static const char *const scrlbl[3] = { "Score:", "Scr:", "S:" };
            QFontMetrics fm(score.label->font());
            for (int i = 0; i < 3; ++i) {
                buf = nh_qsprintf("%s%ld", scrlbl[i], pts);
                // +2: allow couple of pixels at either end to be clipped off
                if (fm.size(0, buf).width() <= (2 + score.width() + 2))
                    break;
            }
            score.setLabel(buf, NetHackQtLabelledIcon::NoNum, pts);
            // with Xp/Exp, we fallback to Xp if the shortest label prefix
            // is still too long; here we just show a clipped value and
            // let user either live with it or turn 'showscore' off (or
            // set statuslines:3 to take advantage of the extra room that
            // the spread out status layout provides)
        }
    } else {
        if (score_toggled) { // toggled Off; if already Off, no need to set ""
            score.setCompareMode(NoCompare);
            score.setLabel(""); // blank when not active
        }
    }
    if (score_toggled)
        score.setCompareMode(BiggerIsBetter);
    had_score = ::flags.showscore ? true : false;
#endif /* SCORE_ON_BOTL */

    if (first_set) {
	first_set=false;

        /*
         * Default compareMode is BiggerIsBetter:  an increased value
         * is an improvement.
         */
	name.highlightWhenChanging();
        dlevel.highlightWhenChanging();
            dlevel.setCompareMode(NeitherIsBetter);

	str.highlightWhenChanging();
	dex.highlightWhenChanging();
	con.highlightWhenChanging();
	intel.highlightWhenChanging();
	wis.highlightWhenChanging();
	cha.highlightWhenChanging();

	hp.highlightWhenChanging();
	power.highlightWhenChanging();
        ac.highlightWhenChanging();
            ac.setCompareMode(SmallerIsBetter);
	level.highlightWhenChanging();
	gold.highlightWhenChanging();

        // don't highlight 'time' because it changes almost continuously
        // [if we did highlight it, we wouldn't show increase as 'Better']
        //time.highlightWhenChanging(); time.setCompareMode(NeitherIsBetter);
	score.highlightWhenChanging();

        align.highlightWhenChanging();
            align.setCompareMode(NeitherIsBetter);

	hunger.highlightWhenChanging();
            hunger.setCompareMode(SmallerIsBetter);
	encumber.highlightWhenChanging();
            encumber.setCompareMode(SmallerIsBetter);
	stoned.highlightWhenChanging();
	slimed.highlightWhenChanging();
	strngld.highlightWhenChanging();
	sick_fp.highlightWhenChanging();
	sick_il.highlightWhenChanging();
	stunned.highlightWhenChanging();
	confused.highlightWhenChanging();
	hallu.highlightWhenChanging();
	blind.highlightWhenChanging();
	deaf.highlightWhenChanging();
        // the default behavior is to highlight a newly shown condition
        // as "worse" but that isn't appropriate for 'other' conds;
        // NetHackQtLabelledIcon::show() uses NeitherIsBetter to handle it
	lev.highlightWhenChanging();
            lev.setCompareMode(NeitherIsBetter);
	fly.highlightWhenChanging();
            fly.setCompareMode(NeitherIsBetter);
	ride.highlightWhenChanging();
            ride.setCompareMode(NeitherIsBetter);
        // not a true status condition; doesn't change (unless 'showvers'
        // gets toggled or 'versinfo' is modified) so doesn't get highlighted
        vers.setCompareMode(NoCompare);
    }
}

/*
 * Turn off hilighted status values after a certain amount of turns.
 */
void NetHackQtStatusWindow::checkTurnEvents()
{
}

// clicking on status window runs #attributes (^X)
void NetHackQtStatusWindow::mousePressEvent(QMouseEvent *event UNUSED)
{
    QWidget *main = NetHackQtBind::mainWidget();
    (static_cast <NetHackQtMainWindow *> (main))->FuncAsCommand(doattributes);
}

} // namespace nethack_qt_
