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