Android Open Source - SpunkyCharts Docking Panel Activity






From Project

Back to project page SpunkyCharts.

License

The source code is released under:

GNU General Public License

If you think the Android project SpunkyCharts listed in this page is inappropriate, such as containing malicious code/tools or violating the copyright, please email info at java2s dot com, thanks.

Java Source Code

package com.jogden.spunkycharts;
/* /*from   w  w w . j a  v a2 s . c o  m*/
Copyright (C) 2014 Jonathon Ogden     < jeog.dev@gmail.com >

This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.

This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
GNU General Public License for more details.

You should have received a copy of the GNU General Public License
along with this program.  If not, see http://www.gnu.org/licenses.
*/

import java.util.Collection;
import java.util.HashMap;
import java.util.HashSet;
import java.util.LinkedHashMap;
import java.util.Map;
import java.util.Set;

import android.app.ActionBar;
import android.app.Activity;
import android.app.Dialog;
import android.app.FragmentManager;
import android.app.FragmentTransaction;
import android.content.Context;
import android.content.DialogInterface;
import android.content.Intent;
import android.content.pm.PackageManager;
import android.content.res.Configuration;
import android.graphics.Color;
import android.os.Bundle;
import android.util.AttributeSet;
import android.util.DisplayMetrics;
import android.util.Log;
import android.view.Menu;
import android.view.MenuItem;
import android.view.MotionEvent;
import android.view.SubMenu;
import android.view.View;
import android.view.View.OnLayoutChangeListener;
import android.view.ViewTreeObserver;
import android.view.ViewTreeObserver.OnGlobalLayoutListener;
import android.view.WindowManager;
import android.view.View.OnClickListener;
import android.view.ViewGroup;
import android.view.ViewGroup.LayoutParams;
import android.view.Window;
import android.widget.Button;
import android.widget.LinearLayout;
import android.widget.RelativeLayout;
import android.widget.TextView;
import android.widget.Toast;

import com.jogden.spunkycharts.BaseChartFragmentA;
import com.jogden.spunkycharts.misc.BorderOverlay;
import com.jogden.spunkycharts.misc.Pair;
import com.jogden.spunkycharts.misc.TextInput;
import com.jogden.spunkycharts.traditionalchart.TraditionalChartFragment;

/** The place where all your charts will be displayed.
 *     It allows you to resize charts, drag-and-drop,
 *     slide off the screen to remove, slide to the top of the screen
 *     to hide, and stack charts on top of each other.
 * </p>
 *     It abstracts away chart type and any of the functionality     
 *     and/or graphical options you may want to add to your chart, 
 *     so you shouldn't have to worry about how this is implemented
 *     or interfaced.
 * </p>
 *     The interface for managing the various chart types, certain
 *     global settings. and a bridge to certain chart-type specific
 *     preferences is provided. There are a number of nested classes
 *     used to assist BaseChartFragmentA in chart movement,
 *     animation, and stacking as well as things like the Fragment
 *     Positioner, found in the action bar, that allows users a graphical
 *     interface for how charts should be positioned, depending on 
 *     screen size.
 * </p> 
 *     You also have the ability, by clicking 'Select Panel'
 *     in the main options menu to move side to side
 *     between up to 5 panels. (The number is somewhat arbitrary,
 *     in the future it may be increased or decreased.) This allows
 *     the panel being shifted away from to go into the background
 *     allowing its child fragments to pass through their life-cycle,
 *     stop streaming data, stop costly drawing, deallocate other
 *     resources, but be ready to be re-instantiated, with the same
 *     state when the user returns to that panel. (It's important to
 *     note each panel is it's own activity and behaves as such, except
 *     that when you return to a panel a new instance is not created,
 *     the old one just moves to the top of the back-stack.
 * </p>*
 */
public class DockingPanelActivity extends Activity 
{    
    private interface FragSizeCallback extends Runnable{
        public void setRawParams(
            float startX, float startY, float endX, float endY
            );
    }
    
    /* our 'side' panels */
    static public final class R2 extends DockingPanelActivity{
        static public final int panelIndx = 4;     
        /* get around the hidden base field */
        @Override
        public final int getPanelIndex(){
            return panelIndx;
        }
    };
    static public final class R1 extends DockingPanelActivity{
        static public final int panelIndx = 3;  
        /* get around the hidden base field */
        @Override
        public final int getPanelIndex(){
            return panelIndx;
        }
    };
    static public final int panelIndx = 2; /* this panel */       
    /* get around the hidden base field */
    public int getPanelIndex(){
        return panelIndx;
    }
    static public final class L1 extends DockingPanelActivity{
        static public final int panelIndx = 1; 
        /* get around the hidden base field */
        @Override
        public final int getPanelIndex(){
            return panelIndx;
        }
    };    
    static public final class L2 extends DockingPanelActivity{
        static public final int panelIndx = 0;     
        /* get around the hidden base field */
        @Override
        public final int getPanelIndex(){
            return panelIndx;
        }
    };

   
    static final int MIN_WIDTH = 100;
    static final int MIN_HEIGHT = 100;        
    static private final int MENU_PREFS = Menu.FIRST;
    static private final int MENU_POS_BOX = MENU_PREFS + 1;
    static private final int MENU_SUB_TYPE = MENU_POS_BOX +1;
    static private final int MENU_SUB_PANELS = MENU_SUB_TYPE + 1; 
    static private final int MENU_SUB_PANELS_L2 = MENU_SUB_PANELS + 1;
    static private final int MENU_SUB_PANELS_L1 = MENU_SUB_PANELS + 2;
    static private final int MENU_SUB_PANELS_C = MENU_SUB_PANELS + 3;
    static private final int MENU_SUB_PANELS_R1 = MENU_SUB_PANELS + 4;
    static private final int MENU_SUB_PANELS_R2 = MENU_SUB_PANELS + 5;
    
