1 //-----------------------------------------------------------------------------
2 // Helper functions for the text-based browser window.
3 //
4 // Copyright 2008-2013 Jonathan Westhues.
5 //-----------------------------------------------------------------------------
6 #include "solvespace.h"
7 #include "generated/icons.h"
8 
9 const TextWindow::Color TextWindow::fgColors[] = {
10     { 'd', RGBi(255, 255, 255) },
11     { 'l', RGBi(100, 100, 255) },
12     { 't', RGBi(255, 200,   0) },
13     { 'h', RGBi( 90,  90,  90) },
14     { 's', RGBi( 40, 255,  40) },
15     { 'm', RGBi(200, 200,   0) },
16     { 'r', RGBi(  0,   0,   0) },
17     { 'x', RGBi(255,  20,  20) },
18     { 'i', RGBi(  0, 255, 255) },
19     { 'g', RGBi(160, 160, 160) },
20     { 'b', RGBi(200, 200, 200) },
21     { 0,   RGBi(  0,   0,   0) }
22 };
23 const TextWindow::Color TextWindow::bgColors[] = {
24     { 'd', RGBi(  0,   0,   0) },
25     { 't', RGBi( 34,  15,  15) },
26     { 'a', RGBi( 25,  25,  25) },
27     { 'r', RGBi(255, 255, 255) },
28     { 0,   RGBi(  0,   0,   0) }
29 };
30 
31 bool TextWindow::SPACER = false;
32 TextWindow::HideShowIcon TextWindow::hideShowIcons[] = {
33     { &(SS.GW.showWorkplanes),  Icon_workplane,     "workplanes from inactive groups"},
34     { &(SS.GW.showNormals),     Icon_normal,        "normals"                        },
35     { &(SS.GW.showPoints),      Icon_point,         "points"                         },
36     { &(SS.GW.showConstraints), Icon_constraint,    "constraints and dimensions"     },
37     { &(SS.GW.showFaces),       Icon_faces,         "XXX - special cased"            },
38     { &SPACER, 0, 0 },
39     { &(SS.GW.showShaded),      Icon_shaded,        "shaded view of solid model"     },
40     { &(SS.GW.showEdges),       Icon_edges,         "edges of solid model"           },
41     { &(SS.GW.showOutlines),    Icon_outlines,      "outline of solid model"         },
42     { &(SS.GW.showMesh),        Icon_mesh,          "triangle mesh of solid model"   },
43     { &SPACER, 0, 0 },
44     { &(SS.GW.showHdnLines),    Icon_hidden_lines,  "hidden lines"                   },
45     { 0, 0, 0 }
46 };
47 
MakeColorTable(const Color * in,float * out)48 void TextWindow::MakeColorTable(const Color *in, float *out) {
49     int i;
50     for(i = 0; in[i].c != 0; i++) {
51         int c = in[i].c;
52         if(c < 0 || c > 255) oops();
53         out[c*3 + 0] = in[i].color.redF();
54         out[c*3 + 1] = in[i].color.greenF();
55         out[c*3 + 2] = in[i].color.blueF();
56     }
57 }
58 
Init(void)59 void TextWindow::Init(void) {
60     ClearSuper();
61 }
62 
ClearSuper(void)63 void TextWindow::ClearSuper(void) {
64     HideEditControl();
65 
66     // Cannot use *this = {} here because TextWindow instances
67     // are 2.4MB long; this causes stack overflows in prologue
68     // when built with MSVC, even with optimizations.
69     memset(this, 0, sizeof(*this));
70 
71     MakeColorTable(fgColors, fgColorTable);
72     MakeColorTable(bgColors, bgColorTable);
73 
74     ClearScreen();
75     Show();
76 }
77 
HideEditControl(void)78 void TextWindow::HideEditControl(void) {
79     editControl.colorPicker.show = false;
80     HideTextEditControl();
81 }
82 
ShowEditControl(int col,const std::string & str,int halfRow)83 void TextWindow::ShowEditControl(int col, const std::string &str, int halfRow) {
84     if(halfRow < 0) halfRow = top[hoveredRow];
85     editControl.halfRow = halfRow;
86     editControl.col = col;
87 
88     int x = LEFT_MARGIN + CHAR_WIDTH*col;
89     int y = (halfRow - SS.TW.scrollPos)*(LINE_HEIGHT/2);
90 
91     ShowTextEditControl(x, y + 18, str);
92 }
93 
ShowEditControlWithColorPicker(int col,RgbaColor rgb)94 void TextWindow::ShowEditControlWithColorPicker(int col, RgbaColor rgb)
95 {
96     SS.ScheduleShowTW();
97 
98     editControl.colorPicker.show = true;
99     editControl.colorPicker.rgb = rgb;
100     editControl.colorPicker.h = 0;
101     editControl.colorPicker.s = 0;
102     editControl.colorPicker.v = 1;
103     ShowEditControl(col, ssprintf("%.2f, %.2f, %.2f", rgb.redF(), rgb.greenF(), rgb.blueF()));
104 }
105 
ClearScreen(void)106 void TextWindow::ClearScreen(void) {
107     int i, j;
108     for(i = 0; i < MAX_ROWS; i++) {
109         for(j = 0; j < MAX_COLS; j++) {
110             text[i][j] = ' ';
111             meta[i][j].fg = 'd';
112             meta[i][j].bg = 'd';
113             meta[i][j].link = NOT_A_LINK;
114         }
115         top[i] = i*2;
116     }
117     rows = 0;
118 }
119 
Printf(bool halfLine,const char * fmt,...)120 void TextWindow::Printf(bool halfLine, const char *fmt, ...) {
121     va_list vl;
122     va_start(vl, fmt);
123 
124     if(rows >= MAX_ROWS) return;
125 
126     int r, c;
127     r = rows;
128     top[r] = (r == 0) ? 0 : (top[r-1] + (halfLine ? 3 : 2));
129     rows++;
130 
131     for(c = 0; c < MAX_COLS; c++) {
132         text[r][c] = ' ';
133         meta[r][c].link = NOT_A_LINK;
134     }
135 
136     char fg = 'd';
137     char bg = 'd';
138     RgbaColor bgRgb = RGBi(0, 0, 0);
139     int link = NOT_A_LINK;
140     uint32_t data = 0;
141     LinkFunction *f = NULL, *h = NULL;
142 
143     c = 0;
144     while(*fmt) {
145         char buf[1024];
146 
147         if(*fmt == '%') {
148             fmt++;
149             if(*fmt == '\0') goto done;
150             strcpy(buf, "");
151             switch(*fmt) {
152                 case 'd': {
153                     int v = va_arg(vl, int);
154                     sprintf(buf, "%d", v);
155                     break;
156                 }
157                 case 'x': {
158                     unsigned int v = va_arg(vl, unsigned int);
159                     sprintf(buf, "%08x", v);
160                     break;
161                 }
162                 case '@': {
163                     double v = va_arg(vl, double);
164                     sprintf(buf, "%.2f", v);
165                     break;
166                 }
167                 case '2': {
168                     double v = va_arg(vl, double);
169                     sprintf(buf, "%s%.2f", v < 0 ? "" : " ", v);
170                     break;
171                 }
172                 case '3': {
173                     double v = va_arg(vl, double);
174                     sprintf(buf, "%s%.3f", v < 0 ? "" : " ", v);
175                     break;
176                 }
177                 case '#': {
178                     double v = va_arg(vl, double);
179                     sprintf(buf, "%.3f", v);
180                     break;
181                 }
182                 case 's': {
183                     char *s = va_arg(vl, char *);
184                     memcpy(buf, s, min(sizeof(buf), strlen(s)+1));
185                     break;
186                 }
187                 case 'c': {
188                     // 'char' is promoted to 'int' when passed through '...'
189                     int v = va_arg(vl, int);
190                     if(v == 0) {
191                         strcpy(buf, "");
192                     } else {
193                         sprintf(buf, "%c", v);
194                     }
195                     break;
196                 }
197                 case 'E':
198                     fg = 'd';
199                     // leave the background, though
200                     link = NOT_A_LINK;
201                     data = 0;
202                     f = NULL;
203                     h = NULL;
204                     break;
205 
206                 case 'F':
207                 case 'B': {
208                     char cc = fmt[1];  // color code
209                     RgbaColor *rgbPtr = NULL;
210                     switch(cc) {
211                         case 0:   goto done;  // truncated directive
212                         case 'p': cc = (char)va_arg(vl, int); break;
213                         case 'z': rgbPtr = va_arg(vl, RgbaColor *); break;
214                     }
215                     if(*fmt == 'F') {
216                         fg = cc;
217                     } else {
218                         bg = cc;
219                         if(rgbPtr) bgRgb = *rgbPtr;
220                     }
221                     fmt++;
222                     break;
223                 }
224                 case 'L':
225                     if(fmt[1] == '\0') goto done;
226                     fmt++;
227                     if(*fmt == 'p') {
228                         link = va_arg(vl, int);
229                     } else {
230                         link = *fmt;
231                     }
232                     break;
233 
234                 case 'f':
235                     f = va_arg(vl, LinkFunction *);
236                     break;
237 
238                 case 'h':
239                     h = va_arg(vl, LinkFunction *);
240                     break;
241 
242                 case 'D': {
243                     unsigned int v = va_arg(vl, unsigned int);
244                     data = (uint32_t)v;
245                     break;
246                 }
247                 case '%':
248                     strcpy(buf, "%");
249                     break;
250             }
251         } else {
252             utf8_iterator it2(fmt), it1 = it2++;
253             strncpy(buf, fmt, it2 - it1);
254             buf[it2 - it1] = '\0';
255         }
256 
257         for(utf8_iterator it(buf); *it; ++it) {
258             for(int i = 0; i < ssglBitmapCharWidth(*it); i++) {
259                 if(c >= MAX_COLS) goto done;
260                 text[r][c] = (i == 0) ? *it : ' ';
261                 meta[r][c].fg = fg;
262                 meta[r][c].bg = bg;
263                 meta[r][c].bgRgb = bgRgb;
264                 meta[r][c].link = link;
265                 meta[r][c].data = data;
266                 meta[r][c].f = f;
267                 meta[r][c].h = h;
268                 c++;
269             }
270         }
271 
272         fmt++;
273     }
274     while(c < MAX_COLS) {
275         meta[r][c].fg = fg;
276         meta[r][c].bg = bg;
277         meta[r][c].bgRgb = bgRgb;
278         c++;
279     }
280 
281 done:
282     va_end(vl);
283 }
284 
285 #define gs (SS.GW.gs)
Show(void)286 void TextWindow::Show(void) {
287     if(!(SS.GW.pending.operation)) SS.GW.ClearPending();
288 
289     SS.GW.GroupSelection();
290 
291     // Make sure these tests agree with test used to draw indicator line on
292     // main list of groups screen.
293     if(SS.GW.pending.description) {
294         // A pending operation (that must be completed with the mouse in
295         // the graphics window) will preempt our usual display.
296         HideEditControl();
297         ShowHeader(false);
298         Printf(false, "");
299         Printf(false, "%s", SS.GW.pending.description);
300         Printf(true, "%Fl%f%Ll(cancel operation)%E",
301             &TextWindow::ScreenUnselectAll);
302     } else if((gs.n > 0 || gs.constraints > 0) &&
303                                     shown.screen != SCREEN_PASTE_TRANSFORMED)
304     {
305         if(edit.meaning != EDIT_TTF_TEXT) HideEditControl();
306         ShowHeader(false);
307         DescribeSelection();
308     } else {
309         if(edit.meaning == EDIT_TTF_TEXT) HideEditControl();
310         ShowHeader(true);
311         switch(shown.screen) {
312             default:
313                 shown.screen = SCREEN_LIST_OF_GROUPS;
314                 // fall through
315             case SCREEN_LIST_OF_GROUPS:     ShowListOfGroups();     break;
316             case SCREEN_GROUP_INFO:         ShowGroupInfo();        break;
317             case SCREEN_GROUP_SOLVE_INFO:   ShowGroupSolveInfo();   break;
318             case SCREEN_CONFIGURATION:      ShowConfiguration();    break;
319             case SCREEN_STEP_DIMENSION:     ShowStepDimension();    break;
320             case SCREEN_LIST_OF_STYLES:     ShowListOfStyles();     break;
321             case SCREEN_STYLE_INFO:         ShowStyleInfo();        break;
322             case SCREEN_PASTE_TRANSFORMED:  ShowPasteTransformed(); break;
323             case SCREEN_EDIT_VIEW:          ShowEditView();         break;
324             case SCREEN_TANGENT_ARC:        ShowTangentArc();       break;
325         }
326     }
327     Printf(false, "");
328 
329     // Make sure there's room for the color picker
330     if(editControl.colorPicker.show) {
331         int pickerHeight = 25;
332         int halfRow = editControl.halfRow;
333         if(top[rows-1] - halfRow < pickerHeight && rows < MAX_ROWS) {
334             rows++;
335             top[rows-1] = halfRow + pickerHeight;
336         }
337     }
338 
339     InvalidateText();
340 }
341 
TimerCallback(void)342 void TextWindow::TimerCallback(void)
343 {
344     tooltippedIcon = hoveredIcon;
345     InvalidateText();
346 }
347 
DrawOrHitTestIcons(int how,double mx,double my)348 void TextWindow::DrawOrHitTestIcons(int how, double mx, double my)
349 {
350     int width, height;
351     GetTextWindowSize(&width, &height);
352 
353     int x = 20, y = 33 + LINE_HEIGHT;
354     y -= scrollPos*(LINE_HEIGHT/2);
355 
356     if(how == PAINT) {
357         double grey = 30.0/255;
358         double top = y - 28, bot = y + 4;
359         glColor4d(grey, grey, grey, 1.0);
360         ssglAxisAlignedQuad(0, width, top, bot);
361     }
362 
363     HideShowIcon *oldHovered = hoveredIcon;
364     if(how != PAINT) {
365         hoveredIcon = NULL;
366     }
367 
368     HideShowIcon *hsi;
369     for(hsi = &(hideShowIcons[0]); hsi->var; hsi++) {
370         if(hsi->var == &SPACER) {
371             // Draw a darker-grey spacer in between the groups of icons.
372             if(how == PAINT) {
373                 int l = x, r = l + 4,
374                     t = y, b = t - 24;
375                 glColor4d(0.17, 0.17, 0.17, 1);
376                 ssglAxisAlignedQuad(l, r, t, b);
377             }
378             x += 12;
379             continue;
380         }
381 
382         if(how == PAINT) {
383             glPushMatrix();
384                 glTranslated(x, y-24, 0);
385                 // Only thing that matters about the color is the alpha,
386                 // should be one for no transparency
387                 glColor3d(0, 0, 0);
388                 ssglDrawPixelsWithTexture(hsi->icon, 24, 24);
389             glPopMatrix();
390 
391             if(hsi == hoveredIcon) {
392                 glColor4d(1, 1, 0, 0.3);
393                 ssglAxisAlignedQuad(x - 2, x + 26, y + 2, y - 26);
394             }
395             if(!*(hsi->var)) {
396                 glColor4d(1, 0, 0, 0.6);
397                 glLineWidth(2);
398                 int s = 0, f = 24;
399                 glBegin(GL_LINES);
400                     glVertex2d(x+s, y-s);
401                     glVertex2d(x+f, y-f);
402                     glVertex2d(x+s, y-f);
403                     glVertex2d(x+f, y-s);
404                 glEnd();
405             }
406         } else {
407             if(mx > x - 2 && mx < x + 26 &&
408                my < y + 2 && my > y - 26)
409             {
410                 // The mouse is hovered over this icon, so do the tooltip
411                 // stuff.
412                 if(hsi != tooltippedIcon) {
413                     oldMousePos = Point2d::From(mx, my);
414                 }
415                 if(hsi != oldHovered || how == CLICK) {
416                     SetTimerFor(1000);
417                 }
418                 hoveredIcon = hsi;
419                 if(how == CLICK) {
420                     SS.GW.ToggleBool(hsi->var);
421                 }
422             }
423         }
424 
425         x += 32;
426     }
427 
428     if(how != PAINT && hoveredIcon != oldHovered) {
429         InvalidateText();
430     }
431 
432     if(tooltippedIcon) {
433         if(how == PAINT) {
434             std::string str;
435 
436             if(tooltippedIcon->icon == Icon_faces) {
437                 if(SS.GW.showFaces) {
438                     str = "Don't make faces selectable with mouse";
439                 } else {
440                     str = "Make faces selectable with mouse";
441                 }
442             } else {
443                 str = ssprintf("%s %s", *(tooltippedIcon->var) ? "Hide" : "Show",
444                     tooltippedIcon->tip);
445             }
446 
447             double ox = oldMousePos.x, oy = oldMousePos.y - LINE_HEIGHT;
448             ox += 3;
449             oy -= 3;
450             int tw = (str.length() + 1)*(CHAR_WIDTH - 1);
451             ox = min(ox, (double) (width - 25) - tw);
452             oy = max(oy, 5.0);
453 
454             ssglInitializeBitmapFont();
455             glLineWidth(1);
456             glColor4d(1.0, 1.0, 0.6, 1.0);
457             ssglAxisAlignedQuad(ox, ox+tw, oy, oy+LINE_HEIGHT);
458             glColor4d(0.0, 0.0, 0.0, 1.0);
459             ssglAxisAlignedLineLoop(ox, ox+tw, oy, oy+LINE_HEIGHT);
460 
461             glColor4d(0, 0, 0, 1);
462             ssglBitmapText(str, Vector::From(ox+5, oy-3+LINE_HEIGHT, 0));
463         } else {
464             if(!hoveredIcon ||
465                 (hoveredIcon != tooltippedIcon))
466             {
467                 tooltippedIcon = NULL;
468                 InvalidateGraphics();
469             }
470             // And if we're hovered, then we've set a timer that will cause
471             // us to show the tool tip later.
472         }
473     }
474 }
475 
476 //----------------------------------------------------------------------------
477 // Given (x, y, z) = (h, s, v) in [0,6), [0,1], [0,1], return (x, y, z) =
478 // (r, g, b) all in [0, 1].
479 //----------------------------------------------------------------------------
HsvToRgb(Vector hsv)480 Vector TextWindow::HsvToRgb(Vector hsv) {
481     if(hsv.x >= 6) hsv.x -= 6;
482 
483     Vector rgb;
484     double hmod2 = hsv.x;
485     while(hmod2 >= 2) hmod2 -= 2;
486     double x = (1 - fabs(hmod2 - 1));
487     if(hsv.x < 1) {
488         rgb = Vector::From(1, x, 0);
489     } else if(hsv.x < 2) {
490         rgb = Vector::From(x, 1, 0);
491     } else if(hsv.x < 3) {
492         rgb = Vector::From(0, 1, x);
493     } else if(hsv.x < 4) {
494         rgb = Vector::From(0, x, 1);
495     } else if(hsv.x < 5) {
496         rgb = Vector::From(x, 0, 1);
497     } else {
498         rgb = Vector::From(1, 0, x);
499     }
500     double c = hsv.y*hsv.z;
501     double m = 1 - hsv.z;
502     rgb = rgb.ScaledBy(c);
503     rgb = rgb.Plus(Vector::From(m, m, m));
504 
505     return rgb;
506 }
507 
HsvPattern2d(void)508 uint8_t *TextWindow::HsvPattern2d(void) {
509     static uint8_t Texture[256*256*3];
510     static bool Init;
511 
512     if(!Init) {
513         int i, j, p;
514         p = 0;
515         for(i = 0; i < 256; i++) {
516             for(j = 0; j < 256; j++) {
517                 Vector hsv = Vector::From(6.0*i/255.0, 1.0*j/255.0, 1);
518                 Vector rgb = HsvToRgb(hsv);
519                 rgb = rgb.ScaledBy(255);
520                 Texture[p++] = (uint8_t)rgb.x;
521                 Texture[p++] = (uint8_t)rgb.y;
522                 Texture[p++] = (uint8_t)rgb.z;
523             }
524         }
525         Init = true;
526     }
527     return Texture;
528 }
529 
HsvPattern1d(double h,double s)530 uint8_t *TextWindow::HsvPattern1d(double h, double s) {
531     static uint8_t Texture[256*4];
532 
533     int i, p;
534     p = 0;
535     for(i = 0; i < 256; i++) {
536         Vector hsv = Vector::From(6*h, s, 1.0*(255 - i)/255.0);
537         Vector rgb = HsvToRgb(hsv);
538         rgb = rgb.ScaledBy(255);
539         Texture[p++] = (uint8_t)rgb.x;
540         Texture[p++] = (uint8_t)rgb.y;
541         Texture[p++] = (uint8_t)rgb.z;
542         // Needs a padding byte, to make things four-aligned
543         p++;
544     }
545     return Texture;
546 }
547 
ColorPickerDone(void)548 void TextWindow::ColorPickerDone(void) {
549     RgbaColor rgb = editControl.colorPicker.rgb;
550     EditControlDone(ssprintf("%.2f, %.2f, %.3f", rgb.redF(), rgb.greenF(), rgb.blueF()).c_str());
551 }
552 
DrawOrHitTestColorPicker(int how,bool leftDown,double x,double y)553 bool TextWindow::DrawOrHitTestColorPicker(int how, bool leftDown,
554                                           double x, double y)
555 {
556     bool mousePointerAsHand = false;
557 
558     if(how == HOVER && !leftDown) {
559         editControl.colorPicker.picker1dActive = false;
560         editControl.colorPicker.picker2dActive = false;
561     }
562 
563     if(!editControl.colorPicker.show) return false;
564     if(how == CLICK || (how == HOVER && leftDown)) InvalidateText();
565 
566     static const RgbaColor BaseColor[12] = {
567         RGBi(255,   0,   0),
568         RGBi(  0, 255,   0),
569         RGBi(  0,   0, 255),
570 
571         RGBi(  0, 255, 255),
572         RGBi(255,   0, 255),
573         RGBi(255, 255,   0),
574 
575         RGBi(255, 127,   0),
576         RGBi(255,   0, 127),
577         RGBi(  0, 255, 127),
578         RGBi(127, 255,   0),
579         RGBi(127,   0, 255),
580         RGBi(  0, 127, 255),
581     };
582 
583     int width, height;
584     GetTextWindowSize(&width, &height);
585 
586     int px = LEFT_MARGIN + CHAR_WIDTH*editControl.col;
587     int py = (editControl.halfRow - SS.TW.scrollPos)*(LINE_HEIGHT/2);
588 
589     py += LINE_HEIGHT + 5;
590 
591     static const int WIDTH = 16, HEIGHT = 12;
592     static const int PITCH = 18, SIZE = 15;
593 
594     px = min(px, width - (WIDTH*PITCH + 40));
595 
596     int pxm = px + WIDTH*PITCH + 11,
597         pym = py + HEIGHT*PITCH + 7;
598 
599     int bw = 6;
600     if(how == PAINT) {
601         glColor4d(0.2, 0.2, 0.2, 1);
602         ssglAxisAlignedQuad(px, pxm+bw, py, pym+bw);
603         glColor4d(0.0, 0.0, 0.0, 1);
604         ssglAxisAlignedQuad(px+(bw/2), pxm+(bw/2), py+(bw/2), pym+(bw/2));
605     } else {
606         if(x < px || x > pxm+(bw/2) ||
607            y < py || y > pym+(bw/2))
608         {
609             return false;
610         }
611     }
612     px += (bw/2);
613     py += (bw/2);
614 
615     int i, j;
616     for(i = 0; i < WIDTH/2; i++) {
617         for(j = 0; j < HEIGHT; j++) {
618             Vector rgb;
619             RgbaColor d;
620             if(i == 0 && j < 8) {
621                 d = SS.modelColor[j];
622                 rgb = Vector::From(d.redF(), d.greenF(), d.blueF());
623             } else if(i == 0) {
624                 double a = (j - 8.0)/3.0;
625                 rgb = Vector::From(a, a, a);
626             } else {
627                 d = BaseColor[j];
628                 rgb = Vector::From(d.redF(), d.greenF(), d.blueF());
629                 if(i >= 2 && i <= 4) {
630                     double a = (i == 2) ? 0.2 : (i == 3) ? 0.3 : 0.4;
631                     rgb = rgb.Plus(Vector::From(a, a, a));
632                 }
633                 if(i >= 5 && i <= 7) {
634                     double a = (i == 5) ? 0.7 : (i == 6) ? 0.4 : 0.18;
635                     rgb = rgb.ScaledBy(a);
636                 }
637             }
638 
639             rgb = rgb.ClampWithin(0, 1);
640             int sx = px + 5 + PITCH*(i + 8) + 4, sy = py + 5 + PITCH*j;
641 
642             if(how == PAINT) {
643                 glColor4d(CO(rgb), 1);
644                 ssglAxisAlignedQuad(sx, sx+SIZE, sy, sy+SIZE);
645             } else if(how == CLICK) {
646                 if(x >= sx && x <= sx+SIZE && y >= sy && y <= sy+SIZE) {
647                     editControl.colorPicker.rgb = RGBf(rgb.x, rgb.y, rgb.z);
648                     ColorPickerDone();
649                 }
650             } else if(how == HOVER) {
651                 if(x >= sx && x <= sx+SIZE && y >= sy && y <= sy+SIZE) {
652                     mousePointerAsHand = true;
653                 }
654             }
655         }
656     }
657 
658     int hxm, hym;
659     int hx = px + 5, hy = py + 5;
660     hxm = hx + PITCH*7 + SIZE;
661     hym = hy + PITCH*2 + SIZE;
662     if(how == PAINT) {
663         ssglColorRGB(editControl.colorPicker.rgb);
664         ssglAxisAlignedQuad(hx, hxm, hy, hym);
665     } else if(how == CLICK) {
666         if(x >= hx && x <= hxm && y >= hy && y <= hym) {
667             ColorPickerDone();
668         }
669     } else if(how == HOVER) {
670         if(x >= hx && x <= hxm && y >= hy && y <= hym) {
671             mousePointerAsHand = true;
672         }
673     }
674 
675     hy += PITCH*3;
676 
677     hxm = hx + PITCH*7 + SIZE;
678     hym = hy + PITCH*1 + SIZE;
679     // The one-dimensional thing to pick the color's value
680     if(how == PAINT) {
681         glBindTexture(GL_TEXTURE_2D, TEXTURE_COLOR_PICKER_1D);
682         glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
683         glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
684         glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S,     GL_CLAMP);
685         glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T,     GL_CLAMP);
686         glTexEnvf(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_DECAL);
687 
688         glTexImage2D(GL_TEXTURE_2D, 0, GL_RGB, 1, 256, 0,
689                      GL_RGB, GL_UNSIGNED_BYTE,
690                          HsvPattern1d(editControl.colorPicker.h,
691                                       editControl.colorPicker.s));
692 
693         glEnable(GL_TEXTURE_2D);
694         glBegin(GL_QUADS);
695             glTexCoord2d(0, 0);
696             glVertex2d(hx, hy);
697 
698             glTexCoord2d(1, 0);
699             glVertex2d(hx, hym);
700 
701             glTexCoord2d(1, 1);
702             glVertex2d(hxm, hym);
703 
704             glTexCoord2d(0, 1);
705             glVertex2d(hxm, hy);
706         glEnd();
707         glDisable(GL_TEXTURE_2D);
708 
709         double cx = hx+(hxm-hx)*(1 - editControl.colorPicker.v);
710         glColor4d(0, 0, 0, 1);
711         glLineWidth(1);
712         glBegin(GL_LINES);
713             glVertex2d(cx, hy);
714             glVertex2d(cx, hym);
715         glEnd();
716     } else if(how == CLICK ||
717           (how == HOVER && leftDown && editControl.colorPicker.picker1dActive))
718     {
719         if(x >= hx && x <= hxm && y >= hy && y <= hym) {
720             editControl.colorPicker.v = 1 - (x - hx)/(hxm - hx);
721 
722             Vector rgb = HsvToRgb(Vector::From(
723                             6*editControl.colorPicker.h,
724                             editControl.colorPicker.s,
725                             editControl.colorPicker.v));
726             editControl.colorPicker.rgb = RGBf(rgb.x, rgb.y, rgb.z);
727 
728             editControl.colorPicker.picker1dActive = true;
729         }
730     }
731     // and advance our vertical position
732     hy += PITCH*2;
733 
734     hxm = hx + PITCH*7 + SIZE;
735     hym = hy + PITCH*6 + SIZE;
736     // Two-dimensional thing to pick a color by hue and saturation
737     if(how == PAINT) {
738         glBindTexture(GL_TEXTURE_2D, TEXTURE_COLOR_PICKER_2D);
739         glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
740         glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
741         glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S,     GL_CLAMP);
742         glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T,     GL_CLAMP);
743         glTexEnvf(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_DECAL);
744 
745         glTexImage2D(GL_TEXTURE_2D, 0, GL_RGB, 256, 256, 0,
746                      GL_RGB, GL_UNSIGNED_BYTE, HsvPattern2d());
747 
748         glEnable(GL_TEXTURE_2D);
749         glBegin(GL_QUADS);
750             glTexCoord2d(0, 0);
751             glVertex2d(hx, hy);
752 
753             glTexCoord2d(1, 0);
754             glVertex2d(hx, hym);
755 
756             glTexCoord2d(1, 1);
757             glVertex2d(hxm, hym);
758 
759             glTexCoord2d(0, 1);
760             glVertex2d(hxm, hy);
761         glEnd();
762         glDisable(GL_TEXTURE_2D);
763 
764         glColor4d(1, 1, 1, 1);
765         glLineWidth(1);
766         double cx = hx+(hxm-hx)*editControl.colorPicker.h,
767                cy = hy+(hym-hy)*editControl.colorPicker.s;
768         glBegin(GL_LINES);
769             glVertex2d(cx - 5, cy);
770             glVertex2d(cx + 4, cy);
771             glVertex2d(cx, cy - 5);
772             glVertex2d(cx, cy + 4);
773         glEnd();
774     } else if(how == CLICK ||
775           (how == HOVER && leftDown && editControl.colorPicker.picker2dActive))
776     {
777         if(x >= hx && x <= hxm && y >= hy && y <= hym) {
778             double h = (x - hx)/(hxm - hx),
779                    s = (y - hy)/(hym - hy);
780             editControl.colorPicker.h = h;
781             editControl.colorPicker.s = s;
782 
783             Vector rgb = HsvToRgb(Vector::From(
784                             6*editControl.colorPicker.h,
785                             editControl.colorPicker.s,
786                             editControl.colorPicker.v));
787             editControl.colorPicker.rgb = RGBf(rgb.x, rgb.y, rgb.z);
788 
789             editControl.colorPicker.picker2dActive = true;
790         }
791     }
792 
793     SetMousePointerToHand(mousePointerAsHand);
794     return true;
795 }
796 
Paint(void)797 void TextWindow::Paint(void) {
798     int width, height;
799     GetTextWindowSize(&width, &height);
800 
801     // We would like things pixel-exact, to avoid shimmering.
802     glViewport(0, 0, width, height);
803     glMatrixMode(GL_PROJECTION);
804     glLoadIdentity();
805     glMatrixMode(GL_MODELVIEW);
806     glLoadIdentity();
807     glClearColor(0, 0, 0, 1);
808     glClear(GL_COLOR_BUFFER_BIT);
809     glColor3d(1, 1, 1);
810 
811     glTranslated(-1, 1, 0);
812     glScaled(2.0/width, -2.0/height, 1);
813     // Make things round consistently, avoiding exact integer boundary
814     glTranslated(-0.1, -0.1, 0);
815 
816     halfRows = height / (LINE_HEIGHT/2);
817 
818     int bottom = top[rows-1] + 2;
819     scrollPos = min(scrollPos, bottom - halfRows);
820     scrollPos = max(scrollPos, 0);
821 
822     // Let's set up the scroll bar first
823     MoveTextScrollbarTo(scrollPos, top[rows - 1] + 1, halfRows);
824 
825     // Create the bitmap font that we're going to use.
826     glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
827     glEnable(GL_BLEND);
828 
829     // Now paint the window.
830     int r, c, a;
831     for(a = 0; a < 2; a++) {
832         if(a == 0) {
833             glBegin(GL_QUADS);
834         } else if(a == 1) {
835             glEnable(GL_TEXTURE_2D);
836             ssglInitializeBitmapFont();
837             glBegin(GL_QUADS);
838         }
839 
840         for(r = 0; r < rows; r++) {
841             int ltop = top[r];
842             if(ltop < (scrollPos-1)) continue;
843             if(ltop > scrollPos+halfRows) break;
844 
845             for(c = 0; c < min((width/CHAR_WIDTH)+1, (int) MAX_COLS); c++) {
846                 int x = LEFT_MARGIN + c*CHAR_WIDTH;
847                 int y = (ltop-scrollPos)*(LINE_HEIGHT/2) + 4;
848 
849                 int fg = meta[r][c].fg;
850                 int bg = meta[r][c].bg;
851                 RgbaColor bgRgb = meta[r][c].bgRgb;
852 
853                 // On the first pass, all the background quads; on the next
854                 // pass, all the foreground (i.e., font) quads.
855                 if(a == 0) {
856                     int bh = LINE_HEIGHT, adj = -2;
857                     if(bg == 'z') {
858                         glColor3f(bgRgb.redF(), bgRgb.greenF(), bgRgb.blueF());
859                         bh = CHAR_HEIGHT;
860                         adj += 2;
861                     } else {
862                         glColor3fv(&(bgColorTable[bg*3]));
863                     }
864 
865                     if(bg != 'd') {
866                         // Move the quad down a bit, so that the descenders
867                         // still have the correct background.
868                         y += adj;
869                         ssglAxisAlignedQuad(x, x + CHAR_WIDTH, y, y + bh, false);
870                         y -= adj;
871                     }
872                 } else if(a == 1) {
873                     glColor3fv(&(fgColorTable[fg*3]));
874                     ssglBitmapCharQuad(text[r][c], x, y + CHAR_HEIGHT);
875 
876                     // If this is a link and it's hovered, then draw the
877                     // underline
878                     if(meta[r][c].link && meta[r][c].link != 'n' &&
879                         (r == hoveredRow && c == hoveredCol))
880                     {
881                         int cs = c, cf = c;
882                         while(cs >= 0 && meta[r][cs].link &&
883                                          meta[r][cs].f    == meta[r][c].f &&
884                                          meta[r][cs].data == meta[r][c].data)
885                         {
886                             cs--;
887                         }
888                         cs++;
889 
890                         while(          meta[r][cf].link &&
891                                         meta[r][cf].f    == meta[r][c].f &&
892                                         meta[r][cf].data == meta[r][c].data)
893                         {
894                             cf++;
895                         }
896 
897                         // But don't underline checkboxes or radio buttons
898                         while(((text[r][cs] >= 0xe000 && text[r][cs] <= 0xefff) ||
899                                 text[r][cs] == ' ') &&
900                               cs < cf)
901                         {
902                             cs++;
903                         }
904 
905                         glEnd();
906 
907                         // Always use the color of the rightmost character
908                         // in the link, so that underline is consistent color
909                         fg = meta[r][cf-1].fg;
910                         glColor3fv(&(fgColorTable[fg*3]));
911                         glDisable(GL_TEXTURE_2D);
912                         glLineWidth(1);
913                         glBegin(GL_LINES);
914                             int yp = y + CHAR_HEIGHT;
915                             glVertex2d(LEFT_MARGIN + cs*CHAR_WIDTH, yp);
916                             glVertex2d(LEFT_MARGIN + cf*CHAR_WIDTH, yp);
917                         glEnd();
918 
919                         glEnable(GL_TEXTURE_2D);
920                         glBegin(GL_QUADS);
921                     }
922                 }
923             }
924         }
925 
926         glEnd();
927         glDisable(GL_TEXTURE_2D);
928     }
929 
930     // The line to indicate the column of radio buttons that indicates the
931     // active group.
932     SS.GW.GroupSelection();
933     // Make sure this test agrees with test to determine which screen is drawn
934     if(!SS.GW.pending.description && gs.n == 0 && gs.constraints == 0 &&
935         shown.screen == SCREEN_LIST_OF_GROUPS)
936     {
937         int x = 29, y = 70 + LINE_HEIGHT;
938         y -= scrollPos*(LINE_HEIGHT/2);
939 
940         glLineWidth(1);
941         glColor3fv(&(fgColorTable['t'*3]));
942         glBegin(GL_LINES);
943             glVertex2d(x, y);
944             glVertex2d(x, y+40);
945         glEnd();
946     }
947 
948     // The header has some icons that are drawn separately from the text
949     DrawOrHitTestIcons(PAINT, 0, 0);
950 
951     // And we may show a color picker for certain editable fields
952     DrawOrHitTestColorPicker(PAINT, false, 0, 0);
953 }
954 
MouseEvent(bool leftClick,bool leftDown,double x,double y)955 void TextWindow::MouseEvent(bool leftClick, bool leftDown, double x, double y) {
956     if(TextEditControlIsVisible() || GraphicsEditControlIsVisible()) {
957         if(DrawOrHitTestColorPicker(leftClick ? CLICK : HOVER, leftDown, x, y))
958         {
959             return;
960         }
961 
962         if(leftClick) {
963             HideEditControl();
964             HideGraphicsEditControl();
965         } else {
966             SetMousePointerToHand(false);
967         }
968         return;
969     }
970 
971     DrawOrHitTestIcons(leftClick ? CLICK : HOVER, x, y);
972 
973     GraphicsWindow::Selection ps = SS.GW.hover;
974     SS.GW.hover.Clear();
975 
976     int prevHoveredRow = hoveredRow,
977         prevHoveredCol = hoveredCol;
978     hoveredRow = 0;
979     hoveredCol = 0;
980 
981     // Find the corresponding character in the text buffer
982     int c = (int)((x - LEFT_MARGIN) / CHAR_WIDTH);
983     int hh = (LINE_HEIGHT)/2;
984     y += scrollPos*hh;
985     int r;
986     for(r = 0; r < rows; r++) {
987         if(y >= top[r]*hh && y <= (top[r]+2)*hh) {
988             break;
989         }
990     }
991     if(r < 0 || c < 0 || r >= rows || c >= MAX_COLS) {
992         SetMousePointerToHand(false);
993         goto done;
994     }
995 
996     hoveredRow = r;
997     hoveredCol = c;
998 
999 #define META (meta[r][c])
1000     if(leftClick) {
1001         if(META.link && META.f) {
1002             (META.f)(META.link, META.data);
1003             Show();
1004             InvalidateGraphics();
1005         }
1006     } else {
1007         if(META.link) {
1008             SetMousePointerToHand(true);
1009             if(META.h) {
1010                 (META.h)(META.link, META.data);
1011             }
1012         } else {
1013             SetMousePointerToHand(false);
1014         }
1015     }
1016 #undef META
1017 
1018 done:
1019     if((!ps.Equals(&(SS.GW.hover))) ||
1020         prevHoveredRow != hoveredRow ||
1021         prevHoveredCol != hoveredCol)
1022     {
1023         InvalidateGraphics();
1024         InvalidateText();
1025     }
1026 }
1027 
MouseLeave(void)1028 void TextWindow::MouseLeave(void) {
1029     tooltippedIcon = NULL;
1030     hoveredIcon = NULL;
1031     hoveredRow = 0;
1032     hoveredCol = 0;
1033     InvalidateText();
1034 }
1035 
ScrollbarEvent(int newPos)1036 void TextWindow::ScrollbarEvent(int newPos) {
1037     if(TextEditControlIsVisible())
1038         return;
1039 
1040     int bottom = top[rows-1] + 2;
1041     newPos = min(newPos, bottom - halfRows);
1042     newPos = max(newPos, 0);
1043 
1044     if(newPos != scrollPos) {
1045         scrollPos = newPos;
1046         MoveTextScrollbarTo(scrollPos, top[rows - 1] + 1, halfRows);
1047         InvalidateText();
1048     }
1049 }
1050 
1051