adium/adium
Clone
Summary
Browse
Changes
Graph
Update build.sh to find the 10.6 SDK with Xcode 4.3. However, hardcode CC to gcc (from MacPorts).
2012-05-08, Thijs Alkemade
67a03e758cf8
Update build.sh to find the 10.6 SDK with Xcode 4.3. However, hardcode CC to gcc (from MacPorts).
Building at least GLib with clang never worked right for me.
/*
* Adium is the legal property of its developers, whose names are listed in the copyright file included
* with this source distribution.
*
* This program is free software; you can redistribute it and/or modify it under the terms of the GNU
* General Public License as published by the Free Software Foundation; either version 2 of the License,
* or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even
* the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General
* Public License for more details.
*
* You should have received a copy of the GNU General Public License along with this program; if not,
* write to the Free Software Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
*/
#import "AIListWindowController.h"
#import "AISCLViewPlugin.h"
#import <Adium/AIListOutlineView.h>
#import <Adium/AIChatControllerProtocol.h>
#import <Adium/AIAccountControllerProtocol.h>
#import <Adium/AIInterfaceControllerProtocol.h>
#import <Adium/AIDockControllerProtocol.h>
#import <AIUtilities/AIWindowAdditions.h>
#import <AIUtilities/AIFunctions.h>
#import <AIUtilities/AIWindowControllerAdditions.h>
#import <AIUtilities/AIImageAdditions.h>
#import <AIUtilities/AIOutlineViewAdditions.h>
#import <Adium/AIListBookmark.h>
#import <Adium/AIListContact.h>
#import <Adium/AIListGroup.h>
#import <Adium/AIListObject.h>
#import <Adium/AIProxyListObject.h>
#import <Adium/AIUserIcons.h>
#import <AIUtilities/AIDockingWindow.h>
#import <AIUtilities/AIEventAdditions.h>
#import <Adium/AIContactList.h>
#import <Adium/AIContactHidingController.h>
#import <AIUtilities/AIOSCompatibility.h>
#import "AISearchFieldCell.h"
#define KEY_HIDE_CONTACT_LIST_GROUPS @"Hide Contact List Groups"
#define SLIDE_ALLOWED_RECT_EDGE_MASK (AIMinXEdgeMask | AIMaxXEdgeMask)
/* Screen edges on which sliding is allowde */
#define DOCK_HIDING_MOUSE_POLL_INTERVAL 0.1f
/* Interval at which to check the mouse position for sliding */
#define WINDOW_SLIDING_DELAY 0.2f
/* Time after the mouse is in the right place before the window slides on screen */
#define WINDOW_ALIGNMENT_TOLERANCE 2.0f
/* Threshold distance far the window from an edge to be considered on it */
#define MOUSE_EDGE_SLIDE_ON_DISTANCE 1.1f
/* ??? */
#define WINDOW_SLIDING_MOUSE_DISTANCE_TOLERANCE 3.0f
/* Distance the mouse must be from the window's frame to be considered outside it */
#define SNAP_DISTANCE 15.0f
/* Distance beween one window's edge and another's at which they should snap together */
@interface
AIListWindowController
()
-
(
id
)
initWithContactList
:
(
id
<
AIContainingObject
>
)
contactList
;
+
(
NSString
*
)
nibName
;
+
(
void
)
updateScreenSlideBoundaryRect:
(
id
)
sender
;
-
(
BOOL
)
shouldSlideWindowOffScreen_mousePositionStrategy
;
-
(
void
)
slideWindowIfNeeded:
(
id
)
sender
;
-
(
BOOL
)
shouldSlideWindowOnScreen_mousePositionStrategy
;
-
(
void
)
delayWindowSlidingForInterval:
(
NSTimeInterval
)
inDelayTime
;
-
(
void
)
showFilterBarWithAnimation:
(
BOOL
)
flag
;
-
(
void
)
hideFilterBarWithAnimation:
(
BOOL
)
flag
;
-
(
void
)
animateFilterBarWithDuration:
(
CGFloat
)
duration
;
-
(
void
)
screenParametersChanged:
(
NSNotification
*
)
notification
;
@end
@implementation
AIListWindowController
@synthesize
windowAnimation
,
filterBarAnimation
;
static
NSMutableDictionary
*
screenSlideBoundaryRectDictionary
=
nil
;
+
(
void
)
initialize
{
if
([
self
isEqual
:
[
AIListWindowController
class
]])
{
[[
NSNotificationCenter
defaultCenter
]
addObserver
:
self
selector
:
@selector
(
updateScreenSlideBoundaryRect
:
)
name
:
NSApplicationDidChangeScreenParametersNotification
object
:
nil
];
[
self
updateScreenSlideBoundaryRect
:
nil
];
}
}
+
(
AIListWindowController
*
)
listWindowControllerForContactList:
(
id
<
AIContainingObject
>
)
contactList
{
return
[[[
self
alloc
]
initWithContactList
:
contactList
]
autorelease
];
}
-
(
id
)
initWithContactList:
(
id
<
AIContainingObject
>
)
contactList
{
if
((
self
=
[
self
initWithWindowNibName
:
[[
self
class
]
nibName
]]))
{
preventHiding
=
NO
;
previousAlpha
=
0
;
typeToFindEnabled
=
!
[[
NSUserDefaults
standardUserDefaults
]
boolForKey
:
@"AIDisableContactListTypeToFind"
];
[
NSBundle
loadNibNamed
:
@"Filter Bar"
owner
:
self
];
[
self
setContactList
:
contactList
];
}
return
self
;
}
-
(
id
<
AIContainingObject
>
)
contactList
{
return
(
contactListRoot
?
contactListRoot
:
[
contactListController
contactList
]);
}
-
(
AIListController
*
)
listController
{
return
contactListController
;
}
-
(
AIListOutlineView
*
)
contactListView
{
return
contactListView
;
}
-
(
void
)
setContactList:
(
id
<
AIContainingObject
>
)
inContactList
{
if
(
inContactList
!=
contactListRoot
)
{
[
contactListRoot
release
];
contactListRoot
=
[
inContactList
retain
];
}
}
//Our window nib name
+
(
NSString
*
)
nibName
{
return
@""
;
}
-
(
Class
)
listControllerClass
{
return
[
AIListController
class
];
}
-
(
void
)
dealloc
{
[
searchField
setDelegate
:
nil
];
[
filterBarAnimation
stopAnimation
];
[
filterBarAnimation
setDelegate
:
nil
];
self
.
filterBarAnimation
=
nil
;
[
filterBarPreviouslySelected
release
];
[[
NSNotificationCenter
defaultCenter
]
removeObserver
:
self
];
[
windowAnimation
stopAnimation
];
[
windowAnimation
setDelegate
:
nil
];
self
.
windowAnimation
=
nil
;
[
contactListController
close
];
[
windowLastScreen
release
];
[
super
dealloc
];
}
-
(
NSString
*
)
adiumFrameAutosaveName
{
AILogWithSignature
(
@"My autosave name is %@"
,[
NSString
stringWithFormat
:
@"Contact List:%@"
,
[[
self
contactList
]
contentsBasedIdentifier
]]);
return
[
NSString
stringWithFormat
:
@"Contact List:%@"
,
[[
self
contactList
]
contentsBasedIdentifier
]];
}
//Setup the window after it has loaded
-
(
void
)
windowDidLoad
{
contactListController
=
[[[
self
listControllerClass
]
alloc
]
initWithContactList
:
[
self
contactList
]
inOutlineView
:
contactListView
inScrollView
:
scrollView_contactList
delegate
:
self
];
//super's windowDidLoad will restore our location, which is based upon the contactListRoot
[
super
windowDidLoad
];
//Exclude this window from the window menu (since we add it manually)
[[
self
window
]
setExcludedFromWindowsMenu
:
YES
];
[[
self
window
]
useOptimizedDrawing
:
YES
];
minWindowSize
=
[[
self
window
]
minSize
];
[
contactListController
setMinWindowSize
:
minWindowSize
];
[[
self
window
]
setTitle
:
AILocalizedString
(
@"Contacts"
,
"Contact List window title"
)];
//Watch for resolution and screen configuration changes
[[
NSNotificationCenter
defaultCenter
]
addObserver
:
self
selector
:
@selector
(
screenParametersChanged
:
)
name
:
NSApplicationDidChangeScreenParametersNotification
object
:
nil
];
// Filter bar
filterBarExpandedGroups
=
NO
;
filterBarIsVisible
=
NO
;
filterBarShownAutomatically
=
NO
;
self
.
filterBarAnimation
=
nil
;
filterBarPreviouslySelected
=
nil
;
[
searchField
setDelegate
:
self
];
//Show the contact list initially even if it is at a screen edge and supposed to slide out of view
[
self
delayWindowSlidingForInterval
:
5
];
id
<
AIPreferenceController
>
preferenceController
=
adium
.
preferenceController
;
//Observe preference changes
[
preferenceController
registerPreferenceObserver
:
self
forGroup
:
PREF_GROUP_CONTACT_LIST
];
[
preferenceController
registerPreferenceObserver
:
self
forGroup
:
PREF_GROUP_CONTACT_LIST_DISPLAY
];
[
preferenceController
registerPreferenceObserver
:
self
forGroup
:
PREF_GROUP_APPEARANCE
];
//Preference code below assumes layout is done before theme.
[
preferenceController
registerPreferenceObserver
:
self
forGroup
:
PREF_GROUP_LIST_LAYOUT
];
[
preferenceController
registerPreferenceObserver
:
self
forGroup
:
PREF_GROUP_LIST_THEME
];
[[
NSNotificationCenter
defaultCenter
]
addObserver
:
self
selector
:
@selector
(
applicationDidUnhide
:
)
name
:
NSApplicationDidUnhideNotification
object
:
nil
];
//Substitute an otherwise identical copy of the search field for one of our class. We don't want to globally pose as class; we just want it here.
[
NSKeyedArchiver
setClassName
:
@"AISearchFieldCell"
forClass
:
[
NSSearchFieldCell
class
]];
[
searchField
setCell
:
[
NSKeyedUnarchiver
unarchiveObjectWithData
:
[
NSKeyedArchiver
archivedDataWithRootObject
:
[
searchField
cell
]]]];
[
NSKeyedArchiver
setClassName
:
@"NSSearchFieldCell"
forClass
:
[
NSSearchFieldCell
class
]];
/* Get rid of the "x" button in the search field that would clear the search.
* It conflicts with the other "x" button that hides the entire bar, and clearing a few characters is probably not necessary.
*/
[[
searchField
cell
]
setCancelButtonCell
:
nil
];
[[
NSNotificationCenter
defaultCenter
]
addObserver
:
self
selector
:
@selector
(
windowDidResignMain
:
)
name
:
NSWindowDidResignMainNotification
object
:[
self
window
]];
//Save our frame immediately for sliding purposes
[
self
setSavedFrame
:
[[
self
window
]
frame
]];
}
//Close the contact list window
-
(
void
)
windowWillClose:
(
NSNotification
*
)
notification
{
if
([
self
windowSlidOffScreenEdgeMask
]
!=
AINoEdges
)
{
//Hide the window while it's still off-screen
[[
self
window
]
setAlphaValue
:
0.0f
];
AILogWithSignature
(
@"Setting to alpha 0 while the window is offscreen"
);
//Then move it back on screen so that we'll save the proper position in -[AIWindowController windowWillClose:]
[
self
slideWindowOnScreenWithAnimation
:
NO
];
}
// When closing the contact list while a search is in progress, reset visibility first.
if
(
!
[[
searchField
stringValue
]
isEqualToString
:
@""
])
{
[
searchField
setStringValue
:
@""
];
[
self
filterContacts
:
searchField
];
}
[
super
windowWillClose
:
notification
];
//Invalidate the dock-like hiding timer
[
slideWindowIfNeededTimer
invalidate
];
[
slideWindowIfNeededTimer
release
];
//Stop observing
[
adium
.
preferenceController
unregisterPreferenceObserver
:
self
];
[[
NSNotificationCenter
defaultCenter
]
removeObserver
:
self
];
[[[
NSWorkspace
sharedWorkspace
]
notificationCenter
]
removeObserver
:
self
];
//Tell the interface to unload our window
NSNotificationCenter
*
adiumNotificationCenter
=
[
NSNotificationCenter
defaultCenter
];
[
adiumNotificationCenter
postNotificationName
:
Interface_ContactListDidResignMain
object
:
self
];
[
adiumNotificationCenter
postNotificationName
:
Interface_ContactListDidClose
object
:
self
];
}
NSInteger
levelForAIWindowLevel
(
AIWindowLevel
windowLevel
)
{
NSInteger
level
;
switch
(
windowLevel
)
{
case
AINormalWindowLevel
:
level
=
NSNormalWindowLevel
;
break
;
case
AIFloatingWindowLevel
:
level
=
NSFloatingWindowLevel
;
break
;
case
AIDesktopWindowLevel
:
level
=
kCGBackstopMenuLevel
;
break
;
default
:
level
=
NSNormalWindowLevel
;
break
;
}
return
level
;
}
-
(
void
)
setWindowLevel:
(
NSInteger
)
level
{
[[
self
window
]
setLevel
:
level
];
}
// A "stationary" window stays pinned to the desktop during ExposŽ
-
(
void
)
setCollectionBehaviorOfWindow:
(
NSWindow
*
)
window
showOnAllSpaces:
(
BOOL
)
allSpaces
isStationary:
(
BOOL
)
stationary
{
NSWindowCollectionBehavior
behavior
=
NSWindowCollectionBehaviorDefault
;
if
(
allSpaces
)
behavior
|=
NSWindowCollectionBehaviorCanJoinAllSpaces
;
if
(
stationary
)
behavior
|=
NSWindowCollectionBehaviorStationary
;
[
window
setCollectionBehavior
:
behavior
];
}
//Preferences have changed
-
(
void
)
preferencesChangedForGroup:
(
NSString
*
)
group
key
:(
NSString
*
)
key
object
:(
AIListObject
*
)
object
preferenceDict
:(
NSDictionary
*
)
prefDict
firstTime
:(
BOOL
)
firstTime
{
BOOL
shouldRevealWindowAndDelaySliding
=
NO
;
// Make sure we're not getting an object-specific update.
if
(
object
!=
nil
)
return
;
if
([
group
isEqualToString
:
PREF_GROUP_CONTACT_LIST
])
{
windowLevel
=
[[
prefDict
objectForKey
:
KEY_CL_WINDOW_LEVEL
]
intValue
];
[
self
setWindowLevel
:
levelForAIWindowLevel
(
windowLevel
)];
listHasShadow
=
[[
prefDict
objectForKey
:
KEY_CL_WINDOW_HAS_SHADOW
]
boolValue
];
[[
self
window
]
setHasShadow
:
listHasShadow
];
windowHidingStyle
=
[[
prefDict
objectForKey
:
KEY_CL_WINDOW_HIDING_STYLE
]
intValue
];
slideOnlyInBackground
=
[[
prefDict
objectForKey
:
KEY_CL_SLIDE_ONLY_IN_BACKGROUND
]
boolValue
];
[[
self
window
]
setHidesOnDeactivate
:
(
windowHidingStyle
==
AIContactListWindowHidingStyleBackground
)];
showOnAllSpaces
=
[[
prefDict
objectForKey
:
KEY_CL_ALL_SPACES
]
boolValue
];
[
self
setCollectionBehaviorOfWindow
:
[
self
window
]
showOnAllSpaces
:
showOnAllSpaces
isStationary
:(
windowLevel
==
AIDesktopWindowLevel
)];
if
(
windowHidingStyle
==
AIContactListWindowHidingStyleSliding
)
{
if
(
!
slideWindowIfNeededTimer
)
{
slideWindowIfNeededTimer
=
[[
NSTimer
scheduledTimerWithTimeInterval
:
DOCK_HIDING_MOUSE_POLL_INTERVAL
target
:
self
selector
:
@selector
(
slideWindowIfNeeded
:
)
userInfo
:
nil
repeats
:
YES
]
retain
];
}
}
else
if
(
slideWindowIfNeededTimer
)
{
[
slideWindowIfNeededTimer
invalidate
];
[
slideWindowIfNeededTimer
release
];
slideWindowIfNeededTimer
=
nil
;
}
[
contactListController
setShowTooltips
:
[[
prefDict
objectForKey
:
KEY_CL_SHOW_TOOLTIPS
]
boolValue
]];
[
contactListController
setShowTooltipsInBackground
:
[[
prefDict
objectForKey
:
KEY_CL_SHOW_TOOLTIPS_IN_BACKGROUND
]
boolValue
]];
}
//Auto-Resizing
if
([
group
isEqualToString
:
PREF_GROUP_APPEARANCE
])
{
AIContactListWindowStyle
windowStyle
=
[[
prefDict
objectForKey
:
KEY_LIST_LAYOUT_WINDOW_STYLE
]
intValue
];
BOOL
autoResizeHorizontally
=
[[
prefDict
objectForKey
:
KEY_LIST_LAYOUT_HORIZONTAL_AUTOSIZE
]
boolValue
];
BOOL
autoResizeVertically
=
YES
;
NSInteger
forcedWindowWidth
,
maxWindowWidth
;
//Determine how to handle vertical autosizing. AIAppearancePreferences must match this behavior for this to make sense.
switch
(
windowStyle
)
{
case
AIContactListWindowStyleStandard
:
case
AIContactListWindowStyleBorderless
:
case
AIContactListWindowStyleGroupChat
:
//Standard and borderless don't have to vertically autosize, but they might
autoResizeVertically
=
[[
prefDict
objectForKey
:
KEY_LIST_LAYOUT_VERTICAL_AUTOSIZE
]
boolValue
];
break
;
case
AIContactListWindowStyleGroupBubbles
:
case
AIContactListWindowStyleContactBubbles
:
case
AIContactListWindowStyleContactBubbles_Fitted
:
//The bubbles styles don't show a window; force them to autosize by leaving autoResizeVertically == YES
break
;
}
/* Avoid the bouncing effect when scrolling on Lion. This looks very bad when using a borderless window.
* TODO: (10.7+) remove this if
*/
if
(
windowStyle
!=
AIContactListWindowStyleStandard
&&
[
scrollView_contactList
respondsToSelector
:
@selector
(
setVerticalScrollElasticity
:
)])
{
[
scrollView_contactList
setVerticalScrollElasticity
:
1
];
// NSScrollElasticityNone
}
if
(
autoResizeHorizontally
)
{
//If autosizing, KEY_LIST_LAYOUT_HORIZONTAL_WIDTH determines the maximum width; no forced width.
maxWindowWidth
=
[[
prefDict
objectForKey
:
KEY_LIST_LAYOUT_HORIZONTAL_WIDTH
]
integerValue
];
forcedWindowWidth
=
-1
;
}
else
{
if
(
windowStyle
==
AIContactListWindowStyleStandard
/* || windowStyle == AIContactListWindowStyleBorderless*/
)
{
//In the non-transparent non-autosizing modes, KEY_LIST_LAYOUT_HORIZONTAL_WIDTH has no meaning
maxWindowWidth
=
10000
;
forcedWindowWidth
=
-1
;
}
else
{
//In the transparent non-autosizing modes, KEY_LIST_LAYOUT_HORIZONTAL_WIDTH determines the width of the window
forcedWindowWidth
=
[[
prefDict
objectForKey
:
KEY_LIST_LAYOUT_HORIZONTAL_WIDTH
]
integerValue
];
maxWindowWidth
=
forcedWindowWidth
;
}
}
//Show the resize indicator if either or both of the autoresizing options is NO
[[
self
window
]
setShowsResizeIndicator
:!
(
autoResizeVertically
&&
autoResizeHorizontally
)];
/*
Reset the minimum and maximum sizes in case [contactListController contactListDesiredSizeChanged]; doesn't cause a sizing change
(and therefore the min and max sizes aren't set there).
*/
NSSize
thisMinimumSize
=
minWindowSize
;
NSSize
thisMaximumSize
=
NSMakeSize
(
maxWindowWidth
,
10000
);
NSRect
currentFrame
=
[[
self
window
]
frame
];
if
(
forcedWindowWidth
!=
-1
)
{
/*
If we have a forced width but we are doing no autoresizing, set our frame now so we don't have to be doing checks every time
contactListDesiredSizeChanged is called.
*/
if
(
!
(
autoResizeVertically
||
autoResizeHorizontally
))
{
thisMinimumSize
.
width
=
forcedWindowWidth
;
[[
self
window
]
setFrame
:
NSMakeRect
(
currentFrame
.
origin
.
x
,
currentFrame
.
origin
.
y
,
forcedWindowWidth
,
currentFrame
.
size
.
height
)
display
:
YES
animate
:
NO
];
}
}
//If vertically resizing, make the minimum and maximum heights the current height
if
(
autoResizeVertically
)
{
thisMinimumSize
.
height
=
currentFrame
.
size
.
height
;
thisMaximumSize
.
height
=
currentFrame
.
size
.
height
;
}
//If horizontally resizing, make the minimum and maximum widths the current width
if
(
autoResizeHorizontally
)
{
thisMinimumSize
.
width
=
currentFrame
.
size
.
width
;
thisMaximumSize
.
width
=
currentFrame
.
size
.
width
;
}
/* For a standard window, inform the contact list that, if asked, it wants to be 175 pixels or more.
* A maximum width less than this can make the list autosize smaller, but if it has its druthers it'll be a sane
* size.
*/
[
contactListView
setMinimumDesiredWidth
:
((
windowStyle
==
AIContactListWindowStyleStandard
)
?
175
:
0
)];
[[
self
window
]
setMinSize
:
thisMinimumSize
];
[[
self
window
]
setMaxSize
:
thisMaximumSize
];
contactListController
.
autoResizeHorizontally
=
autoResizeHorizontally
;
contactListController
.
autoResizeVertically
=
autoResizeVertically
;
[
contactListController
setForcedWindowWidth
:
forcedWindowWidth
];
[
contactListController
setMaxWindowWidth
:
maxWindowWidth
];
// let this happen at the beginning of the next runloop. The View needs to configure itself before we start forcing it to a size.
dispatch_async
(
dispatch_get_main_queue
(),
^
{
NSAutoreleasePool
*
pool
=
[[
NSAutoreleasePool
alloc
]
init
];
[
contactListController
contactListDesiredSizeChanged
];
[
pool
release
];
});
if
(
!
firstTime
)
{
shouldRevealWindowAndDelaySliding
=
YES
;
}
}
//Window opacity
if
([
group
isEqualToString
:
PREF_GROUP_APPEARANCE
])
{
CGFloat
opacity
=
(
CGFloat
)[[
prefDict
objectForKey
:
KEY_LIST_LAYOUT_WINDOW_OPACITY
]
doubleValue
];
[
contactListController
setBackgroundOpacity
:
opacity
];
/*
* If we're using fitted bubbles, we want the default behavior of the winodw, which is to respond to clicks on opaque areas
* and ignore clicks on transparent areas. If we're using any other style, we never want to ignore clicks.
*/
BOOL
forceWindowToCatchMouseEvents
=
([[
prefDict
objectForKey
:
KEY_LIST_LAYOUT_WINDOW_STYLE
]
integerValue
]
!=
AIContactListWindowStyleContactBubbles_Fitted
);
if
(
forceWindowToCatchMouseEvents
)
[[
self
window
]
setIgnoresMouseEvents
:
NO
];
if
(
!
firstTime
)
{
shouldRevealWindowAndDelaySliding
=
YES
;
}
}
if
([
group
isEqualToString
:
PREF_GROUP_CONTACT_LIST_DISPLAY
])
{
[
contactListController
setUseContactListGroups
:!
[[
prefDict
objectForKey
:
KEY_HIDE_CONTACT_LIST_GROUPS
]
boolValue
]];
}
//Layout and Theme ------------
BOOL
groupLayout
=
([
group
isEqualToString
:
PREF_GROUP_LIST_LAYOUT
]);
BOOL
groupTheme
=
([
group
isEqualToString
:
PREF_GROUP_LIST_THEME
]);
if
(
groupLayout
||
(
groupTheme
&&
!
firstTime
))
{
/* We don't want to execute this code twice when initializing */
NSDictionary
*
layoutDict
=
[
adium
.
preferenceController
preferencesForGroup
:
PREF_GROUP_LIST_LAYOUT
];
NSDictionary
*
themeDict
=
[
adium
.
preferenceController
preferencesForGroup
:
PREF_GROUP_LIST_THEME
];
//Layout only
if
(
groupLayout
)
{
NSInteger
iconSize
=
[[
layoutDict
objectForKey
:
KEY_LIST_LAYOUT_USER_ICON_SIZE
]
integerValue
];
[
AIUserIcons
setListUserIconSize
:
NSMakeSize
(
iconSize
,
iconSize
)];
}
//Theme only
if
(
groupTheme
||
firstTime
)
{
NSString
*
imagePath
=
[
themeDict
objectForKey
:
KEY_LIST_THEME_BACKGROUND_IMAGE_PATH
];
//Background Image
if
(
imagePath
&&
[
imagePath
length
]
&&
[[
themeDict
objectForKey
:
KEY_LIST_THEME_BACKGROUND_IMAGE_ENABLED
]
boolValue
])
{
[
contactListView
setBackgroundImage
:
[[[
NSImage
alloc
]
initWithContentsOfFile
:
imagePath
]
autorelease
]];
}
else
{
[
contactListView
setBackgroundImage
:
nil
];
}
}
EXTENDED_STATUS_STYLE
statusStyle
=
[[
layoutDict
objectForKey
:
KEY_LIST_LAYOUT_EXTENDED_STATUS_STYLE
]
intValue
];
EXTENDED_STATUS_POSITION
statusPosition
=
[[
layoutDict
objectForKey
:
KEY_LIST_LAYOUT_EXTENDED_STATUS_POSITION
]
intValue
];
contactListController
.
autoResizeHorizontallyWithIdleTime
=
((
statusStyle
==
IDLE_ONLY
||
statusStyle
==
IDLE_AND_STATUS
)
&&
(
statusPosition
==
EXTENDED_STATUS_POSITION_BESIDE_NAME
||
statusPosition
==
EXTENDED_STATUS_POSITION_BOTH
));
[
contactListController
contactListDesiredSizeChanged
];
//Both layout and theme
[
contactListController
updateLayoutFromPrefDict
:
layoutDict
andThemeFromPrefDict
:
themeDict
];
if
(
!
firstTime
)
{
shouldRevealWindowAndDelaySliding
=
YES
;
}
}
if
(
shouldRevealWindowAndDelaySliding
)
{
[
self
delayWindowSlidingForInterval
:
2
];
[
self
slideWindowOnScreenWithAnimation
:
NO
];
}
else
{
//Do a slide immediately if needed (to display as per our new preferneces)
[
self
slideWindowIfNeeded
:
nil
];
}
}
-
(
IBAction
)
performDefaultActionOnSelectedObject:
(
AIListObject
*
)
selectedObject
sender:
(
NSOutlineView
*
)
sender
{
if
([
selectedObject
isKindOfClass
:
[
AIListGroup
class
]])
{
//Expand or collapse the group
for
(
AIProxyListObject
*
proxyObject
in
selectedObject
.
proxyObjects
)
{
if
([
sender
isItemExpanded
:
proxyObject
])
{
[
sender
collapseItem
:
proxyObject
];
}
else
{
[
sender
expandItem
:
proxyObject
];
}
}
}
else
if
([
selectedObject
isMemberOfClass
:
[
AIListBookmark
class
]])
{
//Hide any tooltip the contactListController is currently showing
[
contactListController
hideTooltip
];
[(
AIListBookmark
*
)
selectedObject
openChat
];
}
else
if
([
selectedObject
isKindOfClass
:
[
AIListContact
class
]])
{
//Hide any tooltip the contactListController is currently showing
[
contactListController
hideTooltip
];
//Open a new message with the contact
[
adium
.
interfaceController
setActiveChat
:
[
adium
.
chatController
openChatWithContact
:
(
AIListContact
*
)
selectedObject
onPreferredAccount
:
YES
]];
}
}
-
(
BOOL
)
canCustomizeToolbar
{
return
NO
;
}
//Interface Container --------------------------------------------------------------------------------------------------
#pragma mark Interface Container
//Close this container
-
(
void
)
close:
(
id
)
sender
{
//In response to windowShouldClose, the interface controller releases us. At that point, no one would be retaining
//this instance of AIContactListWindowController, and we would be deallocated. The call to [self window] will
//crash if we are deallocated. A dirty, but functional fix is to temporarily retain ourself here.
[
self
retain
];
if
([
self
windowShouldClose
:
nil
])
{
[[
self
window
]
close
];
}
[
self
release
];
}
-
(
void
)
makeActive:
(
id
)
sender
{
[[
self
window
]
makeKeyAndOrderFront
:
self
];
}
//Contact list brought to front
-
(
void
)
windowDidBecomeKey:
(
NSNotification
*
)
notification
{
[[
NSNotificationCenter
defaultCenter
]
postNotificationName
:
Interface_ContactListDidBecomeMain
object
:
self
];
}
//Contact list sent back
-
(
void
)
windowDidResignKey:
(
NSNotification
*
)
notification
{
[[
NSNotificationCenter
defaultCenter
]
postNotificationName
:
Interface_ContactListDidResignMain
object
:
self
];
}
-
(
void
)
showWindowInFrontIfAllowed:
(
BOOL
)
inFront
{
//Always show for three seconds at least if we're told to show
[
self
delayWindowSlidingForInterval
:
3
];
//Call super to actually do the showing
[
super
showWindowInFrontIfAllowed
:
inFront
];
NSWindow
*
window
=
[
self
window
];
if
([
self
windowSlidOffScreenEdgeMask
]
!=
AINoEdges
)
{
[
self
slideWindowOnScreenWithAnimation
:
NO
];
}
windowSlidOffScreenEdgeMask
=
AINoEdges
;
currentScreen
=
[
window
screen
];
currentScreenFrame
=
[
currentScreen
frame
];
if
([[
NSScreen
screens
]
count
]
&&
(
currentScreen
==
[[
NSScreen
screens
]
objectAtIndex
:
0
]))
{
currentScreenFrame
.
size
.
height
-=
[[
NSApp
mainMenu
]
menuBarHeight
];
}
//Ensure the window is displaying at the proper level and exposé setting
[
self
setWindowLevel
:
levelForAIWindowLevel
(
windowLevel
)];
}
-
(
void
)
setSavedFrame:
(
NSRect
)
frame
{
oldFrame
=
frame
;
}
-
(
NSRect
)
savedFrame
{
return
oldFrame
;
}
// Auto-resizing support ------------------------------------------------------------------------------------------------
#pragma mark Auto-resizing support
-
(
void
)
respondToScreenParametersChanged:
(
NSNotification
*
)
notification
{
NSWindow
*
window
=
[
self
window
];
NSScreen
*
windowScreen
=
[
window
screen
];
if
(
!
windowScreen
)
{
if
([[
NSScreen
screens
]
containsObject
:
windowLastScreen
])
{
windowScreen
=
windowLastScreen
;
}
else
{
[
windowLastScreen
release
];
windowLastScreen
=
nil
;
windowScreen
=
[
NSScreen
mainScreen
];
}
}
NSRect
newScreenFrame
=
[[
screenSlideBoundaryRectDictionary
objectForKey
:
[
NSValue
valueWithNonretainedObject
:
windowScreen
]]
rectValue
];
if
([
self
windowSlidOffScreenEdgeMask
]
!=
AINoEdges
)
{
NSRect
newWindowFrame
=
AIRectByAligningRect_edge_toRect_edge_
([
window
frame
],
(
NSRectEdge
)[
self
windowSlidOffScreenEdgeMask
],
newScreenFrame
,
(
NSRectEdge
)[
self
windowSlidOffScreenEdgeMask
]);
[[
self
window
]
setFrame
:
newWindowFrame
display
:
NO
];
[
self
delayWindowSlidingForInterval
:
2
];
[
self
slideWindowOnScreenWithAnimation
:
NO
];
}
[
contactListController
contactListDesiredSizeChanged
];
currentScreen
=
[
window
screen
];
currentScreenFrame
=
newScreenFrame
;
[
self
setSavedFrame
:
[
window
frame
]];
}
-
(
void
)
screenParametersChanged:
(
NSNotification
*
)
notification
{
/* Wait until the next run loop so the class method has definitely updated our screen sliding borders. */
[
self
performSelector
:
@selector
(
respondToScreenParametersChanged
:
)
withObject
:
notification
afterDelay
:
0
];
}
// Printing
#pragma mark Printing
-
(
void
)
adiumPrint:
(
id
)
sender
{
[
contactListView
print
:
sender
];
}
// Dock-like hiding -----------------------------------------------------------------------------------------------------
#pragma mark Dock-like hiding
+
(
void
)
updateScreenSlideBoundaryRect:
(
id
)
sender
{
NSArray
*
screens
=
[
NSScreen
screens
];
NSInteger
numScreens
=
[
screens
count
];
[
screenSlideBoundaryRectDictionary
release
];
screenSlideBoundaryRectDictionary
=
[[
NSMutableDictionary
alloc
]
initWithCapacity
:
numScreens
];
if
(
numScreens
>
0
)
{
//The menubar screen is a special case - the menubar is not a part of the rect we're interested in
NSScreen
*
menubarScreen
=
[
screens
objectAtIndex
:
0
];
NSRect
screenSlideBoundaryRect
;
screenSlideBoundaryRect
=
[
menubarScreen
frame
];
screenSlideBoundaryRect
.
size
.
height
=
NSMaxY
([
menubarScreen
visibleFrame
])
-
NSMinY
([
menubarScreen
frame
]);
[
screenSlideBoundaryRectDictionary
setObject
:
[
NSValue
valueWithRect
:
screenSlideBoundaryRect
]
forKey
:[
NSValue
valueWithNonretainedObject
:
menubarScreen
]];
for
(
NSInteger
i
=
1
;
i
<
numScreens
;
i
++
)
{
NSScreen
*
screen
=
[
screens
objectAtIndex
:
i
];
[
screenSlideBoundaryRectDictionary
setObject
:
[
NSValue
valueWithRect
:
[
screen
frame
]]
forKey
:[
NSValue
valueWithNonretainedObject
:
screen
]];
}
}
}
/*!
* @brief Adium unhid
*
* If the contact list is open but not visible when we unhide, we should always display it; it should not, however, steal focus.
*/
-
(
void
)
applicationDidUnhide:
(
NSNotification
*
)
notification
{
if
(
!
[[
self
window
]
isVisible
])
{
[
self
showWindowInFrontIfAllowed
:
NO
];
}
}
-
(
BOOL
)
windowShouldHideOnDeactivate
{
return
(
windowHidingStyle
==
AIContactListWindowHidingStyleBackground
);
}
/*!
* @brief Called on a delay by -[self slideWindowIfNeeded:]
*
* This is a separate function so that the call to it may be canceled if the mouse doesn't
* remain in position long enough.
*/
-
(
void
)
slideWindowOnScreenAfterDelay
{
waitingToSlideOnScreen
=
NO
;
//If we're hiding the window (generally) but now sliding it on screen, make sure it's on top
if
(
windowHidingStyle
==
AIContactListWindowHidingStyleSliding
)
{
[
self
setWindowLevel
:
NSFloatingWindowLevel
];
[
self
setCollectionBehaviorOfWindow
:
[
self
window
]
showOnAllSpaces
:
YES
isStationary
:
YES
];
overrodeWindowLevel
=
YES
;
}
[
self
slideWindowOnScreen
];
}
/*!
* @brief Check what behavior the window should perform and initiate it
*
* Called regularly by a repeating timer to check mouse position against window position.
*/
-
(
void
)
slideWindowIfNeeded:
(
id
)
sender
{
if
([
self
shouldSlideWindowOnScreen
])
{
if
(
!
waitingToSlideOnScreen
)
{
[
self
performSelector
:
@selector
(
slideWindowOnScreenAfterDelay
)
withObject
:
nil
afterDelay
:
WINDOW_SLIDING_DELAY
];
waitingToSlideOnScreen
=
YES
;
}
}
else
{
if
(
waitingToSlideOnScreen
)
{
/* If we were waiting to slide on screen but the mouse moved out of position too soon,
* cancel the selector which would slide us on screen.
*/
waitingToSlideOnScreen
=
NO
;
[[
self
class
]
cancelPreviousPerformRequestsWithTarget
:
self
selector
:
@selector
(
slideWindowOnScreenAfterDelay
)
object
:
nil
];
}
if
([
self
shouldSlideWindowOffScreen
])
{
AIRectEdgeMask
adjacentEdges
=
[
self
slidableEdgesAdjacentToWindow
];
if
(
adjacentEdges
&
(
AIMinXEdgeMask
|
AIMaxXEdgeMask
))
{
[
self
slideWindowOffScreenEdges
:
(
adjacentEdges
&
(
AIMinXEdgeMask
|
AIMaxXEdgeMask
))];
}
else
{
[
self
slideWindowOffScreenEdges
:
adjacentEdges
];
}
/* If we're hiding the window (generally) but now sliding it off screen, set it to kCGBackstopMenuLevel and don't
* let it participate in exposé.
*/
if
(
overrodeWindowLevel
&&
windowHidingStyle
==
AIContactListWindowHidingStyleSliding
)
{
[
self
setWindowLevel
:
kCGBackstopMenuLevel
];
[[
self
window
]
setCollectionBehavior
:
NSWindowCollectionBehaviorCanJoinAllSpaces
];
overrodeWindowLevel
=
YES
;
}
}
else
if
(
overrodeWindowLevel
&&
([
self
slidableEdgesAdjacentToWindow
]
==
AINoEdges
)
&&
([
self
windowSlidOffScreenEdgeMask
]
==
AINoEdges
))
{
/* If the window level was overridden at some point and now we:
* 1. Are on screen AND
* 2. No longer have any edges eligible for sliding
* we should restore our window level.
*/
[
self
setWindowLevel
:
levelForAIWindowLevel
(
windowLevel
)];
[[
self
window
]
setCollectionBehavior
:
showOnAllSpaces
?
NSWindowCollectionBehaviorCanJoinAllSpaces
:
NSWindowCollectionBehaviorDefault
];
overrodeWindowLevel
=
NO
;
}
}
}
-
(
BOOL
)
shouldSlideWindowOnScreen
{
BOOL
shouldSlide
=
NO
;
if
(([
self
windowSlidOffScreenEdgeMask
]
!=
AINoEdges
)
&&
!
[
NSApp
isHidden
])
{
if
(
slideOnlyInBackground
&&
[
NSApp
isActive
])
{
//We only slide while in the background, and the app is not in the background. Slide on screen.
shouldSlide
=
YES
;
}
else
if
(
windowHidingStyle
==
AIContactListWindowHidingStyleSliding
)
{
//Slide on screen if the mouse position indicates we should
shouldSlide
=
[
self
shouldSlideWindowOnScreen_mousePositionStrategy
];
}
else
{
//It's slid off-screen... and it's not supposed to be sliding at all. Slide back on screen!
shouldSlide
=
YES
;
}
}
return
shouldSlide
;
}
-
(
BOOL
)
shouldSlideWindowOffScreen
{
BOOL
shouldSlide
=
NO
;
if
((
windowHidingStyle
==
AIContactListWindowHidingStyleSliding
)
&&
!
preventHiding
&&
([
self
windowSlidOffScreenEdgeMask
]
==
AINoEdges
)
&&
(
!
(
slideOnlyInBackground
&&
[
NSApp
isActive
])))
{
shouldSlide
=
[
self
shouldSlideWindowOffScreen_mousePositionStrategy
];
}
return
shouldSlide
;
}
// slide off screen if the window is aligned to a screen edge and the mouse is not in the strip of screen
// you'd get by translating the window along the screen edge. This is the dock's behavior.
-
(
BOOL
)
shouldSlideWindowOffScreen_mousePositionStrategy
{
BOOL
shouldSlideOffScreen
=
NO
;
NSWindow
*
window
=
[
self
window
];
NSRect
windowFrame
=
[
window
frame
];
NSPoint
mouseLocation
=
[
NSEvent
mouseLocation
];
AIRectEdgeMask
slidableEdgesAdjacentToWindow
=
[
self
slidableEdgesAdjacentToWindow
];
NSRectEdge
screenEdge
;
for
(
screenEdge
=
0
;
screenEdge
<
4
;
screenEdge
++
)
{
if
(
slidableEdgesAdjacentToWindow
&
(
1
<<
screenEdge
))
{
CGFloat
distanceMouseOutsideWindow
=
AISignedExteriorDistanceRect_edge_toPoint_
(
windowFrame
,
AIOppositeRectEdge_
(
screenEdge
),
mouseLocation
);
if
(
distanceMouseOutsideWindow
>
WINDOW_SLIDING_MOUSE_DISTANCE_TOLERANCE
)
shouldSlideOffScreen
=
YES
;
}
}
/* Don't allow the window to slide off if the user is dragging
* This method is hacky and does not completely work. is there a way to detect if the mouse is down?
*/
NSEventType
currentEventType
=
[[
NSApp
currentEvent
]
type
];
if
(
currentEventType
==
NSLeftMouseDragged
||
currentEventType
==
NSRightMouseDragged
||
currentEventType
==
NSOtherMouseDragged
||
currentEventType
==
NSPeriodic
)
{
shouldSlideOffScreen
=
NO
;
}
return
shouldSlideOffScreen
;
}
// note: may be inaccurate when mouse is up against an edge
-
(
NSScreen
*
)
screenForPoint:
(
NSPoint
)
point
{
for
(
NSScreen
*
pointScreen
in
[
NSScreen
screens
])
{
if
(
NSPointInRect
(
point
,
NSInsetRect
([
pointScreen
frame
],
-1
,
-1
)))
return
pointScreen
;
}
return
nil
;
}
-
(
NSRect
)
squareRectWithCenter:
(
NSPoint
)
point
sideLength:
(
CGFloat
)
sideLength
{
return
NSMakeRect
(
point
.
x
-
sideLength
*
0.5f
,
point
.
y
-
sideLength
*
0.5f
,
sideLength
,
sideLength
);
}
-
(
BOOL
)
pointIsInScreenCorner:
(
NSPoint
)
point
{
BOOL
inCorner
=
NO
;
NSScreen
*
menubarScreen
=
[[
NSScreen
screens
]
objectAtIndex
:
0
];
CGFloat
menubarHeight
=
NSMaxY
([
menubarScreen
frame
])
-
NSMaxY
([
menubarScreen
visibleFrame
]);
// breaks if the dock is at the top of the screen (i.e. if the user is insane)
NSRect
screenFrame
=
[[
self
screenForPoint
:
point
]
frame
];
NSPoint
lowerLeft
=
screenFrame
.
origin
;
NSPoint
upperRight
=
NSMakePoint
(
NSMaxX
(
screenFrame
),
NSMaxY
(
screenFrame
));
NSPoint
lowerRight
=
NSMakePoint
(
upperRight
.
x
,
lowerLeft
.
y
);
NSPoint
upperLeft
=
NSMakePoint
(
lowerLeft
.
x
,
upperRight
.
y
);
CGFloat
sideLength
=
menubarHeight
*
2.0f
;
inCorner
=
(
NSPointInRect
(
point
,
[
self
squareRectWithCenter
:
lowerLeft
sideLength
:
sideLength
])
||
NSPointInRect
(
point
,
[
self
squareRectWithCenter
:
lowerRight
sideLength
:
sideLength
])
||
NSPointInRect
(
point
,
[
self
squareRectWithCenter
:
upperLeft
sideLength
:
sideLength
])
||
NSPointInRect
(
point
,
[
self
squareRectWithCenter
:
upperRight
sideLength
:
sideLength
]));
return
inCorner
;
}
/*!
* @brief Should the window be slid on screen given the mouse's position?
*
* This method will never return YES of the cl is slid into a corner, which shouldn't happen, or if the mouse is in a corner.
*
* @result YES if the mouse is against all edges of the screen where we previously slid the window and not in a corner.
*/
-
(
BOOL
)
shouldSlideWindowOnScreen_mousePositionStrategy
{
if
([
self
windowSlidOffScreenEdgeMask
]
!=
AINoEdges
)
{
NSPoint
mouseLocation
=
[
NSEvent
mouseLocation
];
//Initially, assume the mouse is not in an appropriate position
BOOL
mouseNearSlideOffEdges
=
NO
;
NSRectEdge
screenEdge
;
NSRect
screenSlideBoundaryRect
=
[[
screenSlideBoundaryRectDictionary
objectForKey
:
[
NSValue
valueWithNonretainedObject
:
windowLastScreen
]]
rectValue
];
/* Only look at the screen in which the mouse currently resides.
* The mouse may be in no screen if it is over the menu bar.
*/
if
(
NSPointInRect
(
mouseLocation
,
screenSlideBoundaryRect
))
{
//Check each edge
for
(
screenEdge
=
0
;
screenEdge
<
4
;
screenEdge
++
)
{
//But we only care about an edge off of which the window has slid
if
(
windowSlidOffScreenEdgeMask
&
(
1
<<
screenEdge
))
{
CGFloat
mouseOutsideSlideBoundaryRectDistance
=
AISignedExteriorDistanceRect_edge_toPoint_
(
screenSlideBoundaryRect
,
screenEdge
,
mouseLocation
);
//The mouse must be within MOUSE_EDGE_SLIDE_ON_DISTANCE of every slid-off edge to bring the window back on-screen
if
(
mouseOutsideSlideBoundaryRectDistance
<
-
MOUSE_EDGE_SLIDE_ON_DISTANCE
)
{
mouseNearSlideOffEdges
=
NO
;
break
;
}
else
{
mouseNearSlideOffEdges
=
YES
;
}
}
}
}
return
mouseNearSlideOffEdges
&&
!
[
self
pointIsInScreenCorner
:
mouseLocation
];
}
else
{
return
NO
;
}
}
#pragma mark Dock-like hiding
-
(
NSScreen
*
)
windowLastScreen
{
return
windowLastScreen
;
}
-
(
BOOL
)
animationShouldStart:
(
NSAnimation
*
)
animation
{
if
(
!
[
animation
isEqual
:
windowAnimation
])
return
YES
;
//Whenever an animation starts, we should be using the normal shadow setting
[[
self
window
]
setHasShadow
:
listHasShadow
];
//Don't let docking interfere with the animation
if
([[
self
window
]
respondsToSelector
:
@selector
(
setDockingEnabled
:
)])
[(
id
)[
self
window
]
setDockingEnabled
:
NO
];
if
(
windowSlidOffScreenEdgeMask
==
AINoEdges
)
{
[[
self
window
]
setAlphaValue
:
previousAlpha
];
AILogWithSignature
(
@"Set window to previous alpha of %f"
,
previousAlpha
);
}
return
YES
;
}
-
(
void
)
animationDidEnd:
(
NSAnimation
*
)
animation
{
if
([
animation
isEqual
:
windowAnimation
])
{
//Restore docking behavior
if
([[
self
window
]
respondsToSelector
:
@selector
(
setDockingEnabled
:
)])
[(
id
)[
self
window
]
setDockingEnabled
:
YES
];
if
(
windowSlidOffScreenEdgeMask
==
AINoEdges
)
{
//When the window is offscreen, its horizontal autosizing can't occur. Size it now.
[
contactListController
contactListDesiredSizeChanged
];
}
else
{
//Offscreen windows should be told not to cast a shadow
[[
self
window
]
setHasShadow
:
NO
];
previousAlpha
=
[[
self
window
]
alphaValue
];
[[
self
window
]
setAlphaValue
:
0.0f
];
AILogWithSignature
(
@"Previous alpha is now %f; window set to alpha 0.0 "
,
previousAlpha
);
}
self
.
windowAnimation
=
nil
;
}
if
(
animation
==
filterBarAnimation
)
{
if
(
filterBarIsVisible
)
{
// If the filter bar is already visible, remove it from its superview.
[
filterBarView
removeFromSuperview
];
// Set the first responder back to the contact list view.
[[
self
window
]
makeFirstResponder
:
contactListView
];
[
contactListView
selectItemsInArray
:
filterBarPreviouslySelected
];
// Since this wasn't a user-initiated selection change, we need to post a notification for it.
[[
NSNotificationCenter
defaultCenter
]
postNotificationName
:
Interface_ContactSelectionChanged
object
:
nil
];
[
filterBarPreviouslySelected
release
];
filterBarPreviouslySelected
=
nil
;
filterBarIsVisible
=
NO
;
}
else
{
// If the filter bar wasn't visible, make it the first responder.
[[
self
window
]
makeFirstResponder
:
searchField
];
// Set the filter bar as the next responder so the chain works for things like the info inspector
[
filterBarView
setNextResponder
:
contactListView
];
// Bring the contact list to front, in case the find command was triggered from another window like the info inspector
[[
self
window
]
makeKeyAndOrderFront
:
nil
];
filterBarPreviouslySelected
=
[[
contactListView
arrayOfSelectedItems
]
retain
];
filterBarIsVisible
=
YES
;
}
// Let the contact list controller know that our size has changed.
[
contactListController
contactListDesiredSizeChanged
];
// We're no longer animating.
self
.
filterBarAnimation
=
nil
;
}
}
-
(
BOOL
)
keepListOnScreenWhenSliding
{
return
NO
;
}
/*!
* @brief Slide the window to a given point
*
* windowSlidOffScreenEdgeMask must already be set to the resulting offscreen mask (or 0 if the window is sliding on screen)
*
* A standard window (titlebar window) will crash if told to setFrame completely offscreen. Also, using our own movement we can more precisely
* control the movement speed and acceleration.
*/
-
(
void
)
slideWindowToPoint:
(
NSPoint
)
targetPoint
{
NSWindow
*
myWindow
=
[
self
window
];
NSScreen
*
windowScreen
;
windowScreen
=
[
myWindow
screen
];
if
(
!
windowScreen
)
windowScreen
=
[
self
windowLastScreen
];
if
(
!
windowScreen
)
windowScreen
=
[
NSScreen
mainScreen
];
NSRect
frame
=
[
myWindow
frame
];
CGFloat
yOff
=
(
targetPoint
.
y
+
NSHeight
(
frame
))
-
NSMaxY
([
windowScreen
frame
]);
if
(
windowScreen
==
[[
NSScreen
screens
]
objectAtIndex
:
0
])
yOff
-=
[[
NSApp
mainMenu
]
menuBarHeight
];
if
(
yOff
>
0
)
targetPoint
.
y
-=
yOff
;
frame
.
origin
=
targetPoint
;
if
((
windowSlidOffScreenEdgeMask
!=
AINoEdges
)
&&
[
self
keepListOnScreenWhenSliding
])
{
switch
(
windowSlidOffScreenEdgeMask
)
{
case
AIMinXEdgeMask
:
frame
.
origin
.
x
+=
1
;
break
;
case
AIMaxXEdgeMask
:
frame
.
origin
.
x
-=
1
;
break
;
case
AIMaxYEdgeMask
:
frame
.
origin
.
y
-=
1
;
break
;
case
AIMinYEdgeMask
:
frame
.
origin
.
y
+=
1
;
break
;
case
AINoEdges
:
//We'll never get here
break
;
}
}
if
(
windowAnimation
)
{
[
windowAnimation
stopAnimation
];
self
.
windowAnimation
=
nil
;
}
self
.
windowAnimation
=
[[[
NSViewAnimation
alloc
]
initWithViewAnimations
:
[
NSArray
arrayWithObject
:
[
NSDictionary
dictionaryWithObjectsAndKeys
:
myWindow
,
NSViewAnimationTargetKey
,
[
NSValue
valueWithRect
:
frame
],
NSViewAnimationEndFrameKey
,
nil
]]]
autorelease
];
[
windowAnimation
setFrameRate
:
0.0f
];
[
windowAnimation
setDuration
:
0.25f
];
[
windowAnimation
setDelegate
:
self
];
[
windowAnimation
setAnimationBlockingMode
:
NSAnimationNonblocking
];
[
windowAnimation
startAnimation
];
}
-
(
void
)
moveWindowToPoint:
(
NSPoint
)
inOrigin
{
[[
self
window
]
setFrameOrigin
:
inOrigin
];
if
(
windowSlidOffScreenEdgeMask
==
AINoEdges
)
{
/* When the window is offscreen, there are no constraints on its size, for example it will grow downwards as much as
* it needs to to accomodate new rows. Now that it's onscreen, there are constraints.
*/
[
contactListController
contactListDesiredSizeChanged
];
[[
self
window
]
setAlphaValue
:
previousAlpha
];
AILogWithSignature
(
@"Set window to previous alpha of %f"
,
previousAlpha
);
}
}
static
BOOL
AIScreenRectEdgeAdjacentToAnyOtherScreen
(
NSRectEdge
edge
,
NSScreen
*
screen
)
{
NSArray
*
screens
=
[
NSScreen
screens
];
NSUInteger
numScreens
=
[
screens
count
];
if
(
numScreens
>
1
)
{
NSRect
screenSlideBoundaryRect
=
[[
screenSlideBoundaryRectDictionary
objectForKey
:
[
NSValue
valueWithNonretainedObject
:
screen
]]
rectValue
];
NSRect
shiftedScreenFrame
=
screenSlideBoundaryRect
;
BOOL
isAdjacent
=
NO
;
switch
(
edge
)
{
case
NSMinXEdge
:
shiftedScreenFrame
.
origin
.
x
-=
1
;
break
;
case
NSMinYEdge
:
shiftedScreenFrame
.
origin
.
y
-=
1
;
break
;
case
NSMaxXEdge
:
shiftedScreenFrame
.
size
.
width
+=
1
;
break
;
case
NSMaxYEdge
:
shiftedScreenFrame
.
size
.
height
+=
1
;
break
;
}
for
(
NSInteger
i
=
0
;
i
<
numScreens
;
i
++
)
{
NSScreen
*
otherScreen
=
[
screens
objectAtIndex
:
i
];
if
(
otherScreen
!=
screen
)
{
if
(
NSIntersectsRect
([
otherScreen
frame
],
shiftedScreenFrame
))
{
isAdjacent
=
YES
;
break
;
}
}
}
return
isAdjacent
;
}
else
{
return
NO
;
}
}
/*!
* @brief Find the mask specifying what edges are potentially slidable for our window
*
* @result AIRectEdgeMask, which is 0 if no edges are slidable
*/
-
(
AIRectEdgeMask
)
slidableEdgesAdjacentToWindow
{
AIRectEdgeMask
slidableEdges
=
0
;
NSWindow
*
window
=
[
self
window
];
NSRect
windowFrame
=
[
window
frame
];
NSScreen
*
windowScreen
=
[
window
screen
];
NSRect
screenSlideBoundaryRect
=
[[
screenSlideBoundaryRectDictionary
objectForKey
:
[
NSValue
valueWithNonretainedObject
:
windowScreen
]]
rectValue
];
NSRectEdge
edge
;
for
(
edge
=
0
;
edge
<
4
;
edge
++
)
{
if
((
SLIDE_ALLOWED_RECT_EDGE_MASK
&
(
1
<<
edge
))
&&
(
AIRectIsAligned_edge_toRect_edge_tolerance_
(
windowFrame
,
edge
,
screenSlideBoundaryRect
,
edge
,
WINDOW_ALIGNMENT_TOLERANCE
))
&&
(
!
AIScreenRectEdgeAdjacentToAnyOtherScreen
(
edge
,
windowScreen
)))
{
slidableEdges
|=
(
1
<<
edge
);
}
}
return
slidableEdges
;
}
-
(
void
)
slideWindowOffScreenEdges:
(
AIRectEdgeMask
)
rectEdgeMask
{
NSWindow
*
window
;
NSRect
newWindowFrame
;
NSRectEdge
edge
;
if
(
rectEdgeMask
==
AINoEdges
)
return
;
window
=
[
self
window
];
newWindowFrame
=
[
window
frame
];
[
self
setSavedFrame
:
newWindowFrame
];
[
windowLastScreen
release
];
windowLastScreen
=
[[
window
screen
]
retain
];
NSRect
screenSlideBoundaryRect
=
[[
screenSlideBoundaryRectDictionary
objectForKey
:
[
NSValue
valueWithNonretainedObject
:
windowLastScreen
]]
rectValue
];
for
(
edge
=
0
;
edge
<
4
;
edge
++
)
{
if
(
rectEdgeMask
&
(
1
<<
edge
))
{
newWindowFrame
=
AIRectByAligningRect_edge_toRect_edge_
(
newWindowFrame
,
AIOppositeRectEdge_
(
edge
),
screenSlideBoundaryRect
,
edge
);
}
}
windowSlidOffScreenEdgeMask
|=
rectEdgeMask
;
[
self
slideWindowToPoint
:
newWindowFrame
.
origin
];
}
-
(
void
)
slideWindowOnScreenWithAnimation:
(
BOOL
)
animate
{
if
([
self
windowSlidOffScreenEdgeMask
]
!=
AINoEdges
)
{
NSWindow
*
window
=
[
self
window
];
animate
=
animate
&&
!
NSEqualRects
(
window
.
frame
,
oldFrame
);
//Restore shadow and frame if we're appearing from having slid off-screen
[
window
setHasShadow
:
[[
adium
.
preferenceController
preferenceForKey
:
KEY_CL_WINDOW_HAS_SHADOW
group
:
PREF_GROUP_CONTACT_LIST
]
boolValue
]];
[
window
orderFront
:
nil
];
[
contactListController
contactListWillSlideOnScreen
];
windowSlidOffScreenEdgeMask
=
AINoEdges
;
if
(
animate
)
{
[
self
slideWindowToPoint
:
oldFrame
.
origin
];
}
else
{
[
self
moveWindowToPoint
:
oldFrame
.
origin
];
}
[
windowLastScreen
release
];
windowLastScreen
=
nil
;
}
}
-
(
void
)
slideWindowOnScreen
{
[
self
slideWindowOnScreenWithAnimation
:
YES
];
}
-
(
void
)
setPreventHiding:
(
BOOL
)
newPreventHiding
{
preventHiding
=
newPreventHiding
;
}
-
(
void
)
endWindowSlidingDelay
{
[
self
setPreventHiding
:
NO
];
}
-
(
void
)
delayWindowSlidingForInterval:
(
NSTimeInterval
)
inDelayTime
{
[
self
setPreventHiding
:
YES
];
[
NSObject
cancelPreviousPerformRequestsWithTarget
:
self
selector
:
@selector
(
endWindowSlidingDelay
)
object
:
nil
];
[
self
performSelector
:
@selector
(
endWindowSlidingDelay
)
withObject
:
nil
afterDelay
:
inDelayTime
];
}
-
(
AIRectEdgeMask
)
windowSlidOffScreenEdgeMask
{
return
windowSlidOffScreenEdgeMask
;
}
// Snap Groups Together------------------------------------------------------------------------------------------------
#pragma mark Snap Groups Together
/*!
* @brief If window did move and is not docked then snap it to other windows
*/
-
(
void
)
windowDidMove:
(
NSNotification
*
)
notification
{
BOOL
suppressSnapping
=
[
NSEvent
shiftKey
];
attachToBottom
=
nil
;
if
(
windowSlidOffScreenEdgeMask
==
AINoEdges
&&
!
suppressSnapping
)
[
self
snapToOtherWindows
];
}
/*!
* @brief Captures mouse up event to check that if the window snapped underneath
* another window they are merged together
*/
-
(
void
)
mouseUp:
(
NSEvent
*
)
event
{
if
(
attachToBottom
)
{
AIContactList
*
from
=
(
AIContactList
*
)[
self
contactList
];
AIContactList
*
to
=
(
AIContactList
*
)[
attachToBottom
contactList
];
for
(
AIListGroup
*
group
in
from
)
{
[
adium
.
contactController
moveGroup
:
group
fromContactList
:
from
toContactList
:
to
];
}
[[
NSNotificationCenter
defaultCenter
]
postNotificationName
:
DetachedContactListIsEmpty
object
:
from
userInfo
:
nil
];
[[
NSNotificationCenter
defaultCenter
]
postNotificationName
:
@"Contact_ListChanged"
object
:
to
userInfo
:
nil
];
}
[
super
mouseUp
:
event
];
}
/*!
* @brief Snaps window to windows next to it
*/
-
(
void
)
snapToOtherWindows
{
NSWindow
*
myWindow
=
[
self
window
];
NSArray
*
windows
=
[[
NSApplication
sharedApplication
]
windows
];
NSWindow
*
window
;
NSRect
currentFrame
=
[
myWindow
frame
];
NSPoint
suggested
=
currentFrame
.
origin
;
// Check to snap to each guide
for
(
window
in
windows
)
{
// No snapping to itself and it must be within a snapping distance to other windows
if
((
window
!=
myWindow
)
&&
[
window
delegate
]
&&
[
window
isVisible
]
&&
[[
window
delegate
]
conformsToProtocol
:
@
protocol
(
AIInterfaceContainer
)])
{
/* Note: [window delegate] may be invalid if the window is in the middle of closing.
* Checking if it's visible should hopefully cover that case.
*/
suggested
=
[
self
snapTo
:
window
with
:
currentFrame
saveTo
:
suggested
];
}
}
[[
self
window
]
setFrameOrigin
:
suggested
];
}
/*!
* @brief Check that window is inside snappable region of other window
*/
static
BOOL
isInRangeOfRect
(
NSRect
sourceRect
,
NSRect
targetRect
)
{
return
NSIntersectsRect
(
NSInsetRect
(
sourceRect
,
-
SNAP_DISTANCE
,
-
SNAP_DISTANCE
),
targetRect
);
}
/*!
* @brief Check if points are close enough to be snapped together
*/
static
BOOL
canSnap
(
CGFloat
a
,
CGFloat
b
)
{
return
(
AIfabs
(
a
-
b
)
<=
SNAP_DISTANCE
);
}
-
(
NSPoint
)
snapTo:
(
NSWindow
*
)
neighborWindow
with:
(
NSRect
)
currentRect
saveTo:
(
NSPoint
)
location
{
NSRect
neighbor
=
[
neighborWindow
frame
];
NSPoint
spacing
=
[
self
windowSpacing
];
NSUInteger
overlap
=
0
;
NSUInteger
bottom
=
0
;
if
(
!
NSEqualRects
(
neighbor
,
currentRect
)
&&
isInRangeOfRect
(
currentRect
,
neighbor
))
{
// X Snapping
if
(
canSnap
(
NSMaxX
(
currentRect
),
NSMinX
(
neighbor
)))
{
location
.
x
=
NSMinX
(
neighbor
)
-
NSWidth
(
currentRect
)
-
spacing
.
x
;
}
else
if
(
canSnap
(
NSMinX
(
currentRect
),
NSMaxX
(
neighbor
)))
{
location
.
x
=
NSMaxX
(
neighbor
)
+
spacing
.
x
;
}
else
if
(
canSnap
(
NSMinX
(
currentRect
),
NSMinX
(
neighbor
)))
{
location
.
x
=
NSMinX
(
neighbor
);
overlap
++
;
bottom
++
;
}
// Y Snapping
if
(
canSnap
(
NSMaxY
(
neighbor
),
NSMaxY
(
currentRect
)))
{
location
.
y
=
NSMaxY
(
neighbor
)
-
NSHeight
(
currentRect
);
overlap
++
;
}
else
if
(
canSnap
(
NSMinY
(
neighbor
),
NSMaxY
(
currentRect
)))
{
location
.
y
=
NSMinY
(
neighbor
)
-
NSHeight
(
currentRect
)
-
spacing
.
y
;
bottom
++
;
}
else
if
(
canSnap
(
NSMaxY
(
neighbor
),
NSMinY
(
currentRect
)))
{
location
.
y
=
NSMaxY
(
neighbor
)
+
spacing
.
y
;
}
else
if
(
canSnap
(
NSMinY
(
neighbor
),
NSMinY
(
currentRect
)))
{
location
.
y
=
NSMinY
(
neighbor
);
overlap
++
;
}
}
// If we snapped on top of neighbor
if
(
overlap
==
2
)
return
currentRect
.
origin
;
// Save window that we could possible attach to
if
(
bottom
==
2
)
attachToBottom
=
(
AIListWindowController
*
)[
neighborWindow
delegate
];
return
location
;
}
/*!
* @brief Gets space that windows should be apart by based on current window style
*/
-
(
NSPoint
)
windowSpacing
{
AIContactListWindowStyle
style
=
[[
adium
.
preferenceController
preferenceForKey
:
KEY_LIST_LAYOUT_WINDOW_STYLE
group
:
PREF_GROUP_APPEARANCE
]
intValue
];
CGFloat
space
=
(
CGFloat
)[[
adium
.
preferenceController
preferenceForKey
:
@"Group Top Spacing"
group
:
@"List Layout"
]
doubleValue
];
switch
(
style
)
{
case
AIContactListWindowStyleStandard
:
case
AIContactListWindowStyleBorderless
:
case
AIContactListWindowStyleGroupChat
:
return
NSMakePoint
(
0
,
0
);
case
AIContactListWindowStyleGroupBubbles
:
case
AIContactListWindowStyleContactBubbles
:
case
AIContactListWindowStyleContactBubbles_Fitted
:
return
NSMakePoint
(
space
,
space
-
WINDOW_ALIGNMENT_TOLERANCE
);
}
return
NSMakePoint
(
0
,
0
);
}
#pragma mark Filtering
/*!
* @brief Toggles the find bar on, or brings it into focus if it is already visible
*/
-
(
void
)
toggleFindPanel:
(
id
)
sender
;
{
if
(
filterBarIsVisible
)
{
[[
self
window
]
makeFirstResponder
:
searchField
];
}
else
if
([
contactListView
numberOfRows
]
>
0
)
{
filterBarShownAutomatically
=
NO
;
[
self
showFilterBarWithAnimation
:
YES
];
}
else
{
NSBeep
();
}
}
/*!
* @brief Hide the filter bar
*/
-
(
IBAction
)
hideFilterBar
:
(
id
)
sender
;
{
[
self
hideFilterBarWithAnimation
:
YES
];
}
/*!
* @brief Show the filter bar
*
* @param useAnimation If YES, the filter bar will scroll into view, otherwise it appears immediately
*/
-
(
void
)
showFilterBarWithAnimation
:
(
BOOL
)
useAnimation
{
if
(
filterBarIsVisible
||
filterBarAnimation
)
return
;
// While the filter bar is shown, temporarily disable automatic horizontal resizing
contactListController
.
autoResizeHorizontally
=
NO
;
// Disable contact list animation while the filter bar is shown
[
contactListView
setEnableAnimation
:
NO
];
// Animate the filter bar into view
[
self
animateFilterBarWithDuration
:
(
useAnimation
?
0.15f
:
0.0f
)];
}
/*!
* @brief Hide the filter bar
*
* @param useAnimation If YES, the filter bar will scroll out of view, otherwise it disappears immediately
*/
-
(
void
)
hideFilterBarWithAnimation
:
(
BOOL
)
useAnimation
{
if
(
!
filterBarIsVisible
||
filterBarAnimation
)
return
;
// Clear the search field so that visibility is reset
[
searchField
setStringValue
:
@""
];
[
self
filterContacts
:
searchField
];
// Restore the default settings which we temporarily disabled previously
contactListController
.
autoResizeHorizontally
=
[[
adium
.
preferenceController
preferenceForKey
:
KEY_LIST_LAYOUT_HORIZONTAL_AUTOSIZE
group
:
PREF_GROUP_APPEARANCE
]
boolValue
];
[
contactListView
setEnableAnimation
:
[[
adium
.
preferenceController
preferenceForKey
:
KEY_CL_ANIMATE_CHANGES
group
:
PREF_GROUP_CONTACT_LIST
]
boolValue
]];
// Animate the filter bar out of view
[
self
animateFilterBarWithDuration
:
(
useAnimation
?
0.15f
:
0.0f
)];
}
/*!
* @brief Animates the filter bar in and out of view
*
* @param duration The duration the animation will last
*/
-
(
void
)
animateFilterBarWithDuration
:
(
CGFloat
)
duration
{
NSView
*
targetView
=
([
contactListView
enclosingScrollView
]
?
(
NSView
*
)[
contactListView
enclosingScrollView
]
:
contactListView
);
NSRect
targetFrame
=
[
targetView
frame
];
NSDictionary
*
targetViewDict
,
*
filterBarDict
;
// Contact list resizing
if
(
filterBarIsVisible
)
{
targetFrame
.
size
.
height
=
NSHeight
(
targetFrame
)
+
NSHeight
([
filterBarView
bounds
]);
}
else
{
/* We can only have a height less than the filter bar view if we are autosizing vertically, as
* there is a minimum height otherwise which is larger. We can therefore increase our window size to allow space
* for the filter bar with impunity and without undoing this when hiding the bar, as the autosizing of the contact
* list will get us back to the right size later.
*/
if
(
NSHeight
(
targetFrame
)
<
(
NSHeight
([
filterBarView
bounds
])
*
2
))
{
NSRect
windowFrame
=
[[
targetView
window
]
frame
];
[[
targetView
window
]
setFrame
:
NSMakeRect
(
NSMinX
(
windowFrame
),
NSMinY
(
windowFrame
)
-
NSHeight
([
filterBarView
bounds
]),
NSWidth
(
windowFrame
),
NSHeight
(
windowFrame
)
+
NSHeight
([
filterBarView
bounds
]))
display
:
NO
animate
:
NO
];
targetFrame
=
[
targetView
frame
];
}
targetFrame
.
size
.
height
=
NSHeight
(
targetFrame
)
-
NSHeight
([
filterBarView
bounds
]);
}
/* Setting a frame's height to 0 can permanently destroy its ability to display properly.
* This is the case with an NSOutlineView. If our contact list was invisibile (because no contacts
* were visible), create a 1 pixel border rather than traumatizing it for life.
*/
if
(
targetFrame
.
size
.
height
==
0
)
targetFrame
.
size
.
height
=
1
;
// Filter bar resizing
NSRect
barTargetFrame
=
contactListView
.
enclosingScrollView
.
frame
;
if
(
filterBarIsVisible
)
{
barTargetFrame
.
size
.
height
=
NSHeight
(
barTargetFrame
)
+
NSHeight
(
filterBarView
.
bounds
);
}
else
{
barTargetFrame
.
size
.
height
=
NSHeight
(
barTargetFrame
)
-
NSHeight
(
filterBarView
.
bounds
);
}
if
(
!
filterBarIsVisible
)
{
// If the filter bar isn't already visible
[
filterBarView
setFrame
:
NSMakeRect
(
NSMinX
(
barTargetFrame
),
NSHeight
([
contactListView
frame
]),
NSWidth
(
barTargetFrame
),
NSHeight
([
filterBarView
bounds
]))];
// Attach the filter bar to the window
[[[
self
window
]
contentView
]
addSubview
:
filterBarView
];
}
filterBarDict
=
[
NSDictionary
dictionaryWithObjectsAndKeys
:
filterBarView
,
NSViewAnimationTargetKey
,
[
NSValue
valueWithRect
:
NSMakeRect
(
NSMinX
(
barTargetFrame
),
NSHeight
(
barTargetFrame
),
NSWidth
(
barTargetFrame
),
NSHeight
([
filterBarView
bounds
]))],
NSViewAnimationEndFrameKey
,
nil
];
targetViewDict
=
[
NSDictionary
dictionaryWithObjectsAndKeys
:
targetView
,
NSViewAnimationTargetKey
,
[
NSValue
valueWithRect
:
targetFrame
],
NSViewAnimationEndFrameKey
,
nil
];
self
.
filterBarAnimation
=
[[[
NSViewAnimation
alloc
]
initWithViewAnimations
:
[
NSArray
arrayWithObjects
:
targetViewDict
,
filterBarDict
,
nil
]]
autorelease
];
[
filterBarAnimation
setDuration
:
duration
];
[
filterBarAnimation
setAnimationBlockingMode
:
NSAnimationBlocking
];
[
filterBarAnimation
setDelegate
:
self
];
// Start the animation
[
filterBarAnimation
startAnimation
];
}
/*!
* @brief Called when the window loses focus
*/
-
(
void
)
windowDidResignMain
:
(
NSNotification
*
)
sender
{
/* If the filter bar was shown by type-to-find (but not by command-F), and the window is no longer main,
* assume the user is done and hide the filter bar.
*/
if
(
filterBarIsVisible
&&
filterBarShownAutomatically
)
[
self
hideFilterBarWithAnimation
:
NO
];
}
/*!
* @brief Forward typing events from the contact list to the filter bar
*/
-
(
BOOL
)
forwardKeyEventToFindPanel
:
(
NSEvent
*
)
theEvent
;
{
if
(
!
typeToFindEnabled
)
return
NO
;
//if we were not searching something before, we need to show the filter bar first without animation
NSString
*
charString
=
[
theEvent
charactersIgnoringModifiers
];
unichar
pressedChar
=
0
;
//Get the pressed character
if
([
charString
length
]
==
1
)
pressedChar
=
[
charString
characterAtIndex
:
0
];
#define NSEscapeFunctionKey 27
/* Hitting escape once should clear any existing selection. Keys with functional modifiers pressed should not be passed.
* Home and End should be passed to the find panel only if it is already visible.
*/
if
(((
pressedChar
==
NSEscapeFunctionKey
)
&&
([
contactListView
selectedRow
]
!=
-1
||
!
filterBarIsVisible
))
||
(([
theEvent
modifierFlags
]
&
NSCommandKeyMask
)
||
([
theEvent
modifierFlags
]
&
NSAlternateKeyMask
)
||
([
theEvent
modifierFlags
]
&
NSControlKeyMask
))
||
((
pressedChar
==
NSPageUpFunctionKey
)
||
(
pressedChar
==
NSPageDownFunctionKey
)
||
(
pressedChar
==
NSMenuFunctionKey
))
||
(
!
filterBarIsVisible
&&
((
pressedChar
==
NSHomeFunctionKey
)
||
(
pressedChar
==
NSEndFunctionKey
))))
{
return
NO
;
}
else
{
if
(
!
filterBarIsVisible
)
{
[
self
toggleFindPanel
:
nil
];
filterBarShownAutomatically
=
YES
;
}
[[
self
window
]
makeFirstResponder
:
searchField
];
[[[
self
window
]
fieldEditor
:
YES
forObject
:
searchField
]
keyDown
:
theEvent
];
return
YES
;
}
}
/*!
* @brief Process text commands while on the search field
*/
-
(
BOOL
)
control
:
(
NSControl
*
)
control
textView
:
(
NSTextView
*
)
textView
doCommandBySelector
:
(
SEL
)
command
{
// Only process commands when we're in the search field.
if
(
control
!=
searchField
)
return
NO
;
if
(
command
==
@selector
(
insertNewline
:
))
{
// If we have a search term, open a chat with the first contact
if
(
!
[[
textView
string
]
isEqualToString
:
@""
])
[
self
performDefaultActionOnSelectedObject
:
[
contactListView
firstVisibleListContact
]
sender
:
contactListView
];
// Hide the filter bar
[
self
hideFilterBarWithAnimation
:
YES
];
}
else
if
(
command
==
@selector
(
moveDown
:
))
{
// The down arrow functions to move into the contact list view
[[
self
window
]
makeFirstResponder
:
contactListView
];
}
else
if
(
command
==
@selector
(
cancelOperation
:
))
{
// Escape hides the filter bar.
[
self
hideFilterBarWithAnimation
:
YES
];
}
else
{
// If we didn't process a command, return NO.
return
NO
;
}
// We processed a command, return YES.
return
YES
;
}
/*!
* @brief Filter contacts from the search field
*
* This method will expand or contract groups as necessary, as well as handle forwarding the search term to
* the contact hiding controller.
*/
-
(
IBAction
)
filterContacts
:
(
id
)
sender
;
{
if
(
!
[
sender
isKindOfClass
:
[
NSSearchField
class
]])
return
;
if
(
!
filterBarExpandedGroups
&&
!
[[
sender
stringValue
]
isEqualToString
:
@""
])
{
BOOL
modified
=
NO
;
for
(
AIListObject
*
listObject
in
[
self
.
contactList
containedObjects
])
{
if
([
listObject
isKindOfClass
:
[
AIListGroup
class
]]
&&
[(
AIListGroup
*
)
listObject
isExpanded
]
==
NO
)
{
[
listObject
setValue
:
[
NSNumber
numberWithBool
:
YES
]
forProperty
:
@"ExpandedByFiltering"
notify
:
NotifyNever
];
modified
=
YES
;
}
}
filterBarExpandedGroups
=
YES
;
if
(
modified
)
{
[
contactListView
reloadData
];
}
}
else
if
(
filterBarExpandedGroups
&&
[[
sender
stringValue
]
isEqualToString
:
@""
])
{
BOOL
modified
=
NO
;
for
(
AIListObject
*
listObject
in
[
self
.
contactList
containedObjects
])
{
if
([
listObject
isKindOfClass
:
[
AIListGroup
class
]]
&&
[
listObject
boolValueForProperty
:
@"ExpandedByFiltering"
])
{
[
listObject
setValue
:
[
NSNumber
numberWithBool
:
NO
]
forProperty
:
@"ExpandedByFiltering"
notify
:
NotifyNever
];
modified
=
YES
;
}
}
filterBarExpandedGroups
=
NO
;
if
(
modified
)
{
[
contactListView
reloadData
];
}
}
if
([[
AIContactHidingController
sharedController
]
filterContacts
:
[
sender
stringValue
]])
{
// Select the first contact; we're guaranteed at least one visible contact.
[
contactListView
selectRowIndexes
:
[
NSIndexSet
indexSetWithIndex
:
[
contactListView
indexOfFirstVisibleListContact
]]
byExtendingSelection
:
NO
];
// Since this wasn't a user-initiated selection change, we need to post a notification for it.
[[
NSNotificationCenter
defaultCenter
]
postNotificationName
:
Interface_ContactSelectionChanged
object
:
nil
];
[[
searchField
cell
]
setTextColor
:
nil
backgroundColor
:
nil
];
}
else
{
//White on light red (like Firefox!)
[[
searchField
cell
]
setTextColor
:
[
NSColor
whiteColor
]
backgroundColor
:
[
NSColor
colorWithCalibratedHue
:
0.983f
saturation
:
0.43f
brightness
:
0.99f
alpha
:
1.0f
]];
}
}
/*!
* @brief Delegate method for the search field's close button
*/
-
(
void
)
rolloverButton
:
(
AIRolloverButton
*
)
inButton
mouseChangedToInsideButton
:
(
BOOL
)
isInside
{
[
button_cancelFilterBar
setImage
:
[
NSImage
imageNamed
:
(
isInside
?
@"FTProgressStopRollover"
:
@"FTProgressStop"
)
forClass
:[
self
class
]]];
}
@end