]> git.saurik.com Git - wxWidgets.git/blame - src/richtext/richtextstyles.cpp
fixing coordinate conversions
[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__
fe5aa22c 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
fe5aa22c 24 #include "wx/wx.h"
5d7836c4
JS
25#endif
26
5d7836c4
JS
27#include "wx/filename.h"
28#include "wx/clipbrd.h"
29#include "wx/wfstream.h"
5d7836c4 30
5d7836c4
JS
31#include "wx/richtext/richtextctrl.h"
32
33IMPLEMENT_CLASS(wxRichTextStyleDefinition, wxObject)
34IMPLEMENT_CLASS(wxRichTextCharacterStyleDefinition, wxRichTextStyleDefinition)
35IMPLEMENT_CLASS(wxRichTextParagraphStyleDefinition, wxRichTextStyleDefinition)
36
fe5aa22c
JS
37/*!
38 * A definition
39 */
40
41void 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
48bool 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
57void wxRichTextParagraphStyleDefinition::Copy(const wxRichTextParagraphStyleDefinition& def)
58{
59 wxRichTextStyleDefinition::Copy(def);
60
61 m_nextStyle = def.m_nextStyle;
62}
63
64bool wxRichTextParagraphStyleDefinition::operator ==(const wxRichTextParagraphStyleDefinition& def) const
65{
66 return (Eq(def) && m_nextStyle == def.m_nextStyle);
67}
68
5d7836c4
JS
69/*!
70 * The style manager
71 */
72
73IMPLEMENT_CLASS(wxRichTextStyleSheet, wxObject)
74
75/// Initialisation
76void wxRichTextStyleSheet::Init()
77{
78}
79
80/// Add a definition to one of the style lists
81bool 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
89bool wxRichTextStyleSheet::RemoveStyle(wxList& list, wxRichTextStyleDefinition* def, bool deleteStyle)
90{
09f14108 91 wxList::compatibility_iterator node = list.Find(def);
5d7836c4
JS
92 if (node)
93 {
94 wxRichTextStyleDefinition* def = (wxRichTextStyleDefinition*) node->GetData();
09f14108 95 list.Erase(node);
5d7836c4
JS
96 if (deleteStyle)
97 delete def;
98 return true;
99 }
100 else
101 return false;
102}
103
104/// Find a definition by name
105wxRichTextStyleDefinition* wxRichTextStyleSheet::FindStyle(const wxList& list, const wxString& name) const
106{
09f14108 107 for (wxList::compatibility_iterator node = list.GetFirst(); node; node = node->GetNext())
5d7836c4
JS
108 {
109 wxRichTextStyleDefinition* def = (wxRichTextStyleDefinition*) node->GetData();
110 if (def->GetName().Lower() == name.Lower())
111 return def;
112 }
61399247 113 return NULL;
5d7836c4
JS
114}
115
116/// Delete all styles
117void wxRichTextStyleSheet::DeleteStyles()
118{
119 WX_CLEAR_LIST(wxList, m_characterStyleDefinitions);
120 WX_CLEAR_LIST(wxList, m_paragraphStyleDefinitions);
121}
122
fe5aa22c
JS
123/// Add a definition to the character style list
124bool 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
131bool wxRichTextStyleSheet::AddParagraphStyle(wxRichTextParagraphStyleDefinition* def)
132{
133 def->GetStyle().SetParagraphStyleName(def->GetName());
134 return AddStyle(m_paragraphStyleDefinitions, def);
135}
136
137/// Copy
138void 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
158bool wxRichTextStyleSheet::operator==(const wxRichTextStyleSheet& WXUNUSED(sheet)) const
159{
160 // TODO
161 return false;
162}
163
164
5d7836c4
JS
165#if wxUSE_HTML
166/*!
167 * wxRichTextStyleListBox class declaration
168 * A listbox to display styles.
169 */
170
171IMPLEMENT_CLASS(wxRichTextStyleListBox, wxHtmlListBox)
172
173BEGIN_EVENT_TABLE(wxRichTextStyleListBox, wxHtmlListBox)
174 EVT_LISTBOX(wxID_ANY, wxRichTextStyleListBox::OnSelect)
175 EVT_LEFT_DOWN(wxRichTextStyleListBox::OnLeftDown)
e637208a 176 EVT_IDLE(wxRichTextStyleListBox::OnIdle)
5d7836c4
JS
177END_EVENT_TABLE()
178
179wxRichTextStyleListBox::wxRichTextStyleListBox(wxWindow* parent, wxWindowID id, const wxPoint& pos,
e637208a 180 const wxSize& size, long style)
5d7836c4 181{
e637208a
JS
182 Init();
183 Create(parent, id, pos, size, style);
184}
185
186bool 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);
5d7836c4
JS
190}
191
192wxRichTextStyleListBox::~wxRichTextStyleListBox()
193{
194}
195
196/// Returns the HTML for this item
197wxString 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
222wxRichTextStyleDefinition* 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
238void wxRichTextStyleListBox::UpdateStyles()
239{
240 if (GetStyleSheet())
241 {
242 SetItemCount(GetStyleSheet()->GetParagraphStyleCount()+GetStyleSheet()->GetCharacterStyleCount());
243 Refresh();
244 }
245}
246
e637208a
JS
247// Get index for style name
248int 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
270int wxRichTextStyleListBox::SetStyleSelection(const wxString& name)
271{
272 int i = GetIndexForStyle(name);
273 if (i > -1)
274 SetSelection(i);
275 return i;
276}
277
5d7836c4
JS
278// Convert a colour to a 6-digit hex string
279static 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
291wxString 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
61399247 307 size += 12 - def->GetStyle().GetFontSize();
5d7836c4
JS
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
355int 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
367void 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
378void wxRichTextStyleListBox::OnLeftDown(wxMouseEvent& event)
379{
380 wxVListBox::OnLeftDown(event);
381
382 int item = HitTest(event.GetPosition());
86015e55
JS
383 if (item != wxNOT_FOUND && GetApplyOnSelection())
384 ApplyStyle(item);
e637208a 385}
5d7836c4 386
e637208a
JS
387/// Auto-select from style under caret in idle time
388void wxRichTextStyleListBox::OnIdle(wxIdleEvent& event)
389{
390 if (CanAutoSetSelection() && GetRichTextCtrl())
5d7836c4 391 {
fe5aa22c
JS
392 int adjustedCaretPos = GetRichTextCtrl()->GetAdjustedCaretPosition(GetRichTextCtrl()->GetCaretPosition());
393
394 wxRichTextParagraph* para = GetRichTextCtrl()->GetBuffer().GetParagraphAtPosition(adjustedCaretPos);
395 wxRichTextObject* obj = GetRichTextCtrl()->GetBuffer().GetLeafObjectAtPosition(adjustedCaretPos);
e637208a
JS
396
397 wxString styleName;
398
399 // Take into account current default style just chosen by user
400 if (GetRichTextCtrl()->IsDefaultStyleShowing())
5d7836c4 401 {
e637208a
JS
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 }
61399247 415
e637208a
JS
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;
5d7836c4 422
e637208a
JS
423 SetStyleSelection(styleName);
424 }
425 else if (sel != -1)
426 SetSelection(-1);
427 }
428 event.Skip();
429}
5d7836c4 430
e637208a 431/// Do selection
86015e55 432void wxRichTextStyleListBox::ApplyStyle(int item)
e637208a
JS
433{
434 if ( item != wxNOT_FOUND )
435 {
436 wxRichTextStyleDefinition* def = GetStyle(item);
437 if (def && GetRichTextCtrl())
438 {
439 GetRichTextCtrl()->ApplyStyle(def);
440 GetRichTextCtrl()->SetFocus();
5d7836c4
JS
441 }
442 }
443}
444
445#if 0
446wxColour wxRichTextStyleListBox::GetSelectedTextColour(const wxColour& colFg) const
447{
448 return *wxBLACK;
449}
450
451wxColour wxRichTextStyleListBox::GetSelectedTextBgColour(const wxColour& colBg) const
452{
453 return *wxWHITE;
454}
455#endif
456
e637208a
JS
457#if wxUSE_COMBOCTRL
458
459/*!
460 * Style drop-down for a wxComboCtrl
461 */
462
463
464BEGIN_EVENT_TABLE(wxRichTextStyleComboPopup, wxRichTextStyleListBox)
465 EVT_MOTION(wxRichTextStyleComboPopup::OnMouseMove)
466 EVT_LEFT_DOWN(wxRichTextStyleComboPopup::OnMouseClick)
467END_EVENT_TABLE()
468
469void wxRichTextStyleComboPopup::SetStringValue( const wxString& s )
470{
471 m_value = SetStyleSelection(s);
472}
473
474wxString 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
491void 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
505void 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
86015e55 511 // by setting the focus elsewhere e.g. in ApplyStyle
e637208a
JS
512 Dismiss();
513
514 if (m_itemHere >= 0)
86015e55 515 wxRichTextStyleListBox::ApplyStyle(m_itemHere);
e637208a
JS
516}
517
518/*!
519 * wxRichTextStyleComboCtrl
520 * A combo for applying styles.
521 */
522
523IMPLEMENT_CLASS(wxRichTextStyleComboCtrl, wxComboCtrl)
524
525BEGIN_EVENT_TABLE(wxRichTextStyleComboCtrl, wxComboCtrl)
526 EVT_IDLE(wxRichTextStyleComboCtrl::OnIdle)
527END_EVENT_TABLE()
528
529bool 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
557void wxRichTextStyleComboCtrl::OnIdle(wxIdleEvent& event)
558{
559 if (GetRichTextCtrl() && !IsPopupShown())
560 {
fe5aa22c
JS
561 int adjustedCaretPos = GetRichTextCtrl()->GetAdjustedCaretPosition(GetRichTextCtrl()->GetCaretPosition());
562
563 wxRichTextParagraph* para = GetRichTextCtrl()->GetBuffer().GetParagraphAtPosition(adjustedCaretPos);
564 wxRichTextObject* obj = GetRichTextCtrl()->GetBuffer().GetLeafObjectAtPosition(adjustedCaretPos);
e637208a
JS
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
5d7836c4
JS
603#endif
604 // wxUSE_HTML
605
606#endif
607 // wxUSE_RICHTEXT