com.chrynan.guitarchords.view.GuitarChordView.java Source code

Java tutorial

Introduction

Here is the source code for com.chrynan.guitarchords.view.GuitarChordView.java

Source

package com.chrynan.guitarchords.view;

import android.annotation.TargetApi;
import android.content.Context;
import android.content.res.TypedArray;
import android.graphics.Bitmap;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.Paint;
import android.graphics.Path;
import android.graphics.Rect;
import android.graphics.RectF;
import android.os.Build;
import android.support.annotation.ColorInt;
import android.text.Editable;
import android.text.InputType;
import android.text.Selection;
import android.text.SpannableStringBuilder;
import android.util.AttributeSet;
import android.view.GestureDetector;
import android.view.KeyEvent;
import android.view.MotionEvent;
import android.view.View;
import android.view.inputmethod.BaseInputConnection;
import android.view.inputmethod.EditorInfo;
import android.view.inputmethod.InputConnection;
import android.view.inputmethod.InputMethodManager;

import com.chrynan.guitarchords.R;

import org.json.JSONArray;
import org.json.JSONObject;

import java.util.ArrayList;
import java.util.List;

/*
 * Copyright 2016 chRyNaN
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *  http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
/**
 * Created by chRyNaN on 2/2/2016. A View class to display guitar (or other stringed fretted instruments) chords
 * as a chart.
 */
public class GuitarChordView extends View {
    private static final String TAG = GuitarChordView.class.getSimpleName();
    private static final int DEFAULT_COLOR = Color.parseColor("#000000");
    private static final int BLACK = Color.parseColor("#000000");
    private static final int WHITE = Color.parseColor("#FFFFFF");
    private static final String MUTED_TEXT = "X";
    private static final String OPEN_STRING_TEXT = "O";

    //These static fields correspond to inner class static fields for convenience reasons.
    //For instance, rather than having to use GuitarChordView.Chord.NO_FRET, you can simply
    //use GuitarChordView.NO_FRET. Much easier for a user who isn't aware of the inner classes
    //(even though they are public). Keep these values equal to their inner class counterparts.
    public static final int NO_FRET = Chord.NO_FRET;
    public static final int MAX_FRET = Chord.MAX_FRET;
    public static final String BLANK_TITLE = Chord.BLANK_TITLE;
    public static final String TYPE_BAR = ChordMarker.TYPE_BAR;
    public static final String TYPE_NOTE = ChordMarker.TYPE_NOTE;
    public static final String TYPE_MUTE = ChordMarker.TYPE_MUTE;
    public static final String TYPE_BAR_MUTE = ChordMarker.TYPE_BAR_MUTE;
    public static final int FIRST_STRING = ChordMarker.FIRST_STRING; //Corresponds to the high E string on a standard tuned guitar
    public static final int SECOND_STRING = ChordMarker.SECOND_STRING;
    public static final int THIRD_STRING = ChordMarker.THIRD_STRING;
    public static final int FOURTH_STRING = ChordMarker.FOURTH_STRING;
    public static final int FIFTH_STRING = ChordMarker.FIFTH_STRING;
    public static final int SIXTH_STRING = ChordMarker.SIXTH_STRING; //Corresponds to the low E string on a standard tuned guitar
    public static final int NO_FINGER = ChordMarker.NO_FINGER;
    public static final int INDEX_FINGER = ChordMarker.INDEX_FINGER;
    public static final int MIDDLE_FINGER = ChordMarker.MIDDLE_FINGER;
    public static final int RING_FINGER = ChordMarker.RING_FINGER;
    public static final int PINKY = ChordMarker.PINKY;
    public static final int THUMB = ChordMarker.THUMB;
    public static final int OPEN = ChordMarker.OPEN;
    public static final int OPEN_FRET = OPEN;
    public static final int OPEN_STRING = OPEN;
    public static final int FRET_OPEN = OPEN;
    public static final int FRET_MUTE = ChordMarker.FRET_MUTE;
    public static final int MAX_FRET_COUNT = ChordMarker.MAX_FRET_COUNT;

    private Chord chord;
    private boolean showFretNumbers;
    private boolean showFingerNumbers;
    private boolean editable;
    private int stringCount;
    private List<OnChordSelectedListener> listeners;
    private List<OnFretNumberSelectedListener> fretNumberListeners;
    private List<OnStringSelectedListener> stringListeners;

    private GestureDetector detector;
    private ChordMarker touchEventMarker;

    private String mutedText;
    private String openStringText;

    private RectF drawingBounds;
    private RectF stringMarkerBounds;
    private RectF fretNumberBounds;

    private int fretSize; //y value
    private float stringDistance;//x value
    private int bridgeNutSize;
    private int bridgeNutColor;
    private int fretMarkerSize;
    private int fretMarkerColor;
    private float stringSize;
    private int stringColor;
    private int fretNumberSize;
    private int fretNumberColor;
    private int stringMarkerSize;
    private int stringMarkerColor;
    private int noteSize;
    private int noteColor;
    private int noteNumberSize;
    private int noteNumberColor;
    private int barLineColor;

    private Paint bridgeNutPaint;
    private Paint fretMarkerPaint;
    private Paint stringPaint;
    private Paint fretNumberPaint;
    private Paint stringMarkerPaint;
    private Paint notePaint;
    private Paint noteNumberPaint;
    private Paint barLinePaint;
    private Path barLinePath;

    public GuitarChordView(Context context) {
        super(context);
        init(context, null);
    }

    public GuitarChordView(Context context, AttributeSet attrs) {
        super(context, attrs);
        init(context, attrs);
    }

    public GuitarChordView(Context context, AttributeSet attrs, int defStyleAttr) {
        super(context, attrs, defStyleAttr);
        init(context, attrs);
    }

    @TargetApi(Build.VERSION_CODES.LOLLIPOP)
    public GuitarChordView(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) {
        super(context, attrs, defStyleAttr, defStyleRes);
        init(context, attrs);
    }

    private void init(Context context, AttributeSet attrs) {
        detector = new GestureDetector(context, new GestureDetector.SimpleOnGestureListener() {
            @Override
            public boolean onDown(MotionEvent event) {
                boolean isMarkerInChord = false;
                int fret = NO_FRET, string = -1;
                fret = getSelectedFret(event);
                string = getSelectedString(event);
                touchEventMarker = new ChordMarker(string, fret, NO_FINGER);
                return true;
            }

            @Override
            public void onLongPress(MotionEvent event) {
                if (editable && touchEventMarker != null && chord != null && chord.contains(touchEventMarker)) {
                    InputMethodManager imm = (InputMethodManager) getContext()
                            .getSystemService(Context.INPUT_METHOD_SERVICE);
                    imm.showSoftInput(GuitarChordView.this, InputMethodManager.SHOW_IMPLICIT);
                }
            }
        });
        chord = new Chord();
        showFretNumbers = true;
        showFingerNumbers = true;
        editable = false;
        stringCount = 6;
        listeners = new ArrayList<>();
        fretNumberListeners = new ArrayList<>();
        stringListeners = new ArrayList<>();
        touchEventMarker = null;
        mutedText = MUTED_TEXT;
        openStringText = OPEN_STRING_TEXT;
        initPaint();
        if (attrs != null) {
            //TODO handle custom attribute values
            TypedArray a = getContext().getTheme().obtainStyledAttributes(attrs, R.styleable.GuitarChordView, 0, 0);
            try {
                bridgeNutColor = a.getColor(R.styleable.GuitarChordView_bridgeNutColor, DEFAULT_COLOR);
                bridgeNutPaint.setColor(bridgeNutColor);
                fretMarkerColor = a.getColor(R.styleable.GuitarChordView_fretMarkerColor, DEFAULT_COLOR);
                fretMarkerPaint.setColor(fretMarkerColor);
                stringColor = a.getColor(R.styleable.GuitarChordView_stringColor, DEFAULT_COLOR);
                stringPaint.setColor(stringColor);
                fretNumberColor = a.getColor(R.styleable.GuitarChordView_fretNumberColor, DEFAULT_COLOR);
                fretNumberPaint.setColor(fretNumberColor);
                stringMarkerColor = a.getColor(R.styleable.GuitarChordView_stringMarkerColor, DEFAULT_COLOR);
                stringMarkerPaint.setColor(stringMarkerColor);
                noteColor = a.getColor(R.styleable.GuitarChordView_noteColor, DEFAULT_COLOR);
                notePaint.setColor(noteColor);
                noteNumberColor = a.getColor(R.styleable.GuitarChordView_noteNumberColor, WHITE);
                noteNumberPaint.setColor(noteNumberColor);
                barLineColor = a.getColor(R.styleable.GuitarChordView_barLineColor, DEFAULT_COLOR);
                barLinePaint.setColor(barLineColor);
                mutedText = a.getString(R.styleable.GuitarChordView_mutedText);
                mutedText = (mutedText == null) ? MUTED_TEXT : mutedText;
                openStringText = a.getString(R.styleable.GuitarChordView_openStringText);
                openStringText = (openStringText == null) ? OPEN_STRING_TEXT : openStringText;
                stringCount = a.getInt(R.styleable.GuitarChordView_stringAmount, 6);
                editable = a.getBoolean(R.styleable.GuitarChordView_editable, false);
                showFingerNumbers = a.getBoolean(R.styleable.GuitarChordView_showFingerNumbers, true);
                showFretNumbers = a.getBoolean(R.styleable.GuitarChordView_showFretNumbers, true);
            } catch (Exception e) {
                e.printStackTrace();
            } finally {
                a.recycle();
            }
        }
    }

