@@ -69,6 +69,7 @@ define(function (require, exports, module) {
6969 CodeMirror = require ( "thirdparty/CodeMirror2/lib/codemirror" ) ,
7070 Menus = require ( "command/Menus" ) ,
7171 PerfUtils = require ( "utils/PerfUtils" ) ,
72+ PopUpManager = require ( "widgets/PopUpManager" ) ,
7273 PreferencesManager = require ( "preferences/PreferencesManager" ) ,
7374 Strings = require ( "strings" ) ,
7475 TextRange = require ( "document/TextRange" ) . TextRange ,
@@ -198,6 +199,7 @@ define(function (require, exports, module) {
198199 // (if makeMasterEditor, we attach the Doc back to ourselves below once we're fully initialized)
199200
200201 this . _inlineWidgets = [ ] ;
202+ this . _$messagePopover = null ;
201203
202204 // Editor supplies some standard keyboard behavior extensions of its own
203205 var codeMirrorKeyMap = {
@@ -811,7 +813,7 @@ define(function (require, exports, module) {
811813
812814 this . _codeMirror . on ( "blur" , function ( ) {
813815 self . _focused = false ;
814- // EditorManager only cares about other Editors gaining focus, so we don't notify it of anything here
816+ $ ( self ) . triggerHandler ( "blur" , [ self ] ) ;
815817 } ) ;
816818
817819 this . _codeMirror . on ( "update" , function ( instance ) {
@@ -1518,6 +1520,126 @@ define(function (require, exports, module) {
15181520 return this . _inlineWidgets ;
15191521 } ;
15201522
1523+ /**
1524+ * Display temporary popover message at current cursor position. Display message above
1525+ * cursor if space allows, otherwise below.
1526+ *
1527+ * @param {string } errorMsg Error message to display
1528+ */
1529+ Editor . prototype . displayErrorMessageAtCursor = function ( errorMsg ) {
1530+ var arrowBelow , cursorPos , cursorCoord , popoverRect ,
1531+ top , left , clip , arrowLeft ,
1532+ self = this ,
1533+ $editorHolder = $ ( "#editor-holder" ) ,
1534+ POPOVER_MARGIN = 10 ,
1535+ POPOVER_ARROW_HALF_WIDTH = 10 ;
1536+
1537+ function _removeListeners ( ) {
1538+ $ ( self ) . off ( ".msgbox" ) ;
1539+ }
1540+
1541+ // PopUpManager.removePopUp() callback
1542+ function _clearMessagePopover ( ) {
1543+ if ( self . _$messagePopover && self . _$messagePopover . length > 0 ) {
1544+ // self._$messagePopover.remove() is done by PopUpManager
1545+ self . _$messagePopover = null ;
1546+ }
1547+ _removeListeners ( ) ;
1548+ }
1549+
1550+ // PopUpManager.removePopUp() is called either directly by this closure, or by
1551+ // PopUpManager as a result of another popup being invoked.
1552+ function _removeMessagePopover ( ) {
1553+ PopUpManager . removePopUp ( self . _$messagePopover ) ;
1554+ }
1555+
1556+ function _addListeners ( ) {
1557+ $ ( self )
1558+ . on ( "blur.msgbox" , _removeMessagePopover )
1559+ . on ( "change.msgbox" , _removeMessagePopover )
1560+ . on ( "cursorActivity.msgbox" , _removeMessagePopover )
1561+ . on ( "update.msgbox" , _removeMessagePopover ) ;
1562+ }
1563+
1564+ // Only 1 message at a time
1565+ if ( this . _$messagePopover ) {
1566+ _removeMessagePopover ( ) ;
1567+ }
1568+
1569+ // Make sure cursor is in view
1570+ cursorPos = this . getCursorPos ( ) ;
1571+ this . _codeMirror . scrollIntoView ( cursorPos ) ;
1572+
1573+ // Determine if arrow is above or below
1574+ cursorCoord = this . _codeMirror . charCoords ( cursorPos ) ;
1575+
1576+ // Assume popover height is max of 2 lines
1577+ arrowBelow = ( cursorCoord . top > 100 ) ;
1578+
1579+ // Text is dynamic, so build popover first so we can measure final width
1580+ this . _$messagePopover = $ ( "<div/>" ) . addClass ( "popover-message" ) . appendTo ( $ ( "body" ) ) ;
1581+ if ( ! arrowBelow ) {
1582+ $ ( "<div/>" ) . addClass ( "arrowAbove" ) . appendTo ( this . _$messagePopover ) ;
1583+ }
1584+ $ ( "<div/>" ) . addClass ( "text" ) . appendTo ( this . _$messagePopover ) . html ( errorMsg ) ;
1585+ if ( arrowBelow ) {
1586+ $ ( "<div/>" ) . addClass ( "arrowBelow" ) . appendTo ( this . _$messagePopover ) ;
1587+ }
1588+
1589+ // Estimate where to position popover.
1590+ top = ( arrowBelow ) ? cursorCoord . top - this . _$messagePopover . height ( ) - POPOVER_MARGIN
1591+ : cursorCoord . bottom + POPOVER_MARGIN ;
1592+ left = cursorCoord . left - ( this . _$messagePopover . width ( ) / 2 ) ;
1593+
1594+ popoverRect = {
1595+ top : top ,
1596+ left : left ,
1597+ height : this . _$messagePopover . height ( ) ,
1598+ width : this . _$messagePopover . width ( )
1599+ } ;
1600+
1601+ // See if popover is clipped on any side
1602+ clip = ViewUtils . getElementClipSize ( $editorHolder , popoverRect ) ;
1603+
1604+ // Prevent horizontal clipping
1605+ if ( clip . left > 0 ) {
1606+ left += clip . left ;
1607+ } else if ( clip . right > 0 ) {
1608+ left -= clip . right ;
1609+ }
1610+
1611+ // Popover text and arrow are positioned individually
1612+ this . _$messagePopover . css ( { "top" : top , "left" : left } ) ;
1613+
1614+ // Position popover arrow exactly centered over/under cursor
1615+ arrowLeft = cursorCoord . left - left - POPOVER_ARROW_HALF_WIDTH ;
1616+ if ( arrowBelow ) {
1617+ this . _$messagePopover . find ( ".arrowBelow" ) . css ( { "margin-left" : arrowLeft } ) ;
1618+ } else {
1619+ this . _$messagePopover . find ( ".arrowAbove" ) . css ( { "margin-left" : arrowLeft } ) ;
1620+ }
1621+
1622+ // Add listeners
1623+ PopUpManager . addPopUp ( this . _$messagePopover , _clearMessagePopover , true ) ;
1624+ _addListeners ( ) ;
1625+
1626+ // Animate open
1627+ AnimationUtils . animateUsingClass ( this . _$messagePopover [ 0 ] , "animateOpen" ) . done ( function ( ) {
1628+ // Make sure we still have a popover
1629+ if ( self . _$messagePopover && self . _$messagePopover . length > 0 ) {
1630+ self . _$messagePopover . addClass ( "open" ) ;
1631+
1632+ // Don't add scroll listeners until open so we don't get event
1633+ // from scrolling cursor into view
1634+ $ ( self ) . on ( "scroll.msgbox" , _removeMessagePopover ) ;
1635+
1636+ // Animate closed -- which includes delay to show message
1637+ AnimationUtils . animateUsingClass ( self . _$messagePopover [ 0 ] , "animateClose" )
1638+ . done ( _removeMessagePopover ) ;
1639+ }
1640+ } ) ;
1641+ } ;
1642+
15211643 /**
15221644 * Returns the offset of the top of the virtual scroll area relative to the browser window (not the editor
15231645 * itself). Mainly useful for calculations related to scrollIntoView(), where you're starting with the
0 commit comments