1/** <title>GSStandardWindowDecorationView</title>
2
3   Copyright (C) 2004 Free Software Foundation, Inc.
4
5   Author: Alexander Malmberg <alexander@malmberg.org>
6   Date: 2004-03-24
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/NSException.h>
28#import <Foundation/NSNotification.h>
29
30#import "AppKit/NSApplication.h"
31#import "AppKit/NSAttributedString.h"
32#import "AppKit/NSButton.h"
33#import "AppKit/NSEvent.h"
34#import "AppKit/NSImage.h"
35#import "AppKit/NSParagraphStyle.h"
36#import "AppKit/NSScreen.h"
37#import "AppKit/NSStringDrawing.h"
38#import "AppKit/NSWindow.h"
39#import "AppKit/PSOperators.h"
40#import "GNUstepGUI/GSDisplayServer.h"
41#import "GNUstepGUI/GSTheme.h"
42
43#import <GNUstepGUI/GSWindowDecorationView.h>
44
45@interface GSStandardWindowDecorationView (GSTheme)
46- (void) _themeDidActivate: (NSNotification*)notification;
47@end
48
49@implementation GSStandardWindowDecorationView
50
51+ (void) offsets: (float *)l : (float *)r : (float *)t : (float *)b
52    forStyleMask: (NSUInteger)style
53{
54  GSTheme *theme = [GSTheme theme];
55
56  if (style
57    & (NSTitledWindowMask | NSClosableWindowMask
58      | NSMiniaturizableWindowMask | NSResizableWindowMask))
59    {
60      *l = *r = *t = *b = 1.0;
61    }
62  else
63    {
64      *l = *r = *t = *b = 0.0;
65    }
66
67  if (style
68    & (NSTitledWindowMask | NSClosableWindowMask | NSMiniaturizableWindowMask))
69    {
70      *t = [theme titlebarHeight];
71    }
72  if (style & NSResizableWindowMask)
73    {
74      *b = [theme resizebarHeight];
75    }
76}
77
78+ (CGFloat) minFrameWidthWithTitle: (NSString *)aTitle
79		       styleMask: (NSUInteger)aStyle
80{
81  float l, r, t, b, width;
82
83  [self offsets: &l : &r : &t : &b forStyleMask: aStyle];
84
85  width = l + r;
86
87  if (aStyle & NSTitledWindowMask)
88    {
89      width += [aTitle sizeWithAttributes: nil].width;
90    }
91  return width;
92}
93
94- (void) updateRects
95{
96  GSTheme *theme = [GSTheme theme];
97
98  if (hasTitleBar)
99    {
100      CGFloat titleHeight = [theme titlebarHeight];
101
102      titleBarRect = NSMakeRect(0.0, [self bounds].size.height - titleHeight,
103	[self bounds].size.width, titleHeight);
104    }
105  if (hasResizeBar)
106    {
107      resizeBarRect = NSMakeRect(0.0, 0.0, [self bounds].size.width, [theme resizebarHeight]);
108    }
109  if (hasCloseButton)
110    {
111      closeButtonRect = NSMakeRect([self bounds].size.width - [theme titlebarButtonSize] -
112				   [theme titlebarPaddingRight], [self bounds].size.height -
113				   [theme titlebarButtonSize] - [theme titlebarPaddingTop],
114				   [theme titlebarButtonSize], [theme titlebarButtonSize]);
115      [closeButton setFrame: closeButtonRect];
116    }
117
118  if (hasMiniaturizeButton)
119    {
120      miniaturizeButtonRect = NSMakeRect([theme titlebarPaddingLeft], [self bounds].size.height -
121					 [theme titlebarButtonSize] - [theme titlebarPaddingTop],
122					 [theme titlebarButtonSize], [theme titlebarButtonSize]);
123      [miniaturizeButton setFrame: miniaturizeButtonRect];
124    }
125}
126
127- (id) initWithFrame: (NSRect)frame
128	      window: (NSWindow *)w
129{
130  NSUInteger styleMask;
131
132  self = [super initWithFrame: frame window: w];
133  if (!self) return nil;
134
135  styleMask = [w styleMask];
136  if (styleMask
137    & (NSTitledWindowMask | NSClosableWindowMask | NSMiniaturizableWindowMask))
138    {
139      hasTitleBar = YES;
140    }
141  if (styleMask & NSTitledWindowMask)
142    {
143      isTitled = YES;
144    }
145  if (styleMask & NSClosableWindowMask)
146    {
147      hasCloseButton = YES;
148
149      closeButton = [NSWindow standardWindowButton: NSWindowCloseButton
150                              forStyleMask: styleMask];
151      [closeButton setTarget: window];
152      [self addSubview: closeButton];
153    }
154  if (styleMask & NSMiniaturizableWindowMask)
155    {
156      hasMiniaturizeButton = YES;
157
158      miniaturizeButton = [NSWindow standardWindowButton: NSWindowMiniaturizeButton
159                              forStyleMask: styleMask];
160      [miniaturizeButton setTarget: window];
161      [self addSubview: miniaturizeButton];
162    }
163  if (styleMask & NSResizableWindowMask)
164    {
165      hasResizeBar = YES;
166    }
167  [self updateRects];
168
169  [[NSNotificationCenter defaultCenter]
170    addObserver: self
171    selector: @selector(_themeDidActivate:)
172    name: GSThemeDidActivateNotification
173    object: nil];
174  return self;
175}
176
177- (void) dealloc
178{
179  [[NSNotificationCenter defaultCenter] removeObserver: self];
180  [super dealloc];
181}
182
183- (void) drawRect: (NSRect)rect
184{
185  [[GSTheme theme] drawWindowBorder: rect
186                   withFrame: [self bounds]
187                   forStyleMask: [window styleMask]
188                   state: inputState
189                   andTitle: [window title]];
190
191  [super drawRect: rect];
192}
193
194- (void) setTitle: (NSString *)newTitle
195{
196  if (isTitled)
197    [self setNeedsDisplayInRect: titleBarRect];
198  [super setTitle: newTitle];
199}
200
201- (void) setInputState: (int)state
202{
203  NSAssert(state >= 0 && state <= 2, @"Invalid state!");
204  [super setInputState: state];
205  if (hasTitleBar)
206    [self setNeedsDisplayInRect: titleBarRect];
207}
208
209- (void) setDocumentEdited: (BOOL)flag
210{
211  if (flag)
212    {
213      [closeButton setImage: [NSImage imageNamed: @"common_CloseBroken"]];
214      [closeButton setAlternateImage:
215	[NSImage imageNamed: @"common_CloseBrokenH"]];
216    }
217  else
218    {
219      [closeButton setImage: [NSImage imageNamed: @"common_Close"]];
220      [closeButton setAlternateImage:
221	[NSImage imageNamed: @"common_CloseH"]];
222    }
223  [super setDocumentEdited: flag];
224}
225
226- (NSPoint) mouseLocationOnScreenOutsideOfEventStream
227{
228  int screen = [[window screen] screenNumber];
229  return [GSServerForWindow(window) mouseLocationOnScreen: screen
230						   window: NULL];
231}
232
233- (void) moveWindowStartingWithEvent: (NSEvent *)event
234{
235  NSUInteger mask = NSLeftMouseDraggedMask | NSLeftMouseUpMask;
236  NSEvent *currentEvent = event;
237  NSDate *distantPast = [NSDate distantPast];
238  NSPoint delta, point;
239
240  delta = [event locationInWindow];
241
242  [window _captureMouse: nil];
243  do
244    {
245      while (currentEvent && [currentEvent type] != NSLeftMouseUp)
246	{
247	  currentEvent = [_window nextEventMatchingMask: mask
248			   untilDate: distantPast
249			   inMode: NSEventTrackingRunLoopMode
250			   dequeue: YES];
251	}
252
253      point = [self mouseLocationOnScreenOutsideOfEventStream];
254      [window setFrameOrigin: NSMakePoint(point.x - delta.x,
255					  point.y - delta.y)];
256
257      if (currentEvent && [currentEvent type] == NSLeftMouseUp)
258	break;
259
260      currentEvent = [_window nextEventMatchingMask: mask
261			untilDate: [NSDate distantFuture]
262			inMode: NSEventTrackingRunLoopMode
263			dequeue: YES];
264    } while ([currentEvent type] != NSLeftMouseUp);
265  [window _releaseMouse: nil];
266}
267
268
269static NSRect
270calc_new_frame(NSRect frame, NSPoint point, NSPoint firstPoint,
271  int mode, NSSize minSize, NSSize maxSize)
272{
273  NSRect newFrame = frame;
274  newFrame.origin.y = point.y - firstPoint.y;
275  newFrame.size.height = NSMaxY(frame) - newFrame.origin.y;
276  if (newFrame.size.height < minSize.height)
277    {
278      newFrame.size.height = minSize.height;
279      newFrame.origin.y = NSMaxY(frame) - newFrame.size.height;
280    }
281
282  if (mode == 0)
283    {
284      newFrame.origin.x = point.x - firstPoint.x;
285      newFrame.size.width = NSMaxX(frame) - newFrame.origin.x;
286
287      if (newFrame.size.width < minSize.width)
288	{
289	  newFrame.size.width = minSize.width;
290	  newFrame.origin.x = NSMaxX(frame) - newFrame.size.width;
291	}
292    }
293  else if (mode == 1)
294    {
295      newFrame.size.width = point.x - frame.origin.x + frame.size.width
296			    - firstPoint.x;
297
298      if (newFrame.size.width < minSize.width)
299	{
300	  newFrame.size.width = minSize.width;
301	  newFrame.origin.x = frame.origin.x;
302	}
303    }
304  return newFrame;
305}
306
307- (void) resizeWindowStartingWithEvent: (NSEvent *)event
308{
309  NSUInteger mask = NSLeftMouseDraggedMask | NSLeftMouseUpMask | NSPeriodicMask;
310  NSEvent *currentEvent = event;
311  NSDate *distantPast = [NSDate distantPast];
312  NSDate *distantFuture = [NSDate distantFuture];
313  NSPoint firstPoint, point;
314  NSRect newFrame, frame;
315  NSSize minSize, maxSize;
316  int num = 0;
317
318  /*
319  0 drag lower left corner
320  1 drag lower right corner
321  2 drag lower edge
322  */
323  int mode;
324
325  firstPoint = [event locationInWindow];
326  if (resizeBarRect.size.width < 30 * 2
327      && firstPoint.x < resizeBarRect.size.width / 2)
328    mode = 0;
329  else if (firstPoint.x > resizeBarRect.size.width - 29)
330    mode = 1;
331  else if (firstPoint.x < 29)
332    mode = 0;
333  else
334    mode = 2;
335
336  frame = [window frame];
337  minSize = [window minSize];
338  maxSize = [window maxSize];
339
340  [window _captureMouse: nil];
341  [NSEvent startPeriodicEventsAfterDelay: 0.1 withPeriod: 0.1];
342  do
343    {
344      while (currentEvent && [currentEvent type] != NSLeftMouseUp)
345	{
346	  currentEvent = [_window nextEventMatchingMask: mask
347			   untilDate: distantPast
348			   inMode: NSEventTrackingRunLoopMode
349			   dequeue: YES];
350	}
351
352      point = [self mouseLocationOnScreenOutsideOfEventStream];
353      newFrame
354	= calc_new_frame(frame, point, firstPoint, mode, minSize, maxSize);
355
356      if (currentEvent && [currentEvent type] == NSLeftMouseUp)
357	break;
358
359      num++;
360      if (num == 5)
361	{
362	  [window setFrame: newFrame  display: YES];
363	  num = 0;
364	}
365
366      currentEvent = [_window nextEventMatchingMask: mask
367			untilDate: distantFuture
368			inMode: NSEventTrackingRunLoopMode
369			dequeue: YES];
370    } while ([currentEvent type] != NSLeftMouseUp);
371  [NSEvent stopPeriodicEvents];
372  [window _releaseMouse: nil];
373
374  [window setFrame: newFrame  display: YES];
375}
376
377- (BOOL) acceptsFirstMouse: (NSEvent*)theEvent
378{
379  return YES;
380}
381
382- (void) mouseDown: (NSEvent *)event
383{
384  NSPoint p = [self convertPoint: [event locationInWindow] fromView: nil];
385
386  if (NSPointInRect(p, contentRect))
387    {
388      [super mouseDown: event];
389      return;
390    }
391
392  if (NSPointInRect(p, titleBarRect))
393    {
394      [self moveWindowStartingWithEvent: event];
395      return;
396    }
397
398  if (NSPointInRect(p, resizeBarRect))
399    {
400      [self resizeWindowStartingWithEvent: event];
401      return;
402    }
403
404  [super mouseDown: event];
405}
406
407- (void) setFrame: (NSRect)frameRect
408{
409  [super setFrame: frameRect];
410  [self updateRects];
411}
412
413@end
414
415@implementation GSStandardWindowDecorationView (GSTheme)
416
417- (void) _themeDidActivate: (NSNotification*)notification
418{
419  [self updateRects];
420  [self setNeedsDisplay: YES];
421}
422
423@end
424