    private void initPaint() {
        bridgeNutPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
        bridgeNutColor = DEFAULT_COLOR;
        bridgeNutPaint.setColor(bridgeNutColor);
        bridgeNutPaint.setStyle(Paint.Style.STROKE);
        bridgeNutPaint.setStrokeCap(Paint.Cap.BUTT);
        fretMarkerPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
        fretMarkerColor = DEFAULT_COLOR;
        fretMarkerPaint.setColor(fretMarkerColor);
        fretMarkerPaint.setStyle(Paint.Style.STROKE);
        fretMarkerPaint.setStrokeCap(Paint.Cap.ROUND);
        stringPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
        stringColor = DEFAULT_COLOR;
        stringPaint.setColor(stringColor);
        stringPaint.setStyle(Paint.Style.STROKE);
        stringPaint.setStrokeCap(Paint.Cap.BUTT);
        fretNumberPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
        fretNumberColor = DEFAULT_COLOR;
        fretNumberPaint.setColor(fretNumberColor);
        fretNumberPaint.setTextAlign(Paint.Align.CENTER);
        stringMarkerPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
        stringMarkerColor = DEFAULT_COLOR;
        stringMarkerPaint.setColor(stringMarkerColor);
        stringMarkerPaint.setTextAlign(Paint.Align.CENTER);
        notePaint = new Paint(Paint.ANTI_ALIAS_FLAG);
        noteColor = DEFAULT_COLOR;
        notePaint.setColor(noteColor);
        noteNumberPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
        noteNumberColor = WHITE;
        noteNumberPaint.setColor(noteNumberColor);
        noteNumberPaint.setTextAlign(Paint.Align.CENTER);
        barLinePaint = new Paint(Paint.ANTI_ALIAS_FLAG);
        barLineColor = DEFAULT_COLOR;
        barLinePaint.setColor(barLineColor);
        barLinePaint.setStyle(Paint.Style.STROKE);
    }

    private void setPaintSizes() {
        if (fretMarkerPaint == null) {
            initPaint();
        }
        bridgeNutPaint.setStrokeWidth(bridgeNutSize);
        fretMarkerPaint.setStrokeWidth(fretMarkerSize);
        stringPaint.setStrokeWidth(stringSize);
        fretNumberPaint.setTextSize(fretNumberSize);
        stringMarkerPaint.setTextSize(stringMarkerSize);
        notePaint.setStrokeWidth(noteSize);
        noteNumberPaint.setTextSize(noteNumberSize);
        barLinePaint.setStrokeWidth(2 * stringSize);
    }

    @Override
    protected void onSizeChanged(int width, int height, int oldWidth, int oldHeight) {
        //TODO adjust lowest and highest notes to be within the draw bounds (notes on 1st and 6th string exceed draw bounds by half
        //the note size)
        float aWidth = (float) width - ((float) (getPaddingLeft() + getPaddingRight()));
        float aHeight = (float) width - ((float) (getPaddingTop() + getPaddingBottom()));
        float pWidth = aWidth;
        float pHeight = aHeight;
        float w = aHeight * (2f / 3f);
        if (w <= aWidth) {
            aWidth = w;
        } else {
            aHeight = 3 * (aWidth / 2);
        }
        //Center everything
        drawingBounds = new RectF((pWidth - aWidth) / 2, (pHeight - aHeight) / 2, (pWidth - aWidth) / 2 + aWidth,
                (pHeight - aHeight) / 2 + aHeight);
        if (showFretNumbers) {
            fretNumberSize = (int) ((aWidth / (stringCount + 1)) * (3f / 4f));
            stringMarkerSize = fretNumberSize;
            stringDistance = (aWidth - (fretNumberSize + (fretNumberSize / 2))) / stringCount;
        } else {
            fretNumberSize = 0;
            stringMarkerSize = (int) ((aWidth / (stringCount + 1)) * (3f / 4f));
            stringDistance = aWidth / stringCount;
        }
        stringSize = stringDistance / stringCount;
        stringSize = (stringSize < 1) ? 1 : stringSize;
        fretMarkerSize = (int) stringSize;
        bridgeNutSize = (int) (3 * stringSize);
        noteSize = (int) stringDistance;
        noteNumberSize = (int) (noteSize * (3f / 4f));
        //TODO need to take into account whether or not to show bridgeNut
        int fretCount = (chord != null && chord.getFretCount() >= 4) ? chord.getFretCount() : 4;
        fretSize = Math
                .round(((aHeight - (fretNumberSize + fretNumberSize / 2)) - ((fretCount + 1) * fretMarkerSize))
                        / fretCount);
        if (showFretNumbers) {
            stringMarkerBounds = new RectF(drawingBounds.left + (fretNumberSize + (fretNumberSize / 2)),
                    drawingBounds.top, drawingBounds.right,
                    drawingBounds.top + (stringMarkerSize + (stringMarkerSize / 2)));
            fretNumberBounds = new RectF(drawingBounds.left, stringMarkerBounds.bottom,
                    drawingBounds.left + (fretNumberSize + (fretNumberSize / 2)), drawingBounds.bottom);
        } else {
            stringMarkerBounds = new RectF(drawingBounds.left, drawingBounds.top, drawingBounds.right,
                    drawingBounds.top + (stringMarkerSize + (stringMarkerSize / 2)));
            fretNumberBounds = new RectF(0, 0, 0, 0);
        }
        setPaintSizes();
    }//End of onSizeChanged

