ONOS-4733: Simplified Settings Table view (removed redundant data)
 - Now shows just simple class name for component
 - Removed default value column
 - Values that are *not* the same as the default are highlighted (emboldened)
 - Table rows now selectable and show details panel
     - panel shows component fully qualified class name
     - panel currently read-only
     - in future, user will be able to change the property value from here

Change-Id: I01a2af727dcfad82c6b7d2378701a5cb3e24e43a
diff --git a/web/gui/src/main/java/org/onosproject/ui/impl/SettingsViewMessageHandler.java b/web/gui/src/main/java/org/onosproject/ui/impl/SettingsViewMessageHandler.java
index e83dad7..3ca6e88 100644
--- a/web/gui/src/main/java/org/onosproject/ui/impl/SettingsViewMessageHandler.java
+++ b/web/gui/src/main/java/org/onosproject/ui/impl/SettingsViewMessageHandler.java
@@ -36,14 +36,17 @@
     private static final String SETTINGS = "settings";
 
     private static final String COMPONENT = "component";
+    private static final String FQ_COMPONENT = "fqComponent";
     private static final String ID = "id";
     private static final String TYPE = "type";
     private static final String VALUE = "value";
     private static final String DEFAULT = "defValue";
     private static final String DESC = "desc";
 
+    private static final char DOT = '.';
+
     private static final String[] COL_IDS = {
-            COMPONENT, ID, TYPE, VALUE, DEFAULT, DESC
+            COMPONENT, FQ_COMPONENT, ID, TYPE, VALUE, DEFAULT, DESC
     };
 
     @Override
@@ -90,13 +93,25 @@
             }
         }
 
-        private void populateRow(TableModel.Row row, String component, ConfigProperty prop) {
-            row.cell(COMPONENT, component)
+        private void populateRow(TableModel.Row row, String fqComp,
+                                 ConfigProperty prop) {
+            row.cell(COMPONENT, simpleName(fqComp))
+                    .cell(FQ_COMPONENT, fqComp)
                     .cell(ID, prop.name())
-                    .cell(TYPE, prop.type().toString().toLowerCase())
+                    .cell(TYPE, typeName(prop))
                     .cell(VALUE, prop.value())
                     .cell(DEFAULT, prop.defaultValue())
                     .cell(DESC, prop.description());
         }
+
+        // return just simple class name: "a.b.c.MyClass" -> "MyClass"
+        private String simpleName(String component) {
+            int lastDot = component.lastIndexOf(DOT);
+            return lastDot < 0 ? component : component.substring(lastDot + 1);
+        }
+
+        private String typeName(ConfigProperty prop) {
+            return prop.type().toString().toLowerCase();
+        }
     }
 }
diff --git a/web/gui/src/main/webapp/app/view/app/app.js b/web/gui/src/main/webapp/app/view/app/app.js
index 048a78c..83ab06e 100644
--- a/web/gui/src/main/webapp/app/view/app/app.js
+++ b/web/gui/src/main/webapp/app/view/app/app.js
@@ -470,7 +470,7 @@
                 });
                 ks.gestureNotes([
                     ['click', 'Select a row to show application details'],
-                    ['scroll down', 'See more application']
+                    ['scroll down', 'See more applications']
                 ]);
 
                 // if the panelData changes
diff --git a/web/gui/src/main/webapp/app/view/settings/settings.css b/web/gui/src/main/webapp/app/view/settings/settings.css
index ba3ca3d..4ddf491 100644
--- a/web/gui/src/main/webapp/app/view/settings/settings.css
+++ b/web/gui/src/main/webapp/app/view/settings/settings.css
@@ -25,3 +25,41 @@
 #ov-settings div.ctrl-btns {
     width: 45px;
 }
