Do you need help on a specific subject? Use the contact form (Request a blog entry) on the right hand side.

2016-03-16

How to use the NSOutlineView in Swift | An example.

Using NSOutlineView from Swift is not really difficult when we take some care in designing the data class. If you expect NSOutlineView to be similar to NSTableView the you will quickly discover that NSOutlineView is a whole different beast.

NSTableView works with row/column or row/section identifiers while NSOutlineView works with 'item' identifiers.

Item identifiers are of the type AnyObject. Which is to say that they cannot be String's, Int's or similar things. Structs are out. If we do make the mistake of using -for example- String's then the effects are rather peculiar and the elements shown by the view will be 'all over the place'.

So use real object identifiers. And make sure that they are different for each element that you want to show. Don't use identifiers that are 'static'. (Unless of course you already know exactly what you are doing).

A picture, this is what I want to achieve:



By clicking on a value that value will become editable.

The Domain is implemented as a class and has the following structure:

class Domain {
    var name: String = "domain-name.extension"
    var wwwIncluded: Bool = true
    var root: String = "root-folder"
    var forwardUrl: String = ""
    var enabled: Bool = false
}

Notice that all members are struct's. This is important.

I also used a domains managing class called Domains:

class Domains: SequenceType {
    private var domains: Dictionary<String, Domain> = [:]
    var count: Int {...}
    func contains(domainName: String) -> Bool {...}
    func domainForName(name: String) -> Domain? {...}
    func add(domain: Domain) -> Bool {...}
    func remove(name: String) -> Bool {...}
    func update(name: String, withDomain new: Domain) -> Bool {...}
    func updateWithDomains(domains: Domains) {...}
   
    struct DomainGenerator: GeneratorType {...}
    typealias Generator = DomainGenerator
    func generate() -> Generator {...}
}

The view controller has to implement the following operations from the NSOutlineViewDataSource and NSOutlineViewDelegate protocols:

class MainWindowViewController: NSViewController {
    var domains = Domains()
    @IBOutlet weak var domainNameColumn: NSTableColumn!
    @IBOutlet weak var domainValueColumn: NSTableColumn!
    @IBOutlet weak var domainOutlineView: NSOutlineView!
}

extension MainWindowViewController: NSOutlineViewDataSource, NSOutlineViewDelegate {
    
    func outlineView(outlineView: NSOutlineView, numberOfChildrenOfItem item: AnyObject?) -> Int {...}
    
    func outlineView(outlineView: NSOutlineView, child index: Int, ofItem item: AnyObject?) -> AnyObject {...}

    func outlineView(outlineView: NSOutlineView, isItemExpandable item: AnyObject) -> Bool {...}
    
    // Using "Cell Based" content mode (specify this in IB)
    func outlineView(outlineView: NSOutlineView, objectValueForTableColumn tableColumn: NSTableColumn?, byItem item: AnyObject?) -> AnyObject? {...}
    
    func outlineView(outlineView: NSOutlineView, shouldEditTableColumn tableColumn: NSTableColumn?, item: AnyObject) -> Bool {...}
    
    func outlineView(outlineView: NSOutlineView, setObjectValue object: AnyObject?, forTableColumn tableColumn: NSTableColumn?, byItem item: AnyObject?) {...}
    
}

The first operation outlineView:numberOfChildrenOfItem must return the number of children for the given item. If the item is nil, then it must return the number of top-level items. If the item is non-nil, it must return the number of sub items for that item. Note that the 'item' parameter is only an identifier ('id' in Obj-C parlour) the item itself does not need to implement anything specific.

Given that, the implementation of outlineView:numberOfChildrenOfItem can be simpel:

    func outlineView(outlineView: NSOutlineView, numberOfChildrenOfItem item: AnyObject?) -> Int {
        if item == nil { return domains.count }
        return Domain.nofContainedItems

    }

When 'item' is nil, it returns the number of domains. Since I am aiming to keep the controller free of any specific Domain or Domains knowledge, it is best to define this constant in the Domain class:

class Domain {
    static let nofContainedItems: Int = 4
    var name: String {..}

The next operation outlineView:child:ofItem should return an ITEM IDENTIFIER used in subsequent operations to determine which information should be how displayed or edited. This identifier will be the ITEM in subsequent operations.
There is NO NEED for this item to refer to the actual item that will be displayed!
Note that we should NEVER change the item identifier as long as the corresponding item is visible in the outline view!

I used bold capitals because this is crucial to understanding how the NSOutlineView works. Get this right, and everything else is easy. Frankly, I hope that Apple will change this in future releases and adopt a protocol for this. The current approach makes sense for Obj-C, but it simply makes no sense in Swift.

This is the implementation I used for outlineView:child:ofItem :

