#include "solvespace.h"
#include <png.h>

#define clamp01(x) (max(0, min(1, (x))))

const Style::Default Style::Defaults[] = {
    { ACTIVE_GRP,   "ActiveGrp",    RGBf(1.0, 1.0, 1.0), 1.5, },
    { CONSTRUCTION, "Construction", RGBf(0.1, 0.7, 0.1), 1.5, },
    { INACTIVE_GRP, "InactiveGrp",  RGBf(0.5, 0.3, 0.0), 1.5, },
    { DATUM,        "Datum",        RGBf(0.0, 0.8, 0.0), 1.5, },
    { SOLID_EDGE,   "SolidEdge",    RGBf(0.8, 0.8, 0.8), 1.0, },
    { CONSTRAINT,   "Constraint",   RGBf(1.0, 0.1, 1.0), 1.0, },
    { SELECTED,     "Selected",     RGBf(1.0, 0.0, 0.0), 1.5, },
    { HOVERED,      "Hovered",      RGBf(1.0, 1.0, 0.0), 1.5, },
    { CONTOUR_FILL, "ContourFill",  RGBf(0.0, 0.1, 0.1), 1.0, },
    { NORMALS,      "Normals",      RGBf(0.0, 0.4, 0.4), 1.0, },
    { ANALYZE,      "Analyze",      RGBf(0.0, 1.0, 1.0), 1.0, },
    { DRAW_ERROR,   "DrawError",    RGBf(1.0, 0.0, 0.0), 8.0, },
    { DIM_SOLID,    "DimSolid",     RGBf(0.1, 0.1, 0.1), 1.0, },
    { 0,            NULL,           0,                   0.0, },
};

char *Style::CnfColor(char *prefix) {
    static char name[100];
    sprintf(name, "Style_%s_Color", prefix);
    return name;
}
char *Style::CnfWidth(char *prefix) {
    static char name[100];
    sprintf(name, "Style_%s_Width", prefix);
    return name;
}

char *Style::CnfPrefixToName(char *prefix) {
    static char name[100];
    int i = 0, j;
    strcpy(name, "#def-");
    j = 5;
    while(prefix[i] && j < 90) {
        if(isupper(prefix[i]) && i != 0) {
            name[j++] = '-';
        }
        name[j++] = tolower(prefix[i]);
        i++;
    }
    name[j++] = '\0';
    return name;
}

void Style::CreateAllDefaultStyles(void) {
    const Default *d;
    for(d = &(Defaults[0]); d->h.v; d++) {
        (void)Get(d->h);
    }
}

void Style::CreateDefaultStyle(hStyle h) {
    bool isDefaultStyle = true;
    const Default *d;
    for(d = &(Defaults[0]); d->h.v; d++) {
        if(d->h.v == h.v) break;
    }
    if(!d->h.v) {
        // Not a default style; so just create it the same as our default
        // active group entity style.
        d = &(Defaults[0]);
        isDefaultStyle = false;
    }

    Style ns;
    ZERO(&ns);
    ns.color        = CnfThawDWORD(d->color, CnfColor(d->cnfPrefix));
    ns.width        = CnfThawFloat((float)(d->width), CnfWidth(d->cnfPrefix));
    ns.widthAs      = UNITS_AS_PIXELS;
    ns.textHeight   = DEFAULT_TEXT_HEIGHT;
    ns.textHeightAs = UNITS_AS_PIXELS;
    ns.textOrigin   = 0;
    ns.textAngle    = 0;
    ns.visible      = true;
    ns.exportable   = true;
    ns.filled       = false;
    ns.fillColor    = RGBf(0.3, 0.3, 0.3);
    ns.h            = h;
    if(isDefaultStyle) {
        ns.name.strcpy(CnfPrefixToName(d->cnfPrefix));
    } else {
        ns.name.strcpy("new-custom-style");
    }

    SK.style.Add(&ns);
}

