Prevent the default wxWindow-level wxEVT_ERASE_BACKGROUND handler from being used in wxUniv for wxHtmlWindow. This is unnecessary as it has its own handler anyhow and also doesn't work for some reason as erasing wxMemoryDC by drawing a solid rectangle over it seems to be broken in at least wxX11. Work around this problem by erasing the background in wxHtmlWindow itself if no user-defined (as opposed to any, including one defined in wxWindow itself) handler for this event exists. Closes #13880. git-svn-id: https://svn.wxwidgets.org/svn/wx/wxWidgets/trunk@71300 c3d73ce0-8a6f-49c7-b76d-6d57e0e08775
1822 lines
53 KiB
C++
1822 lines
53 KiB
C++
/////////////////////////////////////////////////////////////////////////////
|
|
// Name: src/html/htmlwin.cpp
|
|
// Purpose: wxHtmlWindow class for parsing & displaying HTML (implementation)
|
|
// Author: Vaclav Slavik
|
|
// RCS-ID: $Id$
|
|
// Copyright: (c) 1999 Vaclav Slavik
|
|
// Licence: wxWindows licence
|
|
/////////////////////////////////////////////////////////////////////////////
|
|
|
|
#include "wx/wxprec.h"
|
|
|
|
#ifdef __BORLANDC__
|
|
#pragma hdrstop
|
|
#endif
|
|
|
|
#if wxUSE_HTML && wxUSE_STREAMS
|
|
|
|
#ifndef WX_PRECOMP
|
|
#include "wx/list.h"
|
|
#include "wx/log.h"
|
|
#include "wx/intl.h"
|
|
#include "wx/dcclient.h"
|
|
#include "wx/frame.h"
|
|
#include "wx/dcmemory.h"
|
|
#include "wx/timer.h"
|
|
#include "wx/settings.h"
|
|
#include "wx/dataobj.h"
|
|
#include "wx/statusbr.h"
|
|
#endif
|
|
|
|
#include "wx/html/htmlwin.h"
|
|
#include "wx/html/htmlproc.h"
|
|
#include "wx/clipbrd.h"
|
|
#include "wx/recguard.h"
|
|
|
|
#include "wx/arrimpl.cpp"
|
|
#include "wx/listimpl.cpp"
|
|
|
|
// uncomment this line to visually show the extent of the selection
|
|
//#define DEBUG_HTML_SELECTION
|
|
|
|
// HTML events:
|
|
IMPLEMENT_DYNAMIC_CLASS(wxHtmlLinkEvent, wxCommandEvent)
|
|
IMPLEMENT_DYNAMIC_CLASS(wxHtmlCellEvent, wxCommandEvent)
|
|
|
|
wxDEFINE_EVENT( wxEVT_COMMAND_HTML_CELL_CLICKED, wxHtmlCellEvent );
|
|
wxDEFINE_EVENT( wxEVT_COMMAND_HTML_CELL_HOVER, wxHtmlCellEvent );
|
|
wxDEFINE_EVENT( wxEVT_COMMAND_HTML_LINK_CLICKED, wxHtmlLinkEvent );
|
|
|
|
|
|
#if wxUSE_CLIPBOARD
|
|
// ----------------------------------------------------------------------------
|
|
// wxHtmlWinAutoScrollTimer: the timer used to generate a stream of scroll
|
|
// events when a captured mouse is held outside the window
|
|
// ----------------------------------------------------------------------------
|
|
|
|
class wxHtmlWinAutoScrollTimer : public wxTimer
|
|
{
|
|
public:
|
|
wxHtmlWinAutoScrollTimer(wxScrolledWindow *win,
|
|
wxEventType eventTypeToSend,
|
|
int pos, int orient)
|
|
{
|
|
m_win = win;
|
|
m_eventType = eventTypeToSend;
|
|
m_pos = pos;
|
|
m_orient = orient;
|
|
}
|
|
|
|
virtual void Notify();
|
|
|
|
private:
|
|
wxScrolledWindow *m_win;
|
|
wxEventType m_eventType;
|
|
int m_pos,
|
|
m_orient;
|
|
|
|
wxDECLARE_NO_COPY_CLASS(wxHtmlWinAutoScrollTimer);
|
|
};
|
|
|
|
void wxHtmlWinAutoScrollTimer::Notify()
|
|
{
|
|
// only do all this as long as the window is capturing the mouse
|
|
if ( wxWindow::GetCapture() != m_win )
|
|
{
|
|
Stop();
|
|
}
|
|
else // we still capture the mouse, continue generating events
|
|
{
|
|
// first scroll the window if we are allowed to do it
|
|
wxScrollWinEvent event1(m_eventType, m_pos, m_orient);
|
|
event1.SetEventObject(m_win);
|
|
if ( m_win->GetEventHandler()->ProcessEvent(event1) )
|
|
{
|
|
// and then send a pseudo mouse-move event to refresh the selection
|
|
wxMouseEvent event2(wxEVT_MOTION);
|
|
wxGetMousePosition(&event2.m_x, &event2.m_y);
|
|
|
|
// the mouse event coordinates should be client, not screen as
|
|
// returned by wxGetMousePosition
|
|
wxWindow *parentTop = m_win;
|
|
while ( parentTop->GetParent() )
|
|
parentTop = parentTop->GetParent();
|
|
wxPoint ptOrig = parentTop->GetPosition();
|
|
event2.m_x -= ptOrig.x;
|
|
event2.m_y -= ptOrig.y;
|
|
|
|
event2.SetEventObject(m_win);
|
|
|
|
// FIXME: we don't fill in the other members - ok?
|
|
m_win->GetEventHandler()->ProcessEvent(event2);
|
|
}
|
|
else // can't scroll further, stop
|
|
{
|
|
Stop();
|
|
}
|
|
}
|
|
}
|
|
|
|
#endif // wxUSE_CLIPBOARD
|
|
|
|
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// wxHtmlHistoryItem
|
|
//-----------------------------------------------------------------------------
|
|
|
|
// item of history list
|
|
class WXDLLIMPEXP_HTML wxHtmlHistoryItem
|
|
{
|
|
public:
|
|
wxHtmlHistoryItem(const wxString& p, const wxString& a) {m_Page = p, m_Anchor = a, m_Pos = 0;}
|
|
int GetPos() const {return m_Pos;}
|
|
void SetPos(int p) {m_Pos = p;}
|
|
const wxString& GetPage() const {return m_Page;}
|
|
const wxString& GetAnchor() const {return m_Anchor;}
|
|
|
|
private:
|
|
wxString m_Page;
|
|
wxString m_Anchor;
|
|
int m_Pos;
|
|
};
|
|
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// our private arrays:
|
|
//-----------------------------------------------------------------------------
|
|
|
|
WX_DECLARE_OBJARRAY(wxHtmlHistoryItem, wxHtmlHistoryArray);
|
|
WX_DEFINE_OBJARRAY(wxHtmlHistoryArray)
|
|
|
|
WX_DECLARE_LIST(wxHtmlProcessor, wxHtmlProcessorList);
|
|
WX_DEFINE_LIST(wxHtmlProcessorList)
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// wxHtmlWindowMouseHelper
|
|
//-----------------------------------------------------------------------------
|
|
|
|
wxHtmlWindowMouseHelper::wxHtmlWindowMouseHelper(wxHtmlWindowInterface *iface)
|
|
: m_tmpMouseMoved(false),
|
|
m_tmpLastLink(NULL),
|
|
m_tmpLastCell(NULL),
|
|
m_interface(iface)
|
|
{
|
|
}
|
|
|
|
void wxHtmlWindowMouseHelper::HandleMouseMoved()
|
|
{
|
|
m_tmpMouseMoved = true;
|
|
}
|
|
|
|
bool wxHtmlWindowMouseHelper::HandleMouseClick(wxHtmlCell *rootCell,
|
|
const wxPoint& pos,
|
|
const wxMouseEvent& event)
|
|
{
|
|
if (!rootCell)
|
|
return false;
|
|
|
|
wxHtmlCell *cell = rootCell->FindCellByPos(pos.x, pos.y);
|
|
// this check is needed because FindCellByPos returns terminal cell and
|
|
// containers may have empty borders -- in this case NULL will be
|
|
// returned
|
|
if (!cell)
|
|
return false;
|
|
|
|
// adjust the coordinates to be relative to this cell:
|
|
wxPoint relpos = pos - cell->GetAbsPos(rootCell);
|
|
|
|
return OnCellClicked(cell, relpos.x, relpos.y, event);
|
|
}
|
|
|
|
void wxHtmlWindowMouseHelper::HandleIdle(wxHtmlCell *rootCell,
|
|
const wxPoint& pos)
|
|
{
|
|
wxHtmlCell *cell = rootCell ? rootCell->FindCellByPos(pos.x, pos.y) : NULL;
|
|
|
|
if (cell != m_tmpLastCell)
|
|
{
|
|
wxHtmlLinkInfo *lnk = NULL;
|
|
if (cell)
|
|
{
|
|
// adjust the coordinates to be relative to this cell:
|
|
wxPoint relpos = pos - cell->GetAbsPos(rootCell);
|
|
lnk = cell->GetLink(relpos.x, relpos.y);
|
|
}
|
|
|
|
wxCursor cur;
|
|
if (cell)
|
|
cur = cell->GetMouseCursor(m_interface);
|
|
else
|
|
cur = m_interface->GetHTMLCursor(
|
|
wxHtmlWindowInterface::HTMLCursor_Default);
|
|
|
|
m_interface->GetHTMLWindow()->SetCursor(cur);
|
|
|
|
if (lnk != m_tmpLastLink)
|
|
{
|
|
if (lnk)
|
|
m_interface->SetHTMLStatusText(lnk->GetHref());
|
|
else
|
|
m_interface->SetHTMLStatusText(wxEmptyString);
|
|
|
|
m_tmpLastLink = lnk;
|
|
}
|
|
|
|
m_tmpLastCell = cell;
|
|
}
|
|
else // mouse moved but stayed in the same cell
|
|
{
|
|
if ( cell )
|
|
{
|
|
OnCellMouseHover(cell, pos.x, pos.y);
|
|
}
|
|
}
|
|
|
|
m_tmpMouseMoved = false;
|
|
}
|
|
|
|
bool wxHtmlWindowMouseHelper::OnCellClicked(wxHtmlCell *cell,
|
|
wxCoord x, wxCoord y,
|
|
const wxMouseEvent& event)
|
|
{
|
|
wxHtmlCellEvent ev(wxEVT_COMMAND_HTML_CELL_CLICKED,
|
|
m_interface->GetHTMLWindow()->GetId(),
|
|
cell, wxPoint(x,y), event);
|
|
|
|
if (!m_interface->GetHTMLWindow()->GetEventHandler()->ProcessEvent(ev))
|
|
{
|
|
// if the event wasn't handled, do the default processing here:
|
|
|
|
wxASSERT_MSG( cell, wxT("can't be called with NULL cell") );
|
|
|
|
cell->ProcessMouseClick(m_interface, ev.GetPoint(), ev.GetMouseEvent());
|
|
}
|
|
|
|
// true if a link was clicked, false otherwise
|
|
return ev.GetLinkClicked();
|
|
}
|
|
|
|
void wxHtmlWindowMouseHelper::OnCellMouseHover(wxHtmlCell * cell,
|
|
wxCoord x,
|
|
wxCoord y)
|
|
{
|
|
wxHtmlCellEvent ev(wxEVT_COMMAND_HTML_CELL_HOVER,
|
|
m_interface->GetHTMLWindow()->GetId(),
|
|
cell, wxPoint(x,y), wxMouseEvent());
|
|
m_interface->GetHTMLWindow()->GetEventHandler()->ProcessEvent(ev);
|
|
}
|
|
|
|
|
|
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// wxHtmlWindow
|
|
//-----------------------------------------------------------------------------
|
|
|
|
wxList wxHtmlWindow::m_Filters;
|
|
wxHtmlFilter *wxHtmlWindow::m_DefaultFilter = NULL;
|
|
wxHtmlProcessorList *wxHtmlWindow::m_GlobalProcessors = NULL;
|
|
wxCursor *wxHtmlWindow::ms_cursorLink = NULL;
|
|
wxCursor *wxHtmlWindow::ms_cursorText = NULL;
|
|
|
|
void wxHtmlWindow::CleanUpStatics()
|
|
{
|
|
wxDELETE(m_DefaultFilter);
|
|
WX_CLEAR_LIST(wxList, m_Filters);
|
|
if (m_GlobalProcessors)
|
|
WX_CLEAR_LIST(wxHtmlProcessorList, *m_GlobalProcessors);
|
|
wxDELETE(m_GlobalProcessors);
|
|
wxDELETE(ms_cursorLink);
|
|
wxDELETE(ms_cursorText);
|
|
}
|
|
|
|
void wxHtmlWindow::Init()
|
|
{
|
|
m_tmpCanDrawLocks = 0;
|
|
m_FS = new wxFileSystem();
|
|
#if wxUSE_STATUSBAR
|
|
m_RelatedStatusBar = NULL;
|
|
m_RelatedStatusBarIndex = -1;
|
|
#endif // wxUSE_STATUSBAR
|
|
m_RelatedFrame = NULL;
|
|
m_TitleFormat = wxT("%s");
|
|
m_OpenedPage = m_OpenedAnchor = m_OpenedPageTitle = wxEmptyString;
|
|
m_Cell = NULL;
|
|
m_Parser = new wxHtmlWinParser(this);
|
|
m_Parser->SetFS(m_FS);
|
|
m_HistoryPos = -1;
|
|
m_HistoryOn = true;
|
|
m_History = new wxHtmlHistoryArray;
|
|
m_Processors = NULL;
|
|
SetBorders(10);
|
|
m_selection = NULL;
|
|
m_makingSelection = false;
|
|
#if wxUSE_CLIPBOARD
|
|
m_timerAutoScroll = NULL;
|
|
m_lastDoubleClick = 0;
|
|
#endif // wxUSE_CLIPBOARD
|
|
m_tmpSelFromCell = NULL;
|
|
}
|
|
|
|
bool wxHtmlWindow::Create(wxWindow *parent, wxWindowID id,
|
|
const wxPoint& pos, const wxSize& size,
|
|
long style, const wxString& name)
|
|
{
|
|
if (!wxScrolledWindow::Create(parent, id, pos, size,
|
|
style | wxVSCROLL | wxHSCROLL,
|
|
name))
|
|
return false;
|
|
|
|
// We can't erase our background in EVT_ERASE_BACKGROUND handler and use
|
|
// double buffering in EVT_PAINT handler as this requires blitting back
|
|
// something already drawn on the window to the backing store bitmap when
|
|
// handling EVT_PAINT but blitting in this direction is simply not
|
|
// supported by OS X.
|
|
//
|
|
// So instead we use a hack with artificial EVT_ERASE_BACKGROUND generation
|
|
// from OnPaint() and this means that we never need the "real" erase event
|
|
// at all so disable it to avoid executing any user-defined handlers twice
|
|
// (and to avoid processing unnecessary event if no handlers are defined).
|
|
SetBackgroundStyle(wxBG_STYLE_PAINT);
|
|
SetPage(wxT("<html><body></body></html>"));
|
|
|
|
SetInitialSize(size);
|
|
return true;
|
|
}
|
|
|
|
|
|
wxHtmlWindow::~wxHtmlWindow()
|
|
{
|
|
#if wxUSE_CLIPBOARD
|
|
StopAutoScrolling();
|
|
#endif // wxUSE_CLIPBOARD
|
|
HistoryClear();
|
|
|
|
delete m_selection;
|
|
|
|
delete m_Cell;
|
|
|
|
if ( m_Processors )
|
|
{
|
|
WX_CLEAR_LIST(wxHtmlProcessorList, *m_Processors);
|
|
}
|
|
|
|
delete m_Parser;
|
|
delete m_FS;
|
|
delete m_History;
|
|
delete m_Processors;
|
|
}
|
|
|
|
|
|
|
|
void wxHtmlWindow::SetRelatedFrame(wxFrame* frame, const wxString& format)
|
|
{
|
|
m_RelatedFrame = frame;
|
|
m_TitleFormat = format;
|
|
}
|
|
|
|
|
|
|
|
#if wxUSE_STATUSBAR
|
|
void wxHtmlWindow::SetRelatedStatusBar(int index)
|
|
{
|
|
m_RelatedStatusBarIndex = index;
|
|
}
|
|
|
|
void wxHtmlWindow::SetRelatedStatusBar(wxStatusBar* statusbar, int index)
|
|
{
|
|
m_RelatedStatusBar = statusbar;
|
|
m_RelatedStatusBarIndex = index;
|
|
}
|
|
|
|
#endif // wxUSE_STATUSBAR
|
|
|
|
|
|
|
|
void wxHtmlWindow::SetFonts(const wxString& normal_face, const wxString& fixed_face, const int *sizes)
|
|
{
|
|
m_Parser->SetFonts(normal_face, fixed_face, sizes);
|
|
|
|
// re-layout the page after changing fonts:
|
|
DoSetPage(*(m_Parser->GetSource()));
|
|
}
|
|
|
|
void wxHtmlWindow::SetStandardFonts(int size,
|
|
const wxString& normal_face,
|
|
const wxString& fixed_face)
|
|
{
|
|
m_Parser->SetStandardFonts(size, normal_face, fixed_face);
|
|
|
|
// re-layout the page after changing fonts:
|
|
DoSetPage(*(m_Parser->GetSource()));
|
|
}
|
|
|
|
bool wxHtmlWindow::SetPage(const wxString& source)
|
|
{
|
|
m_OpenedPage = m_OpenedAnchor = m_OpenedPageTitle = wxEmptyString;
|
|
return DoSetPage(source);
|
|
}
|
|
|
|
bool wxHtmlWindow::DoSetPage(const wxString& source)
|
|
{
|
|
wxString newsrc(source);
|
|
|
|
wxDELETE(m_selection);
|
|
|
|
// we will soon delete all the cells, so clear pointers to them:
|
|
m_tmpSelFromCell = NULL;
|
|
|
|
// pass HTML through registered processors:
|
|
if (m_Processors || m_GlobalProcessors)
|
|
{
|
|
wxHtmlProcessorList::compatibility_iterator nodeL, nodeG;
|
|
int prL, prG;
|
|
|
|
if ( m_Processors )
|
|
nodeL = m_Processors->GetFirst();
|
|
if ( m_GlobalProcessors )
|
|
nodeG = m_GlobalProcessors->GetFirst();
|
|
|
|
// VS: there are two lists, global and local, both of them sorted by
|
|
// priority. Since we have to go through _both_ lists with
|
|
// decreasing priority, we "merge-sort" the lists on-line by
|
|
// processing that one of the two heads that has higher priority
|
|
// in every iteration
|
|
while (nodeL || nodeG)
|
|
{
|
|
prL = (nodeL) ? nodeL->GetData()->GetPriority() : -1;
|
|
prG = (nodeG) ? nodeG->GetData()->GetPriority() : -1;
|
|
if (prL > prG)
|
|
{
|
|
if (nodeL->GetData()->IsEnabled())
|
|
newsrc = nodeL->GetData()->Process(newsrc);
|
|
nodeL = nodeL->GetNext();
|
|
}
|
|
else // prL <= prG
|
|
{
|
|
if (nodeG->GetData()->IsEnabled())
|
|
newsrc = nodeG->GetData()->Process(newsrc);
|
|
nodeG = nodeG->GetNext();
|
|
}
|
|
}
|
|
}
|
|
|
|
// ...and run the parser on it:
|
|
wxClientDC *dc = new wxClientDC(this);
|
|
dc->SetMapMode(wxMM_TEXT);
|
|
SetBackgroundColour(wxColour(0xFF, 0xFF, 0xFF));
|
|
SetBackgroundImage(wxNullBitmap);
|
|
|
|
m_Parser->SetDC(dc);
|
|
|
|
// notice that it's important to set m_Cell to NULL here before calling
|
|
// Parse() below, even if it will be overwritten by its return value as
|
|
// without this we may crash if it's used from inside Parse(), so use
|
|
// wxDELETE() and not just delete here
|
|
wxDELETE(m_Cell);
|
|
|
|
m_Cell = (wxHtmlContainerCell*) m_Parser->Parse(newsrc);
|
|
delete dc;
|
|
m_Cell->SetIndent(m_Borders, wxHTML_INDENT_ALL, wxHTML_UNITS_PIXELS);
|
|
m_Cell->SetAlignHor(wxHTML_ALIGN_CENTER);
|
|
CreateLayout();
|
|
if (m_tmpCanDrawLocks == 0)
|
|
Refresh();
|
|
return true;
|
|
}
|
|
|
|
bool wxHtmlWindow::AppendToPage(const wxString& source)
|
|
{
|
|
return DoSetPage(*(GetParser()->GetSource()) + source);
|
|
}
|
|
|
|
bool wxHtmlWindow::LoadPage(const wxString& location)
|
|
{
|
|
wxCHECK_MSG( !location.empty(), false, "location must be non-empty" );
|
|
|
|
wxBusyCursor busyCursor;
|
|
|
|
bool rt_val;
|
|
bool needs_refresh = false;
|
|
|
|
m_tmpCanDrawLocks++;
|
|
if (m_HistoryOn && (m_HistoryPos != -1))
|
|
{
|
|
// store scroll position into history item:
|
|
int x, y;
|
|
GetViewStart(&x, &y);
|
|
(*m_History)[m_HistoryPos].SetPos(y);
|
|
}
|
|
|
|
// first check if we're moving to an anchor in the same page
|
|
size_t posLocalAnchor = location.Find('#');
|
|
if ( posLocalAnchor != wxString::npos && posLocalAnchor != 0 )
|
|
{
|
|
// check if the part before the anchor is the same as the (either
|
|
// relative or absolute) URI of the current page
|
|
const wxString beforeAnchor = location.substr(0, posLocalAnchor);
|
|
if ( beforeAnchor != m_OpenedPage &&
|
|
m_FS->GetPath() + beforeAnchor != m_OpenedPage )
|
|
{
|
|
// indicate that we're not moving to a local anchor
|
|
posLocalAnchor = wxString::npos;
|
|
}
|
|
}
|
|
|
|
if ( posLocalAnchor != wxString::npos )
|
|
{
|
|
m_tmpCanDrawLocks--;
|
|
rt_val = ScrollToAnchor(location.substr(posLocalAnchor + 1));
|
|
m_tmpCanDrawLocks++;
|
|
}
|
|
else // moving to another page
|
|
{
|
|
needs_refresh = true;
|
|
#if wxUSE_STATUSBAR
|
|
// load&display it:
|
|
if (m_RelatedStatusBarIndex != -1)
|
|
{
|
|
SetHTMLStatusText(_("Connecting..."));
|
|
Refresh(false);
|
|
}
|
|
#endif // wxUSE_STATUSBAR
|
|
|
|
wxFSFile *f = m_Parser->OpenURL(wxHTML_URL_PAGE, location);
|
|
|
|
// try to interpret 'location' as filename instead of URL:
|
|
if (f == NULL)
|
|
{
|
|
wxFileName fn(location);
|
|
wxString location2 = wxFileSystem::FileNameToURL(fn);
|
|
f = m_Parser->OpenURL(wxHTML_URL_PAGE, location2);
|
|
}
|
|
|
|
if (f == NULL)
|
|
{
|
|
wxLogError(_("Unable to open requested HTML document: %s"), location.c_str());
|
|
m_tmpCanDrawLocks--;
|
|
SetHTMLStatusText(wxEmptyString);
|
|
return false;
|
|
}
|
|
|
|
else
|
|
{
|
|
wxList::compatibility_iterator node;
|
|
wxString src = wxEmptyString;
|
|
|
|
#if wxUSE_STATUSBAR
|
|
if (m_RelatedStatusBarIndex != -1)
|
|
{
|
|
wxString msg = _("Loading : ") + location;
|
|
SetHTMLStatusText(msg);
|
|
Refresh(false);
|
|
}
|
|
#endif // wxUSE_STATUSBAR
|
|
|
|
node = m_Filters.GetFirst();
|
|
while (node)
|
|
{
|
|
wxHtmlFilter *h = (wxHtmlFilter*) node->GetData();
|
|
if (h->CanRead(*f))
|
|
{
|
|
src = h->ReadFile(*f);
|
|
break;
|
|
}
|
|
node = node->GetNext();
|
|
}
|
|
if (src == wxEmptyString)
|
|
{
|
|
if (m_DefaultFilter == NULL) m_DefaultFilter = GetDefaultFilter();
|
|
src = m_DefaultFilter->ReadFile(*f);
|
|
}
|
|
|
|
m_FS->ChangePathTo(f->GetLocation());
|
|
rt_val = SetPage(src);
|
|
m_OpenedPage = f->GetLocation();
|
|
if (f->GetAnchor() != wxEmptyString)
|
|
{
|
|
ScrollToAnchor(f->GetAnchor());
|
|
}
|
|
|
|
delete f;
|
|
|
|
#if wxUSE_STATUSBAR
|
|
if (m_RelatedStatusBarIndex != -1)
|
|
{
|
|
SetHTMLStatusText(_("Done"));
|
|
}
|
|
#endif // wxUSE_STATUSBAR
|
|
}
|
|
}
|
|
|
|
if (m_HistoryOn) // add this page to history there:
|
|
{
|
|
int c = m_History->GetCount() - (m_HistoryPos + 1);
|
|
|
|
if (m_HistoryPos < 0 ||
|
|
(*m_History)[m_HistoryPos].GetPage() != m_OpenedPage ||
|
|
(*m_History)[m_HistoryPos].GetAnchor() != m_OpenedAnchor)
|
|
{
|
|
m_HistoryPos++;
|
|
for (int i = 0; i < c; i++)
|
|
m_History->RemoveAt(m_HistoryPos);
|
|
m_History->Add(new wxHtmlHistoryItem(m_OpenedPage, m_OpenedAnchor));
|
|
}
|
|
}
|
|
|
|
if (m_OpenedPageTitle == wxEmptyString)
|
|
OnSetTitle(wxFileNameFromPath(m_OpenedPage));
|
|
|
|
if (needs_refresh)
|
|
{
|
|
m_tmpCanDrawLocks--;
|
|
Refresh();
|
|
}
|
|
else
|
|
m_tmpCanDrawLocks--;
|
|
|
|
return rt_val;
|
|
}
|
|
|
|
|
|
bool wxHtmlWindow::LoadFile(const wxFileName& filename)
|
|
{
|
|
wxString url = wxFileSystem::FileNameToURL(filename);
|
|
return LoadPage(url);
|
|
}
|
|
|
|
|
|
bool wxHtmlWindow::ScrollToAnchor(const wxString& anchor)
|
|
{
|
|
const wxHtmlCell *c = m_Cell->Find(wxHTML_COND_ISANCHOR, &anchor);
|
|
if (!c)
|
|
{
|
|
wxLogWarning(_("HTML anchor %s does not exist."), anchor.c_str());
|
|
return false;
|
|
}
|
|
else
|
|
{
|
|
// Go to next visible cell in current container, if it exists. This
|
|
// yields a bit better (even though still imperfect) results in that
|
|
// there's better chance of using a suitable cell for upper Y
|
|
// coordinate value. See bug #11406 for additional discussion.
|
|
const wxHtmlCell *c_save = c;
|
|
while ( c && c->IsFormattingCell() )
|
|
c = c->GetNext();
|
|
if ( !c )
|
|
c = c_save;
|
|
|
|
int y;
|
|
|
|
for (y = 0; c != NULL; c = c->GetParent()) y += c->GetPosY();
|
|
Scroll(-1, y / wxHTML_SCROLL_STEP);
|
|
m_OpenedAnchor = anchor;
|
|
return true;
|
|
}
|
|
}
|
|
|
|
|
|
void wxHtmlWindow::OnSetTitle(const wxString& title)
|
|
{
|
|
if (m_RelatedFrame)
|
|
{
|
|
wxString tit;
|
|
tit.Printf(m_TitleFormat, title.c_str());
|
|
m_RelatedFrame->SetTitle(tit);
|
|
}
|
|
m_OpenedPageTitle = title;
|
|
}
|
|
|
|
|
|
// return scroll steps such that a) scrollbars aren't shown needlessly
|
|
// and b) entire content is viewable (i.e. round up)
|
|
static int ScrollSteps(int size, int available)
|
|
{
|
|
if ( size <= available )
|
|
return 0;
|
|
else
|
|
return (size + wxHTML_SCROLL_STEP - 1) / wxHTML_SCROLL_STEP;
|
|
}
|
|
|
|
|
|
void wxHtmlWindow::CreateLayout()
|
|
{
|
|
// SetScrollbars() results in size change events -- and thus a nested
|
|
// CreateLayout() call -- on some platforms. Ignore nested calls, toplevel
|
|
// CreateLayout() will do the right thing eventually.
|
|
static wxRecursionGuardFlag s_flagReentrancy;
|
|
wxRecursionGuard guard(s_flagReentrancy);
|
|
if ( guard.IsInside() )
|
|
return;
|
|
|
|
if (!m_Cell)
|
|
return;
|
|
|
|
int clientWidth, clientHeight;
|
|
GetClientSize(&clientWidth, &clientHeight);
|
|
|
|
const int vscrollbar = wxSystemSettings::GetMetric(wxSYS_VSCROLL_X);
|
|
const int hscrollbar = wxSystemSettings::GetMetric(wxSYS_HSCROLL_Y);
|
|
|
|
if ( HasScrollbar(wxHORIZONTAL) )
|
|
clientHeight += hscrollbar;
|
|
|
|
if ( HasScrollbar(wxVERTICAL) )
|
|
clientWidth += vscrollbar;
|
|
|
|
if ( HasFlag(wxHW_SCROLLBAR_NEVER) )
|
|
{
|
|
SetScrollbars(1, 1, 0, 0); // always off
|
|
m_Cell->Layout(clientWidth);
|
|
}
|
|
else // !wxHW_SCROLLBAR_NEVER
|
|
{
|
|
// Lay the content out with the assumption that it's too large to fit
|
|
// in the window (this is likely to be the case):
|
|
m_Cell->Layout(clientWidth - vscrollbar);
|
|
|
|
// If the layout is wider than the window, horizontal scrollbar will
|
|
// certainly be shown. Account for it here for subsequent computations.
|
|
if ( m_Cell->GetWidth() > clientWidth )
|
|
clientHeight -= hscrollbar;
|
|
|
|
if ( m_Cell->GetHeight() <= clientHeight )
|
|
{
|
|
// we fit into the window, hide vertical scrollbar:
|
|
SetScrollbars
|
|
(
|
|
wxHTML_SCROLL_STEP, wxHTML_SCROLL_STEP,
|
|
ScrollSteps(m_Cell->GetWidth(), clientWidth - vscrollbar),
|
|
0
|
|
);
|
|
// ...and redo the layout to use the extra space
|
|
m_Cell->Layout(clientWidth);
|
|
}
|
|
else
|
|
{
|
|
// If the content doesn't fit into the window by only a small
|
|
// margin, chances are that it may fit fully with scrollbar turned
|
|
// off. It's something worth trying but on the other hand, we don't
|
|
// want to waste too much time redoing the layout (twice!) for
|
|
// long -- and thus expensive to layout -- pages. The cut-off value
|
|
// is an arbitrary heuristics.
|
|
static const int SMALL_OVERLAP = 60;
|
|
if ( m_Cell->GetHeight() <= clientHeight + SMALL_OVERLAP )
|
|
{
|
|
m_Cell->Layout(clientWidth);
|
|
|
|
if ( m_Cell->GetHeight() <= clientHeight )
|
|
{
|
|
// Great, we fit in. Hide the scrollbar.
|
|
SetScrollbars
|
|
(
|
|
wxHTML_SCROLL_STEP, wxHTML_SCROLL_STEP,
|
|
ScrollSteps(m_Cell->GetWidth(), clientWidth),
|
|
0
|
|
);
|
|
return;
|
|
}
|
|
else
|
|
{
|
|
// That didn't work out, go back to previous layout. Note
|
|
// that redoing the layout once again here isn't as bad as
|
|
// it looks -- thanks to the small cut-off value, it's a
|
|
// reasonably small page.
|
|
m_Cell->Layout(clientWidth - vscrollbar);
|
|
}
|
|
}
|
|
// else: the page is very long, it will certainly need scrollbar
|
|
|
|
SetScrollbars
|
|
(
|
|
wxHTML_SCROLL_STEP, wxHTML_SCROLL_STEP,
|
|
ScrollSteps(m_Cell->GetWidth(), clientWidth - vscrollbar),
|
|
ScrollSteps(m_Cell->GetHeight(), clientHeight)
|
|
);
|
|
}
|
|
}
|
|
}
|
|
|
|
#if wxUSE_CONFIG
|
|
void wxHtmlWindow::ReadCustomization(wxConfigBase *cfg, wxString path)
|
|
{
|
|
wxString oldpath;
|
|
wxString tmp;
|
|
int p_fontsizes[7];
|
|
wxString p_fff, p_ffn;
|
|
|
|
if (path != wxEmptyString)
|
|
{
|
|
oldpath = cfg->GetPath();
|
|
cfg->SetPath(path);
|
|
}
|
|
|
|
m_Borders = cfg->Read(wxT("wxHtmlWindow/Borders"), m_Borders);
|
|
p_fff = cfg->Read(wxT("wxHtmlWindow/FontFaceFixed"), m_Parser->m_FontFaceFixed);
|
|
p_ffn = cfg->Read(wxT("wxHtmlWindow/FontFaceNormal"), m_Parser->m_FontFaceNormal);
|
|
for (int i = 0; i < 7; i++)
|
|
{
|
|
tmp.Printf(wxT("wxHtmlWindow/FontsSize%i"), i);
|
|
p_fontsizes[i] = cfg->Read(tmp, m_Parser->m_FontsSizes[i]);
|
|
}
|
|
SetFonts(p_ffn, p_fff, p_fontsizes);
|
|
|
|
if (path != wxEmptyString)
|
|
cfg->SetPath(oldpath);
|
|
}
|
|
|
|
|
|
|
|
void wxHtmlWindow::WriteCustomization(wxConfigBase *cfg, wxString path)
|
|
{
|
|
wxString oldpath;
|
|
wxString tmp;
|
|
|
|
if (path != wxEmptyString)
|
|
{
|
|
oldpath = cfg->GetPath();
|
|
cfg->SetPath(path);
|
|
}
|
|
|
|
cfg->Write(wxT("wxHtmlWindow/Borders"), (long) m_Borders);
|
|
cfg->Write(wxT("wxHtmlWindow/FontFaceFixed"), m_Parser->m_FontFaceFixed);
|
|
cfg->Write(wxT("wxHtmlWindow/FontFaceNormal"), m_Parser->m_FontFaceNormal);
|
|
for (int i = 0; i < 7; i++)
|
|
{
|
|
tmp.Printf(wxT("wxHtmlWindow/FontsSize%i"), i);
|
|
cfg->Write(tmp, (long) m_Parser->m_FontsSizes[i]);
|
|
}
|
|
|
|
if (path != wxEmptyString)
|
|
cfg->SetPath(oldpath);
|
|
}
|
|
#endif // wxUSE_CONFIG
|
|
|
|
bool wxHtmlWindow::HistoryBack()
|
|
{
|
|
wxString a, l;
|
|
|
|
if (m_HistoryPos < 1) return false;
|
|
|
|
// store scroll position into history item:
|
|
int x, y;
|
|
GetViewStart(&x, &y);
|
|
(*m_History)[m_HistoryPos].SetPos(y);
|
|
|
|
// go to previous position:
|
|
m_HistoryPos--;
|
|
|
|
l = (*m_History)[m_HistoryPos].GetPage();
|
|
a = (*m_History)[m_HistoryPos].GetAnchor();
|
|
m_HistoryOn = false;
|
|
m_tmpCanDrawLocks++;
|
|
if (a == wxEmptyString) LoadPage(l);
|
|
else LoadPage(l + wxT("#") + a);
|
|
m_HistoryOn = true;
|
|
m_tmpCanDrawLocks--;
|
|
Scroll(0, (*m_History)[m_HistoryPos].GetPos());
|
|
Refresh();
|
|
return true;
|
|
}
|
|
|
|
bool wxHtmlWindow::HistoryCanBack()
|
|
{
|
|
if (m_HistoryPos < 1) return false;
|
|
return true ;
|
|
}
|
|
|
|
|
|
bool wxHtmlWindow::HistoryForward()
|
|
{
|
|
wxString a, l;
|
|
|
|
if (m_HistoryPos == -1) return false;
|
|
if (m_HistoryPos >= (int)m_History->GetCount() - 1)return false;
|
|
|
|
m_OpenedPage = wxEmptyString; // this will disable adding new entry into history in LoadPage()
|
|
|
|
m_HistoryPos++;
|
|
l = (*m_History)[m_HistoryPos].GetPage();
|
|
a = (*m_History)[m_HistoryPos].GetAnchor();
|
|
m_HistoryOn = false;
|
|
m_tmpCanDrawLocks++;
|
|
if (a == wxEmptyString) LoadPage(l);
|
|
else LoadPage(l + wxT("#") + a);
|
|
m_HistoryOn = true;
|
|
m_tmpCanDrawLocks--;
|
|
Scroll(0, (*m_History)[m_HistoryPos].GetPos());
|
|
Refresh();
|
|
return true;
|
|
}
|
|
|
|
bool wxHtmlWindow::HistoryCanForward()
|
|
{
|
|
if (m_HistoryPos == -1) return false;
|
|
if (m_HistoryPos >= (int)m_History->GetCount() - 1)return false;
|
|
return true ;
|
|
}
|
|
|
|
|
|
void wxHtmlWindow::HistoryClear()
|
|
{
|
|
m_History->Empty();
|
|
m_HistoryPos = -1;
|
|
}
|
|
|
|
void wxHtmlWindow::AddProcessor(wxHtmlProcessor *processor)
|
|
{
|
|
if (!m_Processors)
|
|
{
|
|
m_Processors = new wxHtmlProcessorList;
|
|
}
|
|
wxHtmlProcessorList::compatibility_iterator node;
|
|
|
|
for (node = m_Processors->GetFirst(); node; node = node->GetNext())
|
|
{
|
|
if (processor->GetPriority() > node->GetData()->GetPriority())
|
|
{
|
|
m_Processors->Insert(node, processor);
|
|
return;
|
|
}
|
|
}
|
|
m_Processors->Append(processor);
|
|
}
|
|
|
|
/*static */ void wxHtmlWindow::AddGlobalProcessor(wxHtmlProcessor *processor)
|
|
{
|
|
if (!m_GlobalProcessors)
|
|
{
|
|
m_GlobalProcessors = new wxHtmlProcessorList;
|
|
}
|
|
wxHtmlProcessorList::compatibility_iterator node;
|
|
|
|
for (node = m_GlobalProcessors->GetFirst(); node; node = node->GetNext())
|
|
{
|
|
if (processor->GetPriority() > node->GetData()->GetPriority())
|
|
{
|
|
m_GlobalProcessors->Insert(node, processor);
|
|
return;
|
|
}
|
|
}
|
|
m_GlobalProcessors->Append(processor);
|
|
}
|
|
|
|
|
|
|
|
void wxHtmlWindow::AddFilter(wxHtmlFilter *filter)
|
|
{
|
|
m_Filters.Append(filter);
|
|
}
|
|
|
|
|
|
bool wxHtmlWindow::IsSelectionEnabled() const
|
|
{
|
|
#if wxUSE_CLIPBOARD
|
|
return !HasFlag(wxHW_NO_SELECTION);
|
|
#else
|
|
return false;
|
|
#endif
|
|
}
|
|
|
|
|
|
#if wxUSE_CLIPBOARD
|
|
wxString wxHtmlWindow::DoSelectionToText(wxHtmlSelection *sel)
|
|
{
|
|
if ( !sel )
|
|
return wxEmptyString;
|
|
|
|
wxClientDC dc(this);
|
|
wxString text;
|
|
|
|
wxHtmlTerminalCellsInterator i(sel->GetFromCell(), sel->GetToCell());
|
|
const wxHtmlCell *prev = NULL;
|
|
|
|
while ( i )
|
|
{
|
|
// When converting HTML content to plain text, the entire paragraph
|
|
// (container in wxHTML) goes on single line. A new paragraph (that
|
|
// should go on its own line) has its own container. Therefore, the
|
|
// simplest way of detecting where to insert newlines in plain text
|
|
// is to check if the parent container changed -- if it did, we moved
|
|
// to a new paragraph.
|
|
if ( prev && prev->GetParent() != i->GetParent() )
|
|
text << '\n';
|
|
|
|
// NB: we don't need to pass the selection to ConvertToText() in the
|
|
// middle of the selected text; it's only useful when only part of
|
|
// a cell is selected
|
|
text << i->ConvertToText(sel);
|
|
|
|
prev = *i;
|
|
++i;
|
|
}
|
|
return text;
|
|
}
|
|
|
|
wxString wxHtmlWindow::ToText()
|
|
{
|
|
if (m_Cell)
|
|
{
|
|
wxHtmlSelection sel;
|
|
sel.Set(m_Cell->GetFirstTerminal(), m_Cell->GetLastTerminal());
|
|
return DoSelectionToText(&sel);
|
|
}
|
|
else
|
|
return wxEmptyString;
|
|
}
|
|
|
|
#endif // wxUSE_CLIPBOARD
|
|
|
|
bool wxHtmlWindow::CopySelection(ClipboardType t)
|
|
{
|
|
#if wxUSE_CLIPBOARD
|
|
if ( m_selection )
|
|
{
|
|
#if defined(__UNIX__) && !defined(__WXMAC__)
|
|
wxTheClipboard->UsePrimarySelection(t == Primary);
|
|
#else // !__UNIX__
|
|
// Primary selection exists only under X11, so don't do anything under
|
|
// the other platforms when we try to access it
|
|
//
|
|
// TODO: this should be abstracted at wxClipboard level!
|
|
if ( t == Primary )
|
|
return false;
|
|
#endif // __UNIX__/!__UNIX__
|
|
|
|
if ( wxTheClipboard->Open() )
|
|
{
|
|
const wxString txt(SelectionToText());
|
|
wxTheClipboard->SetData(new wxTextDataObject(txt));
|
|
wxTheClipboard->Close();
|
|
wxLogTrace(wxT("wxhtmlselection"),
|
|
_("Copied to clipboard:\"%s\""), txt.c_str());
|
|
|
|
return true;
|
|
}
|
|
}
|
|
#else
|
|
wxUnusedVar(t);
|
|
#endif // wxUSE_CLIPBOARD
|
|
|
|
return false;
|
|
}
|
|
|
|
|
|
void wxHtmlWindow::OnLinkClicked(const wxHtmlLinkInfo& link)
|
|
{
|
|
wxHtmlLinkEvent event(GetId(), link);
|
|
event.SetEventObject(this);
|
|
if (!GetEventHandler()->ProcessEvent(event))
|
|
{
|
|
// the default behaviour is to load the URL in this window
|
|
const wxMouseEvent *e = event.GetLinkInfo().GetEvent();
|
|
if (e == NULL || e->LeftUp())
|
|
LoadPage(event.GetLinkInfo().GetHref());
|
|
}
|
|
}
|
|
|
|
void wxHtmlWindow::DoEraseBackground(wxDC& dc)
|
|
{
|
|
// if we don't have any background bitmap we just fill it with background
|
|
// colour and we also must do it if the background bitmap is not fully
|
|
// opaque as otherwise junk could be left there
|
|
if ( !m_bmpBg.IsOk() || m_bmpBg.GetMask() )
|
|
{
|
|
dc.SetBackground(GetBackgroundColour());
|
|
dc.Clear();
|
|
}
|
|
|
|
if ( m_bmpBg.IsOk() )
|
|
{
|
|
// draw the background bitmap tiling it over the entire window area
|
|
const wxSize sz = GetClientSize();
|
|
const wxSize sizeBmp(m_bmpBg.GetWidth(), m_bmpBg.GetHeight());
|
|
for ( wxCoord x = 0; x < sz.x; x += sizeBmp.x )
|
|
{
|
|
for ( wxCoord y = 0; y < sz.y; y += sizeBmp.y )
|
|
{
|
|
dc.DrawBitmap(m_bmpBg, x, y, true /* use mask */);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
void wxHtmlWindow::OnEraseBackground(wxEraseEvent& WXUNUSED(event))
|
|
{
|
|
// We never get real erase background events as we changed our background
|
|
// style to wxBG_STYLE_PAINT in our ctor so the only time when we get here
|
|
// is when an artificial wxEraseEvent is generated by our own OnPaint()
|
|
// below. This handler only exists to stop the event from propagating
|
|
// downwards to wxWindow which may erase the background itself when it gets
|
|
// it in some ports (currently this happens in wxUniv), so we simply stop
|
|
// processing here and set a special flag allowing OnPaint() to see that
|
|
// the event hadn't been really processed.
|
|
m_isBgReallyErased = false;
|
|
}
|
|
|
|
void wxHtmlWindow::OnPaint(wxPaintEvent& WXUNUSED(event))
|
|
{
|
|
wxPaintDC dcPaint(this);
|
|
|
|
if (m_tmpCanDrawLocks > 0 || m_Cell == NULL)
|
|
return;
|
|
|
|
int x, y;
|
|
GetViewStart(&x, &y);
|
|
const wxRect rect = GetUpdateRegion().GetBox();
|
|
const wxSize sz = GetClientSize();
|
|
|
|
// set up the DC we're drawing on: if the window is already double buffered
|
|
// we do it directly on wxPaintDC, otherwise we allocate a backing store
|
|
// buffer and compose the drawing there and then blit it to screen all at
|
|
// once
|
|
wxDC *dc;
|
|
wxMemoryDC dcm;
|
|
if ( IsDoubleBuffered() )
|
|
{
|
|
dc = &dcPaint;
|
|
}
|
|
else // window is not double buffered by the system, do it ourselves
|
|
{
|
|
if ( !m_backBuffer.IsOk() )
|
|
m_backBuffer.Create(sz.x, sz.y);
|
|
dcm.SelectObject(m_backBuffer);
|
|
dc = &dcm;
|
|
}
|
|
|
|
PrepareDC(*dc);
|
|
|
|
// Erase the background: for compatibility, we must generate the event to
|
|
// allow the user-defined handlers to do it, hence this hack with sending
|
|
// an artificial wxEraseEvent to trigger the execution of such handlers.
|
|
wxEraseEvent eraseEvent(GetId(), dc);
|
|
eraseEvent.SetEventObject(this);
|
|
|
|
// Hack inside a hack: the background wasn't really erased if our own
|
|
// OnEraseBackground() was executed, so we need to check for the flag set
|
|
// by it whenever it's called.
|
|
m_isBgReallyErased = true; // Initially assume it wasn't.
|
|
if ( !ProcessWindowEvent(eraseEvent) || !m_isBgReallyErased )
|
|
{
|
|
// erase background ourselves
|
|
DoEraseBackground(*dc);
|
|
}
|
|
//else: background erased by the user-defined handler
|
|
|
|
|
|
// draw the HTML window contents
|
|
dc->SetMapMode(wxMM_TEXT);
|
|
dc->SetBackgroundMode(wxBRUSHSTYLE_TRANSPARENT);
|
|
dc->SetLayoutDirection(GetLayoutDirection());
|
|
|
|
wxHtmlRenderingInfo rinfo;
|
|
wxDefaultHtmlRenderingStyle rstyle;
|
|
rinfo.SetSelection(m_selection);
|
|
rinfo.SetStyle(&rstyle);
|
|
m_Cell->Draw(*dc, 0, 0,
|
|
y * wxHTML_SCROLL_STEP + rect.GetTop(),
|
|
y * wxHTML_SCROLL_STEP + rect.GetBottom(),
|
|
rinfo);
|
|
|
|
#ifdef DEBUG_HTML_SELECTION
|
|
{
|
|
int xc, yc, x, y;
|
|
wxGetMousePosition(&xc, &yc);
|
|
ScreenToClient(&xc, &yc);
|
|
CalcUnscrolledPosition(xc, yc, &x, &y);
|
|
wxHtmlCell *at = m_Cell->FindCellByPos(x, y);
|
|
wxHtmlCell *before =
|
|
m_Cell->FindCellByPos(x, y, wxHTML_FIND_NEAREST_BEFORE);
|
|
wxHtmlCell *after =
|
|
m_Cell->FindCellByPos(x, y, wxHTML_FIND_NEAREST_AFTER);
|
|
|
|
dc->SetBrush(*wxTRANSPARENT_BRUSH);
|
|
dc->SetPen(*wxBLACK_PEN);
|
|
if (at)
|
|
dc->DrawRectangle(at->GetAbsPos(),
|
|
wxSize(at->GetWidth(),at->GetHeight()));
|
|
dc->SetPen(*wxGREEN_PEN);
|
|
if (before)
|
|
dc->DrawRectangle(before->GetAbsPos().x+1, before->GetAbsPos().y+1,
|
|
before->GetWidth()-2,before->GetHeight()-2);
|
|
dc->SetPen(*wxRED_PEN);
|
|
if (after)
|
|
dc->DrawRectangle(after->GetAbsPos().x+2, after->GetAbsPos().y+2,
|
|
after->GetWidth()-4,after->GetHeight()-4);
|
|
}
|
|
#endif // DEBUG_HTML_SELECTION
|
|
|
|
if ( dc != &dcPaint )
|
|
{
|
|
dc->SetDeviceOrigin(0,0);
|
|
dcPaint.Blit(0, rect.GetTop(),
|
|
sz.x, rect.GetBottom() - rect.GetTop() + 1,
|
|
dc,
|
|
0, rect.GetTop());
|
|
}
|
|
}
|
|
|
|
|
|
|
|
|
|
void wxHtmlWindow::OnSize(wxSizeEvent& event)
|
|
{
|
|
event.Skip();
|
|
|
|
m_backBuffer = wxNullBitmap;
|
|
|
|
CreateLayout();
|
|
|
|
// Recompute selection if necessary:
|
|
if ( m_selection )
|
|
{
|
|
m_selection->Set(m_selection->GetFromCell(),
|
|
m_selection->GetToCell());
|
|
m_selection->ClearFromToCharacterPos();
|
|
}
|
|
|
|
Refresh();
|
|
}
|
|
|
|
|
|
void wxHtmlWindow::OnMouseMove(wxMouseEvent& WXUNUSED(event))
|
|
{
|
|
wxHtmlWindowMouseHelper::HandleMouseMoved();
|
|
}
|
|
|
|
void wxHtmlWindow::OnMouseDown(wxMouseEvent& event)
|
|
{
|
|
#if wxUSE_CLIPBOARD
|
|
if ( event.LeftDown() && IsSelectionEnabled() )
|
|
{
|
|
const long TRIPLECLICK_LEN = 200; // 0.2 sec after doubleclick
|
|
if ( wxGetLocalTimeMillis() - m_lastDoubleClick <= TRIPLECLICK_LEN )
|
|
{
|
|
SelectLine(CalcUnscrolledPosition(event.GetPosition()));
|
|
|
|
(void) CopySelection();
|
|
}
|
|
else
|
|
{
|
|
m_makingSelection = true;
|
|
|
|
if ( m_selection )
|
|
{
|
|
wxDELETE(m_selection);
|
|
Refresh();
|
|
}
|
|
m_tmpSelFromPos = CalcUnscrolledPosition(event.GetPosition());
|
|
m_tmpSelFromCell = NULL;
|
|
|
|
CaptureMouse();
|
|
}
|
|
}
|
|
#endif // wxUSE_CLIPBOARD
|
|
|
|
// in any case, let the default handler set focus to this window
|
|
event.Skip();
|
|
}
|
|
|
|
void wxHtmlWindow::OnMouseUp(wxMouseEvent& event)
|
|
{
|
|
#if wxUSE_CLIPBOARD
|
|
if ( m_makingSelection )
|
|
{
|
|
ReleaseMouse();
|
|
m_makingSelection = false;
|
|
|
|
// if m_selection=NULL, the user didn't move the mouse far enough from
|
|
// starting point and the mouse up event is part of a click, the user
|
|
// is not selecting text:
|
|
if ( m_selection )
|
|
{
|
|
CopySelection(Primary);
|
|
|
|
// we don't want mouse up event that ended selecting to be
|
|
// handled as mouse click and e.g. follow hyperlink:
|
|
return;
|
|
}
|
|
}
|
|
#endif // wxUSE_CLIPBOARD
|
|
|
|
wxPoint pos = CalcUnscrolledPosition(event.GetPosition());
|
|
if ( !wxHtmlWindowMouseHelper::HandleMouseClick(m_Cell, pos, event) )
|
|
event.Skip();
|
|
}
|
|
|
|
#if wxUSE_CLIPBOARD
|
|
void wxHtmlWindow::OnMouseCaptureLost(wxMouseCaptureLostEvent& WXUNUSED(event))
|
|
{
|
|
if ( !m_makingSelection )
|
|
return;
|
|
|
|
// discard the selecting operation
|
|
m_makingSelection = false;
|
|
wxDELETE(m_selection);
|
|
m_tmpSelFromCell = NULL;
|
|
Refresh();
|
|
}
|
|
#endif // wxUSE_CLIPBOARD
|
|
|
|
|
|
void wxHtmlWindow::OnInternalIdle()
|
|
{
|
|
wxWindow::OnInternalIdle();
|
|
|
|
if (m_Cell != NULL && DidMouseMove())
|
|
{
|
|
#ifdef DEBUG_HTML_SELECTION
|
|
Refresh();
|
|
#endif
|
|
int xc, yc, x, y;
|
|
wxGetMousePosition(&xc, &yc);
|
|
ScreenToClient(&xc, &yc);
|
|
CalcUnscrolledPosition(xc, yc, &x, &y);
|
|
|
|
wxHtmlCell *cell = m_Cell->FindCellByPos(x, y);
|
|
|
|
// handle selection update:
|
|
if ( m_makingSelection )
|
|
{
|
|
if ( !m_tmpSelFromCell )
|
|
m_tmpSelFromCell = m_Cell->FindCellByPos(
|
|
m_tmpSelFromPos.x,m_tmpSelFromPos.y);
|
|
|
|
// NB: a trick - we adjust selFromPos to be upper left or bottom
|
|
// right corner of the first cell of the selection depending
|
|
// on whether the mouse is moving to the right or to the left.
|
|
// This gives us more "natural" behaviour when selecting
|
|
// a line (specifically, first cell of the next line is not
|
|
// included if you drag selection from left to right over
|
|
// entire line):
|
|
wxPoint dirFromPos;
|
|
if ( !m_tmpSelFromCell )
|
|
{
|
|
dirFromPos = m_tmpSelFromPos;
|
|
}
|
|
else
|
|
{
|
|
dirFromPos = m_tmpSelFromCell->GetAbsPos();
|
|
if ( x < m_tmpSelFromPos.x )
|
|
{
|
|
dirFromPos.x += m_tmpSelFromCell->GetWidth();
|
|
dirFromPos.y += m_tmpSelFromCell->GetHeight();
|
|
}
|
|
}
|
|
bool goingDown = dirFromPos.y < y ||
|
|
(dirFromPos.y == y && dirFromPos.x < x);
|
|
|
|
// determine selection span:
|
|
if ( /*still*/ !m_tmpSelFromCell )
|
|
{
|
|
if (goingDown)
|
|
{
|
|
m_tmpSelFromCell = m_Cell->FindCellByPos(
|
|
m_tmpSelFromPos.x,m_tmpSelFromPos.y,
|
|
wxHTML_FIND_NEAREST_AFTER);
|
|
if (!m_tmpSelFromCell)
|
|
m_tmpSelFromCell = m_Cell->GetFirstTerminal();
|
|
}
|
|
else
|
|
{
|
|
m_tmpSelFromCell = m_Cell->FindCellByPos(
|
|
m_tmpSelFromPos.x,m_tmpSelFromPos.y,
|
|
wxHTML_FIND_NEAREST_BEFORE);
|
|
if (!m_tmpSelFromCell)
|
|
m_tmpSelFromCell = m_Cell->GetLastTerminal();
|
|
}
|
|
}
|
|
|
|
wxHtmlCell *selcell = cell;
|
|
if (!selcell)
|
|
{
|
|
if (goingDown)
|
|
{
|
|
selcell = m_Cell->FindCellByPos(x, y,
|
|
wxHTML_FIND_NEAREST_BEFORE);
|
|
if (!selcell)
|
|
selcell = m_Cell->GetLastTerminal();
|
|
}
|
|
else
|
|
{
|
|
selcell = m_Cell->FindCellByPos(x, y,
|
|
wxHTML_FIND_NEAREST_AFTER);
|
|
if (!selcell)
|
|
selcell = m_Cell->GetFirstTerminal();
|
|
}
|
|
}
|
|
|
|
// NB: it may *rarely* happen that the code above didn't find one
|
|
// of the cells, e.g. if wxHtmlWindow doesn't contain any
|
|
// visible cells.
|
|
if ( selcell && m_tmpSelFromCell )
|
|
{
|
|
if ( !m_selection )
|
|
{
|
|
// start selecting only if mouse movement was big enough
|
|
// (otherwise it was meant as mouse click, not selection):
|
|
const int PRECISION = 2;
|
|
wxPoint diff = m_tmpSelFromPos - wxPoint(x,y);
|
|
if (abs(diff.x) > PRECISION || abs(diff.y) > PRECISION)
|
|
{
|
|
m_selection = new wxHtmlSelection();
|
|
}
|
|
}
|
|
if ( m_selection )
|
|
{
|
|
if ( m_tmpSelFromCell->IsBefore(selcell) )
|
|
{
|
|
m_selection->Set(m_tmpSelFromPos, m_tmpSelFromCell,
|
|
wxPoint(x,y), selcell);
|
|
}
|
|
else
|
|
{
|
|
m_selection->Set(wxPoint(x,y), selcell,
|
|
m_tmpSelFromPos, m_tmpSelFromCell);
|
|
}
|
|
m_selection->ClearFromToCharacterPos();
|
|
Refresh();
|
|
}
|
|
}
|
|
}
|
|
|
|
// handle cursor and status bar text changes:
|
|
|
|
// NB: because we're passing in 'cell' and not 'm_Cell' (so that the
|
|
// leaf cell lookup isn't done twice), we need to adjust the
|
|
// position for the new root:
|
|
wxPoint posInCell(x, y);
|
|
if (cell)
|
|
posInCell -= cell->GetAbsPos();
|
|
wxHtmlWindowMouseHelper::HandleIdle(cell, posInCell);
|
|
}
|
|
}
|
|
|
|
#if wxUSE_CLIPBOARD
|
|
void wxHtmlWindow::StopAutoScrolling()
|
|
{
|
|
if ( m_timerAutoScroll )
|
|
{
|
|
wxDELETE(m_timerAutoScroll);
|
|
}
|
|
}
|
|
|
|
void wxHtmlWindow::OnMouseEnter(wxMouseEvent& event)
|
|
{
|
|
StopAutoScrolling();
|
|
event.Skip();
|
|
}
|
|
|
|
void wxHtmlWindow::OnMouseLeave(wxMouseEvent& event)
|
|
{
|
|
// don't prevent the usual processing of the event from taking place
|
|
event.Skip();
|
|
|
|
// when a captured mouse leave a scrolled window we start generate
|
|
// scrolling events to allow, for example, extending selection beyond the
|
|
// visible area in some controls
|
|
if ( wxWindow::GetCapture() == this )
|
|
{
|
|
// where is the mouse leaving?
|
|
int pos, orient;
|
|
wxPoint pt = event.GetPosition();
|
|
if ( pt.x < 0 )
|
|
{
|
|
orient = wxHORIZONTAL;
|
|
pos = 0;
|
|
}
|
|
else if ( pt.y < 0 )
|
|
{
|
|
orient = wxVERTICAL;
|
|
pos = 0;
|
|
}
|
|
else // we're lower or to the right of the window
|
|
{
|
|
wxSize size = GetClientSize();
|
|
if ( pt.x > size.x )
|
|
{
|
|
orient = wxHORIZONTAL;
|
|
pos = GetVirtualSize().x / wxHTML_SCROLL_STEP;
|
|
}
|
|
else if ( pt.y > size.y )
|
|
{
|
|
orient = wxVERTICAL;
|
|
pos = GetVirtualSize().y / wxHTML_SCROLL_STEP;
|
|
}
|
|
else // this should be impossible
|
|
{
|
|
// but seems to happen sometimes under wxMSW - maybe it's a bug
|
|
// there but for now just ignore it
|
|
|
|
//wxFAIL_MSG( wxT("can't understand where has mouse gone") );
|
|
|
|
return;
|
|
}
|
|
}
|
|
|
|
// only start the auto scroll timer if the window can be scrolled in
|
|
// this direction
|
|
if ( !HasScrollbar(orient) )
|
|
return;
|
|
|
|
delete m_timerAutoScroll;
|
|
m_timerAutoScroll = new wxHtmlWinAutoScrollTimer
|
|
(
|
|
this,
|
|
pos == 0 ? wxEVT_SCROLLWIN_LINEUP
|
|
: wxEVT_SCROLLWIN_LINEDOWN,
|
|
pos,
|
|
orient
|
|
);
|
|
m_timerAutoScroll->Start(50); // FIXME: make configurable
|
|
}
|
|
}
|
|
|
|
void wxHtmlWindow::OnKeyUp(wxKeyEvent& event)
|
|
{
|
|
if ( IsSelectionEnabled() &&
|
|
(event.GetKeyCode() == 'C' && event.CmdDown()) )
|
|
{
|
|
wxClipboardTextEvent evt(wxEVT_COMMAND_TEXT_COPY, GetId());
|
|
|
|
evt.SetEventObject(this);
|
|
|
|
GetEventHandler()->ProcessEvent(evt);
|
|
}
|
|
else
|
|
{
|
|
event.Skip();
|
|
}
|
|
}
|
|
|
|
void wxHtmlWindow::OnCopy(wxCommandEvent& WXUNUSED(event))
|
|
{
|
|
(void) CopySelection();
|
|
}
|
|
|
|
void wxHtmlWindow::OnClipboardEvent(wxClipboardTextEvent& WXUNUSED(event))
|
|
{
|
|
(void) CopySelection();
|
|
}
|
|
|
|
void wxHtmlWindow::OnDoubleClick(wxMouseEvent& event)
|
|
{
|
|
// select word under cursor:
|
|
if ( IsSelectionEnabled() )
|
|
{
|
|
SelectWord(CalcUnscrolledPosition(event.GetPosition()));
|
|
|
|
(void) CopySelection(Primary);
|
|
|
|
m_lastDoubleClick = wxGetLocalTimeMillis();
|
|
}
|
|
else
|
|
event.Skip();
|
|
}
|
|
|
|
void wxHtmlWindow::SelectWord(const wxPoint& pos)
|
|
{
|
|
if ( m_Cell )
|
|
{
|
|
wxHtmlCell *cell = m_Cell->FindCellByPos(pos.x, pos.y);
|
|
if ( cell )
|
|
{
|
|
delete m_selection;
|
|
m_selection = new wxHtmlSelection();
|
|
m_selection->Set(cell, cell);
|
|
RefreshRect(wxRect(CalcScrolledPosition(cell->GetAbsPos()),
|
|
wxSize(cell->GetWidth(), cell->GetHeight())));
|
|
}
|
|
}
|
|
}
|
|
|
|
void wxHtmlWindow::SelectLine(const wxPoint& pos)
|
|
{
|
|
if ( m_Cell )
|
|
{
|
|
wxHtmlCell *cell = m_Cell->FindCellByPos(pos.x, pos.y);
|
|
if ( cell )
|
|
{
|
|
// We use following heuristic to find a "line": let the line be all
|
|
// cells in same container as the cell under mouse cursor that are
|
|
// neither completely above nor completely below the clicked cell
|
|
// (i.e. are likely to be words positioned on same line of text).
|
|
|
|
int y1 = cell->GetAbsPos().y;
|
|
int y2 = y1 + cell->GetHeight();
|
|
int y;
|
|
const wxHtmlCell *c;
|
|
const wxHtmlCell *before = NULL;
|
|
const wxHtmlCell *after = NULL;
|
|
|
|
// find last cell of line:
|
|
for ( c = cell->GetNext(); c; c = c->GetNext())
|
|
{
|
|
y = c->GetAbsPos().y;
|
|
if ( y + c->GetHeight() > y1 && y < y2 )
|
|
after = c;
|
|
else
|
|
break;
|
|
}
|
|
if ( !after )
|
|
after = cell;
|
|
|
|
// find first cell of line:
|
|
for ( c = cell->GetParent()->GetFirstChild();
|
|
c && c != cell; c = c->GetNext())
|
|
{
|
|
y = c->GetAbsPos().y;
|
|
if ( y + c->GetHeight() > y1 && y < y2 )
|
|
{
|
|
if ( ! before )
|
|
before = c;
|
|
}
|
|
else
|
|
before = NULL;
|
|
}
|
|
if ( !before )
|
|
before = cell;
|
|
|
|
delete m_selection;
|
|
m_selection = new wxHtmlSelection();
|
|
m_selection->Set(before, after);
|
|
|
|
Refresh();
|
|
}
|
|
}
|
|
}
|
|
|
|
void wxHtmlWindow::SelectAll()
|
|
{
|
|
if ( m_Cell )
|
|
{
|
|
delete m_selection;
|
|
m_selection = new wxHtmlSelection();
|
|
m_selection->Set(m_Cell->GetFirstTerminal(), m_Cell->GetLastTerminal());
|
|
Refresh();
|
|
}
|
|
}
|
|
|
|
#endif // wxUSE_CLIPBOARD
|
|
|
|
|
|
|
|
IMPLEMENT_ABSTRACT_CLASS(wxHtmlProcessor,wxObject)
|
|
|
|
wxBEGIN_PROPERTIES_TABLE(wxHtmlWindow)
|
|
/*
|
|
TODO PROPERTIES
|
|
style , wxHW_SCROLLBAR_AUTO
|
|
borders , (dimension)
|
|
url , string
|
|
htmlcode , string
|
|
*/
|
|
wxEND_PROPERTIES_TABLE()
|
|
|
|
wxBEGIN_HANDLERS_TABLE(wxHtmlWindow)
|
|
wxEND_HANDLERS_TABLE()
|
|
|
|
wxCONSTRUCTOR_5( wxHtmlWindow , wxWindow* , Parent , wxWindowID , Id , wxPoint , Position , wxSize , Size , long , WindowStyle )
|
|
|
|
wxIMPLEMENT_DYNAMIC_CLASS_XTI(wxHtmlWindow, wxScrolledWindow,"wx/html/htmlwin.h")
|
|
|
|
BEGIN_EVENT_TABLE(wxHtmlWindow, wxScrolledWindow)
|
|
EVT_SIZE(wxHtmlWindow::OnSize)
|
|
EVT_LEFT_DOWN(wxHtmlWindow::OnMouseDown)
|
|
EVT_LEFT_UP(wxHtmlWindow::OnMouseUp)
|
|
EVT_RIGHT_UP(wxHtmlWindow::OnMouseUp)
|
|
EVT_MOTION(wxHtmlWindow::OnMouseMove)
|
|
EVT_PAINT(wxHtmlWindow::OnPaint)
|
|
EVT_ERASE_BACKGROUND(wxHtmlWindow::OnEraseBackground)
|
|
#if wxUSE_CLIPBOARD
|
|
EVT_LEFT_DCLICK(wxHtmlWindow::OnDoubleClick)
|
|
EVT_ENTER_WINDOW(wxHtmlWindow::OnMouseEnter)
|
|
EVT_LEAVE_WINDOW(wxHtmlWindow::OnMouseLeave)
|
|
EVT_MOUSE_CAPTURE_LOST(wxHtmlWindow::OnMouseCaptureLost)
|
|
EVT_KEY_UP(wxHtmlWindow::OnKeyUp)
|
|
EVT_MENU(wxID_COPY, wxHtmlWindow::OnCopy)
|
|
EVT_TEXT_COPY(wxID_ANY, wxHtmlWindow::OnClipboardEvent)
|
|
#endif // wxUSE_CLIPBOARD
|
|
END_EVENT_TABLE()
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// wxHtmlWindowInterface implementation in wxHtmlWindow
|
|
//-----------------------------------------------------------------------------
|
|
|
|
void wxHtmlWindow::SetHTMLWindowTitle(const wxString& title)
|
|
{
|
|
OnSetTitle(title);
|
|
}
|
|
|
|
void wxHtmlWindow::OnHTMLLinkClicked(const wxHtmlLinkInfo& link)
|
|
{
|
|
OnLinkClicked(link);
|
|
}
|
|
|
|
wxHtmlOpeningStatus wxHtmlWindow::OnHTMLOpeningURL(wxHtmlURLType type,
|
|
const wxString& url,
|
|
wxString *redirect) const
|
|
{
|
|
return OnOpeningURL(type, url, redirect);
|
|
}
|
|
|
|
wxPoint wxHtmlWindow::HTMLCoordsToWindow(wxHtmlCell *WXUNUSED(cell),
|
|
const wxPoint& pos) const
|
|
{
|
|
return CalcScrolledPosition(pos);
|
|
}
|
|
|
|
wxWindow* wxHtmlWindow::GetHTMLWindow()
|
|
{
|
|
return this;
|
|
}
|
|
|
|
wxColour wxHtmlWindow::GetHTMLBackgroundColour() const
|
|
{
|
|
return GetBackgroundColour();
|
|
}
|
|
|
|
void wxHtmlWindow::SetHTMLBackgroundColour(const wxColour& clr)
|
|
{
|
|
SetBackgroundColour(clr);
|
|
}
|
|
|
|
void wxHtmlWindow::SetHTMLBackgroundImage(const wxBitmap& bmpBg)
|
|
{
|
|
SetBackgroundImage(bmpBg);
|
|
}
|
|
|
|
void wxHtmlWindow::SetHTMLStatusText(const wxString& text)
|
|
{
|
|
#if wxUSE_STATUSBAR
|
|
if (m_RelatedStatusBarIndex != -1)
|
|
{
|
|
if (m_RelatedStatusBar)
|
|
{
|
|
m_RelatedStatusBar->SetStatusText(text, m_RelatedStatusBarIndex);
|
|
}
|
|
else if (m_RelatedFrame)
|
|
{
|
|
m_RelatedFrame->SetStatusText(text, m_RelatedStatusBarIndex);
|
|
}
|
|
}
|
|
#else
|
|
wxUnusedVar(text);
|
|
#endif // wxUSE_STATUSBAR
|
|
}
|
|
|
|
/*static*/
|
|
wxCursor wxHtmlWindow::GetDefaultHTMLCursor(HTMLCursor type)
|
|
{
|
|
switch (type)
|
|
{
|
|
case HTMLCursor_Link:
|
|
if ( !ms_cursorLink )
|
|
ms_cursorLink = new wxCursor(wxCURSOR_HAND);
|
|
return *ms_cursorLink;
|
|
|
|
case HTMLCursor_Text:
|
|
if ( !ms_cursorText )
|
|
ms_cursorText = new wxCursor(wxCURSOR_IBEAM);
|
|
return *ms_cursorText;
|
|
|
|
case HTMLCursor_Default:
|
|
default:
|
|
return *wxSTANDARD_CURSOR;
|
|
}
|
|
}
|
|
|
|
wxCursor wxHtmlWindow::GetHTMLCursor(HTMLCursor type) const
|
|
{
|
|
return GetDefaultHTMLCursor(type);
|
|
}
|
|
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// wxHtmlWinModule
|
|
//-----------------------------------------------------------------------------
|
|
|
|
// A module to allow initialization/cleanup
|
|
// without calling these functions from app.cpp or from
|
|
// the user's application.
|
|
|
|
class wxHtmlWinModule: public wxModule
|
|
{
|
|
DECLARE_DYNAMIC_CLASS(wxHtmlWinModule)
|
|
public:
|
|
wxHtmlWinModule() : wxModule() {}
|
|
bool OnInit() { return true; }
|
|
void OnExit() { wxHtmlWindow::CleanUpStatics(); }
|
|
};
|
|
|
|
IMPLEMENT_DYNAMIC_CLASS(wxHtmlWinModule, wxModule)
|
|
|
|
|
|
// This hack forces the linker to always link in m_* files
|
|
// (wxHTML doesn't work without handlers from these files)
|
|
#include "wx/html/forcelnk.h"
|
|
FORCE_WXHTML_MODULES()
|
|
|
|
#endif // wxUSE_HTML
|