B4A Library LabelExtras - advanced click handling

LabelExtras enables you to apply click listeners to sub-strings of a Label text.
A click on a sub-string raises an event and you can take whatever action you desire on such a click.

LabelExtras also exposes the Android Html class so you can use basic HTML formatting elements such as <b>, <i> and <u> in your Label text.

Here's the library reference:

LabelExtras
Version:
1.12
  • ClickableSpan
    Events:
    • Click (SelectionStart As Int, SelectionEnd As Int)
    • Click2 (SelectionStart As Int, SelectionEnd As Int, Tag As Object)
    Methods:
    • Initialize (EventName As String, Color As Int, UnderlineText As Boolean)
      Initialize the ClickableSpan.
      Color defines the color of the clickable text, pass -1 if you do not wish to change that text color.
      UnderlineText defines whether the clickable text shall be underlined.
      The Click(SelectionStart As Int, SelectionEnd As Int) event will be raised when this ClickableSpan is clicked.
    • Initialize2 (EventName As String, Color As Int, UnderlineText As Boolean, Tag As Object)
      Initialize the ClickableSpan.
      Color defines the color of the clickable text, pass -1 if you do not wish to change that text color.
      UnderlineText defines whether the clickable text shall be underlined.
      Pass any Object as the Tag and this Object will be returned to the Click2 event.
      The Click2(SelectionStart As Int, SelectionEnd As Int, Tag As Object) event will be raised when this ClickableSpan is clicked.
    • IsInitialized As Boolean
  • Html
    Events:
    • GetDrawable (Source As String) As Object
    • HandleTag (Opening As Boolean, Tag As String, SpannableStringBuilder1 As SpannableStringBuilder)
    Methods:
    • EscapeHtml (CharSequence1 As CharSequence) As String
      Returns an HTML escaped representation of the given plain text.
      Only available with Android AP version 16+.
    • FromHtml (HtmlString As String) As CharSequence
      Returns displayable styled text from the provided HTML string.
    • FromHtml2 (HtmlString As String, ImageGetterEventName As String, TagHandlerEventName As String) As CharSequence
      Returns displayable styled text from the provided HTML string.
      Using optional ImageGetter (to handle HTML IMG tags in the String) and TagHandler (to handle unrecognised tags in the String) callbacks.
      The drawable returned by the ImageGetter sub has to be sized with setBounds.
    • ToHtml (CharSequence1 As CharSequence) As CharSequence
      Returns an HTML representation of the provided Spanned text.
  • LabelExtras
    Methods:
    • SetDrawableBounds (Drawable1 As Drawable, Left As Int, Top As Int, Right As Int, Bottom As Int)
      Specifies a bounding rectangle for the drawable.
      Required for the Html Object ImageGetter callback.
    • SetMovementMethod (Label1 As TextView, MovementMethod1 As MovementMethod)
      Sets the movement method (arrow key handler) to be used for this TextView.
      To enable the LabelExtras click functionality you must set your Label movement method to an instance of LinkMovementMethod.
      The LinkMovementMethod.GetInstance method returns the instance required.
  • LinkMovementMethod
    Methods:
    • GetInstance As MovementMethod
      Returns an instance of the LinkMovementMethod which is required by the LabelExtras.SetMovementMethod method.
  • SpannableStringBuilder
    Methods:
    • Append (Text As CharSequence) As SpannableStringBuilder
    • Append2 (Text As CharSequence, Start As Int, End As Int) As SpannableStringBuilder
    • Append3 (Char1 As Char) As SpannableStringBuilder
    • CharAt (Where As Int) As Char
    • Clear
    • ClearSpans
    • Delete (Start As Int, End As Int) As SpannableStringBuilder
    • GetChars (Start As Int, End As Int, Dest() As Char, DestOffset As Int)
    • GetSpanEnd (Span1 As Object) As Int
    • GetSpanFlags (Span1 As Object) As Int
    • GetSpanStart (Span1 As Object) As Int
    • GetStyleSpans (QueryStart As Int, QueryEnd As Int) As StyleSpan[]
    • GetURLSpans (QueryStart As Int, QueryEnd As Int) As URLSpan[]
    • GetUnderlineSpans (QueryStart As Int, QueryEnd As Int) As UnderlineSpan[]
    • Initialize (CharSequence1 As CharSequence)
      Initialize the SpannableStringBuilder with a CharSequence.
      A CharSequence can be obtained using the Html.FromHtml method, a String can also be passed as a CharacterSequence.
    • IsInitialized As Boolean
    • NextStyleSpanTransition (Start As Int, Limit As Int) As Int
      Return the next offset after Start but less than or equal to Limit where a StyleSpan begins or ends.
    • NextURLSpanTransition (Start As Int, Limit As Int) As Int
      Return the next offset after Start but less than or equal to Limit where a URLSpan begins or ends.
    • NextUnderlineSpanTransition (Start As Int, Limit As Int) As Int
      Return the next offset after Start but less than or equal to Limit where an UnderlineSpan begins or ends.
    • RemoveSpan (Span1 As Object)
    • Replace (Start As Int, End As Int, CharSequence1 As CharSequence) As SpannableStringBuilder
    • Replace2 (Start As Int, End As Int, CharSequence1 As CharSequence, CharSequence1Start As Int, CharSequence1End As Int) As SpannableStringBuilder
    • SetSpan (Span1 As Object, Start As Int, End As Int, Flags As Int)
    Properties:
    • Length As Int [read only]
      Return the number of Chars in the buffer.
  • Spanned
    Fields:
    • SPAN_EXCLUSIVE_EXCLUSIVE As Int
      Constant which can be passed as the SpannableStringBuilder.SetSpan method Flags parameter.
      Spans of type SPAN_EXCLUSIVE_EXCLUSIVE do not expand to include text inserted at either their starting or ending point.
    • SPAN_EXCLUSIVE_INCLUSIVE As Int
      Constant which can be passed as the SpannableStringBuilder.SetSpan method Flags parameter.
      Non-0-length spans of type SPAN_EXCLUSIVE_INCLUSIVE expand to include text inserted at their ending point but not at their starting point.
    • SPAN_INCLUSIVE_EXCLUSIVE As Int
      Constant which can be passed as the SpannableStringBuilder.SetSpan method Flags parameter.
      Non-0-length spans of type SPAN_INCLUSIVE_EXCLUSIVE expand to include text inserted at their starting point but not at their ending point.
    • SPAN_INCLUSIVE_INCLUSIVE As Int
      Constant which can be passed as the SpannableStringBuilder.SetSpan method Flags parameter.
      Spans of type SPAN_INCLUSIVE_INCLUSIVE expand to include text inserted at either their starting or ending point.
  • StyleSpan
    Methods:
    • GetStyle As Int
      Returns the Typeface style of this StyleSpan.
      Typeface.STYLE_BOLD, Typeface.STYLE_ITALIC etc.
    • IsInitialized As Boolean
  • URLSpan
    Methods:
    • GetURL As String
    • IsInitialized As Boolean
  • UnderlineSpan
    Methods:
    • IsInitialized As Boolean

