Home | History | Annotate | Download | only in editor
      1 /*
      2  * Copyright (C) 2009 The Android Open Source Project
      3  *
      4  * Licensed under the Apache License, Version 2.0 (the "License");
      5  * you may not use this file except in compliance with the License.
      6  * You may obtain a copy of the License at
      7  *
      8  *      http://www.apache.org/licenses/LICENSE-2.0
      9  *
     10  * Unless required by applicable law or agreed to in writing, software
     11  * distributed under the License is distributed on an "AS IS" BASIS,
     12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
     13  * See the License for the specific language governing permissions and
     14  * limitations under the License.
     15  */
     16 
     17 package com.android.contacts.editor;
     18 
     19 import com.android.contacts.R;
     20 import com.android.contacts.editor.Editor.EditorListener;
     21 import com.android.contacts.model.DataKind;
     22 import com.android.contacts.model.EntityDelta;
     23 import com.android.contacts.model.EntityDelta.ValuesDelta;
     24 import com.android.contacts.model.EntityModifier;
     25 
     26 import android.content.Context;
     27 import android.text.TextUtils;
     28 import android.util.AttributeSet;
     29 import android.view.LayoutInflater;
     30 import android.view.View;
     31 import android.view.ViewGroup;
     32 import android.widget.LinearLayout;
     33 import android.widget.TextView;
     34 
     35 import java.util.ArrayList;
     36 import java.util.List;
     37 
     38 /**
     39  * Custom view for an entire section of data as segmented by
     40  * {@link DataKind} around a {@link Data#MIMETYPE}. This view shows a
     41  * section header and a trigger for adding new {@link Data} rows.
     42  */
     43 public class KindSectionView extends LinearLayout implements EditorListener {
     44     private static final String TAG = "KindSectionView";
     45 
     46     private TextView mTitle;
     47     private ViewGroup mEditors;
     48     private View mAddFieldFooter;
     49     private String mTitleString;
     50 
     51     private DataKind mKind;
     52     private EntityDelta mState;
     53     private boolean mReadOnly;
     54 
     55     private ViewIdGenerator mViewIdGenerator;
     56 
     57     private LayoutInflater mInflater;
     58 
     59     public KindSectionView(Context context) {
     60         this(context, null);
     61     }
     62 
     63     public KindSectionView(Context context, AttributeSet attrs) {
     64         super(context, attrs);
     65     }
     66 
     67     @Override
     68     public void setEnabled(boolean enabled) {
     69         super.setEnabled(enabled);
     70         if (mEditors != null) {
     71             int childCount = mEditors.getChildCount();
     72             for (int i = 0; i < childCount; i++) {
     73                 mEditors.getChildAt(i).setEnabled(enabled);
     74             }
     75         }
     76 
     77         if (enabled && !mReadOnly) {
     78             mAddFieldFooter.setVisibility(View.VISIBLE);
     79         } else {
     80             mAddFieldFooter.setVisibility(View.GONE);
     81         }
     82     }
     83 
     84     public boolean isReadOnly() {
     85         return mReadOnly;
     86     }
     87 
     88     /** {@inheritDoc} */
     89     @Override
     90     protected void onFinishInflate() {
     91         setDrawingCacheEnabled(true);
     92         setAlwaysDrawnWithCacheEnabled(true);
     93 
     94         mInflater = (LayoutInflater) mContext.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
     95 
     96         mTitle = (TextView) findViewById(R.id.kind_title);
     97         mEditors = (ViewGroup) findViewById(R.id.kind_editors);
     98         mAddFieldFooter = findViewById(R.id.add_field_footer);
     99         mAddFieldFooter.setOnClickListener(new OnClickListener() {
    100             @Override
    101             public void onClick(View v) {
    102                 // Setup click listener to add an empty field when the footer is clicked.
    103                 mAddFieldFooter.setVisibility(View.GONE);
    104                 addItem();
    105             }
    106         });
    107     }
    108 
    109     @Override
    110     public void onDeleteRequested(Editor editor) {
    111         // If there is only 1 editor in the section, then don't allow the user to delete it.
    112         // Just clear the fields in the editor.
    113         if (getEditorCount() == 1) {
    114             editor.clearAllFields();
    115         } else {
    116             // Otherwise it's okay to delete this {@link Editor}
    117             editor.deleteEditor();
    118         }
    119         updateAddFooterVisible();
    120     }
    121 
    122     @Override
    123     public void onRequest(int request) {
    124         // If a field has become empty or non-empty, then check if another row
    125         // can be added dynamically.
    126         if (request == FIELD_TURNED_EMPTY || request == FIELD_TURNED_NON_EMPTY) {
    127             updateAddFooterVisible();
    128         }
    129     }
    130 
    131     public void setState(DataKind kind, EntityDelta state, boolean readOnly, ViewIdGenerator vig) {
    132         mKind = kind;
    133         mState = state;
    134         mReadOnly = readOnly;
    135         mViewIdGenerator = vig;
    136 
    137         setId(mViewIdGenerator.getId(state, kind, null, ViewIdGenerator.NO_VIEW_INDEX));
    138 
    139         // TODO: handle resources from remote packages
    140         mTitleString = (kind.titleRes == -1 || kind.titleRes == 0)
    141                 ? ""
    142                 : getResources().getString(kind.titleRes);
    143         mTitle.setText(mTitleString);
    144 
    145         rebuildFromState();
    146         updateAddFooterVisible();
    147         updateSectionVisible();
    148     }
    149 
    150     public String getTitle() {
    151         return mTitleString;
    152     }
    153 
    154     public void setTitleVisible(boolean visible) {
    155         findViewById(R.id.kind_title_layout).setVisibility(visible ? View.VISIBLE : View.GONE);
    156     }
    157 
    158     /**
    159      * Build editors for all current {@link #mState} rows.
    160      */
    161     public void rebuildFromState() {
    162         // Remove any existing editors
    163         mEditors.removeAllViews();
    164 
    165         // Check if we are displaying anything here
    166         boolean hasEntries = mState.hasMimeEntries(mKind.mimeType);
    167 
    168         if (hasEntries) {
    169             for (ValuesDelta entry : mState.getMimeEntries(mKind.mimeType)) {
    170                 // Skip entries that aren't visible
    171                 if (!entry.isVisible()) continue;
    172                 if (isEmptyNoop(entry)) continue;
    173 
    174                 createEditorView(entry);
    175             }
    176         }
    177     }
    178 
    179 
    180     /**
    181      * Creates an EditorView for the given entry. This function must be used while constructing
    182      * the views corresponding to the the object-model. The resulting EditorView is also added
    183      * to the end of mEditors
    184      */
    185     private View createEditorView(ValuesDelta entry) {
    186         final View view;
    187         try {
    188             view = mInflater.inflate(mKind.editorLayoutResourceId, mEditors, false);
    189         } catch (Exception e) {
    190             throw new RuntimeException(
    191                     "Cannot allocate editor with layout resource ID " +
    192                     mKind.editorLayoutResourceId + " for MIME type " + mKind.mimeType +
    193                     " with error " + e.toString());
    194         }
    195 
    196         view.setEnabled(isEnabled());
    197 
    198         if (view instanceof Editor) {
    199             Editor editor = (Editor) view;
    200             editor.setDeletable(true);
    201             editor.setValues(mKind, entry, mState, mReadOnly, mViewIdGenerator);
    202             editor.setEditorListener(this);
    203         }
    204         mEditors.addView(view);
    205         return view;
    206     }
    207 
    208     /**
    209      * Tests whether the given item has no changes (so it exists in the database) but is empty
    210      */
    211     private boolean isEmptyNoop(ValuesDelta item) {
    212         if (!item.isNoop()) return false;
    213         final int fieldCount = mKind.fieldList.size();
    214         for (int i = 0; i < fieldCount; i++) {
    215             final String column = mKind.fieldList.get(i).column;
    216             final String value = item.getAsString(column);
    217             if (!TextUtils.isEmpty(value)) return false;
    218         }
    219         return true;
    220     }
    221 
    222     private void updateSectionVisible() {
    223         setVisibility(getEditorCount() != 0 ? VISIBLE : GONE);
    224     }
    225 
    226     protected void updateAddFooterVisible() {
    227         if (!mReadOnly && (mKind.typeOverallMax != 1)) {
    228             // First determine whether there are any existing empty editors.
    229             updateEmptyEditors();
    230             // If there are no existing empty editors and it's possible to add
    231             // another field, then make the "add footer" field visible.
    232             if (!hasEmptyEditor() && EntityModifier.canInsert(mState, mKind)) {
    233                 mAddFieldFooter.setVisibility(View.VISIBLE);
    234                 return;
    235             }
    236         }
    237         mAddFieldFooter.setVisibility(View.GONE);
    238     }
    239 
    240     /**
    241      * Updates the editors being displayed to the user removing extra empty
    242      * {@link Editor}s, so there is only max 1 empty {@link Editor} view at a time.
    243      */
    244     private void updateEmptyEditors() {
    245         List<View> emptyEditors = getEmptyEditors();
    246 
    247         // If there is more than 1 empty editor, then remove it from the list of editors.
    248         if (emptyEditors.size() > 1) {
    249             for (View emptyEditorView : emptyEditors) {
    250                 // If no child {@link View}s are being focused on within
    251                 // this {@link View}, then remove this empty editor.
    252                 if (emptyEditorView.findFocus() == null) {
    253                     mEditors.removeView(emptyEditorView);
    254                 }
    255             }
    256         }
    257     }
    258 
    259     /**
    260      * Returns a list of empty editor views in this section.
    261      */
    262     private List<View> getEmptyEditors() {
    263         List<View> emptyEditorViews = new ArrayList<View>();
    264         for (int i = 0; i < mEditors.getChildCount(); i++) {
    265             View view = mEditors.getChildAt(i);
    266             if (((Editor) view).isEmpty()) {
    267                 emptyEditorViews.add(view);
    268             }
    269         }
    270         return emptyEditorViews;
    271     }
    272 
    273     /**
    274      * Returns true if one of the editors has all of its fields empty, or false
    275      * otherwise.
    276      */
    277     private boolean hasEmptyEditor() {
    278         return getEmptyEditors().size() > 0;
    279     }
    280 
    281     /**
    282      * Returns true if all editors are empty.
    283      */
    284     public boolean isEmpty() {
    285         for (int i = 0; i < mEditors.getChildCount(); i++) {
    286             View view = mEditors.getChildAt(i);
    287             if (!((Editor) view).isEmpty()) {
    288                 return false;
    289             }
    290         }
    291         return true;
    292     }
    293 
    294     public void addItem() {
    295         ValuesDelta values = null;
    296         // If this is a list, we can freely add. If not, only allow adding the first.
    297         if (mKind.typeOverallMax == 1) {
    298             if (getEditorCount() == 1) {
    299                 return;
    300             }
    301 
    302             // If we already have an item, just make it visible
    303             ArrayList<ValuesDelta> entries = mState.getMimeEntries(mKind.mimeType);
    304             if (entries != null && entries.size() > 0) {
    305                 values = entries.get(0);
    306             }
    307         }
    308 
    309         // Insert a new child, create its view and set its focus
    310         if (values == null) {
    311             values = EntityModifier.insertChild(mState, mKind);
    312         }
    313 
    314         final View newField = createEditorView(values);
    315         post(new Runnable() {
    316 
    317             @Override
    318             public void run() {
    319                 newField.requestFocus();
    320             }
    321         });
    322 
    323         // Hide the "add field" footer because there is now a blank field.
    324         mAddFieldFooter.setVisibility(View.GONE);
    325 
    326         // Ensure we are visible
    327         updateSectionVisible();
    328     }
    329 
    330     public int getEditorCount() {
    331         return mEditors.getChildCount();
    332     }
    333 
    334     public DataKind getKind() {
    335         return mKind;
    336     }
    337 }
    338