void Style::LoadFactoryDefaults(void) {
    const Default *d;
    for(d = &(Defaults[0]); d->h.v; d++) {
        Style *s = Get(d->h);

        s->color        = d->color;
        s->width        = d->width;
        s->widthAs      = UNITS_AS_PIXELS;
        s->textHeight   = DEFAULT_TEXT_HEIGHT;
        s->textHeightAs = UNITS_AS_PIXELS;
        s->textOrigin   = 0;
        s->textAngle    = 0;
        s->visible      = true;
        s->exportable   = true;
        s->filled       = false;
        s->fillColor    = RGBf(0.3, 0.3, 0.3);
        s->name.strcpy(CnfPrefixToName(d->cnfPrefix));
    }
    SS.backgroundColor = RGB(0, 0, 0);
    if(SS.bgImage.fromFile) MemFree(SS.bgImage.fromFile);
    SS.bgImage.fromFile = NULL;
}

void Style::FreezeDefaultStyles(void) {
    const Default *d;
    for(d = &(Defaults[0]); d->h.v; d++) {
        CnfFreezeDWORD(Color(d->h), CnfColor(d->cnfPrefix));
        CnfFreezeFloat((float)Width(d->h), CnfWidth(d->cnfPrefix));
    }
}

DWORD Style::CreateCustomStyle(void) {
    SS.UndoRemember();
    DWORD vs = max(Style::FIRST_CUSTOM, SK.style.MaximumId() + 1);
    hStyle hs = { vs };
    (void)Style::Get(hs);
    return hs.v;
}

void Style::AssignSelectionToStyle(DWORD v) {
    bool showError = false;
    SS.GW.GroupSelection();

    SS.UndoRemember();
    int i;
    for(i = 0; i < SS.GW.gs.entities; i++) {
        hEntity he = SS.GW.gs.entity[i];
        if(!he.isFromRequest()) {
            showError = true;
            continue;
        }

        hRequest hr = he.request();
        Request *r = SK.GetRequest(hr);
        r->style.v = v;
        SS.MarkGroupDirty(r->group);
    }
    for(i = 0; i < SS.GW.gs.constraints; i++) {
        hConstraint hc = SS.GW.gs.constraint[i];
        Constraint *c = SK.GetConstraint(hc);
        if(c->type != Constraint::COMMENT) continue;

        c->disp.style.v = v;
    }

    if(showError) {
        Error("Can't assign style to an entity that's derived from another "
              "entity; try assigning a style to this entity's parent.");
    }

    SS.GW.ClearSelection();
    InvalidateGraphics();
    SS.later.generateAll = true;

    // And show that style's info screen in the text window.
    SS.TW.GoToScreen(TextWindow::SCREEN_STYLE_INFO);
    SS.TW.shown.style.v = v;
    SS.later.showTW = true;
}

//-----------------------------------------------------------------------------
// Look up a style by its handle. If that style does not exist, then create
// the style, according to our table of default styles.
//-----------------------------------------------------------------------------
Style *Style::Get(hStyle h) {
    if(h.v == 0) h.v = ACTIVE_GRP;

    Style *s = SK.style.FindByIdNoOops(h);
    if(s) {
        // It exists, good.
        return s;
    } else {
        // It doesn't exist; so we should create it and then return that.
        CreateDefaultStyle(h);
        return SK.style.FindById(h);
    }
}

//-----------------------------------------------------------------------------
// A couple of wrappers, so that I can call these functions with either an
// hStyle or with the integer corresponding to that hStyle.v.
//-----------------------------------------------------------------------------
DWORD Style::Color(int s, bool forExport) {
    hStyle hs = { s };
    return Color(hs, forExport);
}
float Style::Width(int s) {
    hStyle hs = { s };
    return Width(hs);
}

//-----------------------------------------------------------------------------
// Return the color associated with our style as 8-bit RGB.
//-----------------------------------------------------------------------------
DWORD Style::Color(hStyle h, bool forExport) {
    Style *s = Get(h);
    if(forExport) {
        Vector rgb = Vector::From(REDf(s->color),
                                  GREENf(s->color),
                                  BLUEf(s->color));
        rgb = rgb.Minus(Vector::From(1, 1, 1));
        if(rgb.Magnitude() < 0.4 && SS.fixExportColors) {
            // This is an almost-white color in a default style, which is
            // good for the default on-screen view (black bg) but probably
            // not desired in the exported files, which typically are shown
            // against white backgrounds.
            return RGB(0, 0, 0);
        }
    }
    return s->color;
}