    func outlineView(outlineView: NSOutlineView, child index: Int, ofItem item: AnyObject?) -> AnyObject {
        if item == nil {
            for (i, d) in domains.enumerate() {
                if i == index { return d }
            }
            return domains // should never happen
        }
        for d in domains {
            if item === d {
                if let result = d.itemForIndex(index) { return result }
                // should never pass here
            }
        }
        return "Index out of range error"

    }

If the 'item' is nil, the 'index' will give the index of the top level. For me that is simply the Domain class object. This is the reason I made the Domain a class and not a Struct!
If the item is not nil, it will refer to a Domain at the top level. Hence it is looked up in the present Domains and it returns the identifier of a sub-item of the domain. Note that an operation has been implemented on the domain to return the actual value of the identifier. This way we can avoid including knowledge about the internals of the domain inside the controller. The implementation of the d.itemForIndex(index) is as follows:

extension Domain {
    
    func itemForIndex(index: Int) -> AnyObject? {
        switch index {
        case 0: return wwwIncludedItemTitle
        case 1: return enabledItemTitle
        case 2: return rootItemTitle
        case 3: return forwardUrlItemTitle
        default: return nil
        }

    }
}

Note that it makes sense to implement this as an extension because it has nothing to do with the actual data in the domain.
Also note that there is no reference to any of the previously defined members of the domain. Instead new objects have been introduced:

class Domain {
        
    let nameItemTitle: NSString = "Domain"
    let wwwIncludedItemTitle: NSString = "Also map 'www' prefix:"
    let rootItemTitle: NSString = "Root folder:"
    let forwardUrlItemTitle: NSString = "Foreward to URL:"
    let enabledItemTitle: NSString = "Enable Domain:"

The reason for this is that the members themselves can change over the lifetime of the domain  because they are structs, not objects. But the identifier used by the NSOutlineView must remain the same over the lifetime over the GUI elements.

Since we also need some label text to display for each member, we can just as well use the same object for both. Normally you would probably want to make these definitions 'static'. Resist that urge here, remember that the identifier of the sub-items must be different for each domain. Yes, it will take up a little extra storage, but so what? don't start optimizing until its really necessary. Also note that the compiler is really clever, so introducing an in-between object will most likely be optimized away and you will wonder afterwards why the NSOutlineView does not work. So don't make these static!

With this infrastructure in place, the rest becomes easy:

To find out if an item is expandable:

    func outlineView(outlineView: NSOutlineView, isItemExpandable item: AnyObject) -> Bool {
        guard let ditem = item as? Domain else { return false }
        return domains.contains(ditem)
    }

I.e. only the domains them self are expandable, not the sub-items.

The all-important content provider:

    func outlineView(outlineView: NSOutlineView, objectValueForTableColumn tableColumn: NSTableColumn?, byItem item: AnyObject?) -> AnyObject? {
        if tableColumn === domainNameColumn {
            for d in domains {
                if let title = d.titleForItem(item) { return title }
            }
        } else if tableColumn === domainValueColumn {
            for d in domains {
                if item === d { return "" }
                if let value = d.valueForItem(item) { return value }
            }
        }
        return nil
    }

This operation returns the values that are actually displayed in the NSOutlineView. Note that we need to be able to differentiate between columns and do so by having an IBOutlet for each column. This way we can find out if we need to display the label or the actual value.
The d.titleForItem(item) and d.valueForItem(item) operations are also extensions on the Domain class:

    func titleForItem(item: AnyObject?) -> NSString? {
        if item === self { return name as NSString }
        if item === wwwIncludedItemTitle { return wwwIncludedItemTitle }
        if item === enabledItemTitle { return enabledItemTitle }
        if item === rootItemTitle { return rootItemTitle }
        if item === forwardUrlItemTitle { return forwardUrlItemTitle }
        return nil
    }
    
