1 /////////////////////////////////////////////////////////////////////////////
2 // Name: src/richtext/richtextstyles.cpp
3 // Purpose: Style management for wxRichTextCtrl
4 // Author: Julian Smart
8 // Copyright: (c) Julian Smart
9 // Licence: wxWindows licence
10 /////////////////////////////////////////////////////////////////////////////
12 // For compilers that support precompilation, includes "wx.h".
13 #include "wx/wxprec.h"
21 #include "wx/richtext/richtextstyles.h"
27 #include "wx/filename.h"
28 #include "wx/clipbrd.h"
29 #include "wx/wfstream.h"
31 #include "wx/richtext/richtextctrl.h"
33 IMPLEMENT_CLASS(wxRichTextStyleDefinition
, wxObject
)
34 IMPLEMENT_CLASS(wxRichTextCharacterStyleDefinition
, wxRichTextStyleDefinition
)
35 IMPLEMENT_CLASS(wxRichTextParagraphStyleDefinition
, wxRichTextStyleDefinition
)
41 void wxRichTextStyleDefinition::Copy(const wxRichTextStyleDefinition
& def
)
44 m_baseStyle
= def
.m_baseStyle
;
45 m_style
= def
.m_style
;
48 bool wxRichTextStyleDefinition::Eq(const wxRichTextStyleDefinition
& def
) const
50 return (m_name
== def
.m_name
&& m_baseStyle
== def
.m_baseStyle
&& m_style
== def
.m_style
);
54 * Paragraph style definition
57 void wxRichTextParagraphStyleDefinition::Copy(const wxRichTextParagraphStyleDefinition
& def
)
59 wxRichTextStyleDefinition::Copy(def
);
61 m_nextStyle
= def
.m_nextStyle
;
64 bool wxRichTextParagraphStyleDefinition::operator ==(const wxRichTextParagraphStyleDefinition
& def
) const
66 return (Eq(def
) && m_nextStyle
== def
.m_nextStyle
);
73 IMPLEMENT_CLASS(wxRichTextStyleSheet
, wxObject
)
76 void wxRichTextStyleSheet::Init()
80 /// Add a definition to one of the style lists
81 bool wxRichTextStyleSheet::AddStyle(wxList
& list
, wxRichTextStyleDefinition
* def
)
89 bool wxRichTextStyleSheet::RemoveStyle(wxList
& list
, wxRichTextStyleDefinition
* def
, bool deleteStyle
)
91 wxList::compatibility_iterator node
= list
.Find(def
);
94 wxRichTextStyleDefinition
* def
= (wxRichTextStyleDefinition
*) node
->GetData();
104 /// Find a definition by name
105 wxRichTextStyleDefinition
* wxRichTextStyleSheet::FindStyle(const wxList
& list
, const wxString
& name
) const
107 for (wxList::compatibility_iterator node
= list
.GetFirst(); node
; node
= node
->GetNext())
109 wxRichTextStyleDefinition
* def
= (wxRichTextStyleDefinition
*) node
->GetData();
110 if (def
->GetName().Lower() == name
.Lower())
116 /// Delete all styles
117 void wxRichTextStyleSheet::DeleteStyles()
119 WX_CLEAR_LIST(wxList
, m_characterStyleDefinitions
);
120 WX_CLEAR_LIST(wxList
, m_paragraphStyleDefinitions
);
123 /// Add a definition to the character style list
124 bool wxRichTextStyleSheet::AddCharacterStyle(wxRichTextCharacterStyleDefinition
* def
)
126 def
->GetStyle().SetCharacterStyleName(def
->GetName());
127 return AddStyle(m_characterStyleDefinitions
, def
);
130 /// Add a definition to the paragraph style list
131 bool wxRichTextStyleSheet::AddParagraphStyle(wxRichTextParagraphStyleDefinition
* def
)
133 def
->GetStyle().SetParagraphStyleName(def
->GetName());
134 return AddStyle(m_paragraphStyleDefinitions
, def
);
138 void wxRichTextStyleSheet::Copy(const wxRichTextStyleSheet
& sheet
)
142 wxList::compatibility_iterator node
;
144 for (node
= sheet
.m_characterStyleDefinitions
.GetFirst(); node
; node
= node
->GetNext())
146 wxRichTextCharacterStyleDefinition
* def
= (wxRichTextCharacterStyleDefinition
*) node
->GetData();
147 AddCharacterStyle(new wxRichTextCharacterStyleDefinition(*def
));
150 for (node
= sheet
.m_paragraphStyleDefinitions
.GetFirst(); node
; node
= node
->GetNext())
152 wxRichTextParagraphStyleDefinition
* def
= (wxRichTextParagraphStyleDefinition
*) node
->GetData();
153 AddParagraphStyle(new wxRichTextParagraphStyleDefinition(*def
));
158 bool wxRichTextStyleSheet::operator==(const wxRichTextStyleSheet
& WXUNUSED(sheet
)) const
167 * wxRichTextStyleListBox class declaration
168 * A listbox to display styles.
171 IMPLEMENT_CLASS(wxRichTextStyleListBox
, wxHtmlListBox
)
173 BEGIN_EVENT_TABLE(wxRichTextStyleListBox
, wxHtmlListBox
)
174 EVT_LISTBOX(wxID_ANY
, wxRichTextStyleListBox::OnSelect
)
175 EVT_LEFT_DOWN(wxRichTextStyleListBox::OnLeftDown
)
176 EVT_IDLE(wxRichTextStyleListBox::OnIdle
)
179 wxRichTextStyleListBox::wxRichTextStyleListBox(wxWindow
* parent
, wxWindowID id
, const wxPoint
& pos
,
180 const wxSize
& size
, long style
)
183 Create(parent
, id
, pos
, size
, style
);
186 bool wxRichTextStyleListBox::Create(wxWindow
* parent
, wxWindowID id
, const wxPoint
& pos
,
187 const wxSize
& size
, long style
)
189 return wxHtmlListBox::Create(parent
, id
, pos
, size
, style
);
192 wxRichTextStyleListBox::~wxRichTextStyleListBox()
196 /// Returns the HTML for this item
197 wxString
wxRichTextStyleListBox::OnGetItem(size_t n
) const
199 if (!GetStyleSheet())
200 return wxEmptyString
;
202 // First paragraph styles, then character
203 if (n
< GetStyleSheet()->GetParagraphStyleCount())
205 wxRichTextParagraphStyleDefinition
* def
= GetStyleSheet()->GetParagraphStyle(n
);
207 wxString str
= CreateHTML(def
);
211 if ((n
- GetStyleSheet()->GetParagraphStyleCount()) < GetStyleSheet()->GetCharacterStyleCount())
213 wxRichTextCharacterStyleDefinition
* def
= GetStyleSheet()->GetCharacterStyle(n
- GetStyleSheet()->GetParagraphStyleCount());
215 wxString str
= CreateHTML(def
);
218 return wxEmptyString
;
221 // Get style for index
222 wxRichTextStyleDefinition
* wxRichTextStyleListBox::GetStyle(size_t i
) const
224 if (!GetStyleSheet())
227 // First paragraph styles, then character
228 if (i
< GetStyleSheet()->GetParagraphStyleCount())
229 return GetStyleSheet()->GetParagraphStyle(i
);
231 if ((i
- GetStyleSheet()->GetParagraphStyleCount()) < GetStyleSheet()->GetCharacterStyleCount())
232 return GetStyleSheet()->GetCharacterStyle(i
- GetStyleSheet()->GetParagraphStyleCount());
238 void wxRichTextStyleListBox::UpdateStyles()
242 SetItemCount(GetStyleSheet()->GetParagraphStyleCount()+GetStyleSheet()->GetCharacterStyleCount());
247 // Get index for style name
248 int wxRichTextStyleListBox::GetIndexForStyle(const wxString
& name
) const
253 for (i
= 0; i
< (int) GetStyleSheet()->GetParagraphStyleCount(); i
++)
255 wxRichTextParagraphStyleDefinition
* def
= GetStyleSheet()->GetParagraphStyle(i
);
256 if (def
->GetName() == name
)
259 for (i
= 0; i
< (int) GetStyleSheet()->GetCharacterStyleCount(); i
++)
261 wxRichTextCharacterStyleDefinition
* def
= GetStyleSheet()->GetCharacterStyle(i
);
262 if (def
->GetName() == name
)
263 return i
+ (int) GetStyleSheet()->GetParagraphStyleCount();
269 /// Set selection for string
270 int wxRichTextStyleListBox::SetStyleSelection(const wxString
& name
)
272 int i
= GetIndexForStyle(name
);
278 // Convert a colour to a 6-digit hex string
279 static wxString
ColourToHexString(const wxColour
& col
)
283 hex
+= wxDecToHex(col
.Red());
284 hex
+= wxDecToHex(col
.Green());
285 hex
+= wxDecToHex(col
.Blue());
290 /// Creates a suitable HTML fragment for a definition
291 wxString
wxRichTextStyleListBox::CreateHTML(wxRichTextStyleDefinition
* def
) const
293 wxString
str(wxT("<table><tr>"));
295 if (def
->GetStyle().GetLeftIndent() > 0)
297 wxClientDC
dc((wxWindow
*) this);
299 str
<< wxT("<td width=") << ConvertTenthsMMToPixels(dc
, def
->GetStyle().GetLeftIndent()) << wxT("></td>");
302 str
<< wxT("<td nowrap>");
306 // Standard size is 12, say
307 size
+= 12 - def
->GetStyle().GetFontSize();
311 str
<< wxT(" size=") << size
;
313 if (!def
->GetStyle().GetFontFaceName().IsEmpty())
314 str
<< wxT(" face=\"") << def
->GetStyle().GetFontFaceName() << wxT("\"");
316 if (def
->GetStyle().GetTextColour().Ok())
317 str
<< wxT(" color=\"#") << ColourToHexString(def
->GetStyle().GetTextColour()) << wxT("\"");
321 bool hasBold
= false;
322 bool hasItalic
= false;
323 bool hasUnderline
= false;
325 if (def
->GetStyle().GetFontWeight() == wxBOLD
)
327 if (def
->GetStyle().GetFontStyle() == wxITALIC
)
329 if (def
->GetStyle().GetFontUnderlined())
339 str
+= def
->GetName();
348 str
<< wxT("</font>");
350 str
+= wxT("</td></tr></table>");
354 // Convert units in tends of a millimetre to device units
355 int wxRichTextStyleListBox::ConvertTenthsMMToPixels(wxDC
& dc
, int units
) const
357 int ppi
= dc
.GetPPI().x
;
359 // There are ppi pixels in 254.1 "1/10 mm"
361 double pixels
= ((double) units
* (double)ppi
) / 254.1;
366 /// React to selection
367 void wxRichTextStyleListBox::OnSelect(wxCommandEvent
& WXUNUSED(event
))
370 wxRichTextStyleDefinition
* def
= GetStyle(event
.GetSelection());
373 wxMessageBox(def
->GetName());
378 void wxRichTextStyleListBox::OnLeftDown(wxMouseEvent
& event
)
380 wxVListBox::OnLeftDown(event
);
382 int item
= HitTest(event
.GetPosition());
383 if (item
!= wxNOT_FOUND
&& GetApplyOnSelection())
387 /// Auto-select from style under caret in idle time
388 void wxRichTextStyleListBox::OnIdle(wxIdleEvent
& event
)
390 if (CanAutoSetSelection() && GetRichTextCtrl())
392 int adjustedCaretPos
= GetRichTextCtrl()->GetAdjustedCaretPosition(GetRichTextCtrl()->GetCaretPosition());
394 wxRichTextParagraph
* para
= GetRichTextCtrl()->GetBuffer().GetParagraphAtPosition(adjustedCaretPos
);
395 wxRichTextObject
* obj
= GetRichTextCtrl()->GetBuffer().GetLeafObjectAtPosition(adjustedCaretPos
);
399 // Take into account current default style just chosen by user
400 if (GetRichTextCtrl()->IsDefaultStyleShowing())
402 if (!GetRichTextCtrl()->GetDefaultStyleEx().GetCharacterStyleName().IsEmpty())
403 styleName
= GetRichTextCtrl()->GetDefaultStyleEx().GetCharacterStyleName();
404 else if (!GetRichTextCtrl()->GetDefaultStyleEx().GetParagraphStyleName().IsEmpty())
405 styleName
= GetRichTextCtrl()->GetDefaultStyleEx().GetParagraphStyleName();
407 else if (obj
&& !obj
->GetAttributes().GetCharacterStyleName().IsEmpty())
409 styleName
= obj
->GetAttributes().GetCharacterStyleName();
411 else if (para
&& !para
->GetAttributes().GetParagraphStyleName().IsEmpty())
413 styleName
= para
->GetAttributes().GetParagraphStyleName();
416 int sel
= GetSelection();
417 if (!styleName
.IsEmpty())
419 // Don't do the selection if it's already set
420 if (sel
== GetIndexForStyle(styleName
))
423 SetStyleSelection(styleName
);
432 void wxRichTextStyleListBox::ApplyStyle(int item
)
434 if ( item
!= wxNOT_FOUND
)
436 wxRichTextStyleDefinition
* def
= GetStyle(item
);
437 if (def
&& GetRichTextCtrl())
439 GetRichTextCtrl()->ApplyStyle(def
);
440 GetRichTextCtrl()->SetFocus();
446 wxColour
wxRichTextStyleListBox::GetSelectedTextColour(const wxColour
& colFg
) const
451 wxColour
wxRichTextStyleListBox::GetSelectedTextBgColour(const wxColour
& colBg
) const
460 * Style drop-down for a wxComboCtrl
464 BEGIN_EVENT_TABLE(wxRichTextStyleComboPopup
, wxRichTextStyleListBox
)
465 EVT_MOTION(wxRichTextStyleComboPopup::OnMouseMove
)
466 EVT_LEFT_DOWN(wxRichTextStyleComboPopup::OnMouseClick
)
469 void wxRichTextStyleComboPopup::SetStringValue( const wxString
& s
)
471 m_value
= SetStyleSelection(s
);
474 wxString
wxRichTextStyleComboPopup::GetStringValue() const
479 wxRichTextStyleDefinition
* def
= GetStyle(sel
);
481 return def
->GetName();
483 return wxEmptyString
;
487 // Popup event handlers
490 // Mouse hot-tracking
491 void wxRichTextStyleComboPopup::OnMouseMove(wxMouseEvent
& event
)
493 // Move selection to cursor if it is inside the popup
495 int itemHere
= wxRichTextStyleListBox::HitTest(event
.GetPosition());
498 wxRichTextStyleListBox::SetSelection(itemHere
);
499 m_itemHere
= itemHere
;
504 // On mouse left, set the value and close the popup
505 void wxRichTextStyleComboPopup::OnMouseClick(wxMouseEvent
& WXUNUSED(event
))
508 m_value
= m_itemHere
;
510 // Ordering is important, so we don't dismiss this popup accidentally
511 // by setting the focus elsewhere e.g. in ApplyStyle
515 wxRichTextStyleListBox::ApplyStyle(m_itemHere
);
519 * wxRichTextStyleComboCtrl
520 * A combo for applying styles.
523 IMPLEMENT_CLASS(wxRichTextStyleComboCtrl
, wxComboCtrl
)
525 BEGIN_EVENT_TABLE(wxRichTextStyleComboCtrl
, wxComboCtrl
)
526 EVT_IDLE(wxRichTextStyleComboCtrl::OnIdle
)
529 bool wxRichTextStyleComboCtrl::Create(wxWindow
* parent
, wxWindowID id
, const wxPoint
& pos
,
530 const wxSize
& size
, long style
)
532 if (!wxComboCtrl::Create(parent
, id
, wxEmptyString
, pos
, size
, style
))
535 SetPopupMaxHeight(400);
537 m_stylePopup
= new wxRichTextStyleComboPopup
;
539 SetPopupControl(m_stylePopup
);
544 /// Auto-select from style under caret in idle time
546 // TODO: must be able to show italic, bold, combinations
547 // in style box. Do we have a concept of automatic, temporary
548 // styles that are added whenever we wish to show a style
549 // that doesn't exist already? E.g. "Bold, Italic, Underline".
550 // Word seems to generate these things on the fly.
551 // If there's a named style already, it uses e.g. Heading1 + Bold, Italic
552 // If you unembolden text in a style that has bold, it uses the
554 // TODO: order styles alphabetically. This means indexes can change,
555 // so need a different way to specify selections, i.e. by name.
557 void wxRichTextStyleComboCtrl::OnIdle(wxIdleEvent
& event
)
559 if (GetRichTextCtrl() && !IsPopupShown())
561 int adjustedCaretPos
= GetRichTextCtrl()->GetAdjustedCaretPosition(GetRichTextCtrl()->GetCaretPosition());
563 wxRichTextParagraph
* para
= GetRichTextCtrl()->GetBuffer().GetParagraphAtPosition(adjustedCaretPos
);
564 wxRichTextObject
* obj
= GetRichTextCtrl()->GetBuffer().GetLeafObjectAtPosition(adjustedCaretPos
);
568 // Take into account current default style just chosen by user
569 if (GetRichTextCtrl()->IsDefaultStyleShowing())
571 if (!GetRichTextCtrl()->GetDefaultStyleEx().GetCharacterStyleName().IsEmpty())
572 styleName
= GetRichTextCtrl()->GetDefaultStyleEx().GetCharacterStyleName();
573 else if (!GetRichTextCtrl()->GetDefaultStyleEx().GetParagraphStyleName().IsEmpty())
574 styleName
= GetRichTextCtrl()->GetDefaultStyleEx().GetParagraphStyleName();
576 else if (obj
&& !obj
->GetAttributes().GetCharacterStyleName().IsEmpty())
578 styleName
= obj
->GetAttributes().GetCharacterStyleName();
580 else if (para
&& !para
->GetAttributes().GetParagraphStyleName().IsEmpty())
582 styleName
= para
->GetAttributes().GetParagraphStyleName();
585 wxString currentValue
= GetValue();
586 if (!styleName
.IsEmpty())
588 // Don't do the selection if it's already set
589 if (currentValue
== styleName
)
594 else if (!currentValue
.IsEmpty())
595 SetValue(wxEmptyString
);