    private Class<?> _selectedChartType = 
        MainApplication.metaChartTypes.get("Traditional Chart"); 
        
    private final Map<
        String,Pair<BaseChartFragmentA,Boolean>
            >    _mstrChrtFrags= new LinkedHashMap<
                String,Pair<BaseChartFragmentA,Boolean>>();
    
    private final DockingPanelActivity _this = this;        
    private final FragmentManager _fragManager = 
        getFragmentManager();
    
    private LinearLayout _smblTabs = null;
    private RelativeLayout _mainPanel = null;
    private ActionBar _actionBar;    
    private TextInput _symbolInput = null;    
    private TextView _leftTab = null;
    private TextView _rightTab = null;
    
    private Configuration _effConfig = null; 
    
    private volatile BaseChartFragmentA _activeFrag = null;    
    private FragPositionClickListener fragPositionClickListener;
    
    private int _tabTxtSz = 
        ApplicationPreferences.getTabTextSize();
    private int _tabTxtClr = 
        ApplicationPreferences.getTabTextColor();
    
    /* be sure to copy this or multiple charts will refer to same */
    private RelativeLayout.LayoutParams _fragParams =
        new RelativeLayout.LayoutParams(
            RelativeLayout.LayoutParams.MATCH_PARENT,
            RelativeLayout.LayoutParams.MATCH_PARENT
            );
        
    /*|--------->         begin PUBLIC interface         <--------|*/    
    
    // TODO: synchronize access with BaseChartFrag handlers 
    public BaseChartFragmentA getActiveFrag()
    {
        return _activeFrag;
    }
    public void setActiveFrag(BaseChartFragmentA frag)
    {
        _activeFrag = frag;
    }    
    /* tag is unique, symbol doesn't have to be     */    
    public void addChartFragment(String symbol)
    {
        String tag = symbol + "-1";
        while( _mstrChrtFrags.containsKey(tag) ){
            String[] strs = tag.split("-");        
            int i = Integer.valueOf(strs[1]).intValue()+1;
            tag = strs[0] + "-" + String.valueOf(i);
        }    
        Bundle infoBundle = new Bundle();
        infoBundle.putString("symbol",symbol); 
        infoBundle.putString("uTag",tag);
        FragmentTransaction ft = _fragManager.beginTransaction(); 
        BaseChartFragmentA newFrag = null;
        try {
            newFrag = 
                (BaseChartFragmentA)_selectedChartType.newInstance();
        } catch (InstantiationException e) {
            e.printStackTrace();            
            newFrag = 
                (BaseChartFragmentA)new TraditionalChartFragment();            
        } catch (IllegalAccessException e) {
            e.printStackTrace();    
            newFrag = 
                (BaseChartFragmentA)new TraditionalChartFragment();            
        }                
        final ViewGroup tvRoot 
            = (ViewGroup)(_this.getLayoutInflater().inflate(
                R.layout.symbol_tab,_smblTabs,true
                ));  
        final BorderOverlay tv 
            = (BorderOverlay)(tvRoot.getChildAt(tvRoot.getChildCount()-1));
        tv.setText(symbol);
        tv.setTag(tag);
        tv.setTextSize(_tabTxtSz);
        tv.setTextColor(_tabTxtClr);
        //
        tv.setBackgroundColor(Color.WHITE);
        //
        tv.setOnTouchListener(new SymbolTabTouchListener(newFrag));        
        try{ /* try run synchronously to get most recent params */
            fragSizeCallback.run();
        }catch(IllegalStateException e){            
        }
        newFrag.setLayoutParams( copyParams(_fragParams) );
        newFrag.setArguments(infoBundle);    
        newFrag.setTabView(tv);   
        ft.add(R.id.main_center_body,newFrag,symbol).commit();    
        _mstrChrtFrags.put( 
            tag, new Pair<BaseChartFragmentA,Boolean>(newFrag, false) 
            );                        
    }        
    public void removeChartFragment(String tag)
    {    
        final FragmentTransaction ft = 
            _fragManager.beginTransaction(); 
        final Pair<BaseChartFragmentA,Boolean> fp =
            _mstrChrtFrags.get(tag);     
        if (fp == null || fp.first == null)
            throw new IllegalStateException(
                "chart fragment doesn't exist in master mapping"
                );   
        final BaseChartFragmentA frag = fp.first;       
        final BorderOverlay tv = 
            (BorderOverlay)_smblTabs.findViewWithTag(tag);
        if( tv != null){            
            frag.releaseTabView(tv);
            _smblTabs.removeView(tv);
        }
        frag.popOffStackAndMove(true,0,0);
        ft.remove(frag);
        ft.commit();
        _mstrChrtFrags.remove(tag);
    }    
    public void minimizeChartFragment(String tag)
    {
        final Pair<BaseChartFragmentA,Boolean> fp =
            _mstrChrtFrags.get(tag);        
        if (fp == null || fp.first == null)
            throw new IllegalStateException(
                "chart fragment doesn't exist in master mapping"
                );   
        final BaseChartFragmentA frag = fp.first;
        frag.hideStack();    
        _mstrChrtFrags.put(
            tag, 
            new Pair<BaseChartFragmentA,Boolean>(frag,true) 
            );
    }    
    public void maximizeChartFragment(String tag)
    {        
        final Pair<BaseChartFragmentA,Boolean> fp =
            _mstrChrtFrags.get(tag);        
        if (fp == null || fp.first == null)
            throw new IllegalStateException(
                "chart fragment doesn't exist in master mapping"
                );   
        final BaseChartFragmentA frag = fp.first;
        frag.showStack();    
        _mstrChrtFrags.put(
            tag, 
            new Pair<BaseChartFragmentA,Boolean>(frag,false) 
            ); 
    }      
    // TODO: clean this up (who has a built-in copy member ?)
    public void resizeAndOrMove(
        RelativeLayout.LayoutParams lParams,     View view, 
        int newWidth, int newHeight, int shiftRight, int shiftDown
    ){ 
            lParams.leftMargin+=shiftRight; 
            lParams.rightMargin -= shiftRight;
            lParams.topMargin+=shiftDown; 
            lParams.bottomMargin -= shiftDown;
            if( newWidth > 0) {
                int mWidth = Math.max(newWidth,MIN_WIDTH);
                int rAdj = mWidth - lParams.width;
                lParams.width = mWidth;
                lParams.rightMargin -= rAdj;                
            } else if( newWidth == LayoutParams.WRAP_CONTENT) {
                lParams.width = newWidth;
            }     else if( newWidth == LayoutParams.MATCH_PARENT) {
                lParams.width = newWidth;
                lParams.leftMargin = lParams.rightMargin = 0;            
            }
            if( newHeight > 0) {
                int mHeight = Math.max(newHeight,MIN_HEIGHT);
                int bAdj = mHeight - lParams.height;
                lParams.height = mHeight;
                lParams.bottomMargin -= bAdj;                
            }     else if( newHeight == LayoutParams.WRAP_CONTENT ) {
                lParams.height = newHeight;
            }     else if(  newHeight == LayoutParams.MATCH_PARENT) {
                lParams.height = newHeight;
                lParams.topMargin = lParams.bottomMargin = 0;            
            }            
            view.setLayoutParams(lParams);    
    }    
    public int getPanelHeight()
    {
        return _mainPanel.getMeasuredHeight();
    }
    public int getPanelWidth()
    {
        return _mainPanel.getMeasuredWidth();
    }
    /* panel specific settings */
    public void setBackgroundColor(int colorId)
    {
        _mainPanel.setBackgroundColor(colorId);
    }
    public void setTabTextSize(int sp)
    {
        _tabTxtSz = sp;
        if(_smblTabs == null)
            return;
        int i = _smblTabs.getChildCount();
        while(i-- > 0)        
            ((TextView)_smblTabs.getChildAt(i)).setTextSize(sp);        
    }
    public void setTabTextColor(int colorId)
    {
        _tabTxtClr = colorId;
        if(_smblTabs == null)
            return;
        int i = _smblTabs.getChildCount();
        while(i-- > 0)        
            ((TextView)_smblTabs.getChildAt(i))
                .setTextColor( colorId );        
    }
    public void setSymbolBarColor(int colorId)
    {
        if(_leftTab != null)
            _leftTab.setBackgroundColor(colorId);
        if(_rightTab != null)
            _rightTab.setBackgroundColor(colorId);   
    }

