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