1 /* -*- Mode: C++; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */
6 * Moonlight List (moonlight-list@lists.ximian.com)
8 * Copyright 2007 Novell, Inc. (http://www.novell.com)
10 * See the LICENSE file included with the distribution for details.
17 #include <sys/types.h>
22 #include "file-downloader.h"
23 #include "textblock.h"
31 #include "deployment.h"
33 // Unicode Line Separator (\u2028)
34 static const char utf8_linebreak
[3] = { 0xe2, 0x80, 0xa8 };
35 #define utf8_linebreak_len 3
44 SetObjectType (Type::INLINE
);
45 font
= new TextFontDescription ();
46 downloaders
= g_ptr_array_new ();
52 CleanupDownloaders ();
53 g_ptr_array_free (downloaders
, true);
58 Inline::CleanupDownloaders ()
60 Downloader
*downloader
;
63 for (i
= 0; i
< downloaders
->len
; i
++) {
64 downloader
= (Downloader
*) downloaders
->pdata
[i
];
65 downloader
->RemoveHandler (Downloader::CompletedEvent
, downloader_complete
, this);
70 g_ptr_array_set_size (downloaders
, 0);
74 Inline::AddFontSource (Downloader
*downloader
)
76 downloader
->AddHandler (downloader
->CompletedEvent
, downloader_complete
, this);
77 g_ptr_array_add (downloaders
, downloader
);
80 if (downloader
->Started () || downloader
->Completed ()) {
81 if (downloader
->Completed ())
82 DownloaderComplete (downloader
);
84 // This is what actually triggers the download
90 Inline::AddFontResource (const char *resource
)
92 FontManager
*manager
= Deployment::GetCurrent ()->GetFontManager ();
93 Application
*application
= Application::GetCurrent ();
94 Downloader
*downloader
;
101 if (!application
|| !uri
->Parse (resource
) || !(path
= application
->GetResourceAsPath (GetResourceBase(), uri
))) {
102 if ((surface
= GetSurface ()) && (downloader
= surface
->CreateDownloader ())) {
103 downloader
->Open ("GET", resource
, FontPolicy
);
104 AddFontSource (downloader
);
105 downloader
->unref ();
113 manager
->AddResource (resource
, path
);
119 Inline::OnPropertyChanged (PropertyChangedEventArgs
*args
, MoonError
*error
)
121 if (args
->GetProperty ()->GetOwnerType () != Type::INLINE
) {
122 DependencyObject::OnPropertyChanged (args
, error
);
126 if (args
->GetId () == Inline::FontFamilyProperty
) {
127 FontFamily
*family
= args
->GetNewValue () ? args
->GetNewValue ()->AsFontFamily () : NULL
;
128 char **families
, *fragment
;
131 CleanupDownloaders ();
133 if (family
&& family
->source
) {
134 families
= g_strsplit (family
->source
, ",", -1);
135 for (i
= 0; families
[i
]; i
++) {
136 g_strstrip (families
[i
]);
137 if ((fragment
= strchr (families
[i
], '#'))) {
138 // the first portion of this string is the resource name...
140 AddFontResource (families
[i
]);
143 g_strfreev (families
);
147 NotifyListenersOfPropertyChange (args
, error
);
151 Inline::OnSubPropertyChanged (DependencyProperty
*prop
, DependencyObject
*obj
, PropertyChangedEventArgs
*subobj_args
)
153 if (prop
&& prop
->GetId () == Inline::ForegroundProperty
) {
154 // this isn't exactly what we want, I don't
155 // think... but it'll have to do.
156 NotifyListenersOfPropertyChange (prop
, NULL
);
158 DependencyObject::OnSubPropertyChanged (prop
, obj
, subobj_args
);
163 Inline::Equals (Inline
*item
)
165 const char *lang0
, *lang1
;
167 if (item
->GetObjectType () != GetObjectType ())
170 if (*item
->GetFontFamily () != *GetFontFamily ())
173 if (item
->GetFontSize () != GetFontSize ())
176 if (item
->GetFontStyle () != GetFontStyle ())
179 if (item
->GetFontWeight () != GetFontWeight ())
182 if (item
->GetFontStretch () != GetFontStretch ())
185 if (item
->GetTextDecorations () != GetTextDecorations ())
188 lang0
= item
->GetLanguage ();
189 lang1
= GetLanguage ();
191 if ((lang0
&& !lang1
) || (!lang0
&& lang1
))
194 if (lang0
&& lang1
&& strcmp (lang0
, lang1
) != 0)
197 // this isn't really correct - we should be checking
198 // the innards of the foreground brushes, but we're
199 // guaranteed to never have a false positive here.
200 if (item
->GetForeground () != GetForeground ())
203 // OK, as best we can tell - they are equal
208 Inline::UpdateFontDescription (const char *source
, bool force
)
210 FontFamily
*family
= GetFontFamily ();
211 bool changed
= false;
213 if (font
->SetSource (source
))
216 if (font
->SetFamily (family
? family
->source
: NULL
))
219 if (font
->SetStretch (GetFontStretch ()->stretch
))
222 if (font
->SetWeight (GetFontWeight ()->weight
))
225 if (font
->SetStyle (GetFontStyle ()->style
))
228 if (font
->SetSize (GetFontSize ()))
231 if (font
->SetLanguage (GetLanguage ()))
243 Inline::downloader_complete (EventObject
*sender
, EventArgs
*calldata
, gpointer closure
)
245 ((Inline
*) closure
)->DownloaderComplete ((Downloader
*) sender
);
249 Inline::DownloaderComplete (Downloader
*downloader
)
251 FontManager
*manager
= Deployment::GetCurrent ()->GetFontManager ();
252 char *resource
, *filename
;
253 InternalDownloader
*idl
;
257 // get the downloaded file path (enforces a mozilla workaround for files smaller than 64k)
258 if (!(filename
= downloader
->GetDownloadedFilename (NULL
)))
263 if (!(idl
= downloader
->GetInternalDownloader ()))
266 if (!(idl
->GetObjectType () == Type::FILEDOWNLOADER
))
269 uri
= downloader
->GetUri ();
271 // If the downloaded file was a zip file, this'll get the path to the
272 // extracted zip directory, else it will simply be the path to the
274 if (!(path
= ((FileDownloader
*) idl
)->GetUnzippedPath ()))
277 resource
= uri
->ToString ((UriToStringFlags
) (UriHidePasswd
| UriHideQuery
| UriHideFragment
));
278 manager
->AddResource (resource
, path
);
288 Run::Equals (Inline
*item
)
290 const char *text
, *itext
;
292 if (!Inline::Equals (item
))
295 itext
= ((Run
*) item
)->GetText ();
299 if (text
&& itext
&& strcmp (text
, itext
) != 0)
301 else if ((text
&& !itext
) || (!text
&& itext
))
312 TextBlock::TextBlock ()
314 SetObjectType (Type::TEXTBLOCK
);
316 font
= new TextFontDescription ();
318 downloaders
= g_ptr_array_new ();
319 layout
= new TextLayout ();
330 TextBlock::~TextBlock ()
332 CleanupDownloaders (true);
333 g_ptr_array_free (downloaders
, true);
340 TextBlock::CleanupDownloaders (bool all
)
342 Downloader
*downloader
;
345 for (i
= 0; i
< downloaders
->len
; i
++) {
346 downloader
= (Downloader
*) downloaders
->pdata
[i
];
348 if (all
|| downloader
!= source
) {
349 downloader
->RemoveHandler (Downloader::CompletedEvent
, downloader_complete
, this);
350 downloader
->Abort ();
351 downloader
->unref ();
355 g_ptr_array_set_size (downloaders
, 0);
357 if (source
&& !all
) {
358 g_ptr_array_add (downloaders
, source
);
364 g_free (font_source
);
370 TextBlock::AddFontSource (Downloader
*downloader
)
372 downloader
->AddHandler (downloader
->CompletedEvent
, downloader_complete
, this);
373 g_ptr_array_add (downloaders
, downloader
);
376 if (downloader
->Started () || downloader
->Completed ()) {
377 if (downloader
->Completed ())
378 DownloaderComplete (downloader
);
380 // This is what actually triggers the download
386 TextBlock::SetFontSource (Downloader
*downloader
)
388 CleanupDownloaders (true);
392 font_source
= downloader
->GetUri ()->ToString ((UriToStringFlags
) (UriHidePasswd
| UriHideQuery
| UriHideFragment
));
393 AddFontSource (downloader
);
397 UpdateFontDescriptions (true);
404 TextBlock::AddFontResource (const char *resource
)
406 FontManager
*manager
= Deployment::GetCurrent ()->GetFontManager ();
407 Application
*application
= Application::GetCurrent ();
408 Downloader
*downloader
;
415 if (!application
|| !uri
->Parse (resource
) || !(path
= application
->GetResourceAsPath (GetResourceBase(), uri
))) {
416 if ((surface
= GetSurface ()) && (downloader
= surface
->CreateDownloader ())) {
417 downloader
->Open ("GET", resource
, FontPolicy
);
418 AddFontSource (downloader
);
419 downloader
->unref ();
427 manager
->AddResource (resource
, path
);
433 TextBlock::Render (cairo_t
*cr
, Region
*region
, bool path_only
)
436 cairo_set_matrix (cr
, &absolute_xform
);
439 RenderLayoutClip (cr
);
447 TextBlock::ComputeBounds ()
449 Size
actual (GetActualWidth (), GetActualHeight ());
450 Size framework
= ApplySizeConstraints (actual
);
452 framework
= framework
.Max (actual
);
454 Rect extents
= Rect (0,0,framework
.width
, framework
.height
);
456 bounds
= bounds_with_children
= IntersectBoundsWithClipPath (extents
, false).Transform (&absolute_xform
);
460 TextBlock::GetSizeForBrush (cairo_t
*cr
, double *width
, double *height
)
462 *width
= actual_width
;
463 *height
= actual_height
;
467 TextBlock::GetTransformOrigin ()
469 Point
*user_xform_origin
= GetRenderTransformOrigin ();
470 return Point (actual_width
* user_xform_origin
->x
,
471 actual_height
* user_xform_origin
->y
);
475 TextBlock::ComputeActualSize ()
477 Thickness padding
= *GetPadding ();
478 Size result
= FrameworkElement::ComputeActualSize ();
480 if (!LayoutInformation::GetPreviousConstraint (this)) {
481 Size constraint
= Size (INFINITY
, INFINITY
);
483 constraint
= ApplySizeConstraints (constraint
);
485 constraint
= constraint
.GrowBy (-padding
);
489 result
= Size (actual_width
, actual_height
);
490 result
= result
.GrowBy (padding
);
496 TextBlock::MeasureOverride (Size availableSize
)
498 Thickness padding
= *GetPadding ();
502 constraint
= availableSize
.GrowBy (-padding
);
505 desired
= Size (actual_width
, actual_height
).GrowBy (padding
);
507 SetActualHeight (desired
.height
);
508 SetActualWidth (desired
.width
);
510 if (GetUseLayoutRounding ())
511 desired
.width
= ceil (desired
.width
);
513 desired
= desired
.Min (availableSize
);
519 TextBlock::ArrangeOverride (Size finalSize
)
521 Thickness padding
= *GetPadding ();
525 constraint
= finalSize
.GrowBy (-padding
);
528 arranged
= Size (actual_width
, actual_height
).GrowBy (padding
);
530 arranged
= arranged
.Max (finalSize
);
531 arranged
= ApplySizeConstraints (arranged
);
533 layout
->SetAvailableWidth (arranged
.GrowBy (-padding
).width
);
539 TextBlock::UpdateLayoutAttributes ()
541 InlineCollection
*inlines
= GetInlines ();
542 TextLayoutAttributes
*attrs
;
548 InvalidateMeasure ();
549 InvalidateArrange ();
552 UpdateFontDescription (false);
554 if (inlines
!= NULL
) {
555 for (int i
= 0; i
< inlines
->GetCount (); i
++) {
556 item
= inlines
->GetValueAt (i
)->AsInline ();
557 item
->UpdateFontDescription (font_source
, false);
559 switch (item
->GetObjectType ()) {
561 text
= ((Run
*) item
)->GetText ();
563 if (text
&& text
[0]) {
564 attrs
= new TextLayoutAttributes ((ITextAttributes
*) item
, length
);
565 runs
->Append (attrs
);
567 length
+= strlen (text
);
571 case Type::LINEBREAK
:
572 attrs
= new TextLayoutAttributes ((ITextAttributes
*) item
, length
);
573 runs
->Append (attrs
);
575 length
+= utf8_linebreak_len
;
582 if (inlines
->GetCount () > 0)
586 layout
->SetText (GetText (), length
);
587 layout
->SetTextAttributes (runs
);
591 TextBlock::UpdateFontDescription (bool force
)
593 FontFamily
*family
= GetFontFamily ();
594 bool changed
= false;
596 if (font
->SetSource (font_source
))
599 if (font
->SetFamily (family
? family
->source
: NULL
))
602 if (font
->SetStretch (GetFontStretch ()->stretch
))
605 if (font
->SetWeight (GetFontWeight ()->weight
))
608 if (font
->SetStyle (GetFontStyle ()->style
))
611 if (font
->SetSize (GetFontSize ()))
614 if (font
->SetLanguage (GetLanguage ()))
623 layout
->SetBaseFont (font
->GetFont ());
629 TextBlock::UpdateFontDescriptions (bool force
)
631 InlineCollection
*inlines
= GetInlines ();
632 bool changed
= false;
635 changed
= UpdateFontDescription (force
);
637 if (inlines
!= NULL
) {
638 for (int i
= 0; i
< inlines
->GetCount (); i
++) {
639 item
= inlines
->GetValueAt (i
)->AsInline ();
640 if (item
->UpdateFontDescription (font_source
, force
))
645 layout
->ResetState ();
649 InvalidateMeasure ();
650 InvalidateArrange ();
659 TextBlock::Layout (Size constraint
)
661 if (was_set
&& !GetValueNoDefault (TextBlock::TextProperty
)) {
662 // If the Text property had been set once upon a time,
663 // but is currently empty, Silverlight seems to set
664 // the ActualHeight property to the font height. See
665 // bug #405514 for details.
666 TextFontDescription
*desc
= new TextFontDescription ();
667 FontFamily
*family
= GetFontFamily ();
670 desc
->SetFamily (family
? family
->source
: NULL
);
671 desc
->SetStretch (GetFontStretch ()->stretch
);
672 desc
->SetWeight (GetFontWeight ()->weight
);
673 desc
->SetStyle (GetFontStyle ()->style
);
674 desc
->SetSize (GetFontSize ());
676 font
= desc
->GetFont ();
677 actual_height
= font
->Height ();
680 } else if (!was_set
) {
681 // If the Text property has never been set, then its
682 // extents should both be 0.0. See bug #435798 for
687 layout
->SetMaxWidth (constraint
.width
);
690 layout
->GetActualExtents (&actual_width
, &actual_height
);
697 TextBlock::Paint (cairo_t
*cr
)
699 Thickness
*padding
= GetPadding ();
700 Point
offset (padding
->left
, padding
->top
);
702 cairo_set_matrix (cr
, &absolute_xform
);
703 layout
->Render (cr
, GetOriginPoint (), offset
);
705 if (moonlight_flags
& RUNTIME_INIT_SHOW_TEXTBOXES
) {
706 cairo_set_source_rgba (cr
, 0.0, 1.0, 0.0, 1.0);
707 cairo_set_line_width (cr
, 1);
708 cairo_rectangle (cr
, padding
->left
, padding
->top
, actual_width
, actual_height
);
714 TextBlock::GetTextInternal (InlineCollection
*inlines
)
722 return g_strdup ("");
724 block
= g_string_new ("");
726 for (int i
= 0; i
< inlines
->GetCount (); i
++) {
727 item
= inlines
->GetValueAt (i
)->AsInline ();
729 switch (item
->GetObjectType ()) {
731 text
= ((Run
*) item
)->GetText ();
734 g_string_append (block
, text
);
736 case Type::LINEBREAK
:
737 g_string_append_len (block
, utf8_linebreak
, utf8_linebreak_len
);
745 g_string_free (block
, false);
751 TextBlock::SetTextInternal (const char *text
)
753 InlineCollection
*inlines
;
757 // Note: calling GetValue() may cause the InlineCollection to be
758 // autocreated, so we need to prevent reentrancy here.
761 value
= GetValue (TextBlock::InlinesProperty
);
762 inlines
= value
->AsInlineCollection ();
767 run
->SetAutogenerated (true);
772 // setting text to null results in String.Empty
773 SetValue (TextBlock::TextProperty
, Value (""));
780 TextBlock::OnPropertyChanged (PropertyChangedEventArgs
*args
, MoonError
*error
)
782 bool invalidate
= true;
784 if (args
->GetProperty ()->GetOwnerType () != Type::TEXTBLOCK
) {
785 FrameworkElement::OnPropertyChanged (args
, error
);
787 if (args
->GetId () == FrameworkElement::LanguageProperty
) {
788 // a change in xml:lang might change font characteristics
789 if (UpdateFontDescriptions (false)) {
790 InvalidateMeasure ();
791 InvalidateArrange ();
798 if (args->GetId () == FrameworkElement::WidthProperty) {
799 //if (layout->SetMaxWidth (args->GetNewValue()->AsDouble ()))
808 if (args
->GetId () == TextBlock::FontFamilyProperty
) {
809 FontFamily
*family
= args
->GetNewValue () ? args
->GetNewValue ()->AsFontFamily () : NULL
;
810 char **families
, *fragment
;
813 CleanupDownloaders (false);
815 if (family
&& family
->source
) {
816 families
= g_strsplit (family
->source
, ",", -1);
817 for (i
= 0; families
[i
]; i
++) {
818 g_strstrip (families
[i
]);
819 if ((fragment
= strchr (families
[i
], '#'))) {
820 // the first portion of this string is the resource name...
822 AddFontResource (families
[i
]);
825 g_strfreev (families
);
828 if (UpdateFontDescriptions (false))
830 } else if (args
->GetId () == TextBlock::FontSizeProperty
) {
831 if (UpdateFontDescriptions (false))
833 } else if (args
->GetId () == TextBlock::FontStretchProperty
) {
834 if (UpdateFontDescriptions (false))
836 } else if (args
->GetId () == TextBlock::FontStyleProperty
) {
837 if (UpdateFontDescriptions (false))
839 } else if (args
->GetId () == TextBlock::FontWeightProperty
) {
840 if (UpdateFontDescriptions (false))
842 } else if (args
->GetId () == TextBlock::TextProperty
) {
844 // result of a change to the TextBlock.Text property
845 const char *text
= args
->GetNewValue() ? args
->GetNewValue()->AsString () : NULL
;
847 SetTextInternal (text
);
848 UpdateLayoutAttributes ();
851 // result of a change to the TextBlock.Inlines property
852 UpdateLayoutAttributes ();
855 } else if (args
->GetId () == TextBlock::TextDecorationsProperty
) {
857 } else if (args
->GetId () == TextBlock::TextWrappingProperty
) {
858 dirty
= layout
->SetTextWrapping ((TextWrapping
) args
->GetNewValue()->AsInt32 ());
859 } else if (args
->GetId () == TextBlock::InlinesProperty
) {
861 // result of a change to the TextBlock.Inlines property
862 InlineCollection
*inlines
= args
->GetNewValue() ? args
->GetNewValue()->AsInlineCollection () : NULL
;
865 SetValue (TextBlock::TextProperty
, Value (GetTextInternal (inlines
), true));
868 UpdateLayoutAttributes ();
871 // this should be the result of Inlines being autocreated
872 UpdateLayoutAttributes ();
875 } else if (args
->GetId () == TextBlock::LineStackingStrategyProperty
) {
876 dirty
= layout
->SetLineStackingStrategy ((LineStackingStrategy
) args
->GetNewValue()->AsInt32 ());
877 } else if (args
->GetId () == TextBlock::LineHeightProperty
) {
878 dirty
= layout
->SetLineHeight (args
->GetNewValue()->AsDouble ());
879 } else if (args
->GetId () == TextBlock::TextAlignmentProperty
) {
880 dirty
= layout
->SetTextAlignment ((TextAlignment
) args
->GetNewValue()->AsInt32 ());
881 } else if (args
->GetId () == TextBlock::PaddingProperty
) {
883 } else if (args
->GetId () == TextBlock::FontSourceProperty
) {
884 FontSource
*source
= args
->GetNewValue () ? args
->GetNewValue ()->AsFontSource () : NULL
;
885 FontManager
*manager
= Deployment::GetCurrent ()->GetFontManager ();
887 // FIXME: ideally we'd remove the old item from the cache (or,
888 // rather, 'unref' it since some other textblocks/boxes might
889 // still be using it).
891 g_free (font_source
);
893 if (source
&& source
->stream
)
894 font_source
= manager
->AddResource (source
->stream
);
898 UpdateFontDescriptions (true);
904 InvalidateMeasure ();
905 InvalidateArrange ();
912 NotifyListenersOfPropertyChange (args
, error
);
916 TextBlock::OnSubPropertyChanged (DependencyProperty
*prop
, DependencyObject
*obj
, PropertyChangedEventArgs
*subobj_args
)
918 if (prop
&& prop
->GetId () == TextBlock::ForegroundProperty
) {
921 FrameworkElement::OnSubPropertyChanged (prop
, obj
, subobj_args
);
926 TextBlock::OnCollectionChanged (Collection
*col
, CollectionChangedEventArgs
*args
)
928 InlineCollection
*inlines
= GetInlines ();
930 if (col
!= inlines
) {
931 FrameworkElement::OnCollectionChanged (col
, args
);
935 if (args
->GetChangedAction () == CollectionChangedActionClearing
)
939 // changes being handled elsewhere...
944 SetValue (TextBlock::TextProperty
, Value (GetTextInternal (inlines
), true));
947 UpdateLayoutAttributes ();
948 InvalidateMeasure ();
949 InvalidateArrange ();
955 TextBlock::OnCollectionItemChanged (Collection
*col
, DependencyObject
*obj
, PropertyChangedEventArgs
*args
)
957 InlineCollection
*inlines
= GetInlines ();
959 if (col
!= inlines
) {
960 FrameworkElement::OnCollectionItemChanged (col
, obj
, args
);
964 if (args
->GetId () != Inline::ForegroundProperty
) {
965 if (args
->GetId () == Run::TextProperty
) {
966 // update our TextProperty
968 SetValue (TextBlock::TextProperty
, Value (GetTextInternal (inlines
), true));
971 UpdateLayoutAttributes ();
973 // likely a font property change...
974 ((Inline
*) obj
)->UpdateFontDescription (font_source
, true);
977 // All non-Foreground property changes require
978 // recalculating layout which can change the bounds.
979 InvalidateMeasure ();
980 InvalidateArrange ();
984 // A simple Foreground brush change does not require
985 // recalculating layout. Invalidate() and we're done.
992 TextBlock::downloader_complete (EventObject
*sender
, EventArgs
*calldata
, gpointer closure
)
994 ((TextBlock
*) closure
)->DownloaderComplete ((Downloader
*) sender
);
998 TextBlock::DownloaderComplete (Downloader
*downloader
)
1000 FontManager
*manager
= Deployment::GetCurrent ()->GetFontManager ();
1001 char *resource
, *filename
;
1002 InternalDownloader
*idl
;
1007 InvalidateMeasure ();
1008 InvalidateArrange ();
1010 // get the downloaded file path (enforces a mozilla workaround for files smaller than 64k)
1011 if (!(filename
= downloader
->GetDownloadedFilename (NULL
)))
1016 if (!(idl
= downloader
->GetInternalDownloader ()))
1019 if (!(idl
->GetObjectType () == Type::FILEDOWNLOADER
))
1022 uri
= downloader
->GetUri ();
1024 // If the downloaded file was a zip file, this'll get the path to the
1025 // extracted zip directory, else it will simply be the path to the
1027 if (!(path
= ((FileDownloader
*) idl
)->GetUnzippedPath ()))
1030 resource
= uri
->ToString ((UriToStringFlags
) (UriHidePasswd
| UriHideQuery
| UriHideFragment
));
1031 manager
->AddResource (resource
, path
);
1034 if (UpdateFontDescriptions (true)) {
1037 UpdateBounds (true);