    /* universal settings delegated to each frag instance */
    public void setSelectAnimation(String animName)
    {
        for( Pair<BaseChartFragmentA,Boolean> p 
                : _mstrChrtFrags.values())
            p.first.setSelectAnimation(animName);        
    }
    public void  resetAnimationPrefs()
    {
        for( Pair<BaseChartFragmentA,Boolean> p 
                : _mstrChrtFrags.values())
            p.first.resetAnimationPrefs();
    }
    public void setAxisFontSize(int sp)
    {
        for( Pair<BaseChartFragmentA,Boolean> p 
                : _mstrChrtFrags.values())
            p.first.setAxisFontSize(sp);
    }
    public void setAxisFontColor(int colorId)
    {
        for( Pair<BaseChartFragmentA,Boolean> p 
                : _mstrChrtFrags.values())
            p.first.setAxisFontColor(colorId);    
    }
    public void setAxisTimeout(int value)
    {
        for( Pair<BaseChartFragmentA,Boolean> p 
                : _mstrChrtFrags.values())
            p.first.setAxisTimeout(value);
    }
    public void setTimeoutIncrement(int value)
    {
        for( Pair<BaseChartFragmentA,Boolean> p 
                : _mstrChrtFrags.values())
            p.first.setTimeoutIncrement(value);
    }
    public void setXAxisPaddingSize(int pixSize)
    {
        for( Pair<BaseChartFragmentA,Boolean> p 
                : _mstrChrtFrags.values())
            p.first.setXAxisPaddingSize(pixSize);
    }
    public void refreshCharts()
    {
        for( Pair<BaseChartFragmentA,Boolean> p 
                : _mstrChrtFrags.values())
            p.first.refresh();
    }    
    static public RelativeLayout.LayoutParams copyParams( 
        RelativeLayout.LayoutParams lParams 
    ){
        RelativeLayout.LayoutParams lp = 
            new RelativeLayout.LayoutParams(
                lParams.width,lParams.height
                );
        lp.alignWithParent = lParams.alignWithParent;
        lp.bottomMargin = lParams.bottomMargin;
        lp.leftMargin = lParams.leftMargin;
        lp.rightMargin = lParams.rightMargin;
        lp.topMargin = lParams.topMargin;
        lp.layoutAnimationParameters = 
            lParams.layoutAnimationParameters;        
        return lp;
    }
    /*|--------->         end PUBLIC interface         <--------|*/
    /*|--------->         begin PRIVATE methods         <--------|*/    
    