    @Override
    protected void onDraw(Canvas canvas) {
        //This method handles a lot of work, possibly too much for an onDraw method, but since I'm not animating
        //or updating the view often, it should be capable of performing the tasks without too much effort.
        //I can optimize this later.

        //First draw the strings and fret markers
        //Fret markers; not worrying about whether or not to show the bridge nut now (since that wasn't calculated
        //in to the drawing size)
        //TODO need to take into account whether or not to show bridgeNut
        int fretCount = chord.getFretCount();
        fretCount = (fretCount < 4) ? 4 : fretCount;
        for (int i = 0; i < fretCount + 1; i++) {
            canvas.drawLine(drawingBounds.left + fretNumberBounds.width(),
                    (drawingBounds.top + stringMarkerBounds.height()) + (i * fretSize) + (i * fretMarkerSize),
                    drawingBounds.right - stringSize,
                    (drawingBounds.top + stringMarkerBounds.height()) + (i * fretSize) + (i * fretMarkerSize),
                    fretMarkerPaint);
        }
        //Strings
        for (int i = 0; i < stringCount; i++) {
            canvas.drawLine((stringMarkerBounds.left) + (i * stringDistance) + (i * stringSize),
                    drawingBounds.top + stringMarkerBounds.height(),
                    (stringMarkerBounds.left) + (i * stringDistance) + (i * stringSize),
                    drawingBounds.bottom - fretMarkerSize, stringPaint);
        }
        //Next draw the fret numbers and string markers
        //Fret numbers; check if we are showing them or not
        if (showFretNumbers) {
            for (int i = 0; i < fretCount; i++) {
                canvas.drawText(String.valueOf(i + 1), drawingBounds.left + fretNumberBounds.width() / 2,
                        getVerticalCenterTextPosition(
                                stringMarkerBounds.bottom + (i * fretMarkerSize) + (i * fretSize) + (fretSize / 2),
                                String.valueOf(i + 1), fretNumberPaint),
                        fretNumberPaint);
            }
        }
        //String markers
        for (int i : chord.getMutedStrings()) {
            canvas.drawText(mutedText,
                    (drawingBounds.left + fretNumberBounds.width()) + ((stringCount - i) * stringDistance)
                            + ((stringCount - i) * stringSize),
                    getVerticalCenterTextPosition(drawingBounds.top + (stringMarkerBounds.height() / 2), mutedText,
                            stringMarkerPaint),
                    stringMarkerPaint);
        }
        for (int i : chord.getOpenStrings()) {
            canvas.drawText(openStringText,
                    (drawingBounds.left + fretNumberBounds.width()) + ((stringCount - i) * stringDistance)
                            + ((stringCount - i) * stringSize),
                    getVerticalCenterTextPosition(drawingBounds.top + (stringMarkerBounds.height() / 2),
                            openStringText, stringMarkerPaint),
                    stringMarkerPaint);
        }
        //Finally, draw all the notes and the note text
        //Bars
        float startCenterX;
        float startCenterY;
        for (ChordMarker cm : chord.getBars()) {
            startCenterX = (drawingBounds.left + fretNumberBounds.width())
                    + ((stringCount - cm.getStartString()) * stringDistance)
                    + ((stringCount - cm.getStartString()) * stringSize);
            startCenterY = stringMarkerBounds.bottom
                    + (((cm.getFret() * fretSize) + (cm.getFret() * fretMarkerSize)) - (fretSize / 2));
            float endCenterX = (drawingBounds.left + fretNumberBounds.width())
                    + ((stringCount - cm.getEndString()) * stringDistance)
                    + ((stringCount - cm.getEndString()) * stringSize);
            float endCenterY = startCenterY;
            canvas.drawCircle(startCenterX, startCenterY, noteSize / 2, notePaint);
            canvas.drawCircle(endCenterX, endCenterY, noteSize / 2, notePaint);
            if (showFingerNumbers) {
                canvas.drawText(String.valueOf(cm.getFinger()), startCenterX, getVerticalCenterTextPosition(
                        startCenterY, String.valueOf(cm.getFinger()), noteNumberPaint), noteNumberPaint);
                canvas.drawText(String.valueOf(cm.getFinger()), endCenterX,
                        getVerticalCenterTextPosition(endCenterY, String.valueOf(cm.getFinger()), noteNumberPaint),
                        noteNumberPaint);
            }
            barLinePath = new Path();
            barLinePath.moveTo(startCenterX, startCenterY - (noteSize / 2));
            barLinePath.quadTo((startCenterX + endCenterX) / 2, (startCenterY + endCenterY - (noteSize / 2)) / 4,
                    endCenterX, endCenterY - (noteSize / 2));
            canvas.drawPath(barLinePath, barLinePaint);
        }
        //Individual notes
        for (ChordMarker cm : chord.getIndividualNotes()) {
            startCenterX = (drawingBounds.left + fretNumberBounds.width())
                    + ((stringCount - cm.getStartString()) * stringDistance)
                    + ((stringCount - cm.getStartString()) * stringSize);
            startCenterY = stringMarkerBounds.bottom
                    + (((cm.getFret() * fretSize) + (cm.getFret() * fretMarkerSize)) - (fretSize / 2));
            canvas.drawCircle(startCenterX, startCenterY, noteSize / 2, notePaint);
            if (showFingerNumbers) {
                canvas.drawText(String.valueOf(cm.getFinger()), startCenterX, getVerticalCenterTextPosition(
                        startCenterY, String.valueOf(cm.getFinger()), noteNumberPaint), noteNumberPaint);
            }
        }
    }//End of onDraw

    @Override
    public boolean onTouchEvent(MotionEvent event) {
        if (isInChartBounds(event)) {
            boolean isMarkerInChord = false;
            int fret = NO_FRET, string = -1;
            fret = getSelectedFret(event);
            string = getSelectedString(event);
            if (event.getAction() == MotionEvent.ACTION_DOWN) {
                detector.onTouchEvent(event);
            }
            if (event.getAction() == MotionEvent.ACTION_UP && touchEventMarker != null) {
                if (fret == touchEventMarker.getFret()) {
                    int startString = 1, endString = 1;
                    startString = (touchEventMarker.getStartString() < string) ? touchEventMarker.getStartString()
                            : string;
                    endString = (touchEventMarker.getEndString() >= string) ? touchEventMarker.getEndString()
                            : string;
                    ChordMarker marker = new ChordMarker(startString, endString, fret,
                            touchEventMarker.getFinger());
                    isMarkerInChord = chord.contains(marker);
                    alertOnChordSelected(event, marker, isMarkerInChord);
                    if (editable) {
                        return true;
                    }
                    return super.onTouchEvent(event);
                }
            }
            if (editable) {
                return true;
            }
        } else if (isInFretNumberBounds(event)) {
            alertOnFretNumberSelected(event, getSelectedFret(event));
        } else if (isInStringMarkerBounds(event)) {
            alertOnStringSelectedListener(event, getSelectedString(event));
        }
        return super.onTouchEvent(event);
    }

    //Needed for handling the text input if this View is editable
    @Override
    public InputConnection onCreateInputConnection(EditorInfo outAttrs) {
        if (editable) {
            final InputConnectionAccomodatingLatinIMETypeNullIssues baseInputConnection = new InputConnectionAccomodatingLatinIMETypeNullIssues(
                    this, false);
            outAttrs.actionLabel = null;
            outAttrs.inputType = InputType.TYPE_NULL;
            outAttrs.imeOptions = EditorInfo.IME_ACTION_DONE;
            setOnKeyListener(new OnKeyListener() {
                @Override
                public boolean onKey(View v, int keyCode, KeyEvent event) {
                    if (editable) {
                        if (event
                                .getUnicodeChar() == (int) EditableAccomodatingLatinIMETypeNullIssues.ONE_UNPROCESSED_CHARACTER
                                        .charAt(0)) {
                            //We are ignoring this character, and we want everyone else to ignore it, too, so
                            // we return true indicating that we have handled it (by ignoring it).
                            return true;
                        }
                        if (keyCode == KeyEvent.KEYCODE_ENTER && event.getAction() == KeyEvent.ACTION_UP) {
                            //Trap the Done key and close the keyboard if it is pressed (if that's what you want to do)
                            InputMethodManager imm = (InputMethodManager) getContext()
                                    .getSystemService(Context.INPUT_METHOD_SERVICE);
                            imm.hideSoftInputFromWindow(GuitarChordView.this.getWindowToken(), 0);
                            if (touchEventMarker != null) {
                                Integer finger;
                                try {
                                    finger = Integer.valueOf(baseInputConnection.getEditable().toString());
                                } catch (Exception e) {
                                    e.printStackTrace();
                                    finger = touchEventMarker.getFinger();
                                }
                                touchEventMarker = new ChordMarker(touchEventMarker.getStartString(),
                                        touchEventMarker.getEndString(), touchEventMarker.getFret(), finger);
                                alertOnChordSelected(null, new ChordMarker(touchEventMarker),
                                        chord.contains(touchEventMarker));
                            }
                            return true;
                        }
                    }
                    return false;
                }
            });
            return baseInputConnection;
        }
        return null;
    }

    protected boolean isInChartBounds(MotionEvent event) {
        float x = event.getX();
        float y = event.getY();
        if (x >= (drawingBounds.left + fretNumberBounds.width()) && x < drawingBounds.right) {
            if (y >= (drawingBounds.top + stringMarkerBounds.height()) && y < drawingBounds.bottom) {
                return true;
            }
        }
        return false;
    }

    protected boolean isInFretNumberBounds(MotionEvent event) {
        if (showFretNumbers) {
            float x = event.getX();
            float y = event.getY();
            if (x >= drawingBounds.left && x < (drawingBounds.left + fretNumberBounds.width())) {
                if (y >= drawingBounds.top && y < drawingBounds.bottom) {
                    return true;
                }
            }
        }
        return false;
    }

    protected boolean isInStringMarkerBounds(MotionEvent event) {
        float x = event.getX();
        float y = event.getY();
        if (x >= drawingBounds.left && x < drawingBounds.right) {
            if (y >= drawingBounds.top && y < (drawingBounds.top + stringMarkerBounds.height())) {
                return true;
            }
        }
        return false;
    }

