]> git.saurik.com Git - wxWidgets.git/blame - src/richtext/richtextstyles.cpp
[ 1563875 ] wxTreeCtrl::FindItem asserts when item not found
[wxWidgets.git] / src / richtext / richtextstyles.cpp
CommitLineData
5d7836c4 1/////////////////////////////////////////////////////////////////////////////
61399247 2// Name: src/richtext/richtextstyles.cpp
5d7836c4
JS
3// Purpose: Style management for wxRichTextCtrl
4// Author: Julian Smart
61399247 5// Modified by:
5d7836c4 6// Created: 2005-09-30
61399247 7// RCS-ID: $Id$
5d7836c4
JS
8// Copyright: (c) Julian Smart
9// Licence: wxWindows licence
10/////////////////////////////////////////////////////////////////////////////
11
12// For compilers that support precompilation, includes "wx.h".
13#include "wx/wxprec.h"
14
15#ifdef __BORLANDC__
02761f6c 16 #pragma hdrstop
5d7836c4
JS
17#endif
18
b01ca8b6
JS
19#if wxUSE_RICHTEXT
20
21#include "wx/richtext/richtextstyles.h"
22
5d7836c4 23#ifndef WX_PRECOMP
61399247 24 #include "wx/dcclient.h"
02761f6c 25 #include "wx/module.h"
5d7836c4
JS
26#endif
27
5d7836c4
JS
28#include "wx/filename.h"
29#include "wx/clipbrd.h"
30#include "wx/wfstream.h"
5d7836c4 31
5d7836c4
JS
32#include "wx/richtext/richtextctrl.h"
33
34IMPLEMENT_CLASS(wxRichTextStyleDefinition, wxObject)
35IMPLEMENT_CLASS(wxRichTextCharacterStyleDefinition, wxRichTextStyleDefinition)
36IMPLEMENT_CLASS(wxRichTextParagraphStyleDefinition, wxRichTextStyleDefinition)
37
38/*!
39 * The style manager
40 */
41
42IMPLEMENT_CLASS(wxRichTextStyleSheet, wxObject)
43
44/// Initialisation
45void wxRichTextStyleSheet::Init()
46{
47}
48
49/// Add a definition to one of the style lists
50bool wxRichTextStyleSheet::AddStyle(wxList& list, wxRichTextStyleDefinition* def)
51{
52 if (!list.Find(def))
53 list.Append(def);
54 return true;
55}
56
57/// Remove a style
58bool wxRichTextStyleSheet::RemoveStyle(wxList& list, wxRichTextStyleDefinition* def, bool deleteStyle)
59{
09f14108 60 wxList::compatibility_iterator node = list.Find(def);
5d7836c4
JS
61 if (node)
62 {
63 wxRichTextStyleDefinition* def = (wxRichTextStyleDefinition*) node->GetData();
09f14108 64 list.Erase(node);
5d7836c4
JS
65 if (deleteStyle)
66 delete def;
67 return true;
68 }
69 else
70 return false;
71}
72
73/// Find a definition by name
74wxRichTextStyleDefinition* wxRichTextStyleSheet::FindStyle(const wxList& list, const wxString& name) const
75{
09f14108 76 for (wxList::compatibility_iterator node = list.GetFirst(); node; node = node->GetNext())
5d7836c4
JS
77 {
78 wxRichTextStyleDefinition* def = (wxRichTextStyleDefinition*) node->GetData();
79 if (def->GetName().Lower() == name.Lower())
80 return def;
81 }
61399247 82 return NULL;
5d7836c4
JS
83}
84
85/// Delete all styles
86void wxRichTextStyleSheet::DeleteStyles()
87{
88 WX_CLEAR_LIST(wxList, m_characterStyleDefinitions);
89 WX_CLEAR_LIST(wxList, m_paragraphStyleDefinitions);
90}
91
92#if wxUSE_HTML
93/*!
94 * wxRichTextStyleListBox class declaration
95 * A listbox to display styles.
96 */
97
98IMPLEMENT_CLASS(wxRichTextStyleListBox, wxHtmlListBox)
99
100BEGIN_EVENT_TABLE(wxRichTextStyleListBox, wxHtmlListBox)
101 EVT_LISTBOX(wxID_ANY, wxRichTextStyleListBox::OnSelect)
102 EVT_LEFT_DOWN(wxRichTextStyleListBox::OnLeftDown)
e637208a 103 EVT_IDLE(wxRichTextStyleListBox::OnIdle)
5d7836c4
JS
104END_EVENT_TABLE()
105
106wxRichTextStyleListBox::wxRichTextStyleListBox(wxWindow* parent, wxWindowID id, const wxPoint& pos,
e637208a 107 const wxSize& size, long style)
5d7836c4 108{
e637208a
JS
109 Init();
110 Create(parent, id, pos, size, style);
111}
112
113bool wxRichTextStyleListBox::Create(wxWindow* parent, wxWindowID id, const wxPoint& pos,
114 const wxSize& size, long style)
115{
116 return wxHtmlListBox::Create(parent, id, pos, size, style);
5d7836c4
JS
117}
118
119wxRichTextStyleListBox::~wxRichTextStyleListBox()
120{
121}
122
123/// Returns the HTML for this item
124wxString wxRichTextStyleListBox::OnGetItem(size_t n) const
125{
126 if (!GetStyleSheet())
127 return wxEmptyString;
128
129 // First paragraph styles, then character
130 if (n < GetStyleSheet()->GetParagraphStyleCount())
131 {
132 wxRichTextParagraphStyleDefinition* def = GetStyleSheet()->GetParagraphStyle(n);
133
134 wxString str = CreateHTML(def);
135 return str;
136 }
137
138 if ((n - GetStyleSheet()->GetParagraphStyleCount()) < GetStyleSheet()->GetCharacterStyleCount())
139 {
140 wxRichTextCharacterStyleDefinition* def = GetStyleSheet()->GetCharacterStyle(n - GetStyleSheet()->GetParagraphStyleCount());
141
142 wxString str = CreateHTML(def);
143 return str;
144 }
145 return wxEmptyString;
146}
147
148// Get style for index
149wxRichTextStyleDefinition* wxRichTextStyleListBox::GetStyle(size_t i) const
150{
151 if (!GetStyleSheet())
152 return NULL;
153
154 // First paragraph styles, then character
155 if (i < GetStyleSheet()->GetParagraphStyleCount())
156 return GetStyleSheet()->GetParagraphStyle(i);
157
158 if ((i - GetStyleSheet()->GetParagraphStyleCount()) < GetStyleSheet()->GetCharacterStyleCount())
159 return GetStyleSheet()->GetCharacterStyle(i - GetStyleSheet()->GetParagraphStyleCount());
160
161 return NULL;
162}
163
164/// Updates the list
165void wxRichTextStyleListBox::UpdateStyles()
166{
167 if (GetStyleSheet())
168 {
169 SetItemCount(GetStyleSheet()->GetParagraphStyleCount()+GetStyleSheet()->GetCharacterStyleCount());
170 Refresh();
171 }
172}
173
e637208a
JS
174// Get index for style name
175int wxRichTextStyleListBox::GetIndexForStyle(const wxString& name) const
176{
177 if (GetStyleSheet())
178 {
179 int i;
180 for (i = 0; i < (int) GetStyleSheet()->GetParagraphStyleCount(); i++)
181 {
182 wxRichTextParagraphStyleDefinition* def = GetStyleSheet()->GetParagraphStyle(i);
183 if (def->GetName() == name)
184 return i;
185 }
186 for (i = 0; i < (int) GetStyleSheet()->GetCharacterStyleCount(); i++)
187 {
188 wxRichTextCharacterStyleDefinition* def = GetStyleSheet()->GetCharacterStyle(i);
189 if (def->GetName() == name)
190 return i + (int) GetStyleSheet()->GetParagraphStyleCount();
191 }
192 }
193 return -1;
194}
195
196/// Set selection for string
197int wxRichTextStyleListBox::SetStyleSelection(const wxString& name)
198{
199 int i = GetIndexForStyle(name);
200 if (i > -1)
201 SetSelection(i);
202 return i;
203}
204
5d7836c4
JS
205// Convert a colour to a 6-digit hex string
206static wxString ColourToHexString(const wxColour& col)
207{
208 wxString hex;
209
210 hex += wxDecToHex(col.Red());
211 hex += wxDecToHex(col.Green());
212 hex += wxDecToHex(col.Blue());
213
214 return hex;
215}
216
217/// Creates a suitable HTML fragment for a definition
218wxString wxRichTextStyleListBox::CreateHTML(wxRichTextStyleDefinition* def) const
219{
220 wxString str(wxT("<table><tr>"));
221
222 if (def->GetStyle().GetLeftIndent() > 0)
223 {
224 wxClientDC dc((wxWindow*) this);
225
226 str << wxT("<td width=") << ConvertTenthsMMToPixels(dc, def->GetStyle().GetLeftIndent()) << wxT("></td>");
227 }
228
229 str << wxT("<td nowrap>");
230
231 int size = 5;
232
233 // Standard size is 12, say
61399247 234 size += 12 - def->GetStyle().GetFontSize();
5d7836c4
JS
235
236 str += wxT("<font");
237
238 str << wxT(" size=") << size;
239
240 if (!def->GetStyle().GetFontFaceName().IsEmpty())
241 str << wxT(" face=\"") << def->GetStyle().GetFontFaceName() << wxT("\"");
242
243 if (def->GetStyle().GetTextColour().Ok())
244 str << wxT(" color=\"#") << ColourToHexString(def->GetStyle().GetTextColour()) << wxT("\"");
245
246 str << wxT(">");
247
248 bool hasBold = false;
249 bool hasItalic = false;
250 bool hasUnderline = false;
251
252 if (def->GetStyle().GetFontWeight() == wxBOLD)
253 hasBold = true;
254 if (def->GetStyle().GetFontStyle() == wxITALIC)
255 hasItalic = true;
256 if (def->GetStyle().GetFontUnderlined())
257 hasUnderline = true;
258
259 if (hasBold)
260 str << wxT("<b>");
261 if (hasItalic)
262 str << wxT("<i>");
263 if (hasUnderline)
264 str << wxT("<u>");
265
266 str += def->GetName();
267
268 if (hasUnderline)
269 str << wxT("</u>");
270 if (hasItalic)
271 str << wxT("</i>");
272 if (hasBold)
273 str << wxT("</b>");
274
275 str << wxT("</font>");
276
277 str += wxT("</td></tr></table>");
278 return str;
279}
280
281// Convert units in tends of a millimetre to device units
282int wxRichTextStyleListBox::ConvertTenthsMMToPixels(wxDC& dc, int units) const
283{
284 int ppi = dc.GetPPI().x;
285
286 // There are ppi pixels in 254.1 "1/10 mm"
287
288 double pixels = ((double) units * (double)ppi) / 254.1;
289
290 return (int) pixels;
291}
292
293/// React to selection
294void wxRichTextStyleListBox::OnSelect(wxCommandEvent& WXUNUSED(event))
295{
296#if 0
297 wxRichTextStyleDefinition* def = GetStyle(event.GetSelection());
298 if (def)
299 {
300 wxMessageBox(def->GetName());
301 }
302#endif
303}
304
305void wxRichTextStyleListBox::OnLeftDown(wxMouseEvent& event)
306{
307 wxVListBox::OnLeftDown(event);
308
309 int item = HitTest(event.GetPosition());
e637208a
JS
310 if (item != wxNOT_FOUND)
311 DoSelection(item);
312}
5d7836c4 313
e637208a
JS
314/// Auto-select from style under caret in idle time
315void wxRichTextStyleListBox::OnIdle(wxIdleEvent& event)
316{
317 if (CanAutoSetSelection() && GetRichTextCtrl())
5d7836c4 318 {
e637208a
JS
319 wxRichTextParagraph* para = GetRichTextCtrl()->GetBuffer().GetParagraphAtPosition(GetRichTextCtrl()->GetCaretPosition());
320 wxRichTextObject* obj = GetRichTextCtrl()->GetBuffer().GetLeafObjectAtPosition(GetRichTextCtrl()->GetCaretPosition());
321
322 wxString styleName;
323
324 // Take into account current default style just chosen by user
325 if (GetRichTextCtrl()->IsDefaultStyleShowing())
5d7836c4 326 {
e637208a
JS
327 if (!GetRichTextCtrl()->GetDefaultStyleEx().GetCharacterStyleName().IsEmpty())
328 styleName = GetRichTextCtrl()->GetDefaultStyleEx().GetCharacterStyleName();
329 else if (!GetRichTextCtrl()->GetDefaultStyleEx().GetParagraphStyleName().IsEmpty())
330 styleName = GetRichTextCtrl()->GetDefaultStyleEx().GetParagraphStyleName();
331 }
332 else if (obj && !obj->GetAttributes().GetCharacterStyleName().IsEmpty())
333 {
334 styleName = obj->GetAttributes().GetCharacterStyleName();
335 }
336 else if (para && !para->GetAttributes().GetParagraphStyleName().IsEmpty())
337 {
338 styleName = para->GetAttributes().GetParagraphStyleName();
339 }
61399247 340
e637208a
JS
341 int sel = GetSelection();
342 if (!styleName.IsEmpty())
343 {
344 // Don't do the selection if it's already set
345 if (sel == GetIndexForStyle(styleName))
346 return;
5d7836c4 347
e637208a
JS
348 SetStyleSelection(styleName);
349 }
350 else if (sel != -1)
351 SetSelection(-1);
352 }
353 event.Skip();
354}
5d7836c4 355
e637208a
JS
356/// Do selection
357void wxRichTextStyleListBox::DoSelection(int item)
358{
359 if ( item != wxNOT_FOUND )
360 {
361 wxRichTextStyleDefinition* def = GetStyle(item);
362 if (def && GetRichTextCtrl())
363 {
364 GetRichTextCtrl()->ApplyStyle(def);
365 GetRichTextCtrl()->SetFocus();
5d7836c4
JS
366 }
367 }
368}
369
370#if 0
371wxColour wxRichTextStyleListBox::GetSelectedTextColour(const wxColour& colFg) const
372{
373 return *wxBLACK;
374}
375
376wxColour wxRichTextStyleListBox::GetSelectedTextBgColour(const wxColour& colBg) const
377{
378 return *wxWHITE;
379}
380#endif
381
e637208a
JS
382#if wxUSE_COMBOCTRL
383
384/*!
385 * Style drop-down for a wxComboCtrl
386 */
387
388
389BEGIN_EVENT_TABLE(wxRichTextStyleComboPopup, wxRichTextStyleListBox)
390 EVT_MOTION(wxRichTextStyleComboPopup::OnMouseMove)
391 EVT_LEFT_DOWN(wxRichTextStyleComboPopup::OnMouseClick)
392END_EVENT_TABLE()
393
394void wxRichTextStyleComboPopup::SetStringValue( const wxString& s )
395{
396 m_value = SetStyleSelection(s);
397}
398
399wxString wxRichTextStyleComboPopup::GetStringValue() const
400{
401 int sel = m_value;
402 if (sel > -1)
403 {
404 wxRichTextStyleDefinition* def = GetStyle(sel);
405 if (def)
406 return def->GetName();
407 }
408 return wxEmptyString;
409}
410
411//
412// Popup event handlers
413//
414
415// Mouse hot-tracking
416void wxRichTextStyleComboPopup::OnMouseMove(wxMouseEvent& event)
417{
418 // Move selection to cursor if it is inside the popup
419
420 int itemHere = wxRichTextStyleListBox::HitTest(event.GetPosition());
421 if ( itemHere >= 0 )
422 {
423 wxRichTextStyleListBox::SetSelection(itemHere);
424 m_itemHere = itemHere;
425 }
426 event.Skip();
427}
428
429// On mouse left, set the value and close the popup
430void wxRichTextStyleComboPopup::OnMouseClick(wxMouseEvent& WXUNUSED(event))
431{
432 if (m_itemHere >= 0)
433 m_value = m_itemHere;
434
435 // Ordering is important, so we don't dismiss this popup accidentally
436 // by setting the focus elsewhere e.g. in DoSelection
437 Dismiss();
438
439 if (m_itemHere >= 0)
440 wxRichTextStyleListBox::DoSelection(m_itemHere);
441}
442
443/*!
444 * wxRichTextStyleComboCtrl
445 * A combo for applying styles.
446 */
447
448IMPLEMENT_CLASS(wxRichTextStyleComboCtrl, wxComboCtrl)
449
450BEGIN_EVENT_TABLE(wxRichTextStyleComboCtrl, wxComboCtrl)
451 EVT_IDLE(wxRichTextStyleComboCtrl::OnIdle)
452END_EVENT_TABLE()
453
454bool wxRichTextStyleComboCtrl::Create(wxWindow* parent, wxWindowID id, const wxPoint& pos,
455 const wxSize& size, long style)
456{
457 if (!wxComboCtrl::Create(parent, id, wxEmptyString, pos, size, style))
458 return false;
459
460 SetPopupMaxHeight(400);
461
462 m_stylePopup = new wxRichTextStyleComboPopup;
463
464 SetPopupControl(m_stylePopup);
465
466 return true;
467}
468
469/// Auto-select from style under caret in idle time
470
471// TODO: must be able to show italic, bold, combinations
472// in style box. Do we have a concept of automatic, temporary
473// styles that are added whenever we wish to show a style
474// that doesn't exist already? E.g. "Bold, Italic, Underline".
475// Word seems to generate these things on the fly.
476// If there's a named style already, it uses e.g. Heading1 + Bold, Italic
477// If you unembolden text in a style that has bold, it uses the
478// term "Not bold".
479// TODO: order styles alphabetically. This means indexes can change,
480// so need a different way to specify selections, i.e. by name.
481
482void wxRichTextStyleComboCtrl::OnIdle(wxIdleEvent& event)
483{
484 if (GetRichTextCtrl() && !IsPopupShown())
485 {
486 wxRichTextParagraph* para = GetRichTextCtrl()->GetBuffer().GetParagraphAtPosition(GetRichTextCtrl()->GetCaretPosition());
487 wxRichTextObject* obj = GetRichTextCtrl()->GetBuffer().GetLeafObjectAtPosition(GetRichTextCtrl()->GetCaretPosition());
488
489 wxString styleName;
490
491 // Take into account current default style just chosen by user
492 if (GetRichTextCtrl()->IsDefaultStyleShowing())
493 {
494 if (!GetRichTextCtrl()->GetDefaultStyleEx().GetCharacterStyleName().IsEmpty())
495 styleName = GetRichTextCtrl()->GetDefaultStyleEx().GetCharacterStyleName();
496 else if (!GetRichTextCtrl()->GetDefaultStyleEx().GetParagraphStyleName().IsEmpty())
497 styleName = GetRichTextCtrl()->GetDefaultStyleEx().GetParagraphStyleName();
498 }
499 else if (obj && !obj->GetAttributes().GetCharacterStyleName().IsEmpty())
500 {
501 styleName = obj->GetAttributes().GetCharacterStyleName();
502 }
503 else if (para && !para->GetAttributes().GetParagraphStyleName().IsEmpty())
504 {
505 styleName = para->GetAttributes().GetParagraphStyleName();
506 }
507
508 wxString currentValue = GetValue();
509 if (!styleName.IsEmpty())
510 {
511 // Don't do the selection if it's already set
512 if (currentValue == styleName)
513 return;
514
515 SetValue(styleName);
516 }
517 else if (!currentValue.IsEmpty())
518 SetValue(wxEmptyString);
519 }
520 event.Skip();
521}
522
523#endif
524 // wxUSE_COMBOCTRL
525
5d7836c4
JS
526#endif
527 // wxUSE_HTML
528
529#endif
530 // wxUSE_RICHTEXT