1/** <title>GSCharacterPanel</title>
2
3   <abstract>Character Panel.</abstract>
4
5   Copyright (C) 2011 Free Software Foundation, Inc.
6
7   Author:  Eric Wasylishen <ewasylishen@gmail.com>
8   Date: July 2011
9
10   This file is part of the GNUstep Application Kit Library.
11
12   This library is free software; you can redistribute it and/or
13   modify it under the terms of the GNU Lesser 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 library 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   Lesser General Public License for more details.
21
22   You should have received a copy of the GNU Lesser General Public
23   License along with this library; see the file COPYING.LIB.
24   If not, see <http://www.gnu.org/licenses/> or write to the
25   Free Software Foundation, 51 Franklin Street, Fifth Floor,
26   Boston, MA 02110-1301, USA.
27*/
28
29#import "config.h"
30
31#import <Foundation/NSIndexSet.h>
32#import <Foundation/NSBundle.h>
33#import <Foundation/NSAutoreleasePool.h>
34#import "AppKit/NSApplication.h"
35#import "AppKit/NSStringDrawing.h"
36#import "AppKit/NSPasteboard.h"
37#import "AppKit/NSTableView.h"
38#import "AppKit/NSTableColumn.h"
39#import "AppKit/NSTextFieldCell.h"
40#import "AppKit/NSScrollView.h"
41#import "AppKit/NSSearchField.h"
42#import "GNUstepGUI/GSCharacterPanel.h"
43#import "GSGuiPrivate.h"
44
45@implementation NSApplication (CharacterPanel)
46
47- (void) orderFrontCharacterPalette: (id)sender
48{
49  [[GSCharacterPanel sharedCharacterPanel] orderFront: sender];
50}
51
52@end
53
54#if defined(HAVE_UNICODE_UCHAR_H) && defined(HAVE_UNICODE_USTRING_H)
55#include <unicode/uchar.h>
56#include <unicode/ustring.h>
57
58@interface GSVerticallyCenteredTextFieldCell : NSTextFieldCell
59{
60}
61@end
62
63@implementation GSVerticallyCenteredTextFieldCell
64
65- (NSRect) titleRectForBounds: (NSRect)aRect
66{
67  NSRect titleRect = [super titleRectForBounds: aRect];
68  NSSize titleSize = [[self attributedStringValue] size];
69  titleRect.origin.y = aRect.origin.y + (aRect.size.height - titleSize.height) / 2.0;
70  titleRect.size.height = titleSize.height;
71  return titleRect;
72}
73
74@end
75
76// Enumerating assigned codepoints
77
78static UBool enumCharNamesFn(void *context, UChar32 code, UCharNameChoice nameChoice, const char *name, int32_t length)
79{
80  [(NSMutableIndexSet*)context addIndex: (NSUInteger)code];
81  return true;
82}
83
84static NSIndexSet *AssignedCodepoints()
85{
86  UErrorCode err = U_ZERO_ERROR;
87  NSMutableIndexSet *set = [NSMutableIndexSet indexSet];
88  u_enumCharNames(UCHAR_MIN_VALUE, UCHAR_MAX_VALUE + 1, enumCharNamesFn, set, U_UNICODE_CHAR_NAME, &err);
89  return set;
90}
91
92// Searching for codepoints
93
94struct searchContext {
95  const char *searchString;
96  NSMutableIndexSet *set;
97};
98
99static UBool searchCharNamesFn(void *context, UChar32 code, UCharNameChoice nameChoice, const char *name, int32_t length)
100{
101  struct searchContext *ctx = (struct searchContext *)context;
102  if (strstr(name, ctx->searchString) != NULL)
103    {
104      [ctx->set addIndex: (NSUInteger)code];
105    }
106  return true;
107}
108
109static NSIndexSet *CodepointsWithNameContainingSubstring(NSString *str)
110{
111  UErrorCode err = U_ZERO_ERROR;
112  struct searchContext ctx;
113
114  ctx.set = [NSMutableIndexSet indexSet];
115  ctx.searchString = [[str uppercaseString] UTF8String];
116
117  u_enumCharNames(UCHAR_MIN_VALUE, UCHAR_MAX_VALUE + 1, searchCharNamesFn, &ctx, U_UNICODE_CHAR_NAME, &err);
118
119  return ctx.set;
120}
121
122
123@implementation GSCharacterPanel
124
125- (void)setVisibleCodepoints: (NSIndexSet*)set
126{
127  ASSIGN(visibleCodepoints, set);
128}
129
130
131+ (GSCharacterPanel *) sharedCharacterPanel
132{
133  static GSCharacterPanel *shared = nil;
134  if (nil == shared)
135    {
136      shared = [[self alloc] init];
137    }
138  return shared;
139}
140
141- (id) init
142{
143  const NSRect contentRect = NSMakeRect(100, 100, 276, 420);
144  self = [super initWithContentRect: contentRect
145			  styleMask: NSTitledWindowMask | NSClosableWindowMask | NSResizableWindowMask | NSUtilityWindowMask
146			    backing: NSBackingStoreBuffered
147			      defer: YES];
148  if (nil != self)
149    {
150      // Setup assignedCodepoints and  visibleCodepointsArray
151      assignedCodepoints = [AssignedCodepoints() retain];
152      [self setVisibleCodepoints: assignedCodepoints];
153
154      [self setTitle: _(@"Character Panel")];
155
156      // Set up the table view
157      table = [[[NSTableView alloc] initWithFrame: NSMakeRect(0, 0, contentRect.size.width - 18, contentRect.size.height - 52)] autorelease];
158
159      // Set up table columns
160      {
161	NSTableColumn *col = [[[NSTableColumn alloc] initWithIdentifier: @"char"] autorelease];
162	[col setDataCell: [[[GSVerticallyCenteredTextFieldCell alloc] init] autorelease]];
163	[[col dataCell] setFont:[NSFont systemFontOfSize: 24]];
164	[[col dataCell] setAlignment: NSCenterTextAlignment];
165	[col setMinWidth: 40];
166	[col setWidth: 40];
167	[table addTableColumn: col];
168      }
169      {
170	NSTableColumn *col = [[[NSTableColumn alloc] initWithIdentifier: @"name"] autorelease];
171	[col setDataCell: [[[GSVerticallyCenteredTextFieldCell alloc] init] autorelease]];
172	[[col dataCell] setFont:[NSFont systemFontOfSize: 10]];
173	[[col headerCell] setStringValue: _(@"Name")];
174	[col setWidth: 195];
175	[table addTableColumn: col];
176      }
177      {
178	NSTableColumn *col = [[[NSTableColumn alloc] initWithIdentifier: @"code"] autorelease];
179	[col setDataCell: [[[GSVerticallyCenteredTextFieldCell alloc] init] autorelease]];
180	[[col dataCell] setFont:[NSFont systemFontOfSize: 10]];
181	[[col dataCell] setAlignment: NSCenterTextAlignment];
182	[[col headerCell] setStringValue: _(@"Code Point")];
183	[col setMinWidth: 80];
184	[col setWidth: 80];
185	[table addTableColumn: col];
186      }
187      {
188	NSTableColumn *col = [[[NSTableColumn alloc] initWithIdentifier: @"block"] autorelease];
189	[col setDataCell: [[[GSVerticallyCenteredTextFieldCell alloc] init] autorelease]];
190	[[col dataCell] setFont:[NSFont systemFontOfSize: 10]];
191	[[col headerCell] setStringValue: _(@"Unicode Block")];
192	[col setMinWidth: 140];
193	[table addTableColumn: col];
194      }
195
196      [table setRowHeight: 32];
197      [table setDataSource: self];
198      [table setDelegate: self];
199      [table setTarget: self];
200      [table setDoubleAction: @selector(doubleClickRow:)];
201
202      // Allow dragging out of the application
203      [table setDraggingSourceOperationMask:NSDragOperationCopy forLocal:NO];
204
205      // Set up scroll view
206      {
207	NSScrollView *scrollView = [[NSScrollView alloc] initWithFrame: NSMakeRect(9, 41, contentRect.size.width - 18, contentRect.size.height - 52)];
208	[scrollView setDocumentView: table];
209	[scrollView setHasHorizontalScroller: YES];
210	[scrollView setHasVerticalScroller: YES];
211	[scrollView setBorderType: NSBezelBorder];
212	[scrollView setAutoresizingMask: (NSViewWidthSizable | NSViewHeightSizable)];
213	[[self contentView] addSubview: scrollView];
214	[scrollView release];
215      }
216
217      // Set up search field
218      {
219	searchfield = [[NSSearchField alloc] initWithFrame: NSMakeRect(9,9,186,22)];
220	[searchfield setTarget: self];
221	[searchfield setAction: @selector(search:)];
222	[[self contentView] addSubview: searchfield];
223	[searchfield release];
224      }
225    }
226
227  return self;
228}
229
230- (void)dealloc
231{
232  [assignedCodepoints release];
233  [visibleCodepoints release];
234  [super dealloc];
235}
236
237- (void)search: (id)sender
238{
239  NSString *str = [searchfield stringValue];
240
241  if ([str length] == 0)
242    {
243      [self setVisibleCodepoints: assignedCodepoints];
244    }
245  else
246    {
247      NSIndexSet *set = CodepointsWithNameContainingSubstring(str);
248      [self setVisibleCodepoints: set];
249    }
250
251  [table reloadData];
252}
253
254- (NSUInteger) codepointAtVisibleRow:(NSUInteger)row
255{
256  //FIXME: Use a binary search
257  NSUInteger curr = 0;
258  NSUInteger currValue = [visibleCodepoints firstIndex];
259
260  while (currValue != NSNotFound)
261    {
262      if (curr == row)
263	{
264	  return currValue;
265	}
266      currValue = [visibleCodepoints indexGreaterThanIndex: currValue];
267      curr++;
268    }
269  return NSNotFound;
270}
271
272- (NSString *)characterForRow: (NSInteger)row
273{
274  if (row >= 0 && row < [visibleCodepoints count])
275    {
276      UChar32 utf32 = [self codepointAtVisibleRow: row];
277      UChar utf16buf[2];
278      int32_t utf16bufLength = 0;
279      UErrorCode error = U_ZERO_ERROR;
280      u_strFromUTF32(utf16buf, 2, &utf16bufLength, &utf32, 1, &error);
281
282      return [[[NSString alloc] initWithCharacters: utf16buf
283					    length: utf16bufLength] autorelease];
284    }
285  return @"";
286}
287
288- (void) doubleClickRow: (id)sender
289{
290  NSWindow *mainWindow = [NSApp mainWindow];
291  NSResponder *firstResponder = [mainWindow firstResponder];
292  NSString *str = [self characterForRow: [table clickedRow]];
293
294  [firstResponder insertText: str];
295}
296
297- (BOOL) tableView: (NSTableView *)aTable shouldEditTableColumn: (NSTableColumn *)aColumn row: (NSInteger)row
298{
299  return NO;
300}
301
302// NSTableViewDataSource protocol
303
304- (NSInteger)numberOfRowsInTableView:(NSTableView *)tableView
305{
306  return [visibleCodepoints count];
307}
308
309- (id)tableView:(NSTableView *)tableView objectValueForTableColumn:(NSTableColumn *)tableColumn row:(NSInteger)row
310{
311  UChar32 utf32 = [self codepointAtVisibleRow: row];
312
313  if ([[tableColumn identifier] isEqualToString: @"char"])
314    {
315      return [self characterForRow: row];
316    }
317  else if ([[tableColumn identifier] isEqualToString: @"name"])
318    {
319      UErrorCode error = U_ZERO_ERROR;
320      int32_t size = u_charName(utf32, U_UNICODE_CHAR_NAME, NULL, 0, &error);
321
322      if (size > 0)
323	{
324	  char name[512];
325	  error = U_ZERO_ERROR;
326	  u_charName(utf32, U_UNICODE_CHAR_NAME, name, 512, &error);
327
328	  NSString *nameObj = [[[NSString alloc] initWithBytes: name
329							length: size
330						      encoding: NSASCIIStringEncoding] autorelease];
331	  return [[nameObj lowercaseString] capitalizedString];
332	}
333      return @"";
334    }
335  else if ([[tableColumn identifier] isEqualToString: @"code"])
336    {
337      return [NSString stringWithFormat:@"U+%04X", (int)utf32];
338    }
339  else if ([[tableColumn identifier] isEqualToString: @"block"])
340    {
341      int32_t val = u_getIntPropertyValue(utf32, UCHAR_BLOCK);
342      const char *name = u_getPropertyValueName(UCHAR_BLOCK, val, U_LONG_PROPERTY_NAME);
343      if (name != NULL)
344	{
345	  return [[[[NSString alloc] initWithBytes: name
346					    length: strlen(name)
347					  encoding: NSASCIIStringEncoding] autorelease] stringByReplacingOccurrencesOfString: @"_" withString: @" "];
348	}
349    }
350  return nil;
351}
352
353- (BOOL)tableView:(NSTableView *)aTableView writeRowsWithIndexes:(NSIndexSet *)rowIndexes toPasteboard:(NSPasteboard *)pboard
354{
355  NSString *str = [self characterForRow: [rowIndexes firstIndex]];
356
357	[pboard declareTypes: [NSArray arrayWithObject: NSStringPboardType]
358			owner: nil];
359	[pboard setString: str
360		forType: NSStringPboardType];
361
362	return YES;
363}
364
365@end
366
367#else // !(defined(HAVE_UNICODE_UCHAR_H) && defined(HAVE_UNICODE_USTRING_H))
368
369@implementation GSCharacterPanel
370
371+ (GSCharacterPanel *) sharedCharacterPanel
372{
373  return nil;
374}
375
376@end
377
378#endif
379