]> git.lyx.org Git - lyx.git/blob - src/frontends/xforms/XWorkArea.C
Nothing but a changed email address (trying to factor my tree in in small steps)
[lyx.git] / src / frontends / xforms / XWorkArea.C
1 /**
2  * \file XWorkArea.C
3  * Copyright 1995-2002 the LyX Team
4  * Read the file COPYING
5  *
6  * \author unknown
7  * \author John Levon <moz@compsoc.man.ac.uk>
8  */
9
10 #include <config.h>
11
12 #ifdef __GNUG__
13 #pragma implementation
14 #endif
15
16 #include "XWorkArea.h"
17 #include "debug.h"
18 #include "LyXView.h"
19 #include "XLyXKeySym.h"
20 #include "ColorHandler.h"
21
22 #if FL_VERSION < 1 && (FL_REVISION < 89 || (FL_REVISION == 89 && FL_FIXLEVEL < 5))
23 #include "lyxlookup.h"
24 #endif
25
26 #include "support/filetools.h" // LibFileSearch
27 #include "support/lstrings.h"
28 #include "support/LAssert.h"
29
30 #include <cmath>
31 #include <cctype>
32
33 // xforms doesn't define this (but it should be in <forms.h>).
34 extern "C"
35 FL_APPEVENT_CB fl_set_preemptive_callback(Window, FL_APPEVENT_CB, void *);
36
37 using std::endl;
38 using std::abs;
39 using std::hex;
40 using std::dec;
41
42 namespace {
43
44 inline
45 void waitForX()
46 {
47         XSync(fl_get_display(), 0);
48 }
49
50
51 void setXtermCursor(Window win)
52 {
53         static Cursor cursor;
54         static bool cursor_undefined = true;
55         if (cursor_undefined) {
56                 cursor = XCreateFontCursor(fl_get_display(), XC_xterm);
57                 XFlush(fl_get_display());
58                 cursor_undefined = false;
59         }
60         XDefineCursor(fl_get_display(), win, cursor);
61         XFlush(fl_get_display());
62 }
63
64
65 mouse_button::state x_button_state(unsigned int button)
66 {
67         mouse_button::state b = mouse_button::none;
68         switch (button) {
69                 case Button1:
70                         b = mouse_button::button1;
71                         break;
72                 case Button2:
73                         b = mouse_button::button2;
74                         break;
75                 case Button3:
76                         b = mouse_button::button3;
77                         break;
78                 case Button4:
79                         b = mouse_button::button4;
80                         break;
81                 case Button5:
82                         b = mouse_button::button5;
83                         break;
84                 default: // FIXME
85                         break;
86         }
87         return b;
88 }
89
90
91 mouse_button::state x_motion_state(unsigned int state)
92 {
93         mouse_button::state b = mouse_button::none;
94         if (state & Button1MotionMask)
95                 b |= mouse_button::button1;
96         if (state & Button2MotionMask)
97                 b |= mouse_button::button2;
98         if (state & Button3MotionMask)
99                 b |= mouse_button::button3;
100         if (state & Button4MotionMask)
101                 b |= mouse_button::button4;
102         if (state & Button5MotionMask)
103                 b |= mouse_button::button5;
104         return b;
105 }
106
107
108 key_modifier::state x_key_state(unsigned int state)
109 {
110         key_modifier::state k = key_modifier::none;
111         if (state & ControlMask)
112                 k |= key_modifier::ctrl;
113         if (state & ShiftMask)
114                 k |= key_modifier::shift;
115         if (state & Mod1Mask)
116                 k |= key_modifier::alt;
117         return k;
118 }
119
120
121 } // anon namespace
122
123
124 extern "C" {
125         // Just a bunch of C wrappers around static members of XWorkArea
126         static
127         void C_XWorkArea_scroll_cb(FL_OBJECT * ob, long)
128         {
129                 XWorkArea * area = static_cast<XWorkArea*>(ob->u_vdata);
130                 area->scroll_cb();
131         }
132
133
134         static
135         int C_XWorkArea_work_area_handler(FL_OBJECT * ob, int event,
136                                          FL_Coord, FL_Coord,
137                                          int key, void * xev)
138         {
139                 return XWorkArea::work_area_handler(ob, event,
140                                                    0, 0, key, xev);
141         }
142
143         static
144         int C_XWorkAreaEventCB(FL_FORM * form, void * xev) {
145                 XWorkArea * wa = static_cast<XWorkArea*>(form->u_vdata);
146                 return wa->event_cb(static_cast<XEvent*>(xev));
147         }
148 }
149
150
151 XWorkArea::XWorkArea(int x, int y, int w, int h)
152         : workareapixmap(0), painter_(*this)
153 {
154         fl_freeze_all_forms();
155
156         FL_OBJECT * obj;
157
158         if (lyxerr.debugging(Debug::WORKAREA))
159                 lyxerr << "\tbackground box: +"
160                        << x << '+' << y << ' '
161                        << w - 15 << 'x' << h << endl;
162         backgroundbox = obj = fl_add_box(FL_BORDER_BOX,
163                                          x, y,
164                                          w - 15,
165                                          h, "");
166         fl_set_object_resize(obj, FL_RESIZE_ALL);
167         fl_set_object_gravity(obj, NorthWestGravity, SouthEastGravity);
168
169         scrollbar = obj = fl_add_scrollbar(FL_VERT_SCROLLBAR,
170                                            x + w - 15,
171                                            y, 17, h, "");
172         fl_set_object_boxtype(obj, FL_UP_BOX);
173         fl_set_object_resize(obj, FL_RESIZE_ALL);
174         fl_set_object_gravity(obj, NorthEastGravity, SouthEastGravity);
175         obj->u_vdata = this;
176         fl_set_object_callback(obj, C_XWorkArea_scroll_cb, 0);
177         fl_set_scrollbar_bounds(scrollbar, 0.0, 0.0);
178         fl_set_scrollbar_value(scrollbar, 0.0);
179         fl_set_scrollbar_size(scrollbar, scrollbar->h);
180
181         int const bw = int(abs(fl_get_border_width()));
182
183         // Create the workarea pixmap
184         // FIXME remove redraw(w - 15 - 2 * bw, h - 2 * bw);
185
186         if (lyxerr.debugging(Debug::WORKAREA))
187                 lyxerr << "\tfree object: +"
188                        << x + bw << '+' << y + bw << ' '
189                        << w - 15 - 2 * bw << 'x'
190                        << h - 2 * bw << endl;
191
192         // We add this object as late as possible to avoid problems
193         // with drawing.
194         // FIXME: like ??
195         work_area = obj = fl_add_free(FL_ALL_FREE,
196                                       x + bw, y + bw,
197                                       w - 15 - 2 * bw,
198                                       h - 2 * bw, "",
199                                       C_XWorkArea_work_area_handler);
200         obj->wantkey = FL_KEY_ALL;
201         obj->u_vdata = this;
202
203         fl_set_object_boxtype(obj,FL_DOWN_BOX);
204         fl_set_object_resize(obj, FL_RESIZE_ALL);
205         fl_set_object_gravity(obj, NorthWestGravity, SouthEastGravity);
206
207         /// X selection hook - xforms gets it wrong
208         fl_current_form->u_vdata = this;
209         fl_register_raw_callback(fl_current_form, FL_ALL_EVENT, C_XWorkAreaEventCB);
210
211         fl_unfreeze_all_forms();
212
213         XGCValues val;
214
215         val.function = GXcopy;
216         copy_gc = XCreateGC(fl_get_display(), RootWindow(fl_get_display(), 0),
217                 GCFunction, &val);
218 }
219
220
221 XWorkArea::~XWorkArea()
222 {
223         XFreeGC(fl_get_display(), copy_gc);
224         if (workareapixmap)
225                 XFreePixmap(fl_get_display(), workareapixmap);
226 }
227
228
229 namespace {
230 void destroy_object(FL_OBJECT * obj)
231 {
232         if (!obj)
233                 return;
234
235         if (obj->visible) {
236                 fl_hide_object(obj);
237         }
238         fl_delete_object(obj);
239         fl_free_object(obj);
240 }
241 } // namespace anon
242
243
244 void XWorkArea::redraw(int width, int height)
245 {
246         static int cur_width = -1;
247         static int cur_height = -1;
248
249         if (cur_width == width && cur_height == height && workareapixmap) {
250                 XCopyArea(fl_get_display(),
251                         getPixmap(), getWin(), copy_gc,
252                         0, 0, width, height, xpos(), ypos());
253                 return;
254         }
255
256         cur_width = width;
257         cur_height = height;
258
259         if (lyxerr.debugging(Debug::WORKAREA)) {
260                 lyxerr << "(Re)creating pixmap ("
261                        << width << 'x' << height << ")" << endl;
262         }
263
264         if (workareapixmap) {
265                 XFreePixmap(fl_get_display(), workareapixmap);
266         }
267
268         workareapixmap = XCreatePixmap(fl_get_display(),
269                                        RootWindow(fl_get_display(), 0),
270                                        width,
271                                        height,
272                                        fl_get_visual_depth());
273
274         workAreaResize();
275 }
276
277
278 void XWorkArea::setScrollbarParams(int height, int pos, int line_height)
279 {
280         // we need to cache this for scroll_cb
281         doc_height_ = height;
282
283         if (height == 0) {
284                 fl_set_scrollbar_value(scrollbar, 0.0);
285                 fl_set_scrollbar_size(scrollbar, scrollbar->h);
286                 return;
287         }
288
289         long const work_height = workHeight();
290
291         lyxerr[Debug::GUI] << "scroll: height now " << height << endl;
292         lyxerr[Debug::GUI] << "scroll: work_height " << work_height << endl;
293
294         /* If the text is smaller than the working area, the scrollbar
295          * maximum must be the working area height. No scrolling will
296          * be possible */
297         if (height <= work_height) {
298                 lyxerr[Debug::GUI] << "scroll: doc smaller than workarea !" << endl;
299                 fl_set_scrollbar_bounds(scrollbar, 0.0, 0.0);
300                 fl_set_scrollbar_value(scrollbar, pos);
301                 fl_set_scrollbar_size(scrollbar, scrollbar->h);
302                 return;
303         }
304
305         fl_set_scrollbar_bounds(scrollbar, 0.0, height - work_height);
306         fl_set_scrollbar_increment(scrollbar, work_area->h - line_height, line_height);
307
308         fl_set_scrollbar_value(scrollbar, pos);
309
310         double const slider_size =
311                 (height == 0) ? 1.0 : 1.0 / double(height);
312
313         fl_set_scrollbar_size(scrollbar, scrollbar->h * slider_size);
314 }
315
316
317 // callback for scrollbar slider
318 void XWorkArea::scroll_cb()
319 {
320         double const val = fl_get_scrollbar_value(scrollbar);
321         lyxerr[Debug::GUI] << "scroll: val: " << val << endl;
322         lyxerr[Debug::GUI] << "scroll: height: " << scrollbar->h << endl;
323         lyxerr[Debug::GUI] << "scroll: docheight: " << doc_height_ << endl;
324         scrollDocView(int(val));
325         waitForX();
326 }
327
328
329 int XWorkArea::work_area_handler(FL_OBJECT * ob, int event,
330                                 FL_Coord, FL_Coord,
331                                 int key, void * xev)
332 {
333         static int x_old = -1;
334         static int y_old = -1;
335         static double scrollbar_value_old = -1.0;
336
337         XEvent * ev = static_cast<XEvent*>(xev);
338         XWorkArea * area = static_cast<XWorkArea*>(ob->u_vdata);
339
340         if (!area) return 1;
341
342         switch (event) {
343         case FL_DRAW:
344                 if (!area->work_area ||
345                     !area->work_area->form->visible)
346                         return 1;
347                 lyxerr[Debug::WORKAREA] << "Workarea event: DRAW" << endl;
348                 area->redraw(area->workWidth(), area->workHeight());
349                 break;
350         case FL_PUSH:
351                 if (!ev || ev->xbutton.button == 0) break;
352                 // Should really have used xbutton.state
353                 lyxerr[Debug::WORKAREA] << "Workarea event: PUSH" << endl;
354                 area->workAreaButtonPress(ev->xbutton.x - ob->x,
355                                           ev->xbutton.y - ob->y,
356                                           x_button_state(ev->xbutton.button));
357                 break;
358         case FL_RELEASE:
359                 if (!ev || ev->xbutton.button == 0) break;
360                 // Should really have used xbutton.state
361                 lyxerr[Debug::WORKAREA] << "Workarea event: RELEASE" << endl;
362                 area->workAreaButtonRelease(ev->xbutton.x - ob->x,
363                                       ev->xbutton.y - ob->y,
364                                       x_button_state(ev->xbutton.button));
365                 break;
366 #if FL_VERSION < 1 && FL_REVISION < 89
367         case FL_MOUSE:
368 #else
369         case FL_DRAG:
370 #endif
371                 if (!ev || ! area->scrollbar) break;
372                 if (ev->xmotion.x != x_old ||
373                     ev->xmotion.y != y_old ||
374                     fl_get_scrollbar_value(area->scrollbar) != scrollbar_value_old
375                         ) {
376                         x_old = ev->xmotion.x;
377                         y_old = ev->xmotion.y;
378                         scrollbar_value_old = fl_get_scrollbar_value(area->scrollbar);
379                         lyxerr[Debug::WORKAREA] << "Workarea event: MOUSE" << endl;
380                         area->workAreaMotionNotify(ev->xmotion.x - ob->x,
381                                              ev->xmotion.y - ob->y,
382                                              x_motion_state(ev->xbutton.state));
383                 }
384                 break;
385 #if FL_VERSION < 1 && FL_REVISION < 89
386         case FL_KEYBOARD:
387 #else
388         case FL_KEYPRESS:
389 #endif
390         {
391                 lyxerr[Debug::WORKAREA] << "Workarea event: KEYBOARD" << endl;
392
393                 KeySym keysym = 0;
394                 char dummy[1];
395                 XKeyEvent * xke = reinterpret_cast<XKeyEvent *>(ev);
396 #if FL_VERSION < 1 && (FL_REVISION < 89 || (FL_REVISION == 89 && FL_FIXLEVEL < 5))
397                 // XForms < 0.89.5 does not have compose support
398                 // so we are using our own compose support
399                 LyXLookupString(ev, dummy, 1, &keysym);
400 #else
401                 XLookupString(xke, dummy, 1, &keysym, 0);
402 //              int num_keys = XLookupString(xke, dummy, 10, &keysym, &xcs);
403 //              lyxerr << "We have " << num_keys << " keys in the returned buffer" << endl;
404 //              lyxerr << "Our dummy string is " << dummy << endl;
405 #endif
406
407                 if (lyxerr.debugging(Debug::KEY)) {
408                         char const * tmp = XKeysymToString(key);
409                         char const * tmp2 = XKeysymToString(keysym);
410                         string const stm = (tmp ? tmp : "");
411                         string const stm2 = (tmp2 ? tmp2 : "");
412
413                         lyxerr[Debug::KEY] << "XWorkArea: Key is `" << stm << "' ["
414                                << key << "]" << endl;
415                         lyxerr[Debug::KEY] << "XWorkArea: Keysym is `" << stm2 << "' ["
416                                << keysym << "]" << endl;
417                 }
418
419 #if FL_VERSION < 1 && (FL_REVISION < 89 || (FL_REVISION == 89 && FL_FIXLEVEL < 5))
420                 if (keysym == NoSymbol) {
421                         lyxerr[Debug::KEY]
422                                 << "Empty kdb action (probably composing)"
423                                 << endl;
424                         break;
425                 }
426                 KeySym ret_key = keysym;
427 #else
428                 // Note that we need this handling because of a bug
429                 // in XForms 0.89, if this bug is resolved in the way I hope
430                 // we can just use the keysym directly with out looking
431                 // at key at all. (Lgb)
432                 KeySym ret_key = 0;
433                 if (!key) {
434                         // We migth have to add more keysyms here also,
435                         // we will do that as the issues arise. (Lgb)
436                         if (keysym == XK_space) {
437                                 ret_key = keysym;
438                                 lyxerr[Debug::KEY] << "Using keysym [A]"
439                                                    << endl;
440                         } else
441                                 break;
442                 } else {
443                         // It seems that this was a bit optimistic...
444                         // With this hacking things seems to be better (Lgb)
445                         //if (!iscntrl(key)) {
446                         //      ret_key = key;
447                         //      lyxerr[Debug::KEY]
448                         //              << "Using key [B]\n"
449                         //              << "Uchar["
450                         //              << static_cast<unsigned char>(key)
451                         //              << endl;
452                         //} else {
453                                 ret_key = (keysym ? keysym : key);
454                                 lyxerr[Debug::KEY] << "Using keysym [B]"
455                                                    << endl;
456                                 //}
457                 }
458
459 #endif
460                 unsigned int const ret_state = xke->state;
461
462                 // If you have a better way to handle "wild-output" of
463                 // characters after the key has been released than the one
464                 // below, please contact me. (Lgb)
465                 static Time last_time_pressed;
466                 static unsigned int last_key_pressed;
467                 static unsigned int last_state_pressed;
468                 lyxerr[Debug::KEY] << "Workarea Diff: "
469                                    << xke->time - last_time_pressed
470                                    << endl;
471                 if (xke->time - last_time_pressed < 25 // should perhaps be tunable
472                     && ret_state == last_state_pressed
473                     && xke->keycode == last_key_pressed) {
474                         lyxerr[Debug::KEY]
475                                 << "Workarea: Purging X events." << endl;
476                         //lyxerr << "Workarea Events: "
477                         //       << XEventsQueued(fl_get_display(), QueuedAlready)
478                         //       << endl;
479                         if (XEventsQueued(fl_get_display(), QueuedAlready) > 0)
480                                 XSync(fl_get_display(), 1);
481                         // This purge make f.ex. scrolling stop immidiatly when
482                         // releasing the PageDown button. The question is if
483                         // this purging of XEvents can cause any harm...
484                         // after some testing I can see no problems, but
485                         // I'd like other reports too.
486                         break;
487                 }
488                 last_time_pressed = xke->time;
489                 last_key_pressed = xke->keycode;
490                 last_state_pressed = ret_state;
491
492                 XLyXKeySym * xlk = new XLyXKeySym;
493                 xlk->initFromKeySym(ret_key);
494
495                 area->workAreaKeyPress(LyXKeySymPtr(xlk),
496                                        x_key_state(ret_state));
497         }
498         break;
499
500 #if FL_VERSION > 0 || FL_REVISION >= 89
501         case FL_KEYRELEASE:
502                 lyxerr[Debug::WORKAREA] << "Workarea event: KEYRELEASE" << endl;
503                 break;
504 #endif
505
506         case FL_ENTER:
507                 lyxerr[Debug::WORKAREA] << "Workarea event: ENTER" << endl;
508                 break;
509         case FL_LEAVE:
510                 lyxerr[Debug::WORKAREA] << "Workarea event: LEAVE" << endl;
511                 break;
512         case FL_DBLCLICK:
513                 if (!ev) break;
514                 lyxerr[Debug::WORKAREA] << "Workarea event: DBLCLICK" << endl;
515                 area->workAreaDoubleClick(ev->xbutton.x - ob->x,
516                                           ev->xbutton.y - ob->y,
517                                           x_button_state(ev->xbutton.button));
518                 break;
519         case FL_TRPLCLICK:
520                 if (!ev) break;
521                 lyxerr[Debug::WORKAREA] << "Workarea event: TRPLCLICK" << endl;
522                 area->workAreaTripleClick(ev->xbutton.x - ob->x,
523                                           ev->xbutton.y - ob->y,
524                                           x_button_state(ev->xbutton.button));
525                 break;
526         case FL_OTHER:
527                 if (!ev) break;
528                 lyxerr[Debug::WORKAREA] << "Workarea event: OTHER" << endl;
529                 break;
530         }
531
532         return 1;
533 }
534
535
536 namespace {
537
538 string clipboard_selection;
539 bool clipboard_read = false;
540
541 extern "C" {
542
543         static
544         int request_clipboard_cb(FL_OBJECT * /*ob*/, long /*type*/,
545                                  void const * data, long size)
546         {
547                 clipboard_selection.erase();
548
549                 if (size > 0)
550                         clipboard_selection.reserve(size);
551                 for (int i = 0; i < size; ++i)
552                         clipboard_selection +=
553                                 static_cast<char const *>(data)[i];
554                 clipboard_read = true;
555                 return 0;
556         }
557
558 }
559
560 } // namespace anon
561
562
563 int XWorkArea::event_cb(XEvent * xev)
564 {
565         int ret = 0;
566         switch (xev->type) {
567                 case SelectionRequest:
568                         lyxerr[Debug::GUI] << "X requested selection." << endl;
569                         selectionRequested();
570                         break;
571                 case SelectionClear:
572                         lyxerr[Debug::GUI] << "Lost selection." << endl;
573                         selectionLost();
574                         break;
575         }
576         return ret;
577 }
578
579
580 void XWorkArea::haveSelection(bool yes) const
581 {
582         if (!yes) {
583                 XSetSelectionOwner(fl_get_display(), XA_PRIMARY, None, CurrentTime);
584                 return;
585         }
586
587         XSetSelectionOwner(fl_get_display(), XA_PRIMARY, FL_ObjWin(work_area), CurrentTime);
588 }
589
590
591 string const XWorkArea::getClipboard() const
592 {
593         clipboard_read = false;
594
595         if (fl_request_clipboard(work_area, 0, request_clipboard_cb) == -1)
596                 return string();
597
598         XEvent ev;
599
600         while (!clipboard_read) {
601                 if (fl_check_forms() == FL_EVENT) {
602                         fl_XNextEvent(&ev);
603                         lyxerr << "Received unhandled X11 event" << endl;
604                         lyxerr << "Type: 0x" << hex << ev.xany.type <<
605                                 " Target: 0x" << hex << ev.xany.window << dec << endl;
606                 }
607         }
608         return clipboard_selection;
609 }
610
611
612 void XWorkArea::putClipboard(string const & s) const
613 {
614         static string hold;
615         hold = s;
616
617         fl_stuff_clipboard(work_area, 0, hold.data(), hold.size(), 0);
618 }