From cc2aecdef579e57f84eaa07cc72f62a43fc474ba Mon Sep 17 00:00:00 2001 From: Julian Smart Date: Wed, 25 Jan 2012 15:10:09 +0000 Subject: [PATCH] Added further API for intercepting deletion and content insertion Added simple implementation of locked objects to sample git-svn-id: https://svn.wxwidgets.org/svn/wx/wxWidgets/trunk@70465 c3d73ce0-8a6f-49c7-b76d-6d57e0e08775 --- include/wx/richtext/richtextbuffer.h | 6 + include/wx/richtext/richtextctrl.h | 18 +++ interface/wx/richtext/richtextbuffer.h | 6 + interface/wx/richtext/richtextctrl.h | 18 +++ samples/richtext/richtext.cpp | 172 +++++++++++++++++++++++- src/richtext/richtextbuffer.cpp | 13 ++ src/richtext/richtextctrl.cpp | 177 +++++++++++++++++++------ 7 files changed, 369 insertions(+), 41 deletions(-) diff --git a/include/wx/richtext/richtextbuffer.h b/include/wx/richtext/richtextbuffer.h index 87c39441ec..cdcd75b837 100644 --- a/include/wx/richtext/richtextbuffer.h +++ b/include/wx/richtext/richtextbuffer.h @@ -3231,6 +3231,12 @@ public: virtual wxRichTextObject* Clone() const { return new wxRichTextParagraphLayoutBox(*this); } + /** + Prepares the content just before insertion (or after buffer reset). + Currently is only called if undo mode is on. + */ + virtual void PrepareContent(wxRichTextParagraphLayoutBox& container); + /** Insert fragment into this box at the given position. If partialParagraph is true, it is assumed that the last (or only) paragraph is just a piece of data with no paragraph diff --git a/include/wx/richtext/richtextctrl.h b/include/wx/richtext/richtextctrl.h index 2007281245..87f085122c 100644 --- a/include/wx/richtext/richtextctrl.h +++ b/include/wx/richtext/richtextctrl.h @@ -1658,6 +1658,24 @@ public: */ virtual wxString GetPropertiesMenuLabel(wxRichTextObject* obj) { return obj->GetPropertiesMenuLabel(); } + /** + Prepares the content just before insertion (or after buffer reset). Called by the same function in wxRichTextBuffer. + Currently is only called if undo mode is on. + */ + virtual void PrepareContent(wxRichTextParagraphLayoutBox& WXUNUSED(container)) {} + + /** + Can we delete this range? + Sends an event to the control. + */ + virtual bool CanDeleteRange(wxRichTextParagraphLayoutBox& container, const wxRichTextRange& range) const; + + /** + Can we insert content at this position? + Sends an event to the control. + */ + virtual bool CanInsertContent(wxRichTextParagraphLayoutBox& container, long pos) const; + // Command handlers /** diff --git a/interface/wx/richtext/richtextbuffer.h b/interface/wx/richtext/richtextbuffer.h index 86a7baf5ef..4d10e41574 100644 --- a/interface/wx/richtext/richtextbuffer.h +++ b/interface/wx/richtext/richtextbuffer.h @@ -3111,6 +3111,12 @@ public: virtual wxRichTextObject* Clone() const { return new wxRichTextParagraphLayoutBox(*this); } + /** + Prepares the content just before insertion (or after buffer reset). + Currently is only called if undo mode is on. + */ + virtual void PrepareContent(wxRichTextParagraphLayoutBox& container); + /** Insert fragment into this box at the given position. If partialParagraph is true, it is assumed that the last (or only) paragraph is just a piece of data with no paragraph diff --git a/interface/wx/richtext/richtextctrl.h b/interface/wx/richtext/richtextctrl.h index 4252ca8e23..7cbe45a4d1 100644 --- a/interface/wx/richtext/richtextctrl.h +++ b/interface/wx/richtext/richtextctrl.h @@ -1617,6 +1617,24 @@ public: */ virtual wxString GetPropertiesMenuLabel(wxRichTextObject* obj); + /** + Prepares the content just before insertion (or after buffer reset). Called by the same function in wxRichTextBuffer. + Currently is only called if undo mode is on. + */ + virtual void PrepareContent(wxRichTextParagraphLayoutBox& WXUNUSED(container)) {} + + /** + Can we delete this range? + Sends an event to the control. + */ + virtual bool CanDeleteRange(wxRichTextParagraphLayoutBox& container, const wxRichTextRange& range) const; + + /** + Can we insert content at this position? + Sends an event to the control. + */ + virtual bool CanInsertContent(wxRichTextParagraphLayoutBox& container, long pos) const; + // Command handlers /** diff --git a/samples/richtext/richtext.cpp b/samples/richtext/richtext.cpp index 860e15d022..6198a57498 100644 --- a/samples/richtext/richtext.cpp +++ b/samples/richtext/richtext.cpp @@ -89,6 +89,49 @@ // private classes // ---------------------------------------------------------------------------- +// Define a new application type, each program should derive a class from wxApp +class MyRichTextCtrl: public wxRichTextCtrl +{ +public: + MyRichTextCtrl( wxWindow* parent, wxWindowID id = -1, const wxString& value = wxEmptyString, const wxPoint& pos = wxDefaultPosition, const wxSize& size = wxDefaultSize, + long style = wxRE_MULTILINE, const wxValidator& validator = wxDefaultValidator, const wxString& name = wxTextCtrlNameStr): + wxRichTextCtrl(parent, id, value, pos, size, style, validator, name) + { + m_lockId = 0; + m_locked = false; + } + + void SetLockId(long id) { m_lockId = id; } + long GetLockId() const { return m_lockId; } + + void BeginLock() { m_lockId ++; m_locked = true; } + void EndLock() { m_locked = false; } + bool IsLocked() const { return m_locked; } + + static void SetEnhancedDrawingHandler(); + + /** + Prepares the content just before insertion (or after buffer reset). Called by the same function in wxRichTextBuffer. + Currently is only called if undo mode is on. + */ + virtual void PrepareContent(wxRichTextParagraphLayoutBox& container); + + /** + Can we delete this range? + Sends an event to the control. + */ + virtual bool CanDeleteRange(wxRichTextParagraphLayoutBox& container, const wxRichTextRange& range) const; + + /** + Can we insert content at this position? + Sends an event to the control. + */ + virtual bool CanInsertContent(wxRichTextParagraphLayoutBox& container, long pos) const; + + long m_lockId; + bool m_locked; +}; + // Define a new application type, each program should derive a class from wxApp class MyApp : public wxApp { @@ -205,7 +248,7 @@ private: // any class wishing to process wxWidgets events must use this macro DECLARE_EVENT_TABLE() - wxRichTextCtrl* m_richTextCtrl; + MyRichTextCtrl* m_richTextCtrl; }; // ---------------------------------------------------------------------------- @@ -387,6 +430,8 @@ bool MyApp::OnInit() CreateStyles(); + MyRichTextCtrl::SetEnhancedDrawingHandler(); + // Add extra handlers (plain text is automatically added) wxRichTextBuffer::AddHandler(new wxRichTextXMLHandler); wxRichTextBuffer::AddHandler(new wxRichTextHTMLHandler); @@ -736,7 +781,7 @@ MyFrame::MyFrame(const wxString& title, wxWindowID id, const wxPoint& pos, wxFont boldFont = wxFont(12, wxROMAN, wxNORMAL, wxBOLD); wxFont italicFont = wxFont(12, wxROMAN, wxITALIC, wxNORMAL); - m_richTextCtrl = new wxRichTextCtrl(splitter, ID_RICHTEXT_CTRL, wxEmptyString, wxDefaultPosition, wxSize(200, 200), wxVSCROLL|wxHSCROLL|wxWANTS_CHARS); + m_richTextCtrl = new MyRichTextCtrl(splitter, ID_RICHTEXT_CTRL, wxEmptyString, wxDefaultPosition, wxSize(200, 200), wxVSCROLL|wxHSCROLL|wxWANTS_CHARS); wxFont font(12, wxROMAN, wxNORMAL, wxNORMAL); m_richTextCtrl->SetFont(font); @@ -773,10 +818,27 @@ MyFrame::MyFrame(const wxString& title, wxWindowID id, const wxPoint& pos, // Write text void MyFrame::WriteInitialText() { - wxRichTextCtrl& r = *m_richTextCtrl; + MyRichTextCtrl& r = *m_richTextCtrl; r.SetDefaultStyle(wxRichTextAttr()); + // Add some locked content first - needs Undo to be enabled + { + r.BeginLock(); + r.WriteText(wxString(wxT("This is a locked object."))); + r.EndLock(); + + r.WriteText(wxString(wxT(" This is unlocked text. "))); + + r.BeginLock(); + r.WriteText(wxString(wxT("More locked content."))); + r.EndLock(); + r.Newline(); + + // Flush the Undo buffer + r.GetCommandProcessor()->ClearCommands(); + } + r.BeginSuppressUndo(); r.Freeze(); @@ -1807,3 +1869,107 @@ void MyFrame::OnPageSetup(wxCommandEvent& WXUNUSED(event)) // wxGetApp().GetPrinting()->PageSetup(); } + +void MyRichTextCtrl::PrepareContent(wxRichTextParagraphLayoutBox& container) +{ + if (IsLocked()) + { + // Lock all content that's about to be added to the control + wxRichTextObjectList::compatibility_iterator node = container.GetChildren().GetFirst(); + while (node) + { + wxRichTextParagraph* para = wxDynamicCast(node->GetData(), wxRichTextParagraph); + if (para) + { + wxRichTextObjectList::compatibility_iterator childNode = para->GetChildren().GetFirst(); + while (childNode) + { + wxRichTextObject* obj = childNode->GetData(); + obj->GetProperties().SetProperty(wxT("Lock"), m_lockId); + + childNode = childNode->GetNext(); + } + } + node = node->GetNext(); + } + } +} + +bool MyRichTextCtrl::CanDeleteRange(wxRichTextParagraphLayoutBox& container, const wxRichTextRange& range) const +{ + long i; + for (i = range.GetStart(); i < range.GetEnd(); i++) + { + wxRichTextObject* obj = container.GetLeafObjectAtPosition(i); + if (obj && obj->GetProperties().HasProperty(wxT("Lock"))) + { + return false; + } + } + return true; +} + +bool MyRichTextCtrl::CanInsertContent(wxRichTextParagraphLayoutBox& container, long pos) const +{ + wxRichTextObject* child1 = container.GetLeafObjectAtPosition(pos); + wxRichTextObject* child2 = container.GetLeafObjectAtPosition(pos-1); + + long lock1 = -1, lock2 = -1; + + if (child1 && child1->GetProperties().HasProperty(wxT("Lock"))) + lock1 = child1->GetProperties().GetPropertyLong(wxT("Lock")); + if (child2 && child2->GetProperties().HasProperty(wxT("Lock"))) + lock2 = child2->GetProperties().GetPropertyLong(wxT("Lock")); + + if (lock1 != -1 && lock1 == lock2) + return false; + + // Don't allow insertion before a locked object if it's at the beginning of the buffer. + if (pos == 0 && lock1 != -1) + return false; + + return true; +} + + +class wxRichTextEnhancedDrawingHandler: public wxRichTextDrawingHandler +{ +public: + wxRichTextEnhancedDrawingHandler() + { + SetName(wxT("enhanceddrawing")); + m_lockBackgroundColour = wxColour(220, 220, 220); + } + + /** + Returns @true if this object has virtual attributes that we can provide. + */ + virtual bool HasVirtualAttributes(wxRichTextObject* obj) const; + + /** + Provides virtual attributes that we can provide. + */ + virtual bool GetVirtualAttributes(wxRichTextAttr& attr, wxRichTextObject* obj) const; + + wxColour m_lockBackgroundColour; +}; + +bool wxRichTextEnhancedDrawingHandler::HasVirtualAttributes(wxRichTextObject* obj) const +{ + return obj->GetProperties().HasProperty(wxT("Lock")); +} + +bool wxRichTextEnhancedDrawingHandler::GetVirtualAttributes(wxRichTextAttr& attr, wxRichTextObject* obj) const +{ + if (obj->GetProperties().HasProperty(wxT("Lock"))) + { + attr.SetBackgroundColour(m_lockBackgroundColour); + return true; + } + return false; +} + +void MyRichTextCtrl::SetEnhancedDrawingHandler() +{ + wxRichTextBuffer::AddDrawingHandler(new wxRichTextEnhancedDrawingHandler); +} diff --git a/src/richtext/richtextbuffer.cpp b/src/richtext/richtextbuffer.cpp index 1303fe8379..e8aee90954 100644 --- a/src/richtext/richtextbuffer.cpp +++ b/src/richtext/richtextbuffer.cpp @@ -3524,6 +3524,14 @@ bool wxRichTextParagraphLayoutBox::HasParagraphAttributes(const wxRichTextRange& return foundCount == matchingCount && foundCount != 0; } +void wxRichTextParagraphLayoutBox::PrepareContent(wxRichTextParagraphLayoutBox& container) +{ + wxRichTextBuffer* buffer = GetBuffer(); + if (buffer && buffer->GetRichTextCtrl()) + buffer->GetRichTextCtrl()->PrepareContent(container); +} + + /// Set character or paragraph properties bool wxRichTextParagraphLayoutBox::SetProperties(const wxRichTextRange& range, const wxRichTextProperties& properties, int flags) { @@ -3697,6 +3705,8 @@ void wxRichTextParagraphLayoutBox::Reset() AddParagraph(wxEmptyString); + PrepareContent(*this); + InvalidateHierarchy(wxRICHTEXT_ALL); } @@ -7201,6 +7211,9 @@ bool wxRichTextBuffer::EndBatchUndo() /// Submit immediately, or delay according to whether collapsing is on bool wxRichTextBuffer::SubmitAction(wxRichTextAction* action) { + if (action && !action->GetNewParagraphs().IsEmpty()) + PrepareContent(action->GetNewParagraphs()); + if (BatchingUndo() && m_batchedCommand && !SuppressingUndo()) { wxRichTextCommand* cmd = new wxRichTextCommand(action->GetName()); diff --git a/src/richtext/richtextctrl.cpp b/src/richtext/richtextctrl.cpp index 9395714d2d..f5302ee6ca 100644 --- a/src/richtext/richtextctrl.cpp +++ b/src/richtext/richtextctrl.cpp @@ -1113,12 +1113,26 @@ void wxRichTextCtrl::OnChar(wxKeyEvent& event) // Must process this before translation, otherwise it's translated into a WXK_DELETE event. if (event.CmdDown() && event.GetKeyCode() == WXK_BACK) { + if (!IsEditable()) + { + return; + } + + if (HasSelection() && !CanDeleteRange(* GetFocusObject(), GetSelectionRange())) + { + return; + } + BeginBatchUndo(_("Delete Text")); long newPos = m_caretPosition; bool processed = DeleteSelectedContent(& newPos); + int deletions = 0; + if (processed) + deletions ++; + // Submit range in character positions, which are greater than caret positions, // so subtract 1 for deleted character and add 1 for conversion to character position. if (newPos > -1) @@ -1128,13 +1142,25 @@ void wxRichTextCtrl::OnChar(wxKeyEvent& event) long pos = wxRichTextCtrl::FindNextWordPosition(-1); if (pos < newPos) { - GetFocusObject()->DeleteRangeWithUndo(wxRichTextRange(pos+1, newPos), this, & GetBuffer()); + wxRichTextRange range(pos+1, newPos); + if (CanDeleteRange(* GetFocusObject(), range.FromInternal())) + { + GetFocusObject()->DeleteRangeWithUndo(range, this, & GetBuffer()); + deletions ++; + } processed = true; } } if (!processed) - GetFocusObject()->DeleteRangeWithUndo(wxRichTextRange(newPos, newPos), this, & GetBuffer()); + { + wxRichTextRange range(newPos, newPos); + if (CanDeleteRange(* GetFocusObject(), range.FromInternal())) + { + GetFocusObject()->DeleteRangeWithUndo(range, this, & GetBuffer()); + deletions ++; + } + } } EndBatchUndo(); @@ -1150,14 +1176,17 @@ void wxRichTextCtrl::OnChar(wxKeyEvent& event) ScrollIntoView(m_caretPosition, WXK_LEFT); - wxRichTextEvent cmdEvent( - wxEVT_COMMAND_RICHTEXT_DELETE, - GetId()); - cmdEvent.SetEventObject(this); - cmdEvent.SetFlags(flags); - cmdEvent.SetPosition(m_caretPosition+1); - cmdEvent.SetContainer(GetFocusObject()); - GetEventHandler()->ProcessEvent(cmdEvent); + if (deletions > 0) + { + wxRichTextEvent cmdEvent( + wxEVT_COMMAND_RICHTEXT_DELETE, + GetId()); + cmdEvent.SetEventObject(this); + cmdEvent.SetFlags(flags); + cmdEvent.SetPosition(m_caretPosition+1); + cmdEvent.SetContainer(GetFocusObject()); + GetEventHandler()->ProcessEvent(cmdEvent); + } Update(); } @@ -1177,10 +1206,18 @@ void wxRichTextCtrl::OnChar(wxKeyEvent& event) if (event.GetKeyCode() == WXK_RETURN) { - BeginBatchUndo(_("Insert Text")); + if (!CanInsertContent(* GetFocusObject(), m_caretPosition+1)) + return; long newPos = m_caretPosition; + if (HasSelection() && !CanDeleteRange(* GetFocusObject(), GetSelectionRange())) + { + return; + } + + BeginBatchUndo(_("Insert Text")); + DeleteSelectedContent(& newPos); if (event.ShiftDown()) @@ -1219,12 +1256,21 @@ void wxRichTextCtrl::OnChar(wxKeyEvent& event) } else if (event.GetKeyCode() == WXK_BACK) { - BeginBatchUndo(_("Delete Text")); - long newPos = m_caretPosition; + if (HasSelection() && !CanDeleteRange(* GetFocusObject(), GetSelectionRange())) + { + return; + } + + BeginBatchUndo(_("Delete Text")); + bool processed = DeleteSelectedContent(& newPos); + int deletions = 0; + if (processed) + deletions ++; + // Submit range in character positions, which are greater than caret positions, // so subtract 1 for deleted character and add 1 for conversion to character position. if (newPos > -1) @@ -1234,13 +1280,25 @@ void wxRichTextCtrl::OnChar(wxKeyEvent& event) long pos = wxRichTextCtrl::FindNextWordPosition(-1); if (pos < newPos) { - GetFocusObject()->DeleteRangeWithUndo(wxRichTextRange(pos+1, newPos), this, & GetBuffer()); + wxRichTextRange range(pos+1, newPos); + if (CanDeleteRange(* GetFocusObject(), range.FromInternal())) + { + GetFocusObject()->DeleteRangeWithUndo(range, this, & GetBuffer()); + deletions ++; + } processed = true; } } if (!processed) - GetFocusObject()->DeleteRangeWithUndo(wxRichTextRange(newPos, newPos), this, & GetBuffer()); + { + wxRichTextRange range(newPos, newPos); + if (CanDeleteRange(* GetFocusObject(), range.FromInternal())) + { + GetFocusObject()->DeleteRangeWithUndo(range, this, & GetBuffer()); + deletions ++; + } + } } EndBatchUndo(); @@ -1256,25 +1314,37 @@ void wxRichTextCtrl::OnChar(wxKeyEvent& event) ScrollIntoView(m_caretPosition, WXK_LEFT); - wxRichTextEvent cmdEvent( - wxEVT_COMMAND_RICHTEXT_DELETE, - GetId()); - cmdEvent.SetEventObject(this); - cmdEvent.SetFlags(flags); - cmdEvent.SetPosition(m_caretPosition+1); - cmdEvent.SetContainer(GetFocusObject()); - GetEventHandler()->ProcessEvent(cmdEvent); + if (deletions > 0) + { + wxRichTextEvent cmdEvent( + wxEVT_COMMAND_RICHTEXT_DELETE, + GetId()); + cmdEvent.SetEventObject(this); + cmdEvent.SetFlags(flags); + cmdEvent.SetPosition(m_caretPosition+1); + cmdEvent.SetContainer(GetFocusObject()); + GetEventHandler()->ProcessEvent(cmdEvent); + } Update(); } else if (event.GetKeyCode() == WXK_DELETE) { - BeginBatchUndo(_("Delete Text")); - long newPos = m_caretPosition; + if (HasSelection() && !CanDeleteRange(* GetFocusObject(), GetSelectionRange())) + { + return; + } + + BeginBatchUndo(_("Delete Text")); + bool processed = DeleteSelectedContent(& newPos); + int deletions = 0; + if (processed) + deletions ++; + // Submit range in character positions, which are greater than caret positions, if (newPos < GetFocusObject()->GetOwnRange().GetEnd()+1) { @@ -1283,13 +1353,25 @@ void wxRichTextCtrl::OnChar(wxKeyEvent& event) long pos = wxRichTextCtrl::FindNextWordPosition(1); if (pos != -1 && (pos > newPos)) { - GetFocusObject()->DeleteRangeWithUndo(wxRichTextRange(newPos+1, pos), this, & GetBuffer()); + wxRichTextRange range(newPos+1, pos); + if (CanDeleteRange(* GetFocusObject(), range.FromInternal())) + { + GetFocusObject()->DeleteRangeWithUndo(range, this, & GetBuffer()); + deletions ++; + } processed = true; } } if (!processed && newPos < (GetLastPosition()-1)) - GetFocusObject()->DeleteRangeWithUndo(wxRichTextRange(newPos+1, newPos+1), this, & GetBuffer()); + { + wxRichTextRange range(newPos+1, newPos+1); + if (CanDeleteRange(* GetFocusObject(), range.FromInternal())) + { + GetFocusObject()->DeleteRangeWithUndo(range, this, & GetBuffer()); + deletions ++; + } + } } EndBatchUndo(); @@ -1305,14 +1387,17 @@ void wxRichTextCtrl::OnChar(wxKeyEvent& event) ScrollIntoView(m_caretPosition, WXK_LEFT); - wxRichTextEvent cmdEvent( - wxEVT_COMMAND_RICHTEXT_DELETE, - GetId()); - cmdEvent.SetEventObject(this); - cmdEvent.SetFlags(flags); - cmdEvent.SetPosition(m_caretPosition+1); - cmdEvent.SetContainer(GetFocusObject()); - GetEventHandler()->ProcessEvent(cmdEvent); + if (deletions > 0) + { + wxRichTextEvent cmdEvent( + wxEVT_COMMAND_RICHTEXT_DELETE, + GetId()); + cmdEvent.SetEventObject(this); + cmdEvent.SetFlags(flags); + cmdEvent.SetPosition(m_caretPosition+1); + cmdEvent.SetContainer(GetFocusObject()); + GetEventHandler()->ProcessEvent(cmdEvent); + } Update(); } @@ -1377,6 +1462,12 @@ void wxRichTextCtrl::OnChar(wxKeyEvent& event) } } + if (!CanInsertContent(* GetFocusObject(), m_caretPosition+1)) + return; + + if (HasSelection() && !CanDeleteRange(* GetFocusObject(), GetSelectionRange())) + return; + BeginBatchUndo(_("Insert Text")); long newPos = m_caretPosition; @@ -3018,12 +3109,12 @@ bool wxRichTextCtrl::CanCopy() const bool wxRichTextCtrl::CanCut() const { - return HasSelection() && IsEditable(); + return CanDeleteSelection(); } bool wxRichTextCtrl::CanPaste() const { - if ( !IsEditable() ) + if ( !IsEditable() || !GetFocusObject() || !CanInsertContent(* GetFocusObject(), m_caretPosition+1)) return false; return GetBuffer().CanPasteFromClipboard(); @@ -3031,7 +3122,7 @@ bool wxRichTextCtrl::CanPaste() const bool wxRichTextCtrl::CanDeleteSelection() const { - return HasSelection() && IsEditable(); + return HasSelection() && IsEditable() && CanDeleteRange(* GetFocusObject(), GetSelectionRange()); } @@ -4460,6 +4551,16 @@ bool wxRichTextDropSource::GiveFeedback(wxDragResult WXUNUSED(effect)) } #endif // wxUSE_DRAG_AND_DROP +bool wxRichTextCtrl::CanDeleteRange(wxRichTextParagraphLayoutBox& WXUNUSED(container), const wxRichTextRange& WXUNUSED(range)) const +{ + return true; +} + +bool wxRichTextCtrl::CanInsertContent(wxRichTextParagraphLayoutBox& WXUNUSED(container), long WXUNUSED(pos)) const +{ + return true; +} + #if wxRICHTEXT_USE_OWN_CARET -- 2.47.2