|
David@0
|
1 |
/* |
|
David@0
|
2 |
* Adium is the legal property of its developers, whose names are listed in the copyright file included |
|
David@0
|
3 |
* with this source distribution. |
|
David@0
|
4 |
* |
|
David@0
|
5 |
* This program is free software; you can redistribute it and/or modify it under the terms of the GNU |
|
David@0
|
6 |
* General Public License as published by the Free Software Foundation; either version 2 of the License, |
|
David@0
|
7 |
* or (at your option) any later version. |
|
David@0
|
8 |
* |
|
David@0
|
9 |
* This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even |
|
David@0
|
10 |
* the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General |
|
David@0
|
11 |
* Public License for more details. |
|
David@0
|
12 |
* |
|
David@0
|
13 |
* You should have received a copy of the GNU General Public License along with this program; if not, |
|
David@0
|
14 |
* write to the Free Software Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. |
|
David@0
|
15 |
*/ |
|
David@0
|
16 |
|
|
David@0
|
17 |
// $Id$ |
|
David@0
|
18 |
|
|
David@0
|
19 |
#import "AIInterfaceController.h" |
|
David@0
|
20 |
|
|
David@0
|
21 |
#import <Adium/AIAccountControllerProtocol.h> |
|
David@0
|
22 |
#import <Adium/AIContactControllerProtocol.h> |
|
David@0
|
23 |
#import <Adium/AIChatControllerProtocol.h> |
|
David@0
|
24 |
#import <Adium/AIContentControllerProtocol.h> |
|
David@0
|
25 |
#import <Adium/AIMenuControllerProtocol.h> |
|
zacw@1260
|
26 |
#import <Adium/AIAuthorizationRequestsWindowController.h> |
|
David@0
|
27 |
#import <AIUtilities/AIAttributedStringAdditions.h> |
|
David@0
|
28 |
#import <AIUtilities/AIColorAdditions.h> |
|
David@0
|
29 |
#import <AIUtilities/AIFontAdditions.h> |
|
David@0
|
30 |
#import <AIUtilities/AIImageDrawingAdditions.h> |
|
David@0
|
31 |
#import <AIUtilities/AIMenuAdditions.h> |
|
David@0
|
32 |
#import <AIUtilities/AIStringAdditions.h> |
|
David@0
|
33 |
#import <AIUtilities/AITooltipUtilities.h> |
|
David@0
|
34 |
#import <AIUtilities/AIWindowAdditions.h> |
|
David@0
|
35 |
#import <AIUtilities/AITextAttributes.h> |
|
David@0
|
36 |
#import <AIUtilities/AIWindowControllerAdditions.h> |
|
David@0
|
37 |
#import <Adium/AIChat.h> |
|
David@0
|
38 |
#import <Adium/AIListContact.h> |
|
David@0
|
39 |
#import <Adium/AIListGroup.h> |
|
David@0
|
40 |
#import <Adium/AIListObject.h> |
|
David@0
|
41 |
#import <Adium/AIMetaContact.h> |
|
David@0
|
42 |
#import <Adium/AIService.h> |
|
David@0
|
43 |
#import <Adium/AIServiceIcons.h> |
|
David@0
|
44 |
#import <Adium/AISortController.h> |
|
David@0
|
45 |
#import "AIMessageTabViewItem.h" |
|
catfish@1894
|
46 |
#import "KNShelfSplitview.h" |
|
David@52
|
47 |
#import <Adium/AIContactList.h> |
|
David@0
|
48 |
|
|
David@0
|
49 |
#import "AIMessageViewController.h" |
|
David@0
|
50 |
|
|
David@0
|
51 |
#define ERROR_MESSAGE_WINDOW_TITLE AILocalizedString(@"Adium : Error","Error message window title") |
|
David@0
|
52 |
#define LABEL_ENTRY_SPACING 4.0 |
|
David@0
|
53 |
#define DISPLAY_IMAGE_ON_RIGHT NO |
|
David@0
|
54 |
|
|
David@0
|
55 |
#define PREF_GROUP_FORMATTING @"Formatting" |
|
David@0
|
56 |
#define KEY_FORMATTING_FONT @"Default Font" |
|
David@0
|
57 |
|
|
David@0
|
58 |
#define MESSAGES_WINDOW_MENU_TITLE AILocalizedString(@"Chats","Title for the messages window menu item") |
|
David@0
|
59 |
|
|
David@0
|
60 |
//#define LOG_RESPONDER_CHAIN |
|
David@0
|
61 |
|
|
David@84
|
62 |
@interface AIInterfaceController () |
|
David@0
|
63 |
- (void)_resetOpenChatsCache; |
|
David@0
|
64 |
- (void)_addItemToMainMenuAndDock:(NSMenuItem *)item; |
|
David@0
|
65 |
- (NSAttributedString *)_tooltipTitleForObject:(AIListObject *)object; |
|
David@0
|
66 |
- (NSAttributedString *)_tooltipBodyForObject:(AIListObject *)object; |
|
David@0
|
67 |
- (void)_pasteWithPreferredSelector:(SEL)preferredSelector sender:(id)sender; |
|
David@0
|
68 |
- (void)updateCloseMenuKeys; |
|
David@0
|
69 |
|
|
David@0
|
70 |
- (void)saveContainers; |
|
David@0
|
71 |
- (void)restoreSavedContainers; |
|
David@0
|
72 |
|
|
David@0
|
73 |
//Window Menu |
|
David@0
|
74 |
- (void)updateActiveWindowMenuItem; |
|
David@0
|
75 |
- (void)buildWindowMenu; |
|
David@0
|
76 |
|
|
David@0
|
77 |
- (AIChat *)mostRecentActiveChat; |
|
David@0
|
78 |
@end |
|
David@0
|
79 |
|
|
David@0
|
80 |
/*! |
|
David@0
|
81 |
* @class AIInterfaceController |
|
David@0
|
82 |
* @brief Interface controller |
|
David@0
|
83 |
* |
|
David@0
|
84 |
* Chat window related requests, such as opening and closing chats, are routed through the interface controller |
|
David@0
|
85 |
* to the appropriate component. The interface controller keeps track of the most recently active chat, handles chat |
|
David@0
|
86 |
* cycling (switching between chats), chat sorting, and so on. The interface controller also handles switching to |
|
David@0
|
87 |
* an appropriate window or chat when the dock icon is clicked for a 'reopen' event. |
|
David@0
|
88 |
* |
|
David@0
|
89 |
* Contact list window requests, such as toggling window visibilty are routed to the contact list controller component. |
|
David@0
|
90 |
* |
|
David@0
|
91 |
* Error messages are routed through the interface controller. |
|
David@0
|
92 |
* |
|
David@0
|
93 |
* Tooltips, such as seen on hover in the contact list are generated and displayed here. Tooltip display components and |
|
David@0
|
94 |
* plugins register with the interface controller to be queried for contact information when a tooltip is displayed. |
|
David@0
|
95 |
* |
|
David@0
|
96 |
* When displays in Adium flash, such as in the dock or the contact list for unviewed content, the interface controller |
|
David@0
|
97 |
* manages keeping the flashing synchronized. |
|
David@0
|
98 |
* |
|
David@0
|
99 |
* Finally, the interface controller manages many menu items, providing better menu item validation and target routing |
|
David@0
|
100 |
* than the responder chain alone would do. |
|
David@0
|
101 |
*/ |
|
David@0
|
102 |
@implementation AIInterfaceController |
|
David@0
|
103 |
|
|
David@0
|
104 |
- (id)init |
|
David@0
|
105 |
{ |
|
David@0
|
106 |
if ((self = [super init])) { |
|
David@0
|
107 |
contactListViewArray = [[NSMutableArray alloc] init]; |
|
David@0
|
108 |
messageViewArray = [[NSMutableArray alloc] init]; |
|
David@0
|
109 |
contactListTooltipEntryArray = [[NSMutableArray alloc] init]; |
|
David@0
|
110 |
contactListTooltipSecondaryEntryArray = [[NSMutableArray alloc] init]; |
|
David@0
|
111 |
closeMenuConfiguredForChat = NO; |
|
David@0
|
112 |
_cachedOpenChats = nil; |
|
David@0
|
113 |
mostRecentActiveChat = nil; |
|
David@0
|
114 |
activeChat = nil; |
|
David@0
|
115 |
|
|
David@0
|
116 |
tooltipListObject = nil; |
|
David@0
|
117 |
tooltipTitle = nil; |
|
David@0
|
118 |
tooltipBody = nil; |
|
David@0
|
119 |
tooltipImage = nil; |
|
David@0
|
120 |
flashObserverArray = nil; |
|
David@0
|
121 |
flashTimer = nil; |
|
David@0
|
122 |
flashState = 0; |
|
David@0
|
123 |
|
|
David@0
|
124 |
windowMenuArray = nil; |
|
David@0
|
125 |
|
|
David@0
|
126 |
#ifdef LOG_RESPONDER_CHAIN |
|
David@0
|
127 |
[NSTimer scheduledTimerWithTimeInterval:2.0 target:self selector:@selector(reportResponderChain:) userInfo:nil repeats:YES]; |
|
David@0
|
128 |
#endif |
|
David@0
|
129 |
} |
|
David@0
|
130 |
|
|
David@0
|
131 |
return self; |
|
David@0
|
132 |
} |
|
David@0
|
133 |
|
|
David@0
|
134 |
#ifdef LOG_RESPONDER_CHAIN |
|
David@0
|
135 |
//Can be called by a timer to periodically log the responder chain |
|
David@0
|
136 |
//[NSTimer scheduledTimerWithTimeInterval:2.0 target:self selector:@selector(reportResponderChain:) userInfo:nil repeats:YES]; |
|
David@0
|
137 |
- (void)reportResponderChain:(NSTimer *)inTimer |
|
David@0
|
138 |
{ |
|
David@0
|
139 |
NSMutableString *responderChain = [NSMutableString string]; |
|
David@0
|
140 |
|
|
David@0
|
141 |
NSWindow *keyWindow = [[NSApplication sharedApplication] keyWindow]; |
|
David@3
|
142 |
#warning 64BIT: Check formatting arguments |
|
David@0
|
143 |
[responderChain appendFormat:@"%@ (%i): ",keyWindow,[keyWindow respondsToSelector:@selector(print:)]]; |
|
David@0
|
144 |
|
|
David@0
|
145 |
NSResponder *responder = [keyWindow firstResponder]; |
|
David@0
|
146 |
|
|
David@0
|
147 |
//First, walk down the responder chain looking for a responder which can handle the preferred selector |
|
David@0
|
148 |
while (responder) { |
|
David@3
|
149 |
#warning 64BIT: Check formatting arguments |
|
David@0
|
150 |
[responderChain appendFormat:@"%@ (%i)",responder,[responder respondsToSelector:@selector(print:)]]; |
|
David@0
|
151 |
responder = [responder nextResponder]; |
|
David@0
|
152 |
if (responder) [responderChain appendString:@" -> "]; |
|
David@0
|
153 |
} |
|
David@0
|
154 |
|
|
David@0
|
155 |
NSLog(responderChain); |
|
David@0
|
156 |
} |
|
David@0
|
157 |
#endif |
|
David@0
|
158 |
|
|
David@0
|
159 |
- (void)controllerDidLoad |
|
David@0
|
160 |
{ |
|
David@0
|
161 |
//Load the interface |
|
David@0
|
162 |
[interfacePlugin openInterface]; |
|
David@0
|
163 |
|
|
David@0
|
164 |
//Open the contact list window |
|
David@0
|
165 |
[self showContactList:nil]; |
|
David@0
|
166 |
|
|
David@0
|
167 |
//Userlist show/hide item |
|
David@0
|
168 |
menuItem_toggleUserlist = [[NSMenuItem allocWithZone:[NSMenu menuZone]] initWithTitle:AILocalizedString(@"Toggle User List", nil) |
|
David@0
|
169 |
target:self |
|
David@0
|
170 |
action:@selector(toggleUserlist:) |
|
zacw@872
|
171 |
keyEquivalent:@"/"]; |
|
zacw@872
|
172 |
[menuItem_toggleUserlist setKeyEquivalentModifierMask:(NSCommandKeyMask | NSAlternateKeyMask)]; |
|
zacw@872
|
173 |
|
|
zacw@1543
|
174 |
[adium.menuController addMenuItem:menuItem_toggleUserlist toLocation:LOC_Display_General]; |
|
zacw@1587
|
175 |
|
|
zacw@1587
|
176 |
menuItem_toggleUserlistSide = [[NSMenuItem allocWithZone:[NSMenu menuZone]] initWithTitle:AILocalizedString(@"Toggle User List Side", nil) |
|
zacw@1587
|
177 |
target:self |
|
zacw@1587
|
178 |
action:@selector(toggleUserlistSide:) |
|
zacw@1587
|
179 |
keyEquivalent:@""]; |
|
zacw@1587
|
180 |
|
|
zacw@1587
|
181 |
[adium.menuController addMenuItem:menuItem_toggleUserlistSide toLocation:LOC_Display_General]; |
|
zacw@1244
|
182 |
|
|
zacw@1244
|
183 |
NSMenuItem *menuItem = [[[NSMenuItem allocWithZone:[NSMenu menuZone]] initWithTitle:AILocalizedString(@"Toggle User List", nil) |
|
zacw@1244
|
184 |
target:self |
|
zacw@1689
|
185 |
action:@selector(toggleUserlist:) |
|
zacw@1244
|
186 |
keyEquivalent:@""] autorelease]; |
|
zacw@1240
|
187 |
|
|
zacw@1244
|
188 |
[adium.menuController addContextualMenuItem:menuItem toLocation:Context_GroupChat_Action]; |
|
zacw@1588
|
189 |
|
|
zacw@1588
|
190 |
// Clear display |
|
zacw@1588
|
191 |
menuItem_clearDisplay = [[NSMenuItem allocWithZone:[NSMenu menuZone]] initWithTitle:AILocalizedString(@"Clear Display", nil) |
|
zacw@1588
|
192 |
target:self |
|
zacw@1588
|
193 |
action:@selector(clearDisplay:) |
|
zacw@1588
|
194 |
keyEquivalent:@""]; |
|
zacw@1588
|
195 |
[adium.menuController addMenuItem:menuItem_clearDisplay toLocation:LOC_Display_MessageControl]; |
|
David@0
|
196 |
|
|
David@0
|
197 |
//Contact list menu item |
|
zacw@1244
|
198 |
menuItem = [[NSMenuItem allocWithZone:[NSMenu menuZone]] initWithTitle:AILocalizedString(@"Contact List","Name of the window which lists contacts") |
|
David@0
|
199 |
target:self |
|
David@0
|
200 |
action:@selector(toggleContactList:) |
|
David@0
|
201 |
keyEquivalent:@"/"]; |
|
David@100
|
202 |
[adium.menuController addMenuItem:menuItem toLocation:LOC_Window_Fixed]; |
|
David@100
|
203 |
[adium.menuController addMenuItem:[[menuItem copy] autorelease] toLocation:LOC_Dock_Status]; |
|
David@0
|
204 |
[menuItem release]; |
|
David@0
|
205 |
|
|
David@0
|
206 |
menuItem = [[NSMenuItem allocWithZone:[NSMenu menuZone]] initWithTitle:AILocalizedString(@"Close Chat","Title for the close chat menu item") |
|
David@0
|
207 |
target:self |
|
David@0
|
208 |
action:@selector(closeContextualChat:) |
|
David@0
|
209 |
keyEquivalent:@""]; |
|
David@100
|
210 |
[adium.menuController addContextualMenuItem:menuItem toLocation:Context_Tab_Action]; |
|
David@0
|
211 |
[menuItem release]; |
|
zacw@1260
|
212 |
|
|
zacw@1260
|
213 |
// Authorization requests menu item |
|
zacw@2693
|
214 |
menuItem = [[NSMenuItem alloc] initWithTitle:AILocalizedStringFromTableInBundle(@"Authorization Requests",nil, [NSBundle bundleForClass:[AIAuthorizationRequestsWindowController class]], nil) |
|
zacw@2693
|
215 |
target:self |
|
zacw@2693
|
216 |
action:@selector(openAuthorizationWindow:) |
|
zacw@2693
|
217 |
keyEquivalent:@""]; |
|
zacw@1260
|
218 |
|
|
zacw@1260
|
219 |
[adium.menuController addMenuItem:menuItem toLocation:LOC_Window_Auxiliary]; |
|
zacw@1260
|
220 |
[menuItem release]; |
|
David@0
|
221 |
|
|
David@0
|
222 |
//Observe content so we can open chats as necessary |
|
David@1109
|
223 |
[[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(didReceiveContent:) |
|
David@0
|
224 |
name:CONTENT_MESSAGE_RECEIVED object:nil]; |
|
David@1109
|
225 |
[[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(didReceiveContent:) |
|
David@0
|
226 |
name:CONTENT_MESSAGE_RECEIVED_GROUP object:nil]; |
|
David@0
|
227 |
|
|
David@0
|
228 |
//Observe Adium finishing loading so we can do things which may require other components or plugins |
|
David@1109
|
229 |
[[NSNotificationCenter defaultCenter] addObserver:self |
|
David@0
|
230 |
selector:@selector(adiumDidFinishLoading:) |
|
David@0
|
231 |
name:AIApplicationDidFinishLoadingNotification |
|
David@0
|
232 |
object:nil]; |
|
David@0
|
233 |
|
|
David@0
|
234 |
//Observe quits so we can save containers. |
|
David@1109
|
235 |
[[NSNotificationCenter defaultCenter] addObserver:self |
|
David@0
|
236 |
selector:@selector(saveContainersOnQuit:) |
|
David@0
|
237 |
name:AIAppWillTerminateNotification |
|
David@0
|
238 |
object:nil]; |
|
David@0
|
239 |
} |
|
David@0
|
240 |
|
|
David@0
|
241 |
- (void)controllerWillClose |
|
David@0
|
242 |
{ |
|
David@0
|
243 |
[contactListPlugin closeContactList]; |
|
David@0
|
244 |
[interfacePlugin closeInterface]; |
|
David@0
|
245 |
} |
|
David@0
|
246 |
|
|
David@0
|
247 |
// Dealloc |
|
David@0
|
248 |
- (void)dealloc |
|
David@0
|
249 |
{ |
|
David@0
|
250 |
[contactListViewArray release]; contactListViewArray = nil; |
|
David@0
|
251 |
[messageViewArray release]; messageViewArray = nil; |
|
David@0
|
252 |
[interfaceArray release]; interfaceArray = nil; |
|
David@0
|
253 |
|
|
David@0
|
254 |
[tooltipListObject release]; tooltipListObject = nil; |
|
David@0
|
255 |
[tooltipTitle release]; tooltipTitle = nil; |
|
David@0
|
256 |
[tooltipBody release]; tooltipBody = nil; |
|
David@0
|
257 |
[tooltipImage release]; tooltipImage = nil; |
|
David@0
|
258 |
|
|
David@1109
|
259 |
[[NSNotificationCenter defaultCenter] removeObserver:self]; |
|
David@95
|
260 |
[adium.preferenceController unregisterPreferenceObserver:self]; |
|
David@0
|
261 |
|
|
David@0
|
262 |
[super dealloc]; |
|
David@0
|
263 |
} |
|
David@0
|
264 |
|
|
David@0
|
265 |
- (void)adiumDidFinishLoading:(NSNotification *)inNotification |
|
David@0
|
266 |
{ |
|
David@0
|
267 |
//Observe preference changes. This will also restore saved containers if appropriate. |
|
David@95
|
268 |
[adium.preferenceController registerPreferenceObserver:self forGroup:PREF_GROUP_INTERFACE]; |
|
David@0
|
269 |
|
|
David@1109
|
270 |
[[NSNotificationCenter defaultCenter] removeObserver:self |
|
David@0
|
271 |
name:AIApplicationDidFinishLoadingNotification |
|
David@0
|
272 |
object:nil]; |
|
David@0
|
273 |
} |
|
David@0
|
274 |
|
|
David@0
|
275 |
//Registers code to handle the interface |
|
David@0
|
276 |
- (void)registerInterfaceController:(id <AIInterfaceComponent>)inController |
|
David@0
|
277 |
{ |
|
David@0
|
278 |
if (!interfacePlugin) interfacePlugin = [inController retain]; |
|
David@0
|
279 |
} |
|
David@0
|
280 |
|
|
David@0
|
281 |
//Register code to handle the contact list |
|
David@0
|
282 |
- (void)registerContactListController:(id <AIContactListComponent>)inController |
|
David@0
|
283 |
{ |
|
David@0
|
284 |
if (!contactListPlugin) contactListPlugin = [inController retain]; |
|
David@0
|
285 |
} |
|
David@0
|
286 |
|
|
David@0
|
287 |
//Preferences changed |
|
David@0
|
288 |
- (void)preferencesChangedForGroup:(NSString *)group key:(NSString *)key |
|
David@0
|
289 |
object:(AIListObject *)object preferenceDict:(NSDictionary *)prefDict firstTime:(BOOL)firstTime |
|
David@0
|
290 |
{ |
|
David@0
|
291 |
if (!object) { |
|
David@0
|
292 |
//Update prefs |
|
David@0
|
293 |
tabbedChatting = [[prefDict objectForKey:KEY_TABBED_CHATTING] boolValue]; |
|
David@0
|
294 |
groupChatsByContactGroup = [[prefDict objectForKey:KEY_GROUP_CHATS_BY_GROUP] boolValue]; |
|
David@0
|
295 |
saveContainers = [[prefDict objectForKey:KEY_SAVE_CONTAINERS] boolValue]; |
|
David@0
|
296 |
|
|
David@0
|
297 |
if (firstTime) { |
|
David@0
|
298 |
if (saveContainers) { |
|
David@0
|
299 |
//Restore saved containers |
|
David@0
|
300 |
[self restoreSavedContainers]; |
|
David@0
|
301 |
} else if ([prefDict objectForKey:KEY_CONTAINERS]) { |
|
David@0
|
302 |
/* We've loaded without wanting to save containers; clear any saved |
|
David@0
|
303 |
* from a previous session. |
|
David@0
|
304 |
*/ |
|
David@95
|
305 |
[adium.preferenceController setPreference:nil |
|
David@0
|
306 |
forKey:KEY_CONTAINERS |
|
David@0
|
307 |
group:PREF_GROUP_INTERFACE]; |
|
David@0
|
308 |
} |
|
David@0
|
309 |
} |
|
David@0
|
310 |
} |
|
David@0
|
311 |
} |
|
David@0
|
312 |
|
|
David@0
|
313 |
//Handle a reopen/dock icon click |
|
David@0
|
314 |
- (BOOL)handleReopenWithVisibleWindows:(BOOL)visibleWindows |
|
David@0
|
315 |
{ |
|
David@0
|
316 |
if (![self contactListIsVisibleAndMain] && [[interfacePlugin openContainerIDs] count] == 0) { |
|
David@0
|
317 |
//The contact list is not visible, and there are no chat windows. Make the contact list visible. |
|
David@0
|
318 |
[self showContactList:nil]; |
|
David@0
|
319 |
|
|
David@0
|
320 |
} else { |
|
David@0
|
321 |
AIChat *mostRecentUnviewedChat; |
|
David@0
|
322 |
|
|
David@0
|
323 |
//If windows are open, try switching to a chat with unviewed content |
|
David@95
|
324 |
if ((mostRecentUnviewedChat = [adium.chatController mostRecentUnviewedChat])) { |
|
David@0
|
325 |
if ([mostRecentActiveChat unviewedContentCount]) { |
|
David@0
|
326 |
//If the most recently active chat has unviewed content, ensure it is in the front |
|
David@0
|
327 |
[self setActiveChat:mostRecentActiveChat]; |
|
David@0
|
328 |
} else { |
|
David@0
|
329 |
//Otherwise, switch to the chat which most recently received content |
|
David@0
|
330 |
[self setActiveChat:mostRecentUnviewedChat]; |
|
David@0
|
331 |
} |
|
David@0
|
332 |
|
|
David@0
|
333 |
} else { |
|
Evan@166
|
334 |
NSWindow *targetWindow = nil; |
|
Evan@166
|
335 |
BOOL unMinimizedWindows = 0; |
|
David@0
|
336 |
|
|
David@0
|
337 |
//If there was no unviewed content, ensure that atleast one of Adium's windows is unminimized |
|
Evan@166
|
338 |
for (NSWindow *window in [NSApp windows]) { |
|
David@0
|
339 |
//Check stylemask to rule out the system menu's window (Which reports itself as visible like a real window) |
|
David@0
|
340 |
if (([window styleMask] & (NSTitledWindowMask | NSClosableWindowMask | NSMiniaturizableWindowMask))) { |
|
David@0
|
341 |
if (!targetWindow) targetWindow = window; |
|
David@0
|
342 |
if (![window isMiniaturized]) unMinimizedWindows++; |
|
David@0
|
343 |
} |
|
David@0
|
344 |
} |
|
David@0
|
345 |
|
|
David@0
|
346 |
//If there are no unminimized windows, unminimize the last one |
|
David@0
|
347 |
if (unMinimizedWindows == 0 && targetWindow) { |
|
David@0
|
348 |
[targetWindow deminiaturize:nil]; |
|
David@0
|
349 |
} |
|
David@0
|
350 |
} |
|
David@0
|
351 |
} |
|
David@0
|
352 |
|
|
David@0
|
353 |
return YES; |
|
David@0
|
354 |
} |
|
David@0
|
355 |
|
|
David@0
|
356 |
//Contact List --------------------------------------------------------------------------------------------------------- |
|
David@0
|
357 |
#pragma mark Contact list |
|
David@0
|
358 |
/*! |
|
David@0
|
359 |
* @brief Toggles contact list between visible and hiden |
|
David@0
|
360 |
*/ |
|
David@0
|
361 |
- (IBAction)toggleContactList:(id)sender |
|
David@0
|
362 |
{ |
|
David@0
|
363 |
if ([self contactListIsVisibleAndMain]) { |
|
David@0
|
364 |
[self closeContactList:nil]; |
|
David@0
|
365 |
} else { |
|
David@0
|
366 |
[[NSApplication sharedApplication] activateIgnoringOtherApps:YES]; |
|
David@0
|
367 |
[self showContactList:nil]; |
|
David@0
|
368 |
} |
|
David@0
|
369 |
} |
|
David@0
|
370 |
|
|
David@0
|
371 |
/*! |
|
David@0
|
372 |
* @brief Brings contact list to the front |
|
David@0
|
373 |
*/ |
|
David@0
|
374 |
- (IBAction)showContactList:(id)sender |
|
David@0
|
375 |
{ |
|
David@0
|
376 |
[contactListPlugin showContactListAndBringToFront:YES]; |
|
David@0
|
377 |
} |
|
David@0
|
378 |
|
|
David@0
|
379 |
/*! |
|
David@0
|
380 |
* @brief Show the contact list window and bring Adium to the front |
|
David@0
|
381 |
*/ |
|
David@0
|
382 |
- (IBAction)showContactListAndBringToFront:(id)sender |
|
David@0
|
383 |
{ |
|
David@0
|
384 |
[contactListPlugin showContactListAndBringToFront:YES]; |
|
David@0
|
385 |
[[NSApplication sharedApplication] activateIgnoringOtherApps:YES]; |
|
David@0
|
386 |
} |
|
David@0
|
387 |
|
|
David@0
|
388 |
/*! |
|
David@0
|
389 |
* @brief Close the contact list window |
|
David@0
|
390 |
*/ |
|
David@0
|
391 |
- (IBAction)closeContactList:(id)sender |
|
David@0
|
392 |
{ |
|
David@0
|
393 |
[contactListPlugin closeContactList]; |
|
David@0
|
394 |
} |
|
David@0
|
395 |
|
|
David@0
|
396 |
/*! |
|
David@0
|
397 |
* @returns YES if contact list is visible and selected, otherwise NO |
|
David@0
|
398 |
*/ |
|
David@0
|
399 |
- (BOOL)contactListIsVisibleAndMain |
|
David@0
|
400 |
{ |
|
David@0
|
401 |
return [contactListPlugin contactListIsVisibleAndMain]; |
|
David@0
|
402 |
} |
|
David@0
|
403 |
|
|
David@0
|
404 |
/*! |
|
David@0
|
405 |
* @returns YES if contact list is visible, otherwise NO |
|
David@0
|
406 |
*/ |
|
David@0
|
407 |
- (BOOL)contactListIsVisible |
|
David@0
|
408 |
{ |
|
David@0
|
409 |
return [contactListPlugin contactListIsVisible]; |
|
David@0
|
410 |
} |
|
David@0
|
411 |
|
|
David@0
|
412 |
//Detachable Contact List ---------------------------------------------------------------------------------------------- |
|
David@0
|
413 |
#pragma mark Detachable Contact List |
|
David@0
|
414 |
|
|
David@0
|
415 |
/*! |
|
David@0
|
416 |
* @returns Created contact list controller for detached contact list |
|
David@0
|
417 |
*/ |
|
David@52
|
418 |
- (AIListWindowController *)detachContactList:(AIContactList *)aContactList |
|
David@0
|
419 |
{ |
|
David@0
|
420 |
return [contactListPlugin detachContactList:aContactList]; |
|
David@0
|
421 |
} |
|
David@0
|
422 |
|
|
David@0
|
423 |
|
|
David@0
|
424 |
#pragma mark Container Saving |
|
David@0
|
425 |
/*! |
|
David@0
|
426 |
* @brief Restores containers saved from a previous session |
|
David@0
|
427 |
*/ |
|
David@0
|
428 |
- (void)restoreSavedContainers |
|
David@0
|
429 |
{ |
|
David@95
|
430 |
NSData *savedData = [adium.preferenceController preferenceForKey:KEY_CONTAINERS |
|
David@0
|
431 |
group:PREF_GROUP_INTERFACE]; |
|
David@0
|
432 |
|
|
David@0
|
433 |
// If there's no data, we can't restore anything. |
|
David@0
|
434 |
if (!savedData) |
|
David@0
|
435 |
return; |
|
Evan@166
|
436 |
|
|
Evan@166
|
437 |
for (NSDictionary *dict in [NSKeyedUnarchiver unarchiveObjectWithData:savedData]) { |
|
David@0
|
438 |
AIMessageWindowController *windowController = [self openContainerWithID:[dict objectForKey:@"ID"] |
|
David@0
|
439 |
name:[dict objectForKey:@"Name"]]; |
|
Evan@166
|
440 |
AIChat *containerActiveChat = nil; |
|
David@0
|
441 |
|
|
David@0
|
442 |
// Position the container where it was last saved (using -savedFrameFromString: to prevent going offscreen) |
|
David@0
|
443 |
[[windowController window] setFrame:[windowController savedFrameFromString:[dict objectForKey:@"Frame"]] display:YES]; |
|
David@0
|
444 |
|
|
Evan@166
|
445 |
for (NSDictionary *chatDict in [dict objectForKey:@"Content"]) { |
|
David@0
|
446 |
AIChat *chat = nil; |
|
David@95
|
447 |
AIService *service = [adium.accountController firstServiceWithServiceID:[chatDict objectForKey:@"serviceID"]]; |
|
David@95
|
448 |
AIAccount *account = [adium.accountController accountWithInternalObjectID:[chatDict objectForKey:@"AccountID"]]; |
|
David@0
|
449 |
|
|
David@0
|
450 |
if ([[chatDict objectForKey:@"IsGroupChat"] boolValue]) { |
|
David@95
|
451 |
chat = [adium.chatController chatWithName:[chatDict objectForKey:@"Name"] |
|
David@0
|
452 |
identifier:nil |
|
David@0
|
453 |
onAccount:account |
|
David@0
|
454 |
chatCreationInfo:[chatDict objectForKey:@"ChatCreationInfo"]]; |
|
David@0
|
455 |
} else { |
|
David@89
|
456 |
AIListContact *contact = [adium.contactController contactWithService:service |
|
David@0
|
457 |
account:account |
|
David@0
|
458 |
UID:[chatDict objectForKey:@"UID"]]; |
|
David@0
|
459 |
|
|
David@95
|
460 |
chat = [adium.chatController chatWithContact:contact]; |
|
David@0
|
461 |
} |
|
David@0
|
462 |
|
|
David@0
|
463 |
// Tag the chat as restored. |
|
David@0
|
464 |
[chat setValue:[NSNumber numberWithBool:YES] |
|
David@0
|
465 |
forProperty:@"Restored Chat" |
|
David@0
|
466 |
notify:NotifyNow]; |
|
David@0
|
467 |
|
|
David@0
|
468 |
if ([[chatDict objectForKey:@"ActiveChat"] boolValue]) { |
|
David@0
|
469 |
containerActiveChat = chat; |
|
David@0
|
470 |
} |
|
David@0
|
471 |
|
|
David@0
|
472 |
// Open the chat into the container we've created above. |
|
David@0
|
473 |
[self openChat:chat inContainerWithID:[dict objectForKey:@"ID"] atIndex:-1]; |
|
David@0
|
474 |
} |
|
David@0
|
475 |
|
|
David@0
|
476 |
if (containerActiveChat) |
|
David@0
|
477 |
[self setActiveChat:containerActiveChat]; |
|
David@0
|
478 |
} |
|
David@0
|
479 |
} |
|
David@0
|
480 |
|
|
David@0
|
481 |
/*! |
|
David@0
|
482 |
* @brief Saves open container information with their content when Adium quits |
|
David@0
|
483 |
*/ |
|
David@0
|
484 |
- (void)saveContainersOnQuit:(NSNotification *)notification |
|
David@0
|
485 |
{ |
|
David@0
|
486 |
[self saveContainers]; |
|
David@0
|
487 |
} |
|
David@0
|
488 |
|
|
David@0
|
489 |
/*! |
|
David@0
|
490 |
* @brief Save opened containers and windows |
|
David@0
|
491 |
* |
|
David@0
|
492 |
* @param withContent Save the current buffer of the window to restore at a later point |
|
David@0
|
493 |
*/ |
|
David@0
|
494 |
- (void)saveContainers |
|
David@0
|
495 |
{ |
|
David@0
|
496 |
if (!saveContainers) { |
|
David@0
|
497 |
// Don't save anything if we're not set to. |
|
David@0
|
498 |
return; |
|
David@0
|
499 |
} |
|
David@0
|
500 |
|
|
David@0
|
501 |
// Save active containers. |
|
David@0
|
502 |
NSMutableArray *savedContainers = [NSMutableArray array]; |
|
David@0
|
503 |
|
|
Evan@166
|
504 |
for (NSDictionary *dict in [interfacePlugin openContainersAndChats]) { |
|
David@0
|
505 |
NSMutableArray *containerContents = [NSMutableArray array]; |
|
David@0
|
506 |
|
|
Evan@166
|
507 |
for (AIChat *chat in [dict objectForKey:@"Content"]) { |
|
David@0
|
508 |
NSMutableDictionary *newContainerDict = [NSMutableDictionary dictionary]; |
|
David@0
|
509 |
|
|
David@428
|
510 |
[newContainerDict setObject:chat.account.internalObjectID forKey:@"AccountID"]; |
|
David@0
|
511 |
|
|
David@0
|
512 |
// Save chat-specific information. |
|
Evan@166
|
513 |
if (chat.isGroupChat) { |
|
David@0
|
514 |
// -chatCreationDictionary may be nil, so put it last. |
|
David@0
|
515 |
[newContainerDict addEntriesFromDictionary:[NSDictionary dictionaryWithObjectsAndKeys: |
|
David@0
|
516 |
[NSNumber numberWithBool:YES], @"IsGroupChat", |
|
David@0
|
517 |
[NSNumber numberWithBool:([dict objectForKey:@"ActiveChat"] == chat)], @"ActiveChat", |
|
David@721
|
518 |
chat.name, @"Name", |
|
David@0
|
519 |
[chat chatCreationDictionary], @"ChatCreationInfo",nil]]; |
|
David@0
|
520 |
} else { |
|
David@0
|
521 |
[newContainerDict addEntriesFromDictionary:[NSDictionary dictionaryWithObjectsAndKeys: |
|
David@0
|
522 |
[NSNumber numberWithBool:([dict objectForKey:@"ActiveChat"] == chat)], @"ActiveChat", |
|
David@426
|
523 |
chat.listObject.UID, @"UID", |
|
David@715
|
524 |
chat.account.service.serviceID, @"serviceID", |
|
David@426
|
525 |
chat.account.internalObjectID, @"AccountID",nil]]; |
|
David@0
|
526 |
} |
|
David@0
|
527 |
|
|
David@0
|
528 |
[containerContents addObject:newContainerDict]; |
|
David@0
|
529 |
} |
|
David@0
|
530 |
|
|
David@0
|
531 |
// Replace the "Content" key in -openContainersAndChats with our version of the content. |
|
David@0
|
532 |
// Remove the ActiveChat reference |
|
David@0
|
533 |
// We use the same keys otherwise that -openContainersAndChats provides (Name, ID, Frame) |
|
David@0
|
534 |
NSMutableDictionary *saveDict = [[dict mutableCopy] autorelease]; |
|
David@0
|
535 |
|
|
David@0
|
536 |
[saveDict removeObjectForKey:@"ActiveChat"]; |
|
David@0
|
537 |
|
|
David@0
|
538 |
[saveDict setObject:containerContents |
|
David@0
|
539 |
forKey:@"Content"]; |
|
David@0
|
540 |
|
|
David@0
|
541 |
[savedContainers addObject:saveDict]; |
|
David@0
|
542 |
} |
|
David@0
|
543 |
|
|
David@95
|
544 |
[adium.preferenceController setPreference:[NSKeyedArchiver archivedDataWithRootObject:savedContainers] |
|
David@0
|
545 |
forKey:KEY_CONTAINERS |
|
David@0
|
546 |
group:PREF_GROUP_INTERFACE]; |
|
David@0
|
547 |
} |
|
David@0
|
548 |
|
|
David@0
|
549 |
//Messaging ------------------------------------------------------------------------------------------------------------ |
|
David@0
|
550 |
//Methods for instructing the interface to provide a representation of chats, and to determine which chat has user focus |
|
David@0
|
551 |
#pragma mark Messaging |
|
David@0
|
552 |
|
|
David@0
|
553 |
/*! |
|
David@0
|
554 |
* @brief Opens window for chat |
|
David@0
|
555 |
*/ |
|
David@0
|
556 |
- (void)openChat:(AIChat *)inChat |
|
David@0
|
557 |
{ |
|
David@0
|
558 |
NSArray *containerIDs = [interfacePlugin openContainerIDs]; |
|
David@0
|
559 |
NSString *containerID = nil; |
|
David@0
|
560 |
NSString *containerName = nil; |
|
David@0
|
561 |
|
|
David@0
|
562 |
//Determine the correct container for this chat |
|
David@0
|
563 |
|
|
David@0
|
564 |
if (!tabbedChatting) { |
|
David@0
|
565 |
//We're not using tabs; each chat starts in its own container, based on the destination object or the chat name |
|
David@0
|
566 |
if ([inChat listObject]) { |
|
David@426
|
567 |
containerID = inChat.listObject.internalObjectID; |
|
David@0
|
568 |
} else { |
|
David@426
|
569 |
containerID = inChat.name; |
|
David@0
|
570 |
} |
|
David@0
|
571 |
|
|
David@0
|
572 |
} else if (groupChatsByContactGroup) { |
|
David@426
|
573 |
if (inChat.isGroupChat) { |
|
David@0
|
574 |
containerID = AILocalizedString(@"Group Chats",nil); |
|
David@0
|
575 |
|
|
David@0
|
576 |
} else { |
|
David@594
|
577 |
//XXX multiple containers: this is "correct" but maybe not desirable, as it is non-deterministic |
|
David@594
|
578 |
AIListGroup *group = inChat.listObject.parentContact.groups.anyObject; |
|
David@0
|
579 |
|
|
David@0
|
580 |
//If the contact is in the contact list root, we don't have a group |
|
David@52
|
581 |
if (group && ![group isKindOfClass:[AIContactList class]]) { |
|
David@426
|
582 |
containerID = group.displayName; |
|
David@0
|
583 |
} |
|
David@0
|
584 |
} |
|
David@0
|
585 |
|
|
David@0
|
586 |
containerName = containerID; |
|
David@0
|
587 |
} |
|
David@0
|
588 |
|
|
David@0
|
589 |
if (!containerID) { |
|
David@0
|
590 |
//Open new chats into the first container (if not available, create a new one) |
|
David@0
|
591 |
if ([containerIDs count] > 0) { |
|
David@0
|
592 |
containerID = [containerIDs objectAtIndex:0]; |
|
David@0
|
593 |
} else { |
|
David@0
|
594 |
containerID = nil; |
|
David@0
|
595 |
} |
|
David@0
|
596 |
} |
|
David@0
|
597 |
|
|
David@0
|
598 |
//Determine the correct placement for this chat within the container |
|
David@0
|
599 |
[interfacePlugin openChat:inChat inContainerWithID:containerID withName:containerName atIndex:-1]; |
|
David@0
|
600 |
if (![inChat isOpen]) { |
|
David@0
|
601 |
[inChat setIsOpen:YES]; |
|
David@0
|
602 |
|
|
David@0
|
603 |
//Post the notification last, so observers receive a chat whose isOpen flag is yes. |
|
David@1109
|
604 |
[[NSNotificationCenter defaultCenter] postNotificationName:Chat_DidOpen object:inChat userInfo:nil]; |
|
David@0
|
605 |
} |
|
David@0
|
606 |
} |
|
David@0
|
607 |
|
|
David@3
|
608 |
- (id)openChat:(AIChat *)inChat inContainerWithID:(NSString *)containerID atIndex:(NSUInteger)index |
|
David@0
|
609 |
{ |
|
David@0
|
610 |
NSArray *openContainerIDs = [interfacePlugin openContainerIDs]; |
|
David@0
|
611 |
|
|
David@0
|
612 |
if (!containerID) { |
|
David@0
|
613 |
//Open new chats into the first container (if not available, create a new one) |
|
David@0
|
614 |
if ([openContainerIDs count] > 0) { |
|
David@0
|
615 |
containerID = [openContainerIDs objectAtIndex:0]; |
|
David@0
|
616 |
} else { |
|
David@0
|
617 |
containerID = AILocalizedString(@"Chats",nil); |
|
David@0
|
618 |
} |
|
David@0
|
619 |
} |
|
David@0
|
620 |
|
|
David@0
|
621 |
//Determine the correct placement for this chat within the container |
|
David@0
|
622 |
id tabViewItem = [interfacePlugin openChat:inChat inContainerWithID:containerID withName:nil atIndex:index]; |
|
David@0
|
623 |
if (![inChat isOpen]) { |
|
David@0
|
624 |
[inChat setIsOpen:YES]; |
|
David@0
|
625 |
|
|
David@0
|
626 |
//Post the notification last, so observers receive a chat whose isOpen flag is yes. |
|
David@1109
|
627 |
[[NSNotificationCenter defaultCenter] postNotificationName:Chat_DidOpen object:inChat userInfo:nil]; |
|
David@0
|
628 |
} |
|
David@0
|
629 |
return tabViewItem; |
|
David@0
|
630 |
} |
|
David@0
|
631 |
|
|
David@0
|
632 |
/** |
|
David@0
|
633 |
* @brief Opens a container with a specific ID |
|
David@0
|
634 |
* |
|
David@0
|
635 |
* Asks the interfacePlugin to openContainerWithID: |
|
David@0
|
636 |
*/ |
|
David@0
|
637 |
- (AIMessageWindowController *)openContainerWithID:(NSString *)containerID name:(NSString *)containerName |
|
David@0
|
638 |
{ |
|
David@0
|
639 |
return [interfacePlugin openContainerWithID:containerID name:containerName]; |
|
David@0
|
640 |
} |
|
David@0
|
641 |
|
|
David@0
|
642 |
/*! |
|
David@0
|
643 |
* @brief Close the interface for a chat |
|
David@0
|
644 |
* |
|
David@0
|
645 |
* Tell the interface plugin to close the chat. |
|
David@0
|
646 |
*/ |
|
David@0
|
647 |
- (void)closeChat:(AIChat *)inChat |
|
David@0
|
648 |
{ |
|
David@0
|
649 |
if (inChat) { |
|
David@95
|
650 |
if ([adium.chatController closeChat:inChat]) { |
|
David@0
|
651 |
[interfacePlugin closeChat:inChat]; |
|
David@0
|
652 |
} |
|
David@0
|
653 |
} |
|
David@0
|
654 |
} |
|
David@0
|
655 |
|
|
David@0
|
656 |
/*! |
|
David@0
|
657 |
* @brief Consolidate chats into a single container |
|
David@0
|
658 |
*/ |
|
David@0
|
659 |
- (void)consolidateChats |
|
David@0
|
660 |
{ |
|
David@0
|
661 |
//We work with copies of these arrays, since moving chats may change their contents |
|
David@0
|
662 |
NSArray *openContainerIDs = [[interfacePlugin openContainerIDs] copy]; |
|
David@0
|
663 |
NSEnumerator *containerEnumerator = [openContainerIDs objectEnumerator]; |
|
David@0
|
664 |
NSString *firstContainerID = [containerEnumerator nextObject]; |
|
David@0
|
665 |
NSString *containerID; |
|
David@0
|
666 |
|
|
David@0
|
667 |
//For all containers but the first, move the chats they contain to the first container |
|
David@0
|
668 |
while ((containerID = [containerEnumerator nextObject])) { |
|
David@0
|
669 |
NSArray *openChats = [[interfacePlugin openChatsInContainerWithID:containerID] copy]; |
|
David@0
|
670 |
NSEnumerator *chatEnumerator = [openChats objectEnumerator]; |
|
David@0
|
671 |
AIChat *chat; |
|
David@0
|
672 |
|
|
David@0
|
673 |
//Move all the chats, providing a target index if chat sorting is enabled |
|
David@0
|
674 |
while ((chat = [chatEnumerator nextObject])) { |
|
David@0
|
675 |
[interfacePlugin moveChat:chat |
|
David@0
|
676 |
toContainerWithID:firstContainerID |
|
David@0
|
677 |
index:-1]; |
|
David@0
|
678 |
} |
|
David@0
|
679 |
|
|
David@0
|
680 |
[openChats release]; |
|
David@0
|
681 |
} |
|
David@0
|
682 |
|
|
David@0
|
683 |
[self chatOrderDidChange]; |
|
David@0
|
684 |
|
|
David@0
|
685 |
[openContainerIDs release]; |
|
David@0
|
686 |
} |
|
David@0
|
687 |
|
|
David@0
|
688 |
- (void)moveChatToNewContainer:(AIChat *)inChat |
|
David@0
|
689 |
{ |
|
David@0
|
690 |
[interfacePlugin moveChatToNewContainer:inChat]; |
|
David@0
|
691 |
} |
|
David@0
|
692 |
|
|
David@0
|
693 |
/*! |
|
David@0
|
694 |
* @returns Active chat |
|
David@0
|
695 |
*/ |
|
David@0
|
696 |
- (AIChat *)activeChat |
|
David@0
|
697 |
{ |
|
David@0
|
698 |
return activeChat; |
|
David@0
|
699 |
} |
|
David@0
|
700 |
|
|
David@0
|
701 |
/*! |
|
David@0
|
702 |
* @brief Set the active chat window |
|
David@0
|
703 |
*/ |
|
David@0
|
704 |
- (void)setActiveChat:(AIChat *)inChat |
|
David@0
|
705 |
{ |
|
David@0
|
706 |
[interfacePlugin setActiveChat:inChat]; |
|
David@0
|
707 |
} |
|
David@0
|
708 |
|
|
David@0
|
709 |
/*! |
|
David@0
|
710 |
* @returns Last chat to be active, nil if not chat is open |
|
David@0
|
711 |
*/ |
|
David@0
|
712 |
- (AIChat *)mostRecentActiveChat |
|
David@0
|
713 |
{ |
|
David@0
|
714 |
return mostRecentActiveChat; |
|
David@0
|
715 |
} |
|
David@0
|
716 |
|
|
David@0
|
717 |
/*! |
|
David@0
|
718 |
* @brief Sets active chat window based on chat |
|
David@0
|
719 |
*/ |
|
David@0
|
720 |
- (void)setMostRecentActiveChat:(AIChat *)inChat |
|
David@0
|
721 |
{ |
|
David@0
|
722 |
[self setActiveChat:inChat]; |
|
David@0
|
723 |
} |
|
David@0
|
724 |
|
|
David@0
|
725 |
/*! |
|
David@0
|
726 |
* @returns Array of open chats (cached, so call as frequently as desired) |
|
David@0
|
727 |
*/ |
|
David@0
|
728 |
- (NSArray *)openChats |
|
David@0
|
729 |
{ |
|
David@0
|
730 |
if (!_cachedOpenChats) { |
|
David@0
|
731 |
_cachedOpenChats = [[interfacePlugin openChats] retain]; |
|
David@0
|
732 |
} |
|
David@0
|
733 |
|
|
David@0
|
734 |
return _cachedOpenChats; |
|
David@0
|
735 |
} |
|
David@0
|
736 |
|
|
David@0
|
737 |
- (NSArray *)openContainerIDs |
|
David@0
|
738 |
{ |
|
David@0
|
739 |
return [interfacePlugin openContainerIDs]; |
|
David@0
|
740 |
} |
|
David@0
|
741 |
|
|
David@0
|
742 |
/*! |
|
David@0
|
743 |
* @param containerID ID for chat window |
|
David@0
|
744 |
* |
|
David@0
|
745 |
* @returns Array of all chats in chat window |
|
David@0
|
746 |
*/ |
|
David@0
|
747 |
- (NSArray *)openChatsInContainerWithID:(NSString *)containerID |
|
David@0
|
748 |
{ |
|
David@0
|
749 |
return [interfacePlugin openChatsInContainerWithID:containerID]; |
|
David@0
|
750 |
} |
|
David@0
|
751 |
|
|
David@0
|
752 |
/*! |
|
David@0
|
753 |
* @brief Resets the cache of open chats |
|
David@0
|
754 |
*/ |
|
David@0
|
755 |
- (void)_resetOpenChatsCache |
|
David@0
|
756 |
{ |
|
David@0
|
757 |
[_cachedOpenChats release]; _cachedOpenChats = nil; |
|
David@0
|
758 |
} |
|
David@0
|
759 |
|
|
David@0
|
760 |
|
|
David@0
|
761 |
|
|
David@0
|
762 |
//Interface plugin callbacks ------------------------------------------------------------------------------------------- |
|
David@0
|
763 |
//These methods are called by the interface to let us know what's going on. We're informed of chats opening, closing, |
|
David@0
|
764 |
//changing order, etc. |
|
David@0
|
765 |
#pragma mark Interface plugin callbacks |
|
David@0
|
766 |
/*! |
|
David@0
|
767 |
* @brief A chat window did open: rebuild our window menu to show the new chat |
|
David@0
|
768 |
* |
|
David@0
|
769 |
* This should be called by the interface plugin (e.g. AIDualWindowInterfacePlugin) after a chat opens |
|
David@0
|
770 |
* |
|
David@0
|
771 |
* @param inChat Newly created chat |
|
David@0
|
772 |
*/ |
|
David@0
|
773 |
- (void)chatDidOpen:(AIChat *)inChat |
|
David@0
|
774 |
{ |
|
David@0
|
775 |
[self _resetOpenChatsCache]; |
|
David@0
|
776 |
[self buildWindowMenu]; |
|
David@0
|
777 |
[self saveContainers]; |
|
David@0
|
778 |
} |
|
David@0
|
779 |
|
|
David@0
|
780 |
/*! |
|
David@0
|
781 |
* @brief A chat has become active: update our chat closing keys and flag this chat as selected in the window menu |
|
David@0
|
782 |
* |
|
David@0
|
783 |
* @param inChat Chat which has become active |
|
David@0
|
784 |
*/ |
|
David@0
|
785 |
- (void)chatDidBecomeActive:(AIChat *)inChat |
|
David@0
|
786 |
{ |
|
David@0
|
787 |
AIChat *previouslyActiveChat = activeChat; |
|
David@0
|
788 |
|
|
David@0
|
789 |
activeChat = [inChat retain]; |
|
David@0
|
790 |
|
|
David@0
|
791 |
[self updateCloseMenuKeys]; |
|
David@0
|
792 |
[self updateActiveWindowMenuItem]; |
|
David@0
|
793 |
|
|
David@0
|
794 |
if (inChat && (inChat != mostRecentActiveChat)) { |
|
David@0
|
795 |
[mostRecentActiveChat release]; mostRecentActiveChat = nil; |
|
David@0
|
796 |
mostRecentActiveChat = [inChat retain]; |
|
David@0
|
797 |
} |
|
David@0
|
798 |
|
|
David@1109
|
799 |
[[NSNotificationCenter defaultCenter] postNotificationName:Chat_BecameActive |
|
David@0
|
800 |
object:inChat |
|
David@0
|
801 |
userInfo:(previouslyActiveChat ? |
|
David@0
|
802 |
[NSDictionary dictionaryWithObject:previouslyActiveChat |
|
David@0
|
803 |
forKey:@"PreviouslyActiveChat"] : |
|
David@0
|
804 |
nil)]; |
|
David@0
|
805 |
|
|
David@0
|
806 |
if (inChat) { |
|
David@0
|
807 |
/* Clear the unviewed content on the next event loop so other methods have a chance to react to the chat becoming |
|
David@0
|
808 |
* active. Specifically, this lets the handleReopenWithVisibleWindows: method have a chance to know that this chat |
|
David@0
|
809 |
* had unviewed content. |
|
David@0
|
810 |
*/ |
|
David@0
|
811 |
[inChat performSelector:@selector(clearUnviewedContentCount) |
|
David@0
|
812 |
withObject:nil |
|
David@0
|
813 |
afterDelay:0]; |
|
David@0
|
814 |
} |
|
David@0
|
815 |
|
|
David@0
|
816 |
[previouslyActiveChat release]; |
|
David@0
|
817 |
} |
|
David@0
|
818 |
|
|
David@0
|
819 |
/*! |
|
David@0
|
820 |
* @brief A chat has become visible: send out a notification for components and plugins to take action |
|
David@0
|
821 |
* |
|
David@0
|
822 |
* @param inChat Chat that has become active |
|
David@0
|
823 |
* @param nWindow Containing chat window |
|
David@0
|
824 |
*/ |
|
David@0
|
825 |
- (void)chatDidBecomeVisible:(AIChat *)inChat inWindow:(NSWindow *)inWindow |
|
David@0
|
826 |
{ |
|
David@1109
|
827 |
[[NSNotificationCenter defaultCenter] postNotificationName:@"AIChatDidBecomeVisible" |
|
David@0
|
828 |
object:inChat |
|
David@0
|
829 |
userInfo:[NSDictionary dictionaryWithObject:inWindow |
|
David@0
|
830 |
forKey:@"NSWindow"]]; |
|
David@0
|
831 |
} |
|
David@0
|
832 |
|
|
David@0
|
833 |
/*! |
|
David@0
|
834 |
* @brief Find the window currently displaying a chat |
|
David@0
|
835 |
* |
|
David@0
|
836 |
* @returns Window for chat otherwise if the chat is not in any window, or is not visible in any window, returns nil |
|
David@0
|
837 |
*/ |
|
David@0
|
838 |
- (NSWindow *)windowForChat:(AIChat *)inChat |
|
David@0
|
839 |
{ |
|
David@0
|
840 |
return [interfacePlugin windowForChat:inChat]; |
|
David@0
|
841 |
} |
|
David@0
|
842 |
|
|
David@0
|
843 |
/*! |
|
David@0
|
844 |
* @brief Find the chat active in a window |
|
David@0
|
845 |
* |
|
David@0
|
846 |
* If the window does not have an active chat, nil is returned |
|
David@0
|
847 |
*/ |
|
David@0
|
848 |
- (AIChat *)activeChatInWindow:(NSWindow *)window |
|
David@0
|
849 |
{ |
|
David@0
|
850 |
return [interfacePlugin activeChatInWindow:window]; |
|
David@0
|
851 |
} |
|
David@0
|
852 |
|
|
David@0
|
853 |
/*! |
|
David@0
|
854 |
* @brief A chat window did close: rebuild our window menu to remove the chat |
|
David@0
|
855 |
* |
|
David@0
|
856 |
* @param inChat Chat that closed |
|
David@0
|
857 |
*/ |
|
David@0
|
858 |
- (void)chatDidClose:(AIChat *)inChat |
|
David@0
|
859 |
{ |
|
David@0
|
860 |
[self _resetOpenChatsCache]; |
|
David@0
|
861 |
[inChat clearUnviewedContentCount]; |
|
David@0
|
862 |
[self buildWindowMenu]; |
|
David@0
|
863 |
|
|
David@426
|
864 |
if (!adium.isQuitting) { |
|
David@0
|
865 |
// Don't save containers when the chats are closed while quitting |
|
David@0
|
866 |
[self saveContainers]; |
|
David@0
|
867 |
} |
|
David@0
|
868 |
|
|
David@0
|
869 |
if (inChat == activeChat) { |
|
David@0
|
870 |
[activeChat release]; activeChat = nil; |
|
David@0
|
871 |
} |
|
David@0
|
872 |
|
|
David@0
|
873 |
if (inChat == mostRecentActiveChat) { |
|
David@0
|
874 |
[mostRecentActiveChat release]; mostRecentActiveChat = nil; |
|
David@0
|
875 |
} |
|
David@0
|
876 |
} |
|
David@0
|
877 |
|
|
David@0
|
878 |
/*! |
|
David@0
|
879 |
* @brief The order of chats has changed: rebuild our window menu to reflect the new order |
|
David@0
|
880 |
*/ |
|
David@0
|
881 |
- (void)chatOrderDidChange |
|
David@0
|
882 |
{ |
|
David@0
|
883 |
[self _resetOpenChatsCache]; |
|
David@0
|
884 |
[self buildWindowMenu]; |
|
David@0
|
885 |
|
|
David@426
|
886 |
if (!adium.isQuitting) { |
|
David@0
|
887 |
// Don't save containers when the chats are closed while quitting |
|
David@0
|
888 |
[self saveContainers]; |
|
David@0
|
889 |
} |
|
David@0
|
890 |
|
|
David@1109
|
891 |
[[NSNotificationCenter defaultCenter] postNotificationName:Chat_OrderDidChange object:nil userInfo:nil]; |
|
David@0
|
892 |
|
|
David@0
|
893 |
} |
|
David@0
|
894 |
|
|
David@0
|
895 |
#pragma mark Unviewed content |
|
David@0
|
896 |
|
|
David@0
|
897 |
/*! |
|
David@0
|
898 |
* @breif Content was received, increase the unviewed content count of the chat (if it's not currently active) |
|
David@0
|
899 |
*/ |
|
David@0
|
900 |
- (void)didReceiveContent:(NSNotification *)notification |
|
David@0
|
901 |
{ |
|
David@0
|
902 |
AIChat *chat = [[notification userInfo] objectForKey:@"AIChat"]; |
|
David@0
|
903 |
|
|
David@0
|
904 |
if (chat != activeChat) { |
|
David@0
|
905 |
[chat incrementUnviewedContentCount]; |
|
David@0
|
906 |
} |
|
David@0
|
907 |
} |
|
David@0
|
908 |
|
|
David@0
|
909 |
|
|
David@0
|
910 |
//Chat close menus ----------------------------------------------------------------------------------------------------- |
|
David@0
|
911 |
#pragma mark Chat close menus |
|
David@0
|
912 |
|
|
David@0
|
913 |
/*! |
|
David@0
|
914 |
* @brief Closes currently active window |
|
David@0
|
915 |
*/ |
|
David@0
|
916 |
- (IBAction)closeMenu:(id)sender |
|
David@0
|
917 |
{ |
|
David@0
|
918 |
[[[NSApplication sharedApplication] keyWindow] performClose:nil]; |
|
David@0
|
919 |
} |
|
David@0
|
920 |
|
|
David@0
|
921 |
/*! |
|
David@0
|
922 |
* @brief Closes currently active chat (if there is an active chat) |
|
David@0
|
923 |
*/ |
|
David@0
|
924 |
- (IBAction)closeChatMenu:(id)sender |
|
David@0
|
925 |
{ |
|
David@0
|
926 |
if (activeChat) [self closeChat:activeChat]; |
|
David@0
|
927 |
} |
|
David@0
|
928 |
|
|
David@0
|
929 |
/*! |
|
David@0
|
930 |
* @brief Closes currently selected chat based on current chat contextual menu |
|
David@0
|
931 |
*/ |
|
David@0
|
932 |
- (IBAction)closeContextualChat:(id)sender |
|
David@0
|
933 |
{ |
|
David@100
|
934 |
[self closeChat:[adium.menuController currentContextMenuChat]]; |
|
David@0
|
935 |
} |
|
David@0
|
936 |
|
|
David@0
|
937 |
/*! |
|
David@0
|
938 |
* @brief Loop through open chats and close them |
|
David@0
|
939 |
*/ |
|
David@0
|
940 |
- (IBAction)closeAllChats:(id)sender |
|
David@0
|
941 |
{ |
|
David@426
|
942 |
for (AIChat *chatToClose in [[interfacePlugin.openChats copy] autorelease]) { |
|
David@0
|
943 |
[self closeChat:chatToClose]; |
|
David@0
|
944 |
} |
|
David@0
|
945 |
} |
|
David@0
|
946 |
|
|
David@0
|
947 |
/*! |
|
David@0
|
948 |
* @brief Updates the key equivalents on 'close' and 'close chat' (dynamically changed to make cmd-w less destructive) |
|
David@0
|
949 |
*/ |
|
David@0
|
950 |
- (void)updateCloseMenuKeys |
|
David@0
|
951 |
{ |
|
David@0
|
952 |
if (activeChat && !closeMenuConfiguredForChat) { |
|
David@0
|
953 |
[menuItem_close setKeyEquivalent:@"W"]; |
|
David@0
|
954 |
[menuItem_closeChat setKeyEquivalent:@"w"]; |
|
David@0
|
955 |
closeMenuConfiguredForChat = YES; |
|
David@0
|
956 |
} else if (!activeChat && closeMenuConfiguredForChat) { |
|
David@0
|
957 |
[menuItem_close setKeyEquivalent:@"w"]; |
|
David@0
|
958 |
[menuItem_closeChat removeKeyEquivalent]; |
|
David@0
|
959 |
closeMenuConfiguredForChat = NO; |
|
David@0
|
960 |
} |
|
David@0
|
961 |
} |
|
David@0
|
962 |
|
|
David@0
|
963 |
|
|
David@0
|
964 |
//Window Menu ---------------------------------------------------------------------------------------------------------- |
|
David@0
|
965 |
#pragma mark Window Menu |
|
David@0
|
966 |
|
|
David@0
|
967 |
/*! |
|
zacw@1260
|
968 |
* @brief Open the authorization requests window. |
|
zacw@1260
|
969 |
*/ |
|
zacw@1260
|
970 |
- (void)openAuthorizationWindow:(id)sender |
|
zacw@1260
|
971 |
{ |
|
zacw@1260
|
972 |
[[AIAuthorizationRequestsWindowController sharedController] showWindow:nil]; |
|
zacw@1260
|
973 |
} |
|
zacw@1260
|
974 |
|
|
zacw@1260
|
975 |
/*! |
|
David@0
|
976 |
* @brief Make a chat window active |
|
David@0
|
977 |
* |
|
David@0
|
978 |
* Invoked by a selection in the window menu |
|
David@0
|
979 |
*/ |
|
David@0
|
980 |
- (IBAction)showChatWindow:(id)sender |
|
David@0
|
981 |
{ |
|
David@0
|
982 |
[self setActiveChat:[sender representedObject]]; |
|
David@0
|
983 |
[[NSApplication sharedApplication] activateIgnoringOtherApps:YES]; |
|
David@0
|
984 |
} |
|
David@0
|
985 |
|
|
David@0
|
986 |
/*! |
|
David@0
|
987 |
* @brief Updates the 'check' icon so it's next to the active window |
|
David@0
|
988 |
*/ |
|
David@0
|
989 |
- (void)updateActiveWindowMenuItem |
|
David@0
|
990 |
{ |
|
David@0
|
991 |
NSMenuItem *item; |
|
David@0
|
992 |
|
|
David@76
|
993 |
for (item in windowMenuArray) { |
|
David@0
|
994 |
if ([item representedObject]) [item setState:([item representedObject] == activeChat ? NSOnState : NSOffState)]; |
|
David@0
|
995 |
} |
|
David@0
|
996 |
} |
|
David@0
|
997 |
|
|
David@0
|
998 |
/*! |
|
David@0
|
999 |
* @brief Builds the window menu |
|
David@0
|
1000 |
* |
|
David@0
|
1001 |
* This function gets called whenever chats are opened, closed, or re-ordered - so improvements and optimizations here |
|
David@0
|
1002 |
* would probably be helpful |
|
David@0
|
1003 |
*/ |
|
David@0
|
1004 |
- (void)buildWindowMenu |
|
David@0
|
1005 |
{ |
|
David@0
|
1006 |
NSMenuItem *item; |
|
David@3
|
1007 |
NSInteger windowKey = 1; |
|
David@0
|
1008 |
|
|
David@0
|
1009 |
//Remove any existing menus |
|
David@76
|
1010 |
for (item in windowMenuArray) { |
|
David@100
|
1011 |
[adium.menuController removeMenuItem:item]; |
|
David@0
|
1012 |
} |
|
David@0
|
1013 |
[windowMenuArray release]; windowMenuArray = [[NSMutableArray alloc] init]; |
|
David@0
|
1014 |
|
|
Evan@166
|
1015 |
//Messages window and any open messasges |
|
Evan@166
|
1016 |
for (NSDictionary *containerDict in [interfacePlugin openContainersAndChats]) { |
|
David@0
|
1017 |
NSString *containerName = [containerDict objectForKey:@"Name"]; |
|
David@0
|
1018 |
NSArray *contentArray = [containerDict objectForKey:@"Content"]; |
|
David@0
|
1019 |
|
|
David@0
|
1020 |
//Add a menu item for the container |
|
Evan@166
|
1021 |
if (contentArray.count > 1) { |
|
David@0
|
1022 |
item = [[NSMenuItem allocWithZone:[NSMenu menuZone]] initWithTitle:([containerName length] ? containerName : AILocalizedString(@"Chats", nil)) |
|
David@0
|
1023 |
target:nil |
|
David@0
|
1024 |
action:nil |
|
David@0
|
1025 |
keyEquivalent:@""]; |
|
David@0
|
1026 |
[self _addItemToMainMenuAndDock:item]; |
|
David@0
|
1027 |
[item release]; |
|
David@0
|
1028 |
} |
|
David@0
|
1029 |
|
|
David@0
|
1030 |
//Add items for the chats it contains |
|
Evan@166
|
1031 |
for (AIChat *chat in [contentArray objectEnumerator]) { |
|
David@0
|
1032 |
NSString *windowKeyString; |
|
David@0
|
1033 |
|
|
David@0
|
1034 |
//Prepare a key equivalent for the controller |
|
David@0
|
1035 |
if (windowKey < 10) { |
|
Evan@166
|
1036 |
windowKeyString = [NSString stringWithFormat:@"%ld", (windowKey)]; |
|
David@0
|
1037 |
} else if (windowKey == 10) { |
|
David@0
|
1038 |
windowKeyString = [NSString stringWithString:@"0"]; |
|
David@0
|
1039 |
} else { |
|
David@0
|
1040 |
windowKeyString = [NSString stringWithString:@""]; |
|
David@0
|
1041 |
} |
|
David@0
|
1042 |
|
|
Evan@166
|
1043 |
item = [[NSMenuItem allocWithZone:[NSMenu menuZone]] initWithTitle:chat.displayName |
|
David@0
|
1044 |
target:self |
|
David@0
|
1045 |
action:@selector(showChatWindow:) |
|
David@0
|
1046 |
keyEquivalent:windowKeyString]; |
|
David@0
|
1047 |
if ([contentArray count] > 1) [item setIndentationLevel:1]; |
|
David@0
|
1048 |
[item setRepresentedObject:chat]; |
|
Evan@166
|
1049 |
[item setImage:chat.chatMenuImage]; |
|
David@0
|
1050 |
[self _addItemToMainMenuAndDock:item]; |
|
David@0
|
1051 |
[item release]; |
|
David@0
|
1052 |
|
|
David@0
|
1053 |
windowKey++; |
|
David@0
|
1054 |
} |
|
David@0
|
1055 |
} |
|
David@0
|
1056 |
|
|
David@0
|
1057 |
[self updateActiveWindowMenuItem]; |
|
David@0
|
1058 |
} |
|
David@0
|
1059 |
|
|
David@0
|
1060 |
/*! |
|
David@0
|
1061 |
* brief Adds a menu item to the internal array, dock menu, and main menu |
|
David@0
|
1062 |
* |
|
David@0
|
1063 |
* Should be used for adding a new window to the window menu (and dock menu) |
|
David@0
|
1064 |
*/ |
|
David@0
|
1065 |
- (void)_addItemToMainMenuAndDock:(NSMenuItem *)item |
|
David@0
|
1066 |
{ |
|
David@0
|
1067 |
//Add to main menu first |
|
David@100
|
1068 |
[adium.menuController addMenuItem:item toLocation:LOC_Window_Fixed]; |
|
David@0
|
1069 |
[windowMenuArray addObject:item]; |
|
David@0
|
1070 |
|
|
David@0
|
1071 |
//Make a copy, and add to the dock |
|
David@0
|
1072 |
item = [item copy]; |
|
David@0
|
1073 |
[item setKeyEquivalent:@""]; |
|
David@100
|
1074 |
[adium.menuController addMenuItem:item toLocation:LOC_Dock_Status]; |
|
David@0
|
1075 |
[windowMenuArray addObject:item]; |
|
David@0
|
1076 |
[item release]; |
|
David@0
|
1077 |
} |
|
David@0
|
1078 |
|
|
David@0
|
1079 |
|
|
David@0
|
1080 |
//Chat Cycling --------------------------------------------------------------------------------------------------------- |
|
David@0
|
1081 |
#pragma mark Chat Cycling |
|
David@0
|
1082 |
|
|
David@0
|
1083 |
/*! |
|
David@0
|
1084 |
* @brief Cycles to the next active chat |
|
David@0
|
1085 |
*/ |
|
David@0
|
1086 |
- (void)nextChat:(id)sender |
|
David@0
|
1087 |
{ |
|
David@0
|
1088 |
NSArray *openChats = [self openChats]; |
|
David@0
|
1089 |
|
|
David@0
|
1090 |
if ([openChats count]) { |
|
David@0
|
1091 |
if (activeChat) { |
|
David@3
|
1092 |
NSInteger chatIndex = [openChats indexOfObject:activeChat]+1; |
|
David@0
|
1093 |
[self setActiveChat:[openChats objectAtIndex:(chatIndex < [openChats count] ? chatIndex : 0)]]; |
|
David@0
|
1094 |
} else { |
|
David@0
|
1095 |
[self setActiveChat:[openChats objectAtIndex:0]]; |
|
David@0
|
1096 |
} |
|
David@0
|
1097 |
} |
|
David@0
|
1098 |
} |
|
David@0
|
1099 |
|
|
David@0
|
1100 |
/*! |
|
David@0
|
1101 |
* @brief Cycles to the previus active chat |
|
David@0
|
1102 |
*/ |
|
David@0
|
1103 |
- (void)previousChat:(id)sender |
|
David@0
|
1104 |
{ |
|
David@0
|
1105 |
NSArray *openChats = [self openChats]; |
|
David@0
|
1106 |
|
|
David@0
|
1107 |
if ([openChats count]) { |
|
David@0
|
1108 |
if (activeChat) { |
|
David@3
|
1109 |
NSInteger chatIndex = [openChats indexOfObject:activeChat]-1; |
|
David@0
|
1110 |
[self setActiveChat:[openChats objectAtIndex:(chatIndex >= 0 ? chatIndex : [openChats count]-1)]]; |
|
David@0
|
1111 |
} else { |
|
David@0
|
1112 |
[self setActiveChat:[openChats lastObject]]; |
|
David@0
|
1113 |
} |
|
David@0
|
1114 |
} |
|
David@0
|
1115 |
} |
|
David@0
|
1116 |
|
|
David@0
|
1117 |
//Selected contact ------------------------------------------------ |
|
David@0
|
1118 |
#pragma mark Selected contact |
|
David@0
|
1119 |
- (id)_performSelectorOnFirstAvailableResponder:(SEL)selector |
|
David@0
|
1120 |
{ |
|
David@0
|
1121 |
NSResponder *responder = [[[NSApplication sharedApplication] mainWindow] firstResponder]; |
|
David@0
|
1122 |
//Check the first responder |
|
David@0
|
1123 |
if ([responder respondsToSelector:selector]) { |
|
David@0
|
1124 |
return [responder performSelector:selector]; |
|
David@0
|
1125 |
} |
|
David@0
|
1126 |
|
|
David@0
|
1127 |
//Search the responder chain |
|
David@0
|
1128 |
do{ |
|
David@0
|
1129 |
responder = [responder nextResponder]; |
|
David@0
|
1130 |
if ([responder respondsToSelector:selector]) { |
|
David@0
|
1131 |
return [responder performSelector:selector]; |
|
David@0
|
1132 |
} |
|
David@0
|
1133 |
|
|
David@0
|
1134 |
} while (responder != nil); |
|
David@0
|
1135 |
|
|
David@0
|
1136 |
//None found, return nil |
|
David@0
|
1137 |
return nil; |
|
David@0
|
1138 |
} |
|
David@0
|
1139 |
- (id)_performSelectorOnFirstAvailableResponder:(SEL)selector conformingToProtocol:(Protocol *)protocol |
|
David@0
|
1140 |
{ |
|
David@0
|
1141 |
NSResponder *responder = [[[NSApplication sharedApplication] mainWindow] firstResponder]; |
|
David@0
|
1142 |
//Check the first responder |
|
David@0
|
1143 |
if ([responder conformsToProtocol:protocol] && [responder respondsToSelector:selector]) { |
|
David@0
|
1144 |
return [responder performSelector:selector]; |
|
David@0
|
1145 |
} |
|
David@0
|
1146 |
|
|
David@0
|
1147 |
//Search the responder chain |
|
David@0
|
1148 |
do{ |
|
David@0
|
1149 |
responder = [responder nextResponder]; |
|
David@0
|
1150 |
if ([responder conformsToProtocol:protocol] && [responder respondsToSelector:selector]) { |
|
David@0
|
1151 |
return [responder performSelector:selector]; |
|
David@0
|
1152 |
} |
|
David@0
|
1153 |
|
|
David@0
|
1154 |
} while (responder != nil); |
|
David@0
|
1155 |
|
|
David@0
|
1156 |
//None found, return nil |
|
David@0
|
1157 |
return nil; |
|
David@0
|
1158 |
} |
|
David@0
|
1159 |
|
|
David@0
|
1160 |
/*! |
|
David@0
|
1161 |
* @returns The "selected"(represented) contact (By finding the first responder that returns a contact) |
|
David@0
|
1162 |
* If no listObject is found, try to find a list object selected in a group chat |
|
David@0
|
1163 |
*/ |
|
David@0
|
1164 |
- (AIListObject *)selectedListObject |
|
David@0
|
1165 |
{ |
|
David@0
|
1166 |
AIListObject *listObject = [self _performSelectorOnFirstAvailableResponder:@selector(listObject)]; |
|
David@0
|
1167 |
if ( !listObject) { |
|
David@0
|
1168 |
listObject = [self _performSelectorOnFirstAvailableResponder:@selector(preferredListObject)]; |
|
David@0
|
1169 |
} |
|
David@0
|
1170 |
return listObject; |
|
David@0
|
1171 |
} |
|
David@0
|
1172 |
|
|
David@0
|
1173 |
- (AIListObject *)selectedListObjectInContactList |
|
David@0
|
1174 |
{ |
|
David@0
|
1175 |
return [self _performSelectorOnFirstAvailableResponder:@selector(listObject) conformingToProtocol:@protocol(ContactListOutlineView)]; |
|
David@0
|
1176 |
} |
|
David@0
|
1177 |
- (NSArray *)arrayOfSelectedListObjectsInContactList |
|
David@0
|
1178 |
{ |
|
David@0
|
1179 |
return [self _performSelectorOnFirstAvailableResponder:@selector(arrayOfListObjects) conformingToProtocol:@protocol(ContactListOutlineView)]; |
|
David@0
|
1180 |
} |
|
zacw@2131
|
1181 |
- (NSArray *)arrayOfSelectedListObjectsWithGroupsInContactList |
|
zacw@2131
|
1182 |
{ |
|
zacw@2131
|
1183 |
return [self _performSelectorOnFirstAvailableResponder:@selector(arrayOfListObjectsWithGroups) conformingToProtocol:@protocol(ContactListOutlineView)]; |
|
zacw@2131
|
1184 |
} |
|
David@0
|
1185 |
|
|
David@0
|
1186 |
//Message View --------------------------------------------------------------------------------------------------------- |
|
David@0
|
1187 |
//Message view is abstracted from the containing interface, since they're not directly related to eachother |
|
David@0
|
1188 |
#pragma mark Message View |
|
David@0
|
1189 |
//Registers a view to handle the contact list |
|
David@0
|
1190 |
- (void)registerMessageDisplayPlugin:(id <AIMessageDisplayPlugin>)inPlugin |
|
David@0
|
1191 |
{ |
|
David@0
|
1192 |
[messageViewArray addObject:inPlugin]; |
|
David@0
|
1193 |
} |
|
David@0
|
1194 |
- (void)unregisterMessageDisplayPlugin:(id <AIMessageDisplayPlugin>)inPlugin |
|
David@0
|
1195 |
{ |
|
David@0
|
1196 |
[messageViewArray removeObject:inPlugin]; |
|
David@0
|
1197 |
} |
|
David@0
|
1198 |
- (id <AIMessageDisplayController>)messageDisplayControllerForChat:(AIChat *)inChat |
|
David@0
|
1199 |
{ |
|
David@0
|
1200 |
//Sometimes our users find it amusing to disable plugins that are located within the Adium bundle. This error |
|
David@0
|
1201 |
//trap prevents us from crashing if they happen to disable all the available message view plugins. |
|
David@0
|
1202 |
//PUT THAT PLUGIN BACK IT WAS IMPORTANT! |
|
David@0
|
1203 |
if ([messageViewArray count] == 0) { |
|
David@0
|
1204 |
NSRunCriticalAlertPanel(@"No Message View Plugin Installed", |
|
David@0
|
1205 |
@"Adium cannot find its message view plugin. Please re-install. If you've manually disabled Adium's message view plugin, please re-enable it.", |
|
David@0
|
1206 |
@"Quit", |
|
David@0
|
1207 |
nil, |
|
David@0
|
1208 |
nil); |
|
David@0
|
1209 |
[NSApp terminate:nil]; |
|
David@0
|
1210 |
} |
|
David@0
|
1211 |
|
|
David@0
|
1212 |
return [[messageViewArray objectAtIndex:0] messageDisplayControllerForChat:inChat]; |
|
David@0
|
1213 |
} |
|
David@0
|
1214 |
|
|
David@0
|
1215 |
|
|
David@0
|
1216 |
//Error Display -------------------------------------------------------------------------------------------------------- |
|
David@0
|
1217 |
#pragma mark Error Display |
|
David@0
|
1218 |
- (void)handleErrorMessage:(NSString *)inTitle withDescription:(NSString *)inDesc |
|
David@0
|
1219 |
{ |
|
David@0
|
1220 |
[self handleMessage:inTitle withDescription:inDesc withWindowTitle:ERROR_MESSAGE_WINDOW_TITLE]; |
|
David@0
|
1221 |
} |
|
David@0
|
1222 |
|
|
David@0
|
1223 |
- (void)handleMessage:(NSString *)inTitle withDescription:(NSString *)inDesc withWindowTitle:(NSString *)inWindowTitle; |
|
David@0
|
1224 |
{ |
|
David@0
|
1225 |
NSDictionary *errorDict; |
|
David@0
|
1226 |
|
|
David@0
|
1227 |
//Post a notification that an error was recieved |
|
David@0
|
1228 |
errorDict = [NSDictionary dictionaryWithObjectsAndKeys:inTitle,@"Title",inDesc,@"Description",inWindowTitle,@"Window Title",nil]; |
|
David@1109
|
1229 |
[[NSNotificationCenter defaultCenter] postNotificationName:Interface_ShouldDisplayErrorMessage object:nil userInfo:errorDict]; |
|
David@0
|
1230 |
} |
|
David@0
|
1231 |
|
|
David@0
|
1232 |
//Display then clear the last disconnection error |
|
David@0
|
1233 |
- (void)account:(AIAccount *)inAccount disconnectedWithError:(NSString *)disconnectionError |
|
David@0
|
1234 |
{ |
|
zacw@1370
|
1235 |
|
|
David@0
|
1236 |
} |
|
David@0
|
1237 |
|
|
David@0
|
1238 |
//Question Display ----------------------------------------------------------------------------------------------------- |
|
David@0
|
1239 |
#pragma mark Question Display |
|
David@0
|
1240 |
- (void)displayQuestion:(NSString *)inTitle withAttributedDescription:(NSAttributedString *)inDesc withWindowTitle:(NSString *)inWindowTitle |
|
wixardy@2118
|
1241 |
defaultButton:(NSString *)inDefaultButton alternateButton:(NSString *)inAlternateButton otherButton:(NSString *)inOtherButton suppression:(NSString *)inSuppression |
|
David@0
|
1242 |
target:(id)inTarget selector:(SEL)inSelector userInfo:(id)inUserInfo |
|
David@0
|
1243 |
{ |
|
David@0
|
1244 |
NSMutableDictionary *questionDict = [NSMutableDictionary dictionary]; |
|
David@0
|
1245 |
|
|
David@0
|
1246 |
if(inTitle != nil) |
|
David@0
|
1247 |
[questionDict setObject:inTitle forKey:@"Title"]; |
|
David@0
|
1248 |
if(inDesc != nil) |
|
David@0
|
1249 |
[questionDict setObject:inDesc forKey:@"Description"]; |
|
David@0
|
1250 |
if(inWindowTitle != nil) |
|
David@0
|
1251 |
[questionDict setObject:inWindowTitle forKey:@"Window Title"]; |
|
David@0
|
1252 |
if(inDefaultButton != nil) |
|
David@0
|
1253 |
[questionDict setObject:inDefaultButton forKey:@"Default Button"]; |
|
David@0
|
1254 |
if(inAlternateButton != nil) |
|
David@0
|
1255 |
[questionDict setObject:inAlternateButton forKey:@"Alternate Button"]; |
|
David@0
|
1256 |
if(inOtherButton != nil) |
|
David@0
|
1257 |
[questionDict setObject:inOtherButton forKey:@"Other Button"]; |
|
wixardy@2118
|
1258 |
if(inSuppression != nil) |
|
wixardy@2118
|
1259 |
[questionDict setObject:inSuppression forKey:@"Suppression Checkbox"]; |
|
David@0
|
1260 |
if(inTarget != nil) |
|
David@0
|
1261 |
[questionDict setObject:inTarget forKey:@"Target"]; |
|
David@0
|
1262 |
if(inSelector != NULL) |
|
David@0
|
1263 |
[questionDict setObject:NSStringFromSelector(inSelector) forKey:@"Selector"]; |
|
David@0
|
1264 |
if(inUserInfo != nil) |
|
David@0
|
1265 |
[questionDict setObject:inUserInfo forKey:@"Userinfo"]; |
|
David@0
|
1266 |
|
|
David@1109
|
1267 |
[[NSNotificationCenter defaultCenter] postNotificationName:Interface_ShouldDisplayQuestion object:nil userInfo:questionDict]; |
|
David@0
|
1268 |
} |
|
David@0
|
1269 |
|
|
David@0
|
1270 |
- (void)displayQuestion:(NSString *)inTitle withDescription:(NSString *)inDesc withWindowTitle:(NSString *)inWindowTitle |
|
wixardy@2118
|
1271 |
defaultButton:(NSString *)inDefaultButton alternateButton:(NSString *)inAlternateButton otherButton:(NSString *)inOtherButton suppression:(NSString *)inSuppression |
|
David@0
|
1272 |
target:(id)inTarget selector:(SEL)inSelector userInfo:(id)inUserInfo |
|
David@0
|
1273 |
{ |
|
David@0
|
1274 |
[self displayQuestion:inTitle |
|
David@0
|
1275 |
withAttributedDescription:[[[NSAttributedString alloc] initWithString:inDesc |
|
David@0
|
1276 |
attributes:[NSDictionary dictionaryWithObject:[NSFont systemFontOfSize:0] |
|
David@0
|
1277 |
forKey:NSFontAttributeName]] autorelease] |
|
David@0
|
1278 |
withWindowTitle:inWindowTitle |
|
David@0
|
1279 |
defaultButton:inDefaultButton |
|
David@0
|
1280 |
alternateButton:inAlternateButton |
|
David@0
|
1281 |
otherButton:inOtherButton |
|
wixardy@2118
|
1282 |
suppression:inSuppression |
|
David@0
|
1283 |
target:inTarget |
|
David@0
|
1284 |
selector:inSelector |
|
David@0
|
1285 |
userInfo:inUserInfo]; |
|
David@0
|
1286 |
} |
|
David@0
|
1287 |
//Synchronized Flashing ------------------------------------------------------------------------------------------------ |
|
David@0
|
1288 |
#pragma mark Synchronized Flashing |
|
David@0
|
1289 |
//Register to observe the synchronized flashing |
|
David@0
|
1290 |
- (void)registerFlashObserver:(id <AIFlashObserver>)inObserver |
|
David@0
|
1291 |
{ |
|
David@0
|
1292 |
//Setup the timer if we don't have one yet |
|
David@0
|
1293 |
if (!flashObserverArray) { |
|
David@0
|
1294 |
flashObserverArray = [[NSMutableArray alloc] init]; |
|
David@0
|
1295 |
flashTimer = [[NSTimer scheduledTimerWithTimeInterval:(1.0/2.0) |
|
David@0
|
1296 |
target:self |
|
David@0
|
1297 |
selector:@selector(flashTimer:) |
|
David@0
|
1298 |
userInfo:nil |
|
David@0
|
1299 |
repeats:YES] retain]; |
|
David@0
|
1300 |
} |
|
David@0
|
1301 |
|
|
David@0
|
1302 |
//Add the new observer to the array |
|
David@0
|
1303 |
[flashObserverArray addObject:inObserver]; |
|
David@0
|
1304 |
} |
|
David@0
|
1305 |
|
|
David@0
|
1306 |
//Unregister from observing flashing |
|
David@0
|
1307 |
- (void)unregisterFlashObserver:(id <AIFlashObserver>)inObserver |
|
David@0
|
1308 |
{ |
|
David@0
|
1309 |
//Remove the observer from our array |
|
David@0
|
1310 |
[flashObserverArray removeObject:inObserver]; |
|
David@0
|
1311 |
|
|
David@0
|
1312 |
//Release the observer array and uninstall the timer |
|
David@0
|
1313 |
if ([flashObserverArray count] == 0) { |
|
David@0
|
1314 |
[flashObserverArray release]; flashObserverArray = nil; |
|
David@0
|
1315 |
[flashTimer invalidate]; |
|
David@0
|
1316 |
[flashTimer release]; flashTimer = nil; |
|
David@0
|
1317 |
} |
|
David@0
|
1318 |
} |
|
David@0
|
1319 |
|
|
David@0
|
1320 |
//Timer, invoke a flash |
|
David@0
|
1321 |
- (void)flashTimer:(NSTimer *)inTimer |
|
David@0
|
1322 |
{ |
|
David@79
|
1323 |
flashState++; |
|
David@79
|
1324 |
|
|
David@81
|
1325 |
for (id<AIFlashObserver>observer in [[flashObserverArray copy] autorelease]) { |
|
David@79
|
1326 |
[observer flash:flashState]; |
|
David@79
|
1327 |
} |
|
David@0
|
1328 |
} |
|
David@0
|
1329 |
|
|
David@0
|
1330 |
//Current state of flashing. This is an integer the increases by 1 with every flash. Mod to whatever range is desired |
|
David@0
|
1331 |
- (int)flashState |
|
David@0
|
1332 |
{ |
|
David@0
|
1333 |
return flashState; |
|
David@0
|
1334 |
} |
|
David@0
|
1335 |
|
|
David@0
|
1336 |
|
|
David@0
|
1337 |
//Tooltips ------------------------------------------------------------------------------------------------------------- |
|
David@0
|
1338 |
#pragma mark Tooltips |
|
David@0
|
1339 |
//Registers code to display tooltip info about a contact |
|
David@0
|
1340 |
- (void)registerContactListTooltipEntry:(id <AIContactListTooltipEntry>)inEntry secondaryEntry:(BOOL)isSecondary |
|
David@0
|
1341 |
{ |
|
David@0
|
1342 |
if (isSecondary) |
|
David@0
|
1343 |
[contactListTooltipSecondaryEntryArray addObject:inEntry]; |
|
David@0
|
1344 |
else |
|
David@0
|
1345 |
[contactListTooltipEntryArray addObject:inEntry]; |
|
David@0
|
1346 |
} |
|
David@0
|
1347 |
|
|
David@0
|
1348 |
//Unregisters code to display tooltip info about a contact |
|
David@0
|
1349 |
- (void)unregisterContactListTooltipEntry:(id <AIContactListTooltipEntry>)inEntry secondaryEntry:(BOOL)isSecondary |
|
David@0
|
1350 |
{ |
|
David@0
|
1351 |
if (isSecondary) |
|
David@0
|
1352 |
[contactListTooltipSecondaryEntryArray removeObject:inEntry]; |
|
David@0
|
1353 |
else |
|
David@0
|
1354 |
[contactListTooltipEntryArray removeObject:inEntry]; |
|
David@0
|
1355 |
} |
|
David@0
|
1356 |
|
|
David@0
|
1357 |
- (NSArray *)contactListTooltipPrimaryEntries |
|
David@0
|
1358 |
{ |
|
David@0
|
1359 |
return contactListTooltipEntryArray; |
|
David@0
|
1360 |
} |
|
David@0
|
1361 |
|
|
David@0
|
1362 |
- (NSArray *)contactListTooltipSecondaryEntries |
|
David@0
|
1363 |
{ |
|
David@0
|
1364 |
return contactListTooltipSecondaryEntryArray; |
|
David@0
|
1365 |
} |
|
David@0
|
1366 |
|
|
David@0
|
1367 |
//list object tooltips |
|
David@0
|
1368 |
- (void)showTooltipForListObject:(AIListObject *)object atScreenPoint:(NSPoint)point onWindow:(NSWindow *)inWindow |
|
David@0
|
1369 |
{ |
|
David@0
|
1370 |
if (object) { |
|
David@0
|
1371 |
if (object == tooltipListObject) { //If we already have this tooltip open |
|
David@0
|
1372 |
//Move the existing tooltip |
|
David@0
|
1373 |
[AITooltipUtilities showTooltipWithTitle:tooltipTitle |
|
David@0
|
1374 |
body:tooltipBody |
|
David@0
|
1375 |
image:tooltipImage |
|
David@0
|
1376 |
imageOnRight:DISPLAY_IMAGE_ON_RIGHT |
|
David@0
|
1377 |
onWindow:inWindow |
|
David@0
|
1378 |
atPoint:point |
|
David@0
|
1379 |
orientation:TooltipBelow]; |
|
David@0
|
1380 |
|
|
David@0
|
1381 |
} else { //This is a new tooltip |
|
David@0
|
1382 |
NSArray *tabArray; |
|
David@0
|
1383 |
NSMutableParagraphStyle *paragraphStyleTitle = [[NSParagraphStyle defaultParagraphStyle] mutableCopy]; |
|
David@0
|
1384 |
NSMutableParagraphStyle *paragraphStyle = [[NSParagraphStyle defaultParagraphStyle] mutableCopy]; |
|
David@0
|
1385 |
|
|
David@0
|
1386 |
//Hold onto the new object |
|
David@0
|
1387 |
[tooltipListObject release]; tooltipListObject = [object retain]; |
|
David@0
|
1388 |
|
|
David@0
|
1389 |
//Buddy Icon |
|
David@0
|
1390 |
[tooltipImage release]; |
|
David@0
|
1391 |
tooltipImage = [[tooltipListObject userIcon] retain]; |
|
David@0
|
1392 |
if (!tooltipImage) tooltipImage = [[AIServiceIcons serviceIconForObject:tooltipListObject |
|
David@0
|
1393 |
type:AIServiceIconLarge |
|
David@0
|
1394 |
direction:AIIconNormal] retain]; |
|
David@0
|
1395 |
|
|
David@0
|
1396 |
//Reset the maxLabelWidth for the tooltip generation |
|
David@0
|
1397 |
maxLabelWidth = 0; |
|
David@0
|
1398 |
|
|
David@0
|
1399 |
//Build a tooltip string for the primary information |
|
David@0
|
1400 |
[tooltipTitle release]; tooltipTitle = [[self _tooltipTitleForObject:object] retain]; |
|
David@0
|
1401 |
|
|
David@0
|
1402 |
//If there is an image, set the title tab and indentation settings independently |
|
David@0
|
1403 |
if (tooltipImage) { |
|
David@0
|
1404 |
//Set a right-align tab at the maximum label width and a left-align just past it |
|
David@0
|
1405 |
tabArray = [[NSArray alloc] initWithObjects:[[[NSTextTab alloc] initWithType:NSRightTabStopType |
|
David@0
|
1406 |
location:maxLabelWidth] autorelease] |
|
David@0
|
1407 |
,[[[NSTextTab alloc] initWithType:NSLeftTabStopType |
|
David@0
|
1408 |
location:maxLabelWidth + LABEL_ENTRY_SPACING] autorelease] |
|
David@0
|
1409 |
,nil]; |
|
David@0
|
1410 |
|
|
David@0
|
1411 |
[paragraphStyleTitle setTabStops:tabArray]; |
|
David@0
|
1412 |
[tabArray release]; |
|
David@0
|
1413 |
tabArray = nil; |
|
David@0
|
1414 |
[paragraphStyleTitle setHeadIndent:(maxLabelWidth + LABEL_ENTRY_SPACING)]; |
|
David@0
|
1415 |
|
|
David@0
|
1416 |
[tooltipTitle addAttribute:NSParagraphStyleAttributeName |
|
David@0
|
1417 |
value:paragraphStyleTitle |
|
David@0
|
1418 |
range:NSMakeRange(0,[tooltipTitle length])]; |
|
David@0
|
1419 |
|
|
David@0
|
1420 |
//Reset the max label width since the body will be independent |
|
David@0
|
1421 |
maxLabelWidth = 0; |
|
David@0
|
1422 |
} |
|
David@0
|
1423 |
|
|
David@0
|
1424 |
//Build a tooltip string for the secondary information |
|
David@0
|
1425 |
[tooltipBody release]; tooltipBody = nil; |
|
David@0
|
1426 |
tooltipBody = [[self _tooltipBodyForObject:object] retain]; |
|
David@0
|
1427 |
|
|
David@0
|
1428 |
//Set a right-align tab at the maximum label width for the body and a left-align just past it |
|
David@0
|
1429 |
tabArray = [[NSArray alloc] initWithObjects:[[[NSTextTab alloc] initWithType:NSRightTabStopType |
|
David@0
|
1430 |
location:maxLabelWidth] autorelease] |
|
David@0
|
1431 |
,[[[NSTextTab alloc] initWithType:NSLeftTabStopType |
|
David@0
|
1432 |
location:maxLabelWidth + LABEL_ENTRY_SPACING] autorelease] |
|
David@0
|
1433 |
,nil]; |
|
David@0
|
1434 |
[paragraphStyle setTabStops:tabArray]; |
|
David@0
|
1435 |
[tabArray release]; |
|
David@0
|
1436 |
[paragraphStyle setHeadIndent:(maxLabelWidth + LABEL_ENTRY_SPACING)]; |
|
David@0
|
1437 |
|
|
David@0
|
1438 |
[tooltipBody addAttribute:NSParagraphStyleAttributeName value:paragraphStyle range:NSMakeRange(0,[tooltipBody length])]; |
|
David@0
|
1439 |
//If there is no image, also use these settings for the top part |
|
David@0
|
1440 |
if (!tooltipImage) { |
|
David@0
|
1441 |
[tooltipTitle addAttribute:NSParagraphStyleAttributeName value:paragraphStyle range:NSMakeRange(0,[tooltipTitle length])]; |
|
David@0
|
1442 |
} |
|
David@0
|
1443 |
|
|
David@0
|
1444 |
//Display the new tooltip |
|
David@0
|
1445 |
[AITooltipUtilities showTooltipWithTitle:tooltipTitle |
|
David@0
|
1446 |
body:tooltipBody |
|
David@0
|
1447 |
image:tooltipImage |
|
David@0
|
1448 |
imageOnRight:DISPLAY_IMAGE_ON_RIGHT |
|
David@0
|
1449 |
onWindow:inWindow |
|
David@0
|
1450 |
atPoint:point |
|
David@0
|
1451 |
orientation:TooltipBelow]; |
|
David@0
|
1452 |
|
|
David@0
|
1453 |
[paragraphStyleTitle release]; |
|
David@0
|
1454 |
[paragraphStyle release]; |
|
David@0
|
1455 |
} |
|
David@0
|
1456 |
|
|
David@0
|
1457 |
} else { |
|
David@0
|
1458 |
//Hide the existing tooltip |
|
David@0
|
1459 |
if (tooltipListObject) { |
|
David@0
|
1460 |
[AITooltipUtilities showTooltipWithTitle:nil |
|
David@0
|
1461 |
body:nil |
|
David@0
|
1462 |
image:nil |
|
David@0
|
1463 |
onWindow:nil |
|
David@0
|
1464 |
atPoint:point |
|
David@0
|
1465 |
orientation:TooltipBelow]; |
|
David@0
|
1466 |
[tooltipListObject release]; tooltipListObject = nil; |
|
David@0
|
1467 |
|
|
David@0
|
1468 |
[tooltipTitle release]; tooltipTitle = nil; |
|
David@0
|
1469 |
[tooltipBody release]; tooltipBody = nil; |
|
David@0
|
1470 |
[tooltipImage release]; tooltipImage = nil; |
|
David@0
|
1471 |
} |
|
David@0
|
1472 |
} |
|
David@0
|
1473 |
} |
|
David@0
|
1474 |
|
|
David@0
|
1475 |
- (NSAttributedString *)_tooltipTitleForObject:(AIListObject *)object |
|
David@0
|
1476 |
{ |
|
David@0
|
1477 |
NSMutableAttributedString *titleString = [[NSMutableAttributedString alloc] init]; |
|
David@0
|
1478 |
|
|
David@0
|
1479 |
id <AIContactListTooltipEntry> tooltipEntry; |
|
David@0
|
1480 |
NSEnumerator *labelEnumerator; |
|
David@0
|
1481 |
NSMutableArray *labelArray = [NSMutableArray array]; |
|
David@0
|
1482 |
NSMutableArray *entryArray = [NSMutableArray array]; |
|
David@0
|
1483 |
NSMutableAttributedString *entryString; |
|
David@3
|
1484 |
CGFloat labelWidth; |
|
David@0
|
1485 |
BOOL isFirst = YES; |
|
David@0
|
1486 |
|
|
David@837
|
1487 |
NSString *formattedUID = object.formattedUID; |
|
David@0
|
1488 |
|
|
David@0
|
1489 |
//Configure fonts and attributes |
|
David@0
|
1490 |
NSFontManager *fontManager = [NSFontManager sharedFontManager]; |
|
David@0
|
1491 |
NSFont *toolTipsFont = [NSFont toolTipsFontOfSize:10]; |
|
David@0
|
1492 |
NSMutableDictionary *titleDict = [NSMutableDictionary dictionaryWithObject:[fontManager convertFont:[NSFont toolTipsFontOfSize:12] toHaveTrait:NSBoldFontMask] |
|
David@0
|
1493 |
forKey:NSFontAttributeName]; |
|
David@0
|
1494 |
NSMutableDictionary *labelDict = [NSMutableDictionary dictionaryWithObject:[fontManager convertFont:[NSFont toolTipsFontOfSize:9] toHaveTrait:NSBoldFontMask] |
|
David@0
|
1495 |
forKey:NSFontAttributeName]; |
|
David@0
|
1496 |
NSMutableDictionary *labelEndLineDict = [NSMutableDictionary dictionaryWithObject:[NSFont toolTipsFontOfSize:2] |
|
David@0
|
1497 |
forKey:NSFontAttributeName]; |
|
David@0
|
1498 |
NSMutableDictionary *entryDict = [NSMutableDictionary dictionaryWithObject:toolTipsFont |
|
David@0
|
1499 |
forKey:NSFontAttributeName]; |
|
David@0
|
1500 |
|
|
David@0
|
1501 |
//Get the user's display name as an attributed string |
|
David@837
|
1502 |
NSAttributedString *displayName = [[NSAttributedString alloc] initWithString:object.displayName |
|
David@0
|
1503 |
attributes:titleDict]; |
|
David@95
|
1504 |
NSAttributedString *filteredDisplayName = [adium.contentController filterAttributedString:displayName |
|
David@0
|
1505 |
usingFilterType:AIFilterTooltips |
|
David@0
|
1506 |
direction:AIFilterIncoming |
|
David@0
|
1507 |
context:nil]; |
|
David@0
|
1508 |
|
|
David@0
|
1509 |
//Append the user's display name |
|
David@0
|
1510 |
if (filteredDisplayName) { |
|
David@0
|
1511 |
[titleString appendAttributedString:filteredDisplayName]; |
|
David@0
|
1512 |
} |
|
David@0
|
1513 |
|
|
David@0
|
1514 |
//Append the user's formatted UID if there is one that's different to the display name |
|
David@0
|
1515 |
if (formattedUID && (!([[[displayName string] compactedString] isEqualToString:[formattedUID compactedString]]))) { |
|
David@0
|
1516 |
[titleString appendString:[NSString stringWithFormat:@" (%@)", formattedUID] withAttributes:titleDict]; |
|
David@0
|
1517 |
} |
|
David@0
|
1518 |
[displayName release]; |
|
David@0
|
1519 |
|
|
David@0
|
1520 |
if ([object isKindOfClass:[AIListContact class]]) { |
|
David@0
|
1521 |
if ((![object isKindOfClass:[AIMetaContact class]] || [(AIMetaContact *)object containsOnlyOneService]) && |
|
David@0
|
1522 |
[object userIcon]) { |
|
David@0
|
1523 |
NSImage *serviceIcon = [[AIServiceIcons serviceIconForObject:object type:AIServiceIconSmall direction:AIIconNormal] |
|
David@0
|
1524 |
imageByScalingToSize:NSMakeSize(14,14)]; |
|
David@0
|
1525 |
if (serviceIcon) { |
|
David@0
|
1526 |
NSTextAttachment *attachment; |
|
David@0
|
1527 |
NSTextAttachmentCell *cell; |
|
David@0
|
1528 |
|
|
David@0
|
1529 |
cell = [[NSTextAttachmentCell alloc] init]; |
|
David@0
|
1530 |
[cell setImage:serviceIcon]; |
|
David@0
|
1531 |
|
|
David@0
|
1532 |
attachment = [[NSTextAttachment alloc] init]; |
|
David@0
|
1533 |
[attachment setAttachmentCell:cell]; |
|
David@0
|
1534 |
[cell release]; |
|
David@0
|
1535 |
|
|
David@0
|
1536 |
[titleString appendString:@" " withAttributes:nil]; |
|
David@0
|
1537 |
[titleString appendAttributedString:[NSAttributedString attributedStringWithAttachment:attachment]]; |
|
David@0
|
1538 |
[attachment release]; |
|
David@0
|
1539 |
} |
|
David@0
|
1540 |
} |
|
David@0
|
1541 |
} |
|
David@0
|
1542 |
|
|
David@0
|
1543 |
if ([object isKindOfClass:[AIListGroup class]]) { |
|
Peter@1061
|
1544 |
[titleString appendString:[NSString stringWithFormat:@" (%ld/%ld)",[(AIListGroup *)object visibleCount],[(AIListGroup *)object countOfContainedObjects]] |
|
David@0
|
1545 |
withAttributes:titleDict]; |
|
David@0
|
1546 |
} |
|
David@0
|
1547 |
|
|
David@0
|
1548 |
//Entries from plugins |
|
David@0
|
1549 |
|
|
David@0
|
1550 |
//Calculate the widest label while loading the arrays |
|
David@0
|
1551 |
|
|
David@76
|
1552 |
for (tooltipEntry in contactListTooltipEntryArray) { |
|
David@0
|
1553 |
|
|
David@0
|
1554 |
entryString = [[tooltipEntry entryForObject:object] mutableCopy]; |
|
David@0
|
1555 |
if (entryString && [entryString length]) { |
|
David@0
|
1556 |
|
|
David@0
|
1557 |
NSString *labelString = [tooltipEntry labelForObject:object]; |
|
David@0
|
1558 |
if (labelString && [labelString length]) { |
|
David@0
|
1559 |
|
|
David@0
|
1560 |
[entryArray addObject:entryString]; |
|
David@0
|
1561 |
[labelArray addObject:labelString]; |
|
David@0
|
1562 |
|
|
David@0
|
1563 |
NSAttributedString * labelAttribString = [[NSAttributedString alloc] initWithString:[NSString stringWithFormat:@"%@:",labelString] |
|
David@0
|
1564 |
attributes:labelDict]; |
|
David@0
|
1565 |
|
|
David@0
|
1566 |
//The largest size should be the label's size plus the distance to the next tab at least a space past its end |
|
David@0
|
1567 |
labelWidth = [labelAttribString size].width; |
|
David@0
|
1568 |
[labelAttribString release]; |
|
David@0
|
1569 |
|
|
David@0
|
1570 |
if (labelWidth > maxLabelWidth) |
|
David@0
|
1571 |
maxLabelWidth = labelWidth; |
|
David@0
|
1572 |
} |
|
David@0
|
1573 |
} |
|
David@0
|
1574 |
[entryString release]; |
|
David@0
|
1575 |
} |
|
David@0
|
1576 |
|
|
David@0
|
1577 |
//Add labels plus entires to the toolTip |
|
David@0
|
1578 |
labelEnumerator = [labelArray objectEnumerator]; |
|
David@0
|
1579 |
|
|
David@76
|
1580 |
for (entryString in entryArray) { |
|
David@0
|
1581 |
NSAttributedString * labelAttribString = [[NSAttributedString alloc] initWithString:[NSString stringWithFormat:@"\t%@:\t",[labelEnumerator nextObject]] |
|
David@0
|
1582 |
attributes:labelDict]; |
|
David@0
|
1583 |
|
|
David@0
|
1584 |
//Add a carriage return |
|
David@0
|
1585 |
[titleString appendString:@"\n" withAttributes:labelEndLineDict]; |
|
David@0
|
1586 |
|
|
David@0
|
1587 |
if (isFirst) { |
|
David@0
|
1588 |
//skip a line |
|
David@0
|
1589 |
[titleString appendString:@"\n" withAttributes:labelEndLineDict]; |
|
David@0
|
1590 |
isFirst = NO; |
|
David@0
|
1591 |
} |
|
David@0
|
1592 |
|
|
David@0
|
1593 |
//Add the label (with its spacing) |
|
David@0
|
1594 |
[titleString appendAttributedString:labelAttribString]; |
|
David@0
|
1595 |
[labelAttribString release]; |
|
David@0
|
1596 |
|
|
David@0
|
1597 |
[entryString addAttributes:entryDict range:NSMakeRange(0,[entryString length])]; |
|
David@0
|
1598 |
[titleString appendAttributedString:entryString]; |
|
David@0
|
1599 |
} |
|
David@0
|
1600 |
|
|
David@0
|
1601 |
return [titleString autorelease]; |
|
David@0
|
1602 |
} |
|
David@0
|
1603 |
|
|
David@0
|
1604 |
- (NSAttributedString *)_tooltipBodyForObject:(AIListObject *)object |
|
David@0
|
1605 |
{ |
|
David@0
|
1606 |
NSMutableAttributedString *tipString = [[NSMutableAttributedString alloc] init]; |
|
David@0
|
1607 |
|
|
David@0
|
1608 |
//Configure fonts and attributes |
|
David@0
|
1609 |
NSFontManager *fontManager = [NSFontManager sharedFontManager]; |
|
David@0
|
1610 |
NSFont *toolTipsFont = [NSFont toolTipsFontOfSize:10]; |
|
David@0
|
1611 |
NSMutableDictionary *labelDict = [NSMutableDictionary dictionaryWithObject:[fontManager convertFont:[NSFont toolTipsFontOfSize:9] toHaveTrait:NSBoldFontMask] |
|
David@0
|
1612 |
forKey:NSFontAttributeName]; |
|
David@0
|
1613 |
NSMutableDictionary *labelEndLineDict = [NSMutableDictionary dictionaryWithObject:[NSFont toolTipsFontOfSize:1] |
|
David@0
|
1614 |
forKey:NSFontAttributeName]; |
|
David@0
|
1615 |
NSMutableDictionary *entryDict = [NSMutableDictionary dictionaryWithObject:toolTipsFont |
|
David@0
|
1616 |
forKey:NSFontAttributeName]; |
|
David@0
|
1617 |
|
|
David@0
|
1618 |
//Entries from plugins |
|
David@0
|
1619 |
NSEnumerator *labelEnumerator; |
|
Evan@166
|
1620 |
NSMutableArray *labelArray = [NSMutableArray array]; //Array of NSStrings |
|
Evan@166
|
1621 |
NSMutableArray *entryArray = [NSMutableArray array]; //Array of NSMutableStrings |
|
Evan@166
|
1622 |
CGFloat labelWidth; |
|
David@0
|
1623 |
BOOL firstEntry = YES; |
|
David@0
|
1624 |
|
|
David@0
|
1625 |
//Calculate the widest label while loading the arrays |
|
Evan@166
|
1626 |
for (id <AIContactListTooltipEntry>tooltipEntry in contactListTooltipSecondaryEntryArray) { |
|
Evan@166
|
1627 |
NSMutableAttributedString *entryString = [[tooltipEntry entryForObject:object] mutableCopy]; |
|
Evan@166
|
1628 |
if (entryString && entryString.length) { |
|
David@0
|
1629 |
NSString *labelString = [tooltipEntry labelForObject:object]; |
|
Evan@166
|
1630 |
|
|
Evan@166
|
1631 |
if (labelString && labelString.length) { |
|
David@0
|
1632 |
[entryArray addObject:entryString]; |
|
David@0
|
1633 |
[labelArray addObject:labelString]; |
|
David@0
|
1634 |
|
|
Evan@166
|
1635 |
NSAttributedString *labelAttribString = [[NSAttributedString alloc] initWithString:[NSString stringWithFormat:@"%@:",labelString] |
|
Evan@166
|
1636 |
attributes:labelDict]; |
|
David@0
|
1637 |
|
|
David@0
|
1638 |
//The largest size should be the label's size plus the distance to the next tab at least a space past its end |
|
Evan@166
|
1639 |
labelWidth = labelAttribString.size.width; |
|
David@0
|
1640 |
[labelAttribString release]; |
|
David@0
|
1641 |
|
|
David@0
|
1642 |
if (labelWidth > maxLabelWidth) |
|
David@0
|
1643 |
maxLabelWidth = labelWidth; |
|
David@0
|
1644 |
} |
|
David@0
|
1645 |
} |
|
David@0
|
1646 |
[entryString release]; |
|
David@0
|
1647 |
} |
|
David@0
|
1648 |
|
|
David@0
|
1649 |
//Add labels plus entires to the toolTip |
|
David@0
|
1650 |
labelEnumerator = [labelArray objectEnumerator]; |
|
Evan@166
|
1651 |
for (NSMutableAttributedString *entryString in entryArray) { |
|
David@0
|
1652 |
NSMutableAttributedString *labelString = [[NSMutableAttributedString alloc] initWithString:[NSString stringWithFormat:@"\t%@:\t",[labelEnumerator nextObject]] |
|
David@0
|
1653 |
attributes:labelDict]; |
|
David@0
|
1654 |
|
|
David@0
|
1655 |
if (firstEntry) { |
|
David@0
|
1656 |
firstEntry = NO; |
|
David@0
|
1657 |
} else { |
|
David@0
|
1658 |
//Add a carriage return and skip a line |
|
David@0
|
1659 |
[tipString appendString:@"\n\n" withAttributes:labelEndLineDict]; |
|
David@0
|
1660 |
} |
|
David@0
|
1661 |
|
|
David@0
|
1662 |
//Add the label (with its spacing) |
|
David@0
|
1663 |
[tipString appendAttributedString:labelString]; |
|
David@0
|
1664 |
[labelString release]; |
|
David@0
|
1665 |
|
|
David@0
|
1666 |
NSRange fullLength = NSMakeRange(0, [entryString length]); |
|
David@0
|
1667 |
|
|
David@0
|
1668 |
//remove any background coloration |
|
David@0
|
1669 |
[entryString removeAttribute:NSBackgroundColorAttributeName range:fullLength]; |
|
David@0
|
1670 |
|
|
David@0
|
1671 |
//adjust foreground colors for the tooltip background |
|
David@0
|
1672 |
[entryString adjustColorsToShowOnBackground:[NSColor colorWithCalibratedRed:1.000 green:1.000 blue:0.800 alpha:1.0]]; |
|
David@0
|
1673 |
|
|
David@0
|
1674 |
//headIndent doesn't apply to the first line of a paragraph... so when new lines are in the entry, we need to tab over to the proper location |
|
David@0
|
1675 |
if ([entryString replaceOccurrencesOfString:@"\r" withString:@"\r\t\t" options:NSLiteralSearch range:fullLength]) |
|
David@0
|
1676 |
fullLength = NSMakeRange(0, [entryString length]); |
|
David@0
|
1677 |
if ([entryString replaceOccurrencesOfString:@"\n" withString:@"\n\t\t" options:NSLiteralSearch range:fullLength]) |
|
David@0
|
1678 |
fullLength = NSMakeRange(0, [entryString length]); |
|
David@0
|
1679 |
|
|
David@0
|
1680 |
//Run the entry through the filters and add it to tipString |
|
David@95
|
1681 |
entryString = [[adium.contentController filterAttributedString:entryString |
|
David@0
|
1682 |
usingFilterType:AIFilterTooltips |
|
David@0
|
1683 |
direction:AIFilterIncoming |
|
David@0
|
1684 |
context:object] mutableCopy]; |
|
David@0
|
1685 |
|
|
David@0
|
1686 |
[entryString addAttributes:entryDict range:NSMakeRange(0,[entryString length])]; |
|
David@0
|
1687 |
[tipString appendAttributedString:entryString]; |
|
David@0
|
1688 |
[entryString release]; |
|
David@0
|
1689 |
} |
|
David@0
|
1690 |
|
|
David@0
|
1691 |
return [tipString autorelease]; |
|
David@0
|
1692 |
} |
|
David@0
|
1693 |
|
|
David@0
|
1694 |
//Custom pasting ---------------------------------------------------------------------------------------------------- |
|
David@0
|
1695 |
#pragma mark Custom Pasting |
|
David@0
|
1696 |
//Paste, stripping formatting |
|
David@0
|
1697 |
- (IBAction)paste:(id)sender |
|
David@0
|
1698 |
{ |
|
David@0
|
1699 |
[self _pasteWithPreferredSelector:@selector(pasteAsPlainTextWithTraits:) sender:sender]; |
|
David@0
|
1700 |
} |
|
David@0
|
1701 |
|
|
David@0
|
1702 |
//Paste with formatting |
|
David@0
|
1703 |
- (IBAction)pasteAndMatchStyle:(id)sender |
|
David@0
|
1704 |
{ |
|
David@0
|
1705 |
[self _pasteWithPreferredSelector:@selector(pasteAsPlainText:) sender:sender]; |
|
David@0
|
1706 |
} |
|
David@0
|
1707 |
|
|
David@0
|
1708 |
- (IBAction)pasteWithImagesAndColors:(id)sender |
|
David@0
|
1709 |
{ |
|
David@0
|
1710 |
[self _pasteWithPreferredSelector:@selector(pasteAsRichText:) sender:sender]; |
|
David@0
|
1711 |
} |
|
David@0
|
1712 |
|
|
David@0
|
1713 |
/*! |
|
David@0
|
1714 |
* @brief Send a paste message, using preferredSelector if possible and paste: if not |
|
David@0
|
1715 |
* |
|
David@0
|
1716 |
* Walks the responder chain looking for a responder which can handle pasting, skipping instances of |
|
David@0
|
1717 |
* WebHTMLView. These are skipped because we can control what paste does to WebView (by using a custom subclass) but |
|
David@0
|
1718 |
* have no control over what the WebHTMLView would do. |
|
David@0
|
1719 |
* |
|
David@0
|
1720 |
* If no responder is found, repeats the process looking for the simpler paste: selector. |
|
David@0
|
1721 |
*/ |
|
David@0
|
1722 |
- (void)_pasteWithPreferredSelector:(SEL)selector sender:(id)sender |
|
David@0
|
1723 |
{ |
|
David@0
|
1724 |
NSWindow *keyWindow = [[NSApplication sharedApplication] keyWindow]; |
|
David@0
|
1725 |
NSResponder *responder; |
|
David@0
|
1726 |
|
|
David@0
|
1727 |
//First, look for a responder which can handle the preferred selector |
|
David@0
|
1728 |
if (!(responder = [keyWindow earliestResponderWhichRespondsToSelector:selector |
|
David@0
|
1729 |
andIsNotOfClass:NSClassFromString(@"WebHTMLView")])) { |
|
David@0
|
1730 |
//No responder found. Try again, looking for one which will respond to paste: |
|
David@0
|
1731 |
selector = @selector(paste:); |
|
David@0
|
1732 |
responder = [keyWindow earliestResponderWhichRespondsToSelector:selector |
|
David@0
|
1733 |
andIsNotOfClass:NSClassFromString(@"WebHTMLView")]; |
|
David@0
|
1734 |
} |
|
David@0
|
1735 |
|
|
David@0
|
1736 |
//Sending pasteAsRichText: to a non rich text NSTextView won't do anything; change it to a generic paste: |
|
David@0
|
1737 |
if ([responder isKindOfClass:[NSTextView class]] && ![(NSTextView *)responder isRichText]) { |
|
David@0
|
1738 |
selector = @selector(paste:); |
|
David@0
|
1739 |
} |
|
David@0
|
1740 |
|
|
David@0
|
1741 |
if (selector) { |
|
David@0
|
1742 |
[keyWindow makeFirstResponder:responder]; |
|
David@0
|
1743 |
[responder performSelector:selector |
|
David@0
|
1744 |
withObject:sender]; |
|
David@0
|
1745 |
} |
|
David@0
|
1746 |
} |
|
David@0
|
1747 |
|
|
David@0
|
1748 |
//Custom Printing ------------------------------------------------------------------------------------------------------ |
|
David@0
|
1749 |
#pragma mark Custom Printing |
|
David@0
|
1750 |
- (IBAction)adiumPrint:(id)sender |
|
David@0
|
1751 |
{ |
|
David@0
|
1752 |
//Pass the print command to the window, which is responsible for routing it to the correct place or |
|
David@0
|
1753 |
//creating a view and printing. Adium will not print from a window that does not respond to adiumPrint: |
|
David@0
|
1754 |
NSWindow *keyWindowController = [[[NSApplication sharedApplication] keyWindow] windowController]; |
|
David@0
|
1755 |
if ([keyWindowController respondsToSelector:@selector(adiumPrint:)]) { |
|
David@0
|
1756 |
[keyWindowController performSelector:@selector(adiumPrint:) |
|
David@0
|
1757 |
withObject:sender]; |
|
David@0
|
1758 |
} |
|
David@0
|
1759 |
} |
|
David@0
|
1760 |
|
|
David@0
|
1761 |
#pragma mark Preferences Display |
|
David@0
|
1762 |
- (IBAction)showPreferenceWindow:(id)sender |
|
David@0
|
1763 |
{ |
|
David@95
|
1764 |
[adium.preferenceController showPreferenceWindow:sender]; |
|
David@0
|
1765 |
} |
|
David@0
|
1766 |
|
|
David@0
|
1767 |
#pragma mark Font Panel |
|
David@0
|
1768 |
- (IBAction)toggleFontPanel:(id)sender |
|
David@0
|
1769 |
{ |
|
David@0
|
1770 |
if ([NSFontPanel sharedFontPanelExists] && |
|
David@0
|
1771 |
[[NSFontPanel sharedFontPanel] isVisible]) { |
|
David@0
|
1772 |
[[NSFontPanel sharedFontPanel] close]; |
|
David@0
|
1773 |
|
|
David@0
|
1774 |
} else { |
|
David@0
|
1775 |
NSFontPanel *fontPanel = [NSFontPanel sharedFontPanel]; |
|
David@0
|
1776 |
|
|
David@0
|
1777 |
if (!fontPanelAccessoryView) { |
|
David@0
|
1778 |
[NSBundle loadNibNamed:@"FontPanelAccessoryView" owner:self]; |
|
David@0
|
1779 |
[fontPanel setAccessoryView:fontPanelAccessoryView]; |
|
David@0
|
1780 |
} |
|
David@0
|
1781 |
|
|
David@0
|
1782 |
[fontPanel orderFront:self]; |
|
David@0
|
1783 |
} |
|
David@0
|
1784 |
} |
|
David@0
|
1785 |
|
|
David@0
|
1786 |
- (IBAction)setFontPanelSettingsAsDefaultFont:(id)sender |
|
David@0
|
1787 |
{ |
|
David@0
|
1788 |
NSFont *selectedFont = [[NSFontManager sharedFontManager] selectedFont]; |
|
David@0
|
1789 |
|
|
David@95
|
1790 |
[adium.preferenceController setPreference:[selectedFont stringRepresentation] |
|
David@0
|
1791 |
forKey:KEY_FORMATTING_FONT |
|
David@0
|
1792 |
group:PREF_GROUP_FORMATTING]; |
|
David@0
|
1793 |
|
|
David@0
|
1794 |
//We can't get foreground/background color from the font panel so far as I can tell... so we do the best we can. |
|
David@0
|
1795 |
NSWindow *keyWindow = [[NSApplication sharedApplication] keyWindow]; |
|
David@0
|
1796 |
NSResponder *responder = [keyWindow firstResponder]; |
|
David@0
|
1797 |
if ([responder isKindOfClass:[NSTextView class]]) { |
|
David@0
|
1798 |
NSDictionary *typingAttributes = [(NSTextView *)responder typingAttributes]; |
|
David@0
|
1799 |
NSColor *foregroundColor, *backgroundColor; |
|
David@0
|
1800 |
|
|
David@0
|
1801 |
if ((foregroundColor = [typingAttributes objectForKey:NSForegroundColorAttributeName])) { |
|
David@95
|
1802 |
[adium.preferenceController setPreference:[foregroundColor stringRepresentation] |
|
David@0
|
1803 |
forKey:KEY_FORMATTING_TEXT_COLOR |
|
David@0
|
1804 |
group:PREF_GROUP_FORMATTING]; |
|
David@0
|
1805 |
} |
|
David@0
|
1806 |
|
|
David@0
|
1807 |
if ((backgroundColor = [typingAttributes objectForKey:AIBodyColorAttributeName])) { |
|
David@95
|
1808 |
[adium.preferenceController setPreference:[backgroundColor stringRepresentation] |
|
David@0
|
1809 |
forKey:KEY_FORMATTING_BACKGROUND_COLOR |
|
David@0
|
1810 |
group:PREF_GROUP_FORMATTING]; |
|
David@0
|
1811 |
} |
|
David@0
|
1812 |
} |
|
David@0
|
1813 |
} |
|
David@0
|
1814 |
|
|
David@0
|
1815 |
//Custom Dimming menu items -------------------------------------------------------------------------------------------- |
|
David@0
|
1816 |
#pragma mark Custom Dimming menu items |
|
David@0
|
1817 |
//The standard ones do not dim correctly when unavailable |
|
David@0
|
1818 |
- (IBAction)toggleFontTrait:(id)sender |
|
David@0
|
1819 |
{ |
|
David@0
|
1820 |
NSFontManager *fontManager = [NSFontManager sharedFontManager]; |
|
David@0
|
1821 |
|
|
David@0
|
1822 |
if ([fontManager traitsOfFont:[fontManager selectedFont]] & [sender tag]) { |
|
David@0
|
1823 |
[fontManager removeFontTrait:sender]; |
|
David@0
|
1824 |
} else { |
|
David@0
|
1825 |
[fontManager addFontTrait:sender]; |
|
David@0
|
1826 |
} |
|
David@0
|
1827 |
} |
|
David@0
|
1828 |
|
|
David@0
|
1829 |
- (void)toggleToolbarShown:(id)sender |
|
David@0
|
1830 |
{ |
|
David@0
|
1831 |
NSWindow *window = [[NSApplication sharedApplication] keyWindow]; |
|
David@0
|
1832 |
[window toggleToolbarShown:sender]; |
|
David@0
|
1833 |
} |
|
David@0
|
1834 |
|
|
David@0
|
1835 |
- (void)runToolbarCustomizationPalette:(id)sender |
|
David@0
|
1836 |
{ |
|
David@0
|
1837 |
NSWindow *window = [[NSApplication sharedApplication] keyWindow]; |
|
David@0
|
1838 |
[window runToolbarCustomizationPalette:sender]; |
|
David@0
|
1839 |
} |
|
David@0
|
1840 |
|
|
David@0
|
1841 |
//Menu item validation |
|
David@0
|
1842 |
- (BOOL)validateMenuItem:(NSMenuItem *)menuItem |
|
David@0
|
1843 |
{ |
|
David@0
|
1844 |
|
|
David@0
|
1845 |
NSWindow *keyWindow = [[NSApplication sharedApplication] keyWindow]; |
|
David@0
|
1846 |
NSResponder *responder = [keyWindow firstResponder]; |
|
David@0
|
1847 |
|
|
David@0
|
1848 |
if (menuItem == menuItem_bold || menuItem == menuItem_italic) { |
|
David@0
|
1849 |
NSFont *selectedFont = [[NSFontManager sharedFontManager] selectedFont]; |
|
David@0
|
1850 |
|
|
David@0
|
1851 |
//We must be in a text view, have text on the pasteboard, and have a font that supports bold or italic |
|
David@0
|
1852 |
if ([responder isKindOfClass:[NSTextView class]]) { |
|
David@0
|
1853 |
return (menuItem == menuItem_bold ? [selectedFont supportsBold] : [selectedFont supportsItalics]); |
|
David@0
|
1854 |
} |
|
David@0
|
1855 |
return NO; |
|
David@0
|
1856 |
|
|
David@0
|
1857 |
} else if (menuItem == menuItem_paste || menuItem == menuItem_pasteAndMatchStyle || menuItem == menuItem_pasteWithImagesAndColors) { |
|
David@0
|
1858 |
|
|
David@0
|
1859 |
//The user can paste if the pasteboard contains an image, some text, one or more files, or one or more URLs. |
|
David@0
|
1860 |
NSPasteboard *pboard = [NSPasteboard generalPasteboard]; |
|
David@0
|
1861 |
NSArray *nonImageTypes = [NSArray arrayWithObjects: |
|
David@0
|
1862 |
NSStringPboardType, |
|
David@0
|
1863 |
NSRTFPboardType, |
|
David@0
|
1864 |
NSURLPboardType, |
|
David@0
|
1865 |
NSFilenamesPboardType, |
|
David@0
|
1866 |
NSFilesPromisePboardType, |
|
David@0
|
1867 |
NSRTFDPboardType, |
|
David@0
|
1868 |
nil]; |
|
David@0
|
1869 |
return ([pboard availableTypeFromArray:nonImageTypes] != nil) || [NSImage canInitWithPasteboard:pboard]; |
|
David@0
|
1870 |
|
|
David@0
|
1871 |
} else if (menuItem == menuItem_showToolbar) { |
|
David@0
|
1872 |
[menuItem_showToolbar setTitle:([[keyWindow toolbar] isVisible] ? |
|
David@0
|
1873 |
AILocalizedString(@"Hide Toolbar",nil) : |
|
David@0
|
1874 |
AILocalizedString(@"Show Toolbar",nil))]; |
|
David@0
|
1875 |
return [keyWindow toolbar] != nil; |
|
David@0
|
1876 |
|
|
David@0
|
1877 |
} else if (menuItem == menuItem_customizeToolbar) { |
|
David@0
|
1878 |
return ([keyWindow toolbar] != nil && [[keyWindow toolbar] isVisible] && [[keyWindow windowController] canCustomizeToolbar]); |
|
David@0
|
1879 |
|
|
David@0
|
1880 |
} else if (menuItem == menuItem_close) { |
|
David@0
|
1881 |
return (keyWindow && ([[keyWindow standardWindowButton:NSWindowCloseButton] isEnabled] || |
|
David@0
|
1882 |
([[keyWindow windowController] respondsToSelector:@selector(windowPermitsClose)] && |
|
David@0
|
1883 |
[[keyWindow windowController] windowPermitsClose]))); |
|
David@0
|
1884 |
|
|
zacw@1588
|
1885 |
} else if (menuItem == menuItem_closeChat || menuItem == menuItem_clearDisplay) { |
|
David@0
|
1886 |
return activeChat != nil; |
|
David@0
|
1887 |
|
|
David@0
|
1888 |
} else if( menuItem == menuItem_closeAllChats) { |
|
David@0
|
1889 |
return [[self openChats] count] > 0; |
|
David@0
|
1890 |
|
|
David@0
|
1891 |
} else if (menuItem == menuItem_print) { |
|
David@0
|
1892 |
NSWindowController *windowController = [keyWindow windowController]; |
|
David@0
|
1893 |
|
|
David@0
|
1894 |
return ([windowController respondsToSelector:@selector(adiumPrint:)] && |
|
David@0
|
1895 |
(![windowController respondsToSelector:@selector(validatePrintMenuItem:)] || |
|
David@0
|
1896 |
[windowController validatePrintMenuItem:menuItem])); |
|
David@0
|
1897 |
|
|
David@0
|
1898 |
} else if (menuItem == menuItem_showFonts) { |
|
David@0
|
1899 |
[menuItem_showFonts setTitle:(([NSFontPanel sharedFontPanelExists] && [[NSFontPanel sharedFontPanel] isVisible]) ? |
|
David@0
|
1900 |
AILocalizedString(@"Hide Fonts",nil) : |
|
David@0
|
1901 |
AILocalizedString(@"Show Fonts",nil))]; |
|
David@0
|
1902 |
return YES; |
|
zacw@1587
|
1903 |
} else if (menuItem == menuItem_toggleUserlist || menuItem == menuItem_toggleUserlistSide) { |
|
David@812
|
1904 |
return self.activeChat.isGroupChat; |
|
David@0
|
1905 |
} else { |
|
David@0
|
1906 |
return YES; |
|
David@0
|
1907 |
} |
|
David@0
|
1908 |
} |
|
David@0
|
1909 |
|
|
David@0
|
1910 |
#pragma mark Window levels |
|
David@0
|
1911 |
- (NSMenu *)menuForWindowLevelsNotifyingTarget:(id)target |
|
David@0
|
1912 |
{ |
|
David@0
|
1913 |
NSMenu *windowPositionMenu = [[NSMenu allocWithZone:[NSMenu zone]] init]; |
|
David@0
|
1914 |
NSMenuItem *menuItem; |
|
David@0
|
1915 |
|
|
David@0
|
1916 |
menuItem = [[NSMenuItem allocWithZone:[NSMenu menuZone]] initWithTitle:AILocalizedString(@"Above other windows",nil) |
|
David@0
|
1917 |
target:target |
|
David@0
|
1918 |
action:@selector(selectedWindowLevel:) |
|
David@0
|
1919 |
keyEquivalent:@""]; |
|
David@0
|
1920 |
[menuItem setEnabled:YES]; |
|
David@0
|
1921 |
[menuItem setTag:AIFloatingWindowLevel]; |
|
David@0
|
1922 |
[windowPositionMenu addItem:menuItem]; |
|
David@0
|
1923 |
[menuItem release]; |
|
David@0
|
1924 |
|
|
David@0
|
1925 |
menuItem = [[NSMenuItem allocWithZone:[NSMenu menuZone]] initWithTitle:AILocalizedString(@"Normally",nil) |
|
David@0
|
1926 |
target:target |
|
David@0
|
1927 |
action:@selector(selectedWindowLevel:) |
|
David@0
|
1928 |
keyEquivalent:@""]; |
|
David@0
|
1929 |
[menuItem setEnabled:YES]; |
|
David@0
|
1930 |
[menuItem setTag:AINormalWindowLevel]; |
|
David@0
|
1931 |
[windowPositionMenu addItem:menuItem]; |
|
David@0
|
1932 |
[menuItem release]; |
|
David@0
|
1933 |
|
|
David@0
|
1934 |
menuItem = [[NSMenuItem allocWithZone:[NSMenu menuZone]] initWithTitle:AILocalizedString(@"Below other windows",nil) |
|
David@0
|
1935 |
target:target |
|
David@0
|
1936 |
action:@selector(selectedWindowLevel:) |
|
David@0
|
1937 |
keyEquivalent:@""]; |
|
David@0
|
1938 |
[menuItem setEnabled:YES]; |
|
David@0
|
1939 |
[menuItem setTag:AIDesktopWindowLevel]; |
|
David@0
|
1940 |
[windowPositionMenu addItem:menuItem]; |
|
David@0
|
1941 |
[menuItem release]; |
|
David@0
|
1942 |
|
|
David@0
|
1943 |
[windowPositionMenu setAutoenablesItems:NO]; |
|
David@0
|
1944 |
|
|
David@0
|
1945 |
return [windowPositionMenu autorelease]; |
|
David@0
|
1946 |
} |
|
David@0
|
1947 |
|
|
David@0
|
1948 |
-(void)toggleUserlist:(id)sender |
|
David@0
|
1949 |
{ |
|
zacw@1588
|
1950 |
[self.activeChat.chatContainer.chatViewController toggleUserList]; |
|
zacw@1244
|
1951 |
} |
|
zacw@1244
|
1952 |
|
|
zacw@1587
|
1953 |
-(void)toggleUserlistSide:(id)sender |
|
zacw@1587
|
1954 |
{ |
|
zacw@1588
|
1955 |
[self.activeChat.chatContainer.chatViewController toggleUserListSide]; |
|
zacw@1588
|
1956 |
} |
|
zacw@1588
|
1957 |
|
|
zacw@1588
|
1958 |
-(void)clearDisplay:(id)sender |
|
zacw@1588
|
1959 |
{ |
|
zacw@1588
|
1960 |
[self.activeChat.chatContainer.messageViewController.messageDisplayController clearView]; |
|
zacw@1587
|
1961 |
} |
|
David@0
|
1962 |
|
|
David@0
|
1963 |
@end |