]> git.lyx.org Git - lyx.git/blob - src/BufferView.C
90723b6a3caec87c670c350a1e21d61f174878ca
[lyx.git] / src / BufferView.C
1 // -*- C++ -*-
2 /* This file is part of
3  * ====================================================== 
4  * 
5  *           LyX, The Document Processor
6  *        
7  *           Copyright 1995 Matthias Ettrich
8  *           Copyright 1995-2000 The LyX Team.
9  *
10  * ====================================================== */
11
12 #include <config.h>
13
14 #include <algorithm>
15 using std::for_each;
16
17 #include <cstdlib>
18 #include <csignal>
19
20 #include <unistd.h>
21 #include <sys/wait.h>
22
23 #include "support/lstrings.h"
24
25 #ifdef __GNUG__
26 #pragma implementation
27 #endif
28
29 #include "commandtags.h"
30 #include "BufferView.h"
31 #include "bufferlist.h"
32 #include "LyXView.h"
33 #include "lyxfunc.h"
34 #include "insets/lyxinset.h"
35 #include "minibuffer.h"
36 #include "lyxscreen.h"
37
38 #include "debug.h"
39 #include "lyx_gui_misc.h"
40 #include "BackStack.h"
41 #include "lyxtext.h"
42 #include "lyx_cb.h"
43 #include "gettext.h"
44 #include "layout.h"
45 #include "TextCache.h"
46 #include "intl.h"
47 #include "lyxrc.h"
48 #include "lyxrow.h"
49 #include "WorkArea.h"
50 #ifndef USE_PAINTER
51 #include "lyxdraw.h"
52 #endif
53
54 using std::find_if;
55
56 extern BufferList bufferlist;
57 extern LyXRC * lyxrc;
58
59 void sigchldhandler(pid_t pid, int * status);
60
61 extern void SetXtermCursor(Window win);
62 extern bool input_prohibited;
63 extern bool selection_possible;
64 extern char ascii_type;
65 extern void MenuPasteSelection(char at);
66 extern InsetUpdateStruct * InsetUpdateList;
67 extern void UpdateInsetUpdateList();
68 extern void FreeUpdateTimer();
69
70 BufferView::BufferView(LyXView * o, int xpos, int ypos,
71                        int width, int height)
72         : owner_(o)
73 {
74         buffer_ = 0;
75         text = 0;
76         screen = 0;
77         workarea = new WorkArea(this, xpos, ypos, width, height);
78         timer_cursor = 0;
79         create_view();
80         current_scrollbar_value = 0;
81         // Activate the timer for the cursor 
82         fl_set_timer(timer_cursor, 0.4);
83         workarea->setFocus();
84         work_area_focus = true;
85         lyx_focus = false;
86         the_locking_inset = 0;
87         inset_slept = false;
88 }
89
90
91 BufferView::~BufferView()
92 {
93         delete text;
94 }
95
96
97 #ifdef USE_PAINTER
98 Painter & BufferView::painter() 
99 {
100         return workarea->getPainter();
101 }
102 #endif
103
104
105 void BufferView::buffer(Buffer * b)
106 {
107         lyxerr[Debug::INFO] << "Setting buffer in BufferView ("
108                             << b << ")" << endl;
109         if (buffer_) {
110                 insetSleep();
111                 buffer_->delUser(this);
112
113                 // Put the old text into the TextCache, but
114                 // only if the buffer is still loaded.
115                 // Also set the owner of the test to 0
116                 text->owner(0);
117                 textcache.add(text);
118                 if (lyxerr.debugging())
119                         textcache.show(lyxerr, "BufferView::buffer");
120                 
121                 text = 0;
122         }
123
124         // Set current buffer
125         buffer_ = b;
126
127         if (bufferlist.getState() == BufferList::CLOSING) return;
128         
129         // Nuke old image
130         // screen is always deleted when the buffer is changed.
131         delete screen;
132         screen = 0;
133
134         // If we are closing the buffer, use the first buffer as current
135         if (!buffer_) {
136                 buffer_ = bufferlist.first();
137         }
138
139         if (buffer_) {
140                 lyxerr[Debug::INFO] << "Buffer addr: " << buffer_ << endl;
141                 buffer_->addUser(this);
142                 owner_->getMenus()->showMenus();
143                 // If we don't have a text object for this, we make one
144                 if (text == 0)
145                         resizeCurrentBuffer();
146                 else {
147                         updateScreen();
148                         updateScrollbar();
149                 }
150                 screen->first = screen->TopCursorVisible();
151                 redraw();
152                 updateAllVisibleBufferRelatedPopups();
153                 insetWakeup();
154         } else {
155                 lyxerr[Debug::INFO] << "  No Buffer!" << endl;
156                 owner_->getMenus()->hideMenus();
157                 updateScrollbar();
158                 workarea->redraw();
159
160                 // Also remove all remaining text's from the testcache.
161                 // (there should not be any!) (if there is any it is a
162                 // bug!)
163                 if (lyxerr.debugging())
164                         textcache.show(lyxerr, "buffer delete all");
165                 textcache.clear();
166         }
167         // should update layoutchoice even if we don't have a buffer.
168         owner_->updateLayoutChoice();
169         owner_->getMiniBuffer()->Init();
170         owner_->updateWindowTitle();
171 }
172
173
174 void BufferView::updateScreen()
175 {
176         // Regenerate the screen.
177         delete screen;
178         screen = new LyXScreen(this,
179                                workarea->getWin(),
180                                workarea->getPixmap(),
181                                workarea->workWidth(),
182                                workarea->height(),
183                                workarea->xpos(),
184                                workarea->ypos(),
185                                text);
186 }
187
188
189 void BufferView::resize(int xpos, int ypos, int width, int height)
190 {
191         workarea->resize(xpos, ypos, width, height);
192         update(3);
193         redraw();
194 }
195
196
197 void BufferView::resize()
198 {
199         // This will resize the buffer. (Asger)
200         if (buffer_)
201                 resizeCurrentBuffer();
202 }
203
204
205 void BufferView::redraw()
206 {
207         lyxerr[Debug::INFO] << "BufferView::redraw()" << endl;
208         workarea->redraw();
209 }
210
211
212 void BufferView::fitCursor()
213 {
214         if (screen) screen->FitCursor();
215         updateScrollbar();
216 }
217
218
219 void BufferView::update()
220 {
221         if (screen) screen->Update();
222 }
223
224
225 void BufferView::updateScrollbar()
226 {
227         /* If the text is smaller than the working area, the scrollbar
228          * maximum must be the working area height. No scrolling will 
229          * be possible */
230
231         if (!buffer_) {
232                 workarea->setScrollbar(0, 1.0);
233                 return;
234         }
235         
236         static long max2 = 0;
237         static long height2 = 0;
238
239         long cbth = 0;
240         long cbsf = 0;
241
242         if (text)
243                 cbth = text->height;
244         if (screen)
245                 cbsf = screen->first;
246
247         // check if anything has changed.
248         if (max2 == cbth &&
249             height2 == workarea->height() &&
250             current_scrollbar_value == cbsf)
251                 return; // no
252         max2 = cbth;
253         height2 = workarea->height();
254         current_scrollbar_value = cbsf;
255
256         if (cbth <= height2) { // text is smaller than screen
257                 workarea->setScrollbar(0, 1.0); // right?
258                 return;
259         }
260
261         long maximum_height = workarea->height() * 3 / 4 + cbth;
262         long value = cbsf;
263
264         // set the scrollbar
265         double hfloat = workarea->height();
266         double maxfloat = maximum_height;
267
268         float slider_size = 0.0;
269         int slider_value = value;
270
271         workarea->setScrollbarBounds(0, text->height - workarea->height());
272         double lineh = text->DefaultHeight();
273         workarea->setScrollbarIncrements(lineh);
274         if (maxfloat > 0.0) {
275                 if ((hfloat / maxfloat) * float(height2) < 3)
276                         slider_size = 3.0/float(height2);
277                 else
278                         slider_size = hfloat / maxfloat;
279         } else
280                 slider_size = hfloat;
281
282         workarea->setScrollbar(slider_value, slider_size / workarea->height());
283 }
284
285
286 void BufferView::redoCurrentBuffer()
287 {
288         lyxerr[Debug::INFO] << "BufferView::redoCurrentBuffer" << endl;
289         if (buffer_ && text) {
290                 resize();
291                 owner_->updateLayoutChoice();
292         }
293 }
294
295
296 int BufferView::resizeCurrentBuffer()
297 {
298         lyxerr[Debug::INFO] << "resizeCurrentBuffer" << endl;
299         
300         LyXParagraph * par = 0;
301         LyXParagraph * selstartpar = 0;
302         LyXParagraph * selendpar = 0;
303         int pos = 0;
304         int selstartpos = 0;
305         int selendpos = 0;
306         int selection = 0;
307         int mark_set = 0;
308
309         ProhibitInput();
310
311         owner_->getMiniBuffer()->Set(_("Formatting document..."));   
312
313         if (text) {
314                 par = text->cursor.par;
315                 pos = text->cursor.pos;
316                 selstartpar = text->sel_start_cursor.par;
317                 selstartpos = text->sel_start_cursor.pos;
318                 selendpar = text->sel_end_cursor.par;
319                 selendpos = text->sel_end_cursor.pos;
320                 selection = text->selection;
321                 mark_set = text->mark_set;
322                 delete text;
323                 text = new LyXText(this, workarea->workWidth(), buffer_);
324         } else {
325                 // See if we have a text in TextCache that fits
326                 // the new buffer_ with the correct width.
327                 text = textcache.findFit(buffer_, workarea->workWidth());
328                 if (text) {
329                         if (lyxerr.debugging()) {
330                                 lyxerr << "Found a LyXText that fits:\n";
331                                 textcache.show(lyxerr, text);
332                         }
333                         // Set the owner of the newly found text
334                         text->owner(this);
335                         if (lyxerr.debugging())
336                                 textcache.show(lyxerr, "resizeCurrentBuffer");
337                 } else {
338                         text = new LyXText(this, workarea->workWidth(), buffer_);
339                 }
340         }
341         updateScreen();
342
343         if (par) {
344                 text->selection = true;
345                 /* at this point just to avoid the Delete-Empty-Paragraph
346                  * Mechanism when setting the cursor */
347                 text->mark_set = mark_set;
348                 if (selection) {
349                         text->SetCursor(selstartpar, selstartpos);
350                         text->sel_cursor = text->cursor;
351                         text->SetCursor(selendpar, selendpos);
352                         text->SetSelection();
353                         text->SetCursor(par, pos);
354                 } else {
355                         text->SetCursor(par, pos);
356                         text->sel_cursor = text->cursor;
357                         text->selection = false;
358                 }
359         }
360         screen->first = screen->TopCursorVisible(); /* this will scroll the
361                                                      * screen such that the
362                                                      * cursor becomes
363                                                      * visible */ 
364         updateScrollbar();
365         redraw();
366         owner_->getMiniBuffer()->Init();
367         SetState();
368         AllowInput();
369
370         // Now if the title form still exist kill it
371         TimerCB(0, 0);
372
373         return 0;
374 }
375
376
377 void BufferView::gotoError()
378 {
379         if (!screen)
380                 return;
381    
382         screen->HideCursor();
383         beforeChange();
384         update(-2);
385         LyXCursor tmp;
386
387         if (!text->GotoNextError()) {
388                 if (text->cursor.pos 
389                     || text->cursor.par != text->FirstParagraph()) {
390                         tmp = text->cursor;
391                         text->cursor.par = text->FirstParagraph();
392                         text->cursor.pos = 0;
393                         if (!text->GotoNextError()) {
394                                 text->cursor = tmp;
395                                 owner_->getMiniBuffer()
396                                         ->Set(_("No more errors"));
397                                 LyXBell();
398                         }
399                 } else {
400                         owner_->getMiniBuffer()->Set(_("No more errors"));
401                         LyXBell();
402                 }
403         }
404         update(0);
405         text->sel_cursor = text->cursor;
406 }
407
408
409 extern "C" {
410         void C_BufferView_CursorToggleCB(FL_OBJECT * ob, long buf)
411         {
412                 BufferView::CursorToggleCB(ob, buf);
413         }
414 }
415
416
417 void BufferView::create_view()
418 {
419         FL_OBJECT * obj;
420
421         //
422         // TIMERS
423         //
424         
425         // timer_cursor
426         timer_cursor = obj = fl_add_timer(FL_HIDDEN_TIMER,
427                                           0, 0, 0, 0, "Timer");
428         fl_set_object_callback(obj, C_BufferView_CursorToggleCB, 0);
429         obj->u_vdata = this;
430 }
431
432
433 // Callback for scrollbar up button
434 void BufferView::UpCB(long time, int button)
435 {
436         if (buffer_ == 0) return;
437
438         switch (button) {
439         case 3:
440                 ScrollUpOnePage();
441                 break;
442         case 2:
443                 ScrollDownOnePage();
444                 break;
445         default:
446                 ScrollUp(time);
447                 break;
448         }
449 }
450
451
452 static inline
453 void waitForX()
454 {
455         XSync(fl_get_display(), 0);
456 }
457
458
459 // Callback for scrollbar slider
460 void BufferView::ScrollCB(double value)
461 {
462         extern bool cursor_follows_scrollbar;
463         
464         if (buffer_ == 0) return;
465
466         current_scrollbar_value = long(value);
467         if (current_scrollbar_value < 0)
468                 current_scrollbar_value = 0;
469    
470         if (!screen)
471                 return;
472
473         screen->Draw(current_scrollbar_value);
474
475         if (cursor_follows_scrollbar) {
476                 LyXText * vbt = text;
477                 int height = vbt->DefaultHeight();
478                 
479                 if (vbt->cursor.y < screen->first + height) {
480                         vbt->SetCursorFromCoordinates(0,
481                                                       screen->first +
482                                                       height);
483                 } else if (vbt->cursor.y >
484                            screen->first + workarea->height() - height) {
485                         vbt->SetCursorFromCoordinates(0,
486                                                       screen->first +
487                                                       workarea->height()  -
488                                                       height);
489                 }
490         }
491         waitForX();
492 }
493
494
495 // Callback for scrollbar down button
496 void BufferView::DownCB(long time, int button)
497 {
498         if (buffer_ == 0) return;
499         
500         switch (button) {
501         case 2:
502                 ScrollUpOnePage();
503                 break;
504         case 3:
505                 ScrollDownOnePage();
506                 break;
507         default:
508                 ScrollDown(time);
509                 break;
510         }
511 }
512
513
514 int BufferView::ScrollUp(long time)
515 {
516         if (buffer_ == 0) return 0;
517         if (!screen) return 0;
518    
519         double value = workarea->getScrollbarValue();
520    
521         if (value == 0) return 0;
522
523         float add_value =  (text->DefaultHeight()
524                             + float(time) * float(time) * 0.125);
525    
526         if (add_value > workarea->height())
527                 add_value = float(workarea->height() -
528                                   text->DefaultHeight());
529    
530         value -= add_value;
531
532         if (value < 0)
533                 value = 0;
534    
535         workarea->setScrollbarValue(value);
536    
537         ScrollCB(value); 
538         return 0;
539 }
540
541
542 int BufferView::ScrollDown(long time)
543 {
544         if (buffer_ == 0) return 0;
545         if (!screen) return 0;
546    
547         double value= workarea->getScrollbarValue();
548         pair<double, double> p = workarea->getScrollbarBounds();
549         double max = p.second;
550         
551         if (value == max) return 0;
552
553         float add_value =  (text->DefaultHeight()
554                             + float(time) * float(time) * 0.125);
555    
556         if (add_value > workarea->height())
557                 add_value = float(workarea->height() -
558                                   text->DefaultHeight());
559    
560         value += add_value;
561    
562         if (value > max)
563                 value = max;
564
565         workarea->setScrollbarValue(value);
566         
567         ScrollCB(value); 
568         return 0;
569 }
570
571
572 void BufferView::ScrollUpOnePage()
573 {
574         if (buffer_ == 0) return;
575         if (!screen) return;
576    
577         long y = screen->first;
578
579         if (!y) return;
580
581         Row * row = text->GetRowNearY(y);
582
583         y = y - workarea->height() + row->height;
584
585         workarea->setScrollbarValue(y);
586         
587         ScrollCB(y); 
588 }
589
590
591 void BufferView::ScrollDownOnePage()
592 {
593         if (buffer_ == 0) return;
594         if (!screen) return;
595    
596         long y = screen->first;
597
598         if (y > text->height - workarea->height())
599                 return;
600    
601         y += workarea->height();
602         text->GetRowNearY(y);
603
604         workarea->setScrollbarValue(y);
605         
606         ScrollCB(y); 
607 }
608
609
610 void BufferView::WorkAreaMotionNotify(int x, int y, unsigned int state)
611 {
612         if (buffer_ == 0 || !screen) return;
613
614         // Check for inset locking
615         if (the_locking_inset) {
616                 LyXCursor cursor = text->cursor;
617                 the_locking_inset->
618                         InsetMotionNotify(x - cursor.x,
619                                           y - cursor.y,
620                                           state);
621                 return;
622         }
623
624         // Only use motion with button 1
625         if (!state & Button1MotionMask)
626                 return; 
627    
628         /* The selection possible is needed, that only motion events are 
629          * used, where the bottom press event was on the drawing area too */
630         if (selection_possible) {
631                 screen->HideCursor();
632
633                 text->SetCursorFromCoordinates(x, y + screen->first);
634       
635                 if (!text->selection)
636                         update(-3); // Maybe an empty line was deleted
637       
638                 text->SetSelection();
639                 screen->ToggleToggle();
640                 if (screen->FitCursor())
641                         updateScrollbar(); 
642                 screen->ShowCursor();
643         }
644         return;
645 }
646
647
648 #ifdef USE_PAINTER
649 extern int bibitemMaxWidth(Painter &, LyXFont const &);
650 #else
651 extern int bibitemMaxWidth(LyXFont const &);
652 #endif
653
654 // Single-click on work area
655 void BufferView::WorkAreaButtonPress(int xpos, int ypos, unsigned int button)
656 {
657         last_click_x = -1;
658         last_click_y = -1;
659
660         if (buffer_ == 0 || !screen) return;
661
662         Inset * inset_hit = checkInsetHit(xpos, ypos);
663
664         // ok ok, this is a hack.
665         if (button == 4 || button == 5) {
666                 switch (button) {
667                 case 4:
668                         ScrollUp(100); // This number is only temporary
669                         break;
670                 case 5:
671                         ScrollDown(100);
672                         break;
673                 }
674         }
675         
676         if (the_locking_inset) {
677                 // We are in inset locking mode
678                 
679                 /* Check whether the inset was hit. If not reset mode,
680                    otherwise give the event to the inset */
681                 if (inset_hit) {
682                         the_locking_inset->
683                                 InsetButtonPress(xpos, ypos,
684                                                  button);
685                         return;
686                 } else {
687                         unlockInset(the_locking_inset);
688                 }
689         }
690         
691         selection_possible = true;
692         screen->HideCursor();
693         
694         // Right button mouse click on a table
695         if (button == 3 &&
696             (text->cursor.par->table ||
697              text->MouseHitInTable(xpos, ypos + screen->first))) {
698                 // Set the cursor to the press-position
699                 text->SetCursorFromCoordinates(xpos, ypos + screen->first);
700                 bool doit = true;
701                 
702                 // Only show the table popup if the hit is in
703                 // the table, too
704                 if (!text->HitInTable(text->cursor.row, xpos))
705                         doit = false;
706                 
707                 // Hit above or below the table?
708                 if (doit) {
709                         if (!text->selection) {
710                                 screen->ToggleSelection();
711                                 text->ClearSelection();
712                                 text->FullRebreak();
713                                 screen->Update();
714                                 updateScrollbar();
715                         }
716                         // Popup table popup when on a table.
717                         // This is obviously temporary, since we
718                         // should be able to popup various
719                         // context-sensitive-menus with the
720                         // the right mouse. So this should be done more
721                         // general in the future. Matthias.
722                         selection_possible = false;
723                         owner_->getLyXFunc()
724                                 ->Dispatch(LFUN_LAYOUT_TABLE,
725                                            "true");
726                         return;
727                 }
728         }
729         
730         int screen_first = screen->first;
731         
732         // Middle button press pastes if we have a selection
733         bool paste_internally = false;
734         if (button == 2
735             && text->selection) {
736                 owner_->getLyXFunc()->Dispatch(LFUN_COPY);
737                 paste_internally = true;
738         }
739         
740         // Clear the selection
741         screen->ToggleSelection();
742         text->ClearSelection();
743         text->FullRebreak();
744         screen->Update();
745         updateScrollbar();
746         
747         // Single left click in math inset?
748         if (inset_hit != 0 && inset_hit->Editable() == 2) {
749                 // Highly editable inset, like math
750                 selection_possible = false;
751                 owner_->updateLayoutChoice();
752                 owner_->getMiniBuffer()->Set(inset_hit->EditMessage());
753                 inset_hit->Edit(xpos, ypos);
754                 return;
755         } 
756         
757         // Right click on a footnote flag opens float menu
758         if (button == 3) { 
759                 selection_possible = false;
760                 return;
761         }
762         
763         text->SetCursorFromCoordinates(xpos, ypos + screen_first);
764         text->FinishUndo();
765         text->sel_cursor = text->cursor;
766         text->cursor.x_fix = text->cursor.x;
767         
768         owner_->updateLayoutChoice();
769         if (screen->FitCursor()){
770                 updateScrollbar();
771                 selection_possible = false;
772         }
773         
774         // Insert primary selection with middle mouse
775         // if there is a local selection in the current buffer,
776         // insert this
777         if (button == 2) {
778                 if (paste_internally)
779                         owner_->getLyXFunc()->Dispatch(LFUN_PASTE);
780                 else
781                         owner_->getLyXFunc()->Dispatch(LFUN_PASTESELECTION,
782                                                        "paragraph");
783                 selection_possible = false;
784                 return;
785         }
786 }
787
788
789 void BufferView::WorkAreaButtonRelease(int x, int y, unsigned int button)
790 {
791         if (buffer_ == 0 || screen == 0) return;
792
793         // If we hit an inset, we have the inset coordinates in these
794         // and inset_hit points to the inset.  If we do not hit an
795         // inset, inset_hit is 0, and inset_x == x, inset_y == y.
796         Inset * inset_hit = checkInsetHit(x, y);
797
798         if (the_locking_inset) {
799                 // We are in inset locking mode.
800
801                 /* LyX does a kind of work-area grabbing for insets.
802                    Only a ButtonPress Event outside the inset will 
803                    force a InsetUnlock. */
804                 the_locking_inset->
805                         InsetButtonRelease(x, x, button);
806                 return;
807         }
808         
809         selection_possible = false;
810         if (text->cursor.par->table) {
811                 int cell = text->
812                         NumberOfCell(text->cursor.par,
813                                      text->cursor.pos);
814                 if (text->cursor.par->table->IsContRow(cell) &&
815                     text->cursor.par->table->
816                     CellHasContRow(text->cursor.par->table->
817                                    GetCellAbove(cell))<0) {
818                         text->CursorUp();
819                 }
820         }
821         
822         if (button >= 2) return;
823
824         SetState();
825         owner_->getMiniBuffer()->Set(CurrentState());
826
827         // Did we hit an editable inset?
828         if (inset_hit != 0) {
829                 // Inset like error, notes and figures
830                 selection_possible = false;
831 #ifdef WITH_WARNINGS
832 #warning fix this proper in 0.13
833 #endif
834                 // Following a ref shouldn't issue
835                 // a push on the undo-stack
836                 // anylonger, now that we have
837                 // keybindings for following
838                 // references and returning from
839                 // references.  IMHO though, it
840                 // should be the inset's own business
841                 // to push or not push on the undo
842                 // stack. They don't *have* to
843                 // alter the document...
844                 // (Joacim)
845                 // ...or maybe the SetCursorParUndo()
846                 // below isn't necessary at all anylonger?
847                 if (inset_hit->LyxCode() == Inset::REF_CODE) {
848                         text->SetCursorParUndo();
849                 }
850
851                 owner_->getMiniBuffer()->Set(inset_hit->EditMessage());
852                 inset_hit->Edit(x, y);
853                 return;
854         }
855
856         // check whether we want to open a float
857         if (text) {
858                 bool hit = false;
859                 char c = ' ';
860                 if (text->cursor.pos <
861                     text->cursor.par->Last()) {
862                         c = text->cursor.par->
863                                 GetChar(text->cursor.pos);
864                 }
865                 if (c == LyXParagraph::META_FOOTNOTE
866                     || c == LyXParagraph::META_MARGIN
867                     || c == LyXParagraph::META_FIG
868                     || c == LyXParagraph::META_TAB
869                     || c == LyXParagraph::META_WIDE_FIG
870                     || c == LyXParagraph::META_WIDE_TAB
871                     || c == LyXParagraph::META_ALGORITHM){
872                         hit = true;
873                 } else if (text->cursor.pos - 1 >= 0) {
874                         c = text->cursor.par->
875                                 GetChar(text->cursor.pos - 1);
876                         if (c == LyXParagraph::META_FOOTNOTE
877                             || c == LyXParagraph::META_MARGIN
878                             || c == LyXParagraph::META_FIG
879                             || c == LyXParagraph::META_TAB
880                             || c == LyXParagraph::META_WIDE_FIG 
881                             || c == LyXParagraph::META_WIDE_TAB
882                             || c == LyXParagraph::META_ALGORITHM){
883                                 // We are one step too far to the right
884                                 text->CursorLeft();
885                                 hit = true;
886                         }
887                 }
888                 if (hit == true) {
889                         toggleFloat();
890                         selection_possible = false;
891                         return;
892                 }
893         }
894
895         // Do we want to close a float? (click on the float-label)
896         if (text->cursor.row->par->footnoteflag == 
897             LyXParagraph::OPEN_FOOTNOTE
898             //&& text->cursor.pos == 0
899             && text->cursor.row->previous &&
900             text->cursor.row->previous->par->
901             footnoteflag != LyXParagraph::OPEN_FOOTNOTE){
902                 LyXFont font (LyXFont::ALL_SANE);
903                 font.setSize(LyXFont::SIZE_FOOTNOTE);
904
905                 int box_x = 20; // LYX_PAPER_MARGIN;
906                 box_x += font.textWidth(" wide-tab ", 10);
907
908                 int screen_first = screen->first;
909
910                 if (x < box_x
911                     && y + screen_first > text->cursor.y -
912                     text->cursor.row->baseline
913                     && y + screen_first < text->cursor.y -
914                     text->cursor.row->baseline
915                     + font.maxAscent() * 1.2 + font.maxDescent() * 1.2) {
916                         toggleFloat();
917                         selection_possible = false;
918                         return;
919                 }
920         }
921
922         // Maybe we want to edit a bibitem ale970302
923 #ifdef USE_PAINTER
924         if (text->cursor.par->bibkey && x < 20 + 
925             bibitemMaxWidth(painter(),
926                             textclasslist
927                             .TextClass(buffer_->
928                                        params.textclass).defaultfont())) {
929                 text->cursor.par->bibkey->Edit(0, 0);
930         }
931 #else
932         if (text->cursor.par->bibkey && x < 20 + 
933             bibitemMaxWidth(textclasslist
934                             .TextClass(buffer_->
935                                        params.textclass).defaultfont())) {
936                 text->cursor.par->bibkey->Edit(0, 0);
937         }
938 #endif
939
940         return;
941 }
942
943
944 /* 
945  * Returns an inset if inset was hit. 0 otherwise.
946  * If hit, the coordinates are changed relative to the inset. 
947  * Otherwise coordinates are not changed, and false is returned.
948  */
949 #ifdef USE_PAINTER
950 Inset * BufferView::checkInsetHit(int & x, int & y)
951 {
952         if (!getScreen())
953                 return 0;
954   
955         int y_tmp = y + getScreen()->first;
956   
957         LyXCursor & cursor = text->cursor;
958         LyXDirection direction = text->real_current_font.getFontDirection();
959
960         if (cursor.pos < cursor.par->Last()
961             && cursor.par->GetChar(cursor.pos) == LyXParagraph::META_INSET
962             && cursor.par->GetInset(cursor.pos)
963             && cursor.par->GetInset(cursor.pos)->Editable()) {
964
965                 // Check whether the inset really was hit
966                 Inset * tmpinset = cursor.par->GetInset(cursor.pos);
967                 LyXFont font = text->GetFont(cursor.par, cursor.pos);
968                 int start_x, end_x;
969                 if (direction == LYX_DIR_LEFT_TO_RIGHT) {
970                         start_x = cursor.x;
971                         end_x = cursor.x + tmpinset->width(painter(), font);
972                 } else {
973                         start_x = cursor.x - tmpinset->width(painter(), font);
974                         end_x = cursor.x;
975                 }
976
977                 if (x > start_x && x < end_x
978                     && y_tmp > cursor.y - tmpinset->ascent(painter(), font)
979                     && y_tmp < cursor.y + tmpinset->descent(painter(), font)) {
980                         x = x - start_x;
981                         // The origin of an inset is on the baseline
982                         y = y_tmp - (cursor.y); 
983                         return tmpinset;
984                 }
985         }
986
987         if (cursor.pos - 1 >= 0
988                    && cursor.par->GetChar(cursor.pos - 1) == LyXParagraph::META_INSET
989                    && cursor.par->GetInset(cursor.pos - 1)
990                    && cursor.par->GetInset(cursor.pos - 1)->Editable()) {
991                 text->CursorLeft();
992                 Inset * tmpinset = cursor.par->GetInset(cursor.pos);
993                 LyXFont font = text->GetFont(cursor.par, cursor.pos);
994                 int start_x, end_x;
995                 if (direction == LYX_DIR_LEFT_TO_RIGHT) {
996                         start_x = cursor.x;
997                         end_x = cursor.x + tmpinset->width(painter(), font);
998                 } else {
999                         start_x = cursor.x - tmpinset->width(painter(), font);
1000                         end_x = cursor.x;
1001                 }
1002                 if (x > start_x && x < end_x
1003                     && y_tmp > cursor.y - tmpinset->ascent(painter(), font)
1004                     && y_tmp < cursor.y + tmpinset->descent(painter(), font)) {
1005                         x = x - start_x;
1006                         // The origin of an inset is on the baseline
1007                         y = y_tmp - (cursor.y); 
1008                         return tmpinset;
1009                 } else {
1010                         text->CursorRight();
1011                         return 0;
1012                 }
1013         }
1014         return 0;
1015 }
1016 #else
1017 Inset * BufferView::checkInsetHit(int & x, int & y)
1018 {
1019         if (!getScreen())
1020                 return 0;
1021   
1022         int y_tmp = y + getScreen()->first;
1023   
1024         LyXCursor & cursor = text->cursor;
1025         LyXDirection direction = text->real_current_font.getFontDirection();
1026
1027         if (cursor.pos < cursor.par->Last()
1028             && cursor.par->GetChar(cursor.pos) == LyXParagraph::META_INSET
1029             && cursor.par->GetInset(cursor.pos)
1030             && cursor.par->GetInset(cursor.pos)->Editable()) {
1031
1032                 // Check whether the inset really was hit
1033                 Inset * tmpinset = cursor.par->GetInset(cursor.pos);
1034                 LyXFont font = text->GetFont(cursor.par, cursor.pos);
1035                 int start_x, end_x;
1036                 if (direction == LYX_DIR_LEFT_TO_RIGHT) {
1037                         start_x = cursor.x;
1038                         end_x = cursor.x + tmpinset->Width(font);
1039                 } else {
1040                         start_x = cursor.x - tmpinset->Width(font);
1041                         end_x = cursor.x;
1042                 }
1043
1044                 if (x > start_x && x < end_x
1045                     && y_tmp > cursor.y - tmpinset->Ascent(font)
1046                     && y_tmp < cursor.y + tmpinset->Descent(font)) {
1047                         x = x - start_x;
1048                         // The origin of an inset is on the baseline
1049                         y = y_tmp - (cursor.y); 
1050                         return tmpinset;
1051                 }
1052         }
1053
1054         if (cursor.pos - 1 >= 0
1055                    && cursor.par->GetChar(cursor.pos - 1) == LyXParagraph::META_INSET
1056                    && cursor.par->GetInset(cursor.pos - 1)
1057                    && cursor.par->GetInset(cursor.pos - 1)->Editable()) {
1058                 text->CursorLeft();
1059                 Inset * tmpinset = cursor.par->GetInset(cursor.pos);
1060                 LyXFont font = text->GetFont(cursor.par, cursor.pos);
1061                 int start_x, end_x;
1062                 if (direction == LYX_DIR_LEFT_TO_RIGHT) {
1063                         start_x = cursor.x;
1064                         end_x = cursor.x + tmpinset->Width(font);
1065                 } else {
1066                         start_x = cursor.x - tmpinset->Width(font);
1067                         end_x = cursor.x;
1068                 }
1069                 if (x > start_x && x < end_x
1070                     && y_tmp > cursor.y - tmpinset->Ascent(font)
1071                     && y_tmp < cursor.y + tmpinset->Descent(font)) {
1072                         x = x - start_x;
1073                         // The origin of an inset is on the baseline
1074                         y = y_tmp - (cursor.y); 
1075                         return tmpinset;
1076                 } else {
1077                         text->CursorRight();
1078                         return 0;
1079                 }
1080         }
1081         return 0;
1082 }
1083 #endif
1084
1085
1086 void BufferView::workAreaExpose()
1087 {
1088         // this is a hack to ensure that we only call this through
1089         // BufferView::redraw().
1090         //if (!lgb_hack) {
1091         //      redraw();
1092         //}
1093         
1094         static int work_area_width = -1;
1095         static int work_area_height = -1;
1096
1097         bool widthChange = workarea->workWidth() != work_area_width;
1098         bool heightChange = workarea->height() != work_area_height;
1099
1100         // update from work area
1101         work_area_width = workarea->workWidth();
1102         work_area_height = workarea->height();
1103         if (buffer_ != 0) {
1104                 if (widthChange) {
1105                         // All buffers need a resize
1106                         bufferlist.resize();
1107
1108                         // Remove all texts from the textcache
1109                         // This is not _really_ what we want to do. What
1110                         // we really want to do is to delete in textcache
1111                         // that does not have a BufferView with matching
1112                         // width, but as long as we have only one BufferView
1113                         // deleting all gives the same result.
1114                         if (lyxerr.debugging())
1115                                 textcache.show(lyxerr, "Expose delete all");
1116                         textcache.clear();
1117                 } else if (heightChange) {
1118                         // Rebuild image of current screen
1119                         updateScreen();
1120                         // fitCursor() ensures we don't jump back
1121                         // to the start of the document on vertical
1122                         // resize
1123                         fitCursor();
1124
1125                         // The main window size has changed, repaint most stuff
1126                         redraw();
1127                         // ...including the minibuffer
1128                         owner_->getMiniBuffer()->Init();
1129
1130                 } else if (screen) screen->Redraw();
1131         } else {
1132                 // Grey box when we don't have a buffer
1133                 workarea->greyOut();
1134         }
1135
1136         // always make sure that the scrollbar is sane.
1137         updateScrollbar();
1138         owner_->updateLayoutChoice();
1139         return;
1140 }
1141
1142
1143 // Callback for cursor timer
1144 void BufferView::CursorToggleCB(FL_OBJECT * ob, long)
1145 {
1146         BufferView * view = static_cast<BufferView*>(ob->u_vdata);
1147         
1148         // Quite a nice place for asyncron Inset updating, isn't it?
1149         // Actually no! This is run even if no buffer exist... so (Lgb)
1150         if (view && !view->buffer_) {
1151                 goto set_timer_and_return;
1152         }
1153
1154         // NOTE:
1155         // On my quest to solve the gs render hangups I am now
1156         // disabling the SIGHUP completely, and will do a wait
1157         // now and then instead. If the guess that xforms somehow
1158         // destroys something is true, this is likely (hopefully)
1159         // to solve the problem...at least I hope so. Lgb
1160
1161         // ...Ok this seems to work...at least it does not make things
1162         // worse so far. However I still see gs processes that hangs.
1163         // I would really like to know _why_ they are hanging. Anyway
1164         // the solution without the SIGCHLD handler seems to be easier
1165         // to debug.
1166
1167         // When attaching gdb to a a running gs that hangs it shows
1168         // that it is waiting for input(?) Is it possible for us to
1169         // provide that input somehow? Or figure what it is expecing
1170         // to read?
1171
1172         // One solution is to, after some time, look if there are some
1173         // old gs processes still running and if there are: kill them
1174         // and re render.
1175
1176         // Another solution is to provide the user an option to rerender
1177         // a picture. This would, for the picture in question, check if
1178         // there is a gs running for it, if so kill it, and start a new
1179         // rendering process.
1180
1181         // these comments posted to lyx@via
1182         {
1183                 int status = 1;
1184                 int pid = waitpid(static_cast<pid_t>(0), &status, WNOHANG);
1185                 if (pid == -1) // error find out what is wrong
1186                         ; // ignore it for now.
1187                 else if (pid > 0)
1188                         sigchldhandler(pid, &status);
1189         }
1190         if (InsetUpdateList) 
1191                 UpdateInsetUpdateList();
1192
1193         if (view && !view->screen){
1194                 goto set_timer_and_return;
1195         }
1196
1197         if (view->lyx_focus && view->work_area_focus) {
1198                 if (!view->the_locking_inset) {
1199                         view->screen->CursorToggle();
1200                 } else {
1201                         view->the_locking_inset->
1202                                 ToggleInsetCursor();
1203                 }
1204                 goto set_timer_and_return;
1205         } else {
1206                 // Make sure that the cursor is visible.
1207                 if (!view->the_locking_inset) {
1208                         view->screen->ShowCursor();
1209                 } else {
1210                         if (!view->the_locking_inset->isCursorVisible())
1211                                 view->the_locking_inset->
1212                                         ToggleInsetCursor();
1213                 }
1214                 // This is only run when work_area_focus or lyx_focus is false.
1215                 Window tmpwin;
1216                 int tmp;
1217                 XGetInputFocus(fl_display, &tmpwin, &tmp);
1218                 // Commenting this out, we have not had problems with this
1219                 // for a long time. We will probably work on this code later
1220                 // and we can reenable this debug code then. Now it only
1221                 // anoying when debugging. (Lgb)
1222                 //if (lyxerr.debugging(Debug::INFO)) {
1223                 //      lyxerr << "tmpwin: " << tmpwin
1224                 //             << "\nwindow: " << view->owner_->getForm()->window
1225                 //             << "\nwork_area_focus: " << view->work_area_focus
1226                 //             << "\nlyx_focus      : " << view->lyx_focus
1227                 //             << endl;
1228                 //}
1229                 if (tmpwin != view->owner_->getForm()->window) {
1230                         view->lyx_focus = false;
1231                         goto skip_timer;
1232                 } else {
1233                         view->lyx_focus = true;
1234                         if (!view->work_area_focus)
1235                                 goto skip_timer;
1236                         else
1237                                 goto set_timer_and_return;
1238                 }
1239         }
1240
1241   set_timer_and_return:
1242         fl_set_timer(ob, 0.4);
1243   skip_timer:
1244         return;
1245 }
1246
1247
1248 static
1249 string fromClipboard(Window win, XEvent * event)
1250 {
1251         string strret;
1252         if (event->xselection.type == XA_STRING
1253             && event->xselection.property) {
1254                 Atom tmpatom;
1255                 unsigned long ul1;
1256                 unsigned long ul2;
1257                 unsigned char * uc = 0;
1258                 int tmpint;
1259                 if (XGetWindowProperty(
1260                         event->xselection.display,  // display
1261                         win,                        // w
1262                         event->xselection.property, // property
1263                         0,                          // long_offset      
1264                         0,                          // logn_length      
1265                         False,                      // delete   
1266                         XA_STRING,                  // req_type 
1267                         &tmpatom,                   // actual_type_return
1268                         &tmpint,                    // actual_format_return
1269                         &ul1,
1270                         &ul2,
1271                         &uc                         // prop_return      
1272                         ) != Success) {
1273                         return strret;
1274                 }
1275                 if (uc) {
1276                         free(uc);
1277                         uc = 0;
1278                 }
1279                 if (XGetWindowProperty(
1280                         event->xselection.display,             // display
1281                         win,                        // w
1282                         event->xselection.property, // property
1283                         0,                          // long_offset
1284                         ul2/4+1,                    // long_length
1285                         True,                       // delete
1286                         XA_STRING,                  // req_type
1287                         &tmpatom,                   // actual_type_return
1288                         &tmpint,                    // actual_format_return
1289                         &ul1,                       // nitems_return
1290                         &ul2,                       // bytes_after_return
1291                         &uc                         // prop_return */
1292                         ) != Success) {
1293                         return strret;
1294                 }
1295                 if (uc) {
1296                         strret = reinterpret_cast<char*>(uc);
1297                         free(uc); // yes free!
1298                         uc = 0;
1299                 }
1300         }
1301         return strret;
1302 }
1303
1304
1305 void BufferView::WorkAreaSelectionNotify(Window win, XEvent * event)
1306 {
1307         if (buffer_ == 0) return;
1308
1309         screen->HideCursor();
1310         beforeChange();
1311         string clb = fromClipboard(win, event);
1312         if (!clb.empty()) {
1313                 if (!ascii_type)
1314                         text->InsertStringA(clb.c_str());
1315                 else
1316                         text->InsertStringB(clb.c_str());
1317
1318                 update(1);
1319                 
1320         }
1321 }
1322
1323
1324 void BufferView::cursorPrevious()
1325 {
1326         if (!text->cursor.row->previous) return;
1327         
1328         long y = getScreen()->first;
1329         Row * cursorrow = text->cursor.row;
1330         text->SetCursorFromCoordinates(text->cursor.x_fix, y);
1331         text->FinishUndo();
1332         // This is to allow jumping over large insets
1333         if ((cursorrow == text->cursor.row))
1334                 text->CursorUp();
1335         
1336         if (text->cursor.row->height < workarea->height())
1337                 getScreen()->Draw(text->cursor.y
1338                                   - text->cursor.row->baseline
1339                                   + text->cursor.row->height
1340                                   - workarea->height() + 1 );
1341 }
1342
1343
1344 void BufferView::cursorNext()
1345 {
1346         if (!text->cursor.row->next) return;
1347         
1348         long y = getScreen()->first;
1349         text->GetRowNearY(y);
1350         Row * cursorrow = text->cursor.row;
1351         text->SetCursorFromCoordinates(text->cursor.x_fix, y
1352                                        + workarea->height());
1353         text->FinishUndo();
1354         // This is to allow jumping over large insets
1355         if ((cursorrow == text->cursor.row))
1356                 text->CursorDown();
1357         
1358         if (text->cursor.row->height < workarea->height())
1359                 getScreen()->Draw(text->cursor.y
1360                                   - text->cursor.row->baseline);
1361 }
1362
1363
1364 bool BufferView::available() const
1365 {
1366         if (buffer_ && text) return true;
1367         return false;
1368 }
1369
1370
1371 void BufferView::beforeChange()
1372 {
1373         getScreen()->ToggleSelection();
1374         text->ClearSelection();
1375         FreeUpdateTimer();
1376 }
1377
1378
1379 void BufferView::savePosition()
1380 {
1381         backstack.push(buffer()->fileName(),
1382                        text->cursor.x,
1383                        text->cursor.y);
1384 }
1385
1386
1387 void BufferView::restorePosition()
1388 {
1389         if (backstack.empty()) return;
1390         
1391         int  x, y;
1392         string fname = backstack.pop(&x, &y);
1393         
1394         beforeChange();
1395         Buffer * b = bufferlist.exists(fname) ?
1396                 bufferlist.getBuffer(fname) :
1397                 bufferlist.loadLyXFile(fname); // don't ask, just load it
1398         buffer(b);
1399         text->SetCursorFromCoordinates(x, y);
1400         update(0);
1401
1402
1403
1404 void BufferView::update(signed char f)
1405 {
1406         owner()->updateLayoutChoice();
1407
1408         if (!text->selection && f > -3)
1409                 text->sel_cursor = text->cursor;
1410         
1411         FreeUpdateTimer();
1412         text->FullRebreak();
1413
1414         update();
1415
1416         if (f != 3 && f != -3) {
1417                 fitCursor();
1418                 updateScrollbar();
1419         }
1420
1421         if (f == 1 || f == -1) {
1422                 if (buffer()->isLyxClean()) {
1423                         buffer()->markDirty();
1424                         owner()->getMiniBuffer()->setTimer(4);
1425                 } else {
1426                         buffer()->markDirty();
1427                 }
1428         }
1429 }
1430
1431
1432 void BufferView::smallUpdate(signed char f)
1433 {
1434         getScreen()->SmallUpdate();
1435         if (getScreen()->TopCursorVisible()
1436             != getScreen()->first) {
1437                 update(f);
1438                 return;
1439         }
1440
1441         fitCursor();
1442         updateScrollbar();
1443
1444         if (!text->selection)
1445                 text->sel_cursor = text->cursor;
1446
1447         if (f == 1 || f == -1) {
1448                 if (buffer()->isLyxClean()) {
1449                         buffer()->markDirty();
1450                         owner()->getMiniBuffer()->setTimer(4);
1451                 } else {
1452                         buffer()->markDirty();
1453                 }
1454         }
1455 }
1456
1457
1458 void BufferView::SetState()
1459 {
1460         if (!lyxrc->rtl_support)
1461                 return;
1462         
1463         if (text->real_current_font.getFontDirection()
1464             == LYX_DIR_LEFT_TO_RIGHT) {
1465                 if (!owner_->getIntl()->primarykeymap)
1466                         owner_->getIntl()->KeyMapPrim();
1467         } else {
1468                 if (owner_->getIntl()->primarykeymap)
1469                         owner_->getIntl()->KeyMapSec();
1470         }
1471 }
1472
1473
1474 void BufferView::insetSleep()
1475 {
1476         if (the_locking_inset && !inset_slept) {
1477                 the_locking_inset->GetCursorPos(slx, sly);
1478                 the_locking_inset->InsetUnlock();
1479                 inset_slept = true;
1480         }
1481 }
1482
1483
1484 void BufferView::insetWakeup()
1485 {
1486         if (the_locking_inset && inset_slept) {
1487                 the_locking_inset->Edit(slx, sly);
1488                 inset_slept = false;
1489         }
1490 }
1491
1492
1493 void BufferView::insetUnlock()
1494 {
1495         if (the_locking_inset) {
1496                 if (!inset_slept) the_locking_inset->InsetUnlock();
1497                 the_locking_inset = 0;
1498                 text->FinishUndo();
1499                 inset_slept = false;
1500         }
1501 }
1502
1503
1504 bool BufferView::focus() const
1505 {
1506         return workarea->hasFocus();
1507 }
1508
1509
1510 void BufferView::focus(bool f)
1511 {
1512         if (f) workarea->setFocus();
1513 }
1514
1515
1516 bool BufferView::active() const
1517 {
1518         return workarea->active();
1519 }