1/* -*- js-indent-level: 8 -*- */ 2/* 3 * L.Control.StatusBar 4 */ 5 6/* global $ w2ui _ _UNO */ 7L.Control.StatusBar = L.Control.extend({ 8 9 initialize: function () { 10 }, 11 12 onAdd: function (map) { 13 this.map = map; 14 map.on('doclayerinit', this.onDocLayerInit, this); 15 map.on('commandvalues', this.onCommandValues, this); 16 map.on('commandstatechanged', this.onCommandStateChanged, this); 17 this.create(); 18 19 $(window).resize(function() { 20 if ($(window).width() !== map.getSize().x) { 21 var statusbar = w2ui['actionbar']; 22 statusbar.resize(); 23 } 24 }); 25 }, 26 27 hideTooltip: function(toolbar, id) { 28 if (toolbar.touchStarted) { 29 setTimeout(function() { 30 toolbar.tooltipHide(id, {}); 31 }, 5000); 32 toolbar.touchStarted = false; 33 } 34 }, 35 36 updateToolbarItem: function(toolbar, id, html) { 37 var item = toolbar.get(id); 38 if (item) { 39 item.html = html; 40 } 41 }, 42 43 localizeStateTableCell: function(text) { 44 var stateArray = text.split(';'); 45 var stateArrayLength = stateArray.length; 46 var localizedText = ''; 47 for (var i = 0; i < stateArrayLength; i++) { 48 var labelValuePair = stateArray[i].split(':'); 49 localizedText += _(labelValuePair[0].trim()) + ':' + labelValuePair[1]; 50 if (stateArrayLength > 1 && i < stateArrayLength - 1) { 51 localizedText += '; '; 52 } 53 } 54 return localizedText; 55 }, 56 57 toLocalePattern: function(pattern, regex, text, sub1, sub2) { 58 var matches = new RegExp(regex, 'g').exec(text); 59 if (matches) { 60 text = pattern.toLocaleString().replace(sub1, parseInt(matches[1].replace(',','')).toLocaleString(String.locale)).replace(sub2, parseInt(matches[2].replace(',','')).toLocaleString(String.locale)); 61 } 62 return text; 63 }, 64 65 _updateVisibilityForToolbar: function(toolbar) { 66 if (!toolbar) 67 return; 68 69 var toShow = []; 70 var toHide = []; 71 72 toolbar.items.forEach(function(item) { 73 if (window.ThisIsTheiOSApp && window.mode.isTablet() && item.iosapptablet === false) { 74 toHide.push(item.id); 75 } 76 else if (((window.mode.isMobile() && item.mobile === false) || (window.mode.isTablet() && item.tablet === false) || (window.mode.isDesktop() && item.desktop === false) || (!window.ThisIsAMobileApp && item.mobilebrowser === false)) && !item.hidden) { 77 toHide.push(item.id); 78 } 79 else if (((window.mode.isMobile() && item.mobile === true) || (window.mode.isTablet() && item.tablet === true) || (window.mode.isDesktop() && item.desktop === true) || (window.ThisIsAMobileApp && item.mobilebrowser === true)) && item.hidden) { 80 toShow.push(item.id); 81 } 82 }); 83 84 console.log('explicitly hiding: ' + toHide); 85 console.log('explicitly showing: ' + toShow); 86 87 toHide.forEach(function(item) { toolbar.hide(item); }); 88 toShow.forEach(function(item) { toolbar.show(item); }); 89 }, 90 91 _updateToolbarsVisibility: function() { 92 this._updateVisibilityForToolbar(w2ui['actionbar']); 93 }, 94 95 onClick: function(e, id, item, subItem) { 96 if ('actionbar' in w2ui && w2ui['actionbar'].get(id) !== null) { 97 var toolbar = w2ui['actionbar']; 98 item = toolbar.get(id); 99 } 100 101 // In the iOS app we don't want clicking on the toolbar to pop up the keyboard. 102 if (!window.ThisIsTheiOSApp && id !== 'zoomin' && id !== 'zoomout' && id !== 'mobile_wizard' && id !== 'insertion_mobile_wizard') { 103 this.map.focus(this.map.canAcceptKeyboardInput()); // Maintain same keyboard state. 104 } 105 106 if (item.disabled) { 107 return; 108 } 109 110 var docLayer = this.map._docLayer; 111 112 if (item.uno) { 113 if (item.unosheet && this.map.getDocType() === 'spreadsheet') { 114 this.map.toggleCommandState(item.unosheet); 115 } 116 else { 117 this.map.toggleCommandState(window.getUNOCommand(item.uno)); 118 } 119 } 120 else if (id === 'zoomin' && this.map.getZoom() < this.map.getMaxZoom()) { 121 this.map.zoomIn(1); 122 } 123 else if (id === 'zoomout' && this.map.getZoom() > this.map.getMinZoom()) { 124 this.map.zoomOut(1); 125 } 126 else if (item.scale) { 127 this.map.setZoom(item.scale); 128 } 129 else if (id === 'zoomreset') { 130 this.map.setZoom(this.map.options.zoom); 131 } 132 else if (id === 'prev' || id === 'next') { 133 if (docLayer._docType === 'text') { 134 this.map.goToPage(id); 135 } 136 else { 137 this.map.setPart(id); 138 } 139 } 140 else if (id === 'searchprev') { 141 this.map.search(L.DomUtil.get('search-input').value, true); 142 } 143 else if (id === 'searchnext') { 144 this.map.search(L.DomUtil.get('search-input').value); 145 } 146 else if (id === 'cancelsearch') { 147 this._cancelSearch(); 148 } 149 else if (id.startsWith('StateTableCellMenu') && subItem) { 150 e.done(function () { 151 var menu = w2ui['actionbar'].get('StateTableCellMenu'); 152 if (subItem.id === '1') { // 'None' was clicked, remove all other options 153 menu.selected = ['1']; 154 } 155 else { // Something else was clicked, remove the 'None' option from the array 156 var index = menu.selected.indexOf('1'); 157 if (index > -1) { 158 menu.selected.splice(index, 1); 159 } 160 } 161 var value = 0; 162 for (var it = 0; it < menu.selected.length; it++) { 163 value = +value + parseInt(menu.selected[it]); 164 } 165 var command = { 166 'StatusBarFunc': { 167 type: 'unsigned short', 168 value: value 169 } 170 }; 171 this.map.sendUnoCommand('.uno:StatusBarFunc', command); 172 }.bind(this)); 173 } 174 else if (id === 'userlist') { 175 this.map.fire('openuserlist'); 176 } 177 }, 178 179 create: function() { 180 var toolbar = $('#toolbar-down'); 181 var that = this; 182 183 if (!window.mode.isMobile()) { 184 toolbar.w2toolbar({ 185 name: 'actionbar', 186 items: [ 187 {type: 'html', id: 'search', 188 html: '<div style="padding: 3px 5px 3px 10px;" class="loleaflet-font">' + 189 '<input size="15" id="search-input" placeholder="' + _('Search') + '"' + 190 'style="padding: 3px; border-radius: 2px; border: 1px solid silver"/>' + 191 '</div>' 192 }, 193 {type: 'button', id: 'searchprev', img: 'prev', hint: _UNO('.uno:UpSearch'), disabled: true}, 194 {type: 'button', id: 'searchnext', img: 'next', hint: _UNO('.uno:DownSearch'), disabled: true}, 195 {type: 'button', id: 'cancelsearch', img: 'cancel', hint: _('Cancel the search'), hidden: true}, 196 {type: 'html', id: 'left'}, 197 {type: 'html', id: 'right'}, 198 {type: 'drop', id: 'userlist', img: 'users', hidden: true, html: L.control.createUserListWidget()}, 199 {type: 'break', id: 'userlistbreak', hidden: true, mobile: false }, 200 {type: 'button', id: 'prev', img: 'prev', hint: _UNO('.uno:PageUp', 'text')}, 201 {type: 'button', id: 'next', img: 'next', hint: _UNO('.uno:PageDown', 'text')}, 202 {type: 'break', id: 'prevnextbreak'}, 203 ].concat(window.mode.isTablet() ? [] : [ 204 {type: 'button', id: 'zoomreset', img: 'zoomreset', hint: _('Reset zoom')}, 205 {type: 'button', id: 'zoomout', img: 'zoomout', hint: _UNO('.uno:ZoomMinus')}, 206 {type: 'menu-radio', id: 'zoom', text: '100', 207 selected: 'zoom100', 208 mobile: false, 209 items: [ 210 { id: 'zoom20', text: '20', scale: 1}, 211 { id: 'zoom25', text: '25', scale: 2}, 212 { id: 'zoom30', text: '30', scale: 3}, 213 { id: 'zoom35', text: '35', scale: 4}, 214 { id: 'zoom40', text: '40', scale: 5}, 215 { id: 'zoom50', text: '50', scale: 6}, 216 { id: 'zoom60', text: '60', scale: 7}, 217 { id: 'zoom70', text: '70', scale: 8}, 218 { id: 'zoom85', text: '85', scale: 9}, 219 { id: 'zoom100', text: '100', scale: 10}, 220 { id: 'zoom120', text: '120', scale: 11}, 221 { id: 'zoom150', text: '150', scale: 12}, 222 { id: 'zoom175', text: '175', scale: 13}, 223 { id: 'zoom200', text: '200', scale: 14}, 224 { id: 'zoom235', text: '235', scale: 15}, 225 { id: 'zoom280', text: '280', scale: 16}, 226 { id: 'zoom335', text: '335', scale: 17}, 227 { id: 'zoom400', text: '400', scale: 18}, 228 ] 229 }, 230 {type: 'button', id: 'zoomin', img: 'zoomin', hint: _UNO('.uno:ZoomPlus')} 231 ]), 232 onClick: function (e) { 233 that.hideTooltip(this, e.target); 234 that.onClick(e, e.target, e.item, e.subItem); 235 }, 236 onRefresh: function() { 237 $('#tb_actionbar_item_userlist .w2ui-tb-caption').addClass('loleaflet-font'); 238 window.setupSearchInput(); 239 } 240 }); 241 if (window.mode.isDesktop()) 242 toolbar.tooltip(); 243 } 244 245 toolbar.bind('touchstart', function() { 246 w2ui['actionbar'].touchStarted = true; 247 }); 248 249 this.map.on('zoomend', function () { 250 var zoomPercent = 100; 251 var zoomSelected = null; 252 switch (that.map.getZoom()) { 253 case 1: zoomPercent = 20; zoomSelected = 'zoom20'; break; // 0.2102 254 case 2: zoomPercent = 25; zoomSelected = 'zoom25'; break; // 0.2500 255 case 3: zoomPercent = 30; zoomSelected = 'zoom30'; break; // 0.2973 256 case 4: zoomPercent = 35; zoomSelected = 'zoom35'; break; // 0.3535 257 case 5: zoomPercent = 40; zoomSelected = 'zoom40'; break; // 0.4204 258 case 6: zoomPercent = 50; zoomSelected = 'zoom50'; break; // 0.5 259 case 7: zoomPercent = 60; zoomSelected = 'zoom60'; break; // 0.5946 260 case 8: zoomPercent = 70; zoomSelected = 'zoom70'; break; // 0.7071 261 case 9: zoomPercent = 85; zoomSelected = 'zoom85'; break; // 0.8409 262 case 10: zoomPercent = 100; zoomSelected = 'zoom100'; break; // 1 263 case 11: zoomPercent = 120; zoomSelected = 'zoom120'; break; // 1.1892 264 // Why do we call this 150% even if it is actually closer to 140% 265 case 12: zoomPercent = 150; zoomSelected = 'zoom150'; break; // 1.4142 266 case 13: zoomPercent = 170; zoomSelected = 'zoom170'; break; // 1.6818 267 case 14: zoomPercent = 200; zoomSelected = 'zoom200'; break; // 2 268 case 15: zoomPercent = 235; zoomSelected = 'zoom235'; break; // 2.3784 269 case 16: zoomPercent = 280; zoomSelected = 'zoom280'; break; // 2.8284 270 case 17: zoomPercent = 335; zoomSelected = 'zoom335'; break; // 3.3636 271 case 18: zoomPercent = 400; zoomSelected = 'zoom400'; break; // 4 272 default: 273 var zoomRatio = that.map.getZoomScale(that.map.getZoom(), that.map.options.zoom); 274 zoomPercent = Math.round(zoomRatio * 100); 275 break; 276 } 277 w2ui['actionbar'].set('zoom', {text: zoomPercent, selected: zoomSelected}); 278 }); 279 }, 280 281 onDocLayerInit: function () { 282 var statusbar = w2ui['actionbar']; 283 var docType = this.map.getDocType(); 284 285 switch (docType) { 286 case 'spreadsheet': 287 if (statusbar) 288 statusbar.remove('prev', 'next', 'prevnextbreak'); 289 290 if (!window.mode.isMobile()) { 291 statusbar.insert('left', [ 292 {type: 'break', id: 'break1'}, 293 { 294 type: 'html', id: 'StatusDocPos', 295 html: '<div id="StatusDocPos" class="loleaflet-font" title="' + _('Number of Sheets') + '" style="padding: 5px 5px;">  </div>' 296 }, 297 {type: 'break', id: 'break2'}, 298 { 299 type: 'html', id: 'RowColSelCount', 300 html: '<div id="RowColSelCount" class="loleaflet-font" title="' + _('Selected range of cells') + '" style="padding: 5px 5px;line-height:0;">  </div>' 301 }, 302 {type: 'break', id: 'break3', tablet: false}, 303 { 304 type: 'html', id: 'InsertMode', mobile: false, tablet: false, 305 html: '<div id="InsertMode" class="loleaflet-font" title="' + _('Entering text mode') + '" style="padding: 5px 5px;">  </div>' 306 }, 307 {type: 'break', id: 'break4', tablet: false}, 308 {type: 'menu-radio', id: 'LanguageStatus', 309 mobile: false 310 }, 311 {type: 'break', id: 'break5', tablet: false}, 312 { 313 type: 'html', id: 'StatusSelectionMode', mobile: false, tablet: false, 314 html: '<div id="StatusSelectionMode" class="loleaflet-font" title="' + _('Selection Mode') + '" style="padding: 5px 5px;">  </div>' 315 }, 316 {type: 'break', id: 'break8', mobile: false, tablet: false}, 317 { 318 type: 'html', id: 'StateTableCell', mobile: false, tablet: false, 319 html: '<div id="StateTableCell" class="loleaflet-font" title="' + _('Choice of functions') + '" style="padding: 5px 5px;">  </div>' 320 }, 321 { 322 type: 'menu-check', id: 'StateTableCellMenu', caption: '', selected: ['2', '512'], items: [ 323 {id: '2', text: _('Average')}, 324 {id: '8', text: _('CountA')}, 325 {id: '4', text: _('Count')}, 326 {id: '16', text: _('Maximum')}, 327 {id: '32', text: _('Minimum')}, 328 {id: '512', text: _('Sum')}, 329 {id: '8192', text: _('Selection count')}, 330 {id: '1', text: _('None')} 331 ], tablet: false 332 }, 333 {type: 'break', id: 'break9', mobile: false} 334 ]); 335 } 336 break; 337 338 case 'text': 339 if (!window.mode.isMobile()) { 340 statusbar.insert('left', [ 341 {type: 'break', id: 'break1'}, 342 { 343 type: 'html', id: 'StatePageNumber', 344 html: '<div id="StatePageNumber" class="loleaflet-font" title="' + _('Number of Pages') + '" style="padding: 5px 5px;">  </div>' 345 }, 346 {type: 'break', id: 'break2'}, 347 { 348 type: 'html', id: 'StateWordCount', mobile: false, tablet: false, 349 html: '<div id="StateWordCount" class="loleaflet-font" title="' + _('Word Counter') + '" style="padding: 5px 5px;">  </div>' 350 }, 351 {type: 'break', id: 'break5', mobile: false, tablet: false}, 352 { 353 type: 'html', id: 'InsertMode', mobile: false, tablet: false, 354 html: '<div id="InsertMode" class="loleaflet-font" title="' + _('Entering text mode') + '" style="padding: 5px 5px;">  </div>' 355 }, 356 {type: 'break', id: 'break6', mobile: false, tablet: false}, 357 { 358 type: 'html', id: 'StatusSelectionMode', mobile: false, tablet: false, 359 html: '<div id="StatusSelectionMode" class="loleaflet-font" title="' + _('Selection Mode') + '" style="padding: 5px 5px;">  </div>' 360 }, 361 {type: 'break', id: 'break7', mobile: false, tablet: false}, 362 {type: 'menu-radio', id: 'LanguageStatus', 363 mobile: false 364 }, 365 {type: 'break', id: 'break8', mobile: false} 366 ]); 367 } 368 break; 369 370 case 'presentation': 371 if (!window.mode.isMobile()) { 372 statusbar.insert('left', [ 373 {type: 'break', id: 'break1'}, 374 { 375 type: 'html', id: 'PageStatus', 376 html: '<div id="PageStatus" class="loleaflet-font" title="' + _('Number of Slides') + '" style="padding: 5px 5px;">  </div>' 377 }, 378 {type: 'break', id: 'break2', mobile: false, tablet: false}, 379 {type: 'menu-radio', id: 'LanguageStatus', 380 mobile: false 381 }, 382 {type: 'break', id: 'break8', mobile: false} 383 ]); 384 } 385 386 // FALLTHROUGH intended 387 case 'drawing': 388 if (statusbar) 389 statusbar.show('prev', 'next'); 390 break; 391 } 392 393 this.map.fire('updateuserlistcount'); 394 395 this._updateToolbarsVisibility(); 396 397 if (statusbar) 398 statusbar.refresh(); 399 400 var showStatusbar = this.map.uiManager.getSavedStateOrDefault('ShowStatusbar'); 401 if (showStatusbar) 402 $('#toolbar-down').show(); 403 else 404 this.map.uiManager.hideStatusBar(true); 405 }, 406 407 _cancelSearch: function() { 408 var toolbar = window.mode.isMobile() ? w2ui['searchbar'] : w2ui['actionbar']; 409 var searchInput = L.DomUtil.get('search-input'); 410 this.map.resetSelection(); 411 toolbar.hide('cancelsearch'); 412 toolbar.disable('searchprev'); 413 toolbar.disable('searchnext'); 414 searchInput.value = ''; 415 if (window.mode.isMobile()) { 416 searchInput.focus(); 417 // odd, but on mobile we need to invoke it twice 418 toolbar.hide('cancelsearch'); 419 } 420 421 this.map._onGotFocus(); 422 }, 423 424 onCommandStateChanged: function(e) { 425 var statusbar = w2ui['actionbar']; 426 var commandName = e.commandName; 427 var state = e.state; 428 429 if (!commandName) 430 return; 431 432 if (commandName === '.uno:StatusDocPos') { 433 state = this.toLocalePattern('Sheet %1 of %2', 'Sheet (\\d+) of (\\d+)', state, '%1', '%2'); 434 this.updateToolbarItem(statusbar, 'StatusDocPos', $('#StatusDocPos').html(state ? state : '  ').parent().html()); 435 } 436 else if (commandName === '.uno:LanguageStatus') { 437 var code = state; 438 var language = _(state); 439 var split = code.split(';'); 440 if (split.length > 1) { 441 language = _(split[0]); 442 code = split[1]; 443 } 444 w2ui['actionbar'].set('LanguageStatus', {text: language, selected: language}); 445 } 446 else if (commandName === '.uno:RowColSelCount') { 447 state = this.toLocalePattern('$1 rows, $2 columns selected', '(\\d+) rows, (\\d+) columns selected', state, '$1', '$2'); 448 state = this.toLocalePattern('$1 of $2 records found', '(\\d+) of (\\d+) records found', state, '$1', '$2'); 449 this.updateToolbarItem(statusbar, 'RowColSelCount', $('#RowColSelCount').html(state ? state : '<span class="ToolbarStatusInactive"> Select multiple cells </span>').parent().html()); 450 } 451 else if (commandName === '.uno:InsertMode') { 452 this.updateToolbarItem(statusbar, 'InsertMode', $('#InsertMode').html(state ? L.Styles.insertMode[state].toLocaleString() : '<span class="ToolbarStatusInactive"> Insert mode: inactive </span>').parent().html()); 453 454 if (!state && this.map.hyperlinkPopup) { 455 this.map.hyperlinkUnderCursor = null; 456 this.map.closePopup(this.map.hyperlinkPopup); 457 this.map.hyperlinkPopup = null; 458 } 459 } 460 else if (commandName === '.uno:StatusSelectionMode' || 461 commandName === '.uno:SelectionMode') { 462 this.updateToolbarItem(statusbar, 'StatusSelectionMode', $('#StatusSelectionMode').html(state ? L.Styles.selectionMode[state].toLocaleString() : '<span class="ToolbarStatusInactive"> Selection mode: inactive </span>').parent().html()); 463 } 464 else if (commandName == '.uno:StateTableCell') { 465 this.updateToolbarItem(statusbar, 'StateTableCell', $('#StateTableCell').html(state ? this.localizeStateTableCell(state) : '  ').parent().html()); 466 } 467 else if (commandName === '.uno:StatusBarFunc') { 468 var item = statusbar.get('StateTableCellMenu'); 469 if (item) { 470 item.selected = []; 471 // Check 'None' even when state is 0 472 if (state === '0') { 473 state = 1; 474 } 475 for (var it = 0; it < item.items.length; it++) { 476 if (item.items[it].id & state) { 477 item.selected.push(item.items[it].id); 478 } 479 } 480 } 481 } 482 else if (commandName === '.uno:StatePageNumber') { 483 state = this.toLocalePattern('Page %1 of %2', 'Page (\\d+) of (\\d+)', state, '%1', '%2'); 484 this.updateToolbarItem(statusbar, 'StatePageNumber', $('#StatePageNumber').html(state ? state : '  ').parent().html()); 485 } 486 else if (commandName === '.uno:StateWordCount') { 487 state = this.toLocalePattern('%1 words, %2 characters', '([\\d,]+) words, ([\\d,]+) characters', state, '%1', '%2'); 488 this.updateToolbarItem(statusbar, 'StateWordCount', $('#StateWordCount').html(state ? state : '  ').parent().html()); 489 } 490 else if (commandName === '.uno:PageStatus') { 491 state = this.toLocalePattern('Slide %1 of %2', 'Slide (\\d+) of (\\d+)', state, '%1', '%2'); 492 this.updateToolbarItem(statusbar, 'PageStatus', $('#PageStatus').html(state ? state : '  ').parent().html()); 493 } 494 }, 495 496 onCommandValues: function(e) { 497 if (e.commandName === '.uno:LanguageStatus' && L.Util.isArray(e.commandValues)) { 498 var translated, neutral; 499 var constLang = '.uno:LanguageStatus?Language:string='; 500 var constDefault = 'Default_RESET_LANGUAGES'; 501 var constNone = 'Default_LANGUAGE_NONE'; 502 var resetLang = _('Reset to Default Language'); 503 var noneLang = _('None (Do not check spelling)'); 504 var languages = []; 505 e.commandValues.forEach(function (language) { 506 languages.push({ translated: _(language.split(';')[0]), neutral: language }); 507 }); 508 languages.sort(function (a, b) { 509 return a.translated < b.translated ? -1 : a.translated > b.translated ? 1 : 0; 510 }); 511 512 var toolbaritems = []; 513 toolbaritems.push({ text: noneLang, 514 id: 'nonelanguage', 515 uno: constLang + constNone }); 516 517 518 for (var lang in languages) { 519 translated = languages[lang].translated; 520 neutral = languages[lang].neutral; 521 var splitNeutral = neutral.split(';'); 522 toolbaritems.push({ id: neutral, text: translated, uno: constLang + encodeURIComponent('Default_' + splitNeutral[0]) }); 523 } 524 525 toolbaritems.push({ id: 'reset', text: resetLang, uno: constLang + constDefault }); 526 527 w2ui['actionbar'].set('LanguageStatus', {items: toolbaritems}); 528 } 529 }, 530}); 531 532L.control.statusBar = function () { 533 return new L.Control.StatusBar(); 534}; 535
