Home | History | Annotate | Download | only in ui
      1 /*
      2  * Copyright (C) 2012 Google Inc.
      3  * Licensed to The Android Open Source Project.
      4  *
      5  * Licensed under the Apache License, Version 2.0 (the "License");
      6  * you may not use this file except in compliance with the License.
      7  * You may obtain a copy of the License at
      8  *
      9  *      http://www.apache.org/licenses/LICENSE-2.0
     10  *
     11  * Unless required by applicable law or agreed to in writing, software
     12  * distributed under the License is distributed on an "AS IS" BASIS,
     13  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
     14  * See the License for the specific language governing permissions and
     15  * limitations under the License.
     16  */
     17 
     18 package com.android.mail.ui;
     19 
     20 import android.content.Context;
     21 import android.content.res.Resources;
     22 import android.graphics.Canvas;
     23 import android.graphics.LinearGradient;
     24 import android.graphics.Paint;
     25 import android.graphics.RectF;
     26 import android.graphics.Shader;
     27 import android.support.v4.text.BidiFormatter;
     28 
     29 import com.android.mail.R;
     30 import com.android.mail.providers.Conversation;
     31 import com.android.mail.providers.Folder;
     32 import com.android.mail.providers.UIProvider.FolderType;
     33 import com.android.mail.utils.FolderUri;
     34 import com.android.mail.utils.LogTag;
     35 import com.android.mail.utils.Utils;
     36 import com.google.common.collect.Sets;
     37 
     38 import java.util.NavigableSet;
     39 import java.util.Set;
     40 
     41 /**
     42  * Used to generate folder display information given a raw folders string.
     43  * (The raw folders string can be obtained from {@link Conversation#getRawFolders()}.)
     44  */
     45 public abstract class FolderDisplayer {
     46     public static final String LOG_TAG = LogTag.getLogTag();
     47     protected Context mContext;
     48     protected final NavigableSet<Folder> mFoldersSortedSet = Sets.newTreeSet();
     49     protected final FolderDrawableResources mFolderDrawableResources =
     50             new FolderDrawableResources();
     51 
     52     public static class FolderDrawableResources {
     53         public int defaultFgColor;
     54         public int defaultBgColor;
     55         public int folderRoundedCornerRadius;
     56         public int overflowGradientPadding;
     57         public int folderHorizontalPadding;
     58         public int folderInBetweenPadding;
     59         public int folderFontSize;
     60         public int folderVerticalOffset;
     61     }
     62 
     63     public FolderDisplayer(Context context) {
     64         mContext = context;
     65         initializeDrawableResources();
     66     }
     67 
     68     protected void initializeDrawableResources() {
     69         // Set default values used across all folder chips
     70         final Resources res = mContext.getResources();
     71         mFolderDrawableResources.defaultFgColor =
     72                 res.getColor(R.color.default_folder_foreground_color);
     73         mFolderDrawableResources.defaultBgColor =
     74                 res.getColor(R.color.default_folder_background_color);
     75         mFolderDrawableResources.folderRoundedCornerRadius =
     76                 res.getDimensionPixelOffset(R.dimen.folder_rounded_corner_radius);
     77         mFolderDrawableResources.folderInBetweenPadding =
     78                 res.getDimensionPixelOffset(R.dimen.folder_start_padding);
     79     }
     80 
     81     /**
     82      * Configure the FolderDisplayer object by filtering and copying from the list of raw folders.
     83      *
     84      * @param conv {@link Conversation} containing the folders to display.
     85      * @param ignoreFolderUri (optional) folder to omit from the displayed set
     86      * @param ignoreFolderType -1, or the {@link FolderType} to omit from the displayed set
     87      */
     88     public void loadConversationFolders(Conversation conv, final FolderUri ignoreFolderUri,
     89             final int ignoreFolderType) {
     90         mFoldersSortedSet.clear();
     91         for (Folder folder : conv.getRawFolders()) {
     92             // Skip the ignoreFolderType
     93             if (ignoreFolderType >= 0 && folder.isType(ignoreFolderType)) {
     94                 continue;
     95             }
     96             // skip the ignoreFolder
     97             if (ignoreFolderUri != null && ignoreFolderUri.equals(folder.folderUri)) {
     98                 continue;
     99             }
    100             mFoldersSortedSet.add(folder);
    101         }
    102     }
    103 
    104     /**
    105      * Reset this FolderDisplayer so that it can be reused.
    106      */
    107     public void reset() {
    108         mFoldersSortedSet.clear();
    109     }
    110 
    111     /**
    112      * Helper function to calculate exactly how much space the displayed folders should take.
    113      * @param folders the set of folders to display.
    114      * @param maxCellWidth this signifies the absolute max for each folder cell, no exceptions.
    115      * @param maxLayoutWidth the view's layout width, aka how much space we have.
    116      * @param foldersInBetweenPadding the padding between folder chips.
    117      * @param foldersHorizontalPadding the padding between the edge of the chip and the text.
    118      * @param maxFolderCount the maximum number of folder chips to display.
    119      * @param paint work paint.
    120      * @return an array of integers that signifies the length of each folder chip.
    121      */
    122     public static int[] measureFolderDimen(Set<Folder> folders, int maxCellWidth,
    123             int maxLayoutWidth, int foldersInBetweenPadding, int foldersHorizontalPadding,
    124             int maxFolderCount, Paint paint) {
    125 
    126         final int numDisplayedFolders = Math.min(maxFolderCount, folders.size());
    127         if (numDisplayedFolders == 0) {
    128             return new int[0];
    129         }
    130 
    131         // This variable is calculated based on the number of folders we are displaying
    132         final int maxAllowedCellSize = Math.min(maxCellWidth, (maxLayoutWidth -
    133                 (numDisplayedFolders - 1) * foldersInBetweenPadding) / numDisplayedFolders);
    134         final int[] measurements = new int[numDisplayedFolders];
    135 
    136         int count = 0;
    137         int missingWidth = 0;
    138         int extraWidth = 0;
    139         for (Folder f : folders) {
    140             if (count > numDisplayedFolders - 1) {
    141                 break;
    142             }
    143 
    144             final String folderString = f.name;
    145             final int neededWidth = (int) paint.measureText(folderString) +
    146                     2 * foldersHorizontalPadding;
    147 
    148             if (neededWidth > maxAllowedCellSize) {
    149                 // What we can take from others is the minimum of the width we need to borrow
    150                 // and the width we are allowed to borrow.
    151                 final int borrowedWidth = Math.min(neededWidth - maxAllowedCellSize,
    152                         maxCellWidth - maxAllowedCellSize);
    153                 final int extraWidthLeftover = extraWidth - borrowedWidth;
    154                 if (extraWidthLeftover >= 0) {
    155                     measurements[count] = Math.min(neededWidth, maxCellWidth);
    156                     extraWidth = extraWidthLeftover;
    157                 } else {
    158                     measurements[count] = maxAllowedCellSize + extraWidth;
    159                     extraWidth = 0;
    160                 }
    161                 missingWidth = -extraWidthLeftover;
    162             } else {
    163                 extraWidth = maxAllowedCellSize - neededWidth;
    164                 measurements[count] = neededWidth;
    165                 if (missingWidth > 0) {
    166                     if (extraWidth >= missingWidth) {
    167                         measurements[count - 1] += missingWidth;
    168                         extraWidth -= missingWidth;
    169                     } else {
    170                         measurements[count - 1] += extraWidth;
    171                         extraWidth = 0;
    172                     }
    173                 }
    174                 missingWidth = 0;
    175             }
    176 
    177             count++;
    178         }
    179 
    180         return measurements;
    181     }
    182 
    183     public static void drawFolder(Canvas canvas, float x, float y, int width, int height,
    184             Folder f, FolderDisplayer.FolderDrawableResources res, BidiFormatter formatter,
    185             Paint paint) {
    186         drawFolder(canvas, x, y, width, height, f.name,
    187                 f.getForegroundColor(res.defaultFgColor), f.getBackgroundColor(res.defaultBgColor),
    188                 res, formatter, paint);
    189     }
    190 
    191     public static void drawFolder(Canvas canvas, float x, float y, int width, int height,
    192             String name, int fgColor, int bgColor, FolderDisplayer.FolderDrawableResources res,
    193             BidiFormatter formatter, Paint paint) {
    194         canvas.save();
    195         canvas.translate(x, y + res.folderVerticalOffset);
    196 
    197         // Draw the box.
    198         paint.setColor(bgColor);
    199         paint.setStyle(Paint.Style.FILL);
    200         final RectF rect = new RectF(0, 0, width, height);
    201         canvas.drawRoundRect(rect, res.folderRoundedCornerRadius, res.folderRoundedCornerRadius,
    202                 paint);
    203 
    204         // Draw the text based on the language locale and layout direction.
    205         paint.setColor(fgColor);
    206         paint.setStyle(Paint.Style.FILL);
    207 
    208         // Compute the text/gradient indices
    209         final int textLength = (int) paint.measureText(name);
    210         final int gradientX0;
    211         final int gradientX1;
    212         final int textX;
    213 
    214 /***************************************************************************************************
    215  * width               - the actual folder chip rectangle.                                         *
    216  * textLength          - the length of the folder's full name (can be longer than                  *
    217  *                         the actual chip, which is what overflow gradient is for).               *
    218  * innerPadding        - the padding between the text and the chip edge.                           *
    219  * overflowPadding     - the padding between start of overflow and the chip edge.                  *
    220  *                                                                                                 *
    221  *                                                                                                 *
    222  * text is in a RTL language                                                                       *
    223  *                                                                                                 *
    224  *                   index-0                                                                       *
    225  *                      |<---------------------------- width ---------------------------->|        *
    226  *        |<-------------------------textLength------------------>|                       |        *
    227  *        |             |<----- overflowPadding ----->|                                   |        *
    228  *        |             |<- innerPadding ->|<-------->|<--------->|<- horizontalPadding ->|        *
    229  *       textX                            gX1        gX0                                           *
    230  *                                                                                                 *
    231  *                                                                                                 *
    232  * text is in a LTR language.                                                                      *
    233  *                                                                                                 *
    234  *     index-0                                                                                     *
    235  *        |<------------------------------ width ------------------------------->|                 *
    236  *        |                       |<-------------------------textLength-------------------->|      *
    237  *        |                                   |<-------- overflowPadding ------->|                 *
    238  *        |<- horizontalPadding ->|<--------->|<-------->|<- horizontalPadding ->|                 *
    239  *                              textX        gX0        gX1                                        *
    240  *                                                                                                 *
    241  **************************************************************************************************/
    242         if (formatter.isRtl(name)) {
    243             gradientX0 = res.overflowGradientPadding;
    244             gradientX1 = res.folderHorizontalPadding;
    245             textX = width - res.folderHorizontalPadding - textLength;
    246         } else {
    247             gradientX0 = width - res.overflowGradientPadding;
    248             gradientX1 = width - res.folderHorizontalPadding;
    249             textX = res.folderHorizontalPadding;
    250         }
    251 
    252         // Draw the text and the possible overflow gradient
    253         // Overflow happens when the text is longer than the chip width minus side paddings.
    254         if (textLength > width - 2 * res.folderHorizontalPadding) {
    255             final Shader shader = new LinearGradient(gradientX0, 0, gradientX1, 0, fgColor,
    256                     Utils.getTransparentColor(fgColor), Shader.TileMode.CLAMP);
    257             paint.setShader(shader);
    258         }
    259         final int textY = height / 2 - (int) (paint.descent() + paint.ascent()) / 2;
    260         canvas.drawText(name, textX, textY, paint);
    261         paint.setShader(null);
    262 
    263         canvas.restore();
    264     }
    265 }
    266