Index: /CKEditor/branches/prototype/_source/core/tools.js
===================================================================
--- /CKEditor/branches/prototype/_source/core/tools.js	(revision 2750)
+++ /CKEditor/branches/prototype/_source/core/tools.js	(revision 2751)
@@ -364,4 +364,15 @@
 		}
 		return -1;
+	},
+
+	/**
+	 * Binds a function with an object as its this reference.
+	 * @param {Function} func The function to be bounded.
+	 * @param {Object} obj This this reference to bind to the function.
+	 * @returns {Function} The bound function.
+	 */
+	bind : function( func, obj )
+	{
+		return function(){ return func.apply( obj, arguments ); }
 	}
 };
Index: /CKEditor/branches/prototype/_source/lang/en.js
===================================================================
--- /CKEditor/branches/prototype/_source/lang/en.js	(revision 2750)
+++ /CKEditor/branches/prototype/_source/lang/en.js	(revision 2751)
@@ -58,4 +58,25 @@
 	horizontalrule	: 'Insert Horizontal Line',
 	specialChar		: 'Insert Special Character',
+
+	// Common terms.
+	browseServer	: 'Browser Server',
+	url				: 'URL',
+	protocol		: 'Protocol',
+	upload			: 'Upload',
+	uploadSubmit	: 'Send it to the Server',
+	image			: 'Image',
+	form			: 'Form',
+	checkbox		: 'Checkbox',
+	radio		: 'Radio Button',
+	textField		: 'Text Field',
+	textarea		: 'Textarea',
+	hiddenField		: 'Hidden Field',
+	button			: 'Button',
+	select	: 'Selection Field',
+	imageButton		: 'Image Button',
+	find			: 'Find',
+	replace			: 'Replace',
+
+	// Link dialog.
 	link			: 'Link',
 	linkTitle		: 'Link',
@@ -110,23 +131,8 @@
 	linkEmailSubject	: 'Message Subject',
 	linkEmailBody		: 'Message Body',
-	browseServer	: 'Browser Server',
-	url				: 'URL',
-	protocol		: 'Protocol',
-	upload			: 'Upload',
-	uploadSubmit	: 'Send it to the Server',
-	image			: 'Image',
-	form			: 'Form',
-	checkbox		: 'Checkbox',
-	radio		: 'Radio Button',
-	textField		: 'Text Field',
-	textarea		: 'Textarea',
-	hiddenField		: 'Hidden Field',
-	button			: 'Button',
-	select	: 'Selection Field',
-	imageButton		: 'Image Button',
-	find			: 'Find',
-	replace			: 'Replace',
-
-	//Dialog titles.
+	linkNoUrl			: 'Please type the link URL',
+	linkNoEmail			: 'Please type the e-mail address',
+
+	// Dialog titles.
 	formProp		: 'Form Properties',
 	buttonProp		: 'Button Properties',
Index: /CKEditor/branches/prototype/_source/plugins/dialog/plugin.js
===================================================================
--- /CKEditor/branches/prototype/_source/plugins/dialog/plugin.js	(revision 2750)
+++ /CKEditor/branches/prototype/_source/plugins/dialog/plugin.js	(revision 2751)
@@ -771,4 +771,15 @@
 			this._.selectedRanges = selection.getRanges();
 		}
+	},
+
+	/**
+	 * Clears the saved selection in the dialog object.
+	 * This function should be called if the dialog's code has already changed the
+	 * current selection position because the dialog closed. (e.g. at onOk())
+	 * @example
+	 */
+	clearSavedSelection : function()
+	{
+		delete this._.selectedRanges;
 	},
 
@@ -2125,2 +2136,121 @@
 	}
 };
