├── AUTHORS ├── Arts ├── quick-edit-view-white.psd ├── toolbar-item-selected.psd └── quick-edit-view-white-black.psd ├── Resources ├── TNMessageBoard │ ├── Bubble │ │ ├── 1.png │ │ ├── 2.png │ │ ├── 3.png │ │ ├── 4.png │ │ ├── 5.png │ │ ├── 6.png │ │ ├── 7.png │ │ ├── 8.png │ │ ├── 9.png │ │ ├── 7-alt.png │ │ └── 9-alt.png │ ├── BubbleAlt │ │ ├── 1.png │ │ ├── 2.png │ │ ├── 3.png │ │ ├── 4.png │ │ ├── 5.png │ │ ├── 6.png │ │ ├── 7.png │ │ ├── 8.png │ │ ├── 9.png │ │ ├── 7-alt.png │ │ └── 9-alt.png │ ├── BubbleNotice │ │ ├── 1.png │ │ ├── 2.png │ │ ├── 3.png │ │ ├── 4.png │ │ ├── 5.png │ │ ├── 6.png │ │ ├── 7.png │ │ ├── 8.png │ │ ├── 9.png │ │ ├── 7-alt.png │ │ └── 9-alt.png │ └── user-unknown.png ├── TNToolbar │ ├── toolbar-hud-background.png │ ├── toolbar-item-selected-left.png │ ├── toolbar-item-selected-center.png │ ├── toolbar-item-selected-right.png │ ├── toolbar-hud-item-selected-left.png │ ├── toolbar-hud-item-selected-right.png │ └── toolbar-hud-item-selected-center.png ├── TNAttachedWindow │ ├── Black │ │ ├── attached-window-left.png │ │ ├── attached-window-right.png │ │ ├── attached-window-top.png │ │ ├── attached-window-bottom.png │ │ ├── attached-window-center.png │ │ ├── attached-window-arrow-left.png │ │ ├── attached-window-arrow-top.png │ │ ├── attached-window-top-left.png │ │ ├── attached-window-top-right.png │ │ ├── attached-window-arrow-bottom.png │ │ ├── attached-window-arrow-right.png │ │ ├── attached-window-bottom-left.png │ │ ├── attached-window-bottom-right.png │ │ ├── attached-window-button-close.png │ │ └── attached-window-button-close-pressed.png │ └── White │ │ ├── attached-window-left.png │ │ ├── attached-window-right.png │ │ ├── attached-window-top.png │ │ ├── attached-window-bottom.png │ │ ├── attached-window-center.png │ │ ├── attached-window-arrow-left.png │ │ ├── attached-window-arrow-top.png │ │ ├── attached-window-top-left.png │ │ ├── attached-window-top-right.png │ │ ├── attached-window-arrow-bottom.png │ │ ├── attached-window-arrow-right.png │ │ ├── attached-window-bottom-left.png │ │ ├── attached-window-bottom-right.png │ │ ├── attached-window-button-close.png │ │ └── attached-window-button-close-pressed.png └── TNUIKitScrollView │ ├── scroller-vertical-knob-top.png │ ├── scroller-horizontal-knob-center.png │ ├── scroller-horizontal-knob-left.png │ ├── scroller-horizontal-knob-right.png │ ├── scroller-vertical-knob-bottom.png │ └── scroller-vertical-knob-center.png ├── .gitignore ├── README.markdown ├── TNAnimation.j ├── Info.plist ├── TNKit.j ├── Jakefile ├── TNTextFieldStepper.j ├── TNWorker.j ├── TNStackView.j ├── TNAlert.j ├── TNOutlineViewDataSource.j ├── TNMessageBoard.j ├── TNTabViewItemPrototype.j ├── LICENSE ├── TNTableViewLazyDataSource.j ├── TNToolbar.j ├── TNUIKitScrollView.j ├── TNTableViewDataSource.j ├── TNFlipView.j ├── TNRangeSelectorView.j ├── TNSwipeView.j ├── TNTabView.j └── TNMessageView.j /AUTHORS: -------------------------------------------------------------------------------- 1 | Antoine Mercadal 2 | project owner 3 | lead developer 4 | 5 | -------------------------------------------------------------------------------- /Arts/quick-edit-view-white.psd: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ArchipelProject/TNKit/HEAD/Arts/quick-edit-view-white.psd -------------------------------------------------------------------------------- /Arts/toolbar-item-selected.psd: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ArchipelProject/TNKit/HEAD/Arts/toolbar-item-selected.psd -------------------------------------------------------------------------------- /Arts/quick-edit-view-white-black.psd: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ArchipelProject/TNKit/HEAD/Arts/quick-edit-view-white-black.psd -------------------------------------------------------------------------------- /Resources/TNMessageBoard/Bubble/1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ArchipelProject/TNKit/HEAD/Resources/TNMessageBoard/Bubble/1.png -------------------------------------------------------------------------------- /Resources/TNMessageBoard/Bubble/2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ArchipelProject/TNKit/HEAD/Resources/TNMessageBoard/Bubble/2.png -------------------------------------------------------------------------------- /Resources/TNMessageBoard/Bubble/3.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ArchipelProject/TNKit/HEAD/Resources/TNMessageBoard/Bubble/3.png -------------------------------------------------------------------------------- /Resources/TNMessageBoard/Bubble/4.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ArchipelProject/TNKit/HEAD/Resources/TNMessageBoard/Bubble/4.png -------------------------------------------------------------------------------- /Resources/TNMessageBoard/Bubble/5.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ArchipelProject/TNKit/HEAD/Resources/TNMessageBoard/Bubble/5.png -------------------------------------------------------------------------------- /Resources/TNMessageBoard/Bubble/6.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ArchipelProject/TNKit/HEAD/Resources/TNMessageBoard/Bubble/6.png -------------------------------------------------------------------------------- /Resources/TNMessageBoard/Bubble/7.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ArchipelProject/TNKit/HEAD/Resources/TNMessageBoard/Bubble/7.png -------------------------------------------------------------------------------- /Resources/TNMessageBoard/Bubble/8.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ArchipelProject/TNKit/HEAD/Resources/TNMessageBoard/Bubble/8.png -------------------------------------------------------------------------------- /Resources/TNMessageBoard/Bubble/9.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ArchipelProject/TNKit/HEAD/Resources/TNMessageBoard/Bubble/9.png -------------------------------------------------------------------------------- /Resources/TNMessageBoard/BubbleAlt/1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ArchipelProject/TNKit/HEAD/Resources/TNMessageBoard/BubbleAlt/1.png -------------------------------------------------------------------------------- /Resources/TNMessageBoard/BubbleAlt/2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ArchipelProject/TNKit/HEAD/Resources/TNMessageBoard/BubbleAlt/2.png -------------------------------------------------------------------------------- /Resources/TNMessageBoard/BubbleAlt/3.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ArchipelProject/TNKit/HEAD/Resources/TNMessageBoard/BubbleAlt/3.png -------------------------------------------------------------------------------- /Resources/TNMessageBoard/BubbleAlt/4.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ArchipelProject/TNKit/HEAD/Resources/TNMessageBoard/BubbleAlt/4.png -------------------------------------------------------------------------------- /Resources/TNMessageBoard/BubbleAlt/5.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ArchipelProject/TNKit/HEAD/Resources/TNMessageBoard/BubbleAlt/5.png -------------------------------------------------------------------------------- /Resources/TNMessageBoard/BubbleAlt/6.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ArchipelProject/TNKit/HEAD/Resources/TNMessageBoard/BubbleAlt/6.png -------------------------------------------------------------------------------- /Resources/TNMessageBoard/BubbleAlt/7.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ArchipelProject/TNKit/HEAD/Resources/TNMessageBoard/BubbleAlt/7.png -------------------------------------------------------------------------------- /Resources/TNMessageBoard/BubbleAlt/8.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ArchipelProject/TNKit/HEAD/Resources/TNMessageBoard/BubbleAlt/8.png -------------------------------------------------------------------------------- /Resources/TNMessageBoard/BubbleAlt/9.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ArchipelProject/TNKit/HEAD/Resources/TNMessageBoard/BubbleAlt/9.png -------------------------------------------------------------------------------- /Resources/TNMessageBoard/Bubble/7-alt.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ArchipelProject/TNKit/HEAD/Resources/TNMessageBoard/Bubble/7-alt.png -------------------------------------------------------------------------------- /Resources/TNMessageBoard/Bubble/9-alt.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ArchipelProject/TNKit/HEAD/Resources/TNMessageBoard/Bubble/9-alt.png -------------------------------------------------------------------------------- /Resources/TNMessageBoard/BubbleNotice/1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ArchipelProject/TNKit/HEAD/Resources/TNMessageBoard/BubbleNotice/1.png -------------------------------------------------------------------------------- /Resources/TNMessageBoard/BubbleNotice/2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ArchipelProject/TNKit/HEAD/Resources/TNMessageBoard/BubbleNotice/2.png -------------------------------------------------------------------------------- /Resources/TNMessageBoard/BubbleNotice/3.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ArchipelProject/TNKit/HEAD/Resources/TNMessageBoard/BubbleNotice/3.png -------------------------------------------------------------------------------- /Resources/TNMessageBoard/BubbleNotice/4.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ArchipelProject/TNKit/HEAD/Resources/TNMessageBoard/BubbleNotice/4.png -------------------------------------------------------------------------------- /Resources/TNMessageBoard/BubbleNotice/5.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ArchipelProject/TNKit/HEAD/Resources/TNMessageBoard/BubbleNotice/5.png -------------------------------------------------------------------------------- /Resources/TNMessageBoard/BubbleNotice/6.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ArchipelProject/TNKit/HEAD/Resources/TNMessageBoard/BubbleNotice/6.png -------------------------------------------------------------------------------- /Resources/TNMessageBoard/BubbleNotice/7.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ArchipelProject/TNKit/HEAD/Resources/TNMessageBoard/BubbleNotice/7.png -------------------------------------------------------------------------------- /Resources/TNMessageBoard/BubbleNotice/8.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ArchipelProject/TNKit/HEAD/Resources/TNMessageBoard/BubbleNotice/8.png -------------------------------------------------------------------------------- /Resources/TNMessageBoard/BubbleNotice/9.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ArchipelProject/TNKit/HEAD/Resources/TNMessageBoard/BubbleNotice/9.png -------------------------------------------------------------------------------- /Resources/TNMessageBoard/user-unknown.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ArchipelProject/TNKit/HEAD/Resources/TNMessageBoard/user-unknown.png -------------------------------------------------------------------------------- /Resources/TNMessageBoard/BubbleAlt/7-alt.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ArchipelProject/TNKit/HEAD/Resources/TNMessageBoard/BubbleAlt/7-alt.png -------------------------------------------------------------------------------- /Resources/TNMessageBoard/BubbleAlt/9-alt.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ArchipelProject/TNKit/HEAD/Resources/TNMessageBoard/BubbleAlt/9-alt.png -------------------------------------------------------------------------------- /Resources/TNMessageBoard/BubbleNotice/7-alt.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ArchipelProject/TNKit/HEAD/Resources/TNMessageBoard/BubbleNotice/7-alt.png -------------------------------------------------------------------------------- /Resources/TNMessageBoard/BubbleNotice/9-alt.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ArchipelProject/TNKit/HEAD/Resources/TNMessageBoard/BubbleNotice/9-alt.png -------------------------------------------------------------------------------- /Resources/TNToolbar/toolbar-hud-background.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ArchipelProject/TNKit/HEAD/Resources/TNToolbar/toolbar-hud-background.png -------------------------------------------------------------------------------- /Resources/TNToolbar/toolbar-item-selected-left.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ArchipelProject/TNKit/HEAD/Resources/TNToolbar/toolbar-item-selected-left.png -------------------------------------------------------------------------------- /Resources/TNToolbar/toolbar-item-selected-center.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ArchipelProject/TNKit/HEAD/Resources/TNToolbar/toolbar-item-selected-center.png -------------------------------------------------------------------------------- /Resources/TNToolbar/toolbar-item-selected-right.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ArchipelProject/TNKit/HEAD/Resources/TNToolbar/toolbar-item-selected-right.png -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | syntax: glob 2 | .DS_Store 3 | *~ 4 | Build 5 | Documentation 6 | *Build 7 | debug.txt 8 | *.xcodeproj 9 | .XcodeSupport 10 | *.tm_properties 11 | -------------------------------------------------------------------------------- /Resources/TNToolbar/toolbar-hud-item-selected-left.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ArchipelProject/TNKit/HEAD/Resources/TNToolbar/toolbar-hud-item-selected-left.png -------------------------------------------------------------------------------- /Resources/TNToolbar/toolbar-hud-item-selected-right.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ArchipelProject/TNKit/HEAD/Resources/TNToolbar/toolbar-hud-item-selected-right.png -------------------------------------------------------------------------------- /Resources/TNAttachedWindow/Black/attached-window-left.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ArchipelProject/TNKit/HEAD/Resources/TNAttachedWindow/Black/attached-window-left.png -------------------------------------------------------------------------------- /Resources/TNAttachedWindow/Black/attached-window-right.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ArchipelProject/TNKit/HEAD/Resources/TNAttachedWindow/Black/attached-window-right.png -------------------------------------------------------------------------------- /Resources/TNAttachedWindow/Black/attached-window-top.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ArchipelProject/TNKit/HEAD/Resources/TNAttachedWindow/Black/attached-window-top.png -------------------------------------------------------------------------------- /Resources/TNAttachedWindow/White/attached-window-left.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ArchipelProject/TNKit/HEAD/Resources/TNAttachedWindow/White/attached-window-left.png -------------------------------------------------------------------------------- /Resources/TNAttachedWindow/White/attached-window-right.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ArchipelProject/TNKit/HEAD/Resources/TNAttachedWindow/White/attached-window-right.png -------------------------------------------------------------------------------- /Resources/TNAttachedWindow/White/attached-window-top.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ArchipelProject/TNKit/HEAD/Resources/TNAttachedWindow/White/attached-window-top.png -------------------------------------------------------------------------------- /Resources/TNToolbar/toolbar-hud-item-selected-center.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ArchipelProject/TNKit/HEAD/Resources/TNToolbar/toolbar-hud-item-selected-center.png -------------------------------------------------------------------------------- /Resources/TNUIKitScrollView/scroller-vertical-knob-top.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ArchipelProject/TNKit/HEAD/Resources/TNUIKitScrollView/scroller-vertical-knob-top.png -------------------------------------------------------------------------------- /Resources/TNAttachedWindow/Black/attached-window-bottom.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ArchipelProject/TNKit/HEAD/Resources/TNAttachedWindow/Black/attached-window-bottom.png -------------------------------------------------------------------------------- /Resources/TNAttachedWindow/Black/attached-window-center.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ArchipelProject/TNKit/HEAD/Resources/TNAttachedWindow/Black/attached-window-center.png -------------------------------------------------------------------------------- /Resources/TNAttachedWindow/White/attached-window-bottom.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ArchipelProject/TNKit/HEAD/Resources/TNAttachedWindow/White/attached-window-bottom.png -------------------------------------------------------------------------------- /Resources/TNAttachedWindow/White/attached-window-center.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ArchipelProject/TNKit/HEAD/Resources/TNAttachedWindow/White/attached-window-center.png -------------------------------------------------------------------------------- /Resources/TNAttachedWindow/Black/attached-window-arrow-left.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ArchipelProject/TNKit/HEAD/Resources/TNAttachedWindow/Black/attached-window-arrow-left.png -------------------------------------------------------------------------------- /Resources/TNAttachedWindow/Black/attached-window-arrow-top.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ArchipelProject/TNKit/HEAD/Resources/TNAttachedWindow/Black/attached-window-arrow-top.png -------------------------------------------------------------------------------- /Resources/TNAttachedWindow/Black/attached-window-top-left.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ArchipelProject/TNKit/HEAD/Resources/TNAttachedWindow/Black/attached-window-top-left.png -------------------------------------------------------------------------------- /Resources/TNAttachedWindow/Black/attached-window-top-right.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ArchipelProject/TNKit/HEAD/Resources/TNAttachedWindow/Black/attached-window-top-right.png -------------------------------------------------------------------------------- /Resources/TNAttachedWindow/White/attached-window-arrow-left.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ArchipelProject/TNKit/HEAD/Resources/TNAttachedWindow/White/attached-window-arrow-left.png -------------------------------------------------------------------------------- /Resources/TNAttachedWindow/White/attached-window-arrow-top.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ArchipelProject/TNKit/HEAD/Resources/TNAttachedWindow/White/attached-window-arrow-top.png -------------------------------------------------------------------------------- /Resources/TNAttachedWindow/White/attached-window-top-left.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ArchipelProject/TNKit/HEAD/Resources/TNAttachedWindow/White/attached-window-top-left.png -------------------------------------------------------------------------------- /Resources/TNAttachedWindow/White/attached-window-top-right.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ArchipelProject/TNKit/HEAD/Resources/TNAttachedWindow/White/attached-window-top-right.png -------------------------------------------------------------------------------- /Resources/TNUIKitScrollView/scroller-horizontal-knob-center.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ArchipelProject/TNKit/HEAD/Resources/TNUIKitScrollView/scroller-horizontal-knob-center.png -------------------------------------------------------------------------------- /Resources/TNUIKitScrollView/scroller-horizontal-knob-left.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ArchipelProject/TNKit/HEAD/Resources/TNUIKitScrollView/scroller-horizontal-knob-left.png -------------------------------------------------------------------------------- /Resources/TNUIKitScrollView/scroller-horizontal-knob-right.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ArchipelProject/TNKit/HEAD/Resources/TNUIKitScrollView/scroller-horizontal-knob-right.png -------------------------------------------------------------------------------- /Resources/TNUIKitScrollView/scroller-vertical-knob-bottom.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ArchipelProject/TNKit/HEAD/Resources/TNUIKitScrollView/scroller-vertical-knob-bottom.png -------------------------------------------------------------------------------- /Resources/TNUIKitScrollView/scroller-vertical-knob-center.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ArchipelProject/TNKit/HEAD/Resources/TNUIKitScrollView/scroller-vertical-knob-center.png -------------------------------------------------------------------------------- /Resources/TNAttachedWindow/Black/attached-window-arrow-bottom.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ArchipelProject/TNKit/HEAD/Resources/TNAttachedWindow/Black/attached-window-arrow-bottom.png -------------------------------------------------------------------------------- /Resources/TNAttachedWindow/Black/attached-window-arrow-right.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ArchipelProject/TNKit/HEAD/Resources/TNAttachedWindow/Black/attached-window-arrow-right.png -------------------------------------------------------------------------------- /Resources/TNAttachedWindow/Black/attached-window-bottom-left.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ArchipelProject/TNKit/HEAD/Resources/TNAttachedWindow/Black/attached-window-bottom-left.png -------------------------------------------------------------------------------- /Resources/TNAttachedWindow/Black/attached-window-bottom-right.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ArchipelProject/TNKit/HEAD/Resources/TNAttachedWindow/Black/attached-window-bottom-right.png -------------------------------------------------------------------------------- /Resources/TNAttachedWindow/Black/attached-window-button-close.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ArchipelProject/TNKit/HEAD/Resources/TNAttachedWindow/Black/attached-window-button-close.png -------------------------------------------------------------------------------- /Resources/TNAttachedWindow/White/attached-window-arrow-bottom.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ArchipelProject/TNKit/HEAD/Resources/TNAttachedWindow/White/attached-window-arrow-bottom.png -------------------------------------------------------------------------------- /Resources/TNAttachedWindow/White/attached-window-arrow-right.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ArchipelProject/TNKit/HEAD/Resources/TNAttachedWindow/White/attached-window-arrow-right.png -------------------------------------------------------------------------------- /Resources/TNAttachedWindow/White/attached-window-bottom-left.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ArchipelProject/TNKit/HEAD/Resources/TNAttachedWindow/White/attached-window-bottom-left.png -------------------------------------------------------------------------------- /Resources/TNAttachedWindow/White/attached-window-bottom-right.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ArchipelProject/TNKit/HEAD/Resources/TNAttachedWindow/White/attached-window-bottom-right.png -------------------------------------------------------------------------------- /Resources/TNAttachedWindow/White/attached-window-button-close.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ArchipelProject/TNKit/HEAD/Resources/TNAttachedWindow/White/attached-window-button-close.png -------------------------------------------------------------------------------- /Resources/TNAttachedWindow/Black/attached-window-button-close-pressed.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ArchipelProject/TNKit/HEAD/Resources/TNAttachedWindow/Black/attached-window-button-close-pressed.png -------------------------------------------------------------------------------- /Resources/TNAttachedWindow/White/attached-window-button-close-pressed.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ArchipelProject/TNKit/HEAD/Resources/TNAttachedWindow/White/attached-window-button-close-pressed.png -------------------------------------------------------------------------------- /README.markdown: -------------------------------------------------------------------------------- 1 | # TNKit 2 | 3 | 4 | ## What is TNKit ? 5 | 6 | TNKit is a general framework that contains different things 7 | 8 | ### TNAttachedWindow 9 | A property view acting like the one that pops up when you double click on a meeting in iCal 10 | 11 | ### TNTableViewDataSource 12 | A pretty cool ready to use datasource with filtering support for CPTableView 13 | 14 | ### TNTextFieldStepper 15 | A CPStepper with a textfield 16 | Note : CPStepper should be included in Capp soon. If still not, you can find the patch [here](http://github.com/primalmotion/cappuccino/tree/cpstepper-implementation) 17 | 18 | ### TNAlert 19 | A simple wrapper of CPAlert to make alert quickly and not having to deal with delegates 20 | 21 | 22 | ## Build 23 | 24 | To build TNKit you can type 25 | 26 | # jake debug ; jake release 27 | 28 | 29 | ## Quick Start 30 | 31 | Simply include the TNKit framework in your Frameworks directory and include TNKit.j 32 | 33 | @import 34 | 35 | 36 | ## Demo application 37 | 38 | You can see a demo application here: [Demo](http://github.com/primalmotion/TNKit-Example/) 39 | 40 | 41 | ## Documentation 42 | 43 | To generate the documentation execute the following : 44 | 45 | # jake docs 46 | 47 | 48 | ## Help / Suggestion 49 | 50 | You can reach us at irc://irc.freenode.net/#archipel -------------------------------------------------------------------------------- /TNAnimation.j: -------------------------------------------------------------------------------- 1 | /* 2 | * TNAnimation.j 3 | * 4 | * Copyright (C) 2010 Antoine Mercadal 5 | * 6 | * This library is free software; you can redistribute it and/or 7 | * modify it under the terms of the GNU Lesser General Public 8 | * License as published by the Free Software Foundation; either 9 | * version 3.0 of the License, or (at your option) any later version. 10 | * 11 | * This library is distributed in the hope that it will be useful, 12 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 13 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 14 | * Lesser General Public License for more details. 15 | * 16 | * You should have received a copy of the GNU Lesser General Public 17 | * License along with this library; if not, write to the Free Software 18 | * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA 19 | */ 20 | 21 | @import 22 | 23 | @import 24 | 25 | 26 | 27 | /*! @ingroup tnkit 28 | 29 | Simple CPAnimation subclass that calls the delegate at each progress 30 | */ 31 | @implementation TNAnimation: CPAnimation 32 | { 33 | id userInfo @accessors; 34 | } 35 | 36 | - (void)setCurrentProgress:(float)aProgress 37 | { 38 | [super setCurrentProgress:aProgress]; 39 | [self currentValue]; 40 | } 41 | @end -------------------------------------------------------------------------------- /Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CPBundleIdentifier 6 | org.archipelproject.tnkit 7 | CPBundleInfoDictionaryVersion 8 | 6.0 9 | CPBundleName 10 | TNKit 11 | CPBundlePackageType 12 | FMWK 13 | 14 | 17 | TNAttachedWindowUseGlowEffect 18 | 1 19 | 20 | 21 | 24 | TNAttachedWindowWhiteMaskTopColor 25 | ffffff 26 | TNAttachedWindowWhiteMaskBottomColor 27 | ebebeb 28 | TNAttachedWindowWhiteMaskBorderColor 29 | ADEDFF 30 | 31 | 34 | TNAttachedWindowBlackMaskTopColor 35 | 363636 36 | TNAttachedWindowBlackMaskBottomColor 37 | 212121 38 | TNAttachedWindowBlackMaskBorderColor 39 | 131313 40 | 41 | 42 | 43 | -------------------------------------------------------------------------------- /TNKit.j: -------------------------------------------------------------------------------- 1 | /* 2 | * TNKit.j 3 | * 4 | * Copyright (C) 2010 Antoine Mercadal 5 | * 6 | * This library is free software; you can redistribute it and/or 7 | * modify it under the terms of the GNU Lesser General Public 8 | * License as published by the Free Software Foundation; either 9 | * version 3.0 of the License, or (at your option) any later version. 10 | * 11 | * This library is distributed in the hope that it will be useful, 12 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 13 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 14 | * Lesser General Public License for more details. 15 | * 16 | * You should have received a copy of the GNU Lesser General Public 17 | * License along with this library; if not, write to the Free Software 18 | * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA 19 | */ 20 | 21 | @import "TNAlert.j" 22 | @import "TNAnimation.j" 23 | @import "TNFlipView.j" 24 | @import "TNMessageBoard.j" 25 | @import "TNMessageView.j" 26 | @import "TNOutlineViewDataSource.j" 27 | @import "TNRangeSelectorView.j" 28 | @import "TNStackView.j" 29 | @import "TNSwipeView.j" 30 | @import "TNTableViewDataSource.j" 31 | @import "TNTableViewLazyDataSource.j" 32 | @import "TNTabView.j" 33 | @import "TNTextFieldStepper.j" 34 | @import "TNToolbar.j" 35 | @import "TNUIKitScrollView.j" 36 | @import "TNWorker.j" 37 | 38 | 39 | /*! @mainpage 40 | TNKit is distributed under the @ref license "AGPL". 41 | 42 | @htmlonly
@endhtmlonly
43 |     @htmlinclude README
44 |     @htmlonly 
@endhtmlonly 45 | 46 | @page license License 47 | @htmlonly
@endhtmlonly
48 |     @htmlinclude LICENSE
49 |     @htmlonly 
@endhtmlonly 50 | 51 | @defgroup tnkit TNKit 52 | */ 53 | -------------------------------------------------------------------------------- /Jakefile: -------------------------------------------------------------------------------- 1 | /* 2 | * Jakefile 3 | * 4 | * Copyright (C) 2010 Antoine Mercadal 5 | * 6 | * This library is free software; you can redistribute it and/or 7 | * modify it under the terms of the GNU Lesser General Public 8 | * License as published by the Free Software Foundation; either 9 | * version 3.0 of the License, or (at your option) any later version. 10 | * 11 | * This library is distributed in the hope that it will be useful, 12 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 13 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 14 | * Lesser General Public License for more details. 15 | * 16 | * You should have received a copy of the GNU Lesser General Public 17 | * License along with this library; if not, write to the Free Software 18 | * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA 19 | */ 20 | 21 | var ENV = require("system").env, 22 | FILE = require("file"), 23 | OS = require("os"), 24 | JAKE = require("jake"), 25 | task = JAKE.task, 26 | CLEAN = require("jake/clean").CLEAN, 27 | FileList = JAKE.FileList, 28 | framework = require("cappuccino/jake").framework, 29 | configuration = ENV["CONFIG"] || ENV["CONFIGURATION"] || ENV["c"] || "Release"; 30 | 31 | framework ("TNKit", function(task) 32 | { 33 | task.setBuildIntermediatesPath(FILE.join(ENV["CAPP_BUILD"], "TNKit.build", configuration)); 34 | task.setBuildPath(FILE.join(ENV["CAPP_BUILD"], configuration)); 35 | 36 | task.setProductName("TNKit"); 37 | task.setIdentifier("org.archipelproject.TNKit"); 38 | task.setVersion("1.0"); 39 | task.setAuthor("Antoine Mercadal"); 40 | task.setEmail("antoine.mercadal @nospam@ inframonde.eu"); 41 | task.setSummary("TNKit"); 42 | task.setSources(new FileList("*.j", "TNKit/*.j")); 43 | task.setResources(new FileList("Resources/**/**")); 44 | task.setInfoPlistPath("Info.plist"); 45 | 46 | if (configuration === "Debug") 47 | task.setCompilerFlags("-DDEBUG -g"); 48 | else 49 | task.setCompilerFlags("-O2"); 50 | }); 51 | 52 | task("build", ["TNKit"]); 53 | 54 | task("debug", function() 55 | { 56 | ENV["CONFIG"] = "Debug" 57 | JAKE.subjake(["."], "build", ENV); 58 | }); 59 | 60 | task("release", function() 61 | { 62 | ENV["CONFIG"] = "Release" 63 | JAKE.subjake(["."], "build", ENV); 64 | }); 65 | 66 | task ("documentation", function() 67 | { 68 | OS.system("doxygen TNKit.doxygen") 69 | }); 70 | 71 | task("test", ["test-only"]); 72 | 73 | task("test-only", function() 74 | { 75 | ENV["OBJJ_INCLUDE_PATHS"] = "Frameworks"; 76 | 77 | OS.system("capp gen -fl . --force"); 78 | 79 | var tests = new FileList('Test/*Test.j'), 80 | cmd = ["ojtest"].concat(tests.items()), 81 | cmdString = cmd.map(OS.enquote).join(" "), 82 | code = OS.system(cmdString); 83 | 84 | OS.system("rm -rf Frameworks"); 85 | 86 | if (code !== 0) 87 | OS.exit(code); 88 | }); 89 | 90 | task ("default", ["release"]); 91 | task ("docs", ["documentation"]); 92 | task ("all", ["release", "debug", "documentation"]); 93 | -------------------------------------------------------------------------------- /TNTextFieldStepper.j: -------------------------------------------------------------------------------- 1 | /* 2 | * TNTextFieldStepper.j 3 | * 4 | * Copyright (C) 2010 Antoine Mercadal 5 | * 6 | * This library is free software; you can redistribute it and/or 7 | * modify it under the terms of the GNU Lesser General Public 8 | * License as published by the Free Software Foundation; either 9 | * version 3.0 of the License, or (at your option) any later version. 10 | * 11 | * This library is distributed in the hope that it will be useful, 12 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 13 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 14 | * Lesser General Public License for more details. 15 | * 16 | * You should have received a copy of the GNU Lesser General Public 17 | * License along with this library; if not, write to the Free Software 18 | * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA 19 | */ 20 | 21 | @import 22 | 23 | @import 24 | @import 25 | 26 | 27 | var TNStepperButtonsSize; 28 | 29 | /*! @ingroup tnkit 30 | TNTextFieldStepper is a subclass of CPStepper. it contains a textfield that displays the current stepper value 31 | */ 32 | @implementation TNTextFieldStepper : CPControl 33 | { 34 | CPTextField _textField; 35 | CPStepper _stepper; 36 | } 37 | 38 | 39 | #pragma mark - 40 | #pragma mark Initialization 41 | 42 | /*! Initializes the TNTextFieldStepper with the textfield 43 | @param aFrame the frame of the control 44 | @return initialized TNTextFieldStepper 45 | */ 46 | - (id)initWithFrame:(CGRect)aFrame 47 | { 48 | aFrame.origin.x -= 6; 49 | aFrame.origin.y -= 3; 50 | aFrame.size.width += 14; 51 | if (self = [super initWithFrame:aFrame]) 52 | { 53 | [self _init]; 54 | } 55 | 56 | return self; 57 | } 58 | 59 | - (void)_init 60 | { 61 | var frame = [self frame]; 62 | 63 | _stepper = [CPStepper stepper]; 64 | [_stepper setFrame:CGRectMake(frame.size.width - 35, 1.0, 25, 25)] 65 | [_stepper setAutoresizingMask:CPViewMinXMargin]; 66 | 67 | _textField = [CPTextField textFieldWithStringValue:@"" placeholder:@"" width:frame.size.width - 36]; 68 | [_textField setAutoresizingMask:CPViewWidthSizable]; 69 | [_textField bind:CPValueBinding toObject:_stepper withKeyPath:@"doubleValue" options:nil]; 70 | 71 | [self addSubview:_stepper]; 72 | [self addSubview:_textField]; 73 | } 74 | 75 | - (void)setEnabled:(BOOL)shouldEnabled 76 | { 77 | [_stepper setEnabled:shouldEnabled]; 78 | [_textField setEnabled:shouldEnabled]; 79 | } 80 | 81 | - (void)setValueWraps:(BOOL)shouldWrap 82 | { 83 | [_stepper setValueWraps:shouldWrap]; 84 | } 85 | 86 | - (int)valueWraps 87 | { 88 | return [_stepper valueWraps]; 89 | } 90 | 91 | - (void)setAutorepeat:(BOOL)shouldAutorepeat 92 | { 93 | [_stepper setAutorepeat:shouldAutorepeat]; 94 | } 95 | 96 | - (int)autorepeat 97 | { 98 | return [_stepper autorepeat]; 99 | } 100 | 101 | - (void)setMinValue:(int)aValue 102 | { 103 | [_stepper setMinValue:aValue]; 104 | } 105 | 106 | - (int)minValue 107 | { 108 | return [_stepper minValue]; 109 | } 110 | 111 | - (void)setMaxValue:(int)aValue 112 | { 113 | [_stepper setMaxValue:aValue]; 114 | } 115 | 116 | - (int)maxValue 117 | { 118 | return [_stepper maxValue]; 119 | } 120 | 121 | - (void)setDoubleValue:(double)aValue 122 | { 123 | [_stepper setDoubleValue:aValue]; 124 | } 125 | 126 | - (double)doubleValue 127 | { 128 | return [_stepper doubleValue]; 129 | } 130 | 131 | - (void)setTarget:(id)aTarget 132 | { 133 | [_stepper setTarget:aTarget]; 134 | [_textField setTarget:aTarget]; 135 | } 136 | 137 | - (id)target 138 | { 139 | return [_stepper target]; 140 | } 141 | 142 | - (void)setAction:(SEL)aSelector 143 | { 144 | [_stepper setAction:aSelector]; 145 | [_textField setAction:aSelector]; 146 | } 147 | 148 | - (SEL)selector 149 | { 150 | return [_stepper action]; 151 | } 152 | 153 | @end 154 | 155 | 156 | @implementation TNTextFieldStepper (CPCodingCompliance) 157 | 158 | - (id)initWithCoder:(CPCoder)aCoder 159 | { 160 | if (self = [super initWithCoder:aCoder]) 161 | { 162 | [self _init]; 163 | } 164 | 165 | return self; 166 | } 167 | 168 | @end 169 | -------------------------------------------------------------------------------- /TNWorker.j: -------------------------------------------------------------------------------- 1 | /* 2 | * TNWorker.j 3 | * 4 | * Copyright (C) 2010 Antoine Mercadal 5 | * 6 | * This library is free software; you can redistribute it and/or 7 | * modify it under the terms of the GNU Lesser General Public 8 | * License as published by the Free Software Foundation; either 9 | * version 3.0 of the License, or (at your option) any later version. 10 | * 11 | * This library is distributed in the hope that it will be useful, 12 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 13 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 14 | * Lesser General Public License for more details. 15 | * 16 | * You should have received a copy of the GNU Lesser General Public 17 | * License along with this library; if not, write to the Free Software 18 | * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA 19 | */ 20 | 21 | @import 22 | 23 | @global Worker 24 | 25 | 26 | /*! @ingroup tnkit 27 | Wrapper for Web Worker in Cappuccino 28 | */ 29 | @implementation TNWorker: CPObject 30 | { 31 | id _worker; 32 | CPString _workerBlob @accessors(property=workerBlob); 33 | CPURL _workerURL @accessors(property=workerURL); 34 | id _delegate @accessors(property=delegate); 35 | } 36 | 37 | /*! Initialize the TNWorker with a given script as string blob 38 | @param aBlob CPString containing the script blob 39 | */ 40 | - (TNWorker)initWithBlob:(CPString)aBlob 41 | { 42 | if (self = [super init]) 43 | { 44 | _workerBlob = aBlob; 45 | 46 | var blobBuilder, 47 | blobURL; 48 | 49 | if (window.BlobBuilder) 50 | blobBuilder = new window.BlobBuilder(); 51 | else if (window.WebKitBlobBuilder) 52 | blobBuilder = new window.WebKitBlobBuilder(); 53 | else if (window.MozBlobBuilder) 54 | blobBuilder = new window.MozBlobBuilder(); 55 | 56 | blobBuilder.append(_workerBlob); 57 | 58 | if (window.webkitURL) 59 | blobURL = window.webkitURL.createObjectURL(blobBuilder.getBlob()); 60 | else if (window.URL) 61 | blobURL = window.URL.createObjectURL(blobBuilder.getBlob()); 62 | 63 | _worker = new Worker(blobURL); 64 | _worker.onmessage = function(e) { 65 | [self _didReceiveData:e.data]; 66 | }; 67 | _worker.onerror = function(error) { 68 | [self _didReceiveError:error]; 69 | }; 70 | } 71 | 72 | return self; 73 | } 74 | 75 | /*! Initialize the TNWorker with a given URL 76 | @param anURL CPURL representing the worker script URL 77 | */ 78 | - (TNWorker)initWithURL:(CPURL)anURL 79 | { 80 | if (self = [super init]) 81 | { 82 | _workerURL = anURL; 83 | _worker = new Worker([_workerURL absoluteString]); 84 | _worker.onmessage = function(e) { 85 | [self _didReceiveData:e.data]; 86 | } 87 | _worker.onerror = function(error) { 88 | [self _didReceiveError:error]; 89 | } 90 | } 91 | 92 | return self; 93 | } 94 | 95 | /*! post a message 96 | @param anObject an object to send to the worker. Beware! you can't manipulate DOM object 97 | in worker, so don't give any object containing a reference to the DOM. 98 | */ 99 | - (void)postMessage:(id)anObject 100 | { 101 | _worker.postMessage(anObject) 102 | } 103 | 104 | /*! Terminate the worker it will be unusable 105 | */ 106 | - (void)terminate 107 | { 108 | _worker.terminate() 109 | _worker.onmessage = function() {}; 110 | _worker.onerror = function() {}; 111 | } 112 | 113 | /*! @ignore 114 | */ 115 | - (void)_didReceiveData:(id)someData 116 | { 117 | if (!_delegate) 118 | return; 119 | if ([_delegate respondsToSelector:@selector(worker:didReceiveData:)]) 120 | [_delegate worker:self didReceiveData:someData]; 121 | } 122 | 123 | /*! @ignore 124 | */ 125 | - (void)_didReceiveError:(id)anError 126 | { 127 | if (!_delegate) 128 | return; 129 | 130 | if ([_delegate respondsToSelector:@selector(worker:didReceiveError:file:line:)]) 131 | [_delegate worker:self didReceiveError:anError.message file:anError.filename line:anError.lineno]; 132 | } 133 | 134 | 135 | @end 136 | -------------------------------------------------------------------------------- /TNStackView.j: -------------------------------------------------------------------------------- 1 | /* 2 | * TNStackView.j 3 | * 4 | * Copyright (C) 2010 Antoine Mercadal 5 | * 6 | * This library is free software; you can redistribute it and/or 7 | * modify it under the terms of the GNU Lesser General Public 8 | * License as published by the Free Software Foundation; either 9 | * version 3.0 of the License, or (at your option) any later version. 10 | * 11 | * This library is distributed in the hope that it will be useful, 12 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 13 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 14 | * Lesser General Public License for more details. 15 | * 16 | * You should have received a copy of the GNU Lesser General Public 17 | * License along with this library; if not, write to the Free Software 18 | * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA 19 | */ 20 | 21 | @import 22 | 23 | @import 24 | 25 | 26 | 27 | /*! @ingroup messageboard 28 | This class allows to create a view that can stack different subviews. 29 | It will resize if in width to fill completely the view, but keeps the height 30 | It is also possible to set padding between views and reverse it. 31 | 32 | when the positionning of the view is done, it will call eventual selector [aStackedView layout] 33 | of each subviews. The subview can then, if needed adjust its height, it's content 34 | call it's mom or whatever 35 | */ 36 | @implementation TNStackView : CPView 37 | { 38 | CPArray _dataSource @accessors(property=dataSource); 39 | int _padding @accessors(property=padding); 40 | BOOL _reversed @accessors(getter=isReversed, setter=setReversed:); 41 | CPArray _stackedViews; 42 | } 43 | 44 | /*! initialize the TNStackView 45 | @param aFrame the frame 46 | @return a instancied TNStackView 47 | */ 48 | - (id)initWithFrame:(CGRect)aFrame 49 | { 50 | if (self = [super initWithFrame:aFrame]) 51 | { 52 | [self _init]; 53 | } 54 | 55 | return self; 56 | } 57 | 58 | - (void)_init 59 | { 60 | _dataSource = [CPArray array]; 61 | _stackedViews = [CPArray array]; 62 | _padding = 0; 63 | _reversed = NO; 64 | } 65 | 66 | /*! @ignore 67 | */ 68 | - (CGRect)_nextPosition 69 | { 70 | var lastStackedView = [_stackedViews lastObject], 71 | position; 72 | 73 | if (lastStackedView) 74 | { 75 | position = [lastStackedView frame]; 76 | position.origin.y = CGRectGetMaxY(position) + _padding; 77 | position.origin.x = _padding; 78 | } 79 | else 80 | position = CGRectMake(_padding, _padding, CGRectGetWidth([self bounds]) - (_padding * 2), 0); 81 | 82 | return position 83 | } 84 | 85 | /*! reload the content of the datasource 86 | */ 87 | - (void)reload 88 | { 89 | var frame = [self frame]; 90 | 91 | frame.size.height = 0; 92 | [self setFrame:frame]; 93 | 94 | for (var i = 0, c = [_stackedViews count]; i < c; i++) 95 | { 96 | var view = [_stackedViews objectAtIndex:i]; 97 | 98 | if ([view superview]) 99 | [view removeFromSuperview]; 100 | } 101 | 102 | [_stackedViews removeAllObjects]; 103 | [self layout]; 104 | } 105 | 106 | /*! @position the different subviews in the daasource 107 | */ 108 | - (void)layout 109 | { 110 | var stackViewFrame = [self frame], 111 | workingArray = _reversed ? [_dataSource copy].reverse() : _dataSource; 112 | 113 | stackViewFrame.size.height = 0; 114 | 115 | for (var i = 0, c = [workingArray count]; i < c; i++) 116 | { 117 | var currentView = workingArray[i], 118 | position = [self _nextPosition]; 119 | 120 | position.size.height = [currentView frameSize].height; 121 | [currentView setAutoresizingMask:CPViewWidthSizable]; 122 | [currentView setFrame:position]; 123 | 124 | if ([currentView respondsToSelector:@selector(layout)]) 125 | [currentView layout]; 126 | 127 | [self addSubview:currentView]; 128 | [_stackedViews addObject:currentView]; 129 | 130 | stackViewFrame.size.height += [currentView frame].size.height + _padding; 131 | } 132 | 133 | stackViewFrame.size.height += _padding; 134 | [self setFrame:stackViewFrame]; 135 | } 136 | 137 | /*! remove all items as an @action 138 | */ 139 | - (@action)removeAllViews:(id)aSender 140 | { 141 | for (var i = 0, c = [_dataSource count]; i < c; i++) 142 | [[_dataSource objectAtIndex:i] removeFromSuperview]; 143 | 144 | [_dataSource removeAllObjects]; 145 | 146 | [self reload]; 147 | } 148 | 149 | /*! reverse the display of the view (but not in the Datasource) as an @action 150 | */ 151 | - (@action)reverse:(id)sender 152 | { 153 | _reversed = !_reversed; 154 | 155 | [self reload]; 156 | } 157 | 158 | #pragma mark - 159 | #pragma mark CPCoding compliance 160 | 161 | /*! CPCoder compliance 162 | */ 163 | - (id)initWithCoder:(CPCoder)aCoder 164 | { 165 | self = [super initWithCoder:aCoder]; 166 | 167 | if (self) 168 | { 169 | [self _init]; 170 | } 171 | 172 | return self; 173 | } 174 | 175 | /*! CPCoder compliance 176 | */ 177 | - (void)encodeWithCoder:(CPCoder)aCoder 178 | { 179 | [super encodeWithCoder:aCoder]; 180 | } 181 | 182 | @end 183 | -------------------------------------------------------------------------------- /TNAlert.j: -------------------------------------------------------------------------------- 1 | /* 2 | * TNAlert.j 3 | * 4 | * Copyright (C) 2010 Antoine Mercadal 5 | * 6 | * This library is free software; you can redistribute it and/or 7 | * modify it under the terms of the GNU Lesser General Public 8 | * License as published by the Free Software Foundation; either 9 | * version 3.0 of the License, or (at your option) any later version. 10 | * 11 | * This library is distributed in the hope that it will be useful, 12 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 13 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 14 | * Lesser General Public License for more details. 15 | * 16 | * You should have received a copy of the GNU Lesser General Public 17 | * License along with this library; if not, write to the Free Software 18 | * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA 19 | */ 20 | 21 | @import 22 | 23 | @import 24 | 25 | /*! @ingroup tnkit 26 | A very easy to use alert without all the NSAlert mess 27 | */ 28 | @implementation TNAlert : CPAlert 29 | { 30 | CPArray _actions @accessors(getter=actions); 31 | id _helpTarget @accessors(getter=helpTarget); 32 | id _target @accessors(property=target); 33 | id _userInfo @accessors(property=userInfo); 34 | SEL _helpAction @accessors(getter=helpAction); 35 | } 36 | 37 | 38 | #pragma mark - 39 | #pragma mark Initialization 40 | 41 | /*! create a TNAlert and show alert with given parameters 42 | @param aTitle CPString containing the title 43 | @param aMessage CPString containing the message 44 | 45 | @return ready to use TNAlert 46 | */ 47 | + (void)showAlertWithMessage:(CPString)aTitle informative:(CPString)aMessage 48 | { 49 | var tnalert = [[TNAlert alloc] initWithMessage:aTitle informative:aMessage target:nil actions:[["Ok", nil]]]; 50 | 51 | [tnalert runModal]; 52 | } 53 | 54 | /*! create a TNAlert and show alert with given parameters 55 | @param aTitle CPString containing the title 56 | @param aMessage CPString containing the message 57 | @param aStyle the style of the alert 58 | 59 | @return ready to use TNAlert 60 | */ 61 | + (void)showAlertWithMessage:(CPString)aTitle informative:(CPString)aMessage style:(int)aStyle 62 | { 63 | var tnalert = [[TNAlert alloc] initWithMessage:aTitle informative:aMessage target:nil actions:[["Ok", nil]]]; 64 | 65 | [tnalert setAlertStyle:aStyle]; 66 | [tnalert runModal]; 67 | } 68 | 69 | /*! create a TNAlert alert with given parameters 70 | @param aTitle CPString containing the title 71 | @param aMessage CPString containing the message 72 | @param aTarget the target of the actions 73 | @param actions CPArray containing name of buttons and associated actions 74 | (ex [["Ok", @selector(performOK:)],["NOK", @selector(performNOK:)]]) 75 | 76 | @return ready to use TNAlert 77 | */ 78 | + (void)alertWithMessage:(CPString)aTitle informative:(CPString)aMessage target:(id)aTarget actions:(CPArray)someActions 79 | { 80 | var tnalert = [[TNAlert alloc] initWithMessage:aTitle informative:aMessage target:aTarget actions:someActions]; 81 | 82 | return tnalert; 83 | } 84 | 85 | 86 | /*! create a TNAlert alert with given parameters 87 | @param aTitle CPString containing the title 88 | @param aMessage CPString containing the message 89 | @param aTarget the target of the actions 90 | @param actions CPArray containing name of buttons and associated actions 91 | (ex [["Ok", @selector(performOK:)],["NOK", @selector(performNOK:)]]) 92 | 93 | @return ready to use TNAlert 94 | */ 95 | - (TNAlert)initWithMessage:(CPString)aTitle informative:(CPString)aMessage target:(id)aTarget actions:(CPArray)someActions 96 | { 97 | if (self = [super init]) 98 | { 99 | _actions = someActions; 100 | _target = aTarget; 101 | 102 | [self setMessageText:aTitle]; 103 | [self setInformativeText:aMessage]; 104 | [self setDelegate:self]; 105 | [self setAlertStyle:CPInformationalAlertStyle]; 106 | 107 | for (var i = 0, c = [_actions count]; i < c; i++) 108 | [self addButtonWithTitle:_actions[i][0]]; 109 | } 110 | 111 | return self; 112 | } 113 | 114 | /*! define the target and action for help button 115 | if set, help button will be automatically set. 116 | to remove it, simply set aTarget of anAction to nil 117 | @param aTarget the target of the help action 118 | @param anAction the action to execute if user click the help button 119 | */ 120 | - (void)setHelpTarget:(id)aTarget action:(SEL)anAction 121 | { 122 | if (aTarget && anAction) 123 | { 124 | _helpTarget = aTarget; 125 | _helpAction = anAction; 126 | [self setShowsHelp:YES]; 127 | } 128 | else 129 | { 130 | _helpTarget = nil; 131 | _helpAction = nil; 132 | [self setShowsHelp:NO]; 133 | 134 | } 135 | } 136 | 137 | 138 | #pragma mark - 139 | #pragma mark Delegates 140 | 141 | /*! @ignore 142 | */ 143 | - (void)alertDidEnd:(CPAlert)theAlert returnCode:(int)returnCode 144 | { 145 | var selector = _actions[returnCode][1]; 146 | 147 | if ([_target respondsToSelector:selector]) 148 | [_target performSelector:selector withObject:_userInfo]; 149 | } 150 | 151 | /*! @ignore 152 | */ 153 | - (void)alertShowHelp:(CPAlert)anAlert 154 | { 155 | if ([_helpTarget respondsToSelector:_helpAction]) 156 | [_helpTarget performSelector:_helpAction withObject:anAlert]; 157 | } 158 | 159 | @end 160 | -------------------------------------------------------------------------------- /TNOutlineViewDataSource.j: -------------------------------------------------------------------------------- 1 | /* 2 | * TNOutlineViewDataSource.j 3 | * 4 | * Copyright (C) 2010 Antoine Mercadal 5 | * 6 | * This library is free software; you can redistribute it and/or 7 | * modify it under the terms of the GNU Lesser General Public 8 | * License as published by the Free Software Foundation; either 9 | * version 3.0 of the License, or (at your option) any later version. 10 | * 11 | * This library is distributed in the hope that it will be useful, 12 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 13 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 14 | * Lesser General Public License for more details. 15 | * 16 | * You should have received a copy of the GNU Lesser General Public 17 | * License along with this library; if not, write to the Free Software 18 | * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA 19 | */ 20 | 21 | @import 22 | 23 | /*! @ingroup tnkit 24 | datasource of the snaphot outline view 25 | NOT COMPLETE DO NOT USE 26 | */ 27 | @implementation TNOutlineViewDataSource : CPObject 28 | { 29 | BOOL filterInstalled @accessors(setter=setFilterInstalled:, getter=isFilterInstalled); 30 | CPArray _contents @accessors(property=contents); 31 | CPArray _searchableKeyPaths @accessors(property=searchableKeyPaths); 32 | CPString _childCompKeyPath @accessors(property=childCompKeyPath); 33 | CPString _parentKeyPath @accessors(property=parentKeyPath); 34 | } 35 | 36 | 37 | #pragma mark - 38 | #pragma mark Initialization 39 | /*! Initialization of the class 40 | @return an initialized instance of TNVMCastDatasource 41 | */ 42 | - (id)init 43 | { 44 | if (self = [super init]) 45 | { 46 | alert("you should not use TNOutlineViewDataSource. it doesn't work very well for now") 47 | _contents = [CPArray array]; 48 | } 49 | 50 | return self; 51 | } 52 | 53 | 54 | #pragma mark - 55 | #pragma mark Data Information 56 | 57 | /*! return the number of object in datasource 58 | @return number of objectss 59 | */ 60 | - (int)count 61 | { 62 | return [_contents count]; 63 | } 64 | 65 | /*! return the content of the datasource 66 | @return CPArray containing all objects 67 | */ 68 | - (CPArray)objects 69 | { 70 | return _contents; 71 | } 72 | 73 | /*! get the object at given index 74 | @param anIndex the index 75 | @return the object at given index 76 | */ 77 | - (id)objectAtIndex:(int)anIndex 78 | { 79 | return _contents[anIndex]; 80 | } 81 | 82 | /*! get the object at given indexes 83 | @param anIndex the index 84 | @return CPArray of objects at given indexes 85 | */ 86 | - (CPArray)objectsAtIndexes:(CPIndexSet)indexes 87 | { 88 | return [_contents objectsAtIndexes:indexes]; 89 | } 90 | 91 | /*! get all the roots objects 92 | @return CPArray of the root objects 93 | */ 94 | - (CPArray)getRootObjects 95 | { 96 | var array = [CPArray array]; 97 | 98 | for (var i = 0, c = [_contents count]; i < c; i++) 99 | { 100 | var object = _contents[i]; 101 | 102 | if ([object valueForKeyPath:_parentKeyPath] == nil) 103 | [array addObject:object]; 104 | } 105 | 106 | return array; 107 | } 108 | 109 | /*! return all children of an object 110 | @param anObject the object 111 | @return CPArray containing the children of given object 112 | */ 113 | - (CPArray)getChildrenOfObject:(id)anObject 114 | { 115 | var array = [CPArray array]; 116 | 117 | for (var i = 0, c = [_contents count]; i < c; i++) 118 | { 119 | var object = _contents[i]; 120 | if ([object valueForKey:_parentKeyPath] == [anObject valueForKey:_childCompKeyPath]) 121 | [array addObject:object]; 122 | } 123 | 124 | return array; 125 | } 126 | 127 | 128 | #pragma mark - 129 | #pragma mark Data manipulation 130 | 131 | /*! add an object to the datasource 132 | @param anObject the object to add 133 | */ 134 | - (void)addObject:(id)anObject 135 | { 136 | [_contents addObject:anObject]; 137 | } 138 | 139 | /*! remove all objects from datasource 140 | */ 141 | - (void)removeAllObjects 142 | { 143 | [_contents removeAllObjects]; 144 | } 145 | 146 | 147 | #pragma mark - 148 | #pragma mark Datasource implementation 149 | 150 | - (int)outlineView:(CPOutlineView)anOutlineView numberOfChildrenOfItem:(id)item 151 | { 152 | if (!item) 153 | return [[self getRootObjects] count]; 154 | else 155 | return [[self getChildrenOfObject:item] count]; 156 | } 157 | 158 | - (BOOL)outlineView:(CPOutlineView)anOutlineView isItemExpandable:(id)item 159 | { 160 | if (!item) 161 | return YES; 162 | 163 | return ([[self getChildrenOfObject:item] count] > 0) ? YES : NO; 164 | } 165 | 166 | - (id)outlineView:(CPOutlineView)anOutlineView child:(int)index ofItem:(id)item 167 | { 168 | if (!item) 169 | return [self getRootObjects][index]; 170 | else 171 | return [self getChildrenOfObject:item][index]; 172 | } 173 | 174 | - (id)outlineView:(CPOutlineView)anOutlineView objectValueForTableColumn:(CPTableColumn)tableColumn byItem:(id)item 175 | { 176 | var identifier = [tableColumn identifier]; 177 | 178 | if (identifier == @"outline") 179 | return nil; 180 | 181 | return [item valueForKey:identifier]; 182 | } 183 | 184 | - (void)tableView:(CPTableView)aTableView sortDescriptorsDidChange:(CPArray)oldDescriptors 185 | { 186 | [_contents sortUsingDescriptors:[aTableView sortDescriptors]]; 187 | 188 | [aTableView reloadData]; 189 | } 190 | 191 | @end 192 | -------------------------------------------------------------------------------- /TNMessageBoard.j: -------------------------------------------------------------------------------- 1 | /* 2 | * TNMessageBoard.j 3 | * 4 | * Copyright (C) 2010 Antoine Mercadal 5 | * 6 | * This library is free software; you can redistribute it and/or 7 | * modify it under the terms of the GNU Lesser General Public 8 | * License as published by the Free Software Foundation; either 9 | * version 3.0 of the License, or (at your option) any later version. 10 | * 11 | * This library is distributed in the hope that it will be useful, 12 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 13 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 14 | * Lesser General Public License for more details. 15 | * 16 | * You should have received a copy of the GNU Lesser General Public 17 | * License along with this library; if not, write to the Free Software 18 | * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA 19 | */ 20 | 21 | @import 22 | 23 | @import 24 | @import 25 | 26 | @import "TNMessageView.j" 27 | 28 | /*! @ingroup messageboard 29 | This class allows to stack TNMessageView in a variable 30 | row height CPTableView with shortcuts. Result will display 31 | a sort of chat view 32 | */ 33 | @implementation TNMessageBoard : CPTableView 34 | { 35 | CPArray _messages; 36 | TNMessageView _dataView; 37 | } 38 | 39 | #pragma mark - 40 | #pragma mark Initialization 41 | 42 | /*! initialize a new TNMessageBoard 43 | @param aFrame the frame of the TNMessageBoard 44 | @return an initialized TNMessageBoard 45 | */ 46 | - (TNMessageBoard)initWithFrame:(CGRect)aFrame 47 | { 48 | if (self = [super initWithFrame:aFrame]) 49 | { 50 | _messages = [CPArray array]; 51 | 52 | [self setAutoresizingMask: CPViewWidthSizable | CPViewHeightSizable]; 53 | [self setColumnAutoresizingStyle:CPTableViewLastColumnOnlyAutoresizingStyle]; 54 | [self setSelectionHighlightStyle:CPTableViewSelectionHighlightStyleNone]; 55 | [self setAllowsColumnReordering:NO]; 56 | [self setAllowsColumnResizing:NO]; 57 | [self setAllowsEmptySelection:YES]; 58 | [self setAllowsMultipleSelection:NO]; 59 | [self setDataSource:self]; 60 | [self setDelegate:self]; 61 | [self setHeaderView:nil]; 62 | [self setCornerView:nil]; 63 | 64 | var messageColumn = [[CPTableColumn alloc] initWithIdentifier:@"messages"]; 65 | 66 | [messageColumn setWidth:800]; 67 | [[messageColumn headerView] setStringValue:@"Messages"]; 68 | 69 | [self addTableColumn:messageColumn]; 70 | 71 | _dataView = [[TNMessageView alloc] initWithFrame:CGRectMake(0, 0, 100, 100)]; 72 | } 73 | 74 | return self; 75 | } 76 | 77 | 78 | #pragma mark - 79 | #pragma mark Controls 80 | 81 | /*! stack a new message 82 | @param aMessage the content of the message 83 | @param anAuthor sender of the message 84 | @param aColor a CPColor that will be used as background 85 | @param aDate the date of the message 86 | */ 87 | - (void)addMessage:(CPString)aMessage from:(CPString)anAuthor date:(CPDate)aDate color:(int)aColor 88 | { 89 | [self addMessage:aMessage from:anAuthor date:aDate color:aColor avatar:nil position:nil]; 90 | } 91 | 92 | /*! stack a new message 93 | @param aMessage the content of the message 94 | @param anAuthor sender of the message 95 | @param aColor a CPColor that will be used as background 96 | @param aDate the date of the message 97 | @param anAvatar CPImage containing anAvatar 98 | */ 99 | - (void)addMessage:(CPString)aMessage from:(CPString)anAuthor date:(CPDate)aDate color:(int)aColor avatar:(CPImage)anAvatar position:(int)aPosition 100 | { 101 | var messageInfo = [CPDictionary dictionaryWithObjectsAndKeys:anAuthor, @"author", 102 | aMessage, @"message", 103 | aDate, @"date", 104 | aPosition, @"position", 105 | aColor, @"color", 106 | anAvatar, @"avatar"]; 107 | [_messages addObject:messageInfo]; 108 | [self reloadData]; 109 | } 110 | 111 | /*! compatibility 112 | @deprecated 113 | */ 114 | - (void)reload 115 | { 116 | CPLog.warn("TNMessageBoard reload is deprecated. please use reloadData") 117 | [self reloadData]; 118 | } 119 | 120 | 121 | #pragma mark - 122 | #pragma mark Actions 123 | 124 | /*! remove all message. 125 | @deprecated 126 | */ 127 | - (@action)removeAllMessages:(id)aSender 128 | { 129 | CPLog.warn("TNMessageBoard removeAllMessages: is deprecated. please use removeAllViews:") 130 | [self removeAllViews:aSender]; 131 | } 132 | 133 | /*! remove all items as an @action 134 | */ 135 | - (@action)removeAllViews:(id)aSender 136 | { 137 | [_messages removeAllObjects]; 138 | [self reloadData]; 139 | } 140 | 141 | 142 | #pragma mark - 143 | #pragma mark DataSource 144 | 145 | /*! tableview data source protocol 146 | */ 147 | - (void)numberOfRowsInTableView:(CPTableView)aTableView 148 | { 149 | return [_messages count]; 150 | } 151 | 152 | /*! tableview data source protocol 153 | */ 154 | - (void)tableView:(CPTableView)aTableView objectValueForTableColumn:(CPTableColumn)aColumnu row:(int)aRow 155 | { 156 | return _messages[aRow]; 157 | } 158 | 159 | 160 | #pragma mark - 161 | #pragma mark Delegate 162 | 163 | /*! tableview delegate 164 | */ 165 | - (void)tableView:(CPTableView)aTableView viewForTableColumn:(CPTableColumn)aColumn row:(id)aRow 166 | { 167 | var copy = [CPKeyedArchiver archivedDataWithRootObject:_dataView]; 168 | return [CPKeyedUnarchiver unarchiveObjectWithData:copy]; 169 | } 170 | 171 | /*! tableview delegate 172 | */ 173 | - (void)tableView:(CPTableView)aTableView heightOfRow:(int)aRow 174 | { 175 | return [TNMessageView sizeOfMessageViewWithText:[_messages[aRow] objectForKey:@"message"] inWidth:([self frameSize].width)]; 176 | } 177 | 178 | /*! tableview delegate 179 | */ 180 | - (void)tableView:(CPTableView)aTableView shouldSelectRow:(int)aRow 181 | { 182 | return YES; 183 | } 184 | 185 | 186 | @end 187 | -------------------------------------------------------------------------------- /TNTabViewItemPrototype.j: -------------------------------------------------------------------------------- 1 | /* 2 | * TNTabViewItemsPrototypes.j 3 | * 4 | * Copyright (C) 2010 Antoine Mercadal 5 | * This program is free software: you can redistribute it and/or modify 6 | * it under the terms of the GNU Affero General Public License as 7 | * published by the Free Software Foundation, either version 3 of the 8 | * License, or (at your option) any later version. 9 | * 10 | * This program is distributed in the hope that it will be useful, 11 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 | * GNU Affero General Public License for more details. 14 | * 15 | * You should have received a copy of the GNU Affero General Public License 16 | * along with this program. If not, see . 17 | */ 18 | 19 | @import 20 | @import 21 | @import 22 | @import 23 | @import 24 | 25 | TNTabItemPrototypeThemeStateSelected = CPThemeState("TNTabItemPrototypeThemeStateSelected"); 26 | 27 | 28 | /*! @ingroup tnkit 29 | This class represent the prototype of a TNTabView item 30 | you can overide it and use setTabItemViewPrototype: of TNTabView 31 | to implement your own item view 32 | */ 33 | @implementation TNTabItemPrototype : CPControl 34 | { 35 | CPButton _button @accessors(getter=button); 36 | CPColor _errorColor @accessors(property=errorColor); 37 | CPTabViewItem _tabViewItem @accessors(property=tabViewItem); 38 | CPTextField _errorLabel @accessors(getter=errorLabel); 39 | int _index @accessors(property=index); 40 | } 41 | 42 | 43 | #pragma mark - 44 | #pragma mark Class methods 45 | 46 | + (TNTabItemPrototype)new 47 | { 48 | return [[self alloc] initWithFrame:CGRectMake(0.0, 0.0, [self size].width, [self size].height)]; 49 | } 50 | 51 | /*! return the actual size of a TNTabItemPrototype 52 | This is used to layout the TNTabView 53 | */ 54 | + (CGSize)size 55 | { 56 | return CGSizeMake(115.0, 25.0); 57 | } 58 | 59 | + (BOOL)isImage 60 | { 61 | return NO; 62 | } 63 | 64 | - (CGSize)size 65 | { 66 | return [[self class] size]; 67 | } 68 | 69 | - (BOOL)isImage 70 | { 71 | return [[self class] isImage]; 72 | } 73 | 74 | + (float)margin 75 | { 76 | return 30.0; 77 | } 78 | 79 | - (float)margin 80 | { 81 | return [[self class] margin]; 82 | } 83 | 84 | - (int)width 85 | { 86 | if (![self isImage] && _tabViewItem && _button) 87 | { 88 | var contentInset = [_button currentValueForThemeAttribute:@"content-inset"]; 89 | return [[_tabViewItem label] sizeWithFont:[_button currentValueForThemeAttribute:@"font"]].width + contentInset.left + contentInset.right; 90 | } 91 | 92 | return [self size].width; 93 | } 94 | 95 | #pragma mark - 96 | #pragma mark Initialization 97 | 98 | /*! initialize a new TNTabItemPrototype 99 | */ 100 | - (TNTabItemPrototype)initWithFrame:(CGRect)aFrame 101 | { 102 | if (self = [super initWithFrame:aFrame]) 103 | { 104 | _button = [[CPButton alloc] initWithFrame:CGRectMake(0, 0, [self size].width, [self size].height)]; 105 | [_button setAutoresizingMask:CPViewMinXMargin | CPViewMinYMargin | CPViewWidthSizable]; 106 | 107 | _errorLabel = [[CPTextField alloc] initWithFrame:CGRectMakeZero()]; 108 | 109 | [self prepareTheme]; 110 | 111 | [self addSubview:_errorLabel]; 112 | [self addSubview:_button]; 113 | 114 | [_button setAlignment:CPCenterTextAlignment]; 115 | [_button setTarget:self]; 116 | [_button setAction:@selector(_didClick:)]; 117 | _index = -1; 118 | } 119 | 120 | return self; 121 | } 122 | 123 | - (void)prepareTheme 124 | { 125 | [_button setValue:[CPColor colorWithHexString:@"777D7D"] forThemeAttribute:@"text-color"]; 126 | [_button setValue:[CPColor whiteColor] forThemeAttribute:@"text-shadow-color"]; 127 | [_button setValue:[CPColor colorWithHexString:@"B3D0FF"] forThemeAttribute:@"text-color" inState:CPThemeStateHighlighted]; 128 | [_button setValue:[CPColor colorWithHexString:@"6B94EC"] forThemeAttribute:@"text-color" inState:TNTabItemPrototypeThemeStateSelected]; 129 | [_button setBordered:NO]; 130 | [_button setFont:[CPFont systemFontOfSize:12]]; 131 | } 132 | 133 | 134 | /*! @ignore 135 | */ 136 | - (@action)_didClick:(id)aSender 137 | { 138 | [[self target] performSelector:[self action] withObject:self]; 139 | } 140 | 141 | #pragma mark - 142 | #pragma mark Getters / Setters 143 | 144 | /*! set the background color for the tabs view 145 | @param aColor the color 146 | */ 147 | - (void)setErrorsNumber:(int)nbErrors 148 | { 149 | [_errorLabel setObjectValue:nbErrors]; 150 | [_errorLabel sizeToFit]; 151 | [_errorLabel setHidden:!nbErrors]; 152 | 153 | var labelFrame = [_errorLabel frame], 154 | labelWidth = labelFrame.size.width + 5; 155 | 156 | [_errorLabel setFrame:CGRectMake(-labelWidth - 4, [self size].height / 2 - labelFrame.size.height / 2, labelWidth, labelFrame.size.height)]; 157 | } 158 | 159 | - (void)setErrorColor:(CPColor)aColor 160 | { 161 | if (_errorColor == aColor) 162 | return; 163 | 164 | _errorColor = aColor 165 | [_errorLabel setBackgroundColor:_errorColor]; 166 | } 167 | 168 | 169 | #pragma mark - 170 | #pragma mark Overrides 171 | 172 | /*! called to set the content of the view 173 | from the CPTabViewItem passed by TNTabView 174 | @passed anItem the CPTabViewItem 175 | */ 176 | - (void)setTabViewItem:(CPTabViewItem)anItem 177 | { 178 | _tabViewItem = anItem; 179 | [_button setTitle:[anItem label]]; 180 | } 181 | 182 | 183 | /*! used to set theme state of subviews 184 | @param aThemeState the theme state 185 | */ 186 | - (BOOL)setThemeState:(ThemeState)aThemeState 187 | { 188 | [_button setThemeState:aThemeState]; 189 | return YES; 190 | } 191 | 192 | /*! used to unset theme state of subviews 193 | @param aThemeState the theme state 194 | */ 195 | - (BOOL)unsetThemeState:(ThemeState)aThemeState 196 | { 197 | [_button unsetThemeState:aThemeState]; 198 | return YES; 199 | } 200 | 201 | /*! CPCoder compliance 202 | */ 203 | - (id)initWithCoder:(CPCoder)aCoder 204 | { 205 | if (self = [super initWithCoder:aCoder]) 206 | { 207 | _button = [aCoder decodeObjectForKey:@"_button"]; 208 | _errorLabel = [aCoder decodeObjectForKey:@"_errorLabel"]; 209 | _errorColor = [aCoder decodeObjectForKey:@"_errorColor"]; 210 | 211 | [_errorLabel setAlignment:CPCenterTextAlignment]; 212 | [_errorLabel setBackgroundColor:_errorColor]; 213 | [_errorLabel setBorderRadius:100]; 214 | [_errorLabel setEditable:NO]; 215 | [_errorLabel setFont:[CPFont systemFontOfSize:9]]; 216 | [_errorLabel setHidden:YES]; 217 | [_errorLabel setValue:[CPColor whiteColor] forThemeAttribute:@"text-color"]; 218 | 219 | [self setClipsToBounds:NO]; 220 | } 221 | 222 | 223 | return self; 224 | } 225 | 226 | /*! CPCoder compliance 227 | */ 228 | - (void)encodeWithCoder:(CPCoder)aCoder 229 | { 230 | [super encodeWithCoder:aCoder]; 231 | 232 | [aCoder encodeObject:_button forKey:@"_button"]; 233 | [aCoder encodeObject:_errorColor forKey:@"_errorColor"]; 234 | [aCoder encodeObject:_errorLabel forKey:@"_errorLabel"]; 235 | } 236 | 237 | @end 238 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | GNU LESSER GENERAL PUBLIC LICENSE 2 | Version 3, 29 June 2007 3 | 4 | Copyright (C) 2007 Free Software Foundation, Inc. 5 | Everyone is permitted to copy and distribute verbatim copies 6 | of this license document, but changing it is not allowed. 7 | 8 | 9 | This version of the GNU Lesser General Public License incorporates 10 | the terms and conditions of version 3 of the GNU General Public 11 | License, supplemented by the additional permissions listed below. 12 | 13 | 0. Additional Definitions. 14 | 15 | As used herein, "this License" refers to version 3 of the GNU Lesser 16 | General Public License, and the "GNU GPL" refers to version 3 of the GNU 17 | General Public License. 18 | 19 | "The Library" refers to a covered work governed by this License, 20 | other than an Application or a Combined Work as defined below. 21 | 22 | An "Application" is any work that makes use of an interface provided 23 | by the Library, but which is not otherwise based on the Library. 24 | Defining a subclass of a class defined by the Library is deemed a mode 25 | of using an interface provided by the Library. 26 | 27 | A "Combined Work" is a work produced by combining or linking an 28 | Application with the Library. The particular version of the Library 29 | with which the Combined Work was made is also called the "Linked 30 | Version". 31 | 32 | The "Minimal Corresponding Source" for a Combined Work means the 33 | Corresponding Source for the Combined Work, excluding any source code 34 | for portions of the Combined Work that, considered in isolation, are 35 | based on the Application, and not on the Linked Version. 36 | 37 | The "Corresponding Application Code" for a Combined Work means the 38 | object code and/or source code for the Application, including any data 39 | and utility programs needed for reproducing the Combined Work from the 40 | Application, but excluding the System Libraries of the Combined Work. 41 | 42 | 1. Exception to Section 3 of the GNU GPL. 43 | 44 | You may convey a covered work under sections 3 and 4 of this License 45 | without being bound by section 3 of the GNU GPL. 46 | 47 | 2. Conveying Modified Versions. 48 | 49 | If you modify a copy of the Library, and, in your modifications, a 50 | facility refers to a function or data to be supplied by an Application 51 | that uses the facility (other than as an argument passed when the 52 | facility is invoked), then you may convey a copy of the modified 53 | version: 54 | 55 | a) under this License, provided that you make a good faith effort to 56 | ensure that, in the event an Application does not supply the 57 | function or data, the facility still operates, and performs 58 | whatever part of its purpose remains meaningful, or 59 | 60 | b) under the GNU GPL, with none of the additional permissions of 61 | this License applicable to that copy. 62 | 63 | 3. Object Code Incorporating Material from Library Header Files. 64 | 65 | The object code form of an Application may incorporate material from 66 | a header file that is part of the Library. You may convey such object 67 | code under terms of your choice, provided that, if the incorporated 68 | material is not limited to numerical parameters, data structure 69 | layouts and accessors, or small macros, inline functions and templates 70 | (ten or fewer lines in length), you do both of the following: 71 | 72 | a) Give prominent notice with each copy of the object code that the 73 | Library is used in it and that the Library and its use are 74 | covered by this License. 75 | 76 | b) Accompany the object code with a copy of the GNU GPL and this license 77 | document. 78 | 79 | 4. Combined Works. 80 | 81 | You may convey a Combined Work under terms of your choice that, 82 | taken together, effectively do not restrict modification of the 83 | portions of the Library contained in the Combined Work and reverse 84 | engineering for debugging such modifications, if you also do each of 85 | the following: 86 | 87 | a) Give prominent notice with each copy of the Combined Work that 88 | the Library is used in it and that the Library and its use are 89 | covered by this License. 90 | 91 | b) Accompany the Combined Work with a copy of the GNU GPL and this license 92 | document. 93 | 94 | c) For a Combined Work that displays copyright notices during 95 | execution, include the copyright notice for the Library among 96 | these notices, as well as a reference directing the user to the 97 | copies of the GNU GPL and this license document. 98 | 99 | d) Do one of the following: 100 | 101 | 0) Convey the Minimal Corresponding Source under the terms of this 102 | License, and the Corresponding Application Code in a form 103 | suitable for, and under terms that permit, the user to 104 | recombine or relink the Application with a modified version of 105 | the Linked Version to produce a modified Combined Work, in the 106 | manner specified by section 6 of the GNU GPL for conveying 107 | Corresponding Source. 108 | 109 | 1) Use a suitable shared library mechanism for linking with the 110 | Library. A suitable mechanism is one that (a) uses at run time 111 | a copy of the Library already present on the user's computer 112 | system, and (b) will operate properly with a modified version 113 | of the Library that is interface-compatible with the Linked 114 | Version. 115 | 116 | e) Provide Installation Information, but only if you would otherwise 117 | be required to provide such information under section 6 of the 118 | GNU GPL, and only to the extent that such information is 119 | necessary to install and execute a modified version of the 120 | Combined Work produced by recombining or relinking the 121 | Application with a modified version of the Linked Version. (If 122 | you use option 4d0, the Installation Information must accompany 123 | the Minimal Corresponding Source and Corresponding Application 124 | Code. If you use option 4d1, you must provide the Installation 125 | Information in the manner specified by section 6 of the GNU GPL 126 | for conveying Corresponding Source.) 127 | 128 | 5. Combined Libraries. 129 | 130 | You may place library facilities that are a work based on the 131 | Library side by side in a single library together with other library 132 | facilities that are not Applications and are not covered by this 133 | License, and convey such a combined library under terms of your 134 | choice, if you do both of the following: 135 | 136 | a) Accompany the combined library with a copy of the same work based 137 | on the Library, uncombined with any other library facilities, 138 | conveyed under the terms of this License. 139 | 140 | b) Give prominent notice with the combined library that part of it 141 | is a work based on the Library, and explaining where to find the 142 | accompanying uncombined form of the same work. 143 | 144 | 6. Revised Versions of the GNU Lesser General Public License. 145 | 146 | The Free Software Foundation may publish revised and/or new versions 147 | of the GNU Lesser General Public License from time to time. Such new 148 | versions will be similar in spirit to the present version, but may 149 | differ in detail to address new problems or concerns. 150 | 151 | Each version is given a distinguishing version number. If the 152 | Library as you received it specifies that a certain numbered version 153 | of the GNU Lesser General Public License "or any later version" 154 | applies to it, you have the option of following the terms and 155 | conditions either of that published version or of any later version 156 | published by the Free Software Foundation. If the Library as you 157 | received it does not specify a version number of the GNU Lesser 158 | General Public License, you may choose any version of the GNU Lesser 159 | General Public License ever published by the Free Software Foundation. 160 | 161 | If the Library as you received it specifies that a proxy can decide 162 | whether future versions of the GNU Lesser General Public License shall 163 | apply, that proxy's public statement of acceptance of any version is 164 | permanent authorization for you to choose that version for the 165 | Library. -------------------------------------------------------------------------------- /TNTableViewLazyDataSource.j: -------------------------------------------------------------------------------- 1 | /* 2 | * TNTableViewLazyDataSource.j 3 | * 4 | * Copyright (C) 2010 Antoine Mercadal 5 | * This program is free software: you can redistribute it and/or modify 6 | * it under the terms of the GNU Affero General Public License as 7 | * published by the Free Software Foundation, either version 3 of the 8 | * License, or (at your option) any later version. 9 | * 10 | * This program is distributed in the hope that it will be useful, 11 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 | * GNU Affero General Public License for more details. 14 | * 15 | * You should have received a copy of the GNU Affero General Public License 16 | * along with this program. If not, see . 17 | */ 18 | 19 | @import 20 | 21 | @import 22 | @import 23 | 24 | 25 | /*! @ingroup tnkit 26 | Simple table view datasource managing lazy loading with filtering support 27 | Must have a delegate reponsible for fetching data, and the delegate must implement 28 |
- (void)tableViewDataSourceNeedsLoading:
29 |
- (void)tableViewDataSource:applyFilter:
30 |
- (void)tableViewDataSource:removeFilter:
31 | The delegate is reponsible for adding/removing content into the datasource 32 | */ 33 | @implementation TNTableViewLazyDataSource: CPObject 34 | { 35 | BOOL _currentlyLoading @accessors(getter=isCurrentlyLoading, setter=setCurrentlyLoading:); 36 | CPArray _content @accessors(property=content); 37 | CPArray _searchableKeyPaths @accessors(property=searchableKeyPaths); 38 | CPTableView _table @accessors(property=table); 39 | id _delegate @accessors(property=delegate); 40 | int _lazyLoadingTrigger @accessors(property=lazyLoadingTrigger); 41 | int _totalCount @accessors(property=totalCount); 42 | 43 | CPSearchField _searchField; 44 | CPString _filter; 45 | } 46 | 47 | #pragma mark - 48 | #pragma mark Initialization 49 | 50 | - (id)init 51 | { 52 | if (self = [super init]) 53 | { 54 | _searchableKeyPaths = [CPArray array]; 55 | _content = [CPArray array]; 56 | _lazyLoadingTrigger = 10; 57 | _currentlyLoading = NO; 58 | _totalCount = -1; 59 | } 60 | return self; 61 | } 62 | 63 | 64 | #pragma mark - 65 | #pragma mark Filtering 66 | 67 | /*! this action should be bound to a CPSearchField 68 | it will filter the content of the datasource according to the sender value 69 | @param sender the sender of the action 70 | */ 71 | - (@action)filterObjects:(id)sender 72 | { 73 | if (!_searchField) 74 | _searchField = sender; 75 | 76 | _filter = [[sender stringValue] uppercaseString]; 77 | 78 | if (!(_filter) || (_filter == @"")) 79 | { 80 | if (!_currentlyLoading && _delegate && [_delegate respondsToSelector:@selector(tableViewDataSource:removeFilter:)]) 81 | { 82 | _currentlyLoading = YES; 83 | [_delegate tableViewDataSource:self removeFilter:_filter]; 84 | } 85 | return; 86 | } 87 | 88 | if (!_currentlyLoading && _delegate && [_delegate respondsToSelector:@selector(tableViewDataSource:applyFilter:)]) 89 | { 90 | _currentlyLoading = YES; 91 | [_delegate tableViewDataSource:self applyFilter:_filter]; 92 | } 93 | } 94 | 95 | 96 | #pragma mark - 97 | #pragma mark Content management 98 | 99 | /*! set the given array of object as the content of the datasource 100 | @param aContent CPArray containing the datas of the datasource 101 | */ 102 | - (void)setContent:(CPArray)aContent 103 | { 104 | _content = aContent; 105 | } 106 | 107 | /*! add an object to the datasource 108 | @param anObject object to add 109 | */ 110 | - (void)addObject:(id)anObject 111 | { 112 | [_content addObject:anObject]; 113 | } 114 | 115 | /*! Chek if contents contains given object 116 | @param anObject the object to search 117 | @return YES if anObject is in the contents 118 | */ 119 | - (void)containsObject:(id)anObject 120 | { 121 | return [_content containsObject:anObject]; 122 | } 123 | 124 | /*! insert an object at a given index in the datasource 125 | @param anObject object to add 126 | @param anObject int representing the position 127 | */ 128 | - (void)insertObject:(id)anObject atIndex:(int)anIndex 129 | { 130 | [_content insertObject:anObject atIndex:anIndex]; 131 | } 132 | 133 | /*! return the object at given index 134 | @param anObject int representing the position 135 | @return the object 136 | */ 137 | - (void)objectAtIndex:(int)index 138 | { 139 | return _content[index]; 140 | } 141 | 142 | /*! return the objects at given indexes contained in a CPIndexSet 143 | @param anObject CPIndexSet representing the positions 144 | @return the objects 145 | */ 146 | - (CPArray)objectsAtIndexes:(CPIndexSet)aSet 147 | { 148 | return [_content objectsAtIndexes:aSet]; 149 | } 150 | 151 | /*! removes the object at given index 152 | @param anObject int representing the position 153 | */ 154 | - (void)removeObjectAtIndex:(int)index 155 | { 156 | [_content removeObjectAtIndex:index]; 157 | } 158 | 159 | /*! removes the object at given indexes contained in a CPIndexSet 160 | @param anObject CPIndexSet representing the positions 161 | */ 162 | - (void)removeObjectsAtIndexes:(CPIndexSet)aSet 163 | { 164 | [_content removeObjectsAtIndexes:aSet]; 165 | } 166 | 167 | /*! remove the given object from the array 168 | @param anObject the object to remove 169 | */ 170 | - (void)removeObject:(id)anObject 171 | { 172 | [_content removeObject:anObject]; 173 | } 174 | 175 | /*! remove all objects 176 | */ 177 | - (void)removeAllObjects 178 | { 179 | [_content removeAllObjects]; 180 | } 181 | 182 | /*! remove last object 183 | */ 184 | - (void)removeLastObject 185 | { 186 | [_content removeLastObject]; 187 | } 188 | 189 | /*! remove first object 190 | */ 191 | - (void)removeFirstObject 192 | { 193 | [_content removeFirstObject]; 194 | } 195 | 196 | /*! return the index of the given object 197 | @param anObject the object 198 | @return the index of the given object 199 | */ 200 | - (int)indexOfObject:(id)anObject 201 | { 202 | return [_content indexOfObject:anObject]; 203 | } 204 | 205 | /*! returns the number of object in the datasource 206 | If a filter is applied, it will return the number of objects 207 | matching the filyter , not the total 208 | @return the number of object 209 | */ 210 | - (int)count 211 | { 212 | return [_content count]; 213 | } 214 | 215 | 216 | #pragma mark - 217 | #pragma mark Datasource implementation 218 | 219 | - (CPNumber)numberOfRowsInTableView:(CPTableView)aTable 220 | { 221 | return [_content count]; 222 | } 223 | 224 | - (id)tableView:(CPTableView)aTable objectValueForTableColumn:(CPNumber)aCol row:(CPNumber)aRow 225 | { 226 | var identifier = [aCol identifier]; 227 | 228 | if (!_currentlyLoading 229 | && (_filter == @"" || !_filter) 230 | && [_content count] < _totalCount 231 | && (aRow + _lazyLoadingTrigger >= [_content count]) 232 | && _delegate 233 | && [_delegate respondsToSelector:@selector(tableViewDataSourceNeedsLoading:)]) 234 | { 235 | _currentlyLoading = YES; 236 | [_delegate tableViewDataSourceNeedsLoading:self]; 237 | } 238 | 239 | return [_content[aRow] valueForKeyPath:identifier]; 240 | } 241 | 242 | - (void)tableView:(CPTableView)aTableView sortDescriptorsDidChange:(CPArray)oldDescriptors 243 | { 244 | var indexes = [aTableView selectedRowIndexes], 245 | selectedObjects = [_content objectsAtIndexes:indexes], 246 | indexesToSelect = [[CPIndexSet alloc] init]; 247 | 248 | [_content sortUsingDescriptors:[aTableView sortDescriptors]]; 249 | 250 | for (var i = 0, c = [selectedObjects count]; i < c; i++) 251 | { 252 | var object = selectedObjects[i]; 253 | [indexesToSelect addIndex:[_content indexOfObject:object]]; 254 | } 255 | 256 | [_table selectRowIndexes:indexesToSelect byExtendingSelection:NO]; 257 | } 258 | 259 | - (void)tableView:(CPTableView)aTableView setObjectValue:(id)aValue forTableColumn:(CPTableColumn)aCol row:(int)aRow 260 | { 261 | var identifier = [aCol identifier]; 262 | 263 | [_content[aRow] setValue:aValue forKeyPath:identifier]; 264 | } 265 | 266 | @end 267 | -------------------------------------------------------------------------------- /TNToolbar.j: -------------------------------------------------------------------------------- 1 | /* 2 | * TNToolbar.j 3 | * 4 | * Copyright (C) 2010 Antoine Mercadal 5 | * 6 | * This library is free software; you can redistribute it and/or 7 | * modify it under the terms of the GNU Lesser General Public 8 | * License as published by the Free Software Foundation; either 9 | * version 3.0 of the License, or (at your option) any later version. 10 | * 11 | * This library is distributed in the hope that it will be useful, 12 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 13 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 14 | * Lesser General Public License for more details. 15 | * 16 | * You should have received a copy of the GNU Lesser General Public 17 | * License along with this library; if not, write to the Free Software 18 | * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA 19 | */ 20 | 21 | @import 22 | 23 | @import 24 | @import 25 | @import 26 | @import 27 | @import 28 | 29 | 30 | var TNToolbarSelectedBgImage, 31 | TNToolbarSelectedBgImageHUD; 32 | 33 | /*! @ingroup tnkit 34 | subclass of CPToolbar that allow dynamic insertion and item selection 35 | */ 36 | @implementation TNToolbar : CPToolbar 37 | { 38 | CPArray _customSubViews @accessors(property=customSubViews); 39 | CPToolbarItem _selectedToolbarItem @accessors(getter=selectedToolbarItem); 40 | 41 | BOOL _iconSelected; 42 | CPArray _sortedToolbarItems; 43 | CPDictionary _toolbarItems; 44 | CPDictionary _toolbarItemsOrder; 45 | CPImageView _imageViewSelection; 46 | 47 | BOOL _isHUD; 48 | } 49 | 50 | 51 | #pragma mark - 52 | #pragma mark Initialization 53 | 54 | + (void)initialize 55 | { 56 | var bundle = [CPBundle bundleForClass:TNToolbar]; 57 | 58 | TNToolbarSelectedBgImage = [[CPThreePartImage alloc] initWithImageSlices:[ 59 | [[CPImage alloc] initWithContentsOfFile:[bundle pathForResource:@"TNToolbar/toolbar-item-selected-left.png"] size:CGSizeMake(3.0, 60.0)], 60 | [[CPImage alloc] initWithContentsOfFile:[bundle pathForResource:@"TNToolbar/toolbar-item-selected-center.png"] size:CGSizeMake(1.0, 60.0)], 61 | [[CPImage alloc] initWithContentsOfFile:[bundle pathForResource:@"TNToolbar/toolbar-item-selected-right.png"] size:CGSizeMake(3.0, 60.0)] 62 | ] isVertical:NO]; 63 | 64 | TNToolbarSelectedBgImageHUD = [[CPThreePartImage alloc] initWithImageSlices:[ 65 | [[CPImage alloc] initWithContentsOfFile:[bundle pathForResource:@"TNToolbar/toolbar-hud-item-selected-left.png"] size:CGSizeMake(1.0, 60.0)], 66 | [[CPImage alloc] initWithContentsOfFile:[bundle pathForResource:@"TNToolbar/toolbar-hud-item-selected-center.png"] size:CGSizeMake(1.0, 60.0)], 67 | [[CPImage alloc] initWithContentsOfFile:[bundle pathForResource:@"TNToolbar/toolbar-hud-item-selected-right.png"] size:CGSizeMake(1.0, 60.0)] 68 | ] isVertical:NO]; 69 | } 70 | 71 | /*! initialize the class with a target 72 | @param aTarget the target 73 | @return a initialized instance of TNToolbar 74 | */ 75 | - (id)init 76 | { 77 | if (self = [super init]) 78 | { 79 | _toolbarItems = [CPDictionary dictionary]; 80 | _toolbarItemsOrder = [CPDictionary dictionary]; 81 | _imageViewSelection = [[CPImageView alloc] initWithFrame:CGRectMake(0.0, 0.0, 60.0, 60.0)]; 82 | _iconSelected = NO; 83 | _customSubViews = [CPArray array]; 84 | 85 | [_imageViewSelection setBackgroundColor:[CPColor colorWithPatternImage:TNToolbarSelectedBgImage]]; 86 | 87 | [self setDelegate:self]; 88 | } 89 | 90 | return self; 91 | } 92 | 93 | /*! initialize the class with HUD Style 94 | @return a initialized instance of TNToolbar 95 | */ 96 | - (id)initWithHUDStyle 97 | { 98 | if (self = [self init]) 99 | { 100 | var bundle = [CPBundle bundleForClass:[self class]]; 101 | 102 | _isHUD = YES; 103 | 104 | [[self _toolbarView] setBackgroundColor: 105 | [CPColor colorWithPatternImage: 106 | [[CPImage alloc] initWithContentsOfFile:[bundle pathForResource:@"TNToolbar/toolbar-hud-background.png"] size:CGSizeMake(1.0, 59.0)]]]; 107 | 108 | [_imageViewSelection setBackgroundColor:[CPColor colorWithPatternImage:TNToolbarSelectedBgImageHUD]]; 109 | } 110 | 111 | return self; 112 | } 113 | 114 | #pragma mark - 115 | #pragma mark Accesors 116 | 117 | /*! return the actual view of the CPToolBar 118 | Usefull for hacks. 119 | */ 120 | - (CPView)toolbarView 121 | { 122 | return _toolbarView; 123 | } 124 | 125 | #pragma mark - 126 | #pragma mark Content management 127 | 128 | /*! add given item with the given indentifier 129 | @param anItem the ToolbarItem to add 130 | @param anIdentifier the identifer to use for the item 131 | */ 132 | - (void)addItem:(CPToolbarItem)anItem withIdentifier:(CPString)anIdentifier 133 | { 134 | [_toolbarItems setObject:anItem forKey:anIdentifier]; 135 | } 136 | 137 | /*! add a new CPToolbarItem with a custom view 138 | @param anIdentifier CPString containing the identifier 139 | @param aLabel CPString containing the label 140 | @param anImage CPImage containing the icon of the item 141 | @param aTarget an object that will be the target of the item 142 | @param anAction a selector of the aTarget to perform on click 143 | @param toolTip the toolTip 144 | */ 145 | - (CPToolbarItem)addItemWithIdentifier:(CPString)anIdentifier label:(CPString)aLabel view:(CPView)aView target:(id)aTarget action:(SEL)anAction toolTip:(CPString)aToolTip 146 | { 147 | var newItem = [[CPToolbarItem alloc] initWithItemIdentifier:anIdentifier]; 148 | 149 | [newItem setLabel:aLabel || @""]; 150 | [newItem setView:aView]; 151 | [newItem setTarget:aTarget]; 152 | [newItem setAction:anAction]; 153 | [newItem setToolTip:aToolTip]; 154 | 155 | [_toolbarItems setObject:newItem forKey:anIdentifier]; 156 | 157 | return newItem; 158 | } 159 | 160 | /*! add a new CPToolbarItem with a custom view 161 | @param anIdentifier CPString containing the identifier 162 | @param aLabel CPString containing the label 163 | @param anImage CPImage containing the icon of the item 164 | @param aTarget an object that will be the target of the item 165 | @param anAction a selector of the aTarget to perform on click 166 | */ 167 | - (CPToolbarItem)addItemWithIdentifier:(CPString)anIdentifier label:(CPString)aLabel view:(CPView)aView target:(id)aTarget action:(SEL)anAction 168 | { 169 | return [self addItemWithIdentifier:anIdentifier label:aLabel view:aView target:aTarget action:anAction toolTip:nil]; 170 | } 171 | 172 | 173 | /*! add a new CPToolbarItem 174 | @param anIdentifier CPString containing the identifier 175 | @param aLabel CPString containing the label 176 | @param anImage CPImage containing the icon of the item 177 | @param anotherImage CPImage containing the alternative icon of the item 178 | @param aTarget an object that will be the target of the item 179 | @param anAction a selector of the aTarget to perform on click 180 | @param toolTip the toolTip 181 | */ 182 | - (CPToolbarItem)addItemWithIdentifier:(CPString)anIdentifier label:(CPString)aLabel icon:(CPImage)anImage altIcon:(CPImage)anotherImage target:(id)aTarget action:(SEL)anAction toolTip:(CPString)aToolTip 183 | { 184 | var newItem = [[CPToolbarItem alloc] initWithItemIdentifier:anIdentifier]; 185 | 186 | [newItem setLabel:aLabel || @""]; 187 | [newItem setImage:anImage]; 188 | if (anotherImage) 189 | [newItem setAlternateImage:anotherImage]; 190 | [newItem setTarget:aTarget]; 191 | [newItem setAction:anAction]; 192 | [newItem setToolTip:aToolTip]; 193 | 194 | [_toolbarItems setObject:newItem forKey:anIdentifier]; 195 | 196 | return newItem; 197 | } 198 | 199 | /*! add a new CPToolbarItem 200 | @param anIdentifier CPString containing the identifier 201 | @param aLabel CPString containing the label 202 | @param anImage CPImage containing the icon of the item 203 | @param aTarget an object that will be the target of the item 204 | @param anAction a selector of the aTarget to perform on click 205 | @param toolTip the toolTip 206 | */ 207 | - (CPToolbarItem)addItemWithIdentifier:(CPString)anIdentifier label:(CPString)aLabel icon:(CPImage)anImage target:(id)aTarget action:(SEL)anAction toolTip:(CPString)aToolTip 208 | { 209 | return [self addItemWithIdentifier:anIdentifier label:aLabel icon:anImage altIcon:nil target:aTarget action:anAction toolTip:nil]; 210 | } 211 | 212 | /*! add a new CPToolbarItem 213 | @param anIdentifier CPString containing the identifier 214 | @param aLabel CPString containing the label 215 | @param anImage CPImage containing the icon of the item 216 | @param aTarget an object that will be the target of the item 217 | @param anAction a selector of the aTarget to perform on click 218 | */ 219 | - (CPToolbarItem)addItemWithIdentifier:(CPString)anIdentifier label:(CPString)aLabel icon:(CPImage)anImage target:(id)aTarget action:(SEL)anAction 220 | { 221 | return [self addItemWithIdentifier:anIdentifier label:aLabel icon:anImage target:aTarget action:anAction toolTip:nil]; 222 | } 223 | 224 | - (void)removeItemWithIdentifier:(CPString)anIdentifier 225 | { 226 | [_toolbarItems removeObjectForKey:anIdentifier]; 227 | 228 | var keys = [_toolbarItemsOrder allKeysForObject:anIdentifier]; 229 | for (var i = [keys count] - 1; i >= 0; i--) 230 | [_toolbarItemsOrder removeObjectForKey:keys[i]]; 231 | } 232 | 233 | 234 | /*! define the position of a given existing CPToolbarItem according to its identifier 235 | @param anIndentifier CPString containing the identifier 236 | */ 237 | - (void)setPosition:(CPNumber)aPosition forToolbarItemIdentifier:(CPString)anIndentifier 238 | { 239 | [_toolbarItemsOrder setObject:anIndentifier forKey:aPosition]; 240 | } 241 | 242 | /*! @ignore 243 | */ 244 | - (void)_reloadToolbarItems 245 | { 246 | var sortFunction = function(a, b, context){ 247 | var indexA = a, 248 | indexB = b; 249 | if (a < b) 250 | return CPOrderedAscending; 251 | else if (a > b) 252 | return CPOrderedDescending; 253 | else 254 | return CPOrderedSame; 255 | }, 256 | sortedKeys = [[_toolbarItemsOrder allKeys] sortedArrayUsingFunction:sortFunction]; 257 | 258 | _sortedToolbarItems = [CPArray array]; 259 | 260 | for (var i = 0, c = [sortedKeys count]; i < c; i++) 261 | { 262 | var key = sortedKeys[i]; 263 | [_sortedToolbarItems addObject:[_toolbarItemsOrder objectForKey:key]]; 264 | } 265 | 266 | [super _reloadToolbarItems]; 267 | 268 | if (_iconSelected) 269 | [_toolbarView addSubview:_imageViewSelection positioned:CPWindowBelow relativeTo:nil]; 270 | 271 | for (var i = 0, c = [_customSubViews count]; i < c; i++) 272 | [_toolbarView addSubview:_customSubViews[i]]; 273 | 274 | if (_isHUD) 275 | { 276 | var items = [self items], 277 | count = [items count]; 278 | 279 | while (count--) 280 | [[_toolbarView viewForItem:items[count]] FIXME_setIsHUD:YES]; 281 | } 282 | } 283 | 284 | /*! reloads all the items in the toolbar 285 | */ 286 | - (void)reloadToolbarItems 287 | { 288 | [self _reloadToolbarItems]; 289 | } 290 | 291 | 292 | #pragma mark - 293 | #pragma mark Item selection 294 | 295 | /*! make the item identified by the given identifier selected 296 | @param aToolbarItem the toolbaritem you want to select 297 | */ 298 | - (void)selectToolbarItem:(CPToolbarItem)aToolbarItem 299 | { 300 | var toolbarItemView, 301 | subviews = [_toolbarView subviews]; 302 | 303 | for (var i = 0, c = [subviews count]; i < c; i++) 304 | { 305 | toolbarItemView = subviews[i]; 306 | 307 | if ([toolbarItemView._toolbarItem itemIdentifier] === [aToolbarItem itemIdentifier]) 308 | break; 309 | } 310 | var frame = [toolbarItemView convertRect:[toolbarItemView bounds] toView:_toolbarView], 311 | labelFrame = [aToolbarItem label] ? [[aToolbarItem label] sizeWithFont:[CPFont boldSystemFontOfSize:12]] : [aToolbarItem minSize]; 312 | _iconSelected = YES; 313 | 314 | [_imageViewSelection setFrameSize:CGSizeMake(MAX(labelFrame.width + 4, 50.0), 60.0)]; 315 | [_imageViewSelection setFrameOrigin:CGPointMake(CGRectGetMinX(frame) + (CGRectGetWidth(frame) - CGRectGetWidth([_imageViewSelection frame])) / 2.0, 0.0)]; 316 | 317 | [_toolbarView addSubview:_imageViewSelection positioned:CPWindowBelow relativeTo:nil]; 318 | 319 | _selectedToolbarItem = aToolbarItem; 320 | } 321 | 322 | /*! deselect current selected item 323 | */ 324 | - (void)deselectToolbarItem 325 | { 326 | _selectedToolbarItem = nil; 327 | _iconSelected = NO; 328 | [_imageViewSelection removeFromSuperview]; 329 | } 330 | 331 | /*! get the toolbar item with the given identifier 332 | */ 333 | - (CPToolbarItem)itemWithIdentifier:(id)anIdentifier 334 | { 335 | // for (var i = 0; i < [[self visibleItems] count]; i++) 336 | // { 337 | // if ([[self visibleItems][i] itemIdentifier] == anIdentifier) 338 | // return [self visibleItems][i]; 339 | // } 340 | 341 | return [_toolbarItems objectForKey:anIdentifier]; 342 | } 343 | 344 | #pragma mark - 345 | #pragma mark CPToolbar DataSource implementation 346 | 347 | /*! CPToolbar Protocol 348 | */ 349 | - (CPArray)toolbarAllowedItemIdentifiers:(CPToolbar)aToolbar 350 | { 351 | return _sortedToolbarItems; 352 | } 353 | 354 | /*! CPToolbar Protocol 355 | */ 356 | - (CPArray)toolbarDefaultItemIdentifiers:(CPToolbar)aToolbar 357 | { 358 | return _sortedToolbarItems; 359 | } 360 | 361 | /*! CPToolbar Protocol 362 | */ 363 | - (CPToolbarItem)toolbar:(CPToolbar)aToolbar itemForItemIdentifier:(CPString)anItemIdentifier willBeInsertedIntoToolbar:(BOOL)aFlag 364 | { 365 | var toolbarItem = [[CPToolbarItem alloc] initWithItemIdentifier:anItemIdentifier]; 366 | 367 | return ([_toolbarItems objectForKey:anItemIdentifier]) ? [_toolbarItems objectForKey:anItemIdentifier] : toolbarItem; 368 | } 369 | 370 | 371 | @end 372 | -------------------------------------------------------------------------------- /TNUIKitScrollView.j: -------------------------------------------------------------------------------- 1 | // /* 2 | // * TNUIKitScrollView.j 3 | // * 4 | // * Copyright (C) 2010 Antoine Mercadal 5 | // * This program is free software: you can redistribute it and/or modify 6 | // * it under the terms of the GNU Affero General Public License as 7 | // * published by the Free Software Foundation, either version 3 of the 8 | // * License, or (at your option) any later version. 9 | // * 10 | // * This program is distributed in the hope that it will be useful, 11 | // * but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | // * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 | // * GNU Affero General Public License for more details. 14 | // * 15 | // * You should have received a copy of the GNU Affero General Public License 16 | // * along with this program. If not, see . 17 | // */ 18 | // 19 | @import 20 | 21 | @import 22 | @import 23 | 24 | @implementation TNUIKitScrollView : CPScroller 25 | 26 | - (id)initWithFrame:(CGRect)aRect 27 | { 28 | if (self = [super initWithFrame:aRect]) 29 | { 30 | CPLog.warn("Deprecated: TNUIKitScrollView is deprecated!") 31 | } 32 | 33 | return self; 34 | } 35 | @end 36 | 37 | 38 | 39 | // This is 40 | // /*! @ingroup TNKit 41 | // an iPhone/Mac OS X Lion like scrollers 42 | // */ 43 | // @implementation TNUIKitScroller : CPScroller 44 | // { 45 | // CPViewAnimation _animationScroller; 46 | // CPDictionary _paramAnimFadeOut; 47 | // } 48 | // 49 | // + (float)scrollerWidth 50 | // { 51 | // return 10.0; 52 | // } 53 | // 54 | // - (id)initWithFrame:(CGRect)aFrame 55 | // { 56 | // if (self = [super initWithFrame:aFrame]) 57 | // { 58 | // [self setValue:[CPNull null] forThemeAttribute:@"decrement-line-color"]; 59 | // [self setValue:[CPNull null] forThemeAttribute:@"increment-line-color"]; 60 | // [self setValue:[CPNull null] forThemeAttribute:@"knob-slot-color"]; 61 | // [self setValue:CGSizeMake(10, 0) forThemeAttribute:@"increment-line-size"]; 62 | // [self setValue:CGSizeMake(10, 0) forThemeAttribute:@"decrement-line-size"]; 63 | // [self setValue:10.0 forThemeAttribute:@"scroller-width"]; 64 | // 65 | // if ([self isVertical]) 66 | // { 67 | // [self setValue:CGInsetMake(10, 2, 10, 1) forThemeAttribute:@"track-inset"]; 68 | // [self setValue:CGInsetMake(2, 2, 2, 0) forThemeAttribute:@"knob-inset"]; 69 | // [self setValue:TNKnobColorVertical forThemeAttribute:@"knob-color"]; 70 | // } 71 | // else 72 | // { 73 | // [self setValue:CGInsetMake(0.0, 0.0, 2.0, 0.0) forThemeAttribute:@"track-inset"]; 74 | // [self setValue:CGInsetMake(0, 2, 2, 2) forThemeAttribute:@"knob-inset"]; 75 | // [self setValue:TNKnobColorHorizontal forThemeAttribute:@"knob-color"]; 76 | // } 77 | // 78 | // _paramAnimFadeOut = [CPDictionary dictionaryWithObjects:[self, CPViewAnimationFadeOutEffect] 79 | // forKeys:[CPViewAnimationTargetKey, CPViewAnimationEffectKey]]; 80 | // 81 | // _animationScroller = [[CPViewAnimation alloc] initWithDuration:0.2 animationCurve:CPAnimationEaseInOut]; 82 | // 83 | // [_animationScroller setViewAnimations:[_paramAnimFadeOut]]; 84 | // [_animationScroller setDelegate:self]; 85 | // [self setAlphaValue:0.0]; 86 | // } 87 | // 88 | // return self; 89 | // } 90 | // 91 | // - (void)mouseEntered:(CPEvent)anEvent 92 | // { 93 | // [self fadeIn]; 94 | // } 95 | // 96 | // - (void)mouseExited:(CPEvent)anEvent 97 | // { 98 | // [self fadeOut]; 99 | // } 100 | // 101 | // - (void)fadeIn 102 | // { 103 | // [self setAlphaValue:1.0]; 104 | // } 105 | // 106 | // - (void)fadeOut 107 | // { 108 | // [_animationScroller startAnimation]; 109 | // } 110 | // 111 | // - (void)animationDidEnd:(CPAnimation)animation 112 | // { 113 | // [self setHidden:NO]; 114 | // } 115 | // 116 | // @end 117 | // 118 | // /*! @ingroup TNKit 119 | // an iPhone/Mac OS X Lion like scrollview 120 | // */ 121 | // @implementation TNUIKitScrollView : CPScrollView 122 | // { 123 | // CPTimer _timerScrollersHide; 124 | // } 125 | // 126 | // - (void)scrollWheel:(CPEvent)anEvent 127 | // { 128 | // if (_timerScrollersHide) 129 | // [_timerScrollersHide invalidate]; 130 | // if (![_verticalScroller isHidden]) 131 | // [_verticalScroller fadeIn]; 132 | // if (![_horizontalScroller isHidden]) 133 | // [_horizontalScroller fadeIn]; 134 | // if (![_horizontalScroller isHidden] || ![_verticalScroller isHidden]) 135 | // _timerScrollersHide = [CPTimer scheduledTimerWithTimeInterval:1.2 target:self selector:@selector(_hideScrollers:) userInfo:nil repeats:NO]; 136 | // 137 | // [super scrollWheel:anEvent]; 138 | // } 139 | // 140 | // - (void)_hideScrollers:(CPTimer)theTimer 141 | // { 142 | // [_verticalScroller fadeOut]; 143 | // [_horizontalScroller fadeOut]; 144 | // _timerScrollersHide = nil; 145 | // } 146 | // 147 | // - (void)setHasHorizontalScroller:(BOOL)shouldHaveHorizontalScroller 148 | // { 149 | // if (_hasHorizontalScroller === shouldHaveHorizontalScroller) 150 | // return; 151 | // 152 | // _hasHorizontalScroller = shouldHaveHorizontalScroller; 153 | // 154 | // if (_hasHorizontalScroller && !_horizontalScroller) 155 | // { 156 | // var bounds = [self _insetBounds]; 157 | // 158 | // [self setHorizontalScroller:[[TNUIKitScroller alloc] initWithFrame:CGRectMake(0.0, 0.0, MAX(CGRectGetWidth(bounds), [TNUIKitScroller scrollerWidth] + 1), [TNUIKitScroller scrollerWidth])]]; 159 | // [[self horizontalScroller] setFrameSize:CGSizeMake(CGRectGetWidth(bounds), [TNUIKitScroller scrollerWidth])]; 160 | // } 161 | // 162 | // [self reflectScrolledClipView:_contentView]; 163 | // } 164 | // 165 | // - (void)setHasVerticalScroller:(BOOL)shouldHaveVerticalScroller 166 | // { 167 | // if (_hasVerticalScroller === shouldHaveVerticalScroller) 168 | // return; 169 | // 170 | // _hasVerticalScroller = shouldHaveVerticalScroller; 171 | // 172 | // if (_hasVerticalScroller && !_verticalScroller) 173 | // { 174 | // var bounds = [self _insetBounds]; 175 | // 176 | // [self setVerticalScroller:[[TNUIKitScroller alloc] initWithFrame:CGRectMake(0.0, 0.0, [TNUIKitScroller scrollerWidth], MAX(CGRectGetHeight(bounds), [TNUIKitScroller scrollerWidth] + 1))]]; 177 | // [[self verticalScroller] setFrameSize:CGSizeMake([TNUIKitScroller scrollerWidth], CGRectGetHeight(bounds))]; 178 | // } 179 | // 180 | // [self reflectScrolledClipView:_contentView]; 181 | // } 182 | // 183 | // - (void)reflectScrolledClipView:(CPClipView)aClipView 184 | // { 185 | // if (_contentView !== aClipView) 186 | // return; 187 | // 188 | // if (_recursionCount > 5) 189 | // return; 190 | // 191 | // ++_recursionCount; 192 | // 193 | // var documentView = [self documentView]; 194 | // 195 | // if (!documentView) 196 | // { 197 | // if (_autohidesScrollers) 198 | // { 199 | // [_verticalScroller setHidden:YES]; 200 | // [_horizontalScroller setHidden:YES]; 201 | // } 202 | // 203 | // [_contentView setFrame:[self _insetBounds]]; 204 | // [_headerClipView setFrame:CGRectMakeZero()]; 205 | // 206 | // --_recursionCount; 207 | // 208 | // return; 209 | // } 210 | // 211 | // var documentFrame = [documentView frame], // the size of the whole document 212 | // contentFrame = [self _insetBounds], // assume it takes up the entire size of the scrollview (no scrollers) 213 | // headerClipViewFrame = [self _headerClipViewFrame], 214 | // headerClipViewHeight = CGRectGetHeight(headerClipViewFrame); 215 | // 216 | // contentFrame.origin.y += headerClipViewHeight; 217 | // contentFrame.size.height -= headerClipViewHeight; 218 | // 219 | // var difference = CGSizeMake(CGRectGetWidth(documentFrame) - CGRectGetWidth(contentFrame), CGRectGetHeight(documentFrame) - CGRectGetHeight(contentFrame)), 220 | // verticalScrollerWidth = CGRectGetWidth([_verticalScroller frame]), 221 | // horizontalScrollerHeight = CGRectGetHeight([_horizontalScroller frame]), 222 | // hasVerticalScroll = difference.height > 0.0, 223 | // hasHorizontalScroll = difference.width > 0.0, 224 | // shouldShowVerticalScroller = _hasVerticalScroller && (!_autohidesScrollers || hasVerticalScroll), 225 | // shouldShowHorizontalScroller = _hasHorizontalScroller && (!_autohidesScrollers || hasHorizontalScroll); 226 | // 227 | // //Now we have to account for the shown scrollers affecting the deltas. 228 | // if (shouldShowVerticalScroller) 229 | // { 230 | // difference.width += verticalScrollerWidth; 231 | // hasHorizontalScroll = difference.width > 0.0; 232 | // shouldShowHorizontalScroller = _hasHorizontalScroller && (!_autohidesScrollers || hasHorizontalScroll); 233 | // } 234 | // 235 | // if (shouldShowHorizontalScroller) 236 | // { 237 | // difference.height += horizontalScrollerHeight; 238 | // hasVerticalScroll = difference.height > 0.0; 239 | // shouldShowVerticalScroller = _hasVerticalScroller && (!_autohidesScrollers || hasVerticalScroll); 240 | // } 241 | // 242 | // // We now definitively know which scrollers are shown or not, as well as whether they are showing scroll values. 243 | // [_verticalScroller setHidden:!shouldShowVerticalScroller]; 244 | // [_verticalScroller setEnabled:hasVerticalScroll]; 245 | // 246 | // [_horizontalScroller setHidden:!shouldShowHorizontalScroller]; 247 | // [_horizontalScroller setEnabled:hasHorizontalScroll]; 248 | // 249 | // // We can thus appropriately account for them changing the content size. 250 | // // if (shouldShowVerticalScroller) 251 | // // contentFrame.size.width -= verticalScrollerWidth; 252 | // // 253 | // // if (shouldShowHorizontalScroller) 254 | // // contentFrame.size.height -= horizontalScrollerHeight; 255 | // 256 | // var scrollPoint = [_contentView bounds].origin, 257 | // wasShowingVerticalScroller = ![_verticalScroller isHidden], 258 | // wasShowingHorizontalScroller = ![_horizontalScroller isHidden]; 259 | // 260 | // if (shouldShowVerticalScroller) 261 | // { 262 | // var verticalScrollerY = 263 | // MAX(CGRectGetMinY(contentFrame), MAX(CGRectGetMaxY([self _cornerViewFrame]), CGRectGetMaxY(headerClipViewFrame))); 264 | // 265 | // var verticalScrollerHeight = CGRectGetMaxY(contentFrame) - verticalScrollerY; 266 | // 267 | // [_verticalScroller setFloatValue:(difference.height <= 0.0) ? 0.0 : scrollPoint.y / difference.height]; 268 | // [_verticalScroller setKnobProportion:CGRectGetHeight(contentFrame) / CGRectGetHeight(documentFrame)]; 269 | // [_verticalScroller setFrame:CGRectMake(CGRectGetMaxX(contentFrame) - 10.0, verticalScrollerY, verticalScrollerWidth, verticalScrollerHeight)]; 270 | // } 271 | // else if (wasShowingVerticalScroller) 272 | // { 273 | // [_verticalScroller setFloatValue:0.0]; 274 | // [_verticalScroller setKnobProportion:1.0]; 275 | // } 276 | // 277 | // if (shouldShowHorizontalScroller) 278 | // { 279 | // [_horizontalScroller setFloatValue:(difference.width <= 0.0) ? 0.0 : scrollPoint.x / difference.width]; 280 | // [_horizontalScroller setKnobProportion:CGRectGetWidth(contentFrame) / CGRectGetWidth(documentFrame)]; 281 | // [_horizontalScroller setFrame:CGRectMake(CGRectGetMinX(contentFrame), CGRectGetMaxY(contentFrame) - 10.0, CGRectGetWidth(contentFrame), horizontalScrollerHeight)]; 282 | // } 283 | // else if (wasShowingHorizontalScroller) 284 | // { 285 | // [_horizontalScroller setFloatValue:0.0]; 286 | // [_horizontalScroller setKnobProportion:1.0]; 287 | // } 288 | // 289 | // [_contentView setFrame:contentFrame]; 290 | // [_headerClipView setFrame:headerClipViewFrame]; 291 | // [_cornerView setFrame:[self _cornerViewFrame]]; 292 | // 293 | // [[self bottomCornerView] setFrame:[self _bottomCornerViewFrame]]; 294 | // [[self bottomCornerView] setBackgroundColor:[self currentValueForThemeAttribute:@"bottom-corner-color"]]; 295 | // 296 | // --_recursionCount; 297 | // } 298 | // 299 | // @end 300 | // 301 | // /*! exctracted from Cappuccino's CPTheme because this rocks 302 | // */ 303 | // function PatternColor() 304 | // { 305 | // if (arguments.length < 3) 306 | // { 307 | // var slices = arguments[0], 308 | // imageSlices = [], 309 | // bundle = [CPBundle bundleForClass:TNUIKitScrollView]; 310 | // 311 | // for (var i = 0; i < slices.length; ++i) 312 | // { 313 | // var slice = slices[i]; 314 | // 315 | // imageSlices.push(slice ? [[CPImage alloc] initWithContentsOfFile:[bundle pathForResource:slice[0]] size:CGSizeMake(slice[1], slice[2])] : nil); 316 | // } 317 | // 318 | // if (arguments.length == 2) 319 | // return [CPColor colorWithPatternImage:[[CPThreePartImage alloc] initWithImageSlices:imageSlices isVertical:arguments[1]]]; 320 | // else 321 | // return [CPColor colorWithPatternImage:[[CPNinePartImage alloc] initWithImageSlices:imageSlices]]; 322 | // } 323 | // else if (arguments.length == 3) 324 | // { 325 | // return [CPColor colorWithPatternImage:PatternImage(arguments[0], arguments[1], arguments[2])]; 326 | // } 327 | // else 328 | // { 329 | // return nil; 330 | // } 331 | // } 332 | // 333 | // var TNKnobColorVertical = PatternColor( 334 | // [ 335 | // ["TNUIKitScrollView/scroller-vertical-knob-top.png", 8.0, 5.0], 336 | // ["TNUIKitScrollView/scroller-vertical-knob-center.png", 8.0, 1.0], 337 | // ["TNUIKitScrollView/scroller-vertical-knob-bottom.png", 8.0, 5.0] 338 | // ], YES), 339 | // TNKnobColorHorizontal = PatternColor( 340 | // [ 341 | // ["TNUIKitScrollView/scroller-horizontal-knob-left.png", 5.0, 8.0], 342 | // ["TNUIKitScrollView/scroller-horizontal-knob-center.png", 1.0, 8.0], 343 | // ["TNUIKitScrollView/scroller-horizontal-knob-right.png", 5.0, 8.0] 344 | // ], NO); 345 | -------------------------------------------------------------------------------- /TNTableViewDataSource.j: -------------------------------------------------------------------------------- 1 | /* 2 | * TNTableViewDataSource.j 3 | * 4 | * Copyright (C) 2010 Antoine Mercadal 5 | * 6 | * This library is free software; you can redistribute it and/or 7 | * modify it under the terms of the GNU Lesser General Public 8 | * License as published by the Free Software Foundation; either 9 | * version 3.0 of the License, or (at your option) any later version. 10 | * 11 | * This library is distributed in the hope that it will be useful, 12 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 13 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 14 | * Lesser General Public License for more details. 15 | * 16 | * You should have received a copy of the GNU Lesser General Public 17 | * License along with this library; if not, write to the Free Software 18 | * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA 19 | */ 20 | 21 | @import 22 | 23 | @import 24 | @import 25 | 26 | 27 | /*! @ingroup tnkit 28 | Simple table view datasource with filtering support 29 | Exemple of uses 30 | 31 | var colA = [[CPTableColumn alloc] initWithIdentifier:@"keypath1"], 32 | colB = [[CPTableColumn alloc] initWithIdentifier:@"keypath2"]; 33 | 34 | [aTableView addTableColumn:colA]; 35 | [aTableView addTableColumn:colB]; 36 | 37 | ... 38 | 39 | datasource = [[TNTableViewDataSource alloc] init]; 40 | 41 | [datasource setTable:aTableView]; 42 | [datasource setSearchableKeyPaths:[@"keypath1"]]; // only key path1 is searchable 43 | 44 | [aTableView setDataSource:datasource]; 45 | 46 | ... 47 | 48 | // bind a CPSearchField to the datasource 49 | 50 | [aSearchField setTarget:datasource]; 51 | [aSearchField setAction:@selector(filterObjects:)]; 52 | 53 | ... 54 | 55 | [datasource addObject:anObject]; 56 | [aTableView reloadData]; 57 | 58 | Not the anObject must have keypath1, keypath2 if you want to be able to search and display 59 | 60 | */ 61 | @implementation TNTableViewDataSource: CPObject 62 | { 63 | CPArray _content @accessors(property=content); 64 | CPArray _searchableKeyPaths @accessors(property=searchableKeyPaths); 65 | CPTableView _table @accessors(property=table); 66 | CPPredicate _displayFilter @accessors(property=displayFilter); 67 | id _delegate @accessors(property=delegate); 68 | 69 | CPArray _filteredContent; 70 | CPSearchField _searchField; 71 | CPPredicate _filter; 72 | BOOL _needsFilter; 73 | } 74 | 75 | #pragma mark - 76 | #pragma mark Initialization 77 | 78 | - (id)init 79 | { 80 | if (self = [super init]) 81 | { 82 | _content = [CPArray array]; 83 | _filteredContent = [CPArray array]; 84 | _searchableKeyPaths = [CPArray array]; 85 | 86 | _filter = nil; 87 | _needsFilter = NO; 88 | } 89 | return self; 90 | } 91 | 92 | 93 | #pragma mark - 94 | #pragma mark Filtering 95 | 96 | /*! this action should be bound to a CPSearchField 97 | it will filter the content of the datasource according to the sender value 98 | @param sender the sender of the action 99 | */ 100 | - (@action)filterObjects:(id)sender 101 | { 102 | if (!_searchField) 103 | _searchField = sender; 104 | 105 | [self setFilterString:[sender stringValue]]; 106 | } 107 | 108 | - (void)setFilterPredicate:(CPPredicate)aPredicate 109 | { 110 | _filter = aPredicate; 111 | [self _performFiltering]; 112 | } 113 | 114 | /*! Set the filter. The filter must be a string 115 | @param aString CPString representing the filter 116 | */ 117 | - (void)setFilterString:(CPString)aString 118 | { 119 | if (aString && [aString length]) 120 | { 121 | try {_filter = [CPPredicate predicateWithFormat:aString];}catch(e){}; 122 | 123 | // if predicate creation failed, build a predicate according to searchable ketpaths 124 | if (!_filter) 125 | { 126 | var tempPredicateString = @""; 127 | 128 | for (var i = 0, c = [_searchableKeyPaths count]; i < c; i++) 129 | { 130 | tempPredicateString += _searchableKeyPaths[i] + " contains[c] '" + aString + "' "; 131 | if (i + 1 < [_searchableKeyPaths count]) 132 | tempPredicateString += " OR "; 133 | } 134 | 135 | if ([tempPredicateString length]) 136 | _filter = [CPPredicate predicateWithFormat:tempPredicateString]; 137 | } 138 | } 139 | else 140 | _filter = nil; 141 | 142 | [self _performFiltering]; 143 | } 144 | 145 | /*! @ignore 146 | Return a filtered content using a filter predicate 147 | @param aPredicate the predicate 148 | @return CPArray with filtered content 149 | */ 150 | - (void)_filterWithPredicate:(CPPredicate)aPredicate 151 | { 152 | if (_displayFilter) 153 | return [[_content filteredArrayUsingPredicate:_filter] filteredArrayUsingPredicate:_displayFilter]; 154 | else 155 | return [_content filteredArrayUsingPredicate:_filter]; 156 | } 157 | 158 | - (void)_performFiltering 159 | { 160 | _filteredContent = [CPArray array]; 161 | 162 | if (!_filter) 163 | _filteredContent = _displayFilter ? [[_content copy] filteredArrayUsingPredicate:_displayFilter] : [_content copy]; 164 | else 165 | _filteredContent = [self _filterWithPredicate:_filter]; 166 | 167 | // [_table reloadData]; 168 | } 169 | 170 | 171 | #pragma mark - 172 | #pragma mark Content management 173 | 174 | /*! set the given array of object as the content of the datasource 175 | @param aContent CPArray containing the datas of the datasource 176 | */ 177 | - (void)setContent:(CPArray)aContent 178 | { 179 | _content = aContent; 180 | _filteredContent = _displayFilter ? [[_content copy] filteredArrayUsingPredicate:_displayFilter] : [_content copy]; 181 | 182 | _needsFilter = YES; 183 | } 184 | 185 | /*! add an object to the datasource 186 | @param anObject object to add 187 | */ 188 | - (void)addObject:(id)anObject 189 | { 190 | [_content addObject:anObject]; 191 | 192 | if (!_displayFilter || [_displayFilter evaluateWithObject:anObject]) 193 | [_filteredContent addObject:anObject]; 194 | 195 | _needsFilter = YES; 196 | } 197 | 198 | /*! add some objects to the datasource 199 | @param someObjects array of objects to add 200 | */ 201 | - (void)addObjectsFromArray:(CPArray)someObjects 202 | { 203 | [_content addObjectsFromArray:someObjects]; 204 | 205 | if (_displayFilter) 206 | { 207 | for (var i = 0, c = [someObjects count]; i < c; i++) 208 | { 209 | var obj = someObjects[i]; 210 | 211 | if ([_displayFilter evaluateWithObject:obj]) 212 | [_filteredContent addObject:obj]; 213 | } 214 | } 215 | else 216 | { 217 | _filteredContent = [_content copy]; 218 | } 219 | 220 | _needsFilter = YES; 221 | } 222 | 223 | /*! Chek if contents contains given object 224 | @param anObject the object to search 225 | @return YES if anObject is in the contents 226 | */ 227 | - (void)containsObject:(id)anObject 228 | { 229 | return [_filteredContent containsObject:anObject]; 230 | } 231 | 232 | /*! insert an object at a given index in the datasource 233 | @param anObject object to add 234 | @param anObject int representing the position 235 | */ 236 | - (void)insertObject:(id)anObject atIndex:(int)anIndex 237 | { 238 | [_content insertObject:anObject atIndex:anIndex]; 239 | 240 | if (!_displayFilter || [_displayFilter evaluateWithObject:anObject]) 241 | [_filteredContent insertObject:anObject atIndex:anIndex]; 242 | 243 | _needsFilter = YES; 244 | } 245 | 246 | /*! return the object at given index 247 | @param anObject int representing the position 248 | @return the object 249 | */ 250 | - (void)objectAtIndex:(int)index 251 | { 252 | return _filteredContent[index]; 253 | } 254 | 255 | /*! return the objects at given indexes contained in a CPIndexSet 256 | @param anObject CPIndexSet representing the positions 257 | @return the objects 258 | */ 259 | - (CPArray)objectsAtIndexes:(CPIndexSet)aSet 260 | { 261 | return [_filteredContent objectsAtIndexes:aSet]; 262 | } 263 | 264 | /*! removes the object at given index 265 | @param anObject int representing the position 266 | */ 267 | - (void)removeObjectAtIndex:(int)index 268 | { 269 | var object = _filteredContent[index]; 270 | 271 | [_filteredContent removeObjectAtIndex:index]; 272 | [_content removeObject:object]; 273 | 274 | _needsFilter = YES; 275 | } 276 | 277 | /*! removes the object at given indexes contained in a CPIndexSet 278 | @param anObject CPIndexSet representing the positions 279 | */ 280 | - (void)removeObjectsAtIndexes:(CPIndexSet)aSet 281 | { 282 | try 283 | { 284 | var objects = [_filteredContent objectsAtIndexes:aSet]; 285 | 286 | [_filteredContent removeObjectsAtIndexes:aSet]; 287 | [_content removeObjectsInArray:objects]; 288 | 289 | _needsFilter = YES; 290 | } 291 | catch(e) 292 | { 293 | CPLog.error(e); 294 | } 295 | } 296 | 297 | /*! remove the given object from the array 298 | @param anObject the object to remove 299 | */ 300 | - (void)removeObject:(id)anObject 301 | { 302 | [_content removeObject:anObject]; 303 | [_filteredContent removeObject:anObject]; 304 | 305 | _needsFilter = YES; 306 | } 307 | 308 | /*! remove all objects 309 | */ 310 | - (void)removeAllObjects 311 | { 312 | [_content removeAllObjects]; 313 | [_filteredContent removeAllObjects]; 314 | 315 | _needsFilter = YES; 316 | } 317 | 318 | /*! remove last object 319 | */ 320 | - (void)removeLastObject 321 | { 322 | [_content removeLastObject]; 323 | [_filteredContent removeLastObject]; 324 | 325 | _needsFilter = YES; 326 | } 327 | 328 | /*! remove first object 329 | */ 330 | - (void)removeFirstObject 331 | { 332 | [_content removeFirstObject]; 333 | [_filteredContent removeFirstObject]; 334 | 335 | _needsFilter = YES; 336 | } 337 | 338 | /*! return the index of the given object 339 | @param anObject the object 340 | @return the index of the given object 341 | */ 342 | - (int)indexOfObject:(id)anObject 343 | { 344 | return [_filteredContent indexOfObject:anObject]; 345 | } 346 | 347 | /*! returns the number of object in the datasource 348 | If a filter is applied, it will return the number of objects 349 | matching the filyter , not the total 350 | @return the number of object 351 | */ 352 | - (int)count 353 | { 354 | return [_filteredContent count]; 355 | } 356 | 357 | /*! Removes the objects from the array 358 | */ 359 | - (void)removeObjectsInArray:(CPArray)someObjects 360 | { 361 | [_content removeObjectsInArray:someObjects]; 362 | [_filteredContent removeObjectsInArray:someObjects]; 363 | 364 | _needsFilter = YES; 365 | } 366 | 367 | /*! Sort the content 368 | */ 369 | - (void)sortUsingDescriptors:(CPArray)someDescriptors 370 | { 371 | [_content sortUsingDescriptors:someDescriptors]; 372 | [_filteredContent sortUsingDescriptors:someDescriptors]; 373 | } 374 | 375 | /*! Returns the the full content, filtered with the given predicate 376 | */ 377 | - (CPArray)filteredArrayUsingPredicate:(CPPredicate)aPredicate 378 | { 379 | return [_content filteredArrayUsingPredicate:aPredicate]; 380 | } 381 | 382 | - (id)firstObject 383 | { 384 | return [_filteredContent firstObject]; 385 | } 386 | 387 | - (id)lastObject 388 | { 389 | return [_filteredContent lastObject]; 390 | } 391 | 392 | 393 | #pragma mark - 394 | #pragma mark Datasource implementation 395 | 396 | - (CPNumber)numberOfRowsInTableView:(CPTableView)aTable 397 | { 398 | return [_filteredContent count]; 399 | } 400 | 401 | - (id)tableView:(CPTableView)aTableView objectValueForTableColumn:(CPNumber)aCol row:(CPNumber)aRow 402 | { 403 | if (_needsFilter) 404 | { 405 | _needsFilter = NO; 406 | [self filterObjects:_searchField]; 407 | } 408 | 409 | if (aRow >= [_filteredContent count]) 410 | return nil; 411 | 412 | if (_delegate 413 | && [_delegate respondsToSelector:@selector(dataSource:willReachEndOfData:)] 414 | && [aTableView numberOfRows] == aRow + 1) 415 | { 416 | [_delegate dataSource:self willReachEndOfData:aRow]; 417 | } 418 | 419 | return [aCol identifier] == "self" ? _filteredContent[aRow] : [_filteredContent[aRow] valueForKeyPath:[aCol identifier]]; 420 | } 421 | 422 | - (void)tableView:(CPTableView)aTableView sortDescriptorsDidChange:(CPArray)oldDescriptors 423 | { 424 | var indexes = [aTableView selectedRowIndexes], 425 | selectedObjects = [_filteredContent objectsAtIndexes:indexes], 426 | indexesToSelect = [[CPIndexSet alloc] init]; 427 | 428 | [_filteredContent sortUsingDescriptors:[aTableView sortDescriptors]]; 429 | [_content sortUsingDescriptors:[aTableView sortDescriptors]]; 430 | 431 | // [_table reloadData]; 432 | 433 | for (var i = 0, c = [selectedObjects count]; i < c; i++) 434 | { 435 | var object = selectedObjects[i]; 436 | [indexesToSelect addIndex:[_filteredContent indexOfObject:object]]; 437 | } 438 | 439 | [_table selectRowIndexes:indexesToSelect byExtendingSelection:NO]; 440 | 441 | } 442 | 443 | - (void)tableView:(CPTableView)aTableView setObjectValue:(id)aValue forTableColumn:(CPTableColumn)aCol row:(int)aRow 444 | { 445 | if (aRow >= [_filteredContent count]) 446 | return; 447 | 448 | var identifier = [aCol identifier]; 449 | 450 | [_filteredContent[aRow] setValue:aValue forKeyPath:identifier]; 451 | } 452 | 453 | 454 | #pragma mark - 455 | #pragma mark Drag and drop 456 | 457 | /*! DataSource delegate 458 | */ 459 | - (BOOL)tableView:(CPTableView)aTableView writeRowsWithIndexes:(CPIndexSet)rowIndexes toPasteboard:(CPPasteboard)thePasteBoard 460 | { 461 | if (_delegate && [_delegate respondsToSelector:@selector(dataSource:writeRowsWithIndexes:toPasteboard:)]) 462 | return [_delegate dataSource:self writeRowsWithIndexes:rowIndexes toPasteboard:thePasteBoard]; 463 | 464 | return NO; 465 | } 466 | 467 | /*! DataSource delegate 468 | */ 469 | - (CPDragOperation)tableView:(CPTableView)aTableView validateDrop:(CPDraggingInfo)info proposedRow:(int)row proposedDropOperation:(CPTableViewDropOperation)operation 470 | { 471 | if (_delegate && [_delegate respondsToSelector:@selector(dataSource:validateDrop:proposedRow:proposedDropOperation:)]) 472 | return [_delegate dataSource:self validateDrop:info proposedRow:row proposedDropOperation:operation]; 473 | 474 | return CPDragOperationNone; 475 | } 476 | 477 | /*! DataSource delegate 478 | */ 479 | - (BOOL)tableView:(CPTableView)aTableView acceptDrop:(CPDraggingInfo)info row:(int)row dropOperation:(CPTableViewDropOperation)operation 480 | { 481 | if (_delegate && [_delegate respondsToSelector:@selector(dataSource:acceptDrop:row:dropOperation:)]) 482 | return [_delegate dataSource:self acceptDrop:info row:row dropOperation:operation]; 483 | 484 | return NO; 485 | } 486 | 487 | @end 488 | -------------------------------------------------------------------------------- /TNFlipView.j: -------------------------------------------------------------------------------- 1 | /* 2 | * TNFlipView.j 3 | * 4 | * Copyright (C) 2010 Antoine Mercadal 5 | * 6 | * This library is free software; you can redistribute it and/or 7 | * modify it under the terms of the GNU Lesser General Public 8 | * License as published by the Free Software Foundation; either 9 | * version 3.0 of the License, or (at your option) any later version. 10 | * 11 | * This library is distributed in the hope that it will be useful, 12 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 13 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 14 | * Lesser General Public License for more details. 15 | * 16 | * You should have received a copy of the GNU Lesser General Public 17 | * License along with this library; if not, write to the Free Software 18 | * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA 19 | */ 20 | 21 | @import 22 | @import 23 | 24 | TNFlipViewAnimationStyleRotate = 1; 25 | TNFlipViewAnimationStyleTranslate = 2; 26 | TNFlipViewAnimationStyleTranslateHorizontal = 1; 27 | TNFlipViewAnimationStyleTranslateVertical = 2; 28 | 29 | var TNFlipView_didShowView_ = 1 << 1; 30 | 31 | 32 | 33 | /*! @ingroup TNKit 34 | this widget allow to set a back view and a front view and 35 | is able to flip them just like widget in Mac OS X Dashboard 36 | Note that is use webkit CSS transformation 37 | */ 38 | @implementation TNFlipView : CPView 39 | { 40 | BOOL _flipped @accessors(getter=isFlipped); 41 | CPView _backView @accessors(property=backView); 42 | CPView _frontView @accessors(property=frontView); 43 | float _animationDuration @accessors(getter=animationDuration); 44 | int _animationDirection @accessors(getter=animationDirection); 45 | int _animationStyle @accessors(getter=animationStyle); 46 | id _delegate @accessors(getter=delegate); 47 | 48 | int _implementedDelegateMethods; 49 | 50 | CPView _currentBackViewContent; 51 | CPView _currentFrontViewContent; 52 | } 53 | 54 | 55 | #pragma mark - 56 | #pragma mark Initialization 57 | 58 | /*! initialize the TNFlipView 59 | @param aRect the frame 60 | */ 61 | - (TNFlipView)initWithFrame:(CGRect)aRect 62 | { 63 | if (self = [super initWithFrame:aRect]) 64 | { 65 | [self _init]; 66 | } 67 | 68 | return self; 69 | } 70 | 71 | /*! @ignore 72 | */ 73 | - (void)_init 74 | { 75 | _implementedDelegateMethods = 0; 76 | _animationDuration = 0.5; 77 | _flipped = NO; 78 | _animationDirection = TNFlipViewAnimationStyleTranslateHorizontal; 79 | 80 | _backView = [[CPView alloc] initWithFrame:[self bounds]]; 81 | _frontView = [[CPView alloc] initWithFrame:[self bounds]]; 82 | 83 | _backView._DOMElement.style.backfaceVisibility = @"hidden"; 84 | _backView._DOMElement.style.WebkitBackfaceVisibility = @"hidden"; 85 | _backView._DOMElement.style.MozBackfaceVisibility = @"hidden"; 86 | _backView._DOMElement.style.transformStyle = @"preserve-3d"; 87 | _backView._DOMElement.style.WebkitTransformStyle = @"preserve-3d"; 88 | _backView._DOMElement.style.MozTransformStyle = @"preserve-3d"; 89 | _backView._DOMElement.style.transitionTimingFunction = @"ease"; 90 | _backView._DOMElement.style.WebkitTransitionTimingFunction = @"ease"; 91 | _backView._DOMElement.style.MozTransitionTimingFunction = @"ease"; 92 | _backView._DOMElement.style.perspective = 1000; 93 | _backView._DOMElement.style.WebkitPerspective = 1000; 94 | _backView._DOMElement.style.MozPerspective = 1000; 95 | 96 | _frontView._DOMElement.style.backfaceVisibility = @"hidden"; 97 | _frontView._DOMElement.style.WebkitBackfaceVisibility = @"hidden"; 98 | _frontView._DOMElement.style.MozBackfaceVisibility = @"hidden"; 99 | _frontView._DOMElement.style.transformStyle = @"preserve-3d"; 100 | _frontView._DOMElement.style.WebkitTransformStyle = @"preserve-3d"; 101 | _frontView._DOMElement.style.MozTransformStyle = @"preserve-3d"; 102 | _frontView._DOMElement.style.transitionTimingFunction = @"ease"; 103 | _frontView._DOMElement.style.WebkitTransitionTimingFunction = @"ease"; 104 | _frontView._DOMElement.style.MozTransitionTimingFunction = @"ease"; 105 | _frontView._DOMElement.style.perspective = 1000; 106 | _frontView._DOMElement.style.WebkitPerspective = 1000; 107 | _frontView._DOMElement.style.MozPerspective = 1000; 108 | 109 | [self setAnimationStyle:TNFlipViewAnimationStyleRotate direction:TNFlipViewAnimationStyleTranslateHorizontal]; 110 | 111 | [_backView setAutoresizingMask:CPViewHeightSizable | CPViewWidthSizable]; 112 | [_frontView setAutoresizingMask:CPViewHeightSizable | CPViewWidthSizable]; 113 | 114 | [self addSubview:_backView]; 115 | [self addSubview:_frontView]; 116 | } 117 | 118 | 119 | #pragma mark - 120 | #pragma mark Setters and getters 121 | 122 | /*! set the animation style 123 | style can be TNFlipViewAnimationStyleRotate or TNFlipViewAnimationStyleTranslateHorizontal. 124 | If style is TNFlipViewAnimationStyleTranslate you can add a direction 125 | (TNFlipViewAnimationStyleTranslateHorizontal or TNFlipViewAnimationStyleTranslateVertical) 126 | @param anAnimationStyle the animation style 127 | @param aDirection the animation direction 128 | */ 129 | - (void)setAnimationStyle:(int)anAnimationStyle direction:(int)aDirection 130 | { 131 | if ((anAnimationStyle == _animationStyle) && (aDirection == _animationDirection)) 132 | return; 133 | 134 | _animationStyle = anAnimationStyle; 135 | _animationDirection = aDirection || (_animationDirection ? _animationDirection : TNFlipViewAnimationStyleTranslateHorizontal); 136 | 137 | if (_flipped) 138 | [self _applyShowBackTransformation]; 139 | else 140 | [self _applyShowFrontTransformatiom]; 141 | 142 | } 143 | 144 | /*! set the durationof the flip animation 145 | @param aDuration the duratiom 146 | */ 147 | - (void)setAnimationDuration:(float)aDuration 148 | { 149 | if (aDuration == _animationDuration) 150 | return; 151 | 152 | _animationDuration = aDuration; 153 | 154 | var duration = _animationDuration + "s"; 155 | 156 | _frontView._DOMElement.style.transitionDuration = duration; 157 | _frontView._DOMElement.style.WebkitTransitionDuration = duration; 158 | _frontView._DOMElement.style.MozTransitionDuration = duration; 159 | _frontView._DOMElement.style.OTransitionDuration = duration; 160 | 161 | _backView._DOMElement.style.transitionDuration = duration; 162 | _backView._DOMElement.style.WebkitTransitionDuration = duration; 163 | _backView._DOMElement.style.MozTransitionDuration = duration; 164 | _backView._DOMElement.style.OTransitionDuration = duration; 165 | } 166 | 167 | /*! set the content of the back view 168 | @param aView the back view 169 | */ 170 | - (void)setBackView:(CPView)aView 171 | { 172 | if (_currentBackViewContent == aView) 173 | return; 174 | 175 | if (_currentBackViewContent) 176 | [_currentBackViewContent removeFromSuperview]; 177 | 178 | _currentBackViewContent = aView; 179 | 180 | if (!_currentBackViewContent) 181 | return; 182 | 183 | [_currentBackViewContent setFrame:[self bounds]]; 184 | [_currentBackViewContent setAutoresizingMask:CPViewHeightSizable | CPViewWidthSizable]; 185 | [_backView addSubview:aView]; 186 | 187 | _backView._DOMElement.style.transitionProperty = "translateX, translateY, rotateY"; 188 | _backView._DOMElement.style.transitionDuration = _animationDuration + "s"; 189 | } 190 | 191 | /*! set the content of the front view 192 | @param aView the front view 193 | */ 194 | - (void)setFrontView:(CPView)aView 195 | { 196 | if (_currentFrontViewContent == aView) 197 | return; 198 | 199 | if (_currentFrontViewContent) 200 | [_currentFrontViewContent removeFromSuperview]; 201 | 202 | _currentFrontViewContent = aView; 203 | 204 | if (!_currentFrontViewContent) 205 | return; 206 | 207 | [_currentFrontViewContent setFrame:[self bounds]]; 208 | [_currentFrontViewContent setAutoresizingMask:CPViewHeightSizable | CPViewWidthSizable]; 209 | [_frontView addSubview:aView]; 210 | 211 | _frontView._DOMElement.style.transitionProperty = "translateX, translateY, rotateY"; 212 | _frontView._DOMElement.style.transitionDuration = _animationDuration + "s"; 213 | } 214 | 215 | /*! set the delegate. The delegate can implement 216 | - (void)flipView:(TNFlipView)aFlipView didShowView:(CPView)aView; 217 | 218 | @param aDelegate the delegate 219 | */ 220 | - (void)setDelegate:(id)aDelegate 221 | { 222 | if (aDelegate == _delegate) 223 | return; 224 | 225 | _delegate = aDelegate; 226 | _implementedDelegateMethods = 0; 227 | 228 | if ([_delegate respondsToSelector:@selector(flipView:didShowView:)]) 229 | _implementedDelegateMethods |= TNFlipView_didShowView_; 230 | } 231 | 232 | 233 | #pragma mark - 234 | #pragma mark Utilities 235 | 236 | /*! @ignore 237 | */ 238 | - (void)_delegateDidShowView 239 | { 240 | if (_implementedDelegateMethods & TNFlipView_didShowView_) 241 | [_delegate flipView:self didShowView:_flipped ? _currentBackViewContent : _currentFrontViewContent]; 242 | } 243 | 244 | /*! @ignore 245 | */ 246 | - (void)_listenNextTransformation 247 | { 248 | var frontFunction = function(e) 249 | { 250 | this.removeEventListener("transitionEnd", arguments.callee, NO); 251 | this.removeEventListener("webkitAnimationEnd", arguments.callee, NO); 252 | 253 | _flipped = !_flipped; 254 | 255 | [[CPRunLoop currentRunLoop] limitDateForMode:CPDefaultRunLoopMode]; 256 | 257 | [self _delegateDidShowView]; 258 | }; 259 | 260 | _frontView._DOMElement.addEventListener("transitionEnd", frontFunction, NO); 261 | _frontView._DOMElement.addEventListener("webkitAnimationEnd", frontFunction, NO); 262 | 263 | var backFunction = function(e) 264 | { 265 | this.removeEventListener("transitionEnd", arguments.callee, NO); 266 | this.removeEventListener("webkitAnimationEnd", arguments.callee, NO); 267 | 268 | [[CPRunLoop currentRunLoop] limitDateForMode:CPDefaultRunLoopMode]; 269 | }; 270 | 271 | _backView._DOMElement.addEventListener("transitionEnd", backFunction, NO); 272 | _backView._DOMElement.addEventListener("webkitAnimationEnd", backFunction, NO); 273 | } 274 | 275 | /*! @ignore 276 | */ 277 | - (void)_applyTransformation:(CPString)aTransformation toView:(CPView)aView 278 | { 279 | aView._DOMElement.style.transform = aTransformation; 280 | aView._DOMElement.style.WebkitTransform = aTransformation; 281 | aView._DOMElement.style.MozTransform = aTransformation; 282 | } 283 | 284 | /*! @ignore 285 | */ 286 | - (void)_applyShowFrontTransformatiom 287 | { 288 | switch (_animationStyle) 289 | { 290 | case TNFlipViewAnimationStyleTranslate: 291 | 292 | var directionHorizontal = _animationDirection == TNFlipViewAnimationStyleTranslateHorizontal, 293 | frameSize = [self frameSize], 294 | CSSFunction = directionHorizontal ? "translateX" : "translateY", 295 | offset = directionHorizontal ? frameSize.width : frameSize.height; 296 | 297 | [self _applyTransformation:CSSFunction + "(0px)" toView:_frontView]; 298 | [self _applyTransformation:CSSFunction + "(" + offset + "px)" toView:_backView]; 299 | break; 300 | 301 | case TNFlipViewAnimationStyleRotate: 302 | [self _applyTransformation:"rotateY(0deg)" toView:_frontView]; 303 | [self _applyTransformation:"rotateY(180deg)" toView:_backView]; 304 | break; 305 | } 306 | } 307 | 308 | /*! @ignore 309 | */ 310 | - (void)_applyShowBackTransformation 311 | { 312 | switch (_animationStyle) 313 | { 314 | case TNFlipViewAnimationStyleTranslate: 315 | 316 | var directionHorizontal = _animationDirection == TNFlipViewAnimationStyleTranslateHorizontal, 317 | frameSize = [self frameSize], 318 | CSSFunction = directionHorizontal ? "translateX" : "translateY", 319 | offset = directionHorizontal ? frameSize.width : frameSize.height; 320 | 321 | [self _applyTransformation:CSSFunction + "(-" + offset + "px)" toView:_frontView]; 322 | [self _applyTransformation:CSSFunction + "(0px)" toView:_backView]; 323 | break; 324 | 325 | case TNFlipViewAnimationStyleRotate: 326 | [self _applyTransformation:"rotateY(180deg)" toView:_frontView]; 327 | [self _applyTransformation:"rotateY(0deg)" toView:_backView]; 328 | break; 329 | } 330 | } 331 | 332 | /*! show the front view 333 | */ 334 | - (void)showFront 335 | { 336 | if (!_flipped) 337 | return; 338 | 339 | [self _listenNextTransformation]; 340 | [self _applyShowFrontTransformatiom]; 341 | } 342 | 343 | /*! show the back view 344 | */ 345 | - (void)showBack 346 | { 347 | if (_flipped) 348 | return; 349 | 350 | [self _listenNextTransformation]; 351 | [self _applyShowBackTransformation]; 352 | } 353 | 354 | 355 | #pragma mark - 356 | #pragma mark Action 357 | 358 | /*! flip or unflip the view 359 | @param aSender the sender of the acion 360 | */ 361 | - (@action)flip:(id)aSender 362 | { 363 | if (_flipped) 364 | [self showFront]; 365 | else 366 | [self showBack]; 367 | } 368 | 369 | @end 370 | 371 | 372 | @implementation TNFlipView (CPCoding) 373 | 374 | /*! CPCoder compliance 375 | */ 376 | - (id)initWithCoder:(CPCoder)aCoder 377 | { 378 | self = [super initWithCoder:aCoder]; 379 | 380 | if (self) 381 | { 382 | _flipped = [aCoder decodeObjectForKey:@"_flipped"]; 383 | _backView = [aCoder decodeObjectForKey:@"_backView"]; 384 | _frontView = [aCoder decodeObjectForKey:@"_frontView"]; 385 | _animationDuration = [aCoder decodeObjectForKey:@"_animationDuration"]; 386 | _currentBackViewContent = [aCoder decodeObjectForKey:@"_currentBackViewContent"]; 387 | _currentFrontViewContent = [aCoder decodeObjectForKey:@"_currentFrontViewContent"]; 388 | _animationDirection = [aCoder decodeIntForKey:@"_animationDirection"]; 389 | 390 | [self _init]; 391 | } 392 | 393 | return self; 394 | } 395 | 396 | /*! CPCoder compliance 397 | */ 398 | - (void)encodeWithCoder:(CPCoder)aCoder 399 | { 400 | [super encodeWithCoder:aCoder]; 401 | 402 | [aCoder encodeObject:_flipped forKey:@"_flipped"]; 403 | [aCoder encodeObject:_backView forKey:@"_backView"]; 404 | [aCoder encodeObject:_frontView forKey:@"_frontView"]; 405 | [aCoder encodeObject:_animationDuration forKey:@"_animationDuration"]; 406 | [aCoder encodeObject:_currentBackViewContent forKey:@"_currentBackViewContent"]; 407 | [aCoder encodeObject:_currentFrontViewContent forKey:@"_currentFrontViewContent"]; 408 | [aCoder encodeInt:_animationDirection forKey:@"_animationDirection"]; 409 | } 410 | 411 | @end 412 | -------------------------------------------------------------------------------- /TNRangeSelectorView.j: -------------------------------------------------------------------------------- 1 | /* 2 | * TNRangeSelectorView.j 3 | * 4 | * Copyright (C) 2010 Antoine Mercadal 5 | * This program is free software: you can redistribute it and/or modify 6 | * it under the terms of the GNU Affero General Public License as 7 | * published by the Free Software Foundation, either version 3 of the 8 | * License, or (at your option) any later version. 9 | * 10 | * This program is distributed in the hope that it will be useful, 11 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 | * GNU Affero General Public License for more details. 14 | * 15 | * You should have received a copy of the GNU Affero General Public License 16 | * along with this program. If not, see . 17 | */ 18 | 19 | @import 20 | @import 21 | @import 22 | @import 23 | @import 24 | 25 | @global CPWindowBelow 26 | 27 | var TNRangeSelectorViewDelegate_rangeSelectorView_didChangeLeftValue = 1 << 1, 28 | TNRangeSelectorViewDelegate_rangeSelectorView_didChangeRightValue = 1 << 2; 29 | 30 | 31 | /*! @ingroup TNKit 32 | this widget allows you to choose a range 33 | */ 34 | @implementation TNRangeSelectorView : CPControl 35 | { 36 | CPRange _rangeValue @accessors(getter=rangeValue); 37 | float _leftValue @accessors(getter=leftValue); 38 | float _maxValue @accessors(property=maxValue); 39 | float _minValue @accessors(property=minValue); 40 | float _rightValue @accessors(getter=rightValue); 41 | id _delegate @accessors(getter=delegate); 42 | CPView _backgroundView @accessors(getter=backgroundView); 43 | 44 | CPSplitView _splitView; 45 | CPTextField _fieldLeftValue; 46 | CPTextField _fieldRightValue; 47 | CPView _viewInnerBounds; 48 | CPView _viewOuterBoundsLeft; 49 | CPView _viewOuterBoundsRight; 50 | CPTimer _timerBeforeAction; 51 | int _implementedDelegateMethods; 52 | } 53 | 54 | 55 | #pragma mark - 56 | #pragma mark Initialization 57 | 58 | /*! Initialize a new TNRangeSelectorView 59 | */ 60 | - (TNRangeSelectorView)initWithFrame:(CGRect)aFrame 61 | { 62 | if (self = [super initWithFrame:aFrame]) 63 | { 64 | _minValue = 0; 65 | _maxValue = 100; 66 | 67 | _splitView = [[CPSplitView alloc] initWithFrame:[self bounds]]; 68 | [_splitView setAutoresizingMask:CPViewWidthSizable | CPViewHeightSizable]; 69 | 70 | _viewOuterBoundsLeft = [[CPView alloc] initWithFrame:CGRectMakeZero()]; 71 | [_viewOuterBoundsLeft setAutoresizingMask:CPViewWidthSizable | CPViewHeightSizable]; 72 | [_viewOuterBoundsLeft setBackgroundColor:[CPColor colorWithHexString:@"555"]]; 73 | [_viewOuterBoundsLeft setAlphaValue:0.3]; 74 | 75 | _viewOuterBoundsRight = [[CPView alloc] initWithFrame:CGRectMakeZero()]; 76 | [_viewOuterBoundsRight setAutoresizingMask:CPViewWidthSizable | CPViewHeightSizable]; 77 | [_viewOuterBoundsRight setBackgroundColor:[CPColor colorWithHexString:@"555"]]; 78 | [_viewOuterBoundsRight setAlphaValue:0.3]; 79 | 80 | _viewInnerBounds = [[CPView alloc] initWithFrame:CGRectMakeZero()]; 81 | [_viewInnerBounds setAutoresizingMask:CPViewWidthSizable | CPViewHeightSizable]; 82 | 83 | [_splitView addSubview:_viewOuterBoundsLeft]; 84 | [_splitView addSubview:_viewInnerBounds]; 85 | [_splitView addSubview:_viewOuterBoundsRight]; 86 | 87 | _fieldLeftValue = [CPTextField labelWithTitle:@"10000"]; 88 | [_fieldLeftValue setTextColor:[CPColor colorWithHexString:@"444"]]; 89 | [_fieldLeftValue setFont:[CPFont systemFontOfSize:10]]; 90 | [_fieldLeftValue setLineBreakMode:CPLineBreakByTruncatingTail]; 91 | [_fieldLeftValue bind:CPValueBinding toObject:self withKeyPath:@"leftValue" options:nil]; 92 | [_fieldLeftValue setAutoresizingMask:CPViewMaxXMargin | CPViewMinYMargin | CPViewMaxYMargin]; 93 | [_fieldLeftValue setCenter:[_viewInnerBounds center]]; 94 | var origin = [_fieldLeftValue frameOrigin]; 95 | origin.x = 3; 96 | [_fieldLeftValue setFrameOrigin:origin]; 97 | [_viewInnerBounds addSubview:_fieldLeftValue]; 98 | 99 | 100 | _fieldRightValue = [CPTextField labelWithTitle:@"10000"]; 101 | [_fieldRightValue setTextColor:[CPColor colorWithHexString:@"444"]]; 102 | [_fieldRightValue setFont:[CPFont systemFontOfSize:10]]; 103 | [_fieldRightValue setAlignment:CPRightTextAlignment]; 104 | [_fieldRightValue setLineBreakMode:CPLineBreakByTruncatingTail]; 105 | [_fieldRightValue bind:CPValueBinding toObject:self withKeyPath:@"rightValue" options:nil]; 106 | [_fieldRightValue setAutoresizingMask:CPViewMinXMargin | CPViewMinYMargin | CPViewMaxYMargin]; 107 | [_fieldRightValue setCenter:[_viewInnerBounds center]]; 108 | var origin = [_fieldRightValue frameOrigin]; 109 | origin.x = [_viewInnerBounds frameSize].width - 3 - [_fieldRightValue frameSize].width; 110 | [_fieldRightValue setFrameOrigin:origin]; 111 | [_viewInnerBounds addSubview:_fieldRightValue]; 112 | 113 | [self addSubview:_splitView]; 114 | 115 | [_splitView setDelegate:self]; 116 | [self _init]; 117 | } 118 | 119 | return self; 120 | } 121 | 122 | - (void)_init 123 | { 124 | self._DOMElement.style.border = "1px solid #A5A5A5"; 125 | self._DOMElement.style.borderRadius = "3px"; 126 | _splitView._DOMElement.style.borderRadius = "3px"; 127 | if (_backgroundView) 128 | _backgroundView._DOMElement.style.borderRadius = "3px"; 129 | } 130 | 131 | 132 | #pragma mark - 133 | #pragma mark Setters and Getters 134 | 135 | /*! Set the left value. 136 | @param aValue the value for left bound 137 | */ 138 | - (void)setLeftValue:(float)aValue 139 | { 140 | if (aValue == _leftValue) 141 | return; 142 | 143 | var frameWidth = [self frameSize].width; 144 | 145 | [_splitView setDelegate:nil]; 146 | 147 | [self willChangeValueForKey:@"rangeValue"]; 148 | [self willChangeValueForKey:@"leftValue"]; 149 | _leftValue = MIN(MAX(aValue, _minValue), _maxValue); 150 | [self didChangeValueForKey:@"leftValue"]; 151 | [self didChangeValueForKey:@"rangeValue"]; 152 | 153 | [_splitView setPosition:(frameWidth * [self _calculateProgress:aValue]) ofDividerAtIndex:0]; 154 | 155 | [_splitView setDelegate:self]; 156 | 157 | [self _didChangeLeftValue]; 158 | } 159 | 160 | /*! Set the right value. 161 | @param aValue the value for right bound 162 | */ 163 | - (void)setRightValue:(float)aValue 164 | { 165 | if (aValue == _rightValue) 166 | return; 167 | 168 | var frameWidth = [self frameSize].width; 169 | 170 | [_splitView setDelegate:nil]; 171 | 172 | [self willChangeValueForKey:@"rangeValue"]; 173 | [self willChangeValueForKey:@"rightValue"]; 174 | _rightValue = MIN(MAX(aValue, _minValue), _maxValue); 175 | [self didChangeValueForKey:@"rangeValue"]; 176 | [self didChangeValueForKey:@"rightValue"]; 177 | 178 | [_splitView setPosition:(frameWidth * [self _calculateProgress:aValue]) ofDividerAtIndex:1]; 179 | 180 | [_splitView setDelegate:self]; 181 | 182 | [self _didChangeRightValue]; 183 | } 184 | 185 | /*! Set the left and right value using a range 186 | @param aRange the range to use 187 | */ 188 | - (void)setRangeValue:(CPRange)aRange 189 | { 190 | [self willChangeValueForKey:@"rangeValue"]; 191 | 192 | var start = aRange.location, 193 | end = start + aRange.length; 194 | 195 | [self setLeftValue:start]; 196 | [self setRightValue:end]; 197 | 198 | [self didChangeValueForKey:@"rangeValue"]; 199 | } 200 | 201 | - (CPRange)rangeValue 202 | { 203 | return CPMakeRange(_leftValue, _rightValue - _leftValue); 204 | } 205 | 206 | 207 | /*! Set the value field color 208 | @param aColor the color to use 209 | */ 210 | - (void)setValuesTextColor:(CPColor)aColor 211 | { 212 | [_fieldRightValue setTextColor:aColor]; 213 | [_fieldLeftValue setTextColor:aColor]; 214 | } 215 | 216 | /*! Gets the value field color 217 | @return the CPColor of the fields 218 | */ 219 | - (CPColor)valuesTextColor 220 | { 221 | return [_fieldRightValue textColor]; 222 | } 223 | 224 | /*! Set the value fields visibility 225 | @param shouldHide if YES, field will be hidden 226 | */ 227 | - (void)setValueFieldsHidden:(BOOL)shouldHide 228 | { 229 | [_fieldRightValue setHidden:shouldHide]; 230 | [_fieldLeftValue setHidden:shouldHide]; 231 | } 232 | 233 | /*! Gets the value fields visibility 234 | @return YES if value fields are hidden 235 | */ 236 | - (BOOL)areValueFieldsHidden 237 | { 238 | return [_fieldRightValue isHidden]; 239 | } 240 | 241 | /*! Set the color and opacity of bounds 242 | @param aColor the background color 243 | @param anAlphaValue the alpha value (between 0 and 1) 244 | */ 245 | - (void)setOuterBoundsViewsColor:(CPColor)aColor alphaValue:(float)anAlphaValue 246 | { 247 | [_viewOuterBoundsRight setBackgroundColor:aColor]; 248 | [_viewOuterBoundsRight setAlphaValue:anAlphaValue]; 249 | [_viewOuterBoundsLeft setBackgroundColor:aColor]; 250 | [_viewOuterBoundsLeft setAlphaValue:anAlphaValue]; 251 | } 252 | 253 | /*! Set the background view to use. 254 | @param aView the background view to use 255 | */ 256 | - (void)setBackgroundView:(CPView)aView 257 | { 258 | if (aView === _backgroundView) 259 | return; 260 | 261 | if (_backgroundView) 262 | [_backgroundView removeFromSuperview]; 263 | 264 | _backgroundView = aView; 265 | 266 | [_backgroundView setFrame:[self bounds]]; 267 | _backgroundView._DOMElement.style.borderRadius = "3px"; 268 | [_backgroundView setAutoresizingMask:CPViewWidthSizable | CPViewHeightSizable]; 269 | [self addSubview:_backgroundView positioned:CPWindowBelow relativeTo:nil];; 270 | } 271 | 272 | 273 | #pragma mark - 274 | #pragma mark Delegate Management 275 | 276 | /*! Set the delegate 277 | Delegate can implement 278 | - (void)rangeSelectorView:didChangeLeftValue: 279 | - (void)rangeSelectorView:didChangeRightValue: 280 | */ 281 | - (void)setDelegate:(id)aDelegate 282 | { 283 | if (aDelegate == _delegate) 284 | return; 285 | 286 | _delegate = aDelegate 287 | _implementedDelegateMethods = 0; 288 | 289 | if ([_delegate respondsToSelector:@selector(rangeSelectorView:didChangeLeftValue:)]) 290 | _implementedDelegateMethods |= TNRangeSelectorViewDelegate_rangeSelectorView_didChangeLeftValue; 291 | 292 | if ([_delegate respondsToSelector:@selector(rangeSelectorView:didChangeRightValue:)]) 293 | _implementedDelegateMethods |= TNRangeSelectorViewDelegate_rangeSelectorView_didChangeRightValue; 294 | } 295 | 296 | /*! @ignore 297 | Send delegate message if needed for left value 298 | */ 299 | - (void)_didChangeLeftValue 300 | { 301 | if (!(_implementedDelegateMethods & TNRangeSelectorViewDelegate_rangeSelectorView_didChangeLeftValue)) 302 | return; 303 | 304 | [_delegate rangeSelectorView:self didChangeLeftValue:_leftValue]; 305 | } 306 | 307 | /*! @ignore 308 | Send delegate message if needed for right value 309 | */ 310 | - (void)_didChangeRightValue 311 | { 312 | if (!(_implementedDelegateMethods & TNRangeSelectorViewDelegate_rangeSelectorView_didChangeRightValue)) 313 | return; 314 | 315 | [_delegate rangeSelectorView:self didChangeRightValue:_rightValue]; 316 | } 317 | 318 | 319 | #pragma mark - 320 | #pragma mark Utils 321 | 322 | /*! @ignore 323 | Calculate the progress value. 324 | */ 325 | - (float)_calculateProgress:(float)aValue 326 | { 327 | var diff = aValue - _minValue, 328 | scope = _maxValue - _minValue; 329 | 330 | return diff ? diff / scope : 0; 331 | } 332 | 333 | 334 | #pragma mark - 335 | #pragma mark Delegates 336 | 337 | /*! CPSplitView Delegate 338 | */ 339 | - (void)splitViewDidResizeSubviews:(CPNotification)aNotification 340 | { 341 | var frameWidth = [self frameSize].width, 342 | changeHasBeenMade = NO, 343 | leftDividerPosition = CGRectMakeCopy([_splitView rectOfDividerAtIndex:0]).origin.x, 344 | rightDividerPosition = CGRectMakeCopy([_splitView rectOfDividerAtIndex:1]).origin.x + 1, 345 | leftValue = Math.floor((leftDividerPosition / frameWidth * (_maxValue - _minValue)) + _minValue), 346 | rightValue = Math.floor((rightDividerPosition / frameWidth * (_maxValue - _minValue)) + _minValue); 347 | 348 | if (_rightValue != rightValue || _leftValue != leftValue) 349 | { 350 | changeHasBeenMade = YES; 351 | [self willChangeValueForKey:@"rangeValue"]; 352 | } 353 | 354 | if (_rightValue != rightValue) 355 | { 356 | [self willChangeValueForKey:@"rightValue"]; 357 | _rightValue = rightValue; 358 | [self didChangeValueForKey:@"rightValue"]; 359 | [self _didChangeRightValue]; 360 | } 361 | 362 | if (_leftValue != leftValue) 363 | { 364 | [self willChangeValueForKey:@"leftValue"]; 365 | _leftValue = leftValue; 366 | [self didChangeValueForKey:@"leftValue"]; 367 | [self _didChangeLeftValue]; 368 | } 369 | 370 | if (changeHasBeenMade) 371 | { 372 | [self didChangeValueForKey:@"rangeValue"]; 373 | 374 | if (_timerBeforeAction) 375 | [_timerBeforeAction invalidate]; 376 | 377 | if ([self target] && [self action]) 378 | _timerBeforeAction = [CPTimer scheduledTimerWithTimeInterval:1.0 target:self selector:@selector(_shouldSendAction:) userInfo:nil repeats:NO]; 379 | } 380 | } 381 | 382 | - (void)_shouldSendAction:(CPTimer)aTimer 383 | { 384 | _timerBeforeAction = nil; 385 | [[self target] performSelector:[self action] withObject:self]; 386 | } 387 | 388 | @end 389 | 390 | 391 | @implementation TNRangeSelectorView (CPCoding) 392 | 393 | /*! CPCoder compliance 394 | */ 395 | - (id)initWithCoder:(CPCoder)aCoder 396 | { 397 | if (self = [super initWithCoder:aCoder]) 398 | { 399 | _backgroundView = [aCoder decodeObjectForKey:@"_backgroundView"]; 400 | _fieldLeftValue = [aCoder decodeObjectForKey:@"_fieldLeftValue"]; 401 | _fieldRightValue = [aCoder decodeObjectForKey:@"_fieldRightValue"]; 402 | _implementedDelegateMethods = [aCoder decodeIntForKey:@"_implementedDelegateMethods"]; 403 | _leftValue = [aCoder decodeFloatForKey:@"_leftValue"]; 404 | _maxValue = [aCoder decodeFloatForKey:@"_maxValue"]; 405 | _minValue = [aCoder decodeFloatForKey:@"_minValue"]; 406 | _rightValue = [aCoder decodeFloatForKey:@"_rightValue"]; 407 | _splitView = [aCoder decodeObjectForKey:@"_splitView"]; 408 | _viewInnerBounds = [aCoder decodeObjectForKey:@"_viewInnerBounds"]; 409 | _viewOuterBoundsLeft = [aCoder decodeObjectForKey:@"_viewOuterBoundsLeft"]; 410 | _viewOuterBoundsRight = [aCoder decodeObjectForKey:@"_viewOuterBoundsRight"]; 411 | 412 | [self _init]; 413 | } 414 | 415 | return self; 416 | } 417 | 418 | /*! CPCoder compliance 419 | */ 420 | - (void)encodeWithCoder:(CPCoder)aCoder 421 | { 422 | [super encodeWithCoder:aCoder]; 423 | 424 | [aCoder encodeFloat:_fieldLeftValue forKey:@"_fieldLeftValue"]; 425 | [aCoder encodeFloat:_fieldRightValue forKey:@"_fieldRightValue"]; 426 | [aCoder encodeFloat:_leftValue forKey:@"_leftValue"]; 427 | [aCoder encodeFloat:_maxValue forKey:@"_maxValue"]; 428 | [aCoder encodeFloat:_minValue forKey:@"_minValue"]; 429 | [aCoder encodeFloat:_rightValue forKey:@"_rightValue"]; 430 | [aCoder encodeInt:_implementedDelegateMethods forKey:@"_implementedDelegateMethods"]; 431 | [aCoder encodeObject:_backgroundView forKey:@"_backgroundView"]; 432 | [aCoder encodeObject:_splitView forKey:@"_splitView"]; 433 | [aCoder encodeObject:_viewInnerBounds forKey:@"_viewInnerBounds"]; 434 | [aCoder encodeObject:_viewOuterBoundsLeft forKey:@"_viewOuterBoundsLeft"]; 435 | [aCoder encodeObject:_viewOuterBoundsRight forKey:@"_viewOuterBoundsRight"]; 436 | } 437 | 438 | @end 439 | -------------------------------------------------------------------------------- /TNSwipeView.j: -------------------------------------------------------------------------------- 1 | /* 2 | * TNSwipeView.j 3 | * 4 | * Copyright (C) 2010 Antoine Mercadal 5 | * 6 | * This library is free software; you can redistribute it and/or 7 | * modify it under the terms of the GNU Lesser General Public 8 | * License as published by the Free Software Foundation; either 9 | * version 3.0 of the License, or (at your option) any later version. 10 | * 11 | * This library is distributed in the hope that it will be useful, 12 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 13 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 14 | * Lesser General Public License for more details. 15 | * 16 | * You should have received a copy of the GNU Lesser General Public 17 | * License along with this library; if not, write to the Free Software 18 | * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA 19 | */ 20 | 21 | @import 22 | @import 23 | @import 24 | 25 | var CSSProperties = { 26 | "webkit" : { 27 | "transform": "transform", 28 | "backfaceVisibility": "backfaceVisibility", 29 | "perspective": "perspective", 30 | "transformStyle": "transformStyle", 31 | "transition": "transition", 32 | "transitionTimingFunction": "transitionTimingFunction", 33 | "transitionEnd": "transitionEnd" 34 | }, 35 | "gecko" : { 36 | "transform": "MozTransform", 37 | "backfaceVisibility": "MozBackfaceVisibility", 38 | "perspective": "MozPerspective", 39 | "transformStyle": "MozTransformStyle", 40 | "transition": "MozTransition", 41 | "transitionTimingFunction": "MozTransitionTimingFunction", 42 | "transitionEnd": "transitionend" 43 | } 44 | }; 45 | 46 | TNSwipeViewDirectionRight = 1; 47 | TNSwipeViewDirectionLeft = 2; 48 | 49 | TNSwipeViewCSSTranslateFunctionX = @"translateX"; 50 | TNSwipeViewCSSTranslateFunctionY = @"translateY"; 51 | 52 | // handle flatten & press 53 | try 54 | { 55 | TNSwipeViewBrowserEngine = (typeof(document.body.style.WebkitTransform) != "undefined") ? "webkit" : "gecko"; 56 | } 57 | catch(e) 58 | { 59 | TNSwipeViewBrowserEngine = "gecko"; 60 | } 61 | 62 | 63 | 64 | /*! @ingroup TNKit 65 | This widget allows to add custom views in it and swipe them as pages 66 | Note that is use CSS transformation 67 | */ 68 | @implementation TNSwipeView : CPControl 69 | { 70 | CPArray _views @accessors(getter=views); 71 | CPString _translationFunction @accessors(getter=translationFunction); 72 | float _animationDuration @accessors(property=animationDuration); 73 | float _minimalRatio @accessors(property=minimalRatio); 74 | 75 | BOOL _isAnimating; 76 | CGPoint _generalInitialTrackingPoint; 77 | CGPoint _initialTrackingPoint; 78 | CPView _mainView; 79 | Function _validateFunction; 80 | CPTimer _animationGuardTimer; 81 | int _currentViewIndex; 82 | } 83 | 84 | 85 | #pragma mark - 86 | #pragma mark Initialization 87 | 88 | /*! initialize the TNFlipView 89 | @param aRect the frame 90 | */ 91 | - (TNSwipeView)initWithFrame:(CGRect)aRect 92 | { 93 | if (self = [super initWithFrame:aRect]) 94 | { 95 | _animationDuration = 0.3; 96 | _currentViewIndex = 0; 97 | _minimalRatio = 0.3; 98 | _isAnimating = NO; 99 | _mainView = [[CPView alloc] initWithFrame:[self bounds]]; 100 | _translationFunction = TNSwipeViewCSSTranslateFunctionX; 101 | _views = [CPArray array]; 102 | 103 | _validateFunction = function(e){ 104 | this.removeEventListener(CSSProperties[TNSwipeViewBrowserEngine].transitionEnd, _validateFunction, YES); 105 | _mainView._DOMElement.style[CSSProperties[TNSwipeViewBrowserEngine].transition] = "0s"; 106 | [self _commitAnimation]; 107 | }; 108 | 109 | [_mainView setAutoresizingMask:CPViewHeightSizable]; 110 | [self addSubview:_mainView]; 111 | [self setNeedsLayout]; 112 | } 113 | 114 | return self; 115 | } 116 | 117 | /*! initialize the TNSwipeView 118 | @param aRect the frame 119 | @param aFunction the CSS transformation to use 120 | */ 121 | - (TNSwipeView)initWithFrame:(CGRect)aRect translationFunction:(CPString)aFunction 122 | { 123 | if (self = [self initWithFrame:aRect]) 124 | { 125 | _translationFunction = aFunction; 126 | } 127 | 128 | return self 129 | } 130 | 131 | 132 | #pragma mark - 133 | #pragma mark Notification handlers 134 | 135 | - (void)resetAnimating:(CPTimer)aTimer 136 | { 137 | _isAnimating = NO; 138 | _animationGuardTimer = nil; 139 | } 140 | 141 | 142 | #pragma mark - 143 | #pragma mark Getters / Setters 144 | 145 | /*! set the content of the TNSwipeView 146 | @param someViews CPArray containing views 147 | */ 148 | - (void)setViews:(CPArray)someViews 149 | { 150 | _views = someViews; 151 | [self setNeedsLayout]; 152 | } 153 | 154 | 155 | #pragma mark - 156 | #pragma mark Overrides 157 | 158 | - (void)mouseDown:(CPEvent)anEvent 159 | { 160 | _initialTrackingPoint = [_mainView convertPointFromBase:[anEvent globalLocation]]; 161 | _generalInitialTrackingPoint = [self convertPointFromBase:[anEvent globalLocation]]; 162 | 163 | [super mouseDown:anEvent]; 164 | } 165 | 166 | - (void)trackMouse:(CPEvent)anEvent 167 | { 168 | if (!_isAnimating) 169 | { 170 | var currentDraggingPoint = [_mainView convertPointFromBase:[anEvent globalLocation]]; 171 | 172 | if (_translationFunction == TNSwipeViewCSSTranslateFunctionX) 173 | { 174 | currentDraggingPoint.x -= _initialTrackingPoint.x; 175 | [self _setSlideValue:currentDraggingPoint.x speed:0.05 shouldCommit:NO]; 176 | 177 | } 178 | else 179 | { 180 | currentDraggingPoint.y -= _initialTrackingPoint.y; 181 | [self _setSlideValue:currentDraggingPoint.y speed:0.05 shouldCommit:NO]; 182 | } 183 | } 184 | else 185 | { 186 | CPLog.warn("Animating.... forget it"); 187 | } 188 | [super trackMouse:anEvent]; 189 | 190 | } 191 | 192 | - (void)stopTracking:(CGPoint)lastPoint at:(CGPoint)aPoint mouseIsUp:(BOOL)mouseIsUp 193 | { 194 | [super stopTracking:lastPoint at:aPoint mouseIsUp:mouseIsUp]; 195 | 196 | if (!mouseIsUp) 197 | return; 198 | 199 | if (_isAnimating) 200 | return; 201 | 202 | var movement, 203 | minimalMovement; 204 | 205 | if (_translationFunction == TNSwipeViewCSSTranslateFunctionX) 206 | { 207 | movement = _generalInitialTrackingPoint.x - aPoint.x; 208 | minimalMovement = [self frameSize].width * _minimalRatio; 209 | } 210 | else 211 | { 212 | movement = _generalInitialTrackingPoint.y - aPoint.y; 213 | minimalMovement = [self frameSize].height * _minimalRatio; 214 | } 215 | 216 | if (movement != 0 && Math.abs(movement) >= minimalMovement) 217 | { 218 | if (movement < 0) 219 | { 220 | if (_currentViewIndex > 0) 221 | { 222 | [self _performDirectionalSlide:TNSwipeViewDirectionRight]; 223 | return; 224 | } 225 | } 226 | else 227 | { 228 | if (_currentViewIndex < [_views count] - 1) 229 | { 230 | [self _performDirectionalSlide:TNSwipeViewDirectionLeft]; 231 | return; 232 | } 233 | } 234 | } 235 | 236 | [self _resetTranslation]; 237 | } 238 | 239 | - (void)layoutSubviews 240 | { 241 | if (_translationFunction == TNSwipeViewCSSTranslateFunctionX) 242 | { 243 | [_mainView setFrameSize:CGSizeMake([self frameSize].width * [_views count], [self frameSize].height)]; 244 | for (var i = 0, c = [_views count]; i < c; i++) 245 | { 246 | var currentView = _views[i]; 247 | 248 | [currentView setFrame:[self bounds]]; 249 | [currentView setFrameOrigin:CGPointMake(i * [self frameSize].width, 0)]; 250 | [_mainView addSubview:currentView]; 251 | } 252 | } 253 | else 254 | { 255 | [_mainView setFrameSize:CGSizeMake([self frameSize].width, [self frameSize].height * [_views count])]; 256 | for (var i = 0, c = [_views count]; i < c; i++) 257 | { 258 | var currentView = _views[i]; 259 | 260 | [currentView setFrame:[self bounds]]; 261 | [currentView setFrameOrigin:CGPointMake(0, i * [self frameSize].height)]; 262 | [_mainView addSubview:currentView]; 263 | } 264 | } 265 | } 266 | 267 | - (void)setFrame:(CGRect)aFrame 268 | { 269 | var currentFrameWidth = [self frameSize].width, 270 | widthOffset; 271 | 272 | [super setFrame:aFrame]; 273 | 274 | if (_currentViewIndex == 0) 275 | return; 276 | 277 | widthOffset = aFrame.size.width - currentFrameWidth; 278 | [_mainView setFrameOrigin:CGPointMake(([_mainView frameOrigin].x - (widthOffset * _currentViewIndex)) , 0)]; 279 | } 280 | 281 | 282 | 283 | #pragma mark - 284 | #pragma mark Utilities 285 | 286 | /*! @ignore 287 | */ 288 | - (void)_setSlideValue:(float)aDirectionalSlideValue speed:(float)aSpeed shouldCommit:(BOOL)shouldCommit 289 | { 290 | _mainView._DOMElement.style[CSSProperties[TNSwipeViewBrowserEngine].transition] = aSpeed + @"s"; 291 | 292 | if (shouldCommit) 293 | { 294 | // yeah yeah, we'll try to fix this later... 295 | _animationGuardTimer = [CPTimer scheduledTimerWithTimeInterval:1.0 296 | target:self 297 | selector:@selector(resetAnimating:) 298 | userInfo:nil 299 | repeats:NO]; 300 | _isAnimating = YES; 301 | _mainView._DOMElement.addEventListener(CSSProperties[TNSwipeViewBrowserEngine].transitionEnd, _validateFunction, YES); 302 | } 303 | 304 | _mainView._DOMElement.style[CSSProperties[TNSwipeViewBrowserEngine].backfaceVisibility] = "hidden"; 305 | _mainView._DOMElement.style[CSSProperties[TNSwipeViewBrowserEngine].perspective] = 1000; 306 | _mainView._DOMElement.style[CSSProperties[TNSwipeViewBrowserEngine].transformStyle] = "preserve-3d"; 307 | _mainView._DOMElement.style[CSSProperties[TNSwipeViewBrowserEngine].transform] = _translationFunction + @"(" + aDirectionalSlideValue + @"px)"; 308 | } 309 | 310 | /*! _performDirectionalSlide directly to the current view index. If anIndex is 311 | greater than the actual number of views, last view will be selected 312 | if anIndex is lesser than 0, then the first view will be selected 313 | @param anIndex the index of the view to display 314 | */ 315 | - (void)slideToViewIndex:(int)anIndex 316 | { 317 | if (anIndex == _currentViewIndex) 318 | return; 319 | 320 | if (anIndex > [_views count] - 1) 321 | anIndex == [_views count] - 1; 322 | 323 | if (anIndex < 0) 324 | anIndex = 0; 325 | 326 | 327 | if (anIndex > _currentViewIndex) 328 | { 329 | if (_translationFunction == TNSwipeViewCSSTranslateFunctionX) 330 | [self _setSlideValue:- (anIndex - _currentViewIndex) * [self frameSize].width speed:_animationDuration shouldCommit:YES]; 331 | else 332 | [self _setSlideValue:- (anIndex - _currentViewIndex) * [self frameSize].height speed:_animationDuration shouldCommit:YES]; 333 | 334 | } 335 | else if (anIndex < _currentViewIndex) 336 | { 337 | if (_translationFunction == TNSwipeViewCSSTranslateFunctionX) 338 | [self _setSlideValue:(_currentViewIndex - anIndex) * [self frameSize].width speed:_animationDuration shouldCommit:YES]; 339 | else 340 | [self _setSlideValue:(_currentViewIndex - anIndex) * [self frameSize].height speed:_animationDuration shouldCommit:YES]; 341 | 342 | } 343 | 344 | _currentViewIndex = anIndex; 345 | } 346 | 347 | /*! @ignore 348 | reset the eventual not commited translation 349 | */ 350 | - (void)_resetTranslation 351 | { 352 | [self _setSlideValue:0.0 speed:_animationDuration shouldCommit:YES]; 353 | } 354 | 355 | /*! @ignore 356 | perform a _performDirectionalSlide by 1 in given direction. 357 | @param the direction TNSwipeViewDirectionRight, or TNSwipeViewDirectionLeft 358 | */ 359 | - (void)_performDirectionalSlide:(int)aDirection 360 | { 361 | if (_isAnimating) 362 | return; 363 | 364 | var offset; 365 | switch (aDirection) 366 | { 367 | case TNSwipeViewDirectionLeft: 368 | if (_currentViewIndex + 1 < [_views count]) 369 | { 370 | _currentViewIndex++; 371 | if (_translationFunction == TNSwipeViewCSSTranslateFunctionX) 372 | offset = - [self frameSize].width; 373 | else 374 | offset = - [self frameSize].height; 375 | } 376 | else 377 | { 378 | _currentViewIndex = 0; 379 | if (_translationFunction == TNSwipeViewCSSTranslateFunctionX) 380 | offset = [self frameSize].width * ([_views count] - 1); 381 | else 382 | offset = [self frameSize].height * ([_views count] - 1); 383 | } 384 | break; 385 | 386 | case TNSwipeViewDirectionRight: 387 | if (_currentViewIndex > 0) 388 | { 389 | _currentViewIndex--; 390 | if (_translationFunction == TNSwipeViewCSSTranslateFunctionX) 391 | offset = [self frameSize].width; 392 | else 393 | offset = [self frameSize].height; 394 | } 395 | else 396 | { 397 | _currentViewIndex = [_views count] - 1; 398 | if (_translationFunction == TNSwipeViewCSSTranslateFunctionX) 399 | offset = - (_currentViewIndex * [self frameSize].width); 400 | else 401 | offset = - (_currentViewIndex * [self frameSize].height); 402 | } 403 | break; 404 | } 405 | [self _setSlideValue:offset speed:_animationDuration shouldCommit:YES]; 406 | } 407 | 408 | /*! @ignore 409 | returns the current translation offset 410 | @return integer representing the pixel offset 411 | */ 412 | - (int)_currentTranslation 413 | { 414 | var t = _mainView._DOMElement.style[CSSProperties[TNSwipeViewBrowserEngine].transform]; 415 | t = t.replace(_translationFunction + @"(", @""); 416 | t = t.replace(@"px)", @""); 417 | if (t == "") 418 | t = 0; 419 | return parseInt(t); 420 | } 421 | 422 | /*! @ignore 423 | commit the current CSS translation by moving the actual 424 | position of the main view and be reseting the translation offset 425 | */ 426 | - (void)_commitAnimation 427 | { 428 | [[CPRunLoop currentRunLoop] limitDateForMode:CPDefaultRunLoopMode]; 429 | 430 | if (_translationFunction == TNSwipeViewCSSTranslateFunctionX) 431 | { 432 | var tx = [self _currentTranslation], 433 | newX = [_mainView frameOrigin].x + tx; 434 | [_mainView setFrameOrigin:CGPointMake(newX, 0)]; 435 | } 436 | else 437 | { 438 | var ty = [self _currentTranslation], 439 | newY = [_mainView frameOrigin].y + ty; 440 | [_mainView setFrameOrigin:CGPointMake(0, newY)]; 441 | } 442 | _mainView._DOMElement.style[CSSProperties[TNSwipeViewBrowserEngine].transform] = _translationFunction + @"(0px)"; 443 | if (_animationGuardTimer) 444 | [_animationGuardTimer invalidate]; 445 | [_mainView setNeedsDisplay:YES]; 446 | _animationGuardTimer = nil; 447 | _isAnimating = NO; 448 | } 449 | 450 | 451 | #pragma mark - 452 | #pragma mark Actions 453 | 454 | /*! select the next view. 455 | if current view is the last one, first view will be selected 456 | @param aSender the sender of the action 457 | */ 458 | - (@action)nextView:(id)aSender 459 | { 460 | [self _performDirectionalSlide:TNSwipeViewDirectionLeft]; 461 | } 462 | 463 | /*! select the previous view. 464 | if current view is the first one, last view will be selected 465 | @param aSender the sender of the action 466 | */ 467 | - (@action)previousView:(id)aSender 468 | { 469 | [self _performDirectionalSlide:TNSwipeViewDirectionRight]; 470 | } 471 | 472 | @end 473 | 474 | 475 | @implementation TNSwipeView (CPCoding) 476 | 477 | /*! CPCoder compliance 478 | */ 479 | - (id)initWithCoder:(CPCoder)aCoder 480 | { 481 | self = [super initWithCoder:aCoder]; 482 | 483 | if (self) 484 | { 485 | _views = [aCoder decodeObjectForKey:@"_views"]; 486 | _translationFunction = [aCoder decodeObjectForKey:@"_translationFunction"]; 487 | _animationDuration = [aCoder decodeObjectForKey:@"_animationDuration"]; 488 | _mainView = [aCoder decodeObjectForKey:@"_mainView"]; 489 | _minimalRatio = [aCoder decodeObjectForKey:@"_minimalRatio"]; 490 | } 491 | 492 | return self; 493 | } 494 | 495 | /*! CPCoder compliance 496 | */ 497 | - (void)encodeWithCoder:(CPCoder)aCoder 498 | { 499 | [super encodeWithCoder:aCoder]; 500 | 501 | [aCoder encodeObject:_views forKey:@"_views"]; 502 | [aCoder encodeObject:_translationFunction forKey:@"_translationFunction"]; 503 | [aCoder encodeObject:_animationDuration forKey:@"_animationDuration"]; 504 | [aCoder encodeObject:_mainView forKey:@"_mainView"]; 505 | [aCoder encodeObject:_minimalRatio forKey:@"_minimalRatio"]; 506 | } 507 | 508 | @end 509 | -------------------------------------------------------------------------------- /TNTabView.j: -------------------------------------------------------------------------------- 1 | /* 2 | * TNTabView.j 3 | * 4 | * Copyright (C) 2010 Antoine Mercadal 5 | * This program is free software: you can redistribute it and/or modify 6 | * it under the terms of the GNU Affero General Public License as 7 | * published by the Free Software Foundation, either version 3 of the 8 | * License, or (at your option) any later version. 9 | * 10 | * This program is distributed in the hope that it will be useful, 11 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 | * GNU Affero General Public License for more details. 14 | * 15 | * You should have received a copy of the GNU Affero General Public License 16 | * along with this program. If not, see . 17 | */ 18 | 19 | @import 20 | 21 | @import 22 | @import 23 | @import 24 | @import 25 | @import 26 | @import 27 | @import 28 | @import 29 | @import 30 | @import 31 | 32 | @import "TNTabViewItemPrototype.j" 33 | 34 | @global CPPopoverBehaviorTransient 35 | 36 | var TNTabViewDelegate_tabView_shouldSelectTabViewItem_ = 1 << 1, 37 | TNTabViewDelegate_tabView_didSelectTabViewItem_ = 1 << 2, 38 | TNTabViewDelegate_tabView_willSelectTabViewItem_ = 1 << 3, 39 | TNTabViewDelegate_tabViewDidChangeNumberOfTabViewItems_ = 1 << 4; 40 | 41 | @protocol TNTabViewDelegate 42 | 43 | @optional 44 | - (BOOL)tabView:(TNTabView)aTabView shouldSelectTabViewItem:(CPTabViewItem)aTabViewItem; 45 | - (void)tabView:(TNTabView)aTabView didSelectTabViewItem:(CPTabViewItem)aTabViewItem; 46 | - (void)tabView:(TNTabView)aTabView willSelectTabViewItem:(CPTabViewItem)aTabViewItem; 47 | - (void)tabViewDidChangeNumberOfTabViewItems:(TNTabView)aTabView; 48 | 49 | @end 50 | 51 | /*! @ingroup tnkit 52 | this class is a sort of CPTabView (protocol compliant) 53 | */ 54 | @implementation TNTabView : CPControl 55 | { 56 | id _delegate @accessors(property=delegate); 57 | 58 | CPArray _itemObjects; 59 | CPView _currentSelectedItemView; 60 | CPView _viewTabs; 61 | int _currentSelectedIndex; 62 | CPView _contentView; 63 | TNTabItemPrototype _tabItemViewPrototype; 64 | unsigned _implementedDelegateMethods; 65 | } 66 | 67 | 68 | #pragma mark - 69 | #pragma mark Initialization 70 | 71 | /*! initialize the TNTabView 72 | @param aFrame the frame of the view 73 | */ 74 | - (TNTabView)initWithFrame:(CGRect)aFrame 75 | { 76 | if (self = [super initWithFrame:aFrame]) 77 | { 78 | [self _init]; 79 | } 80 | 81 | return self; 82 | } 83 | 84 | - (void)_init 85 | { 86 | _viewTabs = [[CPView alloc] initWithFrame:CGRectMake(0.0, 0.0, [self frameSize].width, [TNTabItemPrototype size].height)]; 87 | [_viewTabs setAutoresizingMask:CPViewWidthSizable]; 88 | _viewTabs._DOMElement.style.borderBottom = "1px solid #F2F2F2"; 89 | _viewTabs._DOMElement.style.borderTop = "1px solid #F2F2F2"; 90 | _viewTabs._DOMElement.style.boxSizing = @"border-box"; 91 | _viewTabs._DOMElement.style.MozBoxSizing = @"border-box"; 92 | _viewTabs._DOMElement.style.WebkitBoxSizing = @"border-box"; 93 | 94 | _contentView = [[CPView alloc] initWithFrame:CGRectMake(0, [TNTabItemPrototype size].height, CGRectGetWidth([self frame]), CGRectGetHeight([self frame]) - [TNTabItemPrototype size].height)]; 95 | [_contentView setAutoresizingMask:CPViewWidthSizable | CPViewHeightSizable]; 96 | 97 | _currentSelectedIndex = -1; 98 | _itemObjects = []; 99 | _tabItemViewPrototype = [TNTabItemPrototype new]; 100 | 101 | [self addSubview:_viewTabs]; 102 | [self addSubview:_contentView]; 103 | [self setTabViewBackgroundColor:[CPColor whiteColor]]; 104 | } 105 | 106 | 107 | #pragma mark - 108 | #pragma mark Delegate methods 109 | 110 | - (void)setDelegate:(id )aDelegate 111 | { 112 | if (aDelegate == _delegate) 113 | return; 114 | 115 | _implementedDelegateMethods = 0; 116 | _delegate = aDelegate; 117 | 118 | if ([_delegate respondsToSelector:@selector(tabViewDidChangeNumberOfTabViewItems:)]) 119 | _implementedDelegateMethods |= TNTabViewDelegate_tabViewDidChangeNumberOfTabViewItems_; 120 | 121 | if ([_delegate respondsToSelector:@selector(tabView:willSelectTabViewItem:)]) 122 | _implementedDelegateMethods |= TNTabViewDelegate_tabView_willSelectTabViewItem_; 123 | 124 | if ([_delegate respondsToSelector:@selector(tabView:didSelectTabViewItem:)]) 125 | _implementedDelegateMethods |= TNTabViewDelegate_tabView_didSelectTabViewItem_; 126 | 127 | if ([_delegate respondsToSelector:@selector(tabView:shouldSelectTabViewItem:)]) 128 | _implementedDelegateMethods |= TNTabViewDelegate_tabView_shouldSelectTabViewItem_; 129 | } 130 | 131 | 132 | #pragma mark - 133 | #pragma mark Getters / Setters 134 | 135 | /*! set the background color for the tabs view 136 | @param aColor the color 137 | */ 138 | - (void)setTabViewBackgroundColor:(CPColor)aColor 139 | { 140 | [_viewTabs setBackgroundColor:aColor]; 141 | } 142 | 143 | /*! set the background color for the tabs view 144 | @param aColor the color 145 | */ 146 | - (void)setContentBackgroundColor:(CPColor)aColor 147 | { 148 | [_contentView setBackgroundColor:aColor]; 149 | } 150 | 151 | /*! set the prototype view for items 152 | @param anItemPrototype a subclass of TNTabItemPrototype that represent the prototype 153 | */ 154 | - (void)setTabItemViewPrototype:(TNTabItemPrototype)anItemPrototype 155 | { 156 | _tabItemViewPrototype = anItemPrototype; 157 | } 158 | 159 | /*! @ignore 160 | Returns a new copy of the current item view prototype 161 | @return a new copy of current TNTabItemPrototype 162 | */ 163 | - (void)_newTabItemPrototype 164 | { 165 | return [CPKeyedUnarchiver unarchiveObjectWithData:[CPKeyedArchiver archivedDataWithRootObject:_tabItemViewPrototype]]; 166 | } 167 | 168 | - (void)_getTabItemAtIndex:(int)anIndex 169 | { 170 | if (anIndex == -1 || anIndex >= [_itemObjects count]) 171 | return nil; 172 | 173 | return [_itemObjects[anIndex] tabViewItem]; 174 | } 175 | 176 | - (void)_getTabViewAtIndex:(int)anIndex 177 | { 178 | if (anIndex >= [_itemObjects count]) 179 | return nil; 180 | 181 | return _itemObjects[anIndex]; 182 | } 183 | 184 | 185 | #pragma mark - 186 | #pragma mark Events 187 | 188 | /*! stop the event progation 189 | */ 190 | - (void)mouseDown:(CPEvent)anEvent 191 | { 192 | } 193 | 194 | 195 | #pragma mark - 196 | #pragma mark CPTabView protocol 197 | 198 | /*! implement CPTabViewProtocol 199 | see documentation for CPTabView 200 | */ 201 | - (void)addTabViewItem:(CPTabViewItem)anItem 202 | { 203 | [self insertTabViewItem:anItem atIndex:[_itemObjects count]]; 204 | } 205 | 206 | /*! implement CPTabViewProtocol 207 | see documentation for CPTabView 208 | */ 209 | - (void)insertTabViewItem:(CPTabViewItem)anItem atIndex:(int)anIndex 210 | { 211 | var previousSelectedItem = [self selectedTabViewItem], 212 | itemView = [self _newTabItemPrototype]; 213 | 214 | [itemView setTabViewItem:anItem]; 215 | [itemView setTarget:self]; 216 | [itemView setAction:@selector(_tabItemCliked:)]; 217 | [_itemObjects insertObject:itemView atIndex:anIndex]; 218 | 219 | [self _sendDelegateMethodTabViewDidChangeNumberOfTabViewItems]; 220 | [self setNeedsLayout]; 221 | } 222 | 223 | 224 | /*! implement CPTabViewProtocol 225 | see documentation for CPTabView 226 | */ 227 | - (int)indexOfTabViewItem:(CPTabViewItem)anItem 228 | { 229 | for (var i = 0; i < [_itemObjects count]; i++) 230 | if ([_itemObjects[i] tabViewItem] == anItem) 231 | return i; 232 | 233 | return CPNotFound; 234 | } 235 | 236 | /*! implement CPTabViewProtocol 237 | see documentation for CPTabView 238 | */ 239 | - (int)indexOfTabViewItemWithIdentifier:(CPString)anIdentifer 240 | { 241 | for (var i = 0; i < [_itemObjects count]; i++) 242 | if ([[_itemObjects[i] tabViewItem] identifier] == anIdentifer) 243 | return i; 244 | 245 | return CPNotFound; 246 | } 247 | 248 | /*! implement CPTabViewProtocol 249 | see documentation for CPTabView 250 | */ 251 | - (void)numberOfTabViewItems 252 | { 253 | return [_itemObjects count]; 254 | } 255 | 256 | /*! implement CPTabViewProtocol 257 | see documentation for CPTabView 258 | */ 259 | - (void)removeTabViewItem:(CPTabViewItem)anItem 260 | { 261 | var itemIndex = [self indexOfTabViewItemWithIdentifier:[anItem identifier]], 262 | currentItemView = [self _getTabViewAtIndex:itemIndex], 263 | associatedView = [[currentItemView tabViewItem] view]; 264 | 265 | [associatedView removeFromSuperview]; 266 | 267 | [currentItemView removeFromSuperview]; 268 | [_itemObjects removeObjectAtIndex:[self indexOfTabViewItemWithIdentifier:[anItem identifier]]]; 269 | 270 | [self _sendDelegateMethodTabViewDidChangeNumberOfTabViewItems]; 271 | 272 | [self setNeedsLayout]; 273 | } 274 | 275 | /*! implement CPTabViewProtocol 276 | see documentation for CPTabView 277 | */ 278 | - (void)selectTabViewItem:(CPTabViewItem)anItem 279 | { 280 | [self selectTabViewItemAtIndex:[self indexOfTabViewItem:anItem]]; 281 | } 282 | 283 | /*! implement CPTabViewProtocol 284 | see documentation for CPTabView 285 | */ 286 | - (void)selectedTabViewItem 287 | { 288 | return [self _getTabItemAtIndex:_currentSelectedIndex]; 289 | } 290 | 291 | /*! implement CPTabViewProtocol 292 | see documentation for CPTabView 293 | */ 294 | - (void)selectedTabViewIndex 295 | { 296 | return _currentSelectedIndex; 297 | } 298 | 299 | /*! implement CPTabViewProtocol 300 | see documentation for CPTabView 301 | */ 302 | - (void)selectFirstTabViewItem:(id)aSender 303 | { 304 | [self selectTabViewItemAtIndex:0]; 305 | } 306 | 307 | /*! implement CPTabViewProtocol 308 | see documentation for CPTabView 309 | */ 310 | - (void)selectLastTabViewItem:(id)aSender 311 | { 312 | [self selectTabViewItemAtIndex:[_itemObjects count] - 1]; 313 | } 314 | 315 | /*! implement CPTabViewProtocol 316 | see documentation for CPTabView 317 | */ 318 | - (void)selectNextTabViewItem:(id)aSender 319 | { 320 | var nextIndex = _currentSelectedIndex + 1; 321 | 322 | if (nextIndex > [self numberOfTabViewItems] - 1) 323 | nextIndex = 0; 324 | 325 | [self selectTabViewItemAtIndex:nextIndex]; 326 | } 327 | 328 | /*! implement CPTabViewProtocol 329 | see documentation for CPTabView 330 | */ 331 | - (void)selectPreviousTabViewItem:(id)aSender 332 | { 333 | var nextIndex = _currentSelectedIndex - 1; 334 | 335 | if (nextIndex < 0) 336 | nextIndex = [self numberOfTabViewItems] - 1; 337 | 338 | [self selectTabViewItemAtIndex:nextIndex]; 339 | } 340 | 341 | /*! implement CPTabViewProtocol 342 | see documentation for CPTabView 343 | */ 344 | - (void)selectTabViewItemAtIndex:(int)anIndex 345 | { 346 | var pendingItem = [self _getTabItemAtIndex:anIndex]; 347 | 348 | if (![self _sendDelegateMethodShouldSelectTabViewItem:pendingItem]) 349 | return; 350 | 351 | [self _sendDelegateMethodWillSelectTabViewItem:pendingItem]; 352 | 353 | [[[self selectedTabViewItem] view] removeFromSuperview]; 354 | 355 | [_currentSelectedItemView unsetThemeState:TNTabItemPrototypeThemeStateSelected]; 356 | _currentSelectedItemView = [self _getTabViewAtIndex:anIndex]; 357 | [_currentSelectedItemView setThemeState:TNTabItemPrototypeThemeStateSelected]; 358 | 359 | if (pendingItem) 360 | { 361 | [[pendingItem view] setFrame:[_contentView bounds]]; 362 | [_contentView addSubview:[pendingItem view]]; 363 | } 364 | 365 | _currentSelectedIndex = anIndex; 366 | 367 | [self _sendDelegateMethodDidSelectTabViewItem:pendingItem]; 368 | } 369 | 370 | /*! implement CPTabViewProtocol 371 | see documentation for CPTabView 372 | */ 373 | - (CPTabViewItem)tabViewItemAtIndex:(int)anIndex 374 | { 375 | return [self _getTabItemAtIndex:anIndex]; 376 | } 377 | 378 | /*! implement CPTabViewProtocol 379 | see documentation for CPTabView 380 | */ 381 | - (CPArray)tabViewItems 382 | { 383 | var ret = [CPArray array]; 384 | 385 | for (var i = 0; i < [_itemObjects count]; i++) 386 | [ret addObject:[_itemObjects[i] tabViewItem]]; 387 | 388 | return ret; 389 | } 390 | 391 | #pragma mark - 392 | #pragma mark Responder Chain 393 | 394 | - (BOOL)acceptsFirstResponder 395 | { 396 | return YES; 397 | } 398 | 399 | - (void)keyUp:(CPEvent)anEvent 400 | { 401 | var keyCode = [anEvent keyCode]; 402 | 403 | if (keyCode == CPLeftArrowKeyCode) 404 | [self selectPreviousTabViewItem:self]; 405 | else if (keyCode == CPRightArrowKeyCode) 406 | [self selectNextTabViewItem:self]; 407 | else 408 | [super keyUp:anEvent]; 409 | } 410 | 411 | 412 | #pragma mark - 413 | #pragma mark Layouting 414 | 415 | /*! layout the TNTabView 416 | */ 417 | - (void)layoutSubviews 418 | { 419 | var tabSize = [_viewTabs frameSize].width, 420 | margin = [_tabItemViewPrototype margin], 421 | totalSize = 0, 422 | widths = [], 423 | shouldAddPopoverButton = NO; 424 | 425 | for (var i = 0; i < [_itemObjects count]; i++) 426 | { 427 | var item = _itemObjects[i], 428 | width = [item width]; 429 | 430 | [widths addObject:width]; 431 | totalSize += width + margin; 432 | } 433 | 434 | while (totalSize >= tabSize) 435 | { 436 | margin -= 3; 437 | totalSize -= [widths count] * 3; 438 | } 439 | 440 | var currentXOrigin = tabSize / 2 - totalSize / 2 + margin / 2; 441 | 442 | for (var i = 0; i < [widths count]; i++) 443 | { 444 | var itemView = _itemObjects[i], 445 | width = widths[i], 446 | itemFrame = [itemView frame], 447 | tabContentView = [[itemView tabViewItem] view]; 448 | 449 | itemFrame.size.width = width; 450 | itemFrame.origin.x = currentXOrigin; 451 | itemFrame.origin.y = [_viewTabs frameSize].height / 2 - itemFrame.size.height / 2; 452 | 453 | [itemView setIndex:i]; 454 | [itemView setFrame:itemFrame]; 455 | 456 | [tabContentView setFrame:[_contentView bounds]]; 457 | [self addSubview:itemView]; 458 | 459 | currentXOrigin += width + margin; 460 | } 461 | } 462 | 463 | 464 | #pragma mark - 465 | #pragma mark Actions 466 | 467 | /*! @ignore 468 | */ 469 | - (@action)_tabItemCliked:(id)aSender 470 | { 471 | [[self window] makeFirstResponder:self]; 472 | 473 | if (_currentSelectedIndex == [aSender index]) 474 | return; 475 | 476 | [self selectTabViewItemAtIndex:[aSender index]]; 477 | } 478 | 479 | @end 480 | 481 | var TNTabViewDelegateKey = @"TNTabViewDelegateKey", 482 | TNTabViewItemObjectsKey = @"TNTabViewItemObjectsKey", 483 | TNTabViewCurrentSelectedItemViewKey = @"TNTabViewCurrentSelectedItemViewKey", 484 | TNTabViewViewTabsKey = @"TNTabViewViewTabsKey", 485 | TNTabViewCurrentSelectedIndexKey = @"TNTabViewCurrentSelectedIndexKey", 486 | TNTabViewContentViewKey = @"TNTabViewContentViewKey", 487 | TNTabViewtTabItemViewPrototypeKey = @"TNTabViewtTabItemViewPrototypeKey", 488 | TNTabViewtNextTabItemViewPrototypeKey = @"TNTabViewtNextTabItemViewPrototypeKey", 489 | TNTabViewtTabItemTableViewPrototypeKey = @"TNTabViewtTabItemTableViewPrototypeKey"; 490 | 491 | @implementation TNTabView (TNTabViewCoding) 492 | 493 | - (id)initWithCoder:(CPCoder)aCoder 494 | { 495 | if (self = [super initWithCoder:aCoder]) 496 | { 497 | [self _init]; 498 | 499 | _delegate = [aCoder decodeObjectForKey:TNTabViewDelegateKey] || _delegate; 500 | _itemObjects = [aCoder decodeObjectForKey:TNTabViewItemObjectsKey] || _itemObjects; 501 | _currentSelectedItemView = [aCoder decodeObjectForKey:TNTabViewCurrentSelectedItemViewKey] || _currentSelectedItemView; 502 | _viewTabs = [aCoder decodeObjectForKey:TNTabViewViewTabsKey] || _viewTabs; 503 | _currentSelectedIndex = [aCoder decodeObjectForKey:TNTabViewCurrentSelectedIndexKey] || _currentSelectedIndex; 504 | _contentView = [aCoder decodeObjectForKey:TNTabViewContentViewKey] || _contentView; 505 | _tabItemViewPrototype = [aCoder decodeObjectForKey:TNTabViewtTabItemViewPrototypeKey] || _tabItemViewPrototype; 506 | } 507 | 508 | return self; 509 | } 510 | 511 | - (void)encodeWithCoder:(CPCoder)aCoder 512 | { 513 | [super encodeWithCoder:aCoder]; 514 | 515 | [aCoder encodeObject:_delegate forKey:TNTabViewDelegateKey]; 516 | [aCoder encodeObject:_itemObjects forKey:TNTabViewItemObjectsKey]; 517 | [aCoder encodeObject:_currentSelectedItemView forKey:TNTabViewCurrentSelectedItemViewKey]; 518 | [aCoder encodeObject:_viewTabs forKey:TNTabViewViewTabsKey]; 519 | [aCoder encodeObject:_currentSelectedIndex forKey:TNTabViewCurrentSelectedIndexKey]; 520 | [aCoder encodeObject:_contentView forKey:TNTabViewContentViewKey]; 521 | [aCoder encodeObject:_tabItemViewPrototype forKey:TNTabViewtTabItemViewPrototypeKey]; 522 | } 523 | 524 | @end 525 | 526 | 527 | @implementation TNTabView (TNTabViewDelegate) 528 | 529 | - (BOOL)_sendDelegateMethodShouldSelectTabViewItem:(CPTabViewItem)aTabViewItem 530 | { 531 | if (!(_implementedDelegateMethods & TNTabViewDelegate_tabView_shouldSelectTabViewItem_)) 532 | return YES; 533 | 534 | return [_delegate tabView:self shouldSelectTabViewItem:aTabViewItem]; 535 | } 536 | 537 | - (void)_sendDelegateMethodDidSelectTabViewItem:(CPTabViewItem)aTabViewItem 538 | { 539 | if (!(_implementedDelegateMethods & TNTabViewDelegate_tabView_didSelectTabViewItem_)) 540 | return; 541 | 542 | return [_delegate tabView:self didSelectTabViewItem:aTabViewItem]; 543 | } 544 | 545 | - (void)_sendDelegateMethodWillSelectTabViewItem:(CPTabViewItem)aTabViewItem 546 | { 547 | if (!(_implementedDelegateMethods & TNTabViewDelegate_tabView_willSelectTabViewItem_)) 548 | return; 549 | 550 | [_delegate tabView:self willSelectTabViewItem:aTabViewItem]; 551 | } 552 | 553 | - (void)_sendDelegateMethodTabViewDidChangeNumberOfTabViewItems 554 | { 555 | if (!(_implementedDelegateMethods & TNTabViewDelegate_tabViewDidChangeNumberOfTabViewItems_)) 556 | return; 557 | 558 | [_delegate tabViewDidChangeNumberOfTabViewItems:self]; 559 | } 560 | 561 | @end 562 | -------------------------------------------------------------------------------- /TNMessageView.j: -------------------------------------------------------------------------------- 1 | /* 2 | * TNMessageView.j 3 | * 4 | * Copyright (C) 2010 Antoine Mercadal 5 | * 6 | * This library is free software; you can redistribute it and/or 7 | * modify it under the terms of the GNU Lesser General Public 8 | * License as published by the Free Software Foundation; either 9 | * version 3.0 of the License, or (at your option) any later version. 10 | * 11 | * This library is distributed in the hope that it will be useful, 12 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 13 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 14 | * Lesser General Public License for more details. 15 | * 16 | * You should have received a copy of the GNU Lesser General Public 17 | * License along with this library; if not, write to the Free Software 18 | * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA 19 | */ 20 | 21 | @import 22 | 23 | @import 24 | @import 25 | @import 26 | @import 27 | @import 28 | 29 | 30 | TNMessageViewAvatarPositionRight = @"TNMessageViewAvatarPositionRight"; 31 | TNMessageViewAvatarPositionLeft = @"TNMessageViewAvatarPositionLeft"; 32 | 33 | TNMessageViewBubbleColorNormal = 1; 34 | TNMessageViewBubbleColorAlt = 2; 35 | TNMessageViewBubbleColorNotice = 3; 36 | 37 | var TNMessageViewBackgroundColorLeftNormal, 38 | TNMessageViewBackgroundColorLeftAlt, 39 | TNMessageViewBackgroundColorLeftNotice, 40 | TNMessageViewBackgroundColorRightNormal, 41 | TNMessageViewBackgroundColorRightAlt, 42 | TNMessageViewBackgroundColorRightNotice; 43 | 44 | 45 | /*! @ingroup messageboard 46 | CPView that contains information to display chat information 47 | */ 48 | @implementation TNMessageView : CPView 49 | { 50 | CPImage _imageDefaultAvatar; 51 | CPImageView _imageViewAvatar; 52 | CPString _author; 53 | CPString _message; 54 | CPString _subject; 55 | CPString _timestamp; 56 | CPTextField _fieldAuthor; 57 | CPTextField _fieldTimestamp; 58 | CPView _viewContainer; 59 | int _bgColor; 60 | int _position; 61 | CPTextField _fieldMessage; 62 | } 63 | 64 | 65 | #pragma mark - 66 | #pragma mark Class methods 67 | 68 | + (void)initialize 69 | { 70 | TNMessageViewBackgroundColorLeftNormal = [CPColor colorWithPatternImage:[[CPNinePartImage alloc] initWithImageSlices:[ 71 | [[CPImage alloc] initWithContentsOfFile:[[CPBundle bundleForClass:TNMessageView] pathForResource:@"TNMessageBoard/Bubble/1.png"] size:CGSizeMake(24.0, 14.0)], 72 | [[CPImage alloc] initWithContentsOfFile:[[CPBundle bundleForClass:TNMessageView] pathForResource:@"TNMessageBoard/Bubble/2.png"] size:CGSizeMake(1.0, 14.0)], 73 | [[CPImage alloc] initWithContentsOfFile:[[CPBundle bundleForClass:TNMessageView] pathForResource:@"TNMessageBoard/Bubble/3.png"] size:CGSizeMake(24.0, 14.0)], 74 | [[CPImage alloc] initWithContentsOfFile:[[CPBundle bundleForClass:TNMessageView] pathForResource:@"TNMessageBoard/Bubble/4.png"] size:CGSizeMake(24.0, 1.0)], 75 | [[CPImage alloc] initWithContentsOfFile:[[CPBundle bundleForClass:TNMessageView] pathForResource:@"TNMessageBoard/Bubble/5.png"] size:CGSizeMake(1.0, 1.0)], 76 | [[CPImage alloc] initWithContentsOfFile:[[CPBundle bundleForClass:TNMessageView] pathForResource:@"TNMessageBoard/Bubble/6.png"] size:CGSizeMake(24.0, 1.0)], 77 | [[CPImage alloc] initWithContentsOfFile:[[CPBundle bundleForClass:TNMessageView] pathForResource:@"TNMessageBoard/Bubble/7.png"] size:CGSizeMake(24.0, 16.0)], 78 | [[CPImage alloc] initWithContentsOfFile:[[CPBundle bundleForClass:TNMessageView] pathForResource:@"TNMessageBoard/Bubble/8.png"] size:CGSizeMake(1.0, 16.0)], 79 | [[CPImage alloc] initWithContentsOfFile:[[CPBundle bundleForClass:TNMessageView] pathForResource:@"TNMessageBoard/Bubble/9.png"] size:CGSizeMake(24.0, 16.0)], 80 | ]]]; 81 | TNMessageViewBackgroundColorLeftAlt = [CPColor colorWithPatternImage:[[CPNinePartImage alloc] initWithImageSlices:[ 82 | [[CPImage alloc] initWithContentsOfFile:[[CPBundle bundleForClass:TNMessageView] pathForResource:@"TNMessageBoard/BubbleAlt/1.png"] size:CGSizeMake(24.0, 14.0)], 83 | [[CPImage alloc] initWithContentsOfFile:[[CPBundle bundleForClass:TNMessageView] pathForResource:@"TNMessageBoard/BubbleAlt/2.png"] size:CGSizeMake(1.0, 14.0)], 84 | [[CPImage alloc] initWithContentsOfFile:[[CPBundle bundleForClass:TNMessageView] pathForResource:@"TNMessageBoard/BubbleAlt/3.png"] size:CGSizeMake(24.0, 14.0)], 85 | [[CPImage alloc] initWithContentsOfFile:[[CPBundle bundleForClass:TNMessageView] pathForResource:@"TNMessageBoard/BubbleAlt/4.png"] size:CGSizeMake(24.0, 1.0)], 86 | [[CPImage alloc] initWithContentsOfFile:[[CPBundle bundleForClass:TNMessageView] pathForResource:@"TNMessageBoard/BubbleAlt/5.png"] size:CGSizeMake(1.0, 1.0)], 87 | [[CPImage alloc] initWithContentsOfFile:[[CPBundle bundleForClass:TNMessageView] pathForResource:@"TNMessageBoard/BubbleAlt/6.png"] size:CGSizeMake(24.0, 1.0)], 88 | [[CPImage alloc] initWithContentsOfFile:[[CPBundle bundleForClass:TNMessageView] pathForResource:@"TNMessageBoard/BubbleAlt/7.png"] size:CGSizeMake(24.0, 16.0)], 89 | [[CPImage alloc] initWithContentsOfFile:[[CPBundle bundleForClass:TNMessageView] pathForResource:@"TNMessageBoard/BubbleAlt/8.png"] size:CGSizeMake(1.0, 16.0)], 90 | [[CPImage alloc] initWithContentsOfFile:[[CPBundle bundleForClass:TNMessageView] pathForResource:@"TNMessageBoard/BubbleAlt/9.png"] size:CGSizeMake(24.0, 16.0)], 91 | ]]]; 92 | TNMessageViewBackgroundColorLeftNotice = [CPColor colorWithPatternImage:[[CPNinePartImage alloc] initWithImageSlices:[ 93 | [[CPImage alloc] initWithContentsOfFile:[[CPBundle bundleForClass:TNMessageView] pathForResource:@"TNMessageBoard/BubbleNotice/1.png"] size:CGSizeMake(24.0, 14.0)], 94 | [[CPImage alloc] initWithContentsOfFile:[[CPBundle bundleForClass:TNMessageView] pathForResource:@"TNMessageBoard/BubbleNotice/2.png"] size:CGSizeMake(1.0, 14.0)], 95 | [[CPImage alloc] initWithContentsOfFile:[[CPBundle bundleForClass:TNMessageView] pathForResource:@"TNMessageBoard/BubbleNotice/3.png"] size:CGSizeMake(24.0, 14.0)], 96 | [[CPImage alloc] initWithContentsOfFile:[[CPBundle bundleForClass:TNMessageView] pathForResource:@"TNMessageBoard/BubbleNotice/4.png"] size:CGSizeMake(24.0, 1.0)], 97 | [[CPImage alloc] initWithContentsOfFile:[[CPBundle bundleForClass:TNMessageView] pathForResource:@"TNMessageBoard/BubbleNotice/5.png"] size:CGSizeMake(1.0, 1.0)], 98 | [[CPImage alloc] initWithContentsOfFile:[[CPBundle bundleForClass:TNMessageView] pathForResource:@"TNMessageBoard/BubbleNotice/6.png"] size:CGSizeMake(24.0, 1.0)], 99 | [[CPImage alloc] initWithContentsOfFile:[[CPBundle bundleForClass:TNMessageView] pathForResource:@"TNMessageBoard/BubbleNotice/7.png"] size:CGSizeMake(24.0, 16.0)], 100 | [[CPImage alloc] initWithContentsOfFile:[[CPBundle bundleForClass:TNMessageView] pathForResource:@"TNMessageBoard/BubbleNotice/8.png"] size:CGSizeMake(1.0, 16.0)], 101 | [[CPImage alloc] initWithContentsOfFile:[[CPBundle bundleForClass:TNMessageView] pathForResource:@"TNMessageBoard/BubbleNotice/9.png"] size:CGSizeMake(24.0, 16.0)], 102 | ]]]; 103 | TNMessageViewBackgroundColorRightNormal = [CPColor colorWithPatternImage:[[CPNinePartImage alloc] initWithImageSlices:[ 104 | [[CPImage alloc] initWithContentsOfFile:[[CPBundle bundleForClass:TNMessageView] pathForResource:@"TNMessageBoard/Bubble/1.png"] size:CGSizeMake(24.0, 14.0)], 105 | [[CPImage alloc] initWithContentsOfFile:[[CPBundle bundleForClass:TNMessageView] pathForResource:@"TNMessageBoard/Bubble/2.png"] size:CGSizeMake(1.0, 14.0)], 106 | [[CPImage alloc] initWithContentsOfFile:[[CPBundle bundleForClass:TNMessageView] pathForResource:@"TNMessageBoard/Bubble/3.png"] size:CGSizeMake(24.0, 14.0)], 107 | [[CPImage alloc] initWithContentsOfFile:[[CPBundle bundleForClass:TNMessageView] pathForResource:@"TNMessageBoard/Bubble/4.png"] size:CGSizeMake(24.0, 1.0)], 108 | [[CPImage alloc] initWithContentsOfFile:[[CPBundle bundleForClass:TNMessageView] pathForResource:@"TNMessageBoard/Bubble/5.png"] size:CGSizeMake(1.0, 1.0)], 109 | [[CPImage alloc] initWithContentsOfFile:[[CPBundle bundleForClass:TNMessageView] pathForResource:@"TNMessageBoard/Bubble/6.png"] size:CGSizeMake(24.0, 1.0)], 110 | [[CPImage alloc] initWithContentsOfFile:[[CPBundle bundleForClass:TNMessageView] pathForResource:@"TNMessageBoard/Bubble/7-alt.png"] size:CGSizeMake(24.0, 16.0)], 111 | [[CPImage alloc] initWithContentsOfFile:[[CPBundle bundleForClass:TNMessageView] pathForResource:@"TNMessageBoard/Bubble/8.png"] size:CGSizeMake(1.0, 16.0)], 112 | [[CPImage alloc] initWithContentsOfFile:[[CPBundle bundleForClass:TNMessageView] pathForResource:@"TNMessageBoard/Bubble/9-alt.png"] size:CGSizeMake(24.0, 16.0)], 113 | ]]]; 114 | TNMessageViewBackgroundColorRightAlt = [CPColor colorWithPatternImage:[[CPNinePartImage alloc] initWithImageSlices:[ 115 | [[CPImage alloc] initWithContentsOfFile:[[CPBundle bundleForClass:TNMessageView] pathForResource:@"TNMessageBoard/BubbleAlt/1.png"] size:CGSizeMake(24.0, 14.0)], 116 | [[CPImage alloc] initWithContentsOfFile:[[CPBundle bundleForClass:TNMessageView] pathForResource:@"TNMessageBoard/BubbleAlt/2.png"] size:CGSizeMake(1.0, 14.0)], 117 | [[CPImage alloc] initWithContentsOfFile:[[CPBundle bundleForClass:TNMessageView] pathForResource:@"TNMessageBoard/BubbleAlt/3.png"] size:CGSizeMake(24.0, 14.0)], 118 | [[CPImage alloc] initWithContentsOfFile:[[CPBundle bundleForClass:TNMessageView] pathForResource:@"TNMessageBoard/BubbleAlt/4.png"] size:CGSizeMake(24.0, 1.0)], 119 | [[CPImage alloc] initWithContentsOfFile:[[CPBundle bundleForClass:TNMessageView] pathForResource:@"TNMessageBoard/BubbleAlt/5.png"] size:CGSizeMake(1.0, 1.0)], 120 | [[CPImage alloc] initWithContentsOfFile:[[CPBundle bundleForClass:TNMessageView] pathForResource:@"TNMessageBoard/BubbleAlt/6.png"] size:CGSizeMake(24.0, 1.0)], 121 | [[CPImage alloc] initWithContentsOfFile:[[CPBundle bundleForClass:TNMessageView] pathForResource:@"TNMessageBoard/BubbleAlt/7-alt.png"] size:CGSizeMake(24.0, 16.0)], 122 | [[CPImage alloc] initWithContentsOfFile:[[CPBundle bundleForClass:TNMessageView] pathForResource:@"TNMessageBoard/BubbleAlt/8.png"] size:CGSizeMake(1.0, 16.0)], 123 | [[CPImage alloc] initWithContentsOfFile:[[CPBundle bundleForClass:TNMessageView] pathForResource:@"TNMessageBoard/BubbleAlt/9-alt.png"] size:CGSizeMake(24.0, 16.0)], 124 | ]]]; 125 | TNMessageViewBackgroundColorRightNotice = [CPColor colorWithPatternImage:[[CPNinePartImage alloc] initWithImageSlices:[ 126 | [[CPImage alloc] initWithContentsOfFile:[[CPBundle bundleForClass:TNMessageView] pathForResource:@"TNMessageBoard/BubbleNotice/1.png"] size:CGSizeMake(24.0, 14.0)], 127 | [[CPImage alloc] initWithContentsOfFile:[[CPBundle bundleForClass:TNMessageView] pathForResource:@"TNMessageBoard/BubbleNotice/2.png"] size:CGSizeMake(1.0, 14.0)], 128 | [[CPImage alloc] initWithContentsOfFile:[[CPBundle bundleForClass:TNMessageView] pathForResource:@"TNMessageBoard/BubbleNotice/3.png"] size:CGSizeMake(24.0, 14.0)], 129 | [[CPImage alloc] initWithContentsOfFile:[[CPBundle bundleForClass:TNMessageView] pathForResource:@"TNMessageBoard/BubbleNotice/4.png"] size:CGSizeMake(24.0, 1.0)], 130 | [[CPImage alloc] initWithContentsOfFile:[[CPBundle bundleForClass:TNMessageView] pathForResource:@"TNMessageBoard/BubbleNotice/5.png"] size:CGSizeMake(1.0, 1.0)], 131 | [[CPImage alloc] initWithContentsOfFile:[[CPBundle bundleForClass:TNMessageView] pathForResource:@"TNMessageBoard/BubbleNotice/6.png"] size:CGSizeMake(24.0, 1.0)], 132 | [[CPImage alloc] initWithContentsOfFile:[[CPBundle bundleForClass:TNMessageView] pathForResource:@"TNMessageBoard/BubbleNotice/7-alt.png"] size:CGSizeMake(24.0, 16.0)], 133 | [[CPImage alloc] initWithContentsOfFile:[[CPBundle bundleForClass:TNMessageView] pathForResource:@"TNMessageBoard/BubbleNotice/8.png"] size:CGSizeMake(1.0, 16.0)], 134 | [[CPImage alloc] initWithContentsOfFile:[[CPBundle bundleForClass:TNMessageView] pathForResource:@"TNMessageBoard/BubbleNotice/9-alt.png"] size:CGSizeMake(24.0, 16.0)], 135 | ]]]; 136 | } 137 | 138 | /*! this class method return the height of a TNMessageView with given text in given width 139 | @param aText the text 140 | @param aWidth the width 141 | @return int value representing the height 142 | */ 143 | + (int)sizeOfMessageViewWithText:(CPString)aText inWidth:(int)aWidth 144 | { 145 | var messageHeight = [aText sizeWithFont:[CPFont systemFontOfSize:12] inWidth:(aWidth - 100)]; 146 | return messageHeight.height + 65; 147 | } 148 | 149 | 150 | #pragma mark - 151 | #pragma mark Initialization 152 | 153 | /*! instanciate a TNMessageView 154 | @param aFrame the frame of the view 155 | @return initialized view 156 | */ 157 | - (id)initWithFrame:(CGRect)aFrame 158 | { 159 | if (self = [super initWithFrame:aFrame]) 160 | { 161 | _author = @""; 162 | _subject = @""; 163 | _message = @""; 164 | _timestamp = @""; 165 | _bgColor = TNMessageViewBubbleColorNormal; 166 | 167 | [self setAutoresizingMask:CPViewWidthSizable]; 168 | 169 | var bundle = [CPBundle bundleForClass:[self class]]; 170 | 171 | _imageDefaultAvatar = [[CPImage alloc] initWithContentsOfFile:[bundle pathForResource:@"TNMessageBoard/user-unknown.png"] size:CGSizeMake(36, 36)]; 172 | 173 | _position = TNMessageViewAvatarPositionLeft; 174 | _viewContainer = [[CPView alloc] initWithFrame:CGRectMake(50, 10, CGRectGetWidth(aFrame) - 60, 80)] 175 | [_viewContainer setAutoresizingMask:CPViewWidthSizable | CPViewHeightSizable]; 176 | 177 | _imageViewAvatar = [[CPImageView alloc] initWithFrame:CGRectMake(6, CGRectGetHeight(aFrame) - 46, 36, 36)]; 178 | [_imageViewAvatar setImageScaling:CPScaleProportionally]; 179 | [_imageViewAvatar setImage:_imageDefaultAvatar]; 180 | 181 | _fieldAuthor = [[CPTextField alloc] initWithFrame:CGRectMake(20, 10, CGRectGetWidth([_viewContainer frame]) - 30, 20)]; 182 | [_fieldAuthor setFont:[CPFont boldSystemFontOfSize:12]]; 183 | [_fieldAuthor setTextColor:[CPColor grayColor]]; 184 | [_fieldAuthor setAutoresizingMask:CPViewWidthSizable]; 185 | [_fieldAuthor setSelectable:YES]; 186 | [_fieldAuthor setEditable:NO]; 187 | 188 | _fieldMessage = [[CPTextField alloc] initWithFrame:CGRectMake(20, 30, CGRectGetWidth([_viewContainer frame]) - 40 , CGRectGetHeight([_viewContainer frame]))]; 189 | [_fieldMessage setAutoresizingMask:CPViewWidthSizable | CPViewHeightSizable]; 190 | [_fieldMessage setLineBreakMode:CPLineBreakByWordWrapping]; 191 | [_fieldMessage setSelectable:YES]; 192 | [_fieldMessage setEditable:NO]; 193 | 194 | _fieldTimestamp = [[CPTextField alloc] initWithFrame:CGRectMake(CGRectGetWidth([_viewContainer frame]) - 210, 10, 190, 20)]; 195 | [_fieldTimestamp setAutoresizingMask:CPViewMinXMargin]; 196 | [_fieldTimestamp setValue:[CPColor colorWithHexString:@"f2f0e4"] forThemeAttribute:@"text-shadow-color" inState:CPThemeStateNormal]; 197 | [_fieldTimestamp setValue:[CPFont systemFontOfSize:9.0] forThemeAttribute:@"font" inState:CPThemeStateNormal]; 198 | [_fieldTimestamp setValue:[CPColor colorWithHexString:@"808080"] forThemeAttribute:@"text-color" inState:CPThemeStateNormal]; 199 | [_fieldTimestamp setAlignment:CPRightTextAlignment]; 200 | [_fieldTimestamp setSelectable:YES]; 201 | [_fieldTimestamp setEditable:NO]; 202 | 203 | [_viewContainer addSubview:_fieldAuthor]; 204 | [_viewContainer addSubview:_fieldMessage]; 205 | [_viewContainer addSubview:_fieldTimestamp]; 206 | 207 | [self addSubview:_imageViewAvatar]; 208 | [self addSubview:_viewContainer]; 209 | } 210 | 211 | return self; 212 | } 213 | 214 | 215 | #pragma mark - 216 | #pragma mark CPTableView protocol 217 | 218 | /*! CPTableView dataview protocol 219 | */ 220 | - (void)setObjectValue:(id)anObject 221 | { 222 | [_fieldAuthor setStringValue:[anObject objectForKey:@"author"]]; 223 | [_fieldMessage setStringValue:[anObject objectForKey:@"message"]]; 224 | [_fieldTimestamp setStringValue:[anObject objectForKey:@"date"]]; 225 | [_imageViewAvatar setImage:[anObject objectForKey:@"avatar"] || _imageDefaultAvatar]; 226 | 227 | _position = [anObject objectForKey:@"position"] || TNMessageViewAvatarPositionLeft; 228 | _bgColor = [anObject objectForKey:@"color"] || TNMessageViewBubbleColorNormal; 229 | 230 | CPLog.debug(anObject); 231 | [self layout]; 232 | } 233 | 234 | 235 | #pragma mark - 236 | #pragma mark Drawing 237 | 238 | /*! called by TNStackView. This will resize the content of the message's CPTextField in heigth 239 | according to it's size its own frame to display this field. 240 | */ 241 | - (void)layout 242 | { 243 | switch (_position) 244 | { 245 | case TNMessageViewAvatarPositionLeft: 246 | [_viewContainer setFrameOrigin:CGPointMake(50, 10)]; 247 | [_imageViewAvatar setFrame:CGRectMake(6, CGRectGetHeight([self frame]) - 46, 36, 36)]; 248 | [_imageViewAvatar setAutoresizingMask:CPViewMinYMargin]; 249 | break; 250 | 251 | case TNMessageViewAvatarPositionRight: 252 | [_viewContainer setFrameOrigin:CGPointMake(10, 10)]; 253 | [_imageViewAvatar setFrame:CGRectMake(CGRectGetWidth([self frame]) - 46, CGRectGetHeight([self frame]) - 46 , 36, 36)]; 254 | [_imageViewAvatar setAutoresizingMask:CPViewMinXMargin | CPViewMinYMargin]; 255 | break; 256 | } 257 | 258 | switch (_bgColor) 259 | { 260 | case TNMessageViewBubbleColorNormal: 261 | [_viewContainer setBackgroundColor:(_position == TNMessageViewAvatarPositionLeft) ? TNMessageViewBackgroundColorLeftNormal : TNMessageViewBackgroundColorRightNormal]; 262 | break; 263 | 264 | case TNMessageViewBubbleColorAlt: 265 | [_viewContainer setBackgroundColor:(_position == TNMessageViewAvatarPositionLeft) ? TNMessageViewBackgroundColorLeftAlt : TNMessageViewBackgroundColorRightAlt]; 266 | break; 267 | 268 | case TNMessageViewBubbleColorNotice: 269 | [_viewContainer setBackgroundColor:(_position == TNMessageViewAvatarPositionLeft) ? TNMessageViewBackgroundColorLeftNotice : TNMessageViewBackgroundColorRightNotice]; 270 | break; 271 | } 272 | } 273 | 274 | @end 275 | 276 | 277 | 278 | @implementation TNMessageView (CPCoding) 279 | 280 | /*! CPCoder compliance 281 | */ 282 | - (id)initWithCoder:(CPCoder)aCoder 283 | { 284 | if (self = [super initWithCoder:aCoder]) 285 | { 286 | _author = [aCoder decodeObjectForKey:@"_author"]; 287 | _bgColor = [aCoder decodeObjectForKey:@"_bgColor"]; 288 | _fieldAuthor = [aCoder decodeObjectForKey:@"_fieldAuthor"]; 289 | _fieldMessage = [aCoder decodeObjectForKey:@"_fieldMessage"]; 290 | _fieldTimestamp = [aCoder decodeObjectForKey:@"_fieldTimestamp"]; 291 | _imageDefaultAvatar = [aCoder decodeObjectForKey:@"_imageDefaultAvatar"]; 292 | _imageViewAvatar = [aCoder decodeObjectForKey:@"_imageViewAvatar"]; 293 | _message = [aCoder decodeObjectForKey:@"_message"]; 294 | _position = [aCoder decodeObjectForKey:@"_position"]; 295 | _subject = [aCoder decodeObjectForKey:@"_subject"]; 296 | _timestamp = [aCoder decodeObjectForKey:@"_timestamp"]; 297 | _viewContainer = [aCoder decodeObjectForKey:@"_viewContainer"]; 298 | } 299 | 300 | return self; 301 | } 302 | 303 | /*! CPCoder compliance 304 | */ 305 | - (void)encodeWithCoder:(CPCoder)aCoder 306 | { 307 | [super encodeWithCoder:aCoder]; 308 | [aCoder encodeObject:_author forKey:@"_author"]; 309 | [aCoder encodeObject:_bgColor forKey:@"_bgColor"]; 310 | [aCoder encodeObject:_fieldAuthor forKey:@"_fieldAuthor"]; 311 | [aCoder encodeObject:_fieldMessage forKey:@"_fieldMessage"]; 312 | [aCoder encodeObject:_fieldTimestamp forKey:@"_fieldTimestamp"]; 313 | [aCoder encodeObject:_imageDefaultAvatar forKey:@"_imageDefaultAvatar"]; 314 | [aCoder encodeObject:_imageViewAvatar forKey:@"_imageViewAvatar"]; 315 | [aCoder encodeObject:_message forKey:@"_message"]; 316 | [aCoder encodeObject:_position forKey:@"_position"]; 317 | [aCoder encodeObject:_subject forKey:@"_subject"]; 318 | [aCoder encodeObject:_timestamp forKey:@"_timestamp"]; 319 | [aCoder encodeObject:_viewContainer forKey:@"_viewContainer"]; 320 | } 321 | 322 | @end 323 | --------------------------------------------------------------------------------