//-----------------------------------------------------------------------------
// Return the width associated with our style in pixels..
//-----------------------------------------------------------------------------
float Style::Width(hStyle h) {
    double r = 1.0;
    Style *s = Get(h);
    if(s->widthAs == UNITS_AS_MM) {
        r = s->width * SS.GW.scale;
    } else if(s->widthAs == UNITS_AS_PIXELS) {
        r = s->width;
    }
    // This returns a float because glLineWidth expects a float, avoid casts.
    return (float)r;
}

//-----------------------------------------------------------------------------
// Return the width associated with our style in millimeters..
//-----------------------------------------------------------------------------
double Style::WidthMm(int hs) {
    double widthpx = Width(hs);
    return widthpx / SS.GW.scale;
}

//-----------------------------------------------------------------------------
// Return the associated text height, in pixels.
//-----------------------------------------------------------------------------
double Style::TextHeight(hStyle hs) {
    Style *s = Get(hs);
    if(s->textHeightAs == UNITS_AS_MM) {
        return s->textHeight * SS.GW.scale;
    } else if(s->textHeightAs == UNITS_AS_PIXELS) {
        return s->textHeight;
    } else {
        return DEFAULT_TEXT_HEIGHT;
    }
}

//-----------------------------------------------------------------------------
// Should lines and curves from this style appear in the output file? Only
// if it's both shown and exportable.
//-----------------------------------------------------------------------------
bool Style::Exportable(int si) {
    hStyle hs = { si };
    Style *s = Get(hs);
    return (s->exportable) && (s->visible);
}

//-----------------------------------------------------------------------------
// Return the appropriate style for our entity. If the entity has a style
// explicitly assigned, then it's that style. Otherwise it's the appropriate
// default style.
//-----------------------------------------------------------------------------
hStyle Style::ForEntity(hEntity he) {
    Entity *e = SK.GetEntity(he);
    // If the entity has a special style, use that. If that style doesn't
    // exist yet, then it will get created automatically later.
    if(e->style.v != 0) {
        return e->style;
    }

    // Otherwise, we use the default rules.
    hStyle hs;
    if(e->group.v != SS.GW.activeGroup.v) {
        hs.v = INACTIVE_GRP;
    } else if(e->construction) {
        hs.v = CONSTRUCTION;
    } else {
        hs.v = ACTIVE_GRP;
    }
    return hs;
}

char *Style::DescriptionString(void) {
    static char ret[100];
    if(name.str[0]) {
        sprintf(ret, "s%03x-%s", h.v, name.str);
    } else {
        sprintf(ret, "s%03x-(unnamed)", h.v);
    }
    return ret;
}


void TextWindow::ScreenShowListOfStyles(int link, DWORD v) {
    SS.TW.GoToScreen(SCREEN_LIST_OF_STYLES);
}
void TextWindow::ScreenShowStyleInfo(int link, DWORD v) {
    SS.TW.GoToScreen(SCREEN_STYLE_INFO);
    SS.TW.shown.style.v = v;
}

void TextWindow::ScreenLoadFactoryDefaultStyles(int link, DWORD v) {
    Style::LoadFactoryDefaults();
    SS.TW.GoToScreen(SCREEN_LIST_OF_STYLES);
}

void TextWindow::ScreenCreateCustomStyle(int link, DWORD v) {
    Style::CreateCustomStyle();
}

void TextWindow::ScreenChangeBackgroundColor(int link, DWORD v) {
    DWORD rgb = SS.backgroundColor;
    char str[300];
    sprintf(str, "%.2f, %.2f, %.2f", REDf(rgb), GREENf(rgb), BLUEf(rgb));
    ShowTextEditControl(v, 3, str);
    SS.TW.edit.meaning = EDIT_BACKGROUND_COLOR;
}

static int RoundUpToPowerOfTwo(int v)
{
    int i;
    for(i = 0; i < 31; i++) {
        int vt = (1 << i);
        if(vt >= v) {
            return vt;
        }
    }
    return 0;
}

