]> git.lyx.org Git - lyx.git/blob - src/frontends/screen.C
e9f64b0e29fd7973b84e3ef073413e388f3ac4fc
[lyx.git] / src / frontends / screen.C
1 /* This file is part of
2 * ======================================================
3 *
4 *           LyX, The Document Processor
5 *
6 *           Copyright 1995 Matthias Ettrich
7 *           Copyright 1995-1998 The LyX Team
8 *
9 * ====================================================== */
10
11 #include <config.h>
12
13 #ifdef __GNUG__
14 #pragma implementation
15 #endif
16
17 #include <algorithm>
18
19 #include "screen.h"
20 #include "lyxtext.h"
21 #include "lyxrow.h"
22 #include "frontends/Painter.h"
23 #include "frontends/WorkArea.h"
24 #include "buffer.h"
25 #include "BufferView.h"
26 #include "font_metrics.h"
27 #include "insets/insettext.h"
28 #include "frontends/xforms/ColorHandler.h"
29 #include "language.h"
30
31 using std::max;
32 using std::min;
33
34 namespace {
35
36 GC createGC()
37 {
38         XGCValues val;
39         val.foreground = BlackPixel(fl_get_display(),
40                                     DefaultScreen(fl_get_display()));
41
42         val.function = GXcopy;
43         val.graphics_exposures = false;
44         val.line_style = LineSolid;
45         val.line_width = 0;
46         return XCreateGC(fl_get_display(), RootWindow(fl_get_display(), 0),
47                          GCForeground | GCFunction | GCGraphicsExposures
48                          | GCLineWidth | GCLineStyle , &val);
49 }
50
51 } // namespace anon
52
53
54 // Constructor
55 LScreen::LScreen(WorkArea & o)
56         : owner(o), force_clear(true)
57 {
58         // the cursor isnt yet visible
59         cursor_visible = false;
60         cursor_pixmap = 0;
61         cursor_pixmap_x = 0;
62         cursor_pixmap_y = 0;
63         cursor_pixmap_w = 0;
64         cursor_pixmap_h = 0;
65
66         // We need this GC
67         gc_copy = createGC();
68 }
69
70
71 LScreen::~LScreen()
72 {
73         XFreeGC(fl_get_display(), gc_copy);
74 }
75
76
77 void LScreen::reset()
78 {
79         XFreeGC(fl_get_display(), gc_copy);
80         // the cursor isnt yet visible
81         cursor_visible = false;
82         cursor_pixmap = 0;
83         cursor_pixmap_x = 0;
84         cursor_pixmap_y = 0;
85         cursor_pixmap_w = 0;
86         cursor_pixmap_h = 0;
87
88         // We need this GC
89         gc_copy = createGC();
90 }
91  
92  
93 void LScreen::setCursorColor()
94 {
95         if (!lyxColorHandler.get()) return;
96
97         GC gc = lyxColorHandler->getGCForeground(LColor::cursor);
98
99         XGCValues val;
100         XGetGCValues(fl_get_display(),
101                      gc, GCForeground, &val);
102         XChangeGC(fl_get_display(), gc_copy, GCForeground, &val);
103 }
104
105
106 void LScreen::redraw(LyXText * text, BufferView * bv)
107 {
108         drawFromTo(text, bv, 0, owner.workHeight(), 0, 0, text == bv->text);
109         expose(0, 0, owner.workWidth(), owner.workHeight());
110         if (cursor_visible) {
111                 cursor_visible = false;
112                 bv->showCursor();
113         }
114 }
115
116
117 void LScreen::expose(int x, int y, int exp_width, int exp_height)
118 {
119         XCopyArea(fl_get_display(),
120                   owner.getPixmap(),
121                   owner.getWin(),
122                   gc_copy,
123                   x, y,
124                   exp_width, exp_height,
125                   x + owner.xpos(),
126                   y + owner.ypos());
127 }
128
129
130 void LScreen::drawFromTo(LyXText * text, BufferView * bv,
131                            int y1, int y2, int y_offset, int x_offset,
132                            bool internal)
133 {
134         int y_text = text->first_y + y1;
135
136         // get the first needed row
137         Row * row = text->getRowNearY(y_text);
138         // y_text is now the real beginning of the row
139
140         int y = y_text - text->first_y;
141         // y1 is now the real beginning of row on the screen
142
143         while (row != 0 && y < y2) {
144                 LyXText::text_status st = text->status();
145                 text->getVisibleRow(bv, y + y_offset,
146                                     x_offset, row, y + text->first_y);
147                 internal = internal && (st != LyXText::CHANGED_IN_DRAW);
148                 while (internal && text->status() == LyXText::CHANGED_IN_DRAW) {
149                         text->fullRebreak(bv);
150                         st = LyXText::NEED_MORE_REFRESH;
151                         text->setCursor(bv, text->cursor.par(),
152                                         text->cursor.pos());
153                         text->status(bv, st);
154                         // we should be sure our row-pointer is still valid, so it's
155                         // better to recompute it.
156                         y_text = y + text->first_y;
157                         row = text->getRowNearY(y_text);
158                         y = y_text - text->first_y;
159                         text->getVisibleRow(bv, y + y_offset,
160                                             x_offset, row, y + text->first_y);
161                 }
162                 y += row->height();
163                 row = row->next();
164         }
165         force_clear = false;
166
167         // maybe we have to clear the screen at the bottom
168         if ((y < y2) && text->bv_owner) {
169                 owner.getPainter().fillRectangle(0, y,
170                                                  owner.workWidth(),
171                                                  y2 - y,
172                                                LColor::bottomarea);
173         }
174 }
175
176
177 void LScreen::drawOneRow(LyXText * text, BufferView * bv, Row * row,
178                            int y_text, int y_offset, int x_offset)
179 {
180         int const y = y_text - text->first_y + y_offset;
181
182         if (((y + row->height()) > 0) &&
183             ((y - row->height()) <= static_cast<int>(owner.workHeight()))) {
184                 // ok there is something visible
185                 text->getVisibleRow(bv, y, x_offset, row, y + text->first_y);
186         }
187         force_clear = false;
188 }
189
190
191 /* draws the screen, starting with textposition y. uses as much already
192  * printed pixels as possible */
193 void LScreen::draw(LyXText * text, BufferView * bv, unsigned int y)
194 {
195         if (cursor_visible) hideCursor();
196
197         int const old_first = text->first_y;
198         bool internal = (text == bv->text);
199         text->first_y = y;
200
201         // is any optimiziation possible?
202         if ((y - old_first) < owner.workHeight()
203             && (old_first - y) < owner.workHeight())
204         {
205                 if (text->first_y < old_first) {
206                         drawFromTo(text, bv, 0,
207                                    old_first - text->first_y, 0, 0, internal);
208                         XCopyArea (fl_get_display(),
209                                    owner.getWin(),
210                                    owner.getWin(),
211                                    gc_copy,
212                                    owner.xpos(),
213                                    owner.ypos(),
214                                    owner.workWidth(),
215                                    owner.workHeight() - old_first + text->first_y,
216                                    owner.xpos(),
217                                    owner.ypos() + old_first - text->first_y
218                                 );
219                         // expose the area drawn
220                         expose(0, 0,
221                                owner.workWidth(),
222                                old_first - text->first_y);
223                 } else  {
224                         drawFromTo(text, bv,
225                                    owner.workHeight() + old_first - text->first_y,
226                                    owner.workHeight(), 0, 0, internal);
227                         XCopyArea (fl_get_display(),
228                                    owner.getWin(),
229                                    owner.getWin(),
230                                    gc_copy,
231                                    owner.xpos(),
232                                    owner.ypos() + text->first_y - old_first,
233                                    owner.workWidth(),
234                                    owner.workHeight() + old_first - text->first_y,
235                                    owner.xpos(),
236                                    owner.ypos());
237                         // expose the area drawn
238                         expose(0, owner.workHeight() + old_first - text->first_y,
239                                owner.workWidth(), text->first_y - old_first);
240                 }
241         } else {
242                 // make a dumb new-draw
243                 drawFromTo(text, bv, 0, owner.workHeight(), 0, 0, internal);
244                 expose(0, 0, owner.workWidth(), owner.workHeight());
245         }
246 }
247
248
249 void LScreen::showCursor(LyXText const * text, BufferView const * bv)
250 {
251         if (!cursor_visible) {
252                 Cursor_Shape shape = BAR_SHAPE;
253                 if (text->real_current_font.language() !=
254                     bv->buffer()->params.language
255                     || text->real_current_font.isVisibleRightToLeft()
256                     != bv->buffer()->params.language->RightToLeft())
257                         shape = (text->real_current_font.isVisibleRightToLeft())
258                                 ? REVERSED_L_SHAPE : L_SHAPE;
259                 showManualCursor(text, text->cursor.x(), text->cursor.y(),
260                                  font_metrics::maxAscent(text->real_current_font),
261                                  font_metrics::maxDescent(text->real_current_font),
262                                  shape);
263         }
264 }
265
266
267 /* returns true if first has changed, otherwise false */
268 bool LScreen::fitManualCursor(LyXText * text, BufferView * bv,
269                                 int /*x*/, int y, int asc, int desc)
270 {
271         int newtop = text->first_y;
272
273         if (y + desc - text->first_y >= static_cast<int>(owner.workHeight()))
274                 newtop = y - 3 * owner.workHeight() / 4;  // the scroll region must be so big!!
275         else if (y - asc < text->first_y
276                 && text->first_y > 0) {
277                 newtop = y - owner.workHeight() / 4;
278         }
279
280         newtop = max(newtop, 0); // can newtop ever be < 0? (Lgb)
281
282         if (newtop != text->first_y) {
283                 draw(text, bv, newtop);
284                 text->first_y = newtop;
285                 return true;
286         }
287         return false;
288 }
289
290
291 void LScreen::showManualCursor(LyXText const * text, int x, int y,
292                                  int asc, int desc, Cursor_Shape shape)
293 {
294         // Update the cursor color.
295         setCursorColor();
296
297         int const y1 = max(y - text->first_y - asc, 0);
298         int const y_tmp = min(y - text->first_y + desc,
299                               static_cast<int>(owner.workHeight()));
300
301         // Secure against very strange situations
302         int const y2 = max(y_tmp, y1);
303
304         if (cursor_pixmap) {
305                 XFreePixmap(fl_get_display(), cursor_pixmap);
306                 cursor_pixmap = 0;
307         }
308
309         if (y2 > 0 && y1 < int(owner.workHeight())) {
310                 cursor_pixmap_h = y2 - y1 + 1;
311                 cursor_pixmap_y = y1;
312
313                 switch (shape) {
314                 case BAR_SHAPE:
315                         cursor_pixmap_w = 1;
316                         cursor_pixmap_x = x;
317                         break;
318                 case L_SHAPE:
319                         cursor_pixmap_w = cursor_pixmap_h/3;
320                         cursor_pixmap_x = x;
321                         break;
322                 case REVERSED_L_SHAPE:
323                         cursor_pixmap_w = cursor_pixmap_h/3;
324                         cursor_pixmap_x = x - cursor_pixmap_w + 1;
325                         break;
326                 }
327
328                 cursor_pixmap =
329                         XCreatePixmap (fl_get_display(),
330                                        fl_root,
331                                        cursor_pixmap_w,
332                                        cursor_pixmap_h,
333                                        fl_get_visual_depth());
334                 XCopyArea (fl_get_display(),
335                            owner.getWin(),
336                            cursor_pixmap,
337                            gc_copy,
338                            owner.xpos() + cursor_pixmap_x,
339                            owner.ypos() + cursor_pixmap_y,
340                            cursor_pixmap_w,
341                            cursor_pixmap_h,
342                            0, 0);
343                 XDrawLine(fl_get_display(),
344                           owner.getWin(),
345                           gc_copy,
346                           x + owner.xpos(),
347                           y1 + owner.ypos(),
348                           x + owner.xpos(),
349                           y2 + owner.ypos());
350                 switch (shape) {
351                 case BAR_SHAPE:
352                         break;
353                 case L_SHAPE:
354                 case REVERSED_L_SHAPE:
355                         int const rectangle_h = (cursor_pixmap_h + 10) / 20;
356                         XFillRectangle(fl_get_display(),
357                                        owner.getWin(),
358                                        gc_copy,
359                                        cursor_pixmap_x + owner.xpos(),
360                                        y2 - rectangle_h + 1 + owner.ypos(),
361                                        cursor_pixmap_w - 1, rectangle_h);
362                         break;
363                 }
364
365         }
366         cursor_visible = true;
367 }
368
369
370 void LScreen::hideCursor()
371 {
372         if (!cursor_visible) return;
373
374         if (cursor_pixmap) {
375                 XCopyArea (fl_get_display(),
376                            cursor_pixmap,
377                            owner.getWin(),
378                            gc_copy,
379                            0, 0,
380                            cursor_pixmap_w, cursor_pixmap_h,
381                            cursor_pixmap_x + owner.xpos(),
382                            cursor_pixmap_y + owner.ypos());
383         }
384         cursor_visible = false;
385 }
386
387
388 void LScreen::cursorToggle(BufferView * bv) const
389 {
390         if (cursor_visible)
391                 bv->hideCursor();
392         else
393                 bv->showCursor();
394 }
395
396
397 /* returns a new top so that the cursor is visible */
398 unsigned int LScreen::topCursorVisible(LyXText const * text)
399 {
400         int newtop = text->first_y;
401
402         Row * row = text->cursor.row();
403
404         // Is this a hack? Yes, probably... (Lgb)
405         if (!row)
406                 return max(newtop, 0);
407
408         if (text->cursor.y() - row->baseline() + row->height()
409             - text->first_y >= owner.workHeight()) {
410                 if (row->height() < owner.workHeight()
411                     && row->height() > owner.workHeight() / 4) {
412                         newtop = text->cursor.y()
413                                 + row->height()
414                                 - row->baseline() - owner.workHeight();
415                 } else {
416                         // scroll down
417                         newtop = text->cursor.y()
418                                 - owner.workHeight() / 2;   /* the scroll region must be so big!! */
419                 }
420
421         } else if (static_cast<int>((text->cursor.y()) - row->baseline()) <
422                    text->first_y && text->first_y > 0) {
423                 if (row->height() < owner.workHeight()
424                     && row->height() > owner.workHeight() / 4) {
425                         newtop = text->cursor.y() - row->baseline();
426                 } else {
427                         // scroll up
428                         newtop = text->cursor.y() - owner.workHeight() / 2;
429                         newtop = min(newtop, text->first_y);
430                 }
431         }
432
433         newtop = max(newtop, 0);
434
435         return newtop;
436 }
437
438
439 /* scrolls the screen so that the cursor is visible, if necessary.
440 * returns true if a change was made, otherwise false */
441 bool LScreen::fitCursor(LyXText * text, BufferView * bv)
442 {
443         // Is a change necessary?
444         int const newtop = topCursorVisible(text);
445         bool const result = (newtop != text->first_y);
446         if (result)
447                 draw(text, bv, newtop);
448         return result;
449 }
450
451
452 void LScreen::update(LyXText * text, BufferView * bv,
453                        int y_offset, int x_offset)
454 {
455         switch (text->status()) {
456         case LyXText::NEED_MORE_REFRESH:
457         {
458                 int const y = max(int(text->refresh_y - text->first_y), 0);
459                 drawFromTo(text, bv, y, owner.workHeight(), y_offset, x_offset);
460                 text->refresh_y = 0;
461                 // otherwise this is called ONLY from BufferView_pimpl(update)
462                 // or we should see to set this flag accordingly
463                 if (text != bv->text)
464                         text->status(bv, LyXText::UNCHANGED);
465                 expose(0, y, owner.workWidth(), owner.workHeight() - y);
466         }
467         break;
468         case LyXText::NEED_VERY_LITTLE_REFRESH:
469         {
470                 // ok I will update the current cursor row
471                 drawOneRow(text, bv, text->refresh_row, text->refresh_y,
472                            y_offset, x_offset);
473                 // this because if we had a major update the refresh_row could
474                 // have been set to 0!
475                 if (text->refresh_row) {
476                         // otherwise this is called ONLY from BufferView_pimpl(update)
477                         // or we should see to set this flag accordingly
478                         if (text != bv->text)
479                                 text->status(bv, LyXText::UNCHANGED);
480                         expose(0, text->refresh_y - text->first_y + y_offset,
481                                    owner.workWidth(), text->refresh_row->height());
482                 }
483         }
484         break;
485         case LyXText::CHANGED_IN_DRAW: // just to remove the warning
486         case LyXText::UNCHANGED:
487                 // Nothing needs done
488                 break;
489         }
490 }
491
492
493 void LScreen::toggleSelection(LyXText * text, BufferView * bv,
494                                 bool kill_selection,
495                                 int y_offset, int x_offset)
496 {
497         // only if there is a selection
498         if (!text->selection.set()) return;
499
500         int const bottom = min(
501                 max(static_cast<int>(text->selection.end.y()
502                                      - text->selection.end.row()->baseline()
503                                      + text->selection.end.row()->height()),
504                     text->first_y),
505                 static_cast<int>(text->first_y + owner.workHeight()));
506         int const top = min(
507                 max(static_cast<int>(text->selection.start.y() -
508                                      text->selection.start.row()->baseline()),
509                     text->first_y),
510                 static_cast<int>(text->first_y + owner.workHeight()));
511
512         if (kill_selection)
513                 text->selection.set(false);
514         drawFromTo(text, bv, top - text->first_y, bottom - text->first_y,
515                    y_offset, x_offset);
516         expose(0, top - text->first_y,
517                owner.workWidth(),
518                bottom - text->first_y - (top - text->first_y));
519 }
520
521
522 void LScreen::toggleToggle(LyXText * text, BufferView * bv,
523                              int y_offset, int x_offset)
524 {
525         if (text->toggle_cursor.par() == text->toggle_end_cursor.par()
526             && text->toggle_cursor.pos() == text->toggle_end_cursor.pos())
527                 return;
528
529         int const top_tmp = text->toggle_cursor.y()
530                 - text->toggle_cursor.row()->baseline();
531         int const bottom_tmp = text->toggle_end_cursor.y()
532                 - text->toggle_end_cursor.row()->baseline()
533                 + text->toggle_end_cursor.row()->height();
534
535         int const offset = y_offset < 0 ? y_offset : 0;
536         int const bottom = min(max(bottom_tmp, text->first_y),
537                      static_cast<int>(text->first_y + owner.workHeight()))-offset;
538         int const top = min(max(top_tmp, text->first_y),
539                   static_cast<int>(text->first_y + owner.workHeight()))-offset;
540
541         drawFromTo(text, bv, top - text->first_y,
542                    bottom - text->first_y, y_offset,
543                    x_offset);
544         expose(0, top - text->first_y, owner.workWidth(),
545                bottom - text->first_y - (top - text->first_y));
546 }