B4A Library LabelExtras - advanced click handling

Informatix

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

Martin.
Great. And if you have enough time, could you fix your script xml2bb? It removes the returned value of events, as you can see in post #11.
 

warwound

Expert
Licensed User
At last Mothers Day comes to an end in the UK and i can return to my computer...

Thanks for the contribution Informatix, it has enabled me to complete the library update.
I've tested and it seems to work well - but i'll post a demo project and (beta) library files here for others to test before 'properly' uploading the update etc.

So an example Activity:

B4X:
Sub Process_Globals
End Sub

Sub Globals
   Dim ClickableLabel As Label
   Dim LabelExtras1 As LabelExtras
   Dim LogLabel As Label
   Dim Html1 As Html
End Sub

Sub Activity_Create(FirstTime As Boolean)
   Activity.LoadLayout("Main")
   
   '   Dim the objects from the LabelExtras library
   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="<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 <myTag>continue</myTag>.<br><br>bogus_filename.png does not exist <img src='bogus_filename.png' /><br><br>What is a question <img src='question-mark.gif' /><br><br>This is a <tag>tag</tag> <img src='tag.png' />"
   
   '   test the new FromHtml2 method
   SpannableStringBuilder1.Initialize(Html1.FromHtml2(HtmlString, "ImageGetter", "TagHandler"))
   
   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
   
   If SelectedText="HERE" Then
      StartActivity(ActivityTwo)
   End If
End Sub

Sub ImageGetter_GetDrawable(Source As String) As Object
   Log("ImageGetter1_GetDrawable: "&Source)
   
   If File.Exists(File.DirAssets, Source) Then
      Dim Bitmap1 As Bitmap
      Bitmap1=LoadBitmap(File.DirAssets, Source)
      
      Dim BitmapDrawable1 As BitmapDrawable
      BitmapDrawable1.Initialize(Bitmap1)
      
      '   LabelExtras Object now contains a SetDrawableBounds helper method to enable setting of a Drawable Bounds
      LabelExtras1.SetDrawableBounds(BitmapDrawable1, 0, 0, Bitmap1.Width, Bitmap1.Height)
      
      Return BitmapDrawable1
   Else
      '   the default placeholder image will be used if Null is returned by this Sub
      Return Null
   End If
End Sub

Sub TagHandler_HandleTag(Opening As Boolean, Tag As String, SpannableStringBuilder1 As SpannableStringBuilder)
   Log("TagHandler_HandleTag: "&Opening&", "&Tag)
   '   i've made unknown HTML tags into BB type tags
   If Opening Then
      SpannableStringBuilder1.Append("[").Append(Tag).Append("]")
   Else
      SpannableStringBuilder1.Append("[/").Append(Tag).Append("]")
   End If
End Sub
The HTML String contains 3 IMG tags, and the ImageGetter checks if the image source can be found in the application assets, if it can then it is returned to the library for use as the IMG tag, otherwise Null is returned to the library and the default placeholder image is used for the IMG tag.

The HTML String also contains an unknown 'myTag' tag, the TagHandler gets called as expected when the library tries to parse this unknown tag.
But notice how the TagHandler also gets called with tags such as 'html' 'spans' and 'body'?

If anyone (Informatix!) wants to give the code a test and post any comments then i'll make the update a 'proper' update.

Note to Informatix: LabelExtras Object now contains a SetDrawableBounds helper method to enable setting of a Drawable Bounds. I thought that more logical than adding the method to the Html Object.
I'm afraid i've no time to patch my xml2bb script so have not generated an HTML document for this update.

Martin.
 

Attachments

Last edited:

Informatix

Expert
Licensed User
It's all good for me!

The tag handler is called whenever it detects an unknown tag. "<Html>", "<Body>", and other current HTML tags, are considered as unknown because they are of no use to change the text appearance or its layout. "<spans>" is not a valid HTML tag, so it is viewed also as unknown. The recognized tags are "<B>", "<H2>", "<U>", "<P>", "<FONT...", etc. The main goal of the HTML class in the API is to return a displayable styled text, nothing else.

With your new version, we can render simple HTML pages in our apps without WebView. It's ideal for helps/documentations.
 

warwound

Expert
Licensed User

raaiman

Member
Licensed User
use it on EditText

Hi warwound& Informatix,
thank you for your good jobs.your library works fine on the label.now i want to use it on EditText.but i meet some problems.i want to do something like inserting both text and emotion in editbox.for the 1st emotion,it shows well,when i try to insert the 2nd one ,it shows out,but the 1st one becomes OBJ.
here is my codes:
B4X:
        Dim sp As SpannableStringBuilder
        Sub emotion_Click
        sp.Clear
   sp.Append(EditText1.Text)
   sp.Append(html1.FromHtml2("<img src='1.gif' />","ImageGetter",""))
   EditText1.Text = sp
        End Sub
