/*
* Copyright (c) 2015 Jonas Kalderstam.
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package com.nononsenseapps.notepad.ui.common;
import android.content.Context;
import android.content.res.TypedArray;
import android.text.Layout;
import android.text.Selection;
import android.text.Spannable;
import android.text.SpannableString;
import android.text.Spanned;
import android.text.style.ClickableSpan;
import android.text.style.RelativeSizeSpan;
import android.text.style.StyleSpan;
import android.text.style.TypefaceSpan;
import android.text.util.Linkify;
import android.util.AttributeSet;
import android.view.MotionEvent;
import android.widget.TextView;
import com.nononsenseapps.notepad.R;
/**
* A TextView that highlights the first line and makes links clickable. The text
* is not selectable. This is intended to be used in a ListView where the text
* on items is not intended to be selectable.
*/
public class TitleNoteTextView extends TextView {
Object titleStyleSpan;
Object titleSizeSpan;
Object titleFamilySpan;
Object bodyFamilySpan;
private int primaryColor;
private int secondaryColor;
private float mTitleRelativeSize;
private int mBodyFontFamily;
private int mTitleFontFamily;
private int mTitleFontStyle;
private boolean mLinkify;
private String mStyledText;
private String mTitle = "";
private String mRest = "";
public TitleNoteTextView(Context context, AttributeSet attrs) {
super(context, attrs);
TypedArray a = context.getTheme().obtainStyledAttributes(attrs,
R.styleable.StyledTextView, 0, 0);
try {
mTitleRelativeSize = a.getFloat(
R.styleable.StyledTextView_titleRelativeSize, 1.0f);
mTitleFontFamily = a.getInteger(
R.styleable.StyledTextView_titleFontFamily, 0);
mTitleFontStyle = a.getInteger(
R.styleable.StyledTextView_titleFontStyle, 0);
mBodyFontFamily = a.getInteger(
R.styleable.StyledTextView_bodyFontFamily, 0);
mStyledText = a.getString(R.styleable.StyledTextView_styledText);
mLinkify = a.getBoolean(R.styleable.StyledTextView_linkify, false);
primaryColor = super.getCurrentTextColor();
secondaryColor = a.getColor(
R.styleable.StyledTextView_secondaryColor, primaryColor);
} finally {
a.recycle();
}
titleSizeSpan = new RelativeSizeSpan(mTitleRelativeSize);
setTitleFontFamily(mTitleFontFamily);
setTitleFontStyle(mTitleFontStyle);
setBodyFontFamily(mBodyFontFamily);
}
/**
* Useful method for widgets where only default textviews may be used. Use
* this method in your widget adapter to get the functionality this view
* would have offered.
* <p/>
* First argument is a relative size of the first line, like 1.3
* <p/>
* Second argument is of type: android.graphics.Typeface.BOLD, ITALIC etc
* <p/>
* Third is: 0 (normal), 1 (condensed), 2 (light), 3 (thin)
*/
public static CharSequence getStyledText(final String title,
final String rest, final float titleRelSize, final int face,
final int font) {
final StringBuilder textBuilder = new StringBuilder(title);
if (rest.length() > 0) {
textBuilder.append("\n").append(rest);
}
return getStyledText(textBuilder.toString(), titleRelSize, face, font);
}
/**
* Useful method for widgets where only default textviews may be used. Use
* this method in your widget adapter to get the functionality this view
* would have offered.
* <p/>
* First argument is a relative size of the first line, like 1.3
* <p/>
* Second argument is of type: android.graphics.Typeface.BOLD, ITALIC etc
* <p/>
* Third is: 0 (normal), 1 (condensed), 2 (light), 3 (thin)
*/
public static CharSequence getStyledText(final String text,
final float titleRelSize, final int face, final int font) {
if (text == null) return null;
int titleEnd = text.indexOf("\n");
if (titleEnd < 0) {
titleEnd = text.length();
}
TypefaceSpan fontSpan;
switch (font) {
case 1:
fontSpan = new TypefaceSpan("sans-serif-condensed");
break;
case 2:
fontSpan = new TypefaceSpan("sans-serif-light");
break;
case 3:
fontSpan = new TypefaceSpan("sans-serif-thin");
break;
default:
fontSpan = new TypefaceSpan("sans-serif");
break;
}
SpannableString ss = new SpannableString(text);
if (titleEnd > 0) {
ss.setSpan(new StyleSpan(face), 0, titleEnd,
Spannable.SPAN_EXCLUSIVE_EXCLUSIVE);
ss.setSpan(new RelativeSizeSpan(titleRelSize), 0, titleEnd,
Spannable.SPAN_EXCLUSIVE_EXCLUSIVE);
ss.setSpan(fontSpan, 0, titleEnd,
Spannable.SPAN_EXCLUSIVE_EXCLUSIVE);
}
return ss;
}
/**
* @param family matches order defined in xml
*/
public void setTitleFontFamily(final int family) {
if (mTitleFontFamily == family)
return;
mTitleFontFamily = family;
switch (family) {
case 1:
titleFamilySpan = new TypefaceSpan("sans-serif-condensed");
break;
case 2:
titleFamilySpan = new TypefaceSpan("sans-serif-light");
break;
case 3:
titleFamilySpan = new TypefaceSpan("sans-serif-thin");
break;
default:
titleFamilySpan = new TypefaceSpan("sans-serif");
break;
}
}
/**
* @param style matches order defined in xml
*/
public void setTitleFontStyle(final int style) {
if (mTitleFontStyle == style)
return;
mTitleFontStyle = style;
switch (style) {
case 1:
titleStyleSpan = new StyleSpan(android.graphics.Typeface.BOLD);
break;
case 2:
titleStyleSpan = new StyleSpan(android.graphics.Typeface.ITALIC);
break;
default:
titleStyleSpan = new StyleSpan(android.graphics.Typeface.NORMAL);
break;
}
}
/**
* @param family matches order defined in xml
*/
public void setBodyFontFamily(final int family) {
if (mBodyFontFamily == family)
return;
mBodyFontFamily = family;
switch (family) {
case 1:
bodyFamilySpan = new TypefaceSpan("sans-serif-condensed");
break;
case 2:
bodyFamilySpan = new TypefaceSpan("sans-serif-light");
break;
case 3:
bodyFamilySpan = new TypefaceSpan("sans-serif-thin");
break;
default:
bodyFamilySpan = new TypefaceSpan("sans-serif");
break;
}
}
public void useSecondaryColor(final boolean useSecondary) {
if (secondaryColor != primaryColor) {
if (useSecondary) {
super.setTextColor(secondaryColor);
} else {
super.setTextColor(primaryColor);
}
}
}
public String getStyledText() {
return mStyledText;
}
public void setStyledText(final String styledText) {
if (styledText != null) {
this.mStyledText = styledText;
try {
int titleEnd = mStyledText.indexOf("\n");
if (titleEnd < 0) {
titleEnd = mStyledText.length();
}
// Need to link first so we can avoid the title
if (titleEnd > 0) {
SpannableString text = new SpannableString(mStyledText);
text.setSpan(titleStyleSpan, 0, titleEnd,
Spannable.SPAN_EXCLUSIVE_EXCLUSIVE);
text.setSpan(titleSizeSpan, 0, titleEnd,
Spannable.SPAN_EXCLUSIVE_EXCLUSIVE);
text.setSpan(titleFamilySpan, 0, titleEnd,
Spannable.SPAN_EXCLUSIVE_EXCLUSIVE);
if (titleEnd < mStyledText.length()) {
text.setSpan(bodyFamilySpan, titleEnd, mStyledText.length(),
Spannable.SPAN_EXCLUSIVE_EXCLUSIVE);
}
setText(text, BufferType.SPANNABLE);
if (mLinkify) {
Linkify.addLinks(this, Linkify.ALL);
// Make sure links dont steal click focus everywhere
setMovementMethod(null);
}
} else {
// Emtpy string
setText(new SpannableString(mStyledText), BufferType.SPANNABLE);
}
} catch (NullPointerException miuibug) {
/*
* A bug reported on Miui Android 4.4. NullPointerException inside setText method
* due to some nullpointer in android.text.SpannableStringInternal.equals method.
* See crashlog in issue #291
*/
setText(mStyledText);
}
}
}
@Override
public boolean onTouchEvent(MotionEvent event) {
TextView widget = (TextView) this;
Object text = widget.getText();
if (text instanceof Spanned) {
Spannable buffer = (Spannable) text;
int action = event.getAction();
if (action == MotionEvent.ACTION_UP
|| action == MotionEvent.ACTION_DOWN) {
int x = (int) event.getX();
int y = (int) event.getY();
x -= widget.getTotalPaddingLeft();
y -= widget.getTotalPaddingTop();
x += widget.getScrollX();
y += widget.getScrollY();
Layout layout = widget.getLayout();
int line = layout.getLineForVertical(y);
int off = layout.getOffsetForHorizontal(line, x);
ClickableSpan[] link = buffer.getSpans(off, off,
ClickableSpan.class);
// Cant click to the right of a span, if the line ends with the span!
if (x > layout.getLineRight(line)) {
// Don't call the span
} else if (link.length != 0) {
if (action == MotionEvent.ACTION_UP) {
link[0].onClick(widget);
} else if (action == MotionEvent.ACTION_DOWN) {
Selection.setSelection(buffer,
buffer.getSpanStart(link[0]),
buffer.getSpanEnd(link[0]));
}
return true;
}
}
}
return false;
}
public String getTextRest() {
return mRest;
}
public void setTextRest(final String rest) {
if (rest != null) {
this.mRest = rest;
// Make sure it starts with a new line
if (mRest.length() > 0) {
mRest = (rest.startsWith("\n") ? "" : "\n") + rest;
}
setStyledText(mTitle + mRest);
}
}
public String getTextTitle() {
return mTitle;
}
public void setTextTitle(final String title) {
if (title != null) {
// Make sure it does not end with a newline
this.mTitle = (title.endsWith("\n") ? title.substring(0,
title.length() - 1) : title);
setStyledText(mTitle + mRest);
}
}
/**
* @param clickable if links should be clickable
*/
public void setLinkify(final boolean clickable) {
this.mLinkify = clickable;
}
/**
* @param size 0, 1 or 2 representing small/medium/large
*/
public void setTheTextSize(final int size) {
switch (size) {
case 0:
// small
super.setTextSize(12.0f);
break;
case 2:
// large
super.setTextSize(18.0f);
break;
case 1:
default:
// medium
super.setTextSize(14.0f);
break;
}
}
}