Example code to follow.

Martin.
 

Attachments

  • LabelExtras_v1_12.zip
    15.9 KB · Views: 1,962
Last edited:

warwound

Expert
Licensed User
Longtime User
And here's a simple example of LabelExtras:

B4X:
Sub Process_Globals

End Sub

Sub Globals
   Dim ClickableLabel As Label
   Dim LogLabel As Label
End Sub

Sub Activity_Create(FirstTime As Boolean)
   '   the Labels used in this demo are defined in the layout file "Main"
   Activity.LoadLayout("Main")
   
   '   Dim the objects from the LabelExtras library
   Dim Html1 As Html
   Dim LabelExtras1 As LabelExtras
   Dim LinkMovementMethod1 As LinkMovementMethod
   Dim SpannableStringBuilder1 As SpannableStringBuilder
   Dim StyleSpans() As StyleSpan
   
   '   this is the String which contains HTML elements that we want to make clickable
   '   note that the Html FromHtml method parses the String as HTML so if you try to use the CRLF constant in this String it will not create a new line, instead the HTML <br> element can be used
   Dim HtmlString As String="Any text that is within HTML <b>BOLD</b> or <i>ITALIC</i> tags will have a click listener added.<br><br>Click <b>here</b> to start ActivityTwo."
   
   SpannableStringBuilder1.Initialize(Html1.FromHtml(HtmlString))
   
   '   get the SpanStyles and iterate through them
   '   making each StyleSpan a ClickableSpan
   StyleSpans=SpannableStringBuilder1.GetStyleSpans(0, SpannableStringBuilder1.Length)
   For Each StyleSpan1 As StyleSpan In StyleSpans
      Dim SpanStart As Int=SpannableStringBuilder1.GetSpanStart(StyleSpan1)
      Dim SpanEnd As Int=SpannableStringBuilder1.GetSpanEnd(StyleSpan1)
      Dim SpanFlags As Int=SpannableStringBuilder1.GetSpanFlags(StyleSpan1)
      
      Dim ClickableSpan1 As ClickableSpan
      '   these clickable spans will have red text but not be underlined
      ClickableSpan1.Initialize("ClickableSpan1", Colors.Red, False)
      
      SpannableStringBuilder1.SetSpan(ClickableSpan1, SpanStart, SpanEnd, SpanFlags)
   Next
   
   '   now set the Label.Text property 
   ClickableLabel.Text=SpannableStringBuilder1
   
   '   finally call the LabelExtras.SetMovementMethod
   '   this is required so that the click functionality works as desired
   LabelExtras1.SetMovementMethod(ClickableLabel, LinkMovementMethod1.GetInstance)

