]> git.lyx.org Git - lyx.git/blob - src/Painter.C
8d44c84fb244872c03f71c0eb96e569081b6e8a3
[lyx.git] / src / Painter.C
1 // -*- C++ -*-
2 /* This file is part of
3  * ======================================================
4  * 
5  *           LyX, The Document Processor
6  *       
7  *          Copyright 1998-2000 The LyX Team
8  *
9  *======================================================*/
10
11 #include <config.h>
12
13 #ifdef __GNUG__
14 #pragma implementation
15 #endif
16
17 #ifdef USE_STL_MEMORY
18 #include <memory>
19 #endif
20
21 #include <cmath>
22
23 #include FORMS_H_LOCATION
24 #include "Painter.h"
25 #include "LString.h"
26 #include "debug.h"
27 #include "lyxfont.h"
28 #include "support/LAssert.h"
29 #include "support/lstrings.h"
30 #include "WorkArea.h"
31 #include "font.h"
32
33 using std::endl;
34 using std::max;
35
36 Painter::Painter(WorkArea & wa)
37         : PainterBase(wa)
38 {
39         colormap = fl_state[fl_get_vclass()].colormap;
40         // Clear the GC cache
41         for (int i = 0; i <= LColor::ignore; ++i) {
42                 colorGCcache[i] = 0;
43         }
44 }
45
46
47 Painter::~Painter() {
48         // Release all the registered GCs
49         for (int i = 0; i <= LColor::ignore; ++i) {
50                 if (colorGCcache[i] != 0) {
51                         XFreeGC(display, colorGCcache[i]);
52                 }
53         }
54         // Iterate over the line cache and Free the GCs
55         for (LineGCCache::iterator lit = lineGCcache.begin();
56              lit != lineGCcache.end(); ++lit) {
57                 XFreeGC(display, (*lit).second);
58         }
59 }
60
61
62 Drawable Painter::drawable() const 
63 {
64         return owner.getPixmap();
65 }
66
67
68 /* Basic drawing routines */
69
70 extern bool Lgb_bug_find_hack;
71
72 PainterBase & Painter::point(int x, int y, LColor::color c)
73 {
74         if (lyxerr.debugging()) {
75                 if (!Lgb_bug_find_hack)
76                         lyxerr << "point not called from "
77                                 "workarea::workhandler\n";
78                 lyxerr.debug() << "Painter drawable: " << drawable() << endl;
79         }
80         
81         XDrawPoint(display, drawable(), getGCForeground(c), x, y);
82         return *this;
83 }
84
85
86 PainterBase & Painter::line(int x1, int y1, int x2, int y2,
87                         LColor::color col,
88                         enum line_style ls,
89                         enum line_width lw)
90 {
91         if (lyxerr.debugging()) {
92                 if (!Lgb_bug_find_hack)
93                         lyxerr << "line not called from "
94                                 "workarea::workhandler\n";
95                 lyxerr.debug() << "Painter drawable: " << drawable() << endl;
96         }
97         
98         XDrawLine(display, drawable(), 
99                   getGCLinepars(ls, lw, col), x1, y1, x2, y2);
100         return *this;
101 }
102
103
104 PainterBase & Painter::lines(int const * xp, int const * yp, int np,
105                              LColor::color col,
106                              enum line_style ls,
107                              enum line_width lw)
108 {
109         if (lyxerr.debugging()) {
110                 if (!Lgb_bug_find_hack)
111                         lyxerr << "lines not called from "
112                                 "workarea::workhandler\n";
113                 lyxerr.debug() << "Painter drawable: " << drawable() << endl;
114         }
115         
116 #ifndef HAVE_AUTO_PTR
117         XPoint * points = new XPoint[np];
118 #else
119         auto_ptr<XPoint> points(new Xpoint[np]);
120 #endif
121         for (int i = 0; i < np; ++i) {
122                 points[i].x = xp[i];
123                 points[i].y = yp[i];
124         }
125
126         XDrawLines(display, drawable(), getGCLinepars(ls, lw, col), 
127                    points, np, CoordModeOrigin);
128
129 #ifndef HAVE_AUTO_PTR
130         delete[] points;
131 #endif  
132         return *this;
133 }      
134
135
136 PainterBase & Painter::rectangle(int x, int y, int w, int h,
137                                  LColor::color col,
138                                  enum line_style ls,
139                                  enum line_width lw)
140 {
141         if (lyxerr.debugging()) {
142                 if (!Lgb_bug_find_hack)
143                         lyxerr << "rectangle not called from "
144                                 "workarea::workhandler\n";
145                 lyxerr << "Painter drawable: " << drawable() << endl;
146         }
147         
148         XDrawRectangle(display, drawable(), getGCLinepars(ls, lw, col), 
149                        x, y, w, h);
150         return *this;
151 }
152
153
154 PainterBase & Painter::fillRectangle(int x, int y, int w, int h,
155                                  LColor::color col)
156 {
157         if (lyxerr.debugging()) {
158                 if (!Lgb_bug_find_hack)
159                         lyxerr << "fillrectangle not called from "
160                                 "workarea::workhandler\n";
161                 lyxerr << "Painter drawable: " << drawable() << endl;
162         }
163         
164         XFillRectangle(display, drawable(), getGCForeground(col), x, y, w, h);
165         return *this;
166 }
167
168
169 PainterBase & Painter::fillPolygon(int const * xp, int const * yp, int np,
170                                LColor::color col)
171 {
172         if (lyxerr.debugging()) {
173                 if (!Lgb_bug_find_hack)
174                         lyxerr <<"fillpolygon not called from "
175                                 "workarea::workhandler\n";
176                 lyxerr << "Painter drawable: " << drawable() << endl;
177         }
178         
179 #ifndef HAVE_AUTO_PTR
180         XPoint * points = new XPoint[np];
181 #else
182         auto_ptr<XPoint> points(new XPoint[np]);
183 #endif
184         for (int i=0; i < np; ++i) {
185                 points[i].x = xp[i];
186                 points[i].y = yp[i];
187         }
188
189         XFillPolygon(display, drawable(), getGCForeground(col), points, np, 
190                      Nonconvex, CoordModeOrigin);
191 #ifndef HAVE_AUTO_PTR
192         delete[] points;
193 #endif  
194         return *this;
195 }      
196
197
198 PainterBase & Painter::arc(int x, int y,
199                   unsigned int w, unsigned int h,
200                   int a1, int a2, LColor::color col)
201 {
202         if (lyxerr.debugging()) {
203                 if (!Lgb_bug_find_hack)
204                         lyxerr << "arc not called from "
205                                 "workarea::workhandler\n";
206                 lyxerr << "Painter drawable: " << drawable() << endl;
207         }
208         
209         XDrawArc(display, drawable(), getGCForeground(col),
210                  x, y, w, h, a1, a2);
211         return *this;
212 }     
213
214
215 /// Draw lines from x1,y1 to x2,y2. They are arrays
216 PainterBase & Painter::segments(int const * x1, int const * y1, 
217                             int const * x2, int const * y2, int ns,
218                             LColor::color col,
219                             enum line_style ls, enum line_width lw)
220 {
221         if (lyxerr.debugging()) {
222                 if (!Lgb_bug_find_hack)
223                         lyxerr << "segments not called from "
224                                 "workarea::workhandler\n";
225                 lyxerr << "Painter drawable: " << drawable() << endl;
226         }
227         
228 #ifndef HAVE_AUTO_PTR
229         XSegment * s= new XSegment[ns];
230 #else
231         auto_ptr<XSegment> s(new XSegment[ns]);
232 #endif
233         for (int i=0; i<ns; ++i) {
234                 s[i].x1 = x1[i];
235                 s[i].y1 = y1[i];
236                 s[i].x2 = x2[i];
237                 s[i].y2 = y2[i];
238         }
239         XDrawSegments(display, drawable(), getGCLinepars(ls, lw, col), s, ns);
240
241 #ifndef HAVE_AUTO_PTR
242         delete [] s;
243 #endif
244         return *this;
245 }
246
247
248 PainterBase & Painter::pixmap(int x, int y, int w, int h, Pixmap bitmap)
249 {
250         if (lyxerr.debugging()) {
251                 if (!Lgb_bug_find_hack)
252                         lyxerr << "workAreaExpose not called from "
253                                 "workarea::workhandler\n";
254                 lyxerr << "Painter drawable: " << drawable() << endl;
255         }
256         
257         XGCValues val;
258         val.function = GXcopy;
259         GC gc = XCreateGC(display, drawable(),
260                           GCFunction, &val);
261         XCopyArea(display, bitmap, drawable(), gc,
262                   0, 0, w, h, x, y);
263         XFreeGC(display, gc);
264         return *this;
265 }
266
267
268 PainterBase & Painter::text(int x, int y, string const & s, LyXFont const & f)
269 {
270         return text(x, y, s.c_str(), s.length(), f);
271 }
272
273
274 PainterBase & Painter::text(int x, int y, char c, LyXFont const & f)
275 {
276         char s[2] = { c, '\0' };
277         return text(x, y, s, 1, f);
278 }
279
280
281 PainterBase & Painter::text(int x, int y, char const * s, int ls,
282                         LyXFont const & f)
283 {
284         if (lyxerr.debugging()) {
285                 if (!Lgb_bug_find_hack)
286                         lyxerr << "text not called from "
287                                 "workarea::workhandler\n";
288                 lyxerr << "Painter drawable: " << drawable() << endl;
289         }
290         GC gc = getGCForeground(f.realColor());
291         if (f.realShape() != LyXFont::SMALLCAPS_SHAPE) {
292                 lyxfont::XSetFont(display, gc, f);
293                 XDrawString(display, drawable(), gc, x, y, s, ls);
294         } else {
295                 LyXFont smallfont(f);
296                 smallfont.decSize().decSize().setShape(LyXFont::UP_SHAPE);
297                 char c;
298                 int tmpx = x;
299                 for(int i = 0; i < ls; ++i) {
300                         c = s[i];
301                         if (islower(static_cast<unsigned char>(c))) {
302                                 c = toupper(c);
303                                 lyxfont::XSetFont(display, gc, smallfont);
304                                 XDrawString(display, drawable(),
305                                             gc, tmpx, y, &c, 1);
306                                 tmpx += lyxfont::XTextWidth(smallfont, &c, 1);
307                                 //tmpx += lyxfont::width(c, f);
308                         } else {
309                                 lyxfont::XSetFont(display, gc, f);
310                                 XDrawString(display, drawable(),
311                                             gc, tmpx, y, &c, 1);
312                                 tmpx += lyxfont::XTextWidth(f, &c, 1);
313                                 //tmpx += lyxfont::width(c, f);
314                         }
315                 }
316         }
317         underline(f, x, y, lyxfont::width(s, ls, f));
318         return *this;
319 }
320
321
322 void Painter::underline(LyXFont const & f, int x, int y, int width)
323 {
324         // What about underbars?
325         if (f.underbar() == LyXFont::ON && f.latex() != LyXFont::ON) {
326                 int below = max(lyxfont::maxDescent(f) / 2, 2);
327                 int height = max((lyxfont::maxDescent(f) / 4) - 1, 1);
328                 if (height < 2)
329                         line(x, y + below, x + width, y + below, f.color());
330                 else
331                         fillRectangle(x, y + below, width, below + height,
332                                       f.color());
333         }
334 }
335
336
337 // Gets GC according to color
338 // Uses caching
339 GC Painter::getGCForeground(LColor::color c)
340 {
341         if (lyxerr.debugging()) {
342                 lyxerr << "Painter drawable: " << drawable() << endl;
343         }
344         
345         if (colorGCcache[c] != 0) return colorGCcache[c];
346
347         XColor xcol, ccol;
348         string s = lcolor.getX11Name(c);
349         XGCValues val;
350
351         // Look up the RGB values for the color, and an approximate
352         // color that we can hope to get on this display.
353         if (XLookupColor(display, colormap, s.c_str(), &xcol, &ccol) == 0) {
354                 lyxerr << _("LyX: Unknown X11 color ") << s
355                        << _(" for ") << lcolor.getGUIName(c) << '\n'
356                        << _("     Using black instead, sorry!.") << endl;
357                 unsigned long bla = BlackPixel(display,
358                                                DefaultScreen(display));
359                 val.foreground = bla;
360         // Try the exact RGB values first, then the approximate.
361         } else if (XAllocColor(display, colormap, &xcol) != 0) {
362                 if (lyxerr.debugging()) {
363                         lyxerr << _("LyX: X11 color ") << s
364                                << _(" allocated for ") 
365                                << lcolor.getGUIName(c) << endl;
366                 }
367                 val.foreground = xcol.pixel;
368         } else if (XAllocColor(display, colormap, &ccol)) {
369                 lyxerr << _("LyX: Using approximated X11 color ") << s
370                        << _(" allocated for ")
371                        << lcolor.getGUIName(c) << endl;
372                 val.foreground = xcol.pixel;
373         } else {
374                 // Here we are traversing the current colormap to find
375                 // the color closest to the one we want.
376                 Visual * vi = DefaultVisual(display, DefaultScreen(display));
377
378                 XColor * cmap = new XColor[vi->map_entries];
379
380                 for(int i = 0; i < vi->map_entries; ++i) {
381                         cmap[i].pixel = i;
382                 }
383                 XQueryColors(display, colormap, cmap, vi->map_entries);
384
385                 // Walk through the cmap and look for close colors.
386                 int closest_pixel = 0;
387                 double closest_distance = 1e20; // we want to minimize this
388                 double distance = 0;
389                 for(int t = 0; t < vi->map_entries; ++t) {
390                         // The Euclidean distance between two points in 
391                         // a three-dimensional space, the RGB color-cube,
392                         // is used as the distance measurement between two
393                         // colors.
394
395                         // Since square-root is monotonous, we don't have to
396                         // take the square-root to find the minimum, and thus 
397                         // we use the squared distance instead to be faster.
398
399                         // If we want to get fancy, we could convert the RGB
400                         // coordinates to a different color-cube, maybe HSV,
401                         // but the RGB cube seems to work great.  (Asger)
402                         distance = pow(cmap[t].red   - xcol.red,   2.0) +
403                                    pow(cmap[t].green - xcol.green, 2.0) +
404                                    pow(cmap[t].blue  - xcol.blue,  2.0);
405                         if (distance < closest_distance) {
406                                 closest_distance = distance;
407                                 closest_pixel = t;
408                         }
409                 }
410                 lyxerr << _("LyX: Couldn't allocate '") << s 
411                        << _("' for ") << lcolor.getGUIName(c)
412                        << _(" with (r,g,b)=(") 
413                        << xcol.red << "," << xcol.green << ","
414                        << xcol.blue << ").\n"
415                        << _("     Using closest allocated "
416                             "color with (r,g,b)=(") 
417                        << cmap[closest_pixel].red << ","
418                        << cmap[closest_pixel].green << ","
419                        << cmap[closest_pixel].blue << ") instead.\n"
420                        << "Pixel [" << closest_pixel << "] is used." << endl;
421                 val.foreground = cmap[closest_pixel].pixel;
422                 delete[] cmap;
423         }
424
425         val.function = GXcopy;
426         return colorGCcache[c] = XCreateGC(display, drawable(),
427                                     GCForeground | GCFunction, &val);
428 }
429
430
431 // Gets GC for line
432 GC Painter::getGCLinepars(enum line_style ls,
433                           enum line_width lw, LColor::color c)
434 {
435         if (lyxerr.debugging()) {
436                 lyxerr << "Painter drawable: " << drawable() << endl;
437         }
438         
439         int index = lw + (ls << 1) + (c << 3);
440
441         if (lineGCcache.find(index) != lineGCcache.end())
442                 return lineGCcache[index];
443
444         XGCValues val;
445         XGetGCValues(display, getGCForeground(c), GCForeground, &val);
446         
447         switch (lw) {
448         case line_thin:         val.line_width = 0; break;
449         case line_thick:        val.line_width = 2; break;
450         }
451         
452         switch (ls) {
453         case line_solid:        val.line_style = LineSolid; break;
454         case line_onoffdash:    val.line_style = LineOnOffDash; break;
455         case line_doubledash:   val.line_style = LineDoubleDash; break;
456         }
457
458
459         val.cap_style = CapRound;
460         val.join_style = JoinRound;
461         val.function = GXcopy;
462
463         return lineGCcache[index] =
464                 XCreateGC(display, drawable(), 
465                           GCForeground | GCLineStyle | GCLineWidth | 
466                           GCCapStyle | GCJoinStyle | GCFunction, &val);
467 }