Editable images into a text view

Hello,

I found nothing about it, but is there a possibility to edit an image embedded in the text of a text view?

There are some text editors (Ragtime, Word, Appleworks.) that allow to copy, paste and even resize an image into a text, considering it like a “special character”.

You can paste an image into a NSTextView, and delete it, but you can’t copy it to the clipboard like an image and paste it elsewhere (in a non-textview).

Similarly, I suppose there is no NSResizableView in Cocoa, except whose the user may program himself.

Has anybody been confronted to the same problems or has implemented this?

Regards,

I’ve seen some discussion of this on the web, but it doesn’t look like it would be easy to do – I think there might be quite a bit of subclassing that you would need to do to make something behave like Pages, where you can drag an image around, resize it, have text wrap around it etc. What functionality do you really need? Positioning? Resizing? Some individual attributes might be possible using other methods, like multiple text containers for instance, that might give you some of what you want.

Ric

Hi Ric,

I don’t need something as elaborated as Pages. It could be nice if a double click on an image could fire a handler asking for the image percentage for example.

I suppose that a “double action” method may be attributed to an ImageWell (not the image itself) ? For the text view it may be more complicated.

The problem is: I want my files to stay reasonable in size, an the user may drag a big image to an imagewell who is only 48x48 in size (and will stay at this size). So I thought a double click may allow the user to permanently reduce the image definition at 48x48 to save memory.

For textviews, the problem is that the image cannot be copied and pasted elsewhere, nor resized. It could be nice to size them by double-clicking, which brings a dialog asking for the new percentage (proportional reducing).

Regards,

Do you want your users to be able to drag images into both an image view and a text view? And, if so, are these events separate, or is the image in the image view the same one that you want to put in the text view?

Actually, you can cut, copy and paste an image in a text view to other places in the text view or between text views.

Ric

In fact, the frameworks do it for me :slight_smile:

Yes, and you can drag an image from an imageview to a textview, but you can’t to the opposite. Once an image is embedded into text, it must be stored into the text data beyond my control.

When TextEdit saves its file, it makes a file wrapper with one file for the text, and one for the TIFF image. That means that it’s able to extract it from text and make a separate image. But the default behavior of NSText does not allow to do so – it’s like you’re copying and pasting a text character: the pasteboard indicates RTF, not TIFF.

TextEdit’s source code is available in the Examples folder of the developer tools . :wink:

Bernard,

I think you are mis-interpreting my questions. I wasn’t asking what can be done, but how you want your users to interact with the app – do you want them to drag an image directly into the text view (and then have it resized if possible), or will they be putting the image into an image view, and then have that show up (either automatically or by their action) in the text view? Or, do you want them to be able to drag to both places, directly from an image in their file system?

Thank you, I didn’t know. Now that I know, I don’t know more: this code is so compact I can only guess what it’s doing. And this syntax carnaval-like coloring makes it even less readable. :confused:

Ric,

Sorry for the misunderstanding–

That is already possible: I defined my image views as editable in IB, and my textview rich-text with image edition.

When the file is dragged into the image or the text view, it behaves differently: In an image view, the contents is a NSImage – I suppose I could resize it to a 48x48 thumbnail automatically or optionally.

When the file is dragged into text, I loose control over it and it is considered as part of the text. The user should be able to double-click on it and fix its size with a dialog – and this is not possible.

It’s all in the frameworks for you. Have a look at NSText’s writeRTFDToFile:atomically:, readRTFDFromFile: and RTFDFromRange:. They handle it all.

Hello Shane,

That’s my problem, not my solution. :slight_smile:

Bernard,

If subclassing your text view doesn’t screw things up, this method should work to resize a dragged image on the fly. I don’t know if there’s any way to interrupt it in the middle so you can set the size you want the image to be – this just sets it to 48x48 with a compression factor of 1. To try it out, just add a new applescript class to your project, paste in this code and change the class of your text view to your new class. I do some minimal error checking here to make sure the dragged file is a jpeg.

