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"
32 // Unicode Line Separator (\u2028)
33 static const char utf8_linebreak
[3] = { 0xe2, 0x80, 0xa8 };
34 #define utf8_linebreak_len 3
43 SetObjectType (Type::INLINE
);
44 font
= new TextFontDescription ();
45 downloaders
= g_ptr_array_new ();
51 CleanupDownloaders ();
52 g_ptr_array_free (downloaders
, true);
57 Inline::CleanupDownloaders ()
59 Downloader
*downloader
;
62 for (i
= 0; i
< downloaders
->len
; i
++) {
63 downloader
= (Downloader
*) downloaders
->pdata
[i
];
64 downloader
->RemoveHandler (Downloader::CompletedEvent
, downloader_complete
, this);
69 g_ptr_array_set_size (downloaders
, 0);
73 Inline::AddFontSource (Downloader
*downloader
)
75 downloader
->AddHandler (downloader
->CompletedEvent
, downloader_complete
, this);
76 g_ptr_array_add (downloaders
, downloader
);
79 if (downloader
->Started () || downloader
->Completed ()) {
80 if (downloader
->Completed ())
81 DownloaderComplete (downloader
);
83 // This is what actually triggers the download
89 Inline::AddFontResource (const char *resource
)
91 FontManager
*manager
= Deployment::GetCurrent ()->GetFontManager ();
92 Application
*application
= Application::GetCurrent ();
93 Downloader
*downloader
;
100 if (!application
|| !uri
->Parse (resource
) || !(path
= application
->GetResourceAsPath (GetResourceBase(), uri
))) {
101 if ((surface
= GetSurface ()) && (downloader
= surface
->CreateDownloader ())) {
102 downloader
->Open ("GET", resource
, FontPolicy
);
103 AddFontSource (downloader
);
104 downloader
->unref ();
112 manager
->AddResource (resource
, path
);
118 Inline::OnPropertyChanged (PropertyChangedEventArgs
*args
, MoonError
*error
)
120 if (args
->GetProperty ()->GetOwnerType () != Type::INLINE
) {
121 DependencyObject::OnPropertyChanged (args
, error
);
125 if (args
->GetId () == Inline::FontFamilyProperty
) {
126 FontFamily
*family
= args
->GetNewValue () ? args
->GetNewValue ()->AsFontFamily () : NULL
;
127 char **families
, *fragment
;
130 CleanupDownloaders ();
132 if (family
&& family
->source
) {
133 families
= g_strsplit (family
->source
, ",", -1);
134 for (i
= 0; families
[i
]; i
++) {
135 g_strstrip (families
[i
]);
136 if ((fragment
= strchr (families
[i
], '#'))) {
137 // the first portion of this string is the resource name...
139 AddFontResource (families
[i
]);
142 g_strfreev (families
);
146 NotifyListenersOfPropertyChange (args
, error
);
150 Inline::OnSubPropertyChanged (DependencyProperty
*prop
, DependencyObject
*obj
, PropertyChangedEventArgs
*subobj_args
)
152 if (prop
&& prop
->GetId () == Inline::ForegroundProperty
) {
153 // this isn't exactly what we want, I don't
154 // think... but it'll have to do.
155 NotifyListenersOfPropertyChange (prop
, NULL
);
157 DependencyObject::OnSubPropertyChanged (prop
, obj
, subobj_args
);
162 Inline::Equals (Inline
*item
)
164 const char *lang0
, *lang1
;
166 if (item
->GetObjectType () != GetObjectType ())
169 if (*item
->GetFontFamily () != *GetFontFamily ())
172 if (item
->GetFontSize () != GetFontSize ())
175 if (item
->GetFontStyle () != GetFontStyle ())
178 if (item
->GetFontWeight () != GetFontWeight ())
181 if (item
->GetFontStretch () != GetFontStretch ())
184 if (item
->GetTextDecorations () != GetTextDecorations ())
187 lang0
= item
->GetLanguage ();
188 lang1
= GetLanguage ();
190 if ((lang0
&& !lang1
) || (!lang0
&& lang1
))
193 if (lang0
&& lang1
&& strcmp (lang0
, lang1
) != 0)
196 // this isn't really correct - we should be checking
197 // the innards of the foreground brushes, but we're
198 // guaranteed to never have a false positive here.
199 if (item
->GetForeground () != GetForeground ())
202 // OK, as best we can tell - they are equal
207 Inline::UpdateFontDescription (const char *source
, bool force
)
209 FontFamily
*family
= GetFontFamily ();
210 bool changed
= false;
212 if (font
->SetSource (source
))
215 if (font
->SetFamily (family
? family
->source
: NULL
))
218 if (font
->SetStretch (GetFontStretch ()->stretch
))
221 if (font
->SetWeight (GetFontWeight ()->weight
))
224 if (font
->SetStyle (GetFontStyle ()->style
))
227 if (font
->SetSize (GetFontSize ()))
230 if (font
->SetLanguage (GetLanguage ()))
242 Inline::downloader_complete (EventObject
*sender
, EventArgs
*calldata
, gpointer closure
)
244 ((Inline
*) closure
)->DownloaderComplete ((Downloader
*) sender
);
248 Inline::DownloaderComplete (Downloader
*downloader
)
250 FontManager
*manager
= Deployment::GetCurrent ()->GetFontManager ();
251 char *resource
, *filename
;
252 InternalDownloader
*idl
;
256 // get the downloaded file path (enforces a mozilla workaround for files smaller than 64k)
257 if (!(filename
= downloader
->GetDownloadedFilename (NULL
)))
262 if (!(idl
= downloader
->GetInternalDownloader ()))
265 if (!(idl
->GetObjectType () == Type::FILEDOWNLOADER
))
268 uri
= downloader
->GetUri ();
270 // If the downloaded file was a zip file, this'll get the path to the
271 // extracted zip directory, else it will simply be the path to the
273 if (!(path
= ((FileDownloader
*) idl
)->GetUnzippedPath ()))
276 resource
= uri
->ToString ((UriToStringFlags
) (UriHidePasswd
| UriHideQuery
| UriHideFragment
));
277 manager
->AddResource (resource
, path
);
287 Run::Equals (Inline
*item
)
289 const char *text
, *itext
;
291 if (!Inline::Equals (item
))
294 itext
= ((Run
*) item
)->GetText ();
298 if (text
&& itext
&& strcmp (text
, itext
) != 0)
300 else if ((text
&& !itext
) || (!text
&& itext
))
310 TextBlock::TextBlock ()
312 SetObjectType (Type::TEXTBLOCK
);
314 downloaders
= g_ptr_array_new ();
315 layout
= new TextLayout ();
326 TextBlock::~TextBlock ()
328 CleanupDownloaders (true);
329 g_ptr_array_free (downloaders
, true);
335 TextBlock::CleanupDownloaders (bool all
)
337 Downloader
*downloader
;
340 for (i
= 0; i
< downloaders
->len
; i
++) {
341 downloader
= (Downloader
*) downloaders
->pdata
[i
];
343 if (all
|| downloader
!= source
) {
344 downloader
->RemoveHandler (Downloader::CompletedEvent
, downloader_complete
, this);
345 downloader
->Abort ();
346 downloader
->unref ();
350 g_ptr_array_set_size (downloaders
, 0);
352 if (source
&& !all
) {
353 g_ptr_array_add (downloaders
, source
);
359 g_free (font_source
);
365 TextBlock::AddFontSource (Downloader
*downloader
)
367 downloader
->AddHandler (downloader
->CompletedEvent
, downloader_complete
, this);
368 g_ptr_array_add (downloaders
, downloader
);
371 if (downloader
->Started () || downloader
->Completed ()) {
372 if (downloader
->Completed ())
373 DownloaderComplete (downloader
);
375 // This is what actually triggers the download
381 TextBlock::SetFontSource (Downloader
*downloader
)
383 CleanupDownloaders (true);
387 font_source
= downloader
->GetUri ()->ToString ((UriToStringFlags
) (UriHidePasswd
| UriHideQuery
| UriHideFragment
));
388 AddFontSource (downloader
);
392 UpdateFontDescriptions (true);
399 TextBlock::AddFontResource (const char *resource
)
401 FontManager
*manager
= Deployment::GetCurrent ()->GetFontManager ();
402 Application
*application
= Application::GetCurrent ();
403 Downloader
*downloader
;
410 if (!application
|| !uri
->Parse (resource
) || !(path
= application
->GetResourceAsPath (GetResourceBase(), uri
))) {
411 if ((surface
= GetSurface ()) && (downloader
= surface
->CreateDownloader ())) {
412 downloader
->Open ("GET", resource
, FontPolicy
);
413 AddFontSource (downloader
);
414 downloader
->unref ();
422 manager
->AddResource (resource
, path
);
428 TextBlock::Render (cairo_t
*cr
, Region
*region
, bool path_only
)
431 cairo_set_matrix (cr
, &absolute_xform
);
434 RenderLayoutClip (cr
);
442 TextBlock::ComputeBounds ()
444 Size
actual (GetActualWidth (), GetActualHeight ());
445 Size framework
= ApplySizeConstraints (actual
);
447 framework
= framework
.Max (actual
);
449 Rect extents
= Rect (0,0,framework
.width
, framework
.height
);
451 bounds
= bounds_with_children
= IntersectBoundsWithClipPath (extents
, false).Transform (&absolute_xform
);
455 TextBlock::GetSizeForBrush (cairo_t
*cr
, double *width
, double *height
)
457 *width
= actual_width
;
458 *height
= actual_height
;
462 TextBlock::GetTransformOrigin ()
464 Point
*user_xform_origin
= GetRenderTransformOrigin ();
465 return Point (actual_width
* user_xform_origin
->x
,
466 actual_height
* user_xform_origin
->y
);
472 TextBlock::ComputeActualSize ()
474 Thickness padding
= *GetPadding ();
475 Size result
= FrameworkElement::ComputeActualSize ();
478 if (!LayoutInformation::GetPreviousConstraint (this)) {
479 Size constraint
= Size (INFINITY
, INFINITY
);
481 constraint
= ApplySizeConstraints (constraint
);
483 constraint
= constraint
.GrowBy (-padding
);
488 result
= Size (actual_width
, actual_height
);
489 result
= result
.GrowBy (padding
);
495 TextBlock::MeasureOverride (Size availableSize
)
497 Thickness padding
= *GetPadding ();
501 constraint
= availableSize
.GrowBy (-padding
);
504 desired
= Size (actual_width
, actual_height
).GrowBy (padding
);
506 SetActualHeight (desired
.height
);
507 SetActualWidth (desired
.width
);
509 desired
= desired
.Min (availableSize
);
515 TextBlock::ArrangeOverride (Size finalSize
)
517 Thickness padding
= *GetPadding ();
521 constraint
= finalSize
.GrowBy (-padding
);
524 arranged
= Size (actual_width
, actual_height
).GrowBy (padding
);
526 arranged
= arranged
.Max (finalSize
);
527 arranged
= ApplySizeConstraints (arranged
);
529 layout
->SetAvailableWidth (arranged
.GrowBy (-padding
).width
);
535 TextBlock::UpdateLayoutAttributes ()
537 InlineCollection
*inlines
= GetInlines ();
538 TextLayoutAttributes
*attrs
;
544 InvalidateMeasure ();
545 InvalidateArrange ();
548 if (inlines
!= NULL
) {
549 for (int i
= 0; i
< inlines
->GetCount (); i
++) {
550 item
= inlines
->GetValueAt (i
)->AsInline ();
551 item
->UpdateFontDescription (font_source
, false);
553 switch (item
->GetObjectType ()) {
555 text
= ((Run
*) item
)->GetText ();
557 if (text
&& text
[0]) {
558 attrs
= new TextLayoutAttributes ((ITextAttributes
*) item
, length
);
559 runs
->Append (attrs
);
561 length
+= strlen (text
);
565 case Type::LINEBREAK
:
566 attrs
= new TextLayoutAttributes ((ITextAttributes
*) item
, length
);
567 runs
->Append (attrs
);
569 length
+= utf8_linebreak_len
;
576 if (inlines
->GetCount () > 0)
580 layout
->SetText (GetText (), length
);
581 layout
->SetTextAttributes (runs
);
585 TextBlock::UpdateFontDescriptions (bool force
)
587 InlineCollection
*inlines
= GetInlines ();
588 bool changed
= false;
591 if (inlines
!= NULL
) {
592 for (int i
= 0; i
< inlines
->GetCount (); i
++) {
593 item
= inlines
->GetValueAt (i
)->AsInline ();
594 if (item
->UpdateFontDescription (font_source
, force
))
599 layout
->ResetState ();
602 //ClearValue (TextBlock::ActualWidthProperty);
603 //ClearValue (TextBlock::ActualHeightProperty);
604 InvalidateMeasure ();
605 InvalidateArrange ();
613 TextBlock::Layout (Size constraint
)
615 if (was_set
&& !GetValueNoDefault (TextBlock::TextProperty
)) {
616 // If the Text property had been set once upon a time,
617 // but is currently empty, Silverlight seems to set
618 // the ActualHeight property to the font height. See
619 // bug #405514 for details.
620 TextFontDescription
*desc
= new TextFontDescription ();
621 FontFamily
*family
= GetFontFamily ();
624 desc
->SetFamily (family
? family
->source
: NULL
);
625 desc
->SetStretch (GetFontStretch ()->stretch
);
626 desc
->SetWeight (GetFontWeight ()->weight
);
627 desc
->SetStyle (GetFontStyle ()->style
);
628 desc
->SetSize (GetFontSize ());
630 font
= desc
->GetFont ();
631 actual_height
= font
->Height ();
634 } else if (!was_set
) {
635 // If the Text property has never been set, then its
636 // extents should both be 0.0. See bug #435798 for
641 layout
->SetMaxWidth (constraint
.width
);
644 layout
->GetActualExtents (&actual_width
, &actual_height
);
651 TextBlock::Paint (cairo_t
*cr
)
653 Thickness
*padding
= GetPadding ();
654 Point
offset (padding
->left
, padding
->top
);
656 cairo_set_matrix (cr
, &absolute_xform
);
657 layout
->Render (cr
, GetOriginPoint (), offset
);
659 if (moonlight_flags
& RUNTIME_INIT_SHOW_TEXTBOXES
) {
660 cairo_set_source_rgba (cr
, 0.0, 1.0, 0.0, 1.0);
661 cairo_set_line_width (cr
, 1);
662 cairo_rectangle (cr
, padding
->left
, padding
->top
, actual_width
, actual_height
);
668 TextBlock::GetTextInternal (InlineCollection
*inlines
)
676 return g_strdup ("");
678 block
= g_string_new ("");
680 for (int i
= 0; i
< inlines
->GetCount (); i
++) {
681 item
= inlines
->GetValueAt (i
)->AsInline ();
683 switch (item
->GetObjectType ()) {
685 text
= ((Run
*) item
)->GetText ();
688 g_string_append (block
, text
);
690 case Type::LINEBREAK
:
691 g_string_append_len (block
, utf8_linebreak
, utf8_linebreak_len
);
699 g_string_free (block
, false);
705 TextBlock::SetTextInternal (const char *text
)
707 InlineCollection
*inlines
;
711 // Note: calling GetValue() may cause the InlineCollection to be
712 // autocreated, so we need to prevent reentrancy here.
715 value
= GetValue (TextBlock::InlinesProperty
);
716 inlines
= value
->AsInlineCollection ();
721 run
->SetAutogenerated (true);
725 // setting text to null results in String.Empty
726 SetValue (TextBlock::TextProperty
, Value (""));
735 TextBlock::OnPropertyChanged (PropertyChangedEventArgs
*args
, MoonError
*error
)
737 bool invalidate
= true;
739 if (args
->GetProperty ()->GetOwnerType () != Type::TEXTBLOCK
) {
740 FrameworkElement::OnPropertyChanged (args
, error
);
742 if (args
->GetId () == FrameworkElement::LanguageProperty
) {
743 // a change in xml:lang might change font characteristics
744 if (UpdateFontDescriptions (false)) {
745 InvalidateMeasure ();
746 InvalidateArrange ();
753 if (args->GetId () == FrameworkElement::WidthProperty) {
754 //if (layout->SetMaxWidth (args->GetNewValue()->AsDouble ()))
763 if (args
->GetId () == TextBlock::FontFamilyProperty
) {
764 FontFamily
*family
= args
->GetNewValue () ? args
->GetNewValue ()->AsFontFamily () : NULL
;
765 char **families
, *fragment
;
768 CleanupDownloaders (false);
770 if (family
&& family
->source
) {
771 families
= g_strsplit (family
->source
, ",", -1);
772 for (i
= 0; families
[i
]; i
++) {
773 g_strstrip (families
[i
]);
774 if ((fragment
= strchr (families
[i
], '#'))) {
775 // the first portion of this string is the resource name...
777 AddFontResource (families
[i
]);
780 g_strfreev (families
);
783 if (UpdateFontDescriptions (false))
785 } else if (args
->GetId () == TextBlock::FontSizeProperty
) {
786 if (UpdateFontDescriptions (false))
788 } else if (args
->GetId () == TextBlock::FontStretchProperty
) {
789 if (UpdateFontDescriptions (false))
791 } else if (args
->GetId () == TextBlock::FontStyleProperty
) {
792 if (UpdateFontDescriptions (false))
794 } else if (args
->GetId () == TextBlock::FontWeightProperty
) {
795 if (UpdateFontDescriptions (false))
797 } else if (args
->GetId () == TextBlock::TextProperty
) {
799 // result of a change to the TextBlock.Text property
800 const char *text
= args
->GetNewValue() ? args
->GetNewValue()->AsString () : NULL
;
802 if (!SetTextInternal (text
)) {
803 // no change so nothing to invalidate
806 UpdateLayoutAttributes ();
810 // result of a change to the TextBlock.Inlines property
811 UpdateLayoutAttributes ();
814 } else if (args
->GetId () == TextBlock::TextDecorationsProperty
) {
816 } else if (args
->GetId () == TextBlock::TextWrappingProperty
) {
817 dirty
= layout
->SetTextWrapping ((TextWrapping
) args
->GetNewValue()->AsInt32 ());
818 } else if (args
->GetId () == TextBlock::InlinesProperty
) {
820 // result of a change to the TextBlock.Inlines property
821 InlineCollection
*inlines
= args
->GetNewValue() ? args
->GetNewValue()->AsInlineCollection () : NULL
;
824 // Note: this will cause UpdateLayoutAttributes() to be called in the TextProperty changed logic above
825 SetValue (TextBlock::TextProperty
, Value (GetTextInternal (inlines
), true));
830 // result of a change to the TextBlock.Text property
833 } else if (args
->GetId () == TextBlock::LineStackingStrategyProperty
) {
834 dirty
= layout
->SetLineStackingStrategy ((LineStackingStrategy
) args
->GetNewValue()->AsInt32 ());
835 } else if (args
->GetId () == TextBlock::LineHeightProperty
) {
836 dirty
= layout
->SetLineHeight (args
->GetNewValue()->AsDouble ());
837 } else if (args
->GetId () == TextBlock::TextAlignmentProperty
) {
838 dirty
= layout
->SetTextAlignment ((TextAlignment
) args
->GetNewValue()->AsInt32 ());
839 } else if (args
->GetId () == TextBlock::PaddingProperty
) {
841 } else if (args
->GetId () == TextBlock::FontSourceProperty
) {
842 FontSource
*source
= args
->GetNewValue () ? args
->GetNewValue ()->AsFontSource () : NULL
;
843 FontManager
*manager
= Deployment::GetCurrent ()->GetFontManager ();
845 g_free (font_source
);
847 if (source
&& source
->stream
) {
848 font_source
= g_strdup_printf ("font-source://%p.%p", this, source
);
849 manager
->AddResource (font_source
, source
->stream
);
857 InvalidateMeasure ();
858 InvalidateArrange ();
865 NotifyListenersOfPropertyChange (args
, error
);
869 TextBlock::OnSubPropertyChanged (DependencyProperty
*prop
, DependencyObject
*obj
, PropertyChangedEventArgs
*subobj_args
)
871 if (prop
&& prop
->GetId () == TextBlock::ForegroundProperty
) {
874 FrameworkElement::OnSubPropertyChanged (prop
, obj
, subobj_args
);
879 TextBlock::OnCollectionChanged (Collection
*col
, CollectionChangedEventArgs
*args
)
881 InlineCollection
*inlines
= GetInlines ();
883 if (col
!= inlines
) {
884 FrameworkElement::OnCollectionChanged (col
, args
);
888 if (args
->GetChangedAction () == CollectionChangedActionClearing
)
892 // changes being handled elsewhere...
897 // Note: this will cause UpdateLayoutAttributes() to be called in the TextProperty changed logic above
898 SetValue (TextBlock::TextProperty
, Value (GetTextInternal (inlines
), true));
901 InvalidateMeasure ();
902 InvalidateArrange ();
908 TextBlock::OnCollectionItemChanged (Collection
*col
, DependencyObject
*obj
, PropertyChangedEventArgs
*args
)
910 InlineCollection
*inlines
= GetInlines ();
912 if (col
!= inlines
) {
913 FrameworkElement::OnCollectionItemChanged (col
, obj
, args
);
917 if (args
->GetId () != Inline::ForegroundProperty
) {
918 if (args
->GetId () == Run::TextProperty
) {
919 // update our TextProperty
921 // Note: this will cause UpdateLayoutAttributes() to be called in the TextProperty changed logic above
922 SetValue (TextBlock::TextProperty
, Value (GetTextInternal (inlines
), true));
925 // likely a font property change...
926 ((Inline
*) obj
)->UpdateFontDescription (font_source
, true);
929 // All non-Foreground property changes require
930 // recalculating layout which can change the bounds.
931 InvalidateMeasure ();
932 InvalidateArrange ();
936 // A simple Foreground brush change does not require
937 // recalculating layout. Invalidate() and we're done.
944 TextBlock::downloader_complete (EventObject
*sender
, EventArgs
*calldata
, gpointer closure
)
946 ((TextBlock
*) closure
)->DownloaderComplete ((Downloader
*) sender
);
950 TextBlock::DownloaderComplete (Downloader
*downloader
)
952 FontManager
*manager
= Deployment::GetCurrent ()->GetFontManager ();
953 char *resource
, *filename
;
954 InternalDownloader
*idl
;
959 InvalidateMeasure ();
960 InvalidateArrange ();
962 // get the downloaded file path (enforces a mozilla workaround for files smaller than 64k)
963 if (!(filename
= downloader
->GetDownloadedFilename (NULL
)))
968 if (!(idl
= downloader
->GetInternalDownloader ()))
971 if (!(idl
->GetObjectType () == Type::FILEDOWNLOADER
))
974 uri
= downloader
->GetUri ();
976 // If the downloaded file was a zip file, this'll get the path to the
977 // extracted zip directory, else it will simply be the path to the
979 if (!(path
= ((FileDownloader
*) idl
)->GetUnzippedPath ()))
982 resource
= uri
->ToString ((UriToStringFlags
) (UriHidePasswd
| UriHideQuery
| UriHideFragment
));
983 manager
->AddResource (resource
, path
);
986 if (UpdateFontDescriptions (true)) {