    private void _loadAppPrefs()
    {
        setTabTextSize( 
            ApplicationPreferences.getTabTextSize() 
            );
        setTabTextColor( 
            ApplicationPreferences.getTabTextColor() 
            );
        setSymbolBarColor( 
            ApplicationPreferences.getSymbolBarColor()
            );  
        setBackgroundColor(
            ApplicationPreferences.getDockingPanelColor(panelIndx)
            );
    }    
    private boolean _isValidSymbol(String symbol)
    {      
        //
        //TODO: add logic for checking symbol input
        //        
        return true;        
    }            
    /* setup action bar */
    private void _initActionBar()
    {
        _actionBar = this.getActionBar();            
        _actionBar.setDisplayShowCustomEnabled(true);
        _actionBar.setDisplayShowHomeEnabled(false);
        _actionBar.setDisplayShowTitleEnabled(false);
        _actionBar.setCustomView(R.layout.action_bar);         
        View cView = _actionBar.getCustomView();
        _symbolInput = 
            (TextInput)cView.findViewById(R.id.symbol_input);
        _symbolInput.setOnClickListener( 
            new SymbolEntryClickListener() 
            );       
        _smblTabs = 
            (LinearLayout)cView.findViewById(R.id.symbol_header);  
    }
    
    /* when a tab is clicked (above) start a new DockingPanelActivity 
     * if it doesn't exist; if it does move it to the top of the back-stack */
    private void _startContiguousPanelActivity( int shift )
    {
        int nActivityIndx = MainApplication.availablePanels.indexOf(
                MainApplication.currentDockingPanelActivity
                );                    
        int sz = MainApplication.availablePanels.size();
        int modIndx = (nActivityIndx+shift) % sz;
        if( modIndx < 0) 
            modIndx+=sz;                    
        Class<?> nActivity = (Class<?>)(
            MainApplication.availablePanels.toArray()
            )[modIndx];       
        Intent nIntent = 
            new Intent(MainApplication.getInstance(),nActivity);
        nIntent.setFlags(
            Intent.FLAG_ACTIVITY_REORDER_TO_FRONT
            );
        _this.startActivity(nIntent);       
    }        
    
    /* called to adjust starting dimensions/position of frags */
    private final FragSizeCallback fragSizeCallback =        
        new FragSizeCallback(){
            private float startX, startY, endX, endY;
            private Thread callingThread;
            private Thread ourThread;
            @Override
            public void setRawParams(
                float startX, float startY,float endX, float endY
            ){                        
                this.startX =startX;
                this.startY = startY; 
                this.endX = endX;
                this.endY = endY;
                this.callingThread = Thread.currentThread();
            }
            @Override
            public void run() {
                int h,w;
                ourThread = Thread.currentThread();            
                while((h = _mainPanel.getHeight()) <= 0 || 
                    (w = _mainPanel.getWidth()) <= 0)
                        if( ourThread == callingThread)
                            throw new IllegalStateException(
                                "DockingPanelActivity exposes no valid params yet."
                                );
                        else                                    
                            try {
                                Thread.sleep(
                                    ApplicationPreferences.getTimeoutIncrement()
                                    );
                            } catch (InterruptedException e) {                            
                            }                            
                float adjLeft = startX * (float)w;
                float adjRight = (1-endX) * (float)w; 
                float adjTop = startY * (float)h;
                float adjBottom = (1 - endY) * (float)h;
                RelativeLayout.LayoutParams nlParams =
                    new RelativeLayout.LayoutParams(
                        (int)(w-adjLeft-adjRight),(int)(h-adjTop-adjBottom)
                        );
                nlParams.leftMargin = (int)adjLeft;
                nlParams.rightMargin = (int)adjRight;
                nlParams.topMargin = (int)adjTop;
                nlParams.bottomMargin = (int)adjBottom;
                _fragParams = nlParams;                            
            }                
        };        
        
    /*|--------->         end PRIVATE methods         <--------|*/
    /*|--------->     begin LIFE-CYCLE handlers     <--------|*/
    
    @Override
    protected void onCreate(Bundle savedInstanceState) 
    {
        super.onCreate(savedInstanceState);         
        MainApplication.dockingPanels.add(this);  
        setContentView(R.layout.docking_panel); 
        _mainPanel = 
            (RelativeLayout)_this.findViewById(R.id.main_center_body);  
        _loadAppPrefs();        
        _mainPanel.setOnTouchListener( 
            new BackgroundTouchListener() 
            );        
        _initActionBar(); 
        fragPositionClickListener = 
            new FragPositionClickListener( 
                _this, fragSizeCallback 
                );
        _effConfig = new Configuration(getResources().getConfiguration());          
    }    
    
