1/*
2 * Copyright 2012 AndroidPlot.com
3 *
4 *    Licensed under the Apache License, Version 2.0 (the "License");
5 *    you may not use this file except in compliance with the License.
6 *    You may obtain a copy of the License at
7 *
8 *        http://www.apache.org/licenses/LICENSE-2.0
9 *
10 *    Unless required by applicable law or agreed to in writing, software
11 *    distributed under the License is distributed on an "AS IS" BASIS,
12 *    WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 *    See the License for the specific language governing permissions and
14 *    limitations under the License.
15 */
16
17package com.androidplot;
18
19import android.content.Context;
20import android.graphics.*;
21import android.os.Build;
22import android.os.Looper;
23import android.util.AttributeSet;
24import android.util.Log;
25import android.view.View;
26import com.androidplot.exception.PlotRenderException;
27import com.androidplot.ui.*;
28import com.androidplot.ui.Formatter;
29import com.androidplot.ui.TextOrientationType;
30import com.androidplot.ui.widget.TextLabelWidget;
31import com.androidplot.ui.widget.Widget;
32import com.androidplot.ui.SeriesRenderer;
33import com.androidplot.util.Configurator;
34import com.androidplot.util.DisplayDimensions;
35import com.androidplot.util.PixelUtils;
36import com.androidplot.ui.XLayoutStyle;
37import com.androidplot.ui.YLayoutStyle;
38
39import java.util.*;
40
41/**
42 * Base class for all other Plot implementations..
43 */
44public abstract class Plot<SeriesType extends Series, FormatterType extends Formatter, RendererType extends SeriesRenderer>
45        extends View implements Resizable{
46    private static final String TAG = Plot.class.getName();
47    private static final String XML_ATTR_PREFIX      = "androidplot";
48
49    private static final String ATTR_TITLE           = "title";
50    private static final String ATTR_RENDER_MODE     = "renderMode";
51
52    public DisplayDimensions getDisplayDimensions() {
53        return displayDims;
54    }
55
56    public enum BorderStyle {
57        ROUNDED,
58        SQUARE,
59        NONE
60    }
61
62    /**
63     * The RenderMode used by a Plot to draw it's self onto the screen.  The RenderMode can be set
64     * in two ways.
65     *
66     * In an xml layout:
67     *
68     * <code>
69     * <com.androidplot.xy.XYPlot
70     * android:id="@+id/mySimpleXYPlot"
71     * android:layout_width="fill_parent"
72     * android:layout_height="fill_parent"
73     * title="@string/sxy_title"
74     * renderMode="useBackgroundThread"/>
75     * </code>
76     *
77     * Programatically:
78     *
79     * <code>
80     * XYPlot myPlot = new XYPlot(context "MyPlot", Plot.RenderMode.USE_MAIN_THREAD);
81     * </code>
82     *
83     * A Plot's  RenderMode cannot be changed after the plot has been initialized.
84     * @since 0.5.1
85     */
86    public enum RenderMode {
87        /**
88         * Use a second thread and an off-screen buffer to do drawing.  This is the preferred method
89         * of drawing dynamic data and static data that consists of a large number of points.  This mode
90         * provides more efficient CPU utilization at the cost of increased memory usage.  As of
91         * version 0.5.1 this is the default RenderMode.
92         *
93         * XML value: use_background_thread
94         * @since 0.5.1
95         */
96        USE_BACKGROUND_THREAD,
97
98        /**
99         * Do everything in the primary thread.  This is the preferred method of drawing static charts
100         * and dynamic data that consists of a small number of points. This mode uses less memory at
101         * the cost of poor CPU utilization.
102         *
103         * XML value: use_main_thread
104         * @since 0.5.1
105         */
106        USE_MAIN_THREAD
107    }
108    private BoxModel boxModel = new BoxModel(3, 3, 3, 3, 3, 3, 3, 3);
109    private BorderStyle borderStyle = Plot.BorderStyle.SQUARE;
110    private float borderRadiusX = 15;
111    private float borderRadiusY = 15;
112    private boolean drawBorderEnabled = true;
113    private Paint borderPaint;
114    private Paint backgroundPaint;
115    private LayoutManager layoutManager;
116    private TextLabelWidget titleWidget;
117    private DisplayDimensions displayDims = new DisplayDimensions();
118    private RenderMode renderMode = RenderMode.USE_MAIN_THREAD;
119    private final BufferedCanvas pingPong = new BufferedCanvas();
120
121    // used to get rid of flickering when drawing offScreenBitmap to the visible Canvas.
122    private final Object renderSynch = new Object();
123
124    /**
125     * Used for caching renderer instances.  Note that once a renderer is initialized it remains initialized
126     * for the life of the application; does not and should not be destroyed until the application exits.
127     */
128    private LinkedList<RendererType> renderers;
129
130    /**
131     * Associates lists series and formatter pairs with the class of the Renderer used to render them.
132     */
133    private LinkedHashMap<Class, SeriesAndFormatterList<SeriesType,FormatterType>> seriesRegistry;
134
135    private final ArrayList<PlotListener> listeners;
136
137    private Thread renderThread;
138    private boolean keepRunning = false;
139    private boolean isIdle = true;
140
141    {
142        listeners = new ArrayList<PlotListener>();
143        seriesRegistry = new LinkedHashMap<Class, SeriesAndFormatterList<SeriesType,FormatterType>>();
144        renderers = new LinkedList<RendererType>();
145        borderPaint = new Paint();
146        borderPaint.setColor(Color.rgb(150, 150, 150));
147        borderPaint.setStyle(Paint.Style.STROKE);
148        borderPaint.setStrokeWidth(1.0f);
149        borderPaint.setAntiAlias(true);
150        backgroundPaint = new Paint();
151        backgroundPaint.setColor(Color.DKGRAY);
152        backgroundPaint.setStyle(Paint.Style.FILL);
153    }
154
155
156    /**
157     *  Any rendering that utilizes a buffer from this class should synchronize rendering on the instance of this class
158     *  that is being used.
159     */
160    private class BufferedCanvas {
161        private volatile Bitmap bgBuffer;  // all drawing is done on this buffer.
162        private volatile Bitmap fgBuffer;
163        private Canvas canvas = new Canvas();
164
165        /**
166         * Call this method once drawing on a Canvas retrieved by {@link #getCanvas()} to mark
167         * the buffer as fully rendered.  Failure to call this method will result in nothing being drawn.
168         */
169        public synchronized void swap() {
170            Bitmap tmp = bgBuffer;
171            bgBuffer = fgBuffer;
172            fgBuffer = tmp;
173        }
174
175        public synchronized void resize(int h, int w) {
176            if (w <= 0 || h <= 0) {
177                bgBuffer = null;
178                fgBuffer = null;
179            } else {
180                bgBuffer = Bitmap.createBitmap(w, h, Bitmap.Config.ARGB_4444);
181                fgBuffer = Bitmap.createBitmap(w, h, Bitmap.Config.ARGB_4444);
182            }
183        }
184
185
186        /**
187         * Get a Canvas for drawing.  Actual drawing should be synchronized on the instance
188         * of BufferedCanvas being used.
189         * @return The Canvas instance to draw onto.  Returns null if drawing buffers have not
190         *         been initialized a la {@link #resize(int, int)}.
191         */
192        public synchronized Canvas getCanvas() {
193            if(bgBuffer != null) {
194                canvas.setBitmap(bgBuffer);
195                return canvas;
196            } else {
197                return null;
198            }
199        }
200
201        /**
202         * @return The most recent fully rendered Bitmsp
203         */
204        public Bitmap getBitmap() {
205            return fgBuffer;
206        }
207    }
208
209    /**
210     * Convenience constructor - wraps {@link #Plot(android.content.Context, String, com.androidplot.Plot.RenderMode)}.
211     * RenderMode is set to {@link RenderMode#USE_BACKGROUND_THREAD}.
212     * @param context
213     * @param title The display title of this Plot.
214     */
215    public Plot(Context context, String title) {
216        this(context, title, RenderMode.USE_MAIN_THREAD);
217    }
218
219    /**
220     * Used for programmatic instantiation.
221     * @param context
222     * @param title The display title of this Plot.
223     */
224    public Plot(Context context, String title, RenderMode mode) {
225        super(context);
226        this.renderMode = mode;
227        init(null, null);
228        setTitle(title);
229    }
230
231
232    /**
233     * Required by super-class. Extending class' implementations should add
234     * the following code immediately before exiting to ensure that loadAttrs
235     * is called only once by the derived class:
236     * <code>
237     * if(getClass().equals(DerivedPlot.class) {
238     *     loadAttrs(context, attrs);
239     * }
240     * </code>
241     *
242     * See {@link com.androidplot.xy.XYPlot#XYPlot(android.content.Context, android.util.AttributeSet)}
243     * for an example.
244     * @param context
245     * @param attrs
246     */
247    public Plot(Context context, AttributeSet attrs) {
248        super(context, attrs);
249        init(context, attrs);
250    }
251
252    /**
253     * Required by super-class. Extending class' implementations should add
254     * the following code immediately before exiting to ensure that loadAttrs
255     * is called only once by the derived class:
256     * <code>
257     * if(getClass().equals(DerivedPlot.class) {
258     *     loadAttrs(context, attrs);
259     * }
260     * </code>
261     *
262     * See {@link com.androidplot.xy.XYPlot#XYPlot(android.content.Context, android.util.AttributeSet, int)}
263     * for an example.
264     * @param context
265     * @param attrs
266     * @param defStyle
267     */
268    public Plot(Context context, AttributeSet attrs, int defStyle) {
269        super(context, attrs, defStyle);
270        init(context, attrs);
271    }
272
273    /**
274     * Can be overridden by derived classes to control hardware acceleration state.
275     * Note that this setting is only used on Honeycomb and later environments.
276     * @return True if hardware acceleration is allowed, false otherwise.
277     * @since 0.5.1
278     */
279    @SuppressWarnings("BooleanMethodIsAlwaysInverted")
280    protected boolean isHwAccelerationSupported() {
281        return false;
282    }
283
284    /**
285     * Sets the render mode used by the Plot.
286     * WARNING: This method is not currently designed for general use outside of Configurator.
287     * Attempting to reassign the render mode at runtime will result in unexpected behavior.
288     * @param mode
289     */
290    public void setRenderMode(RenderMode mode) {
291        this.renderMode = mode;
292    }
293
294    /**
295     * Concrete implementations should do any final setup / initialization
296     * here.  Immediately following this method's invocation, AndroidPlot assumes
297     * that the Plot instance is ready for final configuration via the Configurator.
298     */
299    protected abstract void onPreInit();
300
301
302    private void init(Context context, AttributeSet attrs) {
303        PixelUtils.init(getContext());
304        layoutManager = new LayoutManager();
305        titleWidget = new TextLabelWidget(layoutManager, new SizeMetrics(25,
306                SizeLayoutType.ABSOLUTE, 100,
307                SizeLayoutType.ABSOLUTE),
308                TextOrientationType.HORIZONTAL);
309        titleWidget.position(0, XLayoutStyle.RELATIVE_TO_CENTER, 0,
310                YLayoutStyle.ABSOLUTE_FROM_TOP, AnchorPosition.TOP_MIDDLE);
311
312        onPreInit();
313        // make sure the title widget is always the topmost widget:
314        layoutManager.moveToTop(titleWidget);
315        if(context != null && attrs != null) {
316            loadAttrs(attrs);
317        }
318
319        layoutManager.onPostInit();
320        Log.d(TAG, "AndroidPlot RenderMode: " + renderMode);
321        if (renderMode == RenderMode.USE_BACKGROUND_THREAD) {
322            renderThread = new Thread(new Runnable() {
323                @Override
324                public void run() {
325
326                    keepRunning = true;
327                    while (keepRunning) {
328                        isIdle = false;
329                        synchronized (pingPong) {
330                            Canvas c = pingPong.getCanvas();
331                            renderOnCanvas(c);
332                            pingPong.swap();
333                        }
334                        synchronized (renderSynch) {
335                            postInvalidate();
336                            // prevent this thread from becoming an orphan
337                            // after the view is destroyed
338                            if (keepRunning) {
339                                try {
340                                    renderSynch.wait();
341                                } catch (InterruptedException e) {
342                                    keepRunning = false;
343                                }
344                            }
345                        }
346                    }
347                    System.out.println("AndroidPlot render thread finished.");
348                }
349            });
350        }
351    }
352
353    /**
354     * Parse XML Attributes.  Should only be called once and at the end of the base class constructor.
355     *
356     * @param attrs
357     */
358    private void loadAttrs(AttributeSet attrs) {
359
360        if (attrs != null) {
361            // filter out androidplot prefixed attrs:
362            HashMap<String, String> attrHash = new HashMap<String, String>();
363            for (int i = 0; i < attrs.getAttributeCount(); i++) {
364                String attrName = attrs.getAttributeName(i);
365
366                // case insensitive check to see if this attr begins with our prefix:
367                if (attrName.toUpperCase().startsWith(XML_ATTR_PREFIX.toUpperCase())) {
368                    attrHash.put(attrName.substring(XML_ATTR_PREFIX.length() + 1), attrs.getAttributeValue(i));
369                }
370            }
371            Configurator.configure(getContext(), this, attrHash);
372        }
373    }
374
375    public RenderMode getRenderMode() {
376        return renderMode;
377    }
378
379    public synchronized boolean addListener(PlotListener listener) {
380        return !listeners.contains(listener) && listeners.add(listener);
381    }
382
383    public synchronized boolean removeListener(PlotListener listener) {
384        return listeners.remove(listener);
385    }
386
387    protected void notifyListenersBeforeDraw(Canvas canvas) {
388        for (PlotListener listener : listeners) {
389            listener.onBeforeDraw(this, canvas);
390        }
391    }
392
393    protected void notifyListenersAfterDraw(Canvas canvas) {
394        for (PlotListener listener : listeners) {
395            listener.onAfterDraw(this, canvas);
396        }
397    }
398
399    /**
400     * @param series
401     */
402    public synchronized boolean addSeries(SeriesType series, FormatterType formatter) {
403        Class rendererClass = formatter.getRendererClass();
404        SeriesAndFormatterList<SeriesType, FormatterType> sfList = seriesRegistry.get(rendererClass);
405
406        // if there is no list for this renderer type, we need to getInstance one:
407        if(sfList == null) {
408            // make sure there is not already an instance of this renderer available:
409            if(getRenderer(rendererClass) == null) {
410                renderers.add((RendererType) formatter.getRendererInstance(this));
411            }
412            sfList = new SeriesAndFormatterList<SeriesType,FormatterType>();
413            seriesRegistry.put(rendererClass, sfList);
414        }
415
416        // if this series implements PlotListener, add it as a listener:
417        if(series instanceof PlotListener) {
418            addListener((PlotListener)series);
419        }
420
421        // do nothing if this series already associated with the renderer:
422        if(sfList.contains(series)) {
423            return false;
424        } else {
425            sfList.add(series, formatter);
426            return true;
427        }
428    }
429
430    public synchronized boolean removeSeries(SeriesType series, Class rendererClass) {
431        boolean result = seriesRegistry.get(rendererClass).remove(series);
432        if(seriesRegistry.get(rendererClass).size() <= 0) {
433            seriesRegistry.remove(rendererClass);
434        }
435
436        // if series implements PlotListener, remove it from listeners:
437        if(series instanceof PlotListener) {
438            removeListener((PlotListener) series);
439        }
440        return result;
441    }
442
443    /**
444     * Remove all occorrences of series from all renderers
445     * @param series
446     */
447    public synchronized void removeSeries(SeriesType series) {
448
449        // remove all occurrences of series from all renderers:
450        for(Class rendererClass : seriesRegistry.keySet()) {
451            seriesRegistry.get(rendererClass).remove(series);
452        }
453
454        // remove empty SeriesAndFormatterList instances from the registry:
455        for(Iterator<SeriesAndFormatterList<SeriesType,FormatterType>> it = seriesRegistry.values().iterator(); it.hasNext();) {
456            if(it.next().size() <= 0) {
457                it.remove();
458            }
459        }
460
461        // if series implements PlotListener, remove it from listeners:
462        if (series instanceof PlotListener) {
463            removeListener((PlotListener) series);
464        }
465    }
466
467    /**
468     * Remove all series from all renderers
469     */
470    public void clear() {
471        for(Iterator<SeriesAndFormatterList<SeriesType,FormatterType>> it = seriesRegistry.values().iterator(); it.hasNext();) {
472            it.next();
473            it.remove();
474        }
475    }
476
477    public boolean isEmpty() {
478        return seriesRegistry.isEmpty();
479    }
480
481    public FormatterType getFormatter(SeriesType series, Class rendererClass) {
482        return seriesRegistry.get(rendererClass).getFormatter(series);
483    }
484
485    public SeriesAndFormatterList<SeriesType,FormatterType> getSeriesAndFormatterListForRenderer(Class rendererClass) {
486        return seriesRegistry.get(rendererClass);
487    }
488
489    /**
490     * Returns a list of all series assigned to the various renderers within the Plot.
491     * The returned List will contain no duplicates.
492     * @return
493     */
494    public Set<SeriesType> getSeriesSet() {
495        Set<SeriesType> seriesSet = new LinkedHashSet<SeriesType>();
496        for (SeriesRenderer renderer : getRendererList()) {
497            List<SeriesType> seriesList = getSeriesListForRenderer(renderer.getClass());
498            if (seriesList != null) {
499                for (SeriesType series : seriesList) {
500                    seriesSet.add(series);
501                }
502            }
503        }
504        return seriesSet;
505    }
506
507    public List<SeriesType> getSeriesListForRenderer(Class rendererClass) {
508        SeriesAndFormatterList<SeriesType,FormatterType> lst = seriesRegistry.get(rendererClass);
509        if(lst == null) {
510            return null;
511        } else {
512            return lst.getSeriesList();
513        }
514    }
515
516    public RendererType getRenderer(Class rendererClass) {
517        for(RendererType renderer : renderers) {
518            if(renderer.getClass() == rendererClass) {
519                return renderer;
520            }
521        }
522        return null;
523    }
524
525    public List<RendererType> getRendererList() {
526        return renderers;
527    }
528
529    public void setMarkupEnabled(boolean enabled) {
530        this.layoutManager.setMarkupEnabled(enabled);
531    }
532
533    /**
534     * Causes the plot to be redrawn.
535     * @since 0.5.1
536     */
537    public void redraw() {
538
539        if (renderMode == RenderMode.USE_BACKGROUND_THREAD) {
540
541            // only enter synchronized block if the call is expected to block OR
542            // if the render thread is idle, so we know that we won't have to wait to
543            // obtain a lock.
544            if (isIdle) {
545                synchronized (renderSynch) {
546                    renderSynch.notify();
547                }
548            }
549        } else if(renderMode == RenderMode.USE_MAIN_THREAD) {
550
551            // are we on the UI thread?
552            if (Looper.myLooper() == Looper.getMainLooper()) {
553                invalidate();
554            } else {
555                postInvalidate();
556            }
557        } else {
558            throw new IllegalArgumentException("Unsupported Render Mode: " + renderMode);
559        }
560    }
561
562    @Override
563    public synchronized void layout(final DisplayDimensions dims) {
564        displayDims = dims;
565        layoutManager.layout(displayDims);
566    }
567
568    @Override
569    protected void onDetachedFromWindow() {
570        synchronized(renderSynch) {
571            keepRunning = false;
572            renderSynch.notify();
573        }
574    }
575
576
577    @Override
578    protected synchronized void onSizeChanged (int w, int h, int oldw, int oldh) {
579
580        // update pixel conversion values
581        PixelUtils.init(getContext());
582
583        // disable hardware acceleration if it's not explicitly supported
584        // by the current Plot implementation. this check only applies to
585        // honeycomb and later environments.
586        if (Build.VERSION.SDK_INT >= 11) {
587            if (!isHwAccelerationSupported() && isHardwareAccelerated()) {
588                setLayerType(View.LAYER_TYPE_SOFTWARE, null);
589            }
590        }
591
592        // pingPong is only used in background rendering mode.
593        if(renderMode == RenderMode.USE_BACKGROUND_THREAD) {
594            pingPong.resize(h, w);
595        }
596
597        RectF cRect = new RectF(0, 0, w, h);
598        RectF mRect = boxModel.getMarginatedRect(cRect);
599        RectF pRect = boxModel.getPaddedRect(mRect);
600
601        layout(new DisplayDimensions(cRect, mRect, pRect));
602        super.onSizeChanged(w, h, oldw, oldh);
603        if(renderThread != null && !renderThread.isAlive()) {
604            renderThread.start();
605        }
606    }
607
608    /**
609     * Called whenever the plot needs to be drawn via the Handler, which invokes invalidate().
610     * Should never be called directly; use {@link #redraw()} instead.
611     * @param canvas
612     */
613    @Override
614    protected void onDraw(Canvas canvas) {
615        if (renderMode == RenderMode.USE_BACKGROUND_THREAD) {
616            synchronized(pingPong) {
617                Bitmap bmp = pingPong.getBitmap();
618                if(bmp != null) {
619                    canvas.drawBitmap(bmp, 0, 0, null);
620                }
621            }
622        } else if (renderMode == RenderMode.USE_MAIN_THREAD) {
623            renderOnCanvas(canvas);
624        } else {
625            throw new IllegalArgumentException("Unsupported Render Mode: " + renderMode);
626        }
627    }
628
629    /**
630     * Renders the plot onto a canvas.  Used by both main thread to draw directly
631     * onto the View's canvas as well as by background draw to render onto a
632     * Bitmap buffer.  At the end of the day this is the main entry for a plot's
633     * "heavy lifting".
634     * @param canvas
635     */
636    protected synchronized void renderOnCanvas(Canvas canvas) {
637        try {
638            // any series interested in synchronizing with plot should
639            // implement PlotListener.onBeforeDraw(...) and do a read lock from within its
640            // invocation.  This is the entry point into that call:
641            notifyListenersBeforeDraw(canvas);
642            try {
643                // need to completely erase what was on the canvas before redrawing, otherwise
644                // some odd aliasing artifacts begin to build up around the edges of aa'd entities
645                // over time.
646                canvas.drawColor(Color.TRANSPARENT, PorterDuff.Mode.CLEAR);
647                if (backgroundPaint != null) {
648                    drawBackground(canvas, displayDims.marginatedRect);
649                }
650
651                layoutManager.draw(canvas);
652
653                if (getBorderPaint() != null) {
654                    drawBorder(canvas, displayDims.marginatedRect);
655                }
656            } catch (PlotRenderException e) {
657                Log.e(TAG, "Exception while rendering Plot.", e);
658                e.printStackTrace();
659            } catch (Exception e) {
660                Log.e(TAG, "Exception while rendering Plot.", e);
661            }
662        } finally {
663            isIdle = true;
664            // any series interested in synchronizing with plot should
665            // implement PlotListener.onAfterDraw(...) and do a read unlock from within that
666            // invocation. This is the entry point for that invocation.
667            notifyListenersAfterDraw(canvas);
668        }
669    }
670
671
672    /**
673     * Sets the visual style of the plot's border.
674     * @param style
675     * @param radiusX Sets the X radius for BorderStyle.ROUNDED.  Use null for all other styles.
676     * @param radiusY Sets the Y radius for BorderStyle.ROUNDED.  Use null for all other styles.
677     */
678    public void setBorderStyle(BorderStyle style, Float radiusX, Float radiusY) {
679        if (style == Plot.BorderStyle.ROUNDED) {
680            if (radiusX == null || radiusY == null){
681                throw new IllegalArgumentException("radiusX and radiusY cannot be null when using BorderStyle.ROUNDED");
682            }
683            this.borderRadiusX = radiusX;
684            this.borderRadiusY = radiusY;
685        }
686        this.borderStyle = style;
687    }
688
689    /**
690     * Draws the plot's outer border.
691     * @param canvas
692     * @throws PlotRenderException
693     */
694    protected void drawBorder(Canvas canvas, RectF dims) {
695        switch (borderStyle) {
696            case ROUNDED:
697                canvas.drawRoundRect(dims, borderRadiusX, borderRadiusY, borderPaint);
698                break;
699            case SQUARE:
700                canvas.drawRect(dims, borderPaint);
701                break;
702            default:
703        }
704    }
705
706    protected void drawBackground(Canvas canvas, RectF dims) {
707        switch (borderStyle) {
708            case ROUNDED:
709                canvas.drawRoundRect(dims, borderRadiusX, borderRadiusY, backgroundPaint);
710                break;
711            case SQUARE:
712                canvas.drawRect(dims, backgroundPaint);
713                break;
714            default:
715        }
716    }
717
718    /**
719     *
720     * @return The displayed title of this Plot.
721     */
722    public String getTitle() {
723        return getTitleWidget().getText();
724    }
725
726    /**
727     *
728     * @param title  The title to display on this Plot.
729     */
730    public void setTitle(String title) {
731        titleWidget.setText(title);
732    }
733
734    public LayoutManager getLayoutManager() {
735        return layoutManager;
736    }
737
738    public void setLayoutManager(LayoutManager layoutManager) {
739        this.layoutManager = layoutManager;
740    }
741
742    public TextLabelWidget getTitleWidget() {
743        return titleWidget;
744    }
745
746    public void setTitleWidget(TextLabelWidget titleWidget) {
747        this.titleWidget = titleWidget;
748    }
749
750    public Paint getBackgroundPaint() {
751        return backgroundPaint;
752    }
753
754    public void setBackgroundPaint(Paint backgroundPaint) {
755        this.backgroundPaint = backgroundPaint;
756    }
757
758    /**
759     * Convenience method - wraps the individual setMarginXXX methods into a single method.
760     * @param left
761     * @param top
762     * @param right
763     * @param bottom
764     */
765    public void setPlotMargins(float left, float top, float right, float bottom) {
766        setPlotMarginLeft(left);
767        setPlotMarginTop(top);
768        setPlotMarginRight(right);
769        setPlotMarginBottom(bottom);
770    }
771
772    /**
773     * Convenience method - wraps the individual setPaddingXXX methods into a single method.
774     * @param left
775     * @param top
776     * @param right
777     * @param bottom
778     */
779    public void setPlotPadding(float left, float top, float right, float bottom) {
780        setPlotPaddingLeft(left);
781        setPlotPaddingTop(top);
782        setPlotPaddingRight(right);
783        setPlotPaddingBottom(bottom);
784    }
785
786    public float getPlotMarginTop() {
787        return boxModel.getMarginTop();
788    }
789
790    public void setPlotMarginTop(float plotMarginTop) {
791        boxModel.setMarginTop(plotMarginTop);
792    }
793
794    public float getPlotMarginBottom() {
795        return boxModel.getMarginBottom();
796    }
797
798    public void setPlotMarginBottom(float plotMarginBottom) {
799        boxModel.setMarginBottom(plotMarginBottom);
800    }
801
802    public float getPlotMarginLeft() {
803        return boxModel.getMarginLeft();
804    }
805
806    public void setPlotMarginLeft(float plotMarginLeft) {
807        boxModel.setMarginLeft(plotMarginLeft);
808    }
809
810    public float getPlotMarginRight() {
811        return boxModel.getMarginRight();
812    }
813
814    public void setPlotMarginRight(float plotMarginRight) {
815        boxModel.setMarginRight(plotMarginRight);
816    }
817
818    public float getPlotPaddingTop() {
819        return boxModel.getPaddingTop();
820    }
821
822    public void setPlotPaddingTop(float plotPaddingTop) {
823        boxModel.setPaddingTop(plotPaddingTop);
824    }
825
826    public float getPlotPaddingBottom() {
827        return boxModel.getPaddingBottom();
828    }
829
830    public void setPlotPaddingBottom(float plotPaddingBottom) {
831        boxModel.setPaddingBottom(plotPaddingBottom);
832    }
833
834    public float getPlotPaddingLeft() {
835        return boxModel.getPaddingLeft();
836    }
837
838    public void setPlotPaddingLeft(float plotPaddingLeft) {
839        boxModel.setPaddingLeft(plotPaddingLeft);
840    }
841
842    public float getPlotPaddingRight() {
843        return boxModel.getPaddingRight();
844    }
845
846    public void setPlotPaddingRight(float plotPaddingRight) {
847        boxModel.setPaddingRight(plotPaddingRight);
848    }
849
850    public Paint getBorderPaint() {
851        return borderPaint;
852    }
853
854    /**
855     * Set's the paint used to draw the border.  Note that this method
856     * copies borderPaint and set's the copy's Paint.Style attribute to
857     * Paint.Style.STROKE.
858     * @param borderPaint
859     */
860    public void setBorderPaint(Paint borderPaint) {
861        if(borderPaint == null) {
862            this.borderPaint = null;
863        } else {
864            this.borderPaint = new Paint(borderPaint);
865            this.borderPaint.setStyle(Paint.Style.STROKE);
866        }
867    }
868}
869