    protected int getSelectedFret(MotionEvent event) {
        int fretStart = chord.getFretStart();
        float y = event.getY();
        int i;
        for (i = 0; i < chord.getFretCount(); i++) {
            if (y < (stringMarkerBounds.bottom + (i * fretSize) + (i * fretMarkerSize))) {
                break;
            }
        }
        i = (fretStart < 0) ? i : (fretStart - 1) + i;
        return i;
    }

    protected int getSelectedString(MotionEvent event) {
        float x = event.getX();
        int i;
        for (i = 0; i < stringCount; i++) {
            if (x < (((stringMarkerBounds.left) + (i * stringDistance) + (i * stringSize))
                    + (stringDistance / 2))) {
                break;
            }
        }
        return stringCount - i;
    }

    protected float getVerticalCenterTextPosition(float originalYPosition, String text, Paint textPaint) {
        Rect bounds = new Rect();
        textPaint.getTextBounds(text, 0, text.length(), bounds);
        return originalYPosition + (bounds.height() / 2);
    }

    protected float getVerticalCenterTextPosition(float originalYPosition, String text, int textSizeInPixels) {
        Paint p = new Paint(Paint.ANTI_ALIAS_FLAG);
        p.setTextAlign(Paint.Align.CENTER);
        p.setTextSize(textSizeInPixels);
        return getVerticalCenterTextPosition(originalYPosition, text, p);
    }

    public Bitmap getBitmap() {
        setDrawingCacheEnabled(true);
        buildDrawingCache();
        Bitmap b = getDrawingCache();
        b = b.copy(Bitmap.Config.ARGB_8888, false);
        destroyDrawingCache();
        setDrawingCacheEnabled(false);
        return b;
    }

    public void addNote(int fret, int string, int finger) {
        if (chord == null) {
            chord = new Chord();
        }
        chord.addMarker(new ChordMarker(string, fret, finger));
        invalidate();
    }

    public void addBar(int fret, int startString, int endString, int finger) {
        if (chord == null) {
            chord = new Chord();
        }
        chord.addMarker(new ChordMarker(startString, endString, fret, finger));
        invalidate();
    }

    public void addMutedString(int string) {
        chord.addMarker(new ChordMarker(string, ChordMarker.FRET_MUTE, ChordMarker.NO_FINGER));
        invalidate();
    }

    public void addOpenString(int string) {
        chord.addMarker(new ChordMarker(string, ChordMarker.FRET_OPEN, ChordMarker.NO_FINGER));
        invalidate();
    }

    public void addMarker(ChordMarker marker) {
        if (marker != null) {
            if (chord == null) {
                chord = new Chord();
            }
            chord.addMarker(marker);
            invalidate();
        }
    }

    public void addMarkers(List<ChordMarker> markers) {
        for (ChordMarker marker : markers) {
            chord.addMarker(marker);
        }
        invalidate();
    }

    public boolean removeMarker(ChordMarker marker) {
        if (marker != null) {
            if (chord != null) {
                boolean b = chord.removeMarker(marker);
                if (b) {
                    invalidate();
                }
                return b;
            }
        }
        return false;
    }

    public void clear() {
        if (chord == null) {
            chord = new Chord();
        }
        chord.clear();
        invalidate();
    }

    //Title is not visible in the View, it's just there for convenience reasons.
    //To make it visible, simple add a TextView to the layout and set it's text as the title.
    public String getTitle() {
        return chord.getTitle();
    }

    //Title is not visible in the View, it's just there for convenience reasons.
    //To make it visible, simple add a TextView to the layout and set it's text as the title.
    public void setTitle(String title) {
        chord.setTitle(title);
    }

    public int getStringAmount() {
        return stringCount;
    }

    public void setStringAmount(int count) {
        this.stringCount = count;
        requestLayout();
    }

    public Chord getChord() {
        return chord;
    }

    public void setChord(Chord chord) {
        this.chord = chord;
        invalidate();
    }

    public boolean showFretNumbers() {
        return showFretNumbers;
    }

    public void showFretNumbers(boolean show) {
        this.showFretNumbers = show;
        requestLayout();
    }

    public boolean showFingerNumbers() {
        return showFingerNumbers;
    }

    public void showFingerNumbers(boolean show) {
        this.showFingerNumbers = show;
        invalidate();
    }

    public int getBridgeNutColor() {
        return bridgeNutColor;
    }

    public void setBridgeNutColor(@ColorInt int color) {
        this.bridgeNutColor = color;
        this.bridgeNutPaint.setColor(color);
        invalidate();
    }

    public int getFretMarkerColor() {
        return fretMarkerColor;
    }

    public void setFretMarkerColor(@ColorInt int color) {
        this.fretMarkerColor = color;
        this.fretMarkerPaint.setColor(color);
        invalidate();
    }

    public int getStringColor() {
        return stringColor;
    }

    public void setStringColor(@ColorInt int color) {
        this.stringColor = color;
        this.stringPaint.setColor(color);
        invalidate();
    }

    public int getFretNumberColor() {
        return fretNumberColor;
    }

    public void setFretNumberColor(@ColorInt int color) {
        this.fretNumberColor = color;
        this.fretNumberPaint.setColor(color);
        invalidate();
    }

    public int getStringMarkerColor() {
        return stringMarkerColor;
    }

    public void setStringMarkerColor(@ColorInt int color) {
        this.stringMarkerColor = color;
        this.stringMarkerPaint.setColor(color);
        invalidate();
    }

    public int getNoteColor() {
        return noteColor;
    }

    public void setNoteColor(@ColorInt int color) {
        this.noteColor = color;
        this.notePaint.setColor(color);
        invalidate();
    }

    public int getNoteNumberColor() {
        return noteNumberColor;
    }

    public void setNoteNumberColor(@ColorInt int color) {
        this.noteNumberColor = color;
        this.noteNumberPaint.setColor(color);
        invalidate();
    }

    public int getBarLineColor() {
        return barLineColor;
    }

    public void setBarLineColor(@ColorInt int color) {
        this.barLineColor = color;
        this.barLinePaint.setColor(color);
        invalidate();
    }

    public boolean isEditable() {
        return editable;
    }

    public void setEditable(boolean edit) {
        this.editable = edit;
    }

    public String getMutedText() {
        return mutedText;
    }

    public void setMutedText(String text) {
        this.mutedText = text;
        invalidate();
    }

    public String getOpenStringText() {
        return openStringText;
    }

    public void setOpenStringText(String text) {
        this.openStringText = text;
        invalidate();
    }

    public interface OnChordSelectedListener {
        void onChordSelected(MotionEvent event, ChordMarker marker, boolean isMarkerInChord);
    }

    public void addOnChordSelectedListener(OnChordSelectedListener l) {
        if (listeners == null) {
            listeners = new ArrayList<>();
        }
        listeners.add(l);
    }

    public boolean removeOnChordSelectedListener(OnChordSelectedListener l) {
        if (listeners != null) {
            return listeners.remove(l);
        }
        return false;
    }

    private void alertOnChordSelected(MotionEvent event, ChordMarker marker, boolean isMarkerInChord) {
        for (OnChordSelectedListener l : listeners) {
            l.onChordSelected(event, marker, isMarkerInChord);
        }
    }

    public interface OnFretNumberSelectedListener {
        void onFretNumberSelected(MotionEvent event, int fret);
    }

    public void addOnFretNumberSelectedListener(OnFretNumberSelectedListener l) {
        if (fretNumberListeners == null) {
            fretNumberListeners = new ArrayList<>();
        }
        fretNumberListeners.add(l);
    }

    public boolean removeOnFretNumberSelectedListener(OnFretNumberSelectedListener l) {
        if (fretNumberListeners != null) {
            return fretNumberListeners.remove(l);
        }
        return false;
    }

    private void alertOnFretNumberSelected(MotionEvent event, int fret) {
        for (OnFretNumberSelectedListener l : fretNumberListeners) {
            l.onFretNumberSelected(event, fret);
        }
    }

    public interface OnStringSelectedListener {
        void onStringSelected(MotionEvent event, int string);
    }

    public void addOnStringSelectedListener(OnStringSelectedListener l) {
        if (stringListeners == null) {
            stringListeners = new ArrayList<>();
        }
        stringListeners.add(l);
    }