End Sub

Sub Activity_Resume

End Sub

Sub Activity_Pause (UserClosed As Boolean)

End Sub

Sub ClickableSpan1_Click(SelectionStart As Int, SelectionEnd As Int)
   Dim LogText As StringBuilder
   LogText.Initialize
   
   LogText.Append("ClickableSpan1_Click: "&DateTime.Now&CRLF)
   LogText.Append("SelectionStart="&SelectionStart&CRLF)
   LogText.Append("SelectionEnd="&SelectionEnd&CRLF)
   
   Dim SelectedText As String=ClickableLabel.Text.SubString2(SelectionStart, SelectionEnd)
   LogText.Append("Selected (clicked) text='"&SelectedText&"'")
   
   LogLabel.Text=LogText.ToString
   
   If SelectedText="here" Then
      StartActivity(ActivityTwo)
   End If
End Sub

Clicks on the text within <b> and <i> HTML elements raise an event.
The event is passed the SelectionStart and SelectionEnd indexes of the clicked text.
In the Sub that handles the event you can take whatever action you desire!

Martin
 

Attachments

  • LabelExtras_simple_example.zip
    9 KB · Views: 1,074

warwound

Expert
Licensed User
Longtime User
Thanks for the replies.
Here is another example, showing how to use the StyleSpan GetStyle method to only add click listeners to StyleSpans that have a style of Typeface.STYLE_BOLD:

B4X:
Sub Process_Globals

End Sub

Sub Globals
   Dim ClickableLabel As Label
   Dim LogLabel As Label
End Sub

Sub Activity_Create(FirstTime As Boolean)
   Activity.LoadLayout("Main")
   
   '   Dim the objects from the LabelExtras library
   Dim Html1 As Html
   Dim LabelExtras1 As LabelExtras
   Dim LinkMovementMethod1 As LinkMovementMethod
   Dim SpannableStringBuilder1 As SpannableStringBuilder
   Dim StyleSpans() As StyleSpan
   
   '   this is the String which contains HTML elements that we want to make clickable
   '   note that the Html FromHtml method parses the String as HTML so if you try to use the CRLF constant in this String it will not create a new line, instead the HTML <br> element can be used
   Dim HtmlString As String="This String contains text that is within <b>BOLD</b> and <i>ITALIC</i> HTML tags, but a click listener will <i>only</i> be added to the text within the <b>BOLD</b> tags."
   
   SpannableStringBuilder1.Initialize(Html1.FromHtml(HtmlString))
   
   StyleSpans=SpannableStringBuilder1.GetStyleSpans(0, SpannableStringBuilder1.Length)
   For Each StyleSpan1 As StyleSpan In StyleSpans
      If StyleSpan1.GetStyle=Typeface.STYLE_BOLD Then
         Dim SpanStart As Int=SpannableStringBuilder1.GetSpanStart(StyleSpan1)
         Dim SpanEnd As Int=SpannableStringBuilder1.GetSpanEnd(StyleSpan1)
         Dim SpanFlags As Int=SpannableStringBuilder1.GetSpanFlags(StyleSpan1)
         
         Dim ClickableSpan1 As ClickableSpan
         ClickableSpan1.Initialize("ClickableSpan1", Colors.Red, False)
         
         SpannableStringBuilder1.SetSpan(ClickableSpan1, SpanStart, SpanEnd, SpanFlags)
      End If
   Next
   
   ClickableLabel.Text=SpannableStringBuilder1
   LabelExtras1.SetMovementMethod(ClickableLabel, LinkMovementMethod1.GetInstance)

