Basic memory hex viewer/editor, using a (heavily stripped down) HexFiend framework

This commit is contained in:
Lior Halphon 2016-08-12 22:49:17 +03:00
parent 806d0775a4
commit e79ddee705
76 changed files with 14278 additions and 9 deletions

View File

@ -7,7 +7,12 @@
@property (strong) IBOutlet NSPanel *consoleWindow; @property (strong) IBOutlet NSPanel *consoleWindow;
@property (strong) IBOutlet NSTextField *consoleInput; @property (strong) IBOutlet NSTextField *consoleInput;
@property (strong) IBOutlet NSWindow *mainWindow; @property (strong) IBOutlet NSWindow *mainWindow;
@property (strong) IBOutlet NSView *memoryView;
@property (strong) IBOutlet NSPanel *memoryWindow;
-(uint8_t) readMemory:(uint16_t) addr;
-(void) writeMemory:(uint16_t) addr value:(uint8_t)value;
-(void) performAtomicBlock: (void (^)())block;
@end @end

View File

@ -4,6 +4,9 @@
#include "AppDelegate.h" #include "AppDelegate.h"
#include "gb.h" #include "gb.h"
#include "debugger.h" #include "debugger.h"
#include "memory.h"
#include "HexFiend/HexFiend.h"
#include "GBMemoryByteArray.h"
@interface Document () @interface Document ()
{ {
@ -14,6 +17,7 @@
bool tooMuchLogs; bool tooMuchLogs;
bool fullScreen; bool fullScreen;
bool in_sync_input; bool in_sync_input;
HFController *hex_controller;
NSString *lastConsoleInput; NSString *lastConsoleInput;
} }
@ -62,7 +66,7 @@ static uint32_t rgbEncode(GB_gameboy_t *gb, uint8_t r, uint8_t g, uint8_t b)
volatile bool stopping; volatile bool stopping;
NSConditionLock *has_debugger_input; NSConditionLock *has_debugger_input;
NSMutableArray *debugger_input_queue; NSMutableArray *debugger_input_queue;
bool is_inited; volatile bool is_inited;
} }
- (instancetype)init { - (instancetype)init {
@ -122,9 +126,12 @@ static uint32_t rgbEncode(GB_gameboy_t *gb, uint8_t r, uint8_t g, uint8_t b)
} andSampleRate:96000]; } andSampleRate:96000];
self.view.mouseHidingEnabled = YES; self.view.mouseHidingEnabled = YES;
[self.audioClient start]; [self.audioClient start];
NSTimer *hex_timer = [NSTimer timerWithTimeInterval:0.25 target:self selector:@selector(reloadMemoryView) userInfo:nil repeats:YES];
[[NSRunLoop mainRunLoop] addTimer:hex_timer forMode:NSDefaultRunLoopMode];
while (running) { while (running) {
GB_run(&gb); GB_run(&gb);
} }
[hex_timer invalidate];
[self.audioClient stop]; [self.audioClient stop];
self.audioClient = nil; self.audioClient = nil;
self.view.mouseHidingEnabled = NO; self.view.mouseHidingEnabled = NO;
@ -154,8 +161,8 @@ static uint32_t rgbEncode(GB_gameboy_t *gb, uint8_t r, uint8_t g, uint8_t b)
{ {
bool was_cgb = gb.is_cgb; bool was_cgb = gb.is_cgb;
[self stop]; [self stop];
GB_free(&gb);
is_inited = false; is_inited = false;
GB_free(&gb);
if (([sender tag] == 0 && was_cgb) || [sender tag] == 2) { if (([sender tag] == 0 && was_cgb) || [sender tag] == 2) {
[self initCGB]; [self initCGB];
} }
@ -200,6 +207,51 @@ static uint32_t rgbEncode(GB_gameboy_t *gb, uint8_t r, uint8_t g, uint8_t b)
} }
- (void) initMemoryView
{
hex_controller = [[HFController alloc] init];
[hex_controller setBytesPerColumn:1];
[hex_controller setFont:[NSFont userFixedPitchFontOfSize:12]];
[hex_controller setEditMode:HFOverwriteMode];
[hex_controller setByteArray:[[GBMemoryByteArray alloc] initWithDocument:self]];
/* Here we're going to make three representers - one for the hex, one for the ASCII, and one for the scrollbar. To lay these all out properly, we'll use a fourth HFLayoutRepresenter. */
HFLayoutRepresenter *layoutRep = [[HFLayoutRepresenter alloc] init];
HFHexTextRepresenter *hexRep = [[HFHexTextRepresenter alloc] init];
HFStringEncodingTextRepresenter *asciiRep = [[HFStringEncodingTextRepresenter alloc] init];
HFVerticalScrollerRepresenter *scrollRep = [[HFVerticalScrollerRepresenter alloc] init];
HFLineCountingRepresenter *lineRep = [[HFLineCountingRepresenter alloc] init];
HFStatusBarRepresenter *statusRep = [[HFStatusBarRepresenter alloc] init];
lineRep.lineNumberFormat = HFLineNumberFormatHexadecimal;
/* Add all our reps to the controller. */
[hex_controller addRepresenter:layoutRep];
[hex_controller addRepresenter:hexRep];
[hex_controller addRepresenter:asciiRep];
[hex_controller addRepresenter:scrollRep];
[hex_controller addRepresenter:lineRep];
[hex_controller addRepresenter:statusRep];
/* Tell the layout rep which reps it should lay out. */
[layoutRep addRepresenter:hexRep];
[layoutRep addRepresenter:scrollRep];
[layoutRep addRepresenter:asciiRep];
[layoutRep addRepresenter:lineRep];
[layoutRep addRepresenter:statusRep];
[(NSView *)[hexRep view] setAutoresizingMask:NSViewWidthSizable | NSViewHeightSizable];
/* Grab the layout rep's view and stick it into our container. */
NSView *layoutView = [layoutRep view];
NSRect layoutViewFrame = self.memoryView.frame;
[layoutView setFrame:layoutViewFrame];
[layoutView setAutoresizingMask:NSViewWidthSizable | NSViewHeightSizable | NSViewMaxYMargin];
[self.memoryView addSubview:layoutView];
}
+ (BOOL)autosavesInPlace { + (BOOL)autosavesInPlace {
return YES; return YES;
} }
@ -333,6 +385,8 @@ static uint32_t rgbEncode(GB_gameboy_t *gb, uint8_t r, uint8_t g, uint8_t b)
NSString *nsstring = @(string); // For ref-counting NSString *nsstring = @(string); // For ref-counting
dispatch_async(dispatch_get_main_queue(), ^{ dispatch_async(dispatch_get_main_queue(), ^{
[hex_controller reloadData];
NSFont *font = [NSFont userFixedPitchFontOfSize:12]; NSFont *font = [NSFont userFixedPitchFontOfSize:12];
NSUnderlineStyle underline = NSUnderlineStyleNone; NSUnderlineStyle underline = NSUnderlineStyleNone;
if (attributes & GB_LOG_BOLD) { if (attributes & GB_LOG_BOLD) {
@ -452,4 +506,44 @@ static uint32_t rgbEncode(GB_gameboy_t *gb, uint8_t r, uint8_t g, uint8_t b)
[self log:log withAttributes:0]; [self log:log withAttributes:0];
} }
- (uint8_t) readMemory:(uint16_t)addr
{
while (!is_inited);
return GB_read_memory(&gb, addr);
}
- (void) writeMemory:(uint16_t)addr value:(uint8_t)value
{
while (!is_inited);
GB_write_memory(&gb, addr, value);
}
- (void) performAtomicBlock: (void (^)())block
{
while (!is_inited);
bool was_running = running;
if (was_running) {
[self stop];
}
block();
if (was_running) {
[self start];
}
}
- (void) reloadMemoryView
{
if (self.memoryWindow.isVisible) {
[hex_controller reloadData];
}
}
- (IBAction) showMemory:(id)sender
{
if (!hex_controller) {
[self initMemoryView];
}
[self.memoryWindow makeKeyAndOrderFront:sender];
}
@end @end

View File

@ -10,6 +10,8 @@
<outlet property="consoleOutput" destination="doS-dM-hnl" id="Gn5-ju-Wb0"/> <outlet property="consoleOutput" destination="doS-dM-hnl" id="Gn5-ju-Wb0"/>
<outlet property="consoleWindow" destination="21F-Ah-yHX" id="eQ4-ug-LsT"/> <outlet property="consoleWindow" destination="21F-Ah-yHX" id="eQ4-ug-LsT"/>
<outlet property="mainWindow" destination="xOd-HO-29H" id="h8M-YB-vcC"/> <outlet property="mainWindow" destination="xOd-HO-29H" id="h8M-YB-vcC"/>
<outlet property="memoryView" destination="8hr-8o-3rN" id="fF0-rh-8ND"/>
<outlet property="memoryWindow" destination="mRm-dL-mCj" id="VPR-lu-vtI"/>
<outlet property="view" destination="uqf-pe-VAF" id="HMP-rf-Yqk"/> <outlet property="view" destination="uqf-pe-VAF" id="HMP-rf-Yqk"/>
<outlet property="window" destination="xOd-HO-29H" id="JIz-fz-R2o"/> <outlet property="window" destination="xOd-HO-29H" id="JIz-fz-R2o"/>
</connections> </connections>
@ -113,5 +115,17 @@
</view> </view>
<point key="canvasLocation" x="348" y="-29"/> <point key="canvasLocation" x="348" y="-29"/>
</window> </window>
<window title="Memory" allowsToolTipsWhenApplicationIsInactive="NO" autorecalculatesKeyViewLoop="NO" hidesOnDeactivate="YES" oneShot="NO" releasedWhenClosed="NO" showsToolbarButton="NO" visibleAtLaunch="NO" animationBehavior="default" id="mRm-dL-mCj" customClass="NSPanel">
<windowStyleMask key="styleMask" titled="YES" closable="YES" miniaturizable="YES" resizable="YES"/>
<windowCollectionBehavior key="collectionBehavior" fullScreenAuxiliary="YES"/>
<windowPositionMask key="initialPositionMask" leftStrut="YES" rightStrut="YES" topStrut="YES" bottomStrut="YES"/>
<rect key="contentRect" x="272" y="172" width="528" height="320"/>
<rect key="screenRect" x="0.0" y="0.0" width="2560" height="1417"/>
<view key="contentView" id="8hr-8o-3rN">
<rect key="frame" x="0.0" y="0.0" width="528" height="320"/>
<autoresizingMask key="autoresizingMask"/>
</view>
<point key="canvasLocation" x="-102" y="60"/>
</window>
</objects> </objects>
</document> </document>

View File

@ -0,0 +1,7 @@
#import "Document.h"
#import "HexFiend/HexFiend.h"
#import "HexFiend/HFByteSlice.h"
@interface GBCompleteByteSlice : HFByteSlice
- (instancetype) initWithByteArray:(HFByteArray *)array;
@end

View File

@ -0,0 +1,26 @@
#import "GBCompleteByteSlice.h"
@implementation GBCompleteByteSlice
{
HFByteArray *_array;
}
- (instancetype) initWithByteArray:(HFByteArray *)array
{
if ((self = [super init])) {
_array = array;
}
return self;
}
- (unsigned long long)length
{
return [_array length];
}
- (void)copyBytes:(unsigned char *)dst range:(HFRange)range
{
[_array copyBytes:dst range:range];
}
@end

View File

@ -0,0 +1,7 @@
#import "Document.h"
#import "HexFiend/HexFiend.h"
#import "HexFiend/HFByteArray.h"
@interface GBMemoryByteArray : HFByteArray
- (instancetype) initWithDocument:(Document *)document;
@end

64
Cocoa/GBMemoryByteArray.m Normal file
View File

@ -0,0 +1,64 @@
#import "GBMemoryByteArray.h"
#import "GBCompleteByteSlice.h"
@implementation GBMemoryByteArray
{
Document *_document;
}
- (instancetype) initWithDocument:(Document *)document
{
if ((self = [super init])) {
_document = document;
}
return self;
}
- (unsigned long long)length
{
return 0x10000;
}
- (void)copyBytes:(unsigned char *)dst range:(HFRange)range
{
uint16_t addr = (uint16_t) range.location;
unsigned long long length = range.length;
while (length) {
*(dst++) = [_document readMemory:addr++];
length--;
}
}
- (NSArray *)byteSlices
{
return @[[[GBCompleteByteSlice alloc] initWithByteArray:self]];
}
- (HFByteArray *)subarrayWithRange:(HFRange)range
{
unsigned char arr[range.length];
[self copyBytes:arr range:range];
HFByteArray *ret = [[HFBTreeByteArray alloc] init];
HFFullMemoryByteSlice *slice = [[HFFullMemoryByteSlice alloc] initWithData:[NSData dataWithBytes:arr length:range.length]];
[ret insertByteSlice:slice inRange:HFRangeMake(0, 0)];
return ret;
}
- (void)insertByteSlice:(HFByteSlice *)slice inRange:(HFRange)lrange
{
if (slice.length != lrange.length) return; /* Insertion is not allowed, only overwriting. */
[_document performAtomicBlock:^{
uint8_t values[lrange.length];
[slice copyBytes:values range:HFRangeMake(0, lrange.length)];
uint16_t addr = (uint16_t) lrange.location;
uint8_t *src = values;
unsigned long long length = lrange.length;
while (length) {
[_document writeMemory:addr++ value:*(src++)];
length--;
}
}];
}
@end

View File

@ -18,12 +18,19 @@
font-size: 11px; font-size: 11px;
font-weight: normal; font-weight: normal;
} }
h3 {
text-align:center;
font-size: 11px;
font-weight: bold;
}
</style> </style>
</head> </head>
<body> <body>
<h1>MIT License</h1> <h1>SameBoy</h1>
<h2>Copyright © 2015-2016 Lior Halphon</h2> <h2>MIT License</h2>
<h3>Copyright © 2015-2016 Lior Halphon</h3>
<p>Permission is hereby granted, free of charge, to any person obtaining a copy <p>Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal of this software and associated documentation files (the "Software"), to deal
@ -42,5 +49,32 @@ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.</p> SOFTWARE.</p>
<h1>Third-party Libraries</h1>
<h2>HexFiend</h2>
<h3>Copyright © 2005-2009, Peter Ammon
All rights reserved.</h3>
<p>Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions are met:</p>
<ul>
<li>Redistributions of source code must retain the above copyright
notice, this list of conditions and the following disclaimer.</li>
<li>Redistributions in binary form must reproduce the above copyright
notice, this list of conditions and the following disclaimer in the
documentation and/or other materials provided with the distribution.</li>
</ul>
<p>THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND ANY
EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
DISCLAIMED. IN NO EVENT SHALL THE REGENTS AND CONTRIBUTORS BE LIABLE FOR ANY
DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE</p>
</body> </body>
</html> </html>

View File

@ -1,5 +1,5 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?> <?xml version="1.0" encoding="UTF-8" standalone="no"?>
<document type="com.apple.InterfaceBuilder3.Cocoa.XIB" version="3.0" toolsVersion="9531" systemVersion="14F1509" targetRuntime="MacOSX.Cocoa" propertyAccessControl="none" useAutolayout="YES" customObjectInstantitationMethod="direct"> <document type="com.apple.InterfaceBuilder3.Cocoa.XIB" version="3.0" toolsVersion="9531" systemVersion="14F1713" targetRuntime="MacOSX.Cocoa" propertyAccessControl="none" useAutolayout="YES" customObjectInstantitationMethod="direct">
<dependencies> <dependencies>
<plugIn identifier="com.apple.InterfaceBuilder.CocoaPlugin" version="9531"/> <plugIn identifier="com.apple.InterfaceBuilder.CocoaPlugin" version="9531"/>
</dependencies> </dependencies>
@ -368,6 +368,13 @@
<action selector="interrupt:" target="-1" id="ZmB-wG-fTs"/> <action selector="interrupt:" target="-1" id="ZmB-wG-fTs"/>
</connections> </connections>
</menuItem> </menuItem>
<menuItem isSeparatorItem="YES" id="M6n-8G-LZS"/>
<menuItem title="Show Memory" id="UIa-n7-LSa">
<modifierMask key="keyEquivalentModifierMask"/>
<connections>
<action selector="showMemory:" target="-1" id="Ngn-2u-n12"/>
</connections>
</menuItem>
</items> </items>
</menu> </menu>
</menuItem> </menuItem>

View File

@ -0,0 +1,57 @@
//
// HFAnnotatedTree.h
// HexFiend_2
//
// Copyright 2010 ridiculous_fish. All rights reserved.
//
#import <Foundation/NSObject.h>
typedef unsigned long long (*HFAnnotatedTreeAnnotaterFunction_t)(id left, id right);
@interface HFAnnotatedTreeNode : NSObject <NSMutableCopying> {
HFAnnotatedTreeNode *left;
HFAnnotatedTreeNode *right;
HFAnnotatedTreeNode *parent;
uint32_t level;
@public
unsigned long long annotation;
}
/* Pure virtual method, which must be overridden. */
- (NSComparisonResult)compare:(HFAnnotatedTreeNode *)node;
/* Returns the next in-order node. */
- (id)nextNode;
- (id)leftNode;
- (id)rightNode;
- (id)parentNode;
#if ! NDEBUG
- (void)verifyIntegrity;
- (void)verifyAnnotation:(HFAnnotatedTreeAnnotaterFunction_t)annotater;
#endif
@end
@interface HFAnnotatedTree : NSObject <NSMutableCopying> {
HFAnnotatedTreeAnnotaterFunction_t annotater;
HFAnnotatedTreeNode *root;
}
- (instancetype)initWithAnnotater:(HFAnnotatedTreeAnnotaterFunction_t)annotater;
- (void)insertNode:(HFAnnotatedTreeNode *)node;
- (void)removeNode:(HFAnnotatedTreeNode *)node;
- (id)rootNode;
- (id)firstNode;
- (BOOL)isEmpty;
#if ! NDEBUG
- (void)verifyIntegrity;
#endif
@end

432
HexFiend/HFAnnotatedTree.m Normal file
View File

@ -0,0 +1,432 @@
//
// HFAnnotatedTree.m
// HexFiend_2
//
// Copyright 2010 ridiculous_fish. All rights reserved.
//
#import "HFAnnotatedTree.h"
#if NDEBUG
#define VERIFY_INTEGRITY() do { } while (0)
#else
#define VERIFY_INTEGRITY() [self verifyIntegrity]
#endif
/* HFAnnotatedTree is an AA tree. */
static unsigned long long null_annotater(id left, id right) { USE(left); USE(right); return 0; }
static void skew(HFAnnotatedTreeNode *node, HFAnnotatedTree *tree);
static BOOL split(HFAnnotatedTreeNode *oldparent, HFAnnotatedTree *tree);
static void rebalanceAfterLeafAdd(HFAnnotatedTreeNode *n, HFAnnotatedTree *tree);
static void delete(HFAnnotatedTreeNode *n, HFAnnotatedTree *tree);
static void verify_integrity(HFAnnotatedTreeNode *n);
static HFAnnotatedTreeNode *next_node(HFAnnotatedTreeNode *node);
static void insert(HFAnnotatedTreeNode *root, HFAnnotatedTreeNode *node, HFAnnotatedTree *tree);
static inline HFAnnotatedTreeNode *get_parent(HFAnnotatedTreeNode *node);
static inline HFAnnotatedTreeNode *get_root(HFAnnotatedTree *tree);
static inline HFAnnotatedTreeNode *create_root(void);
static inline HFAnnotatedTreeAnnotaterFunction_t get_annotater(HFAnnotatedTree *tree);
static void reannotate(HFAnnotatedTreeNode *node, HFAnnotatedTree *tree);
static HFAnnotatedTreeNode *first_node(HFAnnotatedTreeNode *node);
static HFAnnotatedTreeNode *left_child(HFAnnotatedTreeNode *node);
static HFAnnotatedTreeNode *right_child(HFAnnotatedTreeNode *node);
@implementation HFAnnotatedTree
- (instancetype)initWithAnnotater:(HFAnnotatedTreeAnnotaterFunction_t)annot {
self = [super init];
annotater = annot ? annot : null_annotater;
/* root is always an HFAnnotatedTreeNode with a left child but no right child */
root = create_root();
return self;
}
- (void)dealloc {
[root release];
[super dealloc];
}
- (id)rootNode {
return root;
}
- (id)firstNode {
return first_node(root);
}
- (id)mutableCopyWithZone:(NSZone *)zone {
HFAnnotatedTree *copied = [[[self class] alloc] init];
copied->annotater = annotater;
[copied->root release];
copied->root = [root mutableCopyWithZone:zone];
return copied;
}
- (BOOL)isEmpty {
/* We're empty if our root has no children. */
return left_child(root) == nil && right_child(root) == nil;
}
- (void)insertNode:(HFAnnotatedTreeNode *)node {
HFASSERT(node != nil);
HFASSERT(get_parent(node) == nil);
/* Insert into the root */
insert(root, [node retain], self);
VERIFY_INTEGRITY();
}
- (void)removeNode:(HFAnnotatedTreeNode *)node {
HFASSERT(node != nil);
HFASSERT(get_parent(node) != nil);
delete(node, self);
[node release];
VERIFY_INTEGRITY();
}
#if ! NDEBUG
- (void)verifyIntegrity {
[root verifyIntegrity];
[root verifyAnnotation:annotater];
}
#endif
static HFAnnotatedTreeNode *get_root(HFAnnotatedTree *tree) {
return tree->root;
}
static HFAnnotatedTreeAnnotaterFunction_t get_annotater(HFAnnotatedTree *tree) {
return tree->annotater;
}
@end
@implementation HFAnnotatedTreeNode
- (void)dealloc {
[left release];
[right release];
[super dealloc];
}
- (NSComparisonResult)compare:(HFAnnotatedTreeNode *)node {
USE(node);
UNIMPLEMENTED();
}
- (id)nextNode {
return next_node(self);
}
- (id)leftNode { return left; }
- (id)rightNode { return right; }
- (id)parentNode { return parent; }
- (id)mutableCopyWithZone:(NSZone *)zone {
HFAnnotatedTreeNode *copied = [[[self class] alloc] init];
if (left) {
copied->left = [left mutableCopyWithZone:zone];
copied->left->parent = copied;
}
if (right) {
copied->right = [right mutableCopyWithZone:zone];
copied->right->parent = copied;
}
copied->level = level;
copied->annotation = annotation;
return copied;
}
static HFAnnotatedTreeNode *left_child(HFAnnotatedTreeNode *node) {
return node->left;
}
static HFAnnotatedTreeNode *right_child(HFAnnotatedTreeNode *node) {
return node->right;
}
static HFAnnotatedTreeNode *create_root(void) {
HFAnnotatedTreeNode *result = [[HFAnnotatedTreeNode alloc] init];
result->level = UINT_MAX; //the root has a huge level
return result;
}
static void reannotate(HFAnnotatedTreeNode *node, HFAnnotatedTree *tree) {
HFASSERT(node != nil);
HFASSERT(tree != nil);
const HFAnnotatedTreeAnnotaterFunction_t annotater = get_annotater(tree);
node->annotation = annotater(node->left, node->right);
}
static void insert(HFAnnotatedTreeNode *root, HFAnnotatedTreeNode *node, HFAnnotatedTree *tree) {
/* Insert node at the proper place in the tree. root is the root node, and we always insert to the left of root */
BOOL left = YES;
HFAnnotatedTreeNode *parentNode = root, *currentChild;
/* Descend the tree until we find where to insert */
while ((currentChild = (left ? parentNode->left : parentNode->right)) != nil) {
parentNode = currentChild;
left = ([parentNode compare:node] >= 0); //if parentNode is larger than the child, then the child goes to the left of node
}
/* Now insert, potentially unbalancing the tree */
if (left) {
parentNode->left = node;
}
else {
parentNode->right = node;
}
/* Tell our node about its new parent */
node->parent = parentNode;
/* Rebalance and update annotations */
rebalanceAfterLeafAdd(node, tree);
}
static void skew(HFAnnotatedTreeNode *oldparent, HFAnnotatedTree *tree) {
HFAnnotatedTreeNode *newp = oldparent->left;
if (oldparent->parent->left == oldparent) {
/* oldparent is the left child of its parent. Substitute in our left child. */
oldparent->parent->left = newp;
}
else {
/* oldparent is the right child of its parent. Substitute in our left child. */
oldparent->parent->right = newp;
}
/* Tell the child about its new parent */
newp->parent = oldparent->parent;
/* Adopt its right child as our left child, and tell it about its new parent */
oldparent->left = newp->right;
if (oldparent->left) oldparent->left->parent = oldparent;
/* We are now the right child of the new parent */
newp->right = oldparent;
oldparent->parent = newp;
/* If we're now a leaf, our level is 1. Otherwise, it's one more than the level of our child. */
oldparent->level = oldparent->left ? oldparent->left->level + 1 : 1;
/* oldparent and newp both had their children changed, so need to be reannotated */
reannotate(oldparent, tree);
reannotate(newp, tree);
}
static BOOL split(HFAnnotatedTreeNode *oldparent, HFAnnotatedTree *tree) {
HFAnnotatedTreeNode *newp = oldparent->right;
if (newp && newp->right && newp->right->level == oldparent->level) {
if (oldparent->parent->left == oldparent) oldparent->parent->left = newp;
else oldparent->parent->right = newp;
newp->parent = oldparent->parent;
oldparent->parent = newp;
oldparent->right = newp->left;
if (oldparent->right) oldparent->right->parent = oldparent;
newp->left = oldparent;
newp->level = oldparent->level + 1;
/* oldparent and newp both had their children changed, so need to be reannotated */
reannotate(oldparent, tree);
reannotate(newp, tree);
return YES;
}
return NO;
}
static void rebalanceAfterLeafAdd(HFAnnotatedTreeNode *node, HFAnnotatedTree *tree) { // n is a node that has just been inserted and is now a leaf node.
node->level = 1;
node->left = nil;
node->right = nil;
reannotate(node, tree);
HFAnnotatedTreeNode * const root = get_root(tree);
HFAnnotatedTreeNode *probe;
for (probe = node->parent; probe != root; probe = probe->parent) {
reannotate(probe, tree);
// At this point probe->parent->level == probe->level
if (probe->level != (probe->left ? probe->left->level + 1 : 1)) {
// At this point the tree is correct, except (AA2) for n->parent
skew(probe, tree);
// We handle it (a left add) by changing it into a right add using Skew
// If the original add was to the left side of a node that is on the
// right side of a horisontal link, probe now points to the rights side
// of the second horisontal link, which is correct.
// However if the original add was to the left of node with a horizontal
// link, we must get to the right side of the second link.
if (!probe->right || probe->level != probe->right->level) probe = probe->parent;
}
if (! split(probe->parent, tree)) break;
}
while (probe) {
reannotate(probe, tree);
probe = probe->parent;
}
}
static void delete(HFAnnotatedTreeNode *n, HFAnnotatedTree *tree) { // If n is not a leaf, we first swap it out with the leaf node that just
// precedes it.
HFAnnotatedTreeNode *leaf = n, *tmp;
if (n->left) {
/* Descend the right subtree of our left child, to get the closest predecessor */
for (leaf = n->left; leaf->right; leaf = leaf->right) {}
// When we stop, leaf has no 'right' child so it cannot have a left one
}
else if (n->right) {
/* We have no children that precede us, but we have a child after us, so use our closest successor */
leaf = n->right;
}
/* tmp is either the parent who loses the child, or tmp is our right subtree. Either way, we will have to reduce its level. */
tmp = leaf->parent == n ? leaf : leaf->parent;
/* Tell leaf's parent to forget about leaf */
if (leaf->parent->left == leaf) {
leaf->parent->left = NULL;
}
else {
leaf->parent->right = NULL;
}
reannotate(leaf->parent, tree);
if (n != leaf) {
/* Replace ourself as our parent's child with leaf */
if (n->parent->left == n) n->parent->left = leaf;
else n->parent->right = leaf;
/* Leaf's parent is our parent */
leaf->parent = n->parent;
/* Our left and right children are now leaf's left and right children */
if (n->left) n->left->parent = leaf;
leaf->left = n->left;
if (n->right) n->right->parent = leaf;
leaf->right = n->right;
/* Leaf's level is our level */
leaf->level = n->level;
}
/* Since we adopted n's children, transferring the retain, tell n to forget about them so it doesn't release them */
n->left = nil;
n->right = nil;
// free (n);
HFAnnotatedTreeNode * const root = get_root(tree);
while (tmp != root) {
reannotate(tmp, tree);
// One of tmp's childern had its level reduced
if (tmp->level > (tmp->left ? tmp->left->level + 1 : 1)) { // AA2 failed
tmp->level--;
if (split(tmp, tree)) {
if (split(tmp, tree)) skew(tmp->parent->parent, tree);
break;
}
tmp = tmp->parent;
}
else if (tmp->level <= (tmp->right ? tmp->right->level + 1 : 1)){
break;
}
else { // AA3 failed
skew(tmp, tree);
//if (tmp->right) tmp->right->level = tmp->right->left ? tmp->right->left->level + 1 : 1;
if (tmp->level > tmp->parent->level) {
skew(tmp, tree);
split(tmp->parent->parent, tree);
break;
}
tmp = tmp->parent->parent;
}
}
while (tmp) {
reannotate(tmp, tree);
tmp = tmp->parent;
}
}
static HFAnnotatedTreeNode *next_node(HFAnnotatedTreeNode *node) {
/* Return the next in-order node */
HFAnnotatedTreeNode *result;
if (node->right) {
/* We have a right child, which is after us. Descend its left subtree. */
result = node->right;
while (result->left) {
result = result->left;
}
}
else {
/* We have no right child. If we are our parent's left child, then our parent is after us. Otherwise, we're our parent's right child and it was before us, so ascend while we're the parent's right child. */
result = node;
while (result->parent && result->parent->right == result) {
result = result->parent;
}
/* Now result is the left child of the parent (or has NULL parents), so its parent is the next node */
result = result->parent;
}
/* Don't return the root */
if (result != nil && result->parent == nil) {
result = next_node(result);
}
return result;
}
static HFAnnotatedTreeNode *first_node(HFAnnotatedTreeNode *node) {
/* Return the first node */
HFAnnotatedTreeNode *result = nil, *cursor = node->left;
while (cursor) {
/* Descend the left subtree */
result = cursor;
cursor = cursor->left;
}
return result;
}
static HFAnnotatedTreeNode *get_parent(HFAnnotatedTreeNode *node) {
HFASSERT(node != nil);
return node->parent;
}
static void __attribute__((unused))verify_integrity(HFAnnotatedTreeNode *n) {
HFASSERT(!n->left || n->left->parent == n);
HFASSERT(!n->right || n->right->parent == n);
HFASSERT(!next_node(n) || [n compare:next_node(n)] <= 0);
HFASSERT(!n->parent || n->parent->level >= n->level);
if (n->parent == nil) {
/* root node */
HFASSERT(n->level == UINT_MAX);
}
else {
/* non-root node */
HFASSERT(n->level == (n->left == NULL ? 1 : n->left->level + 1));
HFASSERT((n->level <= 1) || (n->right && n->level - n->right->level <= 1));
}
HFASSERT(!n->parent || !n->parent->parent ||
n->parent->parent->level > n->level);
}
#if ! NDEBUG
- (void)verifyIntegrity {
[left verifyIntegrity];
[right verifyIntegrity];
verify_integrity(self);
}
- (void)verifyAnnotation:(HFAnnotatedTreeAnnotaterFunction_t)annotater {
[left verifyAnnotation:annotater];
[right verifyAnnotation:annotater];
unsigned long long expectedAnnotation = annotater(left, right);
HFASSERT(annotation == expectedAnnotation);
}
#endif
@end

40
HexFiend/HFBTree.h Normal file
View File

@ -0,0 +1,40 @@
//
// HFBTree.h
// HexFiend
//
//
#import <Foundation/Foundation.h>
typedef unsigned long long HFBTreeIndex;
@class HFBTreeNode;
@protocol HFBTreeEntry <NSObject>
- (unsigned long long)length;
@end
@interface HFBTree : NSObject <NSMutableCopying, HFBTreeEntry> {
unsigned int depth;
HFBTreeNode *root;
}
- (void)insertEntry:(id)entry atOffset:(HFBTreeIndex)offset;
- (id)entryContainingOffset:(HFBTreeIndex)offset beginningOffset:(HFBTreeIndex *)outBeginningOffset;
- (void)removeEntryAtOffset:(HFBTreeIndex)offset;
- (void)removeAllEntries;
#if HFUNIT_TESTS
- (void)checkIntegrityOfCachedLengths;
- (void)checkIntegrityOfBTreeStructure;
#endif
- (NSEnumerator *)entryEnumerator;
- (NSArray *)allEntries;
- (HFBTreeIndex)length;
/* Applies the given function to the entry at the given offset, continuing with subsequent entries until the function returns NO. Do not modify the tree from within this function. */
- (void)applyFunction:(BOOL (*)(id entry, HFBTreeIndex offset, void *userInfo))func toEntriesStartingAtOffset:(HFBTreeIndex)offset withUserInfo:(void *)userInfo;
@end

1099
HexFiend/HFBTree.m Normal file

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,30 @@
//
// HFBTreeByteArray.h
// HexFiend_2
//
// Created by peter on 4/28/09.
// Copyright 2009 ridiculous_fish. All rights reserved.
//
#import <HexFiend/HFByteArray.h>
@class HFBTree;
/*! @class HFBTreeByteArray
@brief The principal efficient implementation of HFByteArray.
HFBTreeByteArray is an efficient subclass of HFByteArray that stores @link HFByteSlice HFByteSlices@endlink, using a 10-way B+ tree. This allows for insertion, deletion, and searching in approximately log-base-10 time.
Create an HFBTreeByteArray via \c -init. It has no methods other than those on HFByteArray.
*/
@interface HFBTreeByteArray : HFByteArray {
@private
HFBTree *btree;
}
/*! Designated initializer for HFBTreeByteArray.
*/
- (instancetype)init;
@end

279
HexFiend/HFBTreeByteArray.m Normal file
View File

@ -0,0 +1,279 @@
//
// HFBTreeByteArray.m
// HexFiend_2
//
// Created by peter on 4/28/09.
// Copyright 2009 ridiculous_fish. All rights reserved.
//
#import <HexFiend/HFByteArray_Internal.h>
#import <HexFiend/HFByteSlice.h>
#import <HexFiend/HFBTreeByteArray.h>
#import <HexFiend/HFBTree.h>
@implementation HFBTreeByteArray
- (instancetype)init {
if ((self = [super init])) {
btree = [[HFBTree alloc] init];
}
return self;
}
- (void)dealloc {
[btree release];
[super dealloc];
}
- (unsigned long long)length {
return [btree length];
}
- (NSArray *)byteSlices {
return [btree allEntries];
}
- (NSEnumerator *)byteSliceEnumerator {
return [btree entryEnumerator];
}
- (NSString*)description {
NSMutableArray* result = [NSMutableArray array];
NSEnumerator *enumer = [self byteSliceEnumerator];
HFByteSlice *slice;
unsigned long long offset = 0;
while ((slice = [enumer nextObject])) {
unsigned long long length = [slice length];
[result addObject:[NSString stringWithFormat:@"{%llu - %llu}", offset, length]];
offset = HFSum(offset, length);
}
if (! [result count]) return @"(empty tree)";
return [NSString stringWithFormat:@"<%@: %p>: %@", [self class], self, [result componentsJoinedByString:@" "]];
}
struct HFBTreeByteArrayCopyInfo_t {
unsigned char *dst;
unsigned long long startingOffset;
NSUInteger remainingLength;
};
static BOOL copy_bytes(id entry, HFBTreeIndex offset, void *userInfo) {
struct HFBTreeByteArrayCopyInfo_t *info = userInfo;
HFByteSlice *slice = entry;
HFASSERT(slice != nil);
HFASSERT(info != NULL);
HFASSERT(offset <= info->startingOffset);
unsigned long long sliceLength = [slice length];
HFASSERT(sliceLength > 0);
unsigned long long offsetIntoSlice = info->startingOffset - offset;
HFASSERT(offsetIntoSlice < sliceLength);
NSUInteger amountToCopy = ll2l(MIN(info->remainingLength, sliceLength - offsetIntoSlice));
HFRange srcRange = HFRangeMake(info->startingOffset - offset, amountToCopy);
[slice copyBytes:info->dst range:srcRange];
info->dst += amountToCopy;
info->startingOffset = HFSum(info->startingOffset, amountToCopy);
info->remainingLength -= amountToCopy;
return info->remainingLength > 0;
}
- (void)copyBytes:(unsigned char *)dst range:(HFRange)range {
HFASSERT(range.length <= NSUIntegerMax);
HFASSERT(HFMaxRange(range) <= [self length]);
if (range.length > 0) {
struct HFBTreeByteArrayCopyInfo_t copyInfo = {.dst = dst, .remainingLength = ll2l(range.length), .startingOffset = range.location};
[btree applyFunction:copy_bytes toEntriesStartingAtOffset:range.location withUserInfo:&copyInfo];
}
}
- (HFByteSlice *)sliceContainingByteAtIndex:(unsigned long long)offset beginningOffset:(unsigned long long *)actualOffset {
return [btree entryContainingOffset:offset beginningOffset:actualOffset];
}
/* Given a HFByteArray and a range contained within it, return the first byte slice containing that range, and the range within that slice. Modifies the given range to reflect what you get when the returned slice is removed. */
static inline HFByteSlice *findInitialSlice(HFBTree *btree, HFRange *inoutArrayRange, HFRange *outRangeWithinSlice) {
const HFRange arrayRange = *inoutArrayRange;
const unsigned long long arrayRangeEnd = HFMaxRange(arrayRange);
unsigned long long offsetIntoSlice, lengthFromOffsetIntoSlice;
unsigned long long beginningOffset;
HFByteSlice *slice = [btree entryContainingOffset:arrayRange.location beginningOffset:&beginningOffset];
const unsigned long long sliceLength = [slice length];
HFASSERT(beginningOffset <= arrayRange.location);
offsetIntoSlice = arrayRange.location - beginningOffset;
HFASSERT(offsetIntoSlice < sliceLength);
unsigned long long sliceEndInArray = HFSum(sliceLength, beginningOffset);
if (sliceEndInArray <= arrayRangeEnd) {
/* Our slice ends before or at the requested range end */
lengthFromOffsetIntoSlice = sliceLength - offsetIntoSlice;
}
else {
/* Our slice ends after the requested range end */
unsigned long long overflow = sliceEndInArray - arrayRangeEnd;
HFASSERT(HFSum(overflow, offsetIntoSlice) < sliceLength);
lengthFromOffsetIntoSlice = sliceLength - HFSum(overflow, offsetIntoSlice);
}
/* Set the out range to the input range minus the range consumed by the slice */
inoutArrayRange->location = MIN(sliceEndInArray, arrayRangeEnd);
inoutArrayRange->length = arrayRangeEnd - inoutArrayRange->location;
/* Set the out range within the slice to what we computed */
*outRangeWithinSlice = HFRangeMake(offsetIntoSlice, lengthFromOffsetIntoSlice);
return slice;
}
- (BOOL)fastPathInsertByteSlice:(HFByteSlice *)slice atOffset:(unsigned long long)offset {
HFASSERT(offset > 0);
unsigned long long priorSliceOffset;
HFByteSlice *priorSlice = [btree entryContainingOffset:offset - 1 beginningOffset:&priorSliceOffset];
HFByteSlice *appendedSlice = [priorSlice byteSliceByAppendingSlice:slice];
if (appendedSlice) {
[btree removeEntryAtOffset:priorSliceOffset];
[btree insertEntry:appendedSlice atOffset:priorSliceOffset];
return YES;
}
else {
return NO;
}
}
- (void)insertByteSlice:(HFByteSlice *)slice atOffset:(unsigned long long)offset {
[self incrementGenerationOrRaiseIfLockedForSelector:_cmd];
if (offset == 0) {
[btree insertEntry:slice atOffset:0];
}
else if (offset == [btree length]) {
if (! [self fastPathInsertByteSlice:slice atOffset:offset]) {
[btree insertEntry:slice atOffset:offset];
}
}
else {
unsigned long long beginningOffset;
HFByteSlice *overlappingSlice = [btree entryContainingOffset:offset beginningOffset:&beginningOffset];
if (beginningOffset == offset) {
if (! [self fastPathInsertByteSlice:slice atOffset:offset]) {
[btree insertEntry:slice atOffset:offset];
}
}
else {
HFASSERT(offset > beginningOffset);
unsigned long long offsetIntoSlice = offset - beginningOffset;
unsigned long long sliceLength = [overlappingSlice length];
HFASSERT(sliceLength > offsetIntoSlice);
HFByteSlice *left = [overlappingSlice subsliceWithRange:HFRangeMake(0, offsetIntoSlice)];
HFByteSlice *right = [overlappingSlice subsliceWithRange:HFRangeMake(offsetIntoSlice, sliceLength - offsetIntoSlice)];
[btree removeEntryAtOffset:beginningOffset];
[btree insertEntry:right atOffset:beginningOffset];
/* Try the fast appending path */
HFByteSlice *joinedSlice = [left byteSliceByAppendingSlice:slice];
if (joinedSlice) {
[btree insertEntry:joinedSlice atOffset:beginningOffset];
}
else {
[btree insertEntry:slice atOffset:beginningOffset];
[btree insertEntry:left atOffset:beginningOffset];
}
}
}
}
- (void)deleteBytesInRange:(HFRange)range {
[self incrementGenerationOrRaiseIfLockedForSelector:_cmd];
HFRange remainingRange = range;
HFASSERT(HFMaxRange(range) <= [self length]);
if (range.length == 0) return; //nothing to delete
//fast path for deleting everything
if (range.location == 0 && range.length == [self length]) {
[btree removeAllEntries];
return;
}
unsigned long long beforeLength = [self length];
unsigned long long rangeStartLocation = range.location;
HFByteSlice *beforeSlice = nil, *afterSlice = nil;
while (remainingRange.length > 0) {
HFRange rangeWithinSlice;
HFByteSlice *slice = findInitialSlice(btree, &remainingRange, &rangeWithinSlice);
const unsigned long long sliceLength = [slice length];
const unsigned long long rangeWithinSliceEnd = HFMaxRange(rangeWithinSlice);
HFRange lefty = HFRangeMake(0, rangeWithinSlice.location);
HFRange righty = HFRangeMake(rangeWithinSliceEnd, sliceLength - rangeWithinSliceEnd);
HFASSERT(lefty.length == 0 || beforeSlice == nil);
HFASSERT(righty.length == 0 || afterSlice == nil);
unsigned long long beginningOffset = remainingRange.location - HFMaxRange(rangeWithinSlice);
if (lefty.length > 0){
beforeSlice = [slice subsliceWithRange:lefty];
rangeStartLocation = beginningOffset;
}
if (righty.length > 0) afterSlice = [slice subsliceWithRange:righty];
[btree removeEntryAtOffset:beginningOffset];
remainingRange.location = beginningOffset;
}
if (afterSlice) {
[self insertByteSlice:afterSlice atOffset:rangeStartLocation];
}
if (beforeSlice) {
[self insertByteSlice:beforeSlice atOffset:rangeStartLocation];
}
unsigned long long afterLength = [self length];
HFASSERT(beforeLength - afterLength == range.length);
}
- (void)insertByteSlice:(HFByteSlice *)slice inRange:(HFRange)lrange {
[self incrementGenerationOrRaiseIfLockedForSelector:_cmd];
if (lrange.length > 0) {
[self deleteBytesInRange:lrange];
}
if ([slice length] > 0) {
[self insertByteSlice:slice atOffset:lrange.location];
}
}
- (id)mutableCopyWithZone:(NSZone *)zone {
USE(zone);
HFBTreeByteArray *result = [[[self class] alloc] init];
[result->btree release];
result->btree = [btree mutableCopy];
return result;
}
- (id)subarrayWithRange:(HFRange)range {
if (range.location == 0 && range.length == [self length]) {
return [[self mutableCopy] autorelease];
}
HFBTreeByteArray *result = [[[[self class] alloc] init] autorelease];
HFRange remainingRange = range;
unsigned long long offsetInResult = 0;
while (remainingRange.length > 0) {
HFRange rangeWithinSlice;
HFByteSlice *slice = findInitialSlice(btree, &remainingRange, &rangeWithinSlice);
HFByteSlice *subslice;
if (rangeWithinSlice.location == 0 && rangeWithinSlice.length == [slice length]) {
subslice = slice;
}
else {
subslice = [slice subsliceWithRange:rangeWithinSlice];
}
[result insertByteSlice:subslice atOffset:offsetInResult];
offsetInResult = HFSum(offsetInResult, rangeWithinSlice.length);
}
return result;
}
@end

180
HexFiend/HFByteArray.h Normal file
View File

@ -0,0 +1,180 @@
//
// HFByteArray.h
// HexFiend_2
//
// Copyright 2007 ridiculous_fish. All rights reserved.
//
#import <Cocoa/Cocoa.h>
@class HFByteSlice, HFFileReference, HFByteRangeAttributeArray;
typedef NS_ENUM(NSUInteger, HFByteArrayDataStringType) {
HFHexDataStringType,
HFASCIIDataStringType
};
/*! @class HFByteArray
@brief The principal Model class for HexFiend's MVC architecture.
HFByteArray implements the Model portion of HexFiend.framework. It is logically a mutable, resizable array of bytes, with a 64 bit length. It is somewhat analagous to a 64 bit version of NSMutableData, except that it is designed to enable efficient (faster than O(n)) implementations of insertion and deletion.
HFByteArray, being an abstract class, will raise an exception if you attempt to instantiate it directly. For most uses, instantiate HFBTreeByteArray instead, with the usual <tt>[[class alloc] init]</tt>.
HFByteArray also exposes itself as an array of @link HFByteSlice HFByteSlices@endlink, which are logically immutable arrays of bytes. which is useful for operations such as file saving that need to access the underlying byte slices.
HFByteArray contains a generation count, which is incremented whenever the HFByteArray changes (to allow caches to be implemented on top of it). It also includes the notion of locking: a locked HFByteArray will raise an exception if written to, but it may still be read.
ByteArrays have the usual threading restrictions for non-concurrent data structures. It is safe to read an HFByteArray concurrently from multiple threads. It is not safe to read an HFByteArray while it is being modified from another thread, nor is it safe to modify one simultaneously from two threads.
HFByteArray is an abstract class. It will raise an exception if you attempt to instantiate it directly. The principal concrete subclass is HFBTreeByteArray.
*/
@class HFByteRangeAttributeArray;
@interface HFByteArray : NSObject <NSCopying, NSMutableCopying> {
@private
NSUInteger changeLockCounter;
NSUInteger changeGenerationCount;
}
/*! @name Initialization
*/
//@{
/*! Initialize to a byte array containing only the given slice. */
- (instancetype)initWithByteSlice:(HFByteSlice *)slice;
/*! Initialize to a byte array containing the slices of the given array. */
- (instancetype)initWithByteArray:(HFByteArray *)array;
//@}
/*! @name Accessing raw data
*/
//@{
/*! Returns the length of the HFByteArray as a 64 bit unsigned long long. This is an abstract method that concrete subclasses must override. */
- (unsigned long long)length;
/*! Copies a range of bytes into a buffer. This is an abstract method that concrete subclasses must override. */
- (void)copyBytes:(unsigned char *)dst range:(HFRange)range;
//@}
/*! @name Accessing byte slices
Methods to access the byte slices underlying the HFByteArray.
*/
//@{
/*! Returns the contents of the receiver as an array of byte slices. This is an abstract method that concrete subclasses must override. */
- (NSArray *)byteSlices;
/*! Returns an NSEnumerator representing the byte slices of the receiver. This is implemented as enumerating over the result of -byteSlices, but subclasses can override this to be more efficient. */
- (NSEnumerator *)byteSliceEnumerator;
/*! Returns the byte slice containing the byte at the given index, and the actual offset of this slice. */
- (HFByteSlice *)sliceContainingByteAtIndex:(unsigned long long)offset beginningOffset:(unsigned long long *)actualOffset;
//@}
/*! @name Modifying the byte array
Methods to modify the given byte array.
*/
//@{
/*! Insert an HFByteSlice in the given range. The maximum value of the range must not exceed the length of the subarray. The length of the given slice is not required to be equal to length of the range - in other words, this method may change the length of the receiver. This is an abstract method that concrete subclasses must override. */
- (void)insertByteSlice:(HFByteSlice *)slice inRange:(HFRange)lrange;
/*! Insert an HFByteArray in the given range. This is implemented via calling <tt>insertByteSlice:inRange:</tt> with the byte slices from the given byte array. */
- (void)insertByteArray:(HFByteArray *)array inRange:(HFRange)lrange;
/*! Delete bytes in the given range. This is implemented on the base class by creating an empty byte array and inserting it in the range to be deleted, via <tt>insertByteSlice:inRange:</tt>. */
- (void)deleteBytesInRange:(HFRange)range;
/*! Returns a new HFByteArray containing the given range. This is an abstract method that concrete subclasses must override. */
- (HFByteArray *)subarrayWithRange:(HFRange)range;
//@}
/*! @name Write locking and generation count
Methods to lock and query the lock that prevents writes.
*/
//@{
/*! Increment the change lock. Until the change lock reaches 0, all modifications to the receiver will raise an exception. */
- (void)incrementChangeLockCounter;
/*! Decrement the change lock. If the change lock reaches 0, modifications will be allowed again. */
- (void)decrementChangeLockCounter;
/*! Query if the changes are locked. This method is KVO compliant. */
- (BOOL)changesAreLocked;
//@}
/* @name Generation count
Manipulate the generation count */
// @{
/*! Increments the generation count, unless the receiver is locked, in which case it raises an exception. All subclasses of HFByteArray should call this method at the beginning of any overridden method that may modify the receiver.
@param sel The selector that would modify the receiver (e.g. <tt>deleteBytesInRange:</tt>). This is usually <tt>_cmd</tt>. */
- (void)incrementGenerationOrRaiseIfLockedForSelector:(SEL)sel;
/*! Return the change generation count. Every change to the ByteArray increments this by one or more. This can be used for caching layers on top of HFByteArray, to known when to expire their cache. */
- (NSUInteger)changeGenerationCount;
//@}
/*! @name Searching
*/
//@{
/*! Searches the receiver for a byte array matching findBytes within the given range, and returns the index that it was found. This is a concrete method on HFByteArray.
@param findBytes The HFByteArray containing the data to be found (the needle to the receiver's haystack).
@param range The range of the receiver in which to search. The end of the range must not exceed the receiver's length.
@param forwards If this is YES, then the first match within the range is returned. Otherwise the last is returned.
@param progressTracker An HFProgressTracker to allow progress reporting and cancelleation for the search operation.
@return The index in the receiver of bytes equal to <tt>findBytes</tt>, or ULLONG_MAX if the byte array was not found (or the operation was cancelled)
*/
- (unsigned long long)indexOfBytesEqualToBytes:(HFByteArray *)findBytes inRange:(HFRange)range searchingForwards:(BOOL)forwards trackingProgress:(id)progressTracker;
//@}
@end
/*! @category HFByteArray(HFFileWriting)
@brief HFByteArray methods for writing to files, and preparing other HFByteArrays for potentially destructive file writes.
*/
@interface HFByteArray (HFFileWriting)
/*! Attempts to write the receiver to a file. This is a concrete method on HFByteArray.
@param targetURL A URL to the file to be written to. It is OK for the receiver to contain one or more instances of HFByteSlice that are sourced from the file.
@param progressTracker An HFProgressTracker to allow progress reporting and cancelleation for the write operation.
@param error An out NSError parameter.
@return YES if the write succeeded, NO if it failed.
*/
- (BOOL)writeToFile:(NSURL *)targetURL trackingProgress:(id)progressTracker error:(NSError **)error;
/*! Returns the ranges of the file that would be modified, if the receiver were written to it. This is useful (for example) in determining if the clipboard can be preserved after a save operation. This is a concrete method on HFByteArray.
@param reference An HFFileReference to the file to be modified
@return An array of @link HFRangeWrapper HFRangeWrappers@endlink, representing the ranges of the file that would be affected. If no range would be affected, the result is an empty array.
*/
- (NSArray *)rangesOfFileModifiedIfSavedToFile:(HFFileReference *)reference;
/*! Attempts to modify the receiver so that it no longer depends on any of the HFRanges in the array within the given file. It is not necessary to perform this operation on the byte array that is being written to the file.
@param ranges An array of HFRangeWrappers, representing ranges in the given file that the receiver should no longer depend on.
@param reference The HFFileReference that the receiver should no longer depend on.
@param hint A dictionary that can be used to improve the efficiency of the operation, by allowing multiple byte arrays to share the same state. If you plan to call this method on multiple byte arrays, pass the first one an empty NSMutableDictionary, and pass the same dictionary to subsequent calls.
@return A YES return indicates the operation was successful, and the receiver no longer contains byte slices that source data from any of the ranges of the given file (or never did). A NO return indicates that breaking the dependencies would require too much memory, and so the receiver still depends on some of those ranges.
*/
- (BOOL)clearDependenciesOnRanges:(NSArray *)ranges inFile:(HFFileReference *)reference hint:(NSMutableDictionary *)hint;
@end
/*! @category HFByteArray(HFAttributes)
@brief HFByteArray methods for attributes of byte arrays.
*/
@interface HFByteArray (HFAttributes)
/*! Returns a byte range attribute array for the bytes in the given range. */
- (HFByteRangeAttributeArray *)attributesForBytesInRange:(HFRange)range;
/*! Returns the HFByteArray level byte range attribute array. Default is to return nil. */
- (HFByteRangeAttributeArray *)byteRangeAttributeArray;
@end

218
HexFiend/HFByteArray.m Normal file
View File

@ -0,0 +1,218 @@
//
// HFByteArray.m
// HexFiend_2
//
// Copyright 2007 ridiculous_fish. All rights reserved.
//
#import <HexFiend/HFByteArray_Internal.h>
#import <HexFiend/HFFullMemoryByteSlice.h>
@implementation HFByteArray
- (instancetype)init {
if ([self class] == [HFByteArray class]) {
[NSException raise:NSInvalidArgumentException format:@"init sent to HFByteArray, but HFByteArray is an abstract class. Instantiate one of its subclasses instead, like HFBTreeByteArray."];
}
return [super init];
}
- (instancetype)initWithByteSlice:(HFByteSlice *)slice {
if(!(self = [self init])) return nil;
self = [self init];
[self insertByteSlice:slice inRange:HFRangeMake(0, 0)];
return self;
}
- (instancetype)initWithByteArray:(HFByteArray *)array {
if(!(self = [self init])) return nil;
NSEnumerator *e = [array byteSliceEnumerator];
HFByteSlice *slice;
while((slice = [e nextObject])) {
[self insertByteSlice:slice inRange:HFRangeMake([self length], 0)];
}
return self;
}
- (NSArray *)byteSlices { UNIMPLEMENTED(); }
- (unsigned long long)length { UNIMPLEMENTED(); }
- (void)copyBytes:(unsigned char *)dst range:(HFRange)range { USE(dst); USE(range); UNIMPLEMENTED_VOID(); }
- (void)insertByteSlice:(HFByteSlice *)slice inRange:(HFRange)lrange { USE(slice); USE(lrange); UNIMPLEMENTED_VOID(); }
- (NSEnumerator *)byteSliceEnumerator {
return [[self byteSlices] objectEnumerator];
}
- (HFByteSlice *)sliceContainingByteAtIndex:(unsigned long long)offset beginningOffset:(unsigned long long *)actualOffset {
HFByteSlice *slice;
unsigned long long current = 0;
NSEnumerator *enumer = [self byteSliceEnumerator];
while ((slice = [enumer nextObject])) {
unsigned long long sum = HFSum([slice length], current);
if (sum > offset) break;
current = sum;
}
if (actualOffset) *actualOffset = current;
return slice;
}
- (void)insertByteArray:(HFByteArray*)array inRange:(HFRange)lrange {
REQUIRE_NOT_NULL(array);
HFASSERT(HFRangeIsSubrangeOfRange(lrange, HFRangeMake(0, [self length])));
#ifndef NDEBUG
unsigned long long expectedLength = [self length] - lrange.length + [array length];
#endif
[self incrementGenerationOrRaiseIfLockedForSelector:_cmd];
NSEnumerator *sliceEnumerator;
HFByteSlice *byteSlice;
if (array == self) {
/* Guard against self insertion */
sliceEnumerator = [[array byteSlices] objectEnumerator];
}
else {
sliceEnumerator = [array byteSliceEnumerator];
}
while ((byteSlice = [sliceEnumerator nextObject])) {
[self insertByteSlice:byteSlice inRange:lrange];
lrange.location += [byteSlice length];
lrange.length = 0;
}
/* If there were no slices, delete the lrange */
if (lrange.length > 0) {
[self deleteBytesInRange:lrange];
}
#ifndef NDEBUG
HFASSERT(expectedLength == [self length]);
#endif
}
- (HFByteArray *)subarrayWithRange:(HFRange)range { USE(range); UNIMPLEMENTED(); }
- (id)mutableCopyWithZone:(NSZone *)zone {
USE(zone);
return [[self subarrayWithRange:HFRangeMake(0, [self length])] retain];
}
- (id)copyWithZone:(NSZone *)zone {
USE(zone);
return [[self subarrayWithRange:HFRangeMake(0, [self length])] retain];
}
- (void)deleteBytesInRange:(HFRange)lrange {
[self incrementGenerationOrRaiseIfLockedForSelector:_cmd];
HFByteSlice* slice = [[HFFullMemoryByteSlice alloc] initWithData:[NSData data]];
[self insertByteSlice:slice inRange:lrange];
[slice release];
}
- (BOOL)isEqual:v {
REQUIRE_NOT_NULL(v);
if (self == v) return YES;
else if (! [v isKindOfClass:[HFByteArray class]]) return NO;
else {
HFByteArray* obj = v;
unsigned long long length = [self length];
if (length != [obj length]) return NO;
unsigned long long offset;
unsigned char buffer1[1024];
unsigned char buffer2[sizeof buffer1 / sizeof *buffer1];
for (offset = 0; offset < length; offset += sizeof buffer1) {
size_t amountToGrab = sizeof buffer1;
if (amountToGrab > length - offset) amountToGrab = ll2l(length - offset);
[self copyBytes:buffer1 range:HFRangeMake(offset, amountToGrab)];
[obj copyBytes:buffer2 range:HFRangeMake(offset, amountToGrab)];
if (memcmp(buffer1, buffer2, amountToGrab)) return NO;
}
}
return YES;
}
- (unsigned long long)indexOfBytesEqualToBytes:(HFByteArray *)findBytes inRange:(HFRange)range searchingForwards:(BOOL)forwards trackingProgress:(id)progressTracker {
UNIMPLEMENTED();
}
- (BOOL)_debugIsEqual:(HFByteArray *)v {
REQUIRE_NOT_NULL(v);
if (! [v isKindOfClass:[HFByteArray class]]) return NO;
HFByteArray* obj = v;
unsigned long long length = [self length];
if (length != [obj length]) {
printf("Lengths differ: %llu versus %llu\n", length, [obj length]);
abort();
return NO;
}
unsigned long long offset;
unsigned char buffer1[1024];
unsigned char buffer2[sizeof buffer1 / sizeof *buffer1];
for (offset = 0; offset < length; offset += sizeof buffer1) {
memset(buffer1, 0, sizeof buffer1);
memset(buffer2, 0, sizeof buffer2);
size_t amountToGrab = sizeof buffer1;
if (amountToGrab > length - offset) amountToGrab = ll2l(length - offset);
[self copyBytes:buffer1 range:HFRangeMake(offset, amountToGrab)];
[obj copyBytes:buffer2 range:HFRangeMake(offset, amountToGrab)];
size_t i;
for (i=0; i < amountToGrab; i++) {
if (buffer1[i] != buffer2[i]) {
printf("Inconsistency found at %llu (%02x versus %02x)\n", i + offset, buffer1[i], buffer2[i]);
abort();
return NO;
}
}
}
return YES;
}
- (NSData *)_debugData {
NSMutableData *data = [NSMutableData dataWithLength:(NSUInteger)[self length]];
[self copyBytes:[data mutableBytes] range:HFRangeMake(0, [self length])];
return data;
}
- (BOOL)_debugIsEqualToData:(NSData *)val {
REQUIRE_NOT_NULL(val);
HFByteArray *byteArray = [[NSClassFromString(@"HFFullMemoryByteArray") alloc] init];
HFByteSlice *byteSlice = [[HFFullMemoryByteSlice alloc] initWithData:val];
[byteArray insertByteSlice:byteSlice inRange:HFRangeMake(0, 0)];
[byteSlice release];
BOOL result = [self _debugIsEqual:byteArray];
[byteArray release];
return result;
}
- (void)incrementChangeLockCounter {
[self willChangeValueForKey:@"changesAreLocked"];
if (HFAtomicIncrement(&changeLockCounter, NO) == 0) {
[NSException raise:NSInvalidArgumentException format:@"change lock counter overflow for %@", self];
}
[self didChangeValueForKey:@"changesAreLocked"];
}
- (void)decrementChangeLockCounter {
[self willChangeValueForKey:@"changesAreLocked"];
if (HFAtomicDecrement(&changeLockCounter, NO) == NSUIntegerMax) {
[NSException raise:NSInvalidArgumentException format:@"change lock counter underflow for %@", self];
}
[self didChangeValueForKey:@"changesAreLocked"];
}
- (BOOL)changesAreLocked {
return !! changeLockCounter;
}
- (NSUInteger)changeGenerationCount {
return changeGenerationCount;
}
- (void)incrementGenerationOrRaiseIfLockedForSelector:(SEL)sel {
if (changeLockCounter) {
[NSException raise:NSInvalidArgumentException format:@"Selector %@ sent to a locked byte array %@", NSStringFromSelector(sel), self];
}
else {
HFAtomicIncrement(&changeGenerationCount, YES);
}
}
@end

View File

@ -0,0 +1,8 @@
#import <HexFiend/HFByteArray.h>
@interface HFByteArray (HFInternal)
- (BOOL)_debugIsEqual:(HFByteArray *)val;
- (BOOL)_debugIsEqualToData:(NSData *)val;
@end

53
HexFiend/HFByteSlice.h Normal file
View File

@ -0,0 +1,53 @@
//
// HFByteSlice.h
// HexFiend_2
//
// Copyright 2007 ridiculous_fish. All rights reserved.
//
#import <Cocoa/Cocoa.h>
@class HFFileReference, HFByteRangeAttributeArray;
/*! @class HFByteSlice
@brief A class representing a source of data for an HFByteArray.
HFByteSlice is an abstract class encapsulating primitive data sources (files, memory buffers, etc.). Each source must support random access reads, and have a well defined length. All HFByteSlices are \b immutable.
The two principal subclasses of HFByteSlice are HFSharedMemoryByteSlice and HFFileByteSlice, which respectively encapsulate data from memory and from a file.
*/
@interface HFByteSlice : NSObject {
NSUInteger retainCount;
}
/*! Return the length of the byte slice as a 64 bit value. This is an abstract method that concrete subclasses must override. */
- (unsigned long long)length;
/*! Copies a range of data from the byte slice into an in-memory buffer. This is an abstract method that concrete subclasses must override. */
- (void)copyBytes:(unsigned char *)dst range:(HFRange)range;
/*! Returns a new slice containing a subrange of the given slice. This is an abstract method that concrete subclasses must override. */
- (HFByteSlice *)subsliceWithRange:(HFRange)range;
/*! Attempts to create a new byte slice by appending one byte slice to another. This does not modify the receiver or the slice argument (after all, both are immutable). This is provided as an optimization, and is allowed to return nil if the appending cannot be done efficiently. The default implementation returns nil.
*/
- (HFByteSlice *)byteSliceByAppendingSlice:(HFByteSlice *)slice;
/*! Returns YES if the receiver is sourced from a file. The default implementation returns NO. This is used to estimate cost when writing to a file.
*/
- (BOOL)isSourcedFromFile;
/*! For a given file reference, returns the range within the file that the receiver is sourced from. If the receiver is not sourced from this file, returns {ULLONG_MAX, ULLONG_MAX}. The default implementation returns {ULLONG_MAX, ULLONG_MAX}. This is used during file saving to to determine how to properly overwrite a given file.
*/
- (HFRange)sourceRangeForFile:(HFFileReference *)reference;
@end
/*! @category HFByteSlice(HFAttributes)
@brief Methods for querying attributes of individual byte slices. */
@interface HFByteSlice (HFAttributes)
/*! Returns the attributes for the bytes in the given range. */
- (HFByteRangeAttributeArray *)attributesForBytesInRange:(HFRange)range;
@end

85
HexFiend/HFByteSlice.m Normal file
View File

@ -0,0 +1,85 @@
//
// HFByteSlice.m
// HexFiend_2
//
// Copyright 2007 ridiculous_fish. All rights reserved.
//
#import <HexFiend/HFByteSlice.h>
@implementation HFByteSlice
- (instancetype)init {
if ([self class] == [HFByteSlice class]) {
[NSException raise:NSInvalidArgumentException format:@"init sent to HFByteArray, but HFByteArray is an abstract class. Instantiate one of its subclasses instead."];
}
return [super init];
}
- (unsigned long long)length { UNIMPLEMENTED(); }
- (void)copyBytes:(unsigned char*)dst range:(HFRange)range { USE(dst); USE(range); UNIMPLEMENTED_VOID(); }
- (HFByteSlice *)subsliceWithRange:(HFRange)range { USE(range); UNIMPLEMENTED(); }
- (void)constructNewByteSlicesAboutRange:(HFRange)range first:(HFByteSlice **)first second:(HFByteSlice **)second {
const unsigned long long length = [self length];
//clip the range to our extent
range.location = llmin(range.location, length);
range.length = llmin(range.length, length - range.location);
HFRange firstRange = {0, range.location};
HFRange secondRange = {range.location + range.length, [self length] - (range.location + range.length)};
if (first) {
if (firstRange.length > 0)
*first = [self subsliceWithRange:firstRange];
else
*first = nil;
}
if (second) {
if (secondRange.length > 0)
*second = [self subsliceWithRange:secondRange];
else
*second = nil;
}
}
- (HFByteSlice *)byteSliceByAppendingSlice:(HFByteSlice *)slice {
USE(slice);
return nil;
}
- (HFByteRangeAttributeArray *)attributesForBytesInRange:(HFRange)range {
USE(range);
return nil;
}
- (BOOL)isSourcedFromFile {
return NO;
}
- (HFRange)sourceRangeForFile:(HFFileReference *)reference {
USE(reference);
return HFRangeMake(ULLONG_MAX, ULLONG_MAX);
}
- (id)retain {
HFAtomicIncrement(&retainCount, NO);
return self;
}
- (oneway void)release {
if (HFAtomicDecrement(&retainCount, NO) == (NSUInteger)(-1)) {
[self dealloc];
}
}
- (NSUInteger)retainCount {
return 1 + retainCount;
}
@end

View File

@ -0,0 +1,7 @@
#import <HexFiend/HFByteSlice.h>
@interface HFByteSlice (HFByteSlice_Private)
- (void)constructNewByteSlicesAboutRange:(HFRange)range first:(HFByteSlice **)first second:(HFByteSlice **)second;
@end

395
HexFiend/HFController.h Normal file
View File

@ -0,0 +1,395 @@
//
// HFController.h
// HexFiend_2
//
// Copyright 2007 ridiculous_fish. All rights reserved.
//
#import <Cocoa/Cocoa.h>
#import <HexFiend/HFTypes.h>
/*! @header HFController
@abstract The HFController.h header contains the HFController class, which is a central class in Hex Fiend.
*/
@class HFRepresenter, HFByteArray, HFFileReference, HFControllerCoalescedUndo, HFByteRangeAttributeArray;
/*! @enum HFControllerPropertyBits
The HFControllerPropertyBits bitmask is used to inform the HFRepresenters of a change in the current state that they may need to react to. A bitmask of the changed properties is passed to representerChangedProperties:. It is common for multiple properties to be included in such a bitmask.
*/
typedef NS_OPTIONS(NSUInteger, HFControllerPropertyBits) {
HFControllerContentValue = 1 << 0, /*!< Indicates that the contents of the ByteArray has changed within the document. There is no indication as to what the change is. If redisplaying everything is expensive, Representers should cache their displayed data and compute any changes manually. */
HFControllerContentLength = 1 << 1, /*!< Indicates that the length of the ByteArray has changed. */
HFControllerDisplayedLineRange = 1 << 2, /*!< Indicates that the displayedLineRange property of the document has changed (e.g. the user scrolled). */
HFControllerSelectedRanges = 1 << 3, /*!< Indicates that the selectedContentsRanges property of the document has changed (e.g. the user selected some other range). */
HFControllerSelectionPulseAmount = 1 << 4, /*!< Indicates that the amount of "pulse" to show in the Find pulse indicator has changed. */
HFControllerBytesPerLine = 1 << 5, /*!< Indicates that the number of bytes to show per line has changed. */
HFControllerBytesPerColumn = 1 << 6, /*!< Indicates that the number of bytes per column (byte grouping) has changed. */
HFControllerEditable = 1 << 7, /*!< Indicates that the document has become (or is no longer) editable. */
HFControllerFont = 1 << 8, /*!< Indicates that the font property has changed. */
HFControllerAntialias = 1 << 9, /*!< Indicates that the shouldAntialias property has changed. */
HFControllerLineHeight = 1 << 10, /*!< Indicates that the lineHeight property has changed. */
HFControllerViewSizeRatios = 1 << 11, /*!< Indicates that the optimum size for each view may have changed; used by HFLayoutController after font changes. */
HFControllerByteRangeAttributes = 1 << 12, /*!< Indicates that some attributes of the ByteArray has changed within the document. There is no indication as to what the change is. */
HFControllerByteGranularity = 1 << 13, /*!< Indicates that the byte granularity has changed. For example, when moving from ASCII to UTF-16, the byte granularity increases from 1 to 2. */
HFControllerBookmarks = 1 << 14, /*!< Indicates that a bookmark has been added or removed. */
HFControllerColorBytes = 1 << 15, /*!< Indicates that the shouldColorBytes property has changed. */
HFControllerShowCallouts = 1 << 16, /*!< Indicates that the shouldShowCallouts property has changed. */
HFControllerHideNullBytes = 1 << 17, /*!< Indicates that the shouldHideNullBytes property has changed. */
};
/*! @enum HFControllerMovementDirection
The HFControllerMovementDirection enum is used to specify a direction (either left or right) in various text editing APIs. HexFiend does not support left-to-right languages.
*/
typedef NS_ENUM(NSInteger, HFControllerMovementDirection) {
HFControllerDirectionLeft,
HFControllerDirectionRight
};
/*! @enum HFControllerSelectionTransformation
The HFControllerSelectionTransformation enum is used to specify what happens to the selection in various APIs. This is mainly interesting for text-editing style Representers.
*/
typedef NS_ENUM(NSInteger, HFControllerSelectionTransformation) {
HFControllerDiscardSelection, /*!< The selection should be discarded. */
HFControllerShiftSelection, /*!< The selection should be moved, without changing its length. */
HFControllerExtendSelection /*!< The selection should be extended, changing its length. */
};
/*! @enum HFControllerMovementGranularity
The HFControllerMovementGranularity enum is used to specify the granularity of text movement in various APIs. This is mainly interesting for text-editing style Representers.
*/
typedef NS_ENUM(NSInteger, HFControllerMovementGranularity) {
HFControllerMovementByte, /*!< Move by individual bytes */
HFControllerMovementColumn, /*!< Move by a column */
HFControllerMovementLine, /*!< Move by lines */
HFControllerMovementPage, /*!< Move by pages */
HFControllerMovementDocument /*!< Move by the whole document */
};
/*! @enum HFEditMode
HFEditMode enumerates the different edit modes that a document might be in.
*/
typedef NS_ENUM(NSInteger, HFEditMode) {
HFInsertMode,
HFOverwriteMode,
HFReadOnlyMode,
} ;
/*! @class HFController
@brief A central class that acts as the controller layer for HexFiend.framework
HFController acts as the controller layer in the MVC architecture of HexFiend. The HFController plays several significant central roles, including:
- Mediating between the data itself (in the HFByteArray) and the views of the data (the @link HFRepresenter HFRepresenters@endlink).
- Propagating changes to the views.
- Storing properties common to all Representers, such as the currently diplayed range, the currently selected range(s), the font, etc.
- Handling text editing actions, such as selection changes or insertions/deletions.
An HFController is the top point of ownership for a HexFiend object graph. It retains both its ByteArray (model) and its array of Representers (views).
You create an HFController via <tt>[[HFController alloc] init]</tt>. After that, give it an HFByteArray via setByteArray:, and some Representers via addRepresenter:. Then insert the Representers' views in a window, and you're done.
*/
@interface HFController : NSObject <NSCoding> {
@private
NSMutableArray *representers;
HFByteArray *byteArray;
NSMutableArray *selectedContentsRanges;
HFRange displayedContentsRange;
HFFPRange displayedLineRange;
NSUInteger bytesPerLine;
NSUInteger bytesPerColumn;
CGFloat lineHeight;
NSUInteger currentPropertyChangeToken;
NSMutableArray *additionalPendingTransactions;
HFControllerPropertyBits propertiesToUpdateInCurrentTransaction;
NSUndoManager *undoManager;
NSMutableSet *undoOperations;
HFControllerCoalescedUndo *undoCoalescer;
unsigned long long selectionAnchor;
HFRange selectionAnchorRange;
CFAbsoluteTime pulseSelectionStartTime, pulseSelectionCurrentTime;
NSTimer *pulseSelectionTimer;
/* Basic cache support */
HFRange cachedRange;
NSData *cachedData;
NSUInteger cachedGenerationIndex;
struct {
unsigned antialias:1;
unsigned colorbytes:1;
unsigned showcallouts:1;
unsigned hideNullBytes:1;
HFEditMode editMode:2;
unsigned editable:1;
unsigned selectable:1;
unsigned selectionInProgress:1;
unsigned shiftExtendSelection:1;
unsigned commandExtendSelection:1;
unsigned livereload:1;
} _hfflags;
}
/*! @name Representer handling.
Methods for modifying the list of HFRepresenters attached to a controller. Attached representers receive the controllerDidChange: message when various properties of the controller change. A representer may only be attached to one controller at a time. Representers are retained by the controller.
*/
//@{
/// Gets the current array of representers attached to this controller.
@property (readonly, copy) NSArray *representers;
/// Adds a new representer to this controller.
- (void)addRepresenter:(HFRepresenter *)representer;
/// Removes an existing representer from this controller. The representer must be present in the array of representers.
- (void)removeRepresenter:(HFRepresenter *)representer;
//@}
/*! @name Property transactions
Methods for temporarily delaying notifying representers of property changes. There is a property transaction stack, and all property changes are collected until the last token is popped off the stack, at which point all representers are notified of all collected changes via representerChangedProperties:. To use this, call beginPropertyChangeTransaction, and record the token that is returned. Pass it to endPropertyChangeTransaction: to notify representers of all changed properties in bulk.
Tokens cannot be popped out of order - they are used only as a correctness check.
*/
//@{
/*! Begins delaying property change transactions. Returns a token that should be passed to endPropertyChangeTransactions:. */
- (NSUInteger)beginPropertyChangeTransaction;
/*! Pass a token returned from beginPropertyChangeTransaction to this method to pop the transaction off the stack and, if the stack is empty, to notify Representers of all collected changes. Tokens cannot be popped out of order - they are used strictly as a correctness check. */
- (void)endPropertyChangeTransaction:(NSUInteger)token;
//@}
/*! @name Byte array
Set and get the byte array. */
//@{
/*! The byte array must be non-nil. In general, HFRepresenters should not use this to determine what bytes to display. Instead they should use copyBytes:range: or dataForRange: below. */
@property (nonatomic, strong) HFByteArray *byteArray;
/*! Replaces the entire byte array with a new one, preserving as much of the selection as possible. Unlike setByteArray:, this method is undoable, and intended to be used from representers that make a global change (such as Replace All). */
- (void)replaceByteArray:(HFByteArray *)newArray;
//@}
/*! @name Properties shared between all representers
The following properties are considered global among all HFRepresenters attached to the receiver.
*/
//@{
/*! Returns the number of lines on which the cursor may be placed. This is always at least 1, and is equivalent to (unsigned long long)(HFRoundUpToNextMultiple(contentsLength, bytesPerLine) / bytesPerLine) */
- (unsigned long long)totalLineCount;
/*! Indicates the number of bytes per line, which is a global property among all the line-oriented representers. */
- (NSUInteger)bytesPerLine;
/*! Returns the height of a line, in points. This is generally determined by the font. Representers that wish to align things to lines should use this. */
- (CGFloat)lineHeight;
//@}
/*! @name Selection pulsing
Used to show the current selection after a change, similar to Find in Safari
*/
//{@
/*! Begins selection pulsing (e.g. following a successful Find operation). Representers will receive callbacks indicating that HFControllerSelectionPulseAmount has changed. */
- (void)pulseSelection;
/*! Return the amount that the "Find pulse indicator" should show. 0 means no pulse, 1 means maximum pulse. This is useful for Representers that support find and replace. */
- (double)selectionPulseAmount;
//@}
/*! @name Selection handling
Methods for manipulating the current selected ranges. Hex Fiend supports discontiguous selection.
*/
//{@
/*! An array of HFRangeWrappers, representing the selected ranges. It satisfies the following:
The array is non-nil.
There always is at least one selected range.
If any range has length 0, that range is the only range.
No range extends beyond the contentsLength, with the exception of a single zero-length range at the end.
When setting, the setter MUST obey the above criteria. A zero length range when setting or getting represents the cursor position. */
@property (nonatomic, copy) NSArray *selectedContentsRanges;
/*! Selects the entire contents. */
- (IBAction)selectAll:(id)sender;
/*! Returns the smallest value in the selected contents ranges, or the insertion location if the selection is empty. */
- (unsigned long long)minimumSelectionLocation;
/*! Returns the largest HFMaxRange of the selected contents ranges, or the insertion location if the selection is empty. */
- (unsigned long long)maximumSelectionLocation;
/*! Convenience method for creating a byte array containing all of the selected bytes. If the selection has length 0, this returns an empty byte array. */
- (HFByteArray *)byteArrayForSelectedContentsRanges;
//@}
/* Number of bytes used in each column for a text-style representer. */
@property (nonatomic) NSUInteger bytesPerColumn;
/*! @name Edit Mode
Determines what mode we're in, read-only, overwrite or insert. */
@property (nonatomic) HFEditMode editMode;
/*! @name Displayed line range
Methods for setting and getting the current range of displayed lines.
*/
//{@
/*! Get the current displayed line range. The displayed line range is an HFFPRange (range of long doubles) containing the lines that are currently displayed.
The values may be fractional. That is, if only the bottom half of line 4 through the top two thirds of line 8 is shown, then the displayedLineRange.location will be 4.5 and the displayedLineRange.length will be 3.17 ( = 7.67 - 4.5). Representers are expected to be able to handle such fractional values.
When setting the displayed line range, the given range must be nonnegative, and the maximum of the range must be no larger than the total line count.
*/
@property (nonatomic) HFFPRange displayedLineRange;
/*! Modify the displayedLineRange so that as much of the given range as can fit is visible. If possible, moves by as little as possible so that the visible ranges before and afterward intersect with each other. */
- (void)maximizeVisibilityOfContentsRange:(HFRange)range;
/*! Modify the displayedLineRange as to center the given contents range. If the range is near the bottom or top, this will center as close as possible. If contents range is too large to fit, it centers the top of the range. contentsRange may be empty. */
- (void)centerContentsRange:(HFRange)range;
//@}
/*! The current font. */
@property (nonatomic, copy) NSFont *font;
/*! The undo manager. If no undo manager is set, then undo is not supported. By default the undo manager is nil.
*/
@property (nonatomic, strong) NSUndoManager *undoManager;
/*! Whether the user can edit the document. */
@property (nonatomic) BOOL editable;
/*! Whether the text should be antialiased. Note that Mac OS X settings may prevent antialiasing text below a certain point size. */
@property (nonatomic) BOOL shouldAntialias;
/*! When enabled, characters have a background color that correlates to their byte values. */
@property (nonatomic) BOOL shouldColorBytes;
/*! When enabled, byte bookmarks display callout-style labels attached to them. */
@property (nonatomic) BOOL shouldShowCallouts;
/*! When enabled, null bytes are hidden in the hex view. */
@property (nonatomic) BOOL shouldHideNullBytes;
/*! When enabled, unmodified documents are auto refreshed to their latest on disk state. */
@property (nonatomic) BOOL shouldLiveReload;
/*! Representer initiated property changes
Called from a representer to indicate when some internal property of the representer has changed which requires that some properties be recalculated.
*/
//@{
/*! Callback for a representer-initiated change to some property. For example, if some property of a view changes that would cause the number of bytes per line to change, then the representer should call this method which will trigger the HFController to recompute the relevant properties. */
- (void)representer:(HFRepresenter *)rep changedProperties:(HFControllerPropertyBits)properties;
//@}
/*! @name Mouse selection
Methods to handle mouse selection. Representers that allow text selection should call beginSelectionWithEvent:forByteIndex: upon receiving a mouseDown event, and then continueSelectionWithEvent:forByteIndex: for mouseDragged events, terminating with endSelectionWithEvent:forByteIndex: upon receiving the mouse up. HFController will compute the correct selected ranges and propagate any changes via the HFControllerPropertyBits mechanism. */
//@{
/*! Begin a selection session, with a mouse down at the given byte index. */
- (void)beginSelectionWithEvent:(NSEvent *)event forByteIndex:(unsigned long long)byteIndex;
/*! Continue a selection session, whe the user drags over the given byte index. */
- (void)continueSelectionWithEvent:(NSEvent *)event forByteIndex:(unsigned long long)byteIndex;
/*! End a selection session, with a mouse up at the given byte index. */
- (void)endSelectionWithEvent:(NSEvent *)event forByteIndex:(unsigned long long)byteIndex;
/*! @name Scrollling
Support for the mouse wheel and scroll bars. */
//@{
/*! Trigger scrolling appropriate for the given scroll event. */
- (void)scrollWithScrollEvent:(NSEvent *)scrollEvent;
/*! Trigger scrolling by the given number of lines. If lines is positive, then the document is scrolled down; otherwise it is scrolled up. */
- (void)scrollByLines:(long double)lines;
//@}
/*! @name Keyboard navigation
Support for chaging the selection via the keyboard
*/
/*! General purpose navigation function. Modify the selection in the given direction by the given number of bytes. The selection is modifed according to the given transformation. If useAnchor is set, then anchored selection is used; otherwise any anchor is discarded.
This has a few limitations:
- Only HFControllerDirectionLeft and HFControllerDirectionRight movement directions are supported.
- Anchored selection is not supported for HFControllerShiftSelection (useAnchor must be NO)
*/
- (void)moveInDirection:(HFControllerMovementDirection)direction byByteCount:(unsigned long long)amountToMove withSelectionTransformation:(HFControllerSelectionTransformation)transformation usingAnchor:(BOOL)useAnchor;
/*! Navigation designed for key events. */
- (void)moveInDirection:(HFControllerMovementDirection)direction withGranularity:(HFControllerMovementGranularity)granularity andModifySelection:(BOOL)extendSelection;
- (void)moveToLineBoundaryInDirection:(HFControllerMovementDirection)direction andModifySelection:(BOOL)extendSelection;
/*! @name Text editing
Methods to support common text editing operations */
//@{
/*! Replaces the selection with the given data. For something like a hex view representer, it takes two keypresses to create a whole byte; the way this is implemented, the first keypress goes into the data as a complete byte, and the second one (if any) replaces it. If previousByteCount > 0, then that many prior bytes are replaced, without breaking undo coalescing. For previousByteCount to be > 0, the following must be true: There is only one selected range, and it is of length 0, and its location >= previousByteCount
These functions return YES if they succeed, and NO if they fail. Currently they may fail only in overwrite mode, if you attempt to insert data that would require lengthening the byte array.
These methods are undoable.
*/
- (BOOL)insertByteArray:(HFByteArray *)byteArray replacingPreviousBytes:(unsigned long long)previousByteCount allowUndoCoalescing:(BOOL)allowUndoCoalescing;
- (BOOL)insertData:(NSData *)data replacingPreviousBytes:(unsigned long long)previousByteCount allowUndoCoalescing:(BOOL)allowUndoCoalescing;
/*! Deletes the selection. This operation is undoable. */
- (void)deleteSelection;
/*! If the selection is empty, deletes one byte in a given direction, which must be HFControllerDirectionLeft or HFControllerDirectionRight; if the selection is not empty, deletes the selection. Undoable. */
- (void)deleteDirection:(HFControllerMovementDirection)direction;
//@}
/*! @name Reading data
Methods for reading data */
/*! Returns an NSData representing the given HFRange. The length of the HFRange must be of a size that can reasonably be fit in memory. This method may cache the result. */
- (NSData *)dataForRange:(HFRange)range;
/*! Copies data within the given HFRange into an in-memory buffer. This is equivalent to [[controller byteArray] copyBytes:bytes range:range]. */
- (void)copyBytes:(unsigned char *)bytes range:(HFRange)range;
/*! Returns total number of bytes. This is equivalent to [[controller byteArray] length]. */
- (unsigned long long)contentsLength;
- (void) reloadData;
@end
/*! A notification posted whenever any of the HFController's properties change. The object is the HFController. The userInfo contains one key, HFControllerChangedPropertiesKey, which contains an NSNumber with the changed properties as a HFControllerPropertyBits bitmask. This is useful for external objects to be notified of changes. HFRepresenters added to the HFController are notified via the controllerDidChange: message.
*/
extern NSString * const HFControllerDidChangePropertiesNotification;
/*! @name HFControllerDidChangePropertiesNotification keys
*/
//@{
extern NSString * const HFControllerChangedPropertiesKey; //!< A key in the HFControllerDidChangeProperties containing a bitmask of the changed properties, as a HFControllerPropertyBits
//@}
/*! A notification posted from prepareForChangeInFile:fromWritingByteArray: because we are about to write a ByteArray to a file. The object is the FileReference.
Currently, HFControllers do not listen for this notification. This is because under GC there is no way of knowing whether the controller is live or not. However, pasteboard owners do listen for it, because as long as we own a pasteboard we are guaranteed to be live.
*/
extern NSString * const HFPrepareForChangeInFileNotification;
/*! @name HFPrepareForChangeInFileNotification keys
*/
//@{
extern NSString * const HFChangeInFileByteArrayKey; //!< A key in the HFPrepareForChangeInFileNotification specifying the byte array that will be written
extern NSString * const HFChangeInFileModifiedRangesKey; //!< A key in the HFPrepareForChangeInFileNotification specifying the array of HFRangeWrappers indicating which parts of the file will be modified
extern NSString * const HFChangeInFileShouldCancelKey; //!< A key in the HFPrepareForChangeInFileNotification specifying an NSValue containing a pointer to a BOOL. If set to YES, then someone was unable to prepare and the file should not be saved. It's a good idea to check if this value points to YES; if so your notification handler does not have to do anything.
extern NSString * const HFChangeInFileHintKey; //!< The hint parameter that you may pass to clearDependenciesOnRanges:inFile:hint:
//@}

1903
HexFiend/HFController.m Normal file

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,21 @@
//
// HFFullMemoryByteArray.h
// HexFiend_2
//
// Copyright 2007 ridiculous_fish. All rights reserved.
//
#import <HexFiend/HFByteArray.h>
/*!
@class HFFullMemoryByteArray
@brief A naive subclass of HFByteArray suitable mainly for testing. Use HFBTreeByteArray instead.
HFFullMemoryByteArray is a simple subclass of HFByteArray that does not store any byte slices. Because it stores all data in an NSMutableData, it is not efficient. It is mainly useful as a naive implementation for testing. Use HFBTreeByteArray instead.
*/
@interface HFFullMemoryByteArray : HFByteArray {
NSMutableData *data;
}
@end

View File

@ -0,0 +1,70 @@
//
// HFFullMemoryByteArray.m
// HexFiend_2
//
// Copyright 2007 ridiculous_fish. All rights reserved.
//
#import <HexFiend/HFByteArray_Internal.h>
#import <HexFiend/HFFullMemoryByteArray.h>
#import <HexFiend/HFFullMemoryByteSlice.h>
#import <HexFiend/HFByteSlice.h>
@implementation HFFullMemoryByteArray
- (instancetype)init {
self = [super init];
data = [[NSMutableData alloc] init];
return self;
}
- (void)dealloc {
[data release];
[super dealloc];
}
- (unsigned long long)length {
return [data length];
}
- (void)copyBytes:(unsigned char *)dst range:(HFRange)range {
HFASSERT(range.length == 0 || dst != NULL);
HFASSERT(HFSumDoesNotOverflow(range.location, range.length));
HFASSERT(range.location + range.length <= [self length]);
unsigned char* bytes = [data mutableBytes];
memmove(dst, bytes + ll2l(range.location), ll2l(range.length));
}
- (HFByteArray *)subarrayWithRange:(HFRange)lrange {
HFRange entireRange = HFRangeMake(0, [self length]);
HFASSERT(HFRangeIsSubrangeOfRange(lrange, entireRange));
NSRange range;
range.location = ll2l(lrange.location);
range.length = ll2l(lrange.length);
HFFullMemoryByteArray* result = [[[self class] alloc] init];
[result->data setData:[data subdataWithRange:range]];
return [result autorelease];
}
- (NSArray *)byteSlices {
return @[[[[HFFullMemoryByteSlice alloc] initWithData:data] autorelease]];
}
- (void)insertByteSlice:(HFByteSlice *)slice inRange:(HFRange)lrange {
[self incrementGenerationOrRaiseIfLockedForSelector:_cmd];
HFASSERT([slice length] <= NSUIntegerMax);
NSUInteger length = ll2l([slice length]);
NSRange range;
HFASSERT(lrange.location <= NSUIntegerMax);
HFASSERT(lrange.length <= NSUIntegerMax);
HFASSERT(HFSumDoesNotOverflow(lrange.location, lrange.length));
range.location = ll2l(lrange.location);
range.length = ll2l(lrange.length);
void* buff = check_malloc(length);
[slice copyBytes:buff range:HFRangeMake(0, length)];
[data replaceBytesInRange:range withBytes:buff length:length];
free(buff);
}
@end

View File

@ -0,0 +1,21 @@
//
// HFFullMemoryByteSlice.h
// HexFiend_2
//
// Copyright 2007 ridiculous_fish. All rights reserved.
//
#import <HexFiend/HFByteSlice.h>
/*! @class HFFullMemoryByteSlice
@brief A simple subclass of HFByteSlice that wraps an NSData. For most uses, prefer HFSharedMemoryByteSlice.
*/
@interface HFFullMemoryByteSlice : HFByteSlice {
NSData *data;
}
/*! Init with a given NSData, which is copied via the \c -copy message. */
- (instancetype)initWithData:(NSData *)val;
@end

View File

@ -0,0 +1,46 @@
//
// HFFullMemoryByteSlice.m
// HexFiend_2
//
// Copyright 2007 ridiculous_fish. All rights reserved.
//
#import "HFFullMemoryByteSlice.h"
@implementation HFFullMemoryByteSlice
- (instancetype)initWithData:(NSData *)val {
REQUIRE_NOT_NULL(val);
self = [super init];
data = [val copy];
return self;
}
- (void)dealloc {
[data release];
[super dealloc];
}
- (unsigned long long)length { return [data length]; }
- (void)copyBytes:(unsigned char *)dst range:(HFRange)lrange {
NSRange range;
HFASSERT(lrange.location <= NSUIntegerMax);
HFASSERT(lrange.length <= NSUIntegerMax);
HFASSERT(lrange.location + lrange.length >= lrange.location);
range.location = ll2l(lrange.location);
range.length = ll2l(lrange.length);
[data getBytes:dst range:range];
}
- (HFByteSlice *)subsliceWithRange:(HFRange)range {
HFASSERT(range.length > 0);
HFASSERT(range.location < [self length]);
HFASSERT([self length] - range.location >= range.length);
HFASSERT(range.location <= NSUIntegerMax);
HFASSERT(range.length <= NSUIntegerMax);
return [[[[self class] alloc] initWithData:[data subdataWithRange:NSMakeRange(ll2l(range.location), ll2l(range.length))]] autorelease];
}
@end

533
HexFiend/HFFunctions.h Normal file
View File

@ -0,0 +1,533 @@
/* Functions and convenience methods for working with HFTypes */
#import <HexFiend/HFTypes.h>
#import <libkern/OSAtomic.h>
#define HFDEFAULT_FONT (@"Monaco")
#define HFDEFAULT_FONTSIZE ((CGFloat)10.)
#define HFZeroRange (HFRange){0, 0}
/*!
Makes an HFRange. An HFRange is like an NSRange except it uses unsigned long longs.
*/
static inline HFRange HFRangeMake(unsigned long long loc, unsigned long long len) {
return (HFRange){loc, len};
}
/*!
Returns true if a given location is within a given HFRange. If the location is at the end of the range (range.location + range.length) this returns NO.
*/
static inline BOOL HFLocationInRange(unsigned long long location, HFRange range) {
return location >= range.location && location - range.location < range.length;
}
/*!
Like NSRangeToString but for HFRanges
*/
static inline NSString* HFRangeToString(HFRange range) {
return [NSString stringWithFormat:@"{%llu, %llu}", range.location, range.length];
}
/*!
Converts a given HFFPRange to a string.
*/
static inline NSString* HFFPRangeToString(HFFPRange range) {
return [NSString stringWithFormat:@"{%Lf, %Lf}", range.location, range.length];
}
/*!
Returns true if two HFRanges are equal.
*/
static inline BOOL HFRangeEqualsRange(HFRange a, HFRange b) {
return a.location == b.location && a.length == b.length;
}
/*!
Returns true if a + b does not overflow an unsigned long long.
*/
static inline BOOL HFSumDoesNotOverflow(unsigned long long a, unsigned long long b) {
return a + b >= a;
}
/*!
Returns true if a * b does not overflow an unsigned long long.
*/
static inline BOOL HFProductDoesNotOverflow(unsigned long long a, unsigned long long b) {
if (b == 0) return YES;
unsigned long long result = a * b;
return result / b == a;
}
/*!
Returns a * b as an NSUInteger. This asserts on overflow, unless NDEBUG is defined.
*/
static inline NSUInteger HFProductInt(NSUInteger a, NSUInteger b) {
NSUInteger result = a * b;
assert(a == 0 || result / a == b); //detect overflow
return result;
}
/*!
Returns a + b as an NSUInteger. This asserts on overflow unless NDEBUG is defined.
*/
static inline NSUInteger HFSumInt(NSUInteger a, NSUInteger b) {
assert(a + b >= a);
return a + b;
}
/*!
Returns a + b as an NSUInteger, saturating at NSUIntegerMax
*/
static inline NSUInteger HFSumIntSaturate(NSUInteger a, NSUInteger b) {
NSUInteger result = a + b;
return (result < a) ? NSUIntegerMax : result;
}
/*!
Returns a + b as an unsigned long long, saturating at ULLONG_MAX
*/
static inline unsigned long long HFSumULLSaturate(unsigned long long a, unsigned long long b) {
unsigned long long result = a + b;
return (result < a) ? ULLONG_MAX : result;
}
/*!
Returns a * b as an unsigned long long. This asserts on overflow, unless NDEBUG is defined.
*/
static inline unsigned long long HFProductULL(unsigned long long a, unsigned long long b) {
unsigned long long result = a * b;
assert(HFProductDoesNotOverflow(a, b)); //detect overflow
return result;
}
/*!
Returns a + b as an unsigned long long. This asserts on overflow, unless NDEBUG is defined.
*/
static inline unsigned long long HFSum(unsigned long long a, unsigned long long b) {
assert(HFSumDoesNotOverflow(a, b));
return a + b;
}
/*!
Returns a + b as an unsigned long long. This asserts on overflow, unless NDEBUG is defined.
*/
static inline unsigned long long HFMaxULL(unsigned long long a, unsigned long long b) {
return a < b ? b : a;
}
/*!
Returns a - b as an unsigned long long. This asserts on underflow (if b > a), unless NDEBUG is defined.
*/
static inline unsigned long long HFSubtract(unsigned long long a, unsigned long long b) {
assert(a >= b);
return a - b;
}
/*!
Returns the smallest multiple of B that is equal to or larger than A, and asserts on overflow.
*/
static inline unsigned long long HFRoundUpToMultiple(unsigned long long a, unsigned long long b) {
// The usual approach of ((a + (b - 1)) / b) * b doesn't handle overflow correctly
unsigned long long remainder = a % b;
if (remainder == 0) return a;
else return HFSum(a, b - remainder);
}
/*!
Returns the smallest multiple of B that is equal to or larger than A, and asserts on overflow.
*/
static inline NSUInteger HFRoundUpToMultipleInt(NSUInteger a, NSUInteger b) {
// The usual approach of ((a + (b - 1)) / b) * b doesn't handle overflow correctly
NSUInteger remainder = a % b;
if (remainder == 0) return a;
else return (NSUInteger)HFSum(a, b - remainder);
}
/*!
Returns the least common multiple of A and B, and asserts on overflow or if A or B is zero.
*/
static inline NSUInteger HFLeastCommonMultiple(NSUInteger a, NSUInteger b) {
assert(a > 0);
assert(b > 0);
/* Compute GCD. It ends up in U. */
NSUInteger t, u = a, v = b;
while (v > 0) {
t = v;
v = u % v;
u = t;
}
/* Return the product divided by the GCD, in an overflow safe manner */
return HFProductInt(a/u, b);
}
/*!
Returns the smallest multiple of B strictly larger than A, or ULLONG_MAX if it would overflow
*/
static inline unsigned long long HFRoundUpToNextMultipleSaturate(unsigned long long a, unsigned long long b) {
assert(b > 0);
unsigned long long result = a + (b - a % b);
if (result < a) result = ULLONG_MAX; //the saturation...on overflow go to the max
return result;
}
/*! Like NSMaxRange, but for an HFRange. */
static inline unsigned long long HFMaxRange(HFRange a) {
assert(HFSumDoesNotOverflow(a.location, a.length));
return a.location + a.length;
}
/*! Returns YES if needle is fully contained within haystack. Equal ranges are always considered to be subranges of each other (even if they are empty). Furthermore, a zero length needle at the end of haystack is considered a subrange - for example, {6, 0} is a subrange of {3, 3}. */
static inline BOOL HFRangeIsSubrangeOfRange(HFRange needle, HFRange haystack) {
// If needle starts before haystack, or if needle is longer than haystack, it is not a subrange of haystack
if (needle.location < haystack.location || needle.length > haystack.length) return NO;
// Their difference in lengths determines the maximum difference in their start locations. We know that these expressions cannot overflow because of the above checks.
return haystack.length - needle.length >= needle.location - haystack.location;
}
/*! Splits a range about a subrange, returning by reference the prefix and suffix (which may have length zero). */
static inline void HFRangeSplitAboutSubrange(HFRange range, HFRange subrange, HFRange *outPrefix, HFRange *outSuffix) {
// Requires it to be a subrange
assert(HFRangeIsSubrangeOfRange(subrange, range));
outPrefix->location = range.location;
outPrefix->length = HFSubtract(subrange.location, range.location);
outSuffix->location = HFMaxRange(subrange);
outSuffix->length = HFMaxRange(range) - outSuffix->location;
}
/*! Returns YES if the given ranges intersect. Two ranges are considered to intersect if they share at least one index in common. Thus, zero-length ranges do not intersect anything. */
static inline BOOL HFIntersectsRange(HFRange a, HFRange b) {
// Ranges are said to intersect if they share at least one value. Therefore, zero length ranges never intersect anything.
if (a.length == 0 || b.length == 0) return NO;
// rearrange (a.location < b.location + b.length && b.location < a.location + a.length) to not overflow
// = ! (a.location >= b.location + b.length || b.location >= a.location + a.length)
BOOL clause1 = (a.location >= b.location && a.location - b.location >= b.length);
BOOL clause2 = (b.location >= a.location && b.location - a.location >= a.length);
return ! (clause1 || clause2);
}
/*! Returns YES if the given ranges intersect. Two ranges are considered to intersect if any fraction overlaps; zero-length ranges do not intersect anything. */
static inline BOOL HFFPIntersectsRange(HFFPRange a, HFFPRange b) {
// Ranges are said to intersect if they share at least one value. Therefore, zero length ranges never intersect anything.
if (a.length == 0 || b.length == 0) return NO;
if (a.location <= b.location && a.location + a.length >= b.location) return YES;
if (b.location <= a.location && b.location + b.length >= a.location) return YES;
return NO;
}
/*! Returns a range containing the union of the given ranges. These ranges must either intersect or be adjacent: there cannot be any "holes" between them. */
static inline HFRange HFUnionRange(HFRange a, HFRange b) {
assert(HFIntersectsRange(a, b) || HFMaxRange(a) == b.location || HFMaxRange(b) == a.location);
HFRange result;
result.location = MIN(a.location, b.location);
assert(HFSumDoesNotOverflow(a.location, a.length));
assert(HFSumDoesNotOverflow(b.location, b.length));
result.length = MAX(a.location + a.length, b.location + b.length) - result.location;
return result;
}
/*! Returns whether a+b > c+d, as if there were no overflow (so ULLONG_MAX + 1 > 10 + 20) */
static inline BOOL HFSumIsLargerThanSum(unsigned long long a, unsigned long long b, unsigned long long c, unsigned long long d) {
#if 1
// Theory: compare a/2 + b/2 to c/2 + d/2, and if they're equal, compare a%2 + b%2 to c%2 + d%2. We may get into trouble if a and b are both even and c and d are both odd: e.g. a = 2, b = 2, c = 1, d = 3. We would compare 1 + 1 vs 0 + 1, and therefore that 2 + 2 > 1 + 3. To address this, if both remainders are 1, we add this to the sum. We know this cannot overflow because ULLONG_MAX is odd, so (ULLONG_MAX/2) + (ULLONG_MAX/2) + 1 does not overflow.
unsigned int rem1 = (unsigned)(a%2 + b%2);
unsigned int rem2 = (unsigned)(c%2 + d%2);
unsigned long long sum1 = a/2 + b/2 + rem1/2;
unsigned long long sum2 = c/2 + d/2 + rem2/2;
if (sum1 > sum2) return YES;
else if (sum1 < sum2) return NO;
else {
// sum1 == sum2, so compare the remainders. But we have already added in the remainder / 2, so compare the remainders mod 2.
if (rem1%2 > rem2%2) return YES;
else return NO;
}
#else
/* Faster version, but not thoroughly tested yet. */
unsigned long long xor1 = a^b;
unsigned long long xor2 = c^d;
unsigned long long avg1 = (a&b)+(xor1/2);
unsigned long long avg2 = (c&d)+(xor2/2);
unsigned s1l = avg1 > avg2;
unsigned eq = (avg1 == avg2);
return s1l | ((xor1 & ~xor2) & eq);
#endif
}
/*! Returns the absolute value of a - b. */
static inline unsigned long long HFAbsoluteDifference(unsigned long long a, unsigned long long b) {
if (a > b) return a - b;
else return b - a;
}
/*! Returns true if the end of A is larger than the end of B. */
static inline BOOL HFRangeExtendsPastRange(HFRange a, HFRange b) {
return HFSumIsLargerThanSum(a.location, a.length, b.location, b.length);
}
/*! Returns a range containing all indexes in common betwen the two ranges. If there are no indexes in common, returns {0, 0}. */
static inline HFRange HFIntersectionRange(HFRange range1, HFRange range2) {
unsigned long long minend = HFRangeExtendsPastRange(range2, range1) ? range1.location + range1.length : range2.location + range2.length;
if (range2.location <= range1.location && range1.location - range2.location < range2.length) {
return HFRangeMake(range1.location, minend - range1.location);
}
else if (range1.location <= range2.location && range2.location - range1.location < range1.length) {
return HFRangeMake(range2.location, minend - range2.location);
}
return HFRangeMake(0, 0);
}
/*! ceil() for a CGFloat, for compatibility with OSes that do not have the CG versions. */
static inline CGFloat HFCeil(CGFloat a) {
if (sizeof(a) == sizeof(float)) return (CGFloat)ceilf((float)a);
else return (CGFloat)ceil((double)a);
}
/*! floor() for a CGFloat, for compatibility with OSes that do not have the CG versions. */
static inline CGFloat HFFloor(CGFloat a) {
if (sizeof(a) == sizeof(float)) return (CGFloat)floorf((float)a);
else return (CGFloat)floor((double)a);
}
/*! round() for a CGFloat, for compatibility with OSes that do not have the CG versions. */
static inline CGFloat HFRound(CGFloat a) {
if (sizeof(a) == sizeof(float)) return (CGFloat)roundf((float)a);
else return (CGFloat)round((double)a);
}
/*! fmin() for a CGFloat, for compatibility with OSes that do not have the CG versions. */
static inline CGFloat HFMin(CGFloat a, CGFloat b) {
if (sizeof(a) == sizeof(float)) return (CGFloat)fminf((float)a, (float)b);
else return (CGFloat)fmin((double)a, (double)b);
}
/*! fmax() for a CGFloat, for compatibility with OSes that do not have the CG versions. */
static inline CGFloat HFMax(CGFloat a, CGFloat b) {
if (sizeof(a) == sizeof(float)) return (CGFloat)fmaxf((float)a, (float)b);
else return (CGFloat)fmax((double)a, (double)b);
}
/*! Returns true if the given HFFPRanges are equal. */
static inline BOOL HFFPRangeEqualsRange(HFFPRange a, HFFPRange b) {
return a.location == b.location && a.length == b.length;
}
/*! copysign() for a CGFloat */
static inline CGFloat HFCopysign(CGFloat a, CGFloat b) {
#if CGFLOAT_IS_DOUBLE
return copysign(a, b);
#else
return copysignf(a, b);
#endif
}
/*! Atomically increments an NSUInteger, returning the new value. Optionally invokes a memory barrier. */
static inline NSUInteger HFAtomicIncrement(volatile NSUInteger *ptr, BOOL barrier) {
return _Generic(ptr,
volatile unsigned *: (barrier ? OSAtomicIncrement32Barrier : OSAtomicIncrement32)((volatile int32_t *)ptr),
#if ULONG_MAX == UINT32_MAX
volatile unsigned long *: (barrier ? OSAtomicIncrement32Barrier : OSAtomicIncrement32)((volatile int32_t *)ptr),
#else
volatile unsigned long *: (barrier ? OSAtomicIncrement64Barrier : OSAtomicIncrement64)((volatile int64_t *)ptr),
#endif
volatile unsigned long long *: (barrier ? OSAtomicIncrement64Barrier : OSAtomicIncrement64)((volatile int64_t *)ptr));
}
/*! Atomically decrements an NSUInteger, returning the new value. Optionally invokes a memory barrier. */
static inline NSUInteger HFAtomicDecrement(volatile NSUInteger *ptr, BOOL barrier) {
return _Generic(ptr,
volatile unsigned *: (barrier ? OSAtomicDecrement32Barrier : OSAtomicDecrement32)((volatile int32_t *)ptr),
#if ULONG_MAX == UINT32_MAX
volatile unsigned long *: (barrier ? OSAtomicDecrement32Barrier : OSAtomicDecrement32)((volatile int32_t *)ptr),
#else
volatile unsigned long *: (barrier ? OSAtomicDecrement64Barrier : OSAtomicDecrement64)((volatile int64_t *)ptr),
#endif
volatile unsigned long long *: (barrier ? OSAtomicDecrement64Barrier : OSAtomicDecrement64)((volatile int64_t *)ptr));
}
/*! Converts a long double to unsigned long long. Assumes that val is already an integer - use floorl or ceill */
static inline unsigned long long HFFPToUL(long double val) {
assert(val >= 0);
assert(val <= ULLONG_MAX);
unsigned long long result = (unsigned long long)val;
assert((long double)result == val);
return result;
}
/*! Converts an unsigned long long to a long double. */
static inline long double HFULToFP(unsigned long long val) {
long double result = (long double)val;
assert(HFFPToUL(result) == val);
return result;
}
/*! Convenience to return information about a CGAffineTransform for logging. */
static inline NSString *HFDescribeAffineTransform(CGAffineTransform t) {
return [NSString stringWithFormat:@"%f %f 0\n%f %f 0\n%f %f 1", t.a, t.b, t.c, t.d, t.tx, t.ty];
}
/*! Returns 1 + floor(log base 10 of val). If val is 0, returns 1. */
static inline NSUInteger HFCountDigitsBase10(unsigned long long val) {
const unsigned long long kValues[] = {0ULL, 9ULL, 99ULL, 999ULL, 9999ULL, 99999ULL, 999999ULL, 9999999ULL, 99999999ULL, 999999999ULL, 9999999999ULL, 99999999999ULL, 999999999999ULL, 9999999999999ULL, 99999999999999ULL, 999999999999999ULL, 9999999999999999ULL, 99999999999999999ULL, 999999999999999999ULL, 9999999999999999999ULL};
NSUInteger low = 0, high = sizeof kValues / sizeof *kValues;
while (high > low) {
NSUInteger mid = (low + high)/2; //low + high cannot overflow
if (val > kValues[mid]) {
low = mid + 1;
}
else {
high = mid;
}
}
return MAX(1u, low);
}
/*! Returns 1 + floor(log base 16 of val). If val is 0, returns 1. This works by computing the log base 2 based on the number of leading zeros, and then dividing by 4. */
static inline NSUInteger HFCountDigitsBase16(unsigned long long val) {
/* __builtin_clzll doesn't like being passed 0 */
if (val == 0) return 1;
/* Compute the log base 2 */
NSUInteger leadingZeros = (NSUInteger)__builtin_clzll(val);
NSUInteger logBase2 = (CHAR_BIT * sizeof val) - leadingZeros - 1;
return 1 + logBase2/4;
}
/*! Returns YES if the given string encoding is a superset of ASCII. */
BOOL HFStringEncodingIsSupersetOfASCII(NSStringEncoding encoding);
/*! Returns the "granularity" of an encoding, in bytes. ASCII is 1, UTF-16 is 2, etc. Variable width encodings return the smallest (e.g. Shift-JIS returns 1). */
uint8_t HFStringEncodingCharacterLength(NSStringEncoding encoding);
/*! Converts an unsigned long long to NSUInteger. The unsigned long long should be no more than ULONG_MAX. */
static inline NSUInteger ll2l(unsigned long long val) { assert(val <= ULONG_MAX); return (unsigned long)val; }
/*! Converts an unsigned long long to uintptr_t. The unsigned long long should be no more than UINTPTR_MAX. */
static inline uintptr_t ll2p(unsigned long long val) { assert(val <= UINTPTR_MAX); return (uintptr_t)val; }
/*! Returns an unsigned long long, which must be no more than ULLONG_MAX, as an unsigned long. */
static inline CGFloat ld2f(long double val) {
#if ! NDEBUG
if (isfinite(val)) {
assert(val <= CGFLOAT_MAX);
assert(val >= -CGFLOAT_MAX);
if ((val > 0 && val < CGFLOAT_MIN) || (val < 0 && val > -CGFLOAT_MIN)) {
NSLog(@"Warning - conversion of long double %Lf to CGFloat will result in the non-normal CGFloat %f", val, (CGFloat)val);
}
}
#endif
return (CGFloat)val;
}
/*! Returns the quotient of a divided by b, rounding up, for unsigned long longs. Will not overflow. */
static inline unsigned long long HFDivideULLRoundingUp(unsigned long long a, unsigned long long b) {
if (a == 0) return 0;
else return ((a - 1) / b) + 1;
}
/*! Returns the quotient of a divided by b, rounding up, for NSUIntegers. Will not overflow. */
static inline NSUInteger HFDivideULRoundingUp(NSUInteger a, NSUInteger b) {
if (a == 0) return 0;
else return ((a - 1) / b) + 1;
}
/*! Draws a shadow. */
void HFDrawShadow(CGContextRef context, NSRect rect, CGFloat size, NSRectEdge rectEdge, BOOL active, NSRect clip);
/*! Registers a view to have the given notificationSEL invoked (taking the NSNotification object) when the window becomes or loses key. If appToo is YES, this also registers with NSApplication for Activate and Deactivate methods. */
void HFRegisterViewForWindowAppearanceChanges(NSView *view, SEL notificationSEL, BOOL appToo);
/*! Unregisters a view to have the given notificationSEL invoked when the window becomes or loses key. If appToo is YES, this also unregisters with NSApplication. */
void HFUnregisterViewForWindowAppearanceChanges(NSView *view, BOOL appToo);
/*! Returns a description of the given byte count (e.g. "24 kilobytes") */
NSString *HFDescribeByteCount(unsigned long long count);
/*! @brief An object wrapper for the HFRange type.
A simple class responsible for holding an immutable HFRange as an object. Methods that logically work on multiple HFRanges usually take or return arrays of HFRangeWrappers. */
@interface HFRangeWrapper : NSObject <NSCopying> {
@public
HFRange range;
}
/*! Returns the HFRange for this HFRangeWrapper. */
- (HFRange)HFRange;
/*! Creates an autoreleased HFRangeWrapper for this HFRange. */
+ (HFRangeWrapper *)withRange:(HFRange)range;
/*! Creates an NSArray of HFRangeWrappers for this HFRange. */
+ (NSArray *)withRanges:(const HFRange *)ranges count:(NSUInteger)count;
/*! Given an NSArray of HFRangeWrappers, get all of the HFRanges into a C array. */
+ (void)getRanges:(HFRange *)ranges fromArray:(NSArray *)array;
/*! Given an array of HFRangeWrappers, returns a "cleaned up" array of equivalent ranges. This new array represents the same indexes, but overlapping ranges will have been merged, and the ranges will be sorted in ascending order. */
+ (NSArray *)organizeAndMergeRanges:(NSArray *)inputRanges;
@end
/*! @brief A set of HFRanges. HFRangeSet takes the interpetation that all zero-length ranges are identical.
Essentially, a mutable array of ranges that is maintained to be sorted and minimized (i.e. merged with overlapping neighbors).
TODO: The HexFiend codebase currently uses arrays of HFRangeWrappers that have been run through organizeAndMergeRanges:, and not HFRangeSet. The advantage of HFRangeSet is that the sorting & merging is implied by the type, instead of just tacitly assumed. This should lead to less confusion and fewer extra applications of organizeAndMergeRanges.
TODO: HFRangeSet needs to be tested! I guarantee it has bugs! (Which doesn't matter right now because it's all dead code...)
*/
@interface HFRangeSet : NSObject <NSCopying, NSSecureCoding, NSFastEnumeration> {
@private
CFMutableArrayRef array;
}
/*! Create a range set with just one range. */
+ (HFRangeSet *)withRange:(HFRange)range;
/*! Create a range set with a C array of ranges. No prior sorting is necessary. */
+ (HFRangeSet *)withRanges:(const HFRange *)ranges count:(NSUInteger)count;
/*! Create a range set with an array of HFRangeWrappers. No prior sorting is necessary. */
+ (HFRangeSet *)withRangeWrappers:(NSArray *)ranges;
/*! Create a range set as a copy of another. */
+ (HFRangeSet *)withRangeSet:(HFRangeSet *)rangeSet;
/*! Equivalent to HFRangeSet *x = [HFRangeSet withRange:range]; [x removeRange:rangeSet]; */
+ (HFRangeSet *)complementOfRangeSet:(HFRangeSet *)rangeSet inRange:(HFRange)range;
- (void)addRange:(HFRange)range; /*!< Union with range */
- (void)removeRange:(HFRange)range; /*!< Subtract range */
- (void)clipToRange:(HFRange)range; /*!< Intersect with range */
- (void)toggleRange:(HFRange)range; /*!< Symmetric difference with range */
- (void)addRangeSet:(HFRangeSet *)rangeSet; /*!< Union with range set */
- (void)removeRangeSet:(HFRangeSet *)rangeSet; /*!< Subtract range set */
- (void)clipToRangeSet:(HFRangeSet *)rangeSet; /*!< Intersect with range set */
- (void)toggleRangeSet:(HFRangeSet *)rangeSet; /*!< Symmetric difference with range set */
- (BOOL)isEqualToRangeSet:(HFRangeSet *)rangeSet; /*!< Test if two range sets are equivalent. */
- (BOOL)isEmpty; /*!< Test if range set is empty. */
- (BOOL)containsAllRange:(HFRange)range; /*!< Check if the range set covers all of a range. Always true if 'range' is zero length. */
- (BOOL)overlapsAnyRange:(HFRange)range; /*!< Check if the range set covers any of a range. Never true if 'range' is zero length. */
- (BOOL)containsAllRangeSet:(HFRangeSet *)rangeSet; /*!< Check if this range is a superset of another. */
- (BOOL)overlapsAnyRangeSet:(HFRangeSet *)rangeSet; /*!< Check if this range has a nonempty intersection with another. */
- (HFRange)spanningRange; /*!< Return a single range that covers the entire range set */
- (void)assertIntegrity;
@end
#ifndef NDEBUG
void HFStartTiming(const char *name);
void HFStopTiming(void);
#endif

1172
HexFiend/HFFunctions.m Normal file

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,52 @@
#import <Cocoa/Cocoa.h>
@class HFController;
static inline BOOL HFIsRunningOnMountainLionOrLater(void) {
return NSAppKitVersionNumber >= NSAppKitVersionNumber10_8;
}
/* Returns the first index where the strings differ. If the strings do not differ in any characters but are of different lengths, returns the smaller length; if they are the same length and do not differ, returns NSUIntegerMax */
static inline NSUInteger HFIndexOfFirstByteThatDiffers(const unsigned char *a, NSUInteger len1, const unsigned char *b, NSUInteger len2) {
NSUInteger endIndex = MIN(len1, len2);
for (NSUInteger i = 0; i < endIndex; i++) {
if (a[i] != b[i]) return i;
}
if (len1 != len2) return endIndex;
return NSUIntegerMax;
}
/* Returns the last index where the strings differ. If the strings do not differ in any characters but are of different lengths, returns the larger length; if they are the same length and do not differ, returns NSUIntegerMax */
static inline NSUInteger HFIndexOfLastByteThatDiffers(const unsigned char *a, NSUInteger len1, const unsigned char *b, NSUInteger len2) {
if (len1 != len2) return MAX(len1, len2);
NSUInteger i = len1;
while (i--) {
if (a[i] != b[i]) return i;
}
return NSUIntegerMax;
}
static inline unsigned long long llmin(unsigned long long a, unsigned long long b) {
return a < b ? a : b;
}
__private_extern__ NSImage *HFImageNamed(NSString *name);
/* Returns an NSData from an NSString containing hexadecimal characters. Characters that are not hexadecimal digits are silently skipped. Returns by reference whether the last byte contains only one nybble, in which case it will be returned in the low 4 bits of the last byte. */
__private_extern__ NSData *HFDataFromHexString(NSString *string, BOOL* isMissingLastNybble);
__private_extern__ NSString *HFHexStringFromData(NSData *data);
/* Modifies F_NOCACHE for a given file descriptor */
__private_extern__ void HFSetFDShouldCache(int fd, BOOL shouldCache);
__private_extern__ NSString *HFDescribeByteCountWithPrefixAndSuffix(const char *stringPrefix, unsigned long long count, const char *stringSuffix);
/* Function for OSAtomicAdd64 that just does a non-atomic add on PowerPC. This should not be used where atomicity is critical; an example where this is used is updating a progress bar. */
static inline int64_t HFAtomicAdd64(int64_t a, volatile int64_t *b) {
#if __ppc__
return *b += a;
#else
return OSAtomicAdd64(a, b);
#endif
}

49
HexFiend/HFGlyphTrie.h Normal file
View File

@ -0,0 +1,49 @@
/* HFGlyphTrie is used to represent a trie of glyphs that allows multiple concurrent readers, along with one writer. */
#import <ApplicationServices/ApplicationServices.h>
/* BranchFactor is in bits */
#define kHFGlyphTrieBranchFactor 4
#define kHFGlyphTrieBranchCount (1 << kHFGlyphTrieBranchFactor)
typedef uint16_t HFGlyphFontIndex;
#define kHFGlyphFontIndexInvalid ((HFGlyphFontIndex)(-1))
#define kHFGlyphInvalid kCGFontIndexInvalid
struct HFGlyph_t {
HFGlyphFontIndex fontIndex;
CGGlyph glyph;
};
static inline BOOL HFGlyphEqualsGlyph(struct HFGlyph_t a, struct HFGlyph_t b) __attribute__((unused));
static inline BOOL HFGlyphEqualsGlyph(struct HFGlyph_t a, struct HFGlyph_t b) {
return a.glyph == b.glyph && a.fontIndex == b.fontIndex;
}
struct HFGlyphTrieBranch_t {
__strong void *children[kHFGlyphTrieBranchCount];
};
struct HFGlyphTrieLeaf_t {
struct HFGlyph_t glyphs[kHFGlyphTrieBranchCount];
};
struct HFGlyphTrie_t {
uint8_t branchingDepth;
struct HFGlyphTrieBranch_t root;
};
/* Initializes a trie witha given key size */
__private_extern__ void HFGlyphTrieInitialize(struct HFGlyphTrie_t *trie, uint8_t keySize);
/* Inserts a glyph into the trie */
__private_extern__ void HFGlyphTrieInsert(struct HFGlyphTrie_t *trie, NSUInteger key, struct HFGlyph_t value);
/* Attempts to fetch a glyph. If the glyph is not present, returns an HFGlyph_t set to all bits 0. */
__private_extern__ struct HFGlyph_t HFGlyphTrieGet(const struct HFGlyphTrie_t *trie, NSUInteger key);
/* Frees all storage associated with a glyph tree. This is not necessary to call under GC. */
__private_extern__ void HFGlyphTreeFree(struct HFGlyphTrie_t * trie);

93
HexFiend/HFGlyphTrie.m Normal file
View File

@ -0,0 +1,93 @@
#import "HFGlyphTrie.h"
#import <objc/objc-auto.h>
/* If branchingDepth is 1, then this is a leaf and there's nothing to free (a parent frees its children). If branchingDepth is 2, then this is a branch whose children are leaves, so we have to free the leaves but we do not recurse. If branchingDepth is greater than 2, we do have to recurse. */
static void freeTrie(struct HFGlyphTrieBranch_t *branch, uint8_t branchingDepth) {
HFASSERT(branchingDepth >= 1);
NSUInteger i;
if (branchingDepth > 2) {
/* Recurse */
for (i=0; i < kHFGlyphTrieBranchCount; i++) {
if (branch->children[i]) {
freeTrie(branch->children[i], branchingDepth - 1);
}
}
}
if (branchingDepth > 1) {
/* Free our children */
for (i=0; i < kHFGlyphTrieBranchCount; i++) {
free(branch->children[i]);
}
}
}
static void insertTrie(void *node, uint8_t branchingDepth, NSUInteger key, struct HFGlyph_t value) {
HFASSERT(node != NULL);
HFASSERT(branchingDepth >= 1);
if (branchingDepth == 1) {
/* Leaf */
HFASSERT(key < kHFGlyphTrieBranchCount);
((struct HFGlyphTrieLeaf_t *)node)->glyphs[key] = value;
} else {
/* Branch */
struct HFGlyphTrieBranch_t *branch = node;
NSUInteger keySlice = key & ((1 << kHFGlyphTrieBranchFactor) - 1), keyRemainder = key >> kHFGlyphTrieBranchFactor;
__strong void *child = branch->children[keySlice];
if (child == NULL) {
if (branchingDepth == 2) {
child = calloc(1, sizeof(struct HFGlyphTrieLeaf_t));
} else {
child = calloc(1, sizeof(struct HFGlyphTrieBranch_t));
}
/* We just zeroed out a block of memory and we are about to write its address somewhere where another thread could read it, so we need a memory barrier. */
OSMemoryBarrier();
branch->children[keySlice] = child;
}
insertTrie(child, branchingDepth - 1, keyRemainder, value);
}
}
static struct HFGlyph_t getTrie(const void *node, uint8_t branchingDepth, NSUInteger key) {
HFASSERT(node != NULL);
HFASSERT(branchingDepth >= 1);
if (branchingDepth == 1) {
/* Leaf */
HFASSERT(key < kHFGlyphTrieBranchCount);
return ((const struct HFGlyphTrieLeaf_t *)node)->glyphs[key];
} else {
/* Branch */
const struct HFGlyphTrieBranch_t *branch = node;
NSUInteger keySlice = key & ((1 << kHFGlyphTrieBranchFactor) - 1), keyRemainder = key >> kHFGlyphTrieBranchFactor;
if (branch->children[keySlice] == NULL) {
/* Not found */
return (struct HFGlyph_t){0, 0};
} else {
/* This dereference requires a data dependency barrier */
return getTrie(branch->children[keySlice], branchingDepth - 1, keyRemainder);
}
}
}
void HFGlyphTrieInsert(struct HFGlyphTrie_t *trie, NSUInteger key, struct HFGlyph_t value) {
insertTrie(&trie->root, trie->branchingDepth, key, value);
}
struct HFGlyph_t HFGlyphTrieGet(const struct HFGlyphTrie_t *trie, NSUInteger key) {
struct HFGlyph_t result = getTrie(&trie->root, trie->branchingDepth, key);
return result;
}
void HFGlyphTrieInitialize(struct HFGlyphTrie_t *trie, uint8_t keySize) {
/* If the branch factor is 4 (bits) and the key size is 2 bytes = 16 bits, initialize branching depth to 16/4 = 4 */
uint8_t keyBits = keySize * CHAR_BIT;
HFASSERT(keyBits % kHFGlyphTrieBranchFactor == 0);
trie->branchingDepth = keyBits / kHFGlyphTrieBranchFactor;
memset(&trie->root, 0, sizeof(trie->root));
}
void HFGlyphTreeFree(struct HFGlyphTrie_t * trie) {
/* Don't try to free under GC. And don't free if it's never been initialized. */
if (trie->branchingDepth > 0) {
freeTrie(&trie->root, trie->branchingDepth);
}
}

View File

@ -0,0 +1,21 @@
//
// HFHexTextRepresenter.h
// HexFiend_2
//
// Copyright 2007 ridiculous_fish. All rights reserved.
//
#import <HexFiend/HFTextRepresenter.h>
/*! @class HFHexTextRepresenter
@brief HFHexTextRepresenter is an HFRepresenter responsible for showing data in hexadecimal form.
HFHexTextRepresenter is an HFRepresenter responsible for showing data in hexadecimal form. It has no methods except those inherited from HFTextRepresenter.
*/
@interface HFHexTextRepresenter : HFTextRepresenter {
unsigned long long omittedNybbleLocation;
unsigned char unpartneredLastNybble;
}
@end

View File

@ -0,0 +1,203 @@
//
// HFHexTextRepresenter.m
// HexFiend_2
//
// Copyright 2007 ridiculous_fish. All rights reserved.
//
#import <HexFiend/HFHexTextRepresenter.h>
#import <HexFiend/HFRepresenterHexTextView.h>
#import <HexFiend/HFPasteboardOwner.h>
@interface HFHexPasteboardOwner : HFPasteboardOwner {
NSUInteger _bytesPerColumn;
}
@property (nonatomic) NSUInteger bytesPerColumn;
@end
static inline unsigned char hex2char(NSUInteger c) {
HFASSERT(c < 16);
return "0123456789ABCDEF"[c];
}
@implementation HFHexPasteboardOwner
@synthesize bytesPerColumn = _bytesPerColumn;
- (unsigned long long)stringLengthForDataLength:(unsigned long long)dataLength {
if(!dataLength) return 0;
// -1 because no trailing space for an exact multiple.
unsigned long long spaces = _bytesPerColumn ? (dataLength-1)/_bytesPerColumn : 0;
if ((ULLONG_MAX - spaces)/2 <= dataLength) return ULLONG_MAX;
else return dataLength*2 + spaces;
}
- (void)writeDataInBackgroundToPasteboard:(NSPasteboard *)pboard ofLength:(unsigned long long)length forType:(NSString *)type trackingProgress:(id)tracker {
HFASSERT([type isEqual:NSStringPboardType]);
if(length == 0) {
[pboard setString:@"" forType:type];
return;
}
HFByteArray *byteArray = [self byteArray];
HFASSERT(length <= NSUIntegerMax);
NSUInteger dataLength = ll2l(length);
NSUInteger stringLength = ll2l([self stringLengthForDataLength:length]);
HFASSERT(stringLength < ULLONG_MAX);
NSUInteger offset = 0, stringOffset = 0, remaining = dataLength;
unsigned char * restrict const stringBuffer = check_malloc(stringLength);
while (remaining > 0) {
unsigned char dataBuffer[64 * 1024];
NSUInteger amountToCopy = MIN(sizeof dataBuffer, remaining);
NSUInteger bound = offset + amountToCopy - 1;
[byteArray copyBytes:dataBuffer range:HFRangeMake(offset, amountToCopy)];
if(_bytesPerColumn > 0 && offset > 0) { // ensure offset > 0 to skip adding a leading space
NSUInteger left = _bytesPerColumn - (offset % _bytesPerColumn);
if(left != _bytesPerColumn) {
while(left-- > 0 && offset <= bound) {
unsigned char c = dataBuffer[offset++];
stringBuffer[stringOffset] = hex2char(c >> 4);
stringBuffer[stringOffset + 1] = hex2char(c & 0xF);
stringOffset += 2;
}
}
if(offset <= bound)
stringBuffer[stringOffset++] = ' ';
}
if(_bytesPerColumn > 0) while(offset+_bytesPerColumn <= bound) {
for(NSUInteger j = 0; j < _bytesPerColumn; j++) {
unsigned char c = dataBuffer[offset++];
stringBuffer[stringOffset] = hex2char(c >> 4);
stringBuffer[stringOffset + 1] = hex2char(c & 0xF);
stringOffset += 2;
}
stringBuffer[stringOffset++] = ' ';
}
while (offset <= bound) {
unsigned char c = dataBuffer[offset++];
stringBuffer[stringOffset] = hex2char(c >> 4);
stringBuffer[stringOffset + 1] = hex2char(c & 0xF);
stringOffset += 2;
}
remaining -= amountToCopy;
}
NSString *string = [[NSString alloc] initWithBytesNoCopy:stringBuffer length:stringLength encoding:NSASCIIStringEncoding freeWhenDone:YES];
[pboard setString:string forType:type];
[string release];
}
@end
@implementation HFHexTextRepresenter
/* No extra NSCoder support needed */
- (Class)_textViewClass {
return [HFRepresenterHexTextView class];
}
- (void)initializeView {
[super initializeView];
[[self view] setBytesBetweenVerticalGuides:4];
unpartneredLastNybble = UCHAR_MAX;
omittedNybbleLocation = ULLONG_MAX;
}
+ (NSPoint)defaultLayoutPosition {
return NSMakePoint(0, 0);
}
- (void)_clearOmittedNybble {
unpartneredLastNybble = UCHAR_MAX;
omittedNybbleLocation = ULLONG_MAX;
}
- (BOOL)_insertionShouldDeleteLastNybble {
/* Either both the omittedNybbleLocation and unpartneredLastNybble are invalid (set to their respective maxima), or neither are */
HFASSERT((omittedNybbleLocation == ULLONG_MAX) == (unpartneredLastNybble == UCHAR_MAX));
/* We should delete the last nybble if our omittedNybbleLocation is the point where we would insert */
BOOL result = NO;
if (omittedNybbleLocation != ULLONG_MAX) {
HFController *controller = [self controller];
NSArray *selectedRanges = [controller selectedContentsRanges];
if ([selectedRanges count] == 1) {
HFRange selectedRange = [selectedRanges[0] HFRange];
result = (selectedRange.length == 0 && selectedRange.location > 0 && selectedRange.location - 1 == omittedNybbleLocation);
}
}
return result;
}
- (BOOL)_canInsertText:(NSString *)text {
REQUIRE_NOT_NULL(text);
NSCharacterSet *characterSet = [NSCharacterSet characterSetWithCharactersInString:@"0123456789ABCDEFabcdef"];
return [text rangeOfCharacterFromSet:characterSet].location != NSNotFound;
}
- (void)insertText:(NSString *)text {
REQUIRE_NOT_NULL(text);
if (! [self _canInsertText:text]) {
/* The user typed invalid data, and we can ignore it */
return;
}
BOOL shouldReplacePriorByte = [self _insertionShouldDeleteLastNybble];
if (shouldReplacePriorByte) {
HFASSERT(unpartneredLastNybble < 16);
/* Prepend unpartneredLastNybble as a nybble */
text = [NSString stringWithFormat:@"%1X%@", unpartneredLastNybble, text];
}
BOOL isMissingLastNybble;
NSData *data = HFDataFromHexString(text, &isMissingLastNybble);
HFASSERT([data length] > 0);
HFASSERT(shouldReplacePriorByte != isMissingLastNybble);
HFController *controller = [self controller];
BOOL success = [controller insertData:data replacingPreviousBytes: (shouldReplacePriorByte ? 1 : 0) allowUndoCoalescing:YES];
if (isMissingLastNybble && success) {
HFASSERT([data length] > 0);
HFASSERT(unpartneredLastNybble == UCHAR_MAX);
[data getBytes:&unpartneredLastNybble range:NSMakeRange([data length] - 1, 1)];
NSArray *selectedRanges = [controller selectedContentsRanges];
HFASSERT([selectedRanges count] >= 1);
HFRange selectedRange = [selectedRanges[0] HFRange];
HFASSERT(selectedRange.location > 0);
omittedNybbleLocation = HFSubtract(selectedRange.location, 1);
}
else {
[self _clearOmittedNybble];
}
}
- (NSData *)dataFromPasteboardString:(NSString *)string {
REQUIRE_NOT_NULL(string);
return HFDataFromHexString(string, NULL);
}
- (void)controllerDidChange:(HFControllerPropertyBits)bits {
if (bits & HFControllerHideNullBytes) {
[[self view] setHidesNullBytes:[[self controller] shouldHideNullBytes]];
}
[super controllerDidChange:bits];
if (bits & (HFControllerContentValue | HFControllerContentLength | HFControllerSelectedRanges)) {
[self _clearOmittedNybble];
}
}
- (void)copySelectedBytesToPasteboard:(NSPasteboard *)pb {
REQUIRE_NOT_NULL(pb);
HFByteArray *selection = [[self controller] byteArrayForSelectedContentsRanges];
HFASSERT(selection != NULL);
if ([selection length] == 0) {
NSBeep();
} else {
HFHexPasteboardOwner *owner = [HFHexPasteboardOwner ownPasteboard:pb forByteArray:selection withTypes:@[HFPrivateByteArrayPboardType, NSStringPboardType]];
[owner setBytesPerLine:[self bytesPerLine]];
owner.bytesPerColumn = self.bytesPerColumn;
}
}
@end

15
HexFiend/HFHexTextView.h Normal file
View File

@ -0,0 +1,15 @@
//
// HFHexTextView.h
// HexFiend_2
//
// Copyright 2007 __MyCompanyName__. All rights reserved.
//
#import <Cocoa/Cocoa.h>
@interface HFHexTextView : NSTextView {
}
@end

13
HexFiend/HFHexTextView.m Normal file
View File

@ -0,0 +1,13 @@
//
// HFHexTextView.m
// HexFiend_2
//
// Copyright 2007 __MyCompanyName__. All rights reserved.
//
#import "HFHexTextView.h"
@implementation HFHexTextView
@end

View File

@ -0,0 +1,80 @@
//
// HFLayoutRepresenter.h
// HexFiend_2
//
// Copyright 2007 ridiculous_fish. All rights reserved.
//
#import <HexFiend/HFRepresenter.h>
/*! @class HFLayoutRepresenter
@brief An HFRepresenter responsible for arranging the views of other HFRepresenters attached to the same HFController.
HFLayoutRepresenter is an HFRepresenter that manages the views of other HFRepresenters. It arranges their views in its own view, mediating between them to determine their position and size, as well as global properties such as bytes per line.
HFLayoutRepresenter has an array of representers attached to it. When you add an HFRepresenter to this array, HFLayoutRepresenter will add the view of the representer as a subview of its own view.
\b Layout
HFLayoutRepresenter is capable of arranging the views of other HFRepresenters to fit within the bounds of its view. The layout process depends on three things:
-# The \c frame and \c autoresizingMask of the representers' views.
-# The \c minimumViewWidthForBytesPerLine: method, which determines the largest number of bytes per line that the representer can display for a given view width.
-# The representer's \c layoutPosition. This is an NSPoint, but it is not used geometrically. Instead, the relative values of the X and Y coordinates of the \c layoutPosition determine the relative positioning of the views, as described below.
Thus, to have your subclass of HFRepresenter participate in the HFLayoutRepresenter system, override \c defaultLayoutPosition: to control its positioning, and possibly \\c minimumViewWidthForBytesPerLine: if your representer requires a certain width to display some bytes per line. Then ensure your view has its autoresizing mask set properly, and if its frame is fixed size, ensure that its frame is correct as well.
The layout process, in detail, is:
-# The views are sorted vertically by the Y component of their representers' \c layoutPosition into "slices." Smaller values appear towards the bottom of the layout view. There is no space between slices.
-# Views with equal Y components are sorted horizontally by the X component of their representers' \c layoutPosition, with smaller values appearing on the left.
-# The height of each slice is determined by the tallest view within it, excluding views that have \c NSViewHeightSizable set. If there is any leftover vertical space, it is distributed equally among all slices with at least one view with \c NSViewHeightSizable set.
-# If the layout representer is not set to maximize the bytes per line (BPL), then the BPL from the HFController is used. Otherwise:
-# Each representer is queried for its \c minimumViewWidthForBytesPerLine:
-# The largest BPL allowing each row to fit within the layout width is determined via a binary search.
-# The BPL is rounded down to a multiple of the bytes per column (if non-zero).
-# The BPL is then set on the controller.
-# For each row, each view is assigned its minimum view width for the BPL.
-# If there is any horizontal space left over, it is divided evenly between all views in that slice that have \c NSViewWidthSizable set in their autoresizing mask.
*/
@interface HFLayoutRepresenter : HFRepresenter {
NSMutableArray *representers;
BOOL maximizesBytesPerLine;
}
/*! @name Managed representers
Managing the list of representers laid out by the receiver
*/
//@{
/// Return the array of representers managed by the receiver. */
@property (readonly, copy) NSArray *representers;
/*! Adds a new representer to the receiver, triggering relayout. */
- (void)addRepresenter:(HFRepresenter *)representer;
/*! Removes a representer to the receiver (which must be present in the receiver's array of representers), triggering relayout. */
- (void)removeRepresenter:(HFRepresenter *)representer;
//@}
/*! When enabled, the receiver will attempt to maximize the bytes per line so as to consume as much as possible of the bounds rect. If this is YES, then upon relayout, the receiver will recalculate the maximum number of bytes per line that can fit in its boundsRectForLayout. If this is NO, then the receiver will not change the bytes per line. */
@property (nonatomic) BOOL maximizesBytesPerLine;
/*! @name Layout
Methods to get information about layout, and to explicitly trigger it.
*/
//@{
/*! Returns the smallest width that produces the same layout (and, if maximizes bytesPerLine, the same bytes per line) as the proposed width. */
- (CGFloat)minimumViewWidthForLayoutInProposedWidth:(CGFloat)proposedWidth;
/*! Returns the maximum bytes per line that can fit in the proposed width (ignoring maximizesBytesPerLine). This is always a multiple of the bytesPerColumn, and always at least bytesPerColumn. */
- (NSUInteger)maximumBytesPerLineForLayoutInProposedWidth:(CGFloat)proposedWidth;
/*! Returns the smallest width that can support the given bytes per line. */
- (CGFloat)minimumViewWidthForBytesPerLine:(NSUInteger)bytesPerLine;
/*! Relayouts are triggered when representers are added and removed, or when the view is resized. You may call this explicitly to trigger a relayout. */
- (void)performLayout;
//@}
@end

View File

@ -0,0 +1,361 @@
//
// HFRepresenterLayoutView.m
// HexFiend_2
//
// Copyright 2007 ridiculous_fish. All rights reserved.
//
#import <HexFiend/HFLayoutRepresenter.h>
@interface HFRepresenterLayoutViewInfo : NSObject {
@public
HFRepresenter *rep;
NSView *view;
NSPoint layoutPosition;
NSRect frame;
NSUInteger autoresizingMask;
}
@end
@implementation HFRepresenterLayoutViewInfo
- (NSString *)description {
return [NSString stringWithFormat:@"<%@ : %@>", view, NSStringFromRect(frame)];
}
@end
@implementation HFLayoutRepresenter
static NSInteger sortByLayoutPosition(id a, id b, void *self) {
USE(self);
NSPoint pointA = [a layoutPosition];
NSPoint pointB = [b layoutPosition];
if (pointA.y < pointB.y) return -1;
else if (pointA.y > pointB.y) return 1;
else if (pointA.x < pointB.x) return -1;
else if (pointA.x > pointB.x) return 1;
else return 0;
}
- (NSArray *)arraysOfLayoutInfos {
if (! representers) return nil;
NSMutableArray *result = [NSMutableArray array];
NSArray *reps = [representers sortedArrayUsingFunction:sortByLayoutPosition context:self];
NSMutableArray *currentReps = [NSMutableArray array];
CGFloat currentRepY = - CGFLOAT_MAX;
FOREACH(HFRepresenter*, rep, reps) {
HFRepresenterLayoutViewInfo *info = [[HFRepresenterLayoutViewInfo alloc] init];
info->rep = rep;
info->view = [rep view];
info->frame = [info->view frame];
info->layoutPosition = [rep layoutPosition];
info->autoresizingMask = [info->view autoresizingMask];
if (info->layoutPosition.y != currentRepY && [currentReps count] > 0) {
[result addObject:[[currentReps copy] autorelease]];
[currentReps removeAllObjects];
}
currentRepY = info->layoutPosition.y;
[currentReps addObject:info];
[info release];
}
if ([currentReps count]) [result addObject:[[currentReps copy] autorelease]];
return result;
}
- (NSRect)boundsRectForLayout {
NSRect result = [[self view] bounds];
/* Sometimes when we are not yet in a window, we get wonky bounds, so be paranoid. */
if (result.size.width < 0 || result.size.height < 0) result = NSZeroRect;
return result;
}
- (CGFloat)_computeMinHeightForLayoutInfos:(NSArray *)infos {
CGFloat result = 0;
HFASSERT(infos != NULL);
HFASSERT([infos count] > 0);
FOREACH(HFRepresenterLayoutViewInfo *, info, infos) {
if (! (info->autoresizingMask & NSViewHeightSizable)) result = MAX(result, NSHeight([info->view frame]));
}
return result;
}
- (void)_applyYLocation:(CGFloat)yLocation andMinHeight:(CGFloat)height toInfos:(NSArray *)layoutInfos {
FOREACH(HFRepresenterLayoutViewInfo *, info, layoutInfos) {
info->frame.origin.y = yLocation;
if (info->autoresizingMask & NSViewHeightSizable) info->frame.size.height = height;
}
}
- (void)_layoutInfosHorizontally:(NSArray *)infos inRect:(NSRect)layoutRect withBytesPerLine:(NSUInteger)bytesPerLine {
CGFloat nextX = NSMinX(layoutRect);
NSUInteger numHorizontallyResizable = 0;
FOREACH(HFRepresenterLayoutViewInfo *, info, infos) {
CGFloat minWidth = [info->rep minimumViewWidthForBytesPerLine:bytesPerLine];
info->frame.origin.x = nextX;
info->frame.size.width = minWidth;
nextX += minWidth;
numHorizontallyResizable += !! (info->autoresizingMask & NSViewWidthSizable);
}
CGFloat remainingWidth = NSMaxX(layoutRect) - nextX;
if (numHorizontallyResizable > 0 && remainingWidth > 0) {
NSView *view = [self view];
CGFloat remainingPixels = [view convertSize:NSMakeSize(remainingWidth, 0) toView:nil].width;
HFASSERT(remainingPixels > 0);
CGFloat pixelsPerView = HFFloor(HFFloor(remainingPixels) / (CGFloat)numHorizontallyResizable);
if (pixelsPerView > 0) {
CGFloat pointsPerView = [view convertSize:NSMakeSize(pixelsPerView, 0) fromView:nil].width;
CGFloat pointsAdded = 0;
FOREACH(HFRepresenterLayoutViewInfo *, info, infos) {
info->frame.origin.x += pointsAdded;
if (info->autoresizingMask & NSViewWidthSizable) {
info->frame.size.width += pointsPerView;
pointsAdded += pointsPerView;
}
}
}
}
}
- (CGFloat)minimumViewWidthForBytesPerLine:(NSUInteger)bytesPerLine {
CGFloat result = 0;
NSArray *arraysOfLayoutInfos = [self arraysOfLayoutInfos];
FOREACH(NSArray *, layoutInfos, arraysOfLayoutInfos) {
CGFloat minWidthForRow = 0;
FOREACH(HFRepresenterLayoutViewInfo *, info, layoutInfos) {
minWidthForRow += [info->rep minimumViewWidthForBytesPerLine:bytesPerLine];
}
result = MAX(result, minWidthForRow);
}
return result;
}
- (NSUInteger)_computeBytesPerLineForArraysOfLayoutInfos:(NSArray *)arraysOfLayoutInfos forLayoutInRect:(NSRect)layoutRect {
/* The granularity is our own granularity (probably 1), LCMed with the granularities of all other representers */
NSUInteger granularity = [self byteGranularity];
FOREACH(HFRepresenter *, representer, representers) {
granularity = HFLeastCommonMultiple(granularity, [representer byteGranularity]);
}
HFASSERT(granularity >= 1);
NSUInteger newNumGranules = (NSUIntegerMax - 1) / granularity;
FOREACH(NSArray *, layoutInfos, arraysOfLayoutInfos) {
NSUInteger maxKnownGood = 0, minKnownBad = newNumGranules + 1;
while (maxKnownGood + 1 < minKnownBad) {
CGFloat requiredSpace = 0;
NSUInteger proposedNumGranules = maxKnownGood + (minKnownBad - maxKnownGood)/2;
NSUInteger proposedBytesPerLine = proposedNumGranules * granularity;
FOREACH(HFRepresenterLayoutViewInfo *, info, layoutInfos) {
requiredSpace += [info->rep minimumViewWidthForBytesPerLine:proposedBytesPerLine];
if (requiredSpace > NSWidth(layoutRect)) break;
}
if (requiredSpace > NSWidth(layoutRect)) minKnownBad = proposedNumGranules;
else maxKnownGood = proposedNumGranules;
}
newNumGranules = maxKnownGood;
}
return MAX(1u, newNumGranules) * granularity;
}
- (BOOL)_anyLayoutInfoIsVerticallyResizable:(NSArray *)vals {
HFASSERT(vals != NULL);
FOREACH(HFRepresenterLayoutViewInfo *, info, vals) {
if (info->autoresizingMask & NSViewHeightSizable) return YES;
}
return NO;
}
- (BOOL)_addVerticalHeight:(CGFloat)heightPoints andOffset:(CGFloat)offsetPoints toLayoutInfos:(NSArray *)layoutInfos {
BOOL isVerticallyResizable = [self _anyLayoutInfoIsVerticallyResizable:layoutInfos];
CGFloat totalHeight = [self _computeMinHeightForLayoutInfos:layoutInfos] + heightPoints;
FOREACH(HFRepresenterLayoutViewInfo *, info, layoutInfos) {
info->frame.origin.y += offsetPoints;
if (isVerticallyResizable) {
if (info->autoresizingMask & NSViewHeightSizable) {
info->frame.size.height = totalHeight;
}
else {
CGFloat diff = totalHeight - info->frame.size.height;
HFASSERT(diff >= 0);
info->frame.origin.y += HFFloor(diff);
}
}
}
return isVerticallyResizable;
}
- (void)_distributeVerticalSpace:(CGFloat)space toArraysOfLayoutInfos:(NSArray *)arraysOfLayoutInfos {
HFASSERT(space >= 0);
HFASSERT(arraysOfLayoutInfos != NULL);
NSUInteger consumers = 0;
FOREACH(NSArray *, layoutInfos, arraysOfLayoutInfos) {
if ([self _anyLayoutInfoIsVerticallyResizable:layoutInfos]) consumers++;
}
if (consumers > 0) {
NSView *view = [self view];
CGFloat availablePixels = [view convertSize:NSMakeSize(0, space) toView:nil].height;
HFASSERT(availablePixels > 0);
CGFloat pixelsPerView = HFFloor(HFFloor(availablePixels) / (CGFloat)consumers);
CGFloat pointsPerView = [view convertSize:NSMakeSize(0, pixelsPerView) fromView:nil].height;
CGFloat yOffset = 0;
if (pointsPerView > 0) {
FOREACH(NSArray *, layoutInfos, arraysOfLayoutInfos) {
if ([self _addVerticalHeight:pointsPerView andOffset:yOffset toLayoutInfos:layoutInfos]) {
yOffset += pointsPerView;
}
}
}
}
}
- (void)performLayout {
HFController *controller = [self controller];
if (! controller) return;
if (! representers) return;
NSArray *arraysOfLayoutInfos = [self arraysOfLayoutInfos];
if (! [arraysOfLayoutInfos count]) return;
NSUInteger transaction = [controller beginPropertyChangeTransaction];
NSRect layoutRect = [self boundsRectForLayout];
NSUInteger bytesPerLine;
if (maximizesBytesPerLine) bytesPerLine = [self _computeBytesPerLineForArraysOfLayoutInfos:arraysOfLayoutInfos forLayoutInRect:layoutRect];
else bytesPerLine = [controller bytesPerLine];
CGFloat yPosition = NSMinY(layoutRect);
FOREACH(NSArray *, layoutInfos, arraysOfLayoutInfos) {
HFASSERT([layoutInfos count] > 0);
CGFloat minHeight = [self _computeMinHeightForLayoutInfos:layoutInfos];
[self _applyYLocation:yPosition andMinHeight:minHeight toInfos:layoutInfos];
yPosition += minHeight;
[self _layoutInfosHorizontally:layoutInfos inRect:layoutRect withBytesPerLine:bytesPerLine];
}
CGFloat remainingVerticalSpace = NSMaxY(layoutRect) - yPosition;
if (remainingVerticalSpace > 0) {
[self _distributeVerticalSpace:remainingVerticalSpace toArraysOfLayoutInfos:arraysOfLayoutInfos];
}
FOREACH(NSArray *, layoutInfoArray, arraysOfLayoutInfos) {
FOREACH(HFRepresenterLayoutViewInfo *, info, layoutInfoArray) {
[info->view setFrame:info->frame];
}
}
[controller endPropertyChangeTransaction:transaction];
}
- (NSArray *)representers {
return representers ? [[representers copy] autorelease] : @[];
}
- (instancetype)init {
self = [super init];
maximizesBytesPerLine = YES;
return self;
}
- (void)dealloc {
[[NSNotificationCenter defaultCenter] removeObserver:self name:NSViewFrameDidChangeNotification object:[self view]];
[representers release];
[super dealloc];
}
- (void)encodeWithCoder:(NSCoder *)coder {
HFASSERT([coder allowsKeyedCoding]);
[super encodeWithCoder:coder];
[coder encodeObject:representers forKey:@"HFRepresenters"];
[coder encodeBool:maximizesBytesPerLine forKey:@"HFMaximizesBytesPerLine"];
}
- (instancetype)initWithCoder:(NSCoder *)coder {
HFASSERT([coder allowsKeyedCoding]);
self = [super initWithCoder:coder];
representers = [[coder decodeObjectForKey:@"HFRepresenters"] retain];
maximizesBytesPerLine = [coder decodeBoolForKey:@"HFMaximizesBytesPerLine"];
NSView *view = [self view];
[view setPostsFrameChangedNotifications:YES];
[[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(frameChanged:) name:NSViewFrameDidChangeNotification object:view];
return self;
}
- (void)addRepresenter:(HFRepresenter *)representer {
REQUIRE_NOT_NULL(representer);
if (! representers) representers = [[NSMutableArray alloc] init];
HFASSERT([representers indexOfObjectIdenticalTo:representer] == NSNotFound);
[representers addObject:representer];
HFASSERT([[representer view] superview] != [self view]);
[[self view] addSubview:[representer view]];
[self performLayout];
}
- (void)removeRepresenter:(HFRepresenter *)representer {
REQUIRE_NOT_NULL(representer);
HFASSERT([representers indexOfObjectIdenticalTo:representer] != NSNotFound);
NSView *view = [representer view];
HFASSERT([view superview] == [self view]);
[view removeFromSuperview];
[representers removeObjectIdenticalTo:representer];
[self performLayout];
}
- (void)frameChanged:(NSNotification *)note {
USE(note);
[self performLayout];
}
- (void)initializeView {
NSView *view = [self view];
[view setPostsFrameChangedNotifications:YES];
[[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(frameChanged:) name:NSViewFrameDidChangeNotification object:view];
}
- (NSView *)createView {
return [[NSView alloc] initWithFrame:NSMakeRect(0, 0, 100, 100)];
}
- (void)setMaximizesBytesPerLine:(BOOL)val {
maximizesBytesPerLine = val;
}
- (BOOL)maximizesBytesPerLine {
return maximizesBytesPerLine;
}
- (NSUInteger)maximumBytesPerLineForLayoutInProposedWidth:(CGFloat)proposedWidth {
NSArray *arraysOfLayoutInfos = [self arraysOfLayoutInfos];
if (! [arraysOfLayoutInfos count]) return 0;
NSRect layoutRect = [self boundsRectForLayout];
layoutRect.size.width = proposedWidth;
NSUInteger bytesPerLine = [self _computeBytesPerLineForArraysOfLayoutInfos:arraysOfLayoutInfos forLayoutInRect:layoutRect];
return bytesPerLine;
}
- (CGFloat)minimumViewWidthForLayoutInProposedWidth:(CGFloat)proposedWidth {
NSUInteger bytesPerLine;
if ([self maximizesBytesPerLine]) {
bytesPerLine = [self maximumBytesPerLineForLayoutInProposedWidth:proposedWidth];
} else {
bytesPerLine = [[self controller] bytesPerLine];
}
CGFloat newWidth = [self minimumViewWidthForBytesPerLine:bytesPerLine];
return newWidth;
}
- (void)controllerDidChange:(HFControllerPropertyBits)bits {
[super controllerDidChange:bits];
if (bits & (HFControllerViewSizeRatios | HFControllerBytesPerColumn | HFControllerByteGranularity)) {
[self performLayout];
}
}
@end

View File

@ -0,0 +1,67 @@
//
// HFLineCountingRepresenter.h
// HexFiend_2
//
// Copyright 2007 ridiculous_fish. All rights reserved.
//
#import <HexFiend/HFRepresenter.h>
/*! @enum HFLineNumberFormat
HFLineNumberFormat is a simple enum used to determine whether line numbers are in decimal or hexadecimal format.
*/
typedef NS_ENUM(NSUInteger, HFLineNumberFormat) {
HFLineNumberFormatDecimal, //!< Decimal line numbers
HFLineNumberFormatHexadecimal, //!< Hexadecimal line numbers
HFLineNumberFormatMAXIMUM //!< One more than the maximum valid line number format, so that line number formats can be cycled through easily
};
/*! @class HFLineCountingRepresenter
@brief The HFRepresenter used to show the "line number gutter."
HFLineCountingRepresenter is the HFRepresenter used to show the "line number gutter." HFLineCountingRepresenter makes space for a certain number of digits.
*/
@interface HFLineCountingRepresenter : HFRepresenter {
CGFloat lineHeight;
NSUInteger digitsToRepresentContentsLength;
NSUInteger minimumDigitCount;
HFLineNumberFormat lineNumberFormat;
NSInteger interiorShadowEdge;
CGFloat preferredWidth;
CGFloat digitAdvance;
}
/// The minimum digit count. The receiver will always ensure it is big enough to display at least the minimum digit count. The default is 2.
@property (nonatomic) NSUInteger minimumDigitCount;
/// The number of digits we are making space for.
@property (readonly) NSUInteger digitCount;
/// The current width that the HFRepresenter prefers to be laid out with.
@property (readonly) CGFloat preferredWidth;
/// The line number format.
@property (nonatomic) HFLineNumberFormat lineNumberFormat;
/// Switches to the next line number format. This is called from the view.
- (void)cycleLineNumberFormat;
/// The edge (as an NSRectEdge) on which the view draws an interior shadow. -1 means no edge.
@property (nonatomic) NSInteger interiorShadowEdge;
/// The border color used at the edges specified by -borderedEdges.
@property (nonatomic, copy) NSColor *borderColor;
/*! The edges on which borders are drawn. The edge returned by interiorShadowEdge always has a border drawn. The edges are specified by a bitwise or of 1 left shifted by the NSRectEdge values. For example, to draw a border on the min x and max y edges use: (1 << NSMinXEdge) | (1 << NSMaxYEdge). 0 (or -1) specfies no edges. */
@property (nonatomic) NSInteger borderedEdges;
/// The background color
@property (nonatomic, copy) NSColor *backgroundColor;
@property NSUInteger valueOffset;
@end
/*! Notification posted when the HFLineCountingRepresenter's width has changed because the number of digits it wants to show has increased or decreased. The object is the HFLineCountingRepresenter; there is no user info.
*/
extern NSString *const HFLineCountingRepresenterMinimumViewWidthChanged;

View File

@ -0,0 +1,250 @@
//
// HFLineCountingRepresenter.m
// HexFiend_2
//
// Copyright 2007 ridiculous_fish. All rights reserved.
//
#import <HexFiend/HFLineCountingRepresenter.h>
#import <HexFiend/HFLineCountingView.h>
NSString *const HFLineCountingRepresenterMinimumViewWidthChanged = @"HFLineCountingRepresenterMinimumViewWidthChanged";
/* Returns the maximum advance in points for a hexadecimal digit for the given font (interpreted as a screen font) */
static CGFloat maximumDigitAdvanceForFont(NSFont *font) {
REQUIRE_NOT_NULL(font);
font = [font screenFont];
CGFloat maxDigitAdvance = 0;
NSDictionary *attributesDictionary = [[NSDictionary alloc] initWithObjectsAndKeys:font, NSFontAttributeName, nil];
NSTextStorage *storage = [[NSTextStorage alloc] init];
NSLayoutManager *manager = [[NSLayoutManager alloc] init];
[storage setFont:font];
[storage addLayoutManager:manager];
NSSize advancements[16] = {};
NSGlyph glyphs[16];
/* Generate a glyph for every hex digit */
for (NSUInteger i=0; i < 16; i++) {
char c = "0123456789ABCDEF"[i];
NSString *string = [[NSString alloc] initWithBytes:&c length:1 encoding:NSASCIIStringEncoding];
[storage replaceCharactersInRange:NSMakeRange(0, (i ? 1 : 0)) withString:string];
[string release];
glyphs[i] = [manager glyphAtIndex:0 isValidIndex:NULL];
HFASSERT(glyphs[i] != NSNullGlyph);
}
/* Get the advancements of each of those glyphs */
[font getAdvancements:advancements forGlyphs:glyphs count:sizeof glyphs / sizeof *glyphs];
[manager release];
[attributesDictionary release];
[storage release];
/* Find the widest digit */
for (NSUInteger i=0; i < sizeof glyphs / sizeof *glyphs; i++) {
maxDigitAdvance = HFMax(maxDigitAdvance, advancements[i].width);
}
return maxDigitAdvance;
}
@implementation HFLineCountingRepresenter
- (instancetype)init {
if ((self = [super init])) {
minimumDigitCount = 2;
digitsToRepresentContentsLength = minimumDigitCount;
interiorShadowEdge = NSMaxXEdge;
_borderedEdges = (1 << NSMaxXEdge);
_borderColor = [[NSColor darkGrayColor] retain];
_backgroundColor = [[NSColor colorWithCalibratedWhite:(CGFloat).87 alpha:1] retain];
}
return self;
}
- (void)encodeWithCoder:(NSCoder *)coder {
HFASSERT([coder allowsKeyedCoding]);
[super encodeWithCoder:coder];
[coder encodeDouble:lineHeight forKey:@"HFLineHeight"];
[coder encodeInt64:minimumDigitCount forKey:@"HFMinimumDigitCount"];
[coder encodeInt64:lineNumberFormat forKey:@"HFLineNumberFormat"];
[coder encodeObject:self.backgroundColor forKey:@"HFBackgroundColor"];
[coder encodeObject:self.borderColor forKey:@"HFBorderColor"];
[coder encodeInt64:self.borderedEdges forKey:@"HFBorderedEdges"];
}
- (instancetype)initWithCoder:(NSCoder *)coder {
HFASSERT([coder allowsKeyedCoding]);
self = [super initWithCoder:coder];
lineHeight = (CGFloat)[coder decodeDoubleForKey:@"HFLineHeight"];
minimumDigitCount = (NSUInteger)[coder decodeInt64ForKey:@"HFMinimumDigitCount"];
lineNumberFormat = (HFLineNumberFormat)[coder decodeInt64ForKey:@"HFLineNumberFormat"];
_borderedEdges = [coder decodeObjectForKey:@"HFBorderedEdges"] ? (NSInteger)[coder decodeInt64ForKey:@"HFBorderedEdges"] : 0;
_borderColor = [[coder decodeObjectForKey:@"HFBorderColor"] ?: [NSColor darkGrayColor] retain];
_backgroundColor = [[coder decodeObjectForKey:@"HFBackgroundColor"] ?: [NSColor colorWithCalibratedWhite:(CGFloat).87 alpha:1] retain];
return self;
}
- (void)dealloc {
[_borderColor release];
[_backgroundColor release];
[super dealloc];
}
- (NSView *)createView {
HFLineCountingView *result = [[HFLineCountingView alloc] initWithFrame:NSMakeRect(0, 0, 60, 10)];
[result setRepresenter:self];
[result setAutoresizingMask:NSViewHeightSizable];
return result;
}
- (void)postMinimumViewWidthChangedNotification {
[[NSNotificationCenter defaultCenter] postNotificationName:HFLineCountingRepresenterMinimumViewWidthChanged object:self];
}
- (void)updateDigitAdvanceWithFont:(NSFont *)font {
CGFloat newDigitAdvance = maximumDigitAdvanceForFont(font);
if (digitAdvance != newDigitAdvance) {
digitAdvance = newDigitAdvance;
[self postMinimumViewWidthChangedNotification];
}
}
- (void)updateFontAndLineHeight {
HFLineCountingView *view = [self view];
HFController *controller = [self controller];
NSFont *font = controller ? [controller font] : [NSFont fontWithName:HFDEFAULT_FONT size:HFDEFAULT_FONTSIZE];
[view setFont:font];
[view setLineHeight: controller ? [controller lineHeight] : HFDEFAULT_FONTSIZE];
[self updateDigitAdvanceWithFont:font];
}
- (void)updateLineNumberFormat {
[[self view] setLineNumberFormat:lineNumberFormat];
}
- (void)updateBytesPerLine {
[[self view] setBytesPerLine:[[self controller] bytesPerLine]];
}
- (void)updateLineRangeToDraw {
HFFPRange lineRange = {0, 0};
HFController *controller = [self controller];
if (controller) {
lineRange = [controller displayedLineRange];
}
[[self view] setLineRangeToDraw:lineRange];
}
- (CGFloat)preferredWidth {
if (digitAdvance == 0) {
/* This may happen if we were loaded from a nib. We are lazy about fetching the controller's font to avoid ordering issues with nib unarchival. */
[self updateFontAndLineHeight];
}
return (CGFloat)10. + digitsToRepresentContentsLength * digitAdvance;
}
- (void)updateMinimumViewWidth {
HFController *controller = [self controller];
if (controller) {
unsigned long long contentsLength = [controller contentsLength];
NSUInteger bytesPerLine = [controller bytesPerLine];
/* We want to know how many lines are displayed. That's equal to the contentsLength divided by bytesPerLine rounded down, except in the case that we're at the end of a line, in which case we need to show one more. Hence adding 1 and dividing gets us the right result. */
unsigned long long lineCount = contentsLength / bytesPerLine;
unsigned long long contentsLengthRoundedToLine = HFProductULL(lineCount, bytesPerLine) - 1;
NSUInteger digitCount = [HFLineCountingView digitsRequiredToDisplayLineNumber:contentsLengthRoundedToLine inFormat:lineNumberFormat];
NSUInteger digitWidth = MAX(minimumDigitCount, digitCount);
if (digitWidth != digitsToRepresentContentsLength) {
digitsToRepresentContentsLength = digitWidth;
[self postMinimumViewWidthChangedNotification];
}
}
}
- (CGFloat)minimumViewWidthForBytesPerLine:(NSUInteger)bytesPerLine {
USE(bytesPerLine);
return [self preferredWidth];
}
- (HFLineNumberFormat)lineNumberFormat {
return lineNumberFormat;
}
- (void)setLineNumberFormat:(HFLineNumberFormat)format {
HFASSERT(format < HFLineNumberFormatMAXIMUM);
lineNumberFormat = format;
[self updateLineNumberFormat];
[self updateMinimumViewWidth];
}
- (void)cycleLineNumberFormat {
lineNumberFormat = (lineNumberFormat + 1) % HFLineNumberFormatMAXIMUM;
[self updateLineNumberFormat];
[self updateMinimumViewWidth];
}
- (void)initializeView {
[self updateFontAndLineHeight];
[self updateLineNumberFormat];
[self updateBytesPerLine];
[self updateLineRangeToDraw];
[self updateMinimumViewWidth];
}
- (void)controllerDidChange:(HFControllerPropertyBits)bits {
if (bits & HFControllerDisplayedLineRange) [self updateLineRangeToDraw];
if (bits & HFControllerBytesPerLine) [self updateBytesPerLine];
if (bits & (HFControllerFont | HFControllerLineHeight)) [self updateFontAndLineHeight];
if (bits & (HFControllerContentLength)) [self updateMinimumViewWidth];
}
- (void)setMinimumDigitCount:(NSUInteger)width {
minimumDigitCount = width;
[self updateMinimumViewWidth];
}
- (NSUInteger)minimumDigitCount {
return minimumDigitCount;
}
- (NSUInteger)digitCount {
return digitsToRepresentContentsLength;
}
+ (NSPoint)defaultLayoutPosition {
return NSMakePoint(-1, 0);
}
- (void)setInteriorShadowEdge:(NSInteger)edge {
self->interiorShadowEdge = edge;
if ([self isViewLoaded]) {
[[self view] setNeedsDisplay:YES];
}
}
- (NSInteger)interiorShadowEdge {
return interiorShadowEdge;
}
- (void)setBorderColor:(NSColor *)color {
[_borderColor autorelease];
_borderColor = [color copy];
if ([self isViewLoaded]) {
[[self view] setNeedsDisplay:YES];
}
}
- (void)setBackgroundColor:(NSColor *)color {
[_backgroundColor autorelease];
_backgroundColor = [color copy];
if ([self isViewLoaded]) {
[[self view] setNeedsDisplay:YES];
}
}
@end

View File

@ -0,0 +1,32 @@
//
// HFLineCountingView.h
// HexFiend_2
//
// Copyright 2007 ridiculous_fish. All rights reserved.
//
#import <Cocoa/Cocoa.h>
#import <HexFiend/HFLineCountingRepresenter.h>
@interface HFLineCountingView : NSView {
NSLayoutManager *layoutManager;
NSTextStorage *textStorage;
NSTextContainer *textContainer;
NSDictionary *textAttributes;
unsigned long long storedLineIndex;
NSUInteger storedLineCount;
BOOL useStringDrawingPath;
BOOL registeredForAppNotifications;
}
@property (nonatomic, copy) NSFont *font;
@property (nonatomic) CGFloat lineHeight;
@property (nonatomic) HFFPRange lineRangeToDraw;
@property (nonatomic) NSUInteger bytesPerLine;
@property (nonatomic) HFLineNumberFormat lineNumberFormat;
@property (nonatomic, assign) HFLineCountingRepresenter *representer;
+ (NSUInteger)digitsRequiredToDisplayLineNumber:(unsigned long long)lineNumber inFormat:(HFLineNumberFormat)format;
@end

View File

@ -0,0 +1,689 @@
//
// HFLineCountingView.m
// HexFiend_2
//
// Copyright 2007 ridiculous_fish. All rights reserved.
//
#import <HexFiend/HFLineCountingView.h>
#import <HexFiend/HFLineCountingRepresenter.h>
#import <HexFiend/HFFunctions.h>
#define TIME_LINE_NUMBERS 0
#define HEX_LINE_NUMBERS_HAVE_0X_PREFIX 0
#define INVALID_LINE_COUNT NSUIntegerMax
#if TIME_LINE_NUMBERS
@interface HFTimingTextView : NSTextView
@end
@implementation HFTimingTextView
- (void)drawRect:(NSRect)rect {
CFAbsoluteTime startTime = CFAbsoluteTimeGetCurrent();
[super drawRect:rect];
CFAbsoluteTime endTime = CFAbsoluteTimeGetCurrent();
NSLog(@"TextView line number time: %f", endTime - startTime);
}
@end
#endif
@implementation HFLineCountingView
- (void)_sharedInitLineCountingView {
layoutManager = [[NSLayoutManager alloc] init];
textStorage = [[NSTextStorage alloc] init];
[textStorage addLayoutManager:layoutManager];
textContainer = [[NSTextContainer alloc] init];
[textContainer setLineFragmentPadding:(CGFloat)5];
[textContainer setContainerSize:NSMakeSize(self.bounds.size.width, [textContainer containerSize].height)];
[layoutManager addTextContainer:textContainer];
}
- (instancetype)initWithFrame:(NSRect)frame {
self = [super initWithFrame:frame];
if (self) {
[self _sharedInitLineCountingView];
}
return self;
}
- (void)dealloc {
HFUnregisterViewForWindowAppearanceChanges(self, registeredForAppNotifications);
[_font release];
[layoutManager release];
[textContainer release];
[textStorage release];
[textAttributes release];
[super dealloc];
}
- (void)encodeWithCoder:(NSCoder *)coder {
HFASSERT([coder allowsKeyedCoding]);
[super encodeWithCoder:coder];
[coder encodeObject:_font forKey:@"HFFont"];
[coder encodeDouble:_lineHeight forKey:@"HFLineHeight"];
[coder encodeObject:_representer forKey:@"HFRepresenter"];
[coder encodeInt64:_bytesPerLine forKey:@"HFBytesPerLine"];
[coder encodeInt64:_lineNumberFormat forKey:@"HFLineNumberFormat"];
[coder encodeBool:useStringDrawingPath forKey:@"HFUseStringDrawingPath"];
}
- (instancetype)initWithCoder:(NSCoder *)coder {
HFASSERT([coder allowsKeyedCoding]);
self = [super initWithCoder:coder];
[self _sharedInitLineCountingView];
_font = [[coder decodeObjectForKey:@"HFFont"] retain];
_lineHeight = (CGFloat)[coder decodeDoubleForKey:@"HFLineHeight"];
_representer = [coder decodeObjectForKey:@"HFRepresenter"];
_bytesPerLine = (NSUInteger)[coder decodeInt64ForKey:@"HFBytesPerLine"];
_lineNumberFormat = (NSUInteger)[coder decodeInt64ForKey:@"HFLineNumberFormat"];
useStringDrawingPath = [coder decodeBoolForKey:@"HFUseStringDrawingPath"];
return self;
}
- (BOOL)isFlipped { return YES; }
- (void)getLineNumberFormatString:(char *)outString length:(NSUInteger)length {
HFLineNumberFormat format = self.lineNumberFormat;
if (format == HFLineNumberFormatDecimal) {
strlcpy(outString, "%llu", length);
}
else if (format == HFLineNumberFormatHexadecimal) {
#if HEX_LINE_NUMBERS_HAVE_0X_PREFIX
// we want a format string like 0x%08llX
snprintf(outString, length, "0x%%0%lullX", (unsigned long)self.representer.digitCount - 2);
#else
// we want a format string like %08llX
snprintf(outString, length, "%%0%lullX", (unsigned long)self.representer.digitCount);
#endif
}
else {
strlcpy(outString, "", length);
}
}
- (void)windowDidChangeKeyStatus:(NSNotification *)note {
USE(note);
[self setNeedsDisplay:YES];
}
- (void)viewDidMoveToWindow {
HFRegisterViewForWindowAppearanceChanges(self, @selector(windowDidChangeKeyStatus:), !registeredForAppNotifications);
registeredForAppNotifications = YES;
[super viewDidMoveToWindow];
}
- (void)viewWillMoveToWindow:(NSWindow *)newWindow {
HFUnregisterViewForWindowAppearanceChanges(self, NO);
[super viewWillMoveToWindow:newWindow];
}
- (void)drawGradientWithClip:(NSRect)clip {
[_representer.backgroundColor set];
NSRectFill(clip);
NSInteger shadowEdge = _representer.interiorShadowEdge;
if (shadowEdge >= 0) {
const CGFloat shadowWidth = 6;
NSWindow *window = self.window;
BOOL drawActive = (window == nil || [window isKeyWindow] || [window isMainWindow]);
HFDrawShadow([[NSGraphicsContext currentContext] graphicsPort], self.bounds, shadowWidth, shadowEdge, drawActive, clip);
}
}
- (void)drawDividerWithClip:(NSRect)clipRect {
USE(clipRect);
#if 1
NSInteger edges = _representer.borderedEdges;
NSRect bounds = self.bounds;
// -1 means to draw no edges
if (edges == -1) {
edges = 0;
}
[_representer.borderColor set];
if ((edges & (1 << NSMinXEdge)) > 0) {
NSRect lineRect = bounds;
lineRect.size.width = 1;
lineRect.origin.x = 0;
if (NSIntersectsRect(lineRect, clipRect)) {
NSRectFill(lineRect);
}
}
if ((edges & (1 << NSMaxXEdge)) > 0) {
NSRect lineRect = bounds;
lineRect.size.width = 1;
lineRect.origin.x = NSMaxX(bounds) - lineRect.size.width;
if (NSIntersectsRect(lineRect, clipRect)) {
NSRectFill(lineRect);
}
}
if ((edges & (1 << NSMinYEdge)) > 0) {
NSRect lineRect = bounds;
lineRect.size.height = 1;
lineRect.origin.y = 0;
if (NSIntersectsRect(lineRect, clipRect)) {
NSRectFill(lineRect);
}
}
if ((edges & (1 << NSMaxYEdge)) > 0) {
NSRect lineRect = bounds;
lineRect.size.height = 1;
lineRect.origin.y = NSMaxY(bounds) - lineRect.size.height;
if (NSIntersectsRect(lineRect, clipRect)) {
NSRectFill(lineRect);
}
}
// Backwards compatibility to always draw a border on the edge with the interior shadow
NSRect lineRect = bounds;
lineRect.size.width = 1;
NSInteger shadowEdge = _representer.interiorShadowEdge;
if (shadowEdge == NSMaxXEdge) {
lineRect.origin.x = NSMaxX(bounds) - lineRect.size.width;
} else if (shadowEdge == NSMinXEdge) {
lineRect.origin.x = NSMinX(bounds);
} else {
lineRect = NSZeroRect;
}
if (NSIntersectsRect(lineRect, clipRect)) {
NSRectFill(lineRect);
}
#else
if (NSIntersectsRect(lineRect, clipRect)) {
// this looks better when we have no shadow
[[NSColor lightGrayColor] set];
NSRect bounds = self.bounds;
NSRect lineRect = bounds;
lineRect.origin.x += lineRect.size.width - 2;
lineRect.size.width = 1;
NSRectFill(NSIntersectionRect(lineRect, clipRect));
[[NSColor whiteColor] set];
lineRect.origin.x += 1;
NSRectFill(NSIntersectionRect(lineRect, clipRect));
}
#endif
}
static inline int common_prefix_length(const char *a, const char *b) {
int i;
for (i=0; ; i++) {
char ac = a[i];
char bc = b[i];
if (ac != bc || ac == 0 || bc == 0) break;
}
return i;
}
/* Drawing with NSLayoutManager is necessary because the 10_2 typesetting behavior used by the old string drawing does the wrong thing for fonts like Bitstream Vera Sans Mono. Also it's an optimization for drawing the shadow. */
- (void)drawLineNumbersWithClipLayoutManagerPerLine:(NSRect)clipRect {
#if TIME_LINE_NUMBERS
CFAbsoluteTime startTime = CFAbsoluteTimeGetCurrent();
#endif
NSUInteger previousTextStorageCharacterCount = [textStorage length];
CGFloat verticalOffset = ld2f(_lineRangeToDraw.location - floorl(_lineRangeToDraw.location));
NSRect textRect = self.bounds;
textRect.size.height = _lineHeight;
textRect.origin.y -= verticalOffset * _lineHeight;
unsigned long long lineIndex = HFFPToUL(floorl(_lineRangeToDraw.location));
unsigned long long lineValue = lineIndex * _bytesPerLine;
NSUInteger linesRemaining = ll2l(HFFPToUL(ceill(_lineRangeToDraw.length + _lineRangeToDraw.location) - floorl(_lineRangeToDraw.location)));
char previousBuff[256];
int previousStringLength = (int)previousTextStorageCharacterCount;
BOOL conversionResult = [[textStorage string] getCString:previousBuff maxLength:sizeof previousBuff encoding:NSASCIIStringEncoding];
HFASSERT(conversionResult);
while (linesRemaining--) {
char formatString[64];
[self getLineNumberFormatString:formatString length:sizeof formatString];
if (NSIntersectsRect(textRect, clipRect)) {
NSString *replacementCharacters = nil;
NSRange replacementRange;
char buff[256];
int newStringLength = snprintf(buff, sizeof buff, formatString, lineValue);
HFASSERT(newStringLength > 0);
int prefixLength = common_prefix_length(previousBuff, buff);
HFASSERT(prefixLength <= newStringLength);
HFASSERT(prefixLength <= previousStringLength);
replacementRange = NSMakeRange(prefixLength, previousStringLength - prefixLength);
replacementCharacters = [[NSString alloc] initWithBytesNoCopy:buff + prefixLength length:newStringLength - prefixLength encoding:NSASCIIStringEncoding freeWhenDone:NO];
NSUInteger glyphCount;
[textStorage replaceCharactersInRange:replacementRange withString:replacementCharacters];
if (previousTextStorageCharacterCount == 0) {
NSDictionary *atts = [[NSDictionary alloc] initWithObjectsAndKeys:_font, NSFontAttributeName, [NSColor colorWithCalibratedWhite:(CGFloat).1 alpha:(CGFloat).8], NSForegroundColorAttributeName, nil];
[textStorage setAttributes:atts range:NSMakeRange(0, newStringLength)];
[atts release];
}
glyphCount = [layoutManager numberOfGlyphs];
if (glyphCount > 0) {
CGFloat maxX = NSMaxX([layoutManager lineFragmentUsedRectForGlyphAtIndex:glyphCount - 1 effectiveRange:NULL]);
[layoutManager drawGlyphsForGlyphRange:NSMakeRange(0, glyphCount) atPoint:NSMakePoint(textRect.origin.x + textRect.size.width - maxX, textRect.origin.y)];
}
previousTextStorageCharacterCount = newStringLength;
[replacementCharacters release];
memcpy(previousBuff, buff, newStringLength + 1);
previousStringLength = newStringLength;
}
textRect.origin.y += _lineHeight;
lineIndex++;
lineValue = HFSum(lineValue, _bytesPerLine);
}
#if TIME_LINE_NUMBERS
CFAbsoluteTime endTime = CFAbsoluteTimeGetCurrent();
NSLog(@"Line number time: %f", endTime - startTime);
#endif
}
- (void)drawLineNumbersWithClipStringDrawing:(NSRect)clipRect {
CGFloat verticalOffset = ld2f(_lineRangeToDraw.location - floorl(_lineRangeToDraw.location));
NSRect textRect = self.bounds;
textRect.size.height = _lineHeight;
textRect.size.width -= 5;
textRect.origin.y -= verticalOffset * _lineHeight + 1;
unsigned long long lineIndex = HFFPToUL(floorl(_lineRangeToDraw.location));
unsigned long long lineValue = lineIndex * _bytesPerLine;
NSUInteger linesRemaining = ll2l(HFFPToUL(ceill(_lineRangeToDraw.length + _lineRangeToDraw.location) - floorl(_lineRangeToDraw.location)));
if (! textAttributes) {
NSMutableParagraphStyle *mutableStyle = [[NSParagraphStyle defaultParagraphStyle] mutableCopy];
[mutableStyle setAlignment:NSRightTextAlignment];
NSParagraphStyle *paragraphStyle = [mutableStyle copy];
[mutableStyle release];
textAttributes = [[NSDictionary alloc] initWithObjectsAndKeys:_font, NSFontAttributeName, [NSColor colorWithCalibratedWhite:(CGFloat).1 alpha:(CGFloat).8], NSForegroundColorAttributeName, paragraphStyle, NSParagraphStyleAttributeName, nil];
[paragraphStyle release];
}
char formatString[64];
[self getLineNumberFormatString:formatString length:sizeof formatString];
while (linesRemaining--) {
if (NSIntersectsRect(textRect, clipRect)) {
char buff[256];
int newStringLength = snprintf(buff, sizeof buff, formatString, lineValue);
HFASSERT(newStringLength > 0);
NSString *string = [[NSString alloc] initWithBytesNoCopy:buff length:newStringLength encoding:NSASCIIStringEncoding freeWhenDone:NO];
[string drawInRect:textRect withAttributes:textAttributes];
[string release];
}
textRect.origin.y += _lineHeight;
lineIndex++;
if (linesRemaining > 0) lineValue = HFSum(lineValue, _bytesPerLine); //we could do this unconditionally, but then we risk overflow
}
}
- (NSUInteger)characterCountForLineRange:(HFRange)range {
HFASSERT(range.length <= NSUIntegerMax);
NSUInteger characterCount;
NSUInteger lineCount = ll2l(range.length);
const NSUInteger stride = _bytesPerLine;
HFLineCountingRepresenter *rep = self.representer;
HFLineNumberFormat format = self.lineNumberFormat;
if (format == HFLineNumberFormatDecimal) {
unsigned long long lineValue = HFProductULL(range.location, _bytesPerLine);
characterCount = lineCount /* newlines */;
while (lineCount--) {
characterCount += HFCountDigitsBase10(lineValue);
lineValue += stride;
}
}
else if (format == HFLineNumberFormatHexadecimal) {
characterCount = ([rep digitCount] + 1) * lineCount; // +1 for newlines
}
else {
characterCount = -1;
}
return characterCount;
}
- (NSString *)newLineStringForRange:(HFRange)range {
HFASSERT(range.length <= NSUIntegerMax);
if(range.length == 0)
return [[NSString alloc] init]; // Placate the analyzer.
NSUInteger lineCount = ll2l(range.length);
const NSUInteger stride = _bytesPerLine;
unsigned long long lineValue = HFProductULL(range.location, _bytesPerLine);
NSUInteger characterCount = [self characterCountForLineRange:range];
char *buffer = check_malloc(characterCount);
NSUInteger bufferIndex = 0;
char formatString[64];
[self getLineNumberFormatString:formatString length:sizeof formatString];
while (lineCount--) {
int charCount = sprintf(buffer + bufferIndex, formatString, lineValue);
HFASSERT(charCount > 0);
bufferIndex += charCount;
buffer[bufferIndex++] = '\n';
lineValue += stride;
}
HFASSERT(bufferIndex == characterCount);
NSString *string = [[NSString alloc] initWithBytesNoCopy:(void *)buffer length:bufferIndex encoding:NSASCIIStringEncoding freeWhenDone:YES];
return string;
}
- (void)updateLayoutManagerWithLineIndex:(unsigned long long)startingLineIndex lineCount:(NSUInteger)linesRemaining {
const BOOL debug = NO;
[textStorage beginEditing];
if (storedLineCount == INVALID_LINE_COUNT) {
/* This usually indicates that our bytes per line or line number format changed, and we need to just recalculate everything */
NSString *string = [self newLineStringForRange:HFRangeMake(startingLineIndex, linesRemaining)];
[textStorage replaceCharactersInRange:NSMakeRange(0, [textStorage length]) withString:string];
[string release];
}
else {
HFRange leftRangeToReplace, rightRangeToReplace;
HFRange leftRangeToStore, rightRangeToStore;
HFRange oldRange = HFRangeMake(storedLineIndex, storedLineCount);
HFRange newRange = HFRangeMake(startingLineIndex, linesRemaining);
HFRange rangeToPreserve = HFIntersectionRange(oldRange, newRange);
if (rangeToPreserve.length == 0) {
leftRangeToReplace = oldRange;
leftRangeToStore = newRange;
rightRangeToReplace = HFZeroRange;
rightRangeToStore = HFZeroRange;
}
else {
if (debug) NSLog(@"Preserving %llu", rangeToPreserve.length);
HFASSERT(HFRangeIsSubrangeOfRange(rangeToPreserve, newRange));
HFASSERT(HFRangeIsSubrangeOfRange(rangeToPreserve, oldRange));
const unsigned long long maxPreserve = HFMaxRange(rangeToPreserve);
leftRangeToReplace = HFRangeMake(oldRange.location, rangeToPreserve.location - oldRange.location);
leftRangeToStore = HFRangeMake(newRange.location, rangeToPreserve.location - newRange.location);
rightRangeToReplace = HFRangeMake(maxPreserve, HFMaxRange(oldRange) - maxPreserve);
rightRangeToStore = HFRangeMake(maxPreserve, HFMaxRange(newRange) - maxPreserve);
}
if (debug) NSLog(@"Changing %@ -> %@", HFRangeToString(oldRange), HFRangeToString(newRange));
if (debug) NSLog(@"LEFT: %@ -> %@", HFRangeToString(leftRangeToReplace), HFRangeToString(leftRangeToStore));
if (debug) NSLog(@"RIGHT: %@ -> %@", HFRangeToString(rightRangeToReplace), HFRangeToString(rightRangeToStore));
HFASSERT(leftRangeToReplace.length == 0 || HFRangeIsSubrangeOfRange(leftRangeToReplace, oldRange));
HFASSERT(rightRangeToReplace.length == 0 || HFRangeIsSubrangeOfRange(rightRangeToReplace, oldRange));
if (leftRangeToReplace.length > 0 || leftRangeToStore.length > 0) {
NSUInteger charactersToDelete = [self characterCountForLineRange:leftRangeToReplace];
NSRange rangeToDelete = NSMakeRange(0, charactersToDelete);
if (leftRangeToStore.length == 0) {
[textStorage deleteCharactersInRange:rangeToDelete];
if (debug) NSLog(@"Left deleting text range %@", NSStringFromRange(rangeToDelete));
}
else {
NSString *leftRangeString = [self newLineStringForRange:leftRangeToStore];
[textStorage replaceCharactersInRange:rangeToDelete withString:leftRangeString];
if (debug) NSLog(@"Replacing text range %@ with %@", NSStringFromRange(rangeToDelete), leftRangeString);
[leftRangeString release];
}
}
if (rightRangeToReplace.length > 0 || rightRangeToStore.length > 0) {
NSUInteger charactersToDelete = [self characterCountForLineRange:rightRangeToReplace];
NSUInteger stringLength = [textStorage length];
HFASSERT(charactersToDelete <= stringLength);
NSRange rangeToDelete = NSMakeRange(stringLength - charactersToDelete, charactersToDelete);
if (rightRangeToStore.length == 0) {
[textStorage deleteCharactersInRange:rangeToDelete];
if (debug) NSLog(@"Right deleting text range %@", NSStringFromRange(rangeToDelete));
}
else {
NSString *rightRangeString = [self newLineStringForRange:rightRangeToStore];
[textStorage replaceCharactersInRange:rangeToDelete withString:rightRangeString];
if (debug) NSLog(@"Replacing text range %@ with %@ (for range %@)", NSStringFromRange(rangeToDelete), rightRangeString, HFRangeToString(rightRangeToStore));
[rightRangeString release];
}
}
}
if (! textAttributes) {
NSMutableParagraphStyle *mutableStyle = [[NSParagraphStyle defaultParagraphStyle] mutableCopy];
[mutableStyle setAlignment:NSRightTextAlignment];
NSParagraphStyle *paragraphStyle = [mutableStyle copy];
[mutableStyle release];
textAttributes = [[NSDictionary alloc] initWithObjectsAndKeys:_font, NSFontAttributeName, [NSColor colorWithCalibratedWhite:(CGFloat).1 alpha:(CGFloat).8], NSForegroundColorAttributeName, paragraphStyle, NSParagraphStyleAttributeName, nil];
[paragraphStyle release];
[textStorage setAttributes:textAttributes range:NSMakeRange(0, [textStorage length])];
}
[textStorage endEditing];
#if ! NDEBUG
NSString *comparisonString = [self newLineStringForRange:HFRangeMake(startingLineIndex, linesRemaining)];
if (! [comparisonString isEqualToString:[textStorage string]]) {
NSLog(@"Not equal!");
NSLog(@"Expected:\n%@", comparisonString);
NSLog(@"Actual:\n%@", [textStorage string]);
}
HFASSERT([comparisonString isEqualToString:[textStorage string]]);
[comparisonString release];
#endif
storedLineIndex = startingLineIndex;
storedLineCount = linesRemaining;
}
- (void)drawLineNumbersWithClipSingleStringDrawing:(NSRect)clipRect {
USE(clipRect);
unsigned long long lineIndex = HFFPToUL(floorl(_lineRangeToDraw.location));
NSUInteger linesRemaining = ll2l(HFFPToUL(ceill(_lineRangeToDraw.length + _lineRangeToDraw.location) - floorl(_lineRangeToDraw.location)));
CGFloat linesToVerticallyOffset = ld2f(_lineRangeToDraw.location - floorl(_lineRangeToDraw.location));
CGFloat verticalOffset = linesToVerticallyOffset * _lineHeight + 1;
NSRect textRect = self.bounds;
textRect.size.width -= 5;
textRect.origin.y -= verticalOffset;
textRect.size.height += verticalOffset;
if (! textAttributes) {
NSMutableParagraphStyle *mutableStyle = [[NSParagraphStyle defaultParagraphStyle] mutableCopy];
[mutableStyle setAlignment:NSRightTextAlignment];
[mutableStyle setMinimumLineHeight:_lineHeight];
[mutableStyle setMaximumLineHeight:_lineHeight];
NSParagraphStyle *paragraphStyle = [mutableStyle copy];
[mutableStyle release];
textAttributes = [[NSDictionary alloc] initWithObjectsAndKeys:_font, NSFontAttributeName, [NSColor colorWithCalibratedWhite:(CGFloat).1 alpha:(CGFloat).8], NSForegroundColorAttributeName, paragraphStyle, NSParagraphStyleAttributeName, nil];
[paragraphStyle release];
}
NSString *string = [self newLineStringForRange:HFRangeMake(lineIndex, linesRemaining)];
[string drawInRect:textRect withAttributes:textAttributes];
[string release];
}
- (void)drawLineNumbersWithClipSingleStringCellDrawing:(NSRect)clipRect {
USE(clipRect);
const CGFloat cellTextContainerPadding = 2.f;
unsigned long long lineIndex = HFFPToUL(floorl(_lineRangeToDraw.location));
NSUInteger linesRemaining = ll2l(HFFPToUL(ceill(_lineRangeToDraw.length + _lineRangeToDraw.location) - floorl(_lineRangeToDraw.location)));
CGFloat linesToVerticallyOffset = ld2f(_lineRangeToDraw.location - floorl(_lineRangeToDraw.location));
CGFloat verticalOffset = linesToVerticallyOffset * _lineHeight + 1;
NSRect textRect = self.bounds;
textRect.size.width -= 5;
textRect.origin.y -= verticalOffset;
textRect.origin.x += cellTextContainerPadding;
textRect.size.height += verticalOffset;
if (! textAttributes) {
NSMutableParagraphStyle *mutableStyle = [[NSParagraphStyle defaultParagraphStyle] mutableCopy];
[mutableStyle setAlignment:NSRightTextAlignment];
[mutableStyle setMinimumLineHeight:_lineHeight];
[mutableStyle setMaximumLineHeight:_lineHeight];
NSParagraphStyle *paragraphStyle = [mutableStyle copy];
[mutableStyle release];
textAttributes = [[NSDictionary alloc] initWithObjectsAndKeys:_font, NSFontAttributeName, [NSColor colorWithCalibratedWhite:(CGFloat).1 alpha:(CGFloat).8], NSForegroundColorAttributeName, paragraphStyle, NSParagraphStyleAttributeName, nil];
[paragraphStyle release];
}
NSString *string = [self newLineStringForRange:HFRangeMake(lineIndex, linesRemaining)];
NSAttributedString *attributedString = [[NSAttributedString alloc] initWithString:string attributes:textAttributes];
[string release];
NSCell *cell = [[NSCell alloc] initTextCell:@""];
[cell setAttributedStringValue:attributedString];
[cell drawWithFrame:textRect inView:self];
[[NSColor purpleColor] set];
NSFrameRect(textRect);
[cell release];
[attributedString release];
}
- (void)drawLineNumbersWithClipFullLayoutManager:(NSRect)clipRect {
USE(clipRect);
unsigned long long lineIndex = HFFPToUL(floorl(_lineRangeToDraw.location));
NSUInteger linesRemaining = ll2l(HFFPToUL(ceill(_lineRangeToDraw.length + _lineRangeToDraw.location) - floorl(_lineRangeToDraw.location)));
if (lineIndex != storedLineIndex || linesRemaining != storedLineCount) {
[self updateLayoutManagerWithLineIndex:lineIndex lineCount:linesRemaining];
}
CGFloat verticalOffset = ld2f(_lineRangeToDraw.location - floorl(_lineRangeToDraw.location));
NSPoint textPoint = self.bounds.origin;
textPoint.y -= verticalOffset * _lineHeight;
[layoutManager drawGlyphsForGlyphRange:NSMakeRange(0, [layoutManager numberOfGlyphs]) atPoint:textPoint];
}
- (void)drawLineNumbersWithClip:(NSRect)clipRect {
#if TIME_LINE_NUMBERS
CFAbsoluteTime startTime = CFAbsoluteTimeGetCurrent();
#endif
NSInteger drawingMode = (useStringDrawingPath ? 1 : 3);
switch (drawingMode) {
// Drawing can't be done right if fonts are wider than expected, but all
// of these have rather nasty behavior in that case. I've commented what
// that behavior is; the comment is hypothetical 'could' if it shouldn't
// actually be a problem in practice.
// TODO: Make a drawing mode that is "Fonts could get clipped if too wide"
// because that seems like better behavior than any of these.
case 0:
// Most fonts are too wide and every character gets piled on right (unreadable).
[self drawLineNumbersWithClipLayoutManagerPerLine:clipRect];
break;
case 1:
// Last characters could get omitted (*not* clipped) if too wide.
// Also, most fonts have bottoms clipped (very unsigntly).
[self drawLineNumbersWithClipStringDrawing:clipRect];
break;
case 2:
// Most fonts are too wide and wrap (breaks numbering).
[self drawLineNumbersWithClipFullLayoutManager:clipRect];
break;
case 3:
// Fonts could wrap if too wide (breaks numbering).
// *Note that that this is the only mode that generally works.*
[self drawLineNumbersWithClipSingleStringDrawing:clipRect];
break;
case 4:
// Most fonts are too wide and wrap (breaks numbering).
[self drawLineNumbersWithClipSingleStringCellDrawing:clipRect];
break;
}
#if TIME_LINE_NUMBERS
CFAbsoluteTime endTime = CFAbsoluteTimeGetCurrent();
NSLog(@"Line number time: %f", endTime - startTime);
#endif
}
- (void)drawRect:(NSRect)clipRect {
[self drawGradientWithClip:clipRect];
[self drawDividerWithClip:clipRect];
[self drawLineNumbersWithClip:clipRect];
}
- (void)setLineRangeToDraw:(HFFPRange)range {
if (! HFFPRangeEqualsRange(range, _lineRangeToDraw)) {
_lineRangeToDraw = range;
[self setNeedsDisplay:YES];
}
}
- (void)setBytesPerLine:(NSUInteger)val {
if (_bytesPerLine != val) {
_bytesPerLine = val;
storedLineCount = INVALID_LINE_COUNT;
[self setNeedsDisplay:YES];
}
}
- (void)setLineNumberFormat:(HFLineNumberFormat)format {
if (format != _lineNumberFormat) {
_lineNumberFormat = format;
storedLineCount = INVALID_LINE_COUNT;
[self setNeedsDisplay:YES];
}
}
- (BOOL)canUseStringDrawingPathForFont:(NSFont *)testFont {
NSString *name = [testFont fontName];
// No, Menlo does not work here.
return [name isEqualToString:@"Monaco"] || [name isEqualToString:@"Courier"] || [name isEqualToString:@"Consolas"];
}
- (void)setFont:(NSFont *)val {
if (val != _font) {
[_font release];
_font = [val copy];
[textStorage deleteCharactersInRange:NSMakeRange(0, [textStorage length])]; //delete the characters so we know to set the font next time we render
[textAttributes release];
textAttributes = nil;
storedLineCount = INVALID_LINE_COUNT;
useStringDrawingPath = [self canUseStringDrawingPathForFont:_font];
[self setNeedsDisplay:YES];
}
}
- (void)setLineHeight:(CGFloat)height {
if (_lineHeight != height) {
_lineHeight = height;
[self setNeedsDisplay:YES];
}
}
- (void)setFrameSize:(NSSize)size {
[super setFrameSize:size];
[textContainer setContainerSize:NSMakeSize(self.bounds.size.width, [textContainer containerSize].height)];
}
- (void)mouseDown:(NSEvent *)event {
USE(event);
// [_representer cycleLineNumberFormat];
}
- (void)scrollWheel:(NSEvent *)scrollEvent {
[_representer.controller scrollWithScrollEvent:scrollEvent];
}
+ (NSUInteger)digitsRequiredToDisplayLineNumber:(unsigned long long)lineNumber inFormat:(HFLineNumberFormat)format {
switch (format) {
case HFLineNumberFormatDecimal: return HFCountDigitsBase10(lineNumber);
#if HEX_LINE_NUMBERS_HAVE_0X_PREFIX
case HFLineNumberFormatHexadecimal: return 2 + HFCountDigitsBase16(lineNumber);
#else
case HFLineNumberFormatHexadecimal: return HFCountDigitsBase16(lineNumber);
#endif
default: return 0;
}
}
@end

View File

@ -0,0 +1,51 @@
//
// HFPasteboardOwner.h
// HexFiend_2
//
// Copyright 2008 ridiculous_fish. All rights reserved.
//
#import <Cocoa/Cocoa.h>
@class HFByteArray;
extern NSString *const HFPrivateByteArrayPboardType;
@interface HFPasteboardOwner : NSObject {
@private
HFByteArray *byteArray;
NSPasteboard *pasteboard; //not retained
unsigned long long dataAmountToCopy;
NSUInteger bytesPerLine;
BOOL retainedSelfOnBehalfOfPboard;
BOOL backgroundCopyOperationFinished;
BOOL didStartModalSessionForBackgroundCopyOperation;
}
/* Creates an HFPasteboardOwner to own the given pasteboard with the given types. Note that the NSPasteboard retains its owner. */
+ (id)ownPasteboard:(NSPasteboard *)pboard forByteArray:(HFByteArray *)array withTypes:(NSArray *)types;
- (HFByteArray *)byteArray;
/* Performs a copy to pasteboard with progress reporting. This must be overridden if you support types other than the private pboard type. */
- (void)writeDataInBackgroundToPasteboard:(NSPasteboard *)pboard ofLength:(unsigned long long)length forType:(NSString *)type trackingProgress:(id)tracker;
/* NSPasteboard delegate methods, declared here to indicate that subclasses should call super */
- (void)pasteboard:(NSPasteboard *)sender provideDataForType:(NSString *)type;
- (void)pasteboardChangedOwner:(NSPasteboard *)pboard;
/* Useful property that several pasteboard types want to know */
@property (nonatomic) NSUInteger bytesPerLine;
/* For efficiency, Hex Fiend writes pointers to HFByteArrays into pasteboards. In the case that the user quits and relaunches Hex Fiend, we don't want to read a pointer from the old process, so each process we generate a UUID. This is constant for the lifetime of the process. */
+ (NSString *)uuid;
/* Unpacks a byte array from a pasteboard, preferring HFPrivateByteArrayPboardType */
+ (HFByteArray *)unpackByteArrayFromPasteboard:(NSPasteboard *)pasteboard;
/* Used to handle the case where copying data will require a lot of memory and give the user a chance to confirm. */
- (unsigned long long)amountToCopyForDataLength:(unsigned long long)numBytes stringLength:(unsigned long long)stringLength;
/* Must be overridden to return the length of a string containing this number of bytes. */
- (unsigned long long)stringLengthForDataLength:(unsigned long long)dataLength;
@end

287
HexFiend/HFPasteboardOwner.m Executable file
View File

@ -0,0 +1,287 @@
//
// HFPasteboardOwner.m
// HexFiend_2
//
// Copyright 2008 ridiculous_fish. All rights reserved.
//
#import <HexFiend/HFPasteboardOwner.h>
#import <HexFiend/HFController.h>
#import <HexFiend/HFByteArray.h>
#import <objc/message.h>
NSString *const HFPrivateByteArrayPboardType = @"HFPrivateByteArrayPboardType";
@implementation HFPasteboardOwner
+ (void)initialize {
if (self == [HFPasteboardOwner class]) {
[[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(prepareCommonPasteboardsForChangeInFileNotification:) name:HFPrepareForChangeInFileNotification object:nil];
}
}
- (instancetype)initWithPasteboard:(NSPasteboard *)pboard forByteArray:(HFByteArray *)array withTypes:(NSArray *)types {
REQUIRE_NOT_NULL(pboard);
REQUIRE_NOT_NULL(array);
REQUIRE_NOT_NULL(types);
self = [super init];
byteArray = [array retain];
pasteboard = pboard;
[pasteboard declareTypes:types owner:self];
// get notified when we're about to write a file, so that if they're overwriting a file backing part of our byte array, we can properly clear or preserve our pasteboard
[[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(changeInFileNotification:) name:HFPrepareForChangeInFileNotification object:nil];
return self;
}
+ (id)ownPasteboard:(NSPasteboard *)pboard forByteArray:(HFByteArray *)array withTypes:(NSArray *)types {
return [[[self alloc] initWithPasteboard:pboard forByteArray:array withTypes:types] autorelease];
}
- (void)tearDownPasteboardReferenceIfExists {
if (pasteboard) {
pasteboard = nil;
[[NSNotificationCenter defaultCenter] removeObserver:self name:HFPrepareForChangeInFileNotification object:nil];
}
if (retainedSelfOnBehalfOfPboard) {
CFRelease(self);
retainedSelfOnBehalfOfPboard = NO;
}
}
+ (HFByteArray *)_unpackByteArrayFromDictionary:(NSDictionary *)byteArrayDictionary {
HFByteArray *result = nil;
if (byteArrayDictionary) {
NSString *uuid = byteArrayDictionary[@"HFUUID"];
if ([uuid isEqual:[self uuid]]) {
result = (HFByteArray *)[byteArrayDictionary[@"HFByteArray"] unsignedLongValue];
}
}
return result;
}
+ (HFByteArray *)unpackByteArrayFromPasteboard:(NSPasteboard *)pasteboard {
REQUIRE_NOT_NULL(pasteboard);
HFByteArray *result = [self _unpackByteArrayFromDictionary:[pasteboard propertyListForType:HFPrivateByteArrayPboardType]];
return result;
}
/* Try to fix up commonly named pasteboards when a file is about to be saved */
+ (void)prepareCommonPasteboardsForChangeInFileNotification:(NSNotification *)notification {
const BOOL *cancellationPointer = [[notification userInfo][HFChangeInFileShouldCancelKey] pointerValue];
if (*cancellationPointer) return; //don't do anything if someone requested cancellation
NSDictionary *userInfo = [notification userInfo];
NSArray *changedRanges = userInfo[HFChangeInFileModifiedRangesKey];
HFFileReference *fileReference = [notification object];
NSMutableDictionary *hint = userInfo[HFChangeInFileHintKey];
NSString * const names[] = {NSGeneralPboard, NSFindPboard, NSDragPboard};
NSUInteger i;
for (i=0; i < sizeof names / sizeof *names; i++) {
NSPasteboard *pboard = [NSPasteboard pasteboardWithName:names[i]];
HFByteArray *byteArray = [self unpackByteArrayFromPasteboard:pboard];
if (byteArray && ! [byteArray clearDependenciesOnRanges:changedRanges inFile:fileReference hint:hint]) {
/* This pasteboard no longer works */
[pboard declareTypes:@[] owner:nil];
}
}
}
- (void)changeInFileNotification:(NSNotification *)notification {
HFASSERT(pasteboard != nil);
HFASSERT(byteArray != nil);
NSDictionary *userInfo = [notification userInfo];
const BOOL *cancellationPointer = [userInfo[HFChangeInFileShouldCancelKey] pointerValue];
if (*cancellationPointer) return; //don't do anything if someone requested cancellation
NSMutableDictionary *hint = userInfo[HFChangeInFileHintKey];
NSArray *changedRanges = [notification userInfo][HFChangeInFileModifiedRangesKey];
HFFileReference *fileReference = [notification object];
if (! [byteArray clearDependenciesOnRanges:changedRanges inFile:fileReference hint:hint]) {
/* We can't do it */
[self tearDownPasteboardReferenceIfExists];
}
}
- (void)dealloc {
[self tearDownPasteboardReferenceIfExists];
[byteArray release];
[super dealloc];
}
- (void)writeDataInBackgroundToPasteboard:(NSPasteboard *)pboard ofLength:(unsigned long long)length forType:(NSString *)type trackingProgress:(id)tracker {
USE(length);
USE(pboard);
USE(type);
USE(tracker);
UNIMPLEMENTED_VOID();
}
- (void)backgroundMoveDataToPasteboard:(NSString *)type {
@autoreleasepool {
[self writeDataInBackgroundToPasteboard:pasteboard ofLength:dataAmountToCopy forType:type trackingProgress:nil];
[self performSelectorOnMainThread:@selector(backgroundMoveDataFinished:) withObject:nil waitUntilDone:NO];
}
}
- (void)backgroundMoveDataFinished:unused {
USE(unused);
HFASSERT(backgroundCopyOperationFinished == NO);
backgroundCopyOperationFinished = YES;
if (! didStartModalSessionForBackgroundCopyOperation) {
/* We haven't started the modal session, so make sure it never happens */
[NSObject cancelPreviousPerformRequestsWithTarget:self selector:@selector(beginModalSessionForBackgroundCopyOperation:) object:nil];
CFRunLoopWakeUp(CFRunLoopGetCurrent());
}
else {
/* We have started the modal session, so end it. */
[NSApp stopModalWithCode:0];
//stopModal: won't trigger unless we post a do-nothing event
NSEvent *event = [NSEvent otherEventWithType:NSApplicationDefined location:NSZeroPoint modifierFlags:0 timestamp:0 windowNumber:0 context:NULL subtype:0 data1:0 data2:0];
[NSApp postEvent:event atStart:NO];
}
}
- (void)beginModalSessionForBackgroundCopyOperation:(id)unused {
USE(unused);
HFASSERT(backgroundCopyOperationFinished == NO);
HFASSERT(didStartModalSessionForBackgroundCopyOperation == NO);
didStartModalSessionForBackgroundCopyOperation = YES;
}
- (BOOL)moveDataWithProgressReportingToPasteboard:(NSPasteboard *)pboard forType:(NSString *)type {
// The -[NSRunLoop runMode:beforeDate:] call in the middle of this function can cause it to be
// called reentrantly, which was previously causing leaks and use-after-free crashes. For
// some reason this happens basically always when copying lots of data into VMware Fusion.
// I'm not even sure what the ideal behavior would be here, but am fairly certain that this
// is the best that can be done without rewriting a portion of the background copying code.
// TODO: Figure out what the ideal behavior should be here.
HFASSERT(pboard == pasteboard);
[self retain]; //resolving the pasteboard may release us, which deallocates us, which deallocates our tracker...make sure we survive through this function
/* Give the user a chance to request a smaller amount if it's really big */
unsigned long long availableAmount = [byteArray length];
unsigned long long amountToCopy = [self amountToCopyForDataLength:availableAmount stringLength:[self stringLengthForDataLength:availableAmount]];
if (amountToCopy > 0) {
backgroundCopyOperationFinished = NO;
didStartModalSessionForBackgroundCopyOperation = NO;
dataAmountToCopy = amountToCopy;
[NSThread detachNewThreadSelector:@selector(backgroundMoveDataToPasteboard:) toTarget:self withObject:type];
[self performSelector:@selector(beginModalSessionForBackgroundCopyOperation:) withObject:nil afterDelay:1.0 inModes:@[NSModalPanelRunLoopMode]];
while (! backgroundCopyOperationFinished) {
[[NSRunLoop currentRunLoop] runMode:NSModalPanelRunLoopMode beforeDate:[NSDate distantFuture]];
}
}
[self release];
return YES;
}
- (void)pasteboardChangedOwner:(NSPasteboard *)pboard {
HFASSERT(pasteboard == pboard);
[self tearDownPasteboardReferenceIfExists];
}
- (HFByteArray *)byteArray {
return byteArray;
}
- (void)pasteboard:(NSPasteboard *)pboard provideDataForType:(NSString *)type {
if (! pasteboard) {
/* Don't do anything, because we've torn down our pasteboard */
return;
}
if ([type isEqualToString:HFPrivateByteArrayPboardType]) {
if (! retainedSelfOnBehalfOfPboard) {
retainedSelfOnBehalfOfPboard = YES;
CFRetain(self);
}
NSDictionary *dict = @{@"HFByteArray": @((unsigned long)byteArray),
@"HFUUID": [[self class] uuid]};
[pboard setPropertyList:dict forType:type];
}
else {
if (! [self moveDataWithProgressReportingToPasteboard:pboard forType:type]) {
[pboard setData:[NSData data] forType:type];
}
}
}
- (void)setBytesPerLine:(NSUInteger)val { bytesPerLine = val; }
- (NSUInteger)bytesPerLine { return bytesPerLine; }
+ (NSString *)uuid {
static NSString *uuid;
if (! uuid) {
CFUUIDRef uuidRef = CFUUIDCreate(NULL);
uuid = (NSString *)CFUUIDCreateString(NULL, uuidRef);
CFRelease(uuidRef);
}
return uuid;
}
- (unsigned long long)stringLengthForDataLength:(unsigned long long)dataLength { USE(dataLength); UNIMPLEMENTED(); }
- (unsigned long long)amountToCopyForDataLength:(unsigned long long)numBytes stringLength:(unsigned long long)stringLength {
unsigned long long dataLengthResult, stringLengthResult;
NSInteger alertReturn = NSIntegerMax;
const unsigned long long copyOption1 = MAXIMUM_PASTEBOARD_SIZE_TO_EXPORT;
const unsigned long long copyOption2 = MINIMUM_PASTEBOARD_SIZE_TO_WARN_ABOUT;
NSString *option1String = HFDescribeByteCount(copyOption1);
NSString *option2String = HFDescribeByteCount(copyOption2);
NSString* dataSizeDescription = HFDescribeByteCount(stringLength);
if (stringLength >= MAXIMUM_PASTEBOARD_SIZE_TO_EXPORT) {
NSString *option1 = [@"Copy " stringByAppendingString:option1String];
NSString *option2 = [@"Copy " stringByAppendingString:option2String];
alertReturn = NSRunAlertPanel(@"Large Clipboard", @"The copied data would occupy %@ if written to the clipboard. This is larger than the system clipboard supports. Do you want to copy only part of the data?", @"Cancel", option1, option2, dataSizeDescription);
switch (alertReturn) {
case NSAlertDefaultReturn:
default:
stringLengthResult = 0;
break;
case NSAlertAlternateReturn:
stringLengthResult = copyOption1;
break;
case NSAlertOtherReturn:
stringLengthResult = copyOption2;
break;
}
}
else if (stringLength >= MINIMUM_PASTEBOARD_SIZE_TO_WARN_ABOUT) {
NSString *option1 = [@"Copy " stringByAppendingString:HFDescribeByteCount(stringLength)];
NSString *option2 = [@"Copy " stringByAppendingString:HFDescribeByteCount(copyOption2)];
alertReturn = NSRunAlertPanel(@"Large Clipboard", @"The copied data would occupy %@ if written to the clipboard. Performing this copy may take a long time. Do you want to copy only part of the data?", @"Cancel", option1, option2, dataSizeDescription);
switch (alertReturn) {
case NSAlertDefaultReturn:
default:
stringLengthResult = 0;
break;
case NSAlertAlternateReturn:
stringLengthResult = stringLength;
break;
case NSAlertOtherReturn:
stringLengthResult = copyOption2;
break;
}
}
else {
/* Small enough to copy it all */
stringLengthResult = stringLength;
}
/* Convert from string length to data length */
if (stringLengthResult == stringLength) {
dataLengthResult = numBytes;
}
else {
unsigned long long divisor = stringLength / numBytes;
dataLengthResult = stringLengthResult / divisor;
}
return dataLengthResult;
}
@end

View File

@ -0,0 +1,3 @@
#ifndef HF_NO_PRIVILEGED_FILE_OPERATIONS
#define HF_NO_PRIVILEGED_FILE_OPERATIONS
#endif

121
HexFiend/HFRepresenter.h Normal file
View File

@ -0,0 +1,121 @@
//
// HFRepresenter.h
// HexFiend_2
//
// Copyright 2007 ridiculous_fish. All rights reserved.
//
#import <Cocoa/Cocoa.h>
#import <HexFiend/HFController.h>
/*! @class HFRepresenter
@brief The principal view class of Hex Fiend's MVC architecture.
HFRepresenter is a class that visually represents some property of the HFController, such as the data (in various formats), the scroll position, the line number, etc. An HFRepresenter is added to an HFController and then gets notified of changes to various properties, through the controllerDidChange: methods.
HFRepresenters also have a view, accessible through the -view method. The HFRepresenter is expected to update its view to reflect the relevant properties of its HFController. If the user can interact with the view, then the HFRepresenter should pass any changes down to the HFController, which will subsequently notify all HFRepresenters of the change.
HFRepresenter is an abstract class, with a different subclass for each possible view type. Because HFController interacts with HFRepresenters, rather than views directly, an HFRepresenter can use standard Cocoa views and controls.
To add a new view type:
-# Create a subclass of HFRepresenter
-# Override \c -createView to return a view (note that this method should transfer ownership)
-# Override \c -controllerDidChange:, checking the bitmask to see what properties have changed and updating your view as appropriate
-# If you plan on using this view together with other views, override \c +defaultLayoutPosition to control how your view gets positioned in an HFLayoutRepresenter
-# If your view's width depends on the properties of the controller, override some of the measurement methods, such as \c +maximumBytesPerLineForViewWidth:, so that your view gets sized correctly
*/
@interface HFRepresenter : NSObject <NSCoding> {
@private
id view;
HFController *controller;
NSPoint layoutPosition;
}
/*! @name View management
Methods related to accessing and initializing the representer's view.
*/
//@{
/*! Returns the view for the receiver, creating it if necessary. The view for the HFRepresenter is initially nil. When the \c -view method is called, if the view is nil, \c -createView is called and then the result is stored. This method should not be overridden; however you may want to call it to access the view.
*/
- (id)view;
/*! Returns YES if the view has been created, NO if it has not. To create the view, call the view method.
*/
- (BOOL)isViewLoaded;
/*! Override point for creating the view displaying this representation. This is called on your behalf the first time the \c -view method is called, so you would not want to call this explicitly; however this method must be overridden. This follows the "create" rule, and so it should return a retained view.
*/
- (NSView *)createView NS_RETURNS_RETAINED;
/*! Override point for initialization of view, after the HFRepresenter has the view set as its -view property. The default implementation does nothing.
*/
- (void)initializeView;
//@}
/*! @name Accessing the HFController
*/
//@{
/*! Returns the HFController for the receiver. This is set by the controller from the call to \c addRepresenter:. A representer can only be in one controller at a time. */
- (HFController *)controller;
//@}
/*! @name Property change notifications
*/
//@{
/*! Indicates that the properties indicated by the given bits did change, and the view should be updated as to reflect the appropriate properties. This is the main mechanism by which representers are notified of changes to the controller.
*/
- (void)controllerDidChange:(HFControllerPropertyBits)bits;
//@}
/*! @name HFController convenience methods
Convenience covers for certain HFController methods
*/
//@{
/*! Equivalent to <tt>[[self controller] bytesPerLine]</tt> */
- (NSUInteger)bytesPerLine;
/*! Equivalent to <tt>[[self controller] bytesPerColumn]</tt> */
- (NSUInteger)bytesPerColumn;
/*! Equivalent to <tt>[[self controller] representer:self changedProperties:properties]</tt> . You may call this when some internal aspect of the receiver's view (such as its frame) has changed in a way that may globally change some property of the controller, and the controller should recalculate those properties. For example, the text representers call this with HFControllerDisplayedLineRange when the view grows vertically, because more data may be displayed.
*/
- (void)representerChangedProperties:(HFControllerPropertyBits)properties;
//@}
/*! @name Measurement
Methods related to measuring the HFRepresenter, so that it can be laid out properly by an HFLayoutController. All of these methods are candidates for overriding.
*/
//@{
/*! Returns the maximum number of bytes per line for the given view size. The default value is NSUIntegerMax, which means that the representer can display any number of lines for the given view size. */
- (NSUInteger)maximumBytesPerLineForViewWidth:(CGFloat)viewWidth;
/*! Returns the minimum view frame size for the given bytes per line. Default is to return 0, which means that the representer can display the given bytes per line in any view size. Fixed width views should return their fixed width. */
- (CGFloat)minimumViewWidthForBytesPerLine:(NSUInteger)bytesPerLine;
/*! Returns the maximum number of lines that could be displayed at once for a given view height. Default is to return DBL_MAX. */
- (double)maximumAvailableLinesForViewHeight:(CGFloat)viewHeight;
//@}
/*! Returns the required byte granularity. HFLayoutRepresenter will constrain the bytes per line to a multiple of the granularity, e.g. so that UTF-16 characters are not split across lines. If different representers have different granularities, then it will constrain it to a multiple of all granularities, which may be very large. The default implementation returns 1. */
- (NSUInteger)byteGranularity;
/*! @name Auto-layout methods
Methods for simple auto-layout by HFLayoutRepresenter. See the HFLayoutRepresenter class for discussion of how it lays out representer views.
*/
//@{
/// The layout position for the receiver.
@property (nonatomic) NSPoint layoutPosition;
/*! Returns the default layout position for representers of this class. Within the -init method, the view's layout position is set to the default for this class. You may override this to control the default layout position. See HFLayoutRepresenter for a discussion of the significance of the layout postition.
*/
+ (NSPoint)defaultLayoutPosition;
//@}
@end

120
HexFiend/HFRepresenter.m Normal file
View File

@ -0,0 +1,120 @@
//
// HFRepresenter.m
// HexFiend_2
//
// Copyright 2007 ridiculous_fish. All rights reserved.
//
#import "HFRepresenter.h"
@implementation HFRepresenter
- (id)view {
if (! view) {
view = [self createView];
[self initializeView];
}
return view;
}
- (BOOL)isViewLoaded {
return !! view;
}
- (void)initializeView {
}
- (instancetype)init {
self = [super init];
[self setLayoutPosition:[[self class] defaultLayoutPosition]];
return self;
}
- (void)dealloc {
[view release];
[super dealloc];
}
- (void)encodeWithCoder:(NSCoder *)coder {
HFASSERT([coder allowsKeyedCoding]);
[coder encodeObject:controller forKey:@"HFController"];
[coder encodePoint:layoutPosition forKey:@"HFLayoutPosition"];
[coder encodeObject:view forKey:@"HFRepresenterView"];
}
- (instancetype)initWithCoder:(NSCoder *)coder {
HFASSERT([coder allowsKeyedCoding]);
self = [super init];
layoutPosition = [coder decodePointForKey:@"HFLayoutPosition"];
controller = [coder decodeObjectForKey:@"HFController"]; // not retained
view = [[coder decodeObjectForKey:@"HFRepresenterView"] retain];
return self;
}
- (NSView *)createView {
UNIMPLEMENTED();
}
- (HFController *)controller {
return controller;
}
- (void)_setController:(HFController *)val {
controller = val;
}
- (void)controllerDidChange:(HFControllerPropertyBits)bits {
USE(bits);
}
- (NSUInteger)bytesPerLine {
HFASSERT([self controller] != nil);
return [[self controller] bytesPerLine];
}
- (NSUInteger)bytesPerColumn {
HFASSERT([self controller] != nil);
return [[self controller] bytesPerColumn];
}
- (NSUInteger)maximumBytesPerLineForViewWidth:(CGFloat)viewWidth {
USE(viewWidth);
return NSUIntegerMax;
}
- (CGFloat)minimumViewWidthForBytesPerLine:(NSUInteger)bytesPerLine {
USE(bytesPerLine);
return 0;
}
- (NSUInteger)byteGranularity {
return 1;
}
- (double)maximumAvailableLinesForViewHeight:(CGFloat)viewHeight {
USE(viewHeight);
return DBL_MAX;
}
- (void)selectAll:sender {
[[self controller] selectAll:sender];
}
- (void)representerChangedProperties:(HFControllerPropertyBits)properties {
[[self controller] representer:self changedProperties:properties];
}
- (void)setLayoutPosition:(NSPoint)position {
layoutPosition = position;
}
- (NSPoint)layoutPosition {
return layoutPosition;
}
+ (NSPoint)defaultLayoutPosition {
return NSMakePoint(0, 0);
}
@end

View File

@ -0,0 +1,21 @@
//
// HFRepresenterHexTextView.h
// HexFiend_2
//
// Copyright 2007 ridiculous_fish. All rights reserved.
//
#import <HexFiend/HFRepresenterTextView.h>
@interface HFRepresenterHexTextView : HFRepresenterTextView {
CGGlyph glyphTable[17];
CGFloat glyphAdvancement;
CGFloat spaceAdvancement;
BOOL hidesNullBytes;
}
@property(nonatomic) BOOL hidesNullBytes;
@end

View File

@ -0,0 +1,95 @@
//
// HFRepresenterHexTextView.m
// HexFiend_2
//
// Copyright 2007 ridiculous_fish. All rights reserved.
//
#import <HexFiend/HFRepresenterHexTextView.h>
#import <HexFiend/HFRepresenterTextView_Internal.h>
#import <HexFiend/HFHexTextRepresenter.h>
@implementation HFRepresenterHexTextView
- (void)generateGlyphTable {
const UniChar hexchars[17] = {'0','1','2','3','4','5','6','7','8','9','A','B','C','D','E','F',' '/* Plus a space char at the end for null bytes. */};
_Static_assert(sizeof(CGGlyph[17]) == sizeof(glyphTable), "glyphTable is the wrong type");
NSFont *font = [[self font] screenFont];
bool t = CTFontGetGlyphsForCharacters((CTFontRef)font, hexchars, glyphTable, 17);
HFASSERT(t); // We don't take kindly to strange fonts around here.
CGFloat maxAdv = 0.0;
for(int i = 0; i < 17; i++) maxAdv = HFMax(maxAdv, [font advancementForGlyph:glyphTable[i]].width);
glyphAdvancement = maxAdv;
spaceAdvancement = maxAdv;
}
- (void)setFont:(NSFont *)font {
[super setFont:font];
[self generateGlyphTable];
}
- (instancetype)initWithCoder:(NSCoder *)coder {
HFASSERT([coder allowsKeyedCoding]);
self = [super initWithCoder:coder];
[self generateGlyphTable];
return self;
}
//no need for encodeWithCoder
- (void)extractGlyphsForBytes:(const unsigned char *)bytes count:(NSUInteger)numBytes offsetIntoLine:(NSUInteger)offsetIntoLine intoArray:(struct HFGlyph_t *)glyphs advances:(CGSize *)advances resultingGlyphCount:(NSUInteger *)resultGlyphCount {
HFASSERT(bytes != NULL);
HFASSERT(glyphs != NULL);
HFASSERT(numBytes <= NSUIntegerMax);
HFASSERT(resultGlyphCount != NULL);
const NSUInteger bytesPerColumn = [self bytesPerColumn];
NSUInteger glyphIndex = 0, byteIndex = 0;
NSUInteger remainingBytesInThisColumn = (bytesPerColumn ? bytesPerColumn - offsetIntoLine % bytesPerColumn : NSUIntegerMax);
CGFloat advanceBetweenColumns = [self advanceBetweenColumns];
while (byteIndex < numBytes) {
unsigned char byte = bytes[byteIndex++];
CGFloat glyphAdvancementPlusAnySpace = glyphAdvancement;
if (--remainingBytesInThisColumn == 0) {
remainingBytesInThisColumn = bytesPerColumn;
glyphAdvancementPlusAnySpace += advanceBetweenColumns;
}
BOOL useBlank = (hidesNullBytes && byte == 0);
advances[glyphIndex] = CGSizeMake(glyphAdvancement, 0);
glyphs[glyphIndex++] = (struct HFGlyph_t){.fontIndex = 0, .glyph = glyphTable[(useBlank? 16: byte >> 4)]};
advances[glyphIndex] = CGSizeMake(glyphAdvancementPlusAnySpace, 0);
glyphs[glyphIndex++] = (struct HFGlyph_t){.fontIndex = 0, .glyph = glyphTable[(useBlank? 16: byte & 0xF)]};
}
*resultGlyphCount = glyphIndex;
}
- (CGFloat)advancePerCharacter {
return 2 * glyphAdvancement;
}
- (CGFloat)advanceBetweenColumns {
return glyphAdvancement;
}
- (NSUInteger)maximumGlyphCountForByteCount:(NSUInteger)byteCount {
return 2 * byteCount;
}
- (BOOL)hidesNullBytes {
return hidesNullBytes;
}
- (void)setHidesNullBytes:(BOOL)flag
{
flag = !! flag;
if (hidesNullBytes != flag) {
hidesNullBytes = flag;
[self setNeedsDisplay:YES];
}
}
@end

View File

@ -0,0 +1,37 @@
//
// HFRepresenterStringEncodingTextView.h
// HexFiend_2
//
// Copyright 2007 ridiculous_fish. All rights reserved.
//
#import <HexFiend/HFRepresenterTextView.h>
#import <HexFiend/HFGlyphTrie.h>
@interface HFRepresenterStringEncodingTextView : HFRepresenterTextView {
/* Tier 0 data (always up to date) */
NSStringEncoding encoding;
uint8_t bytesPerChar;
/* Tier 1 data (computed synchronously on-demand) */
BOOL tier1DataIsStale;
struct HFGlyph_t replacementGlyph;
CGFloat glyphAdvancement;
/* Tier 2 data (computed asynchronously on-demand) */
struct HFGlyphTrie_t glyphTable;
NSArray *fontCache;
/* Background thread */
OSSpinLock glyphLoadLock;
BOOL requestedCancel;
NSMutableArray *fonts;
NSMutableIndexSet *requestedCharacters;
NSOperationQueue *glyphLoader;
}
/// Set and get the NSStringEncoding that is used
@property (nonatomic) NSStringEncoding encoding;
@end

View File

@ -0,0 +1,540 @@
//
// HFRepresenterStringEncodingTextView.m
// HexFiend_2
//
// Copyright 2007 ridiculous_fish. All rights reserved.
//
#import <HexFiend/HFRepresenterStringEncodingTextView.h>
#import <HexFiend/HFRepresenterTextView_Internal.h>
#include <malloc/malloc.h>
@implementation HFRepresenterStringEncodingTextView
static NSString *copy1CharStringForByteValue(unsigned long long byteValue, NSUInteger bytesPerChar, NSStringEncoding encoding) {
NSString *result = nil;
unsigned char bytes[sizeof byteValue];
/* If we are little endian, then the bytesPerChar doesn't matter, because it will all come out the same. If we are big endian, then it does matter. */
#if ! __BIG_ENDIAN__
*(unsigned long long *)bytes = byteValue;
#else
if (bytesPerChar == sizeof(uint8_t)) {
*(uint8_t *)bytes = (uint8_t)byteValue;
} else if (bytesPerChar == sizeof(uint16_t)) {
*(uint16_t *)bytes = (uint16_t)byteValue;
} else if (bytesPerChar == sizeof(uint32_t)) {
*(uint32_t *)bytes = (uint32_t)byteValue;
} else if (bytesPerChar == sizeof(uint64_t)) {
*(uint64_t *)bytes = (uint64_t)byteValue;
} else {
[NSException raise:NSInvalidArgumentException format:@"Unsupported bytesPerChar of %u", bytesPerChar];
}
#endif
/* ASCII is mishandled :( */
BOOL encodingOK = YES;
if (encoding == NSASCIIStringEncoding && bytesPerChar == 1 && bytes[0] > 0x7F) {
encodingOK = NO;
}
/* Now create a string from these bytes */
if (encodingOK) {
result = [[NSString alloc] initWithBytes:bytes length:bytesPerChar encoding:encoding];
if ([result length] > 1) {
/* Try precomposing it */
NSString *temp = [[result precomposedStringWithCompatibilityMapping] copy];
[result release];
result = temp;
}
/* Ensure it has exactly one character */
if ([result length] != 1) {
[result release];
result = nil;
}
}
/* All done */
return result;
}
static BOOL getGlyphs(CGGlyph *glyphs, NSString *string, NSFont *inputFont) {
NSUInteger length = [string length];
HFASSERT(inputFont != nil);
NEW_ARRAY(UniChar, chars, length);
[string getCharacters:chars range:NSMakeRange(0, length)];
bool result = CTFontGetGlyphsForCharacters((CTFontRef)inputFont, chars, glyphs, length);
/* A NO return means some or all characters were not mapped. This is OK. We'll use the replacement glyph. Unless we're calculating the replacement glyph! Hmm...maybe we should have a series of replacement glyphs that we try? */
////////////////////////
// Workaround for a Mavericks bug. Still present as of 10.9.5
// TODO: Hmm, still? Should look into this again, either it's not a bug or Apple needs a poke.
if(!result) for(NSUInteger i = 0; i < length; i+=15) {
CFIndex x = length-i;
if(x > 15) x = 15;
result = CTFontGetGlyphsForCharacters((CTFontRef)inputFont, chars+i, glyphs+i, x);
if(!result) break;
}
////////////////////////
FREE_ARRAY(chars);
return result;
}
static void generateGlyphs(NSFont *baseFont, NSMutableArray *fonts, struct HFGlyph_t *outGlyphs, NSInteger bytesPerChar, NSStringEncoding encoding, const NSUInteger *charactersToLoad, NSUInteger charactersToLoadCount, CGFloat *outMaxAdvance) {
/* If the caller wants the advance, initialize it to 0 */
if (outMaxAdvance) *outMaxAdvance = 0;
/* Invalid glyph marker */
const struct HFGlyph_t invalidGlyph = {.fontIndex = kHFGlyphFontIndexInvalid, .glyph = -1};
NSCharacterSet *coveredSet = [baseFont coveredCharacterSet];
NSMutableString *coveredGlyphFetchingString = [[NSMutableString alloc] init];
NSMutableIndexSet *coveredGlyphIndexes = [[NSMutableIndexSet alloc] init];
NSMutableString *substitutionFontsGlyphFetchingString = [[NSMutableString alloc] init];
NSMutableIndexSet *substitutionGlyphIndexes = [[NSMutableIndexSet alloc] init];
/* Loop over all the characters, appending them to our glyph fetching string */
NSUInteger idx;
for (idx = 0; idx < charactersToLoadCount; idx++) {
NSString *string = copy1CharStringForByteValue(charactersToLoad[idx], bytesPerChar, encoding);
if (string == nil) {
/* This byte value is not represented in this char set (e.g. upper 128 in ASCII) */
outGlyphs[idx] = invalidGlyph;
} else {
if ([coveredSet characterIsMember:[string characterAtIndex:0]]) {
/* It's covered by our base font */
[coveredGlyphFetchingString appendString:string];
[coveredGlyphIndexes addIndex:idx];
} else {
/* Maybe there's a substitution font */
[substitutionFontsGlyphFetchingString appendString:string];
[substitutionGlyphIndexes addIndex:idx];
}
}
[string release];
}
/* Fetch the non-substitute glyphs */
{
NEW_ARRAY(CGGlyph, cgglyphs, [coveredGlyphFetchingString length]);
BOOL success = getGlyphs(cgglyphs, coveredGlyphFetchingString, baseFont);
HFASSERT(success == YES);
NSUInteger numGlyphs = [coveredGlyphFetchingString length];
/* Fill in our glyphs array */
NSUInteger coveredGlyphIdx = [coveredGlyphIndexes firstIndex];
for (NSUInteger i=0; i < numGlyphs; i++) {
outGlyphs[coveredGlyphIdx] = (struct HFGlyph_t){.fontIndex = 0, .glyph = cgglyphs[i]};
coveredGlyphIdx = [coveredGlyphIndexes indexGreaterThanIndex:coveredGlyphIdx];
/* Record the advancement. Note that this may be more efficient to do in bulk. */
if (outMaxAdvance) *outMaxAdvance = HFMax(*outMaxAdvance, [baseFont advancementForGlyph:cgglyphs[i]].width);
}
HFASSERT(coveredGlyphIdx == NSNotFound); //we must have exhausted the table
FREE_ARRAY(cgglyphs);
}
/* Now do substitution glyphs. */
{
NSUInteger substitutionGlyphIndex = [substitutionGlyphIndexes firstIndex], numSubstitutionChars = [substitutionFontsGlyphFetchingString length];
for (NSUInteger i=0; i < numSubstitutionChars; i++) {
CTFontRef substitutionFont = CTFontCreateForString((CTFontRef)baseFont, (CFStringRef)substitutionFontsGlyphFetchingString, CFRangeMake(i, 1));
if (substitutionFont) {
/* We have a font for this string */
CGGlyph glyph;
unichar c = [substitutionFontsGlyphFetchingString characterAtIndex:i];
NSString *substring = [[NSString alloc] initWithCharacters:&c length:1];
BOOL success = getGlyphs(&glyph, substring, (NSFont *)substitutionFont);
[substring release];
if (! success) {
/* Turns out there wasn't a glyph like we thought there would be, so set an invalid glyph marker */
outGlyphs[substitutionGlyphIndex] = invalidGlyph;
} else {
/* Find the index in fonts. If none, add to it. */
HFASSERT(fonts != nil);
NSUInteger fontIndex = [fonts indexOfObject:(id)substitutionFont];
if (fontIndex == NSNotFound) {
[fonts addObject:(id)substitutionFont];
fontIndex = [fonts count] - 1;
}
/* Now make the glyph */
HFASSERT(fontIndex < UINT16_MAX);
outGlyphs[substitutionGlyphIndex] = (struct HFGlyph_t){.fontIndex = (uint16_t)fontIndex, .glyph = glyph};
}
/* We're done with this */
CFRelease(substitutionFont);
}
substitutionGlyphIndex = [substitutionGlyphIndexes indexGreaterThanIndex:substitutionGlyphIndex];
}
}
[coveredGlyphFetchingString release];
[coveredGlyphIndexes release];
[substitutionFontsGlyphFetchingString release];
[substitutionGlyphIndexes release];
}
static int compareGlyphFontIndexes(const void *p1, const void *p2) {
const struct HFGlyph_t *g1 = p1, *g2 = p2;
if (g1->fontIndex != g2->fontIndex) {
/* Prefer to sort by font index */
return (g1->fontIndex > g2->fontIndex) - (g2->fontIndex > g1->fontIndex);
} else {
/* If they have equal font indexes, sort by glyph value */
return (g1->glyph > g2->glyph) - (g2->glyph > g1->glyph);
}
}
- (void)threadedPrecacheGlyphs:(const struct HFGlyph_t *)glyphs withFonts:(NSArray *)localFonts count:(NSUInteger)count {
/* This method draws glyphs anywhere, so that they get cached by CG and drawing them a second time can be fast. */
NSUInteger i, validGlyphCount;
/* We can use 0 advances */
NEW_ARRAY(CGSize, advances, count);
bzero(advances, count * sizeof *advances);
/* Make a local copy of the glyphs, and sort them according to their font index so that we can draw them with the fewest runs. */
NEW_ARRAY(struct HFGlyph_t, validGlyphs, count);
validGlyphCount = 0;
for (i=0; i < count; i++) {
if (glyphs[i].glyph <= kCGGlyphMax && glyphs[i].fontIndex != kHFGlyphFontIndexInvalid) {
validGlyphs[validGlyphCount++] = glyphs[i];
}
}
qsort(validGlyphs, validGlyphCount, sizeof *validGlyphs, compareGlyphFontIndexes);
/* Remove duplicate glyphs */
NSUInteger trailing = 0;
struct HFGlyph_t lastGlyph = {.glyph = kCGFontIndexInvalid, .fontIndex = kHFGlyphFontIndexInvalid};
for (i=0; i < validGlyphCount; i++) {
if (! HFGlyphEqualsGlyph(lastGlyph, validGlyphs[i])) {
lastGlyph = validGlyphs[i];
validGlyphs[trailing++] = lastGlyph;
}
}
validGlyphCount = trailing;
/* Draw the glyphs in runs */
NEW_ARRAY(CGGlyph, cgglyphs, count);
NSImage *glyphDrawingImage = [[NSImage alloc] initWithSize:NSMakeSize(100, 100)];
[glyphDrawingImage lockFocus];
CGContextRef ctx = [[NSGraphicsContext currentContext] graphicsPort];
HFGlyphFontIndex runFontIndex = -1;
NSUInteger runLength = 0;
for (i=0; i <= validGlyphCount; i++) {
if (i == validGlyphCount || validGlyphs[i].fontIndex != runFontIndex) {
/* End the current run */
if (runLength > 0) {
NSLog(@"Drawing with %@", [localFonts[runFontIndex] screenFont]);
[[localFonts[runFontIndex] screenFont] set];
CGContextSetTextPosition(ctx, 0, 50);
CGContextShowGlyphsWithAdvances(ctx, cgglyphs, advances, runLength);
}
NSLog(@"Drew a run of length %lu", (unsigned long)runLength);
runLength = 0;
if (i < validGlyphCount) runFontIndex = validGlyphs[i].fontIndex;
}
if (i < validGlyphCount) {
/* Append to the current run */
cgglyphs[runLength++] = validGlyphs[i].glyph;
}
}
/* All done */
[glyphDrawingImage unlockFocus];
[glyphDrawingImage release];
FREE_ARRAY(advances);
FREE_ARRAY(validGlyphs);
FREE_ARRAY(cgglyphs);
}
- (void)threadedLoadGlyphs:(id)unused {
/* Note that this is running on a background thread */
USE(unused);
/* Do some things under the lock. Someone else may wish to read fonts, and we're going to write to it, so make a local copy. Also figure out what characters to load. */
NSMutableArray *localFonts;
NSIndexSet *charactersToLoad;
OSSpinLockLock(&glyphLoadLock);
localFonts = [fonts mutableCopy];
charactersToLoad = requestedCharacters;
/* Set requestedCharacters to nil so that the caller knows we aren't going to check again, and will have to re-invoke us. */
requestedCharacters = nil;
OSSpinLockUnlock(&glyphLoadLock);
/* The base font is the first font */
NSFont *font = localFonts[0];
NSUInteger charVal, glyphIdx, charCount = [charactersToLoad count];
NEW_ARRAY(struct HFGlyph_t, glyphs, charCount);
/* Now generate our glyphs */
NEW_ARRAY(NSUInteger, characters, charCount);
[charactersToLoad getIndexes:characters maxCount:charCount inIndexRange:NULL];
generateGlyphs(font, localFonts, glyphs, bytesPerChar, encoding, characters, charCount, NULL);
FREE_ARRAY(characters);
/* The first time we draw glyphs, it's slow, so pre-cache them by drawing them now. */
// This was disabled because it blows up the CG glyph cache
// [self threadedPrecacheGlyphs:glyphs withFonts:localFonts count:charCount];
/* Replace fonts. Do this before we insert into the glyph trie, because the glyph trie references fonts that we're just now putting in the fonts array. */
id oldFonts;
OSSpinLockLock(&glyphLoadLock);
oldFonts = fonts;
fonts = localFonts;
OSSpinLockUnlock(&glyphLoadLock);
[oldFonts release];
/* Now insert all of the glyphs into the glyph trie */
glyphIdx = 0;
for (charVal = [charactersToLoad firstIndex]; charVal != NSNotFound; charVal = [charactersToLoad indexGreaterThanIndex:charVal]) {
HFGlyphTrieInsert(&glyphTable, charVal, glyphs[glyphIdx++]);
}
FREE_ARRAY(glyphs);
/* Trigger a redisplay */
[self performSelectorOnMainThread:@selector(triggerRedisplay:) withObject:nil waitUntilDone:NO];
/* All done. We inherited the retain on requestedCharacters, so release it. */
[charactersToLoad release];
}
- (void)triggerRedisplay:unused {
USE(unused);
[self setNeedsDisplay:YES];
}
- (void)beginLoadGlyphsForCharacters:(NSIndexSet *)charactersToLoad {
/* Create the operation (and maybe the operation queue itself) */
if (! glyphLoader) {
glyphLoader = [[NSOperationQueue alloc] init];
[glyphLoader setMaxConcurrentOperationCount:1];
}
if (! fonts) {
NSFont *font = [self font];
fonts = [[NSMutableArray alloc] initWithObjects:&font count:1];
}
BOOL needToStartOperation;
OSSpinLockLock(&glyphLoadLock);
if (requestedCharacters) {
/* There's a pending request, so just add to it */
[requestedCharacters addIndexes:charactersToLoad];
needToStartOperation = NO;
} else {
/* There's no pending request, so we will create one */
requestedCharacters = [charactersToLoad mutableCopy];
needToStartOperation = YES;
}
OSSpinLockUnlock(&glyphLoadLock);
if (needToStartOperation) {
NSInvocationOperation *op = [[NSInvocationOperation alloc] initWithTarget:self selector:@selector(threadedLoadGlyphs:) object:charactersToLoad];
[glyphLoader addOperation:op];
[op release];
}
}
- (void)dealloc {
HFGlyphTreeFree(&glyphTable);
[fonts release];
[super dealloc];
}
- (void)staleTieredProperties {
tier1DataIsStale = YES;
/* We have to free the glyph table */
requestedCancel = YES;
[glyphLoader waitUntilAllOperationsAreFinished];
requestedCancel = NO;
HFGlyphTreeFree(&glyphTable);
HFGlyphTrieInitialize(&glyphTable, bytesPerChar);
[fonts release];
fonts = nil;
[fontCache release];
fontCache = nil;
}
- (void)setFont:(NSFont *)font {
[self staleTieredProperties];
/* fonts is preloaded with our one font */
if (! fonts) fonts = [[NSMutableArray alloc] init];
[fonts addObject:font];
[super setFont:font];
}
- (instancetype)initWithCoder:(NSCoder *)coder {
HFASSERT([coder allowsKeyedCoding]);
self = [super initWithCoder:coder];
encoding = (NSStringEncoding)[coder decodeInt64ForKey:@"HFStringEncoding"];
bytesPerChar = HFStringEncodingCharacterLength(encoding);
[self staleTieredProperties];
return self;
}
- (instancetype)initWithFrame:(NSRect)frameRect {
self = [super initWithFrame:frameRect];
encoding = NSMacOSRomanStringEncoding;
bytesPerChar = HFStringEncodingCharacterLength(encoding);
[self staleTieredProperties];
return self;
}
- (void)encodeWithCoder:(NSCoder *)coder {
HFASSERT([coder allowsKeyedCoding]);
[super encodeWithCoder:coder];
[coder encodeInt64:encoding forKey:@"HFStringEncoding"];
}
- (NSStringEncoding)encoding {
return encoding;
}
- (void)setEncoding:(NSStringEncoding)val {
if (encoding != val) {
/* Our glyph table is now stale. Call this first to ensure our background operation is complete. */
[self staleTieredProperties];
/* Store the new encoding. */
encoding = val;
/* Compute bytes per character */
bytesPerChar = HFStringEncodingCharacterLength(encoding);
HFASSERT(bytesPerChar > 0);
/* Ensure the tree knows about the new bytes per character */
HFGlyphTrieInitialize(&glyphTable, bytesPerChar);
/* Redraw ourselves with our new glyphs */
[self setNeedsDisplay:YES];
}
}
- (void)loadTier1Data {
NSFont *font = [self font];
/* Use the max advance as the glyph advance */
glyphAdvancement = HFCeil([font maximumAdvancement].width);
/* Generate replacementGlyph */
CGGlyph glyph[1];
BOOL foundReplacement = NO;
if (! foundReplacement) foundReplacement = getGlyphs(glyph, @".", font);
if (! foundReplacement) foundReplacement = getGlyphs(glyph, @"*", font);
if (! foundReplacement) foundReplacement = getGlyphs(glyph, @"!", font);
if (! foundReplacement) {
/* Really we should just fall back to another font in this case */
[NSException raise:NSInternalInconsistencyException format:@"Unable to find replacement glyph for font %@", font];
}
replacementGlyph.fontIndex = 0;
replacementGlyph.glyph = glyph[0];
/* We're no longer stale */
tier1DataIsStale = NO;
}
/* Override of base class method for font substitution */
- (NSFont *)fontAtSubstitutionIndex:(uint16_t)idx {
HFASSERT(idx != kHFGlyphFontIndexInvalid);
if (idx >= [fontCache count]) {
/* Our font cache is out of date. Take the lock and update the cache. */
NSArray *newFonts = nil;
OSSpinLockLock(&glyphLoadLock);
HFASSERT(idx < [fonts count]);
newFonts = [fonts copy];
OSSpinLockUnlock(&glyphLoadLock);
/* Store the new cache */
[fontCache release];
fontCache = newFonts;
/* Now our cache should be up to date */
HFASSERT(idx < [fontCache count]);
}
return fontCache[idx];
}
/* Override of base class method in case we are 16 bit */
- (NSUInteger)bytesPerCharacter {
return bytesPerChar;
}
- (void)extractGlyphsForBytes:(const unsigned char *)bytes count:(NSUInteger)numBytes offsetIntoLine:(NSUInteger)offsetIntoLine intoArray:(struct HFGlyph_t *)glyphs advances:(CGSize *)advances resultingGlyphCount:(NSUInteger *)resultGlyphCount {
HFASSERT(bytes != NULL);
HFASSERT(glyphs != NULL);
HFASSERT(resultGlyphCount != NULL);
HFASSERT(advances != NULL);
USE(offsetIntoLine);
/* Ensure we have advance, etc. before trying to use it */
if (tier1DataIsStale) [self loadTier1Data];
CGSize advance = CGSizeMake(glyphAdvancement, 0);
NSMutableIndexSet *charactersToLoad = nil; //note: in UTF-32 this may have to move to an NSSet
const uint8_t localBytesPerChar = bytesPerChar;
NSUInteger charIndex, numChars = numBytes / localBytesPerChar, byteIndex = 0;
for (charIndex = 0; charIndex < numChars; charIndex++) {
NSUInteger character = -1;
if (localBytesPerChar == 1) {
character = *(const uint8_t *)(bytes + byteIndex);
} else if (localBytesPerChar == 2) {
character = *(const uint16_t *)(bytes + byteIndex);
} else if (localBytesPerChar == 4) {
character = *(const uint32_t *)(bytes + byteIndex);
}
struct HFGlyph_t glyph = HFGlyphTrieGet(&glyphTable, character);
if (glyph.glyph == 0 && glyph.fontIndex == 0) {
/* Unloaded glyph, so load it */
if (! charactersToLoad) charactersToLoad = [[NSMutableIndexSet alloc] init];
[charactersToLoad addIndex:character];
glyph = replacementGlyph;
} else if (glyph.glyph == (uint16_t)-1 && glyph.fontIndex == kHFGlyphFontIndexInvalid) {
/* Missing glyph, so ignore it */
glyph = replacementGlyph;
} else {
/* Valid glyph */
}
HFASSERT(glyph.fontIndex != kHFGlyphFontIndexInvalid);
advances[charIndex] = advance;
glyphs[charIndex] = glyph;
byteIndex += localBytesPerChar;
}
*resultGlyphCount = numChars;
if (charactersToLoad) {
[self beginLoadGlyphsForCharacters:charactersToLoad];
[charactersToLoad release];
}
}
- (CGFloat)advancePerCharacter {
/* The glyph advancement is determined by our glyph table */
if (tier1DataIsStale) [self loadTier1Data];
return glyphAdvancement;
}
- (CGFloat)advanceBetweenColumns {
return 0; //don't have any space between columns
}
- (NSUInteger)maximumGlyphCountForByteCount:(NSUInteger)byteCount {
return byteCount / [self bytesPerCharacter];
}
@end

View File

@ -0,0 +1,146 @@
//
// HFRepresenterTextView.h
// HexFiend_2
//
// Copyright 2007 ridiculous_fish. All rights reserved.
//
#import <Cocoa/Cocoa.h>
#import <HexFiend/HFGlyphTrie.h>
/* Bytes per column philosophy
_hftvflags.bytesPerColumn is the number of bytes that should be displayed consecutively, as one column. A space separates one column from the next. HexFiend 1.0 displayed 1 byte per column, and setting bytesPerColumn to 1 in this version reproduces that behavior. The vertical guidelines displayed by HexFiend 1.0 are only drawn when bytesPerColumn is set to 1.
We use some number of bits to hold the number of bytes per column, so the highest value we can store is ((2 ^ numBits) - 1). We can't tell the user that the max is not a power of 2, so we pin the value to the highest representable power of 2, or (2 ^ (numBits - 1)). We allow integral values from 0 to the pinned maximum, inclusive; powers of 2 are not required. The setter method uses HFTV_BYTES_PER_COLUMN_MAX_VALUE to stay within the representable range.
Since a value of zero is nonsensical, we can use it to specify no spaces at all.
*/
#define HFTV_BYTES_PER_COLUMN_MAX_VALUE (1 << (HFTV_BYTES_PER_COLUMN_BITFIELD_SIZE - 1))
@class HFTextRepresenter;
/* The base class for HFTextRepresenter views - such as the hex or ASCII text view */
@interface HFRepresenterTextView : NSView {
@private;
HFTextRepresenter *representer;
NSArray *cachedSelectedRanges;
CGFloat verticalOffset;
CGFloat horizontalContainerInset;
CGFloat defaultLineHeight;
NSTimer *caretTimer;
NSWindow *pulseWindow;
NSRect pulseWindowBaseFrameInScreenCoordinates;
NSRect lastDrawnCaretRect;
NSRect caretRectToDraw;
NSUInteger bytesBetweenVerticalGuides;
NSUInteger startingLineBackgroundColorIndex;
NSArray *rowBackgroundColors;
NSMutableDictionary *callouts;
void (^byteColoring)(uint8_t byte, uint8_t *r, uint8_t *g, uint8_t *b, uint8_t *a);
struct {
unsigned antialias:1;
unsigned drawCallouts:1;
unsigned editable:1;
unsigned caretVisible:1;
unsigned registeredForAppNotifications:1;
unsigned withinMouseDown:1;
unsigned receivedMouseUp:1;
} _hftvflags;
}
- (instancetype)initWithRepresenter:(HFTextRepresenter *)rep;
- (void)clearRepresenter;
- (HFTextRepresenter *)representer;
@property (nonatomic, copy) NSFont *font;
/* Set and get data. setData: will invalidate the correct regions (perhaps none) */
@property (nonatomic, copy) NSData *data;
@property (nonatomic) CGFloat verticalOffset;
@property (nonatomic) NSUInteger startingLineBackgroundColorIndex;
@property (nonatomic, getter=isEditable) BOOL editable;
@property (nonatomic, copy) NSArray *styles;
@property (nonatomic) BOOL shouldAntialias;
- (BOOL)behavesAsTextField;
- (BOOL)showsFocusRing;
- (BOOL)isWithinMouseDown;
- (NSRect)caretRect;
@property (nonatomic) BOOL shouldDrawCallouts;
- (void)setByteColoring:(void (^)(uint8_t byte, uint8_t *r, uint8_t *g, uint8_t *b, uint8_t *a))coloring;
- (NSPoint)originForCharacterAtByteIndex:(NSInteger)index;
- (NSUInteger)indexOfCharacterAtPoint:(NSPoint)point;
/* The amount of padding space to inset from the left and right side. */
@property (nonatomic) CGFloat horizontalContainerInset;
/* The number of bytes between vertical guides. 0 means no drawing of guides. */
@property (nonatomic) NSUInteger bytesBetweenVerticalGuides;
/* To be invoked from drawRect:. */
- (void)drawCaretIfNecessaryWithClip:(NSRect)clipRect;
- (void)drawSelectionIfNecessaryWithClip:(NSRect)clipRect;
/* For font substitution. An index of 0 means the default (base) font. */
- (NSFont *)fontAtSubstitutionIndex:(uint16_t)idx;
/* Uniformly "rounds" the byte range so that it contains an integer number of characters. The algorithm is to "floor:" any character intersecting the min of the range are included, and any character extending beyond the end of the range is excluded. If both the min and the max are within a single character, then an empty range is returned. */
- (NSRange)roundPartialByteRange:(NSRange)byteRange;
- (void)drawTextWithClip:(NSRect)clipRect restrictingToTextInRanges:(NSArray *)restrictingToRanges;
/* Must be overridden */
- (void)extractGlyphsForBytes:(const unsigned char *)bytes count:(NSUInteger)numBytes offsetIntoLine:(NSUInteger)offsetIntoLine intoArray:(struct HFGlyph_t *)glyphs advances:(CGSize *)advances resultingGlyphCount:(NSUInteger *)resultGlyphCount;
- (void)extractGlyphsForBytes:(const unsigned char *)bytePtr range:(NSRange)byteRange intoArray:(struct HFGlyph_t *)glyphs advances:(CGSize *)advances withInclusionRanges:(NSArray *)restrictingToRanges initialTextOffset:(CGFloat *)initialTextOffset resultingGlyphCount:(NSUInteger *)resultingGlyphCount;
/* Must be overridden - returns the max number of glyphs for a given number of bytes */
- (NSUInteger)maximumGlyphCountForByteCount:(NSUInteger)byteCount;
- (void)updateSelectedRanges;
- (void)terminateSelectionPulse; // Start fading the pulse.
/* Given a rect edge, return an NSRect representing the maximum edge in that direction. The dimension in the direction of the edge is 0 (so if edge is NSMaxXEdge, the resulting width is 0). The returned rect is in the coordinate space of the receiver's view. If the byte range is not displayed, returns NSZeroRect.
*/
- (NSRect)furthestRectOnEdge:(NSRectEdge)edge forRange:(NSRange)range;
/* The background color for the line at the given index. You may override this to return different colors. You may return nil to draw no color in this line (and then the empty space color will appear) */
- (NSColor *)backgroundColorForLine:(NSUInteger)line;
- (NSColor *)backgroundColorForEmptySpace;
/* Defaults to 1, may override */
- (NSUInteger)bytesPerCharacter;
/* Cover method for [[self representer] bytesPerLine] and [[self representer] bytesPerColumn] */
- (NSUInteger)bytesPerLine;
- (NSUInteger)bytesPerColumn;
- (CGFloat)lineHeight;
/* Following two must be overridden */
- (CGFloat)advanceBetweenColumns;
- (CGFloat)advancePerCharacter;
- (CGFloat)advancePerColumn;
- (CGFloat)totalAdvanceForBytesInRange:(NSRange)range;
/* Returns the number of lines that could be shown in this view at its given height (expressed in its local coordinate space) */
- (double)maximumAvailableLinesForViewHeight:(CGFloat)viewHeight;
- (NSUInteger)maximumBytesPerLineForViewWidth:(CGFloat)viewWidth;
- (CGFloat)minimumViewWidthForBytesPerLine:(NSUInteger)bytesPerLine;
- (IBAction)selectAll:sender;
@end

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,31 @@
//
// HFRepresenterTextViewCallout.h
// HexFiend_2
//
// Copyright 2011 ridiculous_fish. All rights reserved.
//
#import <Foundation/Foundation.h>
@class HFRepresenterTextView;
#define kHFRepresenterTextViewCalloutMaxGlyphCount 2u
@interface HFRepresenterTextViewCallout : NSObject {
CGFloat rotation;
NSPoint tipOrigin;
NSPoint pinStart, pinEnd;
}
@property(nonatomic) NSInteger byteOffset;
@property(nonatomic, copy) NSColor *color;
@property(nonatomic, copy) NSString *label;
@property(nonatomic, retain) id representedObject;
@property(readonly) NSRect rect;
+ (void)layoutCallouts:(NSArray *)callouts inView:(HFRepresenterTextView *)textView;
- (void)drawShadowWithClip:(NSRect)clip;
- (void)drawWithClip:(NSRect)clip;
@end

View File

@ -0,0 +1,477 @@
//
// HFRepresenterTextViewCallout.m
// HexFiend_2
//
// Copyright 2011 ridiculous_fish. All rights reserved.
//
#import "HFRepresenterTextViewCallout.h"
#import "HFRepresenterTextView.h"
static const CGFloat HFTeardropRadius = 12;
static const CGFloat HFTeadropTipScale = 2.5;
static const CGFloat HFShadowXOffset = -6;
static const CGFloat HFShadowYOffset = 0;
static const CGFloat HFShadowOffscreenHack = 3100;
static NSPoint rotatePoint(NSPoint center, NSPoint point, CGFloat percent) {
CGFloat radians = percent * M_PI * 2;
CGFloat x = point.x - center.x;
CGFloat y = point.y - center.y;
CGFloat newX = x * cos(radians) + y * sin(radians);
CGFloat newY = x * -sin(radians) + y * cos(radians);
return NSMakePoint(center.x + newX, center.y + newY);
}
static NSPoint scalePoint(NSPoint center, NSPoint point, CGFloat percent) {
CGFloat x = point.x - center.x;
CGFloat y = point.y - center.y;
CGFloat newX = x * percent;
CGFloat newY = y * percent;
return NSMakePoint(center.x + newX, center.y + newY);
}
static NSBezierPath *copyTeardropPath(void) {
static NSBezierPath *sPath = nil;
if (! sPath) {
CGFloat radius = HFTeardropRadius;
CGFloat rotation = 0;
CGFloat droppiness = .15;
CGFloat tipScale = HFTeadropTipScale;
CGFloat tipLengthFromCenter = radius * tipScale;
NSPoint bulbCenter = NSMakePoint(-tipLengthFromCenter, 0);
NSPoint triangleCenter = rotatePoint(bulbCenter, NSMakePoint(bulbCenter.x + radius, bulbCenter.y), rotation);
NSPoint dropCorner1 = rotatePoint(bulbCenter, triangleCenter, droppiness / 2);
NSPoint dropCorner2 = rotatePoint(bulbCenter, triangleCenter, -droppiness / 2);
NSPoint dropTip = scalePoint(bulbCenter, triangleCenter, tipScale);
NSBezierPath *path = [[NSBezierPath alloc] init];
[path appendBezierPathWithArcWithCenter:bulbCenter radius:radius startAngle:-rotation * 360 + droppiness * 180. endAngle:-rotation * 360 - droppiness * 180. clockwise:NO];
[path moveToPoint:dropCorner1];
[path lineToPoint:dropTip];
[path lineToPoint:dropCorner2];
[path closePath];
sPath = path;
}
return [sPath retain];
}
@implementation HFRepresenterTextViewCallout
/* A helpful struct for representing a wedge (portion of a circle). Wedges are counterclockwise. */
typedef struct {
double offset; // 0 <= offset < 1
double length; // 0 <= length <= 1
} Wedge_t;
static inline double normalizeAngle(double x) {
/* Convert an angle to the range [0, 1). We typically only generate angles that are off by a full rotation, so a loop isn't too bad. */
while (x >= 1.) x -= 1.;
while (x < 0.) x += 1.;
return x;
}
static inline double distanceCCW(double a, double b) { return normalizeAngle(b-a); }
static inline double wedgeMax(Wedge_t wedge) {
return normalizeAngle(wedge.offset + wedge.length);
}
/* Computes the smallest wedge containing the two given wedges. Compute the wedge from the min of one to the furthest part of the other, and pick the smaller. */
static Wedge_t wedgeUnion(Wedge_t wedge1, Wedge_t wedge2) {
// empty wedges don't participate
if (wedge1.length <= 0) return wedge2;
if (wedge2.length <= 0) return wedge1;
Wedge_t union1 = wedge1;
union1.length = fmin(1., fmax(union1.length, distanceCCW(union1.offset, wedge2.offset) + wedge2.length));
Wedge_t union2 = wedge2;
union2.length = fmin(1., fmax(union2.length, distanceCCW(union2.offset, wedge1.offset) + wedge1.length));
Wedge_t result = (union1.length <= union2.length ? union1 : union2);
HFASSERT(result.length <= 1);
return result;
}
- (instancetype)init {
self = [super init];
if (self) {
// Initialization code here.
}
return self;
}
- (void)dealloc {
[_representedObject release];
[_color release];
[_label release];
[super dealloc];
}
- (NSComparisonResult)compare:(HFRepresenterTextViewCallout *)callout {
return [_representedObject compare:callout.representedObject];
}
static Wedge_t computeForbiddenAngle(double distanceFromEdge, double angleToEdge) {
Wedge_t newForbiddenAngle;
/* This is how far it is to the center of our teardrop */
const double teardropLength = HFTeardropRadius * HFTeadropTipScale;
if (distanceFromEdge <= 0) {
/* We're above or below. */
if (-distanceFromEdge >= (teardropLength + HFTeardropRadius)) {
/* We're so far above or below we won't be visible at all. No hope. */
newForbiddenAngle = (Wedge_t){.offset = 0, .length = 1};
} else {
/* We're either above or below the bounds, but there's a hope we can be visible */
double invertedAngleToEdge = normalizeAngle(angleToEdge + .5);
double requiredAngle;
if (-distanceFromEdge >= teardropLength) {
// We're too far north or south that all we can do is point in the right direction
requiredAngle = 0;
} else {
// By confining ourselves to required angles, we can make ourselves visible
requiredAngle = acos(-distanceFromEdge / teardropLength) / (2 * M_PI);
}
// Require at least a small spread
requiredAngle = fmax(requiredAngle, .04);
double requiredMin = invertedAngleToEdge - requiredAngle;
double requiredMax = invertedAngleToEdge + requiredAngle;
newForbiddenAngle = (Wedge_t){.offset = requiredMax, .length = distanceCCW(requiredMax, requiredMin) };
}
} else if (distanceFromEdge < teardropLength) {
// We're onscreen, but some angle will be forbidden
double forbiddenAngle = acos(distanceFromEdge / teardropLength) / (2 * M_PI);
// This is a wedge out of the top (or bottom)
newForbiddenAngle = (Wedge_t){.offset = angleToEdge - forbiddenAngle, .length = 2 * forbiddenAngle};
} else {
/* Nothing prohibited at all */
newForbiddenAngle = (Wedge_t){0, 0};
}
return newForbiddenAngle;
}
static double distanceMod1(double a, double b) {
/* Assuming 0 <= a, b < 1, returns the distance between a and b, mod 1 */
if (a > b) {
return fmin(a-b, b-a+1);
} else {
return fmin(b-a, a-b+1);
}
}
+ (void)layoutCallouts:(NSArray *)callouts inView:(HFRepresenterTextView *)textView {
// Keep track of how many drops are at a given location
NSCountedSet *dropsPerByteLoc = [[NSCountedSet alloc] init];
const CGFloat lineHeight = [textView lineHeight];
const NSRect bounds = [textView bounds];
NSMutableArray *remainingCallouts = [[callouts mutableCopy] autorelease];
[remainingCallouts sortUsingSelector:@selector(compare:)];
while ([remainingCallouts count] > 0) {
/* Get the next callout to lay out */
const NSInteger byteLoc = [remainingCallouts[0] byteOffset];
/* Get all the callouts that share that byteLoc */
NSMutableArray *sharedCallouts = [NSMutableArray array];
FOREACH(HFRepresenterTextViewCallout *, testCallout, remainingCallouts) {
if ([testCallout byteOffset] == byteLoc) {
[sharedCallouts addObject:testCallout];
}
}
/* We expect to get at least one */
const NSUInteger calloutCount = [sharedCallouts count];
HFASSERT(calloutCount > 0);
/* Get the character origin */
const NSPoint characterOrigin = [textView originForCharacterAtByteIndex:byteLoc];
Wedge_t forbiddenAngle = {0, 0};
// Compute how far we are from the top (or bottom)
BOOL isNearerTop = (characterOrigin.y < NSMidY(bounds));
double verticalDistance = (isNearerTop ? characterOrigin.y - NSMinY(bounds) : NSMaxY(bounds) - characterOrigin.y);
forbiddenAngle = wedgeUnion(forbiddenAngle, computeForbiddenAngle(verticalDistance, (isNearerTop ? .25 : .75)));
// Compute how far we are from the left (or right)
BOOL isNearerLeft = (characterOrigin.x < NSMidX(bounds));
double horizontalDistance = (isNearerLeft ? characterOrigin.x - NSMinX(bounds) : NSMaxX(bounds) - characterOrigin.x);
forbiddenAngle = wedgeUnion(forbiddenAngle, computeForbiddenAngle(horizontalDistance, (isNearerLeft ? .5 : 0.)));
/* How much will each callout rotate? No more than 1/8th. */
HFASSERT(forbiddenAngle.length <= 1);
double changeInRotationPerCallout = fmin(.125, (1. - forbiddenAngle.length) / calloutCount);
double totalConsumedAmount = changeInRotationPerCallout * calloutCount;
/* We would like to center around .375. */
const double goalCenter = .375;
/* We're going to pretend to work on a line segment that extends from the max prohibited angle all the way back to min */
double segmentLength = 1. - forbiddenAngle.length;
double goalSegmentCenter = normalizeAngle(goalCenter - wedgeMax(forbiddenAngle)); //may exceed segmentLength!
/* Now center us on the goal, or as close as we can get. */
double consumedSegmentCenter;
/* We only need to worry about wrapping around if we have some prohibited angle */
if (forbiddenAngle.length <= 0) { //never expect < 0, but be paranoid
consumedSegmentCenter = goalSegmentCenter;
} else {
/* The consumed segment center is confined to the segment range [amount/2, length - amount/2] */
double consumedSegmentCenterMin = totalConsumedAmount/2;
double consumedSegmentCenterMax = segmentLength - totalConsumedAmount/2;
if (goalSegmentCenter >= consumedSegmentCenterMin && goalSegmentCenter < consumedSegmentCenterMax) {
/* We can hit our goal */
consumedSegmentCenter = goalSegmentCenter;
} else {
/* Pick either the min or max location, depending on which one gets us closer to the goal segment center mod 1. */
if (distanceMod1(goalSegmentCenter, consumedSegmentCenterMin) <= distanceMod1(goalSegmentCenter, consumedSegmentCenterMax)) {
consumedSegmentCenter = consumedSegmentCenterMin;
} else {
consumedSegmentCenter = consumedSegmentCenterMax;
}
}
}
/* Now convert this back to an angle */
double consumedAngleCenter = normalizeAngle(wedgeMax(forbiddenAngle) + consumedSegmentCenter);
// move us slightly towards the character
NSPoint teardropTipOrigin = NSMakePoint(characterOrigin.x + 1, characterOrigin.y + floor(lineHeight / 8.));
// make the pin
NSPoint pinStart, pinEnd;
pinStart = NSMakePoint(characterOrigin.x + .25, characterOrigin.y);
pinEnd = NSMakePoint(pinStart.x, pinStart.y + lineHeight);
// store it all, invalidating as necessary
NSInteger i = 0;
FOREACH(HFRepresenterTextViewCallout *, callout, sharedCallouts) {
/* Compute the rotation */
double seq = (i+1)/2; //0, 1, -1, 2, -2...
if ((i & 1) == 0) seq = -seq;
//if we've got an even number of callouts, we want -.5, .5, -1.5, 1.5...
if (! (calloutCount & 1)) seq -= .5;
// compute the angle of rotation
double angle = consumedAngleCenter + seq * changeInRotationPerCallout;
// our notion of rotation has 0 meaning pointing right and going counterclockwise, but callouts with 0 pointing left and going clockwise, so convert
angle = normalizeAngle(.5 - angle);
NSRect beforeRect = [callout rect];
callout->rotation = angle;
callout->tipOrigin = teardropTipOrigin;
callout->pinStart = pinStart;
callout->pinEnd = pinEnd;
// Only the first gets a pin
pinStart = pinEnd = NSZeroPoint;
NSRect afterRect = [callout rect];
if (! NSEqualRects(beforeRect, afterRect)) {
[textView setNeedsDisplayInRect:beforeRect];
[textView setNeedsDisplayInRect:afterRect];
}
i++;
}
/* We're done laying out these callouts */
[remainingCallouts removeObjectsInArray:sharedCallouts];
}
[dropsPerByteLoc release];
}
- (CGAffineTransform)teardropTransform {
CGAffineTransform trans = CGAffineTransformMakeTranslation(tipOrigin.x, tipOrigin.y);
trans = CGAffineTransformRotate(trans, rotation * M_PI * 2);
return trans;
}
- (NSRect)teardropBaseRect {
NSSize teardropSize = NSMakeSize(HFTeardropRadius * (1 + HFTeadropTipScale), HFTeardropRadius*2);
NSRect result = NSMakeRect(-teardropSize.width, -teardropSize.height/2, teardropSize.width, teardropSize.height);
return result;
}
- (CGAffineTransform)shadowTransform {
CGFloat shadowXOffset = HFShadowXOffset;
CGFloat shadowYOffset = HFShadowYOffset;
CGFloat offscreenOffset = HFShadowOffscreenHack;
// Figure out how much movement the shadow offset produces
CGFloat shadowTranslationDistance = hypot(shadowXOffset, shadowYOffset);
CGAffineTransform transform = CGAffineTransformIdentity;
transform = CGAffineTransformTranslate(transform, tipOrigin.x + offscreenOffset - shadowXOffset, tipOrigin.y - shadowYOffset);
transform = CGAffineTransformRotate(transform, rotation * M_PI * 2 - atan2(shadowTranslationDistance, 2*HFTeardropRadius /* bulbHeight */));
return transform;
}
- (void)drawShadowWithClip:(NSRect)clip {
USE(clip);
CGContextRef ctx = [[NSGraphicsContext currentContext] graphicsPort];
// Set the shadow. Note that these shadows are pretty unphysical for high rotations.
NSShadow *shadow = [[NSShadow alloc] init];
[shadow setShadowBlurRadius:5.];
[shadow setShadowOffset:NSMakeSize(HFShadowXOffset - HFShadowOffscreenHack, HFShadowYOffset)];
[shadow setShadowColor:[NSColor colorWithDeviceWhite:0. alpha:.5]];
[shadow set];
[shadow release];
// Draw the shadow first and separately
CGAffineTransform transform = [self shadowTransform];
CGContextConcatCTM(ctx, transform);
NSBezierPath *teardrop = copyTeardropPath();
[teardrop fill];
[teardrop release];
// Clear the shadow
CGContextSetShadowWithColor(ctx, CGSizeZero, 0, NULL);
// Undo the transform
CGContextConcatCTM(ctx, CGAffineTransformInvert(transform));
}
- (void)drawWithClip:(NSRect)clip {
USE(clip);
CGContextRef ctx = [[NSGraphicsContext currentContext] graphicsPort];
// Here's the font we'll use
CTFontRef ctfont = CTFontCreateWithName(CFSTR("Helvetica-Bold"), 1., NULL);
if (ctfont) {
// Set the font
[(NSFont *)ctfont set];
// Get characters
NSUInteger labelLength = MIN([_label length], kHFRepresenterTextViewCalloutMaxGlyphCount);
UniChar calloutUniLabel[kHFRepresenterTextViewCalloutMaxGlyphCount];
[_label getCharacters:calloutUniLabel range:NSMakeRange(0, labelLength)];
// Get our glyphs and advances
CGGlyph glyphs[kHFRepresenterTextViewCalloutMaxGlyphCount];
CGSize advances[kHFRepresenterTextViewCalloutMaxGlyphCount];
CTFontGetGlyphsForCharacters(ctfont, calloutUniLabel, glyphs, labelLength);
CTFontGetAdvancesForGlyphs(ctfont, kCTFontHorizontalOrientation, glyphs, advances, labelLength);
// Count our glyphs. Note: this won't work with any label containing spaces, etc.
NSUInteger glyphCount;
for (glyphCount = 0; glyphCount < labelLength; glyphCount++) {
if (glyphs[glyphCount] == 0) break;
}
// Set our color.
[_color set];
// Draw the pin first
if (! NSEqualPoints(pinStart, pinEnd)) {
[NSBezierPath setDefaultLineWidth:1.25];
[NSBezierPath strokeLineFromPoint:pinStart toPoint:pinEnd];
}
CGContextSaveGState(ctx);
CGContextBeginTransparencyLayerWithRect(ctx, NSRectToCGRect([self rect]), NULL);
// Rotate and translate in preparation for drawing the teardrop
CGContextConcatCTM(ctx, [self teardropTransform]);
// Draw the teardrop
NSBezierPath *teardrop = copyTeardropPath();
[teardrop fill];
[teardrop release];
// Draw the text with white and alpha. Use blend mode copy so that we clip out the shadow, and when the transparency layer is ended we'll composite over the text.
CGFloat textScale = (glyphCount == 1 ? 24 : 20);
// we are flipped by default, so invert the rotation's sign to get the text direction. Use a little slop so we don't get jitter.
const CGFloat textDirection = (rotation <= .27 || rotation >= .73) ? -1 : 1;
CGPoint positions[kHFRepresenterTextViewCalloutMaxGlyphCount];
CGFloat totalAdvance = 0;
for (NSUInteger i=0; i < glyphCount; i++) {
// make sure to provide negative advances if necessary
positions[i].x = copysign(totalAdvance, -textDirection);
positions[i].y = 0;
CGFloat advance = advances[i].width;
// Workaround 5834794
advance *= textScale;
// Tighten up the advances a little
advance *= .85;
totalAdvance += advance;
}
// Compute the vertical offset
CGFloat textYOffset = (glyphCount == 1 ? 4 : 5);
// LOL
if ([_label isEqualToString:@"6"] || [_label isEqualToString:@"7"] == 7) textYOffset -= 1;
// Apply this text matrix
NSRect bulbRect = [self teardropBaseRect];
CGAffineTransform textMatrix = CGAffineTransformMakeScale(-copysign(textScale, textDirection), copysign(textScale, textDirection)); //roughly the font size we want
textMatrix.tx = NSMinX(bulbRect) + HFTeardropRadius + copysign(totalAdvance/2, textDirection);
if (textDirection < 0) {
textMatrix.ty = NSMaxY(bulbRect) - textYOffset;
} else {
textMatrix.ty = NSMinY(bulbRect) + textYOffset;
}
// Draw
CGContextSetTextMatrix(ctx, textMatrix);
CGContextSetTextDrawingMode(ctx, kCGTextClip);
CGContextShowGlyphsAtPositions(ctx, glyphs, positions, glyphCount);
CGContextSetBlendMode(ctx, kCGBlendModeCopy);
CGContextSetGrayFillColor(ctx, 1., .66); //faint white fill
CGContextFillRect(ctx, NSRectToCGRect(NSInsetRect(bulbRect, -20, -20)));
// Done drawing, so composite
CGContextEndTransparencyLayer(ctx);
CGContextRestoreGState(ctx); // this also restores the clip, which is important
// Done with the font
CFRelease(ctfont);
}
}
- (NSRect)rect {
// get the transformed teardrop rect
NSRect result = NSRectFromCGRect(CGRectApplyAffineTransform(NSRectToCGRect([self teardropBaseRect]), [self teardropTransform]));
// outset a bit for the shadow
result = NSInsetRect(result, -8, -8);
return result;
}
@end

View File

@ -0,0 +1,11 @@
#import <HexFiend/HFRepresenterTextView.h>
#define GLYPH_BUFFER_SIZE 16u
@interface HFRepresenterTextView (HFInternal)
- (NSUInteger)_glyphsForString:(NSString *)string withGeneratingLayoutManager:(NSLayoutManager *)textView glyphs:(CGGlyph *)glyphs;
- (NSUInteger)_glyphsForString:(NSString *)string withGeneratingTextView:(NSTextView *)textView glyphs:(CGGlyph *)glyphs;
- (NSUInteger)_getGlyphs:(CGGlyph *)glyphs forString:(NSString *)string font:(NSFont *)font; //uses CoreText. Here glyphs must have space for [string length] glyphs.
@end

View File

@ -0,0 +1,7 @@
#import <HexFiend/HFRepresenter.h>
@interface HFRepresenter (HFInternalStuff)
- (void)_setController:(HFController *)controller;
@end

View File

@ -0,0 +1,32 @@
//
// HFSharedMemoryByteSlice.h
// HexFiend_2
//
// Copyright 2008 ridiculous_fish. All rights reserved.
//
#import <HexFiend/HFByteSlice.h>
/*! @class HFSharedMemoryByteSlice
@brief A subclass of HFByteSlice for working with data stored in memory.
HFSharedMemoryByteSlice is a subclass of HFByteSlice that represents a portion of data from memory, e.g. typed or pasted in by the user. The term "shared" refers to the ability for mutiple HFSharedMemoryByteSlices to reference the same NSData; it does not mean that the data is in shared memory or shared between processes.
Instances of HFSharedMemoryByteSlice are immutable (like all instances of HFByteSlice). However, to support efficient typing, the backing data is an instance of NSMutableData that may be grown. A referenced range of the NSMutableData will never have its contents changed, but it may be allowed to grow larger, so that the data does not have to be copied merely to append a single byte. This is implemented by overriding the -byteSliceByAppendingSlice: method of HFByteSlice.
*/
@interface HFSharedMemoryByteSlice : HFByteSlice {
NSMutableData *data;
NSUInteger offset;
NSUInteger length;
unsigned char inlineTailLength;
unsigned char inlineTail[15]; //size chosen to exhaust padding of 32-byte allocator
}
// copies the data
- (instancetype)initWithUnsharedData:(NSData *)data;
// retains, does not copy
- (instancetype)initWithData:(NSMutableData *)data;
- (instancetype)initWithData:(NSMutableData *)data offset:(NSUInteger)offset length:(NSUInteger)length;
@end

View File

@ -0,0 +1,209 @@
//
// HFSharedMemoryByteSlice.m
// HexFiend_2
//
// Copyright 2008 ridiculous_fish. All rights reserved.
//
#import <HexFiend/HFByteSlice_Private.h>
#import <HexFiend/HFSharedMemoryByteSlice.h>
#define MAX_FAST_PATH_SIZE (1 << 13)
#define MAX_TAIL_LENGTH (sizeof ((HFSharedMemoryByteSlice *)NULL)->inlineTail / sizeof *((HFSharedMemoryByteSlice *)NULL)->inlineTail)
@implementation HFSharedMemoryByteSlice
- (instancetype)initWithUnsharedData:(NSData *)unsharedData {
self = [super init];
REQUIRE_NOT_NULL(unsharedData);
NSUInteger dataLength = [unsharedData length];
NSUInteger inlineAmount = MIN(dataLength, MAX_TAIL_LENGTH);
NSUInteger sharedAmount = dataLength - inlineAmount;
HFASSERT(inlineAmount <= UCHAR_MAX);
inlineTailLength = (unsigned char)inlineAmount;
length = sharedAmount;
if (inlineAmount > 0) {
[unsharedData getBytes:inlineTail range:NSMakeRange(dataLength - inlineAmount, inlineAmount)];
}
if (sharedAmount > 0) {
data = [[NSMutableData alloc] initWithBytes:[unsharedData bytes] length:sharedAmount];
}
return self;
}
// retains, does not copy
- (instancetype)initWithData:(NSMutableData *)dat {
REQUIRE_NOT_NULL(dat);
return [self initWithData:dat offset:0 length:[dat length]];
}
- (instancetype)initWithData:(NSMutableData *)dat offset:(NSUInteger)off length:(NSUInteger)len {
self = [super init];
REQUIRE_NOT_NULL(dat);
HFASSERT(off + len >= off); //check for overflow
HFASSERT(off + len <= [dat length]);
offset = off;
length = len;
data = [dat retain];
return self;
}
- (instancetype)initWithSharedData:(NSMutableData *)dat offset:(NSUInteger)off length:(NSUInteger)len tail:(const void *)tail tailLength:(NSUInteger)tailLen {
self = [super init];
if (off || len) REQUIRE_NOT_NULL(dat);
if (tailLen) REQUIRE_NOT_NULL(tail);
HFASSERT(tailLen <= MAX_TAIL_LENGTH);
HFASSERT(off + len >= off);
HFASSERT(off + len <= [dat length]);
offset = off;
length = len;
data = [dat retain];
HFASSERT(tailLen <= UCHAR_MAX);
inlineTailLength = (unsigned char)tailLen;
memcpy(inlineTail, tail, tailLen);
HFASSERT([self length] == tailLen + len);
return self;
}
- (void)dealloc {
[data release];
[super dealloc];
}
- (unsigned long long)length {
return length + inlineTailLength;
}
- (void)copyBytes:(unsigned char *)dst range:(HFRange)lrange {
HFASSERT(HFSum(length, inlineTailLength) >= HFMaxRange(lrange));
NSRange requestedRange = NSMakeRange(ll2l(lrange.location), ll2l(lrange.length));
NSRange dataRange = NSMakeRange(0, length);
NSRange tailRange = NSMakeRange(length, inlineTailLength);
NSRange dataRangeToCopy = NSIntersectionRange(requestedRange, dataRange);
NSRange tailRangeToCopy = NSIntersectionRange(requestedRange, tailRange);
HFASSERT(HFSum(dataRangeToCopy.length, tailRangeToCopy.length) == lrange.length);
if (dataRangeToCopy.length > 0) {
HFASSERT(HFSum(NSMaxRange(dataRangeToCopy), offset) <= [data length]);
const void *bytes = [data bytes];
memcpy(dst, bytes + dataRangeToCopy.location + offset, dataRangeToCopy.length);
}
if (tailRangeToCopy.length > 0) {
HFASSERT(tailRangeToCopy.location >= length);
HFASSERT(NSMaxRange(tailRangeToCopy) - length <= inlineTailLength);
memcpy(dst + dataRangeToCopy.length, inlineTail + tailRangeToCopy.location - length, tailRangeToCopy.length);
}
}
- (HFByteSlice *)subsliceWithRange:(HFRange)lrange {
if (HFRangeEqualsRange(lrange, HFRangeMake(0, HFSum(length, inlineTailLength)))) return [[self retain] autorelease];
HFByteSlice *result;
HFASSERT(lrange.length > 0);
HFASSERT(HFSum(length, inlineTailLength) >= HFMaxRange(lrange));
NSRange requestedRange = NSMakeRange(ll2l(lrange.location), ll2l(lrange.length));
NSRange dataRange = NSMakeRange(0, length);
NSRange tailRange = NSMakeRange(length, inlineTailLength);
NSRange dataRangeToCopy = NSIntersectionRange(requestedRange, dataRange);
NSRange tailRangeToCopy = NSIntersectionRange(requestedRange, tailRange);
HFASSERT(HFSum(dataRangeToCopy.length, tailRangeToCopy.length) == lrange.length);
NSMutableData *resultData = NULL;
NSUInteger resultOffset = 0;
NSUInteger resultLength = 0;
const unsigned char *tail = NULL;
NSUInteger tailLength = 0;
if (dataRangeToCopy.length > 0) {
resultData = data;
HFASSERT(resultData != NULL);
resultOffset = offset + dataRangeToCopy.location;
resultLength = dataRangeToCopy.length;
HFASSERT(HFSum(resultOffset, resultLength) <= [data length]);
}
if (tailRangeToCopy.length > 0) {
tail = inlineTail + tailRangeToCopy.location - length;
tailLength = tailRangeToCopy.length;
HFASSERT(tail >= inlineTail && tail + tailLength <= inlineTail + inlineTailLength);
}
HFASSERT(resultLength + tailLength == lrange.length);
result = [[[[self class] alloc] initWithSharedData:resultData offset:resultOffset length:resultLength tail:tail tailLength:tailLength] autorelease];
HFASSERT([result length] == lrange.length);
return result;
}
- (HFByteSlice *)byteSliceByAppendingSlice:(HFByteSlice *)slice {
REQUIRE_NOT_NULL(slice);
const unsigned long long sliceLength = [slice length];
if (sliceLength == 0) return self;
const unsigned long long thisLength = [self length];
HFASSERT(inlineTailLength <= MAX_TAIL_LENGTH);
NSUInteger spaceRemainingInTail = MAX_TAIL_LENGTH - inlineTailLength;
if (sliceLength <= spaceRemainingInTail) {
/* We can do our work entirely within the tail */
NSUInteger newTailLength = (NSUInteger)sliceLength + inlineTailLength;
unsigned char newTail[MAX_TAIL_LENGTH];
memcpy(newTail, inlineTail, inlineTailLength);
[slice copyBytes:newTail + inlineTailLength range:HFRangeMake(0, sliceLength)];
HFByteSlice *result = [[[[self class] alloc] initWithSharedData:data offset:offset length:length tail:newTail tailLength:newTailLength] autorelease];
HFASSERT([result length] == HFSum(sliceLength, thisLength));
return result;
}
else {
/* We can't do our work entirely in the tail; see if we can append some shared data. */
HFASSERT(offset + length >= offset);
if (offset + length == [data length]) {
/* We can append some shared data. But impose some reasonable limit on how big our slice can get; this is 16 MB */
if (HFSum(thisLength, sliceLength) < (1ULL << 24)) {
NSUInteger newDataOffset = offset;
NSUInteger newDataLength = length;
unsigned char newDataTail[MAX_TAIL_LENGTH];
unsigned char newDataTailLength = MAX_TAIL_LENGTH;
NSMutableData *newData = (data ? data : [[[NSMutableData alloc] init] autorelease]);
NSUInteger sliceLengthInt = ll2l(sliceLength);
NSUInteger newTotalTailLength = sliceLengthInt + inlineTailLength;
HFASSERT(newTotalTailLength >= MAX_TAIL_LENGTH);
NSUInteger amountToShiftIntoSharedData = newTotalTailLength - MAX_TAIL_LENGTH;
NSUInteger amountToShiftIntoSharedDataFromTail = MIN(amountToShiftIntoSharedData, inlineTailLength);
NSUInteger amountToShiftIntoSharedDataFromNewSlice = amountToShiftIntoSharedData - amountToShiftIntoSharedDataFromTail;
if (amountToShiftIntoSharedDataFromTail > 0) {
HFASSERT(amountToShiftIntoSharedDataFromTail <= inlineTailLength);
[newData appendBytes:inlineTail length:amountToShiftIntoSharedDataFromTail];
newDataLength += amountToShiftIntoSharedDataFromTail;
}
if (amountToShiftIntoSharedDataFromNewSlice > 0) {
HFASSERT(amountToShiftIntoSharedDataFromNewSlice <= [slice length]);
NSUInteger dataLength = offset + length + amountToShiftIntoSharedDataFromTail;
HFASSERT([newData length] == dataLength);
[newData setLength:dataLength + amountToShiftIntoSharedDataFromNewSlice];
[slice copyBytes:[newData mutableBytes] + dataLength range:HFRangeMake(0, amountToShiftIntoSharedDataFromNewSlice)];
newDataLength += amountToShiftIntoSharedDataFromNewSlice;
}
/* We've updated our data; now figure out the tail */
NSUInteger amountOfTailFromNewSlice = sliceLengthInt - amountToShiftIntoSharedDataFromNewSlice;
HFASSERT(amountOfTailFromNewSlice <= MAX_TAIL_LENGTH);
[slice copyBytes:newDataTail + MAX_TAIL_LENGTH - amountOfTailFromNewSlice range:HFRangeMake(sliceLengthInt - amountOfTailFromNewSlice, amountOfTailFromNewSlice)];
/* Copy the rest, if any, from the end of self */
NSUInteger amountOfTailFromSelf = MAX_TAIL_LENGTH - amountOfTailFromNewSlice;
HFASSERT(amountOfTailFromSelf <= inlineTailLength);
if (amountOfTailFromSelf > 0) {
memcpy(newDataTail, inlineTail + inlineTailLength - amountOfTailFromSelf, amountOfTailFromSelf);
}
HFByteSlice *result = [[[[self class] alloc] initWithSharedData:newData offset:newDataOffset length:newDataLength tail:newDataTail tailLength:newDataTailLength] autorelease];
HFASSERT([result length] == HFSum([slice length], [self length]));
return result;
}
}
}
return nil;
}
@end

View File

@ -0,0 +1,31 @@
//
// HFStatusBarRepresenter.h
// HexFiend_2
//
// Copyright 2007 ridiculous_fish. All rights reserved.
//
#import <HexFiend/HFRepresenter.h>
/*! @enum HFStatusBarMode
The HFStatusBarMode enum is used to describe the format of the byte counts displayed by the status bar.
*/
typedef NS_ENUM(NSUInteger, HFStatusBarMode) {
HFStatusModeDecimal, ///< The status bar should display byte counts in decimal
HFStatusModeHexadecimal, ///< The status bar should display byte counts in hexadecimal
HFStatusModeApproximate, ///< The text should display byte counts approximately (e.g. "56.3 KB")
HFSTATUSMODECOUNT ///< The number of modes, to allow easy cycling
};
/*! @class HFStatusBarRepresenter
@brief The HFRepresenter for the status bar.
HFStatusBarRepresenter is a subclass of HFRepresenter responsible for showing the status bar, which displays information like the total length of the document, or the number of selected bytes.
*/
@interface HFStatusBarRepresenter : HFRepresenter {
HFStatusBarMode statusMode;
}
@property (nonatomic) HFStatusBarMode statusMode;
@end

View File

@ -0,0 +1,266 @@
//
// HFStatusBarRepresenter.m
// HexFiend_2
//
// Copyright 2007 ridiculous_fish. All rights reserved.
//
#import <HexFiend/HFStatusBarRepresenter.h>
#import <HexFiend/HFFunctions.h>
#define kHFStatusBarDefaultModeUserDefaultsKey @"HFStatusBarDefaultMode"
@interface HFStatusBarView : NSView {
NSCell *cell;
NSSize cellSize;
HFStatusBarRepresenter *representer;
NSDictionary *cellAttributes;
BOOL registeredForAppNotifications;
}
- (void)setRepresenter:(HFStatusBarRepresenter *)rep;
- (void)setString:(NSString *)string;
@end
@implementation HFStatusBarView
- (void)_sharedInitStatusBarView {
NSMutableParagraphStyle *style = [[[NSParagraphStyle defaultParagraphStyle] mutableCopy] autorelease];
[style setAlignment:NSCenterTextAlignment];
cellAttributes = [[NSDictionary alloc] initWithObjectsAndKeys:[NSColor colorWithCalibratedWhite:(CGFloat).15 alpha:1], NSForegroundColorAttributeName, [NSFont labelFontOfSize:10], NSFontAttributeName, style, NSParagraphStyleAttributeName, nil];
cell = [[NSCell alloc] initTextCell:@""];
[cell setAlignment:NSCenterTextAlignment];
[cell setBackgroundStyle:NSBackgroundStyleRaised];
}
- (instancetype)initWithFrame:(NSRect)frame {
self = [super initWithFrame:frame];
[self _sharedInitStatusBarView];
return self;
}
- (instancetype)initWithCoder:(NSCoder *)coder {
HFASSERT([coder allowsKeyedCoding]);
self = [super initWithCoder:coder];
[self _sharedInitStatusBarView];
return self;
}
// nothing to do in encodeWithCoder
- (BOOL)isFlipped { return YES; }
- (void)setRepresenter:(HFStatusBarRepresenter *)rep {
representer = rep;
}
- (void)setString:(NSString *)string {
[cell setAttributedStringValue:[[[NSAttributedString alloc] initWithString:string attributes:cellAttributes] autorelease]];
cellSize = [cell cellSize];
[self setNeedsDisplay:YES];
}
- (void)drawDividerWithClip:(NSRect)clipRect {
[[NSColor lightGrayColor] set];
NSRect bounds = [self bounds];
NSRect lineRect = bounds;
lineRect.size.height = 1;
NSRectFill(NSIntersectionRect(lineRect, clipRect));
}
- (NSGradient *)getGradient:(BOOL)active {
static NSGradient *sActiveGradient;
static NSGradient *sInactiveGradient;
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
sActiveGradient = [[NSGradient alloc] initWithColorsAndLocations:
[NSColor colorWithCalibratedWhite:.89 alpha:1.], 0.00,
[NSColor colorWithCalibratedWhite:.77 alpha:1.], 0.9,
[NSColor colorWithCalibratedWhite:.82 alpha:1.], 1.0,
nil];
sInactiveGradient = [[NSGradient alloc] initWithColorsAndLocations:
[NSColor colorWithCalibratedWhite:.93 alpha:1.], 0.00,
[NSColor colorWithCalibratedWhite:.87 alpha:1.], 0.9,
[NSColor colorWithCalibratedWhite:.90 alpha:1.], 1.0,
nil];
});
return active ? sActiveGradient : sInactiveGradient;
}
- (void)drawRect:(NSRect)clip {
USE(clip);
NSRect bounds = [self bounds];
// [[NSColor colorWithCalibratedWhite:(CGFloat).91 alpha:1] set];
// NSRectFill(clip);
NSWindow *window = [self window];
BOOL drawActive = (window == nil || [window isMainWindow] || [window isKeyWindow]);
[[self getGradient:drawActive] drawInRect:bounds angle:90.];
[self drawDividerWithClip:clip];
NSRect cellRect = NSMakeRect(NSMinX(bounds), HFCeil(NSMidY(bounds) - cellSize.height / 2), NSWidth(bounds), cellSize.height);
[cell drawWithFrame:cellRect inView:self];
}
- (void)mouseDown:(NSEvent *)event {
USE(event);
HFStatusBarMode newMode = ([representer statusMode] + 1) % HFSTATUSMODECOUNT;
[representer setStatusMode:newMode];
[[NSUserDefaults standardUserDefaults] setInteger:newMode forKey:kHFStatusBarDefaultModeUserDefaultsKey];
}
- (void)windowDidChangeKeyStatus:(NSNotification *)note {
USE(note);
[self setNeedsDisplay:YES];
}
- (void)viewDidMoveToWindow {
HFRegisterViewForWindowAppearanceChanges(self, @selector(windowDidChangeKeyStatus:), !registeredForAppNotifications);
registeredForAppNotifications = YES;
[super viewDidMoveToWindow];
}
- (void)viewWillMoveToWindow:(NSWindow *)newWindow {
HFUnregisterViewForWindowAppearanceChanges(self, NO);
[super viewWillMoveToWindow:newWindow];
}
- (void)dealloc {
HFUnregisterViewForWindowAppearanceChanges(self, registeredForAppNotifications);
[cell release];
[cellAttributes release];
[super dealloc];
}
@end
@implementation HFStatusBarRepresenter
- (void)encodeWithCoder:(NSCoder *)coder {
HFASSERT([coder allowsKeyedCoding]);
[super encodeWithCoder:coder];
[coder encodeInt64:statusMode forKey:@"HFStatusMode"];
}
- (instancetype)initWithCoder:(NSCoder *)coder {
HFASSERT([coder allowsKeyedCoding]);
self = [super initWithCoder:coder];
statusMode = (NSUInteger)[coder decodeInt64ForKey:@"HFStatusMode"];
return self;
}
- (instancetype)init {
self = [super init];
statusMode = [[NSUserDefaults standardUserDefaults] integerForKey:kHFStatusBarDefaultModeUserDefaultsKey];
return self;
}
- (NSView *)createView {
HFStatusBarView *view = [[HFStatusBarView alloc] initWithFrame:NSMakeRect(0, 0, 100, 18)];
[view setRepresenter:self];
[view setAutoresizingMask:NSViewWidthSizable];
return view;
}
- (NSString *)describeLength:(unsigned long long)length {
switch (statusMode) {
case HFStatusModeDecimal: return [NSString stringWithFormat:@"%llu byte%s", length, length == 1 ? "" : "s"];
case HFStatusModeHexadecimal: return [NSString stringWithFormat:@"0x%llX byte%s", length, length == 1 ? "" : "s"];
case HFStatusModeApproximate: return [NSString stringWithFormat:@"%@", HFDescribeByteCount(length)];
default: [NSException raise:NSInternalInconsistencyException format:@"Unknown status mode %lu", (unsigned long)statusMode]; return @"";
}
}
- (NSString *)describeOffset:(unsigned long long)offset {
switch (statusMode) {
case HFStatusModeDecimal: return [NSString stringWithFormat:@"%llu", offset];
case HFStatusModeHexadecimal: return [NSString stringWithFormat:@"0x%llX", offset];
case HFStatusModeApproximate: return [NSString stringWithFormat:@"%@", HFDescribeByteCount(offset)];
default: [NSException raise:NSInternalInconsistencyException format:@"Unknown status mode %lu", (unsigned long)statusMode]; return @"";
}
}
/* same as describeOffset, except we treat Approximate like Hexadecimal */
- (NSString *)describeOffsetExcludingApproximate:(unsigned long long)offset {
switch (statusMode) {
case HFStatusModeDecimal: return [NSString stringWithFormat:@"%llu", offset];
case HFStatusModeHexadecimal:
case HFStatusModeApproximate: return [NSString stringWithFormat:@"0x%llX", offset];
default: [NSException raise:NSInternalInconsistencyException format:@"Unknown status mode %lu", (unsigned long)statusMode]; return @"";
}
}
- (NSString *)stringForEmptySelectionAtOffset:(unsigned long long)offset length:(unsigned long long)length {
return [NSString stringWithFormat:@"%@ out of %@", [self describeOffset:offset], [self describeLength:length]];
}
- (NSString *)stringForSingleByteSelectionAtOffset:(unsigned long long)offset length:(unsigned long long)length {
return [NSString stringWithFormat:@"Byte %@ selected out of %@", [self describeOffset:offset], [self describeLength:length]];
}
- (NSString *)stringForSingleRangeSelection:(HFRange)range length:(unsigned long long)length {
return [NSString stringWithFormat:@"%@ selected at offset %@ out of %@", [self describeLength:range.length], [self describeOffsetExcludingApproximate:range.location], [self describeLength:length]];
}
- (NSString *)stringForMultipleSelectionsWithLength:(unsigned long long)multipleSelectionLength length:(unsigned long long)length {
return [NSString stringWithFormat:@"%@ selected at multiple offsets out of %@", [self describeLength:multipleSelectionLength], [self describeLength:length]];
}
- (void)updateString {
NSString *string = nil;
HFController *controller = [self controller];
if (controller) {
unsigned long long length = [controller contentsLength];
NSArray *ranges = [controller selectedContentsRanges];
NSUInteger rangeCount = [ranges count];
if (rangeCount == 1) {
HFRange range = [ranges[0] HFRange];
if (range.length == 0) {
string = [self stringForEmptySelectionAtOffset:range.location length:length];
}
else if (range.length == 1) {
string = [self stringForSingleByteSelectionAtOffset:range.location length:length];
}
else {
string = [self stringForSingleRangeSelection:range length:length];
}
}
else {
unsigned long long totalSelectionLength = 0;
FOREACH(HFRangeWrapper *, wrapper, ranges) {
HFRange range = [wrapper HFRange];
totalSelectionLength = HFSum(totalSelectionLength, range.length);
}
string = [self stringForMultipleSelectionsWithLength:totalSelectionLength length:length];
}
}
if (! string) string = @"";
[[self view] setString:string];
}
- (HFStatusBarMode)statusMode {
return statusMode;
}
- (void)setStatusMode:(HFStatusBarMode)mode {
statusMode = mode;
[self updateString];
}
- (void)controllerDidChange:(HFControllerPropertyBits)bits {
if (bits & (HFControllerContentLength | HFControllerSelectedRanges)) {
[self updateString];
}
}
+ (NSPoint)defaultLayoutPosition {
return NSMakePoint(0, -1);
}
@end

View File

@ -0,0 +1,26 @@
//
// HFASCIITextRepresenter.h
// HexFiend_2
//
// Copyright 2007 ridiculous_fish. All rights reserved.
//
#import <HexFiend/HFTextRepresenter.h>
/*! @class HFStringEncodingTextRepresenter
@brief An HFRepresenter responsible for showing data interpreted via an NSStringEncoding.
HFHexTextRepresenter is an HFRepresenter responsible for showing and editing data interpreted via an NSStringEncoding. Currently only supersets of ASCII are supported.
*/
@interface HFStringEncodingTextRepresenter : HFTextRepresenter {
NSStringEncoding stringEncoding;
}
/*! Get the string encoding for this representer. The default encoding is <tt>[NSString defaultCStringEncoding]</tt>. */
@property (nonatomic) NSStringEncoding encoding;
/*! Set the string encoding for this representer. */
@end

View File

@ -0,0 +1,121 @@
//
// HFASCIITextRepresenter.m
// HexFiend_2
//
// Copyright 2007 ridiculous_fish. All rights reserved.
//
#import <HexFiend/HFStringEncodingTextRepresenter.h>
#import <HexFiend/HFRepresenterStringEncodingTextView.h>
#import <HexFiend/HFPasteboardOwner.h>
@interface HFStringEncodingPasteboardOwner : HFPasteboardOwner {
NSStringEncoding encoding;
}
@property (nonatomic) NSStringEncoding encoding;
@end
@implementation HFStringEncodingPasteboardOwner
- (void)setEncoding:(NSStringEncoding)val { encoding = val; }
- (NSStringEncoding)encoding { return encoding; }
- (void)writeDataInBackgroundToPasteboard:(NSPasteboard *)pboard ofLength:(unsigned long long)length forType:(NSString *)type trackingProgress:(id)tracker {
HFASSERT([type isEqual:NSStringPboardType]);
HFByteArray *byteArray = [self byteArray];
HFASSERT(length <= NSUIntegerMax);
NSUInteger dataLength = ll2l(length);
NSUInteger stringLength = dataLength;
NSUInteger offset = 0, remaining = dataLength;
unsigned char * restrict const stringBuffer = check_malloc(stringLength);
while (remaining > 0) {
NSUInteger amountToCopy = MIN(32u * 1024u, remaining);
[byteArray copyBytes:stringBuffer + offset range:HFRangeMake(offset, amountToCopy)];
offset += amountToCopy;
remaining -= amountToCopy;
}
NSString *string = [[NSString alloc] initWithBytesNoCopy:stringBuffer length:stringLength encoding:encoding freeWhenDone:YES];
[pboard setString:string forType:type];
[string release];
}
- (unsigned long long)stringLengthForDataLength:(unsigned long long)dataLength {
return dataLength;
}
@end
@implementation HFStringEncodingTextRepresenter
- (instancetype)init {
self = [super init];
stringEncoding = [NSString defaultCStringEncoding];
return self;
}
- (instancetype)initWithCoder:(NSCoder *)coder {
HFASSERT([coder allowsKeyedCoding]);
self = [super initWithCoder:coder];
stringEncoding = (NSStringEncoding)[coder decodeInt64ForKey:@"HFStringEncoding"];
return self;
}
- (void)encodeWithCoder:(NSCoder *)coder {
HFASSERT([coder allowsKeyedCoding]);
[super encodeWithCoder:coder];
[coder encodeInt64:stringEncoding forKey:@"HFStringEncoding"];
}
- (Class)_textViewClass {
return [HFRepresenterStringEncodingTextView class];
}
- (NSStringEncoding)encoding {
return stringEncoding;
}
- (void)setEncoding:(NSStringEncoding)encoding {
stringEncoding = encoding;
[[self view] setEncoding:encoding];
[[self controller] representer:self changedProperties:HFControllerViewSizeRatios];
}
- (void)initializeView {
[[self view] setEncoding:stringEncoding];
[super initializeView];
}
- (void)insertText:(NSString *)text {
REQUIRE_NOT_NULL(text);
NSData *data = [text dataUsingEncoding:[self encoding] allowLossyConversion:NO];
if (! data) {
NSBeep();
}
else if ([data length]) { // a 0 length text can come about via e.g. option-e
[[self controller] insertData:data replacingPreviousBytes:0 allowUndoCoalescing:YES];
}
}
- (NSData *)dataFromPasteboardString:(NSString *)string {
REQUIRE_NOT_NULL(string);
return [string dataUsingEncoding:[self encoding] allowLossyConversion:NO];
}
+ (NSPoint)defaultLayoutPosition {
return NSMakePoint(1, 0);
}
- (void)copySelectedBytesToPasteboard:(NSPasteboard *)pb {
REQUIRE_NOT_NULL(pb);
HFByteArray *selection = [[self controller] byteArrayForSelectedContentsRanges];
HFASSERT(selection != NULL);
if ([selection length] == 0) {
NSBeep();
}
else {
HFStringEncodingPasteboardOwner *owner = [HFStringEncodingPasteboardOwner ownPasteboard:pb forByteArray:selection withTypes:@[HFPrivateByteArrayPboardType, NSStringPboardType]];
[owner setEncoding:[self encoding]];
[owner setBytesPerLine:[self bytesPerLine]];
}
}
@end

View File

@ -0,0 +1,39 @@
//
// HFTextRepresenter.h
// HexFiend_2
//
// Copyright 2007 ridiculous_fish. All rights reserved.
//
#import <HexFiend/HFRepresenter.h>
#import <HexFiend/HFByteArray.h>
/*! @class HFTextRepresenter
@brief An HFRepresenter that draws text (e.g. the hex or ASCII view).
HFTextRepresenter is an abstract subclass of HFRepresenter that is responsible for displaying text. There are two concrete subclass, HFHexTextRepresenter and HFStringEncodingTextRepresenter.
Most of the functionality of HFTextRepresenter is private, and there is not yet enough exposed to allow creating new representers based on it. However, there is a small amount of configurability.
*/
@interface HFTextRepresenter : HFRepresenter {}
/*! Given a rect edge, return an NSRect representing the maximum edge in that direction, in the coordinate system of the receiver's view. The dimension in the direction of the edge is 0 (so if edge is NSMaxXEdge, the resulting width is 0). The returned rect is in the coordinate space of the receiver's view. If the byte range is not displayed, returns NSZeroRect.
If range is entirely above the visible region, returns an NSRect whose width and height are 0, and whose origin is -CGFLOAT_MAX (the most negative CGFloat). If range is entirely below the visible region, returns the same except with CGFLOAT_MAX (positive).
This raises an exception if range is empty.
*/
- (NSRect)furthestRectOnEdge:(NSRectEdge)edge forByteRange:(HFRange)range;
/*! Returns the origin of the character at the given byte index. The returned point is in the coordinate space of the receiver's view. If the character is not displayed because it would be above the displayed range, returns {0, -CGFLOAT_MAX}. If it is not displayed because it is below the displayed range, returns {0, CGFLOAT_MAX}. As a special affordance, you may pass a byte index one greater than the contents length of the controller, and it will return the result as if the byte existed.
*/
- (NSPoint)locationOfCharacterAtByteIndex:(unsigned long long)byteIndex;
/*! The per-row background colors. Each row is drawn with the next color in turn, cycling back to the beginning when the array is exhausted. Any empty space is filled with the first color in the array. If the array is empty, then the background is drawn with \c clearColor.
*/
@property (nonatomic, copy) NSArray *rowBackgroundColors;
/*! Whether the text view behaves like a text field (YES) or a text view (NO). Currently this determines whether it draws a focus ring when it is the first responder.
*/
@property (nonatomic) BOOL behavesAsTextField;
@end

View File

@ -0,0 +1,373 @@
//
// HFTextRepresenter.m
// HexFiend_2
//
// Copyright 2007 ridiculous_fish. All rights reserved.
//
#import <HexFiend/HFTextRepresenter_Internal.h>
#import <HexFiend/HFRepresenterTextView.h>
#import <HexFiend/HFPasteboardOwner.h>
#import <HexFiend/HFByteArray.h>
#import <HexFiend/HFTextVisualStyleRun.h>
@implementation HFTextRepresenter
- (Class)_textViewClass {
UNIMPLEMENTED();
}
- (instancetype)init {
self = [super init];
NSColor *color1 = [NSColor colorWithCalibratedWhite:1.0 alpha:1.0];
NSColor *color2 = [NSColor colorWithCalibratedRed:.87 green:.89 blue:1. alpha:1.];
_rowBackgroundColors = [@[color1, color2] retain];
return self;
}
- (void)dealloc {
if ([self isViewLoaded]) {
[[self view] clearRepresenter];
}
[_rowBackgroundColors release];
[super dealloc];
}
- (void)encodeWithCoder:(NSCoder *)coder {
HFASSERT([coder allowsKeyedCoding]);
[super encodeWithCoder:coder];
[coder encodeBool:_behavesAsTextField forKey:@"HFBehavesAsTextField"];
[coder encodeObject:_rowBackgroundColors forKey:@"HFRowBackgroundColors"];
}
- (instancetype)initWithCoder:(NSCoder *)coder {
HFASSERT([coder allowsKeyedCoding]);
self = [super initWithCoder:coder];
_behavesAsTextField = [coder decodeBoolForKey:@"HFBehavesAsTextField"];
_rowBackgroundColors = [[coder decodeObjectForKey:@"HFRowBackgroundColors"] retain];
return self;
}
- (NSView *)createView {
HFRepresenterTextView *view = [[[self _textViewClass] alloc] initWithRepresenter:self];
[view setAutoresizingMask:NSViewHeightSizable];
return view;
}
- (HFByteArrayDataStringType)byteArrayDataStringType {
UNIMPLEMENTED();
}
- (HFRange)entireDisplayedRange {
HFController *controller = [self controller];
unsigned long long contentsLength = [controller contentsLength];
HFASSERT(controller != NULL);
HFFPRange displayedLineRange = [controller displayedLineRange];
NSUInteger bytesPerLine = [controller bytesPerLine];
unsigned long long lineStart = HFFPToUL(floorl(displayedLineRange.location));
unsigned long long lineEnd = HFFPToUL(ceill(displayedLineRange.location + displayedLineRange.length));
HFASSERT(lineEnd >= lineStart);
HFRange byteRange = HFRangeMake(HFProductULL(bytesPerLine, lineStart), HFProductULL(lineEnd - lineStart, bytesPerLine));
if (byteRange.length == 0) {
/* This can happen if we are too small to even show one line */
return HFRangeMake(0, 0);
}
else {
HFASSERT(byteRange.location <= contentsLength);
byteRange.length = MIN(byteRange.length, contentsLength - byteRange.location);
HFASSERT(HFRangeIsSubrangeOfRange(byteRange, HFRangeMake(0, [controller contentsLength])));
return byteRange;
}
}
- (NSRect)furthestRectOnEdge:(NSRectEdge)edge forByteRange:(HFRange)byteRange {
HFASSERT(byteRange.length > 0);
HFRange displayedRange = [self entireDisplayedRange];
HFRange intersection = HFIntersectionRange(displayedRange, byteRange);
NSRect result;
if (intersection.length > 0) {
NSRange intersectionNSRange = NSMakeRange(ll2l(intersection.location - displayedRange.location), ll2l(intersection.length));
if (intersectionNSRange.length > 0) {
result = [[self view] furthestRectOnEdge:edge forRange:intersectionNSRange];
}
}
else if (byteRange.location < displayedRange.location) {
/* We're below it. */
return NSMakeRect(-CGFLOAT_MAX, -CGFLOAT_MAX, 0, 0);
}
else if (byteRange.location >= HFMaxRange(displayedRange)) {
/* We're above it */
return NSMakeRect(CGFLOAT_MAX, CGFLOAT_MAX, 0, 0);
}
else {
/* Shouldn't be possible to get here */
[NSException raise:NSInternalInconsistencyException format:@"furthestRectOnEdge: expected an intersection, or a range below or above the byte range, but nothin'"];
}
return result;
}
- (NSPoint)locationOfCharacterAtByteIndex:(unsigned long long)index {
NSPoint result;
HFRange displayedRange = [self entireDisplayedRange];
if (HFLocationInRange(index, displayedRange) || index == HFMaxRange(displayedRange)) {
NSUInteger location = ll2l(index - displayedRange.location);
result = [[self view] originForCharacterAtByteIndex:location];
}
else if (index < displayedRange.location) {
result = NSMakePoint(-CGFLOAT_MAX, -CGFLOAT_MAX);
}
else {
result = NSMakePoint(CGFLOAT_MAX, CGFLOAT_MAX);
}
return result;
}
- (HFTextVisualStyleRun *)styleForAttributes:(NSSet *)attributes range:(NSRange)range {
HFTextVisualStyleRun *run = [[[HFTextVisualStyleRun alloc] init] autorelease];
[run setRange:range];
[run setForegroundColor:[NSColor blackColor]];
return run;
}
- (NSArray *)stylesForRange:(HFRange)range {
return nil;
}
- (void)updateText {
HFController *controller = [self controller];
HFRepresenterTextView *view = [self view];
HFRange entireDisplayedRange = [self entireDisplayedRange];
[view setData:[controller dataForRange:entireDisplayedRange]];
[view setStyles:[self stylesForRange:entireDisplayedRange]];
HFFPRange lineRange = [controller displayedLineRange];
long double offsetLongDouble = lineRange.location - floorl(lineRange.location);
CGFloat offset = ld2f(offsetLongDouble);
[view setVerticalOffset:offset];
[view setStartingLineBackgroundColorIndex:ll2l(HFFPToUL(floorl(lineRange.location)) % NSUIntegerMax)];
}
- (void)initializeView {
[super initializeView];
HFRepresenterTextView *view = [self view];
HFController *controller = [self controller];
if (controller) {
[view setFont:[controller font]];
[view setEditable:[controller editable]];
[self updateText];
}
else {
[view setFont:[NSFont fontWithName:HFDEFAULT_FONT size:HFDEFAULT_FONTSIZE]];
}
}
- (void)scrollWheel:(NSEvent *)event {
[[self controller] scrollWithScrollEvent:event];
}
- (void)selectAll:(id)sender {
[[self controller] selectAll:sender];
}
- (double)selectionPulseAmount {
return [[self controller] selectionPulseAmount];
}
- (void)controllerDidChange:(HFControllerPropertyBits)bits {
if (bits & (HFControllerFont | HFControllerLineHeight)) {
[[self view] setFont:[[self controller] font]];
}
if (bits & (HFControllerContentValue | HFControllerDisplayedLineRange | HFControllerByteRangeAttributes)) {
[self updateText];
}
if (bits & (HFControllerSelectedRanges | HFControllerDisplayedLineRange)) {
[[self view] updateSelectedRanges];
}
if (bits & (HFControllerEditable)) {
[[self view] setEditable:[[self controller] editable]];
}
if (bits & (HFControllerAntialias)) {
[[self view] setShouldAntialias:[[self controller] shouldAntialias]];
}
if (bits & (HFControllerShowCallouts)) {
[[self view] setShouldDrawCallouts:[[self controller] shouldShowCallouts]];
}
if (bits & (HFControllerColorBytes)) {
if([[self controller] shouldColorBytes]) {
[[self view] setByteColoring: ^(uint8_t byte, uint8_t *r, uint8_t *g, uint8_t *b, uint8_t *a){
*r = *g = *b = (uint8_t)(255 * ((255-byte)/255.0*0.6+0.4));
*a = (uint8_t)(255 * 0.7);
}];
} else {
[[self view] setByteColoring:NULL];
}
}
[super controllerDidChange:bits];
}
- (double)maximumAvailableLinesForViewHeight:(CGFloat)viewHeight {
return [[self view] maximumAvailableLinesForViewHeight:viewHeight];
}
- (NSUInteger)maximumBytesPerLineForViewWidth:(CGFloat)viewWidth {
return [[self view] maximumBytesPerLineForViewWidth:viewWidth];
}
- (CGFloat)minimumViewWidthForBytesPerLine:(NSUInteger)bytesPerLine {
return [[self view] minimumViewWidthForBytesPerLine:bytesPerLine];
}
- (NSUInteger)byteGranularity {
HFRepresenterTextView *view = [self view];
NSUInteger bytesPerColumn = MAX([view bytesPerColumn], 1u), bytesPerCharacter = [view bytesPerCharacter];
return HFLeastCommonMultiple(bytesPerColumn, bytesPerCharacter);
}
- (NSArray *)displayedSelectedContentsRanges {
HFController *controller = [self controller];
NSArray *result;
NSArray *selectedRanges = [controller selectedContentsRanges];
HFRange displayedRange = [self entireDisplayedRange];
HFASSERT(displayedRange.length <= NSUIntegerMax);
NEW_ARRAY(NSValue *, clippedSelectedRanges, [selectedRanges count]);
NSUInteger clippedRangeIndex = 0;
FOREACH(HFRangeWrapper *, wrapper, selectedRanges) {
HFRange selectedRange = [wrapper HFRange];
BOOL clippedRangeIsVisible;
NSRange clippedSelectedRange;
/* Necessary because zero length ranges do not intersect anything */
if (selectedRange.length == 0) {
/* Remember that {6, 0} is considered a subrange of {3, 3} */
clippedRangeIsVisible = HFRangeIsSubrangeOfRange(selectedRange, displayedRange);
if (clippedRangeIsVisible) {
HFASSERT(selectedRange.location >= displayedRange.location);
clippedSelectedRange.location = ll2l(selectedRange.location - displayedRange.location);
clippedSelectedRange.length = 0;
}
}
else {
// selectedRange.length > 0
clippedRangeIsVisible = HFIntersectsRange(selectedRange, displayedRange);
if (clippedRangeIsVisible) {
HFRange intersectionRange = HFIntersectionRange(selectedRange, displayedRange);
HFASSERT(intersectionRange.location >= displayedRange.location);
clippedSelectedRange.location = ll2l(intersectionRange.location - displayedRange.location);
clippedSelectedRange.length = ll2l(intersectionRange.length);
}
}
if (clippedRangeIsVisible) clippedSelectedRanges[clippedRangeIndex++] = [NSValue valueWithRange:clippedSelectedRange];
}
result = [NSArray arrayWithObjects:clippedSelectedRanges count:clippedRangeIndex];
FREE_ARRAY(clippedSelectedRanges);
return result;
}
//maps bookmark keys as NSNumber to byte locations as NSNumbers. Because bookmark callouts may extend beyond the lines containing them, allow a larger range by 10 lines.
- (NSDictionary *)displayedBookmarkLocations {
NSMutableDictionary *result = nil;
HFController *controller = [self controller];
NSUInteger rangeExtension = 10 * [controller bytesPerLine];
HFRange displayedRange = [self entireDisplayedRange];
HFRange includedRange = displayedRange;
/* Extend the bottom */
unsigned long long bottomExtension = MIN(includedRange.location, rangeExtension);
includedRange.location -= bottomExtension;
includedRange.length += bottomExtension;
/* Extend the top */
unsigned long long topExtension = MIN([controller contentsLength] - HFMaxRange(includedRange), rangeExtension);
includedRange.length = HFSum(includedRange.length, topExtension);
return result;
}
- (unsigned long long)byteIndexForCharacterIndex:(NSUInteger)characterIndex {
HFController *controller = [self controller];
HFFPRange lineRange = [controller displayedLineRange];
unsigned long long scrollAmount = HFFPToUL(floorl(lineRange.location));
unsigned long long byteIndex = HFProductULL(scrollAmount, [controller bytesPerLine]) + characterIndex * [[self view] bytesPerCharacter];
return byteIndex;
}
- (void)beginSelectionWithEvent:(NSEvent *)event forCharacterIndex:(NSUInteger)characterIndex {
[[self controller] beginSelectionWithEvent:event forByteIndex:[self byteIndexForCharacterIndex:characterIndex]];
}
- (void)continueSelectionWithEvent:(NSEvent *)event forCharacterIndex:(NSUInteger)characterIndex {
[[self controller] continueSelectionWithEvent:event forByteIndex:[self byteIndexForCharacterIndex:characterIndex]];
}
- (void)endSelectionWithEvent:(NSEvent *)event forCharacterIndex:(NSUInteger)characterIndex {
[[self controller] endSelectionWithEvent:event forByteIndex:[self byteIndexForCharacterIndex:characterIndex]];
}
- (void)insertText:(NSString *)text {
USE(text);
UNIMPLEMENTED_VOID();
}
- (void)copySelectedBytesToPasteboard:(NSPasteboard *)pb {
USE(pb);
UNIMPLEMENTED_VOID();
}
- (void)cutSelectedBytesToPasteboard:(NSPasteboard *)pb {
[self copySelectedBytesToPasteboard:pb];
[[self controller] deleteSelection];
}
- (NSData *)dataFromPasteboardString:(NSString *)string {
USE(string);
UNIMPLEMENTED();
}
- (BOOL)canPasteFromPasteboard:(NSPasteboard *)pb {
REQUIRE_NOT_NULL(pb);
if ([[self controller] editable]) {
// we can paste if the pboard contains text or contains an HFByteArray
return [HFPasteboardOwner unpackByteArrayFromPasteboard:pb] || [pb availableTypeFromArray:@[NSStringPboardType]];
}
return NO;
}
- (BOOL)canCut {
/* We can cut if we are editable, we have at least one byte selected, and we are not in overwrite mode */
HFController *controller = [self controller];
if ([controller editMode] != HFInsertMode) return NO;
if (! [controller editable]) return NO;
FOREACH(HFRangeWrapper *, rangeWrapper, [controller selectedContentsRanges]) {
if ([rangeWrapper HFRange].length > 0) return YES; //we have something selected
}
return NO; // we did not find anything selected
}
- (BOOL)pasteBytesFromPasteboard:(NSPasteboard *)pb {
REQUIRE_NOT_NULL(pb);
BOOL result = NO;
HFByteArray *byteArray = [HFPasteboardOwner unpackByteArrayFromPasteboard:pb];
if (byteArray) {
[[self controller] insertByteArray:byteArray replacingPreviousBytes:0 allowUndoCoalescing:NO];
result = YES;
}
else {
NSString *stringType = [pb availableTypeFromArray:@[NSStringPboardType]];
if (stringType) {
NSString *stringValue = [pb stringForType:stringType];
if (stringValue) {
NSData *data = [self dataFromPasteboardString:stringValue];
if (data) {
[[self controller] insertData:data replacingPreviousBytes:0 allowUndoCoalescing:NO];
}
}
}
}
return result;
}
@end

View File

@ -0,0 +1,33 @@
#import <HexFiend/HFTextRepresenter.h>
@interface HFTextRepresenter (HFInternal)
- (NSArray *)displayedSelectedContentsRanges; //returns an array of NSValues representing the selected ranges (as NSRanges) clipped to the displayed range.
- (NSDictionary *)displayedBookmarkLocations; //returns an dictionary mapping bookmark names to bookmark locations. Bookmark locations may be negative.
- (void)beginSelectionWithEvent:(NSEvent *)event forCharacterIndex:(NSUInteger)characterIndex;
- (void)continueSelectionWithEvent:(NSEvent *)event forCharacterIndex:(NSUInteger)characterIndex;
- (void)endSelectionWithEvent:(NSEvent *)event forCharacterIndex:(NSUInteger)characterIndex;
// Copy/Paste methods
- (void)copySelectedBytesToPasteboard:(NSPasteboard *)pb;
- (void)cutSelectedBytesToPasteboard:(NSPasteboard *)pb;
- (BOOL)canPasteFromPasteboard:(NSPasteboard *)pb;
- (BOOL)canCut;
- (BOOL)pasteBytesFromPasteboard:(NSPasteboard *)pb;
// Must be implemented by subclasses
- (void)insertText:(NSString *)text;
// Must be implemented by subclasses. Return NSData representing the string value.
- (NSData *)dataFromPasteboardString:(NSString *)string;
// Value between [0, 1]
- (double)selectionPulseAmount;
- (void)scrollWheel:(NSEvent *)event;
- (void)selectAll:(id)sender;
@end

View File

@ -0,0 +1,128 @@
//
// HFTextRepresenter_KeyBinding.m
// HexFiend_2
//
// Copyright 2007 ridiculous_fish. All rights reserved.
//
#import <HexFiend/HFTextRepresenter.h>
#import <HexFiend/HFRepresenterTextView.h>
#import <HexFiend/HFController.h>
#define FORWARD(x) - (void)x : sender { USE(sender); UNIMPLEMENTED_VOID(); }
@implementation HFTextRepresenter (HFKeyBinding)
- (void)moveRight:unused { USE(unused); [[self controller] moveInDirection:HFControllerDirectionRight withGranularity:HFControllerMovementByte andModifySelection:NO]; }
- (void)moveLeft:unused { USE(unused); [[self controller] moveInDirection:HFControllerDirectionLeft withGranularity:HFControllerMovementByte andModifySelection:NO]; }
- (void)moveUp:unused { USE(unused); [[self controller] moveInDirection:HFControllerDirectionLeft withGranularity:HFControllerMovementLine andModifySelection:NO]; }
- (void)moveDown:unused { USE(unused); [[self controller] moveInDirection:HFControllerDirectionRight withGranularity:HFControllerMovementLine andModifySelection:NO]; }
- (void)moveWordRight:unused { USE(unused); [[self controller] moveInDirection:HFControllerDirectionRight withGranularity:HFControllerMovementColumn andModifySelection:NO]; }
- (void)moveWordLeft:unused { USE(unused); [[self controller] moveInDirection:HFControllerDirectionLeft withGranularity:HFControllerMovementColumn andModifySelection:NO]; }
- (void)moveRightAndModifySelection:unused { USE(unused); [[self controller] moveInDirection:HFControllerDirectionRight withGranularity:HFControllerMovementByte andModifySelection:YES]; }
- (void)moveLeftAndModifySelection:unused { USE(unused); [[self controller] moveInDirection:HFControllerDirectionLeft withGranularity:HFControllerMovementByte andModifySelection:YES]; }
- (void)moveUpAndModifySelection:unused { USE(unused); [[self controller] moveInDirection:HFControllerDirectionLeft withGranularity:HFControllerMovementLine andModifySelection:YES]; }
- (void)moveDownAndModifySelection:unused { USE(unused); [[self controller] moveInDirection:HFControllerDirectionRight withGranularity:HFControllerMovementLine andModifySelection:YES]; }
- (void)moveWordRightAndModifySelection:unused { USE(unused); [[self controller] moveInDirection:HFControllerDirectionRight withGranularity:HFControllerMovementColumn andModifySelection:YES]; }
- (void)moveWordLeftAndModifySelection:unused { USE(unused); [[self controller] moveInDirection:HFControllerDirectionLeft withGranularity:HFControllerMovementColumn andModifySelection:YES]; }
- (void)moveForward:unused { USE(unused); [self moveRight:unused]; }
- (void)moveBackward:unused { USE(unused); [self moveLeft:unused]; }
- (void)moveWordForward:unused { USE(unused); [self moveWordRight:unused]; }
- (void)moveWordBackward:unused { USE(unused); [self moveWordLeft:unused]; }
- (void)moveForwardAndModifySelection:unused { USE(unused); [self moveRightAndModifySelection:unused]; }
- (void)moveBackwardAndModifySelection:unused { USE(unused); [self moveLeftAndModifySelection:unused]; }
- (void)moveWordForwardAndModifySelection:unused { USE(unused); [self moveForwardAndModifySelection:unused]; }
- (void)moveWordBackwardAndModifySelection:unused { USE(unused); [self moveBackwardAndModifySelection:unused]; }
- (void)deleteBackward:unused { USE(unused); [[self controller] deleteDirection:HFControllerDirectionLeft]; }
- (void)deleteForward:unused { USE(unused); [[self controller] deleteDirection:HFControllerDirectionRight]; }
- (void)deleteWordForward:unused { USE(unused); [self deleteForward:unused]; }
- (void)deleteWordBackward:unused { USE(unused); [self deleteBackward:unused]; }
- (void)delete:unused { USE(unused); [self deleteForward:unused]; }
//todo: implement these
- (void)deleteToBeginningOfLine:(id)sender { USE(sender); }
- (void)deleteToEndOfLine:(id)sender { USE(sender); }
- (void)deleteToBeginningOfParagraph:(id)sender { USE(sender); }
- (void)deleteToEndOfParagraph:(id)sender { USE(sender); }
- (void)moveToBeginningOfLine:unused { USE(unused); [[self controller] moveToLineBoundaryInDirection:HFControllerDirectionLeft andModifySelection:NO]; }
- (void)moveToEndOfLine:unused { USE(unused); [[self controller] moveToLineBoundaryInDirection:HFControllerDirectionRight andModifySelection:NO]; }
- (void)moveToBeginningOfDocument:unused { USE(unused); [[self controller] moveInDirection:HFControllerDirectionLeft withGranularity:HFControllerMovementDocument andModifySelection:NO]; }
- (void)moveToEndOfDocument:unused { USE(unused); [[self controller] moveInDirection:HFControllerDirectionRight withGranularity:HFControllerMovementDocument andModifySelection:NO]; }
- (void)moveToBeginningOfLineAndModifySelection:unused { USE(unused); [[self controller] moveToLineBoundaryInDirection:HFControllerDirectionLeft andModifySelection:YES]; }
- (void)moveToEndOfLineAndModifySelection:unused { USE(unused); [[self controller] moveToLineBoundaryInDirection:HFControllerDirectionRight andModifySelection:YES]; }
- (void)moveToBeginningOfDocumentAndModifySelection:unused { USE(unused); [[self controller] moveInDirection:HFControllerDirectionLeft withGranularity:HFControllerMovementDocument andModifySelection:YES]; }
- (void)moveToEndOfDocumentAndModifySelection:unused { USE(unused); [[self controller] moveInDirection:HFControllerDirectionRight withGranularity:HFControllerMovementDocument andModifySelection:YES]; }
- (void)moveToBeginningOfParagraph:unused { USE(unused); [self moveToBeginningOfLine:unused]; }
- (void)moveToEndOfParagraph:unused { USE(unused); [self moveToEndOfLine:unused]; }
- (void)moveToBeginningOfParagraphAndModifySelection:unused { USE(unused); [self moveToBeginningOfLineAndModifySelection:unused]; }
- (void)moveToEndOfParagraphAndModifySelection:unused { USE(unused); [self moveToEndOfLineAndModifySelection:unused]; }
- (void)scrollPageDown:unused { USE(unused); [[self controller] scrollByLines:[[self controller] displayedLineRange].length]; }
- (void)scrollPageUp:unused { USE(unused); [[self controller] scrollByLines: - [[self controller] displayedLineRange].length]; }
- (void)pageDown:unused { USE(unused); [self scrollPageDown:unused]; }
- (void)pageUp:unused { USE(unused); [self scrollPageUp:unused]; }
- (void)centerSelectionInVisibleArea:unused {
USE(unused);
HFController *controller = [self controller];
NSArray *selection = [controller selectedContentsRanges];
unsigned long long min = ULLONG_MAX, max = 0;
HFASSERT([selection count] >= 1);
FOREACH(HFRangeWrapper *, wrapper, selection) {
HFRange range = [wrapper HFRange];
min = MIN(min, range.location);
max = MAX(max, HFMaxRange(range));
}
HFASSERT(max >= min);
[controller maximizeVisibilityOfContentsRange:HFRangeMake(min, max - min)];
}
- (void)insertTab:unused {
USE(unused);
[[[self view] window] selectNextKeyView:nil];
}
- (void)insertBacktab:unused {
USE(unused);
[[[self view] window] selectPreviousKeyView:nil];
}
FORWARD(scrollLineUp)
FORWARD(scrollLineDown)
FORWARD(transpose)
FORWARD(transposeWords)
FORWARD(selectParagraph)
FORWARD(selectLine)
FORWARD(selectWord)
FORWARD(indent)
//FORWARD(insertNewline)
FORWARD(insertParagraphSeparator)
FORWARD(insertNewlineIgnoringFieldEditor)
FORWARD(insertTabIgnoringFieldEditor)
FORWARD(insertLineBreak)
FORWARD(insertContainerBreak)
FORWARD(changeCaseOfLetter)
FORWARD(uppercaseWord)
FORWARD(lowercaseWord)
FORWARD(capitalizeWord)
FORWARD(deleteBackwardByDecomposingPreviousCharacter)
FORWARD(yank)
FORWARD(complete)
FORWARD(setMark)
FORWARD(deleteToMark)
FORWARD(selectToMark)
FORWARD(swapWithMark)
//FORWARD(cancelOperation)
@end

View File

@ -0,0 +1,23 @@
//
// HFTextVisualStyle.h
// HexFiend_2
//
// Copyright 2009 ridiculous_fish. All rights reserved.
//
#import <Cocoa/Cocoa.h>
@interface HFTextVisualStyleRun : NSObject {}
@property (nonatomic, copy) NSColor *foregroundColor;
@property (nonatomic, copy) NSColor *backgroundColor;
@property (nonatomic) NSRange range;
@property (nonatomic) BOOL shouldDraw;
@property (nonatomic) CGFloat scale;
@property (nonatomic, copy) NSIndexSet *bookmarkStarts;
@property (nonatomic, copy) NSIndexSet *bookmarkExtents;
@property (nonatomic, copy) NSIndexSet *bookmarkEnds;
- (void)set;
@end

View File

@ -0,0 +1,79 @@
//
// HFTextVisualStyleRun.m
// HexFiend_2
//
// Copyright 2009 ridiculous_fish. All rights reserved.
//
#import "HFTextVisualStyleRun.h"
@implementation HFTextVisualStyleRun
- (instancetype)init {
self = [super init];
_scale = 1.;
_shouldDraw = YES;
return self;
}
- (void)dealloc {
[_foregroundColor release];
[_backgroundColor release];
[_bookmarkStarts release];
[_bookmarkExtents release];
[super dealloc];
}
- (void)set {
[_foregroundColor set];
if (_scale != (CGFloat)1.0) {
CGContextRef ctx = [[NSGraphicsContext currentContext] graphicsPort];
CGAffineTransform tm = CGContextGetTextMatrix(ctx);
/* Huge hack - adjust downward a little bit if we are scaling */
tm = CGAffineTransformTranslate(tm, 0, -.25 * (_scale - 1));
tm = CGAffineTransformScale(tm, _scale, _scale);
CGContextSetTextMatrix(ctx, tm);
}
}
static inline NSUInteger flip(NSUInteger x) {
return _Generic(x, unsigned: NSSwapInt, unsigned long: NSSwapLong, unsigned long long: NSSwapLongLong)(x);
}
static inline NSUInteger rol(NSUInteger x, unsigned char r) {
r %= sizeof(NSUInteger)*8;
return (x << r) | (x << (sizeof(NSUInteger)*8 - r));
}
- (NSUInteger)hash {
NSUInteger A = 0;
// All these hashes tend to have only low bits, except the double which has only high bits.
#define Q(x, r) rol(x, sizeof(NSUInteger)*r/6)
A ^= flip([_foregroundColor hash] ^ Q([_backgroundColor hash], 2)); // skew high
A ^= Q(_range.length ^ flip(_range.location), 2); // skew low
A ^= flip([_bookmarkStarts hash]) ^ Q([_bookmarkEnds hash], 3) ^ Q([_bookmarkExtents hash], 4); // skew high
A ^= _shouldDraw ? 0 : (NSUInteger)-1;
A ^= *(NSUInteger*)&_scale; // skew high
return A;
#undef Q
}
- (BOOL)isEqual:(HFTextVisualStyleRun *)run {
if(![run isKindOfClass:[self class]]) return NO;
/* Check each field for equality. */
if(!NSEqualRanges(_range, run->_range)) return NO;
if(_scale != run->_scale) return NO;
if(_shouldDraw != run->_shouldDraw) return NO;
if(!!_foregroundColor != !!run->_foregroundColor) return NO;
if(!!_backgroundColor != !!run->_backgroundColor) return NO;
if(!!_bookmarkStarts != !!run->_bookmarkStarts) return NO;
if(!!_bookmarkExtents != !!run->_bookmarkExtents) return NO;
if(!!_bookmarkEnds != !!run->_bookmarkEnds) return NO;
if(![_foregroundColor isEqual: run->_foregroundColor]) return NO;
if(![_backgroundColor isEqual: run->_backgroundColor]) return NO;
if(![_bookmarkStarts isEqual: run->_bookmarkStarts]) return NO;
if(![_bookmarkExtents isEqual: run->_bookmarkExtents]) return NO;
if(![_bookmarkEnds isEqual: run->_bookmarkEnds]) return NO;
return YES;
}
@end

13
HexFiend/HFTypes.h Normal file
View File

@ -0,0 +1,13 @@
/*! @brief HFRange is the 64 bit analog of NSRange, containing a 64 bit location and length. */
typedef struct {
unsigned long long location;
unsigned long long length;
} HFRange;
/*! @brief HFFPRange is a struct used for representing floating point ranges, similar to NSRange. It contains two long doubles.
This is useful for (for example) showing the range of visible lines. A double-precision value has 53 significant bits in the mantissa - so we would start to have precision problems at the high end of the range we can represent. Long double has a 64 bit mantissa on Intel, which means that we would start to run into trouble at the very very end of our range - barely acceptable. */
typedef struct {
long double location;
long double length;
} HFFPRange;

View File

@ -0,0 +1,21 @@
//
// HFRepresenterVerticalScroller.h
// HexFiend_2
//
// Copyright 2007 ridiculous_fish. All rights reserved.
//
#import <HexFiend/HFRepresenter.h>
/*! @class HFVerticalScrollerRepresenter
@brief An HFRepresenter responsible for showing a vertical scroll bar.
HFVerticalScrollerRepresenter is an HFRepresenter whose view is a vertical NSScroller, that represents the current position within an HFController "document." It has no methods beyond those of HFRepresenter.
As HFVerticalScrollerRepresenter is an especially simple representer, it makes for good sample code.
*/
@interface HFVerticalScrollerRepresenter : HFRepresenter {
}
@end

View File

@ -0,0 +1,133 @@
//
// HFRepresenterVerticalScroller.m
// HexFiend_2
//
// Copyright 2007 ridiculous_fish. All rights reserved.
//
/* Note that on Tiger, NSScroller did not support double in any meaningful way; [scroller doubleValue] always returns 0, and setDoubleValue: doesn't look like it works either. */
#import <HexFiend/HFVerticalScrollerRepresenter.h>
@implementation HFVerticalScrollerRepresenter
/* No special NSCoding support needed */
- (NSView *)createView {
NSScroller *scroller = [[NSScroller alloc] initWithFrame:NSMakeRect(0, 0, [NSScroller scrollerWidthForControlSize:NSRegularControlSize scrollerStyle:NSScrollerStyleLegacy], 64)];
[scroller setTarget:self];
[scroller setContinuous:YES];
[scroller setEnabled:YES];
[scroller setTarget:self];
[scroller setAction:@selector(scrollerDidChangeValue:)];
[scroller setAutoresizingMask:NSViewHeightSizable];
return scroller;
}
- (NSUInteger)visibleLines {
HFController *controller = [self controller];
HFASSERT(controller != NULL);
return ll2l(HFFPToUL(ceill([controller displayedLineRange].length)));
}
- (void)scrollByKnobToValue:(double)newValue {
HFASSERT(newValue >= 0. && newValue <= 1.);
HFController *controller = [self controller];
unsigned long long contentsLength = [controller contentsLength];
NSUInteger bytesPerLine = [controller bytesPerLine];
HFASSERT(bytesPerLine > 0);
unsigned long long totalLineCountTimesBytesPerLine = HFRoundUpToNextMultipleSaturate(contentsLength - 1, bytesPerLine);
HFASSERT(totalLineCountTimesBytesPerLine == ULLONG_MAX || totalLineCountTimesBytesPerLine % bytesPerLine == 0);
unsigned long long totalLineCount = HFDivideULLRoundingUp(totalLineCountTimesBytesPerLine, bytesPerLine);
HFFPRange currentLineRange = [controller displayedLineRange];
HFASSERT(currentLineRange.length < HFULToFP(totalLineCount));
long double maxScroll = totalLineCount - currentLineRange.length;
long double newScroll = maxScroll * (long double)newValue;
[controller setDisplayedLineRange:(HFFPRange){newScroll, currentLineRange.length}];
}
- (void)scrollByLines:(long long)linesInt {
if (linesInt == 0) return;
//note - this properly computes the absolute value even for LLONG_MIN
long double lines = HFULToFP((unsigned long long)llabs(linesInt));
HFController *controller = [self controller];
HFASSERT(controller != NULL);
HFFPRange displayedRange = [[self controller] displayedLineRange];
if (linesInt < 0) {
displayedRange.location -= MIN(lines, displayedRange.location);
}
else {
long double availableLines = HFULToFP([controller totalLineCount]);
displayedRange.location = MIN(availableLines - displayedRange.length, displayedRange.location + lines);
}
[controller setDisplayedLineRange:displayedRange];
}
- (void)scrollerDidChangeValue:(NSScroller *)scroller {
assert(scroller == [self view]);
switch ([scroller hitPart]) {
case NSScrollerDecrementPage: [self scrollByLines: -(long long)[self visibleLines]]; break;
case NSScrollerIncrementPage: [self scrollByLines: (long long)[self visibleLines]]; break;
case NSScrollerDecrementLine: [self scrollByLines: -1LL]; break;
case NSScrollerIncrementLine: [self scrollByLines: 1LL]; break;
case NSScrollerKnob: [self scrollByKnobToValue:[scroller doubleValue]]; break;
default: break;
}
}
- (void)updateScrollerValue {
HFController *controller = [self controller];
CGFloat value, proportion;
NSScroller *scroller = [self view];
BOOL enable = YES;
if (controller == nil) {
value = 0;
proportion = 0;
}
else {
unsigned long long length = [controller contentsLength];
HFFPRange lineRange = [controller displayedLineRange];
HFASSERT(lineRange.location >= 0 && lineRange.length >= 0);
if (length == 0) {
value = 0;
proportion = 1;
enable = NO;
}
else {
long double availableLines = HFULToFP([controller totalLineCount]);
long double consumedLines = MAX(1., lineRange.length);
proportion = ld2f(lineRange.length / availableLines);
long double maxScroll = availableLines - consumedLines;
HFASSERT(maxScroll >= lineRange.location);
if (maxScroll == 0.) {
enable = NO;
value = 0;
}
else {
value = ld2f(lineRange.location / maxScroll);
}
}
}
[scroller setDoubleValue:value];
[scroller setKnobProportion:proportion];
[scroller setEnabled:enable];
}
- (CGFloat)minimumViewWidthForBytesPerLine:(NSUInteger)bytesPerLine {
USE(bytesPerLine);
return [NSScroller scrollerWidthForControlSize:[[self view] controlSize] scrollerStyle:NSScrollerStyleLegacy];
}
- (void)controllerDidChange:(HFControllerPropertyBits)bits {
if (bits & (HFControllerContentLength | HFControllerDisplayedLineRange)) [self updateScrollerValue];
}
+ (NSPoint)defaultLayoutPosition {
return NSMakePoint(2, 0);
}
@end

78
HexFiend/HexFiend.h Normal file
View File

@ -0,0 +1,78 @@
/*! @mainpage HexFiend.framework
*
* @section intro Introduction
* HexFiend.framework (hereafter "Hex Fiend" when there is no risk of confusion with the app by the same name) is a framework designed to enable applications to support viewing and editing of binary data. The emphasis is on editing data in a natural way, following Mac OS X text editing conventions.
*
* Hex Fiend is designed to work efficiently with large amounts (64 bits worth) of data. As such, it can work with arbitrarily large files without reading the entire file into memory. This includes insertions, deletions, and in-place editing. Hex Fiend can also efficiently save such changes back to the file, without requiring any additional temporary disk space.
*
* Hex Fiend has a clean separation between the model, view, and controller layers. The model layer allows for efficient manipulation of raw data of mixed sources, making it useful for tools that need to work with large files.
*
* Both the framework and the app are open source under a BSD-style license. In summary, you may use Hex Fiend in any project as long as you include the copyright notice somewhere in the documentation.
*
* @section requirements Requirements
* Hex Fiend is only available on Mac OS X, and supported on Mountain Lion and later.
*
* @section getting_started Getting Started
*
* The Hex Fiend source code is available at http://ridiculousfish.com/hexfiend/ and on GitHub at https://github.com/ridiculousfish/HexFiend
*
* Hex Fiend comes with some sample code ("HexFiendling"), distributed as part of the project. And of course the Hex Fiend application itself is open source, acting as a more sophisticated sample code.
*/
#import <HexFiend/HFTypes.h>
#import <HexFiend/HFFunctions.h>
#import <HexFiend/HFController.h>
#import <HexFiend/HFRepresenter.h>
#import <HexFiend/HFFullMemoryByteArray.h>
#import <HexFiend/HFFullMemoryByteSlice.h>
#import <HexFiend/HFHexTextRepresenter.h>
#import <HexFiend/HFLineCountingRepresenter.h>
#import <HexFiend/HFStatusBarRepresenter.h>
#import <HexFiend/HFLayoutRepresenter.h>
#import <HexFiend/HFStringEncodingTextRepresenter.h>
#import <HexFiend/HFVerticalScrollerRepresenter.h>
#import <HexFiend/HFByteArray.h>
#import <HexFiend/HFBTreeByteArray.h>
/* The following is all for Doxygen */
/*! @defgroup model Model
* Hex Fiend's model classes
*/
///@{
///@class HFByteArray
///@class HFBTreeByteArray
///@class HFFullMemoryByteArray
///@class HFByteSlice
///@class HFFileByteSlice
///@class HFSharedMemoryByteSlice
///@class HFFullMemoryByteSlice
///@}
/*! @defgroup view View
* Hex Fiend's view classes
*/
///@{
///@class HFRepresenter
///@class HFHexTextRepresenter
///@class HFStringEncodingTextRepresenter
///@class HFLayoutRepresenter
///@class HFLineCountingRepresenter
///@class HFStatusBarRepresenter
///@class HFVerticalScrollerRepresenter
///@class HFLineCountingRepresenter
///@}
/*! @defgroup controller Controller
* Hex Fiend's controller classes
*/
///@{
///@class HFController
///@}

View File

@ -0,0 +1,99 @@
//
// Prefix header for all source files of the 'HexFiend_2' target in the 'HexFiend_2' project
//
#ifdef __OBJC__
#import <Cocoa/Cocoa.h>
#import <HexFiend/HFTypes.h>
#endif
#define PRIVATE_EXTERN __private_extern__
#include <assert.h>
#if ! NDEBUG
#define HFASSERT(a) assert(a)
#else
#define HFASSERT(a) if (0 && ! (a)) abort()
#endif
#define UNIMPLEMENTED_VOID() [NSException raise:NSGenericException \
format:@"Message %@ sent to instance of class %@, "\
@"which does not implement that method",\
NSStringFromSelector(_cmd), [[self class] description]]
#define UNIMPLEMENTED() UNIMPLEMENTED_VOID(); return 0
/* Macro to "use" a variable to prevent unused variable warnings. */
#define USE(x) ((void)(x))
#define check_malloc(x) ({ size_t _count = (x); void *_result = malloc(_count); if(!_result) { fprintf(stderr, "Out of memory allocating %lu bytes\n", (unsigned long)_count); exit(EXIT_FAILURE); } _result; })
#define check_calloc(x) ({ size_t _count = (x); void *_result = calloc(_count, 1); if(!_result) { fprintf(stderr, "Out of memory allocating %lu bytes\n", (unsigned long)_count); exit(EXIT_FAILURE); } _result; })
#define check_realloc(p, x) ({ size_t _count = (x); void *_result = realloc((p), x); if(!_result) { fprintf(stderr, "Out of memory reallocating %lu bytes\n", (unsigned long)_count); exit(EXIT_FAILURE); } _result; })
#if ! NDEBUG
#define REQUIRE_NOT_NULL(a) do { \
if ((a)==NULL) {\
fprintf(stderr, "REQUIRE_NOT_NULL failed: NULL value for parameter " #a " on line %d in file %s\n", __LINE__, __FILE__);\
abort();\
}\
} while (0)
#define EXPECT_CLASS(e, c) do { \
if (! [(e) isKindOfClass:[c class]]) {\
fprintf(stderr, "EXPECT_CLASS failed: Expression " #e " is %s on line %d in file %s\n", (e) ? "(nil)" : [[e description] UTF8String], __LINE__, __FILE__);\
abort();\
}\
} while (0)
#else
#define REQUIRE_NOT_NULL(a) USE(a)
#define EXPECT_CLASS(e, c) USE(e)
#endif
#define FOREACH(type, var, exp) for (type var in (exp))
#define NEW_ARRAY(type, name, number) \
type name ## static_ [256];\
type * name = ((number) <= 256 ? name ## static_ : check_malloc((number) * sizeof(type)))
#define FREE_ARRAY(name) \
if (name != name ## static_) free(name)
#if !defined(MIN)
#define MIN(A,B) ({ __typeof__(A) __a = (A); __typeof__(B) __b = (B); __a < __b ? __a : __b; })
#endif
#if !defined(MAX)
#define MAX(A,B) ({ __typeof__(A) __a = (A); __typeof__(B) __b = (B); __a < __b ? __b : __a; })
#endif
//How many bytes should we read at a time when doing a find/replace?
#define SEARCH_CHUNK_SIZE 32768
//What's the smallest clipboard data size we should offer to avoid copying when quitting? This is 5 MB
#define MINIMUM_PASTEBOARD_SIZE_TO_WARN_ABOUT (5UL << 20)
//What's the largest clipboard data size we should support exporting (at all?) This is 500 MB. Note that we can still copy more data than this internally, we just can't put it in, say, TextEdit.
#define MAXIMUM_PASTEBOARD_SIZE_TO_EXPORT (500UL << 20)
// When we save a file, and other byte arrays need to break their dependencies on the file by copying some of its data into memory, what's the max amount we should copy (per byte array)? We currently don't show any progress for this, so this should be a smaller value
#define MAX_MEMORY_TO_USE_FOR_BREAKING_FILE_DEPENDENCIES_ON_SAVE (16 * 1024 * 1024)
#ifdef __OBJC__
#import <HexFiend/HFFunctions.h>
#import "HFFunctions_Private.h"
#endif
#ifndef __has_feature // Optional.
#define __has_feature(x) 0 // Compatibility with non-clang compilers.
#endif
#ifndef NS_RETURNS_RETAINED
#if __has_feature(attribute_ns_returns_retained)
#define NS_RETURNS_RETAINED __attribute__((ns_returns_retained))
#else
#define NS_RETURNS_RETAINED
#endif
#endif

21
HexFiend/License.txt Normal file
View File

@ -0,0 +1,21 @@
Copyright (c) 2005-2009, Peter Ammon
* All rights reserved.
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
*
* * Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* * Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
*
* THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND ANY
* EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
* WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
* DISCLAIMED. IN NO EVENT SHALL THE REGENTS AND CONTRIBUTORS BE LIABLE FOR ANY
* DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
* (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
* LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
* ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
* SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.

View File

@ -17,7 +17,7 @@ OBJ := build/obj
CC := clang CC := clang
CFLAGS += -Werror -Wall -std=gnu11 -ICore -D_GNU_SOURCE -DVERSION="$(VERSION)" CFLAGS += -Werror -Wall -std=gnu11 -ICore -D_GNU_SOURCE -DVERSION="$(VERSION)" -I.
SDL_LDFLAGS := -lSDL SDL_LDFLAGS := -lSDL
LDFLAGS += -lc -lm LDFLAGS += -lc -lm
CONF ?= debug CONF ?= debug
@ -32,7 +32,7 @@ endif
ifeq ($(CONF),debug) ifeq ($(CONF),debug)
CFLAGS += -g CFLAGS += -g
else ifeq ($(CONF), release) else ifeq ($(CONF), release)
CFLAGS += -O3 -flto CFLAGS += -O3 -flto -DNDEBUG
LDFLAGS += -flto LDFLAGS += -flto
else else
$(error Invalid value for CONF: $(CONF). Use "debug" or "release") $(error Invalid value for CONF: $(CONF). Use "debug" or "release")
@ -46,7 +46,7 @@ CORE_SOURCES := $(shell echo Core/*.c)
SDL_SOURCES := $(shell echo SDL/*.c) SDL_SOURCES := $(shell echo SDL/*.c)
ifeq ($(shell uname -s),Darwin) ifeq ($(shell uname -s),Darwin)
COCOA_SOURCES := $(shell echo Cocoa/*.m) COCOA_SOURCES := $(shell echo Cocoa/*.m) $(shell echo HexFiend/*.m)
SDL_SOURCES += $(shell echo SDL/*.m) SDL_SOURCES += $(shell echo SDL/*.m)
endif endif
@ -75,6 +75,11 @@ $(OBJ)/%.c.o: %.c
-@mkdir -p $(dir $@) -@mkdir -p $(dir $@)
$(CC) $(CFLAGS) -c $< -o $@ $(CC) $(CFLAGS) -c $< -o $@
# HexFiend requires more flags
$(OBJ)/HexFiend/%.m.o: HexFiend/%.m
-@mkdir -p $(dir $@)
$(CC) $(CFLAGS) $(OCFLAGS) -c $< -o $@ -fno-objc-arc -include HexFiend/HexFiend_2_Framework_Prefix.pch
$(OBJ)/%.m.o: %.m $(OBJ)/%.m.o: %.m
-@mkdir -p $(dir $@) -@mkdir -p $(dir $@)
$(CC) $(CFLAGS) $(OCFLAGS) -c $< -o $@ $(CC) $(CFLAGS) $(OCFLAGS) -c $< -o $@