WinRT TextBox MaxLength does not count \n\r as two characters - windows-runtime

I have TextBox with MaxLength set to 10 but it is accepting 11 characters when Enter key is pressed. Looks like it is counting \n\r as 1 character instead of two. Is there anyway to make it count \n\r as two char length?

If you really want to allow line breaks in your text box and limit its text length, I see two options:
Either bind MaxLength through a converter so that it changes its value according to how many line breaks (\r\n) the text contains, as shown in this question
Alternatively, you might define your own attached property MaxLength that calculates text length correctly. This might look somewhat like the following (just as an example you'll need to adapt that to take into account special cases etc.):
public class TextBoxExtensions: DependencyObject
{
public static readonly DependencyProperty MaxLengthProperty = DependencyProperty.RegisterAttached(
"MaxLength", typeof (int), typeof (MaxLengthBehavior), new PropertyMetadata(default(int), PropertyChangedCallback));
public static void SetMaxLength(DependencyObject element, int value)
{
element.SetValue(MaxLengthProperty, value);
}
public static int GetMaxLength(DependencyObject element)
{
return (int) element.GetValue(MaxLengthProperty);
}
private static void PropertyChangedCallback(DependencyObject dependencyObject, DependencyPropertyChangedEventArgs args)
{
var tb = dependencyObject as TextBox;
if (tb != null)
{
tb.KeyDown -= TbOnKeyDown;
tb.KeyDown += TbOnKeyDown;
}
}
private static void TbOnKeyDown(object sender, KeyRoutedEventArgs args)
{
var tb = sender as TextBox;
if (tb != null)
{
int max = GetMaxLength(tb);
if (tb.Text.Length >= max)
args.Handled = true;
}
}
}
<TextBox local:TextBoxExtensions.MaxLength="10" />

Related

How to center align text in JTextArea?

I have requirement of Text wrapping + Text Centering + Nimbus Look and Feel + with a background color black.
I tried out with the following 2 components
1. JTextPane
-------supports: Text Wrapping + Text Centering using StyledDocument
-------issue : custom background color doesn't work Nimbus Look and Feel(be it disabled or enabled)
which is bug as show here http://bugs.sun.com/bugdatabase/view_bug.do;jsessionid=ab1938d61a7fd83ca2b54eb8df7?bug_id=6789980
my jdk version is : 1.6.0_33
Fixes tried :
UIManager.getLookAndFeelDefaults().put("TextPane.background", Color.RED);
but this also is unable to override the Nimbus default which is #d6d9df (214,217,223)
Can anyone help me with text Centering in the JTextArea?
Thanks in advance
1. JTextArea
-------supports: Text Wrapping + background coloring with Nimbus LnF (only when its enabled)
-------issue : Text Centering is an issue in here
To center text you should use a JTextPane not a JTextArea. Centering text is a big issue.
It is better to solve the background problem with the JTextPane.
When using the Nimbus LAF it looks like you need to provide a custom Painter to just paint the background as a solid color. Check out this answer by #mKorbel. You would need to change the property tag. Also in the FillPainter I changed:
g.setColor(color);
g.setColor(object.getBackground());
Thanks .... Working ,,,,
This is what I used ...
UIManager.getLookAndFeelDefaults().put("TextPane[Enabled].backgroundPainter", new FillPainter());`
public class FillPainter implements Painter<JComponent> {
#Override
public void paint(Graphics2D g, JComponent object, int width,int height) {
g.setColor(object.getBackground());
}
}
I use this to center text in JTextArea
public static void centerText (JTextArea ta)
{
BufferedImage fake1 = new BufferedImage(1, 1, BufferedImage.TYPE_INT_ARGB);
Graphics2D fake2 = fake1.createGraphics();
FontMetrics fm = fake2.getFontMetrics(ta.getFont());
int lines = ta.getLineCount();
ArrayList<String> list = new ArrayList<>();
try
{
for (int i = 0; i < lines; i++)
{
int start = ta.getLineStartOffset(i);
int end = ta.getLineEndOffset(i);
String line = ta.getText(start, end - start).replace("\n","");
list.add (line.trim());
}
}
catch (BadLocationException e)
{
System.out.println(e);
}
alignLines (list, fm, ta);
}
private static void alignLines (ArrayList<String> list, FontMetrics fm, JTextArea ta)
{
String leading = " ";
int longest = -1;
for (String s : list)
{
if (fm.stringWidth(s) > longest)
longest = fm.stringWidth(s);
}
for (int n=0; n<list.size(); n++)
{
String s = list.get(n);
if (fm.stringWidth(s) >= longest)
continue;
while (fm.stringWidth(s) < longest)
s = ' '+s+' ';
list.set(n, s);
}
StringBuilder sb = new StringBuilder();
for (String s : list)
{
sb.append(leading).append(s).append('\n');
}
ta.setText (sb.toString());
}
call CenterText with your filled JTextArea as argument

JTable row color change based on a column value- on pop up click

My jTable is loaded with data and this is where I call my Pop up functionality on jTable.
jTable.addMouseListener(new TablePopupListener(jTable));
displayTable();
So basically, if I right-click a row, a popup(credit check) comes up and if I click it is setting a value to the last cell in that row. Now, based on this column cell value I have to define the color of a row. Let's say if the cell value fails then turn the row to red else to green. I have tried customCellRenderer and defined my condition but there is no change in row color. The custom cell renderer worked great for a button functionality that I had to write, though. The below code uses prepare cellRenderer which I felt is easy but I don't see any change in row color.
I am missing some connection, plz provide me help.
Thanks in advance.
class TablePopupListener extends MouseAdapter implements ActionListener {
JPopupMenu popup;
JTable table;
int[] selRows;
TableModel model;
ArrayList rowValueList = new ArrayList();
JMenuItem creditCheck = new JMenuItem("Credit Check");
public TablePopupListener(JTable jTable) {
this.table = jTable;
model = table.getModel();
popup = new JPopupMenu();
JMenuItem creditCheck = new JMenuItem("Credit Check");
creditCheck.addActionListener(this);
popup.add(creditCheck);
}
public void mousePressed(MouseEvent me) {
firePopup(me);
}
public void mouseReleased(MouseEvent me) {
firePopup(me);
}
public void firePopup(MouseEvent me) {
/*
* The popup menu will be shown only if there is a row selection in the
* table
*/
// popup.show(table, me.getX(), me.getY());
if (me.isPopupTrigger() && table.getModel().getRowCount() != 0
&& table.getSelectedRow() != -1) {
// popup.show(table,me.getX(),me.getY());
if (me.isPopupTrigger()) {
JTable source = (JTable) me.getSource();
int row = source.rowAtPoint(me.getPoint());
int column = source.columnAtPoint(me.getPoint());
if (!source.isRowSelected(row))
source.changeSelection(row, column, false, false);
popup.show(table, me.getX(), me.getY());
}
}
}
public void actionPerformed(ActionEvent ae) {
if (ae.getActionCommand().equals("Credit Check")) {
System.out.println("you have clicked creditCheckpopup");
selRows = table.getSelectedRows();
if (selRows.length > 0) {
for (int i = 0; i < selRows.length; i++) {
// get Table data
for (int j = 1; j < (table.getColumnCount()) - 1; j++) {
rowValueList.add(model.getValueAt(selRows[i], j));
}
System.out.println("Selection : " + rowValueList);
}
} else {
System.out.println("you have clicked something idiot");
}
int result = new COpxDeal(rowValueList).CheckCredit();
if (result == 1)
rowValueList.add("pass");
else
rowValueList.add("fail");
String aValue = (String) rowValueList.get(14);
for (int i = 0; i < selRows.length; i++) {
model.setValueAt(aValue, selRows[i], 15);
}
// inserted comment (Kleopatra): where are we? that's outside of the TablePopup?
// okay, nothing like copying the code into an IDE and let that do the formatting, silly me ;-)
// this is indeed _inside_ the popup, that is the table is recreated
table = new JTable(model) {
public Component prepareRenderer(TableCellRenderer renderer,
int row, int column) {
Component c = super.prepareRenderer(renderer, row, column);
JComponent jc = (JComponent) c;
// if (!isRowSelected(row)){
// c.setBackground(getBackground());
// System.out.println(isRowSelected(row));
// }
int modelRow = convertRowIndexToModel(row);
String strTestValue = "fail";
String strTblValue = (String) getModel().getValueAt(
modelRow, 15);
System.out.println("result :" + strTblValue);
if (strTblValue == null || strTblValue.equals(""))
System.out.println("there is nothing in strTblValue");
else if (strTestValue.equals(strTblValue)) {
jc.setBackground(Color.RED);
} else {
jc.setBackground(Color.green);
}
return c;
}
};
}
}
}
after some formatting (believe me, it's important for code to be readable ;-) seems like you instantiate a new table inside your popupMenu and only that table has the custom renderer. Which you can do, but doesn't have any effect on the your real table.
Move the prepareRenderer into your real table (the one you pass into the popup as parameter) and you should see the coloring. Beware: due to a bug in DefaultTableCellRenderer, you have to set the color always, that is
if (nothingToDo) {
setBackground(normal)
} else if ... {
setBackground(one)
} else {
setBackground(other)
}
Edit: trying to explain the changes in code structure, pseudo-code snippets
Current state, that's what you are doing:
JTable table = new JTable();
table.addMouseListener(new TablePopupListener(table));
// keep listener-local reference to table
JTable table = table;
....
// in the listener guts, the reference is replaced
table = new JTable() {
#Override
Component prepareRenderer(...
}
Change to, that's what you should do:
JTable table = new JTable() {
#Override
Component prepareRenderer(...
};
table.addMouseListener(new TablePopupListener(table));
// keep listener-local reference to table
JTable table = table;
// don't replace ever, it's for reading only
edit 2:
- changed the pseudo-code to actually register the listener)
- the code indented below the addMouseListener is mean as an outline of the code inside the TablePopupListener

Get all static variables in a class

I have this ObjectType class which is a class to help me do something like this:
object.type = ObjectType.TWO
//ObjectType.as
package
{
public class ObjectType
{
public static var ONE:String = "one";
public static var TWO:String = "two";
public static var THREE:String = "three";
public function ObjectType()
{
}
}
}
Let's suppose I'm creating a new class and I need a property named type. In that property set function I want to make sure that it's value is one of the ObjectType variables. How can I achieve this?
public function set type(value:String):void
{
for (var o:Object in ObjectType) {
if (value == o)
this._type = value;
} else {
//error
}
}
}
Not performance aware but without modifying anything you can use describeType function to check the static field and get the value back:
function valueInClass(clazz:Class, value:*):Boolean {
return describeType(clazz).variable.(clazz[#name.toString()] == value).length() != 0
}
public function set type(value:String):void
{
if (valueInClass(ObjectType, value)) {
this._type = value;
} else {
//error
}
}
I suppose the second code example you presented doesn't work...
I think it is because you're using the for in loop a little bit wrong.
for (var blah:String in somewhere){
// blah represents a KEY of the somewhere object
// to get the value of this key, use:
var theValue = somewhere[blah];
}
It's the for each loop that loops through the values. But for now I'll use the for in.
Also, it's not in ObjectType, but rather in the class' prototype, that is in ObjectType.prototype.
So, to fix this:
for (var o:* in ObjectType.prototype) {
if (value == ObjectType.prototype[o])
this._type = value;
} else {
//error
}
}
You can solve this using reflection.
A similar question was asked just a few days ago, you should be able to use the same solution, found here.
It should be noted that while the the accepted answer is right, it's also really slow. Not something that you want to do a lot. There are three simpler solutions.
One: Check the value itself:
public function set type(value:String):void
{
if( value != ObjectType.ONE && value != ObjectType.TWO && value != ObjectType.THREE )
return;
}
Obviously, the more constants you have the check the harder this becomes.
Two: Use ints as your constants
Change your ObjectType class to use ints:
public class ObjectType
{
public static var NONE:int = 0;
public static var ONE:int = 1;
public static var TWO:int = 2;
public static var THREE:int = 3;
public static var TOTAL:int = 4;
}
Notice the NONE and TOTAL in there? This makes it easy to check if your value is in the right range:
public function set type(value:int):void
{
if( value <= ObjectType.NONE || value >= ObjectType.TOTAL )
return;
}
You can add more values as needed and you just need to update TOTAL and it'll still work. This needs each value to be in order though.
Three: Use Enums
While Flash has no in-build class for enums, there's a lot of solutions available. Check our the Enum class from Scott Bilas: http://scottbilas.com/blog/ultimate-as3-fake-enums/
Using this as your base class your ObjectType class becomes:
public final class ObjectType extends Enum
{
{ initEnum( ObjectType ); } // static ctor
public static const ONE:ObjectType = new ObjectType;
public static const TWO:ObjectType = new ObjectType;
public static const THREE:ObjectType = new ObjectType;
}
And your check now becomes:
public function set type(value:ObjectType):void
{
...
}
Here, your setter now becomes type safe and will throw errors if anything other than an ObjectType is used.
It turns out that if using an ENUM type of check you should check for the constants property, not variables as showin in the example here:
ActionScript - Determine If Value is Class Constant

Add text background color to JTable column

The motivation is that I want to see trailing spaces in table cells. For instance, if the cell contains "Foo Bar ", I would like to see the space character after "Bar". Is there a way to change the text background color so that I can see all the characters easily in a JTable cell? I'm looking to do this for a whole column.
try modifying the component in the cell defaulttablecellrenderer for column i.
I think arg4 and arg5 are row and column of the table so you can control the renderer for the cells here too.
JTable table = new JTable();
table.getColumn(i).setCellRenderer(new DefaultTableCellRenderer() {
#Override
public Component getTableCellRendererComponent(JTable arg0, Object arg1,
boolean arg2, boolean arg3, int arg4, int arg5) {
Component component = super.getTableCellRendererComponent(arg0, arg1, arg2, arg3, arg4, arg5);
// modify this component here
// component.setForeground(Color.black.black);
// component.setBackground(Color.black.black);
return component;
}
});
final JTable table = new JTable(tableModel);
table.setPreferredScrollableViewportSize(TABLE_SIZE);
table.setFillsViewportHeight(true);
table.setAutoResizeMode(JTable.AUTO_RESIZE_OFF);
table.getTableHeader().addMouseListener(new MouseAdapter() {
#Override
public void mouseClicked(MouseEvent mouseEvent) {
int selectedHeader = table.convertColumnIndexToModel(table
.columnAtPoint(mouseEvent.getPoint()));
table.getColumn(table.getColumnName(selectedHeader))
.setCellRenderer(new DefaultTableCellRenderer() {
public void setBackground(Color c) {
super.setBackground(Color.blue);
}
});
};
});

Android: How to use the Html.TagHandler?

I am trying to build an android application for a message board. To display formatted html for the post contents I have chosen the TextView and the Html.fromHtml() method. That, unfortunately, covers only a few html tags. The unknown tags are handled by a class that implements TagHandler and has to be generated by myself.
Now, I googled a lot and can't find an example of how this class should work. Let's consider I have an u tag for underlining some text (I know that this is deprecated, but whatever). How does my TagHandler look like?
It is called in the following way:
public void handleTag(boolean opening, String tag, Editable output, XMLReader xmlReader) {
The first two arguments are fine. I guess I have to modify output using output.append(). But how do I attach something underlined there?
So, i finally figured it out by myself.
public class MyHtmlTagHandler implements TagHandler {
public void handleTag(boolean opening, String tag, Editable output, XMLReader xmlReader) {
if(tag.equalsIgnoreCase("strike") || tag.equals("s")) {
processStrike(opening, output);
}
}
private void processStrike(boolean opening, Editable output) {
int len = output.length();
if(opening) {
output.setSpan(new StrikethroughSpan(), len, len, Spannable.SPAN_MARK_MARK);
} else {
Object obj = getLast(output, StrikethroughSpan.class);
int where = output.getSpanStart(obj);
output.removeSpan(obj);
if (where != len) {
output.setSpan(new StrikethroughSpan(), where, len, Spannable.SPAN_EXCLUSIVE_EXCLUSIVE);
}
}
}
private Object getLast(Editable text, Class kind) {
Object[] objs = text.getSpans(0, text.length(), kind);
if (objs.length == 0) {
return null;
} else {
for(int i = objs.length;i>0;i--) {
if(text.getSpanFlags(objs[i-1]) == Spannable.SPAN_MARK_MARK) {
return objs[i-1];
}
}
return null;
}
}
}
And for your TextView you can call this like:
myTextView.setText (Html.fromHtml(text.toString(), null, new MyHtmlTagHandler()));
if anybody needs it.
Cheers
This solution is found in the Android sdk
In android.text.html. Lines 596 - 626. Copy/pasted
private static <T> Object getLast(Spanned text, Class<T> kind) {
/*
* This knows that the last returned object from getSpans()
* will be the most recently added.
*/
Object[] objs = text.getSpans(0, text.length(), kind);
if (objs.length == 0) {
return null;
} else {
return objs[objs.length - 1];
}
}
private static void start(SpannableStringBuilder text, Object mark) {
int len = text.length();
text.setSpan(mark, len, len, Spannable.SPAN_MARK_MARK);
}
private static <T> void end(SpannableStringBuilder text, Class<T> kind,
Object repl) {
int len = text.length();
Object obj = getLast(text, kind);
int where = text.getSpanStart(obj);
text.removeSpan(obj);
if (where != len) {
text.setSpan(repl, where, len, Spannable.SPAN_EXCLUSIVE_EXCLUSIVE);
}
}
To use, override TagHandler like so:
public void handleTag(boolean opening, String tag, Editable output, XMLReader xmlReader) {
if(tag.equalsIgnoreCase("strike") || tag.equals("s")) {
if(opening){
start((SpannableStringBuilder) output, new Strike();
} else {
end((SpannableStringBuilder) output, Strike.class, new StrikethroughSpan());
}
}
}
/*
* Notice this class. It doesn't really do anything when it spans over the text.
* The reason is we just need to distinguish what needs to be spanned, then on our closing
* tag, we will apply the spannable. For each of your different spannables you implement, just
* create a class here.
*/
private static class Strike{}
I took janoliver's answer and came up with my version that attempts to support more options
String text = ""; // HTML text to convert
// Preprocessing phase to set up for HTML.fromHtml(...)
text = text.replaceAll("<span style=\"(?:color: (#[a-fA-F\\d]{6})?; )?(?:font-family: (.*?); )?(?:font-size: (.*?);)? ?\">(.*?)</span>",
"<font color=\"$1\" face=\"$2\" size=\"$3\">$4</font>");
text = text.replaceAll("(?<=<font color=\"#[a-fA-F0-9]{6}\" )face=\"'(.*?)', .*?\"", "face=\"$1\"");
text = text.replaceAll("(?<=<font color=\"#[a-fA-F0-9]{6}\" )(face=\".*?\" )size=\"xx-small\"", "$1size=\"1\"");
text = text.replaceAll("(?<=<font color=\"#[a-fA-F0-9]{6}\" )(face=\".*?\" )size=\"x-small\"", "$1size=\"2\"");
text = text.replaceAll("(?<=<font color=\"#[a-fA-F0-9]{6}\" )(face=\".*?\" )size=\"small\"", "$1size=\"3\"");
text = text.replaceAll("(?<=<font color=\"#[a-fA-F0-9]{6}\" )(face=\".*?\" )size=\"medium\"", "$1size=\"4\"");
text = text.replaceAll("(?<=<font color=\"#[a-fA-F0-9]{6}\" )(face=\".*?\" )size=\"large\"", "$1size=\"5\"");
text = text.replaceAll("(?<=<font color=\"#[a-fA-F0-9]{6}\" )(face=\".*?\" )size=\"x-large\"", "$1size=\"6\"");
text = text.replaceAll("(?<=<font color=\"#[a-fA-F0-9]{6}\" )(face=\".*?\" )size=\"xx-large\"", "$1size=\"7\"");
text = text.replaceAll("<strong>(.*?)</strong>", "<_em>$1</_em>"); // we use strong for bold-face
text = text.replaceAll("<em>(.*?)</em>", "<strong>$1</strong>"); // and em for italics
text = text.replaceAll("<_em>(.*?)</_em>", "<em>$1</em>"); // but Android uses em for bold-face
text = text.replaceAll("<span style=\"background-color: #([a-fA-F0-9]{6}).*?>(.*?)</span>", "<_$1>$2</_$1>");
text_view.setText(Html.fromHtml(text, null, new Html.TagHandler() {
private List<Object> _format_stack = new LinkedList<Object>();
#Override
public void handleTag(boolean open_tag, String tag, Editable output, XMLReader _) {
if (tag.startsWith("ul"))
processBullet(open_tag, output);
else if (tag.matches(".[a-fA-F0-9]{6}"))
processBackgroundColor(open_tag, output, tag.substring(1));
}
private void processBullet(boolean open_tag, Editable output) {
final int length = output.length();
if (open_tag) {
final Object format = new BulletSpan(BulletSpan.STANDARD_GAP_WIDTH);
_format_stack.add(format);
output.setSpan(format, length, length, Spanned.SPAN_MARK_MARK);
} else {
applySpan(output, length, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
}
}
private void processBackgroundColor(boolean open_tag, Editable output, String color) {
final int length = output.length();
if (open_tag) {
final Object format = new BackgroundColorSpan(Color.parseColor('#' + color));
_format_stack.add(format);
output.setSpan(format, length, length, Spanned.SPAN_MARK_MARK);
} else {
applySpan(output, length, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
}
}
private Object getLast(Editable text, Class kind) {
#SuppressWarnings("unchecked")
final Object[] spans = text.getSpans(0, text.length(), kind);
if (spans.length != 0)
for (int i = spans.length; i > 0; i--)
if (text.getSpanFlags(spans[i-1]) == Spannable.SPAN_MARK_MARK)
return spans[i-1];
return null;
}
private void applySpan(Editable output, int length, int flags) {
if (_format_stack.isEmpty()) return;
final Object format = _format_stack.remove(0);
final Object span = getLast(output, format.getClass());
final int where = output.getSpanStart(span);
output.removeSpan(span);
if (where != length)
output.setSpan(format, where, length, flags);
}
}));
This does seem to get the bullets, foreground color, and background color. It might work for the font-face but you might need to supply the fonts as it doesn't seem that Android supports fonts other than Droid/Roboto.
This is more of a proof-of-concept, you might probably want to convert the regex into String processing, since regex doesn't support combining the preprocessing in any way, meaning this takes a lot of passes over the String. This also doesn't seem to get the font size to change, I've tried defining it like "16sp", "medium", or "4" without seeing changes. If anyone has gotten sizes to work, mind sharing?
I currently would like to be able to add numbered/ordered list support to this, i.e.
item
item
item
NOTE:
To people starting with any of this, it seems that the "tag" that is given to handleTag(...) is just the name of the tag (like "span"), and doesn't contain any of the attributes assigned in the tag (like if you have "), you can see my loophole around this for the background color.
We have been developing internally this library https://github.com/square1-io/rich-text-android for a while now and we use it in a number of content intensive news apps.
The library can parse most common html tags including video and img with remote download of images.
A custom view, RichTextView can then be used as a replacement of TextView to display the parsed content.
We have released it publicly just recently so the doc is still incomplete however the example provided should be easy to follow to see if it fit your needs.
Although I can see it it in Html.java API that style and text-align should be usable with the tags <p> , <div> etc. I am failing to get it to work with <p align="center"> or <p style="text-align: center"> and many other variants. Not being able to do this center alignment of text, and other styles like font size, multiple font faces from my ttf files, background colour, I have made my own htmlTextView based on TextView but with my own tagHandler class. Given one or two minor irritations, most of the tags are fine but my custom alignment tags, left, centre, right work only in special conditions (that I don't understand), otherwise. they don't work or crash the app! This is my alignment tag handle. It has the same structure as all the other custom tag handlers but really behaves weirdly! The basic form of my tag handlers are the same and are not conceived by me! I found the taghandler template after many hours searching on the web. I am grateful to whoever it was that posted it but my memory and organisational ability are such that I can't really remember who or where, so if you recognise this code as yours please let me know. The only link (which is here) I have is in my code: stackoverflow: Android: How to use the Html.TagHandler?
private void ProcessAlignment(Layout.Alignment align, boolean opening, Editable output) {
int len = output.length();
if (opening) {
output.setSpan(new AlignmentSpan.Standard(align), len, len, Spannable.SPAN_MARK_MARK);
} else {
Object obj = getLast(output, AlignmentSpan.Standard.class);
int where = output.getSpanStart(obj);
output.removeSpan(obj);
if (where != len) {
output.setSpan(new AlignmentSpan.Standard(align), where, len, Spannable.SPAN_EXCLUSIVE_EXCLUSIVE);
}
}
}
I think the problem is that the end tag is not getting connected the correct start tag.
private Object getLast(Editable text, Class kind) {
Object[] objs = text.getSpans(0, text.length(), kind);
if (objs.length == 0) {
return null;
} else {
for (int i = objs.length - 1; i >= 0; --i) {
if (text.getSpanFlags(objs[i]) == Spannable.SPAN_MARK_MARK) {
return objs[i];
}
}
return null;
}
}
This is the total class and something is not right. The largest component is my understanding! Perhaps someone can help me understand better...
public class htmlTextView extends AppCompatTextView {
static Typeface mLogo;
static Typeface mGAMZ;
static Typeface mChalk;
static Typeface mSouvenir;
int GS_PAINTFLAGS = FILTER_BITMAP_FLAG | ANTI_ALIAS_FLAG | SUBPIXEL_TEXT_FLAG | HINTING_ON;
public htmlTextView(Context context) {
super(context);
initialise();
}
public htmlTextView(Context context, #Nullable AttributeSet attrs) {
super(context, attrs);
initialise();
}
public htmlTextView(Context context, #Nullable AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
initialise();
}
private void initialise() {
mLogo = Typeface.createFromAsset(theAssetManager, "fonts/logo.ttf");
mGAMZ = Typeface.createFromAsset(theAssetManager, "fonts/GAMZ One.ttf");
mChalk = Typeface.createFromAsset(theAssetManager, "fonts/swapfix.ttf");
mSouvenir = Typeface.createFromAsset(theAssetManager, "fonts/Souvenir Regular.ttf");
setPaintFlags(GS_PAINTFLAGS);
}
public void setDefaultTypefaceSouvenir() {
setTypeface(mSouvenir);
}
public void setDefaultTypefaceGAMZ() {
setTypeface(mGAMZ);
}
public void setDefaultTypefaceChalk() {
setTypeface(mChalk);
}
/*public myTextView(Context context, #Nullable AttributeSet attrs, int defStyleAttr, int defStyleRes) {
super(context, attrs, defStyleAttr, defStyleRes);
}*/
public void setHTML(String htmltext) {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) { // Nougat API 24
setText(Html.fromHtml(htmltext, Html.FROM_HTML_MODE_LEGACY,
null, new TypefaceTagHandler()));
} else {
setText(Html.fromHtml(htmltext, null, new TypefaceTagHandler()));
}
}
#Override
protected void dispatchDraw(Canvas canvas) {
super.dispatchDraw(canvas);
}
#Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
}
#Override
public Bitmap getDrawingCache(boolean autoScale) {
return super.getDrawingCache(autoScale);
}
#Override
public void draw(Canvas canvas) {
super.draw(canvas);
}
// http://stackoverflow.com/questions/4044509/android-how-to-use-the-html-taghandler
private static class TypefaceTagHandler implements Html.TagHandler {
private void ProcessAlignment(Layout.Alignment align, boolean opening, Editable output) {
int len = output.length();
if (opening) {
output.setSpan(new AlignmentSpan.Standard(align), len, len, Spannable.SPAN_MARK_MARK);
} else {
Object obj = getLast(output, AlignmentSpan.Standard.class);
int where = output.getSpanStart(obj);
output.removeSpan(obj);
if (where != len) {
output.setSpan(new AlignmentSpan.Standard(align), where, len, Spannable.SPAN_EXCLUSIVE_EXCLUSIVE);
}
}
}
private void ProcessTypefaceTag(Typeface tf, boolean opening, Editable output) {
int len = output.length();
if (opening) {
output.setSpan(new CustomTypefaceSpan("", tf), len, len,
Spannable.SPAN_MARK_MARK);
} else {
Object obj = getLast(output, CustomTypefaceSpan.class);
int where = output.getSpanStart(obj);
output.removeSpan(obj);
if (where != len) {
output.setSpan(new CustomTypefaceSpan("", tf), where, len,
Spannable.SPAN_EXCLUSIVE_EXCLUSIVE);
}
}
}
private void ProcessScaleTag(float scalefactor, boolean opening, Editable output) {
int len = output.length();
if (opening) {
output.setSpan(new RelativeSizeSpan(scalefactor), len, len,
Spannable.SPAN_MARK_MARK);
} else {
Object obj = getLast(output, RelativeSizeSpan.class);
int where = output.getSpanStart(obj);
output.removeSpan(obj);
if (where != len) {
output.setSpan(new RelativeSizeSpan(scalefactor), where, len,
Spannable.SPAN_EXCLUSIVE_EXCLUSIVE);
}
}
}
private void ProcessBox(int colour, boolean opening, Editable output) {
int len = output.length();
if (opening) {
output.setSpan(new BackgroundColorSpan(colour), len, len,
Spannable.SPAN_MARK_MARK);
} else {
Object obj = getLast(output, BackgroundColorSpan.class);
int where = output.getSpanStart(obj);
output.removeSpan(obj);
if (where != len) {
output.setSpan(new BackgroundColorSpan(colour), where, len,
Spannable.SPAN_EXCLUSIVE_EXCLUSIVE);
}
}
}
private void ProcessTextColour(int colour, boolean opening, Editable output) {
int len = output.length();
if (opening) {
output.setSpan(new ForegroundColorSpan(colour), len, len,
Spannable.SPAN_MARK_MARK);
} else {
Object obj = getLast(output, ForegroundColorSpan.class);
int where = output.getSpanStart(obj);
output.removeSpan(obj);
if (where != len) {
output.setSpan(new ForegroundColorSpan(colour), where, len,
Spannable.SPAN_EXCLUSIVE_EXCLUSIVE);
}
}
}
final HashMap<String, String> attributes = new HashMap<>();
#Override
public void handleTag(boolean opening, String tag, Editable output, XMLReader xmlReader) {
String Attr = "";
//if (!opening) attributes.clear();
processAttributes(xmlReader);
if ("txt".equalsIgnoreCase(tag)) {
Attr = attributes.get("clr");
System.out.println("clr Attr: " + Attr + ", opening: " + opening);
if (Attr == null || Attr.isEmpty()
|| "black".equalsIgnoreCase(Attr)
|| Attr.charAt(0) == 'k') {
System.out.println("did black, opening: " + opening);
ProcessTextColour(parseColor("#000000"), opening, output);
} else {
if (Attr.equalsIgnoreCase("g")) {
ProcessTextColour(parseColor("#b2b3b3"), opening, output);
} else {
System.out.println("did colour, opening: " + opening);
ProcessTextColour(parseColor(Attr), opening, output);
}
}
return;
}
if ("box".equalsIgnoreCase(tag)) {
ProcessBox(parseColor("#d7d6d5"), opening, output);
return;
}
if ("scl".equalsIgnoreCase(tag)) {
Attr = attributes.get("fac");
System.out.println("scl Attr: " + Attr);
if (Attr != null && !Attr.isEmpty()) {
ProcessScaleTag(parseFloat(Attr), opening, output);
}
return;
}
if ("left".equalsIgnoreCase(tag)) {
ProcessAlignment(Layout.Alignment.ALIGN_NORMAL, opening, output);
return;
}
if ("centre".equalsIgnoreCase(tag)) {
ProcessAlignment(Layout.Alignment.ALIGN_CENTER, opening, output);
return;
}
if ("right".equalsIgnoreCase(tag)) {
ProcessAlignment(Layout.Alignment.ALIGN_OPPOSITE, opening, output);
return;
}
if ("logo".equalsIgnoreCase(tag)) {
ProcessTypefaceTag(mLogo, opening, output);
return;
}
if ("gamz".equalsIgnoreCase(tag)) {
ProcessTypefaceTag(mGAMZ, opening, output);
return;
}
if ("chalk".equalsIgnoreCase(tag)) {
System.out.println("chalk " + (opening ? "opening" : "closing"));
ProcessTypefaceTag(mChalk, opening, output);
return;
}
}
private Object getLast(Editable text, Class kind) {
Object[] objs = text.getSpans(0, text.length(), kind);
if (objs.length == 0) {
return null;
} else {
for (int i = objs.length - 1; i >= 0; --i) {
if (text.getSpanFlags(objs[i]) == Spannable.SPAN_MARK_MARK) {
return objs[i];
}
}
return null;
}
}
private void processAttributes(final XMLReader xmlReader) {
try {
Field elementField = xmlReader.getClass().getDeclaredField("theNewElement");
elementField.setAccessible(true);
Object element = elementField.get(xmlReader);
Field attsField = element.getClass().getDeclaredField("theAtts");
attsField.setAccessible(true);
Object atts = attsField.get(element);
Field dataField = atts.getClass().getDeclaredField("data");
dataField.setAccessible(true);
String[] data = (String[])dataField.get(atts);
Field lengthField = atts.getClass().getDeclaredField("length");
lengthField.setAccessible(true);
int len = (Integer)lengthField.get(atts);
/**
* MSH: Look for supported attributes and add to hash map.
* This is as tight as things can get :)
* The data index is "just" where the keys and values are stored.
*/
for(int i = 0; i < len; i++)
attributes.put(data[i * 5 + 1], data[i * 5 + 4]);
}
catch (Exception e) {
Log.d(TAG, "Exception: " + e);
}
}
}
private static class CustomTypefaceSpan extends TypefaceSpan {
private final Typeface newType;
public CustomTypefaceSpan(String family, Typeface type) {
super(family);
newType = type;
}
#Override
public void updateDrawState(TextPaint ds) {
applyCustomTypeFace(ds, newType);
}
#Override
public void updateMeasureState(TextPaint paint) {
applyCustomTypeFace(paint, newType);
}
private void applyCustomTypeFace(Paint paint, Typeface tf) {
int oldStyle;
Typeface old = paint.getTypeface();
if (old == null) {
oldStyle = 0;
} else {
oldStyle = old.getStyle();
}
int fake = oldStyle & ~tf.getStyle();
if ((fake & Typeface.BOLD) != 0) {
paint.setFakeBoldText(true);
}
if ((fake & Typeface.ITALIC) != 0) {
paint.setTextSkewX(-0.25f);
}
paint.setTypeface(tf);
}
}
}
The htmlTextView is created from the activity with:
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
theAssetManager = getAssets();
htmlTextView tv = new htmlTextView(this);
tv.setDefaultTypefaceSouvenir();
tv.setTextColor(BLACK);
tv.setBackgroundColor(0xfff0f0f0);
tv.setPadding(4, 4, 4, 4);
tv.setTextSize(30);
tv.setMovementMethod(new ScrollingMovementMethod());
tv.setHTML(getString(R.string.htmljumblies));
//tv.setHTML(getString(R.string.htmltest));
RelativeLayout rl = (RelativeLayout) findViewById(R.id.rl);
rl.addView(tv);
}
and htmljumblies is defined in strings.xml as below. This particular version will crash the app but if the first <centre>, </centre> tags are removed from lines 7 and 9, The Jumblies will appear centralised? Confusing and frustrating! Keep them and remove the <centre>, </centre> tags enfolding The Jumblies and nothing happens. The line in the header is not centrally aligned!
<string name="htmljumblies">
<![CDATA[&DoubleLongRightArrow;<logo><scl fac="1.1"><font color="#e5053a">GAMZ</font></scl></logo>
<chalk><scl fac="1.8"> SWAP </scl></chalk>
<scl fac="1.00">Set <b>1</b>, Game <b>1</b></scl>
<br>
<centre>
<gamz><font color="#e5053a"><scl fac="1.50">a</scl></font><scl fac="0.90">(9)</scl></gamz>, <gamz><font color="#00a3dd"><scl fac="1.50">e</scl></font><scl fac="0.90">(8)</scl></gamz>, <gamz><font color="#fba311"><scl fac="1.50">i</scl></font><scl fac="0.90">(8)</scl></gamz>, <gamz><font color="#bc5e1e"><scl fac="1.50">o</scl></font><scl fac="0.90">(8)</scl></gamz>, <gamz><font color="#bf30b5"><scl fac="1.50">u</scl></font><scl fac="0.90">(9)</scl></gamz>
</centre>
<br>
This is an example of my custom <b>htmlTextView</b> drawn from HTML format
text with custom tags to use custom fonts, colouring typeface sizing and highlight boxes.
The default font is <b><i>Souvenir</i></b>, but 3 other fonts are used:<br>
The <font color="#e5053a"><b><logo><scl fac="1.1">GAMZ</scl></logo></b></font>
<font color="#000080"><gamz><scl fac="0.8"><box>letter</box>
<box>fonts</box><sc></gamz></font>
and <chalk><scl fac="1.8">swapfix</scl></chalk>, essentially
<chalk><scl fac="0.9">Staccato 555</scl></chalk>,
as used in the words <chalk><scl fac="1.2">SWAP</scl></chalk> and
<chalk><scl fac="1.2">FIX</scl></chalk>
on the <font color="#e5053a"><b><logo><scl fac="1.1">GAMZ</scl></logo></b></font>
boxes.
<br>
<centre>
<scl fac="2"><box><b> <u>The Jumblies</u> </b></box></scl><br>
<font color="#0000ff">
They went to sea in a Sieve, they did,<br>
In a Sieve they went to sea:<br>
In spite of all their friends could say,<br>
On a winter\'s morn, on a stormy day,<br>
In a Sieve they went to sea!<br>
And when the Sieve turned round and round,<br>
And every one cried, \'You\'ll all be drowned!\'<br>
They called aloud, \'Our Sieve ain\'t big,<br>
But we don\'t care a button! we don\'t care a fig!<br>
In a Sieve we\'ll go to sea!\'<br>
Far and few, far and few,<br>
Are the lands where the Jumblies live;<br>
Their heads are green, and their hands are blue,<br>
And they went to sea in a Sieve.<br>
<br>
They sailed away in a Sieve, they did,<br>
In a Sieve they sailed so fast,<br>
With only a beautiful pea-green veil<br>
Tied with a riband by way of a sail,<br>
To a small tobacco-pipe mast;<br>
And every one said, who saw them go,<br>
\'O won\'t they be soon upset, you know!<br>
For the sky is dark, and the voyage is long,<br>
And happen what may, it\'s extremely wrong<br>
In a Sieve to sail so fast!\'<br>
Far and few, far and few,<br>
Are the lands where the Jumblies live;<br>
Their heads are green, and their hands are blue,<br>
And they went to sea in a Sieve.<br>
<br>
The water it soon came in, it did,<br>
The water it soon came in;<br>
So to keep them dry, they wrapped their feet<br>
In a pinky paper all folded neat,<br>
And they fastened it down with a pin.<br>
And they passed the night in a crockery-jar,<br>
And each of them said, \'How wise we are!<br>
Though the sky be dark, and the voyage be long,<br>
Yet we never can think we were rash or wrong,<br>
While round in our Sieve we spin!\'<br>
Far and few, far and few,<br>
Are the lands where the Jumblies live;<br>
Their heads are green, and their hands are blue,<br>
And they went to sea in a Sieve.<br>
<br>
And all night long they sailed away;<br>
And when the sun went down,<br>
They whistled and warbled a moony song<br>
To the echoing sound of a coppery gong,<br>
In the shade of the mountains brown.<br>
\'O Timballo! How happy we are,<br>
When we live in a Sieve and a crockery-jar,<br>
And all night long in the moonlight pale,<br>
We sail away with a pea-green sail,<br>
In the shade of the mountains brown!\'<br>
Far and few, far and few,<br>
Are the lands where the Jumblies live;<br>
Their heads are green, and their hands are blue,<br>
And they went to sea in a Sieve.<br>
<br>
They sailed to the Western Sea, they did,<br>
To a land all covered with trees,<br>
And they bought an Owl, and a useful Cart,<br>
And a pound of Rice, and a Cranberry Tart,<br>
And a hive of silvery Bees.<br>
And they bought a Pig, and some green Jack-daws,<br>
And a lovely Monkey with lollipop paws,<br>
And forty bottles of Ring-Bo-Ree,<br>
And no end of Stilton Cheese.<br>
Far and few, far and few,<br>
Are the lands where the Jumblies live;<br>
Their heads are green, and their hands are blue,<br>
And they went to sea in a Sieve.<br>
<br>
And in twenty years they all came back,<br>
In twenty years or more,<br>
And every one said, \'How tall they\'ve grown!<br>
For they\'ve been to the Lakes, and the Torrible Zone,<br>
And the hills of the Chankly Bore!\'<br>
And they drank their health, and gave them a feast<br>
Of dumplings made of beautiful yeast;<br>
And every one said, \'If we only live,<br>
We too will go to sea in a Sieve,---<br>
To the hills of the Chankly Bore!\'<br>
Far and few, far and few,<br>
Are the lands where the Jumblies live;<br>
Their heads are green, and their hands are blue,<br>
And they went to sea in a Sieve.</centre></font>
]]>
</string>