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
;
100 if (!application
|| !uri
->Parse (resource
) || !(path
= application
->GetResourceAsPath (GetResourceBase(), uri
))) {
101 if (IsAttached () && (downloader
= GetDeployment ()->GetSurface ()->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::InsideObject (cairo_t
*cr
, double x
, double y
)
341 double nx
= x
, ny
= y
;
342 Size total
= GetRenderSize ().Max (GetActualWidth (), GetActualHeight ());
343 total
= total
.Max (ApplySizeConstraints (total
));
345 TransformPoint (&nx
, &ny
);
347 if (nx
< 0 || ny
< 0 || nx
> total
.width
|| ny
> total
.height
)
350 return InsideLayoutClip (x
, y
) && InsideClip (cr
, x
, y
);
354 TextBlock::CleanupDownloaders (bool all
)
356 Downloader
*downloader
;
359 for (i
= 0; i
< downloaders
->len
; i
++) {
360 downloader
= (Downloader
*) downloaders
->pdata
[i
];
362 if (all
|| downloader
!= source
) {
363 downloader
->RemoveHandler (Downloader::CompletedEvent
, downloader_complete
, this);
364 downloader
->Abort ();
365 downloader
->unref ();
369 g_ptr_array_set_size (downloaders
, 0);
371 if (source
&& !all
) {
372 g_ptr_array_add (downloaders
, source
);
378 g_free (font_source
);
384 TextBlock::AddFontSource (Downloader
*downloader
)
386 downloader
->AddHandler (downloader
->CompletedEvent
, downloader_complete
, this);
387 g_ptr_array_add (downloaders
, downloader
);
390 if (downloader
->Started () || downloader
->Completed ()) {
391 if (downloader
->Completed ())
392 DownloaderComplete (downloader
);
394 // This is what actually triggers the download
400 TextBlock::SetFontSource (Downloader
*downloader
)
402 CleanupDownloaders (true);
406 font_source
= downloader
->GetUri ()->ToString ((UriToStringFlags
) (UriHidePasswd
| UriHideQuery
| UriHideFragment
));
407 AddFontSource (downloader
);
411 UpdateFontDescriptions (true);
418 TextBlock::AddFontResource (const char *resource
)
420 FontManager
*manager
= Deployment::GetCurrent ()->GetFontManager ();
421 Application
*application
= Application::GetCurrent ();
422 Downloader
*downloader
;
428 if (!application
|| !uri
->Parse (resource
) || !(path
= application
->GetResourceAsPath (GetResourceBase(), uri
))) {
429 if (IsAttached () && (downloader
= GetDeployment ()->GetSurface ()->CreateDownloader ())) {
430 downloader
->Open ("GET", resource
, FontPolicy
);
431 AddFontSource (downloader
);
432 downloader
->unref ();
440 manager
->AddResource (resource
, path
);
446 TextBlock::Render (cairo_t
*cr
, Region
*region
, bool path_only
)
449 cairo_set_matrix (cr
, &absolute_xform
);
452 RenderLayoutClip (cr
);
460 TextBlock::ComputeBounds ()
462 Rect extents
= layout
->GetRenderExtents ();
463 Thickness padding
= *GetPadding ();
465 extents
.x
+= padding
.left
;
466 extents
.y
+= padding
.top
;
468 bounds
= bounds_with_children
= IntersectBoundsWithClipPath (extents
, false).Transform (&absolute_xform
);
472 TextBlock::GetTransformOrigin ()
474 Point
*user_xform_origin
= GetRenderTransformOrigin ();
475 return Point (actual_width
* user_xform_origin
->x
,
476 actual_height
* user_xform_origin
->y
);
480 TextBlock::ComputeActualSize ()
482 Thickness padding
= *GetPadding ();
483 Size constraint
= ApplySizeConstraints (Size (INFINITY
, INFINITY
));
484 Size result
= Size (0.0, 0.0);
486 if (LayoutInformation::GetLayoutSlot (this) || LayoutInformation::GetPreviousConstraint (this)) {
488 layout
->GetActualExtents (&actual_width
, &actual_height
);
490 constraint
= constraint
.GrowBy (-padding
);
494 result
= Size (actual_width
, actual_height
);
495 result
= result
.GrowBy (padding
);
501 TextBlock::MeasureOverride (Size availableSize
)
503 Thickness padding
= *GetPadding ();
507 constraint
= availableSize
.GrowBy (-padding
);
511 desired
= Size (actual_width
, actual_height
).GrowBy (padding
);
517 TextBlock::ArrangeOverride (Size finalSize
)
519 Thickness padding
= *GetPadding ();
523 constraint
= finalSize
.GrowBy (-padding
);
526 arranged
= Size (actual_width
, actual_height
);
528 arranged
= arranged
.Max (constraint
);
529 layout
->SetAvailableWidth (constraint
.width
);
531 arranged
= arranged
.GrowBy (padding
);
537 TextBlock::UpdateLayoutAttributes ()
539 InlineCollection
*inlines
= GetInlines ();
540 TextLayoutAttributes
*attrs
;
546 InvalidateMeasure ();
547 InvalidateArrange ();
550 UpdateFontDescription (false);
552 if (inlines
!= NULL
) {
553 for (int i
= 0; i
< inlines
->GetCount (); i
++) {
554 item
= inlines
->GetValueAt (i
)->AsInline ();
555 item
->UpdateFontDescription (font_source
, false);
557 switch (item
->GetObjectType ()) {
559 text
= ((Run
*) item
)->GetText ();
561 if (text
&& text
[0]) {
562 attrs
= new TextLayoutAttributes ((ITextAttributes
*) item
, length
);
563 runs
->Append (attrs
);
565 length
+= strlen (text
);
569 case Type::LINEBREAK
:
570 attrs
= new TextLayoutAttributes ((ITextAttributes
*) item
, length
);
571 runs
->Append (attrs
);
573 length
+= utf8_linebreak_len
;
580 if (inlines
->GetCount () > 0)
584 layout
->SetText (GetText (), length
);
585 layout
->SetTextAttributes (runs
);
589 TextBlock::UpdateFontDescription (bool force
)
591 FontFamily
*family
= GetFontFamily ();
592 bool changed
= false;
594 if (font
->SetSource (font_source
))
597 if (font
->SetFamily (family
? family
->source
: NULL
))
600 if (font
->SetStretch (GetFontStretch ()->stretch
))
603 if (font
->SetWeight (GetFontWeight ()->weight
))
606 if (font
->SetStyle (GetFontStyle ()->style
))
609 if (font
->SetSize (GetFontSize ()))
612 if (font
->SetLanguage (GetLanguage ()))
621 layout
->SetBaseFont (font
->GetFont ());
627 TextBlock::UpdateFontDescriptions (bool force
)
629 InlineCollection
*inlines
= GetInlines ();
630 bool changed
= false;
633 changed
= UpdateFontDescription (force
);
635 if (inlines
!= NULL
) {
636 for (int i
= 0; i
< inlines
->GetCount (); i
++) {
637 item
= inlines
->GetValueAt (i
)->AsInline ();
638 if (item
->UpdateFontDescription (font_source
, force
))
643 layout
->ResetState ();
647 InvalidateMeasure ();
648 InvalidateArrange ();
657 TextBlock::Layout (Size constraint
)
659 if (was_set
&& !GetValueNoDefault (TextBlock::TextProperty
)) {
660 // If the Text property had been set once upon a time,
661 // but is currently empty, Silverlight seems to set
662 // the ActualHeight property to the font height. See
663 // bug #405514 for details.
664 TextFontDescription
*desc
= new TextFontDescription ();
665 FontFamily
*family
= GetFontFamily ();
668 desc
->SetFamily (family
? family
->source
: NULL
);
669 desc
->SetStretch (GetFontStretch ()->stretch
);
670 desc
->SetWeight (GetFontWeight ()->weight
);
671 desc
->SetStyle (GetFontStyle ()->style
);
672 desc
->SetSize (GetFontSize ());
674 font
= desc
->GetFont ();
675 actual_height
= font
->Height ();
678 } else if (!was_set
) {
679 // If the Text property has never been set, then its
680 // extents should both be 0.0. See bug #435798 for
685 layout
->SetMaxWidth (constraint
.width
);
688 layout
->GetActualExtents (&actual_width
, &actual_height
);
695 TextBlock::Paint (cairo_t
*cr
)
697 Thickness
*padding
= GetPadding ();
698 Point
offset (padding
->left
, padding
->top
);
700 cairo_set_matrix (cr
, &absolute_xform
);
701 layout
->Render (cr
, GetOriginPoint (), offset
);
705 TextBlock::GetTextInternal (InlineCollection
*inlines
)
713 return g_strdup ("");
715 block
= g_string_new ("");
717 for (int i
= 0; i
< inlines
->GetCount (); i
++) {
718 item
= inlines
->GetValueAt (i
)->AsInline ();
720 switch (item
->GetObjectType ()) {
722 text
= ((Run
*) item
)->GetText ();
725 g_string_append (block
, text
);
727 case Type::LINEBREAK
:
728 g_string_append_len (block
, utf8_linebreak
, utf8_linebreak_len
);
736 g_string_free (block
, false);
742 TextBlock::SetTextInternal (const char *text
)
744 InlineCollection
*inlines
;
748 // Note: calling GetValue() may cause the InlineCollection to be
749 // autocreated, so we need to prevent reentrancy here.
752 value
= GetValue (TextBlock::InlinesProperty
);
753 inlines
= value
->AsInlineCollection ();
758 run
->SetAutogenerated (true);
763 // setting text to null results in String.Empty
764 SetValue (TextBlock::TextProperty
, Value (""));
771 TextBlock::OnPropertyChanged (PropertyChangedEventArgs
*args
, MoonError
*error
)
773 bool invalidate
= true;
775 if (args
->GetProperty ()->GetOwnerType () != Type::TEXTBLOCK
) {
776 FrameworkElement::OnPropertyChanged (args
, error
);
778 if (args
->GetId () == FrameworkElement::LanguageProperty
) {
779 // a change in xml:lang might change font characteristics
780 if (UpdateFontDescriptions (false)) {
781 InvalidateMeasure ();
782 InvalidateArrange ();
789 if (args->GetId () == FrameworkElement::WidthProperty) {
790 //if (layout->SetMaxWidth (args->GetNewValue()->AsDouble ()))
799 if (args
->GetId () == TextBlock::FontFamilyProperty
) {
800 FontFamily
*family
= args
->GetNewValue () ? args
->GetNewValue ()->AsFontFamily () : NULL
;
801 char **families
, *fragment
;
804 CleanupDownloaders (false);
806 if (family
&& family
->source
) {
807 families
= g_strsplit (family
->source
, ",", -1);
808 for (i
= 0; families
[i
]; i
++) {
809 g_strstrip (families
[i
]);
810 if ((fragment
= strchr (families
[i
], '#'))) {
811 // the first portion of this string is the resource name...
813 AddFontResource (families
[i
]);
816 g_strfreev (families
);
819 if (UpdateFontDescriptions (false))
821 } else if (args
->GetId () == TextBlock::FontSizeProperty
) {
822 if (UpdateFontDescriptions (false))
824 } else if (args
->GetId () == TextBlock::FontStretchProperty
) {
825 if (UpdateFontDescriptions (false))
827 } else if (args
->GetId () == TextBlock::FontStyleProperty
) {
828 if (UpdateFontDescriptions (false))
830 } else if (args
->GetId () == TextBlock::FontWeightProperty
) {
831 if (UpdateFontDescriptions (false))
833 } else if (args
->GetId () == TextBlock::TextProperty
) {
835 // result of a change to the TextBlock.Text property
836 const char *text
= args
->GetNewValue() ? args
->GetNewValue()->AsString () : NULL
;
838 SetTextInternal (text
);
839 UpdateLayoutAttributes ();
842 // result of a change to the TextBlock.Inlines property
843 UpdateLayoutAttributes ();
846 } else if (args
->GetId () == TextBlock::TextDecorationsProperty
) {
848 } else if (args
->GetId () == TextBlock::TextWrappingProperty
) {
849 dirty
= layout
->SetTextWrapping ((TextWrapping
) args
->GetNewValue()->AsInt32 ());
850 } else if (args
->GetId () == TextBlock::InlinesProperty
) {
852 // result of a change to the TextBlock.Inlines property
853 InlineCollection
*inlines
= args
->GetNewValue() ? args
->GetNewValue()->AsInlineCollection () : NULL
;
856 SetValue (TextBlock::TextProperty
, Value (GetTextInternal (inlines
), true));
859 UpdateLayoutAttributes ();
862 // this should be the result of Inlines being autocreated
863 UpdateLayoutAttributes ();
866 } else if (args
->GetId () == TextBlock::LineStackingStrategyProperty
) {
867 dirty
= layout
->SetLineStackingStrategy ((LineStackingStrategy
) args
->GetNewValue()->AsInt32 ());
868 } else if (args
->GetId () == TextBlock::LineHeightProperty
) {
869 dirty
= layout
->SetLineHeight (args
->GetNewValue()->AsDouble ());
870 } else if (args
->GetId () == TextBlock::TextAlignmentProperty
) {
871 dirty
= layout
->SetTextAlignment ((TextAlignment
) args
->GetNewValue()->AsInt32 ());
872 } else if (args
->GetId () == TextBlock::PaddingProperty
) {
874 } else if (args
->GetId () == TextBlock::FontSourceProperty
) {
875 FontSource
*source
= args
->GetNewValue () ? args
->GetNewValue ()->AsFontSource () : NULL
;
876 FontManager
*manager
= Deployment::GetCurrent ()->GetFontManager ();
878 // FIXME: ideally we'd remove the old item from the cache (or,
879 // rather, 'unref' it since some other textblocks/boxes might
880 // still be using it).
882 g_free (font_source
);
884 if (source
&& source
->stream
)
885 font_source
= manager
->AddResource (source
->stream
);
889 UpdateFontDescriptions (true);
895 InvalidateMeasure ();
896 InvalidateArrange ();
903 NotifyListenersOfPropertyChange (args
, error
);
907 TextBlock::OnSubPropertyChanged (DependencyProperty
*prop
, DependencyObject
*obj
, PropertyChangedEventArgs
*subobj_args
)
909 if (prop
&& prop
->GetId () == TextBlock::ForegroundProperty
) {
912 FrameworkElement::OnSubPropertyChanged (prop
, obj
, subobj_args
);
917 TextBlock::OnCollectionChanged (Collection
*col
, CollectionChangedEventArgs
*args
)
919 InlineCollection
*inlines
= GetInlines ();
921 if (col
!= inlines
) {
922 FrameworkElement::OnCollectionChanged (col
, args
);
926 if (args
->GetChangedAction () == CollectionChangedActionClearing
)
930 // changes being handled elsewhere...
935 SetValue (TextBlock::TextProperty
, Value (GetTextInternal (inlines
), true));
938 UpdateLayoutAttributes ();
939 InvalidateMeasure ();
940 InvalidateArrange ();
946 TextBlock::OnCollectionItemChanged (Collection
*col
, DependencyObject
*obj
, PropertyChangedEventArgs
*args
)
948 InlineCollection
*inlines
= GetInlines ();
950 if (col
!= inlines
) {
951 FrameworkElement::OnCollectionItemChanged (col
, obj
, args
);
955 if (args
->GetId () != Inline::ForegroundProperty
) {
956 if (args
->GetId () == Run::TextProperty
) {
957 // update our TextProperty
959 SetValue (TextBlock::TextProperty
, Value (GetTextInternal (inlines
), true));
962 UpdateLayoutAttributes ();
964 // likely a font property change...
965 ((Inline
*) obj
)->UpdateFontDescription (font_source
, true);
968 // All non-Foreground property changes require
969 // recalculating layout which can change the bounds.
970 InvalidateMeasure ();
971 InvalidateArrange ();
975 // A simple Foreground brush change does not require
976 // recalculating layout. Invalidate() and we're done.
983 TextBlock::downloader_complete (EventObject
*sender
, EventArgs
*calldata
, gpointer closure
)
985 ((TextBlock
*) closure
)->DownloaderComplete ((Downloader
*) sender
);
989 TextBlock::DownloaderComplete (Downloader
*downloader
)
991 FontManager
*manager
= Deployment::GetCurrent ()->GetFontManager ();
992 char *resource
, *filename
;
993 InternalDownloader
*idl
;
998 InvalidateMeasure ();
999 InvalidateArrange ();
1001 // get the downloaded file path (enforces a mozilla workaround for files smaller than 64k)
1002 if (!(filename
= downloader
->GetDownloadedFilename (NULL
)))
1007 if (!(idl
= downloader
->GetInternalDownloader ()))
1010 if (!(idl
->GetObjectType () == Type::FILEDOWNLOADER
))
1013 uri
= downloader
->GetUri ();
1015 // If the downloaded file was a zip file, this'll get the path to the
1016 // extracted zip directory, else it will simply be the path to the
1018 if (!(path
= ((FileDownloader
*) idl
)->GetUnzippedPath ()))
1021 resource
= uri
->ToString ((UriToStringFlags
) (UriHidePasswd
| UriHideQuery
| UriHideFragment
));
1022 manager
->AddResource (resource
, path
);
1025 if (UpdateFontDescriptions (true)) {
1028 UpdateBounds (true);