1/*
2   GNUstep ProjectCenter - http://www.gnustep.org/experience/ProjectCenter.html
3
4   Copyright (C) 2001-2013 Free Software Foundation
5
6   Authors: Philippe C.D. Robert
7            Serg Stoyan
8            Riccardo Mottola
9
10   This file is part of GNUstep.
11
12   This application is free software; you can redistribute it and/or
13   modify it under the terms of the GNU General Public
14   License as published by the Free Software Foundation; either
15   version 2 of the License, or (at your option) any later version.
16
17   This application is distributed in the hope that it will be useful,
18   but WITHOUT ANY WARRANTY; without even the implied warranty of
19   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
20   Library General Public License for more details.
21
22   You should have received a copy of the GNU General Public
23   License along with this library; if not, write to the Free
24   Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111 USA.
25*/
26
27#import <ProjectCenter/PCDefines.h>
28#import <ProjectCenter/PCProject.h>
29#import <ProjectCenter/PCFileManager.h>
30#import <ProjectCenter/PCFileCreator.h>
31
32#import <ProjectCenter/PCLogController.h>
33
34static PCFileCreator *_creator = nil;
35static NSDictionary  *dict = nil;
36
37@implementation PCFileCreator
38
39+ (id)sharedCreator
40{
41  if (_creator == nil)
42    {
43      NSDictionary *classDict;
44      NSDictionary *headerDict;
45      NSDictionary *ccDict;
46      NSDictionary *chDict;
47      NSDictionary *protocolDict;
48      NSDictionary *gsmarkupDict;
49      NSString     *descr;
50      NSString     *template;
51      NSBundle     *bundle;
52
53      _creator = [[[self class] alloc] init];
54      bundle = [NSBundle bundleForClass:[self class]];
55
56      // Setting up the dictionary needed for registration!
57
58      // Objective C Class
59      descr = @"Generic Objective-C class.\n\n"
60		@"This is a plain subclass of NSObject which includes"
61		@" only Foundation.h.";
62      template = [bundle pathForResource:@"class" ofType:@"template"];
63      classDict = [NSDictionary dictionaryWithObjectsAndKeys:
64	PCClasses, @"ProjectKey",
65	descr, @"TypeDescription",
66	template,@"TemplateFile",
67	nil];
68
69      // Objective C Header
70      descr = @"Generic Objective-C header.\n\n"
71		@"This is a plain interface subclassing NSObject."
72		@" The file includes Foundation.h";
73      template = [bundle pathForResource:@"header" ofType:@"template"];
74      headerDict =[NSDictionary dictionaryWithObjectsAndKeys:
75        PCHeaders,@"ProjectKey",
76        descr,@"TypeDescription",
77	template,@"TemplateFile",
78        nil];
79
80      // C File
81      descr = @"Generic ANSI-C implementation file.\n\n"
82		@"This file contains no Objective-C dependency in any form.";
83      template = [bundle pathForResource:@"cfile" ofType:@"template"];
84      ccDict = [NSDictionary dictionaryWithObjectsAndKeys:
85        PCOtherSources,@"ProjectKey",
86        descr,@"TypeDescription",
87	template,@"TemplateFile",
88        nil];
89
90      // C Header
91      descr = @"Generic ANSI-C header.\n\n"
92		@"This file contains no Objective-C dependency in any form.";
93      template = [bundle pathForResource:@"cheader" ofType:@"template"];
94      chDict = [NSDictionary dictionaryWithObjectsAndKeys:
95        PCHeaders,@"ProjectKey",
96        descr,@"TypeDescription",
97	template,@"TemplateFile",
98        nil];
99
100      // Objective C Protocol
101      descr = @"Generic Objective-C protocol.\n\n"
102		@"This is common Objective-C protocol, comparable"
103		@" i.e. to a Java interface.";
104      template = [bundle pathForResource:@"protocol" ofType:@"template"];
105      protocolDict = [NSDictionary dictionaryWithObjectsAndKeys:
106        PCHeaders,@"ProjectKey",
107        descr,@"TypeDescription",
108	template,@"TemplateFile",
109        nil];
110
111      // GSMarkup
112      descr = @"Generic GSMarkup File.\n\n"
113		@"This is the interface description of GNUstep Renaissance.";
114      template = [bundle pathForResource:@"gsmarkup" ofType:@"template"];
115      gsmarkupDict =[NSDictionary dictionaryWithObjectsAndKeys:
116        PCGSMarkupFiles,@"ProjectKey",
117        descr,@"TypeDescription",
118	template, @"TemplateFile",
119        nil];
120
121
122      dict = [[NSDictionary alloc] initWithObjectsAndKeys:
123	ccDict, CFile,
124        chDict, CHeader,
125        protocolDict, ProtocolFile,
126        headerDict, ObjCHeader,
127        classDict, ObjCClass,
128        gsmarkupDict, GSMarkupFile,
129	nil];
130    }
131
132  return _creator;
133}
134
135- (id)init
136{
137  self = [super init];
138  activeProject = nil;
139
140  return self;
141}
142
143- (void)dealloc
144{
145  RELEASE(newFilePanel);
146  RELEASE(dict);
147
148  [super dealloc];
149}
150
151- (NSDictionary *)creatorDictionary
152{
153  return dict;
154}
155
156- (void)newFileInProject:(PCProject *)aProject
157{
158  // Set to nil after panel closing
159  activeProject = aProject;
160  [self showNewFilePanel];
161}
162
163- (void)createFileOfType:(NSString *)fileType
164		    path:(NSString *)path
165		 project:(PCProject *)project
166{
167  NSDictionary *newFiles;
168
169  newFiles = [self filesToCreateForFileOfType:fileType
170					 path:path
171			    withComplementary:YES];
172
173  [self createFiles:newFiles inProject:project];
174}
175
176- (NSDictionary *)filesToCreateForFileOfType:(NSString *)type
177					path:(NSString *)path
178			   withComplementary:(BOOL)complementary
179{
180  NSMutableDictionary *files = nil;
181  NSString            *newFile = nil;
182
183  // A class and possibly a header
184  files = [NSMutableDictionary dictionaryWithCapacity:2];
185
186  // Remove file extension from "path"
187  if (![[path pathExtension] isEqualToString: @""])
188    {
189      path = [path stringByDeletingPathExtension];
190    }
191
192  // Objective-C Class
193  if ([type isEqualToString:ObjCClass])
194    {
195      newFile = [path stringByAppendingPathExtension:@"m"];
196      [files setObject:[dict objectForKey:ObjCClass] forKey:newFile];
197    }
198  // C File
199  else if ([type isEqualToString:CFile])
200    {
201      newFile = [path stringByAppendingPathExtension:@"c"];
202      [files setObject:[dict objectForKey:CFile] forKey:newFile];
203    }
204
205  // C Header
206  // When creating C file also create C Header file
207  if ([type isEqualToString:CHeader] ||
208      ([type isEqualToString:CFile] && complementary))
209    {
210      newFile = [path stringByAppendingPathExtension:@"h"];
211      [files setObject:[dict objectForKey:CHeader] forKey:newFile];
212    }
213  // Objective-C Header
214  // When creating Objective C Class file also create Objective C Header file
215  else if ([type isEqualToString:ObjCHeader] ||
216	   ([type isEqualToString:ObjCClass] && complementary))
217    {
218      newFile = [path stringByAppendingPathExtension:@"h"];
219      [files setObject:[dict objectForKey:ObjCHeader] forKey:newFile];
220    }
221  // GSMarkup
222  else if ([type isEqualToString:GSMarkupFile])
223    {
224      newFile = [path stringByAppendingPathExtension:@"gsmarkup"];
225      [files setObject:[dict objectForKey:GSMarkupFile] forKey:newFile];
226    }
227  // Objective-C Protocol
228  else if ([type isEqualToString:ProtocolFile])
229    {
230      newFile = [path stringByAppendingPathExtension:@"h"];
231      [files setObject:[dict objectForKey:ProtocolFile] forKey:newFile];
232    }
233
234  return files;
235}
236
237- (BOOL)createFiles:(NSDictionary *)fileList
238	  inProject:(PCProject *)aProject
239{
240  PCFileManager  *pcfm = [PCFileManager defaultManager];
241  NSEnumerator   *enumerator = [[fileList allKeys] objectEnumerator];
242  NSString       *template = nil;
243  NSString       *newFile = nil;
244  NSDictionary   *fileType = nil;
245  NSString       *key = nil;
246
247  while ((newFile = [enumerator nextObject]))
248    {
249      fileType = [fileList objectForKey:newFile];
250      key = [fileType objectForKey:@"ProjectKey"];
251      template = [fileType objectForKey:@"TemplateFile"];
252
253      if ([pcfm copyFile:template toFile:newFile])
254        {
255          [self replaceTagsInFileAtPath:newFile withProject:aProject];
256          [aProject addFiles:[NSArray arrayWithObject:newFile]
257                      forKey:key
258                      notify:YES];
259        }
260    }
261
262  // Notify the browser!
263  [[NSNotificationCenter defaultCenter]
264    postNotificationName:@"ProjectDictDidChangeNotification"
265                  object:self];
266
267  return YES;
268}
269
270- (void)replaceTagsInFileAtPath:(NSString *)newFile
271                    withProject:(PCProject *)aProject
272{
273  NSString *projectName = [aProject projectName];
274  NSString *date = [[NSCalendarDate calendarDate] description];
275  int      year = [[NSCalendarDate calendarDate] yearOfCommonEra];
276  NSString *aFile = [newFile lastPathComponent];
277  NSString *UCfn = [[aFile stringByDeletingPathExtension] uppercaseString];
278  NSString *fn = [aFile stringByDeletingPathExtension];
279  NSRange  subRange;
280
281#ifdef WIN32
282  file = [[NSMutableString stringWithContentsOfFile: newFile
283					   encoding: NSUTF8StringEncoding
284					      error: NULL] retain];
285#else
286  file = [[NSMutableString stringWithContentsOfFile:newFile] retain];
287#endif
288
289  while ((subRange = [file rangeOfString:@"$FULLFILENAME$"]).length)
290    {
291      [file replaceCharactersInRange:subRange withString:aFile];
292    }
293
294  while ((subRange = [file rangeOfString:@"$FILENAME$"]).length)
295    {
296      [file replaceCharactersInRange:subRange withString:fn];
297    }
298
299  while ((subRange = [file rangeOfString:@"$UCFILENAME$"]).length)
300    {
301      [file replaceCharactersInRange:subRange withString:UCfn];
302    }
303
304  while ((subRange = [file rangeOfString:@"$USERNAME$"]).length)
305    {
306      [file replaceCharactersInRange:subRange withString:NSUserName()];
307    }
308
309  while ((subRange = [file rangeOfString:@"$FULLUSERNAME$"]).length)
310    {
311      [file replaceCharactersInRange:subRange withString:NSFullUserName()];
312    }
313
314  while ((subRange = [file rangeOfString:@"$PROJECTNAME$"]).length)
315    {
316      [file replaceCharactersInRange:subRange withString:projectName];
317    }
318
319  while ((subRange = [file rangeOfString:@"$DATE$"]).length)
320    {
321      [file replaceCharactersInRange:subRange withString:date];
322    }
323
324  while ((subRange = [file rangeOfString:@"$YEAR$"]).length)
325    {
326      [file replaceCharactersInRange:subRange
327	withString:[[NSNumber numberWithInt:year] stringValue]];
328    }
329
330#ifdef WIN32
331  [file writeToFile: newFile
332	 atomically: YES
333	   encoding: NSUTF8StringEncoding
334	      error: NULL];
335#else
336  [file writeToFile:newFile atomically:YES];
337#endif
338
339  [file release];
340}
341
342@end
343
344
345@implementation PCFileCreator (UInterface)
346
347// ============================================================================
348// ==== "New File in Project" Panel
349// ============================================================================
350- (void)showNewFilePanel
351{
352  if (!newFilePanel)
353    {
354      if ([NSBundle loadNibNamed:@"NewFile" owner:self] == NO)
355	{
356	  PCLogError(self, @"error loading NewFile NIB!");
357	  return;
358	}
359      [newFilePanel setFrameAutosaveName:@"NewFile"];
360      if (![newFilePanel setFrameUsingName: @"NewFile"])
361    	{
362	  [newFilePanel center];
363	}
364      [nfImage setImage:[NSApp applicationIconImage]];
365      [nfTypePB setRefusesFirstResponder:YES];
366      [nfTypePB removeAllItems];
367      [nfTypePB addItemsWithTitles:
368	[[dict allKeys]
369	  sortedArrayUsingSelector:@selector(caseInsensitiveCompare:)]];
370      [nfTypePB selectItemAtIndex:0];
371      [nfCancelButton setRefusesFirstResponder:YES];
372      [nfCreateButton setRefusesFirstResponder:YES];
373      [nfAddHeaderButton setRefusesFirstResponder:YES];
374      [newFilePanel setDefaultButtonCell:[nfCreateButton cell]];
375    }
376
377  [self newFilePopupChanged:nfTypePB];
378
379  [newFilePanel makeKeyAndOrderFront:self];
380  [nfNameField setStringValue:@""];
381  [newFilePanel makeFirstResponder:nfNameField];
382
383  [newFilePanel setLevel:NSModalPanelWindowLevel];
384  [NSApp runModalForWindow:newFilePanel];
385}
386
387- (void)closeNewFilePanel:(id)sender
388{
389  [newFilePanel orderOut:self];
390  [NSApp stopModal];
391
392  activeProject = nil;
393}
394
395- (void)createFile:(id)sender
396{
397  if ([self createFile])
398    {
399      [self closeNewFilePanel:self];
400    }
401  else
402    {
403      [newFilePanel makeKeyAndOrderFront:self];
404    }
405}
406
407- (void)newFilePopupChanged:(id)sender
408{
409  NSString     *typeTitle = [sender titleOfSelectedItem];
410  NSDictionary *fileType = [dict objectForKey:typeTitle];
411
412  if (!fileType)
413    {
414      return;
415    }
416
417  [nfDescriptionTV setString:[fileType objectForKey:@"TypeDescription"]];
418  [nfAddHeaderButton setState:NSOffState];
419  if ([typeTitle isEqualToString:ObjCClass] ||
420      [typeTitle isEqualToString:CFile])
421    {
422      [nfAddHeaderButton setEnabled:YES];
423    }
424  else
425    {
426      [nfAddHeaderButton setEnabled:NO];
427    }
428}
429
430- (void)controlTextDidChange:(NSNotification *)aNotif
431{
432  if ([aNotif object] != nfNameField)
433    {
434      return;
435    }
436
437  // TODO: Add check for valid file names
438  if ([[nfNameField stringValue] length] > 0)
439    {
440      [nfCreateButton setEnabled:YES];
441    }
442  else
443    {
444      [nfCreateButton setEnabled:NO];
445    }
446}
447
448- (BOOL)createFile
449{
450  NSString      *fileName = [nfNameField stringValue];
451  NSString      *fileType = [nfTypePB titleOfSelectedItem];
452  NSString      *path = nil;
453  NSString      *key = nil;
454  NSDictionary  *newFiles = nil;
455  NSEnumerator  *enumerator = nil;
456  NSString      *filePath = nil;
457  NSFileManager *fm = [NSFileManager defaultManager];
458  BOOL          complementary;
459
460  path = [[activeProject projectPath] stringByAppendingPathComponent:fileName];
461  // Create file
462  if (path)
463    {
464      // Get file list for creation
465      complementary = [nfAddHeaderButton state]==NSOnState ? YES : NO;
466      newFiles = [self filesToCreateForFileOfType:fileType
467			       		     path:path
468				withComplementary:complementary];
469
470      // Check if project already has files with such names
471      enumerator = [[newFiles allKeys] objectEnumerator];
472      while ((filePath = [enumerator nextObject]))
473	{
474	  key = [[newFiles objectForKey:filePath] objectForKey:@"ProjectKey"];
475	  fileName = [filePath lastPathComponent];
476	  if (![activeProject doesAcceptFile:fileName forKey:key])
477	    {
478	      NSRunAlertPanel(@"New File in Project",
479			      @"Project %@ already has file %@ in %@",
480			      @"OK", nil, nil,
481			      [activeProject projectName], fileName, key);
482	      return NO;
483	    }
484	  if ([fm fileExistsAtPath:filePath])
485	    {
486	      int  ret;
487
488	      ret = NSRunAlertPanel
489		(@"New File in Project",
490		 @"Project directory %@ already has file %@.\n"
491		 @"Do you want to overwrite it?",
492		 @"Stop", @"Overwrite", nil,
493		 [filePath stringByDeletingLastPathComponent],
494		 fileName);
495
496	      if (ret == NSAlertDefaultReturn) // Stop
497		{
498		  return NO;
499		}
500	      else // Overwrite. Remove destination of copy operation
501		{
502		  [fm removeFileAtPath:filePath handler:nil];
503		}
504	    }
505	}
506
507      // Create files
508      return [self createFiles:newFiles inProject:activeProject];
509    }
510
511  return NO;
512}
513
514@end
515
516