Merge branch 'fix-html-selection'

Improvements to wxHtmlWindow selection handling: use inactive background
for it when the window doesn't have focus; allow copying it with
Ctrl-Ins and fix annoying changes in layout while selecting.

Closes https://github.com/vadz/wxWidgets/pull/12
This commit is contained in:
Vadim Zeitlin
2019-10-05 14:58:38 +02:00
5 changed files with 178 additions and 24 deletions

View File

@@ -35,7 +35,8 @@ public:
wxHtmlSelection()
: m_fromPos(wxDefaultPosition), m_toPos(wxDefaultPosition),
m_fromCharacterPos(-1), m_toCharacterPos(-1),
m_fromCell(NULL), m_toCell(NULL) {}
m_fromCell(NULL), m_toCell(NULL),
m_extBeforeSel(0), m_extBeforeSelEnd(0) {}
// this version is used for the user selection defined with the mouse
void Set(const wxPoint& fromPos, const wxHtmlCell *fromCell,
@@ -58,6 +59,11 @@ public:
wxCoord GetFromCharacterPos () const { return m_fromCharacterPos; }
wxCoord GetToCharacterPos () const { return m_toCharacterPos; }
void SetExtentBeforeSelection(unsigned ext) { m_extBeforeSel = ext; }
void SetExtentBeforeSelectionEnd(unsigned ext) { m_extBeforeSelEnd = ext; }
unsigned GetExtentBeforeSelection() const { return m_extBeforeSel; }
unsigned GetExtentBeforeSelectionEnd() const { return m_extBeforeSelEnd; }
bool IsEmpty() const
{ return m_fromPos == wxDefaultPosition &&
m_toPos == wxDefaultPosition; }
@@ -66,6 +72,12 @@ private:
wxPoint m_fromPos, m_toPos;
wxCoord m_fromCharacterPos, m_toCharacterPos;
const wxHtmlCell *m_fromCell, *m_toCell;
// Extent of the text before selection start.
unsigned m_extBeforeSel;
// Extent of the text from the beginning to the selection end.
unsigned m_extBeforeSelEnd;
};
@@ -115,8 +127,17 @@ public:
class WXDLLIMPEXP_HTML wxDefaultHtmlRenderingStyle : public wxHtmlRenderingStyle
{
public:
explicit wxDefaultHtmlRenderingStyle(const wxWindowBase* wnd = NULL)
: m_wnd(wnd)
{}
virtual wxColour GetSelectedTextColour(const wxColour& clr) wxOVERRIDE;
virtual wxColour GetSelectedTextBgColour(const wxColour& clr) wxOVERRIDE;
private:
const wxWindowBase* const m_wnd;
wxDECLARE_NO_COPY_CLASS(wxDefaultHtmlRenderingStyle);
};
@@ -320,7 +341,12 @@ public:
// Returns absolute position of the cell on HTML canvas.
// If rootCell is provided, then it's considered to be the root of the
// hierarchy and the returned value is relative to it.
wxPoint GetAbsPos(wxHtmlCell *rootCell = NULL) const;
wxPoint GetAbsPos(const wxHtmlCell *rootCell = NULL) const;
// Returns minimum bounding rectangle of this cell in coordinates, relative
// to the rootCell, if it is provided, or relative to the result of
// GetRootCell() if the rootCell is NULL.
wxRect GetRect(const wxHtmlCell *rootCell = NULL) const;
// Returns root cell of the hierarchy (i.e. grand-grand-...-parent that
// doesn't have a parent itself)
@@ -417,7 +443,8 @@ protected:
void SetSelectionPrivPos(const wxDC& dc, wxHtmlSelection *s) const;
void Split(const wxDC& dc,
const wxPoint& selFrom, const wxPoint& selTo,
unsigned& pos1, unsigned& pos2) const;
unsigned& pos1, unsigned& pos2,
unsigned& ext1, unsigned& ext2) const;
wxString m_Word;
bool m_allowLinebreak;

View File

@@ -408,6 +408,7 @@ protected:
void OnMouseMove(wxMouseEvent& event);
void OnMouseDown(wxMouseEvent& event);
void OnMouseUp(wxMouseEvent& event);
void OnFocusEvent(wxFocusEvent& event);
#if wxUSE_CLIPBOARD
void OnKeyUp(wxKeyEvent& event);
void OnDoubleClick(wxMouseEvent& event);

View File