    @Override
    protected void onSaveInstanceState(Bundle savedState)
    {
        //
        // TODO: any state we need to persist ??
        //
    }    
    @Override
    public void onActivityResult(int request, int result, Intent i)
    {
        super.onActivityResult(request,result,i);    
    }    
    @Override
    protected void onStart()
    {
        super.onStart();
        synchronized( 
            MainApplication.currentDockingPanelMonitor  
            ){
                MainApplication.currentDockingPanel = _this;
                MainApplication.currentDockingPanelActivity = 
                    _this.getClass();
            }        
    }
    /* system wide prefs and local chart pref defs run through here */
    @Override
    public boolean onCreateOptionsMenu(Menu menu)
    {
       super.onCreateOptionsMenu(menu);                    
        int order = 1;
        for(String key : MainApplication.metaChartTypes.keySet())        
            menu.add(
                MENU_SUB_TYPE,
                MainApplication.metaChartTypes.get(key).hashCode(), 
                order++, key
                );    
        menu.add(0,MENU_POS_BOX,order++,R.string.pos_box);
        menu.add(0, MENU_PREFS,order,R.string.preferences);
        menu.setGroupCheckable(MENU_SUB_TYPE, true,true);       
        SubMenu sub = 
            menu.addSubMenu(
                0,MENU_SUB_PANELS,order,R.string.menu_sub_panels
                );
        sub.add(
            MENU_SUB_PANELS, MENU_SUB_PANELS_L2,
            Menu.NONE,R.string.panel_l2
            );
        sub.add(
            MENU_SUB_PANELS, MENU_SUB_PANELS_L1,
            Menu.NONE,R.string.panel_l1
            );
        sub.add(
            MENU_SUB_PANELS, MENU_SUB_PANELS_C,
            Menu.NONE,R.string.panel_cntr
            );
        sub.add(
            MENU_SUB_PANELS, MENU_SUB_PANELS_R1,
            Menu.NONE,R.string.panel_r1
            );
        sub.add(
            MENU_SUB_PANELS, MENU_SUB_PANELS_R2,
            Menu.NONE,R.string.panel_r2
            );
        sub.setGroupCheckable(MENU_SUB_PANELS,true,true);
        return true;
    }        
    @Override
    public boolean onPrepareOptionsMenu(Menu menu)
    {
        menu.findItem(    
            _selectedChartType.hashCode()
            ).setChecked(true);   
        int menuPanelId = 
            getPanelIndex() + 1 + MENU_SUB_PANELS;
        menu.findItem( menuPanelId ).setChecked(true);
        return true;
    }
    @Override
    public boolean onOptionsItemSelected(MenuItem item)
    {
        super.onOptionsItemSelected(item);
        switch(item.getGroupId())
        {
        case MENU_SUB_TYPE:
            for( Class<?> cls 
                : MainApplication.metaChartTypes.values() 
                )                    
                    if( cls.hashCode() == item.getItemId()){
                        _selectedChartType = cls;
                        return true;
                        }        
            break;
        case MENU_SUB_PANELS:
            int indx = item.getItemId() - MENU_SUB_PANELS - 1;
            _startContiguousPanelActivity(indx - getPanelIndex());
            return true;
        }
        switch(item.getItemId())
        {
        case MENU_PREFS:            
            this.startActivityForResult(
                new Intent(this, ApplicationPreferences.class),
                MainApplication.UPDATE_PREFS_REQ
                );
            return true;    
        case MENU_POS_BOX:                
            fragPositionClickListener.onClick(item.getActionView());   
            return true;
        }
        return false;
    }    
    @Override
    public void onResume()
    {
        super.onResume();        
    }    
    @Override
    public void onPause()
    {
        super.onPause();        
    }        
    @Override 
    public void onStop()
    {
        super.onStop();
        synchronized( 
            MainApplication.currentDockingPanelMonitor  
            ){
                if( MainApplication.currentDockingPanel == _this )
                    MainApplication.currentDockingPanel = null;        
                if( MainApplication.currentDockingPanelActivity 
                        == _this.getClass() 
                    ) MainApplication.currentDockingPanelActivity = null;
            }        
    }    
    @Override
    public void onDestroy()
    {
        MainApplication.dockingPanels.remove(this);
        super.onDestroy(); 
    }

    /* even though handling the config change directly is 
     * discouraged we want to avoid having to go through the 
     * costly re-instantiation of multiple chart fragments  */
    @Override
    public void onConfigurationChanged(Configuration config)
    {
        super.onConfigurationChanged(config);          
        fragPositionClickListener.resetLayout();
        
        final float oldPanelHeight = getPanelHeight();
        final float oldPanelWidth= getPanelWidth();    
        // issue in here if frag is removed before all this is completed
        for( final Pair<BaseChartFragmentA,Boolean> p 
            : _mstrChrtFrags.values()
            ){
                if(!p.second){             
                    RelativeLayout.LayoutParams rlParams =
                        (RelativeLayout.LayoutParams)
                            p.first.ourViewGroup.getLayoutParams();
                   
                    Bundle b = new Bundle();
                    b.putFloatArray(
                        "vals", 
                        new float[]{
                            oldPanelHeight,
                            oldPanelWidth,     
                            rlParams.topMargin, // new left
                            rlParams.leftMargin,  // new top
                            rlParams.bottomMargin, // new right
                            rlParams.rightMargin // new bottom                  
                            }
                        );
                    p.first.saveInstanceBundle(b, true);              
                    }
                }
        if(!_mstrChrtFrags.isEmpty())
                _mainPanel.addOnLayoutChangeListener(
                    new OnLayoutChangeListener(){
                        @Override
                        public void onLayoutChange(View v, int left, int top,
                            int right, int bottom, int oldLeft, int oldTop,
                            int oldRight, int oldBottom)
                        {                            
                            _mainPanel.removeOnLayoutChangeListener(this);                 
                            for( final Pair<BaseChartFragmentA,Boolean> p 
                                : _mstrChrtFrags.values()
                                ){
                                    if(!p.second){
                                        float[] bVals = 
                                            p.first.loadInstanceBundle().getFloatArray("vals");                                                                  
                                        RelativeLayout.LayoutParams lParams  = 
                                            (RelativeLayout.LayoutParams)
                                                p.first.ourViewGroup.getLayoutParams();
                                        float newPanelWidth = _mainPanel.getMeasuredWidth();                                    
                                        float newPanelHeight = _mainPanel.getMeasuredHeight();    
                                                                                
                                        /* this can cause problems: it equates port vs land
                                         * state and behavior  with width/height relative sizes;
                                         * seems necessary to avoid a new layout of the mainPanel
                                         * using an earlier "vals" float array(one intended for the 
                                         * previous layout)                                                                */
                                        boolean iAmPort= newPanelHeight > newPanelWidth;
                                        
                                        // if we haven't shifted 'orientation', exit
                                        if( (iAmPort && (_effConfig.orientation == 
                                               Configuration.ORIENTATION_PORTRAIT )) 
                                               || 
                                               ( !iAmPort && (_effConfig.orientation == 
                                               Configuration.ORIENTATION_LANDSCAPE))
                                               ) return;
                           
                                        float oldMax= Math.max(bVals[1],bVals[0]);
                                        float oldMin = Math.min(bVals[1],bVals[0]) ;
                                        float newPanelHAdj = 
                                            newPanelHeight / (iAmPort ? oldMax : oldMin);
                                        float newPanelWAdj = 
                                            newPanelWidth / (iAmPort? oldMin : oldMax);                                          
                          
                                        float nLeft =  bVals[2] * newPanelWAdj;
                                        float nTop = bVals[3] * newPanelHAdj;                            
                                        float nRight = bVals[4] * newPanelWAdj;
                                        float nBottom = bVals[5] * newPanelHAdj;  
                                        
                                        lParams.setMargins(
                                            (int)nLeft, (int)nTop, (int)nRight , (int)nBottom
                                            );                  
                                        
                                        lParams.height = (int)(newPanelHeight - nTop - nBottom);
                                        lParams.width = (int)(newPanelWidth - nLeft - nRight);                  
                            
                                        _effConfig = 
                                            new Configuration( getResources().getConfiguration() );
                                        
                                        p.first.ourViewGroup.setLayoutParams(lParams);
                                        p.first.ourViewGroup.requestLayout();                      
                                        p.first.refresh();           
                                    }
                                }
                            }
                        }
                    );                      
        }
    
