Archive
A better IPhone Header for Smart Mobile Studio
With the introduction of the QTX effects units, we can start to make some interesting components that feel more responsive and alive (for the lack of a better word). If the world of HTML5 apps is about anything – it’s flamboyant and exciting user interfaces which not only rival, but surpass the classical components of native languages.
Since writing your own components is often regarded as a black art, something you really dont want to do which is time consuming and boring – I have decided to show just how little it takes to make a completely new IOS header component for Smart Mobile Studio. The goals for this new header is simple, namely to mimic some of the native effects introduced in iOS 5.x – which in no particular order are:
- When either back or next buttons are set to invisible, they slide out of view
- When a button slides out, the caption follows suit – taking up the newly available space
- When you alter the title of the header, the text fades out before setting the new text, then fades back in
- The title font is not longer weight-bold, but plain and shaded
Sounds very complicated right? Well, under Delphi or C++ builder it would have required more than a fair share of black magic, but under Smart Mobile Studio – which were built for this very purpose, it’s a snap.
Isolating the parts
First, let’s look at what an IOS header consists of. It’s actually a very simple piece of engineering, with only two possible buttons visible at once, a gradient background (and buttons styled to match) and a caption. Depending on your IOS version the title is either centered or left oriented. We are going to opt for a centered caption.
So, what we end up with are just 3 custom controls. That’s all it’s going to take to make our new and exciting IOS header, which will look oh-so-clever in combination with sliding forms whizzing about the place. So let’s get to work.
First, we isolate the button classes:
TQTXBackButton = Class(TW3CustomControl) private FOnVisible: TQTXButtonVisibleEvent; protected procedure setVisible(const aValue:Boolean);reintroduce; public property OnVisibleChange:TQTXButtonVisibleEvent read FOnVisible write FOnVisible; published Property Visible:Boolean read getVisible write setVisible; End; TQTXNextButton = Class(TW3CustomControl) protected procedure setVisible(const aValue:Boolean);reintroduce; End;
Since we want our buttons to be more responsive when they either appear or go-away, we target the visible property on our button components. We also need to inform our toolbar that a button has appeared or disappeared, so we add an event to the mix – simply called OnVisibleChange. This way, we can create a response which deals with re-positioning the title whenever a button is moving.
Next, we need the actual header control. This will be extremely simple since all it’s gonna do is to house 3 child components (two buttons and a label). So that looks like this:
TQTXHeaderBar = Class(TW3CustomControl) private FBackButton: TQTXBackButton; FNextButton: TQTXNextButton; FCaption: TQTXHeaderTitle; Procedure HandleBackButtonVisibleChange(sender:TObject;aVisible:Boolean); protected Procedure Resize;override; Procedure InitializeObject;override; Procedure FinalizeObject;Override; public Property Title:TQTXHeaderTitle read FCaption; Property BackButton:TQTXBackButton read FBackButton; property NextButton:TQTXNextButton read FNextButton; End;
Using the force
With the classes clearly defined, we can start to add some meat to our spanking new component. But first, let’s talk a bit about timing.
When you change the title of what is ultimately a label, you want it execute the change in sequence. First, you want the label to quickly fade out using a smooth effect, then you want the actual text to be altered (invisible at this point), until we quickly fade the label back into view.
This probably sounds very complicated, but it’s really the most simple thing to deal with in this task. In fact, the entire label class is no larger than this:
Procedure TQTXHeaderTitle.SetInheritedCaption(const aValue:String); Begin inherited setCaption(aValue); end; Procedure TQTXHeaderTitle.setCaption(const aValue:String); begin if ObjectReady and TQTXTools.getElementInDOM(Handle) then Begin self.fxZoomOut(0.3, procedure () Begin setInheritedCaption(aValue); self.fxZoomIn(0.3); end); end else inherited setCaption(aValue); end;
You may be wondering why there is a function called “setInheritedCaption” defined. To make a long story short, you can only call inherited methods inside a normal procedure, hence we cant call “inherited setCaption()” to change the caption inside a callback procedure. The only way to solve this is to isolate it outside the setCaption() method.
Now in the setCaption() method we first check if the object is ready to be used, that the handle has become a part of the DOM (document object model). If we omit this, you stand at risk of triggering an effect before the DOM is loaded – and your app wont work. You notice that if things are not ready, we call the ancestor directly, because the text being applied is set from the constructor – so no effect can be applied.
If everything is ready and the component is visible, then we use the QTX effects to zoom the label out in 0.3 seconds. We also provide a callback event (anonymous procedure) which is executed when the effect is done. Here we change the text via setInheritedCaption() and fade the component back in. Easy as apple pie.
Next is the button(s). Like mentioned we wanted an event to let us know whenever a button has it’s visible property changed. The event handler for that presently looks like this:
Procedure TQTXHeaderBar.HandleBackButtonVisibleChange (sender:TObject;aVisible:Boolean); Begin case aVisible of false: Begin FCaption.fxMoveTo(2, (clientHeight div 2) - (FCaption.height div 2), 0.3); end; true: Begin FCaption.fxMoveTo((clientwidth div 2) - (FCaption.width div 2), (clientHeight div 2) - (FCaption.height div 2), 0.3); end; end; end;
So, whenever you alter the visibility of the back-button (button on the left of the header), we either slide the header-label center, or backwards, to occupy the space BackButton used to have.
And lest we forget, here is the code for the actual header. All it does at the moment is to house the other controls, so it’s not that advanced. But it’s a lot more responsive than the default IOS header which ship with Smart Mobile Studio.
Procedure TQTXHeaderBar.InitializeObject; Begin inherited; FBackButton:=TQTXBackButton.Create(self); FBackbutton.InnerHTML:='<b>Back</b>'; FBackbutton.Background.FromColor(clRed); FBackButton.OnVisibleChange:=HandleBackButtonVisibleChange; FNextButton:=TQTXNextButton.Create(self); FCaption:=TQTXHeaderTitle.Create(self); FCaption.Background.FromColor(clRed); end; Procedure TQTXHeaderBar.FinalizeObject; Begin FBackbutton.free; FNextButton.free; FCaption.free; inherited; end; Procedure TQTXHeaderBar.HandleBackButtonVisibleChange (sender:TObject;aVisible:Boolean); Begin case aVisible of false: Begin FCaption.fxMoveTo(2, (clientHeight div 2) - (FCaption.height div 2), 0.3); end; true: Begin FCaption.fxMoveTo((clientwidth div 2) - (FCaption.width div 2), (clientHeight div 2) - (FCaption.height div 2), 0.3); end; end; end; Procedure TQTXHeaderBar.Resize; Begin inherited; FBackbutton.setbounds(2,2,100,24); FNextButton.setBounds((clientwidth-2)-100,2,100,24); if FBackbutton.Visible then FCaption.MoveTo((clientwidth div 2) - (FCaption.width div 2), (clientHeight div 2) - (FCaption.height div 2)) else Begin FCaption.moveto(2, (clientHeight div 2) - (FCaption.height div 2) ); end; end;
Adding some bling
Before we start working on the NextButton (right on the header), let’s have a peek at what we got so far, and let’s add some dummy events to the mix to make sure the effects are working. First, add the following to InitializeComponent:
FCaption.OnClick:=Procedure (sender:TObject) Begin FBackButton.Visible:=not FBackbutton.Visible; end;
So whenever you click on the title-label, this code will toggle the visibility of our back-button. Next, let’s create an instance of our header and add another event, just for fun. So in the initializeObject on your form, create the IOS header like this:
mTemp:=TQTXHeaderBar.Create(display); mTemp.height:=40; mTemp.BackButton.OnClick:=Procedure (sender:TObject) Begin mTemp.Title.Caption:='Testing changes'; w3_callback( procedure () Begin mTemp.title.caption:='And this is cool stuff'; end, 1000); end;
This means that whenever we click the back-button, we alter the caption twice after each other. Now let’s have a peek at what it looks like.
Meeeh.. let’s try that again, and this time with the CSS used by TW3HeaderControl
Final touches
As you probably anticipate already, the Next-Button is more or less a clone of what we did with the first, so I wont cover that. The difference is that the label wont move so much – as resize itself to the new available size.
What we must do however, is to change the ancestor for the button classes to TW3ToolButton (as opposed to TW3CustomControl) – and basically, that’s it! Less than 100 lines of code and you have a fully effect driven clone of the IOS form header.
Now when we navigate our forms the GUI feels so much more alive and responsive. It’s just an optical effect really, but it gives that special “feel” to it which we recognize from native, Objective C components under IOS.
After thought
This example of writing components was extremely simple, but still the topics involved, like using GPU powered CSS effects is right up there with the best of code. Under native Delphi or C++ we would have to code even that, so naturally the amount of source-code we have to write under those languages would be much, much larger.
But that is also the point, namely to point out how simple, elegant and even enjoying it is to write your own HTML5 components. I know it sounds like i’m just basking in my own ideas, but Smart Mobile Studio is the only product which made me feel like I was 15 years old again, hacking away on my Amiga. Nothing is nicer than waking up, grabbing a fresh pot of coffey and sit down with SMS to code something you love working on.
As always, remember to download the Quartex library (which is needed for the effects), this can be found here: https://code.google.com/p/qtxlibrary/ (and check it out into the SMS/Libraries folder).
Here is the full source so far (the finished version will be on QTX tomorrow):
unit qtxheader; //############################################################################# // // Unit: qtxheader.pas // Author: Jon Lennart Aasenden [Cipher Diaz of Quartex] // Company: Jon Lennart Aasenden LTD // Copyright: Copyright Jon Lennart Aasenden, all rights reserved // // About: This unit introduces a replacement for TW3HeaderControl. // It uses CSS3 animation effects to slide and fade header // elements out of view, which makes for a more responsive // and living UI experience. // // // _______ _______ _______ _________ _______ // ( ___ )|\ /|( ___ )( ____ )\__ __/( ____ \|\ /| // | ( ) || ) ( || ( ) || ( )| ) ( | ( \/( \ / ) // | | | || | | || (___) || (____)| | | | (__ \ (_) / // | | | || | | || ___ || __) | | | __) ) _ ( // | | /\| || | | || ( ) || (\ ( | | | ( / ( ) \ // | (_\ \ || (___) || ) ( || ) \ \__ | | | (____/\( / \ ) // (____\/_)(_______)|/ \||/ \__/ )_( (_______/|/ \| // // // //############################################################################# interface uses W3System, w3components, w3graphics, w3ToolButton, w3borders, qtxutils, qtxeffects, qtxlabel; {.$DEFINE USE_ANIMFRAME_SYNC} const CNT_ANIM_DELAY = 0.22; type TQTXButtonVisibleEvent = Procedure (sender:TObject;aVisible:Boolean); (* Isolate commonalities for Back/Next buttons in ancestor class *) TQTXHeaderButton = Class(TW3ToolButton) private FOnVisible: TQTXButtonVisibleEvent; public property OnVisibleChange:TQTXButtonVisibleEvent read FOnVisible write FOnVisible; Protected Procedure setInheritedVisible(const aValue:Boolean); End; (* Back-button, slides to the left out of view *) TQTXBackButton = Class(TQTXHeaderButton) protected procedure setVisible(const aValue:Boolean);reintroduce; published Property Visible:Boolean read getVisible write setVisible; End; (* Next-button, slides to the right out of view *) TQTXNextButton = Class(TQTXHeaderButton) protected procedure setVisible(const aValue:Boolean);reintroduce; published Property Visible:Boolean read getVisible write setVisible; End; (* Header title label, uses fx to change text *) TQTXHeaderTitle = Class(TQTXLabel) private Procedure SetInheritedCaption(const aValue:String); protected procedure setCaption(const aValue:String);override; End; (* Header control, dynamically resizes and positions caption and button based on visibility. Otherwise identical to TW3HeaderControl *) TQTXHeaderBar = Class(TW3CustomControl) private FBackButton: TQTXBackButton; FNextButton: TQTXNextButton; FCaption: TQTXHeaderTitle; FMargin: Integer = 4; FFader: Boolean = false; Procedure HandleBackButtonVisibleChange(sender:TObject;aVisible:Boolean); Procedure HandleNextButtonVisibleChange(sender:TObject;aVisible:Boolean); protected Procedure setMargin(const aValue:Integer); Procedure Resize;override; Procedure InitializeObject;override; Procedure FinalizeObject;Override; public Property FadeTitle:Boolean read FFader write FFader; Property Margin:Integer read FMargin write setMargin; Property Title:TQTXHeaderTitle read FCaption; Property BackButton:TQTXBackButton read FBackButton; property NextButton:TQTXNextButton read FNextButton; End; implementation //############################################################################# // TQTXHeaderButton //############################################################################# (* This method simply exposes access to the inherited version of setVisible. Since inherited method cannot be called from anonymous event-handlers, we expose it here. *) Procedure TQTXHeaderButton.setInheritedVisible(const aValue:Boolean); Begin inherited setVisible(aValue); end; //############################################################################# // TQTXBackButton //############################################################################# procedure TQTXBackButton.setVisible(const aValue:Boolean); var mParent: TQTXHeaderBar; dx: Integer; Begin (* Make sure object is ready and that the button is injected into the DOM *) if ObjectReady and Handle.Ready and TQTXTools.getDocumentReady then Begin (* Make sure parent is valid *) if Parent<>NIL then Begin (* get parent by ref *) mParent:=TQTXHeaderBar(Parent); if aValue<>getVisible then begin case aValue of false: Begin if mParent.ObjectReady and mParent.Handle.Ready then Begin dx:=-Width; {$IFDEF USE_ANIMFRAME_SYNC} w3_requestAnimationFrame( procedure () begin {$ENDIF} self.fxMoveTo(dx,top,CNT_ANIM_DELAY, procedure () begin setInheritedVisible(false); end); {$IFDEF USE_ANIMFRAME_SYNC} end); {$ENDIF} end else setInheritedVisible(false); end; True: Begin setInheritedVisible(true); self.MoveTo(-Width, (mParent.ClientHeight div 2) - self.height div 2); if mParent.ObjectReady and mParent.Handle.Ready then {$IFDEF USE_ANIMFRAME_SYNC} w3_requestAnimationFrame( procedure () {$ENDIF} Begin self.fxMoveTo(mParent.margin, (mParent.ClientHeight div 2) - self.height div 2,CNT_ANIM_DELAY); {$IFDEF USE_ANIMFRAME_SYNC} end); {$ELSE} end; {$ENDIF} end; end; if assigned(OnVisibleChange) and mParent.Handle.Ready then OnVisibleChange(self,aValue); end; end; end else inherited setVisible(aValue); end; //############################################################################# // TQTXNextButton //############################################################################# procedure TQTXNextButton.setVisible(const aValue:Boolean); var dy: Integer; dx: Integer; mParent: TQTXHeaderBar; Begin (* Make sure element is ready and inserted into the DOM *) if ObjectReady and TQTXTools.getDocumentReady and Handle.Ready then Begin (* make sure parent is valid *) if parent<>NIL then begin (* Make sure this represents a change in state *) if aValue<>getVisible then Begin (* cast parent to local variable *) mParent:=TQTXHeaderBar(Parent); case aValue of false: begin (* move button out to the right *) dy:=top; dx:=mParent.Width; {$IFDEF USE_ANIMFRAME_SYNC} w3_requestAnimationFrame( procedure () begin {$ENDIF} self.fxMoveTo(dx,dy,CNT_ANIM_DELAY, procedure () begin setInheritedVisible(false); end); {$IFDEF USE_ANIMFRAME_SYNC} end); {$ENDIF} end; true: begin (* move button in to the left *) setInheritedVisible(true); dy:=top; dx:=(mParent.ClientWidth - mparent.margin) - self.Width; {$IFDEF USE_ANIMFRAME_SYNC} w3_requestAnimationFrame( procedure () begin {$ENDIF} self.fxMoveTo(dx,dy,CNT_ANIM_DELAY); {$IFDEF USE_ANIMFRAME_SYNC} end); {$ENDIF} end; end; if assigned(OnVisibleChange) then OnVisibleChange(self,aValue); end; end; end else inherited setVisible(aValue); end; //############################################################################# // TQTXHeaderTitle //############################################################################# Procedure TQTXHeaderTitle.SetInheritedCaption(const aValue:String); Begin inherited setCaption(aValue); end; Procedure TQTXHeaderTitle.setCaption(const aValue:String); begin (* Make sure we can do this *) if ObjectReady and TQTXTools.getDocumentReady and Handle.Ready then Begin (* Check valid parent *) if Parent<>NIL then Begin (* Use fading at all? *) if TQTXHeaderBar(Parent).FadeTitle then Begin {$IFDEF USE_ANIMFRAME_SYNC} w3_requestAnimationFrame( procedure () begin {$ENDIF} self.fxFadeOut(CNT_ANIM_DELAY, procedure () Begin setInheritedCaption(aValue); self.fxFadeIn(CNT_ANIM_DELAY); end); {$IFDEF USE_ANIMFRAME_SYNC} end); {$ENDIF} end else setInheritedCaption(aValue); end else inherited setCaption(aValue); end else inherited setCaption(aValue); end; //############################################################################# // TQTXHeaderBar //############################################################################# Procedure TQTXHeaderBar.InitializeObject; Begin inherited; StyleClass:='TW3HeaderControl'; FBackButton:=TQTXBackButton.Create(self); FBackButton.setInheritedVisible(false); FBackbutton.styleClass:='TW3ToolButton'; FBackbutton.Caption:='Back'; FBackbutton.Height:=28; FNextButton:=TQTXNextButton.Create(self); FNextButton.setInheritedVisible(false); FNextButton.styleClass:='TW3ToolButton'; FNextButton.Caption:='Next'; FNextButton.height:=28; FCaption:=TQTXHeaderTitle.Create(self); FCaption.Autosize:=False; FCaption.Caption:='Welcome'; //FCaption.handle.style['border']:='1px solid #444444'; //FCaption.handle.style['background-color']:='rgba(255,255,255,0.3)'; (* hook up events when element is injected in the DOM *) TQTXTools.ExecuteOnElementReady(Handle, procedure () Begin (* Use update mechanism, which forces an internal resize when sized flag is set *) beginUpdate; try FBackButton.OnVisibleChange:=HandleBackButtonVisibleChange; FNextButton.OnVisibleChange:=HandleNextButtonVisibleChange; setWasMoved; setWasSized; finally EndUpdate; end; end); end; Procedure TQTXHeaderBar.FinalizeObject; Begin FBackbutton.free; FNextButton.free; FCaption.free; inherited; end; Procedure TQTXHeaderBar.setMargin(const aValue:Integer); Begin if aValue<>FMargin then begin (* If the element is not ready, try again in 100 ms *) if ObjectReady and TQTXTools.getDocumentReady and Handle.Ready then Begin BeginUpdate; FMargin:=TInteger.EnsureRange(aValue,1,MAX_INT); setWasSized; endUpdate; end else w3_callback(procedure () begin setMargin(aValue); end,100); end; end; Procedure TQTXHeaderBar.HandleNextButtonVisibleChange (sender:TObject;aVisible:Boolean); var wd,dx: Integer; Begin case aVisible of false: begin wd:=clientwidth; dec(wd,FMargin); if FBackButton.Visible then dec(wd,FBackButton.width + FMargin); dx:=FMargin; if FBackButton.visible then inc(dx,FBackButton.Width + FMargin); if ObjectReady and Handle.Ready then Begin wd:=wd - FMargin; {$IFDEF USE_ANIMFRAME_SYNC} w3_requestAnimationFrame( procedure () begin {$ENDIF} FCaption.fxScaleTo(dx, (clientHeight div 2) - FCaption.Height div 2, wd, FCaption.height, CNT_ANIM_DELAY, NIL); {$IFDEF USE_ANIMFRAME_SYNC} end); {$ENDIF} end; end; true: Begin dx:=FMargin; if FBackButton.visible then inc(dx,FBackButton.Width + FMargin); wd:=ClientWidth - (2 * FMargin); if FBackButton.Visible then dec(wd,FBackButton.width); dec(wd,FNextButton.Width); dec(wd,FMargin * 2); {$IFDEF USE_ANIMFRAME_SYNC} w3_requestAnimationFrame( procedure () begin {$ENDIF} FCaption.fxSizeTo(wd,FCaption.Height,CNT_ANIM_DELAY, procedure () Begin FCaption.fxMoveTo(dx, (clientHeight div 2) - FCaption.Height div 2, CNT_ANIM_DELAY); end); {$IFDEF USE_ANIMFRAME_SYNC} end); {$ENDIF} end; end; Resize; end; Procedure TQTXHeaderBar.HandleBackButtonVisibleChange (sender:TObject;aVisible:Boolean); var dx: Integer; wd: Integer; Begin case aVisible of false: begin {$IFDEF USE_ANIMFRAME_SYNC} w3_requestAnimationFrame( procedure () begin {$ENDIF} FBackButton.fxMoveTo(-FBackButton.width, (clientheight div 2) - FBackButton.height div 2, CNT_ANIM_DELAY); {$IFDEF USE_ANIMFRAME_SYNC} end); {$ENDIF} end; true: Begin {$IFDEF USE_ANIMFRAME_SYNC} w3_requestAnimationFrame( procedure () begin {$ENDIF} FBackButton.fxMoveTo(FMargin, (clientheight div 2) - FBackButton.height div 2, CNT_ANIM_DELAY); {$IFDEF USE_ANIMFRAME_SYNC} end); {$ENDIF} end; end; case aVisible of false: Begin wd:=ClientWidth - (FMargin * 2); if FNextButton.Visible then Begin dec(wd,FNextButton.Width); dec(wd,FMargin); end; {$IFDEF USE_ANIMFRAME_SYNC} w3_requestAnimationFrame( procedure () begin {$ENDIF} FCaption.fxScaleTo(Fmargin, (clientHeight div 2) - (FCaption.height div 2), wd,FCaption.Height,CNT_ANIM_DELAY,NIL); {$IFDEF USE_ANIMFRAME_SYNC} end); {$ENDIF} end; true: Begin dx:=FMargin + BackButton.Width + FMargin; wd:=ClientWidth - (FMargin * 2); dec(wd,FBackButton.width); if FNextButton.visible then Begin dec(wd,FNextButton.Width); dec(wd,FMargin * 2); end else dec(wd,FMargin); {$IFDEF USE_ANIMFRAME_SYNC} w3_requestAnimationFrame( procedure () begin {$ENDIF} FCaption.fxScaleTo(dx, (clientHeight div 2) - (FCaption.height div 2), wd,FCaption.Height, CNT_ANIM_DELAY, NIL); {$IFDEF USE_ANIMFRAME_SYNC} end); {$ENDIF} end; end; end; Procedure TQTXHeaderBar.Resize; var dx: Integer; wd: Integer; Begin inherited; if FBackbutton.visible then FBackbutton.setbounds(FMargin, (clientheight div 2) - FBackButton.height div 2, FBackButton.width, FBackbutton.height); if FNextButton.visible then FNextButton.setBounds((clientwidth-FMargin)-FNextButton.width, (clientHeight div 2) - FNextButton.height div 2, FNextButton.width, FNextButton.Height); dx:=FMargin; if FBackButton.visible then inc(dx,FBackButton.Width + FMargin); wd:=ClientWidth - FMargin; if FBackButton.visible then dec(wd,FBackButton.Width + FMargin); if FNextButton.visible then begin dec(wd,FNextButton.width + FMargin); dec(wd,FMargin); end else dec(wd,FMargin); FCaption.SetBounds(dx, (clientHeight div 2) - (FCaption.height div 2), wd,FCaption.Height); end; end.
Smart ExplodeStr() implementation
In PHP there is a neat function called Explode() which takes a string and breaks it down into words, returning an array of strings. This is handy when displaying text and working out word-wrap functionality, so I wrote a Smart Pascal variation of the Explode() function. Simple but handy:
function ExplodeStr(const aText:String; const aRetain:Boolean; var aWords:Array of String):Integer; const CNT_INVALID=' ;.,-:&/()*^!' + #13 + #10; var x: Integer; mCache: String; mLen: Integer; begin aWords.Clear; result:=0; mLen:=Length(aText); if mLen>0 then begin x:=0; repeat inc(x); if pos(aText[x],CNT_INVALID)<1 then mCache:=mCache + aText[x] else begin if aRetain then mCache:=trim( mCache + aText[x] ); if length(mCache)>0 then begin aWords.add(mCache); setLength(mCache,0); end; end; until x>mLen; result:=aWords.count; end; end;
The retain parameter simply decides if the break character should be included or not. If you want “clean” words then set this to false.
In short, if you do this:
ExplodeStr('this.is.a.test',false,mData);
The mData array contains the following:
mData[0] = this mData[1] = is mData[2] = a mData[3] = test
But if you retain the characters (which you really have to when measuring width/height of a word) then you get this:
mData[0] = this. mData[1] = is. mData[2] = a. mData[3] = test
Using the crypt unit for something useful
Right, I posted the full crypto unit yesterday, but I didn’t have time to post an example of how to use it. So this time we are going to have some real-life practical examples.
How does this key stuff work?
We are all used to passwords and security these days, but there are a few things that have changed since the golden days of home-made services and DIY websites. Most notably is the fact that you should never store passwords at all. If you think back 5-8 years ago, it would have been perfectly valid for a webservice or a database driven website to store usernames and passwords in a database table. But should someone gain access to your database, they could in fact rob your database of passwords and ruin everything.
The solution? Dont store passwords. You store a hash of a password. A hash is just a number that is generated from analysing a piece of data, it’s sometimes also called a checksum. The important part about a hash is that it will generate the same number exclusively when the data matches. As you can imagine, it’s almost impossible to guess a password from a hash-number.
Another thing to note about key-pairs is that they are not designed to encrypt large sections of data. They are designed to encrypt small things (passwords) and have a limit ranging from 256 to 512 bytes – meaning somthing like 128 unicode characters. Encryption of files and streams is done by generating a hash from an encrypted password – and using that hash as the key for a common cipher like RC4 or Blowfish. These extra steps makes it more or less impossible to decode your data without the absolute and concrete original information.
Here is one way of generating a a portable, encrypted file:
- Generate a hash from a password
- Generate a cipher key-set
- Encrypt the hash using the public key
- Save the encrypted hash to the target-stream
- Save the public key to the target-stream
- Encrypt the password-hash using the private key
- Use encrypted hash as a key-stream for encrypting the “real” data
- Save encrypted data to the target-stream
This creates a dual-key lock. You need both the original private-key and password to decipher the data. Even if you steal the original key-set or gain the original password, the data remains useless without both. By de-coupling these two factors and only providing the public key, another person can only verify that the file belongs to you (by first generating a hash of your password, then decrypting the provided hash using the public key and comparing it), but you still cant read the content of such a file without the private key. This is more or less how secure document transportation works, where you can checkout a document anywhere in the world with a smart-card and personal password. The private key is stored on the card and without both items the data remains unreadable.
How you chose to play around with these schemes is up to you. In some cases rolling your own security is better than buying a solution, because the less that is known about a scheme – the harder it will be to crack.
Generating keys
To generate a key-pair you would write:
procedure TForm1.Button3Click(Sender: TObject); var mPrivate: TStream; mPublic: TStream; mTemp: String; begin if TCryptoAPI.rsaMakeKeys(mprivate,mPublic) then begin try if TCryptoAPI.rsaKeyToBase64(mPrivate,mTemp) then showmessage(mTemp); if TCryptoAPI.rsaKeyToBase64(mPublic,mTemp) then showmessage(mTemp); finally mPrivate.Free; mPublic.Free; end; end; end;
I included two helper functions for turning the streams into Base64 encoded strings, simply called rsaKeyToBase64() and rsaBase64ToKey(), its is sometimes easier to transport the keys as text (and also to display the results while working).
Encrypting a stream
procedure TForm1.Button2Click(Sender: TObject); var mKey: TStringStream; mText: String; mSrc: TStringStream; mdst: TStringStream; begin //Replace mSrc with your filestream mSrc:=TStringStream.Create; try mDst:=TStringStream.Create; try if TCryptoAPI.rsaBase64ToKey("Base64 key text here",TStream(mKey)) then begin try //Remove this obviously when encrypting a file or a memory stream mSrc.WriteString("Text/data to encrypt here"); mSrc.Position:=0; if not TCryptoAPI.rsaCryptStream(mKey,mSrc,mDst) then RaiseLastOSError; finally mKey.Free; end; end; finally mDst.Free; end; finally mSrc.Free; end; end;
To decrypt a stream, do the same as above but call TCryptoAPI.rsaDecryptStream() instead.
Voila! Next we will implement the code for encrypting passwords with the crypto-key-set!
Arrrgh….
Im gonna fix this certificate thing once and for all. Porting this crap to Delphi ASAP!
// Sign void Sign(wchar_t * SignerName, wchar_t * DataFileName, wchar_t * SignatureFileName) { // Variables HCERTSTORE hStoreHandle = NULL; PCCERT_CONTEXT pSignerCert = NULL; HCRYPTPROV hCryptProv = NULL; DWORD dwKeySpec = 0; HCRYPTHASH hHash = NULL; HANDLE hDataFile = NULL; BOOL bResult = FALSE; BYTE rgbFile[BUFSIZE]; DWORD cbRead = 0; DWORD dwSigLen = 0; BYTE * pbSignature = NULL; HANDLE hSignatureFile = NULL; DWORD lpNumberOfBytesWritten = 0; wprintf(L"SIGNING\n\n"); // Open the certificate store. hStoreHandle = CertOpenStore( CERT_STORE_PROV_SYSTEM, 0, NULL, CERT_SYSTEM_STORE_CURRENT_USER, CERT_PERSONAL_STORE_NAME ); CheckError((BOOL)hStoreHandle, L"CertOpenStore....................... "); // Get signer's certificate with access to private key. do { // Get a certificate that matches the search criteria pSignerCert = CertFindCertificateInStore( hStoreHandle, MY_TYPE, 0, CERT_FIND_SUBJECT_STR, SignerName, pSignerCert ); CheckError((BOOL)pSignerCert, L"CertFindCertificateInStore.......... "); // Get the CSP, and check if we can sign with the private key bResult = CryptAcquireCertificatePrivateKey( pSignerCert, 0, NULL, &hCryptProv, &dwKeySpec, NULL ); CheckError(bResult, L"CryptAcquireCertificatePrivateKey... "); } while ((dwKeySpec & AT_SIGNATURE) != AT_SIGNATURE); // Create the hash object. bResult = CryptCreateHash( hCryptProv, CALG_MD5, 0, 0, &hHash ); CheckError(bResult, L"CryptCreateHash..................... "); // Open the file with the content to be signed hDataFile = CreateFileW(DataFileName, GENERIC_READ, FILE_SHARE_READ, NULL, OPEN_EXISTING, FILE_FLAG_SEQUENTIAL_SCAN, NULL ); CheckError((hDataFile != INVALID_HANDLE_VALUE), L"CreateFile.......................... "); // Compute the cryptographic hash of the data. while (bResult = ReadFile(hDataFile, rgbFile, BUFSIZE, &cbRead, NULL)) { if (cbRead == 0) { break; } CheckError(bResult, L"ReadFile............................ "); bResult = CryptHashData( hHash, rgbFile, cbRead, 0 ); CheckError(bResult, L"CryptHashData....................... "); } CheckError(bResult, L"ReadFile............................ "); // Sign the hash object dwSigLen = 0; bResult = CryptSignHash( hHash, AT_SIGNATURE, NULL, 0, NULL, &dwSigLen ); CheckError(bResult, L"CryptSignHash....................... "); pbSignature = (BYTE *)malloc(dwSigLen); CheckError((BOOL)pbSignature, L"malloc.............................. "); bResult = CryptSignHash( hHash, AT_SIGNATURE, NULL, 0, pbSignature, &dwSigLen ); CheckError(bResult, L"CryptSignHash....................... "); // Create a file to save the signature hSignatureFile = CreateFileW( SignatureFileName, GENERIC_WRITE, 0, NULL, CREATE_ALWAYS, FILE_ATTRIBUTE_NORMAL, NULL ); CheckError((hSignatureFile != INVALID_HANDLE_VALUE), L"CreateFile.......................... "); // Write the signature to the file bResult = WriteFile( hSignatureFile, (LPCVOID)pbSignature, dwSigLen, &lpNumberOfBytesWritten, NULL ); CheckError(bResult, L"WriteFile........................... "); // Clean up and free memory. free(pbSignature); CloseHandle(hDataFile); CloseHandle(hSignatureFile); bResult = CryptDestroyHash(hHash); CheckError(bResult, L"CryptDestroyHash.................... "); bResult = CertFreeCertificateContext(pSignerCert); CheckError(bResult, L"CertFreeCertificateContext.......... "); bResult = CertCloseStore( hStoreHandle, CERT_CLOSE_STORE_CHECK_FLAG ); CheckError(bResult, L"CertCloseStore...................... "); } // End of Sign
Leaving Optimale Systemer
As of today I am no longer employed by Optimale Systemer AS. After much thinking I decided it was time to leave, although leaving Smart Mobile Studio behind was the hardest thing I have ever done. It was my baby and something I loved working on. I will sorely miss everyone at the company and everyone on the OP4JS council, but I feel my work is done. I set out to bring object pascal to the new world of HTML5 – and despite all the “it can’t be done” bullshit, well.. me and Eric did it. An especially long nose (with assorted french ornaments from Ludvig the fifth) to Embarcadero.
This really was your job Embarcadero. I cant believe you people are actually selling html5 builder and getting away with it. I know there is a lot of work running a company with your customer base – but not delivering a Delphi to html5 compiler in all these years, and with 10000 times our resources is just.. well plain ridicules if you ask me.
On with the show
Today was my first day at NextSys AS, a dedicated Delphi and object pascal company situated in Lier, outside of Oslo, Norway. Our flagship product is NextSys Dental Office which is being used by the national health service to manage every aspect of insurance and dental plans.
It’s been a very cool day over at NextSys. We visited the local officials house for a meeting, which incidentally took place in my old school (Næringsakademiet, Tønsberg). This was the place where I used to play around with Interbase/dBase waaaay back in 1997. So imagine the deja vu when we started the meeting with… firebird (!)
The same building, the same (well, sort of) software, but still awesome!
Also got my taste of the latest Delphi; although I did spend an hour checking if we could bridge firebird to Smart Mobile Studio with a little binary help. I’ll get back to you on that one 😉
Last words: If you want extraordinary stuff, you need extraordinary people. If you want ordinary solutions to everyday problems, get someone else. But to parallel a bad movie – once Achilles have taken the beach – it’s up to the rest to build on it. I’m thankful for my role in bringing object pascal to the browser and html5 – now it’s up to you to make it stay there.
If you build it, they will come 😉
Delphi days, damn!
I’ve had some contact with detlef, the editor and producer of Blaise Pascal Magazine lately. He really is an icon in the pascal community and holds a passion for our beloved language that is hard to match. If you havent checked out the magazine, make sure you visit the website (link above) and sign up.
During our last Skype session detlef mentioned the upcoming “Delphi tage” (or Delphi days) in germany and also the upcomming delphi meetup in the netherlands. I really, really wanted to go there – especially to meet primoz and detlef – and also to meet some of the guys behind freepascal and Lazarus.
Sadly, the deadline for the next smart mobile update in combination to my boss moving to spain for a year, leaves little room for meetings. Hopefully my boss will make it to one of these meetings to represent smart mobile studio, but I will have to stand guard at the temple of Delphi (so to speak).
Also, I have decided to go solo with a few component packages I’ve made. I normally dont do this – but the license manager (and a lot more cool stuff) wont help anyone if it collects dust on my drive. The components were actually written about 10 years ago – and I have used them in countless products. It’s a good practical solution written in vanilla Delphi (so no assembler, no generics or platform bound code) that is tried and tested by time. So I figured I could put them on the marked where they will help other Delphi developers. As far as I know, this will be the only package (with the exception of Turbopower’s older system) that deals with serial numbers and trial software, written in Delphi only with no external libraries. C#, C++ and even Visual Basic have hundreds of solutions for this – but Delphi havent had a good set of components since TurboPower was in business, Which is around 10 years ago (?). So I’ll put some weekend work into changing that.
In the first version (way back then) I checked for debuggers like soft-ice, rootkit’s etc., but those were the old days and that code was removed because it quite frankly was in the way. It’s not about creating a hack-proof system, that is impossible (windows 8 was cracked in less than 20 minutes?), it’s about trial periods, serial number management and giving the user an incentive to buying the full product.
Well.. I’m off for dinner at a friend’s house, fresh sea-food and beer 🙂 And maybe a game of battlefield to spice up the weekend!
But damn… I really, really wanted to go to the Netherlands and Germany 😦
What a weeked
This was a kick ass weekend. Finally had time to focus completely on my kids. So on friday we were all relaxing on the couch watching “Yogi bear” on blue ray first, then we pigged out on ice-cream + Thai food at a local diner. My wife went off to her kickboxing tournament (and came back quite bruised but happy sunday at around midnight) – so it was just me and the kids.
Saturday we visited the annual medieval and viking festival here in Tønsberg. We decided to drop the outfits at the last moment due to talks of rain, but the weather did hold. The kids had a blast, with my son Niklas testing out his bow and arrow, and Sunniva went berserk with her wooden sword and sadly knocked out a few kids — but mostly herself. After a few apologies and dad accidentally “losing” the wooden sword during lunch, everything calmed down and we watched the ritual slaying of a paper dragon, the kings men and his troops — and then an all out viking music and dance festival.
Sunday was we took the boat out to a local island (Bolærne) where the kids could play pirates, and we all did some barbecuing and let the kids run amok on apple juice. We fished for crabfish, shellfish and god knows what and had a fantastic day 🙂 We also inspected the old army depo in the middle of the mountain (about 10 minutes walk directly into the heart of the bedrock) which was pretty cool.
Needless to say, with my wife out fighting I was exhausted by the time she came back – but exhausted in a good way. Hm.. every time I go out fighting she get’s mad. Women, go figure.. 😛
God I love Norway, there is drama and action woven into the fabric of things — and there is never a dull moment if you got the spirit for it 🙂
A new designer, a new office
Ok. This is has been a very exciting week, but also a very strange week. I feel a bit like the guy in “a hitchhiker’s guide to the galaxy”, that was just going down to the pub to grab a few pints – only to find himself in an alien landscape where things go topsy-turvy.
First, I got so sick of my basement office that I decided to give it to the kids – which are now painting it in all possible colors and filling it up with lego. My new home office is in a room upstairs which is much bigger and has actual sunlight. So the days when i’m working from home, i dont have to feel like i’m working on cracking the enigma machine in a british bunker somewhere near france.
Secondly, I’ve drunk far too much coca-cola, so i have to seriously get my ass back into the gym!
And third, and perhaps more important: I hate the designer in smart mobile. I never got to spend enough time on it, and having written a couple of apps with it — i lay myself down flat: it really sucks. But thankfully the Delphi community is a proverbial shangrila of cool people, so i contacted Greatis Software and they were kind enough to mail me a full version of their latest form designer. So far the tests look good (they previously hooked the window messages without any filter, so several instances of a designer in one app crashes)! Everything seems to work – and I will now test it with frames to make sure we can use it in Smart.
And yes, we are busy adding the “missing bits” to the editor as well. Things like mouse-over hints, ctrl + click lookup and the lines will hopefully (i cant promise anything) make it into the hotfix before the holiday.
But that’s not all the news, oh no — while i’m busy strangling the python at delphi – eric, primoz and andre is kicking persian butt with awesome stuff.. that I cant talk about. Since you already know about webGL support I can mention that, but if you think we’re backing down now — think again 😉
Let’s give em hell
Comparing smart to other alternatives
If you look at smart mobile from the context of Delphi, comparing it directly with Delphi or expecting it to be a re-engineered version of a product that (let’s be honest) has 15 years head start with countless senior architects working on it, then smart will naturally never live up to your expectations.
You have to look at it in context to web development and the tools common to JavaScript developers, and also similar tools available for other programming languages (like visual basic for android). The tools we set out to “beat” in terms of code were:
A fair comparison
First, none of these products (with the exception of monkeycoder) can offer you classes, inheritance, interfaces, virtual and abstract methods or anything even close to such functionality. You have to make do with java script, prototype chaining and a lot of manual labour to create an iPhone app.
Secondly, of these products only dreamweaver and sencha have designers that can render the html “live” (the preview function in smart is actually quite similar to what you find in dreamweaver, except that we don’t open the preview in a separate page).
Monkeycoder
Monkeycoder which is a new version of blitzbasic, have no design features at all. The IDE is basically notepad with a few adaptations. Yet it enjoys quite a large following due to the quality of the code it generates. But the system is very small and is best compared to old turbo pascal in terms of what you get out of the box.
Apple Dashcode
Apple dashcode is an excellent system, both in terms of design, events via the designer and editor functions. But it’s still a mess to work with and JavaScript only. It will be utterly alien to anycome comming purely from Delphi and also boxes you into apple standards forever.
Adobe Dreamweaver
Dreamweaver is a multi million dollar application with at least 10 years of constant development behind it. It is at present being touted as a “mobile web studio”, but that statement is cosmetic and carries little substance behind it. This is for designers more than it is programmers (and it is by far the best designer out there in my view).
Aptana studio
Aptana web studio is source-code only and more or less an html editor with syntax highlighting (but with a very cool plugin system) and auto correction.
Sencha designer
Sencha designer is a bit like Delphi, but the system is pure javascript and have no classes or wrappers except for the visual elements.
Taking a closer look
When you start to compare what these systems deliver, I think most developers will agree that what we have done with smart mobile studio is quite an achievement. We might not have all the “bling” down yet, but we are more interested in rock solid classes than we are superficial pixels. We could have cut development time by 50% had we deviated from the first principle of “Delphi only” components. But part of the motivation was to prove that Delphi still have some life in it and can be used to knock out unique and powerful solutions.
With smart mobile you can knock out a pretty complex web app in hours rather than days, and days rather than weeks. It does require you to write your own event handlers (just like you would under JavaScript, c# monotouch, freepascal and c++) and looking at the RTL units to familiarize with the new HTML5 based component library is a primer. But I’ll set smart mobile against the wast majority of HTML5 “web app makers” out there – any day of the week.
IE9, not as bad as i thought
Im playing around with the IE9 driver for Smart Mobile Studio right now. Like most people I’ve sort of dreaded this task, since IE have so many peculiarities associated with it – but you know what? It’s actually not as bad as I thought it would be. I know IE has a bad rap around the world for being the worst browser ever, but starting with IE 8 things have improved considerably.
I actually got most smart apps and demos running on IE9 now (having changed very little). The driver system got a new function, “getEffectCaps” which returns a bitmask for the current browser capabilities, which means that things like the “form sliding” will just be skipped if the driver finds that these effects are not supported.
What IE9 (or indeed, any browser) support boils down to – is basically:
- CSS pre and post fixing
- The use of special effects
Our javascript is universal to begin with and will run on pretty much everything (I even tested it on Playstation, works like a charm). So the challenge is pushed into the outer rim of the VJL, the classes and methods that use CSS transformations and “special effects”, rather than the core of the product itself.
The dark side of special effects
Effects is sort of a double-edged sword. You want to use it to show off the product and what it can do, but the more you use it – the more you bind the RTL to these effects, which means larger and more time-consuming updates for very little gain (for the end-user). I think it’s important that the VJL provides a solid foundation for the user – but effects should not come at the expense of the customer. Instead of drowning the customer with effects that, when it really count, wont make the customer’s code any better (it will just look better but make things more complex), I like to do things the old fashion way. By old fashion I mean steady and robust. I would rather have a system that can take a punch than a “Barbie doll” that looks good but is useless in real-world scenarios.
So, in short: I will make sure the foundation classes for special effects are up to date, but I wont add everything under the sun just to please a small group of people. Instead, I’m more interested in making the VJL as lightweight as possible and to make each visual control as feature rich as it can be.
I can hardly wait until I can add the delegate system. It’s going to be the frosting on the cake 🙂
Exhausted, but happy
Man what a week! You know how things build up when you have a deadline – and just at the very end things you forgotten about come back to bite you in the ass? Well this was no different. But after 20+ hours straight I got it sorted. We have had good sales with smart mobile already, without any advertising except word of mouth – so I am extremely happy. If we keep this up we can get another guy working on it and double the amount of units and updates the customers get within the calculated timeframe. And it’s going to be awesome. With any luck, we will be the first to implement metro style apps on a large-scale outside of native languages, bringing the onslaught of the VJL 1.1 with us – absorbing all the fancy new API stuff that microsoft have to offer.
And since metro allows you to call native functions .. you can link to your delphi code as well. You can use smart as a ramp, or blend the two (where smart is the online version, and the delphi dll’s are used offline). Microsoft and metro is growing on me, I must admit. After 2 years with Apple I am homesick for Windows perhaps?
Use the force luke
Which has gotten me thinking. If I by some strange accident grow 4 hands, I’ll re-implement phonegap as a C# project, and kill two birds (android and iphone native) with one stone. Perhaps even 3 since C# is the prefered language for metro apps.
I know.. I know, the fact that I actually enjoy C# side by side with object pascal these days makes me a pervert, but Miguel de Icaza did it so well. And he is a really cool guy (don’t worry, I’m an object pascal fundamentalist to the bitter end). I’ve only chatted with him once but he really is a nice dude and his work is outstanding. I’m allergic to microsoft’s vision of C# and the CLR but Miguel brings the “magic” of the open-source hacker scene into the equation – and that resonates with me. You either have that creative, intense energy or you don’t. There is no point trying to explain it in rational terms because there is nothing rational about it. You must create. It’s a part of you.
Commander of the northern legions
Also, I’ve started on the command line compiler for Smart Mobile. I have had a prototype running for a while now, which (drumroll) also works fine on the mac. It’s going to be even cooler when I isolate the RTL files in a single zip. This really is a feature delphi should have had years ago. By housing the RTL in a zip, we can have many different RTL’s without much fuzz. For instance the metro system will require a fair bit of re-design in the forms department – but the user wont have to worry about it. And in the future, should an old project surface — just pick the right RTL zip to compile with, and your are home free. Also a lot easier to use the compiler anywhere when it’s just 2 files. A zip for the RTL and the command line compiler. On the mac it’s even simpler – because I stuffed the RTL into the app folder – so it’s just a single file.
I’m also very impressed by Andre Mussche’s work. He took on smart mobile from the get-go and did things few people imagined it could do. Huge fan of this guy. I’m so looking forward to checking out his remobjects to smart mapper. I love remobjects remoting system and use it as much as possible. Connecting remobjects to smart is the perfect match. Unprecedented server control meets equally limitless client control. It’s gonna make the native javascript solutions look like lego in comparison.
Well, better fire up the grill. We have finally finished the 17 of may celebrations (liberation day), with parades for the children and an unholy amount of sugar. So I’m looking forward to kick back with a good steak, an ice-cold Guinness and my wonderful wife and children. I can hardly remember the last time I had a day off so this is going to be great (apart from the slight cold I got yesterday).
Oh and I finally reached level 36 in Battlefield (yeej!). I find that games is an excellent way to exercise the problem solving muscles of the brain. It might look primitive but it’s great fun and requires some intuition. When you face 8 level 50 gamers by yourself – you really have to use every trick in the book to survive. You either evolve some interesting improvisations (if you have the patience to succeed) or you give up. I never give up, and always win in the end. Good game and good fun!
Finally, it is done
Let me paint a mental picture for you: working from you wake up until you fall asleep, for 12 months straight. Driven completely by the will to reach a goal. That has been my life for the past year.
Think big brother is rough? Try working from home during a Norwegian winter. It takes a fair bit of discipline and stubbornness to actually poll through.
Today we finally finished version 1.0 of smart mobile studio, and I can (after a year in exile, or so it feels) dismantle my home office and return to normal hours. I never thought I would see the day when I was looking forward to spending time in the office, but after a whole year working from home, with only random status meetings, it’s going to feel so good getting up in the morning, working out – and then heading for work. Right now my sleep cycle is in sync with australia I think (in other words: topsy turvy). That my wife havent killed me in my sleep during this period of time is beyond me, the woman is a saint.
But one thing you can bet your life on – I keep my promises. I promised it could be done if we decided on a goal – and I have done it. Not by myself, not because I am special, but because the Delphi community have all the resources it needs to rock the world right under it’s very nose.
Now I’m going to sleep for 4 days
Is the world of google any better?
Lately I have been pondering if the online apps of google really has made the world any better. Dont get me wrong, google is by far the #1 driving force behind moving from native, local applications – to global, browser-based applications. But while their ideas and concepts are brilliant – their minimalistic vision of user interface design leaves a lot to be desired. As does their lack of respect for people’s privacy and how they make their money by boxing people in.
Let’s take the design problem first: which is probably best expressed under Gmail. Where good old outlook express displayed your messages in a listview (express) or tree node (office) structure, making it very easy to see the exact chain of correspondence. This simple interaction between the reader and the content Gmail turns into a complete flurry of text. I often find myself replying not to the latest email, but to an older message by mistake, because it’s not easy to tell the header from the preview line. And yes, I know you can change themes but the palette they have used are either high contrast (which gives me a spiking headache after some hours) or soft crayons (which is just as evil).
When Google and Apple started to take a bite out of the world-wide web I was initially very optimistic. Microsoft had ruled that gig for so long that it was about time that someone new and fresh got into the game – but now I’m starting to question the development of the past 8 years. Is really GMail better than outlook? I mean the news reader in outlook was fantastic and so was the email layout. And also, is really my super-hyped, juiced up mac any better than Windows 7? Was it worth the money? Or is my iPhone really any better to call with than my old Nokia? Have you ever had your iPhone with you while jogging? If your hands are sweaty and someone calls, you can’t even unlock it – because the touchscreen doesn’t register your fingers due to moisture. And at home I can hardly get any reception in my basement office. With my old Nokia I could get a crystal clear reception in the caves of moria if I so desired.
Facts of life vs. bling
When Microsoft created Windows XP, they spent millions of dollars doing design research. They took into account every factor, things like color theorem (which colors complement each other, and what effect they have on the viewer’s mind), proportional layout – and they also took into account how a user interface would appear to people who are color blind, have poor eyesight and so on. But the most important factor was the long-term effect of working with the user interface. How well does the interface work after 8 hours? If the colors are slightly off, you can get anything from a headache to fatigue.
What google have done is to take all these factors and thrown them out of the window. But they have forgotten that these design rules were not invented by Microsoft. They are the laws of nature. The reason we have a color theorem is because, well, that’s how colors affect us from a scientific point of view. The reason a clear-cut border will cause less eye strain after 8+ hours is because the receptors in our eyes pick up clear borders easier than round, pastel colored drab.
Why do I put Apple and Google in the same basket? Because Apple is doing the exact same thing. If you have ever worked with the disabled or people with handicaps, you will know that mac’s are rarely their favorite machines. The combination of graphical effects and contrasts that hardly show up on the colorblind’s radar makes it a torturous adventure.
Cost effective
For me personally, both Google and Apple are awesome companies – but there comes a time when throwing ideas around has run its course. A time when you have to go back to what “works”. Hopefully google is done experimenting with design, privacy and data roaming. And hopefully apple is done bloating the universe with senseless features and denying you access to your own bloody hardware.
And next time, I’m getting a real phone. One with buttons on it. My old Nokia cost me 500 NKR (less than $100) and I called for roughly 400 NKR a month. So give or take: a new Nokia phone + 1 year subscription cost me 5300 NKR a year (around $1000 in total). My iPhone alone cost me 8.500 NKR with a monthly call rate of around 500 NKR. So that’s 8500 + 6000 = 14500 NKR for one phone for a year. And I bought 2 of them (one for my wife as well).
The sub total equals 4 weeks vacation in a 5 star hotel in Sharm El Sheik, Egypt. Which sure as hell would have done a lot more for my headaches than Gmail.
When you sum up the expenses and added factors technology have gained for the past 8 years – from design problems to outright bloat – and weigh them against what is sensible, what works and what you really need to do your job – I find it hard to lobby for further innovation on Google and Apple’s part. There are always things to fix in the world of software, but there is a fine line between perfecting a product and exhausting it. A distinction that is clearly visible between Delphi 7 and Delphi XE2. Delphi 7 was the old Delphi perfected, and we had to wait until Delphi XE2 to find a product of equal quality. Between these versions were ideas that quite frankly exhausted both the technology and the user’s patience.
Turning your life into a product
And where the hell does google get off uploading pictures from my iPhone without asking? My kids were at a birthday party – and I really got pissed to see that my google app had automatically uploaded the pictures to my G+ account. What the hell! You don’t set a function like that to ON by default. As a result I have removed G+ and Facebook from my iPhone and iPad. And I’m seriously considering dropping my google account all together – and going back to Hotmail instead. Kidnapping data, selling your queries to advertising companies, data roaming — This is bordering on the insane. And to think they refused Microsoft to ship Windows with Internet Explorer. They should have blocked the google street view car as a threat to national security. It is by definition espionage on an international scale.
You must be logged in to post a comment.