i wonder if it is because the type of EditText1.Text is string,so it loose some information about the image tag?
i also tried the simliar code on eclipse+java,it works well.
what is the difference after transfering to B4A?
can anyone help me?i've research this for the whole day and no further progress.
thanks.
 

Attachments

warwound

Expert
Licensed User
I think you'll have to upload some code to show more exactly what you're trying to achieve.

Does a user click an icon and then your emotion_Click event is called to insert that icon into the EditText?
Are you allowing the user to insert more than one icon into a single EditText and the first icon works but adding more then one icon causes problems?

In your ImageGetter GetDrawable Sub are you dimming the icon each time it occurs in the EditText or dimming it once even though it occurs more than once in the EditText?
If you're dimming it just once and the icon occurs in the EditText more than once, that may explain why the first icon shows as OBJ.

Post a code example that i can run and i'll take a look.

Martin.
 

raaiman

Member
Licensed User
show emotion example

Hi Martin,
i just made a simple example to show my problem.everytime when clicking the button,it will insert a emotion 1.gif to the EditText Box,but you will see the only the last emotion will show out.
 

Attachments

warwound

Expert
Licensed User
Well i've tried to find a solution but no luck so far.

I can add more than one icon to the EditText if i add all text in one go.
But adding an icon to the text and then getting the text and adding another icon just doesn't work.

I tried this:

B4X:
Sub Button1_Click
   sp.Clear
   
   sp.Append(html1.EscapeHtml(html1.ToHtml(EditText1.Text)))
   
   sp.Append(html1.FromHtml2("<img src='1.gif' />","ImageGetter",""))
   EditText1.Text = sp
   EditText1.SelectionStart = EditText1.Text.Length
   Log("[Button1_Click]text="&EditText1.Text)
End Sub
Hoping it would convert the existing EditText text (which might already contain an icon) into an HTML equivalent.
Then adding the new icon HTML and finally setting the EditText text with the new value of the SpannableStringBuilder.

But i crashes with an exception String cannot be cast to android.text.Spanned, in fact all attempts to use the ToHtml method result in that exception.
I tried a minor library update to fix this but it didn't help.

I also tried this:

B4X:
Sub Button1_Click
   sp.Clear
   
   sp.Append(html1.EscapeHtml(EditText1.Text))
   
   sp.Append(html1.FromHtml2("<img src='1.gif' />","ImageGetter",""))
   EditText1.Text = sp
   EditText1.SelectionStart = EditText1.Text.Length
   Log("[Button1_Click]text="&EditText1.Text)
End Sub
It doesn't work but now the previous icon is display on my tablet as instead of OBJ.

I'd guess that  is a string representation of a memory pointer that points to the drawable icon.

So i think for this to work you need to get the EditText text (which may already contain an icon) as HTML, append more HTML and then set the EditText text again.

Looks like the android EditText getText method returns an object of type Editable.
In a b4a EditText that Editable is i think cast to a String and now it is not valid to be passed as a parameter to the HTML ToHtml method.

So i wrote a temporary new method into the library:

B4X:
   public static CharSequence CharSequenceFromEditTextToHtml(EditTextWrapper EditText1) {
      return android.text.Html.toHtml(EditText1.getObject().getText());
   }
And tried this again:

B4X:
Sub Button1_Click
   sp.Clear
   
   sp.Append(html1.EscapeHtml(html1.CharSequenceFromEditTextToHtml(EditText1)))
   
   sp.Append(html1.FromHtml2("<img src='1.gif' />","ImageGetter",""))
   EditText1.Text = sp
   EditText1.SelectionStart = EditText1.Text.Length
   Log("[Button1_Click]text="&EditText1.Text)
End Sub
It no longer crashes with a class cast exception but fails to work.
The existing icon now displays as .

That leaves me out of ideas i'm afraid.

Martin.
 

raaiman

Member
Licensed User
Hi Martin,
i also think the problem is on the return type of EditText.Text.can you upload your updated library and let me try?
 

warwound

Expert
Licensed User
Here you go, just this new method added to the Html object:

B4X:
   /**
    * Returns an Editable type object:
    * android.text.Html.toHtml(EditText1.getObject().getText());
    */
   public static CharSequence CharSequenceFromEditTextToHtml(EditTextWrapper EditText1) {
      return android.text.Html.toHtml(EditText1.getObject().getText());
   }
