Page Menu
Home
Phabricator (Chris)
Search
Configure Global Search
Log In
Files
F631360
NDKeyboardLayout.m
No One
Temporary
Actions
Download File
Edit File
Delete File
View Transforms
Subscribe
Mute Notifications
Flag For Later
Award Token
Authored By
Unknown
Size
15 KB
Referenced Files
None
Subscribers
None
NDKeyboardLayout.m
View Options
/*
NDKeyboardLayout.m
Created by Nathan Day on 01.18.10 under a MIT-style license.
Copyright (c) 2010 Nathan Day
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.
*/
#import "NDKeyboardLayout.h"
#include
<libkern/OSAtomic.h>
NSString
*
const
NDKeyboardLayoutSelectedKeyboardInputSourceChangedNotification
=
@"NDKeyboardLayoutSelectedKeyboardInputSourceChanged"
;
NSString
*
const
NDKeyboardLayoutPreviousKeyboardLayoutUserInfoKey
=
@"NDKeyboardLayoutPreviousKeyboardLayout"
;
struct
ReverseMappingEntry
{
UniChar
character
;
BOOL
keypad
;
UInt16
keyCode
;
};
struct
UnmappedEntry
{
UniChar
character
;
UInt16
keyCode
;
unichar
description
[
4
];
};
struct
UnmappedEntry
unmappedKeys
[]
=
{
{
NSDeleteFunctionKey
,
0x33
,
{
0x232B
,
'\0'
,
'\0'
,
'\0'
}},
{
NSF17FunctionKey
,
0x40
,
{
'F'
,
'1'
,
'7'
,
'\0'
}},
{
NSClearDisplayFunctionKey
,
0x47
,
{
0x2327
,
'\0'
,
'\0'
,
'\0'
}},
{
NSF18FunctionKey
,
0x4F
,
{
'F'
,
'1'
,
'8'
,
'\0'
}},
{
NSF19FunctionKey
,
0x50
,
{
'F'
,
'1'
,
'9'
,
'\0'
}},
{
NSF5FunctionKey
,
0x60
,
{
'F'
,
'5'
,
'\0'
,
'\0'
}},
{
NSF6FunctionKey
,
0x61
,
{
'F'
,
'6'
,
'\0'
,
'\0'
}},
{
NSF7FunctionKey
,
0x62
,
{
'F'
,
'7'
,
'\0'
,
'\0'
}},
{
NSF3FunctionKey
,
0x63
,
{
'F'
,
'3'
,
'\0'
,
'\0'
}},
{
NSF8FunctionKey
,
0x64
,
{
'F'
,
'8'
,
'\0'
,
'\0'
}},
{
NSF9FunctionKey
,
0x65
,
{
'F'
,
'9'
,
'\0'
,
'\0'
}},
{
NSF11FunctionKey
,
0x67
,
{
'F'
,
'1'
,
'1'
,
'\0'
}},
{
NSF14FunctionKey
,
0x68
,
{
'F'
,
'1'
,
'4'
,
'\0'
}},
{
NSF13FunctionKey
,
0x69
,
{
'F'
,
'1'
,
'3'
,
'\0'
}},
{
NSF16FunctionKey
,
0x6A
,
{
'F'
,
'1'
,
'6'
,
'\0'
}},
{
NSF10FunctionKey
,
0x6D
,
{
'F'
,
'1'
,
'0'
,
'\0'
}},
{
NSF12FunctionKey
,
0x6F
,
{
'F'
,
'1'
,
'2'
,
'\0'
}},
{
NSF15FunctionKey
,
0x71
,
{
'F'
,
'1'
,
'5'
,
'\0'
}},
{
NSHomeFunctionKey
,
0x73
,
{
0x21F1
,
'\0'
,
'\0'
,
'\0'
}},
{
NSPageUpFunctionKey
,
0x74
,
{
0x21DE
,
'\0'
,
'\0'
,
'\0'
}},
{
NSDeleteCharFunctionKey
,
0x75
,
{
0x2326
,
'\0'
,
'\0'
,
'\0'
}},
{
NSF4FunctionKey
,
0x76
,
{
'F'
,
'4'
,
'\0'
,
'\0'
}},
{
NSEndFunctionKey
,
0x77
,
{
0x21F2
,
'\0'
,
'\0'
,
'\0'
}},
{
NSF2FunctionKey
,
0x78
,
{
'F'
,
'2'
,
'\0'
,
'\0'
}},
{
NSPageDownFunctionKey
,
0x79
,
{
0x21DF
,
'\0'
,
'\0'
,
'\0'
}},
{
NSF1FunctionKey
,
0x7A
,
{
'F'
,
'1'
,
'\0'
,
'\0'
}},
{
NSLeftArrowFunctionKey
,
0x7B
,
{
0x2190
,
'\0'
,
'\0'
,
'\0'
}},
{
NSRightArrowFunctionKey
,
0x7C
,
{
0x2192
,
'\0'
,
'\0'
,
'\0'
}},
{
NSDownArrowFunctionKey
,
0x7D
,
{
0x2193
,
'\0'
,
'\0'
,
'\0'
}},
{
NSUpArrowFunctionKey
,
0x7E
,
{
0x2191
,
'\0'
,
'\0'
,
'\0'
}}
// {NSF20FunctionKey, 0xXXXX},
// {NSF21FunctionKey, 0xXXXX},
// {NSF22FunctionKey, 0xXXXX},
// {NSF23FunctionKey, 0xXXXX},
// {NSF24FunctionKey, 0xXXXX},
// {NSF25FunctionKey, 0xXXXX},
// {NSF26FunctionKey, 0xXXXX},
// {NSF27FunctionKey, 0xXXXX},
// {NSF28FunctionKey, 0xXXXX},
// {NSF29FunctionKey, 0xXXXX},
// {NSF30FunctionKey, 0xXXXX},
// {NSF31FunctionKey, 0xXXXX},
// {NSF32FunctionKey, 0xXXXX},
// {NSF33FunctionKey, 0xXXXX},
// {NSF34FunctionKey, 0xXXXX},
// {NSF35FunctionKey, 0xXXXX},
// {NSInsertFunctionKey, 0xXXXX},
// {NSBeginFunctionKey, 0xXXXX},
// {NSPrintScreenFunctionKey, 0xXXXX},
// {NSScrollLockFunctionKey, 0xXXXX},
// {NSPauseFunctionKey, 0xXXXX},
// {NSSysReqFunctionKey, 0xXXXX},
// {NSBreakFunctionKey, 0xXXXX},
// {NSResetFunctionKey, 0xXXXX},
// {NSStopFunctionKey, 0xXXXX},
// {NSMenuFunctionKey, 0xXXXX},
// {NSUserFunctionKey, 0xXXXX},
// {NSSystemFunctionKey, 0xXXXX},
// {NSPrintFunctionKey, 0xXXXX},
// {NSClearLineFunctionKey, 0xXXXX},
// {NSInsertLineFunctionKey, 0xXXXX},
// {NSDeleteLineFunctionKey, 0xXXXX},
// {NSInsertCharFunctionKey, 0xXXXX},
// {NSPrevFunctionKey, 0xXXXX},
// {NSNextFunctionKey, 0xXXXX},
// {NSSelectFunctionKey, 0xXXXX},
// {NSExecuteFunctionKey, 0xXXXX},
// {NSUndoFunctionKey, 0xXXXX},
// {NSRedoFunctionKey, 0xXXXX},
// {NSFindFunctionKey, 0xXXXX},
// {NSHelpFunctionKey, 0xXXXX},
// {NSModeSwitchFunctionKey, 0xXXXX}
};
/*@interface NDKeyboardLayout ()
{
@private
CFDataRef keyboardLayoutData;
struct ReverseMappingEntry * mappings;
NSUInteger numberOfMappings;
}
@property(readonly,nonatomic) const UCKeyboardLayout * keyboardLayoutPtr;
@end*/
static
int
_reverseMappingEntryCmpFunc
(
const
void
*
a
,
const
void
*
b
)
{
struct
ReverseMappingEntry
*
theA
=
(
struct
ReverseMappingEntry
*
)
a
,
*
theB
=
(
struct
ReverseMappingEntry
*
)
b
;
return
theA
->
character
!=
theB
->
character
?
theA
->
character
-
theB
->
character
:
theA
->
keypad
-
theB
->
keypad
;
}
static
struct
ReverseMappingEntry
*
_searchreverseMapping
(
struct
ReverseMappingEntry
*
aMapping
,
NSUInteger
aLength
,
struct
ReverseMappingEntry
*
aSearchValue
)
{
NSInteger
low
=
0
,
high
=
aLength
-
1
,
mid
,
result
;
while
(
low
<=
high
)
{
mid
=
(
low
+
high
)
>>
1
;
result
=
_reverseMappingEntryCmpFunc
(
&
aMapping
[
mid
],
aSearchValue
);
if
(
result
>
0
)
high
=
mid
-
1
;
else
if
(
result
<
0
)
low
=
mid
+
1
;
else
return
&
aMapping
[
mid
];
}
return
NULL
;
}
static
struct
UnmappedEntry
*
_unmappedEntryForKeyCode
(
UInt16
aKeyCode
)
{
NSInteger
low
=
0
,
high
=
sizeof
(
unmappedKeys
)
/
sizeof
(
*
unmappedKeys
)
-
1
,
mid
,
result
;
while
(
low
<=
high
)
{
mid
=
(
low
+
high
)
>>
1
;
result
=
unmappedKeys
[
mid
].
keyCode
-
aKeyCode
;
if
(
result
>
0
)
high
=
mid
-
1
;
else
if
(
result
<
0
)
low
=
mid
+
1
;
else
return
&
unmappedKeys
[
mid
];
}
return
'\0'
;
}
static
const
size_t
kBufferSize
=
4
;
static
NSUInteger
_characterForModifierFlags
(
unichar
aBuff
[
kBufferSize
],
UInt32
aModifierFlags
)
{
NSUInteger
thePos
=
0
;
memset
(
aBuff
,
0
,
kBufferSize
);
if
(
aModifierFlags
&
NSControlKeyMask
)
aBuff
[
thePos
++
]
=
kControlUnicode
;
if
(
aModifierFlags
&
NSAlternateKeyMask
)
aBuff
[
thePos
++
]
=
kOptionUnicode
;
if
(
aModifierFlags
&
NSShiftKeyMask
)
aBuff
[
thePos
++
]
=
kShiftUnicode
;
if
(
aModifierFlags
&
NSCommandKeyMask
)
aBuff
[
thePos
++
]
=
kCommandUnicode
;
return
thePos
;
}
/*
* NDCocoaModifierFlagsForCarbonModifierFlags()
*/
NSUInteger
NDCocoaModifierFlagsForCarbonModifierFlags
(
NSUInteger
aModifierFlags
)
{
NSUInteger
theCocoaModifierFlags
=
0
;
if
(
aModifierFlags
&
shiftKey
)
theCocoaModifierFlags
|=
NSShiftKeyMask
;
if
(
aModifierFlags
&
controlKey
)
theCocoaModifierFlags
|=
NSControlKeyMask
;
if
(
aModifierFlags
&
optionKey
)
theCocoaModifierFlags
|=
NSAlternateKeyMask
;
if
(
aModifierFlags
&
cmdKey
)
theCocoaModifierFlags
|=
NSCommandKeyMask
;
return
theCocoaModifierFlags
;
}
/*
* NDCarbonModifierFlagsForCocoaModifierFlags()
*/
NSUInteger
NDCarbonModifierFlagsForCocoaModifierFlags
(
NSUInteger
aModifierFlags
)
{
NSUInteger
theCarbonModifierFlags
=
0
;
if
(
aModifierFlags
&
NSShiftKeyMask
)
theCarbonModifierFlags
|=
shiftKey
;
if
(
aModifierFlags
&
NSControlKeyMask
)
theCarbonModifierFlags
|=
controlKey
;
if
(
aModifierFlags
&
NSAlternateKeyMask
)
theCarbonModifierFlags
|=
optionKey
;
if
(
aModifierFlags
&
NSCommandKeyMask
)
theCarbonModifierFlags
|=
cmdKey
;
return
theCarbonModifierFlags
;
}
@implementation
NDKeyboardLayout
#pragma mark Utility Methods
-
(
void
)
generateMappings
{
mappings
=
(
struct
ReverseMappingEntry
*
)
calloc
(
128
+
sizeof
(
unmappedKeys
)
/
sizeof
(
*
unmappedKeys
),
sizeof
(
struct
ReverseMappingEntry
)
);
numberOfMappings
=
0
;
for
(
NSUInteger
i
=
0
;
i
<
128
;
i
++
)
{
UInt32
theDeadKeyState
=
0
;
UniCharCount
theLength
=
0
;
if
(
UCKeyTranslate
(
self
.
keyboardLayoutPtr
,
i
,
kUCKeyActionDisplay
,
0
,
LMGetKbdType
(),
kUCKeyTranslateNoDeadKeysBit
,
&
theDeadKeyState
,
1
,
&
theLength
,
&
mappings
[
numberOfMappings
].
character
)
==
noErr
&&
theLength
>
0
&&
isprint
(
mappings
[
numberOfMappings
].
character
)
)
{
mappings
[
numberOfMappings
].
keyCode
=
i
;
numberOfMappings
++
;
}
}
/* add unmapped keys */
for
(
NSUInteger
i
=
0
;
i
<
sizeof
(
unmappedKeys
)
/
sizeof
(
*
unmappedKeys
);
i
++
)
{
mappings
[
numberOfMappings
].
character
=
unmappedKeys
[
i
].
character
;
mappings
[
numberOfMappings
].
keyCode
=
unmappedKeys
[
i
].
keyCode
;
numberOfMappings
++
;
}
mappings
=
(
struct
ReverseMappingEntry
*
)
realloc
(
(
void
*
)
mappings
,
numberOfMappings
*
sizeof
(
struct
ReverseMappingEntry
)
);
// sort so we can perform binary searches
qsort
(
(
void
*
)
mappings
,
numberOfMappings
,
sizeof
(
struct
ReverseMappingEntry
),
_reverseMappingEntryCmpFunc
);
/* find keypad keys and set the keypad flag */
for
(
NSUInteger
i
=
1
;
i
<
numberOfMappings
;
i
++
)
{
NSParameterAssert
(
mappings
[
i
-1
].
keyCode
!=
mappings
[
i
].
keyCode
);
if
(
mappings
[
i
-1
].
character
==
mappings
[
i
].
character
)
// assume large keycode is a keypad
{
if
(
mappings
[
i
-1
].
keyCode
>
mappings
[
i
].
keyCode
)
// make the keypad entry is second
{
UInt16
theTemp
=
mappings
[
i
-1
].
keyCode
;
mappings
[
i
-1
].
keyCode
=
mappings
[
i
].
keyCode
;
mappings
[
i
].
keyCode
=
theTemp
;
}
mappings
[
i
].
keypad
=
YES
;
}
}
#ifdef DEBUGGING_CODE
for
(
NSUInteger
i
=
1
;
i
<
numberOfMappings
;
i
++
)
{
fprintf
(
stderr
,
"%d -> %c[%d]%s
\n
"
,
mappings
[
i
].
keyCode
,
(
char
)
mappings
[
i
].
character
,
mappings
[
i
].
character
,
mappings
[
i
].
keypad
?
" keypad"
:
""
);
NSAssert3
(
mappings
[
i
-1
].
character
<=
mappings
[
i
].
character
,
@"[%d] %d <= %d"
,
i
,
mappings
[
i
-1
].
character
,
mappings
[
i
].
character
);
}
#endif
}
#pragma mark Constructor Methods
static
volatile
NDKeyboardLayout
*
kCurrentKeyboardLayout
=
nil
;
void
NDKeyboardLayoutNotificationCallback
(
CFNotificationCenterRef
aCenter
,
void
*
self
,
CFStringRef
aName
,
const
void
*
anObj
,
CFDictionaryRef
aUserInfo
)
{
NSDictionary
*
theUserInfo
=
[
NSDictionary
dictionaryWithObject
:
kCurrentKeyboardLayout
forKey
:
NDKeyboardLayoutPreviousKeyboardLayoutUserInfoKey
];
@synchronized
(
self
)
{
[
kCurrentKeyboardLayout
release
],
kCurrentKeyboardLayout
=
nil
;
}
[[
NSNotificationCenter
defaultCenter
]
postNotificationName
:
NDKeyboardLayoutSelectedKeyboardInputSourceChangedNotification
object
:
self
userInfo
:
theUserInfo
];
}
+
(
void
)
initialize
{
CFNotificationCenterAddObserver
(
CFNotificationCenterGetLocalCenter
(),
(
const
void
*
)
self
,
NDKeyboardLayoutNotificationCallback
,
kTISNotifySelectedKeyboardInputSourceChanged
,
NULL
,
CFNotificationSuspensionBehaviorDeliverImmediately
);
}
+
(
id
)
keyboardLayout
{
if
(
kCurrentKeyboardLayout
==
nil
)
{
@synchronized
(
self
)
{
/*
Try different method until we succeed.
*/
TISInputSourceRef
(
*
theInputSourceFunctions
[])()
=
{
TISCopyInputMethodKeyboardLayoutOverride
,
TISCopyCurrentKeyboardLayoutInputSource
,
TISCopyCurrentASCIICapableKeyboardLayoutInputSource
};
for
(
NSUInteger
i
=
0
;
i
<
sizeof
(
theInputSourceFunctions
)
/
sizeof
(
*
theInputSourceFunctions
)
&&
kCurrentKeyboardLayout
==
nil
;
i
++
)
{
TISInputSourceRef
theInputSource
=
theInputSourceFunctions
[
i
]();
if
(
theInputSource
!=
NULL
)
{
kCurrentKeyboardLayout
=
[[
self
alloc
]
initWithInputSource
:
theInputSource
];
CFRelease
(
theInputSource
);
}
}
}
}
return
kCurrentKeyboardLayout
;
}
-
(
id
)
init
{
[
self
release
];
return
[[
NDKeyboardLayout
keyboardLayout
]
retain
];
}
-
(
id
)
initWithLanguage:
(
NSString
*
)
aLangauge
{
return
[
self
initWithInputSource
:
TISCopyInputSourceForLanguage
((
CFStringRef
)
aLangauge
)];
}
-
(
id
)
initWithInputSource:
(
TISInputSourceRef
)
aSource
{
if
(
(
self
=
[
super
init
])
!=
nil
)
{
if
(
aSource
!=
NULL
&&
(
keyboardLayoutData
=
(
CFDataRef
)
CFMakeCollectable
(
TISGetInputSourceProperty
(
aSource
,
kTISPropertyUnicodeKeyLayoutData
)))
!=
nil
)
{
CFRetain
(
keyboardLayoutData
);
}
else
self
=
nil
,
[
self
release
];
}
return
self
;
}
-
(
void
)
dealloc
{
if
(
mappings
!=
NULL
)
free
(
(
void
*
)
mappings
);
if
(
keyboardLayoutData
!=
NULL
)
CFRelease
(
keyboardLayoutData
);
[
super
dealloc
];
}
-
(
NSString
*
)
stringForCharacter:
(
unichar
)
aCharacter
modifierFlags:
(
UInt32
)
aModifierFlags
{
return
[
self
stringForKeyCode
:
[
self
keyCodeForCharacter
:
aCharacter
numericPad
:
(
aModifierFlags
&
NSNumericPadKeyMask
)
!=
0
]
modifierFlags
:
aModifierFlags
];
}
-
(
NSString
*
)
stringForKeyCode:
(
UInt16
)
aKeyCode
modifierFlags:
(
UInt32
)
aModifierFlags
{
NSString
*
theResult
=
nil
;
struct
UnmappedEntry
*
theEntry
=
_unmappedEntryForKeyCode
(
aKeyCode
);
// is it one of the unmapped values
if
(
theEntry
!=
NULL
)
{
unichar
theCharacter
[
sizeof
(
theEntry
->
description
)
/
sizeof
(
*
theEntry
->
description
)
+
4
+
1
];
memset
(
theCharacter
,
0
,
sizeof
(
theCharacter
)
);
NSUInteger
thePos
=
_characterForModifierFlags
(
theCharacter
,
aModifierFlags
);
memcpy
(
theCharacter
+
thePos
,
theEntry
->
description
,
sizeof
(
theEntry
->
description
)
);
theResult
=
[
NSString
stringWithCharacters
:
theCharacter
length
:
sizeof
(
theEntry
->
description
)
/
sizeof
(
*
theEntry
->
description
)
+
thePos
];
}
else
{
UInt32
theDeadKeyState
=
0
;
UniCharCount
theLength
=
0
;
UniChar
theCharacter
[
260
];
NSUInteger
thePos
=
_characterForModifierFlags
(
theCharacter
,
aModifierFlags
);
if
(
UCKeyTranslate
(
self
.
keyboardLayoutPtr
,
aKeyCode
,
kUCKeyActionDisplay
,
NDCarbonModifierFlagsForCocoaModifierFlags
(
aModifierFlags
),
LMGetKbdType
(),
kUCKeyTranslateNoDeadKeysBit
,
&
theDeadKeyState
,
sizeof
(
theCharacter
)
/
sizeof
(
*
theCharacter
)
-
thePos
,
&
theLength
,
theCharacter
+
thePos
)
==
noErr
)
{
theResult
=
[[
NSString
stringWithCharacters
:
theCharacter
length
:
theLength
+
thePos
]
uppercaseString
];
}
}
return
theResult
;
}
-
(
unichar
)
characterForKeyCode:
(
UInt16
)
aKeyCode
{
unichar
theChar
=
0
;
struct
UnmappedEntry
*
theEntry
=
_unmappedEntryForKeyCode
(
aKeyCode
);
if
(
theEntry
==
NULL
)
// is it one of the unmapped values
{
UInt32
theDeadKeyState
=
0
;
UniCharCount
theLength
=
0
;
UniChar
theCharacter
[
256
];
if
(
UCKeyTranslate
(
self
.
keyboardLayoutPtr
,
aKeyCode
,
kUCKeyActionDisplay
,
0
,
LMGetKbdType
(),
kUCKeyTranslateNoDeadKeysBit
,
&
theDeadKeyState
,
sizeof
(
theCharacter
)
/
sizeof
(
*
theCharacter
),
&
theLength
,
theCharacter
)
==
noErr
)
{
theChar
=
theCharacter
[
0
];
}
}
else
theChar
=
theEntry
->
character
;
return
toupper
(
theChar
);
}
-
(
UInt16
)
keyCodeForCharacter:
(
unichar
)
aCharacter
{
return
[
self
keyCodeForCharacter
:
aCharacter
numericPad
:
NO
];
}
-
(
UInt16
)
keyCodeForCharacter:
(
unichar
)
aCharacter
numericPad:
(
BOOL
)
aNumericPad
{
struct
ReverseMappingEntry
theSearchValue
=
{
tolower
(
aCharacter
),
aNumericPad
,
0
};
struct
ReverseMappingEntry
*
theEntry
=
NULL
;
if
(
mappings
==
NULL
)
[
self
generateMappings
];
theEntry
=
_searchreverseMapping
(
mappings
,
numberOfMappings
,
&
theSearchValue
);
return
theEntry
?
theEntry
->
keyCode
:
'\0'
;
}
#pragma mark - private
-
(
const
UCKeyboardLayout
*
)
keyboardLayoutPtr
{
return
(
const
UCKeyboardLayout
*
)
CFDataGetBytePtr
(
keyboardLayoutData
);
}
@end
File Metadata
Details
Attached
Mime Type
text/x-objective-c
Expires
Sun, Nov 15, 11:46 AM (1 d, 13 h)
Storage Engine
blob
Storage Format
Raw Data
Storage Handle
152304
Default Alt Text
NDKeyboardLayout.m (15 KB)
Attached To
Mode
R63 darkscreen
Attached
Detach File
Event Timeline