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