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 downloaders
= g_ptr_array_new ();
316 layout
= new TextLayout ();
327 TextBlock::~TextBlock ()
329 CleanupDownloaders (true);
330 g_ptr_array_free (downloaders
, true);
336 TextBlock::CleanupDownloaders (bool all
)
338 Downloader
*downloader
;
341 for (i
= 0; i
< downloaders
->len
; i
++) {
342 downloader
= (Downloader
*) downloaders
->pdata
[i
];
344 if (all
|| downloader
!= source
) {
345 downloader
->RemoveHandler (Downloader::CompletedEvent
, downloader_complete
, this);
346 downloader
->Abort ();
347 downloader
->unref ();
351 g_ptr_array_set_size (downloaders
, 0);
353 if (source
&& !all
) {
354 g_ptr_array_add (downloaders
, source
);
360 g_free (font_source
);
366 TextBlock::AddFontSource (Downloader
*downloader
)
368 downloader
->AddHandler (downloader
->CompletedEvent
, downloader_complete
, this);
369 g_ptr_array_add (downloaders
, downloader
);
372 if (downloader
->Started () || downloader
->Completed ()) {
373 if (downloader
->Completed ())
374 DownloaderComplete (downloader
);
376 // This is what actually triggers the download
382 TextBlock::SetFontSource (Downloader
*downloader
)
384 CleanupDownloaders (true);
388 font_source
= downloader
->GetUri ()->ToString ((UriToStringFlags
) (UriHidePasswd
| UriHideQuery
| UriHideFragment
));
389 AddFontSource (downloader
);
393 UpdateFontDescriptions (true);
400 TextBlock::AddFontResource (const char *resource
)
402 FontManager
*manager
= Deployment::GetCurrent ()->GetFontManager ();
403 Application
*application
= Application::GetCurrent ();
404 Downloader
*downloader
;
411 if (!application
|| !uri
->Parse (resource
) || !(path
= application
->GetResourceAsPath (GetResourceBase(), uri
))) {
412 if ((surface
= GetSurface ()) && (downloader
= surface
->CreateDownloader ())) {
413 downloader
->Open ("GET", resource
, FontPolicy
);
414 AddFontSource (downloader
);
415 downloader
->unref ();
423 manager
->AddResource (resource
, path
);
429 TextBlock::Render (cairo_t
*cr
, Region
*region
, bool path_only
)
432 cairo_set_matrix (cr
, &absolute_xform
);
435 RenderLayoutClip (cr
);
443 TextBlock::ComputeBounds ()
445 Size
actual (GetActualWidth (), GetActualHeight ());
446 Size framework
= ApplySizeConstraints (actual
);
448 framework
= framework
.Max (actual
);
450 Rect extents
= Rect (0,0,framework
.width
, framework
.height
);
452 bounds
= bounds_with_children
= IntersectBoundsWithClipPath (extents
, false).Transform (&absolute_xform
);
456 TextBlock::GetSizeForBrush (cairo_t
*cr
, double *width
, double *height
)
458 *width
= actual_width
;
459 *height
= actual_height
;
463 TextBlock::GetTransformOrigin ()
465 Point
*user_xform_origin
= GetRenderTransformOrigin ();
466 return Point (actual_width
* user_xform_origin
->x
,
467 actual_height
* user_xform_origin
->y
);
471 TextBlock::ComputeActualSize ()
473 Thickness padding
= *GetPadding ();
474 Size result
= FrameworkElement::ComputeActualSize ();
476 if (!LayoutInformation::GetPreviousConstraint (this)) {
477 Size constraint
= Size (INFINITY
, INFINITY
);
479 constraint
= ApplySizeConstraints (constraint
);
481 constraint
= constraint
.GrowBy (-padding
);
485 result
= Size (actual_width
, actual_height
);
486 result
= result
.GrowBy (padding
);
492 TextBlock::MeasureOverride (Size availableSize
)
494 Thickness padding
= *GetPadding ();
498 constraint
= availableSize
.GrowBy (-padding
);
501 desired
= Size (actual_width
, actual_height
).GrowBy (padding
);
503 SetActualHeight (desired
.height
);
504 SetActualWidth (desired
.width
);
506 if (GetUseLayoutRounding ())
507 desired
.width
= ceil (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 ();
603 InvalidateMeasure ();
604 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);
726 // setting text to null results in String.Empty
727 SetValue (TextBlock::TextProperty
, Value (""));
734 TextBlock::OnPropertyChanged (PropertyChangedEventArgs
*args
, MoonError
*error
)
736 bool invalidate
= true;
738 if (args
->GetProperty ()->GetOwnerType () != Type::TEXTBLOCK
) {
739 FrameworkElement::OnPropertyChanged (args
, error
);
741 if (args
->GetId () == FrameworkElement::LanguageProperty
) {
742 // a change in xml:lang might change font characteristics
743 if (UpdateFontDescriptions (false)) {
744 InvalidateMeasure ();
745 InvalidateArrange ();
752 if (args->GetId () == FrameworkElement::WidthProperty) {
753 //if (layout->SetMaxWidth (args->GetNewValue()->AsDouble ()))
762 if (args
->GetId () == TextBlock::FontFamilyProperty
) {
763 FontFamily
*family
= args
->GetNewValue () ? args
->GetNewValue ()->AsFontFamily () : NULL
;
764 char **families
, *fragment
;
767 CleanupDownloaders (false);
769 if (family
&& family
->source
) {
770 families
= g_strsplit (family
->source
, ",", -1);
771 for (i
= 0; families
[i
]; i
++) {
772 g_strstrip (families
[i
]);
773 if ((fragment
= strchr (families
[i
], '#'))) {
774 // the first portion of this string is the resource name...
776 AddFontResource (families
[i
]);
779 g_strfreev (families
);
782 if (UpdateFontDescriptions (false))
784 } else if (args
->GetId () == TextBlock::FontSizeProperty
) {
785 if (UpdateFontDescriptions (false))
787 } else if (args
->GetId () == TextBlock::FontStretchProperty
) {
788 if (UpdateFontDescriptions (false))
790 } else if (args
->GetId () == TextBlock::FontStyleProperty
) {
791 if (UpdateFontDescriptions (false))
793 } else if (args
->GetId () == TextBlock::FontWeightProperty
) {
794 if (UpdateFontDescriptions (false))
796 } else if (args
->GetId () == TextBlock::TextProperty
) {
798 // result of a change to the TextBlock.Text property
799 const char *text
= args
->GetNewValue() ? args
->GetNewValue()->AsString () : NULL
;
801 SetTextInternal (text
);
802 UpdateLayoutAttributes ();
805 // result of a change to the TextBlock.Inlines property
806 UpdateLayoutAttributes ();
809 } else if (args
->GetId () == TextBlock::TextDecorationsProperty
) {
811 } else if (args
->GetId () == TextBlock::TextWrappingProperty
) {
812 dirty
= layout
->SetTextWrapping ((TextWrapping
) args
->GetNewValue()->AsInt32 ());
813 } else if (args
->GetId () == TextBlock::InlinesProperty
) {
815 // result of a change to the TextBlock.Inlines property
816 InlineCollection
*inlines
= args
->GetNewValue() ? args
->GetNewValue()->AsInlineCollection () : NULL
;
819 SetValue (TextBlock::TextProperty
, Value (GetTextInternal (inlines
), true));
822 UpdateLayoutAttributes ();
825 // this should be the result of Inlines being autocreated
826 UpdateLayoutAttributes ();
829 } else if (args
->GetId () == TextBlock::LineStackingStrategyProperty
) {
830 dirty
= layout
->SetLineStackingStrategy ((LineStackingStrategy
) args
->GetNewValue()->AsInt32 ());
831 } else if (args
->GetId () == TextBlock::LineHeightProperty
) {
832 dirty
= layout
->SetLineHeight (args
->GetNewValue()->AsDouble ());
833 } else if (args
->GetId () == TextBlock::TextAlignmentProperty
) {
834 dirty
= layout
->SetTextAlignment ((TextAlignment
) args
->GetNewValue()->AsInt32 ());
835 } else if (args
->GetId () == TextBlock::PaddingProperty
) {
837 } else if (args
->GetId () == TextBlock::FontSourceProperty
) {
838 FontSource
*source
= args
->GetNewValue () ? args
->GetNewValue ()->AsFontSource () : NULL
;
839 FontManager
*manager
= Deployment::GetCurrent ()->GetFontManager ();
841 // FIXME: ideally we'd remove the old item from the cache (or,
842 // rather, 'unref' it since some other textblocks/boxes might
843 // still be using it).
845 g_free (font_source
);
847 if (source
&& source
->stream
)
848 font_source
= manager
->AddResource (source
->stream
);
852 UpdateFontDescriptions (true);
858 InvalidateMeasure ();
859 InvalidateArrange ();
866 NotifyListenersOfPropertyChange (args
, error
);
870 TextBlock::OnSubPropertyChanged (DependencyProperty
*prop
, DependencyObject
*obj
, PropertyChangedEventArgs
*subobj_args
)
872 if (prop
&& prop
->GetId () == TextBlock::ForegroundProperty
) {
875 FrameworkElement::OnSubPropertyChanged (prop
, obj
, subobj_args
);
880 TextBlock::OnCollectionChanged (Collection
*col
, CollectionChangedEventArgs
*args
)
882 InlineCollection
*inlines
= GetInlines ();
884 if (col
!= inlines
) {
885 FrameworkElement::OnCollectionChanged (col
, args
);
889 if (args
->GetChangedAction () == CollectionChangedActionClearing
)
893 // changes being handled elsewhere...
898 SetValue (TextBlock::TextProperty
, Value (GetTextInternal (inlines
), true));
901 UpdateLayoutAttributes ();
902 InvalidateMeasure ();
903 InvalidateArrange ();
909 TextBlock::OnCollectionItemChanged (Collection
*col
, DependencyObject
*obj
, PropertyChangedEventArgs
*args
)
911 InlineCollection
*inlines
= GetInlines ();
913 if (col
!= inlines
) {
914 FrameworkElement::OnCollectionItemChanged (col
, obj
, args
);
918 if (args
->GetId () != Inline::ForegroundProperty
) {
919 if (args
->GetId () == Run::TextProperty
) {
920 // update our TextProperty
922 SetValue (TextBlock::TextProperty
, Value (GetTextInternal (inlines
), true));
925 UpdateLayoutAttributes ();
927 // likely a font property change...
928 ((Inline
*) obj
)->UpdateFontDescription (font_source
, true);
931 // All non-Foreground property changes require
932 // recalculating layout which can change the bounds.
933 InvalidateMeasure ();
934 InvalidateArrange ();
938 // A simple Foreground brush change does not require
939 // recalculating layout. Invalidate() and we're done.
946 TextBlock::downloader_complete (EventObject
*sender
, EventArgs
*calldata
, gpointer closure
)
948 ((TextBlock
*) closure
)->DownloaderComplete ((Downloader
*) sender
);
952 TextBlock::DownloaderComplete (Downloader
*downloader
)
954 FontManager
*manager
= Deployment::GetCurrent ()->GetFontManager ();
955 char *resource
, *filename
;
956 InternalDownloader
*idl
;
961 InvalidateMeasure ();
962 InvalidateArrange ();
964 // get the downloaded file path (enforces a mozilla workaround for files smaller than 64k)
965 if (!(filename
= downloader
->GetDownloadedFilename (NULL
)))
970 if (!(idl
= downloader
->GetInternalDownloader ()))
973 if (!(idl
->GetObjectType () == Type::FILEDOWNLOADER
))
976 uri
= downloader
->GetUri ();
978 // If the downloaded file was a zip file, this'll get the path to the
979 // extracted zip directory, else it will simply be the path to the
981 if (!(path
= ((FileDownloader
*) idl
)->GetUnzippedPath ()))
984 resource
= uri
->ToString ((UriToStringFlags
) (UriHidePasswd
| UriHideQuery
| UriHideFragment
));
985 manager
->AddResource (resource
, path
);
988 if (UpdateFontDescriptions (true)) {