911 lines
31 KiB
C++
911 lines
31 KiB
C++
//-----------------------------------------------------------------------------
|
|
// Implementation of a cosmetic line style, which determines the color and
|
|
// other appearance of a line or curve on-screen and in exported files. Some
|
|
// styles are predefined, and others can be created by the user.
|
|
//
|
|
// Copyright 2008-2013 Jonathan Westhues.
|
|
//-----------------------------------------------------------------------------
|
|
#include "solvespace.h"
|
|
#include <png.h>
|
|
|
|
#define DEFAULT_TEXT_HEIGHT 11.5
|
|
|
|
const Style::Default Style::Defaults[] = {
|
|
{ { ACTIVE_GRP }, "ActiveGrp", RGBf(1.0, 1.0, 1.0), 1.5, 4 },
|
|
{ { CONSTRUCTION }, "Construction", RGBf(0.1, 0.7, 0.1), 1.5, 0 },
|
|
{ { INACTIVE_GRP }, "InactiveGrp", RGBf(0.5, 0.3, 0.0), 1.5, 3 },
|
|
{ { DATUM }, "Datum", RGBf(0.0, 0.8, 0.0), 1.5, 0 },
|
|
{ { SOLID_EDGE }, "SolidEdge", RGBf(0.8, 0.8, 0.8), 1.0, 2 },
|
|
{ { CONSTRAINT }, "Constraint", RGBf(1.0, 0.1, 1.0), 1.0, 0 },
|
|
{ { SELECTED }, "Selected", RGBf(1.0, 0.0, 0.0), 1.5, 0 },
|
|
{ { HOVERED }, "Hovered", RGBf(1.0, 1.0, 0.0), 1.5, 0 },
|
|
{ { CONTOUR_FILL }, "ContourFill", RGBf(0.0, 0.1, 0.1), 1.0, 0 },
|
|
{ { NORMALS }, "Normals", RGBf(0.0, 0.4, 0.4), 1.0, 0 },
|
|
{ { ANALYZE }, "Analyze", RGBf(0.0, 1.0, 1.0), 1.0, 0 },
|
|
{ { DRAW_ERROR }, "DrawError", RGBf(1.0, 0.0, 0.0), 8.0, 0 },
|
|
{ { DIM_SOLID }, "DimSolid", RGBf(0.1, 0.1, 0.1), 1.0, 0 },
|
|
{ { HIDDEN_EDGE }, "HiddenEdge", RGBf(0.8, 0.8, 0.8), 1.0, 1 },
|
|
{ { OUTLINE }, "Outline", RGBf(0.8, 0.8, 0.8), 3.0, 5 },
|
|
{ { 0 }, NULL, RGBf(0.0, 0.0, 0.0), 0.0, 0 }
|
|
};
|
|
|
|
std::string Style::CnfColor(const std::string &prefix) {
|
|
return "Style_" + prefix + "_Color";
|
|
}
|
|
std::string Style::CnfWidth(const std::string &prefix) {
|
|
return "Style_" + prefix + "_Width";
|
|
}
|
|
std::string Style::CnfTextHeight(const std::string &prefix) {
|
|
return "Style_" + prefix + "_TextHeight";
|
|
}
|
|
|
|
std::string Style::CnfPrefixToName(const std::string &prefix) {
|
|
std::string name = "#def-";
|
|
|
|
for(size_t i = 0; i < prefix.length(); i++) {
|
|
if(isupper(prefix[i]) && i != 0)
|
|
name += '-';
|
|
name += tolower(prefix[i]);
|
|
}
|
|
|
|
return name;
|
|
}
|
|
|
|
void Style::CreateAllDefaultStyles() {
|
|
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 = {};
|
|
FillDefaultStyle(&ns, d);
|
|
ns.h = h;
|
|
if(isDefaultStyle) {
|
|
ns.name = CnfPrefixToName(d->cnfPrefix);
|
|
} else {
|
|
ns.name = "new-custom-style";
|
|
}
|
|
|
|
SK.style.Add(&ns);
|
|
}
|
|
|
|
void Style::FillDefaultStyle(Style *s, const Default *d, bool factory) {
|
|
if(d == NULL) d = &Defaults[0];
|
|
s->color = (factory) ? d->color : CnfThawColor(d->color, CnfColor(d->cnfPrefix));
|
|
s->width = (factory) ? d->width : CnfThawFloat((float)(d->width), CnfWidth(d->cnfPrefix));
|
|
s->widthAs = UnitsAs::PIXELS;
|
|
s->textHeight = (factory) ? DEFAULT_TEXT_HEIGHT
|
|
: CnfThawFloat(DEFAULT_TEXT_HEIGHT, CnfTextHeight(d->cnfPrefix));
|
|
s->textHeightAs = UnitsAs::PIXELS;
|
|
s->textOrigin = TextOrigin::NONE;
|
|
s->textAngle = 0;
|
|
s->visible = true;
|
|
s->exportable = true;
|
|
s->filled = false;
|
|
s->fillColor = RGBf(0.3, 0.3, 0.3);
|
|
s->stippleType = (d->h.v == Style::HIDDEN_EDGE) ? StipplePattern::DASH
|
|
: StipplePattern::CONTINUOUS;
|
|
s->stippleScale = 15.0;
|
|
s->zIndex = d->zIndex;
|
|
}
|
|
|
|
void Style::LoadFactoryDefaults() {
|
|
const Default *d;
|
|
for(d = &(Defaults[0]); d->h.v; d++) {
|
|
Style *s = Get(d->h);
|
|
FillDefaultStyle(s, d, /*factory=*/true);
|
|
}
|
|
SS.backgroundColor = RGBi(0, 0, 0);
|
|
SS.bgImage.pixmap.Clear();
|
|
}
|
|
|
|
void Style::FreezeDefaultStyles() {
|
|
const Default *d;
|
|
for(d = &(Defaults[0]); d->h.v; d++) {
|
|
CnfFreezeColor(Color(d->h), CnfColor(d->cnfPrefix));
|
|
CnfFreezeFloat((float)Width(d->h), CnfWidth(d->cnfPrefix));
|
|
CnfFreezeFloat((float)TextHeight(d->h), CnfTextHeight(d->cnfPrefix));
|
|
}
|
|
}
|
|
|
|
uint32_t Style::CreateCustomStyle(bool rememberForUndo) {
|
|
if(rememberForUndo) SS.UndoRemember();
|
|
uint32_t vs = max((uint32_t)Style::FIRST_CUSTOM, SK.style.MaximumId() + 1);
|
|
hStyle hs = { vs };
|
|
(void)Style::Get(hs);
|
|
return hs.v;
|
|
}
|
|
|
|
void Style::AssignSelectionToStyle(uint32_t 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];
|
|
Entity *e = SK.GetEntity(he);
|
|
if(!e->IsStylable()) continue;
|
|
|
|
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->IsStylable()) 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.ScheduleGenerateAll();
|
|
|
|
// 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.ScheduleShowTW();
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// 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.
|
|
//-----------------------------------------------------------------------------
|
|
RgbaColor Style::Color(int s, bool forExport) {
|
|
hStyle hs = { (uint32_t)s };
|
|
return Color(hs, forExport);
|
|
}
|
|
float Style::Width(int s) {
|
|
hStyle hs = { (uint32_t)s };
|
|
return Width(hs);
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// If a color is almost white, then we can rewrite it to black, just so that
|
|
// it won't disappear on file formats with a light background.
|
|
//-----------------------------------------------------------------------------
|
|
RgbaColor Style::RewriteColor(RgbaColor rgbin) {
|
|
Vector rgb = Vector::From(rgbin.redF(), rgbin.greenF(), rgbin.blueF());
|
|
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 RGBi(0, 0, 0);
|
|
} else {
|
|
return rgbin;
|
|
}
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// Return the stroke color associated with our style as 8-bit RGB.
|
|
//-----------------------------------------------------------------------------
|
|
RgbaColor Style::Color(hStyle h, bool forExport) {
|
|
Style *s = Get(h);
|
|
if(forExport) {
|
|
return RewriteColor(s->color);
|
|
} else {
|
|
return s->color;
|
|
}
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// Return the fill color associated with our style as 8-bit RGB.
|
|
//-----------------------------------------------------------------------------
|
|
RgbaColor Style::FillColor(hStyle h, bool forExport) {
|
|
Style *s = Get(h);
|
|
if(forExport) {
|
|
return RewriteColor(s->fillColor);
|
|
} else {
|
|
return s->fillColor;
|
|
}
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// 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 == UnitsAs::MM) {
|
|
r = s->width * SS.GW.scale;
|
|
} else if(s->widthAs == UnitsAs::PIXELS) {
|
|
r = s->width;
|
|
}
|
|
// This returns a float because ssglLineWidth 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 == UnitsAs::MM) {
|
|
return s->textHeight * SS.GW.scale;
|
|
} else /* s->textHeightAs == UNITS_AS_PIXELS */ {
|
|
return s->textHeight;
|
|
}
|
|
}
|
|
|
|
double Style::DefaultTextHeight() {
|
|
hStyle hs { Style::CONSTRAINT };
|
|
return TextHeight(hs);
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// 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 = { (uint32_t)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;
|
|
}
|
|
|
|
StipplePattern Style::PatternType(hStyle hs) {
|
|
Style *s = Get(hs);
|
|
return s->stippleType;
|
|
}
|
|
|
|
double Style::StippleScaleMm(hStyle hs) {
|
|
Style *s = Get(hs);
|
|
if(s->widthAs == UnitsAs::MM) {
|
|
return s->stippleScale;
|
|
} else if(s->widthAs == UnitsAs::PIXELS) {
|
|
return s->stippleScale / SS.GW.scale;
|
|
}
|
|
return 1.0;
|
|
}
|
|
|
|
std::string Style::DescriptionString() const {
|
|
if(name.empty()) {
|
|
return ssprintf("s%03x-(unnamed)", h.v);
|
|
} else {
|
|
return ssprintf("s%03x-%s", h.v, name.c_str());
|
|
}
|
|
}
|
|
|
|
|
|
void TextWindow::ScreenShowListOfStyles(int link, uint32_t v) {
|
|
SS.TW.GoToScreen(Screen::LIST_OF_STYLES);
|
|
}
|
|
void TextWindow::ScreenShowStyleInfo(int link, uint32_t v) {
|
|
SS.TW.GoToScreen(Screen::STYLE_INFO);
|
|
SS.TW.shown.style.v = v;
|
|
}
|
|
|
|
void TextWindow::ScreenLoadFactoryDefaultStyles(int link, uint32_t v) {
|
|
Style::LoadFactoryDefaults();
|
|
SS.TW.GoToScreen(Screen::LIST_OF_STYLES);
|
|
}
|
|
|
|
void TextWindow::ScreenCreateCustomStyle(int link, uint32_t v) {
|
|
Style::CreateCustomStyle();
|
|
}
|
|
|
|
void TextWindow::ScreenChangeBackgroundColor(int link, uint32_t v) {
|
|
RgbaColor rgb = SS.backgroundColor;
|
|
SS.TW.ShowEditControlWithColorPicker(3, rgb);
|
|
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, uint32_t v) {
|
|
SS.bgImage.pixmap.Clear();
|
|
|
|
if(link == 'l') {
|
|
std::string bgImageFile;
|
|
if(GetOpenFile(&bgImageFile, "", PngFileFilter)) {
|
|
FILE *f = ssfopen(bgImageFile, "rb");
|
|
if(f) {
|
|
SS.bgImage.pixmap = Pixmap::FromPNG(f);
|
|
SS.bgImage.scale = SS.GW.scale;
|
|
SS.bgImage.origin = SS.GW.offset.ScaledBy(-1);
|
|
}
|
|
}
|
|
}
|
|
SS.ScheduleShowTW();
|
|
}
|
|
|
|
void TextWindow::ScreenChangeBackgroundImageScale(int link, uint32_t v) {
|
|
SS.TW.edit.meaning = Edit::BACKGROUND_IMG_SCALE;
|
|
SS.TW.ShowEditControl(10, ssprintf("%.3f", SS.bgImage.scale * SS.MmPerUnit()));
|
|
}
|
|
|
|
void TextWindow::ShowListOfStyles() {
|
|
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 %Bz %Bp %Fl%Ll%f%D%s%E",
|
|
darkbg ? 'd' : 'a',
|
|
&s->color,
|
|
darkbg ? 'd' : 'a',
|
|
ScreenShowStyleInfo, s->h.v,
|
|
s->DescriptionString().c_str());
|
|
|
|
darkbg = !darkbg;
|
|
}
|
|
|
|
Printf(true, " %Fl%Ll%fcreate a new custom style%E",
|
|
&ScreenCreateCustomStyle);
|
|
|
|
Printf(false, "");
|
|
|
|
RgbaColor rgb = SS.backgroundColor;
|
|
Printf(false, "%Ft background color (r, g, b)%E");
|
|
Printf(false, "%Ba %@, %@, %@ %Fl%D%f%Ll[change]%E",
|
|
rgb.redF(), rgb.greenF(), rgb.blueF(),
|
|
top[rows-1] + 2, &ScreenChangeBackgroundColor);
|
|
|
|
Printf(false, "");
|
|
Printf(false, "%Ft background bitmap image%E");
|
|
if(!SS.bgImage.pixmap.IsEmpty()) {
|
|
Printf(false, "%Ba %Ftwidth:%E %dpx %Ftheight:%E %dpx",
|
|
SS.bgImage.pixmap.width, SS.bgImage.pixmap.height);
|
|
|
|
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, uint32_t v) {
|
|
hStyle hs = { v };
|
|
Style *s = Style::Get(hs);
|
|
SS.TW.ShowEditControl(12, s->name);
|
|
SS.TW.edit.style = hs;
|
|
SS.TW.edit.meaning = Edit::STYLE_NAME;
|
|
}
|
|
|
|
void TextWindow::ScreenDeleteStyle(int link, uint32_t 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::ScreenChangeStylePatternType(int link, uint32_t v) {
|
|
hStyle hs = { v };
|
|
Style *s = Style::Get(hs);
|
|
s->stippleType = (StipplePattern)(link - 1);
|
|
}
|
|
|
|
void TextWindow::ScreenChangeStyleMetric(int link, uint32_t v) {
|
|
hStyle hs = { v };
|
|
Style *s = Style::Get(hs);
|
|
double val;
|
|
Style::UnitsAs units;
|
|
Edit meaning;
|
|
int col;
|
|
switch(link) {
|
|
case 't':
|
|
val = s->textHeight;
|
|
units = s->textHeightAs;
|
|
col = 10;
|
|
meaning = Edit::STYLE_TEXT_HEIGHT;
|
|
break;
|
|
|
|
case 's':
|
|
val = s->stippleScale;
|
|
units = s->widthAs;
|
|
col = 17;
|
|
meaning = Edit::STYLE_STIPPLE_PERIOD;
|
|
break;
|
|
|
|
case 'w':
|
|
case 'W':
|
|
val = s->width;
|
|
units = s->widthAs;
|
|
col = 9;
|
|
meaning = Edit::STYLE_WIDTH;
|
|
break;
|
|
|
|
default: ssassert(false, "Unexpected link");
|
|
}
|
|
|
|
std::string edit_value;
|
|
if(units == Style::UnitsAs::PIXELS) {
|
|
edit_value = ssprintf("%.2f", val);
|
|
} else {
|
|
edit_value = SS.MmToString(val);
|
|
}
|
|
SS.TW.ShowEditControl(col, edit_value);
|
|
SS.TW.edit.style = hs;
|
|
SS.TW.edit.meaning = meaning;
|
|
}
|
|
|
|
void TextWindow::ScreenChangeStyleTextAngle(int link, uint32_t v) {
|
|
hStyle hs = { v };
|
|
Style *s = Style::Get(hs);
|
|
SS.TW.ShowEditControl(9, ssprintf("%.2f", s->textAngle));
|
|
SS.TW.edit.style = hs;
|
|
SS.TW.edit.meaning = Edit::STYLE_TEXT_ANGLE;
|
|
}
|
|
|
|
void TextWindow::ScreenChangeStyleColor(int link, uint32_t v) {
|
|
hStyle hs = { v };
|
|
Style *s = Style::Get(hs);
|
|
// Same function used for stroke and fill colors
|
|
Edit em;
|
|
RgbaColor rgb;
|
|
if(link == 's') {
|
|
em = Edit::STYLE_COLOR;
|
|
rgb = s->color;
|
|
} else if(link == 'f') {
|
|
em = Edit::STYLE_FILL_COLOR;
|
|
rgb = s->fillColor;
|
|
} else ssassert(false, "Unexpected link");
|
|
SS.TW.ShowEditControlWithColorPicker(13, rgb);
|
|
SS.TW.edit.style = hs;
|
|
SS.TW.edit.meaning = em;
|
|
}
|
|
|
|
void TextWindow::ScreenChangeStyleYesNo(int link, uint32_t v) {
|
|
SS.UndoRemember();
|
|
hStyle hs = { v };
|
|
Style *s = Style::Get(hs);
|
|
switch(link) {
|
|
// Units for the width
|
|
case 'w':
|
|
if(s->widthAs != Style::UnitsAs::MM) {
|
|
s->widthAs = Style::UnitsAs::MM;
|
|
s->width /= SS.GW.scale;
|
|
s->stippleScale /= SS.GW.scale;
|
|
}
|
|
break;
|
|
case 'W':
|
|
if(s->widthAs != Style::UnitsAs::PIXELS) {
|
|
s->widthAs = Style::UnitsAs::PIXELS;
|
|
s->width *= SS.GW.scale;
|
|
s->stippleScale *= SS.GW.scale;
|
|
}
|
|
break;
|
|
|
|
// Units for the height
|
|
case 'g':
|
|
if(s->textHeightAs != Style::UnitsAs::MM) {
|
|
s->textHeightAs = Style::UnitsAs::MM;
|
|
s->textHeight /= SS.GW.scale;
|
|
}
|
|
break;
|
|
|
|
case 'G':
|
|
if(s->textHeightAs != Style::UnitsAs::PIXELS) {
|
|
s->textHeightAs = Style::UnitsAs::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::TextOrigin)((uint32_t)s->textOrigin | (uint32_t)Style::TextOrigin::LEFT);
|
|
s->textOrigin = (Style::TextOrigin)((uint32_t)s->textOrigin & ~(uint32_t)Style::TextOrigin::RIGHT);
|
|
break;
|
|
case 'H':
|
|
s->textOrigin = (Style::TextOrigin)((uint32_t)s->textOrigin & ~(uint32_t)Style::TextOrigin::LEFT);
|
|
s->textOrigin = (Style::TextOrigin)((uint32_t)s->textOrigin & ~(uint32_t)Style::TextOrigin::RIGHT);
|
|
break;
|
|
case 'R':
|
|
s->textOrigin = (Style::TextOrigin)((uint32_t)s->textOrigin & ~(uint32_t)Style::TextOrigin::LEFT);
|
|
s->textOrigin = (Style::TextOrigin)((uint32_t)s->textOrigin | (uint32_t)Style::TextOrigin::RIGHT);
|
|
break;
|
|
|
|
// Vertical text alignment
|
|
case 'B':
|
|
s->textOrigin = (Style::TextOrigin)((uint32_t)s->textOrigin | (uint32_t)Style::TextOrigin::BOT);
|
|
s->textOrigin = (Style::TextOrigin)((uint32_t)s->textOrigin & ~(uint32_t)Style::TextOrigin::TOP);
|
|
break;
|
|
case 'V':
|
|
s->textOrigin = (Style::TextOrigin)((uint32_t)s->textOrigin & ~(uint32_t)Style::TextOrigin::BOT);
|
|
s->textOrigin = (Style::TextOrigin)((uint32_t)s->textOrigin & ~(uint32_t)Style::TextOrigin::TOP);
|
|
break;
|
|
case 'T':
|
|
s->textOrigin = (Style::TextOrigin)((uint32_t)s->textOrigin & ~(uint32_t)Style::TextOrigin::BOT);
|
|
s->textOrigin = (Style::TextOrigin)((uint32_t)s->textOrigin | (uint32_t)Style::TextOrigin::TOP);
|
|
break;
|
|
}
|
|
InvalidateGraphics();
|
|
}
|
|
|
|
bool TextWindow::EditControlDoneForStyles(const char *str) {
|
|
Style *s;
|
|
switch(edit.meaning) {
|
|
case Edit::STYLE_STIPPLE_PERIOD:
|
|
case Edit::STYLE_TEXT_HEIGHT:
|
|
case Edit::STYLE_WIDTH: {
|
|
SS.UndoRemember();
|
|
s = Style::Get(edit.style);
|
|
|
|
double v;
|
|
Style::UnitsAs units = (edit.meaning == Edit::STYLE_TEXT_HEIGHT) ?
|
|
s->textHeightAs : s->widthAs;
|
|
if(units == Style::UnitsAs::MM) {
|
|
v = SS.StringToMm(str);
|
|
} else {
|
|
v = atof(str);
|
|
}
|
|
v = max(0.0, v);
|
|
if(edit.meaning == Edit::STYLE_TEXT_HEIGHT) {
|
|
s->textHeight = v;
|
|
} else if(edit.meaning == Edit::STYLE_STIPPLE_PERIOD) {
|
|
s->stippleScale = 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: {
|
|
Vector rgb;
|
|
if(sscanf(str, "%lf, %lf, %lf", &rgb.x, &rgb.y, &rgb.z)==3) {
|
|
rgb = rgb.ClampWithin(0, 1);
|
|
if(edit.meaning == Edit::STYLE_COLOR) {
|
|
SS.UndoRemember();
|
|
s = Style::Get(edit.style);
|
|
s->color = RGBf(rgb.x, rgb.y, rgb.z);
|
|
} else if(edit.meaning == Edit::STYLE_FILL_COLOR) {
|
|
SS.UndoRemember();
|
|
s = Style::Get(edit.style);
|
|
s->fillColor = RGBf(rgb.x, rgb.y, rgb.z);
|
|
} else {
|
|
SS.backgroundColor = RGBf(rgb.x, rgb.y, rgb.z);
|
|
}
|
|
} else {
|
|
Error("Bad format: specify color as r, g, b");
|
|
}
|
|
break;
|
|
}
|
|
case Edit::STYLE_NAME:
|
|
if(!*str) {
|
|
Error("Style name cannot be empty");
|
|
} else {
|
|
SS.UndoRemember();
|
|
s = Style::Get(edit.style);
|
|
s->name = 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() {
|
|
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().c_str());
|
|
} else {
|
|
Printf(true, "%FtSTYLE %E%s "
|
|
"[%Fl%Ll%D%frename%E/%Fl%Ll%D%fdel%E]",
|
|
s->DescriptionString().c_str(),
|
|
s->h.v, &ScreenChangeStyleName,
|
|
s->h.v, &ScreenDeleteStyle);
|
|
}
|
|
Printf(true, "%Ft line stroke style%E");
|
|
Printf(false, "%Ba %Ftcolor %E%Bz %Ba (%@, %@, %@) %D%f%Ls%Fl[change]%E",
|
|
&s->color,
|
|
s->color.redF(), s->color.greenF(), s->color.blueF(),
|
|
s->h.v, ScreenChangeStyleColor);
|
|
|
|
// The line width, and its units
|
|
if(s->widthAs == Style::UnitsAs::PIXELS) {
|
|
Printf(false, " %Ftwidth%E %@ %D%f%Lp%Fl[change]%E",
|
|
s->width,
|
|
s->h.v, &ScreenChangeStyleMetric,
|
|
(s->h.v < Style::FIRST_CUSTOM) ? 'w' : 'W');
|
|
} else {
|
|
Printf(false, " %Ftwidth%E %s %D%f%Lp%Fl[change]%E",
|
|
SS.MmToString(s->width).c_str(),
|
|
s->h.v, &ScreenChangeStyleMetric,
|
|
(s->h.v < Style::FIRST_CUSTOM) ? 'w' : 'W');
|
|
}
|
|
|
|
if(s->widthAs == Style::UnitsAs::PIXELS) {
|
|
Printf(false, "%Ba %Ftstipple width%E %@ %D%f%Lp%Fl[change]%E",
|
|
s->stippleScale,
|
|
s->h.v, &ScreenChangeStyleMetric, 's');
|
|
} else {
|
|
Printf(false, "%Ba %Ftstipple width%E %s %D%f%Lp%Fl[change]%E",
|
|
SS.MmToString(s->stippleScale).c_str(),
|
|
s->h.v, &ScreenChangeStyleMetric, 's');
|
|
}
|
|
|
|
bool widthpx = (s->widthAs == Style::UnitsAs::PIXELS);
|
|
if(s->h.v < Style::FIRST_CUSTOM) {
|
|
Printf(false," %Ftin units of %Fdpixels%E");
|
|
} else {
|
|
Printf(false,"%Ba %Ftin units of %Fd"
|
|
"%D%f%LW%s pixels%E "
|
|
"%D%f%Lw%s %s",
|
|
s->h.v, &ScreenChangeStyleYesNo,
|
|
widthpx ? RADIO_TRUE : RADIO_FALSE,
|
|
s->h.v, &ScreenChangeStyleYesNo,
|
|
!widthpx ? RADIO_TRUE : RADIO_FALSE,
|
|
SS.UnitName());
|
|
}
|
|
|
|
Printf(false,"%Ba %Ftstipple type:%E");
|
|
|
|
const size_t patternCount = (size_t)StipplePattern::LAST + 1;
|
|
const char *patternsSource[patternCount] = {
|
|
"___________",
|
|
"- - - - ",
|
|
"- - - - - -",
|
|
"__ __ __ __",
|
|
"-.-.-.-.-.-",
|
|
"..-..-..-..",
|
|
"...........",
|
|
"~~~~~~~~~~~",
|
|
"__~__~__~__"
|
|
};
|
|
std::string patterns[patternCount];
|
|
|
|
for(uint32_t i = 0; i <= (uint32_t)StipplePattern::LAST; i++) {
|
|
const char *str = patternsSource[i];
|
|
do {
|
|
switch(*str) {
|
|
case ' ': patterns[i] += " "; break;
|
|
case '.': patterns[i] += "\xEE\x80\x84"; break;
|
|
case '_': patterns[i] += "\xEE\x80\x85"; break;
|
|
case '-': patterns[i] += "\xEE\x80\x86"; break;
|
|
case '~': patterns[i] += "\xEE\x80\x87"; break;
|
|
default: ssassert(false, "Unexpected stipple pattern element");
|
|
}
|
|
} while(*(++str));
|
|
}
|
|
|
|
for(uint32_t i = 0; i <= (uint32_t)StipplePattern::LAST; i++) {
|
|
const char *radio = s->stippleType == (StipplePattern)i ? RADIO_TRUE : RADIO_FALSE;
|
|
Printf(false, "%Bp %D%f%Lp%s %s%E",
|
|
(i % 2 == 0) ? 'd' : 'a',
|
|
s->h.v, &ScreenChangeStylePatternType,
|
|
i + 1, radio, patterns[i].c_str());
|
|
}
|
|
|
|
if(s->h.v >= Style::FIRST_CUSTOM) {
|
|
// The fill color, and whether contours are filled
|
|
|
|
Printf(false, "");
|
|
Printf(false, "%Ft contour fill style%E");
|
|
Printf(false,
|
|
"%Ba %Ftcolor %E%Bz %Ba (%@, %@, %@) %D%f%Lf%Fl[change]%E",
|
|
&s->fillColor,
|
|
s->fillColor.redF(), s->fillColor.greenF(), s->fillColor.blueF(),
|
|
s->h.v, ScreenChangeStyleColor);
|
|
|
|
Printf(false, "%Bd %D%f%Lf%s contours are filled%E",
|
|
s->h.v, &ScreenChangeStyleYesNo,
|
|
s->filled ? CHECK_TRUE : CHECK_FALSE);
|
|
}
|
|
|
|
// The text height, and its units
|
|
Printf(false, "");
|
|
Printf(false, "%Ft text style%E");
|
|
|
|
if(s->textHeightAs == Style::UnitsAs::PIXELS) {
|
|
Printf(false, "%Ba %Ftheight %E%@ %D%f%Lt%Fl%s%E",
|
|
s->textHeight,
|
|
s->h.v, &ScreenChangeStyleMetric,
|
|
"[change]");
|
|
} else {
|
|
Printf(false, "%Ba %Ftheight %E%s %D%f%Lt%Fl%s%E",
|
|
SS.MmToString(s->textHeight).c_str(),
|
|
s->h.v, &ScreenChangeStyleMetric,
|
|
"[change]");
|
|
}
|
|
|
|
bool textHeightpx = (s->textHeightAs == Style::UnitsAs::PIXELS);
|
|
if(s->h.v < Style::FIRST_CUSTOM) {
|
|
Printf(false,"%Bd %Ftin units of %Fdpixels");
|
|
} else {
|
|
Printf(false,"%Bd %Ftin units of %Fd"
|
|
"%D%f%LG%s pixels%E "
|
|
"%D%f%Lg%s %s",
|
|
s->h.v, &ScreenChangeStyleYesNo,
|
|
textHeightpx ? RADIO_TRUE : RADIO_FALSE,
|
|
s->h.v, &ScreenChangeStyleYesNo,
|
|
!textHeightpx ? RADIO_TRUE : RADIO_FALSE,
|
|
SS.UnitName());
|
|
}
|
|
|
|
if(s->h.v >= Style::FIRST_CUSTOM) {
|
|
Printf(false, "%Ba %Ftangle %E%@ %D%f%Ll%Fl[change]%E",
|
|
s->textAngle,
|
|
s->h.v, &ScreenChangeStyleTextAngle);
|
|
|
|
Printf(false, "");
|
|
Printf(false, "%Ft text comment alignment%E");
|
|
bool neither;
|
|
neither = !((uint32_t)s->textOrigin & ((uint32_t)Style::TextOrigin::LEFT | (uint32_t)Style::TextOrigin::RIGHT));
|
|
Printf(false, "%Ba "
|
|
"%D%f%LL%s left%E "
|
|
"%D%f%LH%s center%E "
|
|
"%D%f%LR%s right%E ",
|
|
s->h.v, &ScreenChangeStyleYesNo,
|
|
((uint32_t)s->textOrigin & (uint32_t)Style::TextOrigin::LEFT) ? RADIO_TRUE : RADIO_FALSE,
|
|
s->h.v, &ScreenChangeStyleYesNo,
|
|
neither ? RADIO_TRUE : RADIO_FALSE,
|
|
s->h.v, &ScreenChangeStyleYesNo,
|
|
((uint32_t)s->textOrigin & (uint32_t)Style::TextOrigin::RIGHT) ? RADIO_TRUE : RADIO_FALSE);
|
|
|
|
neither = !((uint32_t)s->textOrigin & ((uint32_t)Style::TextOrigin::BOT | (uint32_t)Style::TextOrigin::TOP));
|
|
Printf(false, "%Bd "
|
|
"%D%f%LB%s bottom%E "
|
|
"%D%f%LV%s center%E "
|
|
"%D%f%LT%s top%E ",
|
|
s->h.v, &ScreenChangeStyleYesNo,
|
|
((uint32_t)s->textOrigin & (uint32_t)Style::TextOrigin::BOT) ? RADIO_TRUE : RADIO_FALSE,
|
|
s->h.v, &ScreenChangeStyleYesNo,
|
|
neither ? RADIO_TRUE : RADIO_FALSE,
|
|
s->h.v, &ScreenChangeStyleYesNo,
|
|
((uint32_t)s->textOrigin & (uint32_t)Style::TextOrigin::TOP) ? RADIO_TRUE : RADIO_FALSE);
|
|
}
|
|
|
|
if(s->h.v >= Style::FIRST_CUSTOM) {
|
|
Printf(false, "");
|
|
|
|
Printf(false, " %Fd%D%f%Lv%s show these objects on screen%E",
|
|
s->h.v, &ScreenChangeStyleYesNo,
|
|
s->visible ? CHECK_TRUE : CHECK_FALSE);
|
|
|
|
Printf(false, " %Fd%D%f%Le%s export these objects%E",
|
|
s->h.v, &ScreenChangeStyleYesNo,
|
|
s->exportable ? CHECK_TRUE : CHECK_FALSE);
|
|
|
|
Printf(false, "");
|
|
Printf(false, "To assign lines or curves to this style,");
|
|
Printf(false, "right-click them on the drawing.");
|
|
}
|
|
}
|
|
|
|
void TextWindow::ScreenAssignSelectionToStyle(int link, uint32_t v) {
|
|
Style::AssignSelectionToStyle(v);
|
|
}
|
|
|