]>
Commit | Line | Data |
---|---|---|
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 | ||
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 | { | |
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 | |
74 | wxRichTextStyleDefinition* 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 | |
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) | |
e637208a | 103 | EVT_IDLE(wxRichTextStyleListBox::OnIdle) |
5d7836c4 JS |
104 | END_EVENT_TABLE() |
105 | ||
106 | wxRichTextStyleListBox::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 | ||
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); | |
5d7836c4 JS |
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 | ||
e637208a JS |
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 | ||
5d7836c4 JS |
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 | |
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 | |
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()); | |
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 |
315 | void 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 |
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(); | |
5d7836c4 JS |
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 | ||
e637208a JS |
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 | ||
5d7836c4 JS |
526 | #endif |
527 | // wxUSE_HTML | |
528 | ||
529 | #endif | |
530 | // wxUSE_RICHTEXT |