script MyTV
	property parent : class "NSTextView"
	property flag : 0
	
	on draggingEntered_(sender)
		set thePicURLString to sender's draggingPasteboard's pasteboardItems()'s lastObject()'s stringForType_("public.file-url")
		set thePicURL to current application's NSURL's URLWithString_(thePicURLString)
		if (thePicURL's pathExtension() as string) is in {"jpg", "JPG", "jpeg", "JPEG"} then set flag to 1
		set theImage to current application's NSImage's alloc()'s initWithContentsOfURL_(thePicURL)
		set newImage to current application's NSImage's alloc()'s initWithSize_({48, 48})
		set dict to current application's NSDictionary's dictionaryWithObject_forKey_(1.0, current application's NSImageCompressionFactor)
		set newRect to {{0, 0}, {48, 48}}
		set oldRect to {{0, 0}, {0, 0}}
		newImage's lockFocus()
		theImage's drawInRect_fromRect_operation_fraction_(newRect, oldRect, current application's NSCompositeCopy, 1)
		set bm to current application's NSBitmapImageRep's alloc()'s initWithFocusedViewRect_(newRect)
		set theData to bm's representationUsingType_properties_(current application's NSJPEGFileType, dict)
		newImage's unlockFocus()
		set smallImage to current application's NSImage's alloc()'s initWithData_(theData)
		sender's draggingPasteboard's clearContents()
		sender's draggingPasteboard's writeObjects_(current application's NSArray's arrayWithObject_(smallImage))
		continue draggingEntered_(sender)
	end draggingEntered_
	
	on prepareForDragOperation_(sender)
		if flag as integer is 1 then
			set flag to 0
			return 1
		else
			log "That's not an jpeg file"
			return 0
		end if
	end prepareForDragOperation_
	
end script

Ric

Ric,

Thank you for your solution.

I implemented a working and simpler handler, called at the end of drop operation, without changing my app’s class:

on imageChanged_(sender)
    set theImage to sender's image()
    set iSize to theImage's |size|()
    if iSize's height > iSize's width then
        set newHeight to 48
        set newWidth to iSize's width/iSize's height*48
    else
        set newWidth to 48
        set newHeight to iSize's height/iSize's width*48
    end if
    set newImage to current application's NSImage's alloc()'s initWithSize_({newWidth, newHeight})
    set dict to current application's NSDictionary's dictionaryWithObject_forKey_(1.0, current application's NSImageCompressionFactor)
    set newRect to {{0, 0}, {newWidth, newHeight}}
    set oldRect to {{0, 0}, {0, 0}}
    newImage's lockFocus()
    theImage's drawInRect_fromRect_operation_fraction_(newRect, oldRect, current application's NSCompositeCopy, 1)
    set bm to current application's NSBitmapImageRep's alloc()'s initWithFocusedViewRect_(newRect)
    set theData to bm's representationUsingType_properties_(current application's NSPNGFileType, dict)
    newImage's unlockFocus()
    set (first item of gCardController's selectedObjects())'s cIcon to current application's NSImage's alloc()'s initWithData_(theData)
end

This is fine and reduces the image to icon size, where saving data size is most critical. I also changed the bitmap representation to NSPNGFileType, so transparency is preserved.

Finally, to keep the proportions of the original image (which is rarely square) a did a little calculation on the width and height. Note there is strictly no type coercion needed :slight_smile:
Thank you!

Hi Bernard,

Is the sender in this method an image view? I made a similar method for image views, but the method I posted above is for text view’s. It seemed easier to change the image before dropping it in that case. However, I’m also looking at a method that would allow you to edit the size of an image that’s already been dropped into a text view (though not with drag handles – I don’t think I want to get that involved).

The method I made for image views was quite similar to yours, but it seems to me that it could be even simpler – if the image view is set to scale the image, the image view has already done the redrawing that you do with drawInRect:fromRect:operation:fraction:, I just haven’t figured out how to access it. Although, if I can do it that way, I don’t know if you could change the compression factor – it might not be useful for you, I just would like to figure out why I can’t make it work.

Ric

I did that once in Pascal for a drawing program – and made it work after hours! I finally created a CResizablePane which was a sort of “container view” for texts, images, controls. and the dessert was that handles were drawn “outside” the view (inside in fact, but I had to shrink the inner contents), so there was a lot of coordinates conversions. I don’t want you to try this, but I’m sure a RDResizableView could be of highest interest for many programmers around here!

What could be more easy: detect a double-click into text, check if an image is embedded at mouse position, and then present a dialog asking for percentage (or absolute size) of the image. Then resize this image if OK is returned.

When I say “easy”. I don’t even guess where to start :confused:

Regards

I am trying to extract the image data from a text view, resize it and then put the resized image back in the text view. I can’t seem to get any of the RTFD methods to work. The first line in the method below does get the image data, but the RTFD method doesn’t work. The data returned by the 2 different methods is different, but they have some of the same data in them and they are about the same length. Here is the code:

on textView_clickedOnCell_inRect_atIndex_(tv, clickedCell, rect, indx)
		set picData to clickedCell's |attachment|()'s fileWrapper()'s regularFileContents() --This data can be used to create an image
		log picData
		
		set picData2 to tv's RTFDFromRange_({indx, 1}) -- This data won't create a new image (the image is null)
		log picData2
		
		tv's replaceCharactersInRange_withString_({indx, 1}, "")
		set picImage to current application's NSImage's alloc()'s initWithData_(picData) -- picData2 gives null here
		set newImage to current application's NSImage's alloc()'s initWithSize_({48, 48})
		set dict to current application's NSDictionary's dictionaryWithObject_forKey_(1.0, current application's NSImageCompressionFactor)
		set newRect to {{0, 0}, {48, 48}}
		set oldRect to {{0, 0}, {0, 0}}
		newImage's lockFocus()
		picImage's drawInRect_fromRect_operation_fraction_(newRect, oldRect, current application's NSCompositeCopy, 1)
		set bm to current application's NSBitmapImageRep's alloc()'s initWithFocusedViewRect_(newRect)
		set theData to bm's representationUsingType_properties_(current application's NSJPEGFileType, dict)
		newImage's unlockFocus()
		
		tv's replaceCharactersInRange_withRTFD_({indx, 0}, theData) --This doesn't work to put the image into the text view
		
		set newImage to current application's NSImage's alloc()'s initWithData_(theData) -- This does work to create a new image
		iv's setImage_(newImage) -- newImage appears in the image view, iv, and is properly resized
	end textView_clickedOnCell_inRect_atIndex_