void TextWindow::ScreenBackgroundImage(int link, DWORD v) {
    if(SS.bgImage.fromFile) MemFree(SS.bgImage.fromFile);
    SS.bgImage.fromFile = NULL;

    if(link == 'l') {
        FILE *f = NULL;
        png_struct *png_ptr = NULL;
        png_info *info_ptr = NULL;

        char importFile[MAX_PATH] = "";
        if(!GetOpenFile(importFile, PNG_EXT, PNG_PATTERN)) goto err;
        f = fopen(importFile, "rb");
        if(!f) goto err;

        BYTE header[8];
        fread(header, 1, 8, f);
        if(png_sig_cmp(header, 0, 8)) goto err;

        png_ptr = png_create_read_struct(PNG_LIBPNG_VER_STRING,
            NULL, NULL, NULL);
        if(!png_ptr) goto err;

        info_ptr = png_create_info_struct(png_ptr);
        if(!info_ptr) goto err;

        if(setjmp(png_jmpbuf(png_ptr))) goto err;

        png_init_io(png_ptr, f);
        png_set_sig_bytes(png_ptr, 8);

        png_read_png(png_ptr, info_ptr,
            PNG_TRANSFORM_EXPAND | PNG_TRANSFORM_STRIP_ALPHA, NULL);
        
        int w = info_ptr->width,
            h = info_ptr->height;
        BYTE **rows = png_get_rows(png_ptr, info_ptr);

        // Round to next-highest powers of two, since the textures require
        // that. And round up to 4, to guarantee DWORD alignment.
        int rw = max(4, RoundUpToPowerOfTwo(w)),
            rh = max(4, RoundUpToPowerOfTwo(h));

        SS.bgImage.fromFile = (BYTE *)MemAlloc(rw*rh*3);
        for(int i = 0; i < h; i++) {
            memcpy(SS.bgImage.fromFile + ((h - 1) - i)*(rw*3), rows[i], w*3);
        }
        SS.bgImage.w      = w;
        SS.bgImage.h      = h;
        SS.bgImage.rw     = rw;
        SS.bgImage.rh     = rh;
        SS.bgImage.scale  = SS.GW.scale;
        SS.bgImage.origin = SS.GW.offset.ScaledBy(-1);

err:
        if(png_ptr) png_destroy_read_struct(&png_ptr, &info_ptr, NULL);
        if(f) fclose(f);
    }
    SS.later.showTW = true;
}

void TextWindow::ScreenChangeBackgroundImageScale(int link, DWORD v) {
    char str[300];
    sprintf(str, "%.3f", SS.bgImage.scale * SS.MmPerUnit());
    SS.TW.edit.meaning = EDIT_BACKGROUND_IMG_SCALE;
    ShowTextEditControl(v, 10, str);
}

void TextWindow::ShowListOfStyles(void) {
    Printf(true, "%Ft color  style-name");

    bool darkbg = false;
    Style *s;
    for(s = SK.style.First(); s; s = SK.style.NextAfter(s)) {
        Printf(false, "%Bp  %Bp   %Bp   %Fl%Ll%f%D%s%E",
            darkbg ? 'd' : 'a',
            0x80000000 | s->color,
            darkbg ? 'd' : 'a',
            ScreenShowStyleInfo, s->h.v,
            s->DescriptionString());

        darkbg = !darkbg;
    }

    Printf(true, "  %Fl%Ll%fcreate a new custom style%E", 
        &ScreenCreateCustomStyle);

    Printf(false, "");

    DWORD rgb = SS.backgroundColor;
    Printf(false, "%Ft background color (r, g, b)%E");
    Printf(false, "%Ba   %@, %@, %@ %Fl%D%f%Ll[change]%E",
        REDf(rgb), GREENf(rgb), BLUEf(rgb),
        top[rows-1] + 2, &ScreenChangeBackgroundColor);

    Printf(false, "");
    Printf(false, "%Ft background bitmap image%E");
    if(SS.bgImage.fromFile) {
        Printf(false, "%Ba   %Ftwidth:%E %dpx   %Ftheight:%E %dpx",
            SS.bgImage.w, SS.bgImage.h);

        Printf(false, "   %Ftscale:%E %# px/%s %Fl%Ll%f%D[change]%E",
            SS.bgImage.scale*SS.MmPerUnit(),
            SS.UnitName(),
            &ScreenChangeBackgroundImageScale, top[rows-1] + 2);

        Printf(false, "%Ba   %Fl%Lc%fclear background image%E",
            &ScreenBackgroundImage);
    } else {
        Printf(false, "%Ba   none - %Fl%Ll%fload background image%E",
            &ScreenBackgroundImage);
        Printf(false, "   (bottom left will be center of view)");
    }

    Printf(false, "");
    Printf(false, "  %Fl%Ll%fload factory defaults%E",
        &ScreenLoadFactoryDefaultStyles);
}


