9 #include "BufferView_pimpl.h"
11 #include "lyxscreen.h"
15 #include "commandtags.h"
17 #include "minibuffer.h"
20 #include "TextCache.h"
21 #include "bufferlist.h"
22 #include "insets/insetbib.h"
26 extern BufferList bufferlist;
27 extern bool selection_possible;
28 extern char ascii_type;
30 extern int bibitemMaxWidth(Painter &, LyXFont const &);
33 void C_BufferView_CursorToggleCB(FL_OBJECT * ob, long buf);
36 BufferView::Pimpl::Pimpl(BufferView * b, LyXView * o,
37 int xpos, int ypos, int width, int height)
41 workarea = new WorkArea(bv_, xpos, ypos, width, height);
45 current_scrollbar_value = 0;
46 fl_set_timer(timer_cursor, 0.4);
48 work_area_focus = true;
52 void BufferView::Pimpl::updateScreen()
54 // Regenerate the screen.
56 screen = new LyXScreen(*workarea, bv_->text);
60 void BufferView::Pimpl::create_view()
69 timer_cursor = obj = fl_add_timer(FL_HIDDEN_TIMER,
71 fl_set_object_callback(obj, C_BufferView_CursorToggleCB, 0);
76 int BufferView::Pimpl::scrollUp(long time)
78 if (buffer_ == 0) return 0;
79 if (!screen) return 0;
81 double value = workarea->getScrollbarValue();
83 if (value == 0) return 0;
85 float add_value = (bv_->text->DefaultHeight()
86 + float(time) * float(time) * 0.125);
88 if (add_value > workarea->height())
89 add_value = float(workarea->height() -
90 bv_->text->DefaultHeight());
97 workarea->setScrollbarValue(value);
104 int BufferView::Pimpl::scrollDown(long time)
106 if (buffer_ == 0) return 0;
107 if (!screen) return 0;
109 double value= workarea->getScrollbarValue();
110 pair<double, double> p = workarea->getScrollbarBounds();
111 double max = p.second;
113 if (value == max) return 0;
115 float add_value = (bv_->text->DefaultHeight()
116 + float(time) * float(time) * 0.125);
118 if (add_value > workarea->height())
119 add_value = float(workarea->height() -
120 bv_->text->DefaultHeight());
127 workarea->setScrollbarValue(value);
129 bv_->scrollCB(value);
134 void BufferView::Pimpl::scrollUpOnePage()
136 if (buffer_ == 0) return;
139 long y = screen->first;
143 Row * row = bv_->text->GetRowNearY(y);
145 y = y - workarea->height() + row->height;
147 workarea->setScrollbarValue(y);
153 void BufferView::Pimpl::scrollDownOnePage()
155 if (buffer_ == 0) return;
158 long y = screen->first;
160 if (y > bv_->text->height - workarea->height())
163 y += workarea->height();
164 bv_->text->GetRowNearY(y);
166 workarea->setScrollbarValue(y);
172 void BufferView::Pimpl::workAreaMotionNotify(int x, int y, unsigned int state)
174 if (buffer_ == 0 || !screen) return;
176 // Check for inset locking
177 if (bv_->the_locking_inset) {
178 LyXCursor cursor = bv_->text->cursor;
179 bv_->the_locking_inset->
180 InsetMotionNotify(bv_,
187 // Only use motion with button 1
188 if (!state & Button1MotionMask)
191 /* The selection possible is needed, that only motion events are
192 * used, where the bottom press event was on the drawing area too */
193 if (selection_possible) {
194 screen->HideCursor();
196 bv_->text->SetCursorFromCoordinates(x, y + screen->first);
198 if (!bv_->text->selection)
199 bv_->update(-3); // Maybe an empty line was deleted
201 bv_->text->SetSelection();
202 screen->ToggleToggle();
203 if (screen->FitCursor())
204 bv_->updateScrollbar();
205 screen->ShowCursor();
211 // Single-click on work area
212 void BufferView::Pimpl::workAreaButtonPress(int xpos, int ypos, unsigned int button)
217 if (buffer_ == 0 || !screen) return;
219 Inset * inset_hit = checkInsetHit(xpos, ypos, button);
221 // ok ok, this is a hack.
222 if (button == 4 || button == 5) {
225 scrollUp(100); // This number is only temporary
233 if (bv_->the_locking_inset) {
234 // We are in inset locking mode
236 /* Check whether the inset was hit. If not reset mode,
237 otherwise give the event to the inset */
238 if (inset_hit == bv_->the_locking_inset) {
239 bv_->the_locking_inset->
240 InsetButtonPress(bv_,
245 bv_->unlockInset(bv_->the_locking_inset);
250 selection_possible = true;
251 screen->HideCursor();
253 // Right button mouse click on a table
255 (bv_->text->cursor.par->table ||
256 bv_->text->MouseHitInTable(xpos, ypos + screen->first))) {
257 // Set the cursor to the press-position
258 bv_->text->SetCursorFromCoordinates(xpos, ypos + screen->first);
261 // Only show the table popup if the hit is in
263 if (!bv_->text->HitInTable(bv_->text->cursor.row, xpos))
266 // Hit above or below the table?
268 if (!bv_->text->selection) {
269 screen->ToggleSelection();
270 bv_->text->ClearSelection();
271 bv_->text->FullRebreak();
273 bv_->updateScrollbar();
275 // Popup table popup when on a table.
276 // This is obviously temporary, since we
277 // should be able to popup various
278 // context-sensitive-menus with the
279 // the right mouse. So this should be done more
280 // general in the future. Matthias.
281 selection_possible = false;
283 ->Dispatch(LFUN_LAYOUT_TABLE,
289 int screen_first = screen->first;
291 // Middle button press pastes if we have a selection
292 bool paste_internally = false;
294 && bv_->text->selection) {
295 owner_->getLyXFunc()->Dispatch(LFUN_COPY);
296 paste_internally = true;
299 // Clear the selection
300 screen->ToggleSelection();
301 bv_->text->ClearSelection();
302 bv_->text->FullRebreak();
304 bv_->updateScrollbar();
306 // Single left click in math inset?
307 if ((inset_hit != 0) &&
308 (inset_hit->Editable()==Inset::HIGHLY_EDITABLE)) {
309 // Highly editable inset, like math
310 UpdatableInset * inset = static_cast<UpdatableInset *>(inset_hit);
311 selection_possible = false;
312 owner_->updateLayoutChoice();
313 owner_->getMiniBuffer()->Set(inset->EditMessage());
314 inset->InsetButtonPress(bv_, xpos, ypos, button);
315 inset->Edit(bv_, xpos, ypos, button);
319 // Right click on a footnote flag opens float menu
321 selection_possible = false;
325 if (!inset_hit) // otherwise it was already set in checkInsetHit(...)
326 bv_->text->SetCursorFromCoordinates(xpos, ypos + screen_first);
327 bv_->text->FinishUndo();
328 bv_->text->sel_cursor = bv_->text->cursor;
329 bv_->text->cursor.x_fix = bv_->text->cursor.x;
331 owner_->updateLayoutChoice();
332 if (screen->FitCursor()){
333 bv_->updateScrollbar();
334 selection_possible = false;
337 // Insert primary selection with middle mouse
338 // if there is a local selection in the current buffer,
341 if (paste_internally)
342 owner_->getLyXFunc()->Dispatch(LFUN_PASTE);
344 owner_->getLyXFunc()->Dispatch(LFUN_PASTESELECTION,
346 selection_possible = false;
352 void BufferView::Pimpl::doubleClick(int /*x*/, int /*y*/, unsigned int button)
355 if (buffer_ && !bv_->the_locking_inset) {
356 if (screen && button == 1) {
357 screen->HideCursor();
358 screen->ToggleSelection();
359 bv_->text->SelectWord();
360 screen->ToggleSelection(false);
361 /* This will fit the cursor on the screen
369 void BufferView::Pimpl::tripleClick(int /*x*/, int /*y*/, unsigned int button)
372 if (buffer_ && screen && button == 1) {
373 screen->HideCursor();
374 screen->ToggleSelection();
375 bv_->text->CursorHome();
376 bv_->text->sel_cursor = bv_->text->cursor;
377 bv_->text->CursorEnd();
378 bv_->text->SetSelection();
379 screen->ToggleSelection(false);
380 /* This will fit the cursor on the screen
387 void BufferView::Pimpl::workAreaButtonRelease(int x, int y, unsigned int button)
389 if (buffer_ == 0 || screen == 0) return;
391 // If we hit an inset, we have the inset coordinates in these
392 // and inset_hit points to the inset. If we do not hit an
393 // inset, inset_hit is 0, and inset_x == x, inset_y == y.
394 Inset * inset_hit = checkInsetHit(x, y, button);
396 if (bv_->the_locking_inset) {
397 // We are in inset locking mode.
399 /* LyX does a kind of work-area grabbing for insets.
400 Only a ButtonPress Event outside the inset will
401 force a InsetUnlock. */
402 bv_->the_locking_inset->
403 InsetButtonRelease(bv_, x, y, button);
407 selection_possible = false;
408 if (bv_->text->cursor.par->table) {
409 int cell = bv_->text->
410 NumberOfCell(bv_->text->cursor.par,
411 bv_->text->cursor.pos);
412 if (bv_->text->cursor.par->table->IsContRow(cell) &&
413 bv_->text->cursor.par->table->
414 CellHasContRow(bv_->text->cursor.par->table->
415 GetCellAbove(cell))<0) {
416 bv_->text->CursorUp();
420 if (button >= 2) return;
423 owner_->getMiniBuffer()->Set(CurrentState());
425 // Did we hit an editable inset?
426 if (inset_hit != 0) {
427 // Inset like error, notes and figures
428 selection_possible = false;
430 #warning fix this proper in 0.13
432 // Following a ref shouldn't issue
433 // a push on the undo-stack
434 // anylonger, now that we have
435 // keybindings for following
436 // references and returning from
437 // references. IMHO though, it
438 // should be the inset's own business
439 // to push or not push on the undo
440 // stack. They don't *have* to
441 // alter the document...
443 // ...or maybe the SetCursorParUndo()
444 // below isn't necessary at all anylonger?
445 if (inset_hit->LyxCode() == Inset::REF_CODE) {
446 bv_->text->SetCursorParUndo();
449 owner_->getMiniBuffer()->Set(inset_hit->EditMessage());
450 if (inset_hit->Editable()==Inset::HIGHLY_EDITABLE) {
451 // Highly editable inset, like math
452 UpdatableInset *inset = (UpdatableInset *)inset_hit;
453 inset->InsetButtonRelease(bv_, x, y, button);
455 inset_hit->Edit(bv_, x, y, button);
460 // check whether we want to open a float
464 if (bv_->text->cursor.pos <
465 bv_->text->cursor.par->Last()) {
466 c = bv_->text->cursor.par->
467 GetChar(bv_->text->cursor.pos);
469 if (c == LyXParagraph::META_FOOTNOTE
470 || c == LyXParagraph::META_MARGIN
471 || c == LyXParagraph::META_FIG
472 || c == LyXParagraph::META_TAB
473 || c == LyXParagraph::META_WIDE_FIG
474 || c == LyXParagraph::META_WIDE_TAB
475 || c == LyXParagraph::META_ALGORITHM){
477 } else if (bv_->text->cursor.pos - 1 >= 0) {
478 c = bv_->text->cursor.par->
479 GetChar(bv_->text->cursor.pos - 1);
480 if (c == LyXParagraph::META_FOOTNOTE
481 || c == LyXParagraph::META_MARGIN
482 || c == LyXParagraph::META_FIG
483 || c == LyXParagraph::META_TAB
484 || c == LyXParagraph::META_WIDE_FIG
485 || c == LyXParagraph::META_WIDE_TAB
486 || c == LyXParagraph::META_ALGORITHM){
487 // We are one step too far to the right
488 bv_->text->CursorLeft();
494 selection_possible = false;
499 // Do we want to close a float? (click on the float-label)
500 if (bv_->text->cursor.row->par->footnoteflag ==
501 LyXParagraph::OPEN_FOOTNOTE
502 //&& text->cursor.pos == 0
503 && bv_->text->cursor.row->previous &&
504 bv_->text->cursor.row->previous->par->
505 footnoteflag != LyXParagraph::OPEN_FOOTNOTE){
506 LyXFont font(LyXFont::ALL_SANE);
507 font.setSize(LyXFont::SIZE_FOOTNOTE);
509 int box_x = 20; // LYX_PAPER_MARGIN;
510 box_x += lyxfont::width(" wide-tab ", font);
512 int screen_first = screen->first;
515 && y + screen_first > bv_->text->cursor.y -
516 bv_->text->cursor.row->baseline
517 && y + screen_first < bv_->text->cursor.y -
518 bv_->text->cursor.row->baseline
519 + lyxfont::maxAscent(font) * 1.2 + lyxfont::maxDescent(font) * 1.2) {
521 selection_possible = false;
526 // Maybe we want to edit a bibitem ale970302
527 if (bv_->text->cursor.par->bibkey && x < 20 +
528 bibitemMaxWidth(bv_->painter(),
531 params.textclass).defaultfont())) {
532 bv_->text->cursor.par->bibkey->Edit(bv_, 0, 0, 0);
540 * Returns an inset if inset was hit. 0 otherwise.
541 * If hit, the coordinates are changed relative to the inset.
542 * Otherwise coordinates are not changed, and false is returned.
544 Inset * BufferView::Pimpl::checkInsetHit(int & x, int & y, unsigned int button)
549 int y_tmp = y + screen->first;
552 bv_->text->SetCursorFromCoordinates(cursor, x, y_tmp);
554 bool move_cursor = true;
556 bool move_cursor = ((cursor.par != bv_->text->cursor.par) ||
557 (cursor.pos != bv_->text->cursor.pos)) && (button < 2);
560 if (cursor.pos < cursor.par->Last()
561 && cursor.par->GetChar(cursor.pos) == LyXParagraph::META_INSET
562 && cursor.par->GetInset(cursor.pos)
563 && cursor.par->GetInset(cursor.pos)->Editable()) {
565 // Check whether the inset really was hit
566 Inset * tmpinset = cursor.par->GetInset(cursor.pos);
567 LyXFont font = bv_->text->GetFont(cursor.par, cursor.pos);
568 bool is_rtl = font.isVisibleRightToLeft();
572 start_x = cursor.x - tmpinset->width(bv_->painter(), font);
576 end_x = cursor.x + tmpinset->width(bv_->painter(), font);
579 if (x > start_x && x < end_x
580 && y_tmp > cursor.y - tmpinset->ascent(bv_->painter(), font)
581 && y_tmp < cursor.y + tmpinset->descent(bv_->painter(), font)) {
583 bv_->text->SetCursorFromCoordinates(x, y_tmp);
585 // The origin of an inset is on the baseline
586 y = y_tmp - (bv_->text->cursor.y);
591 if ((cursor.pos - 1 >= 0) &&
592 (cursor.par->GetChar(cursor.pos-1) == LyXParagraph::META_INSET) &&
593 (cursor.par->GetInset(cursor.pos - 1)) &&
594 (cursor.par->GetInset(cursor.pos - 1)->Editable())) {
595 Inset * tmpinset = cursor.par->GetInset(cursor.pos-1);
596 LyXFont font = bv_->text->GetFont(cursor.par, cursor.pos-1);
597 bool is_rtl = font.isVisibleRightToLeft();
601 start_x = cursor.x - tmpinset->width(bv_->painter(), font);
605 end_x = cursor.x + tmpinset->width(bv_->painter(), font);
607 if (x > start_x && x < end_x
608 && y_tmp > cursor.y - tmpinset->ascent(bv_->painter(), font)
609 && y_tmp < cursor.y + tmpinset->descent(bv_->painter(), font)) {
611 bv_->text->SetCursorFromCoordinates(x, y_tmp);
613 // The origin of an inset is on the baseline
614 y = y_tmp - (bv_->text->cursor.y);
622 void BufferView::Pimpl::workAreaExpose()
624 // this is a hack to ensure that we only call this through
625 // BufferView::redraw().
630 static int work_area_width = -1;
631 static int work_area_height = -1;
633 bool widthChange = workarea->workWidth() != work_area_width;
634 bool heightChange = workarea->height() != work_area_height;
636 // update from work area
637 work_area_width = workarea->workWidth();
638 work_area_height = workarea->height();
641 // All buffers need a resize
644 // Remove all texts from the textcache
645 // This is not _really_ what we want to do. What
646 // we really want to do is to delete in textcache
647 // that does not have a BufferView with matching
648 // width, but as long as we have only one BufferView
649 // deleting all gives the same result.
650 if (lyxerr.debugging())
651 textcache.show(lyxerr, "Expose delete all");
653 } else if (heightChange) {
654 // Rebuild image of current screen
656 // fitCursor() ensures we don't jump back
657 // to the start of the document on vertical
661 // The main window size has changed, repaint most stuff
663 // ...including the minibuffer
664 owner_->getMiniBuffer()->Init();
666 } else if (screen) screen->Redraw();
668 // Grey box when we don't have a buffer
672 // always make sure that the scrollbar is sane.
673 bv_->updateScrollbar();
674 owner_->updateLayoutChoice();
680 string fromClipboard(Window win, XEvent * event)
683 if (event->xselection.type == XA_STRING
684 && event->xselection.property) {
688 unsigned char * uc = 0;
690 if (XGetWindowProperty(
691 event->xselection.display, // display
693 event->xselection.property, // property
697 XA_STRING, // req_type
698 &tmpatom, // actual_type_return
699 &tmpint, // actual_format_return
710 if (XGetWindowProperty(
711 event->xselection.display, // display
713 event->xselection.property, // property
715 ul2/4+1, // long_length
717 XA_STRING, // req_type
718 &tmpatom, // actual_type_return
719 &tmpint, // actual_format_return
720 &ul1, // nitems_return
721 &ul2, // bytes_after_return
722 &uc // prop_return */
727 strret = reinterpret_cast<char*>(uc);
728 free(uc); // yes free!
736 void BufferView::Pimpl::workAreaSelectionNotify(Window win, XEvent * event)
738 if (buffer_ == 0) return;
740 screen->HideCursor();
742 string clb = fromClipboard(win, event);
745 bv_->text->InsertStringA(clb);
747 bv_->text->InsertStringB(clb);