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