1/** <title>GSTitleView</title> 2 3 Copyright (C) 2003 Free Software Foundation, Inc. 4 5 Author: Sergii Stoian <stoyan255@gmail.com> 6 Date: Mar 2003 7 8 This file is part of the GNUstep GUI Library. 9 10 This library is free software; you can redistribute it and/or 11 modify it under the terms of the GNU Lesser General Public 12 License as published by the Free Software Foundation; either 13 version 2 of the License, or (at your option) any later version. 14 15 This library is distributed in the hope that it will be useful, 16 but WITHOUT ANY WARRANTY; without even the implied warranty of 17 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 18 Lesser General Public License for more details. 19 20 You should have received a copy of the GNU Lesser General Public 21 License along with this library; see the file COPYING.LIB. 22 If not, see <http://www.gnu.org/licenses/> or write to the 23 Free Software Foundation, 51 Franklin Street, Fifth Floor, 24 Boston, MA 02110-1301, USA. 25*/ 26 27#import <Foundation/NSDebug.h> 28#import <Foundation/NSNotification.h> 29#import <Foundation/NSRunLoop.h> 30 31#import "AppKit/NSApplication.h" 32#import "AppKit/NSAttributedString.h" 33#import "AppKit/NSButton.h" 34#import "AppKit/NSColor.h" 35#import "AppKit/NSEvent.h" 36#import "AppKit/NSGraphics.h" 37#import "AppKit/NSImage.h" 38#import "AppKit/NSMenu.h" 39#import "AppKit/NSMenuView.h" 40#import "AppKit/NSPanel.h" 41#import "AppKit/NSStringDrawing.h" 42#import "AppKit/NSView.h" 43#import "AppKit/NSWindow.h" 44#import "AppKit/NSScreen.h" 45 46#import "GNUstepGUI/GSTitleView.h" 47#import "GNUstepGUI/GSTheme.h" 48 49@implementation GSTitleView 50 51// ============================================================================ 52// ==== Initialization & deallocation 53// ============================================================================ 54 55+ (float) height 56{ 57 return [NSMenuView menuBarHeight] + 1; 58} 59 60- (id) init 61{ 62 self = [super init]; 63 if (!self) 64 return nil; 65 66 _owner = nil; 67 _ownedByMenu = NO; 68 _isKeyWindow = NO; 69 _isMainWindow = NO; 70 _isActiveApplication = NO; 71 72 [self setAutoresizingMask: NSViewWidthSizable | NSViewMinYMargin]; 73 74 textAttributes = [[NSMutableDictionary alloc] initWithObjectsAndKeys: 75 [NSFont boldSystemFontOfSize: 0], NSFontAttributeName, 76 [NSColor blackColor], NSForegroundColorAttributeName, nil]; 77 78 titleColor = RETAIN ([NSColor lightGrayColor]); 79 80 return self; 81} 82 83- (id) initWithOwner: (id)owner 84{ 85 self = [self init]; 86 if (!self) 87 return nil; 88 89 [self setOwner: owner]; 90 91 return self; 92} 93 94- (void) setOwner: (id)owner 95{ 96 NSNotificationCenter *theCenter = [NSNotificationCenter defaultCenter]; 97 98 if ([owner isKindOfClass: [NSWindow class]]) 99 { 100 NSDebugLLog(@"GSTitleView", @"owner is NSWindow or NSPanel"); 101 _owner = owner; 102 _ownedByMenu = NO; 103 104 [self setFrame: 105 NSMakeRect (-1, [_owner frame].size.height - [GSTitleView height]-40, 106 [_owner frame].size.width+2, [GSTitleView height])]; 107 108 if ([_owner styleMask] & NSClosableWindowMask) 109 { 110 [self addCloseButtonWithAction: @selector(performClose:)]; 111 } 112 if ([_owner styleMask] & NSMiniaturizableWindowMask) 113 { 114 [self addMiniaturizeButtonWithAction: @selector(performMiniaturize:)]; 115 } 116 117 // NSWindow observers 118 [theCenter addObserver: self 119 selector: @selector(windowBecomeKey:) 120 name: NSWindowDidBecomeKeyNotification 121 object: _owner]; 122 [theCenter addObserver: self 123 selector: @selector(windowResignKey:) 124 name: NSWindowDidResignKeyNotification 125 object: _owner]; 126 [theCenter addObserver: self 127 selector: @selector(windowBecomeMain:) 128 name: NSWindowDidBecomeMainNotification 129 object: _owner]; 130 [theCenter addObserver: self 131 selector: @selector(windowResignMain:) 132 name: NSWindowDidResignMainNotification 133 object: _owner]; 134 135 // NSApplication observers 136 [theCenter addObserver: self 137 selector: @selector(applicationBecomeActive:) 138 name: NSApplicationWillBecomeActiveNotification 139 object: NSApp]; 140 [theCenter addObserver: self 141 selector: @selector(applicationResignActive:) 142 name: NSApplicationWillResignActiveNotification 143 object: NSApp]; 144 } 145 else if ([owner isKindOfClass: [NSMenu class]]) 146 { 147 NSColor *textColor; 148 GSTheme *theme; 149 150 NSDebugLLog(@"GSTitleView", @"owner is NSMenu"); 151 _owner = owner; 152 _ownedByMenu = YES; 153 theme = [GSTheme theme]; 154 155 RELEASE (titleColor); 156 titleColor = RETAIN ([theme colorNamed: @"GSMenuBar" state: GSThemeNormalState]); 157 if (titleColor == nil) 158 { 159 titleColor = RETAIN ([NSColor blackColor]); 160 } 161 162 textColor = [theme colorNamed: @"GSMenuBarTitle" state: GSThemeNormalState]; 163 if (textColor == nil) 164 { 165 textColor = [NSColor whiteColor]; 166 } 167 [textAttributes setObject: textColor 168 forKey: NSForegroundColorAttributeName]; 169 } 170 else 171 { 172 NSDebugLLog(@"GSTitleView", 173 @"%@ owner is not NSMenu or NSWindow or NSPanel", 174 [owner className]); 175 return; 176 } 177} 178 179- (id) owner 180{ 181 return _owner; 182} 183 184- (void) dealloc 185{ 186 if (!_ownedByMenu) 187 { 188 [[NSNotificationCenter defaultCenter] removeObserver: self]; 189 } 190 191 RELEASE(textAttributes); 192 RELEASE(titleColor); 193 [[GSTheme theme] setName: nil forElement: [closeButton cell] temporary: NO]; 194 TEST_RELEASE(closeButton); 195 TEST_RELEASE(miniaturizeButton); 196 197 [super dealloc]; 198} 199 200// ============================================================================ 201// ==== Drawing 202// ============================================================================ 203 204- (NSSize) titleSize 205{ 206 return [[_owner title] sizeWithAttributes: textAttributes]; 207} 208 209- (void) drawRect: (NSRect)rect 210{ 211 NSRect workRect = [[GSTheme theme] drawMenuTitleBackground: self 212 withBounds: [self bounds] 213 withClip: rect]; 214 // Draw the title 215 NSSize titleSize = [self titleSize]; 216 workRect.origin.x += 4; 217 218 workRect.origin.y = NSMidY (workRect) - titleSize.height / 2; 219 workRect.size.height = titleSize.height; 220 [[_owner title] drawInRect: workRect withAttributes: textAttributes]; 221} 222 223// ============================================================================ 224// ==== Mouse actions 225// ============================================================================ 226 227- (BOOL) acceptsFirstMouse: (NSEvent *)theEvent 228{ 229 return YES; 230} 231 232- (void) mouseDown: (NSEvent*)theEvent 233{ 234 NSPoint lastLocation; 235 NSPoint location; 236 NSUInteger eventMask = NSLeftMouseUpMask | NSPeriodicMask; 237 BOOL done = NO; 238 BOOL moved = NO; 239 NSDate *theDistantFuture = [NSDate distantFuture]; 240 NSPoint startWindowOrigin; 241 NSPoint endWindowOrigin; 242 NSRect screenFrame = NSZeroRect; 243 CGFloat leftLimit = -1.0; 244 CGFloat topLimit = -1.0 ; 245 CGFloat rightLimit = -1.0; 246 CGFloat bottomLimit = 0.0; 247 248 NSDebugLLog (@"NSMenu", @"Mouse down in title!"); 249 250 // Define move constrains for menu 251 if (_ownedByMenu) 252 { 253 NSRect windowFrame; 254 NSScreen *screen; 255 256 if (_window && (screen = [_window screen])) 257 { 258 windowFrame = [_window frame]; 259 screenFrame = [screen frame]; 260 leftLimit = screenFrame.origin.x; 261 topLimit = NSMaxY(screenFrame) - windowFrame.size.height; 262 rightLimit = NSMaxX(screenFrame) - windowFrame.size.width; 263 bottomLimit = screenFrame.origin.y - 264 (windowFrame.size.height - [self frame].size.height); 265 } 266 } 267 268 // Remember start position of window 269 startWindowOrigin = [_window frame].origin; 270 271 // Remember start location of cursor in window 272 lastLocation = [theEvent locationInWindow]; 273 274 [_window _captureMouse: nil]; 275 276 [NSEvent startPeriodicEventsAfterDelay: 0.02 withPeriod: 0.02]; 277 278 while (!done) 279 { 280 theEvent = [NSApp nextEventMatchingMask: eventMask 281 untilDate: theDistantFuture 282 inMode: NSEventTrackingRunLoopMode 283 dequeue: YES]; 284 switch ([theEvent type]) 285 { 286 case NSRightMouseUp: 287 case NSLeftMouseUp: 288 done = YES; 289 [_window _releaseMouse: nil]; 290 break; 291 case NSPeriodic: 292 location = [_window mouseLocationOutsideOfEventStream]; 293 if (NSEqualPoints(location, lastLocation) == NO) 294 { 295 NSPoint origin = [_window frame].origin; 296 297 moved = YES; 298 origin.x += (location.x - lastLocation.x); 299 origin.y += (location.y - lastLocation.y); 300 if (_ownedByMenu) 301 { 302 if (screenFrame.size.width > 0 && screenFrame.size.height > 0) 303 { 304 if (origin.x <= leftLimit) 305 origin.x = leftLimit; 306 else if (origin.x >= rightLimit) 307 origin.x = rightLimit; 308 309 if (origin.y >= topLimit) 310 origin.y = topLimit; 311 else if (origin.y <= bottomLimit) 312 origin.y = bottomLimit; 313 } 314 315 [_owner nestedSetFrameOrigin: origin]; 316 } 317 else 318 { 319 [_owner setFrameOrigin: origin]; 320 } 321 } 322 break; 323 324 default: 325 break; 326 } 327 } 328 329 // Make menu torn off 330 if (_ownedByMenu && ![_owner isTornOff] && [_owner supermenu]) 331 { 332 endWindowOrigin = [_window frame].origin; 333 if ((startWindowOrigin.x != endWindowOrigin.x 334 || startWindowOrigin.y != endWindowOrigin.y)) 335 { 336 [_owner setTornOff: YES]; 337 } 338 } 339 340 [NSEvent stopPeriodicEvents]; 341 342 if (moved == YES) 343 { 344 // Let everything know the window has moved. 345 [[NSNotificationCenter defaultCenter] 346 postNotificationName: NSWindowDidMoveNotification object: _window]; 347 } 348} 349 350// We do not need app menu over menu 351- (void) rightMouseDown: (NSEvent*)theEvent 352{ 353} 354 355// We do not want to popup menus in this menu. 356- (NSMenu *) menuForEvent: (NSEvent*) theEvent 357{ 358 return nil; 359} 360 361// ============================================================================ 362// ==== NSWindow & NSApplication notifications 363// ============================================================================ 364 365- (void) applicationBecomeActive: (NSNotification *)notification 366{ 367 _isActiveApplication = YES; 368} 369 370- (void) applicationResignActive: (NSNotification *)notification 371{ 372 _isActiveApplication = NO; 373 RELEASE (titleColor); 374 titleColor = RETAIN ([NSColor lightGrayColor]); 375 [textAttributes setObject: [NSColor blackColor] 376 forKey: NSForegroundColorAttributeName]; 377 [self setNeedsDisplay: YES]; 378} 379 380- (void) windowBecomeKey: (NSNotification *)notification 381{ 382 _isKeyWindow = YES; 383 RELEASE (titleColor); 384 titleColor = RETAIN ([NSColor blackColor]); 385 [textAttributes setObject: [NSColor whiteColor] 386 forKey: NSForegroundColorAttributeName]; 387 388 [self setNeedsDisplay: YES]; 389} 390 391- (void) windowResignKey: (NSNotification *)notification 392{ 393 _isKeyWindow = NO; 394 RELEASE (titleColor); 395 if (_isActiveApplication && _isMainWindow) 396 { 397 titleColor = RETAIN ([NSColor darkGrayColor]); 398 [textAttributes setObject: [NSColor whiteColor] 399 forKey: NSForegroundColorAttributeName]; 400 } 401 else 402 { 403 titleColor = RETAIN ([NSColor lightGrayColor]); 404 [textAttributes setObject: [NSColor blackColor] 405 forKey: NSForegroundColorAttributeName]; 406 } 407 [self setNeedsDisplay: YES]; 408} 409 410- (void) windowBecomeMain: (NSNotification *)notification 411{ 412 _isMainWindow = YES; 413} 414 415- (void) windowResignMain: (NSNotification *)notification 416{ 417 _isMainWindow = NO; 418} 419 420// ============================================================================ 421// ==== Buttons 422// ============================================================================ 423 424- (void) addCloseButtonWithAction: (SEL)closeAction 425{ 426 if (closeButton == nil) 427 { 428 NSSize viewSize; 429 NSSize buttonSize; 430 431 [[GSTheme theme] setName: nil forElement: [closeButton cell] temporary: NO]; 432 ASSIGN(closeButton, 433 [NSWindow standardWindowButton: 434 NSWindowCloseButton 435 forStyleMask: 436 NSTitledWindowMask | NSClosableWindowMask 437 | NSMiniaturizableWindowMask]); 438 [[GSTheme theme] setName: @"GSMenuCloseButton" forElement: [closeButton cell] temporary: NO]; 439 440 [closeButton setTarget: _owner]; 441 [closeButton setAction: closeAction]; 442 443 viewSize = [self frame].size; 444 buttonSize = [[closeButton image] size]; 445 buttonSize = NSMakeSize(buttonSize.width + 3, buttonSize.height + 3); 446 447 // Update location 448 [closeButton setFrame: 449 NSMakeRect(viewSize.width - buttonSize.width - 4, 450 (viewSize.height - buttonSize.height) / 2, 451 buttonSize.width, buttonSize.height)]; 452 453 [closeButton setAutoresizingMask: NSViewMinXMargin | NSViewMaxYMargin]; 454 } 455 456 if ([closeButton superview] == nil) 457 { 458 [self addSubview: closeButton]; 459 [self setNeedsDisplay: YES]; 460 } 461} 462 463- (NSButton *) closeButton 464{ 465 return closeButton; 466} 467 468- (void) removeCloseButton 469{ 470 if ([closeButton superview] != nil) 471 { 472 [closeButton removeFromSuperview]; 473 } 474} 475 476- (void) addMiniaturizeButtonWithAction: (SEL)miniaturizeAction 477{ 478 if (miniaturizeButton == nil) 479 { 480 NSSize viewSize; 481 NSSize buttonSize; 482 483 ASSIGN(miniaturizeButton, 484 [NSWindow standardWindowButton: 485 NSWindowMiniaturizeButton 486 forStyleMask: 487 NSTitledWindowMask | NSClosableWindowMask 488 | NSMiniaturizableWindowMask]); 489 [miniaturizeButton setTarget: _owner]; 490 [miniaturizeButton setAction: miniaturizeAction]; 491 492 viewSize = [self frame].size; 493 buttonSize = [[miniaturizeButton image] size]; 494 buttonSize = NSMakeSize(buttonSize.width + 3, buttonSize.height + 3); 495 496 // Update location 497 [miniaturizeButton setFrame: 498 NSMakeRect(4, (viewSize.height - buttonSize.height) / 2, 499 buttonSize.width, buttonSize.height)]; 500 501 [miniaturizeButton setAutoresizingMask: NSViewMaxXMargin | NSViewMaxYMargin]; 502 } 503 504 if ([miniaturizeButton superview] == nil) 505 { 506 [self addSubview: miniaturizeButton]; 507 [self setNeedsDisplay: YES]; 508 } 509} 510 511- (NSButton *) miniaturizeButton 512{ 513 return miniaturizeButton; 514} 515 516- (void) removeMiniaturizeButton 517{ 518 if ([miniaturizeButton superview] != nil) 519 { 520 [miniaturizeButton removeFromSuperview]; 521 } 522} 523 524@end 525