+
+(function()
+{
+	var notEmptyRegex = /^([a]|[^a])+$/,
+		integerRegex = /^\d*$/,
+		numberRegex = /^\d*(?:\.\d+)?$/;
+
+	CKEDITOR.dialog.VALIDATE_OR = 1;
+	CKEDITOR.dialog.VALIDATE_AND = 2;
+
+	CKEDITOR.dialog.validate =
+	{
+		functions : function()
+		{
+			return function()
+			{
+				/** 
+				 * It's important for validate functions to be able to accept the value
+				 * as argument in addition to this.getValue(), so that it is possible to
+				 * combine validate functions together to make more sophisticated
+				 * validators.
+				 */
+				var value = this && this.getValue ? this.getValue() : arguments[0];
+
+				var msg = undefined,
+					relation = CKEDITOR.dialog.VALIDATE_AND,
+					functions = [], i;
+
+				for ( i = 0 ; i < arguments.length ; i++ )
+				{
+					if ( typeof( arguments[i] ) == 'function' )
+						functions.push( arguments[i] );
+					else
+						break;
+				}
+
+				if ( i < arguments.length && typeof( arguments[i] ) == 'string' )
+				{
+					msg = arguments[i];
+					i++;
+				}
+
+				if ( i < arguments.length && typeof( arguments[i]) == 'number' )
+					relation = arguments[i];
+
+				var passed = ( relation == CKEDITOR.dialog.VALIDATE_AND ? true : false );
+				for ( i = 0 ; i < functions.length ; i++ )
+				{
+					if ( relation == CKEDITOR.dialog.VALIDATE_AND )
+						passed = passed && functions[i]( value );
+					else
+						passed = passed || functions[i]( value );
+				}
+
+				if ( !passed )
+				{
+					if ( msg !== undefined )
+						alert( msg );
+					if ( this && ( this.select || this.focus ) )
+						( this.select || this.focus )();
+					return false;
+				}
+
+				return true;
+			};
+		},
+
+		regex : function( regex, msg )
+		{
+			/*
+			 * Can be greatly shortened by deriving from functions validator if code size
+			 * turns out to be more important than performance.
+			 */
+			return function()
+			{
+				var value = this && this.getValue ? this.getValue() : arguments[0];
+				if ( !regex.test( value ) )
+				{
+					if ( msg !== undefined )
+						alert( msg );
+					if ( this && ( this.select || this.focus ) )
+					{
+						if ( this.select )
+							this.select();
+						else
+							this.focus();
+					}
+					return false;
+				}
+				return true;
+			}
+		},
+
+		notEmpty : function( msg )
+		{
+			return this.regex( notEmptyRegex, msg );
+		},
+
+		integer : function( msg )
+		{
+			return this.regex( integerRegex, msg );
+		},
+
+		'number' : function( msg )
+		{
+			return this.regex( numberRegex, msg );
+		},
+
+		equals : function( value, msg )
+		{
+			return this.functions( function( val ){ return val == value; }, msg );
+		},
+
+		notEqual : function( value, msg )
+		{
+			return this.functions( function( val ){ return val != value; }, msg );
+		}
+	};
+})();
Index: /CKEditor/branches/prototype/_source/plugins/dialogui/plugin.js
===================================================================
--- /CKEditor/branches/prototype/_source/plugins/dialogui/plugin.js	(revision 2750)
+++ /CKEditor/branches/prototype/_source/plugins/dialogui/plugin.js	(revision 2751)
@@ -378,4 +378,6 @@
 					{
 						var item = elementDefinition.items[i],
+							title = item[2] !== undefined ? item[2] : item[0],
+							value = item[1] !== undefined ? item[1] : item[0],
 							inputDefinition = CKEDITOR.tools.extend( {}, elementDefinition,
 									{
@@ -387,5 +389,5 @@
 									{
 										id : null,
-										title : item[2]
+										title : title
 									}, true ),
 							inputHtml = [],
@@ -395,7 +397,7 @@
 								'class' : 'cke_dialog_ui_radio_input',
 								name : commonName,
-								value : item[1]
+								value : value
 							};
-						if ( elementDefinition['default'] == item[1] )
+						if ( elementDefinition['default'] == value )
 							inputAttributes.checked = 'checked';
 						cleanInnerDefinition( inputDefinition );
@@ -573,5 +575,5 @@
 					{
 						innerHTML.push( '<option value="',
-							CKEDITOR.tools.htmlEncode( item[1] || item[0] ), '" /> ',
+							CKEDITOR.tools.htmlEncode( item[1] !== undefined ? item[1] : item[0] ), '" /> ',
 							CKEDITOR.tools.htmlEncode( item[0] ) );
 					};
Index: /CKEditor/branches/prototype/_source/plugins/link/dialogs/link.js
===================================================================
--- /CKEditor/branches/prototype/_source/plugins/link/dialogs/link.js	(revision 2750)
+++ /CKEditor/branches/prototype/_source/plugins/link/dialogs/link.js	(revision 2751)
@@ -26,6 +26,7 @@
 	{
 		var popupFeatures = this.getDialog().getContentElement( 'target', 'popupFeatures' ).getElement(),
-			targetName = this.getDialog().getContentElement( 'target', 'linkTargetName' );
-		if ( this.getValue() == 'popup' )
+			targetName = this.getDialog().getContentElement( 'target', 'linkTargetName' ),
+			value = this.getValue();
+		if ( value == 'popup' )
 		{
 			popupFeatures.show();
@@ -36,4 +37,5 @@
 			popupFeatures.hide();
 			targetName.setLabel( editor.lang.linkTargetFrameName );
+			this.getDialog().setValueOf( 'target', 'linkTargetName', value.charAt( 0 ) == '_' ? value : '' );
 		}
 	},
@@ -111,5 +113,5 @@
 											[ 'ftp://' ],
 											[ 'news://' ],
-											[ '<other>' ]
+											[ '<other>', '' ]
 										]
 									},
