Paradox Game Engine  v1.0.0 beta06
 All Classes Namespaces Files Functions Variables Typedefs Enumerations Enumerator Properties Events Macros Pages
TextTrimmingBehavior.cs
Go to the documentation of this file.
1 // Copyright (c) 2014 Silicon Studio Corp. (http://siliconstudio.co.jp)
2 // This file is distributed under GPL v3. See LICENSE.md for details.
3 using System;
4 using System.Collections.Generic;
5 using System.Globalization;
6 using System.Linq;
7 using System.Text;
8 using System.Text.RegularExpressions;
9 using System.Windows;
10 using System.Windows.Controls;
11 using System.Windows.Media;
12 
13 using SiliconStudio.Presentation.Controls;
14 
15 namespace SiliconStudio.Presentation.Behaviors
16 {
17  public enum TrimPosition
18  {
19  Start,
20  Middle,
21  End
22  }
23 
24  [Obsolete]
25  public class TextTrimmingBehavior : DeferredBehaviorBase<TextBlock>
26  {
27  private FrameworkElement parent;
28  private string originalText;
29 
30  public int Offset { get; set; }
31 
32  private string wordSeparators;
33  private string processedWordSeparators;
34  public string WordSeparators
35  {
36  get { return wordSeparators; }
37  set
38  {
39  wordSeparators = value;
40  ProcessWordSeparators();
41  }
42  }
43 
44  private void ProcessWordSeparators()
45  {
46  processedWordSeparators = Regex.Unescape(wordSeparators);
47  }
48 
49  public TrimmingSource TrimmingSource { get; set; }
50  public TextTrimming TextTrimming { get; set; }
51 
52  public double ContentMargin { get; set; }
53 
54  private double trimmedTextWidth = double.MinValue;
55 
57  {
58  WordSeparators = " ";
59  }
60 
61  protected override void OnAttachedOverride()
62  {
63  parent = VisualTreeHelper.GetParent(AssociatedObject) as FrameworkElement;
64  if (parent == null)
65  {
66  parent = LogicalTreeHelper.GetParent(AssociatedObject) as FrameworkElement;
67  if (parent == null)
68  return;
69  }
70 
71  originalText = AssociatedObject.Text;
72  parent.SizeChanged += OnLogicalParentSizeChanged;
73 
74  ProcessText();
75  }
76 
77  protected override void OnDetachingOverride()
78  {
79  if (parent != null)
80  parent.SizeChanged -= OnLogicalParentSizeChanged;
81  }
82 
83  private void OnLogicalParentSizeChanged(object sender, SizeChangedEventArgs e)
84  {
85  ProcessText();
86  }
87 
88  private void ProcessText()
89  {
90  if (TextTrimming == TextTrimming.None)
91  return;
92 
93  double[] sizes;
94 
95  var textWidth = GetTextWidth(AssociatedObject, originalText, out sizes);
96  var availableWidth = parent.ActualWidth - ContentMargin;
97 
98  // Don't process the text if the current available width is the same that the current trimmed text width
99  double epsilon = GetTextWidth(AssociatedObject, ".");
100  if (Math.Abs(availableWidth - trimmedTextWidth) < epsilon)
101  return;
102 
103  if (availableWidth >= textWidth)
104  AssociatedObject.Text = originalText;
105  else
106  {
107  if (TextTrimming == TextTrimming.CharacterEllipsis)
108  PerformEllipsis(sizes, originalText.ToCharArray().Select(c => c.ToString(CultureInfo.InvariantCulture)).ToArray(), availableWidth);
109  else if (TextTrimming == TextTrimming.WordEllipsis)
110  PerformEllipsis(sizes, Split(originalText), availableWidth);
111  else
112  throw new ArgumentException("Invalid 'TextTrimming' argument.");
113  }
114  }
115 
116  private void PerformEllipsis(double[] sizes, string[] text, double availableWidth)
117  {
118  if (TrimmingSource == TrimmingSource.Begin)
119  {
120  var n = text.Length - 1;
121  var sb = new StringBuilder();
122 
123  for (var i = 0; i < Offset; i++)
124  sb.Append(text[i]);
125  sb.Append("...");
126 
127  var starting = sb.ToString();
128  sb.Clear();
129  var currentWidth = GetTextWidth(AssociatedObject, starting);
130 
131  while (true)
132  {
133  var test = currentWidth + sizes[n];
134 
135  if (test > availableWidth)
136  break;
137 
138  sb.Insert(0, text[n--]);
139  currentWidth = test;
140  }
141 
142  trimmedTextWidth = currentWidth;
143  AssociatedObject.Text = string.Format("{0}{1}", starting, sb);
144  }
145  else if (TrimmingSource == TrimmingSource.Middle)
146  {
147  var currentWidth = GetTextWidth(AssociatedObject, "...");
148  var n1 = 0;
149  var n2 = text.Length - 1;
150 
151  var begin = new StringBuilder();
152  var end = new StringBuilder();
153 
154  while (true)
155  {
156  var test = currentWidth + sizes[n1] + sizes[n2];
157 
158  if (test > availableWidth)
159  break;
160 
161  begin.Append(text[n1++]);
162  end.Insert(0, text[n2--]);
163 
164  currentWidth = test;
165  }
166 
167  AssociatedObject.Text = string.Format("{0}...{1}", begin, end);
168  }
169  else if (TrimmingSource == TrimmingSource.End)
170  {
171  var n = 0;
172  var sb = new StringBuilder();
173  for (var i = 0; i < Offset; i++)
174  sb.Insert(0, text[text.Length - i - 1]);
175  sb.Insert(0, "...");
176 
177  var ending = sb.ToString();
178  sb.Clear();
179  var currentWidth = GetTextWidth(AssociatedObject, ending);
180 
181  while (true)
182  {
183  var test = currentWidth + sizes[n];
184 
185  if (test > availableWidth)
186  break;
187 
188  sb.Append(text[n++]);
189  currentWidth = test;
190  }
191 
192  AssociatedObject.Text = string.Format("{0}{1}", sb, ending);
193  }
194  }
195 
196  private double GetTextWidth(TextBlock textBlock, string text)
197  {
198  double[] dummy;
199  return GetTextWidth(textBlock, text, out dummy);
200  }
201 
202  private double GetTextWidth(TextBlock textBlock, string text, out double[] sizes)
203  {
204  var typeface = new Typeface(textBlock.FontFamily, textBlock.FontStyle, textBlock.FontWeight, textBlock.FontStretch);
205 
206  var totalWidth = 0.0;
207 
208  if (TextTrimming == TextTrimming.CharacterEllipsis)
209  {
210  sizes = new double[text.Length];
211 
212  for (var i = 0; i < text.Length; i++)
213  {
214  string token = text[i].ToString(CultureInfo.CurrentUICulture);
215  var formattedText = new FormattedText(token, CultureInfo.CurrentUICulture, FlowDirection.LeftToRight, typeface, textBlock.FontSize, Brushes.Black);
216  double width = formattedText.Width;
217 
218  sizes[i] = width;
219  totalWidth += width;
220  }
221  }
222  else if (TextTrimming == TextTrimming.WordEllipsis)
223  {
224  var words = Split(text);
225  sizes = new double[words.Length];
226 
227  for (var i = 0; i < words.Length; i++)
228  {
229  string token = words[i];
230  var formattedText = new FormattedText(token, CultureInfo.CurrentUICulture, FlowDirection.LeftToRight, typeface, textBlock.FontSize, Brushes.Black);
231  double width = formattedText.Width;
232  sizes[i] = width;
233  totalWidth += width;
234  }
235  }
236  else
237  throw new ArgumentException("Invalid 'TextTrimming' argument.");
238 
239  return totalWidth;
240  }
241 
242  private string[] Split(string text)
243  {
244  var words = new List<string>();
245 
246  var sb = new StringBuilder();
247  foreach (var c in text)
248  {
249  if (processedWordSeparators.Contains(c))
250  {
251  if (sb.Length > 0)
252  words.Add(sb.ToString());
253 
254  sb.Clear();
255 
256  words.Add(c.ToString(CultureInfo.InvariantCulture));
257  continue;
258  }
259 
260  sb.Append(c);
261  }
262 
263  if (sb.Length > 0)
264  words.Add(sb.ToString());
265 
266  return words.ToArray();
267  }
268  }
269 }