@@ -159,7 +159,9 @@ private:
class wxHtmlListBoxStyle : public wxDefaultHtmlRenderingStyle
{
public:
wxHtmlListBoxStyle(const wxHtmlListBox& hlbox) : m_hlbox(hlbox) { }
wxHtmlListBoxStyle(const wxHtmlListBox& hlbox)
: wxDefaultHtmlRenderingStyle(&hlbox), m_hlbox(hlbox)
{ }
virtual wxColour GetSelectedTextColour(const wxColour& colFg) wxOVERRIDE
{

View File

@@ -65,7 +65,12 @@ wxColour
wxDefaultHtmlRenderingStyle::
GetSelectedTextBgColour(const wxColour& WXUNUSED(clr))
{
return wxSystemSettings::GetColour(wxSYS_COLOUR_HIGHLIGHT);
// By default we use the fixed standard selection colour, but if we're
// associated with a window use the colour appropriate for the window
// state, i.e. grey out selection when it's not in focus.
return wxSystemSettings::GetColour(!m_wnd || m_wnd->HasFocus() ?
wxSYS_COLOUR_HIGHLIGHT : wxSYS_COLOUR_BTNSHADOW);
}
@@ -216,10 +221,10 @@ wxHtmlCell *wxHtmlCell::FindCellByPos(wxCoord x, wxCoord y,
}
wxPoint wxHtmlCell::GetAbsPos(wxHtmlCell *rootCell) const
wxPoint wxHtmlCell::GetAbsPos(const wxHtmlCell *rootCell) const
{
wxPoint p(m_PosX, m_PosY);
for (wxHtmlCell *parent = m_Parent; parent && parent != rootCell;
for (const wxHtmlCell *parent = m_Parent; parent && parent != rootCell;
parent = parent->m_Parent)
{
p.x += parent->m_PosX;
@@ -228,6 +233,11 @@ wxPoint wxHtmlCell::GetAbsPos(wxHtmlCell *rootCell) const
return p;
}
wxRect wxHtmlCell::GetRect(const wxHtmlCell* rootCell) const
{
return wxRect(GetAbsPos(rootCell), wxSize(m_Width, m_Height));
}
wxHtmlCell *wxHtmlCell::GetRootCell() const
{
wxHtmlCell *c = wxConstCast(this, wxHtmlCell);
@@ -333,7 +343,8 @@ void wxHtmlWordCell::SetPreviousWord(wxHtmlWordCell *cell)
// where s2 and s3 start:
void wxHtmlWordCell::Split(const wxDC& dc,
const wxPoint& selFrom, const wxPoint& selTo,
unsigned& pos1, unsigned& pos2) const
unsigned& pos1, unsigned& pos2,
unsigned& ext1, unsigned& ext2) const
{
wxPoint pt1 = (selFrom == wxDefaultPosition) ?
wxDefaultPosition : selFrom - GetAbsPos();
@@ -390,21 +401,30 @@ void wxHtmlWordCell::Split(const wxDC& dc,
pos2 = j;
wxASSERT( pos2 >= pos1 );
ext1 = pos1 == 0 ? 0 : (pos1 < widths.size() ? widths[pos1-1] : widths.Last());
ext2 = pos2 == 0 ? 0 : (pos2 < widths.size() ? widths[pos2-1] : widths.Last());
}
void wxHtmlWordCell::SetSelectionPrivPos(const wxDC& dc, wxHtmlSelection *s) const
{
unsigned p1, p2;
unsigned p1, p2, ext1, ext2;
Split(dc,
this == s->GetFromCell() ? s->GetFromPos() : wxDefaultPosition,
this == s->GetToCell() ? s->GetToPos() : wxDefaultPosition,
p1, p2);
p1, p2, ext1, ext2);
if ( this == s->GetFromCell() )
s->SetFromCharacterPos (p1); // selection starts here
{
s->SetFromCharacterPos(p1); // selection starts here
s->SetExtentBeforeSelection(ext1);
}
if ( this == s->GetToCell() )
s->SetToCharacterPos (p2); // selection ends here
{
s->SetToCharacterPos(p2); // selection ends here
s->SetExtentBeforeSelectionEnd(ext2);
}
}
@@ -449,7 +469,6 @@ void wxHtmlWordCell::Draw(wxDC& dc, int x, int y,
// Selection changing, we must draw the word piecewise:
wxHtmlSelection *s = info.GetSelection();
wxString txt;
int w, h;
int ofs = 0;
// NB: this is quite a hack: in order to compute selection boundaries
@@ -469,8 +488,7 @@ void wxHtmlWordCell::Draw(wxDC& dc, int x, int y,
{
txt = m_Word.Mid(0, part1);
dc.DrawText(txt, x + m_PosX, y + m_PosY);
dc.GetTextExtent(txt, &w, &h);
ofs += w;
ofs += s->GetExtentBeforeSelection();
}
SwitchSelState(dc, info, true);
@@ -480,11 +498,9 @@ void wxHtmlWordCell::Draw(wxDC& dc, int x, int y,
if ( (size_t)part2 < m_Word.length() )
{
dc.GetTextExtent(txt, &w, &h);
ofs += w;
SwitchSelState(dc, info, false);
txt = m_Word.Mid(part2);
dc.DrawText(txt, ofs + x + m_PosX, y + m_PosY);
dc.DrawText(txt, x + m_PosX + s->GetExtentBeforeSelectionEnd(), y + m_PosY);
}
else
drawSelectionAfterCell = true;

View File

@@ -1138,7 +1138,7 @@ void wxHtmlWindow::OnPaint(wxPaintEvent& WXUNUSED(event))
dc->SetLayoutDirection(GetLayoutDirection());
wxHtmlRenderingInfo rinfo;
wxDefaultHtmlRenderingStyle rstyle;
wxDefaultHtmlRenderingStyle rstyle(this);
rinfo.SetSelection(m_selection);
rinfo.SetStyle(&rstyle);
m_Cell->Draw(*dc, 0, 0,
@@ -1184,7 +1184,115 @@ void wxHtmlWindow::OnPaint(wxPaintEvent& WXUNUSED(event))
}
}
namespace
{
// Returns true if leftCell is an ancestor of rightCell.
bool IsAncestor(const wxHtmlCell* leftCell, const wxHtmlCell* rightCell)
{
for ( const wxHtmlCell* parent = rightCell->GetParent();
parent; parent = parent->GetParent() )
{
if ( leftCell == parent )
return true;
}
return false;
}
// Returns minimum bounding rectangle of all the cells between fromCell
// and toCell, inclusive.
wxRect GetBoundingRect(const wxHtmlCell* const fromCell,
const wxHtmlCell* const toCell)
{
wxCHECK_MSG(fromCell || toCell, wxRect(), "At least one cell is required");
// Check if we have only one cell or the cells are equal.
if ( !fromCell )
return toCell->GetRect();
else if ( !toCell || fromCell == toCell )
return fromCell->GetRect();
// Check if one of the cells is an ancestor of the other.
if ( IsAncestor(fromCell, toCell) )
return fromCell->GetRect();
else if ( IsAncestor(toCell, fromCell) )
return toCell->GetRect();
// Combine MBRs, starting with the fromCell.
wxRect boundingRect = fromCell->GetRect();
// For each subtree toward the lowest common ancestor,
// combine MBRs until the (subtree of) toCell is reached.
for ( const wxHtmlCell *startCell = fromCell,
*parent = fromCell->GetParent();
parent;
startCell = parent, parent = parent->GetParent() )
{
if ( IsAncestor(parent, toCell) )
{
// Combine all the cells up to the toCell or its subtree.
for ( const wxHtmlCell* nextCell = startCell->GetNext();
nextCell;
nextCell = nextCell->GetNext() )
{
if ( nextCell == toCell )
return boundingRect.Union(toCell->GetRect());
if ( IsAncestor(nextCell, toCell) )
{
return boundingRect.Union(GetBoundingRect(
nextCell->GetFirstTerminal(), toCell));
}
boundingRect.Union(nextCell->GetRect());
}
wxFAIL_MSG("Unexpected: toCell is not reachable from the fromCell");
return GetBoundingRect(toCell, fromCell);
}
else
{
// Combine rest of current container.
for ( const wxHtmlCell* nextCell = startCell->GetNext();
nextCell;
nextCell = nextCell->GetNext() )
{
boundingRect.Union(nextCell->GetRect());
}
}
}
wxFAIL_MSG("The cells have no common ancestor");
return wxRect();
}
} // namespace
void wxHtmlWindow::OnFocusEvent(wxFocusEvent& event)
{
event.Skip();
// Redraw selection, because its background colour depends on
// whether the window has keyboard focus or not.
if ( !m_selection || m_selection->IsEmpty() )
return;
const wxHtmlCell* fromCell = m_selection->GetFromCell();
const wxHtmlCell* toCell = m_selection->GetToCell();
wxCHECK_RET(fromCell || toCell,
"Unexpected: selection is set but cells are not");
wxRect boundingRect = GetBoundingRect(fromCell, toCell);
boundingRect = wxRect
(
CalcScrolledPosition(boundingRect.GetTopLeft()),
CalcScrolledPosition(boundingRect.GetBottomRight())
);
RefreshRect(boundingRect);
}
void wxHtmlWindow::OnSize(wxSizeEvent& event)
@@ -1502,14 +1610,12 @@ void wxHtmlWindow::OnMouseLeave(wxMouseEvent& event)
void wxHtmlWindow::OnKeyUp(wxKeyEvent& event)
{
if ( IsSelectionEnabled() &&
(event.GetKeyCode() == 'C' && event.CmdDown()) )
if ( IsSelectionEnabled() && event.GetModifiers() == wxMOD_CONTROL &&
(event.GetKeyCode() == 'C' || event.GetKeyCode() == WXK_INSERT) )
{
wxClipboardTextEvent evt(wxEVT_TEXT_COPY, GetId());
evt.SetEventObject(this);
GetEventHandler()->ProcessEvent(evt);
ProcessWindowEvent(evt);
}
else
{
@@ -1656,6 +1762,8 @@ wxBEGIN_EVENT_TABLE(wxHtmlWindow, wxScrolledWindow)
EVT_MOTION(wxHtmlWindow::OnMouseMove)
EVT_PAINT(wxHtmlWindow::OnPaint)
EVT_ERASE_BACKGROUND(wxHtmlWindow::OnEraseBackground)
EVT_SET_FOCUS(wxHtmlWindow::OnFocusEvent)
EVT_KILL_FOCUS(wxHtmlWindow::OnFocusEvent)
#if wxUSE_CLIPBOARD
EVT_LEFT_DCLICK(wxHtmlWindow::OnDoubleClick)
EVT_ENTER_WINDOW(wxHtmlWindow::OnMouseEnter)