use 'I' cursor when over text
[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 m_lastDoubleClick = 0;
178 #endif
179 m_backBuffer = NULL;
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 wxHtmlProcessorList *wxHtmlWindow::m_GlobalProcessors = NULL;
695
696 void wxHtmlWindow::CleanUpStatics()
697 {
698 wxDELETE(m_DefaultFilter);
699 m_Filters.DeleteContents(TRUE);
700 m_Filters.Clear();
701 wxDELETE(m_GlobalProcessors);
702 }
703
704
705
706 void wxHtmlWindow::AddFilter(wxHtmlFilter *filter)
707 {
708 m_Filters.Append(filter);
709 }
710
711
712 bool wxHtmlWindow::IsSelectionEnabled() const
713 {
714 #if wxUSE_CLIPBOARD
715 return !(m_Style & wxHW_NO_SELECTION);
716 #else
717 return false;
718 #endif
719 }
720
721
722 #if wxUSE_CLIPBOARD
723 wxString wxHtmlWindow::SelectionToText()
724 {
725 if ( !m_selection )
726 return wxEmptyString;
727
728 wxClientDC dc(this);
729
730 const wxHtmlCell *end = m_selection->GetToCell();
731 wxString text;
732 wxHtmlTerminalCellsInterator i(m_selection->GetFromCell(), end);
733 if ( i )
734 {
735 text << i->ConvertToText(m_selection);
736 ++i;
737 }
738 const wxHtmlCell *prev = *i;
739 while ( i )
740 {
741 if ( prev->GetParent() != i->GetParent() )
742 text << _T('\n');
743 text << i->ConvertToText(*i == end ? m_selection : NULL);
744 prev = *i;
745 ++i;
746 }
747 return text;
748 }
749
750 void wxHtmlWindow::CopySelection(ClipboardType t)
751 {
752 if ( m_selection )
753 {
754 wxTheClipboard->UsePrimarySelection(t == Primary);
755 wxString txt(SelectionToText());
756 if ( wxTheClipboard->Open() )
757 {
758 wxTheClipboard->SetData(new wxTextDataObject(txt));
759 wxTheClipboard->Close();
760 wxLogTrace(_T("wxhtmlselection"),
761 _("Copied to clipboard:\"%s\""), txt.c_str());
762 }
763 }
764 }
765 #endif
766
767
768 void wxHtmlWindow::OnLinkClicked(const wxHtmlLinkInfo& link)
769 {
770 const wxMouseEvent *e = link.GetEvent();
771 if (e == NULL || e->LeftUp())
772 LoadPage(link.GetHref());
773 }
774
775 void wxHtmlWindow::OnCellClicked(wxHtmlCell *cell,
776 wxCoord x, wxCoord y,
777 const wxMouseEvent& event)
778 {
779 wxCHECK_RET( cell, _T("can't be called with NULL cell") );
780
781 cell->OnMouseClick(this, x, y, event);
782 }
783
784 void wxHtmlWindow::OnCellMouseHover(wxHtmlCell * WXUNUSED(cell),
785 wxCoord WXUNUSED(x), wxCoord WXUNUSED(y))
786 {
787 // do nothing here
788 }
789
790 void wxHtmlWindow::OnEraseBackground(wxEraseEvent& event)
791 {
792 }
793
794 void wxHtmlWindow::OnPaint(wxPaintEvent& WXUNUSED(event))
795 {
796 wxPaintDC dc(this);
797
798 if (m_tmpCanDrawLocks > 0 || m_Cell == NULL) return;
799
800 int x, y;
801 GetViewStart(&x, &y);
802 wxRect rect = GetUpdateRegion().GetBox();
803 wxSize sz = GetSize();
804
805 wxMemoryDC dcm;
806 if ( !m_backBuffer )
807 m_backBuffer = new wxBitmap(sz.x, sz.y);
808 dcm.SelectObject(*m_backBuffer);
809 dcm.SetBackground(wxBrush(GetBackgroundColour(), wxSOLID));
810 dcm.Clear();
811 PrepareDC(dcm);
812 dcm.SetMapMode(wxMM_TEXT);
813 dcm.SetBackgroundMode(wxTRANSPARENT);
814
815 wxHtmlRenderingInfo rinfo;
816 wxDefaultHtmlRenderingStyle rstyle;
817 rinfo.SetSelection(m_selection);
818 rinfo.SetStyle(&rstyle);
819 m_Cell->Draw(dcm, 0, 0,
820 y * wxHTML_SCROLL_STEP + rect.GetTop(),
821 y * wxHTML_SCROLL_STEP + rect.GetBottom(),
822 rinfo);
823
824 dcm.SetDeviceOrigin(0,0);
825 dc.Blit(0, rect.GetTop(),
826 sz.x, rect.GetBottom() - rect.GetTop() + 1,
827 &dcm,
828 0, rect.GetTop());
829 }
830
831
832
833
834 void wxHtmlWindow::OnSize(wxSizeEvent& event)
835 {
836 wxDELETE(m_backBuffer);
837
838 wxScrolledWindow::OnSize(event);
839 CreateLayout();
840
841 // Recompute selection if necessary:
842 if ( m_selection )
843 {
844 m_selection->Set(m_selection->GetFromCell(),
845 m_selection->GetToCell());
846 m_selection->ClearPrivPos();
847 }
848
849 Refresh();
850 }
851
852
853 void wxHtmlWindow::OnMouseMove(wxMouseEvent& event)
854 {
855 m_tmpMouseMoved = true;
856 }
857
858 void wxHtmlWindow::OnMouseDown(wxMouseEvent& event)
859 {
860 #if wxUSE_CLIPBOARD
861 if ( event.LeftDown() && IsSelectionEnabled() )
862 {
863 const long TRIPLECLICK_LEN = 200; // 0.2 sec after doubleclick
864 if ( wxGetLocalTimeMillis() - m_lastDoubleClick <= TRIPLECLICK_LEN )
865 {
866 SelectLine(CalcUnscrolledPosition(event.GetPosition()));
867 }
868 else
869 {
870 m_makingSelection = true;
871
872 if ( m_selection )
873 {
874 wxDELETE(m_selection);
875 Refresh();
876 }
877 m_tmpSelFromPos = CalcUnscrolledPosition(event.GetPosition());
878 m_tmpSelFromCell = NULL;
879
880 CaptureMouse();
881 }
882 }
883 #endif
884 }
885
886 void wxHtmlWindow::OnMouseUp(wxMouseEvent& event)
887 {
888 #if wxUSE_CLIPBOARD
889 if ( m_makingSelection )
890 {
891 ReleaseMouse();
892 m_makingSelection = false;
893
894 // did the user move the mouse far enough from starting point?
895 if ( m_selection )
896 {
897 #ifdef __UNIX__
898 CopySelection(Primary);
899 #endif
900 // we don't want mouse up event that ended selecting to be
901 // handled as mouse click and e.g. follow hyperlink:
902 return;
903 }
904 }
905 #endif
906
907 SetFocus();
908 if ( m_Cell )
909 {
910 wxPoint pos = CalcUnscrolledPosition(event.GetPosition());
911 wxHtmlCell *cell = m_Cell->FindCellByPos(pos.x, pos.y);
912
913 // VZ: is it possible that we don't find anything at all?
914 // VS: yes. FindCellByPos returns terminal cell and
915 // containers may have empty borders
916 if ( cell )
917 OnCellClicked(cell, pos.x, pos.y, event);
918 }
919 }
920
921
922
923 void wxHtmlWindow::OnIdle(wxIdleEvent& WXUNUSED(event))
924 {
925 if (m_tmpMouseMoved && (m_Cell != NULL))
926 {
927 int xc, yc, x, y;
928 wxGetMousePosition(&xc, &yc);
929 ScreenToClient(&xc, &yc);
930 CalcUnscrolledPosition(xc, yc, &x, &y);
931
932 wxHtmlCell *cell = m_Cell->FindCellByPos(x, y);
933
934 // handle selection update:
935 if ( m_makingSelection )
936 {
937 bool goingDown = m_tmpSelFromPos.y < y ||
938 m_tmpSelFromPos.y == y && m_tmpSelFromPos.x < x;
939
940 if ( !m_tmpSelFromCell )
941 {
942 if (goingDown)
943 {
944 m_tmpSelFromCell = m_Cell->FindCellByPos(
945 m_tmpSelFromPos.x,m_tmpSelFromPos.y,
946 wxHTML_FIND_NEAREST_AFTER);
947 if (!m_tmpSelFromCell)
948 m_tmpSelFromCell = m_Cell->GetFirstTerminal();
949 }
950 else
951 {
952 m_tmpSelFromCell = m_Cell->FindCellByPos(
953 m_tmpSelFromPos.x,m_tmpSelFromPos.y,
954 wxHTML_FIND_NEAREST_BEFORE);
955 if (!m_tmpSelFromCell)
956 m_tmpSelFromCell = m_Cell->GetLastTerminal();
957 }
958 }
959
960 wxHtmlCell *selcell = cell;
961 if (!selcell)
962 {
963 if (goingDown)
964 {
965 selcell = m_Cell->FindCellByPos(x, y,
966 wxHTML_FIND_NEAREST_AFTER);
967 if (!selcell)
968 selcell = m_Cell->GetLastTerminal();
969 }
970 else
971 {
972 selcell = m_Cell->FindCellByPos(x, y,
973 wxHTML_FIND_NEAREST_BEFORE);
974 if (!selcell)
975 selcell = m_Cell->GetFirstTerminal();
976 }
977 }
978
979 // NB: it may *rarely* happen that the code above didn't find one
980 // of the cells, e.g. if wxHtmlWindow doesn't contain any
981 // visible cells.
982 if ( selcell && m_tmpSelFromCell )
983 {
984 if ( !m_selection )
985 {
986 // start selecting only if mouse movement was big enough
987 // (otherwise it was meant as mouse click, not selection):
988 const int PRECISION = 2;
989 wxPoint diff = m_tmpSelFromPos - wxPoint(x,y);
990 if (abs(diff.x) > PRECISION || abs(diff.y) > PRECISION)
991 {
992 m_selection = new wxHtmlSelection();
993 }
994 }
995 if ( m_selection )
996 {
997 if ( m_tmpSelFromCell->IsBefore(selcell) )
998 {
999 m_selection->Set(m_tmpSelFromPos, m_tmpSelFromCell,
1000 wxPoint(x,y), selcell); }
1001 else
1002 {
1003 m_selection->Set(wxPoint(x,y), selcell,
1004 m_tmpSelFromPos, m_tmpSelFromCell);
1005 }
1006 m_selection->ClearPrivPos();
1007 Refresh();
1008 }
1009 }
1010 }
1011
1012 // handle cursor and status bar text changes:
1013 if ( cell != m_tmpLastCell )
1014 {
1015 wxHtmlLinkInfo *lnk = cell ? cell->GetLink(x, y) : NULL;
1016 wxCursor cur;
1017 if (cell)
1018 cur = cell->GetCursor();
1019 else
1020 cur = *wxSTANDARD_CURSOR;
1021 SetCursor(cur);
1022
1023 if (lnk != m_tmpLastLink)
1024 {
1025 if (lnk == NULL)
1026 {
1027 if (m_RelatedStatusBar != -1)
1028 m_RelatedFrame->SetStatusText(wxEmptyString,
1029 m_RelatedStatusBar);
1030 }
1031 else
1032 {
1033 if (m_RelatedStatusBar != -1)
1034 m_RelatedFrame->SetStatusText(lnk->GetHref(),
1035 m_RelatedStatusBar);
1036 }
1037 m_tmpLastLink = lnk;
1038 }
1039
1040 m_tmpLastCell = cell;
1041 }
1042 else // mouse moved but stayed in the same cell
1043 {
1044 if ( cell )
1045 OnCellMouseHover(cell, x, y);
1046 }
1047
1048 m_tmpMouseMoved = FALSE;
1049 }
1050 }
1051
1052 #if wxUSE_CLIPBOARD
1053 void wxHtmlWindow::StopAutoScrolling()
1054 {
1055 if ( m_timerAutoScroll )
1056 {
1057 wxDELETE(m_timerAutoScroll);
1058 }
1059 }
1060
1061 void wxHtmlWindow::OnMouseEnter(wxMouseEvent& event)
1062 {
1063 StopAutoScrolling();
1064 event.Skip();
1065 }
1066
1067 void wxHtmlWindow::OnMouseLeave(wxMouseEvent& event)
1068 {
1069 // don't prevent the usual processing of the event from taking place
1070 event.Skip();
1071
1072 // when a captured mouse leave a scrolled window we start generate
1073 // scrolling events to allow, for example, extending selection beyond the
1074 // visible area in some controls
1075 if ( wxWindow::GetCapture() == this )
1076 {
1077 // where is the mouse leaving?
1078 int pos, orient;
1079 wxPoint pt = event.GetPosition();
1080 if ( pt.x < 0 )
1081 {
1082 orient = wxHORIZONTAL;
1083 pos = 0;
1084 }
1085 else if ( pt.y < 0 )
1086 {
1087 orient = wxVERTICAL;
1088 pos = 0;
1089 }
1090 else // we're lower or to the right of the window
1091 {
1092 wxSize size = GetClientSize();
1093 if ( pt.x > size.x )
1094 {
1095 orient = wxHORIZONTAL;
1096 pos = GetVirtualSize().x / wxHTML_SCROLL_STEP;
1097 }
1098 else if ( pt.y > size.y )
1099 {
1100 orient = wxVERTICAL;
1101 pos = GetVirtualSize().y / wxHTML_SCROLL_STEP;
1102 }
1103 else // this should be impossible
1104 {
1105 // but seems to happen sometimes under wxMSW - maybe it's a bug
1106 // there but for now just ignore it
1107
1108 //wxFAIL_MSG( _T("can't understand where has mouse gone") );
1109
1110 return;
1111 }
1112 }
1113
1114 // only start the auto scroll timer if the window can be scrolled in
1115 // this direction
1116 if ( !HasScrollbar(orient) )
1117 return;
1118
1119 delete m_timerAutoScroll;
1120 m_timerAutoScroll = new wxHtmlWinAutoScrollTimer
1121 (
1122 this,
1123 pos == 0 ? wxEVT_SCROLLWIN_LINEUP
1124 : wxEVT_SCROLLWIN_LINEDOWN,
1125 pos,
1126 orient
1127 );
1128 m_timerAutoScroll->Start(50); // FIXME: make configurable
1129 }
1130 }
1131
1132 void wxHtmlWindow::OnKeyUp(wxKeyEvent& event)
1133 {
1134 if ( IsSelectionEnabled() &&
1135 event.GetKeyCode() == 'C' && event.ControlDown() )
1136 {
1137 if ( m_selection )
1138 CopySelection();
1139 }
1140 }
1141
1142 void wxHtmlWindow::OnCopy(wxCommandEvent& event)
1143 {
1144 if ( m_selection )
1145 CopySelection();
1146 }
1147
1148 void wxHtmlWindow::OnDoubleClick(wxMouseEvent& event)
1149 {
1150 // select word under cursor:
1151 if ( IsSelectionEnabled() )
1152 {
1153 SelectWord(CalcUnscrolledPosition(event.GetPosition()));
1154 m_lastDoubleClick = wxGetLocalTimeMillis();
1155 }
1156 else
1157 event.Skip();
1158 }
1159
1160 void wxHtmlWindow::SelectWord(const wxPoint& pos)
1161 {
1162 wxHtmlCell *cell = m_Cell->FindCellByPos(pos.x, pos.y);
1163 if ( cell )
1164 {
1165 delete m_selection;
1166 m_selection = new wxHtmlSelection();
1167 m_selection->Set(cell, cell);
1168 RefreshRect(wxRect(CalcScrolledPosition(cell->GetAbsPos()),
1169 wxSize(cell->GetWidth(), cell->GetHeight())));
1170 }
1171 }
1172
1173 void wxHtmlWindow::SelectLine(const wxPoint& pos)
1174 {
1175 wxHtmlCell *cell = m_Cell->FindCellByPos(pos.x, pos.y);
1176 if ( cell )
1177 {
1178 // We use following heuristic to find a "line": let the line be all
1179 // cells in same container as the cell under mouse cursor that are
1180 // neither completely above nor completely bellow the clicked cell
1181 // (i.e. are likely to be words positioned on same line of text).
1182
1183 int y1 = cell->GetAbsPos().y;
1184 int y2 = y1 + cell->GetHeight();
1185 int y;
1186 const wxHtmlCell *c;
1187 const wxHtmlCell *before = NULL;
1188 const wxHtmlCell *after = NULL;
1189
1190 // find last cell of line:
1191 for ( c = cell->GetNext(); c; c = c->GetNext())
1192 {
1193 y = c->GetAbsPos().y;
1194 if ( y + c->GetHeight() > y1 && y < y2 )
1195 after = c;
1196 else
1197 break;
1198 }
1199 if ( !after )
1200 after = cell;
1201
1202 // find first cell of line:
1203 for ( c = cell->GetParent()->GetFirstChild();
1204 c && c != cell; c = c->GetNext())
1205 {
1206 y = c->GetAbsPos().y;
1207 if ( y + c->GetHeight() > y1 && y < y2 )
1208 {
1209 if ( ! before )
1210 before = c;
1211 }
1212 else
1213 before = NULL;
1214 }
1215 if ( !before )
1216 before = cell;
1217
1218 delete m_selection;
1219 m_selection = new wxHtmlSelection();
1220 m_selection->Set(before, after);
1221
1222 Refresh();
1223 }
1224 }
1225 #endif
1226
1227
1228
1229 IMPLEMENT_ABSTRACT_CLASS(wxHtmlProcessor,wxObject)
1230
1231 IMPLEMENT_DYNAMIC_CLASS(wxHtmlWindow,wxScrolledWindow)
1232
1233 BEGIN_EVENT_TABLE(wxHtmlWindow, wxScrolledWindow)
1234 EVT_SIZE(wxHtmlWindow::OnSize)
1235 EVT_LEFT_DOWN(wxHtmlWindow::OnMouseDown)
1236 EVT_LEFT_UP(wxHtmlWindow::OnMouseUp)
1237 EVT_RIGHT_UP(wxHtmlWindow::OnMouseUp)
1238 EVT_MOTION(wxHtmlWindow::OnMouseMove)
1239 EVT_IDLE(wxHtmlWindow::OnIdle)
1240 EVT_ERASE_BACKGROUND(wxHtmlWindow::OnEraseBackground)
1241 EVT_PAINT(wxHtmlWindow::OnPaint)
1242 #if wxUSE_CLIPBOARD
1243 EVT_LEFT_DCLICK(wxHtmlWindow::OnDoubleClick)
1244 EVT_ENTER_WINDOW(wxHtmlWindow::OnMouseEnter)
1245 EVT_LEAVE_WINDOW(wxHtmlWindow::OnMouseLeave)
1246 EVT_KEY_UP(wxHtmlWindow::OnKeyUp)
1247 EVT_MENU(wxID_COPY, wxHtmlWindow::OnCopy)
1248 #endif
1249 END_EVENT_TABLE()
1250
1251
1252
1253
1254
1255 // A module to allow initialization/cleanup
1256 // without calling these functions from app.cpp or from
1257 // the user's application.
1258
1259 class wxHtmlWinModule: public wxModule
1260 {
1261 DECLARE_DYNAMIC_CLASS(wxHtmlWinModule)
1262 public:
1263 wxHtmlWinModule() : wxModule() {}
1264 bool OnInit() { return TRUE; }
1265 void OnExit() { wxHtmlWindow::CleanUpStatics(); }
1266 };
1267
1268 IMPLEMENT_DYNAMIC_CLASS(wxHtmlWinModule, wxModule)
1269
1270
1271 // This hack forces the linker to always link in m_* files
1272 // (wxHTML doesn't work without handlers from these files)
1273 #include "wx/html/forcelnk.h"
1274 FORCE_WXHTML_MODULES()
1275
1276 #endif