Martin.
 

Attachments

raaiman

Member
Licensed User
It works

Hi Martin,
Thank your new library.It is enough to make it work.see the code:
B4X:
Sub Button1_Click
   Dim str As String
   sp.Clear
   str = html1.CharSequenceFromEditTextToHtml(EditText1)
   Dim matcher1 As Matcher
   matcher1 = Regex.Matcher("<(?!br|img)[^>]+>",str)
   Do While matcher1.Find
      str = str.Replace(matcher1.Match,"")
   Loop
   Log("[Button1_Click]str="&str)
   'sp.Append(html1.CharSequenceFromEditTextToHtml(EditText1))
   sp.Append(html1.FromHtml2(str&"<img src='1.gif' />","ImageGetter",""))
   EditText1.Text = sp
   EditText1.SelectionStart = EditText1.Text.Length
   Log("[Button1_Click]text="&EditText1.Text)
End Sub
i just append the string from html1.CharSequenceFromEditTextToHtml(EditText1) to the new input string,and parse the whole string to html again.maybe it seems to be low efficient to pare the whole string every time,but it works at least.
thank you.
 

Inman

Well-Known Member
Licensed User
Great library, Martin. I was using RichString to convert HTML to formatted content and now your library makes it much easier.

But currently I am trying to figure out how to display hyperlinks and detect the clicks on them. Can you please help?

Also while TagHandler_HandleTag event raises for HTML and Body tags, it doesn't raise for <p> <div> etc... Is that how it is supposed to be? No big deal for me as I can parse manually, but I really hope there is a way to display and detect links.
 

warwound

Expert
Licensed User
There is another type of span object, the URLSpan.
I'm not sure if this is of use here.

The current version of LabelExtras has some support for URLSpan already but i disabled the actual URLSpan object pending doing some tests to see if it worked and how it worked.

Can you take a look at the attached project and updated library files - have a play and post back with your results?

As for the TagHandler and raising events, take a look at this post: http://www.basic4ppc.com/forum/additional-libraries-classes-official-updates/25834-labelextras-advanced-click-handling-3.html#post157409.
The TagHandler raises the HandleTag when it encounters an unknown tag - it considers some valid HTML tags as unknown but not all.

Thanks.

Martin.
 
Last edited:

Inman

Well-Known Member
Licensed User
Tested it with many html codes, that too with multiple links and the library parsed all of them flawlessly. Great job.

Now 2 questions.

1. Currently I can get the anchor text of the link. How to get the url itself?

2. How can I adapt this code to an array of labels? That means I will need to know which label has been clicked, in addition to what text. Will it be possible for you to pass the clicked label itself to the ClickableSpan1_Click event?
 

warwound

Expert
Licensed User
Well i have a solution that will i think answer both of your questions.
Updating the ClickableSpan object:

ClickableSpan
Events:
  • Click (SelectionStart As Int, SelectionEnd As Int, Tag As Object)
Methods:
  • Initialize (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.
    Tag is an optional parameter, pass any Object as the Tag and this Object will be returned to the Click event. Pass Null if not required.
  • IsInitialized As Boolean

The URLSpan demo code now looks like this:

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 UrlSpans() As URLSpan
   
   Dim HtmlString As String="This String tests the <u><b>URLSpan</b></u> object.<br><br><a href='http://google.co.uk'>Google<a><br><br><a href='http://www.basic4ppc.com/forum/index.php'>B4A Forum<a>"
   
   SpannableStringBuilder1.Initialize(Html1.FromHtml(HtmlString))
   
   UrlSpans=SpannableStringBuilder1.GetURLSpans(0, SpannableStringBuilder1.Length)
   For Each UrlSpan1 As URLSpan In UrlSpans
      Dim SpanStart As Int=SpannableStringBuilder1.GetSpanStart(UrlSpan1)
      Dim SpanEnd As Int=SpannableStringBuilder1.GetSpanEnd(UrlSpan1)
      Dim SpanFlags As Int=SpannableStringBuilder1.GetSpanFlags(UrlSpan1)
      
      Dim ClickableSpan1 As ClickableSpan
      ClickableSpan1.Initialize("ClickableSpan1", Colors.Blue, True, UrlSpan1.GetURL)
      
      SpannableStringBuilder1.SetSpan(ClickableSpan1, SpanStart, SpanEnd, SpanFlags)
      
      SpannableStringBuilder1.RemoveSpan(UrlSpan1)
      
      Log("URLSpan detected: "&UrlSpan1.GetURL)
   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, Tag As Object)
   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&"'"&CRLF)
   
   Dim Url As String=Tag
   LogText.Append("Tag As String: "&Url)
   
   LogLabel.Text=LogText.ToString