@@ -117,5 +119,13 @@
 										type : 'text',
 										id : 'url',
-										label : editor.lang.url
+										label : editor.lang.url,
+										validate : function()
+										{
+											if ( this.getDialog().getValueOf( 'info', 'linkType' ) != 'url' )
+												return true;
+
+											var func = CKEDITOR.dialog.validate.notEmpty( editor.lang.linkNoUrl );
+											return func.apply( this );
+										}
 									}
 								]
@@ -177,15 +187,23 @@
 						[
 							{
-								'type' : 'text',
+								type : 'text',
 								id : 'emailAddress',
-								label : editor.lang.linkEmailAddress
-							},
-							{
-								'type' : 'text',
+								label : editor.lang.linkEmailAddress,
+								validate : function()
+								{
+									if ( this.getDialog().getValueOf( 'info', 'linkType' ) != 'email' )
+										return true;
+
+									var func = CKEDITOR.dialog.validate.notEmpty( editor.lang.linkNoEmail );
+									return func.apply( this );
+								}
+							},
+							{
+								type : 'text',
 								id : 'emailSubject',
 								label : editor.lang.linkEmailSubject
 							},
 							{
-								'type' : 'textarea',
+								type : 'textarea',
 								id : 'emailBody',
 								label : editor.lang.linkEmailBody,
@@ -219,8 +237,8 @@
 									[ editor.lang.linkTargetFrame, 'frame' ],
 									[ editor.lang.linkTargetPopup, 'popup' ],
-									[ editor.lang.linkTargetNew, 'new' ],
-									[ editor.lang.linkTargetTop, 'top' ],
-									[ editor.lang.linkTargetSelf, 'self' ],
-									[ editor.lang.linkTargetParent, 'parent' ]
+									[ editor.lang.linkTargetNew, '_blank' ],
+									[ editor.lang.linkTargetTop, '_top' ],
+									[ editor.lang.linkTargetSelf, '_self' ],
+									[ editor.lang.linkTargetParent, '_parent' ]
 								],
 								onChange : targetChanged
@@ -256,5 +274,5 @@
 									{
 										type : 'checkbox',
-										id : 'statusBar',
+										id : 'status',
 										label : editor.lang.linkPopupStatusBar
 									}
@@ -267,5 +285,5 @@
 									{
 										type : 'checkbox',
-										id : 'locationBar',
+										id : 'location',
 										label : editor.lang.linkPopupLocationBar
 									},
@@ -399,9 +417,9 @@
 										id : 'advLangDir',
 										label : editor.lang.linkLangDir,
-										'default' : 'notSet',
+										'default' : '',
 										style : 'width: 100%;',
 										items :
 										[
-											[ editor.lang.linkLangDirNotSet, 'notSet' ],
+											[ editor.lang.linkLangDirNotSet, '' ],
 											[ editor.lang.linkLangDirLTR, 'ltr' ],
 											[ editor.lang.linkLangDirRTL, 'rtl' ]
@@ -499,4 +517,104 @@
 		{
 			this.getContentElement( 'info', 'url' ).focus();
+		},
+		onOk : function()
+		{
+			var attributes = { href : 'javascript:void(0)/*' + CKEDITOR.tools.getNextNumber() + '*/' },
+				linkList, subject, body, me = this;
+
+			// Compose the URL.
+			switch ( this.getValueOf( 'info', 'linkType' ) )
+			{
+				case 'url':
+					attributes._cksavedhref = this.getValueOf( 'info', 'protocol' ) + this.getValueOf( 'info', 'url' );
+					break;
+				case 'anchor':
+					attributes._cksavedhref = '#' + ( this.getValueOf( 'info', 'anchorName' ) || this.getValueOf( 'info', 'anchorId' ) );
+					break;
+				case 'email':
+					linkList = [ 'mailto:', this.getValueOf( 'info', 'emailAddress' ) ];
+					subject = encodeURICompoenent( this.getValueOf( 'info', 'emailSubject' ) );
+					body = encodeURIComponent( this.getValueOf( 'info', 'emailBody' ) );
+					if ( subject || body)
+					{
+						linkList.push( '?' );
+						subject && linkList.push( 'subject=', subject );
+						body && body.push( 'body=', body );
+					}
+					attributes._cksavedhref = linkList.join( '' );
+					break;
+				default:
+			}
+			
+			// Popups and target.
+			if ( this.getValueOf( 'target', 'linkTargetType' ) == 'popup' )
+			{
+				var onclickList = [ 'window.open(this.href, \'',
+						this.getValueOf( 'target', 'linkTargetName' ), '\', \'' ],
+					featureList = [ 'resizable', 'status', 'location', 'toolbar', 'menubar', 'fullscreen',
+						'scrollbars', 'dependent' ],
+					featureLength = featureList.length,
+					addFeature = function( inputName, featureName )
+					{
+						if ( me.getValueOf( 'target', inputName ) != '' )
+							featureList.push( featureName + '=' + me.getValueOf( 'target', inputName ) );
+					};
+
+				for ( var i = 0 ; i < featureLength ; i++ )
+					featureList[i] = featureList[i] + ( this.getValueOf( 'target', featureList[i] ) ? '=yes' : '=no' );
+				addFeature( 'popupWidth', 'width' );
+				addFeature( 'popupLeft', 'left' );
+				addFeature( 'popupHeight', 'height' );
+				addFeature( 'popupTop', 'top' );
+
+				onclickList.push( featureList.join( ',' ), '\'); return false;' );
+				attributes._cksavedonclick = onclickList.join( '' );
+			}
+			else
+			{
+				if ( this.getValueOf( 'target', 'linkTargetType' ) != 'notSet' )
+					attributes.target = this.getValueOf( 'target', 'linkTargetName' );
+			}
+
+			// Advanced attributes.
+			var advAttr = function( inputName, attrName )
+			{
+				var value = me.getValueOf( 'advanced', inputName );
+				if ( value != '' )
+					attributes[attrName] = value;
+			};
+			advAttr( 'advLangDir', 'dir' );
+			advAttr( 'advAccessKey', 'accessKey' );
+			advAttr( 'advName', 'name' );
+			advAttr( 'advLangCode', 'lang' );
+			advAttr( 'advTabIndex', 'tabindex' );
+			advAttr( 'advTitle', 'title' );
+			advAttr( 'advContentType', 'type' );
+			advAttr( 'advCSSClasses', 'class' );
+			advAttr( 'advCharset', 'charset' );
+			advAttr( 'advStyles', 'style' );
+
+			// Create element if current selection is collapsed, else apply style.
+			var style = new CKEDITOR.style( { element : 'a', attributes : attributes } );
+			style.type = CKEDITOR.STYLE_INLINE;		// need to override... dunno why.
+			style.apply( this.getParentEditor().document );
+			
+			// Id. Apply only to the first link.
+			if ( this.getValueOf( 'advanced', 'advId' ) != '' )
+			{
+				var links = this.getParentEditor().document.$.getElementsByTagName( 'a' );
+				for ( var i = 0 ; i < links.length ; i++ )
+				{
+					if ( links[i].href == attributes.href )
+					{
+						console.log( 'fadhm' );
+						links[i].id = this.getValueOf( 'advanced', 'advId' );
+						break;
+					}
+				}
+			}
+
+			// style.apply() changed the selection already, so clear the old one.
+			this.clearSavedSelection();
 		}
 	};
Index: /CKEditor/branches/prototype/_source/plugins/link/plugin.js
===================================================================
--- /CKEditor/branches/prototype/_source/plugins/link/plugin.js	(revision 2750)
+++ /CKEditor/branches/prototype/_source/plugins/link/plugin.js	(revision 2751)
@@ -24,5 +24,7 @@
 	init : function( editor, pluginPath )
 	{
+		// Add the link and unlink buttons.
 		editor.addCommand( 'link', new CKEDITOR.dialogCommand( 'link' ) );
+		editor.addCommand( 'unlink', new CKEDITOR.unlinkCommand() );
 		editor.ui.addButton( 'Link',
 			{
@@ -36,4 +38,28 @@
 			} );
 		CKEDITOR.dialog.add( 'link', this.path + 'dialogs/link.js' );
+
+		// Register selection change handler for the unlink button.
+		editor.on( 'selectionChange', function( evt )
+			{
+				var command = editor.getCommand( 'unlink' ),
+					element = evt.data.path.lastElement;
+				if ( element.getName() == 'a' && element.getAttribute( 'href' ) )
+					command.state = CKEDITOR.TRISTATE_OFF;
+				else
+					command.state = CKEDITOR.TRISTATE_DISABLED;
+				command.fire( 'state' );
+			} );
 	}
 } );
+
+CKEDITOR.unlinkCommand = function()
+{
+};
+CKEDITOR.unlinkCommand.prototype =
+{
+	/** @ignore */
+	exec : function( editor )
+	{
+		// TODO: There seems to be no way to remove the <a> tag via style system yet.
+	}
+};
