org.totschnig.myexpenses.fragment.CategoryList.java Source code

Java tutorial

Introduction

Here is the source code for org.totschnig.myexpenses.fragment.CategoryList.java

Source

/*   This file is part of My Expenses.
 *   My Expenses 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.
 *
 *   My Expenses 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 My Expenses.  If not, see <http://www.gnu.org/licenses/>.
*/

package org.totschnig.myexpenses.fragment;

import android.app.SearchManager;
import android.content.Context;
import android.content.Intent;
import android.content.res.TypedArray;
import android.database.Cursor;
import android.graphics.Color;
import android.net.Uri.Builder;
import android.os.Bundle;
import android.support.v4.app.LoaderManager;
import android.support.v4.content.CursorLoader;
import android.support.v4.content.Loader;
import android.support.v4.view.MenuItemCompat;
import android.support.v7.app.ActionBar;
import android.support.v7.widget.SearchView;
import android.text.TextUtils;
import android.util.Log;
import android.util.SparseBooleanArray;
import android.util.TypedValue;
import android.view.ContextMenu;
import android.view.LayoutInflater;
import android.view.Menu;
import android.view.MenuInflater;
import android.view.MenuItem;
import android.view.View;
import android.view.ViewGroup;
import android.widget.AbsListView;
import android.widget.AdapterView;
import android.widget.AdapterView.OnItemSelectedListener;
import android.widget.ExpandableListView;
import android.widget.ExpandableListView.ExpandableListContextMenuInfo;
import android.widget.ExpandableListView.OnChildClickListener;
import android.widget.ExpandableListView.OnGroupClickListener;
import android.widget.ExpandableListView.OnGroupCollapseListener;
import android.widget.ExpandableListView.OnGroupExpandListener;
import android.widget.ListView;
import android.widget.TextView;
import android.widget.Toast;

import com.github.mikephil.charting.charts.PieChart;
import com.github.mikephil.charting.data.Entry;
import com.github.mikephil.charting.data.PieData;
import com.github.mikephil.charting.data.PieDataSet;
import com.github.mikephil.charting.interfaces.OnChartValueSelectedListener;
import com.github.mikephil.charting.utils.ColorTemplate;

import org.apache.commons.lang3.ArrayUtils;
import org.totschnig.myexpenses.MyApplication;
import org.totschnig.myexpenses.MyApplication.ThemeType;
import org.totschnig.myexpenses.R;
import org.totschnig.myexpenses.activity.ManageCategories;
import org.totschnig.myexpenses.activity.ManageCategories.HelpVariant;
import org.totschnig.myexpenses.activity.ProtectedFragmentActivity;
import org.totschnig.myexpenses.dialog.MessageDialogFragment;
import org.totschnig.myexpenses.dialog.SelectMainCategoryDialogFragment;
import org.totschnig.myexpenses.dialog.TransactionListDialogFragment;
import org.totschnig.myexpenses.model.Account;
import org.totschnig.myexpenses.model.Grouping;
import org.totschnig.myexpenses.model.Money;
import org.totschnig.myexpenses.preference.PrefKey;
import org.totschnig.myexpenses.provider.DatabaseConstants;
import org.totschnig.myexpenses.provider.DbUtils;
import org.totschnig.myexpenses.provider.TransactionProvider;
import org.totschnig.myexpenses.task.TaskExecutionFragment;
import org.totschnig.myexpenses.ui.SimpleCursorTreeAdapter;
import org.totschnig.myexpenses.util.Utils;

import java.util.ArrayList;
import java.util.Arrays;
import java.util.Locale;

import static org.totschnig.myexpenses.activity.AmountActivity.EXPENSE;
import static org.totschnig.myexpenses.provider.DatabaseConstants.DAY;
import static org.totschnig.myexpenses.provider.DatabaseConstants.KEY_ACCOUNTID;
import static org.totschnig.myexpenses.provider.DatabaseConstants.KEY_AMOUNT;
import static org.totschnig.myexpenses.provider.DatabaseConstants.KEY_CATID;
import static org.totschnig.myexpenses.provider.DatabaseConstants.KEY_CURRENCY;
import static org.totschnig.myexpenses.provider.DatabaseConstants.KEY_EXCLUDE_FROM_TOTALS;
import static org.totschnig.myexpenses.provider.DatabaseConstants.KEY_LABEL;
import static org.totschnig.myexpenses.provider.DatabaseConstants.KEY_LABEL_NORMALIZED;
import static org.totschnig.myexpenses.provider.DatabaseConstants.KEY_MAX_VALUE;
import static org.totschnig.myexpenses.provider.DatabaseConstants.KEY_PARENTID;
import static org.totschnig.myexpenses.provider.DatabaseConstants.KEY_ROWID;
import static org.totschnig.myexpenses.provider.DatabaseConstants.KEY_SUM;
import static org.totschnig.myexpenses.provider.DatabaseConstants.KEY_THIS_DAY;
import static org.totschnig.myexpenses.provider.DatabaseConstants.KEY_THIS_MONTH;
import static org.totschnig.myexpenses.provider.DatabaseConstants.KEY_THIS_WEEK;
import static org.totschnig.myexpenses.provider.DatabaseConstants.KEY_THIS_YEAR;
import static org.totschnig.myexpenses.provider.DatabaseConstants.KEY_THIS_YEAR_OF_WEEK_START;
import static org.totschnig.myexpenses.provider.DatabaseConstants.KEY_TYPE;
import static org.totschnig.myexpenses.provider.DatabaseConstants.TABLE_ACCOUNTS;
import static org.totschnig.myexpenses.provider.DatabaseConstants.TABLE_CATEGORIES;
import static org.totschnig.myexpenses.provider.DatabaseConstants.TABLE_TEMPLATES;
import static org.totschnig.myexpenses.provider.DatabaseConstants.TABLE_TRANSACTIONS;
import static org.totschnig.myexpenses.provider.DatabaseConstants.THIS_DAY;
import static org.totschnig.myexpenses.provider.DatabaseConstants.THIS_MONTH;
import static org.totschnig.myexpenses.provider.DatabaseConstants.THIS_YEAR;
import static org.totschnig.myexpenses.provider.DatabaseConstants.VIEW_COMMITTED;
import static org.totschnig.myexpenses.provider.DatabaseConstants.WHERE_NOT_VOID;
import static org.totschnig.myexpenses.provider.DatabaseConstants.YEAR;
import static org.totschnig.myexpenses.provider.DatabaseConstants.getMonth;
import static org.totschnig.myexpenses.provider.DatabaseConstants.getThisWeek;
import static org.totschnig.myexpenses.provider.DatabaseConstants.getThisYearOfWeekStart;
import static org.totschnig.myexpenses.provider.DatabaseConstants.getWeek;
import static org.totschnig.myexpenses.provider.DatabaseConstants.getYearOfMonthStart;
import static org.totschnig.myexpenses.provider.DatabaseConstants.getYearOfWeekStart;