    public boolean removeOnStringSelectedListener(OnStringSelectedListener l) {
        if (stringListeners != null) {
            return stringListeners.remove(l);
        }
        return false;
    }

    private void alertOnStringSelectedListener(MotionEvent event, int string) {
        for (OnStringSelectedListener l : stringListeners) {
            l.onStringSelected(event, string);
        }
    }

    public static class Chord {
        public static final int NO_FRET = -1;
        public static final int MAX_FRET = 24;
        public static final String BLANK_TITLE = "";

        private List<ChordMarker> bars;
        private List<ChordMarker> notes;
        private int fretStart;
        private int fretEnd;
        private String title;

        public Chord() {
            this.bars = new ArrayList<>();
            this.notes = new ArrayList<>();
            this.fretStart = 1;
            this.fretEnd = 4;
            this.title = BLANK_TITLE;
        }

        public Chord(String title) {
            this();
            setTitle(title);
        }

        public Chord(Chord chord) {
            this.bars = chord.getBars();
            this.notes = chord.getIndividualNotes();
            this.fretStart = chord.getFretStart();
            this.fretEnd = chord.getFretEnd();
            this.title = chord.getTitle();
        }

        public Chord(JSONObject obj) {
            this();
            fromJSON(obj);
        }

        public void fromJSON(JSONObject obj) {
            try {
                if (obj != null) {
                    if (obj.has("fretStart")) {
                        this.fretStart = obj.getInt("fretStart");
                    }
                    if (obj.has("fretEnd")) {
                        this.fretEnd = obj.getInt("fretEnd");
                    }
                    if (obj.has("title")) {
                        this.title = obj.getString("title");
                    }
                    if (obj.has("bars")) {
                        JSONArray bArray = obj.getJSONArray("bars");
                        for (int i = 0; i < bArray.length(); i++) {
                            bars.add(new ChordMarker(bArray.getJSONObject(i)));
                        }
                    }
                    if (obj.has("notes")) {
                        JSONArray nArray = obj.getJSONArray("notes");
                        for (int i = 0; i < nArray.length(); i++) {
                            notes.add(new ChordMarker(nArray.getJSONObject(i)));
                        }
                    }
                }
            } catch (Exception e) {
                e.printStackTrace();
            }
        }

        public JSONObject toJSON() {
            try {
                JSONObject obj = new JSONObject();
                obj.put("fretStart", fretStart);
                obj.put("fretEnd", fretEnd);
                if (title != null) {
                    obj.put("title", title);
                }
                if (bars != null) {
                    JSONArray bArray = new JSONArray();
                    for (ChordMarker marker : bars) {
                        bArray.put(marker.toJSON());
                    }
                    obj.put("bars", bArray);
                }
                if (notes != null) {
                    JSONArray nArray = new JSONArray();
                    for (ChordMarker marker : notes) {
                        nArray.put(marker.toJSON());
                    }
                    obj.put("notes", nArray);
                }
                return obj;
            } catch (Exception e) {
                e.printStackTrace();
            }
            return null;
        }

        public String toJSONString() {
            JSONObject obj = this.toJSON();
            if (obj != null) {
                return obj.toString();
            }
            return this.toString();
        }

        public void addMarker(ChordMarker marker) {
            if (marker != null) {
                if (marker.getType() != null && (marker.getType().equals(ChordMarker.TYPE_BAR)
                        || marker.getType().equals(ChordMarker.TYPE_BAR_MUTE))) {
                    if (marker.getFret() == ChordMarker.FRET_OPEN) {
                        //No need to add a bar for the open strings
                        //So, simply add note markers for each of the open strings
                        for (int i = marker.getStartString(); i <= marker.getEndString(); i++) {
                            addMarker(new ChordMarker(i, marker.getFret(), marker.getFinger()));
                        }
                    } else {
                        //Add this marking to the bar list
                        bars.add(marker);
                        int fret = marker.getFret();
                        fret = (fret < 1) ? 1 : fret;
                        fret = (fret > MAX_FRET) ? MAX_FRET : fret;
                        if (fretStart == NO_FRET && fretEnd == NO_FRET) {
                            fretStart = fret;
                            fretEnd = fret;
                        } else if (fret < fretStart && fret > 0) {
                            fretStart = fret;
                        } else if (fret > fretEnd) {
                            fretEnd = fret;
                        }
                    }
                } else {
                    //Remove any previous markers that may be on this string
                    List<ChordMarker> removeList = new ArrayList<>(); //Avoid ConcurrentModificationException
                    for (ChordMarker cm : notes) {
                        if (cm.getStartString() == marker.getStartString()) {
                            removeList.add(cm);
                        }
                    }
                    notes.removeAll(removeList); //Avoid ConcurrentModificationException
                    notes.add(marker);
                    int fret = marker.getFret();
                    fret = (fret < 1) ? 1 : fret;
                    fret = (fret > MAX_FRET) ? MAX_FRET : fret;
                    if (fretStart == NO_FRET && fretEnd == NO_FRET) {
                        fretStart = fret;
                        fretEnd = fret;
                    } else if (fret < fretStart && fret > 0) {
                        fretStart = fret;
                    } else if (fret > fretEnd) {
                        fretEnd = fret;
                    }
                }
            }
        }

        public boolean removeMarker(ChordMarker marker) {
            boolean containsMarker = false;
            containsMarker = bars.remove(marker);
            if (notes.remove(marker)) {
                containsMarker = true;
            }
            if (containsMarker && (marker.getFret() < fretStart || marker.getFret() > fretEnd)) {
                for (int i = 0; i < notes.size(); i++) {
                    if (i == 0) {
                        fretStart = notes.get(i).getFret();
                        fretEnd = fretStart;
                    } else {
                        fretStart = (notes.get(i).getFret() < fretStart) ? notes.get(i).getFret() : fretStart;
                        fretEnd = (notes.get(i).getFret() > fretEnd) ? notes.get(i).getFret() : fretEnd;
                    }
                }
                for (ChordMarker cm : bars) {
                    if (cm.getFret() < fretStart) {
                        fretStart = cm.getFret();
                    } else if (cm.getFret() > fretEnd) {
                        fretEnd = cm.getFret();
                    }
                }
            }
            return containsMarker;
        }

        public boolean contains(ChordMarker marker) {
            for (ChordMarker cm : bars) {
                if (cm.equals(marker)) {
                    return true;
                }
            }
            for (ChordMarker cm : notes) {
                if (cm.equals(marker)) {
                    return true;
                }
            }
            return false;
        }

        public int getMarkerAmount() {
            return notes.size() + bars.size();
        }

        public void clear() {
            notes.clear();
            bars.clear();
            fretStart = 1;
            fretEnd = 4;
            title = BLANK_TITLE;
        }

        public int getStringCount() {
            int maxString = 1;
            for (ChordMarker marker : getAllMarkers()) {
                if (marker.getEndString() > maxString) {
                    maxString = marker.getEndString();
                }
            }
            for (Integer i : getOpenStrings()) {
                if (i > maxString) {
                    maxString = i;
                }
            }
            for (Integer i : getMutedStrings()) {
                if (i > maxString) {
                    maxString = i;
                }
            }
            return maxString;
        }

        public int getFretStart() {
            return fretStart;
        }

        public int getFretEnd() {
            return fretEnd;
        }

        public int getFretCount() {
            if ((fretEnd - fretStart + 1) < 4) {
                return 4;
            }
            return fretEnd - fretStart + 1;
        }

        public List<ChordMarker> getBars() {
            return bars;
        }

        public List<ChordMarker> getIndividualNotes() {
            return notes;
        }

        public List<ChordMarker> getAllMarkers() {
            List<ChordMarker> allMarkers = new ArrayList<>();
            allMarkers.addAll(notes);
            allMarkers.addAll(bars);
            return allMarkers;
        }

        public List<Integer> getOpenStrings() {
            List<Integer> openStrings = new ArrayList<>();
            openStrings.add(1);
            openStrings.add(2);
            openStrings.add(3);
            openStrings.add(4);
            openStrings.add(5);
            openStrings.add(6);
            for (ChordMarker cm : notes) {
                if (cm.getFret() != ChordMarker.FRET_OPEN) {
                    openStrings.remove(Integer.valueOf(cm.getStartString()));
                }
            }
            for (ChordMarker cm : bars) {
                for (int i = cm.getStartString(); i <= cm.getEndString(); i++) {
                    if (cm.getFret() != ChordMarker.FRET_OPEN) {
                        openStrings.remove(Integer.valueOf(i));
                    }
                }
            }
            return openStrings;
        }

