mirror of
https://github.com/cappuccino/cappuccino.git
synced 2026-10-25 17:45:36 +00:00
AppKit lacked a public text attachment class, preventing rich text documents
from embedding or inserting images.
* AppKit/CPTextView/CPTextAttachment.j:
- Implement CPTextAttachment (modelled on NSTextAttachment) supporting
initialization via data and file type, contents, file types, images,
bounds, and keyed archiving.
- Adopt a TextKit 2-style view rendering architecture that scales images
to fit text container width and triggers layout updates upon asynchronous loading.
* AppKit/CPTextView/CPTextStorage.j:
- Update attributedStringWithAttachment: to accept CPTextAttachment instances
alongside direct views, routing rendering through existing attachment pathways.
* AppKit/CPTextView/CPTextView.j:
- Implement the insertImage: action (invoking browser file pickers) and
Command-V paste handling for images.
- Configure inserted attachments with responsive view instances scaled to
text width, omitting live views during pasteboard archiving.
* AppKit/CPTextView/CPTextContainer.j:
- Adjust attachment bounds to match updated container widths during layout invalidation.
* AppKit/AppKit.j:
- Export CPTextAttachment.
* Tests/AppKit/CPTextAttachmentTest.j:
- Add test coverage for data URLs, archiving, width scaling, insertion,
and serialized text output.
* Tests/Manual/TextEditor/Resources/MainMenu.xib, MainMenu.cib:
- Add Edit > Insert Image… menu item.
* Tests/Manual/TextEditor/WORKLIST.md:
- Mark image insertion testing as completed; log remaining image tasks as open.
Rich text documents now support embedded images inserted via file selection or clipboard paste.
119 lines
4.6 KiB
Plaintext
119 lines
4.6 KiB
Plaintext
@import <AppKit/CPApplication.j>
|
|
@import <AppKit/CPTextAttachment.j>
|
|
@import <AppKit/CPTextView.j>
|
|
|
|
@implementation CPTextAttachmentTest : OJTestCase
|
|
{
|
|
CPWindow theWindow;
|
|
CPTextView textView;
|
|
}
|
|
|
|
- (void)setUp
|
|
{
|
|
[[CPApplication alloc] init];
|
|
|
|
theWindow = [[CPWindow alloc] initWithContentRect:CGRectMake(0.0, 0.0, 1024.0, 768.0) styleMask:CPWindowNotSizable];
|
|
textView = [[CPTextView alloc] initWithFrame:CGRectMake(0.0, 0.0, 300.0, 300.0)];
|
|
|
|
[textView setRichText:YES];
|
|
[[theWindow contentView] addSubview:textView];
|
|
[theWindow makeFirstResponder:textView];
|
|
}
|
|
|
|
/*
|
|
Returns an attachment with a fixed size and no image, so that no image needs to load.
|
|
*/
|
|
- (CPTextAttachment)attachmentWithSize:(CGSize)aSize
|
|
{
|
|
var attachment = [[CPTextAttachment alloc] initWithData:nil ofType:@"image/png"];
|
|
|
|
[attachment setBounds:CGRectMake(0.0, 0.0, aSize.width, aSize.height)];
|
|
|
|
return attachment;
|
|
}
|
|
|
|
- (void)testAttachmentWithDataURL
|
|
{
|
|
var attachment = [CPTextAttachment _attachmentWithDataURL:@"data:image/gif;base64,R0lGODlhAQABAAAAACw="];
|
|
|
|
[self assert:@"image/gif" equals:[attachment fileType]];
|
|
[self assert:@"R0lGODlhAQABAAAAACw=" equals:[[attachment contents] base64]];
|
|
[self assertNull:[CPTextAttachment _attachmentWithDataURL:@"not a data URL"]];
|
|
}
|
|
|
|
- (void)testArchiving
|
|
{
|
|
var attachment = [[CPTextAttachment alloc] initWithData:[CPData dataWithBase64:@"R0lGODlhAQABAAAAACw="] ofType:@"public.png"];
|
|
|
|
[attachment setBounds:CGRectMake(0.0, 0.0, 40.0, 30.0)];
|
|
|
|
var decoded = [CPKeyedUnarchiver unarchiveObjectWithData:[CPKeyedArchiver archivedDataWithRootObject:attachment]];
|
|
|
|
[self assert:@"R0lGODlhAQABAAAAACw=" equals:[[decoded contents] base64]];
|
|
[self assert:@"public.png" equals:[decoded fileType]];
|
|
[self assert:40.0 equals:[decoded bounds].size.width];
|
|
[self assert:@"image/png" equals:[decoded _MIMEType]];
|
|
}
|
|
|
|
- (void)testDisplaySizeFitsTheMaximumWidth
|
|
{
|
|
var attachment = [self attachmentWithSize:CGSizeMake(400.0, 200.0)],
|
|
view = [attachment _attachmentView];
|
|
|
|
[self assert:400.0 equals:[view frameSize].width];
|
|
|
|
[self assertTrue:[attachment _setMaximumWidth:100.0]];
|
|
[self assert:100.0 equals:[view frameSize].width];
|
|
[self assert:50.0 equals:[view frameSize].height message:@"the image keeps its proportions"];
|
|
|
|
[self assertTrue:[attachment _setMaximumWidth:1000.0]];
|
|
[self assert:400.0 equals:[view frameSize].width message:@"an image is not enlarged beyond its size"];
|
|
}
|
|
|
|
- (void)testAttributedStringWithAttachment
|
|
{
|
|
var attachment = [self attachmentWithSize:CGSizeMake(20.0, 10.0)],
|
|
string = [CPTextStorage attributedStringWithAttachment:attachment],
|
|
view = [[CPView alloc] initWithFrame:CGRectMake(0.0, 0.0, 10.0, 10.0)],
|
|
viewString = [CPTextStorage attributedStringWithAttachment:view];
|
|
|
|
[self assert:_CPAttachmentCharacterAsString equals:[string string]];
|
|
[self assert:attachment equals:[string attribute:CPAttachmentAttributeName atIndex:0 effectiveRange:nil]];
|
|
[self assert:[attachment _attachmentView] equals:[string attribute:_CPAttachmentView atIndex:0 effectiveRange:nil]];
|
|
|
|
[self assert:view equals:[viewString attribute:_CPAttachmentView atIndex:0 effectiveRange:nil] message:@"a view is still inserted directly"];
|
|
[self assertNull:[viewString attribute:CPAttachmentAttributeName atIndex:0 effectiveRange:nil]];
|
|
}
|
|
|
|
- (void)testInsertedAttachmentFitsTheText
|
|
{
|
|
var attachment = [self attachmentWithSize:CGSizeMake(1000.0, 500.0)];
|
|
|
|
[textView setString:@"ab"];
|
|
[textView setSelectedRange:CPMakeRange(1, 0)];
|
|
[textView insertText:[CPTextStorage attributedStringWithAttachment:attachment]];
|
|
|
|
var maximumWidth = [textView _textAttachmentMaximumWidth];
|
|
|
|
[self assert:@"a" + _CPAttachmentCharacterAsString + @"b" equals:[textView string]];
|
|
[self assert:maximumWidth equals:[[attachment _attachmentView] frameSize].width];
|
|
[self assert:1 equals:[textView _indexOfTextAttachment:attachment]];
|
|
}
|
|
|
|
- (void)testArchivedTextHasNoAttachmentViews
|
|
{
|
|
var attachment = [self attachmentWithSize:CGSizeMake(20.0, 10.0)],
|
|
archived = [textView _attributedStringForArchiving:[CPTextStorage attributedStringWithAttachment:attachment]];
|
|
|
|
[self assert:attachment equals:[archived attribute:CPAttachmentAttributeName atIndex:0 effectiveRange:nil]];
|
|
[self assertNull:[archived attribute:_CPAttachmentView atIndex:0 effectiveRange:nil]];
|
|
|
|
// Inserting the text gives the attachment its view again.
|
|
[textView setString:@""];
|
|
[textView insertText:archived];
|
|
|
|
[self assert:[attachment _attachmentView] equals:[[textView textStorage] attribute:_CPAttachmentView atIndex:0 effectiveRange:nil]];
|
|
}
|
|
|
|
@end
|