public class CategoryList extends SortableListFragment
        implements OnChildClickListener, OnGroupClickListener, LoaderManager.LoaderCallbacks<Cursor> {

    private static final String KEY_CHILD_COUNT = "child_count";
    private View mImportButton;

    protected int getMenuResource() {
        return R.menu.categorylist_context;
    }

    private static final int SUM_CURSOR = -2;
    private static final int DATEINFO_CURSOR = -3;

    private MyExpandableListAdapter mAdapter;
    private ExpandableListView mListView;
    private LoaderManager mManager;
    private TextView incomeSumTv, expenseSumTv;
    private View bottomLine;
    private PieChart mChart;
    public Grouping mGrouping;
    int mGroupingYear;
    int mGroupingSecond;
    int thisYear, thisMonth, thisWeek, thisDay, maxValue;

    private Account mAccount;
    private Cursor mGroupCursor;

    protected boolean mType = EXPENSE;
    private ArrayList<Integer> mMainColors, mSubColors;
    private int lastExpandedPosition = -1;

    boolean showChart = false;
    boolean aggregateTypes;
    boolean chartDisplaysSubs;

    String mFilter;

    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setHasOptionsMenu(true);
    }

    @Override
    public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
        aggregateTypes = PrefKey.DISTRIBUTION_AGGREGATE_TYPES.getBoolean(true);
        final ManageCategories ctx = (ManageCategories) getActivity();
        View v;
        Bundle extras = ctx.getIntent().getExtras();
        mManager = getLoaderManager();
        if (ctx.helpVariant.equals(ManageCategories.HelpVariant.distribution)) {
            showChart = PrefKey.DISTRIBUTION_SHOW_CHART.getBoolean(true);
            mMainColors = new ArrayList<>();
            for (int col : ColorTemplate.PASTEL_COLORS)
                mMainColors.add(col);
            for (int col : ColorTemplate.JOYFUL_COLORS)
                mMainColors.add(col);
            for (int col : ColorTemplate.LIBERTY_COLORS)
                mMainColors.add(col);
            for (int col : ColorTemplate.VORDIPLOM_COLORS)
                mMainColors.add(col);
            for (int col : ColorTemplate.COLORFUL_COLORS)
                mMainColors.add(col);
            mMainColors.add(ColorTemplate.getHoloBlue());

            final long id = Utils.getFromExtra(extras, KEY_ACCOUNTID, 0);
            mAccount = Account.getInstanceFromDb(id);
            if (mAccount == null) {
                TextView tv = new TextView(ctx);
                //noinspection SetTextI18n
                tv.setText("Error loading distribution for account " + id);
                return tv;
            }
            Bundle b = savedInstanceState != null ? savedInstanceState : extras;

            mGrouping = (Grouping) b.getSerializable("grouping");
            if (mGrouping == null)
                mGrouping = Grouping.NONE;
            mGroupingYear = b.getInt("groupingYear");
            mGroupingSecond = b.getInt("groupingSecond");
            getActivity().supportInvalidateOptionsMenu();
            mManager.initLoader(SUM_CURSOR, null, this);
            mManager.initLoader(DATEINFO_CURSOR, null, this);
            v = inflater.inflate(R.layout.distribution_list, container, false);
            mChart = (PieChart) v.findViewById(R.id.chart1);
            mChart.setVisibility(showChart ? View.VISIBLE : View.GONE);
            mChart.setDescription("");

            //Typeface tf = Typeface.createFromAsset(getActivity().getAssets(), "OpenSans-Regular.ttf");

            //mChart.setValueTypeface(tf);
            //mChart.setCenterTextTypeface(Typeface.createFromAsset(getActivity().getAssets(), "OpenSans-Light.ttf"));
            //mChart.setUsePercentValues(true);
            //mChart.setCenterText("Quarterly\nRevenue");
            TypedValue typedValue = new TypedValue();
            getActivity().getTheme().resolveAttribute(android.R.attr.textAppearanceMedium, typedValue, true);
            int[] textSizeAttr = new int[] { android.R.attr.textSize };
            int indexOfAttrTextSize = 0;
            TypedArray a = getActivity().obtainStyledAttributes(typedValue.data, textSizeAttr);
            int textSize = a.getDimensionPixelSize(indexOfAttrTextSize, -1);
            a.recycle();
            mChart.setCenterTextSizePixels(textSize);

            // radius of the center hole in percent of maximum radius
            //mChart.setHoleRadius(60f); 
            //mChart.setTransparentCircleRadius(0f);
            mChart.setDrawSliceText(false);
            mChart.setDrawHoleEnabled(true);
            mChart.setDrawCenterText(true);
            mChart.setRotationEnabled(false);
            mChart.setOnChartValueSelectedListener(new OnChartValueSelectedListener() {

                @Override
                public void onValueSelected(Entry e, int dataSetIndex) {
                    int index = e.getXIndex();
                    long packedPosition = (lastExpandedPosition == -1)
                            ? ExpandableListView.getPackedPositionForGroup(index)
                            : ExpandableListView.getPackedPositionForChild(lastExpandedPosition, index);
                    int flatPosition = mListView.getFlatListPosition(packedPosition);
                    mListView.setItemChecked(flatPosition, true);
                    mListView.smoothScrollToPosition(flatPosition);
                    setCenterText(index);
                }

                @Override
                public void onNothingSelected() {
                    mListView.setItemChecked(mListView.getCheckedItemPosition(), false);
                }
            });
        } else {
            v = inflater.inflate(R.layout.categories_list, container, false);
            if (savedInstanceState != null) {
                mFilter = savedInstanceState.getString("filter");
            }
        }
        incomeSumTv = (TextView) v.findViewById(R.id.sum_income);
        expenseSumTv = (TextView) v.findViewById(R.id.sum_expense);
        bottomLine = v.findViewById(R.id.BottomLine);
        updateColor();
        mListView = (ExpandableListView) v.findViewById(R.id.list);
        final View emptyView = v.findViewById(R.id.empty);
        mListView.setEmptyView(emptyView);
        mImportButton = emptyView.findViewById(R.id.importButton);
        mManager.initLoader(SORTABLE_CURSOR, null, this);
        String[] from;
        int[] to;
        if (mAccount != null) {
            from = new String[] { KEY_LABEL, KEY_SUM };
            to = new int[] { R.id.label, R.id.amount };
        } else {
            from = new String[] { KEY_LABEL };
            to = new int[] { R.id.label };
        }
        mAdapter = new MyExpandableListAdapter(ctx, null, R.layout.category_row, R.layout.category_row, from, to,
                from, to);
        mListView.setAdapter(mAdapter);
        if (ctx.helpVariant.equals(ManageCategories.HelpVariant.distribution)) {
            mListView.setOnGroupExpandListener(new OnGroupExpandListener() {
                @Override
                public void onGroupExpand(int groupPosition) {
                    if (showChart) {
                        if (lastExpandedPosition != -1 && groupPosition != lastExpandedPosition) {
                            mListView.collapseGroup(lastExpandedPosition);
                        }
                    }
                    lastExpandedPosition = groupPosition;
                }
            });
            mListView.setOnGroupCollapseListener(new OnGroupCollapseListener() {
                @Override
                public void onGroupCollapse(int groupPosition) {
                    if (showChart) {
                        lastExpandedPosition = -1;
                        setData(mGroupCursor, mMainColors);
                        highlight(groupPosition);
                        long packedPosition = ExpandableListView.getPackedPositionForGroup(groupPosition);
                        int flatPosition = mListView.getFlatListPosition(packedPosition);
                        mListView.setItemChecked(flatPosition, true);
                    }
                }
            });
            mListView.setOnChildClickListener(new OnChildClickListener() {

                @Override
                public boolean onChildClick(ExpandableListView parent, View v, int groupPosition, int childPosition,
                        long id) {
                    if (showChart) {
                        long packedPosition = ExpandableListView.getPackedPositionForChild(groupPosition,
                                childPosition);
                        highlight(childPosition);
                        int flatPosition = mListView.getFlatListPosition(packedPosition);
                        mListView.setItemChecked(flatPosition, true);
                        return true;
                    }
                    return false;
                }
            });
            //the following is relevant when not in touch mode
            mListView.setOnItemSelectedListener(new OnItemSelectedListener() {

                @Override
                public void onItemSelected(AdapterView<?> parent, View view, int position, long id) {
                    if (showChart) {
                        long pos = mListView.getExpandableListPosition(position);
                        int type = ExpandableListView.getPackedPositionType(pos);
                        int group = ExpandableListView.getPackedPositionGroup(pos),
                                child = ExpandableListView.getPackedPositionChild(pos);
                        int highlightedPos;
                        if (type == ExpandableListView.PACKED_POSITION_TYPE_GROUP) {
                            if (lastExpandedPosition != group) {
                                mListView.collapseGroup(lastExpandedPosition);
                            }
                            highlightedPos = lastExpandedPosition == -1 ? group : -1;
                        } else {
                            highlightedPos = child;
                        }
                        highlight(highlightedPos);
                    }
                }

                @Override
                public void onNothingSelected(AdapterView<?> parent) {
                }
            });

            mListView.setChoiceMode(ListView.CHOICE_MODE_SINGLE);
            registerForContextMenu(mListView);
        } else {
            registerForContextualActionBar(mListView);
        }
        return v;
    }

    @Override
    public boolean dispatchCommandMultiple(int command, SparseBooleanArray positions, Long[] itemIds) {
        ManageCategories ctx = (ManageCategories) getActivity();
        ArrayList<Long> idList;
        switch (command) {
        case R.id.DELETE_COMMAND:
            int mappedTransactionsCount = 0, mappedTemplatesCount = 0, hasChildrenCount = 0;
            idList = new ArrayList<>();
            for (int i = 0; i < positions.size(); i++) {
                Cursor c;
                if (positions.valueAt(i)) {
                    boolean deletable = true;
                    int position = positions.keyAt(i);
                    long pos = mListView.getExpandableListPosition(position);
                    int type = ExpandableListView.getPackedPositionType(pos);
                    int group = ExpandableListView.getPackedPositionGroup(pos),
                            child = ExpandableListView.getPackedPositionChild(pos);
                    if (type == ExpandableListView.PACKED_POSITION_TYPE_CHILD) {
                        c = mAdapter.getChild(group, child);
                        c.moveToPosition(child);
                    } else {
                        c = mGroupCursor;
                        c.moveToPosition(group);
                    }
                    long itemId = c.getLong(c.getColumnIndex(KEY_ROWID));
                    Bundle extras = ctx.getIntent().getExtras();
                    if ((extras != null && extras.getLong(KEY_ROWID) == itemId)
                            || c.getInt(c.getColumnIndex(DatabaseConstants.KEY_MAPPED_TRANSACTIONS)) > 0) {
                        mappedTransactionsCount++;
                        deletable = false;
                    } else if (c.getInt(c.getColumnIndex(DatabaseConstants.KEY_MAPPED_TEMPLATES)) > 0) {
                        mappedTemplatesCount++;
                        deletable = false;
                    }
                    if (deletable) {
                        if (type == ExpandableListView.PACKED_POSITION_TYPE_GROUP
                                && c.getInt(c.getColumnIndex(KEY_CHILD_COUNT)) > 0) {
                            hasChildrenCount++;
                        }
                        idList.add(itemId);
                    }
                }
            }
            if (!idList.isEmpty()) {
                Long[] objectIds = idList.toArray(new Long[idList.size()]);
                if (hasChildrenCount > 0) {
                    MessageDialogFragment
                            .newInstance(R.string.dialog_title_warning_delete_main_category,
                                    getResources().getQuantityString(R.plurals.warning_delete_main_category,
                                            hasChildrenCount, hasChildrenCount),
                                    new MessageDialogFragment.Button(android.R.string.yes, R.id.DELETE_COMMAND_DO,
                                            objectIds),
                                    null,
                                    new MessageDialogFragment.Button(android.R.string.no,
                                            R.id.CANCEL_CALLBACK_COMMAND, null))
                            .show(ctx.getSupportFragmentManager(), "DELETE_CATEGORY");
                } else {
                    ctx.dispatchCommand(R.id.DELETE_COMMAND_DO, objectIds);
                }
            }
            if (mappedTransactionsCount > 0 || mappedTemplatesCount > 0) {
                String message = "";
                if (mappedTransactionsCount > 0)
                    message += getResources().getQuantityString(R.plurals.not_deletable_mapped_transactions,
                            mappedTransactionsCount, mappedTransactionsCount);
                if (mappedTemplatesCount > 0)
                    message += getResources().getQuantityString(R.plurals.not_deletable_mapped_templates,
                            mappedTemplatesCount, mappedTemplatesCount);
                Toast.makeText(getActivity(), message, Toast.LENGTH_LONG).show();
            }
            return true;
        case R.id.SELECT_COMMAND_MULTIPLE:
            ArrayList<String> labelList = new ArrayList<>();
            for (int i = 0; i < positions.size(); i++) {
                Cursor c;
                if (positions.valueAt(i)) {
                    int position = positions.keyAt(i);
                    long pos = mListView.getExpandableListPosition(position);
                    int type = ExpandableListView.getPackedPositionType(pos);
                    int group = ExpandableListView.getPackedPositionGroup(pos),
                            child = ExpandableListView.getPackedPositionChild(pos);
                    if (type == ExpandableListView.PACKED_POSITION_TYPE_CHILD) {
                        c = mAdapter.getChild(group, child);
                        c.moveToPosition(child);
                    } else {
                        c = mGroupCursor;
                        c.moveToPosition(group);
                    }
                    labelList.add(c.getString(c.getColumnIndex(KEY_LABEL)));
                }
            }
            Intent intent = new Intent();
            intent.putExtra(KEY_CATID, ArrayUtils.toPrimitive(itemIds));
            intent.putExtra(KEY_LABEL, TextUtils.join(",", labelList));
            ctx.setResult(ManageCategories.RESULT_FIRST_USER, intent);
            ctx.finish();
            return true;
        case R.id.MOVE_COMMAND:
            final Long[] excludedIds;
            final boolean inGroup = expandableListSelectionType == ExpandableListView.PACKED_POSITION_TYPE_GROUP;
            if (inGroup) {
                excludedIds = itemIds;
            } else {
                idList = new ArrayList<>();
                for (int i = 0; i < positions.size(); i++) {
                    if (positions.valueAt(i)) {
                        int position = positions.keyAt(i);
                        long pos = mListView.getExpandableListPosition(position);
                        int group = ExpandableListView.getPackedPositionGroup(pos);
                        mGroupCursor.moveToPosition(group);
                        idList.add(mGroupCursor.getLong(mGroupCursor.getColumnIndex(KEY_ROWID)));
                    }
                }
                excludedIds = idList.toArray(new Long[idList.size()]);
            }
            Bundle args = new Bundle(3);
            args.putBoolean(SelectMainCategoryDialogFragment.KEY_WITH_ROOT, !inGroup);
            args.putLongArray(SelectMainCategoryDialogFragment.KEY_EXCLUDED_ID,
                    ArrayUtils.toPrimitive(excludedIds));
            args.putLongArray(TaskExecutionFragment.KEY_OBJECT_IDS, ArrayUtils.toPrimitive(itemIds));
            SelectMainCategoryDialogFragment.newInstance(args).show(getFragmentManager(), "SELECT_TARGET");
            return true;
        }
        return false;
    }

    @Override
    public boolean dispatchCommandSingle(int command, ContextMenu.ContextMenuInfo info) {
        ManageCategories ctx = (ManageCategories) getActivity();
        ExpandableListContextMenuInfo elcmi = (ExpandableListContextMenuInfo) info;
        int type = ExpandableListView.getPackedPositionType(elcmi.packedPosition);
        Cursor c;
        boolean isMain;
        int group = ExpandableListView.getPackedPositionGroup(elcmi.packedPosition),
                child = ExpandableListView.getPackedPositionChild(elcmi.packedPosition);
        if (type == ExpandableListView.PACKED_POSITION_TYPE_CHILD) {
            c = mAdapter.getChild(group, child);
            isMain = false;
        } else {
            c = mGroupCursor;
            isMain = true;
        }
        if (c == null || c.getCount() == 0) {
            //observed on Blackberry Z10
            return false;
        }
        String label = c.getString(c.getColumnIndex(KEY_LABEL));
        switch (command) {
        case R.id.EDIT_COMMAND:
            ctx.editCat(label, elcmi.id);
            return true;
        case R.id.SELECT_COMMAND:
            if (!isMain && ctx.helpVariant.equals(ManageCategories.HelpVariant.select_mapping)) {
                mGroupCursor.moveToPosition(group);
                label = mGroupCursor.getString(mGroupCursor.getColumnIndex(KEY_LABEL))
                        + TransactionList.CATEGORY_SEPARATOR + label;
            }
            doSelection(elcmi.id, label, isMain);
            finishActionMode();
            return true;
        case R.id.CREATE_COMMAND:
            ctx.createCat(elcmi.id);
            return true;
        }
        return super.dispatchCommandSingle(command, info);
    }

    /**
     * Mapping the categories table into the ExpandableList
     *
     * @author Michael Totschnig
     */
    public class MyExpandableListAdapter extends SimpleCursorTreeAdapter {
        private int colorExpense;
        private int colorIncome;

        public MyExpandableListAdapter(Context context, Cursor cursor, int groupLayout, int childLayout,
                String[] groupFrom, int[] groupTo, String[] childrenFrom, int[] childrenTo) {
            super(context, cursor, groupLayout, groupFrom, groupTo, childLayout, childrenFrom, childrenTo);
            colorIncome = ((ProtectedFragmentActivity) context).getColorIncome();
            colorExpense = ((ProtectedFragmentActivity) context).getColorExpense();
        }

        /* (non-Javadoc)
         * returns a cursor with the subcategories for the group
         * @see android.widget.CursorTreeAdapter#getChildrenCursor(android.database.Cursor)
         */
        @Override
        protected Cursor getChildrenCursor(Cursor groupCursor) {
            // Given the group, we return a cursor for all the children within that group
            long parentId = groupCursor.getLong(groupCursor.getColumnIndexOrThrow(KEY_ROWID));
            Bundle bundle = new Bundle();
            bundle.putLong("parent_id", parentId);
            int groupPos = groupCursor.getPosition();
            if (mManager.getLoader(groupPos) != null && !mManager.getLoader(groupPos).isReset()) {
                try {
                    mManager.restartLoader(groupPos, bundle, CategoryList.this);
                } catch (NullPointerException e) {
                    // a NPE is thrown in the following scenario:
                    //1)open a group
                    //2)orientation change
                    //3)open the same group again
                    //in this scenario getChildrenCursor is called twice, second time leads to error
                    //maybe it is trying to close the group that had been kept open before the orientation change
                    e.printStackTrace();
                }
            } else {
                mManager.initLoader(groupPos, bundle, CategoryList.this);
            }
            return null;
        }

        @Override
        public void setViewText(TextView v, String text) {
            switch (v.getId()) {
            case R.id.amount:
                v.setTextColor(Long.valueOf(text) < 0 ? colorExpense : colorIncome);
                text = Utils.convAmount(text, mAccount.currency);
            }
            super.setViewText(v, text);
        }

        @Override
        public View getGroupView(int groupPosition, boolean isExpanded, View convertView, ViewGroup parent) {
            convertView = super.getGroupView(groupPosition, isExpanded, convertView, parent);
            View colorView = convertView.findViewById(R.id.color1);
            if (showChart) {
                colorView.setBackgroundColor(mMainColors.get(groupPosition % mMainColors.size()));
            } else {
                colorView.setVisibility(View.GONE);
            }
            return convertView;
        }

        @Override
        public View getChildView(int groupPosition, int childPosition, boolean isLastChild, View convertView,
                ViewGroup parent) {
            convertView = super.getChildView(groupPosition, childPosition, isLastChild, convertView, parent);
            View colorView = convertView.findViewById(R.id.color1);
            if (showChart) {
                colorView.setBackgroundColor(mSubColors.get(childPosition % mSubColors.size()));
            } else {
                colorView.setVisibility(View.GONE);
            }
            return convertView;
        }
    }

    private String buildGroupingClause() {
        String year = YEAR + " = " + mGroupingYear;
        switch (mGrouping) {
        case YEAR:
            return year;
        case DAY:
            return year + " AND " + DAY + " = " + mGroupingSecond;
        case WEEK:
            return getYearOfWeekStart() + " = " + mGroupingYear + " AND " + getWeek() + " = " + mGroupingSecond;
        case MONTH:
            return getYearOfMonthStart() + " = " + mGroupingYear + " AND " + getMonth() + " = " + mGroupingSecond;
        default:
            return null;
        }
    }

    @Override
    public Loader<Cursor> onCreateLoader(int id, Bundle bundle) {
        if (getActivity() == null)
            return null;
        if (id == SUM_CURSOR) {
            Builder builder = TransactionProvider.TRANSACTIONS_SUM_URI.buildUpon();
            if (mAccount.getId() < 0) {
                builder.appendQueryParameter(KEY_CURRENCY, mAccount.currency.getCurrencyCode());
            } else {
                builder.appendQueryParameter(KEY_ACCOUNTID, String.valueOf(mAccount.getId()));
            }
            return new CursorLoader(getActivity(), builder.build(), null, buildGroupingClause(), null, null);
        }
        if (id == DATEINFO_CURSOR) {
            ArrayList<String> projectionList = new ArrayList<>(
                    Arrays.asList(new String[] { getThisYearOfWeekStart() + " AS " + KEY_THIS_YEAR_OF_WEEK_START,
                            THIS_YEAR + " AS " + KEY_THIS_YEAR, THIS_MONTH + " AS " + KEY_THIS_MONTH,
                            getThisWeek() + " AS " + KEY_THIS_WEEK, THIS_DAY + " AS " + KEY_THIS_DAY }));
            //if we are at the beginning of the year we are interested in the max of the previous year
            int yearToLookUp = mGroupingSecond == 1 ? mGroupingYear - 1 : mGroupingYear;
            switch (mGrouping) {
            case DAY:
                projectionList.add(
                        String.format(Locale.US, "strftime('%%j','%d-12-31') AS " + KEY_MAX_VALUE, yearToLookUp));
                break;
            case WEEK:
                projectionList.add(
                        String.format(Locale.US, "strftime('%%W','%d-12-31') AS " + KEY_MAX_VALUE, yearToLookUp));
                break;
            case MONTH:
                projectionList.add("12 as " + KEY_MAX_VALUE);
                break;
            default:
                projectionList.add("0 as " + KEY_MAX_VALUE);
            }
            if (mGrouping.equals(Grouping.WEEK)) {
                //we want to find out the week range when we are given a week number
                //we find out the first Monday in the year, which is the beginning of week 1 and than
                //add (weekNumber-1)*7 days to get at the beginning of the week
                projectionList.add(DbUtils.weekStartFromGroupSqlExpression(mGroupingYear, mGroupingSecond));
                projectionList.add(DbUtils.weekEndFromGroupSqlExpression(mGroupingYear, mGroupingSecond));
            }
            return new CursorLoader(getActivity(), TransactionProvider.DUAL_URI,
                    projectionList.toArray(new String[projectionList.size()]), null, null, null);
        }
        //SORTABLE_CURSOR
        long parentId;
        String selection = "", accountSelector = "", sortOrder = null;
        String[] selectionArgs, projection;
        String CATTREE_WHERE_CLAUSE = KEY_CATID + " IN (SELECT " + TABLE_CATEGORIES + "." + KEY_ROWID
                + " UNION SELECT " + KEY_ROWID + " FROM " + TABLE_CATEGORIES + " subtree WHERE " + KEY_PARENTID
                + " = " + TABLE_CATEGORIES + "." + KEY_ROWID + ")";
        String CHILD_COUNT_SELECT = "(select count(*) FROM " + TABLE_CATEGORIES + " subtree where " + KEY_PARENTID
                + " = " + TABLE_CATEGORIES + "." + KEY_ROWID + ") as " + KEY_CHILD_COUNT;
        String catFilter;
        if (mAccount != null) {
            //Distribution
            String accountSelection;
            if (mAccount.getId() < 0) {
                accountSelection = " IN " + "(SELECT " + KEY_ROWID + " from " + TABLE_ACCOUNTS + " WHERE "
                        + KEY_CURRENCY + " = ? AND " + KEY_EXCLUDE_FROM_TOTALS + " = 0 )";
                accountSelector = mAccount.currency.getCurrencyCode();
            } else {
                accountSelection = " = ?";
                accountSelector = String.valueOf(mAccount.getId());
            }
            catFilter = "FROM " + VIEW_COMMITTED + " WHERE " + WHERE_NOT_VOID + " AND " + KEY_ACCOUNTID
                    + accountSelection;
            if (!aggregateTypes) {
                catFilter += " AND " + KEY_AMOUNT + (mType == EXPENSE ? "<" : ">") + "0";
            }
            if (!mGrouping.equals(Grouping.NONE)) {
                catFilter += " AND " + buildGroupingClause();
            }
            //we need to include transactions mapped to children for main categories
            if (bundle == null) {
                catFilter += " AND " + CATTREE_WHERE_CLAUSE;
            } else {
                catFilter += " AND " + KEY_CATID + "  = " + TABLE_CATEGORIES + "." + KEY_ROWID;
            }
            selection = " AND exists (SELECT 1 " + catFilter + ")";
            projection = new String[] { KEY_ROWID, KEY_LABEL, CHILD_COUNT_SELECT,
                    "(SELECT sum(amount) " + catFilter + ") AS " + KEY_SUM };
            sortOrder = "abs(" + KEY_SUM + ") DESC";
        } else {
            //manage, select
            if (bundle == null) {
                catFilter = CATTREE_WHERE_CLAUSE;
            } else {
                catFilter = KEY_CATID + "  = " + TABLE_CATEGORIES + "." + KEY_ROWID;
            }
            projection = new String[] { KEY_ROWID, KEY_LABEL, CHILD_COUNT_SELECT,
                    //here we do not filter out void transactinos since they need to be considered as mapped
                    "(select 1 FROM " + TABLE_TRANSACTIONS + " WHERE " + catFilter + ") AS "
                            + DatabaseConstants.KEY_MAPPED_TRANSACTIONS,
                    "(select 1 FROM " + TABLE_TEMPLATES + " WHERE " + catFilter + ") AS "
                            + DatabaseConstants.KEY_MAPPED_TEMPLATES };
        }
        boolean isFiltered = !TextUtils.isEmpty(mFilter);
        String filterSelection = KEY_LABEL_NORMALIZED + " LIKE ?";
        String[] filterSelectArgs = { "%" + mFilter + "%", "%" + mFilter + "%" };
        if (bundle == null) {
            //group cursor
            selection = KEY_PARENTID + " is null" + selection;
            if (isFiltered) {
                selection += " AND (" + filterSelection + " OR EXISTS (SELECT 1 FROM " + TABLE_CATEGORIES
                        + " subtree WHERE " + KEY_PARENTID + " = " + TABLE_CATEGORIES + "." + KEY_ROWID + " AND ("
                        + filterSelection + " )))";
            }
            selectionArgs = mAccount != null ? new String[] { accountSelector, accountSelector }
                    : (isFiltered ? filterSelectArgs : null);
        } else {
            //child cursor
            parentId = bundle.getLong(KEY_PARENTID);
            selection = KEY_PARENTID + " = ?" + selection;
            if (isFiltered) {
                selection += " AND (" + filterSelection + " OR (SELECT " + KEY_LABEL_NORMALIZED + " FROM "
                        + TABLE_CATEGORIES + " parent WHERE " + KEY_ROWID + " = " + TABLE_CATEGORIES + "."
                        + KEY_PARENTID + ") LIKE ?)";
            }
            selectionArgs = mAccount != null
                    ? new String[] { accountSelector, String.valueOf(parentId), accountSelector }
                    : (isFiltered ? Utils.joinArrays(new String[] { String.valueOf(parentId) }, filterSelectArgs)
                            : new String[] { String.valueOf(parentId) });
        }
        return new CursorLoader(getActivity(), TransactionProvider.CATEGORIES_URI, projection, selection,
                selectionArgs, sortOrder);
    }

    @Override
    public void onLoadFinished(Loader<Cursor> loader, Cursor c) {
        if (getActivity() == null)
            return;
        int id = loader.getId();
        ProtectedFragmentActivity ctx = (ProtectedFragmentActivity) getActivity();
        ActionBar actionBar = ctx.getSupportActionBar();
        switch (id) {
        case SUM_CURSOR:
            boolean[] seen = new boolean[2];
            c.moveToFirst();
            while (c.isAfterLast() == false) {
                int type = c.getInt(c.getColumnIndex(KEY_TYPE));
                updateSum(type > 0 ? "+ " : "- ", type > 0 ? incomeSumTv : expenseSumTv,
                        c.getLong(c.getColumnIndex(KEY_SUM)));
                c.moveToNext();
                seen[type] = true;
            }
            //if we have no income or expense, there is no row in the cursor
            if (!seen[1])
                updateSum("+ ", incomeSumTv, 0);
            if (!seen[0])
                updateSum("- ", expenseSumTv, 0);
            break;
        case DATEINFO_CURSOR:
            c.moveToFirst();
            actionBar.setSubtitle(mGrouping.getDisplayTitle(ctx, mGroupingYear, mGroupingSecond, c));
            thisYear = c.getInt(c.getColumnIndex(KEY_THIS_YEAR));
            thisMonth = c.getInt(c.getColumnIndex(KEY_THIS_MONTH));
            thisWeek = c.getInt(c.getColumnIndex(KEY_THIS_WEEK));
            thisDay = c.getInt(c.getColumnIndex(KEY_THIS_DAY));
            maxValue = c.getInt(c.getColumnIndex(KEY_MAX_VALUE));
            break;
        case SORTABLE_CURSOR:
            mGroupCursor = c;
            mAdapter.setGroupCursor(c);
            if (ctx.helpVariant.equals(ManageCategories.HelpVariant.distribution)) {
                if (c.getCount() > 0) {
                    if (lastExpandedPosition == -1) {
                        mChart.setVisibility(showChart ? View.VISIBLE : View.GONE);
                        setData(c, mMainColors);
                        highlight(0);
                        if (showChart)
                            mListView.setItemChecked(
                                    mListView.getFlatListPosition(ExpandableListView.getPackedPositionForGroup(0)),
                                    true);
                    }
                } else {
                    mChart.setVisibility(View.GONE);
                }
            }
            if (mAccount != null) {
                actionBar.setTitle(mAccount.label);
            }
            invalidateCAB(); //only need to do this for group since children's cab does not depnd on cursor
            break;
        default:
            //check if group still exists
            if (mAdapter.getGroupId(id) != 0) {
                mAdapter.setChildrenCursor(id, c);
                if (ctx.helpVariant.equals(ManageCategories.HelpVariant.distribution)) {
                    long packedPosition;
                    if (c.getCount() > 0) {
                        mSubColors = getSubColors(mMainColors.get(id % mMainColors.size()));
                        setData(c, mSubColors);
                        highlight(0);
                        packedPosition = ExpandableListView.getPackedPositionForChild(id, 0);
                    } else {
                        packedPosition = ExpandableListView.getPackedPositionForGroup(id);
                        if (!chartDisplaysSubs) {//check if a loader running concurrently has already switched to subs
                            highlight(id);
                        }
                    }
                    if (showChart && id == lastExpandedPosition) {
                        //if user has expanded a new group before loading is finished, getFlatListPosition
                        //would run in an NPE
                        mListView.setItemChecked(mListView.getFlatListPosition(packedPosition), true);
                    }
                }
            }
        }
    }

    @Override
    public void onLoaderReset(Loader<Cursor> loader) {
        int id = loader.getId();
        if (id == SORTABLE_CURSOR) {
            mGroupCursor = null;
            mAdapter.setGroupCursor(null);
        } else if (id > 0) {
            // child cursor
            try {
                mAdapter.setChildrenCursor(id, null);
            } catch (NullPointerException e) {
                Log.w("TAG", "Adapter expired, try again on the next query: " + e.getMessage());
            }
        }
    }

    @Override
    protected PrefKey getSortOrderPrefKey() {
        return PrefKey.SORT_ORDER_CATEGORIES;
    }

    @Override
    public void onCreateOptionsMenu(Menu menu, MenuInflater inflater) {
        ManageCategories ctx = (ManageCategories) getActivity();
        if (ctx == null)
            return;

        if (!ctx.helpVariant.equals(ManageCategories.HelpVariant.distribution)) {
            inflater.inflate(R.menu.search, menu);
            SearchManager searchManager = (SearchManager) getActivity().getSystemService(Context.SEARCH_SERVICE);
            MenuItem searchMenuItem = menu.findItem(R.id.search);
            SearchView searchView = (SearchView) MenuItemCompat.getActionView(searchMenuItem);

            searchView.setSearchableInfo(searchManager.getSearchableInfo(getActivity().getComponentName()));
            //searchView.setIconifiedByDefault(true);
            searchView.setOnQueryTextListener(new SearchView.OnQueryTextListener() {
                @Override
                public boolean onQueryTextSubmit(String query) {
                    return false;
                }

                @Override
                public boolean onQueryTextChange(String newText) {
                    if (TextUtils.isEmpty(newText)) {
                        mFilter = "";
                        mImportButton.setVisibility(View.VISIBLE);
                    } else {
                        mFilter = Utils.esacapeSqlLikeExpression(Utils.normalize(newText));
                        // if a filter results in an empty list,
                        // we do not want to show the setup default categories button
                        mImportButton.setVisibility(View.GONE);
                    }
                    collapseAll();
                    mManager.restartLoader(SORTABLE_CURSOR, null, CategoryList.this);
                    return true;
                }
            });
        }
    }

    @Override
    public void onPrepareOptionsMenu(Menu menu) {
        super.onPrepareOptionsMenu(menu);
        if (mGrouping != null) {
            Utils.configureGroupingMenu(menu.findItem(R.id.GROUPING_COMMAND).getSubMenu(), mGrouping);
            boolean grouped = !mGrouping.equals(Grouping.NONE);
            Utils.menuItemSetEnabledAndVisible(menu.findItem(R.id.FORWARD_COMMAND), grouped);
            Utils.menuItemSetEnabledAndVisible(menu.findItem(R.id.BACK_COMMAND), grouped);
        }
        MenuItem m = menu.findItem(R.id.TOGGLE_CHART_COMMAND);
        if (m != null) {
            m.setChecked(showChart);
        }
        m = menu.findItem(R.id.TOGGLE_AGGREGATE_TYPES);
        if (m != null) {
            m.setChecked(aggregateTypes);
            Utils.menuItemSetEnabledAndVisible(menu.findItem(R.id.switchId), !aggregateTypes);
        }

        MenuItem searchMenuItem = menu.findItem(R.id.search);
        if (searchMenuItem != null && mFilter != null) {
            SearchView searchView = (SearchView) MenuItemCompat.getActionView(searchMenuItem);
            searchView.setQuery(mFilter, false);
            searchView.setIconified(false);
            searchView.clearFocus();
        }
    }

    public void back() {
        if (mGrouping.equals(Grouping.YEAR))
            mGroupingYear--;
        else {
            mGroupingSecond--;
            if (mGroupingSecond < 1) {
                mGroupingYear--;
                mGroupingSecond = maxValue;
            }
        }
        reset();
    }

    public void forward() {
        if (mGrouping.equals(Grouping.YEAR))
            mGroupingYear++;
        else {
            mGroupingSecond++;
            if (mGroupingSecond > maxValue) {
                mGroupingYear++;
                mGroupingSecond = 1;
            }
        }
        reset();
    }

    @Override
    public boolean onOptionsItemSelected(MenuItem item) {
        if (handleGrouping(item))
            return true;
        switch (item.getItemId()) {
        case R.id.BACK_COMMAND:
            back();
            return true;
        case R.id.FORWARD_COMMAND:
            forward();
            return true;
        case R.id.TOGGLE_CHART_COMMAND:
            showChart = !showChart;
            PrefKey.DISTRIBUTION_SHOW_CHART.putBoolean(showChart);
            mChart.setVisibility(showChart ? View.VISIBLE : View.GONE);
            if (showChart) {
                collapseAll();
            } else {
                mListView.setItemChecked(mListView.getCheckedItemPosition(), false);
            }
            return true;
        case R.id.TOGGLE_AGGREGATE_TYPES:
            aggregateTypes = !aggregateTypes;
            PrefKey.DISTRIBUTION_AGGREGATE_TYPES.putBoolean(aggregateTypes);
            getActivity().supportInvalidateOptionsMenu();
            reset();
            return true;
        }
        return handleSortOption(item);
    }

    private boolean handleGrouping(MenuItem item) {
        Grouping newGrouping = Utils.getGroupingFromMenuItemId(item.getItemId());
        if (newGrouping != null) {
            if (!item.isChecked()) {
                setGrouping(newGrouping);
            }
            return true;
        }
        return false;
    }

    public void collapseAll() {
        int count = mAdapter.getGroupCount();
        for (int i = 0; i < count; i++)
            mListView.collapseGroup(i);
    }

    /*     (non-Javadoc)
     * return the sub cat to the calling activity
     * @see android.app.ExpandableListActivity#onChildClick(android.widget.ExpandableListView, android.view.View, int, int, long)
    */
    @Override
    public boolean onChildClick(ExpandableListView parent, View v, int groupPosition, int childPosition, long id) {
        if (super.onChildClick(parent, v, groupPosition, childPosition, id))
            return true;
        ManageCategories ctx = (ManageCategories) getActivity();
        if (ctx == null || ctx.helpVariant.equals(ManageCategories.HelpVariant.manage)) {
            return false;
        }
        String label = ((TextView) v.findViewById(R.id.label)).getText().toString();
        if (ctx.helpVariant.equals(ManageCategories.HelpVariant.select_mapping)) {
            mGroupCursor.moveToPosition(groupPosition);
            label = mGroupCursor.getString(mGroupCursor.getColumnIndex(KEY_LABEL))
                    + TransactionList.CATEGORY_SEPARATOR + label;
        }
        doSelection(id, label, false);
        return true;
    }

    @Override
    public boolean onGroupClick(ExpandableListView parent, View v, int groupPosition, long id) {
        if (super.onGroupClick(parent, v, groupPosition, id))
            return true;
        ManageCategories ctx = (ManageCategories) getActivity();
        if (ctx == null || ctx.helpVariant.equals(ManageCategories.HelpVariant.manage)) {
            return false;
        }
        long cat_id = id;
        mGroupCursor.moveToPosition(groupPosition);
        if (mGroupCursor.getInt(mGroupCursor.getColumnIndex(KEY_CHILD_COUNT)) > 0)
            return false;
        String label = ((TextView) v.findViewById(R.id.label)).getText().toString();
        doSelection(cat_id, label, true);
        return true;
    }

    private void doSelection(long cat_id, String label, boolean isMain) {
        ManageCategories ctx = (ManageCategories) getActivity();
        if (ctx.helpVariant.equals(ManageCategories.HelpVariant.distribution)) {
            TransactionListDialogFragment
                    .newInstance(mAccount.getId(), cat_id, isMain, mGrouping, buildGroupingClause(), label)
                    .show(getFragmentManager(), TransactionListDialogFragment.class.getName());
            return;
        }
        Intent intent = new Intent();
        intent.putExtra(KEY_CATID, cat_id);
        intent.putExtra(KEY_LABEL, label);
        ctx.setResult(ManageCategories.RESULT_OK, intent);
        ctx.finish();
    }

    public void setGrouping(Grouping grouping) {
        mGrouping = grouping;
        mGroupingYear = thisYear;
        switch (grouping) {
        case NONE:
            mGroupingYear = 0;
            break;
        case DAY:
            mGroupingSecond = thisDay;
            break;
        case WEEK:
            mGroupingSecond = thisWeek;
            break;
        case MONTH:
            mGroupingSecond = thisMonth;
            break;
        case YEAR:
            mGroupingSecond = 0;
            break;
        }
        getActivity().supportInvalidateOptionsMenu();
        reset();
    }

    public void reset() {
        //TODO: would be nice to retrieve the same open groups on the next or previous group
        //the following does not work since the groups will not necessarily stay the same
        //      if (mListView.isGroupExpanded(i)) {
        //        mGroupCursor.moveToPosition(i);
        //        long parentId = mGroupCursor.getLong(mGroupCursor.getColumnIndexOrThrow(KEY_ROWID));
        //        Bundle bundle = new Bundle();
        //        bundle.putLong("parent_id", parentId);
        //        mManager.restartLoader(i, bundle, CategoryList.this);
        //      }
        collapseAll();
        mManager.restartLoader(SORTABLE_CURSOR, null, this);
        mManager.restartLoader(SUM_CURSOR, null, this);
        mManager.restartLoader(DATEINFO_CURSOR, null, this);
    }

    private void updateSum(String prefix, TextView tv, long amount) {
        if (tv != null) {
            //noinspection SetTextI18n
            tv.setText(prefix + Utils.formatCurrency(new Money(mAccount.currency, amount)));
        }
    }

    private void updateColor() {
        if (bottomLine != null)
            bottomLine.setBackgroundColor(mAccount.color);
    }

    @Override
    public void onSaveInstanceState(Bundle outState) {
        super.onSaveInstanceState(outState);
        outState.putSerializable("grouping", mGrouping);
        outState.putInt("groupingYear", mGroupingYear);
        outState.putInt("groupingSecond", mGroupingSecond);
        if (!TextUtils.isEmpty(mFilter)) {
            outState.putString("filter", mFilter);
        }
    }

    @Override
    protected void configureMenu(Menu menu, int count, int listId) {
        ManageCategories ctx = (ManageCategories) getActivity();
        if (ctx == null) {
            return;
        }
        boolean inGroup = expandableListSelectionType == ExpandableListView.PACKED_POSITION_TYPE_GROUP;
        boolean inFilterOrDistribution = ctx.helpVariant.equals(HelpVariant.select_filter)
                || ctx.helpVariant.equals(HelpVariant.distribution);
        menu.findItem(R.id.EDIT_COMMAND).setVisible(count == 1 && !inFilterOrDistribution);
        menu.findItem(R.id.DELETE_COMMAND).setVisible(!inFilterOrDistribution);
        menu.findItem(R.id.MOVE_COMMAND).setVisible(!inFilterOrDistribution);
        MenuItem menuItem = menu.findItem(R.id.SELECT_COMMAND);
        menuItem.setVisible(count == 1 && (ctx.helpVariant.equals(HelpVariant.distribution)
                || ctx.helpVariant.equals(HelpVariant.select_mapping)));
        if (ctx.helpVariant.equals(HelpVariant.distribution)) {
            menuItem.setTitle(R.string.menu_show_transactions);
        }
        menu.findItem(R.id.SELECT_COMMAND_MULTIPLE).setVisible(ctx.helpVariant.equals(HelpVariant.select_filter));
        menu.findItem(R.id.CREATE_COMMAND).setVisible(inGroup && count == 1 && !inFilterOrDistribution);
    }

    @Override
    protected void configureMenuLegacy(Menu menu, ContextMenu.ContextMenuInfo menuInfo, int listId) {
        super.configureMenuLegacy(menu, menuInfo, listId);
        if (expandableListSelectionType == ExpandableListView.PACKED_POSITION_TYPE_GROUP) {
            ExpandableListContextMenuInfo info = (ExpandableListContextMenuInfo) menuInfo;
            int groupPos = ExpandableListView.getPackedPositionGroup(info.packedPosition);
            configureMenuInternal(menu, hasChildren(groupPos));
        }
    }

    @Override
    protected void configureMenu11(Menu menu, int count, AbsListView lv) {
        super.configureMenu11(menu, count, lv);
        if (expandableListSelectionType == ExpandableListView.PACKED_POSITION_TYPE_GROUP) {
            SparseBooleanArray checkedItemPositions = mListView.getCheckedItemPositions();
            boolean hasChildren = false;
            for (int i = 0; i < checkedItemPositions.size(); i++) {
                if (checkedItemPositions.valueAt(i)) {
                    int position = checkedItemPositions.keyAt(i);
                    long pos = mListView.getExpandableListPosition(position);
                    int groupPos = ExpandableListView.getPackedPositionGroup(pos);
                    if (hasChildren(groupPos)) {
                        hasChildren = true;
                        break;
                    }
                }
            }
            configureMenuInternal(menu, hasChildren);
        }
    }

    private boolean hasChildren(int position) {
        if (position != -1 && mGroupCursor != null) {
            mGroupCursor.moveToPosition(position);
            return mGroupCursor.getInt(mGroupCursor.getColumnIndex(KEY_CHILD_COUNT)) > 0;
        }
        return false;
    }

    private void configureMenuInternal(Menu menu, boolean hasChildren) {
        menu.findItem(R.id.MOVE_COMMAND).setVisible(!hasChildren);
    }

    public void setType(boolean isChecked) {
        mType = isChecked;
        reset();
    }

    private void setData(Cursor c, ArrayList<Integer> colors) {
        chartDisplaysSubs = c != mGroupCursor;
        ArrayList<Entry> entries1 = new ArrayList<>();
        ArrayList<String> xVals = new ArrayList<>();
        if (c != null && c.moveToFirst()) {
            do {
                long sum = c.getLong(c.getColumnIndex(DatabaseConstants.KEY_SUM));
                xVals.add(c.getString(c.getColumnIndex(DatabaseConstants.KEY_LABEL)));
                entries1.add(new Entry((float) sum, c.getPosition()));
            } while (c.moveToNext());
            PieDataSet ds1 = new PieDataSet(entries1, "");

            ds1.setColors(colors);
            ds1.setSliceSpace(2f);
            ds1.setDrawValues(false);
            mChart.setData(new PieData(xVals, ds1));
            mChart.getLegend().setEnabled(false);
            // undo all highlights
            mChart.highlightValues(null);
            mChart.invalidate();
        } else {
            mChart.clear();
        }
    }

    private ArrayList<Integer> getSubColors(int color) {
        //inspired by http://highintegritydesign.com/tools/tinter-shader/scripts/shader-tinter.js
        return MyApplication.getThemeType().equals(ThemeType.dark) ? getTints(color) : getShades(color);

    }

    private ArrayList<Integer> getShades(int color) {
        ArrayList<Integer> result = new ArrayList<>();
        int red = Color.red(color);
        int redDecrement = (int) Math.round(red * 0.1);
        int green = Color.green(color);
        int greenDecrement = (int) Math.round(green * 0.1);
        int blue = Color.blue(color);
        int blueDecrement = (int) Math.round(blue * 0.1);
        for (int i = 0; i < 10; i++) {
            red = red - redDecrement;
            if (red <= 0) {
                red = 0;
            }
            green = green - greenDecrement;
            if (green <= 0) {
                green = 0;
            }
            blue = blue - blueDecrement;
            if (blue <= 0) {
                blue = 0;
            }
            result.add(Color.rgb(red, green, blue));
        }
        result.add(Color.BLACK);
        return result;
    }

    private ArrayList<Integer> getTints(int color) {
        ArrayList<Integer> result = new ArrayList<>();
        int red = Color.red(color);
        int redIncrement = (int) Math.round((255 - red) * 0.1);
        int green = Color.green(color);
        int greenIncrement = (int) Math.round((255 - green) * 0.1);
        int blue = Color.blue(color);
        int blueIncrement = (int) Math.round((255 - blue) * 0.1);
        for (int i = 0; i < 10; i++) {
            red = red + redIncrement;
            if (red >= 255) {
                red = 255;
            }
            green = green + greenIncrement;
            if (green >= 255) {
                red = 255;
            }
            blue = blue + blueIncrement;
            if (blue >= 255) {
                red = 255;
            }
            result.add(Color.rgb(red, green, blue));
        }
        result.add(Color.WHITE);
        return result;
    }

    private void highlight(int position) {
        mChart.highlightValue(position, 0);
        if (position != -1)
            setCenterText(position);
    }

    private void setCenterText(int position) {
        PieData data = mChart.getData();

        String description = data.getXVals().get(position);

        String value = data.getDataSet().getValueFormatter().getFormattedValue(
                Math.abs(mChart.getPercentOfTotal(data.getDataSet().getEntryForXIndex(position).getVal()))) + " %";

        mChart.setCenterText(description + "\n" + value);
    }
}