    func valueForItem(item: AnyObject?) -> NSString? {
        if item === self { return name as NSString }
        if item === wwwIncludedItemTitle { return wwwIncluded.description as NSString }
        if item === enabledItemTitle { return enabled.description as NSString }
        if item === rootItemTitle { return root as NSString }
        if item === forwardUrlItemTitle {
            if forwardUrl.isEmpty { return "-" }
            return forwardUrl as NSString
        }
        return nil
    }

By now you should have no problem implementing outlineView:shouldEditTableColumn:item and  outlineView:setObjectValue:forTableColumn:byItem yourself. They follow the same pattern as above.

Conclusion

NSOutlineView is not the easiest class to use, but once the trick with the item identifiers is understood it also does not pose a big problem anymore. The example above also shows how to isolate the know how of the data internals from the GUI. This makes updating the data structures much easier as all changes are made to the data structures locally (or in the extension) and the GUI will automatically follow the data as it evolves.

Happy coding...

Did this help?, then please help out a small independent.
If you decide that you want to make a small donation, you can do so by clicking this
link: a cup of coffee ($2) or use the popup on the right hand side for different amounts.
Payments will be processed by PayPal, receiver will be sales at balancingrock dot nl
Bitcoins will be gladly accepted at: 1GacSREBxPy1yskLMc9de2nofNv2SNdwqH

We don't get the world we wish for... we get the world we pay for.

2016-03-08

If-then in a guard statement

If we need a condition in a guard statement then we can (presumably) use a closure, but that is imo a bit awkward. But there is an alternative: the '?' operator.

Instead of using a  closure with "if {} else {}" construct simply use the () ? () : () operator like this:

guard (type == .OBJECT) ? (c.name != nil) : true else { return false }

The conditional for c.name != nil is  only executed if the  type == .OBJECT evaluates to 'true'.

Neat.

Happy coding...

Did this help?, then please help out a small independent.
If you decide that you want to make a small donation, you can do so by clicking this
link: a cup of coffee ($2) or use the popup on the right hand side for different amounts.
Payments will be processed by PayPal, receiver will be sales at balancingrock dot nl
Bitcoins will be gladly accepted at: 1GacSREBxPy1yskLMc9de2nofNv2SNdwqH

We don't get the world we wish for... we get the world we pay for.

2016-03-04

Swift Code Library: A byte buffer class providing UnsafeBufferPointer access.

The UnsafeBufferPointer is one of the more beautiful pointer types in Swift. It combines the power of a collection to the sleekness of a pointer.

I needed a buffer over which I have to iterate and that I also have to convert into a String for further processing. In addition I need operations to add and remove bytes from it and I don't want use the high level Swift syntax for that. (That way it is easy to incur more overhead than necessary)

Without further ado, here is the code:

class UInt8Buffer {
    
    
    // The buffer area (is allocated during init)
    
    private var area: UnsafeMutablePointer<UInt8>
    
    
    /// The maximum number of bytes that can be contained in this buffer, is fixed during init and cannot be changed afterwards.
    
    let size: Int
    
    
    /// The number of bytes contained in the buffer.
    
    var fill: Int { return _fill }
    private var _fill = 0
    
    
    /// Access to the data in the buffer area.
    /// - Note: Adding or removing data invalidates previously returned buffer pointers.

    var ptr: UnsafeBufferPointer<UInt8> {
        return UnsafeBufferPointer(start: area, count: _fill)
    }
    
    
    /// - Returns: The value of the received data interpreted as an UTF-8 encoded String. Nil if the data could not be converted.
    /// - Note: The returned value is a struct and thus a copy of the data in the buffer.
    
    var stringValue: String {
        return String(bytes: ptr, encoding: NSUTF8StringEncoding) ?? ""
    }
    
    
    /// Creates a new buffer
    
    init(sizeInBytes: Int) {
        size = sizeInBytes
        area = UnsafeMutablePointer<UInt8>.alloc(size)
    }
    
    
    /// Creates a new buffer with the data from the given buffer starting at the byte at startByteOffset and ending with the byte at endByteOffset.
    /// The fill property will be set to the number of bytes. Hence no further data can be added to this buffer without first making room for it.
    
    convenience init(from: UInt8Buffer, startByteOffset start: Int, endByteOffset end: Int) {
        let theSize = end - start + 1
        self.init(sizeInBytes: theSize)
        memcpy(area, from.area + start, theSize)
        _fill = theSize
    }
    
    
    /// Destroys and frees the data area
    
