adium/adium
Clone
Summary
Browse
Changes
Graph
Prompt the user to authenticate again when using the refresh_token gives an error. Also, show the error to the user.
GoogleOAuth2
2015-04-01, Thijs Alkemade
c4923ddd82bf
Prompt the user to authenticate again when using the refresh_token gives an error. Also, show the error to the user.
/*
* 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 preferences)
[
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