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
))
311 TextBlock::TextBlock ()
313 SetObjectType (Type::TEXTBLOCK
);
315 font
= new TextFontDescription ();
317 downloaders
= g_ptr_array_new ();
318 layout
= new TextLayout ();
329 TextBlock::~TextBlock ()
331 CleanupDownloaders (true);
332 g_ptr_array_free (downloaders
, true);
339 TextBlock::CleanupDownloaders (bool all
)
341 Downloader
*downloader
;
344 for (i
= 0; i
< downloaders
->len
; i
++) {
345 downloader
= (Downloader
*) downloaders
->pdata
[i
];
347 if (all
|| downloader
!= source
) {
348 downloader
->RemoveHandler (Downloader::CompletedEvent
, downloader_complete
, this);
349 downloader
->Abort ();
350 downloader
->unref ();
354 g_ptr_array_set_size (downloaders
, 0);
356 if (source
&& !all
) {
357 g_ptr_array_add (downloaders
, source
);
363 g_free (font_source
);
369 TextBlock::AddFontSource (Downloader
*downloader
)
371 downloader
->AddHandler (downloader
->CompletedEvent
, downloader_complete
, this);
372 g_ptr_array_add (downloaders
, downloader
);
375 if (downloader
->Started () || downloader
->Completed ()) {
376 if (downloader
->Completed ())
377 DownloaderComplete (downloader
);
379 // This is what actually triggers the download
385 TextBlock::SetFontSource (Downloader
*downloader
)
387 CleanupDownloaders (true);
391 font_source
= downloader
->GetUri ()->ToString ((UriToStringFlags
) (UriHidePasswd
| UriHideQuery
| UriHideFragment
));
392 AddFontSource (downloader
);
396 UpdateFontDescriptions (true);
403 TextBlock::AddFontResource (const char *resource
)
405 FontManager
*manager
= Deployment::GetCurrent ()->GetFontManager ();
406 Application
*application
= Application::GetCurrent ();
407 Downloader
*downloader
;
414 if (!application
|| !uri
->Parse (resource
) || !(path
= application
->GetResourceAsPath (GetResourceBase(), uri
))) {
415 if ((surface
= GetSurface ()) && (downloader
= surface
->CreateDownloader ())) {
416 downloader
->Open ("GET", resource
, FontPolicy
);
417 AddFontSource (downloader
);
418 downloader
->unref ();
426 manager
->AddResource (resource
, path
);
432 TextBlock::Render (cairo_t
*cr
, Region
*region
, bool path_only
)
435 cairo_set_matrix (cr
, &absolute_xform
);
438 RenderLayoutClip (cr
);
446 TextBlock::ComputeBounds ()
448 Size
actual (GetActualWidth (), GetActualHeight ());
449 Size framework
= ApplySizeConstraints (actual
);
451 framework
= framework
.Max (actual
);
453 Rect extents
= Rect (0,0,framework
.width
, framework
.height
);
455 bounds
= bounds_with_children
= IntersectBoundsWithClipPath (extents
, false).Transform (&absolute_xform
);
459 TextBlock::GetSizeForBrush (cairo_t
*cr
, double *width
, double *height
)
461 *width
= actual_width
;
462 *height
= actual_height
;
466 TextBlock::GetTransformOrigin ()
468 Point
*user_xform_origin
= GetRenderTransformOrigin ();
469 return Point (actual_width
* user_xform_origin
->x
,
470 actual_height
* user_xform_origin
->y
);
474 TextBlock::ComputeActualSize ()
476 Thickness padding
= *GetPadding ();
477 Size result
= FrameworkElement::ComputeActualSize ();
479 if (!LayoutInformation::GetPreviousConstraint (this)) {
480 Size constraint
= Size (INFINITY
, INFINITY
);
482 constraint
= ApplySizeConstraints (constraint
);
484 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 if (GetUseLayoutRounding ())
510 desired
.width
= ceil (desired
.width
);
512 desired
= desired
.Min (availableSize
);
518 TextBlock::ArrangeOverride (Size finalSize
)
520 Thickness padding
= *GetPadding ();
524 constraint
= finalSize
.GrowBy (-padding
);
527 arranged
= Size (actual_width
, actual_height
).GrowBy (padding
);
529 arranged
= arranged
.Max (finalSize
);
530 arranged
= ApplySizeConstraints (arranged
);
532 layout
->SetAvailableWidth (arranged
.GrowBy (-padding
).width
);
538 TextBlock::UpdateLayoutAttributes ()
540 InlineCollection
*inlines
= GetInlines ();
541 TextLayoutAttributes
*attrs
;
547 InvalidateMeasure ();
548 InvalidateArrange ();
551 UpdateFontDescription (false);
553 if (inlines
!= NULL
) {
554 for (int i
= 0; i
< inlines
->GetCount (); i
++) {
555 item
= inlines
->GetValueAt (i
)->AsInline ();
556 item
->UpdateFontDescription (font_source
, false);
558 switch (item
->GetObjectType ()) {
560 text
= ((Run
*) item
)->GetText ();
562 if (text
&& text
[0]) {
563 attrs
= new TextLayoutAttributes ((ITextAttributes
*) item
, length
);
564 runs
->Append (attrs
);
566 length
+= strlen (text
);
570 case Type::LINEBREAK
:
571 attrs
= new TextLayoutAttributes ((ITextAttributes
*) item
, length
);
572 runs
->Append (attrs
);
574 length
+= utf8_linebreak_len
;
581 if (inlines
->GetCount () > 0)
585 layout
->SetText (GetText (), length
);
586 layout
->SetTextAttributes (runs
);
590 TextBlock::UpdateFontDescription (bool force
)
592 FontFamily
*family
= GetFontFamily ();
593 bool changed
= false;
595 if (font
->SetSource (font_source
))
598 if (font
->SetFamily (family
? family
->source
: NULL
))
601 if (font
->SetStretch (GetFontStretch ()->stretch
))
604 if (font
->SetWeight (GetFontWeight ()->weight
))
607 if (font
->SetStyle (GetFontStyle ()->style
))
610 if (font
->SetSize (GetFontSize ()))
613 if (font
->SetLanguage (GetLanguage ()))
622 layout
->SetBaseFont (font
->GetFont ());
628 TextBlock::UpdateFontDescriptions (bool force
)
630 InlineCollection
*inlines
= GetInlines ();
631 bool changed
= false;
634 changed
= UpdateFontDescription (force
);
636 if (inlines
!= NULL
) {
637 for (int i
= 0; i
< inlines
->GetCount (); i
++) {
638 item
= inlines
->GetValueAt (i
)->AsInline ();
639 if (item
->UpdateFontDescription (font_source
, force
))
644 layout
->ResetState ();
648 InvalidateMeasure ();
649 InvalidateArrange ();
658 TextBlock::Layout (Size constraint
)
660 if (was_set
&& !GetValueNoDefault (TextBlock::TextProperty
)) {
661 // If the Text property had been set once upon a time,
662 // but is currently empty, Silverlight seems to set
663 // the ActualHeight property to the font height. See
664 // bug #405514 for details.
665 TextFontDescription
*desc
= new TextFontDescription ();
666 FontFamily
*family
= GetFontFamily ();
669 desc
->SetFamily (family
? family
->source
: NULL
);
670 desc
->SetStretch (GetFontStretch ()->stretch
);
671 desc
->SetWeight (GetFontWeight ()->weight
);
672 desc
->SetStyle (GetFontStyle ()->style
);
673 desc
->SetSize (GetFontSize ());
675 font
= desc
->GetFont ();
676 actual_height
= font
->Height ();
679 } else if (!was_set
) {
680 // If the Text property has never been set, then its
681 // extents should both be 0.0. See bug #435798 for
686 layout
->SetMaxWidth (constraint
.width
);
689 layout
->GetActualExtents (&actual_width
, &actual_height
);
696 TextBlock::Paint (cairo_t
*cr
)
698 Thickness
*padding
= GetPadding ();
699 Point
offset (padding
->left
, padding
->top
);
701 cairo_set_matrix (cr
, &absolute_xform
);
702 layout
->Render (cr
, GetOriginPoint (), offset
);
704 if (moonlight_flags
& RUNTIME_INIT_SHOW_TEXTBOXES
) {
705 cairo_set_source_rgba (cr
, 0.0, 1.0, 0.0, 1.0);
706 cairo_set_line_width (cr
, 1);
707 cairo_rectangle (cr
, padding
->left
, padding
->top
, actual_width
, actual_height
);
713 TextBlock::GetTextInternal (InlineCollection
*inlines
)
721 return g_strdup ("");
723 block
= g_string_new ("");
725 for (int i
= 0; i
< inlines
->GetCount (); i
++) {
726 item
= inlines
->GetValueAt (i
)->AsInline ();
728 switch (item
->GetObjectType ()) {
730 text
= ((Run
*) item
)->GetText ();
733 g_string_append (block
, text
);
735 case Type::LINEBREAK
:
736 g_string_append_len (block
, utf8_linebreak
, utf8_linebreak_len
);
744 g_string_free (block
, false);
750 TextBlock::SetTextInternal (const char *text
)
752 InlineCollection
*inlines
;
756 // Note: calling GetValue() may cause the InlineCollection to be
757 // autocreated, so we need to prevent reentrancy here.
760 value
= GetValue (TextBlock::InlinesProperty
);
761 inlines
= value
->AsInlineCollection ();
766 run
->SetAutogenerated (true);
771 // setting text to null results in String.Empty
772 SetValue (TextBlock::TextProperty
, Value (""));
779 TextBlock::OnPropertyChanged (PropertyChangedEventArgs
*args
, MoonError
*error
)
781 bool invalidate
= true;
783 if (args
->GetProperty ()->GetOwnerType () != Type::TEXTBLOCK
) {
784 FrameworkElement::OnPropertyChanged (args
, error
);
786 if (args
->GetId () == FrameworkElement::LanguageProperty
) {
787 // a change in xml:lang might change font characteristics
788 if (UpdateFontDescriptions (false)) {
789 InvalidateMeasure ();
790 InvalidateArrange ();
797 if (args->GetId () == FrameworkElement::WidthProperty) {
798 //if (layout->SetMaxWidth (args->GetNewValue()->AsDouble ()))
807 if (args
->GetId () == TextBlock::FontFamilyProperty
) {
808 FontFamily
*family
= args
->GetNewValue () ? args
->GetNewValue ()->AsFontFamily () : NULL
;
809 char **families
, *fragment
;
812 CleanupDownloaders (false);
814 if (family
&& family
->source
) {
815 families
= g_strsplit (family
->source
, ",", -1);
816 for (i
= 0; families
[i
]; i
++) {
817 g_strstrip (families
[i
]);
818 if ((fragment
= strchr (families
[i
], '#'))) {
819 // the first portion of this string is the resource name...
821 AddFontResource (families
[i
]);
824 g_strfreev (families
);
827 if (UpdateFontDescriptions (false))
829 } else if (args
->GetId () == TextBlock::FontSizeProperty
) {
830 if (UpdateFontDescriptions (false))
832 } else if (args
->GetId () == TextBlock::FontStretchProperty
) {
833 if (UpdateFontDescriptions (false))
835 } else if (args
->GetId () == TextBlock::FontStyleProperty
) {
836 if (UpdateFontDescriptions (false))
838 } else if (args
->GetId () == TextBlock::FontWeightProperty
) {
839 if (UpdateFontDescriptions (false))
841 } else if (args
->GetId () == TextBlock::TextProperty
) {
843 // result of a change to the TextBlock.Text property
844 const char *text
= args
->GetNewValue() ? args
->GetNewValue()->AsString () : NULL
;
846 SetTextInternal (text
);
847 UpdateLayoutAttributes ();
850 // result of a change to the TextBlock.Inlines property
851 UpdateLayoutAttributes ();
854 } else if (args
->GetId () == TextBlock::TextDecorationsProperty
) {
856 } else if (args
->GetId () == TextBlock::TextWrappingProperty
) {
857 dirty
= layout
->SetTextWrapping ((TextWrapping
) args
->GetNewValue()->AsInt32 ());
858 } else if (args
->GetId () == TextBlock::InlinesProperty
) {
860 // result of a change to the TextBlock.Inlines property
861 InlineCollection
*inlines
= args
->GetNewValue() ? args
->GetNewValue()->AsInlineCollection () : NULL
;
864 SetValue (TextBlock::TextProperty
, Value (GetTextInternal (inlines
), true));
867 UpdateLayoutAttributes ();
870 // this should be the result of Inlines being autocreated
871 UpdateLayoutAttributes ();
874 } else if (args
->GetId () == TextBlock::LineStackingStrategyProperty
) {
875 dirty
= layout
->SetLineStackingStrategy ((LineStackingStrategy
) args
->GetNewValue()->AsInt32 ());
876 } else if (args
->GetId () == TextBlock::LineHeightProperty
) {
877 dirty
= layout
->SetLineHeight (args
->GetNewValue()->AsDouble ());
878 } else if (args
->GetId () == TextBlock::TextAlignmentProperty
) {
879 dirty
= layout
->SetTextAlignment ((TextAlignment
) args
->GetNewValue()->AsInt32 ());
880 } else if (args
->GetId () == TextBlock::PaddingProperty
) {
882 } else if (args
->GetId () == TextBlock::FontSourceProperty
) {
883 FontSource
*source
= args
->GetNewValue () ? args
->GetNewValue ()->AsFontSource () : NULL
;
884 FontManager
*manager
= Deployment::GetCurrent ()->GetFontManager ();
886 // FIXME: ideally we'd remove the old item from the cache (or,
887 // rather, 'unref' it since some other textblocks/boxes might
888 // still be using it).
890 g_free (font_source
);
892 if (source
&& source
->stream
)
893 font_source
= manager
->AddResource (source
->stream
);
897 UpdateFontDescriptions (true);
903 InvalidateMeasure ();
904 InvalidateArrange ();
911 NotifyListenersOfPropertyChange (args
, error
);
915 TextBlock::OnSubPropertyChanged (DependencyProperty
*prop
, DependencyObject
*obj
, PropertyChangedEventArgs
*subobj_args
)
917 if (prop
&& prop
->GetId () == TextBlock::ForegroundProperty
) {
920 FrameworkElement::OnSubPropertyChanged (prop
, obj
, subobj_args
);
925 TextBlock::OnCollectionChanged (Collection
*col
, CollectionChangedEventArgs
*args
)
927 InlineCollection
*inlines
= GetInlines ();
929 if (col
!= inlines
) {
930 FrameworkElement::OnCollectionChanged (col
, args
);
934 if (args
->GetChangedAction () == CollectionChangedActionClearing
)
938 // changes being handled elsewhere...
943 SetValue (TextBlock::TextProperty
, Value (GetTextInternal (inlines
), true));
946 UpdateLayoutAttributes ();
947 InvalidateMeasure ();
948 InvalidateArrange ();
954 TextBlock::OnCollectionItemChanged (Collection
*col
, DependencyObject
*obj
, PropertyChangedEventArgs
*args
)
956 InlineCollection
*inlines
= GetInlines ();
958 if (col
!= inlines
) {
959 FrameworkElement::OnCollectionItemChanged (col
, obj
, args
);
963 if (args
->GetId () != Inline::ForegroundProperty
) {
964 if (args
->GetId () == Run::TextProperty
) {
965 // update our TextProperty
967 SetValue (TextBlock::TextProperty
, Value (GetTextInternal (inlines
), true));
970 UpdateLayoutAttributes ();
972 // likely a font property change...
973 ((Inline
*) obj
)->UpdateFontDescription (font_source
, true);
976 // All non-Foreground property changes require
977 // recalculating layout which can change the bounds.
978 InvalidateMeasure ();
979 InvalidateArrange ();
983 // A simple Foreground brush change does not require
984 // recalculating layout. Invalidate() and we're done.
991 TextBlock::downloader_complete (EventObject
*sender
, EventArgs
*calldata
, gpointer closure
)
993 ((TextBlock
*) closure
)->DownloaderComplete ((Downloader
*) sender
);
997 TextBlock::DownloaderComplete (Downloader
*downloader
)
999 FontManager
*manager
= Deployment::GetCurrent ()->GetFontManager ();
1000 char *resource
, *filename
;
1001 InternalDownloader
*idl
;
1006 InvalidateMeasure ();
1007 InvalidateArrange ();
1009 // get the downloaded file path (enforces a mozilla workaround for files smaller than 64k)
1010 if (!(filename
= downloader
->GetDownloadedFilename (NULL
)))
1015 if (!(idl
= downloader
->GetInternalDownloader ()))
1018 if (!(idl
->GetObjectType () == Type::FILEDOWNLOADER
))
1021 uri
= downloader
->GetUri ();
1023 // If the downloaded file was a zip file, this'll get the path to the
1024 // extracted zip directory, else it will simply be the path to the
1026 if (!(path
= ((FileDownloader
*) idl
)->GetUnzippedPath ()))
1029 resource
= uri
->ToString ((UriToStringFlags
) (UriHidePasswd
| UriHideQuery
| UriHideFragment
));
1030 manager
->AddResource (resource
, path
);
1033 if (UpdateFontDescriptions (true)) {
1036 UpdateBounds (true);