    deinit {
        area.dealloc(size)
    }
    
    
    /// Add the given data to this buffer.
    /// - Note: This operation invalidates any 'ptr' value that was read previously.
    /// - Returns: True when successful, false when not all data could be added (buffer-full)
    
    func add(data: UInt8Buffer) -> Bool {
        guard _fill < size else { return false }
        let nofBytesToCopy = min((size - _fill), data._fill)
        memcpy(area + _fill, data.area, nofBytesToCopy)
        _fill += nofBytesToCopy
        return nofBytesToCopy == data._fill
    }
    
    
    /// Add the given data to this buffer.
    /// - Note: This operation invalidates any 'ptr' value that was read previously.
    /// - Returns: True when successful, false when not all data could be added (buffer-full)

    func add(srcPtr: UnsafePointer<UInt8>, length: Int) -> Bool {
        guard _fill < size else { return false }
        let nofBytesToCopy = min((size - _fill), length)
        memcpy(area+_fill, srcPtr, nofBytesToCopy)
        _fill += nofBytesToCopy
        return nofBytesToCopy == length
    }
    
    
    /// Add the given data to this buffer.
    /// - Note: This operation invalidates any 'ptr' value that was read previously.
    /// - Returns: True when successful, false when not all data could be added (buffer-full)

    func add(buffer: UnsafeBufferPointer<UInt8>) -> Bool {
        return self.add(buffer.baseAddress, length: buffer.count)
    }
    
    
    /// Add the given data to this buffer.
    /// - Note: This operation invalidates any 'ptr' value that was read previously.
    /// - Returns: True when successful, false when not all data could be added (buffer-full)

    func add(data: NSData) -> Bool {
        return self.add(UnsafePointer<UInt8>(data.bytes), length: data.length)
    }
    
    
    /// Removes the indicated number of bytes from the start of the buffer.
    /// - Note: This operation invalidates any 'ptr' value that was read previously.

    func remove(bytes: Int) {
        let nofBytesToRemove = min(bytes, _fill)
        if nofBytesToRemove == _fill { _fill = 0; return }
        let nofBytesToMove = _fill - nofBytesToRemove
        memcpy(area + nofBytesToRemove, area, nofBytesToMove)
    }
    
    
    /// Removes everything.
    /// - Note: This operation invalidates any 'ptr' value that was read previously.
    
