Back to project page SpunkyCharts.
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.
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 */ } } } } }