        public List<Integer> getMutedStrings() {
            List<Integer> mutedStrings = new ArrayList<>();
            for (ChordMarker cm : notes) {
                if (cm.getFret() == ChordMarker.FRET_MUTE) {
                    mutedStrings.add(cm.getStartString());
                }
            }
            for (ChordMarker cm : bars) {
                if (cm.getType().equals(ChordMarker.TYPE_BAR_MUTE) && cm.getFret() == ChordMarker.FRET_MUTE) {
                    for (int i = cm.getStartString(); i <= cm.getEndString(); i++) {
                        mutedStrings.add(i);
                    }
                }
            }
            return mutedStrings;
        }

        //Returns just single notes on the string, not notes that are part of a bar
        public List<ChordMarker> getNotesOnString(int string) {
            List<ChordMarker> stringNotes = new ArrayList<>();
            for (ChordMarker cm : notes) {
                if (cm.getStartString() == string) {
                    stringNotes.add(cm);
                }
            }
            return stringNotes;
        }

        //Returns any notes on the specified string including barred notes
        public List<ChordMarker> getAllNotesOnString(int string) {
            List<ChordMarker> stringNotes = new ArrayList<>();
            for (ChordMarker cm : notes) {
                if (cm.getStartString() == string) {
                    stringNotes.add(cm);
                }
            }
            for (ChordMarker cm : bars) {
                for (int i = cm.getStartString(); i <= cm.getEndString(); i++) {
                    if (i == string) {
                        stringNotes.add(cm);
                    }
                }
            }
            return stringNotes;
        }

        //Returns any note on any string on the specified fret
        public List<ChordMarker> getNotesOnFret(int fret) {
            List<ChordMarker> fretNotes = new ArrayList<>();
            for (ChordMarker cm : notes) {
                if (cm.getFret() == fret) {
                    fretNotes.add(cm);
                }
            }
            for (ChordMarker cm : bars) {
                if (cm.getFret() == fret) {
                    fretNotes.add(cm);
                }
            }
            return fretNotes;
        }

        public ChordMarker getMarkerAt(int string, int fret) {
            ChordMarker marker = null;
            for (ChordMarker cm : notes) {
                if (cm.getStartString() == string && cm.getFret() == fret) {
                    marker = cm;
                    break;
                }
            }
            if (marker == null) {
                for (ChordMarker cm : bars) {
                    if (cm.getFret() == fret) {
                        for (int i = cm.getStartString(); i <= cm.getEndString(); i++) {
                            if (string == i) {
                                marker = cm;
                                break;
                            }
                        }
                    }
                }
            }
            return marker;
        }

        public int getFret(int string) {
            //returns the highest fret marked on the given string or 0, as in open, if none is marked
            int fret = 0;
            List<ChordMarker> markers = getAllNotesOnString(string);
            if (markers != null) {
                for (ChordMarker marker : markers) {
                    if (marker.getFret() > fret) {
                        fret = marker.getFret();
                    }
                }
            }
            return fret;
        }

        public List<Integer> getStrings(int fret) {
            //returns all the strings that are marked with this fret
            List<Integer> strings = new ArrayList<>();
            if (fret == FRET_OPEN) {
                return getOpenStrings();
            } else if (fret == FRET_MUTE) {
                return getMutedStrings();
            } else {
                List<ChordMarker> markers;
                for (int i = 0; i < getStringCount(); i++) {
                    markers = getAllNotesOnString(i + 1);
                    for (ChordMarker marker : markers) {
                        if (marker.getFret() == fret) {
                            strings.add(new Integer(marker.getFret()));
                        }
                    }
                }
            }
            return strings;
        }

        public String getTitle() {
            return title;
        }

        public void setTitle(String title) {
            this.title = title;
        }

        @Override
        public String toString() {
            return "Chord{" + "bars=" + bars + ", notes=" + notes + ", fretStart=" + fretStart + ", fretEnd="
                    + fretEnd + ", title='" + title + '\'' + '}';
        }

        @Override
        public boolean equals(Object o) {
            if (this == o)
                return true;
            if (o == null || getClass() != o.getClass())
                return false;

            Chord chord = (Chord) o;

            if (fretStart != chord.fretStart)
                return false;
            if (fretEnd != chord.fretEnd)
                return false;
            if (bars != null ? !bars.equals(chord.bars) : chord.bars != null)
                return false;
            if (notes != null ? !notes.equals(chord.notes) : chord.notes != null)
                return false;
            return !(title != null ? !title.equals(chord.title) : chord.title != null);
        }

        @Override
        public int hashCode() {
            int result = bars != null ? bars.hashCode() : 0;
            result = 31 * result + (notes != null ? notes.hashCode() : 0);
            result = 31 * result + fretStart;
            result = 31 * result + fretEnd;
            result = 31 * result + (title != null ? title.hashCode() : 0);
            return result;
        }

    }//End of Chord class

    public static class ChordMarker {
        //Should be used for the type field
        public static final String TYPE_BAR = "Bar";
        public static final String TYPE_NOTE = "Note";
        public static final String TYPE_MUTE = "Mute";
        public static final String TYPE_BAR_MUTE = "Bar_Mute";
        //Should be used for the string field
        public static final int FIRST_STRING = 1; //Corresponds to the high E string on a standard tuned guitar
        public static final int SECOND_STRING = 2;
        public static final int THIRD_STRING = 3;
        public static final int FOURTH_STRING = 4;
        public static final int FIFTH_STRING = 5;
        public static final int SIXTH_STRING = 6; //Corresponds to the low E string on a standard tuned guitar
        //Should be used for the finger field
        public static final int NO_FINGER = 0;
        public static final int INDEX_FINGER = 1;
        public static final int MIDDLE_FINGER = 2;
        public static final int RING_FINGER = 3;
        public static final int PINKY = 4;
        public static final int THUMB = 5;
        //Should be used for the fret field
        public static final int OPEN = 0;
        public static final int OPEN_FRET = OPEN;
        public static final int OPEN_STRING = OPEN;
        public static final int FRET_OPEN = OPEN;
        public static final int FRET_MUTE = -1;
        public static final int MAX_FRET_COUNT = 24;

        private String type;
        private int startString; //Inclusive
        private int endString; //Inclusive
        private int fret;
        private int finger;

        public ChordMarker(int string, int fret, int finger) {
            string = (string < FIRST_STRING) ? FIRST_STRING : string;
            string = (string > SIXTH_STRING) ? SIXTH_STRING : string;
            this.startString = string;
            this.endString = string;
            fret = (fret < FRET_MUTE) ? FRET_MUTE : fret;
            fret = (fret > MAX_FRET_COUNT) ? MAX_FRET_COUNT : fret;
            this.fret = fret;
            finger = (finger < NO_FINGER) ? NO_FINGER : finger;
            finger = (finger > THUMB) ? THUMB : finger;
            this.finger = finger;
            if (this.fret == FRET_MUTE) {
                this.type = TYPE_MUTE;
            } else {
                this.type = TYPE_NOTE;
            }
        }

        public ChordMarker(int startString, int endString, int fret, int finger) {
            startString = (startString < FIRST_STRING) ? FIRST_STRING : startString;
            startString = (startString > SIXTH_STRING) ? SIXTH_STRING : startString;
            this.startString = startString;
            endString = (endString < FIRST_STRING) ? FIRST_STRING : endString;
            endString = (endString > SIXTH_STRING) ? SIXTH_STRING : endString;
            endString = (endString < startString) ? startString : endString;
            this.endString = endString;
            fret = (fret < FRET_MUTE) ? FRET_MUTE : fret;
            fret = (fret > MAX_FRET_COUNT) ? MAX_FRET_COUNT : fret;
            this.fret = fret;
            finger = (finger < NO_FINGER) ? NO_FINGER : finger;
            finger = (finger > THUMB) ? THUMB : finger;
            this.finger = finger;
            if (this.startString == this.endString) {
                if (this.fret == FRET_MUTE) {
                    this.type = TYPE_MUTE;
                } else {
                    this.type = TYPE_NOTE;
                }
            } else {
                if (this.fret == FRET_MUTE) {
                    this.type = TYPE_BAR_MUTE;
                } else {
                    this.type = TYPE_BAR;
                }
            }
        }