    /*|--------->         end LIFE-CYCLE handlers         <--------|*/
    /*|--------->     begin NESTED OBJECT DEFS        <--------|*/    

    /* handles a click of the button part of the symbol input 
     * view located in the action bar.     */
    private class SymbolEntryClickListener 
        implements OnClickListener
    {    
        public void onClick(View v){             
            if(MainApplication.readyForData){
                String symbol = _symbolInput.getText().toString();            
                if( !_isValidSymbol(symbol) )
                    Toast.makeText(
                        _this, "INVALID SYMBOL", Toast.LENGTH_LONG
                        ).show();
                else        
                    addChartFragment(symbol);                
            }
        }            
    }
    
    /* handles touches and drags of the tabs placed in the 
     * 'tab-header' after a chart fragment is created. */
    private class SymbolTabTouchListener 
        implements View.OnTouchListener
    {
        private BaseChartFragmentA frag;        
    //    private volatile boolean hasMoved = false;
        public SymbolTabTouchListener(
            BaseChartFragmentA fragment
            ){
                frag = fragment;                
            }
        public boolean onTouch(View v, MotionEvent event)                        
        {
            int action = event.getAction();    
            switch(action)
            { 
            case(MotionEvent.ACTION_UP):      
                if( (event.getEventTime() - event.getDownTime()) 
                    > ApplicationPreferences.getChartHoldThreshold() 
                    )
                    frag.getLocalPreferencesDialog().show();      
                else if(frag.isMinimized() )            
                   maximizeChartFragment(frag.getUniqueTag());     
                else
                    minimizeChartFragment(frag.getUniqueTag());         
                 break;                                                        
            }                        
            return true;         
        }        
    }        
    
    /* handles a touch of the (non frag) background. Allows 
     *  chart frags to move to a new location from an old one(via 
     *  a drag or a tap), from the tab-holder(via a drag), or to 
     *  'pop' one from a virtual/actual stack(via a tap).  */
    private class BackgroundTouchListener 
        implements View.OnTouchListener
    {
        public boolean onTouch(View v, MotionEvent event)                        
        {        
            int action = event.getAction();        
            switch(action)
            {
            case(MotionEvent.ACTION_DOWN):                     
                if( _activeFrag != null) {                        
                    _activeFrag.popOffStackAndMove(
                        false, event.getX(), event.getY()
                        );                               
                    _activeFrag = null; 
                }
                break;
            case(MotionEvent.ACTION_MOVE):                
            case(MotionEvent.ACTION_CANCEL):                                
            case(MotionEvent.ACTION_UP):                
            }                        
            return true;
        }            
    }
    
