added line selection
[wxWidgets.git] / src / html / htmlwin.cpp
1 /////////////////////////////////////////////////////////////////////////////
2 // Name: htmlwin.cpp
3 // Purpose: wxHtmlWindow class for parsing & displaying HTML (implementation)
4 // Author: Vaclav Slavik
5 // RCS-ID: $Id$
6 // Copyright: (c) 1999 Vaclav Slavik
7 // Licence: wxWindows Licence
8 /////////////////////////////////////////////////////////////////////////////
9
10
11 #ifdef __GNUG__
12 #pragma implementation "htmlwin.h"
13 #pragma implementation "htmlproc.h"
14 #endif
15
16 #include "wx/wxprec.h"
17
18 #include "wx/defs.h"
19 #if wxUSE_HTML && wxUSE_STREAMS
20
21 #ifdef __BORLANDC__
22 #pragma hdrstop
23 #endif
24
25 #ifndef WXPRECOMP
26 #include "wx/log.h"
27 #include "wx/intl.h"
28 #include "wx/dcclient.h"
29 #include "wx/frame.h"
30 #endif
31
32 #include "wx/html/htmlwin.h"
33 #include "wx/html/htmlproc.h"
34 #include "wx/list.h"
35 #include "wx/clipbrd.h"
36 #include "wx/dataobj.h"
37 #include "wx/timer.h"
38 #include "wx/dcmemory.h"
39
40 #include "wx/arrimpl.cpp"
41 #include "wx/listimpl.cpp"
42
43
44
45 #if wxUSE_CLIPBOARD
46 // ----------------------------------------------------------------------------
47 // wxHtmlWinAutoScrollTimer: the timer used to generate a stream of scroll
48 // events when a captured mouse is held outside the window
49 // ----------------------------------------------------------------------------
50
51 class wxHtmlWinAutoScrollTimer : public wxTimer
52 {
53 public:
54 wxHtmlWinAutoScrollTimer(wxScrolledWindow *win,
55 wxEventType eventTypeToSend,
56 int pos, int orient)
57 {
58 m_win = win;
59 m_eventType = eventTypeToSend;
60 m_pos = pos;
61 m_orient = orient;
62 }
63
64 virtual void Notify();
65
66 private:
67 wxScrolledWindow *m_win;
68 wxEventType m_eventType;
69 int m_pos,
70 m_orient;
71
72 DECLARE_NO_COPY_CLASS(wxHtmlWinAutoScrollTimer)
73 };
74
75 void wxHtmlWinAutoScrollTimer::Notify()
76 {
77 // only do all this as long as the window is capturing the mouse
78 if ( wxWindow::GetCapture() != m_win )
79 {
80 Stop();
81 }
82 else // we still capture the mouse, continue generating events
83 {
84 // first scroll the window if we are allowed to do it
85 wxScrollWinEvent event1(m_eventType, m_pos, m_orient);
86 event1.SetEventObject(m_win);
87 if ( m_win->GetEventHandler()->ProcessEvent(event1) )
88 {
89 // and then send a pseudo mouse-move event to refresh the selection
90 wxMouseEvent event2(wxEVT_MOTION);
91 wxGetMousePosition(&event2.m_x, &event2.m_y);
92
93 // the mouse event coordinates should be client, not screen as
94 // returned by wxGetMousePosition
95 wxWindow *parentTop = m_win;
96 while ( parentTop->GetParent() )
97 parentTop = parentTop->GetParent();
98 wxPoint ptOrig = parentTop->GetPosition();
99 event2.m_x -= ptOrig.x;
100 event2.m_y -= ptOrig.y;
101
102 event2.SetEventObject(m_win);
103
104 // FIXME: we don't fill in the other members - ok?
105 m_win->GetEventHandler()->ProcessEvent(event2);
106 }
107 else // can't scroll further, stop
108 {
109 Stop();
110 }
111 }
112 }
113 #endif
114
115
116
117 //-----------------------------------------------------------------------------
118 // wxHtmlHistoryItem
119 //-----------------------------------------------------------------------------
120
121 // item of history list
122 class WXDLLEXPORT wxHtmlHistoryItem
123 {
124 public:
125 wxHtmlHistoryItem(const wxString& p, const wxString& a) {m_Page = p, m_Anchor = a, m_Pos = 0;}
126 int GetPos() const {return m_Pos;}
127 void SetPos(int p) {m_Pos = p;}
128 const wxString& GetPage() const {return m_Page;}
129 const wxString& GetAnchor() const {return m_Anchor;}
130
131 private:
132 wxString m_Page;
133 wxString m_Anchor;
134 int m_Pos;
135 };
136
137
138 //-----------------------------------------------------------------------------
139 // our private arrays:
140 //-----------------------------------------------------------------------------
141
142 WX_DECLARE_OBJARRAY(wxHtmlHistoryItem, wxHtmlHistoryArray);
143 WX_DEFINE_OBJARRAY(wxHtmlHistoryArray);
144
145 WX_DECLARE_LIST(wxHtmlProcessor, wxHtmlProcessorList);
146 WX_DEFINE_LIST(wxHtmlProcessorList);
147
148 //-----------------------------------------------------------------------------
149 // wxHtmlWindow
150 //-----------------------------------------------------------------------------
151
152
153 void wxHtmlWindow::Init()
154 {
155 m_tmpMouseMoved = FALSE;
156 m_tmpLastLink = NULL;
157 m_tmpLastCell = NULL;
158 m_tmpCanDrawLocks = 0;
159 m_FS = new wxFileSystem();
160 m_RelatedStatusBar = -1;
161 m_RelatedFrame = NULL;
162 m_TitleFormat = wxT("%s");
163 m_OpenedPage = m_OpenedAnchor = m_OpenedPageTitle = wxEmptyString;
164 m_Cell = NULL;
165 m_Parser = new wxHtmlWinParser(this);
166 m_Parser->SetFS(m_FS);
167 m_HistoryPos = -1;
168 m_HistoryOn = TRUE;
169 m_History = new wxHtmlHistoryArray;
170 m_Processors = NULL;
171 m_Style = 0;
172 SetBorders(10);
173 m_selection = NULL;
174 m_makingSelection = false;
175 #if wxUSE_CLIPBOARD
176 m_timerAutoScroll = NULL;
177 #endif
178 m_backBuffer = NULL;
179 m_lastDoubleClick = 0;
180 }
181
182 bool wxHtmlWindow::Create(wxWindow *parent, wxWindowID id,
183 const wxPoint& pos, const wxSize& size,
184 long style, const wxString& name)
185 {
186 if (!wxScrolledWindow::Create(parent, id, pos, size,
187 style | wxVSCROLL | wxHSCROLL, name))
188 return FALSE;
189
190 m_Style = style;
191 SetPage(wxT("<html><body></body></html>"));
192 return TRUE;
193 }
194
195
196 wxHtmlWindow::~wxHtmlWindow()
197 {
198 #if wxUSE_CLIPBOARD
199 StopAutoScrolling();
200 #endif
201 HistoryClear();
202
203 if (m_Cell) delete m_Cell;
204
205 delete m_Parser;
206 delete m_FS;
207 delete m_History;
208 delete m_Processors;
209 delete m_backBuffer;
210 }
211
212
213
214 void wxHtmlWindow::SetRelatedFrame(wxFrame* frame, const wxString& format)
215 {
216 m_RelatedFrame = frame;
217 m_TitleFormat = format;
218 }
219
220
221
222 void wxHtmlWindow::SetRelatedStatusBar(int bar)
223 {
224 m_RelatedStatusBar = bar;
225 }
226
227
228
229 void wxHtmlWindow::SetFonts(wxString normal_face, wxString fixed_face, const int *sizes)
230 {
231 wxString op = m_OpenedPage;
232
233 m_Parser->SetFonts(normal_face, fixed_face, sizes);
234 // fonts changed => contents invalid, so reload the page:
235 SetPage(wxT("<html><body></body></html>"));
236 if (!op.IsEmpty()) LoadPage(op);
237 }
238
239
240
241 bool wxHtmlWindow::SetPage(const wxString& source)
242 {
243 wxString newsrc(source);
244
245 wxDELETE(m_selection);
246
247 // pass HTML through registered processors:
248 if (m_Processors || m_GlobalProcessors)
249 {
250 wxHtmlProcessorList::Node *nodeL, *nodeG;
251 int prL, prG;
252
253 nodeL = (m_Processors) ? m_Processors->GetFirst() : NULL;
254 nodeG = (m_GlobalProcessors) ? m_GlobalProcessors->GetFirst() : NULL;
255
256 // VS: there are two lists, global and local, both of them sorted by
257 // priority. Since we have to go through _both_ lists with
258 // decreasing priority, we "merge-sort" the lists on-line by
259 // processing that one of the two heads that has higher priority
260 // in every iteration
261 while (nodeL || nodeG)
262 {
263 prL = (nodeL) ? nodeL->GetData()->GetPriority() : -1;
264 prG = (nodeG) ? nodeG->GetData()->GetPriority() : -1;
265 if (prL > prG)
266 {
267 if (nodeL->GetData()->IsEnabled())
268 newsrc = nodeL->GetData()->Process(newsrc);
269 nodeL = nodeL->GetNext();
270 }
271 else // prL <= prG
272 {
273 if (nodeG->GetData()->IsEnabled())
274 newsrc = nodeG->GetData()->Process(newsrc);
275 nodeG = nodeG->GetNext();
276 }
277 }
278 }
279
280 // ...and run the parser on it:
281 wxClientDC *dc = new wxClientDC(this);
282 dc->SetMapMode(wxMM_TEXT);
283 SetBackgroundColour(wxColour(0xFF, 0xFF, 0xFF));
284 m_OpenedPage = m_OpenedAnchor = m_OpenedPageTitle = wxEmptyString;
285 m_Parser->SetDC(dc);
286 if (m_Cell)
287 {
288 delete m_Cell;
289 m_Cell = NULL;
290 }
291 m_Cell = (wxHtmlContainerCell*) m_Parser->Parse(newsrc);
292 delete dc;
293 m_Cell->SetIndent(m_Borders, wxHTML_INDENT_ALL, wxHTML_UNITS_PIXELS);
294 m_Cell->SetAlignHor(wxHTML_ALIGN_CENTER);
295 CreateLayout();
296 if (m_tmpCanDrawLocks == 0)
297 Refresh();
298 return TRUE;
299 }
300
301 bool wxHtmlWindow::AppendToPage(const wxString& source)
302 {
303 return SetPage(*(GetParser()->GetSource()) + source);
304 }
305
306 bool wxHtmlWindow::LoadPage(const wxString& location)
307 {
308 wxBusyCursor busyCursor;
309
310 wxFSFile *f;
311 bool rt_val;
312 bool needs_refresh = FALSE;
313
314 m_tmpCanDrawLocks++;
315 if (m_HistoryOn && (m_HistoryPos != -1))
316 {
317 // store scroll position into history item:
318 int x, y;
319 GetViewStart(&x, &y);
320 (*m_History)[m_HistoryPos].SetPos(y);
321 }
322
323 if (location[0] == wxT('#'))
324 {
325 // local anchor:
326 wxString anch = location.Mid(1) /*1 to end*/;
327 m_tmpCanDrawLocks--;
328 rt_val = ScrollToAnchor(anch);
329 m_tmpCanDrawLocks++;
330 }
331 else if (location.Find(wxT('#')) != wxNOT_FOUND && location.BeforeFirst(wxT('#')) == m_OpenedPage)
332 {
333 wxString anch = location.AfterFirst(wxT('#'));
334 m_tmpCanDrawLocks--;
335 rt_val = ScrollToAnchor(anch);
336 m_tmpCanDrawLocks++;
337 }
338 else if (location.Find(wxT('#')) != wxNOT_FOUND &&
339 (m_FS->GetPath() + location.BeforeFirst(wxT('#'))) == m_OpenedPage)
340 {
341 wxString anch = location.AfterFirst(wxT('#'));
342 m_tmpCanDrawLocks--;
343 rt_val = ScrollToAnchor(anch);
344 m_tmpCanDrawLocks++;
345 }
346
347 else
348 {
349 needs_refresh = TRUE;
350 // load&display it:
351 if (m_RelatedStatusBar != -1)
352 {
353 m_RelatedFrame->SetStatusText(_("Connecting..."), m_RelatedStatusBar);
354 Refresh(FALSE);
355 }
356
357 f = m_Parser->OpenURL(wxHTML_URL_PAGE, location);
358
359 // try to interpret 'location' as filename instead of URL:
360 if (f == NULL)
361 {
362 wxFileName fn(location);
363 wxString location2 = wxFileSystem::FileNameToURL(fn);
364 f = m_Parser->OpenURL(wxHTML_URL_PAGE, location2);
365 }
366
367 if (f == NULL)
368 {
369 wxLogError(_("Unable to open requested HTML document: %s"), location.c_str());
370 m_tmpCanDrawLocks--;
371 return FALSE;
372 }
373
374 else
375 {
376 wxNode *node;
377 wxString src = wxEmptyString;
378
379 if (m_RelatedStatusBar != -1)
380 {
381 wxString msg = _("Loading : ") + location;
382 m_RelatedFrame->SetStatusText(msg, m_RelatedStatusBar);
383 Refresh(FALSE);
384 }
385
386 node = m_Filters.GetFirst();
387 while (node)
388 {
389 wxHtmlFilter *h = (wxHtmlFilter*) node->GetData();
390 if (h->CanRead(*f))
391 {
392 src = h->ReadFile(*f);
393 break;
394 }
395 node = node->GetNext();
396 }
397 if (src == wxEmptyString)
398 {
399 if (m_DefaultFilter == NULL) m_DefaultFilter = GetDefaultFilter();
400 src = m_DefaultFilter->ReadFile(*f);
401 }
402
403 m_FS->ChangePathTo(f->GetLocation());
404 rt_val = SetPage(src);
405 m_OpenedPage = f->GetLocation();
406 if (f->GetAnchor() != wxEmptyString)
407 {
408 ScrollToAnchor(f->GetAnchor());
409 }
410
411 delete f;
412
413 if (m_RelatedStatusBar != -1) m_RelatedFrame->SetStatusText(_("Done"), m_RelatedStatusBar);
414 }
415 }
416
417 if (m_HistoryOn) // add this page to history there:
418 {
419 int c = m_History->GetCount() - (m_HistoryPos + 1);
420
421 if (m_HistoryPos < 0 ||
422 (*m_History)[m_HistoryPos].GetPage() != m_OpenedPage ||
423 (*m_History)[m_HistoryPos].GetAnchor() != m_OpenedAnchor)
424 {
425 m_HistoryPos++;
426 for (int i = 0; i < c; i++)
427 m_History->RemoveAt(m_HistoryPos);
428 m_History->Add(new wxHtmlHistoryItem(m_OpenedPage, m_OpenedAnchor));
429 }
430 }
431
432 if (m_OpenedPageTitle == wxEmptyString)
433 OnSetTitle(wxFileNameFromPath(m_OpenedPage));
434
435 if (needs_refresh)
436 {
437 m_tmpCanDrawLocks--;
438 Refresh();
439 }
440 else
441 m_tmpCanDrawLocks--;
442
443 return rt_val;
444 }
445
446
447 bool wxHtmlWindow::LoadFile(const wxFileName& filename)
448 {
449 wxString url = wxFileSystem::FileNameToURL(filename);
450 return LoadPage(url);
451 }
452
453
454 bool wxHtmlWindow::ScrollToAnchor(const wxString& anchor)
455 {
456 const wxHtmlCell *c = m_Cell->Find(wxHTML_COND_ISANCHOR, &anchor);
457 if (!c)
458 {
459 wxLogWarning(_("HTML anchor %s does not exist."), anchor.c_str());
460 return FALSE;
461 }
462 else
463 {
464 int y;
465
466 for (y = 0; c != NULL; c = c->GetParent()) y += c->GetPosY();
467 Scroll(-1, y / wxHTML_SCROLL_STEP);
468 m_OpenedAnchor = anchor;
469 return TRUE;
470 }
471 }
472
473
474 void wxHtmlWindow::OnSetTitle(const wxString& title)
475 {
476 if (m_RelatedFrame)
477 {
478 wxString tit;
479 tit.Printf(m_TitleFormat, title.c_str());
480 m_RelatedFrame->SetTitle(tit);
481 }
482 m_OpenedPageTitle = title;
483 }
484
485
486
487
488
489 void wxHtmlWindow::CreateLayout()
490 {
491 int ClientWidth, ClientHeight;
492
493 if (!m_Cell) return;
494
495 if (m_Style & wxHW_SCROLLBAR_NEVER)
496 {
497 SetScrollbars(wxHTML_SCROLL_STEP, 1, m_Cell->GetWidth() / wxHTML_SCROLL_STEP, 0); // always off
498 GetClientSize(&ClientWidth, &ClientHeight);
499 m_Cell->Layout(ClientWidth);
500 }
501
502 else {
503 GetClientSize(&ClientWidth, &ClientHeight);
504 m_Cell->Layout(ClientWidth);
505 if (ClientHeight < m_Cell->GetHeight() + GetCharHeight())
506 {
507 SetScrollbars(
508 wxHTML_SCROLL_STEP, wxHTML_SCROLL_STEP,
509 m_Cell->GetWidth() / wxHTML_SCROLL_STEP,
510 (m_Cell->GetHeight() + GetCharHeight()) / wxHTML_SCROLL_STEP
511 /*cheat: top-level frag is always container*/);
512 }
513 else /* we fit into window, no need for scrollbars */
514 {
515 SetScrollbars(wxHTML_SCROLL_STEP, 1, m_Cell->GetWidth() / wxHTML_SCROLL_STEP, 0); // disable...
516 GetClientSize(&ClientWidth, &ClientHeight);
517 m_Cell->Layout(ClientWidth); // ...and relayout
518 }
519 }
520 }
521
522
523
524 void wxHtmlWindow::ReadCustomization(wxConfigBase *cfg, wxString path)
525 {
526 wxString oldpath;
527 wxString tmp;
528 int p_fontsizes[7];
529 wxString p_fff, p_ffn;
530
531 if (path != wxEmptyString)
532 {
533 oldpath = cfg->GetPath();
534 cfg->SetPath(path);
535 }
536
537 m_Borders = cfg->Read(wxT("wxHtmlWindow/Borders"), m_Borders);
538 p_fff = cfg->Read(wxT("wxHtmlWindow/FontFaceFixed"), m_Parser->m_FontFaceFixed);
539 p_ffn = cfg->Read(wxT("wxHtmlWindow/FontFaceNormal"), m_Parser->m_FontFaceNormal);
540 for (int i = 0; i < 7; i++)
541 {
542 tmp.Printf(wxT("wxHtmlWindow/FontsSize%i"), i);
543 p_fontsizes[i] = cfg->Read(tmp, m_Parser->m_FontsSizes[i]);
544 }
545 SetFonts(p_ffn, p_fff, p_fontsizes);
546
547 if (path != wxEmptyString)
548 cfg->SetPath(oldpath);
549 }
550
551
552
553 void wxHtmlWindow::WriteCustomization(wxConfigBase *cfg, wxString path)
554 {
555 wxString oldpath;
556 wxString tmp;
557
558 if (path != wxEmptyString)
559 {
560 oldpath = cfg->GetPath();
561 cfg->SetPath(path);
562 }
563
564 cfg->Write(wxT("wxHtmlWindow/Borders"), (long) m_Borders);
565 cfg->Write(wxT("wxHtmlWindow/FontFaceFixed"), m_Parser->m_FontFaceFixed);
566 cfg->Write(wxT("wxHtmlWindow/FontFaceNormal"), m_Parser->m_FontFaceNormal);
567 for (int i = 0; i < 7; i++)
568 {
569 tmp.Printf(wxT("wxHtmlWindow/FontsSize%i"), i);
570 cfg->Write(tmp, (long) m_Parser->m_FontsSizes[i]);
571 }
572
573 if (path != wxEmptyString)
574 cfg->SetPath(oldpath);
575 }
576
577
578
579 bool wxHtmlWindow::HistoryBack()
580 {
581 wxString a, l;
582
583 if (m_HistoryPos < 1) return FALSE;
584
585 // store scroll position into history item:
586 int x, y;
587 GetViewStart(&x, &y);
588 (*m_History)[m_HistoryPos].SetPos(y);
589
590 // go to previous position:
591 m_HistoryPos--;
592
593 l = (*m_History)[m_HistoryPos].GetPage();
594 a = (*m_History)[m_HistoryPos].GetAnchor();
595 m_HistoryOn = FALSE;
596 m_tmpCanDrawLocks++;
597 if (a == wxEmptyString) LoadPage(l);
598 else LoadPage(l + wxT("#") + a);
599 m_HistoryOn = TRUE;
600 m_tmpCanDrawLocks--;
601 Scroll(0, (*m_History)[m_HistoryPos].GetPos());
602 Refresh();
603 return TRUE;
604 }
605
606 bool wxHtmlWindow::HistoryCanBack()
607 {
608 if (m_HistoryPos < 1) return FALSE;
609 return TRUE ;
610 }
611
612
613 bool wxHtmlWindow::HistoryForward()
614 {
615 wxString a, l;
616
617 if (m_HistoryPos == -1) return FALSE;
618 if (m_HistoryPos >= (int)m_History->GetCount() - 1)return FALSE;
619
620 m_OpenedPage = wxEmptyString; // this will disable adding new entry into history in LoadPage()
621
622 m_HistoryPos++;
623 l = (*m_History)[m_HistoryPos].GetPage();
624 a = (*m_History)[m_HistoryPos].GetAnchor();
625 m_HistoryOn = FALSE;
626 m_tmpCanDrawLocks++;
627 if (a == wxEmptyString) LoadPage(l);
628 else LoadPage(l + wxT("#") + a);
629 m_HistoryOn = TRUE;
630 m_tmpCanDrawLocks--;
631 Scroll(0, (*m_History)[m_HistoryPos].GetPos());
632 Refresh();
633 return TRUE;
634 }
635
636 bool wxHtmlWindow::HistoryCanForward()
637 {
638 if (m_HistoryPos == -1) return FALSE;
639 if (m_HistoryPos >= (int)m_History->GetCount() - 1)return FALSE;
640 return TRUE ;
641 }
642
643
644 void wxHtmlWindow::HistoryClear()
645 {
646 m_History->Empty();
647 m_HistoryPos = -1;
648 }
649
650 void wxHtmlWindow::AddProcessor(wxHtmlProcessor *processor)
651 {
652 if (!m_Processors)
653 {
654 m_Processors = new wxHtmlProcessorList;
655 m_Processors->DeleteContents(TRUE);
656 }
657 wxHtmlProcessorList::Node *node;
658
659 for (node = m_Processors->GetFirst(); node; node = node->GetNext())
660 {
661 if (processor->GetPriority() > node->GetData()->GetPriority())
662 {
663 m_Processors->Insert(node, processor);
664 return;
665 }
666 }
667 m_Processors->Append(processor);
668 }
669
670 /*static */ void wxHtmlWindow::AddGlobalProcessor(wxHtmlProcessor *processor)
671 {
672 if (!m_GlobalProcessors)
673 {
674 m_GlobalProcessors = new wxHtmlProcessorList;
675 m_GlobalProcessors->DeleteContents(TRUE);
676 }
677 wxHtmlProcessorList::Node *node;
678
679 for (node = m_GlobalProcessors->GetFirst(); node; node = node->GetNext())
680 {
681 if (processor->GetPriority() > node->GetData()->GetPriority())
682 {
683 m_GlobalProcessors->Insert(node, processor);
684 return;
685 }
686 }
687 m_GlobalProcessors->Append(processor);
688 }
689
690
691
692 wxList wxHtmlWindow::m_Filters;
693 wxHtmlFilter *wxHtmlWindow::m_DefaultFilter = NULL;
694 wxCursor *wxHtmlWindow::s_cur_hand = NULL;
695 wxCursor *wxHtmlWindow::s_cur_arrow = NULL;
696 wxHtmlProcessorList *wxHtmlWindow::m_GlobalProcessors = NULL;
697
698 void wxHtmlWindow::CleanUpStatics()
699 {
700 wxDELETE(m_DefaultFilter);
701 m_Filters.DeleteContents(TRUE);
702 m_Filters.Clear();
703 wxDELETE(m_GlobalProcessors);
704 wxDELETE(s_cur_hand);
705 wxDELETE(s_cur_arrow);
706 }
707
708
709
710 void wxHtmlWindow::AddFilter(wxHtmlFilter *filter)
711 {
712 m_Filters.Append(filter);
713 }
714
715
716 bool wxHtmlWindow::IsSelectionEnabled() const
717 {
718 #if wxUSE_CLIPBOARD
719 return !(m_Style & wxHW_NO_SELECTION);
720 #else
721 return false;
722 #endif
723 }
724
725
726 #if wxUSE_CLIPBOARD
727 wxString wxHtmlWindow::SelectionToText()
728 {
729 if ( !m_selection )
730 return wxEmptyString;
731
732 wxClientDC dc(this);
733
734 const wxHtmlCell *end = m_selection->GetToCell();
735 wxString text;
736 wxHtmlTerminalCellsInterator i(m_selection->GetFromCell(), end);
737 if ( i )
738 {
739 text << i->ConvertToText(m_selection);
740 ++i;
741 }
742 const wxHtmlCell *prev = *i;
743 while ( i )
744 {
745 if ( prev->GetParent() != i->GetParent() )
746 text << _T('\n');
747 text << i->ConvertToText(*i == end ? m_selection : NULL);
748 prev = *i;
749 ++i;
750 }
751 return text;
752 }
753
754 void wxHtmlWindow::CopySelection(ClipboardType t)
755 {
756 if ( m_selection )
757 {
758 wxTheClipboard->UsePrimarySelection(t == Primary);
759 wxString txt(SelectionToText());
760 if ( wxTheClipboard->Open() )
761 {
762 wxTheClipboard->SetData(new wxTextDataObject(txt));
763 wxTheClipboard->Close();
764 wxLogTrace(_T("wxhtmlselection"),
765 _("Copied to clipboard:\"%s\""), txt.c_str());
766 }
767 }
768 }
769 #endif
770
771
772 void wxHtmlWindow::OnLinkClicked(const wxHtmlLinkInfo& link)
773 {
774 const wxMouseEvent *e = link.GetEvent();
775 if (e == NULL || e->LeftUp())
776 LoadPage(link.GetHref());
777 }
778
779 void wxHtmlWindow::OnCellClicked(wxHtmlCell *cell,
780 wxCoord x, wxCoord y,
781 const wxMouseEvent& event)
782 {
783 wxCHECK_RET( cell, _T("can't be called with NULL cell") );
784
785 cell->OnMouseClick(this, x, y, event);
786 }
787
788 void wxHtmlWindow::OnCellMouseHover(wxHtmlCell * WXUNUSED(cell),
789 wxCoord WXUNUSED(x), wxCoord WXUNUSED(y))
790 {
791 // do nothing here
792 }
793
794 void wxHtmlWindow::OnEraseBackground(wxEraseEvent& event)
795 {
796 }
797
798 void wxHtmlWindow::OnPaint(wxPaintEvent& WXUNUSED(event))
799 {
800 wxPaintDC dc(this);
801
802 if (m_tmpCanDrawLocks > 0 || m_Cell == NULL) return;
803
804 int x, y;
805 GetViewStart(&x, &y);
806 wxRect rect = GetUpdateRegion().GetBox();
807 wxSize sz = GetSize();
808
809 wxMemoryDC dcm;
810 if ( !m_backBuffer )
811 m_backBuffer = new wxBitmap(sz.x, sz.y);
812 dcm.SelectObject(*m_backBuffer);
813 dcm.SetBackground(wxBrush(GetBackgroundColour(), wxSOLID));
814 dcm.Clear();
815 PrepareDC(dcm);
816 dcm.SetMapMode(wxMM_TEXT);
817 dcm.SetBackgroundMode(wxTRANSPARENT);
818
819 wxHtmlRenderingInfo rinfo;
820 wxDefaultHtmlRenderingStyle rstyle;
821 rinfo.SetSelection(m_selection);
822 rinfo.SetStyle(&rstyle);
823 m_Cell->Draw(dcm, 0, 0,
824 y * wxHTML_SCROLL_STEP + rect.GetTop(),
825 y * wxHTML_SCROLL_STEP + rect.GetBottom(),
826 rinfo);
827
828 dcm.SetDeviceOrigin(0,0);
829 dc.Blit(0, rect.GetTop(),
830 sz.x, rect.GetBottom() - rect.GetTop() + 1,
831 &dcm,
832 0, rect.GetTop());
833 }
834
835
836
837
838 void wxHtmlWindow::OnSize(wxSizeEvent& event)
839 {
840 wxDELETE(m_backBuffer);
841
842 wxScrolledWindow::OnSize(event);
843 CreateLayout();
844
845 // Recompute selection if necessary:
846 if ( m_selection )
847 {
848 m_selection->Set(m_selection->GetFromCell(),
849 m_selection->GetToCell());
850 m_selection->ClearPrivPos();
851 }
852
853 Refresh();
854 }
855
856
857 void wxHtmlWindow::OnMouseMove(wxMouseEvent& event)
858 {
859 m_tmpMouseMoved = true;
860 }
861
862 void wxHtmlWindow::OnMouseDown(wxMouseEvent& event)
863 {
864 #if wxUSE_CLIPBOARD
865 if ( event.LeftDown() && IsSelectionEnabled() )
866 {
867 const long TRIPLECLICK_LEN = 200; // 0.2 sec after doubleclick
868 if ( wxGetLocalTimeMillis() - m_lastDoubleClick <= TRIPLECLICK_LEN )
869 {
870 SelectLine(CalcUnscrolledPosition(event.GetPosition()));
871 }
872 else
873 {
874 m_makingSelection = true;
875
876 if ( m_selection )
877 {
878 wxDELETE(m_selection);
879 Refresh();
880 }
881 m_tmpSelFromPos = CalcUnscrolledPosition(event.GetPosition());
882 m_tmpSelFromCell = NULL;
883
884 CaptureMouse();
885 }
886 }
887 #endif
888 }
889
890 void wxHtmlWindow::OnMouseUp(wxMouseEvent& event)
891 {
892 #if wxUSE_CLIPBOARD
893 if ( m_makingSelection )
894 {
895 ReleaseMouse();
896 m_makingSelection = false;
897
898 // did the user move the mouse far enough from starting point?
899 if ( m_selection )
900 {
901 #ifdef __UNIX__
902 CopySelection(Primary);
903 #endif
904 // we don't want mouse up event that ended selecting to be
905 // handled as mouse click and e.g. follow hyperlink:
906 return;
907 }
908 }
909 #endif
910
911 SetFocus();
912 if ( m_Cell )
913 {
914 wxPoint pos = CalcUnscrolledPosition(event.GetPosition());
915 wxHtmlCell *cell = m_Cell->FindCellByPos(pos.x, pos.y);
916
917 // VZ: is it possible that we don't find anything at all?
918 // VS: yes. FindCellByPos returns terminal cell and
919 // containers may have empty borders
920 if ( cell )
921 OnCellClicked(cell, pos.x, pos.y, event);
922 }
923 }
924
925
926
927 void wxHtmlWindow::OnIdle(wxIdleEvent& WXUNUSED(event))
928 {
929 if (s_cur_hand == NULL)
930 {
931 s_cur_hand = new wxCursor(wxCURSOR_HAND);
932 s_cur_arrow = new wxCursor(wxCURSOR_ARROW);
933 }
934
935 if (m_tmpMouseMoved && (m_Cell != NULL))
936 {
937 int xc, yc, x, y;
938 wxGetMousePosition(&xc, &yc);
939 ScreenToClient(&xc, &yc);
940 CalcUnscrolledPosition(xc, yc, &x, &y);
941
942 wxHtmlCell *cell = m_Cell->FindCellByPos(x, y);
943
944 // handle selection update:
945 if ( m_makingSelection )
946 {
947 bool goingDown = m_tmpSelFromPos.y < y ||
948 m_tmpSelFromPos.y == y && m_tmpSelFromPos.x < x;
949
950 if ( !m_tmpSelFromCell )
951 {
952 if (goingDown)
953 {
954 m_tmpSelFromCell = m_Cell->FindCellByPos(
955 m_tmpSelFromPos.x,m_tmpSelFromPos.y,
956 wxHTML_FIND_NEAREST_AFTER);
957 if (!m_tmpSelFromCell)
958 m_tmpSelFromCell = m_Cell->GetFirstTerminal();
959 }
960 else
961 {
962 m_tmpSelFromCell = m_Cell->FindCellByPos(
963 m_tmpSelFromPos.x,m_tmpSelFromPos.y,
964 wxHTML_FIND_NEAREST_BEFORE);
965 if (!m_tmpSelFromCell)
966 m_tmpSelFromCell = m_Cell->GetLastTerminal();
967 }
968 }
969
970 wxHtmlCell *selcell = cell;
971 if (!selcell)
972 {
973 if (goingDown)
974 {
975 selcell = m_Cell->FindCellByPos(x, y,
976 wxHTML_FIND_NEAREST_AFTER);
977 if (!selcell)
978 selcell = m_Cell->GetLastTerminal();
979 }
980 else
981 {
982 selcell = m_Cell->FindCellByPos(x, y,
983 wxHTML_FIND_NEAREST_BEFORE);
984 if (!selcell)
985 selcell = m_Cell->GetFirstTerminal();
986 }
987 }
988
989 // NB: it may *rarely* happen that the code above didn't find one
990 // of the cells, e.g. if wxHtmlWindow doesn't contain any
991 // visible cells.
992 if ( selcell && m_tmpSelFromCell )
993 {
994 if ( !m_selection )
995 {
996 // start selecting only if mouse movement was big enough
997 // (otherwise it was meant as mouse click, not selection):
998 const int PRECISION = 2;
999 wxPoint diff = m_tmpSelFromPos - wxPoint(x,y);
1000 if (abs(diff.x) > PRECISION || abs(diff.y) > PRECISION)
1001 {
1002 m_selection = new wxHtmlSelection();
1003 }
1004 }
1005 if ( m_selection )
1006 {
1007 if ( m_tmpSelFromCell->IsBefore(selcell) )
1008 {
1009 m_selection->Set(m_tmpSelFromPos, m_tmpSelFromCell,
1010 wxPoint(x,y), selcell); }
1011 else
1012 {
1013 m_selection->Set(wxPoint(x,y), selcell,
1014 m_tmpSelFromPos, m_tmpSelFromCell);
1015 }
1016 m_selection->ClearPrivPos();
1017 Refresh();
1018 }
1019 }
1020 }
1021
1022 // handle cursor and status bar text changes:
1023 if ( cell != m_tmpLastCell )
1024 {
1025 wxHtmlLinkInfo *lnk = cell ? cell->GetLink(x, y) : NULL;
1026
1027 if (lnk != m_tmpLastLink)
1028 {
1029 if (lnk == NULL)
1030 {
1031 SetCursor(*s_cur_arrow);
1032 if (m_RelatedStatusBar != -1)
1033 m_RelatedFrame->SetStatusText(wxEmptyString,
1034 m_RelatedStatusBar);
1035 }
1036 else
1037 {
1038 SetCursor(*s_cur_hand);
1039 if (m_RelatedStatusBar != -1)
1040 m_RelatedFrame->SetStatusText(lnk->GetHref(),
1041 m_RelatedStatusBar);
1042 }
1043 m_tmpLastLink = lnk;
1044 }
1045
1046 m_tmpLastCell = cell;
1047 }
1048 else // mouse moved but stayed in the same cell
1049 {
1050 if ( cell )
1051 OnCellMouseHover(cell, x, y);
1052 }
1053
1054 m_tmpMouseMoved = FALSE;
1055 }
1056 }
1057
1058 #if wxUSE_CLIPBOARD
1059 void wxHtmlWindow::StopAutoScrolling()
1060 {
1061 if ( m_timerAutoScroll )
1062 {
1063 wxDELETE(m_timerAutoScroll);
1064 }
1065 }
1066
1067 void wxHtmlWindow::OnMouseEnter(wxMouseEvent& event)
1068 {
1069 StopAutoScrolling();
1070 event.Skip();
1071 }
1072
1073 void wxHtmlWindow::OnMouseLeave(wxMouseEvent& event)
1074 {
1075 // don't prevent the usual processing of the event from taking place
1076 event.Skip();
1077
1078 // when a captured mouse leave a scrolled window we start generate
1079 // scrolling events to allow, for example, extending selection beyond the
1080 // visible area in some controls
1081 if ( wxWindow::GetCapture() == this )
1082 {
1083 // where is the mouse leaving?
1084 int pos, orient;
1085 wxPoint pt = event.GetPosition();
1086 if ( pt.x < 0 )
1087 {
1088 orient = wxHORIZONTAL;
1089 pos = 0;
1090 }
1091 else if ( pt.y < 0 )
1092 {
1093 orient = wxVERTICAL;
1094 pos = 0;
1095 }
1096 else // we're lower or to the right of the window
1097 {
1098 wxSize size = GetClientSize();
1099 if ( pt.x > size.x )
1100 {
1101 orient = wxHORIZONTAL;
1102 pos = GetVirtualSize().x / wxHTML_SCROLL_STEP;
1103 }
1104 else if ( pt.y > size.y )
1105 {
1106 orient = wxVERTICAL;
1107 pos = GetVirtualSize().y / wxHTML_SCROLL_STEP;
1108 }
1109 else // this should be impossible
1110 {
1111 // but seems to happen sometimes under wxMSW - maybe it's a bug
1112 // there but for now just ignore it
1113
1114 //wxFAIL_MSG( _T("can't understand where has mouse gone") );
1115
1116 return;
1117 }
1118 }
1119
1120 // only start the auto scroll timer if the window can be scrolled in
1121 // this direction
1122 if ( !HasScrollbar(orient) )
1123 return;
1124
1125 delete m_timerAutoScroll;
1126 m_timerAutoScroll = new wxHtmlWinAutoScrollTimer
1127 (
1128 this,
1129 pos == 0 ? wxEVT_SCROLLWIN_LINEUP
1130 : wxEVT_SCROLLWIN_LINEDOWN,
1131 pos,
1132 orient
1133 );
1134 m_timerAutoScroll->Start(50); // FIXME: make configurable
1135 }
1136 }
1137
1138 void wxHtmlWindow::OnKeyUp(wxKeyEvent& event)
1139 {
1140 if ( IsSelectionEnabled() &&
1141 event.GetKeyCode() == 'C' && event.ControlDown() )
1142 {
1143 if ( m_selection )
1144 CopySelection();
1145 }
1146 }
1147
1148 void wxHtmlWindow::OnCopy(wxCommandEvent& event)
1149 {
1150 if ( m_selection )
1151 CopySelection();
1152 }
1153
1154 void wxHtmlWindow::OnDoubleClick(wxMouseEvent& event)
1155 {
1156 // select word under cursor:
1157 if ( IsSelectionEnabled() )
1158 {
1159 SelectWord(CalcUnscrolledPosition(event.GetPosition()));
1160 m_lastDoubleClick = wxGetLocalTimeMillis();
1161 }
1162 else
1163 event.Skip();
1164 }
1165
1166 void wxHtmlWindow::SelectWord(const wxPoint& pos)
1167 {
1168 wxHtmlCell *cell = m_Cell->FindCellByPos(pos.x, pos.y);
1169 if ( cell )
1170 {
1171 delete m_selection;
1172 m_selection = new wxHtmlSelection();
1173 m_selection->Set(cell, cell);
1174 RefreshRect(wxRect(CalcScrolledPosition(cell->GetAbsPos()),
1175 wxSize(cell->GetWidth(), cell->GetHeight())));
1176 }
1177 }
1178
1179 void wxHtmlWindow::SelectLine(const wxPoint& pos)
1180 {
1181 wxHtmlCell *cell = m_Cell->FindCellByPos(pos.x, pos.y);
1182 if ( cell )
1183 {
1184 // We use following heuristic to find a "line": let the line be all
1185 // cells in same container as the cell under mouse cursor that are
1186 // neither completely above nor completely bellow the clicked cell
1187 // (i.e. are likely to be words positioned on same line of text).
1188
1189 int y1 = cell->GetAbsPos().y;
1190 int y2 = y1 + cell->GetHeight();
1191 int y;
1192 const wxHtmlCell *c;
1193 const wxHtmlCell *before = NULL;
1194 const wxHtmlCell *after = NULL;
1195
1196 // find last cell of line:
1197 for ( c = cell->GetNext(); c; c = c->GetNext())
1198 {
1199 y = c->GetAbsPos().y;
1200 if ( y + c->GetHeight() > y1 && y < y2 )
1201 after = c;
1202 else
1203 break;
1204 }
1205 if ( !after )
1206 after = cell;
1207
1208 // find first cell of line:
1209 for ( c = cell->GetParent()->GetFirstChild();
1210 c && c != cell; c = c->GetNext())
1211 {
1212 y = c->GetAbsPos().y;
1213 if ( y + c->GetHeight() > y1 && y < y2 )
1214 {
1215 if ( ! before )
1216 before = c;
1217 }
1218 else
1219 before = NULL;
1220 }
1221 if ( !before )
1222 before = cell;
1223
1224 delete m_selection;
1225 m_selection = new wxHtmlSelection();
1226 m_selection->Set(before, after);
1227
1228 Refresh();
1229 }
1230 }
1231 #endif
1232
1233
1234
1235 IMPLEMENT_ABSTRACT_CLASS(wxHtmlProcessor,wxObject)
1236
1237 IMPLEMENT_DYNAMIC_CLASS(wxHtmlWindow,wxScrolledWindow)
1238
1239 BEGIN_EVENT_TABLE(wxHtmlWindow, wxScrolledWindow)
1240 EVT_SIZE(wxHtmlWindow::OnSize)
1241 EVT_LEFT_DOWN(wxHtmlWindow::OnMouseDown)
1242 EVT_LEFT_UP(wxHtmlWindow::OnMouseUp)
1243 EVT_RIGHT_UP(wxHtmlWindow::OnMouseUp)
1244 EVT_MOTION(wxHtmlWindow::OnMouseMove)
1245 EVT_IDLE(wxHtmlWindow::OnIdle)
1246 EVT_ERASE_BACKGROUND(wxHtmlWindow::OnEraseBackground)
1247 EVT_PAINT(wxHtmlWindow::OnPaint)
1248 #if wxUSE_CLIPBOARD
1249 EVT_LEFT_DCLICK(wxHtmlWindow::OnDoubleClick)
1250 EVT_ENTER_WINDOW(wxHtmlWindow::OnMouseEnter)
1251 EVT_LEAVE_WINDOW(wxHtmlWindow::OnMouseLeave)
1252 EVT_KEY_UP(wxHtmlWindow::OnKeyUp)
1253 EVT_MENU(wxID_COPY, wxHtmlWindow::OnCopy)
1254 #endif
1255 END_EVENT_TABLE()
1256
1257
1258
1259
1260
1261 // A module to allow initialization/cleanup
1262 // without calling these functions from app.cpp or from
1263 // the user's application.
1264
1265 class wxHtmlWinModule: public wxModule
1266 {
1267 DECLARE_DYNAMIC_CLASS(wxHtmlWinModule)
1268 public:
1269 wxHtmlWinModule() : wxModule() {}
1270 bool OnInit() { return TRUE; }
1271 void OnExit() { wxHtmlWindow::CleanUpStatics(); }
1272 };
1273
1274 IMPLEMENT_DYNAMIC_CLASS(wxHtmlWinModule, wxModule)
1275
1276
1277 // This hack forces the linker to always link in m_* files
1278 // (wxHTML doesn't work without handlers from these files)
1279 #include "wx/html/forcelnk.h"
1280 FORCE_WXHTML_MODULES()
1281
1282 #endif