/* * Copyright 2008-2013, ETH Zürich, Samuel Welten, Michael Kuhn, Tobias Langner, * Sandro Affentranger, Lukas Bossard, Michael Grob, Rahul Jain, * Dominic Langenegger, Sonia Mayor Alonso, Roger Odermatt, Tobias Schlueter, * Yannick Stucki, Sebastian Wendland, Samuel Zehnder, Samuel Zihlmann, * Samuel Zweifel * * This file is part of Jukefox. * * Jukefox 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 any later version. Jukefox 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 * Jukefox. If not, see <http://www.gnu.org/licenses/>. */ package ch.ethz.dcg.pancho3.view.overlays; import java.util.ArrayList; import java.util.Collection; import java.util.HashMap; import java.util.List; import java.util.Map; import android.os.Bundle; import android.view.ViewGroup.LayoutParams; import android.widget.CheckBox; import android.widget.CompoundButton; import android.widget.CompoundButton.OnCheckedChangeListener; import android.widget.SeekBar; import android.widget.SeekBar.OnSeekBarChangeListener; import android.widget.TableLayout; import android.widget.TableRow; import ch.ethz.dcg.jukefox.playmode.smartshuffle.agents.AgentManager; import ch.ethz.dcg.jukefox.playmode.smartshuffle.agents.AgentManager.AgentType; import ch.ethz.dcg.jukefox.playmode.smartshuffle.agents.IAgent; import ch.ethz.dcg.pancho3.R; public abstract class AbstractAgentsMenu extends JukefoxOverlayActivity { protected AgentManager agentManager; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); agentManager = AgentManager.initialize(collectionModel.getDbDataPortal(), songProvider, statisticsProvider); setContentView(R.layout.agentsmenu); registerButtonListeners(); createAgentBars(); } private void registerButtonListeners() { // Auto adjust weights CheckBox ckbAutoAdjustWeights = (CheckBox) findViewById(R.id.ckb_auto_adjust_agent_weights); ckbAutoAdjustWeights.setChecked(agentManager.isAutoAdjustAgentWeights()); ckbAutoAdjustWeights.setOnCheckedChangeListener(new OnCheckedChangeListener() { @Override public void onCheckedChanged(CompoundButton btn, boolean isChecked) { agentManager.setAutoAdjustAgentWeights(isChecked); } }); } private void createAgentBars() { // Find the grouped agent list Map<AgentType, List<IAgent>> agentsByType = new HashMap<AgentType, List<IAgent>>(); for (IAgent agent : agentManager.getAgents()) { AgentType type = agent.getAgentType(); if (!agentsByType.containsKey(type)) { agentsByType.put(type, new ArrayList<IAgent>()); } agentsByType.get(type).add(agent); } // Remove all old entries from the agents table TableLayout agentsTable = (TableLayout) findViewById(R.id.layout_agents); agentsTable.removeAllViews(); // Create the agent bars. The order here is the order they appear createAgentBars(AgentType.Suggested, agentsByType.get(AgentType.Suggested)); createAgentBars(AgentType.Top, agentsByType.get(AgentType.Top)); createAgentBars(AgentType.Repetition, agentsByType.get(AgentType.Repetition)); createAgentBars(AgentType.Random, agentsByType.get(AgentType.Random)); setSeekBarPositions(); } /** * Creates the agent bar(s) for the given agents of the type agentType. * * @param agentType * The type all the agents have in common * @param agents * The agents */ protected abstract void createAgentBars(AgentType agentType, List<IAgent> agents); /** * Creates a agent weight seek bar and adds the mapping to {@link #agentToSeekBar}. * * @param agent * The agent * @return The seek bar */ protected SeekBar createSeekBar() { SeekBar seekBar = new SeekBar(this); seekBar.setLayoutParams(new TableRow.LayoutParams(LayoutParams.MATCH_PARENT, LayoutParams.WRAP_CONTENT)); seekBar.setPadding(15, 0, 0, 0); seekBar.setOnSeekBarChangeListener(seekBarChangeListener); return seekBar; } /** * Returns the group title for the given agentType. * * @param agentType * @return */ protected String getGroupTitle(AgentType agentType) { switch (agentType) { case Random: return getString(R.string.agent_title_random); case Repetition: return getString(R.string.agent_title_repetition); case Suggested: return getString(R.string.agent_title_suggested); case Top: return getString(R.string.agent_title_top); default: assert false; return ""; } } /** * Fills the seek bar positions by the agent weights. */ protected abstract void setSeekBarPositions(); /** * Returns the exponent which is used in {@link #getSeekBarPositionForWeight(double, int)} and * {@link #getWeightFromSeekBarPosition(int, int)}. The bigger it is the more the weights get stretched. * * @return The exponent */ protected abstract double getSeekBarStretchFactor(); /** * Returns the seek bar position for the given weight. The position is calculated by the following formula, to have * more space for small agent weights since they are more likely. We also do a range adjustment: We map the weight * range [{@link AgentManager#MIN_AGENT_WEIGHT}, 1] to [0, maxPos]. * * <pre> * sf = {@link #getSeekBarStretchFactor()} * position(weight) = ((weight - {@link AgentManager#MIN_AGENT_WEIGHT}) * 1/(1 - {@link AgentManager#MIN_AGENT_WEIGHT}))<sup>1/sf</sup> * </pre> * * @param weight * The agents weight * @param maxPos * The maximum position of the seek bar */ protected int getSeekBarPositionForWeight(double weight, int maxPos) { double minWeight = AgentManager.MIN_AGENT_WEIGHT; // Ensure minimum constraint of weight weight = Math.max(weight, minWeight); // Calculate the position double adjustedWeight = (weight - minWeight) / (1 - minWeight); double pos = Math.pow(adjustedWeight, 1 / getSeekBarStretchFactor()); return (int) (pos * maxPos); } /** * This is the inverse function of {@link #getSeekBarPositionForWeight(double, int)} * * @param position * The seek bar position * @param maxPos * The maximum position of the seek bar * @return The weight */ private double getWeightFromSeekBarPosition(int position, int maxPos) { double pos = position / (double) maxPos; double adjustedWeight = Math.pow(pos, getSeekBarStretchFactor()); double minWeight = AgentManager.MIN_AGENT_WEIGHT; double weight = adjustedWeight * (1 - minWeight) + minWeight; return weight; } /** * @see #getSeekBarPositionForWeight(double, int) * * @param seekBar * The seek bar * @param weight * The agents weight */ protected void setSeekBarPosition(SeekBar seekBar, double weight) { seekBar.setProgress(getSeekBarPositionForWeight(weight, seekBar.getMax())); } /** * Returns the weight which represents the seek bar position. * * @see #getWeightFromSeekBarPosition(int, int) * * @param seekBar * The seek bar * @return The weight */ private double getWeightFromSeekBar(SeekBar seekBar) { return getWeightFromSeekBarPosition(seekBar.getProgress(), seekBar.getMax()); } /** * Returns the seek bars. * * @return The seek bars */ protected abstract Collection<SeekBar> getSeekBars(); /** * Handles the event, when the seek bar positions have changed. The weights should be passed to * {@link #agentManager}. * * @param weights * The seekBar-to-weight map */ protected abstract void onSeekBarsChanged(Map<SeekBar, Double> weights); /** * Event listener for the seeks, to adjust the other seeks, if one is changed. */ private final OnSeekBarChangeListener seekBarChangeListener = new OnSeekBarChangeListener() { private double oldWeight; private double otherWeightSum; private boolean hasChanged = false; @Override public void onStartTrackingTouch(SeekBar changedSeekBar) { hasChanged = true; oldWeight = getWeightFromSeekBar(changedSeekBar); otherWeightSum = 0.0d; for (SeekBar seekBar : getSeekBars()) { if (seekBar != changedSeekBar) { otherWeightSum += getWeightFromSeekBar(seekBar); } } } private Integer saveRound = 0; @Override public void onStopTrackingTouch(SeekBar changedSeekBar) { // Read out the weights final Collection<SeekBar> seekBars = getSeekBars(); final Map<SeekBar, Double> seekBarWeights = new HashMap<SeekBar, Double>(seekBars.size()); for (SeekBar seekBar : seekBars) { double weight = getWeightFromSeekBar(seekBar); seekBarWeights.put(seekBar, weight); } // Set them async final int ourSaveRound = ++saveRound; new Thread(new Runnable() { @Override public void run() { synchronized (saveRound) { if (ourSaveRound < saveRound) { return; } hasChanged = false; onSeekBarsChanged(seekBarWeights); if (!hasChanged) { // Reset the seek bar positions to the agentManager values (might be adjusted) setSeekBarPositions(); } } } }).start(); } /** * If a seek is changed by the user always adjust the other seeks, so that the weights sum up to 1 again. */ @Override public void onProgressChanged(SeekBar changedSeekBar, int progress, boolean fromUser) { if (!fromUser) { return; } // Calculate adjustment double newWeight = getWeightFromSeekBar(changedSeekBar); double adjustment = 1 + (oldWeight - newWeight) / otherWeightSum; // Adjust the other seek bars for (SeekBar seekBar : getSeekBars()) { if (seekBar != changedSeekBar) { setSeekBarPosition(seekBar, adjustment * getWeightFromSeekBar(seekBar)); } } // Prepare for the next call oldWeight = newWeight; otherWeightSum *= adjustment; } }; }