+
+#ov-settings div.summary-list td.notdef {
+    color: #0071bd;
+    font-weight: bold;
+}
+
+#settings-details-panel .container {
+    padding: 0 30px;
+    overflow-y: scroll;
+}
+
+#settings-details-panel .close-btn {
+    position: absolute;
+    right: 26px;
+    top: 26px;
+    cursor: pointer;
+}
+
+#settings-details-panel .top .settings-title {
+    width: 90%;
+    height: 62px;
+    font-size: 20pt;
+    font-weight: lighter;
+    overflow: hidden;
+    white-space: nowrap;
+    padding: 0 12px 0 2px;
+    margin-top: 19px;
+    margin-bottom: 5px;
+}
+
+#settings-details-panel td.label {
+    font-weight: bold;
+    text-align: right;
+    font-size: 12pt;
+    padding-right: 6px;
+    vertical-align: top;
+    width: 120px;
+}
diff --git a/web/gui/src/main/webapp/app/view/settings/settings.html b/web/gui/src/main/webapp/app/view/settings/settings.html
index fef2ffe..7903977 100644
--- a/web/gui/src/main/webapp/app/view/settings/settings.html
+++ b/web/gui/src/main/webapp/app/view/settings/settings.html
@@ -14,11 +14,10 @@
         <div class="table-header" onos-sortable-header>
             <table>
                 <tr>
-                    <td colId="component" sortable col-width="300px">Component </td>
-                    <td colId="id" sortable col-width="240px">Property </td>
+                    <td colId="component" sortable col-width="260px">Component </td>
+                    <td colId="id" sortable col-width="260px">Property </td>
                     <td colId="type" sortable col-width="100px">Type </td>
                     <td colId="value" sortable col-width="100px">Value </td>
-                    <td colId="defValue" sortable col-width="100px">Default </td>
                     <td colId="desc">Description </td>
                 </tr>
             </table>
@@ -33,17 +32,21 @@
                 </tr>
 
                 <tr ng-repeat="prop in tableData track by $index"
+                    ng-click="selectCallback($event, prop)"
+                    ng-class="{selected: prop.id === selId}"
                     ng-repeat-complete row-id="{{prop.id}}">
                     <td>{{prop.component}}</td>
                     <td>{{prop.id}}</td>
                     <td>{{prop.type}}</td>
-                    <td>{{prop.value}}</td>
-                    <td>{{prop.defValue}}</td>
+                    <td ng-class="{notdef: prop.value !== prop.defValue}">
+                        {{prop.value}}
+                    </td>
                     <td>{{prop.desc}}</td>
                 </tr>
             </table>
         </div>
 
     </div>
+    <settings-details-panel></settings-details-panel>
 
 </div>