void TextWindow::ScreenChangeStyleName(int link, DWORD v) {
    hStyle hs = { v };
    Style *s = Style::Get(hs);
    ShowTextEditControl(10, 13, s->name.str);
    SS.TW.edit.style = hs;
    SS.TW.edit.meaning = EDIT_STYLE_NAME;   
}

void TextWindow::ScreenDeleteStyle(int link, DWORD v) {
    SS.UndoRemember();
    hStyle hs = { v };
    Style *s = SK.style.FindByIdNoOops(hs);
    if(s) {
        SK.style.RemoveById(hs);
        // And it will get recreated automatically if something is still using
        // the style, so no need to do anything else.
    }
    SS.TW.GoToScreen(SCREEN_LIST_OF_STYLES);
    InvalidateGraphics();
}

void TextWindow::ScreenChangeStyleWidthOrTextHeight(int link, DWORD v) {
    hStyle hs = { v };
    Style *s = Style::Get(hs);
    double val   = (link == 't') ? s->textHeight   : s->width;
    int    units = (link == 't') ? s->textHeightAs : s->widthAs;

    char str[300];
    if(units == Style::UNITS_AS_PIXELS) {
        sprintf(str, "%.2f", val);
    } else {
        strcpy(str, SS.MmToString(val));
    }
    int row = 0;
    if(link == 'w') {
        row = 16;               // width for a default style
    } else if(link == 'W') {
        row = 16;               // width for a custom style
    } else if(link == 't') {
        row = 27;               // text height (for custom styles only)
    }
    ShowTextEditControl(row, 13, str);
    SS.TW.edit.style = hs;
    SS.TW.edit.meaning = (link == 't') ? EDIT_STYLE_TEXT_HEIGHT :
                                         EDIT_STYLE_WIDTH;
}

void TextWindow::ScreenChangeStyleTextAngle(int link, DWORD v) {
    hStyle hs = { v };
    Style *s = Style::Get(hs);
    char str[300];
    sprintf(str, "%.2f", s->textAngle);
    ShowTextEditControl(32, 13, str);
    SS.TW.edit.style = hs;
    SS.TW.edit.meaning = EDIT_STYLE_TEXT_ANGLE;
}

void TextWindow::ScreenChangeStyleColor(int link, DWORD v) {
    hStyle hs = { v };
    Style *s = Style::Get(hs);
    // Same function used for stroke and fill colors
    int row, col, em;
    DWORD rgb;
    if(link == 's') {
        row = 13; col = 17;
        em = EDIT_STYLE_COLOR;
        rgb = s->color;
    } else if(link == 'f') {
        row = 21; col = 17;
        em = EDIT_STYLE_FILL_COLOR;
        rgb = s->fillColor;
    } else {
        oops();
    }
    char str[300];
    sprintf(str, "%.2f, %.2f, %.2f", REDf(rgb), GREENf(rgb), BLUEf(rgb));
    ShowTextEditControl(row, col, str);
    SS.TW.edit.style = hs;
    SS.TW.edit.meaning = em;
}