End Sub
When the ClickableSpan is initialized you can pass an optional Tag Object.
The ClickableSpan now raises the click event and passes the Tag Object back to the B4A Sub.

You could set the Tag to a value (or Object) that enables you to identify the Label that contains the ClickableSpan.

Or you could pass Null as the Tag value if it is not required.

The only real problem here is that the update breaks any existing code, the update though is a very useful feature so i shall have a think...
Shall i update the library so that existing code is broken or can i find another way to implement the Tag without breaking existing code?

Martin.
 
Last edited:

Inman

Well-Known Member
Licensed User
I think passing tag is the best way to go. I tested in my app which has 200 labels, each with 3 or 4 links and I could detect the right link on the right label very easily. Thank you so much for this great library.
 

warwound

Expert
Licensed User
LabelExtras updated to version 1.12

This update adds support for the URLSpan object enabling you to parse hyperlinks in your strings.

URLSpan
Methods:
  • GetURL As String
  • IsInitialized As Boolean

The ClickableSpan object has also been updated enabling you to pass a Tag object when initializing it.
The Tag object is then returned to your B4A code when the ClickableSpan raises an event.

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

This update will not break any existing code, if you want to use the new Tag value in your ClickableSpans then you will need to use the Initialize2 method and Click2 event.

Example code using the URLSpan and Tag value:

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 UrlSpans() As URLSpan
   
   Dim HtmlString As String="This String tests the <u><b>URLSpan</b></u> object.<br><br><a href='http://google.co.uk'>Google<a><br><br><a href='http://www.basic4ppc.com/forum/index.php'>B4A Forum<a>"
   
   SpannableStringBuilder1.Initialize(Html1.FromHtml(HtmlString))
   
   UrlSpans=SpannableStringBuilder1.GetURLSpans(0, SpannableStringBuilder1.Length)
   For Each UrlSpan1 As URLSpan In UrlSpans
      Dim SpanStart As Int=SpannableStringBuilder1.GetSpanStart(UrlSpan1)
      Dim SpanEnd As Int=SpannableStringBuilder1.GetSpanEnd(UrlSpan1)
      Dim SpanFlags As Int=SpannableStringBuilder1.GetSpanFlags(UrlSpan1)
      
      Dim ClickableSpan1 As ClickableSpan
      ClickableSpan1.Initialize2("ClickableSpan1", Colors.Blue, True, UrlSpan1.GetURL)
      
      SpannableStringBuilder1.SetSpan(ClickableSpan1, SpanStart, SpanEnd, SpanFlags)
      
      SpannableStringBuilder1.RemoveSpan(UrlSpan1)
      
      Log("URLSpan detected: "&UrlSpan1.GetURL)
   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_Click2(SelectionStart As Int, SelectionEnd As Int, Tag As Object)
   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&"'"&CRLF)
   
   Dim Url As String=Tag
   LogText.Append("Tag As String: "&Url)
   
   LogLabel.Text=LogText.ToString
End Sub
The hyperlinks in the HTMLString variable are retrieved using the SpannableStringBuilder method GetURLSpans.
Each URLSpan is then replaced with a ClickableSpan, the ClickableSpan Tag is set to the URL of the URLSpan that it replaces.

In the (new) ClickableSpan Click2 event this Tag value is returned to B4A - you can then take whatever action you desire knowing the URL of the clicked hyperlink.

Version 1.12 of LabelExtras is attached to the first post in this thread.
And the demo project is attached to this post.

@Inman - be sure to update and use the new methods instead of the previous 'beta' test code.

Martin.
 

Attachments

urikupfer

Member
Licensed User
Hi Uartin
I am using this library a lot , I use the html part to parse html files , is there a way to expend this part to recognize more html tags?
Thanks Uri
 

warwound

Expert
Licensed User
The library uses the SpannableStringBuilder object to get recognised types of Span objects.
Currently it has GetStyleSpans, GetUnderlineSpans and GetURLSpans methods.

So your question is what other types of Span objects can we get from the SpannableStringBuilder?

Take a look at this page CharacterStyle.
I think the 'Known Direct Subclasses' and 'Known Indirect Subclasses' are all possible types of Span objects we can get from the SpannableStringBuilder.

The documentation for these subclasses is rather vague so it's not easy to guess what type of HTML elements they represent.
What types of HTML elements are you hoping to be able to recognise - what types of Span would you like to get from the SpannableStringBuilder?

Martin.
 
Top