    /* handles initial frag position and size depending on screen
     * size and user selection. Click the drawable in the Actionbar to 
     * open a dialog. The congruent, rectangular, highlighted boxes 
         selected will bethe relative shape and position of the next frag */
    @SuppressWarnings("serial")
    static private final class FragPositionClickListener 
        implements View.OnClickListener
    {
        static  final Set<HashSet<Integer>> validBoxes = 
            new HashSet< HashSet<Integer> >();
        static{ /* the valid combo of boxes */
            validBoxes.add( new HashSet<Integer>() {
                {add(1);add(2);}} );
            validBoxes.add( new HashSet<Integer>() {
                {add(1);add(3);}} );
            validBoxes.add( new HashSet<Integer>() {
                {add(2);add(4);}} );
            validBoxes.add( new HashSet<Integer>() {
                {add(3);add(4);}} );
            validBoxes.add( new HashSet<Integer>() {
                {add(3);add(5);}} );
            validBoxes.add( new HashSet<Integer>() {
                {add(4);add(6);}} );
            validBoxes.add( new HashSet<Integer>() {
                {add(5);add(6);}} );
            validBoxes.add( new HashSet<Integer>() {
                {add(1);add(3);add(5);}} );
            validBoxes.add( new HashSet<Integer>() {
                {add(2);add(4);add(6);}} );
            validBoxes.add( new HashSet<Integer>() {
                {add(1);add(2);add(3);add(4);}} );            
            validBoxes.add( new HashSet<Integer>() {
                {add(3);add(4);add(5);add(6);}} );    
            validBoxes.add( new HashSet<Integer>() {
                {add(1);add(2);add(3);add(4);add(5);add(6);}} );
        }            
        private Dialog fPosDialog;
        private ViewGroup fPosView;
        private DockingPanelActivity parentActivity;
        private FragSizeCallback  fragCallback;        
        private Map<
            Integer,Pair<Pair<Float,Float>,Pair<Float,Float>>
                > boxData = new HashMap<
                    Integer,Pair<Pair<Float,Float>,Pair<Float,Float>>>();     
        
        /* get layouts, setup dialog and click listeners, set the default 
         * box, and asynchronously callback to set the frag  dimensions */
        public FragPositionClickListener(
            DockingPanelActivity activity, FragSizeCallback fragSizeCallback
            ){
                parentActivity = activity;
                fragCallback = fragSizeCallback;
                fPosDialog = new Dialog(activity);                
                fPosDialog.requestWindowFeature(
                    Window.FEATURE_NO_TITLE
                    );                   
                fPosDialog.setOnDismissListener( 
                    new DialogDismissListener() 
                    );
                resetLayout();                
            }            
        
        public void resetLayout()
        {
            fPosDialog.setContentView(
                R.layout.chart_fragment_position
                );        
            fPosView = (ViewGroup)fPosDialog.findViewById(
                R.id.chart_frag_pos_lay
                );
            setBoxClickHandlers(fPosView);
            selectDefaultBox();
            (new Thread( (Runnable)fragCallback )).start();
        }

        /* display the dialog at ~ half the width and height of panel */        
        @Override
        public void onClick(View v) {                
            WindowManager.LayoutParams lParams =
                new WindowManager.LayoutParams();               
            lParams.copyFrom(
                fPosDialog.getWindow().getAttributes()
                );
            int h = parentActivity.getPanelHeight();
            int w = parentActivity.getPanelWidth();
            if( h > 0 && w > 0){
                lParams.width = w / 2;
                lParams.height = h / 2;
            }
            fPosDialog.show();        
            fPosDialog.getWindow().setAttributes(lParams);
        }    
        /* recursively set the click listeners on the sub-boxes */
        private void setBoxClickHandlers(View view)
        {
            if( ViewGroup.class.isInstance(view) )    
                for(int i = ((ViewGroup)view).getChildCount(); i > 0; --i)
                    setBoxClickHandlers(((ViewGroup)view).getChildAt(i-1));        
            else if( (view.getClass()).equals( Button.class) )                        
                view.setOnClickListener( new  View.OnClickListener(){            
                    @Override
                    public void onClick(View v) {                
                        handleClick(v,true);
                        }            
                    }
                );
        }
        /* toggle the boxes and store coordinates on a click */
        private void handleClick(View v, boolean toggle)
        {
            String[] prsd = ((String)v.getTag()).split("-"); 
            int boxIndx = Integer.valueOf( prsd[0] );            
            if(v.getAlpha() == 1f && toggle) {
                v.setAlpha(.5f);
                boxData.remove(boxIndx); 
            } else {
                v.setAlpha(1);                
                boxData.put(boxIndx, getBoxCoordinates(prsd));
            }
        }
        /* reset the boxes */
        private void clearBoxes(View view)
        {
            if( ViewGroup.class.isInstance(view) )    
                for(int i = ((ViewGroup)view).getChildCount(); i > 0; --i)
                    clearBoxes(((ViewGroup)view).getChildAt(i-1));        
            else if( (view.getClass()).equals( Button.class) ){
                String[] prsd = ((String)view.getTag()).split("-"); 
                int boxIndx = Integer.valueOf( prsd[0] );    
                view.setAlpha(.5f);
                boxData.remove(boxIndx); 
            }                    
        }
        /* select box #1 as default */
        private void selectDefaultBox()
        {
            Button b1 = (Button)fPosView.findViewById(
                R.id.chart_frag_pos_box1
                );
            b1.setAlpha(1);
            handleClick(b1,false);
            Pair<Pair<Float, Float>, Pair<Float, Float>> 
                b1Coord = getBoxCoordinates(
                    ((String)b1.getTag()).split("-")
                    );
            boxData.put(1, b1Coord);
            fragCallback.setRawParams(
                b1Coord.first.first, b1Coord.first.second,
                b1Coord.second.first, b1Coord.second.second
                );                        
        }
        /* turn coordinate strings  into valid relative coordinates */
        private Pair<Pair<Float,Float>,Pair<Float,Float>> 
        getBoxCoordinates(String[] parsedTag)
        {
            String[] startStr = parsedTag[1].split(",");
            String[] endStr = parsedTag[2].split(",");                    
            return new Pair<Pair<Float,Float>,Pair<Float,Float>>(
                new Pair<Float,Float>(
                    Float.valueOf(startStr[0]),Float.valueOf(startStr[1])
                    ),
                new Pair<Float,Float>(
                    Float.valueOf(endStr[0]),Float.valueOf(endStr[1])
                    )
                );            
        }
        /*run the box-coordinate processing algorithm when user 
         * dismisses the dialog:
         *     1) check the box(es) are valid (from our above set)
         *     2) look for min AND max, x AND y coordinates 
         *     3) pass the those relative values and callback
         * Note: we could eliminate the initial check(1), allowing the 
         * algorithm to clean up the users sloppy-ness in a marginally
         * intuitive way, but there's something to be said for forcing
         * the user to apply a modicum of common sense.                  */
        private final class DialogDismissListener 
            implements Dialog.OnDismissListener
        {
            @Override
            public void onDismiss(DialogInterface dialog) {
                Set<Integer> boxKeys = boxData.keySet();
                if(boxKeys.size() != 1 &&  !validBoxes.contains(boxKeys)){
                    Toast.makeText( 
                        parentActivity, 
                        "Invalid Boxes Selected (please form a square / rectangle)" ,    
                        Toast.LENGTH_LONG
                        ).show();            
                    clearBoxes(fPosView);
                    selectDefaultBox();                    
                } else    {                    
                    float minStartX = 1, minStartY = 1;
                    float maxEndX = 0, maxEndY = 0;
                    float startX, startY, endX, endY;
                    Collection<Pair<Pair<Float,Float>,Pair<Float,Float>>> 
                        boxCoordinates = boxData.values();
                    for( Pair< Pair<Float,Float>, Pair<Float,Float>> 
                        coord : boxCoordinates 
                        ){
                            startX = coord.first.first;
                            startY = coord.first.second;
                            endX = coord.second.first;
                            endY = coord.second.second;
                            if(startX < minStartX)     minStartX = startX;
                            if(startY < minStartY) minStartY = startY;
                            if(endX > maxEndX) maxEndX = endX;
                            if(endY > maxEndY) maxEndY = endY;
                        }
                    fragCallback.setRawParams(
                        minStartX,minStartY,maxEndX,maxEndY
                        );                    
                    fragCallback.run(); /*run the callback synchronously */
                }                
            }            
        }        
    }

}