End Sub

Sub Activity_Resume

End Sub

Sub Activity_Pause (UserClosed As Boolean)

End Sub

Sub ClickableSpan1_Click(SelectionStart As Int, SelectionEnd As Int)
   Dim LogText As StringBuilder
   LogText.Initialize
   
   LogText.Append("ClickableSpan1_Click: "&DateTime.Now&CRLF)
   LogText.Append("SelectionStart="&SelectionStart&CRLF)
   LogText.Append("SelectionEnd="&SelectionEnd&CRLF)
   
   Dim SelectedText As String=ClickableLabel.Text.SubString2(SelectionStart, SelectionEnd)
   LogText.Append("Selected (clicked) text='"&SelectedText&"'")
   
   LogLabel.Text=LogText.ToString
End Sub

It could easily have added click listeners only to StyleSpans that had a style of Typreface.STYLE_ITALIC:

B4X:
If StyleSpan1.GetStyle=Typeface.STYLE_ITALIC Then

I also experimented using a String with both bold and italic tags:

B4X:
Dim HtmlString As String="... but a click listener will <b><i>only</i></b> be added ..."

The StyleSpans Array contains two StyleSpan objects for this String, one StyleSpan has a style of Typeface.STYLE_BOLD and the other has a style of Typeface.STYLE_ITALIC.

Martin.
 

Attachments

  • LabelExtras_bold_only.zip
    8.5 KB · Views: 588

warwound

Expert
Licensed User
Longtime User
The LabelExtras library also contains an UnderlineSpan object which can be used to add click listeners to text within an HTML <u> underline tag:

B4X:
Sub Process_Globals

End Sub

Sub Globals
   Dim ClickableLabel As Label
   Dim LogLabel As Label
End Sub

Sub Activity_Create(FirstTime As Boolean)
   Activity.LoadLayout("Main")
   
   '   Dim the objects from the LabelExtras library
   Dim Html1 As Html
   Dim LabelExtras1 As LabelExtras
   Dim LinkMovementMethod1 As LinkMovementMethod
   Dim SpannableStringBuilder1 As SpannableStringBuilder
   Dim UnderlineSpans() As UnderlineSpan
   
   Dim HtmlString As String="The LabelExtras library also contains an <u>UnderlineSpan</u> object which can be used to add click listeners to text with an HTML &lt;u&gt; underline tag."
   
   SpannableStringBuilder1.Initialize(Html1.FromHtml(HtmlString))
   
   UnderlineSpans=SpannableStringBuilder1.GetUnderlineSpans(0, SpannableStringBuilder1.Length)
   For Each UnderlineSpan1 As UnderlineSpan In UnderlineSpans
      Dim SpanStart As Int=SpannableStringBuilder1.GetSpanStart(UnderlineSpan1)
      Dim SpanEnd As Int=SpannableStringBuilder1.GetSpanEnd(UnderlineSpan1)
      Dim SpanFlags As Int=SpannableStringBuilder1.GetSpanFlags(UnderlineSpan1)
      
      Dim ClickableSpan1 As ClickableSpan
      ClickableSpan1.Initialize("ClickableSpan1", Colors.Blue, True)
      
      SpannableStringBuilder1.SetSpan(ClickableSpan1, SpanStart, SpanEnd, SpanFlags)
   Next
   
   ClickableLabel.Text=SpannableStringBuilder1
   LabelExtras1.SetMovementMethod(ClickableLabel, LinkMovementMethod1.GetInstance)