        public ChordMarker(ChordMarker marker) {
            this.type = marker.getType();
            this.startString = marker.getStartString();
            this.endString = marker.getEndString();
            this.fret = marker.getFret();
            this.finger = marker.getFinger();
        }

        public ChordMarker(JSONObject obj) {
            this.type = null;
            this.startString = -1;
            this.endString = -1;
            this.fret = -1;
            this.finger = -1;
            fromJSON(obj);
        }

        public void fromJSON(JSONObject obj) {
            try {
                if (obj != null) {
                    if (obj.has("type")) {
                        this.type = obj.getString("type");
                    }
                    if (obj.has("startString")) {
                        this.startString = obj.getInt("startString");
                    }
                    if (obj.has("endString")) {
                        this.endString = obj.getInt("endString");
                    }
                    if (obj.has("fret")) {
                        this.fret = obj.getInt("fret");
                    }
                    if (obj.has("finger")) {
                        this.finger = obj.getInt("finger");
                    }
                }
            } catch (Exception e) {
                e.printStackTrace();
            }
        }

        public JSONObject toJSON() {
            try {
                JSONObject obj = new JSONObject();
                if (type != null) {
                    obj.put("type", type);
                }
                obj.put("startString", startString);
                obj.put("endString", endString);
                obj.put("fret", fret);
                obj.put("finger", finger);
                return obj;
            } catch (Exception e) {
                e.printStackTrace();
            }
            return null;
        }

        public String toJSONString() {
            JSONObject obj = this.toJSON();
            if (obj != null) {
                return obj.toString();
            }
            return this.toString();
        }

        public String getType() {
            return type;
        }

        public int getStartString() {
            return startString;
        }

        public int getEndString() {
            return endString;
        }

        public int getFret() {
            return fret;
        }

        public int getFinger() {
            return finger;
        }

        @Override
        public String toString() {
            return "ChordMarker{" + "type='" + type + '\'' + ", startString=" + startString + ", endString="
                    + endString + ", fret=" + fret + ", finger=" + finger + '}';
        }

        @Override
        public boolean equals(Object o) {
            if (this == o)
                return true;
            if (o == null || getClass() != o.getClass())
                return false;

            ChordMarker that = (ChordMarker) o;

            if (startString != that.startString)
                return false;
            if (endString != that.endString)
                return false;
            if (fret != that.fret)
                return false;
            if (finger != that.finger)
                return false;
            return !(type != null ? !type.equals(that.type) : that.type != null);
        }

        @Override
        public int hashCode() {
            int result = type != null ? type.hashCode() : 0;
            result = 31 * result + startString;
            result = 31 * result + endString;
            result = 31 * result + fret;
            result = 31 * result + finger;
            return result;
        }

    }//End of ChordMarker class

    /**
     * Reference: http://stackoverflow.com/a/19980975/1478764
     * @author Carl Gunther
     * There are bugs with the LatinIME keyboard's generation of KEYCODE_DEL events
     * that this class addresses in various ways.  These bugs appear when the app
     * specifies TYPE_NULL, which is the only circumstance under which the app
     * can reasonably expect to receive key events for KEYCODE_DEL.
     *
     * This class is intended for use by a view that overrides
     * onCreateInputConnection() and specifies to the invoking IME that it wishes
     * to use the TYPE_NULL InputType.  This should cause key events to be returned
     * to the view.
     *
     */
    public static class InputConnectionAccomodatingLatinIMETypeNullIssues extends BaseInputConnection {

        //This holds the Editable text buffer that the LatinIME mistakenly *thinks*
        // that it is editing, even though the views that employ this class are
        // completely driven by key events.
        Editable myEditable = null;

        //Basic constructor
        public InputConnectionAccomodatingLatinIMETypeNullIssues(View targetView, boolean fullEditor) {
            super(targetView, fullEditor);
        }

        //This method is called by the IME whenever the view that returned an
        // instance of this class to the IME from its onCreateInputConnection()
        // gains focus.
        @Override
        public Editable getEditable() {
            //Some versions of the Google Keyboard (LatinIME) were delivered with a
            // bug that causes KEYCODE_DEL to no longer be generated once the number
            // of KEYCODE_DEL taps equals the number of other characters that have
            // been typed.  This bug was reported here as issue 62306.
            //
            // As of this writing (1/7/2014), it is fixed in the AOSP code, but that
            // fix has not yet been released.  Even when it is released, there will
            // be many devices having versions of the Google Keyboard that include the bug
            // in the wild for the indefinite future.  Therefore, a workaround is required.
            //
            //This is a workaround for that bug which just jams a single garbage character
            // into the internal buffer that the keyboard THINKS it is editing even
            // though we have specified TYPE_NULL which *should* cause LatinIME to
            // generate key events regardless of what is in that buffer.  We have other
            // code that attempts to ensure as the user edites that there is always
            // one character remaining.
            //
            // The problem arises because when this unseen buffer becomes empty, the IME
            // thinks that there is nothing left to delete, and therefore stops
            // generating KEYCODE_DEL events, even though the app may still be very
            // interested in receiving them.
            //
            //So, for example, if the user taps in ABCDE and then positions the
            // (app-based) cursor to the left of A and taps the backspace key three
            // times without any evident effect on the letters (because the app's own
            // UI code knows that there are no letters to the left of the
            // app-implemented cursor), and then moves the cursor to the right of the
            // E and hits backspace five times, then, after E and D have been deleted,
            // no more KEYCODE_DEL events will be generated by the IME because the
            // unseen buffer will have become empty from five letter key taps followed
            // by five backspace key taps (as the IME is unaware of the app-based cursor
            // movements performed by the user).
            //
            // In other words, if your app is processing KEYDOWN events itself, and
            // maintaining its own cursor and so on, and not telling the IME anything
            // about the user's cursor position, this buggy processing of the hidden
            // buffer will stop KEYCODE_DEL events when your app actually needs them -
            // in whatever Android releases incorporate this LatinIME bug.
            //
            // By creating this garbage characters in the Editable that is initially
            // returned to the IME here, we make the IME think that it still has
            // something to delete, which causes it to keep generating KEYCODE_DEL
            // events in response to backspace key presses.
            //
            // A specific keyboard version that I tested this on which HAS this
            // problem but does NOT have the "KEYCODE_DEL completely gone" (issue 42904)
            // problem that is addressed by the deleteSurroundingText() override below
            // (the two problems are not both present in a single version) is
            // 2.0.19123.914326a, tested running on a Nexus7 2012 tablet.
            // There may be other versions that have issue 62306.
            //
            // A specific keyboard version that I tested this on which does NOT have
            // this problem but DOES have the "KEYCODE_DEL completely gone" (issue
            // 42904) problem that is addressed by the deleteSurroundingText()
            // override below is 1.0.1800.776638, tested running on the Nexus10
            // tablet.  There may be other versions that also have issue 42904.
            //
            // The bug that this addresses was first introduced as of AOSP commit tag
            // 4.4_r0.9, and the next RELEASED Android version after that was
            // android-4.4_r1, which is the first release of Android 4.4.  So, 4.4 will
            // be the first Android version that would have included, in the original
            // RELEASED version, a Google Keyboard for which this bug was present.
            //
            // Note that this bug was introduced exactly at the point that the OTHER bug
            // (the one that is addressed in deleteSurroundingText(), below) was first
            // FIXED.
            //
            // Despite the fact that the above are the RELEASES associated with the bug,
            // the fact is that any 4.x Android release could have been upgraded by the
            // user to a later version of Google Keyboard than was present when the
            // release was originally installed to the device.  I have checked the
            // www.archive.org snapshots of the Google Keyboard listing page on the Google
            // Play store, and all released updates listed there (which go back to early
            // June of 2013) required Android 4.0 and up, so we can be pretty sure that
            // this bug is not present in any version earlier than 4.0 (ICS), which means
            // that we can limit this fix to API level 14 and up.  And once the LatinIME
            // problem is fixed, we can limit the scope of this workaround to end as of
            // the last release that included the problem, since we can assume that
            // users will not upgrade Google Keyboard to an EARLIER version than was
            // originally included in their Android release.
            //
            // The bug that this addresses was FIXED but NOT RELEASED as of this AOSP
            // commit:
            //https://android.googlesource.com/platform/packages/inputmethods/LatinIME/+
            // /b41bea65502ce7339665859d3c2c81b4a29194e4/java/src/com/android
            // /inputmethod/latin/LatinIME.java
            // so it can be assumed to affect all of KitKat released thus far
            // (up to 4.4.2), and could even affect beyond KitKat, although I fully
            // expect it to be incorporated into the next release *after* API level 19.
            //
            // When it IS released, this method should be changed to limit it to no
            // higher than API level 19 (assuming that the fix is released before API
            // level 20), just in order to limit the scope of this fix, since poking
            // 1024 characters into the Editable object returned here is of course a
            // kluge.  But right now the safest thing is just to not have an upper limit
            // on the application of this kluge, since the fix for the problem it
            // addresses has not yet been released (as of 1/7/2014).
            if (Build.VERSION.SDK_INT >= 14) {
                if (myEditable == null) {
                    myEditable = new EditableAccomodatingLatinIMETypeNullIssues(
                            EditableAccomodatingLatinIMETypeNullIssues.ONE_UNPROCESSED_CHARACTER);
                    Selection.setSelection(myEditable, 1);
                } else {
                    int myEditableLength = myEditable.length();
                    if (myEditableLength == 0) {
                        //I actually HAVE seen this be zero on the Nexus 10 with the keyboard
                        // that came with Android 4.4.2
                        // On the Nexus 10 4.4.2 if I tapped away from the view and then back to it, the
                        // myEditable would come back as null and I would create a new one.  This is also
                        // what happens on other devices (e.g., the Nexus 6 with 4.4.2,
                        // which has a slightly later version of the Google Keyboard).  But for the
                        // Nexus 10 4.4.2, the keyboard had a strange behavior
                        // when I tapped on the rack, and then tapped Done on the keyboard to close it,
                        // and then tapped on the rack AGAIN.  In THAT situation,
                        // the myEditable would NOT be set to NULL but its LENGTH would be ZERO.  So, I
                        // just append to it in that situation.
                        myEditable.append(EditableAccomodatingLatinIMETypeNullIssues.ONE_UNPROCESSED_CHARACTER);
                        Selection.setSelection(myEditable, 1);
                    }
                }
                return myEditable;
            } else {
                //Default behavior for keyboards that do not require any fix
                return super.getEditable();
            }
        }