diff --git a/web/gui/src/main/webapp/app/view/settings/settings.js b/web/gui/src/main/webapp/app/view/settings/settings.js
index 00c102e..3197ff0 100644
--- a/web/gui/src/main/webapp/app/view/settings/settings.js
+++ b/web/gui/src/main/webapp/app/view/settings/settings.js
@@ -21,16 +21,215 @@
 (function () {
     'use strict';
 
-    angular.module('ovSettings', [])
-    .controller('OvSettingsCtrl',
-        ['$log', '$scope', 'TableBuilderService',
+    // injected refs
+    var $log, $scope, wss, fs, ks, ps, is;
 
-        function ($log, $scope, tbs) {
+    // internal state
+    var detailsPanel,
+        panelData,
+        top,
+        pStartY,
+        pHeight,
+        wSize = false;
+
+    // constants
+    var pName = 'settings-details-panel',
+        panelWidth = 540,
+        topPdg = 60,
+        propOrder = ['fqComponent', 'id', 'type', 'value', 'defValue', 'desc'],
+        friendlyProps = [
+            'Component', 'Property', 'Type', 'Value', 'Default Value',
+            'Description'
+        ];
+
+    function createDetailsPanel() {
+        detailsPanel = ps.createPanel(pName, {
+            width: wSize.width,
+            margin: 0,
+            hideMargin: 0
+        });
+
+        detailsPanel.el().style({
+            position: 'absolute',
+            top: pStartY + 'px'
+        });
+
+        detailsPanel.hide();
+    }
+
+    function closePanel() {
+        if (detailsPanel.isVisible()) {
+            $scope.selId = null;
+            panelData = null;
+            detailsPanel.hide();
+            return true;
+        }
+        return false;
+    }
+
+    function addCloseBtn(div) {
+        is.loadEmbeddedIcon(div, 'close', 26);
+        div.on('click', closePanel);
+    }
+
+    function setUpPanel() {
+        var container, closeBtn, div;
+
+        detailsPanel.empty();
+        detailsPanel.width(panelWidth);
+
+        container = detailsPanel.append('div').classed('container', true);
+
+        top = container.append('div').classed('top', true);
+        closeBtn = top.append('div').classed('close-btn', true);
+        addCloseBtn(closeBtn);
+
+        div = top.append('div').classed('top-content', true);
+
+        function ndiv(cls, addTable) {
+            var  d = div.append('div').classed(cls, true);
+            if (addTable) {
+                d.append('table');
+            }
+        }
+
+        ndiv('settings-title');
+        ndiv('settings-props', true);
+    }
+
+    function addProp(tbody, index, value) {
+        var tr = tbody.append('tr');
+
+        function addCell(cls, txt) {
+            tr.append('td').attr('class', cls).html(txt);
+        }
+
+        addCell('label', friendlyProps[index] + ':');
+        addCell('value', value);
+    }
+
+    function populateTop(details) {
+        var propsBody = top.select('.settings-props').append('tbody');
+
+        top.select('.settings-title').text(details.id);
+
+        propOrder.forEach(function (prop, i) {
+            addProp(propsBody, i, details[prop]);
+        });
+    }
+
+    function populateDetails() {
+        setUpPanel();
+        populateTop(panelData);
+        detailsPanel.height(pHeight);
+    }
+
+    angular.module('ovSettings', [])
+        .controller('OvSettingsCtrl',
+            ['$log', '$scope',
+            'WebSocketService', 'FnService', 'KeyService', 'PanelService',
+            'IconService', 'TableBuilderService',
+
+        function (_$log_, _$scope_, _wss_, _fs_, _ks_, _ps_, _is_, tbs) {
+            $log = _$log_;
+            $scope = _$scope_;
+            wss = _wss_;
+            fs = _fs_;
+            ks = _ks_;
+            ps = _ps_;
+            is = _is_;
+            $scope.panelData = {};
+
+            function selCb($event, row) {
+                if ($scope.selId) {
+                    panelData = row;
+                    populateDetails();
+                    detailsPanel.show();
+                } else {
+                    panelData = null;
+                    detailsPanel.hide();
+                }
+            }
+
             tbs.buildTable({
                 scope: $scope,
-                tag: 'setting'
+                tag: 'setting',
+                selCb: selCb
+            });
+
+            ks.keyBindings({
+                esc: [$scope.selectCallback, 'Deselect property'],
+                _helpFormat: ['esc']
+            });
+            ks.gestureNotes([
+                ['click row', 'Select / deselect settings property'],
+                ['scroll down', 'See more settings']
+            ]);
+
+            $scope.$on('$destroy', function () {
+                ks.unbindKeys();
             });
 
             $log.log('OvSettingsCtrl has been created');
-        }]);
+        }])
+
+        .directive('settingsDetailsPanel',
+            ['$rootScope', '$window', '$timeout', 'KeyService',
+            function ($rootScope, $window, $timeout, ks) {
+                return function (scope) {
+                    var unbindWatch;
+
+                    function heightCalc() {
+                        pStartY = fs.noPxStyle(d3.select('.tabular-header'), 'height')
+                            + topPdg;
+                        wSize = fs.windowSize(pStartY);
+                        pHeight = wSize.height;
+                    }
+
+                    function initPanel() {
+                        heightCalc();
+                        createDetailsPanel();
+                        $log.debug('start to initialize panel!');
+                    }
+
+                    // Safari has a bug where it renders the fixed-layout table wrong
+                    // if you ask for the window's size too early
+                    if (scope.onos.browser === 'safari') {
+                        $timeout(initPanel);
+                    } else {
+                        initPanel();
+                    }
+                    // create key bindings to handle panel
+                    ks.keyBindings({
+                        esc: [closePanel, 'Close the details panel'],
+                        _helpFormat: ['esc']
+                    });
+                    ks.gestureNotes([
+                        ['click', 'Select a row to show property details'],
+                        ['scroll down', 'See more properties']
+                    ]);
+
+                    // if the window size changes
+                    unbindWatch = $rootScope.$watchCollection(
+                        function () {
+                            return {
+                                h: $window.innerHeight,
+                                w: $window.innerWidth
+                            };
+                        }, function () {
+                            if (panelData) {
+                                heightCalc();
+                                populateDetails();
+                            }
+                        }
+                    );
+
+                    scope.$on('$destroy', function () {
+                        panelData = null;
+                        unbindWatch();
+                        ks.unbindKeys();
+                        ps.destroyPanel(pName);
+                    });
+                };
+            }]);
 }());