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.
SampleSuppliesView.m
/* |
File: SampleSuppliesView.m |
Abstract: Supply level view implementation for sample printer utility for Mac OS X. |
MOST PRINTER DRIVERS DO NOT REQUIRE A PRINTER UTILITY. This utility is |
provided for testing/experimentation with the "Sample Printer" example |
raster printer driver for CUPS. |
Version: 4.0 |
Disclaimer: IMPORTANT: This Apple software is supplied to you by Apple |
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 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 (C) 2011 Apple Inc. All Rights Reserved. |
*/ |
#import <notify.h> |
#import "SampleSuppliesView.h" |
// |
// 'supply_shader()' - A CG shader function that provides a specular highlight... |
// |
static void |
supply_shader(void *info, const CGFloat *in, CGFloat *out) |
{ |
// This shader adds a specular highlight to the bar. |
CGFloat temp = sin(in[0] * M_PI * 1.25); |
out[0] = out[1] = out[2] = 1.0; |
out[3] = 0.45 * fabs(temp * temp * temp); |
} |
@implementation SampleSuppliesView |
// |
// 'dealloc' - Free memory used by the view. |
// |
- (void)dealloc |
{ |
ippDelete(data_); |
ppdClose(ppd_); |
httpClose(http_); |
if (shader_) |
CGShadingRelease(shader_); |
#if !USE_GCD |
if (thread_) |
[thread_ cancel]; |
#endif |
[super dealloc]; |
} |
#if !USE_GCD |
// |
// 'initWithFrame' - Initialize the view. |
// |
- (id)initWithFrame:(NSRect)frame |
{ |
if ((self = [super initWithFrame:frame]) != nil) |
{ |
printerURI_[0] = '\0'; |
resource_[0] = '\0'; |
http_ = NULL; |
data_ = NULL; |
ppd_ = NULL; |
shader_ = NULL; |
thread_ = NULL; |
} |
return (self); |
} |
#endif |
// |
// 'drawRect' - Draw the view. |
// |
- (void)drawRect:(NSRect)rect |
{ |
// Get the values |
ipp_attribute_t *colors = ippFindAttribute(data_, "marker-colors", IPP_TAG_NAME); |
ipp_attribute_t *levels = ippFindAttribute(data_, "marker-levels", IPP_TAG_INTEGER); |
ipp_attribute_t *high_levels = ippFindAttribute(data_, "marker-high-levels", IPP_TAG_INTEGER); |
ipp_attribute_t *low_levels = ippFindAttribute(data_, "marker-low-levels", IPP_TAG_INTEGER); |
ipp_attribute_t *message = ippFindAttribute(data_, "marker-message", IPP_TAG_TEXT); |
ipp_attribute_t *names = ippFindAttribute(data_, "marker-names", IPP_TAG_NAME); |
if (!colors || !levels || !names) |
return; |
// Loop through each supply... |
int i; |
float middle = rect.origin.x + 0.25 * rect.size.width; |
for (i = 0; i < levels->num_values; i ++) |
{ |
// Get the top of this supply... |
float bottom = rect.origin.y + rect.size.height - (i + 1) * SUPPLY_SPACING; |
// Draw the text label for the supply. |
NSString *label = [NSString stringWithUTF8String:names->values[i].string.text]; |
NSSize labelSize = [label sizeWithAttributes:nil]; |
NSRect labelRect = NSMakeRect(middle - labelSize.width - 5.0, bottom + 0.5 * (25.0 - labelSize.height), labelSize.width, labelSize.height); |
[[NSColor blackColor] set]; |
[label drawInRect:labelRect withAttributes:nil]; |
// Draw the supply level... |
int low = 15, high = 100; |
if (low_levels && i < low_levels->num_values) |
low = low_levels->values[i].integer; |
if (high_levels && i < high_levels->num_values) |
high = high_levels->values[i].integer; |
NSRect levelRect = NSMakeRect(middle, bottom + 0.5 * (SUPPLY_SPACING - SUPPLY_HEIGHT), levels->values[i].integer * 0.005 * rect.size.width, SUPPLY_HEIGHT); |
if (colors->values[i].string.text[0] != '#') |
[[NSString stringWithFormat:@"%d%%", levels->values[i].integer] drawInRect:levelRect withAttributes:nil]; |
else |
{ |
// Get the color for this supply... |
unsigned rgb = strtoul(colors->values[i].string.text + 1, NULL, 16); |
CGFloat red = ((rgb >> 16) & 255) / 255.0; |
CGFloat green = ((rgb >> 8) & 255) / 255.0; |
CGFloat blue = (rgb & 255) / 255.0; |
[NSGraphicsContext saveGraphicsState]; |
// Draw the shadow... |
NSRect barRect = NSMakeRect(middle, bottom + 0.5 * (SUPPLY_SPACING - SUPPLY_HEIGHT), 0.5 * rect.size.width, SUPPLY_HEIGHT); |
CGContextRef context = (CGContextRef)[[NSGraphicsContext currentContext] graphicsPort]; |
[[NSColor colorWithCalibratedWhite:0.333 alpha:0.5] set]; |
CGContextSetShadow(context, CGSizeMake(1.0, -1.0), 1.0); |
[NSBezierPath strokeRect:barRect]; |
// Draw the base bar... |
[[NSColor colorWithCalibratedRed:red green:green blue:blue alpha:0.666] setFill]; |
[NSBezierPath fillRect:levelRect]; |
levelRect.origin.x += levelRect.size.width; |
levelRect.size.width = 0.5 * rect.size.width - levelRect.size.width; |
[[NSColor colorWithCalibratedWhite:0.5 alpha:0.5] setFill]; |
[NSBezierPath fillRect:levelRect]; |
// Add tick and low/high marks... |
[[NSColor colorWithCalibratedWhite:1.0 alpha:0.25] setStroke]; |
NSBezierPath *path = [NSBezierPath bezierPath]; |
int j; |
for (j = 0; j <= 10; j ++) |
{ |
[path moveToPoint:NSMakePoint(middle + j * barRect.size.width / 10, barRect.origin.y)]; |
[path relativeLineToPoint:NSMakePoint(0.0, SUPPLY_HEIGHT)]; |
} |
if (low > 0) |
{ |
[path moveToPoint:NSMakePoint(middle + low * barRect.size.width / 100, barRect.origin.y)]; |
[path relativeLineToPoint:NSMakePoint(0.0, SUPPLY_HEIGHT)]; |
} |
if (high < 100) |
{ |
[path moveToPoint:NSMakePoint(middle + high * barRect.size.width / 100, barRect.origin.y)]; |
[path relativeLineToPoint:NSMakePoint(0.0, SUPPLY_HEIGHT)]; |
} |
[path stroke]; |
// Finally, overlay the bar with a shaded specular highlight... |
if (!shader_) |
{ |
// Create the shader... |
CGFunctionCallbacks callbacks = { 0, supply_shader, NULL }; |
CGColorSpaceRef colorSpace = CGColorSpaceCreateDeviceRGB(); |
CGFunctionRef function = CGFunctionCreate(NULL, 1, NULL, 4, NULL, &callbacks); |
shader_ = CGShadingCreateAxial(colorSpace, CGPointMake(0, 1), CGPointMake(0, 0), function, false, false); |
CGFunctionRelease(function); |
CGColorSpaceRelease(colorSpace); |
} |
NSRectClip(barRect); |
NSAffineTransform *transform = [NSAffineTransform transform]; |
[transform translateXBy:barRect.origin.x yBy:barRect.origin.y]; |
[transform scaleXBy:barRect.size.width yBy:barRect.size.height]; |
[transform concat]; |
CGContextDrawShading(context, shader_); |
[NSGraphicsContext restoreGraphicsState]; |
} |
} |
// Then any message... |
if (message && message->values[0].string.text[0]) |
{ |
NSString *label = [NSString stringWithUTF8String:message->values[0].string.text]; |
NSSize labelSize = [label sizeWithAttributes:nil]; |
NSRect labelRect = NSMakeRect(rect.origin.x + 0.5 * (rect.size.width - labelSize.width), rect.origin.y + 0.5 * (25.0 - labelSize.height), labelSize.width, labelSize.height); |
[[NSColor blackColor] set]; |
[label drawInRect:labelRect withAttributes:nil]; |
} |
} |
// |
// 'sendCommand' - Send a printer command in a command file. |
// |
// Note: This method will eventually use a new PMPrinter API... |
// |
- (void)sendCommand: (const char *)command withTitle: (NSString *)title |
{ |
// Check that we have a printer... |
if (!resource_[0]) |
return; |
// Create a temporary file containing the CUPS command file... |
char filename[1024]; |
cups_file_t *fp = cupsTempFile2(filename, sizeof(filename)); |
cupsFilePuts(fp, "#CUPS-COMMAND\n"); |
cupsFilePrintf(fp, "%s\n", command); |
cupsFileClose(fp); |
// Print the file using the Print-Job operation... |
ipp_t *request = ippNewRequest(IPP_PRINT_JOB); |
ippAddString(request, IPP_TAG_OPERATION, IPP_TAG_URI, "printer-uri", NULL, printerURI_); |
ippAddString(request, IPP_TAG_OPERATION, IPP_TAG_NAME, "requesting-user-name", NULL, cupsUser()); |
ippAddString(request, IPP_TAG_OPERATION, IPP_TAG_MIMETYPE, "document-format", NULL, "application/vnd.cups-command"); |
ippAddString(request, IPP_TAG_OPERATION, IPP_TAG_NAME, "job-name", NULL, [title UTF8String]); |
ippDelete(cupsDoFileRequest(http_, request, resource_, filename)); |
if (cupsLastError() != IPP_OK) |
NSRunAlertPanel(NSLocalizedString(@"Unable to send printer command!", NULL), @"%s", @"OK", nil, nil, cupsLastErrorString()); |
// Remove the temporary file and return... |
unlink(filename); |
} |
// |
// 'setPrinterId' - Set the printer that is associated with the supplies. |
// |
- (void)setPrinterId:(NSString *)printerId |
{ |
// Free any existing PPD... |
ppdClose(ppd_); |
ppd_ = NULL; |
// Generate the printer-uri and resource path for this printer... |
httpAssembleURIf(HTTP_URI_CODING_ALL, printerURI_, sizeof(printerURI_), "ipp", NULL, "localhost", 0, "/printers/%s", [printerId UTF8String]); |
snprintf(resource_, sizeof(resource_), "/printers/%s", [printerId UTF8String]); |
// Connect to the server as needed... |
if (!http_) |
http_ = httpConnectEncrypt(cupsServer(), ippPort(), cupsEncryption()); |
if (!http_) |
{ |
NSRunAlertPanel(NSLocalizedString(@"Unable to contact printing system!", NULL), @"%s", NSLocalizedString(@"OK", NULL), nil, nil, strerror(errno)); |
return; |
} |
// Get the PPD for this printer... |
const char *filename = cupsGetPPD2(http_, [printerId UTF8String]); |
if (filename) |
{ |
ppd_ = ppdOpenFile(filename); |
unlink(filename); |
} |
#ifdef USE_GCD |
// Start a background thread that watches for printer state changes |
[self updateLevelsGCD]; |
#else |
if (!thread_) |
{ |
thread_ = [[NSThread alloc] initWithTarget:self |
selector:@selector(updateLevelsThread:) |
object:nil]; |
[thread_ start]; |
} |
#endif |
} |
// |
// 'updateLevels' - Update ink levels for the queue. |
// |
- (void)updateLevels |
{ |
static const char * const attrs[] = |
{ |
"marker-change-time", |
"marker-colors", |
"marker-high-levels", |
"marker-levels", |
"marker-low-levels", |
"marker-message", |
"marker-names", |
"marker-types", |
"printer-commands", |
"printer-state", |
"printer-state-reasons", |
"printer-type" |
}; |
// Check that we have a printer... |
if (!resource_[0]) |
return; |
// Create a Get-Printer-Attributes request for the printer |
ipp_t *request = ippNewRequest(IPP_GET_PRINTER_ATTRIBUTES); |
ippAddString(request, IPP_TAG_OPERATION, IPP_TAG_URI, "printer-uri", NULL, printerURI_); |
ippAddStrings(request, IPP_TAG_OPERATION, IPP_TAG_KEYWORD, "requested-attributes", (int)(sizeof(attrs) / sizeof(attrs[0])), NULL, attrs); |
// Send the request and get the printer attributes response... |
ipp_t *response = cupsDoRequest(http_, request, "/"); |
// Get the current printer state and the last time the marker-* attributes |
// were updated. |
ipp_attribute_t *changed = ippFindAttribute(response, "marker-change-time", IPP_TAG_INTEGER); |
ipp_attribute_t *commands = ippFindAttribute(response, "printer-commands", IPP_TAG_KEYWORD); |
ipp_attribute_t *levels = ippFindAttribute(response, "marker-levels", IPP_TAG_INTEGER); |
ipp_attribute_t *message = ippFindAttribute(response, "marker-message", IPP_TAG_TEXT); |
ipp_attribute_t *reasons = ippFindAttribute(response, "printer-state-reasons", IPP_TAG_KEYWORD); |
ipp_attribute_t *state = ippFindAttribute(response, "printer-state", IPP_TAG_ENUM); |
ipp_attribute_t *type = ippFindAttribute(response, "printer-type", IPP_TAG_ENUM); |
int i, offline = 0, report_levels = 0; |
if (commands) |
{ |
for (i = 0; i < commands->num_values; i ++) |
{ |
if (!strcasecmp(commands->values[i].string.text, "ReportLevels")) |
{ |
report_levels = 1; |
break; |
} |
} |
} |
if (reasons) |
{ |
for (i = 0; i < reasons->num_values; i ++) |
{ |
if (!strcmp(reasons->values[i].string.text, "offline-report")) |
{ |
offline = 1; |
break; |
} |
} |
} |
// If the printer is idle, not offline, doesn't require authentication, we |
// don't have (recent) marker-levels, and the driver supports ReportLevels |
// commands, send a "ReportLevels" command file to the queue. |
if (state && state->values[0].integer == IPP_PRINTER_IDLE && |
!offline && |
type && !(type->values[0].integer & CUPS_PRINTER_AUTHENTICATED) && |
(!levels || |
(changed && (time(NULL) - changed->values[0].integer) > 43200)) && |
report_levels) |
{ |
[self sendCommand:"ReportLevels" withTitle:NSLocalizedString(@"Get Supply Levels", NULL)]; |
} |
// Update the supplies view... |
if (levels) |
{ |
int lines = levels->num_values; |
if (message && message->values[0].string.text[0]) |
lines ++; |
ippDelete(data_); |
data_ = response; |
NSRect windowFrame = [[self window] frame]; |
NSRect windowContent = [[self window] contentRectForFrameRect:windowFrame]; |
NSSize windowSize = NSMakeSize(windowContent.size.width, 90 + SUPPLY_SPACING * lines); |
if (windowContent.size.height != windowSize.height) |
[[self window] setContentSize:windowSize]; |
[self setNeedsDisplay:YES]; |
} |
else |
ippDelete(response); |
} |
#if USE_GCD |
// |
// 'updateLevelsGCD' - Update ink levels for the queue incase there is any changes in the queue |
// |
- (void)updateLevelsGCD; |
{ |
int token = 0; |
dispatch_queue_t q = dispatch_queue_create("sample_supply_queue", NULL); |
void (^workBlock)(void) = ^{ |
[self performSelectorOnMainThread:@selector(updateLevels) withObject:nil waitUntilDone:NO]; |
}; |
// Start the initial work |
dispatch_async(q, workBlock); |
// wait for the printerHistoryChange to update the levels... |
notify_register_dispatch("com.apple.printerHistoryChange", &token, q, ^(int t) { |
workBlock(); |
}); |
} |
#else |
// |
// 'updateLevelsThread' - Update ink levels for the queue. |
// |
- (void)updateLevelsThread: (id)object |
{ |
// Watch for printer state changes... |
int fd, token; |
char data; |
ssize_t bytes; |
notify_register_file_descriptor("com.apple.printerHistoryChange", &fd, 0, &token); |
for (;;) |
{ |
// Update the ink levels |
[self performSelectorOnMainThread:@selector(updateLevels) withObject:nil waitUntilDone:NO]; |
// Read the next notification... |
if ((bytes = read(fd, &data, 1)) == 0) |
break; |
else if (bytes < 0 && errno != EAGAIN && errno != EINTR) |
break; |
} |
notify_cancel(token); |
[NSThread exit]; |
} |
#endif |
@end |
Copyright © 2011 Apple Inc. All Rights Reserved. Terms of Use | Privacy Policy | Updated: 2011-09-06