Skip to content
Browse files

Initial public revision. No major code changes aside from symbol pref…

…ix refactoring. Compiles, but not tested (with refactored class names).
  • Loading branch information...
0 parents commit 3cf79361521296895d1287ac220bccedb9d85e85 @AlanQuatermain committed Apr 17, 2010
6 .gitignore
@@ -0,0 +1,6 @@
+*~
+.DS_Store
+build
+*.xcodeproj/*.pbxuser
+*.xcodeproj/*.mode1
+*.xcodeproj/*.mode1v3
315 AQGridView.xcodeproj/project.pbxproj
@@ -0,0 +1,315 @@
+// !$*UTF8*$!
+{
+ archiveVersion = 1;
+ classes = {
+ };
+ objectVersion = 45;
+ objects = {
+
+/* Begin PBXBuildFile section */
+ 38978177117962D000D5CC8F /* UIKit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 38978176117962D000D5CC8F /* UIKit.framework */; };
+ 389782B9117A311B00D5CC8F /* AQGridView+CellLayout.h in Headers */ = {isa = PBXBuildFile; fileRef = 389782A7117A311B00D5CC8F /* AQGridView+CellLayout.h */; };
+ 389782BA117A311B00D5CC8F /* AQGridView+CellLocationDelegation.h in Headers */ = {isa = PBXBuildFile; fileRef = 389782A8117A311B00D5CC8F /* AQGridView+CellLocationDelegation.h */; };
+ 389782BB117A311B00D5CC8F /* AQGridView.h in Headers */ = {isa = PBXBuildFile; fileRef = 389782A9117A311B00D5CC8F /* AQGridView.h */; };
+ 389782BC117A311B00D5CC8F /* AQGridView.m in Sources */ = {isa = PBXBuildFile; fileRef = 389782AA117A311B00D5CC8F /* AQGridView.m */; };
+ 389782BD117A311B00D5CC8F /* AQGridView_Prefix.pch in Headers */ = {isa = PBXBuildFile; fileRef = 389782AB117A311B00D5CC8F /* AQGridView_Prefix.pch */; };
+ 389782BE117A311B00D5CC8F /* AQGridViewCell+AQGridViewCellPrivate.h in Headers */ = {isa = PBXBuildFile; fileRef = 389782AC117A311B00D5CC8F /* AQGridViewCell+AQGridViewCellPrivate.h */; };
+ 389782BF117A311B00D5CC8F /* AQGridViewCell.h in Headers */ = {isa = PBXBuildFile; fileRef = 389782AD117A311B00D5CC8F /* AQGridViewCell.h */; };
+ 389782C0117A311B00D5CC8F /* AQGridViewCell.m in Sources */ = {isa = PBXBuildFile; fileRef = 389782AE117A311B00D5CC8F /* AQGridViewCell.m */; };
+ 389782C1117A311B00D5CC8F /* AQGridViewController.h in Headers */ = {isa = PBXBuildFile; fileRef = 389782AF117A311B00D5CC8F /* AQGridViewController.h */; };
+ 389782C2117A311B00D5CC8F /* AQGridViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = 389782B0117A311B00D5CC8F /* AQGridViewController.m */; };
+ 389782C3117A311B00D5CC8F /* AQGridViewData.h in Headers */ = {isa = PBXBuildFile; fileRef = 389782B1117A311B00D5CC8F /* AQGridViewData.h */; };
+ 389782C4117A311B00D5CC8F /* AQGridViewData.m in Sources */ = {isa = PBXBuildFile; fileRef = 389782B2117A311B00D5CC8F /* AQGridViewData.m */; };
+ 389782C5117A311B00D5CC8F /* AQGridViewUpdateInfo.h in Headers */ = {isa = PBXBuildFile; fileRef = 389782B3117A311B00D5CC8F /* AQGridViewUpdateInfo.h */; };
+ 389782C6117A311B00D5CC8F /* AQGridViewUpdateInfo.m in Sources */ = {isa = PBXBuildFile; fileRef = 389782B4117A311B00D5CC8F /* AQGridViewUpdateInfo.m */; };
+ 389782C7117A311B00D5CC8F /* AQGridViewUpdateItem.h in Headers */ = {isa = PBXBuildFile; fileRef = 389782B5117A311B00D5CC8F /* AQGridViewUpdateItem.h */; };
+ 389782C8117A311B00D5CC8F /* AQGridViewUpdateItem.m in Sources */ = {isa = PBXBuildFile; fileRef = 389782B6117A311B00D5CC8F /* AQGridViewUpdateItem.m */; };
+ 389782C9117A311B00D5CC8F /* UIColor+AQGridView.h in Headers */ = {isa = PBXBuildFile; fileRef = 389782B7117A311B00D5CC8F /* UIColor+AQGridView.h */; };
+ 389782CA117A311B00D5CC8F /* UIColor+AQGridView.m in Sources */ = {isa = PBXBuildFile; fileRef = 389782B8117A311B00D5CC8F /* UIColor+AQGridView.m */; };
+ AA747D9F0F9514B9006C5449 /* AQGridView_Prefix.pch in Headers */ = {isa = PBXBuildFile; fileRef = AA747D9E0F9514B9006C5449 /* AQGridView_Prefix.pch */; };
+ AACBBE4A0F95108600F1A2B1 /* Foundation.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = AACBBE490F95108600F1A2B1 /* Foundation.framework */; };
+/* End PBXBuildFile section */
+
+/* Begin PBXFileReference section */
+ 38978176117962D000D5CC8F /* UIKit.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = UIKit.framework; path = System/Library/Frameworks/UIKit.framework; sourceTree = SDKROOT; };
+ 389782A7117A311B00D5CC8F /* AQGridView+CellLayout.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = "AQGridView+CellLayout.h"; sourceTree = "<group>"; };
+ 389782A8117A311B00D5CC8F /* AQGridView+CellLocationDelegation.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = "AQGridView+CellLocationDelegation.h"; sourceTree = "<group>"; };
+ 389782A9117A311B00D5CC8F /* AQGridView.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = AQGridView.h; sourceTree = "<group>"; };
+ 389782AA117A311B00D5CC8F /* AQGridView.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = AQGridView.m; sourceTree = "<group>"; };
+ 389782AB117A311B00D5CC8F /* AQGridView_Prefix.pch */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = AQGridView_Prefix.pch; sourceTree = "<group>"; };
+ 389782AC117A311B00D5CC8F /* AQGridViewCell+AQGridViewCellPrivate.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = "AQGridViewCell+AQGridViewCellPrivate.h"; sourceTree = "<group>"; };
+ 389782AD117A311B00D5CC8F /* AQGridViewCell.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = AQGridViewCell.h; sourceTree = "<group>"; };
+ 389782AE117A311B00D5CC8F /* AQGridViewCell.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = AQGridViewCell.m; sourceTree = "<group>"; };
+ 389782AF117A311B00D5CC8F /* AQGridViewController.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = AQGridViewController.h; sourceTree = "<group>"; };
+ 389782B0117A311B00D5CC8F /* AQGridViewController.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = AQGridViewController.m; sourceTree = "<group>"; };
+ 389782B1117A311B00D5CC8F /* AQGridViewData.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = AQGridViewData.h; sourceTree = "<group>"; };
+ 389782B2117A311B00D5CC8F /* AQGridViewData.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = AQGridViewData.m; sourceTree = "<group>"; };
+ 389782B3117A311B00D5CC8F /* AQGridViewUpdateInfo.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = AQGridViewUpdateInfo.h; sourceTree = "<group>"; };
+ 389782B4117A311B00D5CC8F /* AQGridViewUpdateInfo.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = AQGridViewUpdateInfo.m; sourceTree = "<group>"; };
+ 389782B5117A311B00D5CC8F /* AQGridViewUpdateItem.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = AQGridViewUpdateItem.h; sourceTree = "<group>"; };
+ 389782B6117A311B00D5CC8F /* AQGridViewUpdateItem.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = AQGridViewUpdateItem.m; sourceTree = "<group>"; };
+ 389782B7117A311B00D5CC8F /* UIColor+AQGridView.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = "UIColor+AQGridView.h"; sourceTree = "<group>"; };
+ 389782B8117A311B00D5CC8F /* UIColor+AQGridView.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = "UIColor+AQGridView.m"; sourceTree = "<group>"; };
+ 389782CC117A314A00D5CC8F /* AQGridSelection.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = AQGridSelection.png; sourceTree = "<group>"; };
+ 389782CD117A314A00D5CC8F /* AQGridSelectionGray.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = AQGridSelectionGray.png; sourceTree = "<group>"; };
+ 389782CE117A314A00D5CC8F /* AQGridSelectionGrayBlue.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = AQGridSelectionGrayBlue.png; sourceTree = "<group>"; };
+ 389782CF117A314A00D5CC8F /* AQGridSelectionGreen.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = AQGridSelectionGreen.png; sourceTree = "<group>"; };
+ 389782D0117A314A00D5CC8F /* AQGridSelectionRed.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = AQGridSelectionRed.png; sourceTree = "<group>"; };
+ AA747D9E0F9514B9006C5449 /* AQGridView_Prefix.pch */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = AQGridView_Prefix.pch; sourceTree = SOURCE_ROOT; };
+ AACBBE490F95108600F1A2B1 /* Foundation.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = Foundation.framework; path = System/Library/Frameworks/Foundation.framework; sourceTree = SDKROOT; };
+ D2AAC07E0554694100DB518D /* libAQGridView.a */ = {isa = PBXFileReference; explicitFileType = archive.ar; includeInIndex = 0; path = libAQGridView.a; sourceTree = BUILT_PRODUCTS_DIR; };
+/* End PBXFileReference section */
+
+/* Begin PBXFrameworksBuildPhase section */
+ D2AAC07C0554694100DB518D /* Frameworks */ = {
+ isa = PBXFrameworksBuildPhase;
+ buildActionMask = 2147483647;
+ files = (
+ AACBBE4A0F95108600F1A2B1 /* Foundation.framework in Frameworks */,
+ 38978177117962D000D5CC8F /* UIKit.framework in Frameworks */,
+ );
+ runOnlyForDeploymentPostprocessing = 0;
+ };
+/* End PBXFrameworksBuildPhase section */
+
+/* Begin PBXGroup section */
+ 034768DFFF38A50411DB9C8B /* Products */ = {
+ isa = PBXGroup;
+ children = (
+ D2AAC07E0554694100DB518D /* libAQGridView.a */,
+ );
+ name = Products;
+ sourceTree = "<group>";
+ };
+ 0867D691FE84028FC02AAC07 /* AQGridView */ = {
+ isa = PBXGroup;
+ children = (
+ 389782A6117A311B00D5CC8F /* Classes */,
+ 32C88DFF0371C24200C91783 /* Other Sources */,
+ 389782CB117A314A00D5CC8F /* Resources */,
+ 0867D69AFE84028FC02AAC07 /* Frameworks */,
+ 034768DFFF38A50411DB9C8B /* Products */,
+ );
+ name = AQGridView;
+ sourceTree = "<group>";
+ };
+ 0867D69AFE84028FC02AAC07 /* Frameworks */ = {
+ isa = PBXGroup;
+ children = (
+ AACBBE490F95108600F1A2B1 /* Foundation.framework */,
+ 38978176117962D000D5CC8F /* UIKit.framework */,
+ );
+ name = Frameworks;
+ sourceTree = "<group>";
+ };
+ 32C88DFF0371C24200C91783 /* Other Sources */ = {
+ isa = PBXGroup;
+ children = (
+ AA747D9E0F9514B9006C5449 /* AQGridView_Prefix.pch */,
+ );
+ name = "Other Sources";
+ sourceTree = "<group>";
+ };
+ 389782A6117A311B00D5CC8F /* Classes */ = {
+ isa = PBXGroup;
+ children = (
+ 389782A7117A311B00D5CC8F /* AQGridView+CellLayout.h */,
+ 389782A8117A311B00D5CC8F /* AQGridView+CellLocationDelegation.h */,
+ 389782A9117A311B00D5CC8F /* AQGridView.h */,
+ 389782AA117A311B00D5CC8F /* AQGridView.m */,
+ 389782AB117A311B00D5CC8F /* AQGridView_Prefix.pch */,
+ 389782AC117A311B00D5CC8F /* AQGridViewCell+AQGridViewCellPrivate.h */,
+ 389782AD117A311B00D5CC8F /* AQGridViewCell.h */,
+ 389782AE117A311B00D5CC8F /* AQGridViewCell.m */,
+ 389782AF117A311B00D5CC8F /* AQGridViewController.h */,
+ 389782B0117A311B00D5CC8F /* AQGridViewController.m */,
+ 389782B1117A311B00D5CC8F /* AQGridViewData.h */,
+ 389782B2117A311B00D5CC8F /* AQGridViewData.m */,
+ 389782B3117A311B00D5CC8F /* AQGridViewUpdateInfo.h */,
+ 389782B4117A311B00D5CC8F /* AQGridViewUpdateInfo.m */,
+ 389782B5117A311B00D5CC8F /* AQGridViewUpdateItem.h */,
+ 389782B6117A311B00D5CC8F /* AQGridViewUpdateItem.m */,
+ 389782B7117A311B00D5CC8F /* UIColor+AQGridView.h */,
+ 389782B8117A311B00D5CC8F /* UIColor+AQGridView.m */,
+ );
+ path = Classes;
+ sourceTree = "<group>";
+ };
+ 389782CB117A314A00D5CC8F /* Resources */ = {
+ isa = PBXGroup;
+ children = (
+ 389782CC117A314A00D5CC8F /* AQGridSelection.png */,
+ 389782CD117A314A00D5CC8F /* AQGridSelectionGray.png */,
+ 389782CE117A314A00D5CC8F /* AQGridSelectionGrayBlue.png */,
+ 389782CF117A314A00D5CC8F /* AQGridSelectionGreen.png */,
+ 389782D0117A314A00D5CC8F /* AQGridSelectionRed.png */,
+ );
+ path = Resources;
+ sourceTree = "<group>";
+ };
+/* End PBXGroup section */
+
+/* Begin PBXHeadersBuildPhase section */
+ D2AAC07A0554694100DB518D /* Headers */ = {
+ isa = PBXHeadersBuildPhase;
+ buildActionMask = 2147483647;
+ files = (
+ AA747D9F0F9514B9006C5449 /* AQGridView_Prefix.pch in Headers */,
+ 389782B9117A311B00D5CC8F /* AQGridView+CellLayout.h in Headers */,
+ 389782BA117A311B00D5CC8F /* AQGridView+CellLocationDelegation.h in Headers */,
+ 389782BB117A311B00D5CC8F /* AQGridView.h in Headers */,
+ 389782BD117A311B00D5CC8F /* AQGridView_Prefix.pch in Headers */,
+ 389782BE117A311B00D5CC8F /* AQGridViewCell+AQGridViewCellPrivate.h in Headers */,
+ 389782BF117A311B00D5CC8F /* AQGridViewCell.h in Headers */,
+ 389782C1117A311B00D5CC8F /* AQGridViewController.h in Headers */,
+ 389782C3117A311B00D5CC8F /* AQGridViewData.h in Headers */,
+ 389782C5117A311B00D5CC8F /* AQGridViewUpdateInfo.h in Headers */,
+ 389782C7117A311B00D5CC8F /* AQGridViewUpdateItem.h in Headers */,
+ 389782C9117A311B00D5CC8F /* UIColor+AQGridView.h in Headers */,
+ );
+ runOnlyForDeploymentPostprocessing = 0;
+ };
+/* End PBXHeadersBuildPhase section */
+
+/* Begin PBXNativeTarget section */
+ D2AAC07D0554694100DB518D /* AQGridView */ = {
+ isa = PBXNativeTarget;
+ buildConfigurationList = 1DEB921E08733DC00010E9CD /* Build configuration list for PBXNativeTarget "AQGridView" */;
+ buildPhases = (
+ D2AAC07A0554694100DB518D /* Headers */,
+ D2AAC07B0554694100DB518D /* Sources */,
+ D2AAC07C0554694100DB518D /* Frameworks */,
+ );
+ buildRules = (
+ );
+ dependencies = (
+ );
+ name = AQGridView;
+ productName = AQGridView;
+ productReference = D2AAC07E0554694100DB518D /* libAQGridView.a */;
+ productType = "com.apple.product-type.library.static";
+ };
+/* End PBXNativeTarget section */
+
+/* Begin PBXProject section */
+ 0867D690FE84028FC02AAC07 /* Project object */ = {
+ isa = PBXProject;
+ buildConfigurationList = 1DEB922208733DC00010E9CD /* Build configuration list for PBXProject "AQGridView" */;
+ compatibilityVersion = "Xcode 3.1";
+ hasScannedForEncodings = 1;
+ mainGroup = 0867D691FE84028FC02AAC07 /* AQGridView */;
+ productRefGroup = 034768DFFF38A50411DB9C8B /* Products */;
+ projectDirPath = "";
+ projectRoot = "";
+ targets = (
+ D2AAC07D0554694100DB518D /* AQGridView */,
+ );
+ };
+/* End PBXProject section */
+
+/* Begin PBXSourcesBuildPhase section */
+ D2AAC07B0554694100DB518D /* Sources */ = {
+ isa = PBXSourcesBuildPhase;
+ buildActionMask = 2147483647;
+ files = (
+ 389782BC117A311B00D5CC8F /* AQGridView.m in Sources */,
+ 389782C0117A311B00D5CC8F /* AQGridViewCell.m in Sources */,
+ 389782C2117A311B00D5CC8F /* AQGridViewController.m in Sources */,
+ 389782C4117A311B00D5CC8F /* AQGridViewData.m in Sources */,
+ 389782C6117A311B00D5CC8F /* AQGridViewUpdateInfo.m in Sources */,
+ 389782C8117A311B00D5CC8F /* AQGridViewUpdateItem.m in Sources */,
+ 389782CA117A311B00D5CC8F /* UIColor+AQGridView.m in Sources */,
+ );
+ runOnlyForDeploymentPostprocessing = 0;
+ };
+/* End PBXSourcesBuildPhase section */
+
+/* Begin XCBuildConfiguration section */
+ 1DEB921F08733DC00010E9CD /* Debug */ = {
+ isa = XCBuildConfiguration;
+ buildSettings = {
+ ALWAYS_SEARCH_USER_PATHS = NO;
+ ARCHS = "$(ARCHS_STANDARD_32_BIT)";
+ COPY_PHASE_STRIP = NO;
+ DSTROOT = /tmp/AQGridView.dst;
+ GCC_DYNAMIC_NO_PIC = NO;
+ GCC_ENABLE_FIX_AND_CONTINUE = YES;
+ GCC_MODEL_TUNING = G5;
+ GCC_OPTIMIZATION_LEVEL = 0;
+ GCC_PRECOMPILE_PREFIX_HEADER = YES;
+ GCC_PREFIX_HEADER = AQGridView_Prefix.pch;
+ INSTALL_PATH = /usr/local/lib;
+ PRODUCT_NAME = AQGridView;
+ };
+ name = Debug;
+ };
+ 1DEB922008733DC00010E9CD /* Release */ = {
+ isa = XCBuildConfiguration;
+ buildSettings = {
+ ALWAYS_SEARCH_USER_PATHS = NO;
+ ARCHS = "$(ARCHS_STANDARD_32_BIT)";
+ DSTROOT = /tmp/AQGridView.dst;
+ GCC_MODEL_TUNING = G5;
+ GCC_PRECOMPILE_PREFIX_HEADER = YES;
+ GCC_PREFIX_HEADER = AQGridView_Prefix.pch;
+ INSTALL_PATH = /usr/local/lib;
+ PRODUCT_NAME = AQGridView;
+ };
+ name = Release;
+ };
+ 1DEB922308733DC00010E9CD /* Debug */ = {
+ isa = XCBuildConfiguration;
+ buildSettings = {
+ ARCHS = "$(ARCHS_UNIVERSAL_IPHONE_OS)";
+ GCC_C_LANGUAGE_STANDARD = c99;
+ GCC_OPTIMIZATION_LEVEL = 0;
+ GCC_WARN_ABOUT_RETURN_TYPE = YES;
+ GCC_WARN_UNUSED_VARIABLE = YES;
+ IPHONEOS_DEPLOYMENT_TARGET = 3.0;
+ OTHER_LDFLAGS = "-ObjC";
+ PREBINDING = NO;
+ SDKROOT = iphoneos3.2;
+ TARGETED_DEVICE_FAMILY = "1,2";
+ };
+ name = Debug;
+ };
+ 1DEB922408733DC00010E9CD /* Release */ = {
+ isa = XCBuildConfiguration;
+ buildSettings = {
+ ARCHS = "$(ARCHS_UNIVERSAL_IPHONE_OS)";
+ GCC_C_LANGUAGE_STANDARD = c99;
+ GCC_WARN_ABOUT_RETURN_TYPE = YES;
+ GCC_WARN_UNUSED_VARIABLE = YES;
+ IPHONEOS_DEPLOYMENT_TARGET = 3.0;
+ OTHER_LDFLAGS = "-ObjC";
+ PREBINDING = NO;
+ SDKROOT = iphoneos3.2;
+ TARGETED_DEVICE_FAMILY = "1,2";
+ };
+ name = Release;
+ };
+/* End XCBuildConfiguration section */
+
+/* Begin XCConfigurationList section */
+ 1DEB921E08733DC00010E9CD /* Build configuration list for PBXNativeTarget "AQGridView" */ = {
+ isa = XCConfigurationList;
+ buildConfigurations = (
+ 1DEB921F08733DC00010E9CD /* Debug */,
+ 1DEB922008733DC00010E9CD /* Release */,
+ );
+ defaultConfigurationIsVisible = 0;
+ defaultConfigurationName = Release;
+ };
+ 1DEB922208733DC00010E9CD /* Build configuration list for PBXProject "AQGridView" */ = {
+ isa = XCConfigurationList;
+ buildConfigurations = (
+ 1DEB922308733DC00010E9CD /* Debug */,
+ 1DEB922408733DC00010E9CD /* Release */,
+ );
+ defaultConfigurationIsVisible = 0;
+ defaultConfigurationName = Release;
+ };
+/* End XCConfigurationList section */
+ };
+ rootObject = 0867D690FE84028FC02AAC07 /* Project object */;
+}
50 Classes/AQGridView+CellLayout.h
@@ -0,0 +1,50 @@
+/*
+ * AQGridView+CellLayout.h
+ * AQGridView
+ *
+ * Created by Jim Dovey on 8/3/2010.
+ * Copyright (c) 2010 Kobo Inc. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ *
+ * Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ *
+ * Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * Neither the name of the project's author nor the names of its
+ * contributors may be used to endorse or promote products derived from
+ * this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
+ * FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+ * HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED
+ * TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+ * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
+ * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
+ * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+ * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ *
+ */
+
+#import <UIKit/UIKit.h>
+#import "AQGridView.h"
+
+// used by AQGridViewUpdateInfo
+@interface AQGridView (AQCellLayout)
+- (CGRect) fixCellFrame: (CGRect) cellFrame forGridRect: (CGRect) gridRect;
+- (void) updateGridViewBoundsForNewGridData: (AQGridViewData *) newGridData;
+- (AQGridViewCell *) createPreparedCellForIndex: (NSUInteger) index;
+- (AQGridViewCell *) createPreparedCellForIndex: (NSUInteger) index usingGridData: (AQGridViewData *) gridData;
+- (void) insertVisibleCell: (AQGridViewCell *) cell atIndex: (NSUInteger) visibleCellListIndex;
+- (void) deleteVisibleCell: (AQGridViewCell *) cell atIndex: (NSUInteger) visibleCellListIndex appendingNewCell: (AQGridViewCell *) newCell;
+- (void) ensureCellInVisibleList: (AQGridViewCell *) cell;
+- (void) animationWillRevealItemsAtIndices: (NSRange) indices;
+@end
42 Classes/AQGridView+CellLocationDelegation.h
@@ -0,0 +1,42 @@
+/*
+ * AQGridView+CellLocationDelegation.h
+ * AQGridView
+ *
+ * Created by Jim Dovey on 11/3/2010.
+ * Copyright (c) 2010 Kobo Inc. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ *
+ * Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ *
+ * Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * Neither the name of the project's author nor the names of its
+ * contributors may be used to endorse or promote products derived from
+ * this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
+ * FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+ * HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED
+ * TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+ * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
+ * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
+ * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+ * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ *
+ */
+
+#import <Foundation/Foundation.h>
+#import "AQGridView.h"
+
+@interface AQGridView (CellLocationDelegation)
+- (void) delegateWillDisplayCell: (AQGridViewCell *) cell atIndex: (NSUInteger) index;
+@end
233 Classes/AQGridView.h
@@ -0,0 +1,233 @@
+/*
+ * AQGridView.h
+ * AQGridView
+ *
+ * Created by Jim Dovey on 10/2/2010.
+ * Copyright 2010 Kobo Inc. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ *
+ * Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ *
+ * Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * Neither the name of the project's author nor the names of its
+ * contributors may be used to endorse or promote products derived from
+ * this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
+ * FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+ * HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED
+ * TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+ * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
+ * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
+ * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+ * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ *
+ */
+
+#import <UIKit/UIKit.h>
+#import "AQGridViewCell.h"
+
+typedef enum {
+ AQGridViewScrollPositionNone,
+ AQGridViewScrollPositionTop,
+ AQGridViewScrollPositionMiddle,
+ AQGridViewScrollPositionBottom
+} AQGridViewScrollPosition;
+
+typedef enum {
+ AQGridViewItemAnimationFade,
+ AQGridViewItemAnimationRight,
+ AQGridViewItemAnimationLeft,
+ AQGridViewItemAnimationTop,
+ AQGridViewItemAnimationBottom,
+ AQGridViewItemAnimationNone
+} AQGridViewItemAnimation;
+
+@protocol AQGridViewDataSource;
+@class AQGridView, AQGridViewData, AQGridViewUpdateInfo;
+
+@protocol AQGridViewDelegate <NSObject, UIScrollViewDelegate>
+
+@optional
+
+// Display customization
+
+- (void) gridView: (AQGridView *) gridView willDisplayCell: (AQGridViewCell *) cell forItemAtIndex: (NSUInteger) index;
+
+// Selection
+
+// Called before selection occurs. Return a new index, or NSNotFound, to change the proposed selection.
+- (NSUInteger) gridView: (AQGridView *) gridView willSelectItemAtIndex: (NSUInteger) index;
+- (NSUInteger) gridView: (AQGridView *) gridView willDeselectItemAtIndex: (NSUInteger) index;
+// Called after the user changes the selection
+- (void) gridView: (AQGridView *) gridView didSelectItemAtIndex: (NSUInteger) index;
+- (void) gridView: (AQGridView *) gridView didDeselectItemAtIndex: (NSUInteger) index;
+
+// NOT YET IMPLEMENTED
+- (void) gridView: (AQGridView *) gridView gestureRecognizer: (UIGestureRecognizer *) recognizer activatedForItemAtIndex: (NSUInteger) index;
+
+- (CGRect) gridView: (AQGridView *) gridView adjustCellFrame: (CGRect) cellFrame withinGridCellFrame: (CGRect) gridCellFrame;
+
+@end
+
+extern NSString * const AQGridViewSelectionDidChangeNotification;
+
+@interface AQGridView : UIScrollView
+{
+ id<AQGridViewDataSource> _dataSource;
+
+ AQGridViewData * _gridData;
+ AQGridViewUpdateInfo * _updateInfo;
+
+ CGRect _visibleBounds;
+ NSRange _visibleIndices;
+ NSMutableArray * _visibleCells;
+ NSMutableDictionary * _reusableGridCells;
+
+ NSArray * _animatingCells;
+ NSRange _revealingIndices;
+
+ NSMutableIndexSet * _highlightedIndices;
+ UIView * _touchedContentView; // weak reference
+
+ UIView * _backgroundView;
+ UIColor * _separatorColor;
+
+ NSInteger _reloadingSuspendedCount;
+ NSInteger _displaySuspendedCount;
+
+ NSInteger _updateCount;
+
+ NSUInteger _selectedIndex;
+ NSUInteger _pendingSelectionIndex;
+
+ CGPoint _touchBeganPosition;
+
+ UIView * _headerView;
+ UIView * _footerView;
+
+ struct
+ {
+ unsigned resizesCellWidths:1;
+ unsigned numColumns:6;
+ unsigned separatorStyle:3;
+ unsigned allowsSelection:1;
+ unsigned usesPagedHorizontalScrolling:1;
+ unsigned updating:1;
+ unsigned ignoreTouchSelect:1;
+ unsigned needsReload:1;
+ unsigned allCellsNeedLayout:1;
+ unsigned isRotating:1;
+ unsigned clipsContentWidthToBounds:1;
+ unsigned isAnimatingUpdates:1;
+ unsigned requiresSelection:1;
+ unsigned contentSizeFillsBounds:1;
+
+ unsigned delegateWillDisplayCell:1;
+ unsigned delegateWillSelectItem:1;
+ unsigned delegateWillDeselectItem:1;
+ unsigned delegateDidSelectItem:1;
+ unsigned delegateDidDeselectItem:1;
+ unsigned delegateGestureRecognizerActivated:1;
+ unsigned delegateAdjustGridCellFrame:1;
+
+ unsigned dataSourceGridCellSize:1;
+
+ unsigned __RESERVED__:3;
+ } _flags;
+}
+
+@property (nonatomic, assign) IBOutlet id<AQGridViewDataSource> dataSource;
+@property (nonatomic, assign) IBOutlet id<AQGridViewDelegate> delegate;
+
+// Data
+
+- (void) reloadData;
+
+// Info
+
+@property (nonatomic, readonly) NSUInteger numberOfItems;
+@property (nonatomic, readonly) NSUInteger numberOfColumns;
+@property (nonatomic, readonly) NSUInteger numberOfRows;
+
+@property (nonatomic, readonly) CGSize gridCellSize;
+
+- (CGRect) rectForItemAtIndex: (NSUInteger) index;
+- (AQGridViewCell *) cellForItemAtIndex: (NSUInteger) index;
+- (NSUInteger) indexForItemAtPoint: (CGPoint) point;
+- (AQGridViewCell *) cellForItemAtPoint: (CGPoint) point;
+
+- (NSArray *) visibleCells;
+- (NSIndexSet *) visibleCellIndices;
+
+- (void) scrollToItemAtIndex: (NSUInteger) index atScrollPosition: (AQGridViewScrollPosition) scrollPosition animated: (BOOL) animated;
+
+// Insertion/deletion/reloading
+
+- (void) beginUpdates; // allow multiple insert/delete of items to be animated simultaneously. Nestable.
+- (void) endUpdates; // only call insert/delete/reload calls inside an update block.
+
+- (void) insertItemsAtIndices: (NSIndexSet *) indices withAnimation: (AQGridViewItemAnimation) animation;
+- (void) deleteItemsAtIndices: (NSIndexSet *) indices withAnimation: (AQGridViewItemAnimation) animation;
+- (void) reloadItemsAtIndices: (NSIndexSet *) indices withAnimation: (AQGridViewItemAnimation) animation;
+
+- (void) moveItemAtIndex: (NSUInteger) index toIndex: (NSUInteger) newIndex withAnimation: (AQGridViewItemAnimation) animation;
+
+// Selection
+
+@property (nonatomic) BOOL allowsSelection; // default is YES
+@property (nonatomic) BOOL requiresSelection; // if YES, tapping on a selected cell will not de-select it
+
+- (NSUInteger) indexOfSelectedItem; // returns NSNotFound if no item is selected
+- (void) selectItemAtIndex: (NSUInteger) index animated: (BOOL) animated scrollPosition: (AQGridViewScrollPosition) scrollPosition;
+- (void) deselectItemAtIndex: (NSUInteger) index animated: (BOOL) animated;
+
+// Appearance
+
+@property (nonatomic, assign) BOOL resizesCellWidthToFit; // default is NO. Set to YES if the view should resize cells to fill all available space in their grid square. Ignored if separatorStyle == AQGridViewCellSeparatorStyleEmptySpace.
+@property (nonatomic, assign) BOOL clipsContentWidthToBounds; // default is YES. If you want to enable horizontal scrolling, set this to NO.
+
+@property (nonatomic, retain) UIView * backgroundView; // specifies a view to place behind the cells
+@property (nonatomic) BOOL usesPagedHorizontalScrolling; // default is NO, and scrolls verticalls only. Set to YES to have horizontal-only scrolling by page.
+
+@property (nonatomic) AQGridViewCellSeparatorStyle separatorStyle; // default is AQGridViewCellSeparatorStyleEmptySpace
+@property (nonatomic, retain) UIColor * separatorColor; // ignored unless separatorStyle == AQGridViewCellSeparatorStyleSingleLine. Default is standard separator gray.
+
+- (AQGridViewCell *) dequeueReusableCellWithIdentifier: (NSString *) reuseIdentifier;
+
+// Headers and Footers
+
+@property (nonatomic, retain) UIView * gridHeaderView;
+@property (nonatomic, retain) UIView * gridFooterView;
+
+@property (nonatomic, assign) CGFloat leftContentInset;
+@property (nonatomic, assign) CGFloat rightContentInset;
+
+@property (nonatomic, assign) BOOL contentSizeGrowsToFillBounds; // default is YES. Prior to iPhone OS 3.2, pattern colors tile from the bottom-left, necessitating that this be set to NO to avoid specially-constructed background patterns falling 'out of sync' with the cells displayed on top of it.
+
+@end
+
+@protocol AQGridViewDataSource <NSObject>
+
+@required
+
+- (NSUInteger) numberOfItemsInGridView: (AQGridView *) gridView;
+- (AQGridViewCell *) gridView: (AQGridView *) gridView cellForItemAtIndex: (NSUInteger) index;
+
+@optional
+
+// all cells are placed in a logical 'grid cell', all of which are the same size. The default size is 96x128 (portrait).
+// The width/height values returned by this function will be rounded UP to the nearest denominator of the screen width.
+- (CGSize) portraitGridCellSizeForGridView: (AQGridView *) gridView;
+
+@end
1,484 Classes/AQGridView.m
@@ -0,0 +1,1484 @@
+/*
+ * AQGridView.m
+ * AQGridView
+ *
+ * Created by Jim Dovey on 10/2/2010.
+ * Copyright 2010 Kobo Inc. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ *
+ * Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ *
+ * Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * Neither the name of the project's author nor the names of its
+ * contributors may be used to endorse or promote products derived from
+ * this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
+ * FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+ * HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED
+ * TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+ * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
+ * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
+ * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+ * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ *
+ */
+
+#import "AQGridView.h"
+#import "AQGridViewUpdateItem.h"
+#import "AQGridViewData.h"
+#import "AQGridViewUpdateInfo.h"
+#import "AQGridViewCell+AQGridViewCellPrivate.h"
+#import "AQGridView+CellLocationDelegation.h"
+
+// see _basicHitTest:withEvent: below
+#import <objc/objc.h>
+#import <objc/runtime.h>
+
+NSString * const AQGridViewSelectionDidChangeNotification = @"AQGridViewSelectionDidChangeNotification";
+
+@interface AQGridView ()
+@property (nonatomic, copy) NSArray * animatingCells;
+@end
+
+@interface AQGridView (AQCellGridMath)
+- (NSUInteger) visibleCellListIndexForItemIndex: (NSUInteger) itemIndex;
+@end
+
+@interface AQGridView (AQCellLayout)
+- (void) layoutCellsInVisibleCellRange: (NSRange) range;
+- (void) layoutAllCells;
+- (CGRect) fixCellFrame: (CGRect) cellFrame forGridRect: (CGRect) gridRect;
+- (void) updateVisibleGridCellsNow;
+- (AQGridViewCell *) createPreparedCellForIndex: (NSUInteger) index;
+- (void) insertVisibleCell: (AQGridViewCell *) cell atIndex: (NSUInteger) visibleCellListIndex;
+- (void) deleteVisibleCell: (AQGridViewCell *) cell atIndex: (NSUInteger) visibleCellListIndex appendingNewCell: (AQGridViewCell *) newLastCell;
+@end
+
+@implementation AQGridView
+
+@synthesize dataSource=_dataSource, backgroundView=_backgroundView, separatorColor=_separatorColor, animatingCells=_animatingCells;
+
+- (void) _sharedGridViewInit
+{
+ _gridData = [[AQGridViewData alloc] initWithGridView: self];
+ [_gridData setDesiredCellSize: CGSizeMake(96.0, 128.0)];
+
+ _visibleBounds = self.bounds;
+ _visibleCells = [[NSMutableArray alloc] init];
+ _reusableGridCells = [[NSMutableDictionary alloc] init];
+ _highlightedIndices = [[NSMutableIndexSet alloc] init];
+
+ self.separatorColor = [UIColor colorWithWhite: 0.85 alpha: 1.0];
+
+ _selectedIndex = NSNotFound;
+ _pendingSelectionIndex = NSNotFound;
+
+ _flags.resizesCellWidths = 0;
+ _flags.numColumns = [_gridData numberOfItemsPerRow];
+ _flags.separatorStyle = AQGridViewCellSeparatorStyleEmptySpace;
+ _flags.allowsSelection = 1;
+ _flags.usesPagedHorizontalScrolling = NO;
+ _flags.clipsContentWidthToBounds = 1;
+ _flags.contentSizeFillsBounds = 1;
+}
+
+- (id)initWithFrame: (CGRect) frame
+{
+ self = [super initWithFrame:frame];
+ if ( self == nil )
+ return ( nil );
+
+ [self _sharedGridViewInit];
+
+ return ( self );
+}
+
+- (id) initWithCoder: (NSCoder *) aDecoder
+{
+ self = [super initWithCoder: aDecoder];
+ if ( self == nil )
+ return ( nil );
+
+ [self _sharedGridViewInit];
+
+ return ( self );
+}
+
+/*
+// Only override drawRect: if you perform custom drawing.
+// An empty implementation adversely affects performance during animation.
+- (void)drawRect:(CGRect)rect {
+ // Drawing code
+}
+*/
+
+- (void)dealloc
+{
+ [_visibleCells release];
+ [_reusableGridCells release];
+ [_highlightedIndices release];
+ [_backgroundView release];
+ [_separatorColor release];
+ [_gridData release];
+ [_updateInfo release];
+ [_animatingCells release];
+ [_headerView release];
+ [_footerView release];
+
+ [super dealloc];
+}
+
+#pragma mark -
+#pragma mark Properties
+
+- (void) setDelegate: (id<AQGridViewDelegate>) obj
+{
+ if ( (obj != nil) && ([obj conformsToProtocol: @protocol(AQGridViewDelegate)] == NO ))
+ [NSException raise: NSInvalidArgumentException format: @"Argument to -setDelegate must conform to the AQGridViewDelegate protocol"];
+ [super setDelegate: obj];
+
+ _flags.delegateWillDisplayCell = [obj respondsToSelector: @selector(gridView:willDisplayCell:forItemAtIndex:)];
+ _flags.delegateWillSelectItem = [obj respondsToSelector: @selector(gridView:willSelectItemAtIndex:)];
+ _flags.delegateWillDeselectItem = [obj respondsToSelector: @selector(gridView:willDeselectItemAtIndex:)];
+ _flags.delegateDidSelectItem = [obj respondsToSelector: @selector(gridView:didSelectItemAtIndex:)];
+ _flags.delegateDidDeselectItem = [obj respondsToSelector: @selector(gridView:didDeselectItemAtIndex:)];
+ _flags.delegateGestureRecognizerActivated = [obj respondsToSelector: @selector(gridView:gestureRecognizer:activatedForItemAtIndex:)];
+ _flags.delegateAdjustGridCellFrame = [obj respondsToSelector: @selector(gridView:adjustCellFrame:withinGridCellFrame:)];
+}
+
+- (id<AQGridViewDelegate>) delegate
+{
+ id obj = [super delegate];
+ if ( [obj conformsToProtocol: @protocol(AQGridViewDelegate)] == NO )
+ return ( nil );
+ return ( obj );
+}
+
+- (void) setDataSource: (id<AQGridViewDataSource>) obj
+{
+ if ((obj != nil) && ([obj conformsToProtocol: @protocol(AQGridViewDataSource)] == NO ))
+ [NSException raise: NSInvalidArgumentException format: @"Argument to -setDataSource must conform to the AQGridViewDataSource protocol"];
+
+ _dataSource = obj;
+
+ _flags.dataSourceGridCellSize = [obj respondsToSelector: @selector(portraitGridCellSizeForGridView:)];
+}
+
+- (NSUInteger) numberOfItems
+{
+ return ( _gridData.numberOfItems );
+}
+
+- (NSUInteger) numberOfColumns
+{
+ if ( _flags.numColumns == 0 )
+ _flags.numColumns = 1;
+ return ( _flags.numColumns );
+}
+
+- (NSUInteger) numberOfRows
+{
+ return ( _gridData.numberOfItems / _flags.numColumns );
+}
+
+- (BOOL) allowsSelection
+{
+ return ( _flags.allowsSelection );
+}
+
+- (void) setAllowsSelection: (BOOL) value
+{
+ _flags.allowsSelection = (value ? 1 : 0);
+}
+
+- (BOOL) requiresSelection
+{
+ return ( _flags.requiresSelection );
+}
+
+- (void) setRequiresSelection: (BOOL) value
+{
+ _flags.requiresSelection = (value ? 1 : 0);
+}
+
+- (BOOL) resizesCellWidthToFit
+{
+ return ( _flags.resizesCellWidths );
+}
+
+- (void) setResizesCellWidthToFit: (BOOL) value
+{
+ int i = (value ? 1 : 0);
+ if ( _flags.resizesCellWidths == i )
+ return;
+
+ _flags.resizesCellWidths = i;
+ [self setNeedsLayout];
+}
+
+- (BOOL) clipsContentWidthToBounds
+{
+ return ( _flags.clipsContentWidthToBounds );
+}
+
+- (void) setClipsContentWidthToBounds: (BOOL) value
+{
+ _flags.clipsContentWidthToBounds = value;
+}
+
+- (BOOL) usesPagedHorizontalScrolling
+{
+ return ( _flags.usesPagedHorizontalScrolling );
+}
+
+- (void) setUsesPagedHorizontalScrolling: (BOOL) value
+{
+ int i = (value ? 1 : 0);
+ if ( _flags.usesPagedHorizontalScrolling == i )
+ return;
+
+ _flags.usesPagedHorizontalScrolling = i;
+ [self setNeedsLayout];
+}
+
+- (AQGridViewCellSeparatorStyle) separatorStyle
+{
+ return ( _flags.separatorStyle );
+}
+
+- (void) setSeparatorStyle: (AQGridViewCellSeparatorStyle) style
+{
+ if ( style == _flags.separatorStyle )
+ return;
+
+ _flags.separatorStyle = style;
+
+ for ( AQGridViewCell * cell in _visibleCells )
+ {
+ cell.separatorStyle = style;
+ }
+
+ [self setNeedsLayout];
+}
+
+- (CGFloat) leftContentInset
+{
+ return ( _gridData.leftPadding );
+}
+
+- (void) setLeftContentInset: (CGFloat) inset
+{
+ _gridData.leftPadding = inset;
+}
+
+- (CGFloat) rightContentInset
+{
+ return ( _gridData.rightPadding );
+}
+
+- (void) setRightContentInset: (CGFloat) inset
+{
+ _gridData.rightPadding = inset;
+}
+
+- (CGSize) gridCellSize
+{
+ return ( [_gridData cellSize] );
+}
+
+- (UIView *) gridHeaderView
+{
+ return ( [[_headerView retain] autorelease] );
+}
+
+- (void) setGridHeaderView: (UIView *) newHeaderView
+{
+ if ( newHeaderView == _headerView )
+ return;
+
+ [_headerView removeFromSuperview];
+ [_headerView release];
+
+ _headerView = [newHeaderView retain];
+ if ( _headerView == nil )
+ {
+ _gridData.topPadding = 0.0;
+ }
+ else
+ {
+ [self addSubview: _headerView];
+ _gridData.topPadding = _headerView.frame.size.height;
+ }
+
+ [self setNeedsLayout];
+}
+
+- (UIView *) gridFooterView
+{
+ return ( [[_footerView retain] autorelease] );
+}
+
+- (void) setGridFooterView: (UIView *) newFooterView
+{
+ if ( newFooterView == _footerView )
+ return;
+
+ [_footerView removeFromSuperview];
+ [_footerView release];
+
+ _footerView = [newFooterView retain];
+ if ( _footerView == nil )
+ {
+ _gridData.bottomPadding = 0.0;
+ }
+ else
+ {
+ [self addSubview: _footerView];
+ _gridData.bottomPadding = _footerView.frame.size.height;
+ }
+
+ [self setNeedsLayout];
+}
+
+- (BOOL) contentSizeGrowsToFillBounds
+{
+ return ( _flags.contentSizeFillsBounds == 1 );
+}
+
+- (void) setContentSizeGrowsToFillBounds: (BOOL) value
+{
+ _flags.contentSizeFillsBounds = (value ? 1 : 0);
+}
+
+- (void) updateContentRectWithOldMaxY: (CGFloat) oldMaxY gridHeight: (CGFloat) gridHeight
+{
+ // update content size
+ CGFloat contentWidth = _flags.clipsContentWidthToBounds ? self.bounds.size.width : MAX(self.contentSize.width, self.bounds.size.width);
+ self.contentSize = CGSizeMake(contentWidth, gridHeight);
+
+ // fix content offset if applicable
+ CGPoint offset = self.contentOffset;
+ if ( offset.y + self.bounds.size.height > self.contentSize.height )
+ {
+ offset.y = MAX(0.0, self.contentSize.height - self.bounds.size.height);
+ self.contentOffset = offset;
+ }
+ else if ( oldMaxY == self.contentSize.height )
+ {
+ // we were scrolled to the bottom-- stay there as our height decreases
+ offset.y = MAX(0.0, self.contentSize.height - self.bounds.size.height);
+ self.contentOffset = offset;
+ }
+}
+
+- (void) handleGridViewBoundsChanged: (CGRect) oldBounds toNewBounds: (CGRect) bounds
+{
+ //[_gridData gridViewDidChangeToWidth: bounds.size.width];
+ [self updateContentRectWithOldMaxY: CGRectGetMaxY(oldBounds) gridHeight: [_gridData heightForEntireGrid]];
+ [self updateVisibleGridCellsNow];
+ _flags.allCellsNeedLayout = 1;
+}
+
+- (void) setContentSize: (CGSize) newSize
+{
+ if ( (_flags.contentSizeFillsBounds == 1) && (newSize.height < self.bounds.size.height) )
+ newSize.height = self.bounds.size.height;
+
+ CGSize oldSize = self.contentSize;
+ [super setContentSize: newSize];
+
+ if ( oldSize.width != newSize.width )
+ [_gridData gridViewDidChangeToWidth: newSize.width];
+
+ if ( CGRectGetMaxY(self.bounds) > newSize.height )
+ {
+ CGRect b = self.bounds;
+ CGFloat diff = CGRectGetMaxY(b) - newSize.height;
+ b.origin.y = MAX(0.0, b.origin.y - diff);
+ self.bounds = b;
+ }
+}
+
+- (void) setFrame: (CGRect) newFrame
+{
+ CGRect oldBounds = self.bounds;
+ [super setFrame: newFrame];
+ CGRect newBounds = self.bounds;
+
+ if ( newBounds.size.width != oldBounds.size.width )
+ [self handleGridViewBoundsChanged: oldBounds toNewBounds: newBounds];
+}
+
+- (void) setBounds: (CGRect) bounds
+{
+ CGRect oldBounds = self.bounds;
+ [super setBounds: bounds];
+ bounds = self.bounds; // in case it was modified
+
+ if ( bounds.size.width != oldBounds.size.width )
+ [self handleGridViewBoundsChanged: oldBounds toNewBounds: bounds];
+}
+
+#pragma mark -
+#pragma mark Data Management
+
+- (AQGridViewCell *) dequeueReusableCellWithIdentifier: (NSString *) reuseIdentifier
+{
+ NSMutableArray * cells = [_reusableGridCells objectForKey: reuseIdentifier];
+ AQGridViewCell * cell = [[cells lastObject] retain];
+ if ( cell == nil )
+ return ( nil );
+
+ [cell prepareForReuse];
+
+ [cells removeLastObject];
+ return ( [cell autorelease] );
+}
+
+- (void) enqueueReusableCells: (NSArray *) reusableCells
+{
+ for ( AQGridViewCell * cell in reusableCells )
+ {
+ NSMutableArray * reuseArray = [_reusableGridCells objectForKey: cell.reuseIdentifier];
+ if ( reuseArray == nil )
+ {
+ reuseArray = [[NSMutableArray alloc] init];
+ [_reusableGridCells setObject: reuseArray forKey: cell.reuseIdentifier];
+ [reuseArray release];
+ }
+
+ [reuseArray addObject: cell];
+ }
+}
+
+- (CGRect) gridViewVisibleBounds
+{
+ CGRect result = CGRectZero;
+ result.origin = self.contentOffset;
+ result.size = self.bounds.size;
+ return ( result );
+}
+
+- (void) reloadData
+{
+ if ( _reloadingSuspendedCount != 0 )
+ return;
+
+ if ( _flags.dataSourceGridCellSize == 1 )
+ {
+ [_gridData setDesiredCellSize: [_dataSource portraitGridCellSizeForGridView: self]];
+ _flags.numColumns = [_gridData numberOfItemsPerRow];
+ }
+
+ _gridData.numberOfItems = [_dataSource numberOfItemsInGridView: self];
+ if ( _gridData.numberOfItems == 0 )
+ return;
+
+ // update our content size as appropriate
+ CGSize size = self.contentSize;
+ size.height = [_gridData heightForEntireGrid];
+ self.contentSize = size;
+
+ // remove all existing cells
+ _visibleIndices.length = 0;
+
+ [_visibleCells makeObjectsPerformSelector: @selector(removeFromSuperview)];
+ [self enqueueReusableCells: _visibleCells];
+ [_visibleCells removeAllObjects];
+
+ // reload the cell list
+ [self updateVisibleGridCellsNow];
+
+ // layout -- no animation
+ [self setNeedsLayout];
+ _flags.allCellsNeedLayout = 1;
+}
+
+- (void) layoutSubviews
+{
+ if ( (_flags.needsReload == 1) && (_flags.updating == 0) && (_reloadingSuspendedCount == 0) )
+ [self reloadData];
+
+ if ( (_reloadingSuspendedCount == 0) && (!CGRectIsEmpty([self gridViewVisibleBounds])) )
+ {
+ NSAutoreleasePool * pool = [[NSAutoreleasePool alloc] init];
+ [self updateVisibleGridCellsNow];
+ [pool release];
+ }
+
+ if ( _flags.allCellsNeedLayout == 1 )
+ {
+ _flags.allCellsNeedLayout = 0;
+ if ( _visibleIndices.length != 0 )
+ [self layoutAllCells];
+ }
+
+ CGRect rect = CGRectZero;
+ rect.size = self.contentSize;
+ rect.size.height -= (_gridData.topPadding + _gridData.bottomPadding);
+ rect.origin.y += _gridData.topPadding;
+ self.backgroundView.frame = rect;
+
+ if ( _headerView != nil )
+ {
+ rect = _headerView.frame;
+ rect.origin = CGPointZero;
+ rect.size.width = self.bounds.size.width;
+ _headerView.frame = rect;
+ }
+
+ if ( _footerView != nil )
+ {
+ rect = _footerView.frame;
+ rect.origin.x = 0.0;
+ rect.origin.y = self.contentSize.height - rect.size.height;
+ rect.size.width = self.bounds.size.width;
+ _footerView.frame = rect;
+ }
+}
+
+- (CGRect) rectForItemAtIndex: (NSUInteger) index
+{
+ // simple case -- there's a cell already, we can just ask for its frame
+ if ( NSLocationInRange(index, _visibleIndices) )
+ return ( [[_visibleCells objectAtIndex: [self visibleCellListIndexForItemIndex: index]] frame] );
+
+ // complex case-- compute the frame manually
+ return ( [self fixCellFrame: CGRectZero forGridRect: [_gridData cellRectAtIndex: index]] );
+}
+
+- (AQGridViewCell *) cellForItemAtIndex: (NSUInteger) index
+{
+ //if ( NSLocationInRange(index, _visibleIndices) == NO )
+ // return ( nil );
+
+ // we don't clip to visible range-- when animating edits the visible cell list can contain extra items
+ NSUInteger visibleCellListIndex = [self visibleCellListIndexForItemIndex: index];
+ if ( visibleCellListIndex < [_visibleCells count] )
+ return ( [_visibleCells objectAtIndex: visibleCellListIndex] );
+ return ( nil );
+}
+
+- (NSUInteger) indexForItemAtPoint: (CGPoint) point
+{
+ return ( [_gridData itemIndexForPoint: point] );
+}
+
+- (AQGridViewCell *) cellForItemAtPoint: (CGPoint) point
+{
+ return ( [self cellForItemAtIndex: [_gridData itemIndexForPoint: point]] );
+}
+
+- (NSArray *) visibleCells
+{
+ return ( [[_visibleCells copy] autorelease] );
+}
+
+- (NSIndexSet *) visibleCellIndices
+{
+ return ( [NSIndexSet indexSetWithIndexesInRange: _visibleIndices] );
+}
+
+- (void) scrollToItemAtIndex: (NSUInteger) index atScrollPosition: (AQGridViewScrollPosition) scrollPosition
+ animated: (BOOL) animated
+{
+ CGRect gridRect = [_gridData cellRectAtIndex: index];
+ CGRect targetRect = self.bounds;
+
+ switch ( scrollPosition )
+ {
+ case AQGridViewScrollPositionNone:
+ default:
+ targetRect = gridRect; // no special coordinate handling
+ break;
+
+ case AQGridViewScrollPositionTop:
+ targetRect.origin.y = gridRect.origin.y; // set target y origin to cell's y origin
+ break;
+
+ case AQGridViewScrollPositionMiddle:
+ targetRect.origin.y = MAX(gridRect.origin.y - (CGFloat)ceilf((targetRect.size.height - gridRect.size.height) * 0.5), 0.0);
+ break;
+
+ case AQGridViewScrollPositionBottom:
+ targetRect.origin.y = MAX((CGFloat)floorf(gridRect.origin.y - (targetRect.size.height - gridRect.size.height)), 0.0);
+ break;
+ }
+
+ [self scrollRectToVisible: targetRect animated: animated];
+}
+
+#pragma mark -
+#pragma mark Cell Updates
+
+- (BOOL) isRectVisible: (CGRect) frameRect
+{
+ return ( CGRectIntersectsRect(frameRect, self.bounds) );
+}
+
+- (void) fixCellsFromAnimation
+{
+ // update the visible item list appropriately
+ NSIndexSet * indices = [_gridData indicesOfCellsInRect: self.bounds];
+ if ( [indices count] == 0 )
+ {
+ _visibleIndices.location = 0;
+ _visibleIndices.length = 0;
+
+ // update the content size/offset based on the new grid data
+ [self updateContentRectWithOldMaxY: CGRectGetMaxY(self.bounds) gridHeight: [_gridData heightForEntireGrid]];
+ return;
+ }
+
+ _visibleIndices.location = [indices firstIndex];
+ _visibleIndices.length = ([indices lastIndex] - [indices firstIndex]) + 1;
+
+ NSMutableArray * newVisibleCells = [[NSMutableArray alloc] initWithCapacity: _visibleIndices.length];
+ for ( UIView * potentialCellView in self.animatingCells )
+ {
+ if ( [potentialCellView isKindOfClass: [AQGridViewCell class]] == NO )
+ {
+ [potentialCellView removeFromSuperview];
+ continue;
+ }
+
+ if ( [self isRectVisible: [_gridData cellRectForPoint: potentialCellView.center]] == NO )
+ {
+ [potentialCellView removeFromSuperview];
+ continue;
+ }
+
+ [newVisibleCells addObject: potentialCellView];
+ }
+
+ [newVisibleCells sortUsingSelector: @selector(compareOriginAgainstCell:)];
+ [_visibleCells removeObjectsInArray: newVisibleCells];
+ [_visibleCells makeObjectsPerformSelector: @selector(removeFromSuperview)];
+ [_visibleCells setArray: newVisibleCells];
+ [newVisibleCells release];
+ self.animatingCells = nil;
+ _revealingIndices.length = _revealingIndices.location = 0;
+
+ // update the content size/offset based on the new grid data
+ [self updateContentRectWithOldMaxY: CGRectGetMaxY(self.bounds) gridHeight: [_gridData heightForEntireGrid]];
+}
+
+- (void) setupUpdateAnimations
+{
+ _flags.updating = 1;
+ _flags.isAnimatingUpdates = 1;
+ _reloadingSuspendedCount++;
+ if ( _updateInfo == nil )
+ _updateInfo = [[AQGridViewUpdateInfo alloc] initWithOldGridData: _gridData forGridView: self];
+}
+
+- (void) endUpdateAnimations
+{
+ NSAssert(_updateInfo != nil, @"_updateInfo should not be nil at this point" );
+
+ _reloadingSuspendedCount--;
+ if ( _updateInfo.numberOfUpdates == 0 )
+ {
+ //_reloadingSuspendedCount--;
+ [_updateInfo release];
+ _updateInfo = nil;
+ return;
+ }
+
+ [_updateInfo cleanupUpdateItems];
+ self.animatingCells = [_updateInfo animateCellUpdatesUsingVisibleContentRect: [self gridViewVisibleBounds]];
+
+ _flags.updating = 0;
+ [_gridData release];
+ _gridData = [[_updateInfo newGridViewData] retain];
+ if ( _selectedIndex != NSNotFound )
+ _selectedIndex = [_updateInfo newIndexForOldIndex: _selectedIndex];
+ [_updateInfo release];
+ _updateInfo = nil;
+
+ // if no animation occurred, this has already been done
+ if ( _reloadingSuspendedCount == 0 )
+ [self fixCellsFromAnimation];
+}
+
+- (void) cellUpdateAnimationStopped: (NSString *) animationID finished: (BOOL) finished context: (void *) context
+{
+ // if nothing was animated, we don't have to do anything at all
+ if ( self.animatingCells.count != 0 )
+ [self fixCellsFromAnimation];
+
+ _flags.isAnimatingUpdates = 0;
+
+ //_reloadingSuspendedCount--;
+}
+
+- (void) beginUpdates
+{
+ if ( _updateCount++ == 0 )
+ [self setupUpdateAnimations];
+}
+
+- (void) endUpdates
+{
+ if ( --_updateCount == 0 )
+ [self endUpdateAnimations];
+}
+
+- (void) _updateItemsAtIndices: (NSIndexSet *) indices updateAction: (AQGridViewUpdateAction) action withAnimation: (AQGridViewItemAnimation) animation
+{
+ BOOL wasUpdating = (_flags.updating == 1);
+
+ // not in the middle of an update loop -- start animations here
+ if ( wasUpdating == NO )
+ [self setupUpdateAnimations];
+
+ [_updateInfo updateItemsAtIndices: indices updateAction: action withAnimation: animation];
+
+ // not in the middle of an update loop -- commit animations here
+ if ( wasUpdating == NO )
+ [self endUpdateAnimations];
+}
+
+- (void) insertItemsAtIndices: (NSIndexSet *) indices withAnimation: (AQGridViewItemAnimation) animation
+{
+ [self _updateItemsAtIndices: indices updateAction: AQGridViewUpdateActionInsert withAnimation: animation];
+}
+
+- (void) deleteItemsAtIndices: (NSIndexSet *) indices withAnimation: (AQGridViewItemAnimation) animation
+{
+ [self _updateItemsAtIndices: indices updateAction: AQGridViewUpdateActionDelete withAnimation: animation];
+}
+
+- (void) reloadItemsAtIndices: (NSIndexSet *) indices withAnimation: (AQGridViewItemAnimation) animation
+{
+ [self _updateItemsAtIndices: indices updateAction: AQGridViewUpdateActionReload withAnimation: animation];
+}
+
+- (void) moveItemAtIndex: (NSUInteger) index toIndex: (NSUInteger) newIndex withAnimation: (AQGridViewItemAnimation) animation
+{
+ BOOL wasUpdating = (_flags.updating == 1);
+
+ if ( wasUpdating == NO )
+ [self setupUpdateAnimations];
+
+ [_updateInfo moveItemAtIndex: index toIndex: newIndex withAnimation: animation];
+
+ if ( wasUpdating == NO )
+ [self endUpdateAnimations];
+}
+
+#pragma mark -
+#pragma mark Selection
+
+- (NSUInteger) indexOfSelectedItem
+{
+ return ( _selectedIndex );
+}
+
+- (void) selectItemAtIndex: (NSUInteger) index animated: (BOOL) animated
+ scrollPosition: (AQGridViewScrollPosition) scrollPosition
+{
+ if ( _selectedIndex != NSNotFound )
+ [self deselectItemAtIndex: _selectedIndex animated: NO];
+
+ _selectedIndex = index;
+ [self scrollToItemAtIndex: index atScrollPosition: AQGridViewScrollPositionNone animated: animated];
+}
+
+- (void) deselectItemAtIndex: (NSUInteger) index animated: (BOOL) animated
+{
+ AQGridViewCell * cell = [self cellForItemAtIndex: index];
+ if ( cell != nil )
+ [cell setSelected: NO animated: animated];
+
+ if ( _selectedIndex == index )
+ _selectedIndex = NSNotFound;
+}
+
+- (void) highlightItemAtIndex: (NSUInteger) index animated: (BOOL) animated scrollPosition: (AQGridViewScrollPosition) position
+{
+ if ( [_highlightedIndices containsIndex: index] )
+ {
+ if ( position != AQGridViewScrollPositionNone )
+ [self scrollToItemAtIndex: index atScrollPosition: position animated: animated];
+ return;
+ }
+
+ if ( index == NSNotFound )
+ {
+ NSUInteger i = [_highlightedIndices firstIndex];
+ while ( i != NSNotFound )
+ {
+ AQGridViewCell * cell = [self cellForItemAtIndex: i];
+ [cell setHighlighted: NO animated: animated];
+ i = [_highlightedIndices indexGreaterThanIndex: i];
+ }
+
+ [_highlightedIndices removeAllIndexes];
+ return;
+ }
+
+ AQGridViewCell * cell = [self cellForItemAtIndex: index];
+ [cell setHighlighted: YES animated: animated];
+ [_highlightedIndices addIndex: index];
+
+ if ( position != AQGridViewScrollPositionNone )
+ [self scrollToItemAtIndex: index atScrollPosition: position animated: animated];
+}
+
+- (void) unhighlightItemAtIndex: (NSUInteger) index animated: (BOOL) animated
+{
+ if ( [_highlightedIndices containsIndex: index] == NO )
+ return;
+
+ [_highlightedIndices removeIndex: index];
+ AQGridViewCell * cell = [self cellForItemAtIndex: index];
+ if ( cell != nil )
+ [cell setHighlighted: NO animated: animated];
+}
+
+- (void) _deselectItemAtIndex: (NSUInteger) index animated: (BOOL) animated notifyDelegate: (BOOL) notifyDelegate
+{
+ if ( _selectedIndex != index )
+ return;
+
+ if ( notifyDelegate && _flags.delegateWillDeselectItem )
+ [self.delegate gridView: self willDeselectItemAtIndex: index];
+
+ _selectedIndex = NSNotFound;
+ [[self cellForItemAtIndex: index] setSelected: NO animated: animated];
+
+ if ( notifyDelegate && _flags.delegateDidDeselectItem )
+ [self.delegate gridView: self didDeselectItemAtIndex: index];
+
+ if ( notifyDelegate )
+ {
+ [[NSNotificationCenter defaultCenter] postNotificationName: AQGridViewSelectionDidChangeNotification
+ object: self];
+ }
+}
+
+- (void) _selectItemAtIndex: (NSUInteger) index animated: (BOOL) animated
+ scrollPosition: (AQGridViewScrollPosition) position notifyDelegate: (BOOL) notifyDelegate
+{
+ if ( _selectedIndex == index )
+ return; // already selected this item
+
+ if ( _selectedIndex != NSNotFound )
+ [self _deselectItemAtIndex: _selectedIndex animated: animated notifyDelegate: NO];
+
+ if ( _flags.allowsSelection == 0 )
+ return;
+
+ if ( notifyDelegate && _flags.delegateWillSelectItem )
+ [self.delegate gridView: self willSelectItemAtIndex: index];
+
+ _selectedIndex = index;
+ [[self cellForItemAtIndex: index] setSelected: YES animated: animated];
+
+ if ( position != AQGridViewScrollPositionNone )
+ [self scrollToItemAtIndex: index atScrollPosition: position animated: animated];
+
+ if ( notifyDelegate )
+ {
+ [[NSNotificationCenter defaultCenter] postNotificationName: AQGridViewSelectionDidChangeNotification
+ object: self];
+ }
+
+ if ( notifyDelegate && _flags.delegateDidSelectItem )
+ [self.delegate gridView: self didSelectItemAtIndex: index];
+}
+
+#pragma mark -
+#pragma mark Appearance
+
+- (UIView *) backgroundView
+{
+ return ( [[_backgroundView retain] autorelease] );
+}
+
+- (void) setBackgroundView: (UIView *) newView
+{
+ if ( newView == _backgroundView )
+ return;
+
+ [_backgroundView removeFromSuperview];
+ [_backgroundView release];
+
+ _backgroundView = [newView retain];
+ _backgroundView.autoresizingMask = UIViewAutoresizingFlexibleWidth|UIViewAutoresizingFlexibleHeight;
+ CGRect frame = self.bounds;
+ frame.size = self.contentSize;
+ _backgroundView.frame = UIEdgeInsetsInsetRect( frame, self.contentInset );
+
+ [self insertSubview: _backgroundView atIndex: 0];
+
+ // this view is already laid out nicely-- no need to call -setNeedsLayout at all
+}
+
+- (UIColor *) separatorColor
+{
+ return ( [[_separatorColor retain] autorelease] );
+}
+
+- (void) setSeparatorColor: (UIColor *) color
+{
+ if ( color == _separatorColor )
+ return;
+
+ [color retain];
+ [_separatorColor release];
+ _separatorColor = color;
+
+ for ( AQGridViewCell * cell in _visibleCells )
+ {
+ cell.separatorColor = _separatorColor;
+ }
+}
+
+#pragma mark -
+#pragma mark Touch Events
+
+- (UIView *) _basicHitTest: (CGPoint) point withEvent: (UIEvent *) event
+{
+ // STUPID STUPID RAT CREATURES
+ // ===========================
+ //
+ // Problem: we want to do a default hit-test without UIScrollView's processing getting in the way.
+ // UIScrollView implements _defaultHitTest:withEvent: for this, but we can't call that due to it
+ // being a private API.
+ // Instead, we have to manufacture a call to our super-super class here, grr
+ Method method = class_getInstanceMethod( [UIView class], @selector(hitTest:withEvent:) );
+ IMP imp = method_getImplementation( method );
+ return ( (UIView *)imp(self, @selector(hitTest:withEvent:), point, event) ); // -[UIView hitTest:withEvent:]
+}
+
+- (BOOL) _canSelectItemContainingHitView: (UIView *) hitView
+{
+ if ( [hitView isKindOfClass: [UIControl class]] )
+ return ( NO );
+
+ if ( [[hitView superview] isKindOfClass: [AQGridViewCell class]] )
+ return ( YES );
+
+ if ( [hitView isKindOfClass: [AQGridViewCell class]] )
+ return ( YES );
+
+ return ( NO );
+}
+
+- (void) _gridViewDeferredTouchesBegan: (NSNumber *) indexNum
+{
+ if ( (self.dragging == NO) && (_flags.ignoreTouchSelect == 0) && (_pendingSelectionIndex != NSNotFound) )
+ [self highlightItemAtIndex: _pendingSelectionIndex animated: NO scrollPosition: AQGridViewScrollPositionNone];
+ //_pendingSelectionIndex = NSNotFound;
+}
+
+- (void) _userSelectItemAtIndex: (NSNumber *) indexNum
+{
+ NSUInteger index = [indexNum unsignedIntegerValue];
+ [self unhighlightItemAtIndex: index animated: NO];
+ if ( ([[self cellForItemAtIndex: index] isSelected]) && (self.requiresSelection == NO) )
+ [self _deselectItemAtIndex: index animated: NO notifyDelegate: YES];
+ else
+ [self _selectItemAtIndex: index animated: NO scrollPosition: AQGridViewScrollPositionNone notifyDelegate: YES];
+ _pendingSelectionIndex = NSNotFound;
+}
+
+- (void) touchesBegan: (NSSet *) touches withEvent: (UIEvent *) event
+{
+ _flags.ignoreTouchSelect = ([self isDragging] ? 1 : 0);
+
+ UITouch * touch = [touches anyObject];
+ _touchBeganPosition = [touch locationInView: nil];
+ if ( (touch != nil) && (_pendingSelectionIndex == NSNotFound) )
+ {
+ CGPoint pt = [touch locationInView: self];
+ UIView * hitView = [self _basicHitTest: pt withEvent: event];
+ _touchedContentView = hitView;
+
+ // unhighlight anything not here
+ if ( hitView != self )
+ [self highlightItemAtIndex: NSNotFound animated: NO scrollPosition: AQGridViewScrollPositionNone];
+
+ if ( [self _canSelectItemContainingHitView: hitView] )
+ {
+ NSUInteger index = [self indexForItemAtPoint: pt];
+ if ( index != NSNotFound )
+ {
+ if ( _flags.allowsSelection == 1 )
+ {
+ _pendingSelectionIndex = index;
+
+ // NB: In UITableView:
+ // if ( [self usesGestureRecognizers] && [self isDragging] ) skip next line
+ [self performSelector: @selector(_gridViewDeferredTouchesBegan:)
+ withObject: [NSNumber numberWithUnsignedInteger: index]
+ afterDelay: 0.0];
+ }
+ }
+ }
+ }
+
+ [super touchesBegan: touches withEvent: event];
+}
+
+- (void) _cancelContentTouchUsingEvent: (UIEvent *) event forced: (BOOL) forced
+{
+ static char * name = "_cancelContentTouchWithEvent:forced:";
+
+ // more manual ObjC runtime calls...
+ SEL selector = sel_getUid( name );
+ objc_msgSend( self, selector, event, forced );
+}
+
+- (void) touchesMoved: (NSSet *) touches withEvent: (UIEvent *) event
+{
+ if ( _flags.ignoreTouchSelect == 0 )
+ {
+ [self _cancelContentTouchUsingEvent: event forced: NO];
+ [self highlightItemAtIndex: NSNotFound animated: NO scrollPosition: AQGridViewScrollPositionNone];
+ _flags.ignoreTouchSelect = 1;
+ _touchedContentView = nil;
+ }
+
+ [super touchesMoved: touches withEvent: event];
+}
+
+- (void) touchesEnded: (NSSet *) touches withEvent: (UIEvent *) event
+{
+ [[self class] cancelPreviousPerformRequestsWithTarget: self
+ selector: @selector(_gridViewDeferredTouchesBegan:)
+ object: nil];
+
+ UIView * hitView = [_touchedContentView retain];
+ _touchedContentView = nil;
+
+ [super touchesEnded: touches withEvent: event];
+ if ( _touchedContentView != nil )
+ {
+ [hitView release];
+ hitView = [_touchedContentView retain];
+ }
+
+ if ( [hitView superview] == nil )
+ {
+ [hitView release];
+ hitView = nil;
+ }
+
+ // poor-man's goto
+ do
+ {
+ if ( self.dragging )
+ break;
+
+ UITouch * touch = [touches anyObject];
+ if ( touch == nil )
+ break;
+
+ CGPoint pt = [touch locationInView: self];
+ if ( (hitView != nil) && ([self _canSelectItemContainingHitView: hitView] == NO) )
+ break;
+
+ if ( _pendingSelectionIndex != [self indexForItemAtPoint: pt] )
+ break;
+
+ if ( _flags.allowsSelection == 0 )
+ break;
+
+ // run this on the next runloop tick
+ [self performSelector: @selector(_userSelectItemAtIndex:)
+ withObject: [NSNumber numberWithUnsignedInteger: _pendingSelectionIndex]
+ afterDelay: 0.0];
+
+ [hitView release];
+
+ } while (0);
+
+ if ( _pendingSelectionIndex != NSNotFound )
+ [self unhighlightItemAtIndex: _pendingSelectionIndex animated: NO];
+ _pendingSelectionIndex = NSNotFound;
+}
+
+- (void) touchesCancelled: (NSSet *) touches withEvent: (UIEvent *) event
+{
+ _pendingSelectionIndex = NSNotFound;
+ [self highlightItemAtIndex: NSNotFound animated: NO scrollPosition: AQGridViewScrollPositionNone];
+ [super touchesCancelled: touches withEvent: event];
+}
+
+@end
+
+#pragma mark -
+
+@implementation AQGridView (AQCellGridMath)
+
+- (NSUInteger) visibleCellListIndexForItemIndex: (NSUInteger) itemIndex
+{
+ return ( itemIndex - _visibleIndices.location );
+}
+
+@end
+
+#pragma mark -
+
+@implementation AQGridView (AQCellLayout)
+
+- (void) updateGridViewBoundsForNewGridData: (AQGridViewData *) newGridData
+{
+ [self updateContentRectWithOldMaxY: CGRectGetMaxY(self.bounds) gridHeight: [newGridData heightForEntireGrid]];
+}
+
+- (void) updateVisibleGridCellsNow
+{
+ if ( _reloadingSuspendedCount > 0 )
+ return;
+
+ _reloadingSuspendedCount++;
+ NSIndexSet * newVisibleIndices = [_gridData indicesOfCellsInRect: [self gridViewVisibleBounds]];
+
+ NSUInteger beforeTest = (_visibleIndices.location == 0 ? NSNotFound : _visibleIndices.location - 1);
+ NSUInteger afterTest = MIN(_visibleIndices.location+_visibleIndices.length, _gridData.numberOfItems);
+
+ //NSLog( @"New Visible Indices = %@, _visibleIndices = %@", newVisibleIndices, NSStringFromRange(_visibleIndices) );
+
+ // do we need to remove anything?
+ if ( [newVisibleIndices countOfIndexesInRange: _visibleIndices] < _visibleIndices.length )
+ {
+ // remove the last few items
+ NSUInteger numToRemove = 0;
+ BOOL removeFromFront = NO;
+ NSUInteger idx = (_visibleIndices.location + _visibleIndices.length) - 1;
+ do
+ {
+ if ( [newVisibleIndices containsIndex: idx] )
+ break;
+
+ numToRemove++;
+ idx--;
+
+ } while ( idx != NSUIntegerMax );
+
+ if ( numToRemove == 0 )
+ {
+ removeFromFront = YES;
+ idx = _visibleIndices.location;
+ while ( NSLocationInRange(idx, _visibleIndices) )
+ {
+ if ( [newVisibleIndices containsIndex: idx] )
+ break;
+
+ numToRemove++;
+ idx++;
+ }
+ }
+
+ if ( numToRemove == [_gridData numberOfItemsPerRow] )
+ {
+ // optimized version is possible
+ NSRange arrayRange = {0, 0};
+ if ( removeFromFront )
+ arrayRange = NSMakeRange(0, numToRemove);
+ else
+ arrayRange = NSMakeRange([_visibleCells count] - numToRemove, numToRemove);
+
+ //NSLog( @"Removing cells in visible range: %@", NSStringFromRange(arrayRange) );
+
+ // grab the removed cells (retains them)
+ NSMutableArray * removedCells = [[[_visibleCells subarrayWithRange: arrayRange] mutableCopy] autorelease];
+
+ // don't remove cells which are animating right now
+ if ( self.animatingCells.count != 0 )
+ {
+ [removedCells removeObjectsInArray: self.animatingCells];
+ numToRemove = [removedCells count];
+ arrayRange.length = numToRemove;
+ }
+
+ // remove from the visible list
+ [_visibleCells removeObjectsInRange: arrayRange];
+
+ // trim the visible cell index range
+ _visibleIndices.length -= numToRemove;
+
+ if ( removeFromFront )
+ _visibleIndices.location += numToRemove;
+
+ // remove cells from superview
+ [removedCells makeObjectsPerformSelector: @selector(removeFromSuperview)];
+
+ // put them into the recycled cell list
+ [self enqueueReusableCells: removedCells];
+
+ // done removing cells
+ }
+ else
+ {
+ // we need to be much more thorough-- a large number of items have been removed from all over
+ NSMutableArray * removedCells = [[_visibleCells mutableCopy] autorelease];
+ if ( self.animatingCells.count != 0 )
+ [removedCells removeObjectsInArray: self.animatingCells];
+
+ // remove any cells which aren't animating to new positions
+ [_visibleCells removeObjectsInArray: removedCells];
+ [removedCells makeObjectsPerformSelector: @selector(removeFromSuperview)];
+ [self enqueueReusableCells: removedCells];
+
+ // update visible indices as appropriate-- brute force this time
+ _visibleIndices.location = [newVisibleIndices firstIndex];
+ _visibleIndices.length = [newVisibleIndices count];
+
+ // load the new cells
+ NSUInteger idx = [newVisibleIndices firstIndex];
+ while ( idx != NSNotFound )
+ {
+ AQGridViewCell * cell = [self createPreparedCellForIndex: idx];
+ [self delegateWillDisplayCell: cell atIndex: idx];
+ [_visibleCells addObject: cell];
+ idx = [newVisibleIndices indexGreaterThanIndex: idx];
+ }
+
+ [self layoutCellsInVisibleCellRange: NSMakeRange(0, [_visibleCells count])];
+
+ // all done
+ }
+ }
+
+ // no animations on automatic cell layout
+ [UIView setAnimationsEnabled: NO];
+
+ if ( (beforeTest != NSNotFound) && ([newVisibleIndices containsIndex: beforeTest]) )
+ {
+ // moving backwards
+ NSMutableIndexSet * newIndices = [[newVisibleIndices mutableCopy] autorelease];
+
+ // prune the ones we know about already, so we have a list of only the new ones
+ [newIndices removeIndexesInRange: _visibleIndices];
+ [newIndices removeIndexesInRange: _revealingIndices];
+
+ // insert any new cells, in reverse order (so we always insert at index zero)
+ NSUInteger idx = [newIndices lastIndex];
+ while ( idx != NSNotFound )
+ {
+ AQGridViewCell * cell = [self createPreparedCellForIndex: idx];
+ [self delegateWillDisplayCell: cell atIndex: idx];
+ [_visibleCells insertObject: cell atIndex: _revealingIndices.length];
+
+ idx = [newIndices indexLessThanIndex: idx];
+ }
+
+ // update the visibleCell index range
+ _visibleIndices.length += [newIndices count];
+ _visibleIndices.location = [newVisibleIndices firstIndex];
+
+ // get the range of the new items
+ NSRange newCellRange = NSMakeRange([newIndices firstIndex], [newIndices lastIndex] - [newIndices firstIndex] + 1);
+
+ // map this range onto the current visible cell array
+ newCellRange.location = MIN(newCellRange.location - _visibleIndices.location, 0);
+
+ // now update their locations
+ [self layoutCellsInVisibleCellRange: newCellRange];
+ }
+ else if ( (NSLocationInRange(afterTest, _visibleIndices) == NO) && ([newVisibleIndices containsIndex: afterTest]) )
+ {
+ // moving forwards
+ NSMutableIndexSet * newIndices = [[newVisibleIndices mutableCopy] autorelease];
+
+ // prune the ones we know about already, so we have a list of only the new ones
+ [newIndices removeIndexesInRange: _visibleIndices];
+
+ // insert any new cells in growing order, so we can always append
+ NSUInteger idx = [newIndices firstIndex];
+ while ( idx != NSNotFound )
+ {
+ AQGridViewCell * cell = [self createPreparedCellForIndex: idx];
+ [self delegateWillDisplayCell: cell atIndex: idx];
+ [_visibleCells addObject: cell];
+
+ idx = [newIndices indexGreaterThanIndex: idx];
+ }
+
+ // update the visibleCell index range
+ _visibleIndices.length += [newIndices count];
+ _visibleIndices.location = [newVisibleIndices firstIndex];
+
+ // get the range of the new items
+ NSRange newCellRange = NSMakeRange([newIndices firstIndex], [newIndices lastIndex] - [newIndices firstIndex] + 1);
+
+ // map this range onto the current visible cell array
+ newCellRange.location -= _visibleIndices.location;
+
+ // now update their locations
+ [self layoutCellsInVisibleCellRange: newCellRange];
+ }
+
+ [UIView setAnimationsEnabled: YES];
+ _reloadingSuspendedCount--;
+}
+
+- (void) layoutCellsInVisibleCellRange: (NSRange) range
+{
+ NSParameterAssert((range.location >= 0) && (range.location + range.length <= [_visibleCells count]));
+
+ NSAutoreleasePool * pool = [[NSAutoreleasePool alloc] init];
+
+ NSUInteger idx = _visibleIndices.location + (range.location - _visibleIndices.location);
+ NSArray * layoutList = [_visibleCells subarrayWithRange: range];
+ for ( AQGridViewCell * cell in layoutList )
+ {
+ CGRect gridRect = [_gridData cellRectAtIndex: (_visibleIndices.location) + idx];
+ CGRect cellFrame = cell.frame;
+
+ [self delegateWillDisplayCell: cell atIndex: _visibleIndices.location + idx];
+
+ cell.frame = [self fixCellFrame: cellFrame forGridRect: gridRect];
+ cell.selected = (_visibleIndices.location + idx == _selectedIndex);
+
+ idx++;
+ }
+
+ [pool drain];
+}
+
+- (void) layoutAllCells
+{
+ NSRange range = NSMakeRange(0, _visibleIndices.length);
+ [self layoutCellsInVisibleCellRange: range];
+}
+
+- (CGRect) fixCellFrame: (CGRect) cellFrame forGridRect: (CGRect) gridRect
+{
+ if ( _flags.resizesCellWidths == 1 )
+ {
+ cellFrame = gridRect;
+ }
+ else
+ {
+ if ( cellFrame.size.width > gridRect.size.width )
+ cellFrame.size.width = gridRect.size.width;
+ if ( cellFrame.size.height > gridRect.size.height )
+ cellFrame.size.height = gridRect.size.height;
+ cellFrame.origin.x = gridRect.origin.x + floorf( (gridRect.size.width - cellFrame.size.width) * 0.5 );
+ cellFrame.origin.y = gridRect.origin.y + floorf( (gridRect.size.height - cellFrame.size.height) * 0.5 );
+ }
+
+ // let the delegate update it if appropriate
+ if ( _flags.delegateAdjustGridCellFrame )
+ cellFrame = [self.delegate gridView: self adjustCellFrame: cellFrame withinGridCellFrame: gridRect];
+
+ return ( cellFrame );
+}
+
+- (AQGridViewCell *) createPreparedCellForIndex: (NSUInteger) index usingGridData: (AQGridViewData *) gridData
+{
+ [UIView setAnimationsEnabled: NO];
+ AQGridViewCell * cell = [_dataSource gridView: self cellForItemAtIndex: index];
+ cell.separatorStyle = _flags.separatorStyle;
+
+ cell.frame = [self fixCellFrame: cell.frame forGridRect: [gridData cellRectAtIndex: index]];
+ if ( _backgroundView.superview == self )
+ [self insertSubview: cell aboveSubview: _backgroundView];
+ else
+ [self insertSubview: cell atIndex: 0];
+ [UIView setAnimationsEnabled: YES];
+ return ( cell );
+}
+
+- (AQGridViewCell *) createPreparedCellForIndex: (NSUInteger) index
+{
+ return ( [self createPreparedCellForIndex: index usingGridData: _gridData] );
+}
+
+- (void) insertVisibleCell: (AQGridViewCell *) cell atIndex: (NSUInteger) visibleCellListIndex
+{
+ if ( visibleCellListIndex >= [_visibleCells count] )
+ return;
+
+ [_visibleCells insertObject: cell atIndex: visibleCellListIndex];
+}
+
+- (void) deleteVisibleCell: (AQGridViewCell *) cell atIndex: (NSUInteger) visibleCellListIndex appendingNewCell: (AQGridViewCell *) newCell
+{
+ if ( visibleCellListIndex >= [_visibleCells count] )
+ return;
+
+ [_visibleCells removeObjectAtIndex: visibleCellListIndex];
+ [_visibleCells addObject: newCell];
+}
+
+- (void) ensureCellInVisibleList: (AQGridViewCell *) cell
+{
+ if ( [_visibleCells containsObject: cell] == NO )
+ [_visibleCells addObject: cell];
+ [_visibleCells sortUsingSelector: @selector(compareOriginAgainstCell:)];
+}
+
+- (void) animationWillRevealItemsAtIndices: (NSRange) indices
+{
+ _revealingIndices = indices;
+}
+
+@end
+
+@implementation AQGridView (AQGridViewPrivate)
+
+- (void) viewWillRotateToInterfaceOrientation: (UIInterfaceOrientation) orientation
+{
+ // to avoid cell pop-in or pop-out:
+ // if we're switching to landscape, don't update cells until after the transition.
+ // if we're switching to portrait, update cells first.
+ if ( UIInterfaceOrientationIsLandscape(orientation) )
+ _reloadingSuspendedCount++;
+}
+
+- (void) viewDidRotate
+{
+ if ( _reloadingSuspendedCount == 0 )
+ return;
+
+ if ( --_reloadingSuspendedCount == 0 )
+ [self updateVisibleGridCellsNow];
+}
+
+@end
+
+@implementation AQGridView (CellLocationDelegation)
+
+- (void) delegateWillDisplayCell: (AQGridViewCell *) cell atIndex: (NSUInteger) index
+{
+ if ( cell.separatorStyle == AQGridViewCellSeparatorStyleSingleLine )
+ {
+ // determine which edges need a separator
+ AQGridViewCellSeparatorEdge edge = 0;
+ if ( (index % self.numberOfColumns) != self.numberOfColumns-1 )
+ {
+ edge |= AQGridViewCellSeparatorEdgeRight;
+ }
+ //if ( index <= (_gridData.numberOfItems - self.numberOfColumns) )
+ {
+ edge |= AQGridViewCellSeparatorEdgeBottom;
+ }
+
+ cell.separatorEdge = edge;
+ }
+
+ if ( _flags.delegateWillDisplayCell == 0 )
+ return;
+
+ [self.delegate gridView: self willDisplayCell: cell forItemAtIndex: index];
+}
+
+@end
52 Classes/AQGridViewCell+AQGridViewCellPrivate.h
@@ -0,0 +1,52 @@
+/*
+ * AQGridViewCell+AQGridViewCellPrivate.h
+ * AQGridView
+ *
+ * Created by Jim Dovey on 01/3/2010.
+ * Copyright (c) 2010 Kobo Inc. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ *
+ * Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ *
+ * Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * Neither the name of the project's author nor the names of its
+ * contributors may be used to endorse or promote products derived from
+ * this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
+ * FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+ * HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED
+ * TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+ * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
+ * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
+ * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+ * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ *
+ */
+
+#import "AQGridViewCell.h"
+
+enum
+{
+ AQGridViewCellSeparatorEdgeBottom = (1 << 0),
+ AQGridViewCellSeparatorEdgeRight = (1 << 1)
+};
+typedef NSUInteger AQGridViewCellSeparatorEdge;
+
+@interface AQGridViewCell (AQGridViewCellPrivate)
+
+@property (nonatomic, retain) UIColor * separatorColor;
+@property (nonatomic, assign) AQGridViewCellSeparatorStyle separatorStyle;
+@property (nonatomic, assign) AQGridViewCellSeparatorEdge separatorEdge;
+
+@end
113 Classes/AQGridViewCell.h
@@ -0,0 +1,113 @@
+/*
+ * AQGridViewCell.h
+ * AQGridView
+ *
+ * Created by Jim Dovey on 25/2/2010.
+ * Copyright (c) 2010 Kobo Inc. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ *
+ * Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ *
+ * Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * Neither the name of the project's author nor the names of its
+ * contributors may be used to endorse or promote products derived from
+ * this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
+ * FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+ * HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED
+ * TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+ * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
+ * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
+ * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+ * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ *
+ */
+
+#import <UIKit/UIKit.h>
+
+typedef enum {
+ AQGridViewCellSeparatorStyleNone,
+ AQGridViewCellSeparatorStyleEmptySpace,
+ AQGridViewCellSeparatorStyleSingleLine
+} AQGridViewCellSeparatorStyle;
+
+typedef enum {
+ AQGridViewCellSelectionStyleNone,
+ AQGridViewCellSelectionStyleBlue,
+ AQGridViewCellSelectionStyleGray,
+ AQGridViewCellSelectionStyleBlueGray,
+ AQGridViewCellSelectionStyleGreen,
+ AQGridViewCellSelectionStyleRed,
+#if __IPHONE_OS_VERSION_MAX_ALLOWED >= __IPHONE_3_2
+ AQGridViewCellSelectionStyleGlow // see also 'selectionGlowColor' property
+#endif
+} AQGridViewCellSelectionStyle;
+
+@interface AQGridViewCell : UIView
+{
+ NSString * _reuseIdentifier;
+ UIView * _contentView;
+ UIView * _backgroundView;
+ UIView * _selectedBackgroundView;
+ UIView * _selectedOverlayView;
+ CGFloat _selectionFadeDuration;
+ UIColor * _backgroundColor;
+ UIColor * _separatorColor;
+ UIColor * _selectionGlowColor;
+ UIView * _bottomSeparatorView;
+ UIView * _rightSeparatorView;
+ NSTimer * _fadeTimer;
+ CFMutableDictionaryRef _selectionColorInfo;
+ struct {
+ unsigned int separatorStyle:3;
+ unsigned int selectionStyle:3;
+ unsigned int separatorEdge:2;
+ unsigned int animatingSelection:1;
+ unsigned int backgroundColorSet:1;
+ unsigned int selectionGlowColorSet:1;
+ unsigned int usingDefaultSelectedBackgroundView:1;
+ unsigned int selected:1;
+ unsigned int highlighted:1;
+ unsigned int becomingHighlighted:1;
+ unsigned int __RESERVED__:17;
+ } _cellFlags;
+}
+
+- (id) initWithFrame: (CGRect) frame reuseIdentifier: (NSString *) reuseIdentifier;
+
+// If you want to customize cells by simply adding additional views, you should add them to the content view so they will be positioned appropriately as the cell transitions into and out of editing mode.
+@property (nonatomic, readonly, retain) UIView * contentView;
+
+// default is nil. The background view will be added as a subview behind all other views
+@property (nonatomic, retain) UIView * backgroundView;
+
+// The 'selectedBackgroundView' will be added as a subview directly above the backgroundView if not nil, or behind all other views. It is added as a subview only when the cell is selected. Calling -setSelected:animated: will cause the 'selectedBackgroundView' to animate in and out with an alpha fade.
+@property (nonatomic, retain) UIView * selectedBackgroundView;
+
+@property (nonatomic, readonly, copy) NSString * reuseIdentifier;
+- (void) prepareForReuse; // if the cell is reusable (has a reuse identifier), this is called just before the cell is returned from the grid view method dequeueReusableCellWithIdentifier:. If you override, you MUST call super.
+
+@property (nonatomic) AQGridViewCellSelectionStyle selectionStyle; // default is AQGridViewCellSelectionStyleGlow
+@property (nonatomic, getter=isSelected) BOOL selected; // default is NO
+@property (nonatomic, getter=isHighlighted) BOOL highlighted; // default is NO
+@property (nonatomic, retain) UIColor * selectionGlowColor; // default is dark grey, ignored if selectionStyle != AQGridViewCellSelectionStyleGlow
+
+// this can be overridden by subclasses to return a subview's layer to which to add the glow
+// the default implementation returns the contentView's layer
+@property (nonatomic, readonly) CALayer * glowSelectionLayer;
+
+- (void) setSelected: (BOOL) selected animated: (BOOL) animated;
+- (void) setHighlighted: (BOOL) highlighted animated: (BOOL) animated;
+
+@end
624 Classes/AQGridViewCell.m
@@ -0,0 +1,624 @@
+/*
+ * AQGridViewCell.m
+ * AQGridView
+ *
+ * Created by Jim Dovey on 25/2/2010.
+ * Copyright (c) 2010 Kobo Inc. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ *
+ * Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ *
+ * Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * Neither the name of the project's author nor the names of its
+ * contributors may be used to endorse or promote products derived from
+ * this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
+ * FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+ * HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED
+ * TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+ * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
+ * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
+ * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+ * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ *
+ */
+
+#import "AQGridViewCell.h"
+#import "AQGridViewCell+AQGridViewCellPrivate.h"
+#import "UIColor+AQGridView.h"
+#import <QuartzCore/QuartzCore.h>
+#import <objc/runtime.h>
+
+@interface AQGridViewCell ()
+@property (nonatomic, retain) UIView * contentView;
+@property (nonatomic, copy) NSString * reuseIdentifier;
+@end
+
+@implementation AQGridViewCell
+
+@synthesize contentView=_contentView, backgroundView=_backgroundView, selectedBackgroundView=_selectedBackgroundView;
+@synthesize reuseIdentifier=_reuseIdentifier, selectionGlowColor=_selectionGlowColor;
+
+- (id) initWithFrame: (CGRect) frame reuseIdentifier: (NSString *) reuseIdentifier
+{
+ self = [super initWithFrame: frame];
+ if ( self == nil )
+ return ( nil );
+
+ self.reuseIdentifier = reuseIdentifier;
+ _cellFlags.usingDefaultSelectedBackgroundView = 1;
+ _cellFlags.separatorStyle = AQGridViewCellSeparatorStyleEmptySpace;
+
+ if ( [CALayer instancesRespondToSelector: @selector(shadowPath)] )
+ _cellFlags.selectionStyle = AQGridViewCellSelectionStyleGlow;
+ else
+ _cellFlags.selectionStyle = AQGridViewCellSelectionStyleGray;
+ _selectionColorInfo = CFDictionaryCreateMutable( kCFAllocatorDefault, 0, &kCFTypeDictionaryKeyCallBacks, &kCFTypeDictionaryValueCallBacks );
+ self.backgroundColor = [UIColor whiteColor];
+
+ return ( self );
+}
+
+- (void) dealloc
+{
+ [_reuseIdentifier release];
+ [_contentView release];
+ [_backgroundView release];
+ [_selectedBackgroundView release];
+ [_selectedOverlayView release];
+ [_backgroundColor release];
+ [_separatorColor release];
+ [_selectionGlowColor release];
+ [_bottomSeparatorView release];
+ [_rightSeparatorView release];
+ if ( _selectionColorInfo != NULL )
+ CFRelease( _selectionColorInfo );
+ [_fadeTimer release];
+ [super dealloc];
+}
+
+- (NSComparisonResult) compareOriginAgainstCell: (AQGridViewCell *) otherCell
+{
+ CGPoint myOrigin = self.frame.origin;
+ CGPoint theirOrigin = otherCell.frame.origin;
+
+ if ( myOrigin.y > theirOrigin.y )
+ return ( NSOrderedDescending );
+ else if ( myOrigin.y < theirOrigin.y )
+ return ( NSOrderedAscending );
+
+ if ( myOrigin.x > theirOrigin.x )
+ return ( NSOrderedDescending );
+ else if ( myOrigin.x < theirOrigin.x )
+ return ( NSOrderedAscending );
+
+ return ( NSOrderedSame );
+}
+
+- (UIView *) contentView
+{
+ if ( _contentView == nil )
+ {
+ _contentView = [[UIView alloc] initWithFrame: self.bounds];
+ _contentView.autoresizingMask = UIViewAutoresizingFlexibleWidth|UIViewAutoresizingFlexibleHeight;
+ _contentView.autoresizesSubviews = YES;
+ _contentView.backgroundColor = [UIColor whiteColor];
+ [_contentView.layer setValue: [NSNumber numberWithBool: YES] forKey: @"KoboHackInterestingLayer"];
+ [self addSubview: _contentView];
+ }
+ return ( _contentView );
+}
+
+- (CALayer *) glowSelectionLayer
+{
+ return ( _contentView.layer );
+}
+
+- (AQGridViewCellSelectionStyle) selectionStyle
+{
+ return ( _cellFlags.selectionStyle );
+}
+
+- (void) setSelectionStyle: (AQGridViewCellSelectionStyle) style
+{
+ if ( (style == AQGridViewCellSelectionStyleGlow) && ([CALayer instancesRespondToSelector: @selector(shadowPath)] == NO) )
+ style = AQGridViewCellSelectionStyleGray;
+ _cellFlags.selectionStyle = style;
+}
+
+- (AQGridViewCellSeparatorEdge) separatorEdge
+{
+ return ( _cellFlags.separatorEdge );
+}
+
+- (void) setSeparatorEdge: (AQGridViewCellSeparatorEdge) value
+{
+ if ( _cellFlags.separatorEdge == value )
+ return;
+
+ _cellFlags.separatorEdge = value;
+ [self setNeedsLayout];
+}
+
+- (BOOL) isSelected
+{
+ return ( _cellFlags.selected );
+}
+
+- (void) setSelected: (BOOL) value
+{
+ [self setSelected: value animated: NO];
+}
+
+- (void) setSelected: (BOOL) value animated: (BOOL) animated
+{
+ _cellFlags.selected = (value ? 1 : 0);
+ [self setHighlighted: value animated: animated];
+}
+
+- (BOOL) isHighlighted
+{
+ return ( _cellFlags.highlighted );
+}
+
+- (void) setHighlighted: (BOOL) value
+{
+ [self setHighlighted: value animated: NO];
+}
+
+- (void) makeSubviewsOfView: (UIView *) aView nonOpaqueWithBackgroundColor: (UIColor *) color
+{
+ for ( UIView * view in aView.subviews )