Retired Document
Important: This sample code may not represent best practices for current development. The project may use deprecated symbols and illustrate technologies and techniques that are no longer recommended.
Source/CSkWindow.c
/* |
File: CSkWindow.c |
Contains: Implementation of CSkWindow creation and event handling. |
Disclaimer: IMPORTANT: This Apple software is supplied to you by Apple Computer, Inc. |
("Apple") in consideration of your agreement to the following terms, and your |
use, installation, modification or redistribution of this Apple software |
constitutes acceptance of these terms. If you do not agree with these terms, |
please do not use, install, modify or redistribute this Apple software. |
In consideration of your agreement to abide by the following terms, and subject |
to these terms, Apple grants you a personal, non-exclusive license, under AppleÕs |
copyrights in this original Apple software (the "Apple Software"), to use, |
reproduce, modify and redistribute the Apple Software, with or without |
modifications, in source and/or binary forms; provided that if you redistribute |
the Apple Software in its entirety and without modifications, you must retain |
this notice and the following text and disclaimers in all such redistributions of |
the Apple Software. Neither the name, trademarks, service marks or logos of |
Apple Computer, Inc. may be used to endorse or promote products derived from the |
Apple Software without specific prior written permission from Apple. Except as |
expressly stated in this notice, no other rights or licenses, express or implied, |
are granted by Apple herein, including but not limited to any patent rights that |
may be infringed by your derivative works or by other works in which the Apple |
Software may be incorporated. |
The Apple Software is provided by Apple on an "AS IS" basis. APPLE MAKES NO |
WARRANTIES, EXPRESS OR IMPLIED, INCLUDING WITHOUT LIMITATION THE IMPLIED |
WARRANTIES OF NON-INFRINGEMENT, MERCHANTABILITY AND FITNESS FOR A PARTICULAR |
PURPOSE, REGARDING THE APPLE SOFTWARE OR ITS USE AND OPERATION ALONE OR IN |
COMBINATION WITH YOUR PRODUCTS. |
IN NO EVENT SHALL APPLE BE LIABLE FOR ANY SPECIAL, INDIRECT, INCIDENTAL OR |
CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE |
GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) |
ARISING IN ANY WAY OUT OF THE USE, REPRODUCTION, MODIFICATION AND/OR DISTRIBUTION |
OF THE APPLE SOFTWARE, HOWEVER CAUSED AND WHETHER UNDER THEORY OF CONTRACT, TORT |
(INCLUDING NEGLIGENCE), STRICT LIABILITY OR OTHERWISE, EVEN IF APPLE HAS BEEN |
ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. |
Copyright © 2004 Apple Computer, Inc., All Rights Reserved |
*/ |
#include "CSkWindow.h" |
#include "CSkDocStorage.h" |
#include "CSkConstants.h" |
#include "CSkUtils.h" |
#include "CSkObjects.h" |
#include "CSkToolPalette.h" |
#include "CSkPrinting.h" |
#include "NavServicesHandling.h" |
/* ------------------- Coordinate Juggling --------------------- |
Let's say we want to present the drawing area (a "document page") on some background |
(as in AppleWorks, Word etc.). The window has size wWidth x wHeight, and scrolls around on |
the background, provided 2 x hMargin + docWidth is bigger than wWidth, or wHeight is bigger |
than 2 * vMargin + docHeight. If it scrolls around, scrollPosition (relative to the |
background origin, which is at (-kTopMargin, -kLeftMargin) relativ to the document topLeft) |
describes the topLeft of the window. The horizontal scrolling range is then |
2 x hMargin + docWidth - wWidth, the vertical range is 2 x vMargin + docHeight - wHeight. |
However, we also want the support a scaling factor for the document content. So, all dimensions |
above except wWidth and wHeight need to be scaled by the current scaling factor! |
Our drawing objects are described in documentpage-relativ coordinates, with the origin of |
the CGContext at the bottom-left of the page. |
To draw an object into the windowContext (which has the origin reset via SyncCGContextOriginWithPort, |
included in QDBeginCGContext), we need to do the following: |
- CGContextTranslateCTM(scaledCGDocOrigin) // bottom-left of scaled document, relativ to bottom-left of window |
- CGContextScaleCTM(curScaling) |
where scaledCGDocOrigin is computed as follows: |
x = docTopLeft.h * scale - scrollPosition.h |
y = (docTopLeft.v + docSize) * scale - (scrollPosition.v + wHeight) |
To convert mouse-tracking (hit-testing) window-topleft-relative QD coordinates into cgDocument coordinates, |
we need to |
- add scrollPosition (now the QD coordinates are relative to our global scroll area) |
- subtract the (scaled) docOrigin point (still in QD coordintes, now docOrigin-relative). |
- scale by 1/(scaling factor) |
- convert to cg coordinates: (v, h) -> (h, docHeight - v), where docHeight is unscaled. |
-------------------------------------------------------------- */ |
//---------------------------------------------------------------------------------------------------------------- |
// Adjust horizontal and vertical scroll bar maximums; set control minimum and maximum of the "Scale" popup. |
// Needs to get called at window creation, window resizing and scale change. |
static void SetupControls(WindowRef window, DocStorage* docStP) |
{ |
ControlID cntlID = { kControlSignatureMainWindow, kScrollVerticalID }; |
ControlRef control; |
SInt16 cntlMax; |
GetControlByID(window, &cntlID, &control); |
cntlMax = docStP->scale * (docStP->docSize.v + 2 * docStP->docTopLeft.v) - docStP->windowRect.bottom; |
if (cntlMax < 0) |
cntlMax = 0; |
SetControlMaximum(control, cntlMax); |
cntlID.id = kScrollHorizontalID; |
GetControlByID(window, &cntlID, &control); |
cntlMax = docStP->scale * (docStP->docSize.h + 2 * docStP->docTopLeft.h) - docStP->windowRect.right; |
if (cntlMax < 0) // is this built into SetControlMaximum when 0 = cntlMin ? |
cntlMax = 0; |
SetControlMaximum(control, cntlMax); |
cntlID.id = kScalePopupID; |
GetControlByID(window, &cntlID, &control); |
SetControlMinimum(control, 0); |
SetControlMaximum(control, kMaxScaleMenuItem); |
} |
//---------------------------------------------------------------------------------------------------------------- |
// Given a QDPoint in local window coordinates, return a CGPoint in document (drawObject) coordinates. |
static CGPoint QDCoordsToCSkDocCGCoords(Point inPt, DocStorage* docStP) |
{ |
float scale = docStP->scale; |
Point docTopLeft = docStP->docTopLeft; |
float x, y; |
AddPt(docStP->scrollPosition, &inPt); // now on global scaled document background |
x = (float)inPt.h / scale; |
y = (float)inPt.v / scale; // now unscaled |
x -= docTopLeft.h; |
y -= docTopLeft.v; // document-topLeft relative |
return CGPointMake(x, docStP->docSize.v - y); // document-bottomLeft relative |
} |
//---------------------------------------------------------------------------------------------------------------- |
// If either the window size, or the scroll position, or the scale changes, we need to recompute the displayCTM. |
// The docTopLeft would only change in the case of a multi-page document. |
static void RecalculateDisplayCTM(DocStorage* docStP) |
{ |
int wHeight = docStP->windowRect.bottom - docStP->windowRect.top; |
CGPoint scaledCGDocOrigin; // relative to the origin of the window context |
scaledCGDocOrigin.x = docStP->docTopLeft.h * docStP->scale - docStP->scrollPosition.h; |
scaledCGDocOrigin.y = (docStP->scrollPosition.v + wHeight) - (docStP->docTopLeft.v + docStP->docSize.v) * docStP->scale; |
// Create the CGAffineTransform docStP->displayCTM, which includes a translation by scaledCGDocOrigin |
// and a scaling in x and y by docStP->scale in x and y. Each time we draw our content into a "fresh" CGContext, |
// we need to concatenate its CTM with this "displayCTM". |
docStP->displayCTM = CGAffineTransformMake(docStP->scale, 0, 0, docStP->scale, scaledCGDocOrigin.x, scaledCGDocOrigin.y); |
} |
//------------------------------------------------------------------------------------------------------------------ |
// For convenience, we create this "single pixel CGBitmapContext" for hit-testing right away at creation of a new |
// document. Given the purpose of CarbonSketch, there is not much use of it without doing hit-testing, sooner or later. |
// Note that we use the genericColorSpace for consistency, even though it doesn't make much technical sense, here. |
static CGContextRef CreateSinglePixelBitmapContext() |
{ |
const int width = 1; |
const int height = 1; |
const int size = 4 * width * height; |
void* data = calloc(size, 1); |
CGColorSpaceRef genericColorSpace = GetGenericRGBColorSpace(); |
CGContextRef bmCtx = CGBitmapContextCreate(data, width, height, 8, 4 * width, genericColorSpace, kCGImageAlphaPremultipliedFirst); |
// The colorspace story here is mainly for consistency - genericColorSpace throughout! |
CGContextSetFillColorSpace(bmCtx, genericColorSpace); |
CGContextSetStrokeColorSpace(bmCtx, genericColorSpace); |
// After that, all colors passed to CGContextSetFillColor/CGContextSetStrokeColor are arrays |
// float[4] = { r, g, b, a }. In this app, we use the CGrgba type instead, for clarity. |
return bmCtx; |
} |
//-------------------------------------------------------------------------------------- |
// Allocation and initialization of document storage. |
// Note that we rely on NewPtrClear setting everything else to NULL. |
static DocStorage* CreateDocumentStorage(WindowRef window, WindowRef toolPalette) |
{ |
// default document position in window |
const int kLeftMargin = 18; |
const int kTopMargin = 18; |
const int kDocWidth = 576; |
const int kDocHeight = 720; |
const int kGridWidth = 18; |
const Point nullPt = { 0, 0 }; |
DocStorage* docStP = (DocStorage*) NewPtrClear( sizeof(DocStorage) ); |
GetWindowPortBounds(window, &docStP->windowRect); |
docStP->ownerWindow = window; |
docStP->bmCtx = CreateSinglePixelBitmapContext(); |
docStP->docTopLeft.v = kTopMargin; |
docStP->docTopLeft.h = kLeftMargin; |
docStP->docSize.v = kDocHeight; |
docStP->docSize.h = kDocWidth; |
docStP->gridWidth = kGridWidth; |
docStP->scale = 1.0; |
docStP->scrollPosition = nullPt; |
docStP->toolPalette = toolPalette; |
docStP->dupOffset = CGPointMake(9.0, 9.0); |
docStP->pageFormat = kPMNoPageFormat; |
docStP->printSettings = kPMNoPrintSettings; |
RecalculateDisplayCTM(docStP); |
return docStP; |
} |
//-------------------------------------------------------------------------------------- |
// Make all the necessary "..Release" calls, and deallocate any nested storage |
void ReleaseDocumentStorage(DocStorage* docStP) |
{ |
ReleaseDrawObjList(&docStP->objList); |
if (docStP->bmCtx != NULL) |
CGContextRelease(docStP->bmCtx); |
// Don't dispose of toolPalette! |
if (docStP->pdfData != NULL) |
CFRelease(docStP->pdfData); |
if (docStP->pageFormat != kPMNoPageFormat) |
(void)PMRelease(docStP->pageFormat); |
if (docStP->printSettings != kPMNoPrintSettings) |
(void)PMRelease(docStP->printSettings); |
if (docStP->flattenedPageFormat != NULL) |
DisposeHandle(docStP->flattenedPageFormat); |
} |
//-------------------------------------------------------------------------------------- |
// If the srcRect doesn't fit into dstRect, scale the CTM such that it fits, |
// and translate it such that the srcRect topLeft gets mapped into the topleft of the dstRect. |
// We can assume that srcRect and dstRect have their origin at (0, 0). |
// For clarity, only pass in the srcRect.size and the dstRect.size. |
// This is only used to draw an external PDF into the document page. |
static void SetupTransform(CGContextRef ctx, CGSize srcSize, CGSize dstSize) |
{ |
float scaleX = dstSize.width / srcSize.width; |
float scaleY = dstSize.height / srcSize.height; |
float scale = (scaleX < scaleY ? scaleX : scaleY); |
// Do nothing if srcRect fits entirely into dstRect |
if (scale < 1.0) |
{ |
CGContextScaleCTM(ctx, scale, scale); |
} |
// Always put the drawing into the destination's topLeft. Assume srcRect and dstRect have origin (0, 0) |
CGContextTranslateCTM(ctx, dstSize.width - scale * srcSize.width, |
dstSize.height - scale * srcSize.height); |
} |
//-------------------------------------------------------------------------------------- |
// Draw page 1 of pdfData into the dstRect, scaling it (if necessary) to fit. |
static void DrawPDFData(CGContextRef ctx, CFDataRef pdfData, CGRect dstRect) |
{ |
CGDataProviderRef provider; |
CGPDFDocumentRef document; |
CGPDFPageRef page; |
CGRect pageSize; |
provider = CGDataProviderCreateWithData(NULL, CFDataGetBytePtr(pdfData), CFDataGetLength(pdfData), NULL); |
document = CGPDFDocumentCreateWithProvider(provider); |
page = CGPDFDocumentGetPage(document, 1); // always page 1 only |
pageSize = CGPDFPageGetBoxRect(page, kCGPDFMediaBox); |
CGContextSaveGState(ctx); // save GState because we are going to change the CTM |
SetupTransform(ctx, pageSize.size, dstRect.size); // Scale into dstRect, if the page is too big |
CGContextDrawPDFPage(ctx, page); // Just what it says it does ... |
CGContextRestoreGState(ctx); // restore to whatever the previous CTM was |
CFRelease(document); // we created it |
CFRelease(provider); // we created it |
} |
//-------------------------------------------------------------------------------------- |
// Clip the drawing to the WindowPortRect minus the area covered by our controls. |
// Create a path that contains all the rectangles, and use CGContextEOClip (even-odd) |
// rather than CGContextClip (winding number), so the contained rectangles get |
// substracted from the big cgWinRect rectangle for the clip path. |
// We are working in default CGContext user coordinates; so we need the window height |
// to convert the local QD coordinates of the ControlBounds rectangles to CG coordinates. |
static void ClipToContentArea(CGContextRef ctx, WindowRef window, CGRect windowPortRect, int wHeight) |
{ |
ControlID cntlID = { kControlSignatureMainWindow, kScrollHorizontalID }; |
ControlRef control; |
Rect r; |
CGContextBeginPath(ctx); // clear the context's current path |
CGContextAddRect(ctx, windowPortRect); // add rectangular windowPortRect path |
// Subtract areas of controls. Because we will use the "even-odd" rule for the clip, |
// it's enough to just add the rectangles to be subtracted. Otherwise, we would have |
// to add the contour of the rectangles one side after the other in counter-clockwise order. |
// cntlID.id = kScrollHorizontalID; -- set above |
GetControlByID(window, &cntlID, &control); |
GetControlBounds(control, &r); |
CGContextAddRect(ctx, CGRectMake(r.left, wHeight - r.bottom, r.right - r.left, r.bottom - r.top)); |
cntlID.id = kScrollVerticalID; |
GetControlByID(window, &cntlID, &control); |
GetControlBounds(control, &r); |
CGContextAddRect(ctx, CGRectMake(r.left, wHeight - r.bottom, r.right - r.left, r.bottom - r.top)); |
cntlID.id = kScalePopupID; |
GetControlByID(window, &cntlID, &control); |
GetControlBounds(control, &r); |
CGContextAddRect(ctx, CGRectMake(r.left, wHeight - r.bottom, r.right - r.left, r.bottom - r.top)); |
CGContextEOClip(ctx); // Applies the "even-odd" rule for using the above path (a set of rectangles) as clip |
} |
//-------------------------------------------------------------------------------------- |
static void DrawDocumentBackgroundGrid(CGContextRef ctx, Point docSize, float gridWidth) |
// Draw document background grid |
{ |
const CGrgba gridColor = { 0.8, 0.9, 0.8, 1.0 }; // used as float[4] in CGContextSetStrokeColor |
float t; |
CGContextSetStrokeColor(ctx, (float*)&gridColor); |
CGContextSetLineWidth(ctx, 0.5); |
t = 0.5; |
while (t < docSize.h) |
{ |
CGContextMoveToPoint(ctx, t, 0.5); |
CGContextAddLineToPoint(ctx, t, docSize.v); |
t += gridWidth; |
} |
t = 0.5; |
while (t < docSize.v) |
{ |
CGContextMoveToPoint(ctx, 0.5, t); |
CGContextAddLineToPoint(ctx, docSize.h, t); |
t += gridWidth; |
} |
CGContextStrokePath(ctx); |
} |
//-------------------------------------------------------------------------------------- |
static void DrawWindowContent(WindowRef window) |
{ |
DocStorage* docStP = (DocStorage*)GetWRefCon( window ); |
const CGrgba white = { 1.0, 1.0, 1.0, 1.0 }; // used as float[4] in CGContextSetFillColor |
const CGrgba docBackgroundColor = { 0.5, 0.5, 0.5, 1.0 }; |
CGColorSpaceRef genericColorSpace = GetGenericRGBColorSpace(); |
int wHeight = docStP->windowRect.bottom - docStP->windowRect.top; |
CGRect cgWinRect = CGRectMake(0, 0, docStP->windowRect.right - docStP->windowRect.left, wHeight); |
CGContextRef ctx; |
QDBeginCGContext(GetWindowPort(window), &ctx); |
// This CGContextSaveGState/CGContextRestoreGState bracket here is pointless, |
// because we have this ctx only inside of QDBeginCGContext/QDEndCGContext, anyway. |
// But if we decide in the future to hang on to this ctx (in DocStorage), rather |
// than calling QDBeginCGContext/QDEndCGContext all the time, then we will need it. |
CGContextSaveGState(ctx); // save GState before we clip |
// Clip all Quartz drawing to the window content area (e.g. don't overwrite scrollbars). |
// Pass in the cgWinRect in the default CGContext user coordinates, and pass in |
// the window height to convert local controlBounds rectangles to CG coordintes. |
ClipToContentArea(ctx, window, cgWinRect, wHeight); |
// ensure that we are drawing in the correct color space |
CGContextSetFillColorSpace(ctx, genericColorSpace); |
CGContextSetStrokeColorSpace(ctx, genericColorSpace); |
CGContextSetFillColor(ctx, (float*)&docBackgroundColor); |
CGContextFillRect(ctx, cgWinRect); // paint background around document |
CGContextConcatCTM(ctx, docStP->displayCTM); // concatenate with our own transformation matrix |
// (see RecalculateDisplayCTM() ) |
// also clip to document page, now |
CGContextClipToRect(ctx, CGRectMake(0, 0, docStP->docSize.h, docStP->docSize.v)); |
// From now on, all drawing is to be done in "document page coordinates" |
CGContextSetFillColor(ctx, (float*)&white); |
CGContextFillRect(ctx, CGRectMake(0, 0, docStP->docSize.h, docStP->docSize.v )); // paint white background |
// If we have a background pdf, draw it |
if (docStP->pdfData != NULL) |
{ |
DrawPDFData(ctx, docStP->pdfData, CGRectMake(0, 0, docStP->docSize.h, docStP->docSize.v)); |
} |
else |
{ |
DrawDocumentBackgroundGrid(ctx, docStP->docSize, docStP->gridWidth); |
} |
RenderDrawObjList(ctx, &docStP->objList, true); |
CGContextRestoreGState(ctx); // Reset the GState to what it was on entry |
// The rule is: Always call CGContextSynchronize (or CGContextFlush) before QDEndCGContext. |
CGContextSynchronize(ctx); |
QDEndCGContext(GetWindowPort(window), &ctx); |
} // DrawWindowContent |
//-------------------------------------------------------------------------------------- |
// Only go into "dragSelection" mode if points a and b are at least "so many" pixels apart. |
// (This is questionable ...) |
static Boolean BigEnoughForDragSelection(Point a, Point b) |
{ |
int dv = b.v - a.v; |
int dh = b.h - a.h; |
if (dv < 0) dv = -dv; |
if (dh < 0) dh = -dh; |
return (dv > 1) && (dh > 1); |
} |
//-------------------------------------------------------------------------------------- |
// We support "shiftKeyDown" to constrain shape creation to |
// a) only horizontal, vertical or diagonal lines |
// b) only square rectangles and ovals. |
static void AdjustEndPoint(Point* ioPt, Point beginPt, int selectedShape, UInt32 modifiers) |
{ |
Boolean shiftKeyDown = ((modifiers & shiftKey) != 0); |
if ( shiftKeyDown ) |
{ |
int dh = ioPt->h - beginPt.h; |
int dv = ioPt->v - beginPt.v; |
int sign = (dh * dv >= 0 ? 1 : -1); |
if (selectedShape == kLineShape) // allow horizontal, vertical and diagonal only |
{ |
int absDH = (dh > 0 ? dh : -dh); |
int absDV = (dv > 0 ? dv : -dv); |
if (absDH < absDV/4) |
{ |
ioPt->h = beginPt.h; |
} |
else if (absDV < absDH/4) |
{ |
ioPt->v = beginPt.v; |
} |
else // diagonal |
{ |
ioPt->v = beginPt.v + sign * dh; |
ioPt->h = beginPt.h + dh; |
} |
} |
else // make it square |
{ |
ioPt->v = beginPt.v + dh; |
ioPt->h = beginPt.h + dh; |
} |
} |
} |
//----------------------------------------------------------------------------------- |
// DoMouseArrowTracking (for resizing or moving selected objects) and |
// DoMouseToolTracking (for creating new objects) are rather messy. Will try to |
// find a better approach later (when somebody gives me good suggestions ...). |
static void DoMouseArrowTracking( WindowRef window, CGContextRef overlayContext, |
Point beginPt, UInt32 evtModifiers ) |
{ |
const float dashLengths[2] = { 3, 3 }; // for drawing "marching ants" |
static float phase = 1.0; // (make them march) |
enum { |
dragSelect = 1, |
dragMove = 2, |
dragResize = 3 |
}; |
int dragState = dragSelect; |
OSStatus err; |
MouseTrackingResult trackingResult; |
Point endPt; |
CGPoint cgStartPt; |
CGPoint windowContextPt; |
DocStorage* docStP = (DocStorage*)GetWRefCon( window ); |
CGRect docRect = CGRectMake(0, 0, docStP->docSize.h, docStP->docSize.v); |
CSkObjectPtr tempObj = NULL; |
CSkObjectPtr hitObj = NULL; |
Boolean beenDragging = false; |
Boolean isStillDown = true; |
Boolean shiftKeyDown = ((evtModifiers & shiftKey) != 0); |
UInt32 grabNum; |
cgStartPt = QDCoordsToCSkDocCGCoords(beginPt, docStP); |
// First, check if we are down on a selected object. If so, switch to dragMove or dragResize. |
// If not, stay in noDrag, and go to dragSelect if dragging continues. |
windowContextPt = CGPointMake( beginPt.h, (docStP->windowRect.bottom - docStP->windowRect.top) - beginPt.v ); |
hitObj = DrawObjListHitTesting(&docStP->objList, docStP->bmCtx, docStP->displayCTM, windowContextPt, cgStartPt, &grabNum); |
if ( (hitObj != NULL) && IsDrawObjSelected(hitObj) ) |
{ |
dragState = (grabNum > 0 ? dragResize : dragMove); |
if (dragState == dragResize) |
{ |
tempObj = CopyDrawObject(hitObj); |
MakeDrawObjTransparent(tempObj, 0.4); |
SetContextStateForDrawObject(overlayContext, tempObj); |
} |
} |
while ( isStillDown ) |
{ |
UInt32 modifiers; |
CGPoint cgEndPt; |
err = TrackMouseLocationWithOptions( GetWindowPort(window), 0, 0, &endPt, &modifiers, &trackingResult ); |
windowContextPt = CGPointMake( endPt.h, (docStP->windowRect.bottom - docStP->windowRect.top) - endPt.v ); |
cgEndPt = QDCoordsToCSkDocCGCoords(endPt, docStP); |
switch ( trackingResult ) |
{ |
case kMouseTrackingMouseDragged: |
beenDragging = true; |
CGContextClearRect( overlayContext, docRect ); // "Erase" the overlay window (doc coordinates!) |
// Either draw selection rectangle with marching ants, or move the selected objects, or resize the selected object |
if (dragState == dragSelect) |
{ |
if ( BigEnoughForDragSelection(beginPt, endPt) ) |
{ |
CGRect selectionRect = CGRectMake(cgStartPt.x, cgStartPt.y, cgEndPt.x - cgStartPt.x, cgEndPt.y - cgStartPt.y); |
CGContextSetLineDash(overlayContext, phase, dashLengths, 2); |
CGContextStrokeRect(overlayContext, selectionRect); |
phase += 1.0; // will use a timer to keep the ants marching, later ... |
if (!shiftKeyDown) |
CSkObjListSetSelectState(&docStP->objList, false); |
CSkObjListSelectWithinRect(&docStP->objList, selectionRect); |
} |
else |
{ |
hitObj = DrawObjListHitTesting(&docStP->objList, docStP->bmCtx, docStP->displayCTM, windowContextPt, cgEndPt, NULL); |
if (hitObj != NULL) |
{ |
Boolean wasSelected = IsDrawObjSelected(hitObj); |
if (shiftKeyDown) // extend selection, or toggle selected state |
{ |
SetDrawObjSelectState(hitObj, !wasSelected); |
} |
else if (!wasSelected) |
{ |
CSkObjListSetSelectState(&docStP->objList, false); |
SetDrawObjSelectState(hitObj, true); |
} |
} |
else if (!shiftKeyDown) // just deselect everything |
{ |
CSkObjListSetSelectState(&docStP->objList, false); |
} |
} |
DrawWindowContent(window); // to reflect selection state changes |
} |
else if (dragState == dragMove) |
{ |
// redraw selected objects at offset cgEndPt - cgStartPt |
RenderSelectedDrawObjs(overlayContext, &docStP->objList, cgEndPt.x - cgStartPt.x, cgEndPt.y - cgStartPt.y, 0.5); |
} |
else // if (dragState == dragResize) |
{ |
CSkShapeResize(GetCSkObjectShape(tempObj), &grabNum, cgEndPt); |
RenderCSkObject(overlayContext, tempObj, true); |
} |
CGContextSynchronize(overlayContext); // Make sure we get our drawing to the screen |
break; |
case kMouseTrackingMouseUp: |
if (!beenDragging) // there was no dragging, just a single click |
{ |
hitObj = DrawObjListHitTesting(&docStP->objList, docStP->bmCtx, docStP->displayCTM, windowContextPt, cgEndPt, NULL); |
if (shiftKeyDown == false) |
CSkObjListSetSelectState(&docStP->objList,false); |
if (hitObj != NULL) |
{ |
if (shiftKeyDown) |
SetDrawObjSelectState(hitObj, !IsDrawObjSelected(hitObj)); |
else |
SetDrawObjSelectState(hitObj, true); |
} |
} |
else |
{ |
if (dragState == dragMove) |
{ |
MoveSelectedDrawObjs(&docStP->objList, cgEndPt.x - cgStartPt.x, cgEndPt.y - cgStartPt.y); |
} |
else if (dragState == dragResize) // Move new shape geometry from tempObj to hitObj |
{ |
memcpy( GetCSkObjectShape(hitObj), GetCSkObjectShape(tempObj), CSkShapeSize()); |
} |
} |
// fall through to isStillDown = false |
case kMouseTrackingUserCancelled: |
isStillDown = false; |
break; |
} |
} |
if ( (tempObj != NULL) ) |
{ |
ReleaseDrawObj(tempObj); |
} |
CGContextClearRect( overlayContext, docRect ); |
// Make sure we always redraw the new state of the new object list |
DrawWindowContent(window); |
SetThemeCursor( kThemeArrowCursor ); |
} // DoMouseArrowTracking |
//----------------------------------------------------------------------------------- |
static void DoMouseToolTracking( WindowRef window, CGContextRef overlayContext, |
int shapeSelect, Point beginPt ) |
{ |
const CGrgba trStrokeColor = { 0.5, 0.5, 0.5, 0.4 }; // gray with alpha = 0.4 |
const CGrgba trFillColor = { 0.9, 0.9, 0.9, 0.3 }; // ltgray with alpha = 0.3 |
OSStatus err; |
MouseTrackingResult trackingResult; |
Point endPt; |
CGPoint cgStartPt; |
CGPoint cgEndPt; |
CGRect objBounds = CGRectMake(0, 0, 0, 0); |
DocStorage* docStP = (DocStorage*)GetWRefCon( window ); |
CGRect docRect = CGRectMake(0, 0, docStP->docSize.h, docStP->docSize.v); |
CSkObjectPtr tempObj = CreateDrawObj(docStP->toolPalette, shapeSelect); |
Boolean beenDragging = false; |
Boolean isStillDown = true; |
CSkToolPaletteSetContextState(overlayContext, docStP->toolPalette); |
CGContextSetStrokeColor( overlayContext, (float*)&trStrokeColor); |
CGContextSetFillColor( overlayContext, (float*)&trFillColor); |
SetThemeCursor( kThemeCrossCursor ); |
cgStartPt = QDCoordsToCSkDocCGCoords(beginPt, docStP); |
while ( isStillDown ) |
{ |
UInt32 modifiers; |
err = TrackMouseLocationWithOptions( GetWindowPort(window), 0, 0, &endPt, &modifiers, &trackingResult ); |
if (err != noErr) // when would this occur? |
break; |
AdjustEndPoint(&endPt, beginPt, shapeSelect, modifiers); |
cgEndPt = QDCoordsToCSkDocCGCoords(endPt, docStP); |
switch ( trackingResult ) |
{ |
case kMouseTrackingMouseDragged: |
beenDragging = true; |
CGContextClearRect( overlayContext, docRect ); // "Erase" the overlay window (doc coordinates!) |
if (shapeSelect == kLineShape) |
{ |
CSkShapeSetLine(GetCSkObjectShape(tempObj), cgStartPt, cgEndPt); |
} |
else |
{ |
objBounds.origin = cgStartPt; // need to reassign each time, because CGRectStandardize may change it |
objBounds.size = CGSizeMake(cgEndPt.x - cgStartPt.x, cgEndPt.y - cgStartPt.y); |
objBounds = CGRectStandardize(objBounds); // make size components positive |
CSkShapeSetBounds(GetCSkObjectShape(tempObj), objBounds); |
} |
RenderCSkObject(overlayContext, tempObj, false); |
CGContextSynchronize(overlayContext); // Make sure we get our drawing to the screen |
break; |
case kMouseTrackingMouseUp: |
case kMouseTrackingUserCancelled: |
isStillDown = false; |
break; |
} |
} |
CGContextClearRect( overlayContext, docRect ); |
if ( beenDragging ) // we have created a new object |
{ |
SetDrawObjAttributesFromToolPalette(tempObj, docStP->toolPalette); |
CSkObjListSetSelectState(&docStP->objList, false); |
SetDrawObjSelectState(tempObj, true); |
AddDrawObjToList(&docStP->objList, tempObj); |
tempObj = NULL; // don't release it! |
} |
if ( tempObj != NULL ) |
{ |
ReleaseDrawObj(tempObj); |
} |
// Make sure we always redraw the new state of the new object list |
DrawWindowContent(window); |
SetThemeCursor( kThemeArrowCursor ); |
if (beenDragging && (shapeSelect >= kLineShape)) // set ToolPalette selection back to "Arrow" |
{ |
ControlID cntlID = { kControlSignaturePalette, kArrowTool }; |
ControlRef control; |
GetControlByID(docStP->toolPalette, &cntlID, &control); |
SendControlEventHit(control); |
} |
} // DoMouseToolTracking |
//----------------------------------------------------------------------------------------------------------------------- |
// We pass this ControlActionProc to HandleControlClick so we get live scrolling. |
static void ControlActionProc( ControlRef control, ControlPartCode partCode ) |
{ |
WindowRef window = GetControlOwner(control); |
DocStorage* docStP = (DocStorage*)GetWRefCon(window); |
SInt16 value = GetControlValue(control); |
SInt16 minVal = GetControlMinimum(control); |
SInt16 maxVal = GetControlMaximum(control); |
int pageIncr; |
int buttonIncr; |
ControlID cntlID; |
Boolean horizontal; |
GetControlID(control, &cntlID); |
horizontal = (cntlID.id == kScrollHorizontalID); |
pageIncr = (horizontal ? 3 * (docStP->windowRect.right - docStP->windowRect.left) / 4 : 3 * (docStP->windowRect.bottom - docStP->windowRect.top) / 4); |
buttonIncr = (pageIncr > 10 ? pageIncr / 10 : 1); |
switch (partCode) |
{ |
case kControlUpButtonPart: value -= buttonIncr; break; |
case kControlDownButtonPart: value += buttonIncr; break; |
case kControlPageUpPart: value -= pageIncr; break; |
case kControlPageDownPart: value += pageIncr; break; |
} |
if (value < minVal) |
value = minVal; |
else if (value > maxVal) |
value = maxVal; |
SetControlValue(control, value); |
if (horizontal) |
docStP->scrollPosition.h = value; |
else |
docStP->scrollPosition.v = value; |
RecalculateDisplayCTM(docStP); |
DrawWindowContent(window); // for live scrolling |
} |
//--------------------------------------------------------------------------------------------------------------------- |
// Create an overlay window for mouse tracking. Its "overlayWindowCtx" should behave the same as |
// the window context in terms of drawing our objects (except for stroke and fill colors). |
// Note that we do not attempt to keep the overlay window around for repeated use; instead, we |
// create and destroy it each time. This allows to avoid WindowGroups, which could be used |
// to tie the overlay window to its "parent". |
static OSStatus CreateOverlayWindow( Rect* inBounds, WindowRef* outOverlayWindow ) |
{ |
UInt32 flags = kWindowHideOnSuspendAttribute | kWindowIgnoreClicksAttribute; |
OSStatus err = CreateNewWindow( kOverlayWindowClass, flags, inBounds, outOverlayWindow ); |
require_noerr(err, CreateNewWindowFAILED); |
// SetPortWindowPort( *outOverlayWindow ); |
ShowWindow( *outOverlayWindow ); |
CreateNewWindowFAILED: |
return err; |
} |
//----------------------------------------------------------------------------------------------------------------------- |
static void DoContentClick(WindowRef window, Point where, UInt32 modifiers) |
{ |
DocStorage* docStP = (DocStorage*)GetWRefCon( window ); |
ControlPartCode partCode; |
ControlRef control = FindControlUnderMouse ( where, window, &partCode ); |
if (control != NULL) |
{ |
HandleControlClick ( control, where, 0, ControlActionProc ); |
} |
else |
{ |
// Create an overlayWindow for mousetracking feedback |
WindowRef overlayWindow; |
Rect wRect = docStP->windowRect; |
CGRect cgWinRect = CGRectMake(0, 0, wRect.right - wRect.left, wRect.bottom - wRect.top); |
OSStatus err; |
QDLocalToGlobalRect( GetWindowPort(window), &wRect ); |
err = CreateOverlayWindow(&wRect, &overlayWindow); |
if (err == noErr) |
{ |
CGContextRef overlayContext; |
CGColorSpaceRef genericColorSpace = GetGenericRGBColorSpace(); |
int selectedTool = CSkToolPaletteGetTool(docStP->toolPalette); |
QDBeginCGContext(GetWindowPort(overlayWindow), &overlayContext); |
// This is overkill (again) - we shouldn't have to care about calibrated |
// colorspaces in the overlayWindow. But what the heck ... |
CGContextSetFillColorSpace(overlayContext, genericColorSpace); |
CGContextSetStrokeColorSpace(overlayContext, genericColorSpace); |
// We do want to clip drawing in the overlay context, just as the |
// drawing into the window needs to be clipped (look there for more comments). |
// Because we are clipping, we should save and restore the overlayContext's GState. |
// However, because we create and destroy the overlayContext in each ContentClick, |
// this is pointless, here. |
ClipToContentArea(overlayContext, window, cgWinRect, wRect.bottom - wRect.top); |
CGContextConcatCTM(overlayContext, docStP->displayCTM); |
// From now on, all drawing is done in "document page coordinates" |
// also clip to document page, now |
CGRect cgDocRect = CGRectMake(0, 0, docStP->docSize.h, docStP->docSize.v); |
CGContextClipToRect(overlayContext, cgDocRect); |
// The docStP->bmCtx stays around, so we need to save and restore its GState |
// around the following clip! |
CGContextSaveGState(docStP->bmCtx); |
CGContextClipToRect(docStP->bmCtx, cgDocRect); |
if (selectedTool == kArrowTool) |
{ |
DoMouseArrowTracking(window, overlayContext, where, modifiers); |
// if we end up with a new selection, adjust the toolpalette popup check marks |
CSkToolPaletteSyncPopups(FirstSelectedObject(&docStP->objList)); |
} |
else |
{ |
DoMouseToolTracking(window, overlayContext, MapToolToShape(selectedTool), where); |
} |
CGContextRestoreGState(docStP->bmCtx); |
// The rule says to call CGContextSynchronize before QDEndCGContext; but here it |
// really is pointless because we are closing the overlayWindow altogether. |
QDEndCGContext(GetWindowPort(overlayWindow), &overlayContext); |
// Always get rid of the overlayWindow. This way, we don't have to play around with window groups, |
// and still avoid problems when switching between several windows or between applications. |
DisposeWindow(overlayWindow); |
} |
} |
} // DoContentClick |
//----------------------------------------------------------------------------------------------------------------------- |
static void DoResize(WindowRef window, Rect* newBounds) |
{ |
const int kScrollbarWidth = 15; |
DocStorage* docStP = (DocStorage*)GetWRefCon(window); |
ControlID cntlID = { kControlSignatureMainWindow, kScalePopupID }; |
ControlRef control; |
Rect bounds = *newBounds; |
OffsetRect(&bounds, -bounds.left, -bounds.top); |
docStP->windowRect = bounds; |
// Resize and reposition controls |
GetControlByID(window, &cntlID, &control); |
GetControlBounds(control, &bounds); |
OffsetRect(&bounds, 0, docStP->windowRect.bottom - bounds.bottom); |
SetControlBounds(control, &bounds); |
cntlID.signature = kControlSignatureMainWindow; |
cntlID.id = kScrollHorizontalID; |
GetControlByID(window, &cntlID, &control); |
bounds.top = docStP->windowRect.bottom - kScrollbarWidth; |
bounds.left = bounds.right; // scrollbar left = popup button right |
bounds.bottom = docStP->windowRect.bottom; |
bounds.right = docStP->windowRect.right - kScrollbarWidth; |
SetControlBounds(control, &bounds); |
cntlID.id = kScrollVerticalID; |
GetControlByID(window, &cntlID, &control); |
bounds.top = 0; |
bounds.left = docStP->windowRect.right - kScrollbarWidth; |
bounds.bottom = docStP->windowRect.bottom - kScrollbarWidth; |
bounds.right = docStP->windowRect.right; |
SetControlBounds(control, &bounds); |
RecalculateDisplayCTM(docStP); |
SetupControls(window, docStP); |
InvalWindowRect(window, &docStP->windowRect); |
} |
//-------------------------------------------------------------------------------------------------- |
/////////////////////////////// Support for Copy/Paste of PDF Data ///////////////////////////////// |
//-------------------------------------------------------------------------------------------------- |
// To create PDF data for the Pasteboard, we need to set up a CFDataConsumer that collects data in a CFMutableDataRef. |
// Here are the two required callbacks: |
static size_t MyCFDataPutBytes(void* info, const void* buffer, size_t count) |
{ |
CFDataAppendBytes((CFMutableDataRef)info, buffer, count); |
return count; |
} |
static void MyCFDataRelease(void* info) |
{ |
CFRelease((CFMutableDataRef)info); |
} |
//-------------------------------------------------------------------------------------------------- |
// We reuse this routine in NavServicesHandling.c, from "MakePDFDocument": |
void DrawIntoPDFPage(CGContextRef pdfContext, CGRect pageBounds, const DocStorage* docStP, UInt32 pageNumber) |
{ |
#pragma unused(pageNumber) |
CGColorSpaceRef genericColorSpace = GetGenericRGBColorSpace(); |
CGContextBeginPage(pdfContext, &pageBounds); |
// ensure that we are drawing in the correct color space, a calibrated color space |
CGContextSetFillColorSpace(pdfContext, genericColorSpace); |
CGContextSetStrokeColorSpace(pdfContext, genericColorSpace); |
if (docStP->pdfData != NULL) |
{ |
DrawPDFData(pdfContext, docStP->pdfData, pageBounds); |
} |
RenderDrawObjList(pdfContext, &docStP->objList, false); |
CGContextEndPage(pdfContext); |
} |
//-------------------------------------------------------------------------------------------------- |
static OSStatus |
AddWindowContentToPasteboardAsPDF( PasteboardRef pasteboard, const DocStorage* docStP) |
{ |
OSStatus err = noErr; |
CGRect docRect = CGRectMake(0, 0, docStP->docSize.h, docStP->docSize.v); |
CFDataRef pdfData = CFDataCreateMutable( kCFAllocatorDefault, 0); |
CGContextRef pdfContext; |
CGDataConsumerRef consumer; |
CGDataConsumerCallbacks cfDataCallbacks = { MyCFDataPutBytes, MyCFDataRelease }; |
// We need to clear the pasteboard of it's current contents so that this application can |
// own it and add it's own data. |
err = PasteboardClear( pasteboard ); |
require_noerr( err, PasteboardClear_FAILED ); |
consumer = CGDataConsumerCreate((void*)pdfData, &cfDataCallbacks); |
// For now (and for demo purposes), just put the whole window drawing as pdf data |
// on the paste board, regardless of what is selected in the window. |
pdfContext = CGPDFContextCreate(consumer, &docRect, NULL); |
require(pdfContext != NULL, CGPDFContextCreate_FAILED); |
DrawIntoPDFPage(pdfContext, docRect, docStP, 1); |
CGContextRelease(pdfContext); // this finalizes the pdfData |
err = PasteboardPutItemFlavor( pasteboard, (PasteboardItemID)1, |
CFSTR("com.adobe.pdf"), pdfData, 0 ); |
require_noerr( err, PasteboardPutItemFlavor_FAILED ); |
CGPDFContextCreate_FAILED: |
PasteboardPutItemFlavor_FAILED: |
CGDataConsumerRelease(consumer); // this also releases the pdfData, via MyCFDataRelease |
PasteboardClear_FAILED: |
return err; |
} |
//-------------------------------------------------------------------------------------------------- |
// Check whether the pasteboard contains pdf data. |
// If so, return the CFDataRef in the pdfData parameter, if it's not NULL. |
static Boolean PasteboardContainsPDF(PasteboardRef inPasteboard, CFDataRef* pdfData) |
{ |
Boolean gotPDF = false; |
OSStatus err = noErr; |
PasteboardSyncFlags syncFlags; |
ItemCount itemCount; |
UInt32 itemIndex; |
if (inPasteboard == NULL) // only if GetPasteboard() failed to allocate the Pasteboard! |
{ |
return false; |
} |
syncFlags = PasteboardSynchronize(inPasteboard); |
if (syncFlags & kPasteboardModified) |
{ |
fprintf(stderr, "Pasteboard modified\n"); |
} |
// Count the number of items on the pasteboard so we can iterate through them. |
err = PasteboardGetItemCount(inPasteboard, &itemCount); |
require_noerr(err, PasteboardGetItemCount_FAILED); |
for (itemIndex = 1; itemIndex <= itemCount; ++itemIndex) |
{ |
PasteboardItemID itemID; |
CFArrayRef flavorTypeArray; |
CFIndex flavorCount; |
CFIndex flavorIndex; |
// Every item is identified by a unique value. |
err = PasteboardGetItemIdentifier( inPasteboard, itemIndex, &itemID ); |
require_noerr( err, PasteboardGetItemIdentifier_FAILED ); |
// The flavorTypeArray is a CFType and we'll need to call CFRelease on it later. |
err = PasteboardCopyItemFlavors( inPasteboard, itemID, &flavorTypeArray ); |
require_noerr( err, PasteboardCopyItemFlavors_FAILED ); |
flavorCount = CFArrayGetCount( flavorTypeArray ); |
for (flavorIndex = 0; flavorIndex < flavorCount; ++flavorIndex) |
{ |
CFStringRef flavorType; |
CFComparisonResult comparisonResult; |
// grab the flavorType so we can extract it's flags and data |
flavorType = (CFStringRef)CFArrayGetValueAtIndex( flavorTypeArray, flavorIndex ); |
// Don't care about flavorFlags, for now |
{ |
// PasteboardFlavorFlags flavorFlags; |
// err = PasteboardGetItemFlavorFlags( inPasteboard, itemID, flavorType, &flavorFlags ); |
// require_noerr( err, PasteboardGetItemFlavorFlags_FAILED ); |
} |
// Look at the flavorTypeStr for debugging: |
{ |
// char flavorTypeStr[128]; |
// CFStringGetCString( flavorType, flavorTypeStr, 128, kCFStringEncodingMacRoman ); |
// fprintf(stderr, "%s\n", flavorTypeStr); |
} |
// If it's a pdf, get it and return true |
comparisonResult = CFStringCompare(flavorType, CFSTR("com.adobe.pdf"), 0); |
if (comparisonResult == kCFCompareEqualTo) |
{ |
if (pdfData != NULL) |
{ |
err = PasteboardCopyItemFlavorData( inPasteboard, itemID, flavorType, pdfData ); |
require_noerr( err, PasteboardCopyItemFlavorData_FAILED ); |
} |
gotPDF = true; |
break; |
} |
PasteboardCopyItemFlavorData_FAILED: |
//PasteboardGetItemFlavorFlags_FAILED: |
; |
} |
CFRelease(flavorTypeArray); |
PasteboardCopyItemFlavors_FAILED: |
PasteboardGetItemIdentifier_FAILED: |
; |
} |
PasteboardGetItemCount_FAILED: |
return gotPDF; |
} |
//-------------------------------------------------------------------------------------------------- |
// For demo purposes, just keep the pasted-in pdf data separately from the CSkObject list. |
// A new "Paste" replaces any previous pdf data. |
static void AttachPDFToWindow(CFDataRef pdfData, DocStorage* docStP) |
{ |
if (docStP->pdfData != NULL) |
{ |
CFRelease(docStP->pdfData); |
docStP->pdfData = NULL; |
} |
docStP->pdfData = CFRetain(pdfData); |
} |
//-------------------------------------------------------------------------------------------------- |
// Make sure "Copy" and "Paste" are correctly enabled/disabled. |
static void DoCommandUpdateStatus(EventRef inEvent, WindowRef window, DocStorage* docStP) |
{ |
#pragma unused(inEvent, window, docStP) // for now |
MenuRef menu; |
MenuItemIndex unused; |
// For now, always enable "Copy" to put a (potentially empty) pdf on the pasteboard |
GetIndMenuItemWithCommandID(NULL, kHICommandCopy, 1, &menu, &unused); |
EnableMenuCommand(menu, kHICommandCopy); |
// For now, enable "Paste" only if we have a pdf on the pasteboard |
GetIndMenuItemWithCommandID(NULL, kHICommandPaste, 1, &menu, &unused); |
if ( PasteboardContainsPDF(GetPasteboard(), NULL) ) |
EnableMenuCommand(menu, kHICommandPaste); |
else |
DisableMenuCommand(menu, kHICommandPaste); |
} |
//-------------------------------------------------------------------------------------------------- |
// Handle a relatively long list of window HICommands: |
static OSStatus DoCommandProcess(EventRef inEvent, WindowRef window, DocStorage* docStP) |
{ |
OSStatus err = eventNotHandledErr; |
CGrgba color; |
HICommandExtended command; |
short itemNo; |
Boolean scaleChanged = false; |
GetEventParameter( inEvent, kEventParamDirectObject, typeHICommand, NULL, sizeof(HICommand), NULL, &command ); |
// { char* c = &command.commandID; |
// fprintf(stderr, "kEventClassCommand, commandID = %c%c%c%c\n", c[0], c[1], c[2], c[3]); } |
switch (command.commandID) |
{ |
case kHICommandCopy: |
// for now, only demonstrate how to put the current document content as 'pdf' on the clip board |
{ |
err = AddWindowContentToPasteboardAsPDF( GetPasteboard(), docStP); |
} |
break; |
case kHICommandPaste: |
// for now, only demonstrate how to paste in a 'pdf' from the clip board |
{ |
CFDataRef pdfData; |
if ( PasteboardContainsPDF(GetPasteboard(), &pdfData) ) |
{ |
AttachPDFToWindow(pdfData, docStP); // this will retain the pdfData in docStP |
CFRelease(pdfData); |
} |
else |
{ |
fprintf(stderr, "kHICommandPaste: Paste menu item should have been disabled!\n"); |
} |
err = noErr; |
} |
break; |
case kHICommandClose: |
// For now, do nothing here; kEventWindowClose will deal with window closing. |
// Later, we'll ask whether a "dirty" window needs to be saved. |
break; |
case kHICommandPageSetup: |
case kHICommandPrint: |
{ |
ProcessPrintCommand(docStP, command.commandID); |
err = noErr; |
} |
break; |
case kCmdWritePDF: |
(void)SaveAsPDFDocument(window, docStP); |
err = noErr; |
break; |
case kHICommandClear: |
RemoveSelectedDrawObjs(&docStP->objList); |
err = noErr; |
break; |
case kHICommandSelectAll: |
CSkObjListSetSelectState(&docStP->objList, true); |
err = noErr; |
break; |
case kCmdDuplicate: |
DuplicateSelectedDrawObjs(&docStP->objList, docStP->dupOffset); |
err = noErr; |
break; |
case kCmdMoveForward: |
MoveObjectForward(&docStP->objList); |
err = noErr; |
break; |
case kCmdMoveToFront: |
MoveObjectToFront(&docStP->objList); |
err = noErr; |
break; |
case kCmdMoveBackward: |
MoveObjectBackward(&docStP->objList); |
err = noErr; |
break; |
case kCmdMoveToBack: |
MoveObjectToBack(&docStP->objList); |
err = noErr; |
break; |
case kCmdScale50: |
docStP->scale = 0.5; scaleChanged = true; |
itemNo = 1; |
err = noErr; |
break; |
case kCmdScale100: |
docStP->scale = 1.0; scaleChanged = true; |
itemNo = 2; |
err = noErr; |
break; |
case kCmdScale200: |
docStP->scale = 2.0; scaleChanged = true; |
itemNo = 3; |
err = noErr; |
break; |
// Line Width |
case kCmdWidthNone: |
SetLineWidthOfSelecteds(&docStP->objList, 0.0); |
err = noErr; |
break; |
case kCmdWidthOne: |
SetLineWidthOfSelecteds(&docStP->objList, 1.0); |
err = noErr; |
break; |
case kCmdWidthTwo: |
SetLineWidthOfSelecteds(&docStP->objList, 2.0); |
err = noErr; |
break; |
case kCmdWidthFour: |
SetLineWidthOfSelecteds(&docStP->objList, 4.0); |
err = noErr; |
break; |
case kCmdWidthEight: |
SetLineWidthOfSelecteds(&docStP->objList, 8.0); |
err = noErr; |
break; |
case kCmdWidthSixteen: |
SetLineWidthOfSelecteds(&docStP->objList, 16.0); |
err = noErr; |
break; |
case kCmdWidthThinner: |
SetLineWidthOfSelecteds(&docStP->objList, kMakeItThinner); |
err = noErr; |
break; |
case kCmdWidthThicker: |
SetLineWidthOfSelecteds(&docStP->objList, kMakeItThicker); |
err = noErr; |
break; |
case kCmdLineWidthChanged: |
SetLineWidthOfSelecteds(&docStP->objList, CSkToolPaletteGetLineWidth(docStP->toolPalette)); |
err = noErr; |
break; |
// Line Cap |
case kCmdCapButt: |
SetLineCapOfSelecteds(&docStP->objList, kCGLineCapButt); |
err = noErr; |
break; |
case kCmdCapRound: |
SetLineCapOfSelecteds(&docStP->objList, kCGLineCapRound); |
err = noErr; |
break; |
case kCmdCapSquare: |
SetLineCapOfSelecteds(&docStP->objList, kCGLineCapSquare); |
err = noErr; |
break; |
case kCmdLineCapChanged: |
SetLineCapOfSelecteds(&docStP->objList, CSkToolPaletteGetLineCap(docStP->toolPalette)); |
err = noErr; |
break; |
// Line Join |
case kCmdJoinMiter: |
SetLineJoinOfSelecteds(&docStP->objList, kCGLineJoinMiter); |
err = noErr; |
break; |
case kCmdJoinRound: |
SetLineJoinOfSelecteds(&docStP->objList, kCGLineJoinRound); |
err = noErr; |
break; |
case kCmdJoinBevel: |
SetLineJoinOfSelecteds(&docStP->objList, kCGLineJoinBevel); |
err = noErr; |
break; |
case kCmdLineJoinChanged: |
SetLineJoinOfSelecteds(&docStP->objList, CSkToolPaletteGetLineJoin(docStP->toolPalette)); |
err = noErr; |
break; |
// Line Style |
case kCmdStyleSolid: |
SetLineStyleOfSelecteds(&docStP->objList, kStyleSolid); |
err = noErr; |
break; |
case kCmdStyleDashed: |
SetLineStyleOfSelecteds(&docStP->objList, kStyleDashed); |
err = noErr; |
break; |
case kCmdLineStyleChanged: |
SetLineStyleOfSelecteds(&docStP->objList, CSkToolPaletteGetLineStyle(docStP->toolPalette)); |
err = noErr; |
break; |
case kCmdStrokeColorChanged: |
SetStrokeColorOfSelecteds(&docStP->objList, CSkToolPaletteGetStrokeColor(docStP->toolPalette, &color)); |
err = noErr; |
break; |
case kCmdFillColorChanged: |
SetFillColorOfSelecteds(&docStP->objList, CSkToolPaletteGetFillColor(docStP->toolPalette, &color)); |
err = noErr; |
break; |
case kCmdStrokeAlphaChanged: |
{ |
ControlID cntlID = { kControlSignaturePalette, kStrokeAlphaSlider }; |
ControlRef control; |
CGrgba color; |
float alpha; |
GetControlByID(docStP->toolPalette, &cntlID, &control); |
alpha = (float)GetControlValue(control) / 100.0; |
CSkToolPaletteGetStrokeColor(docStP->toolPalette, &color); |
color.a = alpha; |
CSkToolPaletteSetStrokeColor(docStP->toolPalette, &color); |
SetStrokeAlphaOfSelecteds(&docStP->objList, alpha); |
err = noErr; |
} |
break; |
case kCmdFillAlphaChanged: |
{ |
ControlID cntlID = { kControlSignaturePalette, kFillAlphaSlider }; |
ControlRef control; |
CGrgba color; |
float alpha; |
GetControlByID(docStP->toolPalette, &cntlID, &control); |
alpha = (float)GetControlValue(control) / 100.0; |
CSkToolPaletteGetFillColor(docStP->toolPalette, &color); |
color.a = alpha; |
CSkToolPaletteSetFillColor(docStP->toolPalette, &color); |
SetFillAlphaOfSelecteds(&docStP->objList, alpha); |
err = noErr; |
} |
break; |
} // switch |
if (scaleChanged) |
{ |
ControlID cntlID = { kControlSignatureMainWindow, kScalePopupID }; |
ControlRef control; |
Str255 text; |
GetControlByID(window, &cntlID, &control); |
GetMenuItemText(command.source.menu.menuRef, command.source.menu.menuItemIndex, text); |
SetControlTitle(control, text); |
SetControlData(control, kControlEntireControl, kControlBevelButtonMenuValueTag, sizeof(itemNo), &itemNo); |
SetupControls(window, docStP); // do the scroll thumbs still need to be drawn? |
RecalculateDisplayCTM(docStP); |
} |
DrawWindowContent(window); // redraw unconditionally, for now |
return err; |
} // DoCommandProcess |
//----------------------------------------------------------------------------------------------------------------------- |
static pascal OSStatus CSkWindowEventHandler( EventHandlerCallRef inCallRef, EventRef inEvent, void* inUserData ) |
{ |
#pragma unused ( inCallRef ) |
OSStatus err = eventNotHandledErr; |
UInt32 eventKind = GetEventKind( inEvent ); |
UInt32 eventClass = GetEventClass( inEvent ); |
WindowRef window = (WindowRef)inUserData; |
DocStorage* docStP = (DocStorage*)GetWRefCon( window ); |
switch ( eventClass ) |
{ |
case kEventClassKeyboard: |
if (eventKind == kEventRawKeyDown ) |
{ |
char ch; |
err = GetEventParameter( inEvent, |
kEventParamKeyMacCharCodes, |
typeChar, |
NULL, |
sizeof( char ), |
NULL, |
&ch ); |
if (ch == kBackspaceCharCode) |
{ |
HICommand hiCmd; |
memset(&hiCmd, 0, sizeof(hiCmd)); |
hiCmd.commandID = kHICommandClear; |
SendWindowCommandEvent(window, &hiCmd); |
} |
} |
case kEventClassWindow: |
if ( eventKind == kEventWindowDrawContent ) |
{ |
DrawWindowContent(window); |
DrawControls(window); |
err = noErr; |
} |
else if ( eventKind == kEventWindowClose ) // Window is about to close |
{ |
// check if dirty, and if so, whether to save the document |
} |
else if ( eventKind == kEventWindowClosed ) // Dispose extra window storage here |
{ |
ReleaseDocumentStorage( docStP ); |
// Should check whether this is the last window, and if so, close the toolPalette, too |
} |
else if ( eventKind == kEventWindowClickContentRgn ) |
{ |
Point where; |
UInt32 modifiers; |
GetEventParameter(inEvent, kEventParamMouseLocation, typeQDPoint, NULL, sizeof(Point), NULL, &where); |
GetEventParameter(inEvent, kEventParamKeyModifiers, typeUInt32, NULL, sizeof(UInt32), NULL, &modifiers); |
QDGlobalToLocalPoint(GetWindowPort(window), &where); |
DoContentClick(window, where, modifiers); |
//err = noErr; |
} |
else if ( eventKind == kEventWindowBoundsChanged ) |
{ |
Rect r; |
(void) GetEventParameter( inEvent, kEventParamCurrentBounds, typeQDRectangle, NULL, sizeof(Rect), NULL, &r ); |
// We get here also when the window gets moved and doesn't need to be redrawn. |
#define RECTWIDTH(r) ((r).right - (r).left) |
#define RECTHEIGHT(r) ((r).bottom - (r).top) |
if ( (RECTWIDTH(docStP->windowRect) != RECTWIDTH(r)) || (RECTHEIGHT(docStP->windowRect) != RECTHEIGHT(r)) ) |
{ |
DoResize(window, &r); |
} |
} |
break; |
case kEventClassCommand: |
if ( eventKind == kEventCommandUpdateStatus ) |
{ |
DoCommandUpdateStatus(inEvent, window, docStP); |
err = noErr; |
} |
else if ( eventKind == kEventCommandProcess ) |
{ |
err = DoCommandProcess(inEvent, window, docStP); |
} |
break; |
} |
return err; |
} |
//----------------------------------------------------------------------------------------------------------------------- |
OSStatus NewCSkWindow(IBNibRef theNibRef, WindowRef toolPalette) |
{ |
const EventTypeSpec windowEvents[] = { { kEventClassCommand, kEventCommandProcess }, |
{ kEventClassCommand, kEventCommandUpdateStatus }, |
{ kEventClassKeyboard, kEventRawKeyDown }, |
{ kEventClassWindow, kEventWindowClickContentRgn }, |
{ kEventClassWindow, kEventWindowDrawContent }, |
{ kEventClassWindow, kEventWindowBoundsChanged }, |
{ kEventClassWindow, kEventWindowClose }, |
{ kEventClassWindow, kEventWindowClosed } |
}; |
static EventHandlerUPP gCSkWindowEventProc = NULL; |
WindowRef window; |
OSStatus err = CreateWindowFromNib( theNibRef, CFSTR("MainWindow"), &window ); |
if (err == noErr) |
{ |
DocStorage* docStP = CreateDocumentStorage(window, toolPalette); |
SetWRefCon( window, (long) docStP ); // Should use SetWindowProperty() |
if (gCSkWindowEventProc == NULL) |
{ |
gCSkWindowEventProc = NewEventHandlerUPP(CSkWindowEventHandler); |
if (gCSkWindowEventProc == NULL) |
return memFullErr; // We are screwed. Really. |
} |
err = InstallWindowEventHandler( window, gCSkWindowEventProc, GetEventTypeCount(windowEvents), windowEvents, window, NULL ); |
SetupControls(window, docStP); |
ShowWindow( window ); |
} |
return err; |
} |
Copyright © 2005 Apple Computer, Inc. All Rights Reserved. Terms of Use | Privacy Policy | Updated: 2005-03-23