Java Source Code List

com.jogden.spunkycharts.ApplicationPreferences.java
com.jogden.spunkycharts.BaseChartFragmentA.java
com.jogden.spunkycharts.ChartPanelSurfaceView.java
com.jogden.spunkycharts.DockingPanelActivity.java
com.jogden.spunkycharts.GlobalChartPreferences.java
com.jogden.spunkycharts.InitActivity.java
com.jogden.spunkycharts.MainApplication.java
com.jogden.spunkycharts.OpeningView.java
com.jogden.spunkycharts.animations.BaseAnimationA.java
com.jogden.spunkycharts.animations.BaseEntExAnimationA.java
com.jogden.spunkycharts.animations.BaseSelectAnimationA.java
com.jogden.spunkycharts.animations.HorizontalBulgeAnimation.java
com.jogden.spunkycharts.animations.HorizontalShakeAnimation.java
com.jogden.spunkycharts.animations.VerticalBulgeAnimation.java
com.jogden.spunkycharts.animations.VerticalShakeAnimation.java
com.jogden.spunkycharts.animations.WiggleAnimation.java
com.jogden.spunkycharts.data.DataClientLocalDebug.java
com.jogden.spunkycharts.data.DataContentService.java
com.jogden.spunkycharts.misc.BorderOverlay.java
com.jogden.spunkycharts.misc.ColorPaletteDialog.java
com.jogden.spunkycharts.misc.HideHorizontalLeftOverflowWrapper.java
com.jogden.spunkycharts.misc.OHLC.java
com.jogden.spunkycharts.misc.Pair.java
com.jogden.spunkycharts.misc.TextInput.java
com.jogden.spunkycharts.misc.Triple.java
com.jogden.spunkycharts.pricebyvolumechart.PriceByVolumeChartFragmentAdapter.java
com.jogden.spunkycharts.pricebyvolumechart.PriceByVolumeChartFragment.java
com.jogden.spunkycharts.pricebyvolumechart.PriceByVolumeChartPanel.java
com.jogden.spunkycharts.pricebyvolumechart.PriceByVolumeChartPreferences.java
com.jogden.spunkycharts.pricebyvolumechart.draw.DrawSemanticsA.java
com.jogden.spunkycharts.pricebyvolumechart.draw.DrawSemantics_SILO.java
com.jogden.spunkycharts.traditionalchart.InnerXAxis.java
com.jogden.spunkycharts.traditionalchart.TraditionalChartFragmentAdapter.java
com.jogden.spunkycharts.traditionalchart.TraditionalChartFragment.java
com.jogden.spunkycharts.traditionalchart.TraditionalChartPanel.java
com.jogden.spunkycharts.traditionalchart.TraditionalChartPreferences.java
com.jogden.spunkycharts.traditionalchart.XAxisTimeLabel.java
com.jogden.spunkycharts.traditionalchart.YAxisPriceLabel.java
com.jogden.spunkycharts.traditionalchart.draw.DrawSemanticsA_C.java
com.jogden.spunkycharts.traditionalchart.draw.DrawSemanticsA.java
com.jogden.spunkycharts.traditionalchart.draw.DrawSemantics_CANDLE.java
com.jogden.spunkycharts.traditionalchart.draw.DrawSemantics_LINE.java
com.jogden.spunkycharts.traditionalchart.draw.DrawSemantics_OC.java
com.jogden.spunkycharts.traditionalchart.draw.DrawSemantics_OHLC.java
com.jogden.spunkycharts.traditionalchart.draw.DrawSemantics_POINT.java
com.jogden.spunkycharts.traditionalchart.draw.DrawSemantics_SILO.java