Solvespace
944 строки · 32.9 Кб
1//-----------------------------------------------------------------------------
2// Implementation of a cosmetic line style, which determines the color and
3// other appearance of a line or curve on-screen and in exported files. Some
4// styles are predefined, and others can be created by the user.
5//
6// Copyright 2008-2013 Jonathan Westhues.
7//-----------------------------------------------------------------------------
8#include "solvespace.h"
9
10const Style::Default Style::Defaults[] = {
11{ { ACTIVE_GRP }, "ActiveGrp", RGBf(1.0, 1.0, 1.0), 1.5, 4, true, StipplePattern::CONTINUOUS },
12{ { CONSTRUCTION }, "Construction", RGBf(0.1, 0.7, 0.1), 1.5, 0, false, StipplePattern::CONTINUOUS },
13{ { INACTIVE_GRP }, "InactiveGrp", RGBf(0.5, 0.3, 0.0), 1.5, 3, true, StipplePattern::CONTINUOUS },
14{ { DATUM }, "Datum", RGBf(0.0, 0.8, 0.0), 1.5, 0, true, StipplePattern::CONTINUOUS },
15{ { SOLID_EDGE }, "SolidEdge", RGBf(0.8, 0.8, 0.8), 1.0, 2, true, StipplePattern::CONTINUOUS },
16{ { CONSTRAINT }, "Constraint", RGBf(1.0, 0.1, 1.0), 1.0, 0, true, StipplePattern::CONTINUOUS },
17{ { SELECTED }, "Selected", RGBf(1.0, 0.0, 0.0), 1.5, 0, true, StipplePattern::CONTINUOUS },
18{ { HOVERED }, "Hovered", RGBf(1.0, 1.0, 0.0), 1.5, 0, true, StipplePattern::CONTINUOUS },
19{ { CONTOUR_FILL }, "ContourFill", RGBf(0.0, 0.1, 0.1), 1.0, 0, true, StipplePattern::CONTINUOUS },
20{ { NORMALS }, "Normals", RGBf(0.0, 0.4, 0.4), 1.0, 0, true, StipplePattern::CONTINUOUS },
21{ { ANALYZE }, "Analyze", RGBf(0.0, 1.0, 1.0), 3.0, 0, true, StipplePattern::CONTINUOUS },
22{ { DRAW_ERROR }, "DrawError", RGBf(1.0, 0.0, 0.0), 8.0, 0, true, StipplePattern::CONTINUOUS },
23{ { DIM_SOLID }, "DimSolid", RGBf(0.1, 0.1, 0.1), 1.0, 0, true, StipplePattern::CONTINUOUS },
24{ { HIDDEN_EDGE }, "HiddenEdge", RGBf(0.8, 0.8, 0.8), 1.0, 1, true, StipplePattern::DASH },
25{ { OUTLINE }, "Outline", RGBf(0.8, 0.8, 0.8), 3.0, 5, true, StipplePattern::CONTINUOUS },
26{ { 0 }, NULL, RGBf(0.0, 0.0, 0.0), 0.0, 0, true, StipplePattern::CONTINUOUS }
27};
28
29std::string Style::CnfColor(const std::string &prefix) {
30return "Style_" + prefix + "_Color";
31}
32std::string Style::CnfWidth(const std::string &prefix) {
33return "Style_" + prefix + "_Width";
34}
35std::string Style::CnfStippleType(const std::string &prefix) {
36return "Style_" + prefix + "_StippleType";
37}
38std::string Style::CnfStippleScale(const std::string &prefix) {
39return "Style_" + prefix + "_StippleScale";
40}
41std::string Style::CnfTextHeight(const std::string &prefix) {
42return "Style_" + prefix + "_TextHeight";
43}
44std::string Style::CnfExportable(const std::string &prefix) {
45return "Style_" + prefix + "_Exportable";
46}
47
48std::string Style::CnfPrefixToName(const std::string &prefix) {
49std::string name = "#def-";
50
51for(size_t i = 0; i < prefix.length(); i++) {
52if(isupper(prefix[i]) && i != 0)
53name += '-';
54name += tolower(prefix[i]);
55}
56
57return name;
58}
59
60void Style::CreateAllDefaultStyles() {
61const Default *d;
62for(d = &(Defaults[0]); d->h.v; d++) {
63(void)Get(d->h);
64}
65}
66
67void Style::CreateDefaultStyle(hStyle h) {
68bool isDefaultStyle = true;
69const Default *d;
70for(d = &(Defaults[0]); d->h.v; d++) {
71if(d->h == h) break;
72}
73if(!d->h.v) {
74// Not a default style; so just create it the same as our default
75// active group entity style.
76d = &(Defaults[0]);
77isDefaultStyle = false;
78}
79
80Style ns = {};
81FillDefaultStyle(&ns, d);
82ns.h = h;
83if(isDefaultStyle) {
84ns.name = CnfPrefixToName(d->cnfPrefix);
85} else {
86ns.name = "new-custom-style";
87}
88
89SK.style.Add(&ns);
90}
91
92void Style::FillDefaultStyle(Style *s, const Default *d, bool factory) {
93Platform::SettingsRef settings = Platform::GetSettings();
94
95if(d == NULL) d = &Defaults[0];
96s->color = (factory)
97? d->color
98: settings->ThawColor(CnfColor(d->cnfPrefix), d->color);
99s->width = (factory)
100? d->width
101: settings->ThawFloat(CnfWidth(d->cnfPrefix), (float)(d->width));
102s->widthAs = UnitsAs::PIXELS;
103s->textHeight = (factory) ? 11.5
104: settings->ThawFloat(CnfTextHeight(d->cnfPrefix), 11.5);
105s->textHeightAs = UnitsAs::PIXELS;
106s->textOrigin = TextOrigin::NONE;
107s->textAngle = 0;
108s->visible = true;
109s->exportable = (factory)
110? d->exportable
111: settings->ThawBool(CnfExportable(d->cnfPrefix), d->exportable);
112s->filled = false;
113s->fillColor = RGBf(0.3, 0.3, 0.3);
114s->stippleType = (factory)
115? d->stippleType
116: Style::StipplePatternFromString(
117settings->ThawString(CnfStippleType(d->cnfPrefix),
118StipplePatternName(d->stippleType)));
119s->stippleScale = (factory)
120? 15.0
121: settings->ThawFloat(CnfStippleScale(d->cnfPrefix), 15.0);
122s->zIndex = d->zIndex;
123}
124
125void Style::LoadFactoryDefaults() {
126const Default *d;
127for(d = &(Defaults[0]); d->h.v; d++) {
128Style *s = Get(d->h);
129FillDefaultStyle(s, d, /*factory=*/true);
130}
131SS.backgroundColor = RGBi(0, 0, 0);
132}
133
134void Style::FreezeDefaultStyles(Platform::SettingsRef settings) {
135const Default *d;
136for(d = &(Defaults[0]); d->h.v; d++) {
137settings->FreezeColor(CnfColor(d->cnfPrefix), Color(d->h));
138settings->FreezeFloat(CnfWidth(d->cnfPrefix), (float)Width(d->h));
139settings->FreezeString(CnfStippleType(d->cnfPrefix), StipplePatternName(d->h));
140settings->FreezeFloat(CnfStippleScale(d->cnfPrefix), (float)StippleScale(d->h));
141settings->FreezeFloat(CnfTextHeight(d->cnfPrefix), (float)TextHeight(d->h));
142settings->FreezeBool(CnfExportable(d->cnfPrefix), Exportable(d->h.v));
143}
144}
145
146uint32_t Style::CreateCustomStyle(bool rememberForUndo) {
147if(rememberForUndo) SS.UndoRemember();
148uint32_t vs = max((uint32_t)Style::FIRST_CUSTOM, SK.style.MaximumId() + 1);
149hStyle hs = { vs };
150(void)Style::Get(hs);
151return hs.v;
152}
153
154void Style::AssignSelectionToStyle(uint32_t v) {
155bool showError = false;
156SS.GW.GroupSelection();
157
158SS.UndoRemember();
159int i;
160for(i = 0; i < SS.GW.gs.entities; i++) {
161hEntity he = SS.GW.gs.entity[i];
162Entity *e = SK.GetEntity(he);
163if(!e->IsStylable()) continue;
164
165if(!he.isFromRequest()) {
166showError = true;
167continue;
168}
169
170hRequest hr = he.request();
171Request *r = SK.GetRequest(hr);
172r->style.v = v;
173SS.MarkGroupDirty(r->group);
174}
175for(i = 0; i < SS.GW.gs.constraints; i++) {
176hConstraint hc = SS.GW.gs.constraint[i];
177Constraint *c = SK.GetConstraint(hc);
178if(!c->IsStylable()) continue;
179
180c->disp.style.v = v;
181SS.MarkGroupDirty(c->group);
182}
183
184if(showError) {
185Error(_("Can't assign style to an entity that's derived from another "
186"entity; try assigning a style to this entity's parent."));
187}
188
189SS.GW.ClearSelection();
190SS.GW.Invalidate();
191
192// And show that style's info screen in the text window.
193SS.TW.GoToScreen(TextWindow::Screen::STYLE_INFO);
194SS.TW.shown.style.v = v;
195SS.ScheduleShowTW();
196}
197
198//-----------------------------------------------------------------------------
199// Look up a style by its handle. If that style does not exist, then create
200// the style, according to our table of default styles.
201//-----------------------------------------------------------------------------
202Style *Style::Get(hStyle h) {
203if(h.v == 0) h.v = ACTIVE_GRP;
204
205Style *s = SK.style.FindByIdNoOops(h);
206if(s) {
207// It exists, good.
208return s;
209} else {
210// It doesn't exist; so we should create it and then return that.
211CreateDefaultStyle(h);
212return SK.style.FindById(h);
213}
214}
215
216//-----------------------------------------------------------------------------
217// A couple of wrappers, so that I can call these functions with either an
218// hStyle or with the integer corresponding to that hStyle.v.
219//-----------------------------------------------------------------------------
220RgbaColor Style::Color(int s, bool forExport) {
221hStyle hs = { (uint32_t)s };
222return Color(hs, forExport);
223}
224double Style::Width(int s) {
225hStyle hs = { (uint32_t)s };
226return Width(hs);
227}
228
229//-----------------------------------------------------------------------------
230// If a color is almost white, then we can rewrite it to black, just so that
231// it won't disappear on file formats with a light background.
232//-----------------------------------------------------------------------------
233RgbaColor Style::RewriteColor(RgbaColor rgbin) {
234Vector rgb = Vector::From(rgbin.redF(), rgbin.greenF(), rgbin.blueF());
235rgb = rgb.Minus(Vector::From(1, 1, 1));
236if(rgb.Magnitude() < 0.4 && SS.fixExportColors) {
237// This is an almost-white color in a default style, which is
238// good for the default on-screen view (black bg) but probably
239// not desired in the exported files, which typically are shown
240// against white backgrounds.
241return RGBi(0, 0, 0);
242} else {
243return rgbin;
244}
245}
246
247//-----------------------------------------------------------------------------
248// Return the stroke color associated with our style as 8-bit RGB.
249//-----------------------------------------------------------------------------
250RgbaColor Style::Color(hStyle h, bool forExport) {
251Style *s = Get(h);
252if(forExport) {
253return RewriteColor(s->color);
254} else {
255return s->color;
256}
257}
258
259//-----------------------------------------------------------------------------
260// Return the fill color associated with our style as 8-bit RGB.
261//-----------------------------------------------------------------------------
262RgbaColor Style::FillColor(hStyle h, bool forExport) {
263Style *s = Get(h);
264if(forExport) {
265return RewriteColor(s->fillColor);
266} else {
267return s->fillColor;
268}
269}
270
271//-----------------------------------------------------------------------------
272// Return the width associated with our style in pixels..
273//-----------------------------------------------------------------------------
274double Style::Width(hStyle h) {
275Style *s = Get(h);
276switch(s->widthAs) {
277case UnitsAs::MM: return s->width * SS.GW.scale;
278case UnitsAs::PIXELS: return s->width;
279}
280ssassert(false, "Unexpected units");
281}
282
283//-----------------------------------------------------------------------------
284// Return the width associated with our style in millimeters..
285//-----------------------------------------------------------------------------
286double Style::WidthMm(int hs) {
287double widthpx = Width(hs);
288return widthpx / SS.GW.scale;
289}
290
291//-----------------------------------------------------------------------------
292// Return the associated text height, in pixels.
293//-----------------------------------------------------------------------------
294double Style::TextHeight(hStyle h) {
295Style *s = Get(h);
296switch(s->textHeightAs) {
297case UnitsAs::MM: return s->textHeight * SS.GW.scale;
298case UnitsAs::PIXELS: return s->textHeight;
299}
300ssassert(false, "Unexpected units");
301}
302
303double Style::DefaultTextHeight() {
304hStyle hs { Style::CONSTRAINT };
305return TextHeight(hs);
306}
307
308//-----------------------------------------------------------------------------
309// Return the parameters of this style, as a canvas stroke.
310//-----------------------------------------------------------------------------
311Canvas::Stroke Style::Stroke(hStyle hs) {
312Canvas::Stroke stroke = {};
313Style *style = Style::Get(hs);
314stroke.color = style->color;
315stroke.stipplePattern = style->stippleType;
316stroke.stippleScale = style->stippleScale;
317stroke.width = style->width;
318switch(style->widthAs) {
319case Style::UnitsAs::PIXELS:
320stroke.unit = Canvas::Unit::PX;
321break;
322case Style::UnitsAs::MM:
323stroke.unit = Canvas::Unit::MM;
324break;
325}
326return stroke;
327}
328
329Canvas::Stroke Style::Stroke(int hsv) {
330hStyle hs = { (uint32_t) hsv };
331return Style::Stroke(hs);
332}
333
334//-----------------------------------------------------------------------------
335// Should lines and curves from this style appear in the output file? Only
336// if it's both shown and exportable.
337//-----------------------------------------------------------------------------
338bool Style::Exportable(int si) {
339hStyle hs = { (uint32_t)si };
340Style *s = Get(hs);
341return (s->exportable) && (s->visible);
342}
343
344//-----------------------------------------------------------------------------
345// Return the appropriate style for our entity. If the entity has a style
346// explicitly assigned, then it's that style. Otherwise it's the appropriate
347// default style.
348//-----------------------------------------------------------------------------
349hStyle Style::ForEntity(hEntity he) {
350Entity *e = SK.GetEntity(he);
351// If the entity has a special style, use that. If that style doesn't
352// exist yet, then it will get created automatically later.
353if(e->style.v != 0) {
354return e->style;
355}
356
357// Otherwise, we use the default rules.
358hStyle hs;
359if(e->group != SS.GW.activeGroup) {
360hs.v = INACTIVE_GRP;
361} else if(e->construction) {
362hs.v = CONSTRUCTION;
363} else {
364hs.v = ACTIVE_GRP;
365}
366return hs;
367}
368
369StipplePattern Style::StipplePatternFromString(std::string name) {
370std::transform(name.begin(), name.end(), name.begin(), ::tolower);
371if(name == "continuous") {
372return StipplePattern::CONTINUOUS;
373} else if(name == "shortdash") {
374return StipplePattern::SHORT_DASH;
375} else if(name == "dash") {
376return StipplePattern::DASH;
377} else if(name == "longdash") {
378return StipplePattern::LONG_DASH;
379} else if(name == "dashdot") {
380return StipplePattern::DASH_DOT;
381} else if(name == "dashdotdot") {
382return StipplePattern::DASH_DOT_DOT;
383} else if(name == "dot") {
384return StipplePattern::DOT;
385} else if(name == "freehand") {
386return StipplePattern::FREEHAND;
387} else if(name == "zigzag") {
388return StipplePattern::ZIGZAG;
389}
390
391return StipplePattern::CONTINUOUS;
392}
393
394StipplePattern Style::PatternType(hStyle hs) {
395Style *s = Get(hs);
396return s->stippleType;
397}
398
399std::string Style::StipplePatternName(hStyle hs) {
400Style *s = Get(hs);
401return StipplePatternName(s->stippleType);
402}
403
404std::string Style::StipplePatternName(StipplePattern stippleType) {
405switch(stippleType) {
406case StipplePattern::CONTINUOUS: return "Continuous";
407case StipplePattern::SHORT_DASH: return "ShortDash";
408case StipplePattern::DASH: return "Dash";
409case StipplePattern::LONG_DASH: return "LongDash";
410case StipplePattern::DASH_DOT: return "DashDot";
411case StipplePattern::DASH_DOT_DOT: return "DashDotDot";
412case StipplePattern::DOT: return "Dot";
413case StipplePattern::FREEHAND: return "FreeHand";
414case StipplePattern::ZIGZAG: return "ZigZag";
415}
416
417return "Continuous";
418}
419
420double Style::StippleScale(hStyle hs) {
421Style *s = Get(hs);
422return s->stippleScale;
423}
424
425double Style::StippleScaleMm(hStyle hs) {
426Style *s = Get(hs);
427if(s->widthAs == UnitsAs::MM) {
428return s->stippleScale;
429} else if(s->widthAs == UnitsAs::PIXELS) {
430return s->stippleScale / SS.GW.scale;
431}
432return 1.0;
433}
434
435std::string Style::DescriptionString() const {
436if(name.empty()) {
437return ssprintf("s%03x-(unnamed)", h.v);
438} else {
439return ssprintf("s%03x-%s", h.v, name.c_str());
440}
441}
442
443
444void TextWindow::ScreenShowListOfStyles(int link, uint32_t v) {
445SS.TW.GoToScreen(Screen::LIST_OF_STYLES);
446}
447void TextWindow::ScreenShowStyleInfo(int link, uint32_t v) {
448GraphicsWindow::MenuEdit(Command::UNSELECT_ALL);
449SS.TW.GoToScreen(Screen::STYLE_INFO);
450SS.TW.shown.style.v = v;
451}
452
453void TextWindow::ScreenLoadFactoryDefaultStyles(int link, uint32_t v) {
454Style::LoadFactoryDefaults();
455SS.TW.GoToScreen(Screen::LIST_OF_STYLES);
456SS.GW.persistentDirty = true;
457}
458
459void TextWindow::ScreenCreateCustomStyle(int link, uint32_t v) {
460Style::CreateCustomStyle();
461}
462
463void TextWindow::ScreenChangeBackgroundColor(int link, uint32_t v) {
464RgbaColor rgb = SS.backgroundColor;
465SS.TW.ShowEditControlWithColorPicker(3, rgb);
466SS.TW.edit.meaning = Edit::BACKGROUND_COLOR;
467}
468
469void TextWindow::ShowListOfStyles() {
470Printf(true, "%Ft color style-name");
471
472bool darkbg = false;
473for(Style &s : SK.style) {
474Printf(false, "%Bp %Bz %Bp %Fl%Ll%f%D%s%E",
475darkbg ? 'd' : 'a',
476&s.color,
477darkbg ? 'd' : 'a',
478ScreenShowStyleInfo, s.h.v,
479s.DescriptionString().c_str());
480
481darkbg = !darkbg;
482}
483
484Printf(true, " %Fl%Ll%fcreate a new custom style%E",
485&ScreenCreateCustomStyle);
486
487Printf(false, "");
488
489RgbaColor rgb = SS.backgroundColor;
490Printf(false, "%Ft background color (r, g, b)%E");
491Printf(false, "%Ba %@, %@, %@ %Fl%D%f%Ll[change]%E",
492rgb.redF(), rgb.greenF(), rgb.blueF(),
493top[rows-1] + 2, &ScreenChangeBackgroundColor);
494
495Printf(false, "");
496Printf(false, " %Fl%Ll%fload factory defaults%E",
497&ScreenLoadFactoryDefaultStyles);
498}
499
500
501void TextWindow::ScreenChangeStyleName(int link, uint32_t v) {
502hStyle hs = { v };
503Style *s = Style::Get(hs);
504SS.TW.ShowEditControl(12, s->name);
505SS.TW.edit.style = hs;
506SS.TW.edit.meaning = Edit::STYLE_NAME;
507}
508
509void TextWindow::ScreenDeleteStyle(int link, uint32_t v) {
510SS.UndoRemember();
511hStyle hs = { v };
512Style *s = SK.style.FindByIdNoOops(hs);
513if(s) {
514SK.style.RemoveById(hs);
515// And it will get recreated automatically if something is still using
516// the style, so no need to do anything else.
517}
518SS.TW.GoToScreen(Screen::LIST_OF_STYLES);
519SS.GW.Invalidate();
520}
521
522void TextWindow::ScreenChangeStylePatternType(int link, uint32_t v) {
523hStyle hs = { v };
524Style *s = Style::Get(hs);
525s->stippleType = (StipplePattern)(link - 1);
526SS.GW.persistentDirty = true;
527}
528
529void TextWindow::ScreenChangeStyleMetric(int link, uint32_t v) {
530hStyle hs = { v };
531Style *s = Style::Get(hs);
532double val;
533Style::UnitsAs units;
534Edit meaning;
535int col;
536switch(link) {
537case 't':
538val = s->textHeight;
539units = s->textHeightAs;
540col = 10;
541meaning = Edit::STYLE_TEXT_HEIGHT;
542break;
543
544case 's':
545val = s->stippleScale;
546units = s->widthAs;
547col = 17;
548meaning = Edit::STYLE_STIPPLE_PERIOD;
549break;
550
551case 'w':
552case 'W':
553val = s->width;
554units = s->widthAs;
555col = 9;
556meaning = Edit::STYLE_WIDTH;
557break;
558
559default: ssassert(false, "Unexpected link");
560}
561
562std::string edit_value;
563if(units == Style::UnitsAs::PIXELS) {
564edit_value = ssprintf("%.2f", val);
565} else {
566edit_value = SS.MmToString(val, true);
567}
568SS.TW.ShowEditControl(col, edit_value);
569SS.TW.edit.style = hs;
570SS.TW.edit.meaning = meaning;
571}
572
573void TextWindow::ScreenChangeStyleTextAngle(int link, uint32_t v) {
574hStyle hs = { v };
575Style *s = Style::Get(hs);
576SS.TW.ShowEditControl(9, ssprintf("%.2f", s->textAngle));
577SS.TW.edit.style = hs;
578SS.TW.edit.meaning = Edit::STYLE_TEXT_ANGLE;
579}
580
581void TextWindow::ScreenChangeStyleColor(int link, uint32_t v) {
582hStyle hs = { v };
583Style *s = Style::Get(hs);
584// Same function used for stroke and fill colors
585Edit em;
586RgbaColor rgb;
587if(link == 's') {
588em = Edit::STYLE_COLOR;
589rgb = s->color;
590} else if(link == 'f') {
591em = Edit::STYLE_FILL_COLOR;
592rgb = s->fillColor;
593} else ssassert(false, "Unexpected link");
594SS.TW.ShowEditControlWithColorPicker(13, rgb);
595SS.TW.edit.style = hs;
596SS.TW.edit.meaning = em;
597}
598
599void TextWindow::ScreenChangeStyleYesNo(int link, uint32_t v) {
600SS.UndoRemember();
601hStyle hs = { v };
602Style *s = Style::Get(hs);
603switch(link) {
604// Units for the width
605case 'w':
606if(s->widthAs != Style::UnitsAs::MM) {
607s->widthAs = Style::UnitsAs::MM;
608s->width /= SS.GW.scale;
609s->stippleScale /= SS.GW.scale;
610}
611break;
612case 'W':
613if(s->widthAs != Style::UnitsAs::PIXELS) {
614s->widthAs = Style::UnitsAs::PIXELS;
615s->width *= SS.GW.scale;
616s->stippleScale *= SS.GW.scale;
617}
618break;
619
620// Units for the height
621case 'g':
622if(s->textHeightAs != Style::UnitsAs::MM) {
623s->textHeightAs = Style::UnitsAs::MM;
624s->textHeight /= SS.GW.scale;
625}
626break;
627
628case 'G':
629if(s->textHeightAs != Style::UnitsAs::PIXELS) {
630s->textHeightAs = Style::UnitsAs::PIXELS;
631s->textHeight *= SS.GW.scale;
632}
633break;
634
635case 'e':
636s->exportable = !(s->exportable);
637break;
638
639case 'v':
640s->visible = !(s->visible);
641break;
642
643case 'f':
644s->filled = !(s->filled);
645break;
646
647// Horizontal text alignment
648case 'L':
649s->textOrigin = (Style::TextOrigin)((uint32_t)s->textOrigin | (uint32_t)Style::TextOrigin::LEFT);
650s->textOrigin = (Style::TextOrigin)((uint32_t)s->textOrigin & ~(uint32_t)Style::TextOrigin::RIGHT);
651break;
652case 'H':
653s->textOrigin = (Style::TextOrigin)((uint32_t)s->textOrigin & ~(uint32_t)Style::TextOrigin::LEFT);
654s->textOrigin = (Style::TextOrigin)((uint32_t)s->textOrigin & ~(uint32_t)Style::TextOrigin::RIGHT);
655break;
656case 'R':
657s->textOrigin = (Style::TextOrigin)((uint32_t)s->textOrigin & ~(uint32_t)Style::TextOrigin::LEFT);
658s->textOrigin = (Style::TextOrigin)((uint32_t)s->textOrigin | (uint32_t)Style::TextOrigin::RIGHT);
659break;
660
661// Vertical text alignment
662case 'B':
663s->textOrigin = (Style::TextOrigin)((uint32_t)s->textOrigin | (uint32_t)Style::TextOrigin::BOT);
664s->textOrigin = (Style::TextOrigin)((uint32_t)s->textOrigin & ~(uint32_t)Style::TextOrigin::TOP);
665break;
666case 'V':
667s->textOrigin = (Style::TextOrigin)((uint32_t)s->textOrigin & ~(uint32_t)Style::TextOrigin::BOT);
668s->textOrigin = (Style::TextOrigin)((uint32_t)s->textOrigin & ~(uint32_t)Style::TextOrigin::TOP);
669break;
670case 'T':
671s->textOrigin = (Style::TextOrigin)((uint32_t)s->textOrigin & ~(uint32_t)Style::TextOrigin::BOT);
672s->textOrigin = (Style::TextOrigin)((uint32_t)s->textOrigin | (uint32_t)Style::TextOrigin::TOP);
673break;
674}
675SS.GW.Invalidate(/*clearPersistent=*/true);
676}
677
678bool TextWindow::EditControlDoneForStyles(const std::string &str) {
679Style *s;
680switch(edit.meaning) {
681case Edit::STYLE_STIPPLE_PERIOD:
682case Edit::STYLE_TEXT_HEIGHT:
683case Edit::STYLE_WIDTH: {
684SS.UndoRemember();
685s = Style::Get(edit.style);
686
687double v;
688Style::UnitsAs units = (edit.meaning == Edit::STYLE_TEXT_HEIGHT) ?
689s->textHeightAs : s->widthAs;
690if(units == Style::UnitsAs::MM) {
691v = SS.StringToMm(str);
692} else {
693v = atof(str.c_str());
694}
695v = max(0.0, v);
696if(edit.meaning == Edit::STYLE_TEXT_HEIGHT) {
697s->textHeight = v;
698} else if(edit.meaning == Edit::STYLE_STIPPLE_PERIOD) {
699s->stippleScale = v;
700} else {
701s->width = v;
702}
703break;
704}
705case Edit::STYLE_TEXT_ANGLE:
706SS.UndoRemember();
707s = Style::Get(edit.style);
708s->textAngle = WRAP_SYMMETRIC(atof(str.c_str()), 360);
709break;
710
711case Edit::BACKGROUND_COLOR:
712case Edit::STYLE_FILL_COLOR:
713case Edit::STYLE_COLOR: {
714Vector rgb;
715if(sscanf(str.c_str(), "%lf, %lf, %lf", &rgb.x, &rgb.y, &rgb.z)==3) {
716rgb = rgb.ClampWithin(0, 1);
717if(edit.meaning == Edit::STYLE_COLOR) {
718SS.UndoRemember();
719s = Style::Get(edit.style);
720s->color = RGBf(rgb.x, rgb.y, rgb.z);
721} else if(edit.meaning == Edit::STYLE_FILL_COLOR) {
722SS.UndoRemember();
723s = Style::Get(edit.style);
724s->fillColor = RGBf(rgb.x, rgb.y, rgb.z);
725} else {
726SS.backgroundColor = RGBf(rgb.x, rgb.y, rgb.z);
727}
728} else {
729Error(_("Bad format: specify color as r, g, b"));
730}
731break;
732}
733case Edit::STYLE_NAME:
734if(str.empty()) {
735Error(_("Style name cannot be empty"));
736} else {
737SS.UndoRemember();
738s = Style::Get(edit.style);
739s->name = str;
740}
741break;
742
743default: return false;
744}
745SS.GW.persistentDirty = true;
746return true;
747}
748
749void TextWindow::ShowStyleInfo() {
750Printf(true, "%Fl%f%Ll(back to list of styles)%E", &ScreenShowListOfStyles);
751
752Style *s = Style::Get(shown.style);
753
754if(s->h.v < Style::FIRST_CUSTOM) {
755Printf(true, "%FtSTYLE %E%s ", s->DescriptionString().c_str());
756} else {
757Printf(true, "%FtSTYLE %E%s "
758"[%Fl%Ll%D%frename%E/%Fl%Ll%D%fdel%E]",
759s->DescriptionString().c_str(),
760s->h.v, &ScreenChangeStyleName,
761s->h.v, &ScreenDeleteStyle);
762}
763Printf(true, "%Ft line stroke style%E");
764Printf(false, "%Ba %Ftcolor %E%Bz %Ba (%@, %@, %@) %D%f%Ls%Fl[change]%E",
765&s->color,
766s->color.redF(), s->color.greenF(), s->color.blueF(),
767s->h.v, ScreenChangeStyleColor);
768
769// The line width, and its units
770if(s->widthAs == Style::UnitsAs::PIXELS) {
771Printf(false, " %Ftwidth%E %@ %D%f%Lp%Fl[change]%E",
772s->width,
773s->h.v, &ScreenChangeStyleMetric,
774(s->h.v < Style::FIRST_CUSTOM) ? 'w' : 'W');
775} else {
776Printf(false, " %Ftwidth%E %s %D%f%Lp%Fl[change]%E",
777SS.MmToString(s->width).c_str(),
778s->h.v, &ScreenChangeStyleMetric,
779(s->h.v < Style::FIRST_CUSTOM) ? 'w' : 'W');
780}
781
782if(s->widthAs == Style::UnitsAs::PIXELS) {
783Printf(false, "%Ba %Ftstipple width%E %@ %D%f%Lp%Fl[change]%E",
784s->stippleScale,
785s->h.v, &ScreenChangeStyleMetric, 's');
786} else {
787Printf(false, "%Ba %Ftstipple width%E %s %D%f%Lp%Fl[change]%E",
788SS.MmToString(s->stippleScale).c_str(),
789s->h.v, &ScreenChangeStyleMetric, 's');
790}
791
792bool widthpx = (s->widthAs == Style::UnitsAs::PIXELS);
793if(s->h.v < Style::FIRST_CUSTOM) {
794Printf(false," %Ftin units of %Fdpixels%E");
795} else {
796Printf(false,"%Ba %Ftin units of %Fd"
797"%D%f%LW%s pixels%E "
798"%D%f%Lw%s %s",
799s->h.v, &ScreenChangeStyleYesNo,
800widthpx ? RADIO_TRUE : RADIO_FALSE,
801s->h.v, &ScreenChangeStyleYesNo,
802!widthpx ? RADIO_TRUE : RADIO_FALSE,
803SS.UnitName());
804}
805
806Printf(false,"%Ba %Ftstipple type:%E");
807
808const size_t patternCount = (size_t)StipplePattern::LAST + 1;
809const char *patternsSource[patternCount] = {
810"___________",
811"- - - - ",
812"- - - - - -",
813"__ __ __ __",
814"-.-.-.-.-.-",
815"..-..-..-..",
816"...........",
817"~~~~~~~~~~~",
818"__~__~__~__"
819};
820std::string patterns[patternCount];
821
822for(uint32_t i = 0; i <= (uint32_t)StipplePattern::LAST; i++) {
823const char *str = patternsSource[i];
824do {
825switch(*str) {
826case ' ': patterns[i] += " "; break;
827case '.': patterns[i] += "\xEE\x80\x84"; break;
828case '_': patterns[i] += "\xEE\x80\x85"; break;
829case '-': patterns[i] += "\xEE\x80\x86"; break;
830case '~': patterns[i] += "\xEE\x80\x87"; break;
831default: ssassert(false, "Unexpected stipple pattern element");
832}
833} while(*(++str));
834}
835
836for(uint32_t i = 0; i <= (uint32_t)StipplePattern::LAST; i++) {
837const char *radio = s->stippleType == (StipplePattern)i ? RADIO_TRUE : RADIO_FALSE;
838Printf(false, "%Bp %D%f%Lp%s %s%E",
839(i % 2 == 0) ? 'd' : 'a',
840s->h.v, &ScreenChangeStylePatternType,
841i + 1, radio, patterns[i].c_str());
842}
843
844if(s->h.v >= Style::FIRST_CUSTOM) {
845// The fill color, and whether contours are filled
846
847Printf(false, "");
848Printf(false, "%Ft contour fill style%E");
849Printf(false,
850"%Ba %Ftcolor %E%Bz %Ba (%@, %@, %@) %D%f%Lf%Fl[change]%E",
851&s->fillColor,
852s->fillColor.redF(), s->fillColor.greenF(), s->fillColor.blueF(),
853s->h.v, ScreenChangeStyleColor);
854
855Printf(false, "%Bd %D%f%Lf%s contours are filled%E",
856s->h.v, &ScreenChangeStyleYesNo,
857s->filled ? CHECK_TRUE : CHECK_FALSE);
858}
859
860// The text height, and its units
861Printf(false, "");
862Printf(false, "%Ft text style%E");
863
864if(s->textHeightAs == Style::UnitsAs::PIXELS) {
865Printf(false, "%Ba %Ftheight %E%@ %D%f%Lt%Fl%s%E",
866s->textHeight,
867s->h.v, &ScreenChangeStyleMetric,
868"[change]");
869} else {
870Printf(false, "%Ba %Ftheight %E%s %D%f%Lt%Fl%s%E",
871SS.MmToString(s->textHeight).c_str(),
872s->h.v, &ScreenChangeStyleMetric,
873"[change]");
874}
875
876bool textHeightpx = (s->textHeightAs == Style::UnitsAs::PIXELS);
877if(s->h.v < Style::FIRST_CUSTOM) {
878Printf(false,"%Bd %Ftin units of %Fdpixels");
879} else {
880Printf(false,"%Bd %Ftin units of %Fd"
881"%D%f%LG%s pixels%E "
882"%D%f%Lg%s %s",
883s->h.v, &ScreenChangeStyleYesNo,
884textHeightpx ? RADIO_TRUE : RADIO_FALSE,
885s->h.v, &ScreenChangeStyleYesNo,
886!textHeightpx ? RADIO_TRUE : RADIO_FALSE,
887SS.UnitName());
888}
889
890if(s->h.v >= Style::FIRST_CUSTOM) {
891Printf(false, "%Ba %Ftangle %E%@ %D%f%Ll%Fl[change]%E",
892s->textAngle,
893s->h.v, &ScreenChangeStyleTextAngle);
894
895Printf(false, "");
896Printf(false, "%Ft text comment alignment%E");
897bool neither;
898neither = !((uint32_t)s->textOrigin & ((uint32_t)Style::TextOrigin::LEFT | (uint32_t)Style::TextOrigin::RIGHT));
899Printf(false, "%Ba "
900"%D%f%LL%s left%E "
901"%D%f%LH%s center%E "
902"%D%f%LR%s right%E ",
903s->h.v, &ScreenChangeStyleYesNo,
904((uint32_t)s->textOrigin & (uint32_t)Style::TextOrigin::LEFT) ? RADIO_TRUE : RADIO_FALSE,
905s->h.v, &ScreenChangeStyleYesNo,
906neither ? RADIO_TRUE : RADIO_FALSE,
907s->h.v, &ScreenChangeStyleYesNo,
908((uint32_t)s->textOrigin & (uint32_t)Style::TextOrigin::RIGHT) ? RADIO_TRUE : RADIO_FALSE);
909
910neither = !((uint32_t)s->textOrigin & ((uint32_t)Style::TextOrigin::BOT | (uint32_t)Style::TextOrigin::TOP));
911Printf(false, "%Bd "
912"%D%f%LB%s bottom%E "
913"%D%f%LV%s center%E "
914"%D%f%LT%s top%E ",
915s->h.v, &ScreenChangeStyleYesNo,
916((uint32_t)s->textOrigin & (uint32_t)Style::TextOrigin::BOT) ? RADIO_TRUE : RADIO_FALSE,
917s->h.v, &ScreenChangeStyleYesNo,
918neither ? RADIO_TRUE : RADIO_FALSE,
919s->h.v, &ScreenChangeStyleYesNo,
920((uint32_t)s->textOrigin & (uint32_t)Style::TextOrigin::TOP) ? RADIO_TRUE : RADIO_FALSE);
921}
922
923Printf(false, "");
924
925if(s->h.v >= Style::FIRST_CUSTOM) {
926Printf(false, " %Fd%D%f%Lv%s show these objects on screen%E",
927s->h.v, &ScreenChangeStyleYesNo,
928s->visible ? CHECK_TRUE : CHECK_FALSE);
929}
930
931Printf(false, " %Fd%D%f%Le%s export these objects%E",
932s->h.v, &ScreenChangeStyleYesNo,
933s->exportable ? CHECK_TRUE : CHECK_FALSE);
934
935if(s->h.v >= Style::FIRST_CUSTOM) {
936Printf(false, "");
937Printf(false, "To assign lines or curves to this style,");
938Printf(false, "right-click them on the drawing.");
939}
940}
941
942void TextWindow::ScreenAssignSelectionToStyle(int link, uint32_t v) {
943Style::AssignSelectionToStyle(v);
944}
945