Style listbox now shows current style
[wxWidgets.git] / src / richtext / richtextstyles.cpp
1 /////////////////////////////////////////////////////////////////////////////
2 // Name: src/richtext/richtextstyles.cpp
3 // Purpose: Style management for wxRichTextCtrl
4 // Author: Julian Smart
5 // Modified by:
6 // Created: 2005-09-30
7 // RCS-ID: $Id$
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__
16 #pragma hdrstop
17 #endif
18
19 #if wxUSE_RICHTEXT
20
21 #include "wx/richtext/richtextstyles.h"
22
23 #ifndef WX_PRECOMP
24 #include "wx/dcclient.h"
25 #include "wx/module.h"
26 #endif
27
28 #include "wx/filename.h"
29 #include "wx/clipbrd.h"
30 #include "wx/wfstream.h"
31
32 #include "wx/richtext/richtextctrl.h"
33
34 IMPLEMENT_CLASS(wxRichTextStyleDefinition, wxObject)
35 IMPLEMENT_CLASS(wxRichTextCharacterStyleDefinition, wxRichTextStyleDefinition)
36 IMPLEMENT_CLASS(wxRichTextParagraphStyleDefinition, wxRichTextStyleDefinition)
37
38 /*!
39 * The style manager
40 */
41
42 IMPLEMENT_CLASS(wxRichTextStyleSheet, wxObject)
43
44 /// Initialisation
45 void wxRichTextStyleSheet::Init()
46 {
47 }
48
49 /// Add a definition to one of the style lists
50 bool 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
58 bool wxRichTextStyleSheet::RemoveStyle(wxList& list, wxRichTextStyleDefinition* def, bool deleteStyle)
59 {
60 wxList::compatibility_iterator node = list.Find(def);
61 if (node)
62 {
63 wxRichTextStyleDefinition* def = (wxRichTextStyleDefinition*) node->GetData();
64 list.Erase(node);
65 if (deleteStyle)
66 delete def;
67 return true;
68 }
69 else
70 return false;
71 }
72
73 /// Find a definition by name
74 wxRichTextStyleDefinition* wxRichTextStyleSheet::FindStyle(const wxList& list, const wxString& name) const
75 {
76 for (wxList::compatibility_iterator node = list.GetFirst(); node; node = node->GetNext())
77 {
78 wxRichTextStyleDefinition* def = (wxRichTextStyleDefinition*) node->GetData();
79 if (def->GetName().Lower() == name.Lower())
80 return def;
81 }
82 return NULL;
83 }
84
85 /// Delete all styles
86 void 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
98 IMPLEMENT_CLASS(wxRichTextStyleListBox, wxHtmlListBox)
99
100 BEGIN_EVENT_TABLE(wxRichTextStyleListBox, wxHtmlListBox)
101 EVT_LISTBOX(wxID_ANY, wxRichTextStyleListBox::OnSelect)
102 EVT_LEFT_DOWN(wxRichTextStyleListBox::OnLeftDown)
103 EVT_IDLE(wxRichTextStyleListBox::OnIdle)
104 END_EVENT_TABLE()
105
106 wxRichTextStyleListBox::wxRichTextStyleListBox(wxWindow* parent, wxWindowID id, const wxPoint& pos,
107 const wxSize& size, long style)
108 {
109 Init();
110 Create(parent, id, pos, size, style);
111 }
112
113 bool 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);
117 }
118
119 wxRichTextStyleListBox::~wxRichTextStyleListBox()
120 {
121 }
122
123 /// Returns the HTML for this item
124 wxString 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
149 wxRichTextStyleDefinition* 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
165 void wxRichTextStyleListBox::UpdateStyles()
166 {
167 if (GetStyleSheet())
168 {
169 SetItemCount(GetStyleSheet()->GetParagraphStyleCount()+GetStyleSheet()->GetCharacterStyleCount());
170 Refresh();
171 }
172 }
173
174 // Get index for style name
175 int 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
197 int wxRichTextStyleListBox::SetStyleSelection(const wxString& name)
198 {
199 int i = GetIndexForStyle(name);
200 if (i > -1)
201 SetSelection(i);
202 return i;
203 }
204
205 // Convert a colour to a 6-digit hex string
206 static 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
218 wxString 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
234 size += 12 - def->GetStyle().GetFontSize();
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
282 int 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
294 void 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
305 void wxRichTextStyleListBox::OnLeftDown(wxMouseEvent& event)
306 {
307 wxVListBox::OnLeftDown(event);
308
309 int item = HitTest(event.GetPosition());
310 if (item != wxNOT_FOUND)
311 DoSelection(item);
312 }
313
314 /// Auto-select from style under caret in idle time
315 void wxRichTextStyleListBox::OnIdle(wxIdleEvent& event)
316 {
317 if (CanAutoSetSelection() && GetRichTextCtrl())
318 {
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())
326 {
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 }
340
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;
347
348 SetStyleSelection(styleName);
349 }
350 else if (sel != -1)
351 SetSelection(-1);
352 }
353 event.Skip();
354 }
355
356 /// Do selection
357 void 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();
366 }
367 }
368 }
369
370 #if 0
371 wxColour wxRichTextStyleListBox::GetSelectedTextColour(const wxColour& colFg) const
372 {
373 return *wxBLACK;
374 }
375
376 wxColour wxRichTextStyleListBox::GetSelectedTextBgColour(const wxColour& colBg) const
377 {
378 return *wxWHITE;
379 }
380 #endif
381
382 #if wxUSE_COMBOCTRL
383
384 /*!
385 * Style drop-down for a wxComboCtrl
386 */
387
388
389 BEGIN_EVENT_TABLE(wxRichTextStyleComboPopup, wxRichTextStyleListBox)
390 EVT_MOTION(wxRichTextStyleComboPopup::OnMouseMove)
391 EVT_LEFT_DOWN(wxRichTextStyleComboPopup::OnMouseClick)
392 END_EVENT_TABLE()
393
394 void wxRichTextStyleComboPopup::SetStringValue( const wxString& s )
395 {
396 m_value = SetStyleSelection(s);
397 }
398
399 wxString 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
416 void 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
430 void 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
448 IMPLEMENT_CLASS(wxRichTextStyleComboCtrl, wxComboCtrl)
449
450 BEGIN_EVENT_TABLE(wxRichTextStyleComboCtrl, wxComboCtrl)
451 EVT_IDLE(wxRichTextStyleComboCtrl::OnIdle)
452 END_EVENT_TABLE()
453
454 bool 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
482 void 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
526 #endif
527 // wxUSE_HTML
528
529 #endif
530 // wxUSE_RICHTEXT