]> git.lyx.org Git - lyx.git/blob - src/WorkArea.C
Fix working of the spellchecker dialog with ispell when there are no
[lyx.git] / src / WorkArea.C
1 /* This file is part of
2  * ======================================================
3  * 
4  *           LyX, The Document Processor
5  *        
6  *           Copyright 1995 Matthias Ettrich
7  *           Copyright 1995-2001 The LyX Team.
8  *
9  * ====================================================== */
10
11 #include <config.h>
12 #include <cmath>
13 #include <cctype>
14 #ifdef __GNUG__
15 #pragma implementation
16 #endif
17
18 #include "WorkArea.h"
19 #include "debug.h"
20 #include "support/lstrings.h"
21 #include "LyXView.h"
22 #include "support/filetools.h" // LibFileSearch
23 #include "lyxrc.h" // lyxrc.show_banner
24 #include "version.h" // LYX_VERSION
25 #include "support/LAssert.h"
26
27 #if FL_REVISION < 89 || (FL_REVISION == 89 && FL_FIXLEVEL < 5)
28 #include "lyxlookup.h"
29 #endif
30
31 using std::endl;
32
33 FL_OBJECT * figinset_canvas;
34
35 // needed to make the c++ compiler find the correct version of abs.
36 // This is at least true for g++.
37 //using std::abs;
38
39 namespace {
40
41 inline
42 void waitForX()
43 {
44         XSync(fl_get_display(), 0);
45 }
46
47 } // anon namespace
48
49
50 extern "C" {
51         // Just a bunch of C wrappers around static members of WorkArea
52         static
53         void C_WorkArea_scroll_cb(FL_OBJECT * ob, long buf)
54         {
55                 WorkArea::scroll_cb(ob, buf);
56         }
57
58         
59         static
60         int C_WorkArea_work_area_handler(FL_OBJECT * ob, int event,
61                                          FL_Coord, FL_Coord, 
62                                          int key, void * xev)
63         {
64                 return WorkArea::work_area_handler(ob, event,
65                                                    0, 0, key, xev);
66         }
67
68
69         // Resizing the display causes the version string to move relative to
70         // the splash pixmap because the parameters xforms uses to control
71         // resizing are not very sophisticated.
72         // I found it easier, therefore, to just remove the splash screen.
73         // (Angus, 25 September 2001)
74         static
75         int C_WorkAreaSplashPH(FL_OBJECT * ob, int event,
76                                FL_Coord, FL_Coord, int, void *)
77         {
78                 static int counter = 0;
79                 if (event != FL_DRAW || ++counter > 3) {
80                         return 0;
81                 }
82
83                 lyx::Assert(ob && ob->u_vdata);
84                 WorkArea * pre = static_cast<WorkArea *>(ob->u_vdata);
85
86                 if (counter == 3) {
87                         pre->destroySplash();
88                 }
89
90                 return 0;
91         }
92 }
93
94
95 WorkArea::WorkArea(int xpos, int ypos, int width, int height)
96         : splash_(0), splash_text_(0), workareapixmap(0), painter_(*this)
97 {
98         fl_freeze_all_forms();
99
100         figinset_canvas = 0;
101
102         if (lyxerr.debugging(Debug::GUI))
103                 lyxerr << "Creating work area: +"
104                        << xpos << '+' << ypos << ' '
105                        << width << 'x' << height << endl;
106         //
107         FL_OBJECT * obj;
108         int const bw = int(std::abs(float(fl_get_border_width())));
109
110         // We really want to get rid of figinset_canvas.
111         ::figinset_canvas = figinset_canvas = obj =
112                   fl_add_canvas(FL_NORMAL_CANVAS,
113                                 xpos + 1, ypos + 1, 1, 1, "");
114         fl_set_object_boxtype(obj, FL_NO_BOX);
115         fl_set_object_resize(obj, FL_RESIZE_ALL);
116         fl_set_object_gravity(obj, NorthWestGravity, NorthWestGravity);
117
118         // a box
119         if (lyxerr.debugging(Debug::GUI))
120                 lyxerr << "\tbackground box: +"
121                        << xpos << '+' << ypos << ' '
122                        << width - 15 << 'x' << height << endl;
123         backgroundbox = obj = fl_add_box(FL_BORDER_BOX,
124                                          xpos, ypos,
125                                          width - 15,
126                                          height,"");
127         fl_set_object_resize(obj, FL_RESIZE_ALL);
128         fl_set_object_gravity(obj, NorthWestGravity, SouthEastGravity);
129
130         // Add a splash screen to the centre of the work area
131         string const splash_file = (lyxrc.show_banner) ?
132                 LibFileSearch("images", "banner", "xpm") : string();
133
134         if (!splash_file.empty()) {
135                 int const splash_w = 425;
136                 int const splash_h = 290;
137                 int const splash_x = xpos + (width - 15 - splash_w) / 2;
138                 int const splash_y = ypos + (height - splash_h) / 2;
139                 splash_ = obj =
140                         fl_add_pixmapbutton(FL_NORMAL_BUTTON,
141                                             splash_x, splash_y, 
142                                             splash_w, splash_h, "");
143                 obj->u_vdata = this;
144                 fl_set_object_prehandler(obj, C_WorkAreaSplashPH);
145
146                 fl_set_pixmapbutton_file(obj, splash_file.c_str());
147                 fl_set_pixmapbutton_focus_outline(obj, 3);
148                 fl_set_object_boxtype(obj, FL_NO_BOX);
149
150                 int const text_x = splash_x + 248;
151                 int const text_y = splash_y + 265;
152                 splash_text_ = obj =
153                         fl_add_text(FL_NORMAL_TEXT, text_x, text_y, 170, 16,
154                                     LYX_VERSION);
155                 fl_set_object_lsize(obj, FL_NORMAL_SIZE);
156                 fl_mapcolor(FL_FREE_COL2, 0x2b, 0x47, 0x82);
157                 fl_mapcolor(FL_FREE_COL3, 0xe1, 0xd2, 0x9b);
158                 fl_set_object_color(obj, FL_FREE_COL2, FL_FREE_COL2);
159                 fl_set_object_lcol(obj, FL_FREE_COL3);
160                 fl_set_object_lalign(obj, FL_ALIGN_CENTER|FL_ALIGN_INSIDE);
161                 fl_set_object_lstyle(obj, FL_BOLD_STYLE);
162
163                 fl_hide_object(splash_);
164                 fl_hide_object(splash_text_);
165         }
166
167         //
168         // THE SCROLLBAR
169         //
170
171         scrollbar = obj = fl_add_scrollbar(FL_VERT_SCROLLBAR,
172                                            xpos + width - 15,
173                                            ypos, 17, height, "");
174         fl_set_object_boxtype(obj, FL_UP_BOX);
175         fl_set_object_resize(obj, FL_RESIZE_ALL);
176         fl_set_object_gravity(obj, NorthEastGravity, SouthEastGravity);
177         obj->u_vdata = this;
178         fl_set_object_callback(obj, C_WorkArea_scroll_cb, 0);
179
180         ///
181         /// The free object
182
183         // Create the workarea pixmap
184         createPixmap(width - 15 - 2 * bw, height - 2 * bw);
185
186         // We add this object as late as possible to avoit problems
187         // with drawing.
188         if (lyxerr.debugging(Debug::GUI))
189                 lyxerr << "\tfree object: +"
190                        << xpos + bw << '+' << ypos + bw << ' '
191                        << width - 15 - 2 * bw << 'x'
192                        << height - 2 * bw << endl;
193         
194         work_area = obj = fl_add_free(FL_ALL_FREE,
195                                       xpos + bw, ypos + bw,
196                                       width - 15 - 2 * bw, // scrollbarwidth
197                                       height - 2 * bw, "",
198                                       C_WorkArea_work_area_handler);
199         obj->wantkey = FL_KEY_ALL;
200         obj->u_vdata = this; /* This is how we pass the WorkArea
201                                 to the work_area_handler. */
202         fl_set_object_boxtype(obj,FL_DOWN_BOX);
203         fl_set_object_resize(obj, FL_RESIZE_ALL);
204         fl_set_object_gravity(obj, NorthWestGravity, SouthEastGravity);
205
206         fl_unfreeze_all_forms();
207 }
208
209
210 WorkArea::~WorkArea()
211 {
212         if (workareapixmap)
213                 XFreePixmap(fl_get_display(), workareapixmap);
214 }
215
216
217 bool WorkArea::belowMouse() const
218 {
219         FL_Coord x, y;
220         unsigned int button;
221         fl_get_mouse(&x, &y, &button);
222         FL_Coord ulx = work_area->form->x + work_area->x;
223         FL_Coord uly = work_area->form->y + work_area->y;
224         FL_Coord w = work_area->w;
225         FL_Coord h = work_area->h;
226         if (x > ulx && y > uly && x < ulx + h && y < uly + w)
227                 return true;
228         return false;
229         
230         
231         //lyxerr << "Mouse: (" << x << ", " << y <<") button = " << button << endl;
232         //lyxerr << "Workarea: (" << work_area->x + work_area->form->x << ", " << work_area->y + work_area->form->y << ", " << work_area->w << ", " << work_area->h << ")" << endl;
233         //lyxerr << "Below mouse: " << work_area->belowmouse << endl;
234         //return work_area->belowmouse;
235 }
236
237
238 void WorkArea::resize(int xpos, int ypos, int width, int height)
239 {
240         fl_freeze_all_forms();
241         
242         int const bw = int(std::abs(float(fl_get_border_width())));
243
244         // a box
245         fl_set_object_geometry(backgroundbox, xpos, ypos, width - 15, height);
246
247         //
248         // THE SCROLLBAR
249         //
250         fl_set_object_geometry(scrollbar, xpos + width - 15,
251                                ypos, 17, height);
252
253         // Create the workarea pixmap
254         createPixmap(width - 15 - 2 * bw, height - 2 * bw);
255
256         // the free object
257         fl_set_object_geometry(work_area, xpos + bw, ypos + bw,
258                                width - 15 - 2 * bw,
259                                height - 2 * bw);
260
261         destroySplash();
262
263         fl_unfreeze_all_forms();
264 }
265
266
267 void WorkArea::createPixmap(int width, int height)
268 {
269         static int cur_width = -1;
270         static int cur_height = -1;
271
272         if (cur_width == width && cur_height == height && workareapixmap)
273                 return;
274         
275         cur_width = width;
276         cur_height = height;
277
278         if (workareapixmap)
279                 XFreePixmap(fl_get_display(), workareapixmap);
280
281         if (lyxerr.debugging(Debug::GUI))
282                 lyxerr << "Creating pixmap ("
283                        << width << 'x' << height << ")" << endl;
284         
285         workareapixmap = XCreatePixmap(fl_get_display(),
286                                        RootWindow(fl_get_display(), 0),
287                                        width,
288                                        height, 
289                                        fl_get_visual_depth());
290         if (lyxerr.debugging(Debug::GUI))
291                 lyxerr << "\tpixmap=" << workareapixmap << endl;
292 }
293
294
295 void WorkArea::show() const
296 {
297         if (!work_area->visible) {
298                 fl_show_object(work_area);
299         }
300         
301         destroySplash();
302 }
303
304
305 void WorkArea::greyOut() const
306 {
307         if (work_area->visible) {
308                 fl_hide_object(work_area);
309         }
310
311         if (splash_ && !splash_->visible) {
312                 fl_show_object(splash_);
313                 fl_show_object(splash_text_);
314         }
315 }
316
317
318 void WorkArea::destroySplash() const
319 {
320         if (splash_) {
321                 if (splash_->visible) {
322                         fl_hide_object(splash_);
323                 }
324                 fl_set_object_prehandler(splash_, 0);
325                 // Causes a segmentation fault!
326                 // fl_delete_object(splash_);
327                 // fl_free_object(splash_);
328                 splash_ = 0;
329         }
330
331         if (splash_text_) {
332                 if (splash_text_->visible) {
333                         fl_hide_object(splash_text_);
334                 }
335                 fl_delete_object(splash_text_);
336                 fl_free_object(splash_text_);
337                 splash_text_ = 0;
338         }
339 }
340         
341
342 void WorkArea::setFocus() const
343 {
344         fl_set_focus_object(work_area->form, work_area);
345 }
346
347
348 void WorkArea::setScrollbar(double pos, double length_fraction) const
349 {
350         fl_set_scrollbar_value(scrollbar, pos);
351         fl_set_scrollbar_size(scrollbar, scrollbar->h * length_fraction);
352 }
353
354
355 void WorkArea::setScrollbarBounds(double l1, double l2) const
356 {
357         fl_set_scrollbar_bounds(scrollbar, l1, l2);
358 }
359
360
361 void WorkArea::setScrollbarIncrements(double inc) const
362 {
363         fl_set_scrollbar_increment(scrollbar, work_area->h - inc, inc);
364 }
365
366
367 // Callback for scrollbar slider
368 void WorkArea::scroll_cb(FL_OBJECT * ob, long)
369 {
370         WorkArea * area = static_cast<WorkArea*>(ob->u_vdata);
371         // If we really want the accellerating scroll we can do that
372         // from here. IMHO that is a waste of effort since we already
373         // have other ways to move fast around in the document. (Lgb)
374         area->scrollCB(fl_get_scrollbar_value(ob));
375         waitForX();
376 }
377
378
379 bool Lgb_bug_find_hack = false;
380
381 int WorkArea::work_area_handler(FL_OBJECT * ob, int event,
382                                 FL_Coord, FL_Coord ,
383                                 int key, void * xev)
384 {
385         static int x_old = -1;
386         static int y_old = -1;
387         static long scrollbar_value_old = -1;
388         
389         XEvent * ev = static_cast<XEvent*>(xev);
390         WorkArea * area = static_cast<WorkArea*>(ob->u_vdata);
391
392         if (!area) return 1;
393         
394         switch (event){
395         case FL_DRAW:
396                 if (!area->work_area ||
397                     !area->work_area->form->visible)
398                         return 1;
399                 lyxerr[Debug::GUI] << "Workarea event: DRAW" << endl;
400                 area->createPixmap(area->workWidth(), area->height());
401                 Lgb_bug_find_hack = true;
402                 area->workAreaExpose();
403                 Lgb_bug_find_hack = false;
404                 break;
405         case FL_PUSH:
406                 if (!ev || ev->xbutton.button == 0) break;
407                 // Should really have used xbutton.state
408                 lyxerr[Debug::GUI] << "Workarea event: PUSH" << endl;
409                 area->workAreaButtonPress(ev->xbutton.x - ob->x,
410                                           ev->xbutton.y - ob->y,
411                                           ev->xbutton.button);
412                 //area->workAreaKeyPress(XK_Pointer_Button1, ev->xbutton.state);
413                 break; 
414         case FL_RELEASE:
415                 if (!ev || ev->xbutton.button == 0) break;
416                 // Should really have used xbutton.state
417                 lyxerr[Debug::GUI] << "Workarea event: RELEASE" << endl;
418                 area->workAreaButtonRelease(ev->xbutton.x - ob->x,
419                                       ev->xbutton.y - ob->y,
420                                       ev->xbutton.button);
421                 break;
422 #if FL_REVISION < 89
423         case FL_MOUSE:
424 #else
425         case FL_DRAG:
426 #endif
427                 if (!ev || ! area->scrollbar) break;
428                 if (ev->xmotion.x != x_old ||
429                     ev->xmotion.y != y_old ||
430                     fl_get_scrollbar_value(area->scrollbar) != scrollbar_value_old
431                         ) {
432                         lyxerr[Debug::GUI] << "Workarea event: MOUSE" << endl;
433                         area->workAreaMotionNotify(ev->xmotion.x - ob->x,
434                                              ev->xmotion.y - ob->y,
435                                              ev->xbutton.state);
436                 }
437                 break;
438 #if FL_REVISION < 89
439         case FL_KEYBOARD:
440 #else
441         case FL_KEYPRESS:
442 #endif
443         {
444                 lyxerr[Debug::KEY] << "Workarea event: KEYBOARD" << endl;
445                 
446                 KeySym keysym = 0;
447                 char dummy[1];
448                 XKeyEvent * xke = reinterpret_cast<XKeyEvent *>(ev);
449 #if FL_REVISION < 89 || (FL_REVISION == 89 && FL_FIXLEVEL < 5)
450                 // XForms < 0.89.5 does not have compose support
451                 // so we are using our own compose support
452                 LyXLookupString(ev, dummy, 1, &keysym);
453 #else
454                 XLookupString(xke, dummy, 1, &keysym, 0);
455 #endif
456                 if (lyxerr.debugging(Debug::KEY)) {
457                         char const * tmp = XKeysymToString(key);
458                         char const * tmp2 = XKeysymToString(keysym);
459                         string const stm = (tmp ? tmp : "");
460                         string const stm2 = (tmp2 ? tmp2 : "");
461                         
462                         lyxerr << "WorkArea: Key is `" << stm << "' ["
463                                << key << "]" << endl;
464                         lyxerr << "WorkArea: Keysym is `" << stm2 << "' ["
465                                << keysym << "]" << endl;
466                 }
467
468 #if FL_REVISION < 89 || (FL_REVISION == 89 && FL_FIXLEVEL < 5)
469                 if (keysym == NoSymbol) {
470                         lyxerr[Debug::KEY]
471                                 << "Empty kdb action (probably composing)"
472                                 << endl;
473                         break;
474                 }
475                 KeySym ret_key = keysym;
476 #else
477                 // Note that we need this handling because of a bug
478                 // in XForms 0.89, if this bug is resolved in the way I hope
479                 // we can just use the keysym directly with out looking
480                 // at key at all. (Lgb)
481                 KeySym ret_key = 0;
482                 if (!key) {
483                         // We migth have to add more keysyms here also,
484                         // we will do that as the issues arise. (Lgb)
485                         if (keysym == XK_space) {
486                                 ret_key = keysym;
487                                 lyxerr[Debug::KEY] << "Using keysym [A]"
488                                                    << endl;
489                         } else
490                                 break;
491                 } else {
492                         // It seems that this was a bit optimistic...
493                         // With this hacking things seems to be better (Lgb)
494                         //if (!iscntrl(key)) {
495                         //      ret_key = key;
496                         //      lyxerr[Debug::KEY]
497                         //              << "Using key [B]\n"
498                         //              << "Uchar["
499                         //              << static_cast<unsigned char>(key)
500                         //              << endl;
501                         //} else {
502                                 ret_key = (keysym ? keysym : key);
503                                 lyxerr[Debug::KEY] << "Using keysym [B]"
504                                                    << endl;
505                                 //}
506                 }
507                 
508 #endif
509                 unsigned int const ret_state = xke->state;
510
511                 // If you have a better way to handle "wild-output" of
512                 // characters after the key has been released than the one
513                 // below, please contact me. (Lgb)
514                 static Time last_time_pressed;
515                 static unsigned int last_key_pressed;
516                 static unsigned int last_state_pressed;
517                 lyxerr[Debug::KEY] << "Workarea Diff: "
518                                    << xke->time - last_time_pressed
519                                    << endl;
520                 if (xke->time - last_time_pressed < 25 // should perhaps be tunable
521                     && ret_state == last_state_pressed
522                     && xke->keycode == last_key_pressed) {
523                         lyxerr[Debug::KEY]
524                                 << "Workarea: Purging X events." << endl;
525                         //lyxerr << "Workarea Events: "
526                         //       << XEventsQueued(fl_get_display(), QueuedAlready)
527                         //       << endl;
528                         if (XEventsQueued(fl_get_display(), QueuedAlready) > 0)
529                                 XSync(fl_get_display(), 1);
530                         // This purge make f.ex. scrolling stop immidiatly when
531                         // releasing the PageDown button. The question is if
532                         // this purging of XEvents can cause any harm...
533                         // after some testing I can see no problems, but
534                         // I'd like other reports too.
535                         break;
536                 }
537                 last_time_pressed = xke->time;
538                 last_key_pressed = xke->keycode;
539                 last_state_pressed = ret_state;
540                 
541                 area->workAreaKeyPress(ret_key, ret_state);
542         }
543         break;
544
545 #if FL_REVISION >= 89
546         case FL_KEYRELEASE:
547                 lyxerr << "Workarea event: KEYRELEASE" << endl;
548                 break;
549 #endif
550
551         case FL_FOCUS:
552                 lyxerr[Debug::GUI] << "Workarea event: FOCUS" << endl;
553                 area->workAreaFocus();
554                 break;
555         case FL_UNFOCUS:
556                 lyxerr[Debug::GUI] << "Workarea event: UNFOCUS" << endl;
557                 area->workAreaUnfocus();
558                 break;
559         case FL_ENTER:
560                 lyxerr[Debug::GUI] << "Workarea event: ENTER" << endl;
561                 area->workAreaEnter();
562                 break;
563         case FL_LEAVE:
564                 lyxerr[Debug::GUI] << "Workarea event: LEAVE" << endl;
565                 area->workAreaLeave();
566                 break;
567         case FL_DBLCLICK:
568                 if (!ev) break;
569                 lyxerr[Debug::GUI] << "Workarea event: DBLCLICK" << endl;
570                 area->workAreaDoubleClick(ev->xbutton.x - ob->x,
571                                           ev->xbutton.y - ob->y,
572                                           ev->xbutton.button);
573                 break;
574         case FL_TRPLCLICK:
575                 if (!ev) break;
576                 lyxerr[Debug::GUI] << "Workarea event: TRPLCLICK" << endl;
577                 area->workAreaTripleClick(ev->xbutton.x - ob->x,
578                                           ev->xbutton.y - ob->y,
579                                           ev->xbutton.button);
580                 break;
581         case FL_OTHER:
582                 if (!ev) break;
583                         lyxerr[Debug::GUI] << "Workarea event: OTHER" << endl;
584
585                 break;
586         }
587   
588         return 1;
589 }
590
591
592 namespace {
593
594 string clipboard_selection;
595 bool clipboard_read = false;
596
597 extern "C" {
598         
599         static
600         int request_clipboard_cb(FL_OBJECT * /*ob*/, long /*type*/,
601                                  void const * data, long size) 
602         {
603                 clipboard_selection.erase();
604                 
605                 if (size > 0)
606                         clipboard_selection.reserve(size);
607                 for (int i = 0; i < size; ++i)
608                         clipboard_selection +=
609                                 static_cast<char const *>(data)[i];
610                 clipboard_read = true;
611                 return 0;
612         }
613
614 }
615
616 } // namespace anon
617
618 string const WorkArea::getClipboard() const 
619 {
620         clipboard_read = false;
621         
622         if (fl_request_clipboard(work_area, 0, request_clipboard_cb) == -1)
623                 return string();
624
625         XEvent ev;
626         
627         while (!clipboard_read) {
628                 if (fl_check_forms() == FL_EVENT) {
629                         lyxerr << "LyX: This shouldn't happen..." << endl;
630                         fl_XNextEvent(&ev);
631                 }
632         }
633         return clipboard_selection;
634 }
635
636         
637 void WorkArea::putClipboard(string const & s) const
638 {
639         static string hold;
640         hold = s;
641         
642         fl_stuff_clipboard(work_area, 0, hold.data(), hold.size(), 0);
643 }