End Sub

Sub Activity_Resume

End Sub

Sub Activity_Pause (UserClosed As Boolean)

End Sub

Sub ClickableSpan1_Click(SelectionStart As Int, SelectionEnd As Int)
   Dim LogText As StringBuilder
   LogText.Initialize
   
   LogText.Append("ClickableSpan1_Click: "&DateTime.Now&CRLF)
   LogText.Append("SelectionStart="&SelectionStart&CRLF)
   LogText.Append("SelectionEnd="&SelectionEnd&CRLF)
   
   Dim SelectedText As String=ClickableLabel.Text.SubString2(SelectionStart, SelectionEnd)
   LogText.Append("Selected (clicked) text='"&SelectedText&"'")
   
   LogLabel.Text=LogText.ToString
End Sub

There are a few other Android classes that could be added to the library, take a look here: CharacterStyle | Android Developers.
I'm not at all sure what many of these other classes do, URLSpan is presumably an HTML anchor tag.
If there's any interest i'll look at adding more of these 'span' classes to the library.

Martin.
 

Attachments

  • LabelExtras_underline_span.zip
    8.5 KB · Views: 586

warwound

Expert
Licensed User
Longtime User
Here's (yet) another example, showing how to add a click listener to a String using character indexes to define the start and end points of the clickable text:

B4X:
Sub Process_Globals

End Sub

Sub Globals
   Dim ClickableLabel As Label
   Dim LogLabel As Label
End Sub

Sub Activity_Create(FirstTime As Boolean)
   Activity.LoadLayout("Main")
   
   '   Dim the objects from the LabelExtras library
   Dim LabelExtras1 As LabelExtras
   Dim LinkMovementMethod1 As LinkMovementMethod
   Dim SpannableStringBuilder1 As SpannableStringBuilder
   Dim Spanned1 As Spanned
   
   SpannableStringBuilder1.Initialize("This is a plain String."&CRLF&"It contains no HTML tags and has NOT been passed to the Html.FromHtml method."&CRLF&"A click listener will be added to the word 'plain' in the first sentence.")
   
   Dim ClickableSpan1 As ClickableSpan
   ClickableSpan1.Initialize("ClickableSpan1", Colors.Magenta, True)
   SpannableStringBuilder1.SetSpan(ClickableSpan1, 10, 15, Spanned1.SPAN_EXCLUSIVE_EXCLUSIVE)
   
   ClickableLabel.Text=SpannableStringBuilder1
   LabelExtras1.SetMovementMethod(ClickableLabel, LinkMovementMethod1.GetInstance)

End Sub

Sub Activity_Resume

End Sub

Sub Activity_Pause (UserClosed As Boolean)

End Sub

Sub ClickableSpan1_Click(SelectionStart As Int, SelectionEnd As Int)
   Dim LogText As StringBuilder
   LogText.Initialize
   
   LogText.Append("ClickableSpan1_Click: "&DateTime.Now&CRLF)
   LogText.Append("SelectionStart="&SelectionStart&CRLF)
   LogText.Append("SelectionEnd="&SelectionEnd&CRLF)
   
   Dim SelectedText As String=ClickableLabel.Text.SubString2(SelectionStart, SelectionEnd)
   LogText.Append("Selected (clicked) text='"&SelectedText&"'")
   
   LogLabel.Text=LogText.ToString
End Sub

With previous examples, the value passed as the Flags parameter to the SpannableStringBuilder.SetSpan method has been the Flags value obtained from the SpannableStringBuilder1.GetSpanFlags method.
Whatever the orginal Flags value was, it is preserved and set as the Flags of the new ClickableSpan.

With this example we have no original flags to get and then set.
LabelExtras' Spanned object has been created so that constants from the Android Spanned class can be used.
I'm not sure exactly what the various constants do in practice - i've used the constant SPAN_EXCLUSIVE_EXCLUSIVE here and it works...

Martin.
 

Attachments

  • LabelExtras_using_char_indexes.zip
    8.3 KB · Views: 605

susu

Well-Known Member
Licensed User
Longtime User
Thank you Martin. It's very useful because you worked really hard. :sign0188:
 

Informatix