void TextWindow::ScreenChangeStyleYesNo(int link, DWORD v) {
    SS.UndoRemember();
    hStyle hs = { v };
    Style *s = Style::Get(hs);
    switch(link) {
        case 'w':
            // Units for the width
            if(s->widthAs == Style::UNITS_AS_PIXELS) {
                s->widthAs = Style::UNITS_AS_MM;
                s->width /= SS.GW.scale;
            } else {
                s->widthAs = Style::UNITS_AS_PIXELS;
                s->width *= SS.GW.scale;
            }
            break;

        case 'h':
            // Units for the height
            if(s->textHeightAs == Style::UNITS_AS_PIXELS) {
                s->textHeightAs = Style::UNITS_AS_MM;
                s->textHeight /= SS.GW.scale;
            } else {
                s->textHeightAs = Style::UNITS_AS_PIXELS;
                s->textHeight *= SS.GW.scale;
            }
            break;

        case 'e':
            s->exportable = !(s->exportable);
            break;

        case 'v':
            s->visible = !(s->visible);
            break;

        case 'f':
            s->filled = !(s->filled);
            break;

        // Horizontal text alignment
        case 'L':
            s->textOrigin |=  Style::ORIGIN_LEFT;
            s->textOrigin &= ~Style::ORIGIN_RIGHT;
            break;
        case 'H':
            s->textOrigin &= ~Style::ORIGIN_LEFT;
            s->textOrigin &= ~Style::ORIGIN_RIGHT;
            break;
        case 'R':
            s->textOrigin &= ~Style::ORIGIN_LEFT;
            s->textOrigin |=  Style::ORIGIN_RIGHT;
            break;

        // Vertical text alignment
        case 'B':
            s->textOrigin |=  Style::ORIGIN_BOT;
            s->textOrigin &= ~Style::ORIGIN_TOP;
            break;
        case 'V':
            s->textOrigin &= ~Style::ORIGIN_BOT;
            s->textOrigin &= ~Style::ORIGIN_TOP;
            break;
        case 'T':
            s->textOrigin &= ~Style::ORIGIN_BOT;
            s->textOrigin |=  Style::ORIGIN_TOP;
            break;
    }
    InvalidateGraphics();
}

bool TextWindow::EditControlDoneForStyles(char *str) {
    Style *s;
    switch(edit.meaning) {
        case EDIT_STYLE_TEXT_HEIGHT:
        case EDIT_STYLE_WIDTH: {
            SS.UndoRemember();
            s = Style::Get(edit.style);

            double v;
            int units = (edit.meaning == EDIT_STYLE_TEXT_HEIGHT) ?
                            s->textHeightAs : s->widthAs;
            if(units == Style::UNITS_AS_MM) {
                v = SS.StringToMm(str);
            } else {
                v = atof(str);
            }
            v = max(0, v);
            if(edit.meaning == EDIT_STYLE_TEXT_HEIGHT) {
                s->textHeight = v;
            } else {
                s->width = v;
            }
            break;
        }
        case EDIT_STYLE_TEXT_ANGLE:
            SS.UndoRemember();
            s = Style::Get(edit.style);
            s->textAngle = WRAP_SYMMETRIC(atof(str), 360);
            break;

        case EDIT_BACKGROUND_COLOR:
        case EDIT_STYLE_FILL_COLOR:
        case EDIT_STYLE_COLOR: {
            double r, g, b;
            if(sscanf(str, "%lf, %lf, %lf", &r, &g, &b)==3) {
                r = clamp01(r);
                g = clamp01(g);
                b = clamp01(b);
                if(edit.meaning == EDIT_STYLE_COLOR) {
                    SS.UndoRemember();
                    s = Style::Get(edit.style);
                    s->color = RGBf(r, g, b);
                } else if(edit.meaning == EDIT_STYLE_FILL_COLOR) {
                    SS.UndoRemember();
                    s = Style::Get(edit.style);
                    s->fillColor = RGBf(r, g, b);
                } else {
                    SS.backgroundColor = RGBf(r, g, b);
                }
            } else {
                Error("Bad format: specify color as r, g, b");
            }
            break;
        }
        case EDIT_STYLE_NAME:
            if(!StringAllPrintable(str) || !*str) {
                Error("Invalid characters. Allowed are: A-Z a-z 0-9 _ -");
            } else {
                SS.UndoRemember();
                s = Style::Get(edit.style);
                s->name.strcpy(str);
            }
            break;

        case EDIT_BACKGROUND_IMG_SCALE: {
            Expr *e = Expr::From(str, true);
            if(e) {
                double ev = e->Eval();
                if(ev < 0.001 || isnan(ev)) {
                    Error("Scale must not be zero or negative!");
                } else {
                    SS.bgImage.scale = ev / SS.MmPerUnit();
                }
            }
            break;
        }
        default: return false;
    }
    return true;
}