Any ideas why this isn’t working?

Ric

Maybe this:

After Edit: No, finally not: the doc always returns to replaceCharactersInRange:withRTFD:.

I have tried your code: effectively, the image is correctly created, so theData should be valid as rtfdata. I’ll keep trying.

The other way I’ve tried is to create a new attachment and insert it with:

set newFileWrapper to current application's NSFileWrapper's alloc()'s initRegularFileWithContents_(theData)
		set newAttachment to current application's NSTextAttachment's alloc()'s initWithFileWrapper_(newFileWrapper)
		log newAttachment
		set attrib to current application's NSAttributedString's attributedStringWithAttachment_(newAttachment)
		log attrib
		tv's textStorage()'s replaceCharactersInRange_withAttributedString_({indx, 1}, attrib)

But, when I log newAttachment, it’s null. I do get a generic file icon put in the text view, but I can’t figure out what’s wrong with my newAttachment creation (I’ve tested the file wrapper by using its regularFileContents method to create an NSImage and put it in an image view – that works fine).

Ric

After edit: I found out that the “null” is because it had no preferred filename, so setting that gets rid of the null, but I still get a generic icon rather than my image. sigh.

At this stage, all I can suggest is that the rtfdata in somehow invalid. But I get no error when theData is supposed to be inserted – maybe it’s a bug?

I really can’t figure this one out. Even this doesn’t work, where I get the data from the file wrapper, then use that same data to initialize a new wrapper, and use that to create a new attachment.

on textView_clickedOnCell_inRect_atIndex_(tv, clickedCell, rect, indx)
		set picData to clickedCell's |attachment|()'s fileWrapper()'s regularFileContents() --This data can be used to create an image
		set newFileWrapper to current application's NSFileWrapper's alloc()'s initRegularFileWithContents_(picData)
		newFileWrapper's setPreferredFilename_("smallImage")
		set newAttachment to current application's NSTextAttachment's alloc()'s initWithFileWrapper_(newFileWrapper)
		set attrib to current application's NSAttributedString's attributedStringWithAttachment_(newAttachment)
		tv's textStorage()'s replaceCharactersInRange_withAttributedString_({indx, 1}, attrib)
		set newImage to current application's NSImage's alloc()'s initWithData_(newFileWrapper's regularFileContents()) -- This does work to create a new image
		iv's setImage_(newImage) -- newImage appears in the image view, iv, and is properly resized
	end textView_clickedOnCell_inRect_atIndex_

I get the same thing I got with the code I posted above – the image is replaced with an icon rather than my image. I looked at picData and newFileWrapper’s regularFileContents(), and as far as I can tell, they are identical.

Ric