]> git.saurik.com Git - wxWidgets.git/blob - src/html/htmlwin.cpp
wxMessageBox off the main thread lost result code.
[wxWidgets.git] / src / html / htmlwin.cpp
1 /////////////////////////////////////////////////////////////////////////////
2 // Name: src/html/htmlwin.cpp
3 // Purpose: wxHtmlWindow class for parsing & displaying HTML (implementation)
4 // Author: Vaclav Slavik
5 // Copyright: (c) 1999 Vaclav Slavik
6 // Licence: wxWindows licence
7 /////////////////////////////////////////////////////////////////////////////
8
9 #include "wx/wxprec.h"
10
11 #ifdef __BORLANDC__
12 #pragma hdrstop
13 #endif
14
15 #if wxUSE_HTML && wxUSE_STREAMS
16
17 #ifndef WX_PRECOMP
18 #include "wx/list.h"
19 #include "wx/log.h"
20 #include "wx/intl.h"
21 #include "wx/dcclient.h"
22 #include "wx/frame.h"
23 #include "wx/dcmemory.h"
24 #include "wx/timer.h"
25 #include "wx/settings.h"
26 #include "wx/dataobj.h"
27 #include "wx/statusbr.h"
28 #endif
29
30 #include "wx/html/htmlwin.h"
31 #include "wx/html/htmlproc.h"
32 #include "wx/clipbrd.h"
33 #include "wx/recguard.h"
34
35 #include "wx/arrimpl.cpp"
36 #include "wx/listimpl.cpp"
37
38 // uncomment this line to visually show the extent of the selection
39 //#define DEBUG_HTML_SELECTION
40
41 // HTML events:
42 IMPLEMENT_DYNAMIC_CLASS(wxHtmlLinkEvent, wxCommandEvent)
43 IMPLEMENT_DYNAMIC_CLASS(wxHtmlCellEvent, wxCommandEvent)
44
45 wxDEFINE_EVENT( wxEVT_HTML_CELL_CLICKED, wxHtmlCellEvent );
46 wxDEFINE_EVENT( wxEVT_HTML_CELL_HOVER, wxHtmlCellEvent );
47 wxDEFINE_EVENT( wxEVT_HTML_LINK_CLICKED, wxHtmlLinkEvent );
48
49
50 #if wxUSE_CLIPBOARD
51 // ----------------------------------------------------------------------------
52 // wxHtmlWinAutoScrollTimer: the timer used to generate a stream of scroll
53 // events when a captured mouse is held outside the window
54 // ----------------------------------------------------------------------------
55
56 class wxHtmlWinAutoScrollTimer : public wxTimer
57 {
58 public:
59 wxHtmlWinAutoScrollTimer(wxScrolledWindow *win,
60 wxEventType eventTypeToSend,
61 int pos, int orient)
62 {
63 m_win = win;
64 m_eventType = eventTypeToSend;
65 m_pos = pos;
66 m_orient = orient;
67 }
68
69 virtual void Notify();
70
71 private:
72 wxScrolledWindow *m_win;
73 wxEventType m_eventType;
74 int m_pos,
75 m_orient;
76
77 wxDECLARE_NO_COPY_CLASS(wxHtmlWinAutoScrollTimer);
78 };
79
80 void wxHtmlWinAutoScrollTimer::Notify()
81 {
82 // only do all this as long as the window is capturing the mouse
83 if ( wxWindow::GetCapture() != m_win )
84 {
85 Stop();
86 }
87 else // we still capture the mouse, continue generating events
88 {
89 // first scroll the window if we are allowed to do it
90 wxScrollWinEvent event1(m_eventType, m_pos, m_orient);
91 event1.SetEventObject(m_win);
92 if ( m_win->GetEventHandler()->ProcessEvent(event1) )
93 {
94 // and then send a pseudo mouse-move event to refresh the selection
95 wxMouseEvent event2(wxEVT_MOTION);
96 wxGetMousePosition(&event2.m_x, &event2.m_y);
97
98 // the mouse event coordinates should be client, not screen as
99 // returned by wxGetMousePosition
100 wxWindow *parentTop = m_win;
101 while ( parentTop->GetParent() )
102 parentTop = parentTop->GetParent();
103 wxPoint ptOrig = parentTop->GetPosition();
104 event2.m_x -= ptOrig.x;
105 event2.m_y -= ptOrig.y;
106
107 event2.SetEventObject(m_win);
108
109 // FIXME: we don't fill in the other members - ok?
110 m_win->GetEventHandler()->ProcessEvent(event2);
111 }
112 else // can't scroll further, stop
113 {
114 Stop();
115 }
116 }
117 }
118
119 #endif // wxUSE_CLIPBOARD
120
121
122
123 //-----------------------------------------------------------------------------
124 // wxHtmlHistoryItem
125 //-----------------------------------------------------------------------------
126
127 // item of history list
128 class WXDLLIMPEXP_HTML wxHtmlHistoryItem
129 {
130 public:
131 wxHtmlHistoryItem(const wxString& p, const wxString& a) {m_Page = p, m_Anchor = a, m_Pos = 0;}
132 int GetPos() const {return m_Pos;}
133 void SetPos(int p) {m_Pos = p;}
134 const wxString& GetPage() const {return m_Page;}
135 const wxString& GetAnchor() const {return m_Anchor;}
136
137 private:
138 wxString m_Page;
139 wxString m_Anchor;
140 int m_Pos;
141 };
142
143
144 //-----------------------------------------------------------------------------
145 // our private arrays:
146 //-----------------------------------------------------------------------------
147
148 WX_DECLARE_OBJARRAY(wxHtmlHistoryItem, wxHtmlHistoryArray);
149 WX_DEFINE_OBJARRAY(wxHtmlHistoryArray)
150
151 WX_DECLARE_LIST(wxHtmlProcessor, wxHtmlProcessorList);
152 WX_DEFINE_LIST(wxHtmlProcessorList)
153
154 //-----------------------------------------------------------------------------
155 // wxHtmlWindowMouseHelper
156 //-----------------------------------------------------------------------------
157
158 wxHtmlWindowMouseHelper::wxHtmlWindowMouseHelper(wxHtmlWindowInterface *iface)
159 : m_tmpMouseMoved(false),
160 m_tmpLastLink(NULL),
161 m_tmpLastCell(NULL),
162 m_interface(iface)
163 {
164 }
165
166 void wxHtmlWindowMouseHelper::HandleMouseMoved()
167 {
168 m_tmpMouseMoved = true;
169 }
170
171 bool wxHtmlWindowMouseHelper::HandleMouseClick(wxHtmlCell *rootCell,
172 const wxPoint& pos,
173 const wxMouseEvent& event)
174 {
175 if (!rootCell)
176 return false;
177
178 wxHtmlCell *cell = rootCell->FindCellByPos(pos.x, pos.y);
179 // this check is needed because FindCellByPos returns terminal cell and
180 // containers may have empty borders -- in this case NULL will be
181 // returned
182 if (!cell)
183 return false;
184
185 // adjust the coordinates to be relative to this cell:
186 wxPoint relpos = pos - cell->GetAbsPos(rootCell);
187
188 return OnCellClicked(cell, relpos.x, relpos.y, event);
189 }
190
191 void wxHtmlWindowMouseHelper::HandleIdle(wxHtmlCell *rootCell,
192 const wxPoint& pos)
193 {
194 wxHtmlCell *cell = rootCell ? rootCell->FindCellByPos(pos.x, pos.y) : NULL;
195
196 if (cell != m_tmpLastCell)
197 {
198 wxHtmlLinkInfo *lnk = NULL;
199 if (cell)
200 {
201 // adjust the coordinates to be relative to this cell:
202 wxPoint relpos = pos - cell->GetAbsPos(rootCell);
203 lnk = cell->GetLink(relpos.x, relpos.y);
204 }
205
206 wxCursor cur;
207 if (cell)
208 cur = cell->GetMouseCursorAt(m_interface, pos);
209 else
210 cur = m_interface->GetHTMLCursor(
211 wxHtmlWindowInterface::HTMLCursor_Default);
212
213 m_interface->GetHTMLWindow()->SetCursor(cur);
214
215 if (lnk != m_tmpLastLink)
216 {
217 if (lnk)
218 m_interface->SetHTMLStatusText(lnk->GetHref());
219 else
220 m_interface->SetHTMLStatusText(wxEmptyString);
221
222 m_tmpLastLink = lnk;
223 }
224
225 m_tmpLastCell = cell;
226 }
227 else // mouse moved but stayed in the same cell
228 {
229 if ( cell )
230 {
231 // A single cell can have different cursors for different positions,
232 // so update cursor for this case as well.
233 wxCursor cur = cell->GetMouseCursorAt(m_interface, pos);
234 m_interface->GetHTMLWindow()->SetCursor(cur);
235
236 OnCellMouseHover(cell, pos.x, pos.y);
237 }
238 }
239
240 m_tmpMouseMoved = false;
241 }
242
243 bool wxHtmlWindowMouseHelper::OnCellClicked(wxHtmlCell *cell,
244 wxCoord x, wxCoord y,
245 const wxMouseEvent& event)
246 {
247 wxHtmlCellEvent ev(wxEVT_HTML_CELL_CLICKED,
248 m_interface->GetHTMLWindow()->GetId(),
249 cell, wxPoint(x,y), event);
250
251 if (!m_interface->GetHTMLWindow()->GetEventHandler()->ProcessEvent(ev))
252 {
253 // if the event wasn't handled, do the default processing here:
254
255 wxASSERT_MSG( cell, wxT("can't be called with NULL cell") );
256
257 cell->ProcessMouseClick(m_interface, ev.GetPoint(), ev.GetMouseEvent());
258 }
259
260 // true if a link was clicked, false otherwise
261 return ev.GetLinkClicked();
262 }
263
264 void wxHtmlWindowMouseHelper::OnCellMouseHover(wxHtmlCell * cell,
265 wxCoord x,
266 wxCoord y)
267 {
268 wxHtmlCellEvent ev(wxEVT_HTML_CELL_HOVER,
269 m_interface->GetHTMLWindow()->GetId(),
270 cell, wxPoint(x,y), wxMouseEvent());
271 m_interface->GetHTMLWindow()->GetEventHandler()->ProcessEvent(ev);
272 }
273
274
275
276
277 //-----------------------------------------------------------------------------
278 // wxHtmlWindow
279 //-----------------------------------------------------------------------------
280
281 wxList wxHtmlWindow::m_Filters;
282 wxHtmlFilter *wxHtmlWindow::m_DefaultFilter = NULL;
283 wxHtmlProcessorList *wxHtmlWindow::m_GlobalProcessors = NULL;
284 wxCursor *wxHtmlWindow::ms_cursorLink = NULL;
285 wxCursor *wxHtmlWindow::ms_cursorText = NULL;
286
287 void wxHtmlWindow::CleanUpStatics()
288 {
289 wxDELETE(m_DefaultFilter);
290 WX_CLEAR_LIST(wxList, m_Filters);
291 if (m_GlobalProcessors)
292 WX_CLEAR_LIST(wxHtmlProcessorList, *m_GlobalProcessors);
293 wxDELETE(m_GlobalProcessors);
294 wxDELETE(ms_cursorLink);
295 wxDELETE(ms_cursorText);
296 }
297
298 void wxHtmlWindow::Init()
299 {
300 m_tmpCanDrawLocks = 0;
301 m_FS = new wxFileSystem();
302 #if wxUSE_STATUSBAR
303 m_RelatedStatusBar = NULL;
304 m_RelatedStatusBarIndex = -1;
305 #endif // wxUSE_STATUSBAR
306 m_RelatedFrame = NULL;
307 m_TitleFormat = wxT("%s");
308 m_OpenedPage = m_OpenedAnchor = m_OpenedPageTitle = wxEmptyString;
309 m_Cell = NULL;
310 m_Parser = new wxHtmlWinParser(this);
311 m_Parser->SetFS(m_FS);
312 m_HistoryPos = -1;
313 m_HistoryOn = true;
314 m_History = new wxHtmlHistoryArray;
315 m_Processors = NULL;
316 SetBorders(10);
317 m_selection = NULL;
318 m_makingSelection = false;
319 #if wxUSE_CLIPBOARD
320 m_timerAutoScroll = NULL;
321 m_lastDoubleClick = 0;
322 #endif // wxUSE_CLIPBOARD
323 m_tmpSelFromCell = NULL;
324 }
325
326 bool wxHtmlWindow::Create(wxWindow *parent, wxWindowID id,
327 const wxPoint& pos, const wxSize& size,
328 long style, const wxString& name)
329 {
330 if (!wxScrolledWindow::Create(parent, id, pos, size,
331 style | wxVSCROLL | wxHSCROLL,
332 name))
333 return false;
334
335 // We can't erase our background in EVT_ERASE_BACKGROUND handler and use
336 // double buffering in EVT_PAINT handler as this requires blitting back
337 // something already drawn on the window to the backing store bitmap when
338 // handling EVT_PAINT but blitting in this direction is simply not
339 // supported by OS X.
340 //
341 // So instead we use a hack with artificial EVT_ERASE_BACKGROUND generation
342 // from OnPaint() and this means that we never need the "real" erase event
343 // at all so disable it to avoid executing any user-defined handlers twice
344 // (and to avoid processing unnecessary event if no handlers are defined).
345 SetBackgroundStyle(wxBG_STYLE_PAINT);
346 SetPage(wxT("<html><body></body></html>"));
347
348 SetInitialSize(size);
349 return true;
350 }
351
352
353 wxHtmlWindow::~wxHtmlWindow()
354 {
355 #if wxUSE_CLIPBOARD
356 StopAutoScrolling();
357 #endif // wxUSE_CLIPBOARD
358 HistoryClear();
359
360 delete m_selection;
361
362 delete m_Cell;
363
364 if ( m_Processors )
365 {
366 WX_CLEAR_LIST(wxHtmlProcessorList, *m_Processors);
367 }
368
369 delete m_Parser;
370 delete m_FS;
371 delete m_History;
372 delete m_Processors;
373 }
374
375
376
377 void wxHtmlWindow::SetRelatedFrame(wxFrame* frame, const wxString& format)
378 {
379 m_RelatedFrame = frame;
380 m_TitleFormat = format;
381 }
382
383
384
385 #if wxUSE_STATUSBAR
386 void wxHtmlWindow::SetRelatedStatusBar(int index)
387 {
388 m_RelatedStatusBarIndex = index;
389 }
390
391 void wxHtmlWindow::SetRelatedStatusBar(wxStatusBar* statusbar, int index)
392 {
393 m_RelatedStatusBar = statusbar;
394 m_RelatedStatusBarIndex = index;
395 }
396
397 #endif // wxUSE_STATUSBAR
398
399
400
401 void wxHtmlWindow::SetFonts(const wxString& normal_face, const wxString& fixed_face, const int *sizes)
402 {
403 m_Parser->SetFonts(normal_face, fixed_face, sizes);
404
405 // re-layout the page after changing fonts:
406 DoSetPage(*(m_Parser->GetSource()));
407 }
408
409 void wxHtmlWindow::SetStandardFonts(int size,
410 const wxString& normal_face,
411 const wxString& fixed_face)
412 {
413 m_Parser->SetStandardFonts(size, normal_face, fixed_face);
414
415 // re-layout the page after changing fonts:
416 DoSetPage(*(m_Parser->GetSource()));
417 }
418
419 bool wxHtmlWindow::SetPage(const wxString& source)
420 {
421 m_OpenedPage = m_OpenedAnchor = m_OpenedPageTitle = wxEmptyString;
422 return DoSetPage(source);
423 }
424
425 bool wxHtmlWindow::DoSetPage(const wxString& source)
426 {
427 wxString newsrc(source);
428
429 wxDELETE(m_selection);
430
431 // we will soon delete all the cells, so clear pointers to them:
432 m_tmpSelFromCell = NULL;
433
434 // pass HTML through registered processors:
435 if (m_Processors || m_GlobalProcessors)
436 {
437 wxHtmlProcessorList::compatibility_iterator nodeL, nodeG;
438 int prL, prG;
439
440 if ( m_Processors )
441 nodeL = m_Processors->GetFirst();
442 if ( m_GlobalProcessors )
443 nodeG = m_GlobalProcessors->GetFirst();
444
445 // VS: there are two lists, global and local, both of them sorted by
446 // priority. Since we have to go through _both_ lists with
447 // decreasing priority, we "merge-sort" the lists on-line by
448 // processing that one of the two heads that has higher priority
449 // in every iteration
450 while (nodeL || nodeG)
451 {
452 prL = (nodeL) ? nodeL->GetData()->GetPriority() : -1;
453 prG = (nodeG) ? nodeG->GetData()->GetPriority() : -1;
454 if (prL > prG)
455 {
456 if (nodeL->GetData()->IsEnabled())
457 newsrc = nodeL->GetData()->Process(newsrc);
458 nodeL = nodeL->GetNext();
459 }
460 else // prL <= prG
461 {
462 if (nodeG->GetData()->IsEnabled())
463 newsrc = nodeG->GetData()->Process(newsrc);
464 nodeG = nodeG->GetNext();
465 }
466 }
467 }
468
469 // ...and run the parser on it:
470 wxClientDC *dc = new wxClientDC(this);
471 dc->SetMapMode(wxMM_TEXT);
472 SetBackgroundColour(wxColour(0xFF, 0xFF, 0xFF));
473 SetBackgroundImage(wxNullBitmap);
474
475 m_Parser->SetDC(dc);
476
477 // notice that it's important to set m_Cell to NULL here before calling
478 // Parse() below, even if it will be overwritten by its return value as
479 // without this we may crash if it's used from inside Parse(), so use
480 // wxDELETE() and not just delete here
481 wxDELETE(m_Cell);
482
483 m_Cell = (wxHtmlContainerCell*) m_Parser->Parse(newsrc);
484 delete dc;
485 m_Cell->SetIndent(m_Borders, wxHTML_INDENT_ALL, wxHTML_UNITS_PIXELS);
486 m_Cell->SetAlignHor(wxHTML_ALIGN_CENTER);
487 CreateLayout();
488 if (m_tmpCanDrawLocks == 0)
489 Refresh();
490 return true;
491 }
492
493 bool wxHtmlWindow::AppendToPage(const wxString& source)
494 {
495 return DoSetPage(*(GetParser()->GetSource()) + source);
496 }
497
498 bool wxHtmlWindow::LoadPage(const wxString& location)
499 {
500 wxCHECK_MSG( !location.empty(), false, "location must be non-empty" );
501
502 wxBusyCursor busyCursor;
503
504 bool rt_val;
505 bool needs_refresh = false;
506
507 m_tmpCanDrawLocks++;
508 if (m_HistoryOn && (m_HistoryPos != -1))
509 {
510 // store scroll position into history item:
511 int x, y;
512 GetViewStart(&x, &y);
513 (*m_History)[m_HistoryPos].SetPos(y);
514 }
515
516 // first check if we're moving to an anchor in the same page
517 size_t posLocalAnchor = location.Find('#');
518 if ( posLocalAnchor != wxString::npos && posLocalAnchor != 0 )
519 {
520 // check if the part before the anchor is the same as the (either
521 // relative or absolute) URI of the current page
522 const wxString beforeAnchor = location.substr(0, posLocalAnchor);
523 if ( beforeAnchor != m_OpenedPage &&
524 m_FS->GetPath() + beforeAnchor != m_OpenedPage )
525 {
526 // indicate that we're not moving to a local anchor
527 posLocalAnchor = wxString::npos;
528 }
529 }
530
531 if ( posLocalAnchor != wxString::npos )
532 {
533 m_tmpCanDrawLocks--;
534 rt_val = ScrollToAnchor(location.substr(posLocalAnchor + 1));
535 m_tmpCanDrawLocks++;
536 }
537 else // moving to another page
538 {
539 needs_refresh = true;
540 #if wxUSE_STATUSBAR
541 // load&display it:
542 if (m_RelatedStatusBarIndex != -1)
543 {
544 SetHTMLStatusText(_("Connecting..."));
545 Refresh(false);
546 }
547 #endif // wxUSE_STATUSBAR
548
549 wxFSFile *f = m_Parser->OpenURL(wxHTML_URL_PAGE, location);
550
551 // try to interpret 'location' as filename instead of URL:
552 if (f == NULL)
553 {
554 wxFileName fn(location);
555 wxString location2 = wxFileSystem::FileNameToURL(fn);
556 f = m_Parser->OpenURL(wxHTML_URL_PAGE, location2);
557 }
558
559 if (f == NULL)
560 {
561 wxLogError(_("Unable to open requested HTML document: %s"), location.c_str());
562 m_tmpCanDrawLocks--;
563 SetHTMLStatusText(wxEmptyString);
564 return false;
565 }
566
567 else
568 {
569 wxList::compatibility_iterator node;
570 wxString src = wxEmptyString;
571
572 #if wxUSE_STATUSBAR
573 if (m_RelatedStatusBarIndex != -1)
574 {
575 wxString msg = _("Loading : ") + location;
576 SetHTMLStatusText(msg);
577 Refresh(false);
578 }
579 #endif // wxUSE_STATUSBAR
580
581 node = m_Filters.GetFirst();
582 while (node)
583 {
584 wxHtmlFilter *h = (wxHtmlFilter*) node->GetData();
585 if (h->CanRead(*f))
586 {
587 src = h->ReadFile(*f);
588 break;
589 }
590 node = node->GetNext();
591 }
592 if (src == wxEmptyString)
593 {
594 if (m_DefaultFilter == NULL) m_DefaultFilter = GetDefaultFilter();
595 src = m_DefaultFilter->ReadFile(*f);
596 }
597
598 m_FS->ChangePathTo(f->GetLocation());
599 rt_val = SetPage(src);
600 m_OpenedPage = f->GetLocation();
601 if (f->GetAnchor() != wxEmptyString)
602 {
603 ScrollToAnchor(f->GetAnchor());
604 }
605
606 delete f;
607
608 #if wxUSE_STATUSBAR
609 if (m_RelatedStatusBarIndex != -1)
610 {
611 SetHTMLStatusText(_("Done"));
612 }
613 #endif // wxUSE_STATUSBAR
614 }
615 }
616
617 if (m_HistoryOn) // add this page to history there:
618 {
619 int c = m_History->GetCount() - (m_HistoryPos + 1);
620
621 if (m_HistoryPos < 0 ||
622 (*m_History)[m_HistoryPos].GetPage() != m_OpenedPage ||
623 (*m_History)[m_HistoryPos].GetAnchor() != m_OpenedAnchor)
624 {
625 m_HistoryPos++;
626 for (int i = 0; i < c; i++)
627 m_History->RemoveAt(m_HistoryPos);
628 m_History->Add(new wxHtmlHistoryItem(m_OpenedPage, m_OpenedAnchor));
629 }
630 }
631
632 if (m_OpenedPageTitle == wxEmptyString)
633 OnSetTitle(wxFileNameFromPath(m_OpenedPage));
634
635 if (needs_refresh)
636 {
637 m_tmpCanDrawLocks--;
638 Refresh();
639 }
640 else
641 m_tmpCanDrawLocks--;
642
643 return rt_val;
644 }
645
646
647 bool wxHtmlWindow::LoadFile(const wxFileName& filename)
648 {
649 wxString url = wxFileSystem::FileNameToURL(filename);
650 return LoadPage(url);
651 }
652
653
654 bool wxHtmlWindow::ScrollToAnchor(const wxString& anchor)
655 {
656 const wxHtmlCell *c = m_Cell->Find(wxHTML_COND_ISANCHOR, &anchor);
657 if (!c)
658 {
659 wxLogWarning(_("HTML anchor %s does not exist."), anchor.c_str());
660 return false;
661 }
662 else
663 {
664 // Go to next visible cell in current container, if it exists. This
665 // yields a bit better (even though still imperfect) results in that
666 // there's better chance of using a suitable cell for upper Y
667 // coordinate value. See bug #11406 for additional discussion.
668 const wxHtmlCell *c_save = c;
669 while ( c && c->IsFormattingCell() )
670 c = c->GetNext();
671 if ( !c )
672 c = c_save;
673
674 int y;
675
676 for (y = 0; c != NULL; c = c->GetParent()) y += c->GetPosY();
677 Scroll(-1, y / wxHTML_SCROLL_STEP);
678 m_OpenedAnchor = anchor;
679 return true;
680 }
681 }
682
683
684 void wxHtmlWindow::OnSetTitle(const wxString& title)
685 {
686 if (m_RelatedFrame)
687 {
688 wxString tit;
689 tit.Printf(m_TitleFormat, title.c_str());
690 m_RelatedFrame->SetTitle(tit);
691 }
692 m_OpenedPageTitle = title;
693 }
694
695
696 // return scroll steps such that a) scrollbars aren't shown needlessly
697 // and b) entire content is viewable (i.e. round up)
698 static int ScrollSteps(int size, int available)
699 {
700 if ( size <= available )
701 return 0;
702 else
703 return (size + wxHTML_SCROLL_STEP - 1) / wxHTML_SCROLL_STEP;
704 }
705
706
707 void wxHtmlWindow::CreateLayout()
708 {
709 // SetScrollbars() results in size change events -- and thus a nested
710 // CreateLayout() call -- on some platforms. Ignore nested calls, toplevel
711 // CreateLayout() will do the right thing eventually.
712 static wxRecursionGuardFlag s_flagReentrancy;
713 wxRecursionGuard guard(s_flagReentrancy);
714 if ( guard.IsInside() )
715 return;
716
717 if (!m_Cell)
718 return;
719
720 int clientWidth, clientHeight;
721 GetClientSize(&clientWidth, &clientHeight);
722
723 const int vscrollbar = wxSystemSettings::GetMetric(wxSYS_VSCROLL_X);
724 const int hscrollbar = wxSystemSettings::GetMetric(wxSYS_HSCROLL_Y);
725
726 if ( HasScrollbar(wxHORIZONTAL) )
727 clientHeight += hscrollbar;
728
729 if ( HasScrollbar(wxVERTICAL) )
730 clientWidth += vscrollbar;
731
732 if ( HasFlag(wxHW_SCROLLBAR_NEVER) )
733 {
734 SetScrollbars(1, 1, 0, 0); // always off
735 m_Cell->Layout(clientWidth);
736 }
737 else // !wxHW_SCROLLBAR_NEVER
738 {
739 // Lay the content out with the assumption that it's too large to fit
740 // in the window (this is likely to be the case):
741 m_Cell->Layout(clientWidth - vscrollbar);
742
743 // If the layout is wider than the window, horizontal scrollbar will
744 // certainly be shown. Account for it here for subsequent computations.
745 if ( m_Cell->GetWidth() > clientWidth )
746 clientHeight -= hscrollbar;
747
748 if ( m_Cell->GetHeight() <= clientHeight )
749 {
750 // we fit into the window, hide vertical scrollbar:
751 SetScrollbars
752 (
753 wxHTML_SCROLL_STEP, wxHTML_SCROLL_STEP,
754 ScrollSteps(m_Cell->GetWidth(), clientWidth - vscrollbar),
755 0
756 );
757 // ...and redo the layout to use the extra space
758 m_Cell->Layout(clientWidth);
759 }
760 else
761 {
762 // If the content doesn't fit into the window by only a small
763 // margin, chances are that it may fit fully with scrollbar turned
764 // off. It's something worth trying but on the other hand, we don't
765 // want to waste too much time redoing the layout (twice!) for
766 // long -- and thus expensive to layout -- pages. The cut-off value
767 // is an arbitrary heuristics.
768 static const int SMALL_OVERLAP = 60;
769 if ( m_Cell->GetHeight() <= clientHeight + SMALL_OVERLAP )
770 {
771 m_Cell->Layout(clientWidth);
772
773 if ( m_Cell->GetHeight() <= clientHeight )
774 {
775 // Great, we fit in. Hide the scrollbar.
776 SetScrollbars
777 (
778 wxHTML_SCROLL_STEP, wxHTML_SCROLL_STEP,
779 ScrollSteps(m_Cell->GetWidth(), clientWidth),
780 0
781 );
782 return;
783 }
784 else
785 {
786 // That didn't work out, go back to previous layout. Note
787 // that redoing the layout once again here isn't as bad as
788 // it looks -- thanks to the small cut-off value, it's a
789 // reasonably small page.
790 m_Cell->Layout(clientWidth - vscrollbar);
791 }
792 }
793 // else: the page is very long, it will certainly need scrollbar
794
795 SetScrollbars
796 (
797 wxHTML_SCROLL_STEP, wxHTML_SCROLL_STEP,
798 ScrollSteps(m_Cell->GetWidth(), clientWidth - vscrollbar),
799 ScrollSteps(m_Cell->GetHeight(), clientHeight)
800 );
801 }
802 }
803 }
804
805 #if wxUSE_CONFIG
806 void wxHtmlWindow::ReadCustomization(wxConfigBase *cfg, wxString path)
807 {
808 wxString oldpath;
809 wxString tmp;
810 int p_fontsizes[7];
811 wxString p_fff, p_ffn;
812
813 if (path != wxEmptyString)
814 {
815 oldpath = cfg->GetPath();
816 cfg->SetPath(path);
817 }
818
819 m_Borders = cfg->Read(wxT("wxHtmlWindow/Borders"), m_Borders);
820 p_fff = cfg->Read(wxT("wxHtmlWindow/FontFaceFixed"), m_Parser->m_FontFaceFixed);
821 p_ffn = cfg->Read(wxT("wxHtmlWindow/FontFaceNormal"), m_Parser->m_FontFaceNormal);
822 for (int i = 0; i < 7; i++)
823 {
824 tmp.Printf(wxT("wxHtmlWindow/FontsSize%i"), i);
825 p_fontsizes[i] = cfg->Read(tmp, m_Parser->m_FontsSizes[i]);
826 }
827 SetFonts(p_ffn, p_fff, p_fontsizes);
828
829 if (path != wxEmptyString)
830 cfg->SetPath(oldpath);
831 }
832
833
834
835 void wxHtmlWindow::WriteCustomization(wxConfigBase *cfg, wxString path)
836 {
837 wxString oldpath;
838 wxString tmp;
839
840 if (path != wxEmptyString)
841 {
842 oldpath = cfg->GetPath();
843 cfg->SetPath(path);
844 }
845
846 cfg->Write(wxT("wxHtmlWindow/Borders"), (long) m_Borders);
847 cfg->Write(wxT("wxHtmlWindow/FontFaceFixed"), m_Parser->m_FontFaceFixed);
848 cfg->Write(wxT("wxHtmlWindow/FontFaceNormal"), m_Parser->m_FontFaceNormal);
849 for (int i = 0; i < 7; i++)
850 {
851 tmp.Printf(wxT("wxHtmlWindow/FontsSize%i"), i);
852 cfg->Write(tmp, (long) m_Parser->m_FontsSizes[i]);
853 }
854
855 if (path != wxEmptyString)
856 cfg->SetPath(oldpath);
857 }
858 #endif // wxUSE_CONFIG
859
860 bool wxHtmlWindow::HistoryBack()
861 {
862 wxString a, l;
863
864 if (m_HistoryPos < 1) return false;
865
866 // store scroll position into history item:
867 int x, y;
868 GetViewStart(&x, &y);
869 (*m_History)[m_HistoryPos].SetPos(y);
870
871 // go to previous position:
872 m_HistoryPos--;
873
874 l = (*m_History)[m_HistoryPos].GetPage();
875 a = (*m_History)[m_HistoryPos].GetAnchor();
876 m_HistoryOn = false;
877 m_tmpCanDrawLocks++;
878 if (a == wxEmptyString) LoadPage(l);
879 else LoadPage(l + wxT("#") + a);
880 m_HistoryOn = true;
881 m_tmpCanDrawLocks--;
882 Scroll(0, (*m_History)[m_HistoryPos].GetPos());
883 Refresh();
884 return true;
885 }
886
887 bool wxHtmlWindow::HistoryCanBack()
888 {
889 if (m_HistoryPos < 1) return false;
890 return true ;
891 }
892
893
894 bool wxHtmlWindow::HistoryForward()
895 {
896 wxString a, l;
897
898 if (m_HistoryPos == -1) return false;
899 if (m_HistoryPos >= (int)m_History->GetCount() - 1)return false;
900
901 m_OpenedPage = wxEmptyString; // this will disable adding new entry into history in LoadPage()
902
903 m_HistoryPos++;
904 l = (*m_History)[m_HistoryPos].GetPage();
905 a = (*m_History)[m_HistoryPos].GetAnchor();
906 m_HistoryOn = false;
907 m_tmpCanDrawLocks++;
908 if (a == wxEmptyString) LoadPage(l);
909 else LoadPage(l + wxT("#") + a);
910 m_HistoryOn = true;
911 m_tmpCanDrawLocks--;
912 Scroll(0, (*m_History)[m_HistoryPos].GetPos());
913 Refresh();
914 return true;
915 }
916
917 bool wxHtmlWindow::HistoryCanForward()
918 {
919 if (m_HistoryPos == -1) return false;
920 if (m_HistoryPos >= (int)m_History->GetCount() - 1)return false;
921 return true ;
922 }
923
924
925 void wxHtmlWindow::HistoryClear()
926 {
927 m_History->Empty();
928 m_HistoryPos = -1;
929 }
930
931 void wxHtmlWindow::AddProcessor(wxHtmlProcessor *processor)
932 {
933 if (!m_Processors)
934 {
935 m_Processors = new wxHtmlProcessorList;
936 }
937 wxHtmlProcessorList::compatibility_iterator node;
938
939 for (node = m_Processors->GetFirst(); node; node = node->GetNext())
940 {
941 if (processor->GetPriority() > node->GetData()->GetPriority())
942 {
943 m_Processors->Insert(node, processor);
944 return;
945 }
946 }
947 m_Processors->Append(processor);
948 }
949
950 /*static */ void wxHtmlWindow::AddGlobalProcessor(wxHtmlProcessor *processor)
951 {
952 if (!m_GlobalProcessors)
953 {
954 m_GlobalProcessors = new wxHtmlProcessorList;
955 }
956 wxHtmlProcessorList::compatibility_iterator node;
957
958 for (node = m_GlobalProcessors->GetFirst(); node; node = node->GetNext())
959 {
960 if (processor->GetPriority() > node->GetData()->GetPriority())
961 {
962 m_GlobalProcessors->Insert(node, processor);
963 return;
964 }
965 }
966 m_GlobalProcessors->Append(processor);
967 }
968
969
970
971 void wxHtmlWindow::AddFilter(wxHtmlFilter *filter)
972 {
973 m_Filters.Append(filter);
974 }
975
976
977 bool wxHtmlWindow::IsSelectionEnabled() const
978 {
979 #if wxUSE_CLIPBOARD
980 return !HasFlag(wxHW_NO_SELECTION);
981 #else
982 return false;
983 #endif
984 }
985
986
987 #if wxUSE_CLIPBOARD
988 wxString wxHtmlWindow::DoSelectionToText(wxHtmlSelection *sel)
989 {
990 if ( !sel )
991 return wxEmptyString;
992
993 wxClientDC dc(this);
994 wxString text;
995
996 wxHtmlTerminalCellsInterator i(sel->GetFromCell(), sel->GetToCell());
997 const wxHtmlCell *prev = NULL;
998
999 while ( i )
1000 {
1001 // When converting HTML content to plain text, the entire paragraph
1002 // (container in wxHTML) goes on single line. A new paragraph (that
1003 // should go on its own line) has its own container. Therefore, the
1004 // simplest way of detecting where to insert newlines in plain text
1005 // is to check if the parent container changed -- if it did, we moved
1006 // to a new paragraph.
1007 if ( prev && prev->GetParent() != i->GetParent() )
1008 text << '\n';
1009
1010 // NB: we don't need to pass the selection to ConvertToText() in the
1011 // middle of the selected text; it's only useful when only part of
1012 // a cell is selected
1013 text << i->ConvertToText(sel);
1014
1015 prev = *i;
1016 ++i;
1017 }
1018 return text;
1019 }
1020
1021 wxString wxHtmlWindow::ToText()
1022 {
1023 if (m_Cell)
1024 {
1025 wxHtmlSelection sel;
1026 sel.Set(m_Cell->GetFirstTerminal(), m_Cell->GetLastTerminal());
1027 return DoSelectionToText(&sel);
1028 }
1029 else
1030 return wxEmptyString;
1031 }
1032
1033 #endif // wxUSE_CLIPBOARD
1034
1035 bool wxHtmlWindow::CopySelection(ClipboardType t)
1036 {
1037 #if wxUSE_CLIPBOARD
1038 if ( m_selection )
1039 {
1040 #if defined(__UNIX__) && !defined(__WXMAC__)
1041 wxTheClipboard->UsePrimarySelection(t == Primary);
1042 #else // !__UNIX__
1043 // Primary selection exists only under X11, so don't do anything under
1044 // the other platforms when we try to access it
1045 //
1046 // TODO: this should be abstracted at wxClipboard level!
1047 if ( t == Primary )
1048 return false;
1049 #endif // __UNIX__/!__UNIX__
1050
1051 if ( wxTheClipboard->Open() )
1052 {
1053 const wxString txt(SelectionToText());
1054 wxTheClipboard->SetData(new wxTextDataObject(txt));
1055 wxTheClipboard->Close();
1056 wxLogTrace(wxT("wxhtmlselection"),
1057 _("Copied to clipboard:\"%s\""), txt.c_str());
1058
1059 return true;
1060 }
1061 }
1062 #else
1063 wxUnusedVar(t);
1064 #endif // wxUSE_CLIPBOARD
1065
1066 return false;
1067 }
1068
1069
1070 void wxHtmlWindow::OnLinkClicked(const wxHtmlLinkInfo& link)
1071 {
1072 wxHtmlLinkEvent event(GetId(), link);
1073 event.SetEventObject(this);
1074 if (!GetEventHandler()->ProcessEvent(event))
1075 {
1076 // the default behaviour is to load the URL in this window
1077 const wxMouseEvent *e = event.GetLinkInfo().GetEvent();
1078 if (e == NULL || e->LeftUp())
1079 LoadPage(event.GetLinkInfo().GetHref());
1080 }
1081 }
1082
1083 void wxHtmlWindow::DoEraseBackground(wxDC& dc)
1084 {
1085 // if we don't have any background bitmap we just fill it with background
1086 // colour and we also must do it if the background bitmap is not fully
1087 // opaque as otherwise junk could be left there
1088 if ( !m_bmpBg.IsOk() || m_bmpBg.GetMask() )
1089 {
1090 dc.SetBackground(GetBackgroundColour());
1091 dc.Clear();
1092 }
1093
1094 if ( m_bmpBg.IsOk() )
1095 {
1096 // draw the background bitmap tiling it over the entire window area
1097 const wxSize sz = GetVirtualSize();
1098 const wxSize sizeBmp(m_bmpBg.GetWidth(), m_bmpBg.GetHeight());
1099 for ( wxCoord x = 0; x < sz.x; x += sizeBmp.x )
1100 {
1101 for ( wxCoord y = 0; y < sz.y; y += sizeBmp.y )
1102 {
1103 dc.DrawBitmap(m_bmpBg, x, y, true /* use mask */);
1104 }
1105 }
1106 }
1107 }
1108
1109 void wxHtmlWindow::OnEraseBackground(wxEraseEvent& WXUNUSED(event))
1110 {
1111 // We never get real erase background events as we changed our background
1112 // style to wxBG_STYLE_PAINT in our ctor so the only time when we get here
1113 // is when an artificial wxEraseEvent is generated by our own OnPaint()
1114 // below. This handler only exists to stop the event from propagating
1115 // downwards to wxWindow which may erase the background itself when it gets
1116 // it in some ports (currently this happens in wxUniv), so we simply stop
1117 // processing here and set a special flag allowing OnPaint() to see that
1118 // the event hadn't been really processed.
1119 m_isBgReallyErased = false;
1120 }
1121
1122 void wxHtmlWindow::OnPaint(wxPaintEvent& WXUNUSED(event))
1123 {
1124 wxPaintDC dcPaint(this);
1125
1126 if (m_tmpCanDrawLocks > 0 || m_Cell == NULL)
1127 return;
1128
1129 int x, y;
1130 GetViewStart(&x, &y);
1131 const wxRect rect = GetUpdateRegion().GetBox();
1132 const wxSize sz = GetClientSize();
1133
1134 // set up the DC we're drawing on: if the window is already double buffered
1135 // we do it directly on wxPaintDC, otherwise we allocate a backing store
1136 // buffer and compose the drawing there and then blit it to screen all at
1137 // once
1138 wxDC *dc;
1139 wxMemoryDC dcm;
1140 if ( IsDoubleBuffered() )
1141 {
1142 dc = &dcPaint;
1143 }
1144 else // window is not double buffered by the system, do it ourselves
1145 {
1146 if ( !m_backBuffer.IsOk() )
1147 m_backBuffer.Create(sz.x, sz.y);
1148 dcm.SelectObject(m_backBuffer);
1149 dc = &dcm;
1150 }
1151
1152 PrepareDC(*dc);
1153
1154 // Erase the background: for compatibility, we must generate the event to
1155 // allow the user-defined handlers to do it, hence this hack with sending
1156 // an artificial wxEraseEvent to trigger the execution of such handlers.
1157 wxEraseEvent eraseEvent(GetId(), dc);
1158 eraseEvent.SetEventObject(this);
1159
1160 // Hack inside a hack: the background wasn't really erased if our own
1161 // OnEraseBackground() was executed, so we need to check for the flag set
1162 // by it whenever it's called.
1163 m_isBgReallyErased = true; // Initially assume it wasn't.
1164 if ( !ProcessWindowEvent(eraseEvent) || !m_isBgReallyErased )
1165 {
1166 // erase background ourselves
1167 DoEraseBackground(*dc);
1168 }
1169 //else: background erased by the user-defined handler
1170
1171
1172 // draw the HTML window contents
1173 dc->SetMapMode(wxMM_TEXT);
1174 dc->SetBackgroundMode(wxTRANSPARENT);
1175 dc->SetLayoutDirection(GetLayoutDirection());
1176
1177 wxHtmlRenderingInfo rinfo;
1178 wxDefaultHtmlRenderingStyle rstyle;
1179 rinfo.SetSelection(m_selection);
1180 rinfo.SetStyle(&rstyle);
1181 m_Cell->Draw(*dc, 0, 0,
1182 y * wxHTML_SCROLL_STEP + rect.GetTop(),
1183 y * wxHTML_SCROLL_STEP + rect.GetBottom(),
1184 rinfo);
1185
1186 #ifdef DEBUG_HTML_SELECTION
1187 {
1188 int xc, yc, x, y;
1189 wxGetMousePosition(&xc, &yc);
1190 ScreenToClient(&xc, &yc);
1191 CalcUnscrolledPosition(xc, yc, &x, &y);
1192 wxHtmlCell *at = m_Cell->FindCellByPos(x, y);
1193 wxHtmlCell *before =
1194 m_Cell->FindCellByPos(x, y, wxHTML_FIND_NEAREST_BEFORE);
1195 wxHtmlCell *after =
1196 m_Cell->FindCellByPos(x, y, wxHTML_FIND_NEAREST_AFTER);
1197
1198 dc->SetBrush(*wxTRANSPARENT_BRUSH);
1199 dc->SetPen(*wxBLACK_PEN);
1200 if (at)
1201 dc->DrawRectangle(at->GetAbsPos(),
1202 wxSize(at->GetWidth(),at->GetHeight()));
1203 dc->SetPen(*wxGREEN_PEN);
1204 if (before)
1205 dc->DrawRectangle(before->GetAbsPos().x+1, before->GetAbsPos().y+1,
1206 before->GetWidth()-2,before->GetHeight()-2);
1207 dc->SetPen(*wxRED_PEN);
1208 if (after)
1209 dc->DrawRectangle(after->GetAbsPos().x+2, after->GetAbsPos().y+2,
1210 after->GetWidth()-4,after->GetHeight()-4);
1211 }
1212 #endif // DEBUG_HTML_SELECTION
1213
1214 if ( dc != &dcPaint )
1215 {
1216 dc->SetDeviceOrigin(0,0);
1217 dcPaint.Blit(0, rect.GetTop(),
1218 sz.x, rect.GetBottom() - rect.GetTop() + 1,
1219 dc,
1220 0, rect.GetTop());
1221 }
1222 }
1223
1224
1225
1226
1227 void wxHtmlWindow::OnSize(wxSizeEvent& event)
1228 {
1229 event.Skip();
1230
1231 m_backBuffer = wxNullBitmap;
1232
1233 CreateLayout();
1234
1235 // Recompute selection if necessary:
1236 if ( m_selection )
1237 {
1238 m_selection->Set(m_selection->GetFromCell(),
1239 m_selection->GetToCell());
1240 m_selection->ClearFromToCharacterPos();
1241 }
1242
1243 Refresh();
1244 }
1245
1246
1247 void wxHtmlWindow::OnMouseMove(wxMouseEvent& WXUNUSED(event))
1248 {
1249 wxHtmlWindowMouseHelper::HandleMouseMoved();
1250 }
1251
1252 void wxHtmlWindow::OnMouseDown(wxMouseEvent& event)
1253 {
1254 #if wxUSE_CLIPBOARD
1255 if ( event.LeftDown() && IsSelectionEnabled() )
1256 {
1257 const long TRIPLECLICK_LEN = 200; // 0.2 sec after doubleclick
1258 if ( wxGetLocalTimeMillis() - m_lastDoubleClick <= TRIPLECLICK_LEN )
1259 {
1260 SelectLine(CalcUnscrolledPosition(event.GetPosition()));
1261
1262 (void) CopySelection();
1263 }
1264 else
1265 {
1266 m_makingSelection = true;
1267
1268 if ( m_selection )
1269 {
1270 wxDELETE(m_selection);
1271 Refresh();
1272 }
1273 m_tmpSelFromPos = CalcUnscrolledPosition(event.GetPosition());
1274 m_tmpSelFromCell = NULL;
1275
1276 CaptureMouse();
1277 }
1278 }
1279 #endif // wxUSE_CLIPBOARD
1280
1281 // in any case, let the default handler set focus to this window
1282 event.Skip();
1283 }
1284
1285 void wxHtmlWindow::OnMouseUp(wxMouseEvent& event)
1286 {
1287 #if wxUSE_CLIPBOARD
1288 if ( m_makingSelection )
1289 {
1290 ReleaseMouse();
1291 m_makingSelection = false;
1292
1293 // if m_selection=NULL, the user didn't move the mouse far enough from
1294 // starting point and the mouse up event is part of a click, the user
1295 // is not selecting text:
1296 if ( m_selection )
1297 {
1298 CopySelection(Primary);
1299
1300 // we don't want mouse up event that ended selecting to be
1301 // handled as mouse click and e.g. follow hyperlink:
1302 return;
1303 }
1304 }
1305 #endif // wxUSE_CLIPBOARD
1306
1307 wxPoint pos = CalcUnscrolledPosition(event.GetPosition());
1308 if ( !wxHtmlWindowMouseHelper::HandleMouseClick(m_Cell, pos, event) )
1309 event.Skip();
1310 }
1311
1312 #if wxUSE_CLIPBOARD
1313 void wxHtmlWindow::OnMouseCaptureLost(wxMouseCaptureLostEvent& WXUNUSED(event))
1314 {
1315 if ( !m_makingSelection )
1316 return;
1317
1318 // discard the selecting operation
1319 m_makingSelection = false;
1320 wxDELETE(m_selection);
1321 m_tmpSelFromCell = NULL;
1322 Refresh();
1323 }
1324 #endif // wxUSE_CLIPBOARD
1325
1326
1327 void wxHtmlWindow::OnInternalIdle()
1328 {
1329 wxWindow::OnInternalIdle();
1330
1331 if (m_Cell != NULL && DidMouseMove())
1332 {
1333 #ifdef DEBUG_HTML_SELECTION
1334 Refresh();
1335 #endif
1336 int xc, yc, x, y;
1337 wxGetMousePosition(&xc, &yc);
1338 ScreenToClient(&xc, &yc);
1339 CalcUnscrolledPosition(xc, yc, &x, &y);
1340
1341 wxHtmlCell *cell = m_Cell->FindCellByPos(x, y);
1342
1343 // handle selection update:
1344 if ( m_makingSelection )
1345 {
1346 if ( !m_tmpSelFromCell )
1347 m_tmpSelFromCell = m_Cell->FindCellByPos(
1348 m_tmpSelFromPos.x,m_tmpSelFromPos.y);
1349
1350 // NB: a trick - we adjust selFromPos to be upper left or bottom
1351 // right corner of the first cell of the selection depending
1352 // on whether the mouse is moving to the right or to the left.
1353 // This gives us more "natural" behaviour when selecting
1354 // a line (specifically, first cell of the next line is not
1355 // included if you drag selection from left to right over
1356 // entire line):
1357 wxPoint dirFromPos;
1358 if ( !m_tmpSelFromCell )
1359 {
1360 dirFromPos = m_tmpSelFromPos;
1361 }
1362 else
1363 {
1364 dirFromPos = m_tmpSelFromCell->GetAbsPos();
1365 if ( x < m_tmpSelFromPos.x )
1366 {
1367 dirFromPos.x += m_tmpSelFromCell->GetWidth();
1368 dirFromPos.y += m_tmpSelFromCell->GetHeight();
1369 }
1370 }
1371 bool goingDown = dirFromPos.y < y ||
1372 (dirFromPos.y == y && dirFromPos.x < x);
1373
1374 // determine selection span:
1375 if ( /*still*/ !m_tmpSelFromCell )
1376 {
1377 if (goingDown)
1378 {
1379 m_tmpSelFromCell = m_Cell->FindCellByPos(
1380 m_tmpSelFromPos.x,m_tmpSelFromPos.y,
1381 wxHTML_FIND_NEAREST_AFTER);
1382 if (!m_tmpSelFromCell)
1383 m_tmpSelFromCell = m_Cell->GetFirstTerminal();
1384 }
1385 else
1386 {
1387 m_tmpSelFromCell = m_Cell->FindCellByPos(
1388 m_tmpSelFromPos.x,m_tmpSelFromPos.y,
1389 wxHTML_FIND_NEAREST_BEFORE);
1390 if (!m_tmpSelFromCell)
1391 m_tmpSelFromCell = m_Cell->GetLastTerminal();
1392 }
1393 }
1394
1395 wxHtmlCell *selcell = cell;
1396 if (!selcell)
1397 {
1398 if (goingDown)
1399 {
1400 selcell = m_Cell->FindCellByPos(x, y,
1401 wxHTML_FIND_NEAREST_BEFORE);
1402 if (!selcell)
1403 selcell = m_Cell->GetLastTerminal();
1404 }
1405 else
1406 {
1407 selcell = m_Cell->FindCellByPos(x, y,
1408 wxHTML_FIND_NEAREST_AFTER);
1409 if (!selcell)
1410 selcell = m_Cell->GetFirstTerminal();
1411 }
1412 }
1413
1414 // NB: it may *rarely* happen that the code above didn't find one
1415 // of the cells, e.g. if wxHtmlWindow doesn't contain any
1416 // visible cells.
1417 if ( selcell && m_tmpSelFromCell )
1418 {
1419 if ( !m_selection )
1420 {
1421 // start selecting only if mouse movement was big enough
1422 // (otherwise it was meant as mouse click, not selection):
1423 const int PRECISION = 2;
1424 wxPoint diff = m_tmpSelFromPos - wxPoint(x,y);
1425 if (abs(diff.x) > PRECISION || abs(diff.y) > PRECISION)
1426 {
1427 m_selection = new wxHtmlSelection();
1428 }
1429 }
1430 if ( m_selection )
1431 {
1432 if ( m_tmpSelFromCell->IsBefore(selcell) )
1433 {
1434 m_selection->Set(m_tmpSelFromPos, m_tmpSelFromCell,
1435 wxPoint(x,y), selcell);
1436 }
1437 else
1438 {
1439 m_selection->Set(wxPoint(x,y), selcell,
1440 m_tmpSelFromPos, m_tmpSelFromCell);
1441 }
1442 m_selection->ClearFromToCharacterPos();
1443 Refresh();
1444 }
1445 }
1446 }
1447
1448 // handle cursor and status bar text changes:
1449
1450 // NB: because we're passing in 'cell' and not 'm_Cell' (so that the
1451 // leaf cell lookup isn't done twice), we need to adjust the
1452 // position for the new root:
1453 wxPoint posInCell(x, y);
1454 if (cell)
1455 posInCell -= cell->GetAbsPos();
1456 wxHtmlWindowMouseHelper::HandleIdle(cell, posInCell);
1457 }
1458 }
1459
1460 #if wxUSE_CLIPBOARD
1461 void wxHtmlWindow::StopAutoScrolling()
1462 {
1463 if ( m_timerAutoScroll )
1464 {
1465 wxDELETE(m_timerAutoScroll);
1466 }
1467 }
1468
1469 void wxHtmlWindow::OnMouseEnter(wxMouseEvent& event)
1470 {
1471 StopAutoScrolling();
1472 event.Skip();
1473 }
1474
1475 void wxHtmlWindow::OnMouseLeave(wxMouseEvent& event)
1476 {
1477 // don't prevent the usual processing of the event from taking place
1478 event.Skip();
1479
1480 // when a captured mouse leave a scrolled window we start generate
1481 // scrolling events to allow, for example, extending selection beyond the
1482 // visible area in some controls
1483 if ( wxWindow::GetCapture() == this )
1484 {
1485 // where is the mouse leaving?
1486 int pos, orient;
1487 wxPoint pt = event.GetPosition();
1488 if ( pt.x < 0 )
1489 {
1490 orient = wxHORIZONTAL;
1491 pos = 0;
1492 }
1493 else if ( pt.y < 0 )
1494 {
1495 orient = wxVERTICAL;
1496 pos = 0;
1497 }
1498 else // we're lower or to the right of the window
1499 {
1500 wxSize size = GetClientSize();
1501 if ( pt.x > size.x )
1502 {
1503 orient = wxHORIZONTAL;
1504 pos = GetVirtualSize().x / wxHTML_SCROLL_STEP;
1505 }
1506 else if ( pt.y > size.y )
1507 {
1508 orient = wxVERTICAL;
1509 pos = GetVirtualSize().y / wxHTML_SCROLL_STEP;
1510 }
1511 else // this should be impossible
1512 {
1513 // but seems to happen sometimes under wxMSW - maybe it's a bug
1514 // there but for now just ignore it
1515
1516 //wxFAIL_MSG( wxT("can't understand where has mouse gone") );
1517
1518 return;
1519 }
1520 }
1521
1522 // only start the auto scroll timer if the window can be scrolled in
1523 // this direction
1524 if ( !HasScrollbar(orient) )
1525 return;
1526
1527 delete m_timerAutoScroll;
1528 m_timerAutoScroll = new wxHtmlWinAutoScrollTimer
1529 (
1530 this,
1531 pos == 0 ? wxEVT_SCROLLWIN_LINEUP
1532 : wxEVT_SCROLLWIN_LINEDOWN,
1533 pos,
1534 orient
1535 );
1536 m_timerAutoScroll->Start(50); // FIXME: make configurable
1537 }
1538 }
1539
1540 void wxHtmlWindow::OnKeyUp(wxKeyEvent& event)
1541 {
1542 if ( IsSelectionEnabled() &&
1543 (event.GetKeyCode() == 'C' && event.CmdDown()) )
1544 {
1545 wxClipboardTextEvent evt(wxEVT_TEXT_COPY, GetId());
1546
1547 evt.SetEventObject(this);
1548
1549 GetEventHandler()->ProcessEvent(evt);
1550 }
1551 else
1552 {
1553 event.Skip();
1554 }
1555 }
1556
1557 void wxHtmlWindow::OnCopy(wxCommandEvent& WXUNUSED(event))
1558 {
1559 (void) CopySelection();
1560 }
1561
1562 void wxHtmlWindow::OnClipboardEvent(wxClipboardTextEvent& WXUNUSED(event))
1563 {
1564 (void) CopySelection();
1565 }
1566
1567 void wxHtmlWindow::OnDoubleClick(wxMouseEvent& event)
1568 {
1569 // select word under cursor:
1570 if ( IsSelectionEnabled() )
1571 {
1572 SelectWord(CalcUnscrolledPosition(event.GetPosition()));
1573
1574 (void) CopySelection(Primary);
1575
1576 m_lastDoubleClick = wxGetLocalTimeMillis();
1577 }
1578 else
1579 event.Skip();
1580 }
1581
1582 void wxHtmlWindow::SelectWord(const wxPoint& pos)
1583 {
1584 if ( m_Cell )
1585 {
1586 wxHtmlCell *cell = m_Cell->FindCellByPos(pos.x, pos.y);
1587 if ( cell )
1588 {
1589 delete m_selection;
1590 m_selection = new wxHtmlSelection();
1591 m_selection->Set(cell, cell);
1592 RefreshRect(wxRect(CalcScrolledPosition(cell->GetAbsPos()),
1593 wxSize(cell->GetWidth(), cell->GetHeight())));
1594 }
1595 }
1596 }
1597
1598 void wxHtmlWindow::SelectLine(const wxPoint& pos)
1599 {
1600 if ( m_Cell )
1601 {
1602 wxHtmlCell *cell = m_Cell->FindCellByPos(pos.x, pos.y);
1603 if ( cell )
1604 {
1605 // We use following heuristic to find a "line": let the line be all
1606 // cells in same container as the cell under mouse cursor that are
1607 // neither completely above nor completely below the clicked cell
1608 // (i.e. are likely to be words positioned on same line of text).
1609
1610 int y1 = cell->GetAbsPos().y;
1611 int y2 = y1 + cell->GetHeight();
1612 int y;
1613 const wxHtmlCell *c;
1614 const wxHtmlCell *before = NULL;
1615 const wxHtmlCell *after = NULL;
1616
1617 // find last cell of line:
1618 for ( c = cell->GetNext(); c; c = c->GetNext())
1619 {
1620 y = c->GetAbsPos().y;
1621 if ( y + c->GetHeight() > y1 && y < y2 )
1622 after = c;
1623 else
1624 break;
1625 }
1626 if ( !after )
1627 after = cell;
1628
1629 // find first cell of line:
1630 for ( c = cell->GetParent()->GetFirstChild();
1631 c && c != cell; c = c->GetNext())
1632 {
1633 y = c->GetAbsPos().y;
1634 if ( y + c->GetHeight() > y1 && y < y2 )
1635 {
1636 if ( ! before )
1637 before = c;
1638 }
1639 else
1640 before = NULL;
1641 }
1642 if ( !before )
1643 before = cell;
1644
1645 delete m_selection;
1646 m_selection = new wxHtmlSelection();
1647 m_selection->Set(before, after);
1648
1649 Refresh();
1650 }
1651 }
1652 }
1653
1654 void wxHtmlWindow::SelectAll()
1655 {
1656 if ( m_Cell )
1657 {
1658 delete m_selection;
1659 m_selection = new wxHtmlSelection();
1660 m_selection->Set(m_Cell->GetFirstTerminal(), m_Cell->GetLastTerminal());
1661 Refresh();
1662 }
1663 }
1664
1665 #endif // wxUSE_CLIPBOARD
1666
1667
1668
1669 IMPLEMENT_ABSTRACT_CLASS(wxHtmlProcessor,wxObject)
1670
1671 wxBEGIN_PROPERTIES_TABLE(wxHtmlWindow)
1672 /*
1673 TODO PROPERTIES
1674 style , wxHW_SCROLLBAR_AUTO
1675 borders , (dimension)
1676 url , string
1677 htmlcode , string
1678 */
1679 wxEND_PROPERTIES_TABLE()
1680
1681 wxBEGIN_HANDLERS_TABLE(wxHtmlWindow)
1682 wxEND_HANDLERS_TABLE()
1683
1684 wxCONSTRUCTOR_5( wxHtmlWindow , wxWindow* , Parent , wxWindowID , Id , wxPoint , Position , wxSize , Size , long , WindowStyle )
1685
1686 wxIMPLEMENT_DYNAMIC_CLASS_XTI(wxHtmlWindow, wxScrolledWindow,"wx/html/htmlwin.h")
1687
1688 BEGIN_EVENT_TABLE(wxHtmlWindow, wxScrolledWindow)
1689 EVT_SIZE(wxHtmlWindow::OnSize)
1690 EVT_LEFT_DOWN(wxHtmlWindow::OnMouseDown)
1691 EVT_LEFT_UP(wxHtmlWindow::OnMouseUp)
1692 EVT_RIGHT_UP(wxHtmlWindow::OnMouseUp)
1693 EVT_MOTION(wxHtmlWindow::OnMouseMove)
1694 EVT_PAINT(wxHtmlWindow::OnPaint)
1695 EVT_ERASE_BACKGROUND(wxHtmlWindow::OnEraseBackground)
1696 #if wxUSE_CLIPBOARD
1697 EVT_LEFT_DCLICK(wxHtmlWindow::OnDoubleClick)
1698 EVT_ENTER_WINDOW(wxHtmlWindow::OnMouseEnter)
1699 EVT_LEAVE_WINDOW(wxHtmlWindow::OnMouseLeave)
1700 EVT_MOUSE_CAPTURE_LOST(wxHtmlWindow::OnMouseCaptureLost)
1701 EVT_KEY_UP(wxHtmlWindow::OnKeyUp)
1702 EVT_MENU(wxID_COPY, wxHtmlWindow::OnCopy)
1703 EVT_TEXT_COPY(wxID_ANY, wxHtmlWindow::OnClipboardEvent)
1704 #endif // wxUSE_CLIPBOARD
1705 END_EVENT_TABLE()
1706
1707 //-----------------------------------------------------------------------------
1708 // wxHtmlWindowInterface implementation in wxHtmlWindow
1709 //-----------------------------------------------------------------------------
1710
1711 void wxHtmlWindow::SetHTMLWindowTitle(const wxString& title)
1712 {
1713 OnSetTitle(title);
1714 }
1715
1716 void wxHtmlWindow::OnHTMLLinkClicked(const wxHtmlLinkInfo& link)
1717 {
1718 OnLinkClicked(link);
1719 }
1720
1721 wxHtmlOpeningStatus wxHtmlWindow::OnHTMLOpeningURL(wxHtmlURLType type,
1722 const wxString& url,
1723 wxString *redirect) const
1724 {
1725 return OnOpeningURL(type, url, redirect);
1726 }
1727
1728 wxPoint wxHtmlWindow::HTMLCoordsToWindow(wxHtmlCell *WXUNUSED(cell),
1729 const wxPoint& pos) const
1730 {
1731 return CalcScrolledPosition(pos);
1732 }
1733
1734 wxWindow* wxHtmlWindow::GetHTMLWindow()
1735 {
1736 return this;
1737 }
1738
1739 wxColour wxHtmlWindow::GetHTMLBackgroundColour() const
1740 {
1741 return GetBackgroundColour();
1742 }
1743
1744 void wxHtmlWindow::SetHTMLBackgroundColour(const wxColour& clr)
1745 {
1746 SetBackgroundColour(clr);
1747 }
1748
1749 void wxHtmlWindow::SetHTMLBackgroundImage(const wxBitmap& bmpBg)
1750 {
1751 SetBackgroundImage(bmpBg);
1752 }
1753
1754 void wxHtmlWindow::SetHTMLStatusText(const wxString& text)
1755 {
1756 #if wxUSE_STATUSBAR
1757 if (m_RelatedStatusBarIndex != -1)
1758 {
1759 if (m_RelatedStatusBar)
1760 {
1761 m_RelatedStatusBar->SetStatusText(text, m_RelatedStatusBarIndex);
1762 }
1763 else if (m_RelatedFrame)
1764 {
1765 m_RelatedFrame->SetStatusText(text, m_RelatedStatusBarIndex);
1766 }
1767 }
1768 #else
1769 wxUnusedVar(text);
1770 #endif // wxUSE_STATUSBAR
1771 }
1772
1773 /*static*/
1774 wxCursor wxHtmlWindow::GetDefaultHTMLCursor(HTMLCursor type)
1775 {
1776 switch (type)
1777 {
1778 case HTMLCursor_Link:
1779 if ( !ms_cursorLink )
1780 ms_cursorLink = new wxCursor(wxCURSOR_HAND);
1781 return *ms_cursorLink;
1782
1783 case HTMLCursor_Text:
1784 if ( !ms_cursorText )
1785 ms_cursorText = new wxCursor(wxCURSOR_IBEAM);
1786 return *ms_cursorText;
1787
1788 case HTMLCursor_Default:
1789 default:
1790 return *wxSTANDARD_CURSOR;
1791 }
1792 }
1793
1794 wxCursor wxHtmlWindow::GetHTMLCursor(HTMLCursor type) const
1795 {
1796 return GetDefaultHTMLCursor(type);
1797 }
1798
1799
1800 //-----------------------------------------------------------------------------
1801 // wxHtmlWinModule
1802 //-----------------------------------------------------------------------------
1803
1804 // A module to allow initialization/cleanup
1805 // without calling these functions from app.cpp or from
1806 // the user's application.
1807
1808 class wxHtmlWinModule: public wxModule
1809 {
1810 DECLARE_DYNAMIC_CLASS(wxHtmlWinModule)
1811 public:
1812 wxHtmlWinModule() : wxModule() {}
1813 bool OnInit() { return true; }
1814 void OnExit() { wxHtmlWindow::CleanUpStatics(); }
1815 };
1816
1817 IMPLEMENT_DYNAMIC_CLASS(wxHtmlWinModule, wxModule)
1818
1819
1820 // This hack forces the linker to always link in m_* files
1821 // (wxHTML doesn't work without handlers from these files)
1822 #include "wx/html/forcelnk.h"
1823 FORCE_WXHTML_MODULES()
1824
1825 #endif // wxUSE_HTML