void TextWindow::ShowStyleInfo(void) {
    Printf(true, "%Fl%f%Ll(back to list of styles)%E", &ScreenShowListOfStyles);

    Style *s = Style::Get(shown.style);

    if(s->h.v < Style::FIRST_CUSTOM) {
        Printf(true, "%FtSTYLE   %E%s ", s->DescriptionString());
    } else {
        Printf(true, "%FtSTYLE   %E%s "
                     "[%Fl%Ll%D%frename%E/%Fl%Ll%D%fdel%E]",
            s->DescriptionString(),
            s->h.v, &ScreenChangeStyleName,
            s->h.v, &ScreenDeleteStyle);
    }

    Printf(true, "%FtLINE COLOR   %E%Bp  %Bd (%@, %@, %@) %D%f%Ls%Fl[chng]%E",
        0x80000000 | s->color,
        REDf(s->color), GREENf(s->color), BLUEf(s->color),
        s->h.v, ScreenChangeStyleColor);

    // The line width, and its units
    if(s->widthAs == Style::UNITS_AS_PIXELS) {
        Printf(true, "%FtLINE WIDTH   %E%@ %D%f%Lp%Fl[change]%E",
            s->width,
            s->h.v, &ScreenChangeStyleWidthOrTextHeight,
            (s->h.v < Style::FIRST_CUSTOM) ? 'w' : 'W');
    } else {
        Printf(true, "%FtLINE WIDTH   %E%s %D%f%Lp%Fl[change]%E",
            SS.MmToString(s->width),
            s->h.v, &ScreenChangeStyleWidthOrTextHeight,
            (s->h.v < Style::FIRST_CUSTOM) ? 'w' : 'W');
    }

    bool widthpx = (s->widthAs == Style::UNITS_AS_PIXELS);
    if(s->h.v < Style::FIRST_CUSTOM) {
        Printf(false,"%FtIN UNITS OF  %Fspixels%E");
    } else {
        Printf(false,"%FtIN UNITS OF  "
                            "%Fh%D%f%Lw%s%E%Fs%s%E / %Fh%D%f%Lw%s%E%Fs%s%E",
            s->h.v, &ScreenChangeStyleYesNo,
            ( widthpx ? "" : "pixels"),
            ( widthpx ? "pixels" : ""),
            s->h.v, &ScreenChangeStyleYesNo,
            (!widthpx ? "" : SS.UnitName()),
            (!widthpx ? SS.UnitName() : ""));
    }

    if(s->h.v >= Style::FIRST_CUSTOM) {
        // The fill color, and whether contours are filled
        Printf(true,
            "%FtFILL COLOR   %E%Bp  %Bd (%@, %@, %@) %D%f%Lf%Fl[chng]%E",
                0x80000000 | s->fillColor,
                REDf(s->fillColor), GREENf(s->fillColor), BLUEf(s->fillColor),
                s->h.v, ScreenChangeStyleColor);
        Printf(false, "%FtCONTOURS ARE %E"
                            "%Fh%D%f%Lf%s%E%Fs%s%E / %Fh%D%f%Lf%s%E%Fs%s%E",
            s->h.v, &ScreenChangeStyleYesNo,
            ( s->filled ? "" : "filled"),
            ( s->filled ? "filled" : ""),
            s->h.v, &ScreenChangeStyleYesNo,
            (!s->filled ? "" : "not filled"),
            (!s->filled ? "not filled" : ""));
    }

    // The text height, and its units
    Printf(false, "");
    char *chng = (s->h.v < Style::FIRST_CUSTOM) ? "" : "[change]";
    if(s->textHeightAs == Style::UNITS_AS_PIXELS) {
        Printf(false, "%FtTEXT HEIGHT  %E%@ %D%f%Lt%Fl%s%E",
            s->textHeight,
            s->h.v, &ScreenChangeStyleWidthOrTextHeight,
            chng);
    } else {
        Printf(false, "%FtTEXT HEIGHT  %E%s %D%f%Lt%Fl%s%E",
            SS.MmToString(s->textHeight),
            s->h.v, &ScreenChangeStyleWidthOrTextHeight,
            chng);
    }

    bool textHeightpx = (s->textHeightAs == Style::UNITS_AS_PIXELS);
    if(s->h.v < Style::FIRST_CUSTOM) {
        Printf(false,"%FtIN UNITS OF  %Fspixels%E");
    } else {
        Printf(false,"%FtIN UNITS OF  "
                            "%Fh%D%f%Lh%s%E%Fs%s%E / %Fh%D%f%Lh%s%E%Fs%s%E",
            s->h.v, &ScreenChangeStyleYesNo,
            ( textHeightpx ? "" : "pixels"),
            ( textHeightpx ? "pixels" : ""),
            s->h.v, &ScreenChangeStyleYesNo,
            (!textHeightpx ? "" : SS.UnitName()),
            (!textHeightpx ? SS.UnitName() : ""));
    }

    if(s->h.v >= Style::FIRST_CUSTOM) {
        Printf(true, "%FtTEXT ANGLE   %E%@ %D%f%Ll%Fl[change]%E",
            s->textAngle,
            s->h.v, &ScreenChangeStyleTextAngle);

        bool neither;
        neither = !(s->textOrigin & (Style::ORIGIN_LEFT | Style::ORIGIN_RIGHT));
        Printf(true, "%FtALIGN TEXT   "
                            "%Fh%D%f%LL%s%E%Fs%s%E   / "
                            "%Fh%D%f%LH%s%E%Fs%s%E / "
                            "%Fh%D%f%LR%s%E%Fs%s%E",
            s->h.v, &ScreenChangeStyleYesNo,
            ((s->textOrigin & Style::ORIGIN_LEFT) ? "" : "left"),
            ((s->textOrigin & Style::ORIGIN_LEFT) ? "left" : ""),
            s->h.v, &ScreenChangeStyleYesNo,
            (neither ? "" : "center"),
            (neither ? "center" : ""),
            s->h.v, &ScreenChangeStyleYesNo,
            ((s->textOrigin & Style::ORIGIN_RIGHT) ? "" : "right"),
            ((s->textOrigin & Style::ORIGIN_RIGHT) ? "right" : ""));

        neither = !(s->textOrigin & (Style::ORIGIN_BOT | Style::ORIGIN_TOP));
        Printf(false, "%Ft             "
                            "%Fh%D%f%LB%s%E%Fs%s%E / "
                            "%Fh%D%f%LV%s%E%Fs%s%E / "
                            "%Fh%D%f%LT%s%E%Fs%s%E",
            s->h.v, &ScreenChangeStyleYesNo,
            ((s->textOrigin & Style::ORIGIN_BOT) ? "" : "bottom"),
            ((s->textOrigin & Style::ORIGIN_BOT) ? "bottom" : ""),
            s->h.v, &ScreenChangeStyleYesNo,
            (neither ? "" : "center"),
            (neither ? "center" : ""),
            s->h.v, &ScreenChangeStyleYesNo,
            ((s->textOrigin & Style::ORIGIN_TOP) ? "" : "top"),
            ((s->textOrigin & Style::ORIGIN_TOP) ? "top" : ""));
    }

    if(s->h.v >= Style::FIRST_CUSTOM) {
        Printf(false, "");
        Printf(false,
            "%FtOBJECTS ARE  %Fh%D%f%Lv%s%E%Fs%s%E / %Fh%D%f%Lv%s%E%Fs%s%E",
                s->h.v, &ScreenChangeStyleYesNo,
                ( s->visible ? "" : "shown"),
                ( s->visible ? "shown" : ""),
                s->h.v, &ScreenChangeStyleYesNo,
                (!s->visible ? "" : "hidden"),
                (!s->visible ? "hidden" : ""));
        Printf(false,
            "%Ft             %Fh%D%f%Le%s%E%Fs%s%E / %Fh%D%f%Le%s%E%Fs%s%E",
                s->h.v, &ScreenChangeStyleYesNo,
                ( s->exportable ? "" : "exported"),
                ( s->exportable ? "exported" : ""),
                s->h.v, &ScreenChangeStyleYesNo,
                (!s->exportable ? "" : "not exported"),
                (!s->exportable ? "not exported" : ""));

        Printf(false, "");
        Printf(false, "To assign lines or curves to this style,");
        Printf(false, "select them on the drawing. Then commit");
        Printf(false, "by clicking the link at the bottom of");
        Printf(false, "this window.");
    }
}

void TextWindow::ScreenAssignSelectionToStyle(int link, DWORD v) {
    Style::AssignSelectionToStyle(v);
}

