1 /* 2 * Copyright (C) 2013 Google Inc. All rights reserved. 3 * 4 * Redistribution and use in source and binary forms, with or without 5 * modification, are permitted provided that the following conditions are 6 * met: 7 * 8 * * Redistributions of source code must retain the above copyright 9 * notice, this list of conditions and the following disclaimer. 10 * * Redistributions in binary form must reproduce the above 11 * copyright notice, this list of conditions and the following disclaimer 12 * in the documentation and/or other materials provided with the 13 * distribution. 14 * * Neither the name of Google Inc. nor the names of its 15 * contributors may be used to endorse or promote products derived from 16 * this software without specific prior written permission. 17 * 18 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS 19 * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT 20 * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR 21 * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT 22 * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, 23 * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT 24 * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, 25 * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY 26 * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 27 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 28 * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 29 */ 30 31 /** 32 * @constructor 33 * @param {!function()} onHide 34 * @extends {WebInspector.HelpScreen} 35 */ 36 WebInspector.SettingsScreen = function(onHide) 37 { 38 WebInspector.HelpScreen.call(this); 39 this.element.id = "settings-screen"; 40 41 /** @type {function()} */ 42 this._onHide = onHide; 43 44 this._tabbedPane = new WebInspector.TabbedPane(); 45 this._tabbedPane.element.classList.add("help-window-main"); 46 var settingsLabelElement = document.createElementWithClass("div", "help-window-label"); 47 settingsLabelElement.createTextChild(WebInspector.UIString("Settings")); 48 this._tabbedPane.element.insertBefore(settingsLabelElement, this._tabbedPane.element.firstChild); 49 this._tabbedPane.element.appendChild(this._createCloseButton()); 50 this._tabbedPane.appendTab(WebInspector.SettingsScreen.Tabs.General, WebInspector.UIString("General"), new WebInspector.GenericSettingsTab()); 51 this._tabbedPane.appendTab(WebInspector.SettingsScreen.Tabs.Workspace, WebInspector.UIString("Workspace"), new WebInspector.WorkspaceSettingsTab()); 52 if (Runtime.experiments.supportEnabled()) 53 this._tabbedPane.appendTab(WebInspector.SettingsScreen.Tabs.Experiments, WebInspector.UIString("Experiments"), new WebInspector.ExperimentsSettingsTab()); 54 this._tabbedPane.appendTab(WebInspector.SettingsScreen.Tabs.Shortcuts, WebInspector.UIString("Shortcuts"), WebInspector.shortcutsScreen.createShortcutsTabView()); 55 this._tabbedPane.shrinkableTabs = false; 56 this._tabbedPane.verticalTabLayout = true; 57 58 this._lastSelectedTabSetting = WebInspector.settings.createSetting("lastSelectedSettingsTab", WebInspector.SettingsScreen.Tabs.General); 59 this.selectTab(this._lastSelectedTabSetting.get()); 60 this._tabbedPane.addEventListener(WebInspector.TabbedPane.EventTypes.TabSelected, this._tabSelected, this); 61 this.element.addEventListener("keydown", this._keyDown.bind(this), false); 62 this._developerModeCounter = 0; 63 } 64 65 /** 66 * @param {number} min 67 * @param {number} max 68 * @param {string} text 69 * @return {?string} 70 */ 71 WebInspector.SettingsScreen.integerValidator = function(min, max, text) 72 { 73 var value = Number(text); 74 if (isNaN(value)) 75 return WebInspector.UIString("Invalid number format"); 76 if (value < min || value > max) 77 return WebInspector.UIString("Value is out of range [%d, %d]", min, max); 78 return null; 79 } 80 81 WebInspector.SettingsScreen.Tabs = { 82 General: "general", 83 Overrides: "overrides", 84 Workspace: "workspace", 85 Experiments: "experiments", 86 Shortcuts: "shortcuts" 87 } 88 89 WebInspector.SettingsScreen.prototype = { 90 /** 91 * @param {string} tabId 92 */ 93 selectTab: function(tabId) 94 { 95 this._tabbedPane.selectTab(tabId); 96 }, 97 98 /** 99 * @param {!WebInspector.Event} event 100 */ 101 _tabSelected: function(event) 102 { 103 this._lastSelectedTabSetting.set(this._tabbedPane.selectedTabId); 104 }, 105 106 /** 107 * @override 108 */ 109 wasShown: function() 110 { 111 this._tabbedPane.show(this.element); 112 WebInspector.HelpScreen.prototype.wasShown.call(this); 113 }, 114 115 /** 116 * @override 117 * @return {boolean} 118 */ 119 isClosingKey: function(keyCode) 120 { 121 return [ 122 WebInspector.KeyboardShortcut.Keys.Enter.code, 123 WebInspector.KeyboardShortcut.Keys.Esc.code, 124 ].indexOf(keyCode) >= 0; 125 }, 126 127 /** 128 * @override 129 */ 130 willHide: function() 131 { 132 this._onHide(); 133 WebInspector.HelpScreen.prototype.willHide.call(this); 134 }, 135 136 /** 137 * @param {!Event} event 138 */ 139 _keyDown: function(event) 140 { 141 var shiftKeyCode = 16; 142 if (event.keyCode === shiftKeyCode && ++this._developerModeCounter > 5) 143 this.element.classList.add("settings-developer-mode"); 144 }, 145 146 __proto__: WebInspector.HelpScreen.prototype 147 } 148 149 /** 150 * @constructor 151 * @extends {WebInspector.VBox} 152 * @param {string} name 153 * @param {string=} id 154 */ 155 WebInspector.SettingsTab = function(name, id) 156 { 157 WebInspector.VBox.call(this); 158 this.element.classList.add("settings-tab-container"); 159 if (id) 160 this.element.id = id; 161 var header = this.element.createChild("header"); 162 header.createChild("h3").createTextChild(name); 163 this.containerElement = this.element.createChild("div", "help-container-wrapper").createChild("div", "settings-tab help-content help-container"); 164 } 165 166 WebInspector.SettingsTab.prototype = { 167 /** 168 * @param {string=} name 169 * @return {!Element} 170 */ 171 _appendSection: function(name) 172 { 173 var block = this.containerElement.createChild("div", "help-block"); 174 if (name) 175 block.createChild("div", "help-section-title").textContent = name; 176 return block; 177 }, 178 179 _createSelectSetting: function(name, options, setting) 180 { 181 var p = document.createElement("p"); 182 p.createChild("label").textContent = name; 183 184 var select = p.createChild("select", "chrome-select"); 185 var settingValue = setting.get(); 186 187 for (var i = 0; i < options.length; ++i) { 188 var option = options[i]; 189 select.add(new Option(option[0], option[1])); 190 if (settingValue === option[1]) 191 select.selectedIndex = i; 192 } 193 194 function changeListener(e) 195 { 196 // Don't use e.target.value to avoid conversion of the value to string. 197 setting.set(options[select.selectedIndex][1]); 198 } 199 200 select.addEventListener("change", changeListener, false); 201 return p; 202 }, 203 204 __proto__: WebInspector.VBox.prototype 205 } 206 207 /** 208 * @constructor 209 * @extends {WebInspector.SettingsTab} 210 */ 211 WebInspector.GenericSettingsTab = function() 212 { 213 WebInspector.SettingsTab.call(this, WebInspector.UIString("General"), "general-tab-content"); 214 215 this._populateSectionsFromExtensions(); 216 217 var restoreDefaults = this._appendSection().createChild("input", "text-button"); 218 restoreDefaults.type = "button"; 219 restoreDefaults.value = WebInspector.UIString("Restore defaults and reload"); 220 restoreDefaults.addEventListener("click", restoreAndReload, false); 221 222 function restoreAndReload() 223 { 224 if (window.localStorage) 225 window.localStorage.clear(); 226 WebInspector.reload(); 227 } 228 } 229 230 WebInspector.GenericSettingsTab.prototype = { 231 _populateSectionsFromExtensions: function() 232 { 233 /** @const */ 234 var explicitSectionOrder = ["", "Appearance", "Elements", "Sources", "Profiler", "Console", "Extensions"]; 235 236 var allExtensions = self.runtime.extensions("ui-setting"); 237 238 /** @type {!StringMultimap.<!Runtime.Extension>} */ 239 var extensionsBySectionId = new StringMultimap(); 240 /** @type {!StringMultimap.<!Runtime.Extension>} */ 241 var childSettingExtensionsByParentName = new StringMultimap(); 242 243 allExtensions.forEach(function(extension) { 244 var descriptor = extension.descriptor(); 245 var sectionName = descriptor["section"] || ""; 246 if (!sectionName && descriptor["parentSettingName"]) { 247 childSettingExtensionsByParentName.set(descriptor["parentSettingName"], extension); 248 return; 249 } 250 extensionsBySectionId.set(sectionName, extension); 251 }); 252 253 var sectionIds = extensionsBySectionId.keys(); 254 var explicitlyOrderedSections = explicitSectionOrder.keySet(); 255 for (var i = 0; i < explicitSectionOrder.length; ++i) { 256 var extensions = extensionsBySectionId.get(explicitSectionOrder[i]); 257 if (!extensions.size()) 258 continue; 259 this._addSectionWithExtensionProvidedSettings(explicitSectionOrder[i], extensions.values(), childSettingExtensionsByParentName); 260 } 261 for (var i = 0; i < sectionIds.length; ++i) { 262 if (explicitlyOrderedSections[sectionIds[i]]) 263 continue; 264 this._addSectionWithExtensionProvidedSettings(sectionIds[i], extensionsBySectionId.get(sectionIds[i]).values(), childSettingExtensionsByParentName); 265 } 266 }, 267 268 /** 269 * @param {string} sectionName 270 * @param {!Array.<!Runtime.Extension>} extensions 271 * @param {!StringMultimap.<!Runtime.Extension>} childSettingExtensionsByParentName 272 */ 273 _addSectionWithExtensionProvidedSettings: function(sectionName, extensions, childSettingExtensionsByParentName) 274 { 275 var uiSectionName = sectionName && WebInspector.UIString(sectionName); 276 var sectionElement = this._appendSection(uiSectionName); 277 extensions.forEach(processSetting.bind(this, null)); 278 279 /** 280 * @param {?Element} parentFieldset 281 * @param {!Runtime.Extension} extension 282 * @this {WebInspector.GenericSettingsTab} 283 */ 284 function processSetting(parentFieldset, extension) 285 { 286 var descriptor = extension.descriptor(); 287 var experimentName = descriptor["experiment"]; 288 if (experimentName && !Runtime.experiments.isEnabled(experimentName)) 289 return; 290 291 var settingName = descriptor["settingName"]; 292 var setting = WebInspector.settings[settingName]; 293 var instance = extension.instance(); 294 var settingControl; 295 if (instance && descriptor["settingType"] === "custom") { 296 settingControl = instance.settingElement(); 297 if (!settingControl) 298 return; 299 } 300 if (!settingControl) { 301 var uiTitle = WebInspector.UIString(descriptor["title"]); 302 settingControl = createSettingControl.call(this, uiTitle, setting, descriptor, instance); 303 } 304 if (settingName) { 305 var childSettings = childSettingExtensionsByParentName.get(settingName); 306 if (childSettings.size()) { 307 var fieldSet = WebInspector.SettingsUI.createSettingFieldset(setting); 308 settingControl.appendChild(fieldSet); 309 childSettings.values().forEach(function(item) { processSetting.call(this, fieldSet, item); }, this); 310 } 311 } 312 var containerElement = parentFieldset || sectionElement; 313 containerElement.appendChild(settingControl); 314 } 315 316 /** 317 * @param {string} uiTitle 318 * @param {!WebInspector.Setting} setting 319 * @param {!Object} descriptor 320 * @param {?Object} instance 321 * @return {!Element} 322 * @this {WebInspector.GenericSettingsTab} 323 */ 324 function createSettingControl(uiTitle, setting, descriptor, instance) 325 { 326 switch (descriptor["settingType"]) { 327 case "checkbox": 328 return WebInspector.SettingsUI.createSettingCheckbox(uiTitle, setting); 329 case "select": 330 var descriptorOptions = descriptor["options"] 331 var options = new Array(descriptorOptions.length); 332 for (var i = 0; i < options.length; ++i) { 333 // The third array item flags that the option name is "raw" (non-i18n-izable). 334 var optionName = descriptorOptions[i][2] ? descriptorOptions[i][0] : WebInspector.UIString(descriptorOptions[i][0]); 335 options[i] = [WebInspector.UIString(descriptorOptions[i][0]), descriptorOptions[i][1]]; 336 } 337 return this._createSelectSetting(uiTitle, options, setting); 338 default: 339 throw "Invalid setting type: " + descriptor["settingType"]; 340 } 341 } 342 }, 343 344 __proto__: WebInspector.SettingsTab.prototype 345 } 346 347 /** 348 * @constructor 349 * @extends {WebInspector.UISettingDelegate} 350 */ 351 WebInspector.SettingsScreen.SkipStackFramePatternSettingDelegate = function() 352 { 353 WebInspector.UISettingDelegate.call(this); 354 } 355 356 WebInspector.SettingsScreen.SkipStackFramePatternSettingDelegate.prototype = { 357 /** 358 * @override 359 * @return {!Element} 360 */ 361 settingElement: function() 362 { 363 var button = document.createElementWithClass("input", "text-button"); 364 button.type = "button"; 365 button.value = WebInspector.manageBlackboxingButtonLabel(); 366 button.title = WebInspector.UIString("Skip stepping through sources with particular names"); 367 button.addEventListener("click", this._onManageButtonClick.bind(this), false); 368 return button; 369 }, 370 371 _onManageButtonClick: function() 372 { 373 WebInspector.FrameworkBlackboxDialog.show(WebInspector.inspectorView.element); 374 }, 375 376 __proto__: WebInspector.UISettingDelegate.prototype 377 } 378 379 /** 380 * @constructor 381 * @extends {WebInspector.SettingsTab} 382 */ 383 WebInspector.WorkspaceSettingsTab = function() 384 { 385 WebInspector.SettingsTab.call(this, WebInspector.UIString("Workspace"), "workspace-tab-content"); 386 WebInspector.isolatedFileSystemManager.addEventListener(WebInspector.IsolatedFileSystemManager.Events.FileSystemAdded, this._fileSystemAdded, this); 387 WebInspector.isolatedFileSystemManager.addEventListener(WebInspector.IsolatedFileSystemManager.Events.FileSystemRemoved, this._fileSystemRemoved, this); 388 389 this._commonSection = this._appendSection(WebInspector.UIString("Common")); 390 var folderExcludePatternInput = WebInspector.SettingsUI.createSettingInputField(WebInspector.UIString("Folder exclude pattern"), WebInspector.settings.workspaceFolderExcludePattern, false, 0, "270px", WebInspector.SettingsUI.regexValidator); 391 this._commonSection.appendChild(folderExcludePatternInput); 392 393 this._fileSystemsSection = this._appendSection(WebInspector.UIString("Folders")); 394 this._fileSystemsListContainer = this._fileSystemsSection.createChild("p", "settings-list-container"); 395 396 this._addFileSystemRowElement = this._fileSystemsSection.createChild("div"); 397 var addFileSystemButton = this._addFileSystemRowElement.createChild("input", "text-button"); 398 addFileSystemButton.type = "button"; 399 addFileSystemButton.value = WebInspector.UIString("Add folder\u2026"); 400 addFileSystemButton.addEventListener("click", this._addFileSystemClicked.bind(this), false); 401 402 this._editFileSystemButton = this._addFileSystemRowElement.createChild("input", "text-button"); 403 this._editFileSystemButton.type = "button"; 404 this._editFileSystemButton.value = WebInspector.UIString("Folder options\u2026"); 405 this._editFileSystemButton.addEventListener("click", this._editFileSystemClicked.bind(this), false); 406 this._updateEditFileSystemButtonState(); 407 408 this._reset(); 409 } 410 411 WebInspector.WorkspaceSettingsTab.prototype = { 412 wasShown: function() 413 { 414 WebInspector.SettingsTab.prototype.wasShown.call(this); 415 this._reset(); 416 }, 417 418 _reset: function() 419 { 420 this._resetFileSystems(); 421 }, 422 423 _resetFileSystems: function() 424 { 425 this._fileSystemsListContainer.removeChildren(); 426 var fileSystemPaths = WebInspector.isolatedFileSystemManager.mapping().fileSystemPaths(); 427 delete this._fileSystemsList; 428 429 if (!fileSystemPaths.length) { 430 var noFileSystemsMessageElement = this._fileSystemsListContainer.createChild("div", "no-file-systems-message"); 431 noFileSystemsMessageElement.textContent = WebInspector.UIString("You have no file systems added."); 432 return; 433 } 434 435 this._fileSystemsList = new WebInspector.SettingsList([{ id: "path" }], this._renderFileSystem.bind(this)); 436 this._fileSystemsList.element.classList.add("file-systems-list"); 437 this._fileSystemsList.addEventListener(WebInspector.SettingsList.Events.Selected, this._fileSystemSelected.bind(this)); 438 this._fileSystemsList.addEventListener(WebInspector.SettingsList.Events.Removed, this._fileSystemRemovedfromList.bind(this)); 439 this._fileSystemsList.addEventListener(WebInspector.SettingsList.Events.DoubleClicked, this._fileSystemDoubleClicked.bind(this)); 440 this._fileSystemsListContainer.appendChild(this._fileSystemsList.element); 441 for (var i = 0; i < fileSystemPaths.length; ++i) 442 this._fileSystemsList.addItem(fileSystemPaths[i]); 443 this._updateEditFileSystemButtonState(); 444 }, 445 446 _updateEditFileSystemButtonState: function() 447 { 448 this._editFileSystemButton.disabled = !this._selectedFileSystemPath(); 449 }, 450 451 /** 452 * @param {!WebInspector.Event} event 453 */ 454 _fileSystemSelected: function(event) 455 { 456 this._updateEditFileSystemButtonState(); 457 }, 458 459 /** 460 * @param {!WebInspector.Event} event 461 */ 462 _fileSystemDoubleClicked: function(event) 463 { 464 var id = /** @type{?string} */ (event.data); 465 this._editFileSystem(id); 466 }, 467 468 _editFileSystemClicked: function() 469 { 470 this._editFileSystem(this._selectedFileSystemPath()); 471 }, 472 473 /** 474 * @param {?string} id 475 */ 476 _editFileSystem: function(id) 477 { 478 WebInspector.EditFileSystemDialog.show(WebInspector.inspectorView.element, id); 479 }, 480 481 /** 482 * @param {!Element} columnElement 483 * @param {{id: string, placeholder: (string|undefined), options: (!Array.<string>|undefined)}} column 484 * @param {?string} id 485 */ 486 _renderFileSystem: function(columnElement, column, id) 487 { 488 if (!id) 489 return ""; 490 var fileSystemPath = id; 491 var textElement = columnElement.createChild("span", "list-column-text"); 492 var pathElement = textElement.createChild("span", "file-system-path"); 493 pathElement.title = fileSystemPath; 494 495 const maxTotalPathLength = 55; 496 const maxFolderNameLength = 30; 497 498 var lastIndexOfSlash = fileSystemPath.lastIndexOf(WebInspector.isWin() ? "\\" : "/"); 499 var folderName = fileSystemPath.substr(lastIndexOfSlash + 1); 500 var folderPath = fileSystemPath.substr(0, lastIndexOfSlash + 1); 501 folderPath = folderPath.trimMiddle(maxTotalPathLength - Math.min(maxFolderNameLength, folderName.length)); 502 folderName = folderName.trimMiddle(maxFolderNameLength); 503 504 var folderPathElement = pathElement.createChild("span"); 505 folderPathElement.textContent = folderPath; 506 507 var nameElement = pathElement.createChild("span", "file-system-path-name"); 508 nameElement.textContent = folderName; 509 }, 510 511 /** 512 * @param {!WebInspector.Event} event 513 */ 514 _fileSystemRemovedfromList: function(event) 515 { 516 var id = /** @type{?string} */ (event.data); 517 if (!id) 518 return; 519 WebInspector.isolatedFileSystemManager.removeFileSystem(id); 520 }, 521 522 _addFileSystemClicked: function() 523 { 524 WebInspector.isolatedFileSystemManager.addFileSystem(); 525 }, 526 527 _fileSystemAdded: function(event) 528 { 529 var fileSystem = /** @type {!WebInspector.IsolatedFileSystem} */ (event.data); 530 if (!this._fileSystemsList) 531 this._reset(); 532 else 533 this._fileSystemsList.addItem(fileSystem.path()); 534 }, 535 536 _fileSystemRemoved: function(event) 537 { 538 var fileSystem = /** @type {!WebInspector.IsolatedFileSystem} */ (event.data); 539 var selectedFileSystemPath = this._selectedFileSystemPath(); 540 if (this._fileSystemsList.itemForId(fileSystem.path())) 541 this._fileSystemsList.removeItem(fileSystem.path()); 542 if (!this._fileSystemsList.itemIds().length) 543 this._reset(); 544 this._updateEditFileSystemButtonState(); 545 }, 546 547 _selectedFileSystemPath: function() 548 { 549 return this._fileSystemsList ? this._fileSystemsList.selectedId() : null; 550 }, 551 552 __proto__: WebInspector.SettingsTab.prototype 553 } 554 555 556 /** 557 * @constructor 558 * @extends {WebInspector.SettingsTab} 559 */ 560 WebInspector.ExperimentsSettingsTab = function() 561 { 562 WebInspector.SettingsTab.call(this, WebInspector.UIString("Experiments"), "experiments-tab-content"); 563 564 var experiments = Runtime.experiments.allExperiments(); 565 if (experiments.length) { 566 var experimentsSection = this._appendSection(); 567 experimentsSection.appendChild(this._createExperimentsWarningSubsection()); 568 for (var i = 0; i < experiments.length; ++i) 569 experimentsSection.appendChild(this._createExperimentCheckbox(experiments[i])); 570 } 571 } 572 573 WebInspector.ExperimentsSettingsTab.prototype = { 574 /** 575 * @return {!Element} element 576 */ 577 _createExperimentsWarningSubsection: function() 578 { 579 var subsection = document.createElement("div"); 580 var warning = subsection.createChild("span", "settings-experiments-warning-subsection-warning"); 581 warning.textContent = WebInspector.UIString("WARNING:"); 582 subsection.createTextChild(" "); 583 var message = subsection.createChild("span", "settings-experiments-warning-subsection-message"); 584 message.textContent = WebInspector.UIString("These experiments could be dangerous and may require restart."); 585 return subsection; 586 }, 587 588 _createExperimentCheckbox: function(experiment) 589 { 590 var input = document.createElement("input"); 591 input.type = "checkbox"; 592 input.name = experiment.name; 593 input.checked = experiment.isEnabled(); 594 function listener() 595 { 596 experiment.setEnabled(input.checked); 597 } 598 input.addEventListener("click", listener, false); 599 600 var p = document.createElement("p"); 601 p.className = experiment.hidden && !experiment.isEnabled() ? "settings-experiment-hidden" : ""; 602 var label = p.createChild("label"); 603 label.appendChild(input); 604 label.createTextChild(WebInspector.UIString(experiment.title)); 605 p.appendChild(label); 606 return p; 607 }, 608 609 __proto__: WebInspector.SettingsTab.prototype 610 } 611 612 /** 613 * @constructor 614 */ 615 WebInspector.SettingsController = function() 616 { 617 /** @type {?WebInspector.SettingsScreen} */ 618 this._settingsScreen; 619 620 window.addEventListener("resize", this._resize.bind(this), false); 621 } 622 623 WebInspector.SettingsController.prototype = { 624 _onHideSettingsScreen: function() 625 { 626 delete this._settingsScreenVisible; 627 }, 628 629 /** 630 * @param {string=} tabId 631 */ 632 showSettingsScreen: function(tabId) 633 { 634 if (!this._settingsScreen) 635 this._settingsScreen = new WebInspector.SettingsScreen(this._onHideSettingsScreen.bind(this)); 636 637 if (tabId) 638 this._settingsScreen.selectTab(tabId); 639 640 this._settingsScreen.showModal(); 641 this._settingsScreenVisible = true; 642 }, 643 644 _resize: function() 645 { 646 if (this._settingsScreen && this._settingsScreen.isShowing()) 647 this._settingsScreen.doResize(); 648 } 649 } 650 651 /** 652 * @constructor 653 * @implements {WebInspector.ActionDelegate} 654 */ 655 WebInspector.SettingsController.SettingsScreenActionDelegate = function() { } 656 657 WebInspector.SettingsController.SettingsScreenActionDelegate.prototype = { 658 /** 659 * @return {boolean} 660 */ 661 handleAction: function() 662 { 663 WebInspector._settingsController.showSettingsScreen(WebInspector.SettingsScreen.Tabs.General); 664 return true; 665 } 666 } 667 668 /** 669 * @constructor 670 * @extends {WebInspector.Object} 671 * @param {!Array.<{id: string, placeholder: (string|undefined), options: (!Array.<string>|undefined)}>} columns 672 * @param {function(!Element, {id: string, placeholder: (string|undefined), options: (!Array.<string>|undefined)}, ?string)} itemRenderer 673 */ 674 WebInspector.SettingsList = function(columns, itemRenderer) 675 { 676 this.element = document.createElementWithClass("div", "settings-list"); 677 this.element.tabIndex = -1; 678 this._itemRenderer = itemRenderer; 679 /** @type {!StringMap.<!Element>} */ 680 this._listItems = new StringMap(); 681 /** @type {!Array.<?string>} */ 682 this._ids = []; 683 this._columns = columns; 684 } 685 686 WebInspector.SettingsList.Events = { 687 Selected: "Selected", 688 Removed: "Removed", 689 DoubleClicked: "DoubleClicked", 690 } 691 692 WebInspector.SettingsList.prototype = { 693 /** 694 * @param {?string} itemId 695 * @param {?string=} beforeId 696 * @return {!Element} 697 */ 698 addItem: function(itemId, beforeId) 699 { 700 var listItem = document.createElementWithClass("div", "settings-list-item"); 701 listItem._id = itemId; 702 if (typeof beforeId !== "undefined") 703 this.element.insertBefore(listItem, this.itemForId(beforeId)); 704 else 705 this.element.appendChild(listItem); 706 707 var listItemContents = listItem.createChild("div", "settings-list-item-contents"); 708 var listItemColumnsElement = listItemContents.createChild("div", "settings-list-item-columns"); 709 710 listItem.columnElements = {}; 711 for (var i = 0; i < this._columns.length; ++i) { 712 var column = this._columns[i]; 713 var columnElement = listItemColumnsElement.createChild("div", "list-column settings-list-column-" + column.id); 714 listItem.columnElements[column.id] = columnElement; 715 this._itemRenderer(columnElement, column, itemId); 716 } 717 var removeItemButton = this._createRemoveButton(removeItemClicked.bind(this)); 718 listItemContents.addEventListener("click", this.selectItem.bind(this, itemId), false); 719 listItemContents.addEventListener("dblclick", this._onDoubleClick.bind(this, itemId), false); 720 listItemContents.appendChild(removeItemButton); 721 722 this._listItems.set(itemId || "", listItem); 723 if (typeof beforeId !== "undefined") 724 this._ids.splice(this._ids.indexOf(beforeId), 0, itemId); 725 else 726 this._ids.push(itemId); 727 728 /** 729 * @param {!Event} event 730 * @this {WebInspector.SettingsList} 731 */ 732 function removeItemClicked(event) 733 { 734 removeItemButton.disabled = true; 735 this.removeItem(itemId); 736 this.dispatchEventToListeners(WebInspector.SettingsList.Events.Removed, itemId); 737 event.consume(); 738 } 739 740 return listItem; 741 }, 742 743 /** 744 * @param {?string} id 745 */ 746 removeItem: function(id) 747 { 748 var listItem = this._listItems.remove(id || ""); 749 if (listItem) 750 listItem.remove(); 751 this._ids.remove(id); 752 if (id === this._selectedId) { 753 delete this._selectedId; 754 if (this._ids.length) 755 this.selectItem(this._ids[0]); 756 } 757 }, 758 759 /** 760 * @return {!Array.<?string>} 761 */ 762 itemIds: function() 763 { 764 return this._ids.slice(); 765 }, 766 767 /** 768 * @return {!Array.<string>} 769 */ 770 columns: function() 771 { 772 return this._columns.select("id"); 773 }, 774 775 /** 776 * @return {?string} 777 */ 778 selectedId: function() 779 { 780 return this._selectedId; 781 }, 782 783 /** 784 * @return {?Element} 785 */ 786 selectedItem: function() 787 { 788 return this._selectedId ? this.itemForId(this._selectedId) : null; 789 }, 790 791 /** 792 * @param {?string} itemId 793 * @return {?Element} 794 */ 795 itemForId: function(itemId) 796 { 797 return this._listItems.get(itemId || "") || null; 798 }, 799 800 /** 801 * @param {?string} id 802 * @param {!Event=} event 803 */ 804 _onDoubleClick: function(id, event) 805 { 806 this.dispatchEventToListeners(WebInspector.SettingsList.Events.DoubleClicked, id); 807 }, 808 809 /** 810 * @param {?string} id 811 * @param {!Event=} event 812 */ 813 selectItem: function(id, event) 814 { 815 if (typeof this._selectedId !== "undefined") 816 this.itemForId(this._selectedId).classList.remove("selected"); 817 818 this._selectedId = id; 819 if (typeof this._selectedId !== "undefined") 820 this.itemForId(this._selectedId).classList.add("selected"); 821 822 this.dispatchEventToListeners(WebInspector.SettingsList.Events.Selected, id); 823 if (event) 824 event.consume(); 825 }, 826 827 /** 828 * @param {function(!Event)} handler 829 * @return {!Element} 830 */ 831 _createRemoveButton: function(handler) 832 { 833 var removeButton = document.createElementWithClass("div", "remove-item-button"); 834 removeButton.addEventListener("click", handler, false); 835 return removeButton; 836 }, 837 838 __proto__: WebInspector.Object.prototype 839 } 840 841 /** 842 * @constructor 843 * @extends {WebInspector.SettingsList} 844 * @param {!Array.<{id: string, placeholder: (string|undefined), options: (!Array.<string>|undefined)}>} columns 845 * @param {function(string, string):string} valuesProvider 846 * @param {function(?string, !Object):!Array.<string>} validateHandler 847 * @param {function(?string, !Object)} editHandler 848 */ 849 WebInspector.EditableSettingsList = function(columns, valuesProvider, validateHandler, editHandler) 850 { 851 WebInspector.SettingsList.call(this, columns, this._renderColumn.bind(this)); 852 this._valuesProvider = valuesProvider; 853 this._validateHandler = validateHandler; 854 this._editHandler = editHandler; 855 /** @type {!StringMap.<(!HTMLInputElement|!HTMLSelectElement)>} */ 856 this._addInputElements = new StringMap(); 857 /** @type {!StringMap.<!StringMap.<(!HTMLInputElement|!HTMLSelectElement)>>} */ 858 this._editInputElements = new StringMap(); 859 /** @type {!StringMap.<!StringMap.<!HTMLSpanElement>>} */ 860 this._textElements = new StringMap(); 861 862 this._addMappingItem = this.addItem(null); 863 this._addMappingItem.classList.add("item-editing", "add-list-item"); 864 } 865 866 WebInspector.EditableSettingsList.prototype = { 867 /** 868 * @param {?string} itemId 869 * @param {?string=} beforeId 870 * @return {!Element} 871 */ 872 addItem: function(itemId, beforeId) 873 { 874 var listItem = WebInspector.SettingsList.prototype.addItem.call(this, itemId, beforeId); 875 listItem.classList.add("editable"); 876 return listItem; 877 }, 878 879 /** 880 * @param {?string} itemId 881 */ 882 refreshItem: function(itemId) 883 { 884 if (!itemId) 885 return; 886 var listItem = this.itemForId(itemId); 887 if (!listItem) 888 return; 889 for (var i = 0; i < this._columns.length; ++i) { 890 var column = this._columns[i]; 891 var columnId = column.id; 892 893 var value = this._valuesProvider(itemId, columnId); 894 this._setTextElementContent(itemId, columnId, value); 895 896 var editElement = this._editInputElements.get(itemId).get(columnId); 897 this._setEditElementValue(editElement, value || ""); 898 } 899 }, 900 901 /** 902 * @param {?string} itemId 903 * @param {string} columnId 904 */ 905 _textElementContent: function(itemId, columnId) 906 { 907 if (!itemId) 908 return ""; 909 return this._textElements.get(itemId).get(columnId).textContent.replace(/\u200B/g, ""); 910 }, 911 912 /** 913 * @param {string} itemId 914 * @param {string} columnId 915 * @param {string} text 916 */ 917 _setTextElementContent: function(itemId, columnId, text) 918 { 919 var textElement = this._textElements.get(itemId).get(columnId); 920 textElement.textContent = text.replace(/.{4}/g, "$&\u200B"); 921 textElement.title = text; 922 }, 923 924 /** 925 * @param {!Element} columnElement 926 * @param {{id: string, placeholder: (string|undefined), options: (!Array.<string>|undefined)}} column 927 * @param {?string} itemId 928 */ 929 _renderColumn: function(columnElement, column, itemId) 930 { 931 var columnId = column.id; 932 if (itemId === null) { 933 this._createEditElement(columnElement, column, itemId); 934 return; 935 } 936 var validItemId = itemId; 937 938 if (!this._editInputElements.has(itemId)) 939 this._editInputElements.set(itemId, new StringMap()); 940 if (!this._textElements.has(itemId)) 941 this._textElements.set(itemId, new StringMap()); 942 943 var value = this._valuesProvider(itemId, columnId); 944 945 var textElement = /** @type {!HTMLSpanElement} */ (columnElement.createChild("span", "list-column-text")); 946 columnElement.addEventListener("click", rowClicked.bind(this), false); 947 this._textElements.get(itemId).set(columnId, textElement); 948 this._setTextElementContent(itemId, columnId, value); 949 950 this._createEditElement(columnElement, column, itemId, value); 951 952 /** 953 * @param {!Event} event 954 * @this {WebInspector.EditableSettingsList} 955 */ 956 function rowClicked(event) 957 { 958 if (itemId === this._editingId) 959 return; 960 console.assert(!this._editingId); 961 this._editingId = validItemId; 962 var listItem = this.itemForId(validItemId); 963 listItem.classList.add("item-editing"); 964 var editElement = event.target.editElement || this._editInputElements.get(validItemId).get(this.columns()[0]); 965 editElement.focus(); 966 if (editElement.select) 967 editElement.select(); 968 } 969 }, 970 971 /** 972 * @param {!Element} columnElement 973 * @param {{id: string, placeholder: (string|undefined), options: (!Array.<string>|undefined)}} column 974 * @param {?string} itemId 975 * @param {string=} value 976 * @return {!Element} 977 */ 978 _createEditElement: function(columnElement, column, itemId, value) 979 { 980 var options = column.options; 981 if (options) { 982 var editElement = /** @type {!HTMLSelectElement} */ (columnElement.createChild("select", "chrome-select list-column-editor")); 983 for (var i = 0; i < options.length; ++i) { 984 var option = editElement.createChild("option"); 985 option.value = options[i]; 986 option.textContent = options[i]; 987 } 988 editElement.addEventListener("blur", this._editMappingBlur.bind(this, itemId), false); 989 editElement.addEventListener("change", this._editMappingBlur.bind(this, itemId), false); 990 } else { 991 var editElement = /** @type {!HTMLInputElement} */ (columnElement.createChild("input", "list-column-editor")); 992 editElement.addEventListener("blur", this._editMappingBlur.bind(this, itemId), false); 993 editElement.addEventListener("input", this._validateEdit.bind(this, itemId), false); 994 if (itemId === null) 995 editElement.placeholder = column.placeholder || ""; 996 } 997 998 if (itemId === null) 999 this._addInputElements.set(column.id, editElement); 1000 else 1001 this._editInputElements.get(itemId).set(column.id, editElement); 1002 1003 this._setEditElementValue(editElement, value || ""); 1004 columnElement.editElement = editElement; 1005 return editElement; 1006 }, 1007 1008 /** 1009 * @param {!HTMLInputElement|!HTMLSelectElement|undefined} editElement 1010 * @param {string} value 1011 */ 1012 _setEditElementValue: function(editElement, value) 1013 { 1014 if (!editElement) 1015 return; 1016 if (editElement instanceof HTMLSelectElement) { 1017 var options = editElement.options; 1018 for (var i = 0; i < options.length; ++i) 1019 options[i].selected = (options[i].value === value); 1020 } else { 1021 editElement.value = value; 1022 } 1023 }, 1024 1025 /** 1026 * @param {?string} itemId 1027 * @return {!Object} 1028 */ 1029 _data: function(itemId) 1030 { 1031 var inputElements = this._inputElements(itemId); 1032 var data = { __proto__: null }; 1033 var columns = this.columns(); 1034 for (var i = 0; i < columns.length; ++i) 1035 data[columns[i]] = inputElements.get(columns[i]).value; 1036 return data; 1037 }, 1038 1039 /** 1040 * @param {?string} itemId 1041 * @return {?StringMap.<(!HTMLInputElement|!HTMLSelectElement)>} 1042 */ 1043 _inputElements: function(itemId) 1044 { 1045 if (!itemId) 1046 return this._addInputElements; 1047 return this._editInputElements.get(itemId) || null; 1048 }, 1049 1050 /** 1051 * @param {?string} itemId 1052 * @return {boolean} 1053 */ 1054 _validateEdit: function(itemId) 1055 { 1056 var errorColumns = this._validateHandler(itemId, this._data(itemId)); 1057 var hasChanges = this._hasChanges(itemId); 1058 var columns = this.columns(); 1059 for (var i = 0; i < columns.length; ++i) { 1060 var columnId = columns[i]; 1061 var inputElement = this._inputElements(itemId).get(columnId); 1062 if (hasChanges && errorColumns.indexOf(columnId) !== -1) 1063 inputElement.classList.add("editable-item-error"); 1064 else 1065 inputElement.classList.remove("editable-item-error"); 1066 } 1067 return !errorColumns.length; 1068 }, 1069 1070 /** 1071 * @param {?string} itemId 1072 * @return {boolean} 1073 */ 1074 _hasChanges: function(itemId) 1075 { 1076 var columns = this.columns(); 1077 for (var i = 0; i < columns.length; ++i) { 1078 var columnId = columns[i]; 1079 var oldValue = this._textElementContent(itemId, columnId); 1080 var newValue = this._inputElements(itemId).get(columnId).value; 1081 if (oldValue !== newValue) 1082 return true; 1083 } 1084 return false; 1085 }, 1086 1087 /** 1088 * @param {?string} itemId 1089 * @param {!Event} event 1090 */ 1091 _editMappingBlur: function(itemId, event) 1092 { 1093 if (itemId === null) { 1094 this._onAddMappingInputBlur(event); 1095 return; 1096 } 1097 1098 var inputElements = this._editInputElements.get(itemId).values(); 1099 if (inputElements.indexOf(event.relatedTarget) !== -1) 1100 return; 1101 1102 var listItem = this.itemForId(itemId); 1103 listItem.classList.remove("item-editing"); 1104 delete this._editingId; 1105 1106 if (!this._hasChanges(itemId)) 1107 return; 1108 1109 if (!this._validateEdit(itemId)) { 1110 var columns = this.columns(); 1111 for (var i = 0; i < columns.length; ++i) { 1112 var columnId = columns[i]; 1113 var editElement = this._editInputElements.get(itemId).get(columnId); 1114 this._setEditElementValue(editElement, this._textElementContent(itemId, columnId)); 1115 editElement.classList.remove("editable-item-error"); 1116 } 1117 return; 1118 } 1119 this._editHandler(itemId, this._data(itemId)); 1120 }, 1121 1122 /** 1123 * @param {!Event} event 1124 */ 1125 _onAddMappingInputBlur: function(event) 1126 { 1127 var inputElements = this._addInputElements.values(); 1128 if (inputElements.indexOf(event.relatedTarget) !== -1) 1129 return; 1130 1131 if (!this._hasChanges(null)) 1132 return; 1133 1134 if (!this._validateEdit(null)) 1135 return; 1136 1137 this._editHandler(null, this._data(null)); 1138 var columns = this.columns(); 1139 for (var i = 0; i < columns.length; ++i) { 1140 var columnId = columns[i]; 1141 var editElement = this._addInputElements.get(columnId); 1142 this._setEditElementValue(editElement, ""); 1143 } 1144 }, 1145 1146 __proto__: WebInspector.SettingsList.prototype 1147 } 1148 1149 WebInspector._settingsController = new WebInspector.SettingsController(); 1150