    func removeAll() {
        _fill = 0
    }

}

The Unix call 'memcpy' is used to do the actual copying. This is a very efficient way of copying the data around.

Update: 2016-03-14 Fixed a problem on stringValue and did away with an extra zero byte.

Happy coding...

Did this help?, then please help out a small independent.
If you decide that you want to make a small donation, you can do so by clicking this
link: a cup of coffee ($2) or use the popup on the right hand side for different amounts.
Payments will be processed by PayPal, receiver will be sales at balancingrock dot nl
Bitcoins will be gladly accepted at: 1GacSREBxPy1yskLMc9de2nofNv2SNdwqH

We don't get the world we wish for... we get the world we pay for.

2016-03-02

Socket Programming in Swift: Part 9 - SwifterSockets

Updated on 2016-08-12 for Swift 3 Xcode 8 beta 3

I admit it, the name kind-a-sucks. But after having published SwifterJSON, SwifterLog it seems that SwifterSockets is a logical choice. Besides, calling it SocketUtils (used as working name) creates naming conflicts with the thousands of other SocketUtils out there. So SwifterSockets it is.

What is it? It is a collection of APIs that use the Unix socket calls to implement network transfers.

There are few general purpose APIs:

Some socket related helper functions:

enum SocketAddress {...}
func isValidIpAddress(_ address: String) -> Bool
func sockaddrDescription(_ addr: UnsafePointer<sockaddr>) -> (ipAddress: String?, portNumber: String?) { ... }
func fdZero(_ set: inout fd_set) { ... }
func fdSet(_ fd: Int32, set: inout fd_set) { ... }
func fdClr(_ fd: Int32, set: inout fd_set) { ... }
func fdIsSet(_ fd: Int32, set: inout fd_set) -> Bool { ... }
func logAddrInfoIPAddresses(_ infoPtr: UnsafeMutablePointer<addrinfo>) -> String { ... }
func logSocketOptions(_ socket: Int32) -> String { ... }
func closeSocket(_ socket: Int32?) -> Bool?


Then we get to the meat of the matter, initializing a server socket:

func setupServer(
        onPort port: String,
        maxPendingConnectionRequest: Int32) -> SetupServerReturn { ... }
func setupServerOrThrow(
        onPort port: String,
        maxPendingConnectionRequest: Int32) throws -> Int32 { ... }
func setupServerOrThrowAsync(
        onPort port: String,
        maxPendingConnectionRequest: Int32,
        postProcessingQueue: DispatchQueue,
        postProcessor: SetupServerPostProcessing) throws { ... }

Initializing a client socket:

func connectToServer(atAddress address: String, atPort port: String) -> ClientResult { ... }
func connectToServerOrThrow(atAddress address: String, atPort port: String) throws -> Int32 { ... }
func connectToServerOrThrowAsync(
        atAddress address: String,
        atPort port: String,
        onQueue queue: DispatchQueue,
        postProcessor: ClientPostProcessing) throws { ... }
func connectToServerOrThrowTransmitAsync(
        atAddress address: String,
        atPort port: String,
        transmitQueue: DispatchQueue,
        transmitData: String,
        transmitTimeout: TimeInterval,
        transmitTelemetry: TransmitTelemetry?,
        transmitPostProcessor: TransmitPostProcessing?) throws { ... }
func connectToServerOrThrowTransmitAsync(
        atAddress address: String,
        atPort port: String,
        transmitQueue: DispatchQueue,
        transmitData: Data,
        transmitTimeout: TimeInterval,
        transmitTelemetry: TransmitTelemetry?,

        transmitPostProcessor: TransmitPostProcessing?) throws { ... }
func connectToServerOrThrowTransmitAsync(
        atAddress address: String,
        atPort port: String,
        queue: DispatchQueue,
        transmitData: UnsafeBufferPointer<UInt8>,
        transmitTimeout: TimeInterval,
        transmitTelemetry: TransmitTelemetry?,
        transmitPostProcessor: TransmitPostProcessing?) throws { ... }

Setting up a data transfer:

func transmit(
        toSocket socket: Int32,
        fromBuffer buffer: UnsafeBufferPointer<UInt8>,
        timeout: TimeInterval,
        telemetry: TransmitTelemetry?) -> TransmitResult { ... }
func transmit(
        toSocket socket: Int32,
        data: Data,
        timeout: TimeInterval,

        telemetry: TransmitTelemetry?) -> TransmitResult { ... }
func transmit(
        toSocket socket: Int32,
        string: String,
        timeout: TimeInterval,
        telemetry: TransmitTelemetry?) -> TransmitResult { ... }
func transmitOrThrow(
        toSocket socket: Int32,
        fromBuffer buffer: UnsafeBufferPointer<UInt8>,
        timeout: TimeInterval,
        telemetry: TransmitTelemetry?) throws { ... }
func transmitOrThrow(
        toSocket socket: Int32,
        data: Data,
        timeout: TimeInterval,

        telemetry: TransmitTelemetry?) throws { ... }
func transmitOrThrow(
        toSocket socket: Int32,
        string: String,
        timeout: TimeInterval,
        telemetry: TransmitTelemetry?) throws { ... }
func transmitAsync(
        onQueue queue: DispatchQueue,
        toSocket socket: Int32,
        fromBuffer buffer: UnsafeBufferPointer<UInt8>,
        timeout: TimeInterval,
        telemetry: TransmitTelemetry?,
        postProcessor: TransmitPostProcessing?) { ... }
func transmitAsync(
        onQueue queue: DispatchQueue,
        toSocket socket: Int32,
        data: Data,
        timeout: TimeInterval,
        telemetry: TransmitTelemetry?,

        postProcessor: TransmitPostProcessing?) { ... }
func transmitAsync(
        onQueue queue: DispatchQueue,
        toSocket socket: Int32,
        string: String,
        timeout: TimeInterval,
        telemetry: TransmitTelemetry?,
        postProcessor: TransmitPostProcessing?) { ... }

Setting up the receiving end:

func receiveBytes(
        fromSocket socket: Int32,
        intoBuffer buffer: UnsafeMutableBufferPointer<UInt8>,
        timeout: TimeInterval,
        dataEndDetector: DataEndDetector,
        telemetry: ReceiveTelemetry?) -> ReceiveResult { ... }
func receiveData(
        fromSocket socket: Int32,
        timeout: TimeInterval,
        dataEndDetector: DataEndDetector,

        telemetry: ReceiveTelemetry?) -> ReceiveResult { ... }
func receiveString(
        fromSocket socket: Int32,
        timeout: TimeInterval,
        dataEndDetector: DataEndDetector,
        telemetry: ReceiveTelemetry?) -> ReceiveResult { ... }
func receiveBytesOrThrow(
        fromSocket socket: Int32,
        intoBuffer buffer: UnsafeMutableBufferPointer<UInt8>,
        timeout: TimeInterval,
        dataEndDetector: DataEndDetector,
        telemetry: ReceiveTelemetry?) throws -> Int { ... }
func receiveNSDataOrThrow(
        fromSocket socket: Int32,
        timeout: TimeInterval,
        dataEndDetector: DataEndDetector,

        telemetry: ReceiveTelemetry?) throws -> Data { ... }
func receiveStringOrThrow(
        fromSocket socket: Int32,
        timeout: TimeInterval,
        dataEndDetector: DataEndDetector,

        telemetry: ReceiveTelemetry?) throws -> String { ... }
func receiveAsync(
        onQueue queue: DispatchQueue,
        fromSocket socket: Int32,
        timeout: TimeInterval,
        dataEndDetector: DataEndDetector,
        telemetry: ReceiveTelemetry?,
        postProcessor: ReceivePostProcessing?) { ... }

And to accept a connection request:

func acceptNoThrow(
        onSocket socket: Int32,
        abortFlag: inout Bool,
        abortFlagPollInterval: TimeInterval?,
        timeout: TimeInterval? = nil,
        telemetry: AcceptTelemetry? = nil)
        -> AcceptResult { ... }
func acceptOrThrow(
        onSocket socket: Int32,
        abortFlag: inout Bool,
        abortFlagPollInterval: TimeInterval?,
        timeout: TimeInterval? = nil,
        telemetry: AcceptTelemetry?) throws -> Int32 { ... }

Here is how to setup a server that accepts connection requests and processes the received data:

func serverSetup_oldSchool() {
    
    
    // Assume that incoming data ends when a 0x00 byte is received
    
    class DataEndsOnZeroByte: DataEndDetector {
        func endReached(buffer: UnsafeBufferPointer<UInt8>) -> Bool {
            for byte in buffer {
                if byte == 0x00 { return true }
            }
            return false
        }
    }

    
    // Setup a socket for usage as the server socket
    
    let setupResult = SwifterSockets.setupServer(onPort: "80", maxPendingConnectionRequest: 10)
    
    guard case let SwifterSockets.SetupServerReturn.socket(serverSocket) = setupResult else { return }
    
    
    
    // Start the accept loop (ends on accept errors only)
    
    var neverAborts: Bool = false
    
    while true {
        
        
        // Accept a (the next) connection request
        
        let acceptResult = SwifterSockets.acceptNoThrow(onSocket: serverSocket, abortFlag: &neverAborts, abortFlagPollInterval: 10.0, timeout: nil, telemetry: nil)
        
        guard case let SwifterSockets.AcceptResult.accepted(socket: receiveSocket) = acceptResult else { break }
        
        
        // Receive the incoming data
        
        let dataEndsOnZeroByte = DataEndsOnZeroByte()
        
        let receiveResult = SwifterSockets.receiveData(fromSocket: receiveSocket, timeout: 10.0, dataEndDetector: dataEndsOnZeroByte, telemetry: nil)
        
        guard case let SwifterSockets.ReceiveResult.ready(data: receivedData) = receiveResult else { break }
        
        
        // Process the data that was received
        
        processReceivedData(data: receivedData as? Data)
        
        
        // Close the socket
        
        SwifterSockets.closeSocket(receiveSocket)
    }
    
    SwifterSockets.closeSocket(serverSocket)
}

SwifterSockets is available from Github
The project homepage is at Balancingrock

Check out my Port Spy app in the App Store. A utility that helps you debug your socket based application and includes its own source code. So you can see first hand how to implement socket based io in Swift. And you will be helping this blog!

Happy coding...

Did this help?, then please help out a small independent.
If you decide that you want to make a small donation, you can do so by clicking this
link: a cup of coffee ($2) or use the popup on the right hand side for different amounts.
Payments will be processed by PayPal, receiver will be sales at balancingrock dot nl
Bitcoins will be gladly accepted at: 1GacSREBxPy1yskLMc9de2nofNv2SNdwqH

We don't get the world we wish for... we get the world we pay for.