        //This method is called INSTEAD of generating a KEYCODE_DEL event, by
        // versions of Latin IME that have the bug described in Issue 42904.
        @Override
        public boolean deleteSurroundingText(int beforeLength, int afterLength) {
            //If targetSdkVersion is set to anything AT or ABOVE API level 16
            // then for the GOOGLE KEYBOARD versions DELIVERED
            // with Android 4.1.x, 4.2.x or 4.3.x, NO KEYCODE_DEL EVENTS WILL BE
            // GENERATED BY THE GOOGLE KEYBOARD (LatinIME) EVEN when TYPE_NULL
            // is being returned as the InputType by your view from its
            // onCreateInputMethod() override, due to a BUG in THOSE VERSIONS.
            //
            // When TYPE_NULL is specified (as this entire class assumes is being done
            // by the views that use it, what WILL be generated INSTEAD of a KEYCODE_DEL
            // is a deleteSurroundingText(1,0) call.  So, by overriding this
            // deleteSurroundingText() method, we can fire the KEYDOWN/KEYUP events
            // ourselves for KEYCODE_DEL.  This provides a workaround for the bug.
            //
            // The specific AOSP RELEASES involved are 4.1.1_r1 (the very first 4.1
            // release) through 4.4_r0.8 (the release just prior to Android 4.4).
            // This means that all of KitKat should not have the bug and will not
            // need this workaround.
            //
            // Although 4.0.x (ICS) did not have this bug, it was possible to install
            // later versions of the keyboard as an app on anything running 4.0 and up,
            // so those versions are also potentially affected.
            //
            // The first version of separately-installable Google Keyboard shown on the
            // Google Play store site by www.archive.org is Version 1.0.1869.683049,
            // on June 6, 2013, and that version (and probably other, later ones)
            // already had this bug.
            //
            //Since this required at least 4.0 to install, I believe that the bug will
            // not be present on devices running versions of Android earlier than 4.0.
            //
            //AND, it should not be present on versions of Android at 4.4 and higher,
            // since users will not "upgrade" to a version of Google Keyboard that
            // is LOWER than the one they got installed with their version of Android
            // in the first place, and the bug will have been fixed as of the 4.4 release.
            //
            // The above scope of the bug is reflected in the test below, which limits
            // the application of the workaround to Android versions between 4.0.x and 4.3.x.
            //
            //UPDATE:  A popular third party keyboard was found that exhibits this same issue.  It
            // was not fixed at the same time as the Google Play keyboard, and so the bug in that case
            // is still in place beyond API LEVEL 19.  So, even though the Google Keyboard fixed this
            // as of level 19, we cannot take out the fix based on that version number.  And so I've
            // removed the test for an upper limit on the version; the fix will remain in place ad
            // infinitum - but only when TYPE_NULL is used, so it *should* be harmless even when
            // the keyboard does not have the problem...
            if ((Build.VERSION.SDK_INT >= 14) // && (Build.VERSION.SDK_INT < 19)
                    && (beforeLength == 1 && afterLength == 0)) {
                //Send Backspace key down and up events to replace the ones omitted
                // by the LatinIME keyboard.
                return super.sendKeyEvent(new KeyEvent(KeyEvent.ACTION_DOWN, KeyEvent.KEYCODE_DEL))
                        && super.sendKeyEvent(new KeyEvent(KeyEvent.ACTION_UP, KeyEvent.KEYCODE_DEL));
            } else {
                //Really, I can't see how this would be invoked, given that we're using
                // TYPE_NULL, for non-buggy versions, but in order to limit the impact
                // of this change as much as possible (i.e., to versions at and above 4.0)
                // I am using the original behavior here for non-affected versions.
                return super.deleteSurroundingText(beforeLength, afterLength);
            }
        }
    }

    //Reference: http://stackoverflow.com/a/19980975/1478764
    public static class EditableAccomodatingLatinIMETypeNullIssues extends SpannableStringBuilder {
        EditableAccomodatingLatinIMETypeNullIssues(CharSequence source) {
            super(source);
        }

        //This character must be ignored by your onKey() code.
        public static CharSequence ONE_UNPROCESSED_CHARACTER = "/";

        @Override
        public SpannableStringBuilder replace(final int spannableStringStart, final int spannableStringEnd,
                CharSequence replacementSequence, int replacementStart, int replacementEnd) {
            if (replacementEnd > replacementStart) {
                //In this case, there is something in the replacementSequence that the IME
                // is attempting to replace part of the editable with.
                //We don't really care about whatever might already be in the editable;
                // we only care about making sure that SOMETHING ends up in it,
                // so that the backspace key will continue to work.
                // So, start by zeroing out whatever is there to begin with.
                super.replace(0, length(), "", 0, 0);

                //We DO care about preserving the new stuff that is replacing the stuff in the
                // editable, because this stuff might be sent to us as a keydown event.  So, we
                // insert the new stuff (typically, a single character) into the now-empty editable,
                // and return the result to the caller.
                return super.replace(0, 0, replacementSequence, replacementStart, replacementEnd);
            } else if (spannableStringEnd > spannableStringStart) {
                //In this case, there is NOTHING in the replacementSequence, and something is
                // being replaced in the editable.
                // This is characteristic of a DELETION.
                // So, start by zeroing out whatever is being replaced in the editable.
                super.replace(0, length(), "", 0, 0);

                //And now, we will place our ONE_UNPROCESSED_CHARACTER into the editable buffer, and return it.
                return super.replace(0, 0, ONE_UNPROCESSED_CHARACTER, 0, 1);
            }

            // In this case, NOTHING is being replaced in the editable.  This code assumes that there
            // is already something there.  This assumption is probably OK because in our
            // InputConnectionAccomodatingLatinIMETypeNullIssues.getEditable() method
            // we PLACE a ONE_UNPROCESSED_CHARACTER into the newly-created buffer.  So if there
            // is nothing replacing the identified part
            // of the editable, and no part of the editable that is being replaced, then we just
            // leave whatever is in the editable ALONE,
            // and we can be confident that there will be SOMETHING there.  This call to super.replace()
            // in that case will be a no-op, except
            // for the value it returns.
            return super.replace(spannableStringStart, spannableStringEnd, replacementSequence, replacementStart,
                    replacementEnd);
        }
    }

}//End of GuitarChordView class