]> git.saurik.com Git - wxWidgets.git/blob - samples/richedit/wxlwindow.cpp
made Shift-JIS encoding upper case; made SJIS the canonical name for it (just because...
[wxWidgets.git] / samples / richedit / wxlwindow.cpp
1 /*-*- c++ -*-********************************************************
2 * wxLwindow.h : a scrolled Window for displaying/entering rich text*
3 * *
4 * (C) 1998-2000 by Karsten Ballüder (ballueder@gmx.net) *
5 * *
6 * $Id$
7 *******************************************************************/
8
9 // ============================================================================
10 // declarations
11 // ============================================================================
12
13 // ----------------------------------------------------------------------------
14 // headers
15 // ----------------------------------------------------------------------------
16
17 #include "wx/wxprec.h"
18
19 #ifdef __BORLANDC__
20 # pragma hdrstop
21 #endif
22
23 #include "Mpch.h"
24
25 #ifdef M_BASEDIR
26 # ifndef USE_PCH
27 # include "Mcommon.h"
28 # include "gui/wxMenuDefs.h"
29 # include "gui/wxMApp.h"
30 # endif // USE_PCH
31 # include "gui/wxlwindow.h"
32 # include "gui/wxlparser.h"
33
34 # include "MDialogs.h"
35 # include "strutil.h"
36 #else
37 # ifdef __WXMSW__
38 # include "wx/msw/private.h"
39 # endif
40
41 # include "wxlwindow.h"
42 # include "wxlparser.h"
43 #endif
44
45 #include "wx/clipbrd.h"
46 #include "wx/textctrl.h"
47 #include "wx/dataobj.h"
48
49 #ifdef WXLAYOUT_USE_CARET
50 # include "wx/caret.h"
51 #endif // WXLAYOUT_USE_CARET
52
53 #include <ctype.h>
54
55
56 // ----------------------------------------------------------------------------
57 // macros
58 // ----------------------------------------------------------------------------
59
60 #ifdef DEBUG
61 # define WXLO_DEBUG(x) wxLogDebug x
62 #else
63 # define WXLO_DEBUG(x)
64 #endif
65
66 // for profiling in debug mode:
67 WXLO_TIMER_DEFINE(UpdateTimer);
68 WXLO_TIMER_DEFINE(BlitTimer);
69 WXLO_TIMER_DEFINE(LayoutTimer);
70 WXLO_TIMER_DEFINE(TmpTimer);
71 WXLO_TIMER_DEFINE(DrawTimer);
72
73 // ----------------------------------------------------------------------------
74 // constants
75 // ----------------------------------------------------------------------------
76
77 /// offsets to put a nice frame around text
78 #define WXLO_XOFFSET 4
79 #define WXLO_YOFFSET 4
80
81 /// offset to the right and bottom for when to redraw scrollbars
82 #define WXLO_ROFFSET 20
83 #define WXLO_BOFFSET 20
84
85 /// scroll margins when selecting with the mouse
86 #define WXLO_SCROLLMARGIN_X 10
87 #define WXLO_SCROLLMARGIN_Y 10
88
89 /// the size of one scrollbar page in pixels
90 static const int X_SCROLL_PAGE = 10;
91 static const int Y_SCROLL_PAGE = 20;
92
93
94
95 // ----------------------------------------------------------------------------
96 // event tables
97 // ----------------------------------------------------------------------------
98
99 BEGIN_EVENT_TABLE(wxLayoutWindow,wxScrolledWindow)
100 EVT_SIZE (wxLayoutWindow::OnSize)
101
102 EVT_PAINT (wxLayoutWindow::OnPaint)
103
104 EVT_CHAR (wxLayoutWindow::OnChar)
105 EVT_KEY_UP (wxLayoutWindow::OnKeyUp)
106
107 EVT_LEFT_DOWN(wxLayoutWindow::OnLeftMouseDown)
108 EVT_LEFT_UP(wxLayoutWindow::OnLeftMouseUp)
109 EVT_RIGHT_DOWN(wxLayoutWindow::OnRightMouseClick)
110 EVT_LEFT_DCLICK(wxLayoutWindow::OnMouseDblClick)
111 EVT_MIDDLE_DOWN(wxLayoutWindow::OnMiddleMouseDown)
112 EVT_MOTION (wxLayoutWindow::OnMouseMove)
113
114 EVT_UPDATE_UI(WXLOWIN_MENU_UNDERLINE, wxLayoutWindow::OnUpdateMenuUnderline)
115 EVT_UPDATE_UI(WXLOWIN_MENU_BOLD, wxLayoutWindow::OnUpdateMenuBold)
116 EVT_UPDATE_UI(WXLOWIN_MENU_ITALICS, wxLayoutWindow::OnUpdateMenuItalic)
117 EVT_MENU_RANGE(WXLOWIN_MENU_FIRST, WXLOWIN_MENU_LAST, wxLayoutWindow::OnMenu)
118
119 EVT_SET_FOCUS(wxLayoutWindow::OnSetFocus)
120 EVT_KILL_FOCUS(wxLayoutWindow::OnKillFocus)
121
122 // EVT_IDLE(wxLayoutWindow::ResizeScrollbars)
123 END_EVENT_TABLE()
124
125 // ----------------------------------------------------------------------------
126 // function prototypes
127 // ----------------------------------------------------------------------------
128
129 /// returns true if keyCode is one of arrows/home/end/page{up|down} keys
130 static bool IsDirectionKey(long keyCode);
131
132 // ============================================================================
133 // implementation
134 // ============================================================================
135
136 #ifndef wxWANTS_CHARS
137 # define wxWANTS_CHARS 0
138 #endif
139
140 // ----------------------------------------------------------------------------
141 // wxLayoutWindow
142 // ----------------------------------------------------------------------------
143
144 wxLayoutWindow::wxLayoutWindow(wxWindow *parent)
145 : wxScrolledWindow(parent, wxID_ANY,
146 wxDefaultPosition, wxDefaultSize,
147 wxHSCROLL | wxVSCROLL |
148 wxBORDER |
149 wxWANTS_CHARS),
150 m_llist(NULL)
151 {
152 #if wxUSE_STATUSBAR
153 SetStatusBar(NULL); // don't use statusbar
154 #endif // wxUSE_STATUSBAR
155 m_Editable = false;
156 m_doSendEvents = false;
157 m_ViewStartX = 0; m_ViewStartY = 0;
158 m_DoPopupMenu = true;
159 m_PopupMenu = MakeFormatMenu();
160 m_memDC = new wxMemoryDC;
161 m_bitmap = new wxBitmap(4,4);
162 m_bitmapSize = wxPoint(4,4);
163 m_llist = new wxLayoutList();
164 m_BGbitmap = NULL;
165 m_ScrollToCursor = false;
166 #ifndef __WXMSW__
167 m_FocusFollowMode = false;
168 #endif
169 SetWordWrap(false);
170 SetWrapMargin(0);
171
172 // no scrollbars initially
173 m_hasHScrollbar =
174 m_hasVScrollbar = false;
175
176 m_Selecting = false;
177
178 #ifdef WXLAYOUT_USE_CARET
179 // FIXME cursor size shouldn't be hardcoded
180 wxCaret *caret = new wxCaret(this, 2, 20);
181 SetCaret(caret);
182 m_llist->SetCaret(caret);
183 #endif // WXLAYOUT_USE_CARET
184
185 m_HaveFocus = false;
186 m_HandCursor = false;
187 m_CursorVisibility = -1;
188 SetCursor(wxCURSOR_IBEAM);
189 SetDirty();
190
191 // at least under Windows, this should be the default behaviour
192 m_AutoDeleteSelection = true;
193 }
194
195 wxLayoutWindow::~wxLayoutWindow()
196 {
197 delete m_memDC; // deletes bitmap automatically (?)
198 delete m_bitmap;
199 delete m_llist;
200 delete m_PopupMenu;
201 SetBackgroundBitmap(NULL);
202 }
203
204 void
205 wxLayoutWindow::Clear(int family,
206 int size,
207 int style,
208 int weight,
209 int underline,
210 wxColour *fg,
211 wxColour *bg)
212 {
213 GetLayoutList()->Clear(family,size,style,weight,underline,fg,bg);
214 SetBackgroundColour(GetLayoutList()->GetDefaultStyleInfo().GetBGColour());
215 //wxScrolledWindow::Clear();
216 ResizeScrollbars(true);
217 SetDirty();
218 SetModified(false);
219 if ( m_Editable )
220 m_CursorVisibility = 1;
221
222 #ifdef WXLAYOUT_USE_CARET
223 if ( m_CursorVisibility == 1 )
224 GetCaret()->Show();
225 #endif // WXLAYOUT_USE_CARET
226
227 RequestUpdate((wxRect *)NULL);
228 }
229
230 void wxLayoutWindow::Refresh(bool eraseBackground, const wxRect *rect)
231 {
232 wxScrolledWindow::Refresh(eraseBackground, rect);
233 }
234
235 void
236 wxLayoutWindow::OnMouse(int eventId, wxMouseEvent& event)
237 {
238 wxClientDC dc( this );
239 PrepareDC( dc );
240 if ( (eventId != WXLOWIN_MENU_MOUSEMOVE
241 #ifndef __WXMSW__
242 || m_FocusFollowMode
243 #endif
244 )
245 && (wxWindow::FindFocus() != this) )
246 {
247 SetFocus();
248 }
249
250 wxPoint findPos;
251 findPos.x = dc.DeviceToLogicalX(event.GetX());
252 findPos.y = dc.DeviceToLogicalY(event.GetY());
253
254 findPos.x -= WXLO_XOFFSET;
255 findPos.y -= WXLO_YOFFSET;
256
257 if(findPos.x < 0)
258 findPos.x = 0;
259
260 if(findPos.y < 0)
261 findPos.y = 0;
262
263 m_ClickPosition = wxPoint(event.GetX(), event.GetY());
264
265 // Scroll the window if the mouse is at the end of it:
266 if(m_Selecting && eventId == WXLOWIN_MENU_MOUSEMOVE)
267 {
268 //WXLO_DEBUG(("selecting at : %d/%d", (int) event.GetX(), (int)event.GetY()));
269 int left, top;
270 GetViewStart(&left, &top);
271 wxSize size = GetClientSize();
272 int xdelta, ydelta;
273
274 if(event.GetX() < WXLO_SCROLLMARGIN_X)
275 xdelta = -(WXLO_SCROLLMARGIN_X-event.GetX());
276 else if(event.GetX() > size.x-WXLO_SCROLLMARGIN_X)
277 xdelta = event.GetX()-size.x+WXLO_SCROLLMARGIN_X;
278 else
279 xdelta = 0;
280
281 if(event.GetY() < WXLO_SCROLLMARGIN_Y)
282 ydelta = -(WXLO_SCROLLMARGIN_Y-event.GetY());
283 else if(event.GetY() > size.y-WXLO_SCROLLMARGIN_Y)
284 ydelta = event.GetY()-size.y+WXLO_SCROLLMARGIN_Y;
285 else
286 ydelta = 0;
287
288 //WXLO_DEBUG(("xdelta: %d", (int) xdelta));
289 if(xdelta != 0 || ydelta != 0)
290 {
291 top += ydelta; if(top < 0) top = 0;
292 left += xdelta; if(left < 0) left = 0;
293 Scroll(left, top);
294 }
295 }
296
297 wxPoint cursorPos;
298 bool found;
299 wxLayoutObject *obj = m_llist->FindObjectScreen(dc, findPos,
300 &cursorPos, &found);
301 wxLayoutObject::UserData *u = obj ? obj->GetUserData() : NULL;
302
303 // has the mouse only been moved?
304 switch ( eventId )
305 {
306 case WXLOWIN_MENU_MOUSEMOVE:
307 {
308 // this variables is used to only erase the message in the status
309 // bar if we had put it there previously - otherwise empting status
310 // bar might be undesirable
311 #if wxUSE_STATUSBAR
312 static bool s_hasPutMessageInStatusBar = false;
313 #endif // wxUSE_STATUSBAR
314
315 // found is only true if we are really over an object, not just
316 // behind it
317 if(found && u && ! m_Selecting)
318 {
319 if(!m_HandCursor)
320 SetCursor(wxCURSOR_HAND);
321 m_HandCursor = true;
322 #if wxUSE_STATUSBAR
323 if(m_StatusBar && m_StatusFieldLabel != -1)
324 {
325 const wxString &label = u->GetLabel();
326 if(label.Length())
327 {
328 m_StatusBar->SetStatusText(label,m_StatusFieldLabel);
329 s_hasPutMessageInStatusBar = true;
330 }
331 }
332 #endif // wxUSE_STATUSBAR
333 }
334 else
335 {
336 if(m_HandCursor)
337 SetCursor(wxCURSOR_IBEAM);
338 m_HandCursor = false;
339 #if wxUSE_STATUSBAR
340 if( m_StatusBar && m_StatusFieldLabel != -1 &&
341 s_hasPutMessageInStatusBar )
342 {
343 m_StatusBar->SetStatusText(wxEmptyString, m_StatusFieldLabel);
344 }
345 #endif // wxUSE_STATUSBAR
346 }
347 }
348
349 // selecting?
350 if ( event.LeftIsDown() )
351 {
352 // m_Selecting might not be set if the button got pressed
353 // outside this window, so check for it:
354 if( m_Selecting )
355 {
356 m_llist->ContinueSelection(cursorPos, m_ClickPosition);
357 RequestUpdate(); // TODO: we don't have to redraw everything!
358 }
359 }
360
361 if ( u )
362 {
363 u->DecRef();
364 u = NULL;
365 }
366 break;
367
368 case WXLOWIN_MENU_LDOWN:
369 {
370 // always move cursor to mouse click:
371 m_llist->MoveCursorTo(cursorPos);
372
373 // clicking a mouse removes the selection
374 if ( m_llist->HasSelection() )
375 {
376 m_llist->DiscardSelection();
377 m_Selecting = false;
378 RequestUpdate(); // TODO: we don't have to redraw everything!
379 }
380
381 // Calculate where the top of the visible area is:
382 int x0, y0;
383 GetViewStart(&x0,&y0);
384 int dx, dy;
385 GetScrollPixelsPerUnit(&dx, &dy);
386 x0 *= dx; y0 *= dy;
387
388 wxPoint offset(-x0+WXLO_XOFFSET, -y0+WXLO_YOFFSET);
389
390 if(m_CursorVisibility == -1)
391 m_CursorVisibility = 1;
392
393 #ifdef WXLAYOUT_USE_CARET
394 if ( m_CursorVisibility == 1 )
395 GetCaret()->Show();
396 #endif // WXLAYOUT_USE_CARET
397
398 if(m_CursorVisibility)
399 {
400 // draw a thick cursor for editable windows with focus
401 m_llist->DrawCursor(dc, m_HaveFocus && IsEditable(), offset);
402 }
403
404 #ifdef __WXGTK__
405 RequestUpdate(); // RequestUpdate suppresses flicker under GTK
406 #endif // wxGTK
407
408 // start selection
409 m_llist->StartSelection(wxPoint(-1, -1), m_ClickPosition);
410 m_Selecting = true;
411 }
412 break;
413
414 case WXLOWIN_MENU_LUP:
415 if ( m_Selecting )
416 {
417 // end selection at the cursor position corresponding to the
418 // current mouse position, but don´t move cursor there.
419 m_llist->EndSelection(cursorPos,m_ClickPosition);
420 m_Selecting = false;
421
422 RequestUpdate(); // TODO: we don't have to redraw everything!
423 }
424 break;
425
426 case WXLOWIN_MENU_MDOWN:
427 Paste(true);
428 break;
429
430 case WXLOWIN_MENU_DBLCLICK:
431 // select a word under cursor
432 m_llist->MoveCursorTo(cursorPos);
433 m_llist->MoveCursorWord(-1);
434 m_llist->StartSelection();
435 m_llist->MoveCursorWord(1, false);
436 m_llist->EndSelection();
437 m_Selecting = false;
438 RequestUpdate(); // TODO: we don't have to redraw everything!
439 break;
440 }
441
442 // notify about mouse events?
443 if( m_doSendEvents )
444 {
445 // only do the menu if activated, editable and not on a clickable object
446 if(eventId == WXLOWIN_MENU_RCLICK
447 && IsEditable()
448 && (! obj || u == NULL))
449 {
450 PopupMenu(m_PopupMenu, m_ClickPosition.x, m_ClickPosition.y);
451 if(u) u->DecRef();
452 return;
453 }
454
455 // find the object at this position
456 if(obj)
457 {
458 wxCommandEvent commandEvent(wxEVT_COMMAND_MENU_SELECTED, eventId);
459 commandEvent.SetEventObject( this );
460 commandEvent.SetClientData((char *)obj);
461 GetEventHandler()->ProcessEvent(commandEvent);
462 }
463 }
464
465 if( u ) u->DecRef();
466 }
467
468 // ----------------------------------------------------------------------------
469 // keyboard handling.
470 // ----------------------------------------------------------------------------
471
472 void
473 wxLayoutWindow::OnChar(wxKeyEvent& event)
474 {
475 int keyCode = event.GetKeyCode();
476 bool ctrlDown = event.ControlDown();
477
478 #ifdef WXLAYOUT_DEBUG
479 if(keyCode == WXK_F1)
480 {
481 m_llist->Debug();
482 return;
483 }
484 #endif
485
486 // Force m_Selecting to be false if shift is no longer
487 // pressed. OnKeyUp() cannot catch all Shift-Up events.
488 if(m_Selecting && !event.ShiftDown())
489 {
490 m_Selecting = false;
491 m_llist->EndSelection();
492 m_llist->DiscardSelection(); //FIXME: correct?
493 }
494
495 // If we deleted the selection here, we must not execute the
496 // deletion in Delete/Backspace handling.
497 bool deletedSelection = false;
498 // pressing any non-arrow key optionally replaces the selection:
499 if(m_AutoDeleteSelection
500 && IsEditable()
501 && !m_Selecting
502 && m_llist->HasSelection()
503 && ! IsDirectionKey(keyCode)
504 && ! (event.AltDown() || ctrlDown) )
505 {
506 m_llist->DeleteSelection();
507 deletedSelection = true;
508 SetDirty();
509 }
510
511 // <Shift>+<arrow> starts selection
512 if ( IsDirectionKey(keyCode) )
513 {
514 // just continue the old selection
515 if ( m_Selecting && event.ShiftDown() )
516 {
517 m_llist->ContinueSelection();
518 }
519 else
520 {
521 m_llist->DiscardSelection();
522 m_Selecting = false;
523 if( event.ShiftDown() )
524 {
525 m_Selecting = true;
526 m_llist->StartSelection();
527 }
528 }
529 }
530
531 // If needed, make cursor visible:
532 if(m_CursorVisibility == -1)
533 m_CursorVisibility = 1;
534
535 /* These two nested switches work like this:
536 The first one processes all non-editing keycodes, to move the
537 cursor, etc. It's default will process all keycodes causing
538 modifications to the buffer, but only if editing is allowed.
539 */
540 switch(keyCode)
541 {
542
543 case WXK_RIGHT:
544 if ( ctrlDown )
545 m_llist->MoveCursorWord(1);
546 else
547 m_llist->MoveCursorHorizontally(1);
548 break;
549
550 case WXK_LEFT:
551 if ( ctrlDown )
552 m_llist->MoveCursorWord(-1);
553 else
554 m_llist->MoveCursorHorizontally(-1);
555
556 break;
557
558 case WXK_UP:
559 m_llist->MoveCursorVertically(-1);
560 break;
561
562 case WXK_DOWN:
563 m_llist->MoveCursorVertically(1);
564 break;
565
566 case WXK_PRIOR:
567 m_llist->MoveCursorVertically(-Y_SCROLL_PAGE);
568 break;
569
570 case WXK_NEXT:
571 m_llist->MoveCursorVertically(Y_SCROLL_PAGE);
572 break;
573
574 case WXK_HOME:
575 if ( ctrlDown )
576 m_llist->MoveCursorTo(wxPoint(0, 0));
577 else
578 m_llist->MoveCursorToBeginOfLine();
579 break;
580
581 case WXK_END:
582 if ( ctrlDown )
583 m_llist->MoveCursorToEnd();
584 else
585 m_llist->MoveCursorToEndOfLine();
586 break;
587
588 default:
589
590 if(ctrlDown && ! IsEditable())
591 {
592 switch(keyCode)
593 {
594
595 case 'c':
596 // this should work even in read-only mode
597 Copy(true, true);
598 break;
599
600 case 's': // search
601 Find(wxEmptyString);
602 break;
603
604 case 't': // search again
605 FindAgain();
606 break;
607
608 default:
609 // we don't handle it, maybe an accelerator?
610 event.Skip();
611 ;
612 }
613 }
614 else if( IsEditable() )
615 {
616 /* First, handle control keys */
617 if(ctrlDown && ! event.AltDown())
618 {
619 if(keyCode >= 'A' && keyCode <= 'Z')
620 keyCode = tolower(keyCode);
621
622 switch(keyCode)
623 {
624
625 case WXK_INSERT:
626 Copy();
627 break;
628
629 case WXK_DELETE :
630 if(! deletedSelection)
631 {
632 m_llist->DeleteWord();
633 SetDirty();
634 }
635 break;
636
637 case 'd':
638 if(! deletedSelection) // already done
639 {
640 m_llist->Delete(1);
641 SetDirty();
642 }
643 break;
644
645 case 'y':
646 m_llist->DeleteLines(1);
647 SetDirty();
648 break;
649
650 case 'h': // like backspace
651 if(m_llist->MoveCursorHorizontally(-1))
652 {
653 m_llist->Delete(1);
654 SetDirty();
655 }
656 break;
657
658 case 's': // search
659 Find(wxEmptyString);
660 break;
661
662 case 't': // search again
663 FindAgain();
664 break;
665
666 case 'u':
667 m_llist->DeleteToBeginOfLine();
668 SetDirty();
669 break;
670
671 case 'k':
672 m_llist->DeleteToEndOfLine();
673 SetDirty();
674 break;
675
676 case 'c':
677 Copy(true, true);
678 break;
679
680 case 'v':
681 Paste(true);
682 break;
683
684 case 'x':
685 Cut();
686 break;
687
688 case 'w':
689 if(m_WrapMargin > 0)
690 m_llist->WrapLine(m_WrapMargin);
691 break;
692
693 case 'q':
694 if(m_WrapMargin > 0)
695 m_llist->WrapAll(m_WrapMargin);
696 break;
697
698 #ifdef WXLAYOUT_DEBUG
699 case WXK_F1:
700 m_llist->SetFont(-1,-1,-1,-1,true); // underlined
701 break;
702
703 case 'l':
704 Refresh(true);
705 break;
706 #endif
707
708 default:
709 // we don't handle it, maybe an accelerator?
710 event.Skip();
711 }
712 }
713 // ALT only:
714 else if( event.AltDown() && ! event.ControlDown() )
715 {
716 switch(keyCode)
717 {
718 case WXK_DELETE:
719 case 'd':
720 m_llist->DeleteWord();
721 SetDirty();
722 break;
723
724 default:
725 // we don't handle it, maybe an accelerator?
726 event.Skip();
727 }
728 }
729 // no control keys:
730 else if ( ! event.AltDown() && ! event.ControlDown())
731 {
732 switch(keyCode)
733 {
734 case WXK_INSERT:
735 if(event.ShiftDown())
736 Paste();
737 break;
738
739 case WXK_DELETE :
740 if(event.ShiftDown())
741 {
742 Cut();
743 }
744 else if(! deletedSelection)
745 {
746 m_llist->Delete(1);
747 SetDirty();
748 }
749 break;
750
751 case WXK_BACK: // backspace
752 if(! deletedSelection)
753 {
754 if(m_llist->MoveCursorHorizontally(-1))
755 {
756 m_llist->Delete(1);
757 SetDirty();
758 }
759 }
760 break;
761
762 case WXK_RETURN:
763 if (m_DoWordWrap &&
764 m_WrapMargin > 0
765 && m_llist->GetCursorPos().x > m_WrapMargin )
766 {
767 m_llist->WrapLine(m_WrapMargin);
768 }
769
770 m_llist->LineBreak();
771 SetDirty();
772 break;
773
774 case WXK_TAB:
775 if ( !event.ShiftDown() )
776 {
777 // TODO should be configurable
778 static const int tabSize = 8;
779
780 CoordType x = m_llist->GetCursorPos().x;
781 size_t numSpaces = tabSize - x % tabSize;
782 m_llist->Insert(wxString(' ', numSpaces));
783 SetDirty();
784 }
785 break;
786
787 default:
788 if ( ( !(event.ControlDown() || event.AltDown()) )
789 && (keyCode < 256 && keyCode >= 32) )
790 {
791 if ( m_DoWordWrap
792 && m_WrapMargin > 0
793 && m_llist->GetCursorPos().x > m_WrapMargin
794 && isspace(keyCode) )
795 {
796 m_llist->WrapLine(m_WrapMargin);
797 }
798
799 m_llist->Insert((wxChar)keyCode);
800 SetDirty();
801 }
802 else
803 {
804 // we don't handle it, maybe an accelerator?
805 event.Skip();
806 }
807 break;
808 }
809
810 }
811 }// if(IsEditable())
812 else
813 {
814 // we don't handle it, maybe an accelerator?
815 event.Skip();
816 }
817 }// first switch()
818
819 if ( m_Selecting )
820 {
821 // continue selection to the current (new) cursor position
822 m_llist->ContinueSelection();
823 }
824
825 ScrollToCursor();
826 // refresh the screen
827 RequestUpdate(m_llist->GetUpdateRect());
828 }
829
830 void
831 wxLayoutWindow::OnKeyUp(wxKeyEvent& event)
832 {
833 if ( event.GetKeyCode() == WXK_SHIFT && m_Selecting )
834 {
835 m_llist->EndSelection();
836 m_Selecting = false;
837 }
838
839 event.Skip();
840 }
841
842
843 void
844 wxLayoutWindow::ScrollToCursor()
845 {
846 //is always needed to make sure we know where the cursor is
847 //if(IsDirty())
848 //RequestUpdate(m_llist->GetUpdateRect());
849
850
851 ResizeScrollbars();
852
853 int x0,y0,x1,y1, dx, dy;
854
855 // Calculate where the top of the visible area is:
856 GetViewStart(&x0,&y0);
857 GetScrollPixelsPerUnit(&dx, &dy);
858 x0 *= dx; y0 *= dy;
859
860 WXLO_DEBUG(("ScrollToCursor: GetViewStart is %d/%d", x0, y0));
861
862 // Get the size of the visible window:
863 GetClientSize(&x1, &y1);
864
865 // Make sure that the scrollbars are at a position so that the cursor is
866 // visible if we are editing
867 WXLO_DEBUG(("m_ScrollToCursor = %d", (int) m_ScrollToCursor));
868 wxPoint cc = m_llist->GetCursorScreenPos();
869
870 // the cursor should be completely visible in both directions
871 wxPoint cs(m_llist->GetCursorSize());
872 int nx = -1,
873 ny = -1;
874
875 if ( cc.x < x0 || cc.x >= x0 + x1 - cs.x )
876 {
877 nx = cc.x - x1/2;
878 if ( nx < 0 )
879 nx = 0;
880 }
881
882 if ( cc.y < y0 || cc.y >= y0 + y1 - cs.y )
883 {
884 ny = cc.y - y1/2;
885 if ( ny < 0)
886 ny = 0;
887 }
888
889 if( nx != -1 || ny != -1 )
890 {
891 // set new view start
892 Scroll(nx == -1 ? -1 : (nx+dx-1)/dx, ny == -1 ? -1 : (ny+dy-1)/dy);
893 // avoid recursion
894 m_ScrollToCursor = false;
895 RequestUpdate();
896 }
897 }
898
899 void
900 wxLayoutWindow::OnPaint( wxPaintEvent &WXUNUSED(event))
901 {
902 wxRect region = GetUpdateRegion().GetBox();
903 InternalPaint(&region);
904 }
905
906 void
907 wxLayoutWindow::RequestUpdate(const wxRect *
908 #ifdef __WXGTK__
909 updateRect
910 #else
911 WXUNUSED(updateRect)
912 #endif
913 )
914 {
915 #ifdef __WXGTK__
916 // Calling Refresh() causes bad flicker under wxGTK!!!
917 InternalPaint(updateRect);
918 #else
919 // shouldn't specify the update rectangle if it doesn't include all the
920 // changed locations - otherwise, they won't be repainted at all because
921 // the system clips the display to the update rect
922 Refresh(false); //, updateRect);
923 #endif
924 }
925
926 void
927 wxLayoutWindow::InternalPaint(const wxRect *updateRect)
928 {
929
930 wxPaintDC dc( this );
931 PrepareDC( dc );
932
933 #ifdef WXLAYOUT_USE_CARET
934 // hide the caret before drawing anything
935 GetCaret()->Hide();
936 #endif // WXLAYOUT_USE_CARET
937
938 int x0,y0,x1,y1, dx, dy;
939
940 // Calculate where the top of the visible area is:
941 GetViewStart(&x0,&y0);
942 GetScrollPixelsPerUnit(&dx, &dy);
943 x0 *= dx; y0 *= dy;
944
945 // Get the size of the visible window:
946 GetClientSize(&x1,&y1);
947 wxASSERT(x1 >= 0);
948 wxASSERT(y1 >= 0);
949
950 if(updateRect)
951 {
952 WXLO_DEBUG(("Update rect: %ld,%ld / %ld,%ld",
953 updateRect->x, updateRect->y,
954 updateRect->x+updateRect->width,
955 updateRect->y+updateRect->height));
956 }
957
958 ResizeScrollbars(true);
959
960 WXLO_TIMER_START(TmpTimer);
961 /* Check whether the window has grown, if so, we need to reallocate
962 the bitmap to be larger. */
963 if(x1 > m_bitmapSize.x || y1 > m_bitmapSize.y)
964 {
965 wxASSERT(m_bitmapSize.x > 0);
966 wxASSERT(m_bitmapSize.y > 0);
967
968 m_memDC->SelectObject(wxNullBitmap);
969 delete m_bitmap;
970 m_bitmapSize = wxPoint(x1,y1);
971 m_bitmap = new wxBitmap(x1,y1);
972 m_memDC->SelectObject(*m_bitmap);
973 }
974
975 m_memDC->SetDeviceOrigin(0,0);
976 m_memDC->SetBackground(wxBrush(m_llist->GetDefaultStyleInfo().GetBGColour(),wxSOLID));
977 m_memDC->SetPen(wxPen(m_llist->GetDefaultStyleInfo().GetBGColour(),
978 0,wxTRANSPARENT));
979 m_memDC->SetLogicalFunction(wxCOPY);
980 m_memDC->Clear();
981 WXLO_TIMER_STOP(TmpTimer);
982
983 // fill the background with the background bitmap
984 if(m_BGbitmap)
985 {
986 CoordType
987 y, x,
988 w = m_BGbitmap->GetWidth(),
989 h = m_BGbitmap->GetHeight();
990 for(y = 0; y < y1; y+=h)
991 {
992 for(x = 0; x < x1; x+=w)
993 {
994 m_memDC->DrawBitmap(*m_BGbitmap, x, y);
995 }
996 }
997
998 m_memDC->SetBackgroundMode(wxTRANSPARENT);
999 }
1000
1001 // This is the important bit: we tell the list to draw itself
1002 #if WXLO_DEBUG_URECT
1003 if(updateRect)
1004 {
1005 WXLO_DEBUG(("Update rect: %ld,%ld / %ld,%ld",
1006 updateRect->x, updateRect->y,
1007 updateRect->x+updateRect->width,
1008 updateRect->y+updateRect->height));
1009 }
1010 #endif
1011
1012 // Device origins on the memDC are suspect, we translate manually
1013 // with the translate parameter of Draw().
1014 wxPoint offset(-x0+WXLO_XOFFSET,-y0+WXLO_YOFFSET);
1015 m_llist->Draw(*m_memDC,offset, y0, y0+y1);
1016
1017 // We start calculating a new update rect before drawing the
1018 // cursor, so that the cursor coordinates get included in the next
1019 // update rectangle (although they are drawn on the memDC, this is
1020 // needed to erase it):
1021 m_llist->InvalidateUpdateRect();
1022 if(m_CursorVisibility == 1)
1023 {
1024 // draw a thick cursor for editable windows with focus
1025 m_llist->DrawCursor(*m_memDC,
1026 m_HaveFocus && IsEditable(),
1027 offset);
1028 }
1029
1030 WXLO_TIMER_START(BlitTimer);
1031 // Now copy everything to the screen:
1032 #if 0
1033 // This somehow doesn't work, but even the following bit with the
1034 // whole rect at once is still a bit broken I think.
1035 wxRegionIterator ri ( GetUpdateRegion() );
1036 if(ri)
1037 while(ri)
1038 {
1039 WXLO_DEBUG(("UpdateRegion: %ld,%ld, %ld,%ld",
1040 ri.GetX(),ri.GetY(),ri.GetW(),ri.GetH()));
1041
1042 dc.Blit(x0+ri.GetX(),y0+ri.GetY(),ri.GetW(),ri.GetH(),
1043 m_memDC,ri.GetX(),ri.GetY(),wxCOPY,false);
1044 ri++;
1045 }
1046 else
1047 #endif
1048 {
1049 // FIXME: Trying to copy only the changed parts, but it does not seem
1050 // to work:
1051 // x0 = updateRect->x; y0 = updateRect->y;
1052 // if(updateRect->height < y1)
1053 // y1 = updateRect->height;
1054 // y1 += WXLO_YOFFSET; //FIXME might not be needed
1055 dc.Blit(x0,y0,x1,y1,m_memDC,0,0,wxCOPY,false);
1056 }
1057
1058 WXLO_TIMER_STOP(BlitTimer);
1059
1060
1061 #ifdef WXLAYOUT_USE_CARET
1062 // show the caret back after everything is redrawn
1063 GetCaret()->Show();
1064 #endif // WXLAYOUT_USE_CARET
1065
1066 ResetDirty();
1067
1068 #if wxUSE_STATUSBAR
1069 if ( m_StatusBar && m_StatusFieldCursor != -1 )
1070 {
1071 static wxPoint s_oldCursorPos(-1, -1);
1072
1073 wxPoint pos(m_llist->GetCursorPos());
1074
1075 // avoid unnecessary status bar refreshes
1076 if ( pos != s_oldCursorPos )
1077 {
1078 s_oldCursorPos = pos;
1079
1080 wxString label;
1081 label.Printf(_("Ln:%d Col:%d"), pos.y + 1, pos.x + 1);
1082 m_StatusBar->SetStatusText(label, m_StatusFieldCursor);
1083 }
1084 }
1085 #endif // wxUSE_STATUSBAR
1086
1087 WXLO_TIMER_PRINT(LayoutTimer);
1088 WXLO_TIMER_PRINT(BlitTimer);
1089 WXLO_TIMER_PRINT(TmpTimer);
1090 }
1091
1092 void
1093 wxLayoutWindow::OnSize(wxSizeEvent &event)
1094 {
1095 if ( m_llist )
1096 ResizeScrollbars();
1097
1098 event.Skip();
1099 }
1100
1101 /*
1102 Change the range and position of scrollbars. Has evolved into a
1103 generic Update function which will at some time later cause a repaint
1104 as needed.
1105 */
1106
1107 void
1108 wxLayoutWindow::ResizeScrollbars(bool exact)
1109 {
1110 wxClientDC dc( this );
1111 PrepareDC( dc );
1112 // m_llist->ForceTotalLayout();
1113
1114 if(! IsDirty())
1115 {
1116 // we are laying out just the minimum, but always up to the
1117 // cursor line, so the cursor position is updated.
1118 m_llist->Layout(dc, 0);
1119 return;
1120 }
1121
1122 WXLO_TIMER_START(LayoutTimer);
1123 m_llist->Layout(dc, -1);
1124 WXLO_TIMER_STOP(LayoutTimer);
1125 ResetDirty();
1126
1127 wxPoint max = m_llist->GetSize();
1128 wxSize size = GetClientSize();
1129
1130 WXLO_DEBUG(("ResizeScrollbars: max size = (%ld, %ld)",
1131 (long int)max.x, (long int) max.y));
1132
1133 // in the absence of scrollbars we should compare with the client size
1134 if ( !m_hasHScrollbar )
1135 m_maxx = size.x;// - WXLO_ROFFSET;
1136
1137 if ( !m_hasVScrollbar )
1138 m_maxy = size.y;// - WXLO_BOFFSET;
1139
1140 // check if the text hasn't become too big
1141 // TODO why do we set both at once? they're independent...
1142 if( max.x > m_maxx - WXLO_ROFFSET
1143 || max.y > m_maxy - WXLO_BOFFSET
1144 || (max.x < m_maxx - X_SCROLL_PAGE)
1145 || (max.y < m_maxy - Y_SCROLL_PAGE)
1146 || exact )
1147 {
1148 // text became too large
1149 if ( !exact )
1150 {
1151 // add an extra bit to the sizes to avoid future updates
1152 max.x += WXLO_ROFFSET;
1153 max.y += WXLO_BOFFSET;
1154 }
1155
1156 bool done = false;
1157
1158 if(max.x < X_SCROLL_PAGE && m_hasHScrollbar)
1159 {
1160 SetScrollbars(0,-1,0,-1,0,-1,true);
1161 m_hasHScrollbar = false;
1162 done = true;
1163 }
1164
1165 if(max.y < Y_SCROLL_PAGE && m_hasVScrollbar)
1166 {
1167 SetScrollbars(-1,0,-1,0,-1,0,true);
1168 m_hasVScrollbar = false;
1169 done = true;
1170 }
1171
1172 if (! done &&
1173 // (max.x > X_SCROLL_PAGE || max.y > Y_SCROLL_PAGE)
1174 (max.x > size.x - X_SCROLL_PAGE|| max.y > size.y - Y_SCROLL_PAGE) )
1175 {
1176 GetViewStart(&m_ViewStartX, &m_ViewStartY);
1177
1178 SetScrollbars(X_SCROLL_PAGE,
1179 Y_SCROLL_PAGE,
1180 max.x / X_SCROLL_PAGE + 2,
1181 max.y / Y_SCROLL_PAGE + 2,
1182 m_ViewStartX,
1183 m_ViewStartY,
1184 true);
1185
1186 m_hasHScrollbar =
1187 m_hasVScrollbar = true;
1188 // ScrollToCursor();
1189 }
1190
1191 m_maxx = max.x + X_SCROLL_PAGE;
1192 m_maxy = max.y + Y_SCROLL_PAGE;
1193 }
1194 }
1195
1196 // ----------------------------------------------------------------------------
1197 //
1198 // clipboard operations
1199 //
1200 // ----------------------------------------------------------------------------
1201
1202 void
1203 wxLayoutWindow::Paste(bool usePrivate, bool primary)
1204 {
1205 // this only has an effect under X11:
1206 wxTheClipboard->UsePrimarySelection(primary);
1207 // Read some text
1208 if (wxTheClipboard->Open())
1209 {
1210 if(usePrivate)
1211 {
1212 wxLayoutDataObject wxldo;
1213 if (wxTheClipboard->IsSupported( wxldo.GetFormat() ))
1214 {
1215 if(wxTheClipboard->GetData(wxldo))
1216 {
1217 wxTheClipboard->Close();
1218 wxString str = wxldo.GetLayoutData();
1219 m_llist->Read(str);
1220 SetDirty();
1221 RequestUpdate();
1222 return;
1223 }
1224 }
1225 }
1226
1227 wxTextDataObject data;
1228 if (wxTheClipboard->IsSupported( data.GetFormat() )
1229 && wxTheClipboard->GetData(data) )
1230 {
1231 wxTheClipboard->Close();
1232 wxString text = data.GetText();
1233 wxLayoutImportText( m_llist, text);
1234 SetDirty();
1235 RequestUpdate();
1236 return;
1237 }
1238 }
1239 // if everything failed we can still try the primary:
1240 wxTheClipboard->Close();
1241 if(! primary) // not tried before
1242 {
1243 wxTheClipboard->UsePrimarySelection();
1244 if (wxTheClipboard->Open())
1245 {
1246 wxTextDataObject data;
1247 if (wxTheClipboard->IsSupported( data.GetFormat() )
1248 && wxTheClipboard->GetData(data) )
1249 {
1250 wxString text = data.GetText();
1251 wxLayoutImportText( m_llist, text);
1252 SetDirty();
1253 RequestUpdate();
1254 }
1255 wxTheClipboard->Close();
1256 }
1257 }
1258 }
1259
1260 bool
1261 wxLayoutWindow::Copy(bool invalidate, bool privateFormat, bool primary)
1262 {
1263 // Calling GetSelection() will automatically do an EndSelection()
1264 // on the list, but we need to take a note of it, too:
1265 if(m_Selecting)
1266 {
1267 m_Selecting = false;
1268 m_llist->EndSelection();
1269 }
1270
1271 wxLayoutDataObject *wldo = new wxLayoutDataObject;
1272 wxLayoutList *llist = m_llist->GetSelection(wldo, invalidate);
1273 if(! llist)
1274 return false;
1275 // Export selection as text:
1276 wxString text;
1277 wxLayoutExportObject *exp;
1278 wxLayoutExportStatus status(llist);
1279 while((exp = wxLayoutExport( &status, WXLO_EXPORT_AS_TEXT)) != NULL)
1280 {
1281 if(exp->type == WXLO_EXPORT_TEXT)
1282 text << *(exp->content.text);
1283
1284 delete exp;
1285 }
1286
1287 delete llist;
1288
1289 // The exporter always appends a newline, so we chop it off if it
1290 // is there:
1291 {
1292 size_t len = text.Length();
1293 if(len > 2 && text[len-2] == '\r') // Windows
1294 text = text.Mid(0,len-2);
1295 else if(len > 1 && text[len-1] == '\n')
1296 text = text.Mid(0,len-1);
1297 }
1298
1299 #if 0
1300 if(! primary) // always copy as text-only to primary selection
1301 {
1302 wxTheClipboard->UsePrimarySelection();
1303 if (wxTheClipboard->Open())
1304 {
1305 wxTextDataObject *data = new wxTextDataObject( text );
1306 wxTheClipboard->SetData( data );
1307 wxTheClipboard->Close();
1308 }
1309 }
1310 #endif
1311
1312 wxTheClipboard->UsePrimarySelection(primary);
1313 if (wxTheClipboard->Open())
1314 {
1315 wxTextDataObject *data = new wxTextDataObject( text );
1316 bool rc = wxTheClipboard->SetData( data );
1317
1318 if(privateFormat)
1319 rc |= wxTheClipboard->SetData( wldo );
1320
1321 wxTheClipboard->Close();
1322 return rc;
1323 }
1324 else
1325 {
1326 delete wldo;
1327 }
1328
1329 return false;
1330 }
1331
1332 bool
1333 wxLayoutWindow::Cut(bool privateFormat, bool usePrimary)
1334 {
1335 if(Copy(false, privateFormat, usePrimary)) // do not invalidate selection after copy
1336 {
1337 m_llist->DeleteSelection();
1338 SetDirty();
1339 return true;
1340 }
1341 else
1342 {
1343 return false;
1344 }
1345 }
1346
1347 // ----------------------------------------------------------------------------
1348 // searching
1349 // ----------------------------------------------------------------------------
1350
1351 bool
1352 wxLayoutWindow::Find(
1353 #ifdef M_BASEDIR
1354 const wxString &needle,
1355 wxPoint * fromWhere,
1356 const wxString &configPath
1357 #else
1358 const wxString & WXUNUSED(needle),
1359 wxPoint * WXUNUSED(fromWhere),
1360 const wxString & WXUNUSED(configPath)
1361 #endif
1362 )
1363 {
1364 #ifdef M_BASEDIR
1365 wxPoint found;
1366
1367 if(needle.Length() == 0)
1368 {
1369 if( ! MInputBox(&m_FindString,
1370 _("Find text"),
1371 _(" Find:"),
1372 this,
1373 configPath, "")
1374 || strutil_isempty(m_FindString))
1375 {
1376 return true;
1377 }
1378 }
1379 else
1380 {
1381 m_FindString = needle;
1382 }
1383
1384 if(fromWhere == NULL)
1385 found = m_llist->FindText(m_FindString, m_llist->GetCursorPos());
1386 else
1387 found = m_llist->FindText(m_FindString, *fromWhere);
1388
1389 if(found.x != -1)
1390 {
1391 if(fromWhere)
1392 {
1393 *fromWhere = found;
1394 fromWhere->x ++;
1395 }
1396
1397 m_llist->MoveCursorTo(found);
1398 ScrollToCursor();
1399 RequestUpdate();
1400
1401 return true;
1402 }
1403 #endif
1404
1405 return false;
1406 }
1407
1408
1409 bool
1410 wxLayoutWindow::FindAgain()
1411 {
1412 bool rc = Find(m_FindString);
1413 return rc;
1414 }
1415
1416 // ----------------------------------------------------------------------------
1417 // popup menu stuff
1418 // ----------------------------------------------------------------------------
1419
1420 wxMenu *
1421 wxLayoutWindow::MakeFormatMenu()
1422 {
1423 wxMenu *m = new wxMenu(_("Layout Menu"));
1424
1425 m->Append(WXLOWIN_MENU_LARGER ,_("&Larger"),_("Switch to larger font."));
1426 m->Append(WXLOWIN_MENU_SMALLER ,_("&Smaller"),_("Switch to smaller font."));
1427 m->AppendSeparator();
1428 m->Append(WXLOWIN_MENU_UNDERLINE, _("&Underline"),_("Underline mode."), wxITEM_CHECK);
1429 m->Append(WXLOWIN_MENU_BOLD, _("&Bold"),_("Bold mode."), wxITEM_CHECK);
1430 m->Append(WXLOWIN_MENU_ITALICS, _("&Italics"),_("Italics mode."), wxITEM_CHECK);
1431 m->AppendSeparator();
1432 m->Append(WXLOWIN_MENU_ROMAN ,_("&Roman"),_("Switch to roman font."));
1433 m->Append(WXLOWIN_MENU_TYPEWRITER,_("&Typewriter"),_("Switch to typewriter font."));
1434 m->Append(WXLOWIN_MENU_SANSSERIF ,_("&Sans Serif"),_("Switch to sans serif font."));
1435
1436 return m;
1437 }
1438
1439 void wxLayoutWindow::OnUpdateMenuUnderline(wxUpdateUIEvent& event)
1440 {
1441 event.Check(m_llist->IsFontUnderlined());
1442 }
1443
1444 void wxLayoutWindow::OnUpdateMenuBold(wxUpdateUIEvent& event)
1445 {
1446 event.Check(m_llist->IsFontBold());
1447 }
1448
1449 void wxLayoutWindow::OnUpdateMenuItalic(wxUpdateUIEvent& event)
1450 {
1451 event.Check(m_llist->IsFontItalic());
1452 }
1453
1454 void wxLayoutWindow::OnMenu(wxCommandEvent& event)
1455 {
1456 switch (event.GetId())
1457 {
1458 case WXLOWIN_MENU_LARGER:
1459 m_llist->SetFontLarger(); RequestUpdate(); break;
1460
1461 case WXLOWIN_MENU_SMALLER:
1462 m_llist->SetFontSmaller(); RequestUpdate(); break;
1463
1464 case WXLOWIN_MENU_UNDERLINE:
1465 m_llist->ToggleFontUnderline(); RequestUpdate(); break;
1466
1467 case WXLOWIN_MENU_BOLD:
1468 m_llist->ToggleFontWeight(); RequestUpdate(); break;
1469
1470 case WXLOWIN_MENU_ITALICS:
1471 m_llist->ToggleFontItalics(); RequestUpdate(); break;
1472
1473 case WXLOWIN_MENU_ROMAN:
1474 m_llist->SetFontFamily(wxROMAN); RequestUpdate(); break;
1475
1476 case WXLOWIN_MENU_TYPEWRITER:
1477 m_llist->SetFontFamily(wxFIXED); RequestUpdate(); break;
1478
1479 case WXLOWIN_MENU_SANSSERIF:
1480 m_llist->SetFontFamily(wxSWISS); RequestUpdate(); break;
1481 }
1482 }
1483
1484 // ----------------------------------------------------------------------------
1485 // focus
1486 // ----------------------------------------------------------------------------
1487
1488 void
1489 wxLayoutWindow::OnSetFocus(wxFocusEvent &ev)
1490 {
1491 m_HaveFocus = true;
1492 ev.Skip();
1493 RequestUpdate(); // cursor must change
1494 }
1495
1496 void
1497 wxLayoutWindow::OnKillFocus(wxFocusEvent &ev)
1498 {
1499 m_HaveFocus = false;
1500 ev.Skip();
1501 RequestUpdate();// cursor must change
1502 }
1503
1504 // ----------------------------------------------------------------------------
1505 // private functions
1506 // ----------------------------------------------------------------------------
1507
1508 static bool IsDirectionKey(long keyCode)
1509 {
1510 switch(keyCode)
1511 {
1512 case WXK_UP:
1513 case WXK_DOWN:
1514 case WXK_RIGHT:
1515 case WXK_LEFT:
1516 case WXK_PRIOR:
1517 case WXK_NEXT:
1518 case WXK_HOME:
1519 case WXK_END:
1520 return true;
1521
1522 default:
1523 return false;
1524 }
1525 }
1526