Expert
Licensed User
Longtime User
Could you add the fromHTML function allowing to provide the image and to replace the unknown tags?
Something like this (for handleTag, it's just a code skeleton):
B4X:
private String evtImageGetter, evtTagHandler;
private BA _ba;

   /**
    *Returns a displayable styled text from the provided HTML string.
    *Any &lt;img&gt; tags in the HTML will call the specified ImageGetter sub to request a representation of the image (a drawable or null for a generic replacement image) and the specified TagHandler sub to handle unknown tags.
    */
   public CharSequence FromHtmlWithEvents(final BA ba, String HtmlString, String EventNameForImageGetter, String EventNameForTagHandler)
   {
      evtImageGetter = EventNameForImageGetter.toLowerCase(BA.cul);
      evtTagHandler = EventNameForTagHandler.toLowerCase(BA.cul);
      _ba = ba;
      ImageGetter imageGetter = new ImageGetter() {
         @Override
         public Drawable getDrawable(String source) {
            if (_ba.subExists(evtImageGetter)) {
               return (Drawable) _ba.raiseEvent(this, evtImageGetter, new Object[] {source});
            }
            return null;
         }
      };
      TagHandler tagHandler = new TagHandler() {
         @Override
         public void handleTag(boolean opening, String tag, Editable output, XMLReader xmlReader) {
            if (_ba.subExists(evtTagHandler)) {
               Spanned result;
               result = (Spanned) _ba.raiseEvent(this, evtTagHandler, new Object[] {opening, tag});
               if (result != null) {

               }
            }
         }
      };
      return android.text.Html.fromHtml(HtmlString, imageGetter, tagHandler);
   }
 

warwound

Expert
Licensed User
Longtime User
Ok, i have added support for the ImageGetter and TagHandler interfaces.

ImageGetter raises this event: GetDrawable(Source As String) As Drawable.

There should be no problems returning an instance of a Drawable from the b4a event sub to the library i think?
Is there a way to set a Drawable bounds in b4a or does that require me to create a helper object to set the Drawable bounds?

ImageGetter
Events:
  • GetDrawable (Source As String)
Methods:
  • Initialize (EventName As String)
    ImageGetter raises the GetDrawable(Source As String) As Drawable event when the HTML parser encounters an IMG tag.
    The Source argument is the string from the "src" attribute.
    The event handling Sub should return a Drawable representation of the image or null for a generic replacement image.
    Make sure you call setBounds() on your Drawable if it doesn't already have its bounds set.
  • IsInitialized As Boolean


TagHandler raises this event: HandleTag(Opening As Boolean, Tag As String, SpannableStringBuilder1 As SpannableStringBuilder).

The android.text.Html.TagHandler interface actually passes an Editable to it's handleTag method.
Editable is an interface and i'm hoping that casting the Editable to a wrapped SpannableStringBuilder will work.

TagHandler
Events:
  • HandleTag (Opening As Boolean, Tag As String, SpannableStringBuilder1 As SpannableStringBuilder)
Methods:
  • Initialize (EventName As String)
    TagHandler raises the HandleTag(Opening As Boolean, Tag As String, SpannableStringBuilder1 As SpannableStringBuilder)
    event when he HTML parser encounters a tag that it does not know how to interpret.
  • IsInitialized As Boolean

I've attached the update, if you can test it and report back with your findings.
And if it works it'd be handy if you could upload any code examples to help other forum members use the new interfaces.

The library also includeds some other unfinished updates - these other new objects have _TEST added to their ShortNames so if you ignore them and i'll decide what to do with them once you've tested the ImageGetter and TagHandler objects.

Martin.
 
Last edited:

Informatix

Expert
Licensed User
Longtime User
I don't understand what you did. It cannot work because there's no redirection to the two handlers. And I find that creating new classes for the handlers is a bit cumbersome. I would prefer to declare only the HTML class with an event prefix as parameter.
An event cannot return a Drawable. Change "drawable" by "object" in the event declaration.

There's a bug with your script converting the XML file. The returned object for events is missing.

And a big thank for considering my demand.
 

warwound

Expert
Licensed User
Longtime User
That was a bit of an epic update then!
I was busy and didn't have time to properly test it.

So forget that update then and look at the update attached to this post.

I've added a new method to the Html object as you requested:

Html
Events:
  • GetDrawable (Source As String) As Object
  • HandleTag (Opening As Boolean, Tag As String, SpannableStringBuilder1 As SpannableStringBuilder)
Methods:
  • FromHtml2 (HtmlString As String, ImageGetterEventName As String, TagHandlerEventName As String) As CharSequence
    Returns displayable styled text from the provided HTML string.

I took one of the previous examples and changed the HTML String to:

B4X:
Dim HtmlString As String="<b>Any</b> text that you want to detect <b><i>clicks</i></b> on must be within an <spans>HTML</span> <b>BOLD</b> element, the <u>StyleSpanClick</u> event is passed SelectionStart and SelectionEnd parameters.<br><br>Click <b>HERE</b> to <martin>continue</martin>.<br><img src='http://localhost/myimage.jpg' />"

So there is an IMG tag and an unknown tag (martin!).
I added some Log statements and the event handler Subs get called, now we have a problem with the ImageGetter Sub.
The events raised by the ImageGetter and TagHandler interfaces are raised from a different thread than the UI thread.
How can the ImageGetter event Sub return a value?
Here's the source for the new FromHtml2 method:

B4X:
   public static CharSequence FromHtml2(final BA pBA, String HtmlString, String ImageGetterEventName, String TagHandlerEventName) {

      ImageGetter imageGetter = null;
      final String imageGetterEventName = ImageGetterEventName.toLowerCase(BA.cul)
            + "_getdrawable";
      if (pBA.subExists(imageGetterEventName)) {
         imageGetter = new ImageGetter() {
            @Override
            public Drawable getDrawable(String pSource) {
               //   problem - cannot return a value from the UI thread
               Log.d("B4A", "getDrawable callback: "+imageGetterEventName);
               return (Drawable) pBA.raiseEventFromDifferentThread(null, null, mTaskId++, imageGetterEventName, false, new Object[]{pSource});
            }
         };
      }

      TagHandler tagHandler = null;
      final String tagHandlerEventName = TagHandlerEventName.toLowerCase(BA.cul)
            + "_handletag";
      if (pBA.subExists(tagHandlerEventName)) {
         tagHandler = new TagHandler() {
            @Override
            public void handleTag(boolean pOpening, String pTag, Editable pOutput, XMLReader pXMLReader) {
               Log.d("B4A", "handleTag callback: "+tagHandlerEventName);
               //   pBA.raiseEvent(null, tagHandlerEventName, new Object[]{pOpening, pTag, new SpannableStringBuilder((android.text.SpannableStringBuilder) pOutput)});
               pBA.raiseEventFromDifferentThread(null, null, mTaskId++, tagHandlerEventName, false, new Object[]{pOpening, pTag, new SpannableStringBuilder((android.text.SpannableStringBuilder) pOutput)});
            }
         };
      }

      return android.text.Html.fromHtml(HtmlString, imageGetter, tagHandler);
   }

The b4a events do not get raised if i use the BA raiseEvent method, raiseEventFromDifferentThread is documented as having an Object return type but it is not useable.
See here: http://www.b4x.com/forum/basic4andr...099-jsinterface-problem-msgbox.html#post62436
The return type of raiseEventFromDifferentThread is always Null.

Problem 2 is how to process an unknown tag?

B4X:
Sub TagHandler_HandleTag(Opening As Boolean, Tag As String, SpannableStringBuilder1 As SpannableStringBuilder)
   Log("TagHandler1_HandleTag: "&Opening&", "&Tag)
   If Tag="martin" Then
      Log("Tag=martin")
      If Opening Then
         SpannableStringBuilder1.Append("START")
      Else
         SpannableStringBuilder1.Append("END")
      End If
   End If

   Log(SpannableStringBuilder1)
End Sub

This Sub gets called a for the unknown 'martin' tag opening and closing tag elements, as a quick attempt to 'do something' i've appended START and END to the SpannableStringBuilder thinking it might replace the unknow tag with that text - it didn't.
I then added the Log(SpannableStringBuilder1) line and can see my appended text at the end of the String representation of the SpannableStringBuilder.

I'll look at it more tomorrow - meanwhile if you want to try yourself then both my example code and the updated library files are attached.

Martin.
 
Last edited:

Informatix

Expert
Licensed User
Longtime User
The events raised by the ImageGetter and TagHandler interfaces are raised from a different thread than the UI thread.
How can the ImageGetter event Sub return a value?
...
raiseEventFromDifferentThread is documented as having an Object return type but it is not useable.

RaiseEventFromDifferentThread creates a runnable (a piece of code to execute) and put it in the main message queue, then exit with the null value. It cannot return anything from your B4A code because it does not call the event handler. It's just a message creator/sender. The runnable will be executed (=the event handler will be called) when the message is processed by the message handler.
So we're a little bit stuck here.
 
Last edited:

warwound

Expert
Licensed User
Longtime User
Try the attached library version - all i've changed is the code that raises the events.

The attached version is compiled from this:

B4X:
   public static CharSequence FromHtml2(final BA pBA, String HtmlString, String ImageGetterEventName, String TagHandlerEventName) {

      ImageGetter imageGetter = null;
      final String imageGetterEventName = ImageGetterEventName.toLowerCase(BA.cul)
            + "_getdrawable";
      if (pBA.subExists(imageGetterEventName)) {
         imageGetter = new ImageGetter() {
            @Override
            public Drawable getDrawable(String pSource) {
               //   problem - cannot return a value from the UI thread
               Log.d("B4A", "getDrawable callback: "+imageGetterEventName);
               return (Drawable) pBA.raiseEvent(null, imageGetterEventName, new Object[]{pSource});
               //   return (Drawable) pBA.raiseEventFromDifferentThread(null, null, mTaskId++, imageGetterEventName, false, new Object[]{pSource});
            }
         };
      }

      TagHandler tagHandler = null;
      final String tagHandlerEventName = TagHandlerEventName.toLowerCase(BA.cul)
            + "_handletag";
      if (pBA.subExists(tagHandlerEventName)) {
         tagHandler = new TagHandler() {
            @Override
            public void handleTag(boolean pOpening, String pTag, Editable pOutput, XMLReader pXMLReader) {
               Log.d("B4A", "handleTag callback: "+tagHandlerEventName);
               pBA.raiseEvent(null, tagHandlerEventName, new Object[]{pOpening, pTag, new SpannableStringBuilder((android.text.SpannableStringBuilder) pOutput)});
               //   pBA.raiseEventFromDifferentThread(null, null, mTaskId++, tagHandlerEventName, false, new Object[]{pOpening, pTag, new SpannableStringBuilder((android.text.SpannableStringBuilder) pOutput)});
            }
         };
      }

      return android.text.Html.fromHtml(HtmlString, imageGetter, tagHandler);
   }

For me on a Jelly Bean tablet that fails to raise any b4a events, the logs created within the library callbacks appear but no events are raised.

The version of the library in my previous post was compiled using raiseEventFromDifferentThread - you can see i've commented out these lines and replaced them with raiseEvent.

I was planning to look at the various CallSub methods available to library code - i'm busy most of tomorrow so that might not be until Weds.
If you'd like me to export the Eclipse project so you can experiment with it then let me know.

Martin.
 
Last edited:

Informatix

Expert
Licensed User
Longtime User
In the meantime, I decompiled your code and tried to find a solution. I confirm that raiseEvent/rE2 does not fire anything. BUT I'VE FOUND A WAY ! I'm trying to do the things properly now (without my ugly hack)... I'll post when it's ready.
 

Informatix

Expert
Licensed User
Longtime User
Et voilà !
With my hack, I saw that the activity was considered as paused by B4A so I changed the second parameter of RaiseEvent2 and that worked! We were very close to the solution.
You'll find in the archive my B4A test project, the Java code for the html class (ready-made) and the recompiled library. I removed escapetoHTML because it is only available with API 16 and I don't think it's very useful.
 

Attachments

  • lblextras_html.zip
    23.3 KB · Views: 525

warwound

Expert
Licensed User
Longtime User
The recompiled library provided in the previous post does not include all the original documentation. The HTML java file should be merged with the existing library and recompiled properly.

OK no problems.
I plan to look at it all at the weekend and will then post again.

Martin.
 
Top