1/*
2  Copyright 2002, 2003 Alexander Malmberg <alexander@malmberg.org>
3            2016 Riccardo Mottola
4            2016 Tim Sheridan
5
6  This file is a part of Terminal.app. Terminal.app is free software; you
7  can redistribute it and/or modify it under the terms of the GNU General
8  Public License as published by the Free Software Foundation; version 2
9  of the License. See COPYING or main.m for more information.
10*/
11
12#include <math.h>
13#include <sys/wait.h>
14
15#import <Foundation/NSBundle.h>
16#import <Foundation/NSDebug.h>
17#import <Foundation/NSNotification.h>
18#import <Foundation/NSString.h>
19#import <Foundation/NSUserDefaults.h>
20#import <AppKit/NSApplication.h>
21#import <AppKit/NSScroller.h>
22#import <AppKit/NSTabViewItem.h>
23#import <AppKit/NSWindow.h>
24#import <GNUstepGUI/GSHbox.h>
25
26#import "TerminalWindow.h"
27
28#import "TerminalWindowPrefs.h"
29#import "TerminalView.h"
30
31
32/* TODO: this needs cleaning up. chances are this will interfere
33with NSTask */
34static void get_zombies(void)
35{
36	int status,pid;
37	while ((pid=waitpid(-1,&status,WNOHANG))>0)
38	{
39//		printf("got %i\n",pid);
40	}
41}
42
43
44static int num_instances;
45
46
47NSString *TerminalWindowNoMoreActiveWindowsNotification=
48	@"TerminalWindowNoMoreActiveWindowsNotification";
49
50
51@implementation TerminalWindowController
52
53- init
54{
55  if ((self = [super init]))
56    {
57	NSWindow *win;
58	NSScroller *scroller;
59	GSHbox *hb;
60	CGFloat fx,fy;
61	CGFloat scroller_width;
62	NSRect contentRect,windowRect;
63	NSSize contentSize,minSize;
64        NSTabViewItem *tab_item;
65        TerminalView *tv;
66	int sx,sy;
67
68        isShowingTabs = NO;
69        if ([self showTabBar])
70          isShowingTabs = YES;
71
72	{
73		NSSize size=[TerminalView characterCellSize];
74		fx=size.width;
75		fy=size.height;
76	}
77
78	sx=[TerminalWindowPrefs defaultWindowWidth];
79	sy=[TerminalWindowPrefs defaultWindowHeight];
80
81	scroller_width=[NSScroller scrollerWidth];
82
83	// calc the rects for our window
84	contentSize = NSMakeSize (fx * sx + scroller_width + 1, fy * sy + 1);
85	minSize = NSMakeSize (fx * 20 + scroller_width + 1, fy * 4 + 1);
86
87	// add the borders to the size
88	contentSize.width += 8;
89	minSize.width += 8;
90	if ([TerminalWindowPrefs addYBorders])
91	{
92		contentSize.height += 8;
93		minSize.height += 8;
94	}
95
96	contentRect = NSMakeRect (100, 100, contentSize.width, contentSize.height);
97
98	win=[[NSWindow alloc] initWithContentRect: contentRect
99		styleMask: NSClosableWindowMask|NSTitledWindowMask|NSResizableWindowMask|NSMiniaturizableWindowMask
100		backing: NSBackingStoreRetained
101		defer: YES];
102	if (!(self=[super initWithWindow: win])) return nil;
103
104	num_instances++;
105
106	windowRect = [win frame];
107	minSize.width += windowRect.size.width - contentSize.width;
108	minSize.height += windowRect.size.height - contentSize.height;
109
110	[win setTitle: @"Terminal"];
111	[win setDelegate: self];
112
113	[win setContentSize: contentSize];
114	[win setResizeIncrements: NSMakeSize (fx , fy)];
115	[win setMinSize: minSize];
116
117	hb=[[GSHbox alloc] init];
118
119	scroller=[[NSScroller alloc] initWithFrame: NSMakeRect(0,0,[NSScroller scrollerWidth],fy)];
120	[scroller setArrowsPosition: NSScrollerArrowsMaxEnd];
121	[scroller setEnabled: YES];
122	[scroller setAutoresizingMask: NSViewHeightSizable];
123	[hb addView: scroller  enablingXResizing: NO];
124	[scroller release];
125
126	tab_view = [[NSTabView alloc] init];
127	[tab_view setDelegate:self];
128	tab_item = [[NSTabViewItem alloc] init];
129	[tab_item setLabel:@"Terminal"];
130        if (isShowingTabs)
131          [tab_item setView:hb];
132	[tab_view addTabViewItem:tab_item];
133        [tab_item release];
134
135	tv = [[TerminalView alloc] init];
136	[tv setIgnoreResize: YES];
137	[tv setAutoresizingMask: NSViewHeightSizable|NSViewWidthSizable];
138	[tv setScroller: scroller];
139	[hb addView: tv];
140	[tv release];
141	[win makeFirstResponder: tv];
142	[tv setIgnoreResize: NO];
143
144	terminal_views = [[NSMutableArray alloc] init];
145	[terminal_views addObject:tv];
146
147	if ([TerminalWindowPrefs addYBorders])
148		[tv setBorder: 4 : 4];
149	else
150		[tv setBorder: 4 : 0];
151
152        if (isShowingTabs)
153          {
154            [win setContentView: tab_view];
155          }
156        else
157          {
158            [win setContentView: hb];
159          }
160	DESTROY(hb);
161
162
163	[win release];
164
165	[[NSNotificationCenter defaultCenter]
166		addObserver: self
167		selector: @selector(_becameIdle:)
168		name: TerminalViewBecameIdleNotification
169		object: tv];
170	[[NSNotificationCenter defaultCenter]
171		addObserver: self
172		selector: @selector(_becameNonIdle:)
173		name: TerminalViewBecameNonIdleNotification
174		object: tv];
175	[[NSNotificationCenter defaultCenter]
176		addObserver: self
177		selector: @selector(_updateTitle:)
178		name: TerminalViewTitleDidChangeNotification
179		object: tv];
180
181    }
182  return self;
183}
184
185
186-(void) _updateTitleFromTerminalView: (TerminalView *)tv
187{
188  NSUInteger index;
189
190  index = [terminal_views indexOfObjectIdenticalTo:tv];
191  if (index == NSNotFound)
192    {
193      NSLog(@"updateTitle view not found: %@ %@", [tv windowTitle], [tv representedFilename]);
194      NSLog(@"view is: %@, views are: %@", tv, terminal_views);
195      return;
196    }
197	[[tab_view tabViewItemAtIndex:index] setLabel:[tv windowTitle]];
198	[tab_view display];
199
200	if (tv == [self frontTerminalView]) {
201		[[self window] setTitle: [tv windowTitle]];
202		[[self window] setMiniwindowTitle: [tv miniwindowTitle]];
203		[[self window] setRepresentedFilename: [tv representedFilename]];
204	}
205}
206
207-(void) _updateTitle: (NSNotification *)n
208{
209	TerminalView *tv = [n object];
210	[self _updateTitleFromTerminalView:tv];
211}
212
213
214-(void) dealloc
215{
216	num_instances--;
217	[TerminalWindowController checkActiveWindows];
218	[[NSNotificationCenter defaultCenter]
219		removeObserver: self];
220        [terminal_views release];
221	[super dealloc];
222}
223
224
225static NSMutableArray *idle_list;
226
227-(void) windowWillClose: (NSNotification *)n
228{
229	get_zombies();
230	[idle_list removeObject: self];
231	[self autorelease];
232}
233
234-(void) _becameIdle: (NSNotification *)n
235{
236	NSDebugLLog(@"idle",@"%@ _becameIdle",self);
237
238	if (close_on_idle)
239	{
240		TerminalView *tv = [n object];
241		[self closeTerminalTab:tv inWindow:[self window]];
242		return;
243	}
244
245	[idle_list addObject: self];
246	NSDebugLLog(@"idle",@"idle list: %@",idle_list);
247
248	{
249		NSString *t;
250
251		t=[[self window] title];
252		t=[t stringByAppendingString: _(@" (idle)")];
253		[[self window] setTitle: t];
254
255		t=[[self window] miniwindowTitle];
256		t=[t stringByAppendingString: _(@" (idle)")];
257		[[self window] setMiniwindowTitle: t];
258	}
259
260	[TerminalWindowController checkActiveWindows];
261}
262
263-(void) _becameNonIdle: (NSNotification *)n
264{
265	NSDebugLLog(@"idle",@"%@ _becameNonIdle",self);
266	[idle_list removeObject: self];
267	NSDebugLLog(@"idle",@"idle list: %@",idle_list);
268}
269
270
271-(TerminalView *) frontTerminalView
272{
273	NSTabViewItem *item = [tab_view selectedTabViewItem];
274	NSInteger index = [tab_view indexOfTabViewItem:item];
275	return [terminal_views objectAtIndex:index];
276}
277
278-(void) setShouldCloseWhenIdle: (BOOL)should
279{
280	close_on_idle=should;
281}
282
283
284+(void) initialize
285{
286	if (!idle_list)
287		idle_list=[[NSMutableArray alloc] init];
288}
289
290
291+(TerminalWindowController *) newTerminalWindow
292{
293	TerminalWindowController *twc;
294
295	twc=[[self alloc] init];
296	if ([TerminalWindowPrefs windowCloseBehavior]==0)
297		[twc setShouldCloseWhenIdle: YES];
298	[twc showWindow: self];
299	return twc;
300}
301
302+(TerminalWindowController *) idleTerminalWindow
303{
304	TerminalWindowController *new;
305
306	NSDebugLLog(@"idle",@"get idle window from idle list: %@",idle_list);
307	if ([idle_list count])
308		return [idle_list objectAtIndex: 0];
309	new=[[self alloc] init];
310	[new showWindow: self];
311	return new;
312}
313
314+(int) numberOfActiveWindows
315{
316	return num_instances-[idle_list count];
317}
318
319+(void) checkActiveWindows
320{
321	if (![self numberOfActiveWindows])
322	{
323		[[NSNotificationCenter defaultCenter]
324			postNotificationName: TerminalWindowNoMoreActiveWindowsNotification
325			object: self];
326	}
327}
328
329-(BOOL) showTabBar
330{
331	NSUserDefaults *defaults = [NSUserDefaults standardUserDefaults];
332	return [defaults boolForKey:@"ShowTabBar"];
333}
334
335-(void) setShowTabBar:(BOOL)visible inWindow:(NSWindow *)window
336{
337	// TODO more explicit default value
338	NSUserDefaults *defaults = [NSUserDefaults standardUserDefaults];
339	[defaults setBool:visible forKey:@"ShowTabBar"];
340	[defaults synchronize];
341
342	if ([tab_view numberOfTabViewItems] == 1) {
343		if (visible) {
344                  NSView *view;
345
346                  view = [window contentView];
347                  [view retain];
348                  [window setContentView:tab_view];
349                  [[tab_view tabViewItemAtIndex:0] setView:view];
350                  [tab_view selectFirstTabViewItem:nil];
351                  [tab_view display];
352                  [view release];
353                  isShowingTabs = YES;
354		} else {
355                  NSView *view = [[tab_view selectedTabViewItem] view];
356                  [window setContentView:view];
357                  isShowingTabs = NO;
358		}
359	}
360}
361
362-(void) newTerminalTabInWindow:(NSWindow *)window
363{
364        NSTabViewItem *tab_item;
365        TerminalView *tv;
366        NSScroller *scroller;
367        GSHbox *hb;
368	CGFloat fy;
369
370	if (!isShowingTabs)
371          {
372            NSView *view;
373
374            view = [window contentView];
375            [view retain];
376            [window setContentView:tab_view];
377            [[tab_view tabViewItemAtIndex:0] setView:view];
378            [view release];
379            isShowingTabs = YES;
380          }
381
382	{
383		NSSize size=[TerminalView characterCellSize];
384		fy=size.height;
385	}
386
387	tab_item = [[NSTabViewItem alloc] init];
388	[tab_item setLabel:@"Terminal"];
389	[tab_view addTabViewItem:tab_item];
390        [tab_item release];
391
392	hb=[[GSHbox alloc] init];
393
394	scroller=[[NSScroller alloc] initWithFrame: NSMakeRect(0,0,[NSScroller scrollerWidth],fy)];
395	[scroller setArrowsPosition: NSScrollerArrowsMaxEnd];
396	[scroller setEnabled: YES];
397	[scroller setAutoresizingMask: NSViewHeightSizable];
398	[hb addView: scroller  enablingXResizing: NO];
399	[scroller release];
400
401	tv = [[TerminalView alloc] init];
402	[tv setIgnoreResize: YES];
403	[tv setAutoresizingMask: NSViewHeightSizable|NSViewWidthSizable];
404	[tv setScroller: scroller];
405	[hb addView: tv];
406	[tv release];
407	[tv setIgnoreResize: NO];
408
409	[terminal_views addObject:tv];
410
411	if ([TerminalWindowPrefs addYBorders])
412		[tv setBorder: 4 : 4];
413	else
414		[tv setBorder: 4 : 0];
415
416	[tab_item setView:hb];
417        DESTROY(hb);
418
419	[tab_view selectLastTabViewItem:nil];
420
421	[[NSNotificationCenter defaultCenter]
422		addObserver: self
423		selector: @selector(_becameIdle:)
424		name: TerminalViewBecameIdleNotification
425		object: tv];
426	[[NSNotificationCenter defaultCenter]
427		addObserver: self
428		selector: @selector(_becameNonIdle:)
429		name: TerminalViewBecameNonIdleNotification
430		object: tv];
431	[[NSNotificationCenter defaultCenter]
432		addObserver: self
433		selector: @selector(_updateTitle:)
434		name: TerminalViewTitleDidChangeNotification
435		object: tv];
436}
437
438-(void) closeTerminalTab:(TerminalView *)tv inWindow:(NSWindow *)window
439{
440	NSTabViewItem *item;
441        NSInteger index;
442        NSInteger first_tab_index;
443        NSInteger last_tab_index;
444
445        if ([tab_view numberOfTabViewItems] == 1) {
446		[window performClose:nil];
447		return;
448	}
449
450	item = [tab_view selectedTabViewItem];
451	index = [tab_view indexOfTabViewItem:item];
452
453	// Select new tab before removing old one
454	first_tab_index = 0;
455	last_tab_index = [tab_view numberOfTabViewItems] - 1;
456	// TODO A better tab selection heuristic (e.g. most recently used)
457	if (index == first_tab_index) {
458		[tab_view selectNextTabViewItem:nil];
459	} else if (index == last_tab_index) {
460		[tab_view selectPreviousTabViewItem:nil];
461	} else {
462		[tab_view selectNextTabViewItem:nil];
463	}
464
465	[tab_view removeTabViewItem:item];
466	[terminal_views removeObjectAtIndex:index];
467
468	if ([tab_view numberOfTabViewItems] == 1)
469          {
470            NSView *view;
471
472            view = [[tab_view tabViewItemAtIndex:0] view];
473            [view retain];
474            [window setContentView: view];
475            [view release];
476            [window makeFirstResponder:[terminal_views objectAtIndex:0]];
477
478		// Follow tab bar visible setting
479		[self setShowTabBar:[self showTabBar] inWindow:window];
480	}
481
482	[tab_view display];
483}
484
485-(void) showPreviousTab
486{
487	// Clamp
488	NSInteger first_tab_index = 0;
489	NSTabViewItem *item = [tab_view selectedTabViewItem];
490	NSInteger index = [tab_view indexOfTabViewItem:item];
491	if (index == first_tab_index) {
492		return;
493	}
494
495	[tab_view selectPreviousTabViewItem:nil];
496}
497
498-(void) showNextTab
499{
500	// Clamp
501	NSInteger last_tab_index = [tab_view numberOfTabViewItems] - 1;
502	NSTabViewItem *item = [tab_view selectedTabViewItem];
503	NSInteger index = [tab_view indexOfTabViewItem:item];
504	if (index == last_tab_index) {
505		return;
506	}
507
508	[tab_view selectNextTabViewItem:nil];
509}
510
511-(void) moveTabLeft
512{
513	NSTabViewItem *old_item;
514
515        // Clamp
516	NSInteger first_tab_index = 0;
517	NSTabViewItem *item = [tab_view selectedTabViewItem];
518	NSInteger index = [tab_view indexOfTabViewItem:item];
519	if (index == first_tab_index) {
520		return;
521	}
522
523	NSInteger destination_index = index - 1;
524
525	TerminalView *old_tv = [terminal_views objectAtIndex:destination_index];
526	TerminalView *new_tv = [terminal_views objectAtIndex:index];
527	[terminal_views replaceObjectAtIndex:destination_index withObject:new_tv];
528	[terminal_views replaceObjectAtIndex:index             withObject:old_tv];
529
530	old_item = [tab_view tabViewItemAtIndex:destination_index];
531        [old_item retain];
532	[tab_view removeTabViewItem:old_item];
533	[tab_view insertTabViewItem:old_item atIndex:index];
534        [old_item release];
535
536	[self _updateTitleFromTerminalView:old_tv];
537	[self _updateTitleFromTerminalView:new_tv];
538
539	[tab_view selectTabViewItemAtIndex:destination_index];
540
541	[tab_view display];
542}
543
544-(void) moveTabRight
545{
546	NSTabViewItem *old_item;
547        NSInteger destination_index;
548        TerminalView *old_tv;
549        TerminalView *new_tv;
550
551        // Clamp
552	NSInteger last_tab_index = [tab_view numberOfTabViewItems] - 1;
553	NSTabViewItem *item = [tab_view selectedTabViewItem];
554	NSInteger index = [tab_view indexOfTabViewItem:item];
555	if (index == last_tab_index) {
556		return;
557	}
558
559	destination_index = index + 1;
560
561	old_tv = [terminal_views objectAtIndex:destination_index];
562	new_tv = [terminal_views objectAtIndex:index];
563	[terminal_views replaceObjectAtIndex:destination_index withObject:new_tv];
564	[terminal_views replaceObjectAtIndex:index             withObject:old_tv];
565
566	old_item = [tab_view tabViewItemAtIndex:destination_index];
567        [old_item retain];
568	[tab_view removeTabViewItem:old_item];
569	[tab_view insertTabViewItem:old_item atIndex:index];
570        [old_item release];
571
572	[self _updateTitleFromTerminalView:old_tv];
573	[self _updateTitleFromTerminalView:new_tv];
574
575	[tab_view selectTabViewItemAtIndex:destination_index];
576
577	[tab_view display];
578}
579
580- (void) tabView: (NSTabView*)tabView didSelectTabViewItem: (NSTabViewItem*)tabViewItem
581{
582	NSInteger tab_number = [tabView indexOfTabViewItem:tabViewItem];
583
584	TerminalView *tv = [terminal_views objectAtIndex:tab_number];
585	[self _updateTitleFromTerminalView:tv];
586
587	[[self window] makeFirstResponder:[terminal_views objectAtIndex:tab_number]];
588	[tv setNeedsDisplay:YES];
589}
590
591@end
592
593