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