diff --git a/web/pgadmin/preferences/static/css/preferences.css b/web/pgadmin/preferences/static/css/preferences.css index 9f8de62..509a0a2 100644 --- a/web/pgadmin/preferences/static/css/preferences.css +++ b/web/pgadmin/preferences/static/css/preferences.css @@ -25,10 +25,6 @@ div.pgadmin-preference-body div.ajs-content { bottom: 0px !important; } -.preferences_content .control-label, .preferences_content .pgadmin-controls { - min-width: 100px !important; -} - .pgadmin-preference-body { min-width: 300px !important; min-height: 400px !important; @@ -40,3 +36,11 @@ div.pgadmin-preference-body div.ajs-content { min-height: 480px !important; } } + +.keyboard-shortcut-label { + font-weight: normal !important; +} + +.preferences_content input[type="checkbox"]:focus { + outline: 5px auto -webkit-focus-ring-color; +} diff --git a/web/pgadmin/preferences/static/js/preferences.js b/web/pgadmin/preferences/static/js/preferences.js index d025be1..f487782 100644 --- a/web/pgadmin/preferences/static/js/preferences.js +++ b/web/pgadmin/preferences/static/js/preferences.js @@ -256,6 +256,8 @@ define('pgadmin.preferences', [ return 'textarea'; case 'switch': return 'switch'; + case 'keyboardshortcut': + return 'keyboardshortcut'; default: if (console && console.warn) { // Warning for developer only. diff --git a/web/pgadmin/static/js/backform.pgadmin.js b/web/pgadmin/static/js/backform.pgadmin.js index 4f3b144..4794827 100644 --- a/web/pgadmin/static/js/backform.pgadmin.js +++ b/web/pgadmin/static/js/backform.pgadmin.js @@ -79,7 +79,6 @@ define([ return type; }; - var BackformControlInit = Backform.Control.prototype.initialize, BackformControlRemove = Backform.Control.prototype.remove; @@ -2504,5 +2503,209 @@ define([ }, }); + var ControlFormatter = Backform.KeyCodeControlFormatter = function() {}; + + _.extend(ControlFormatter.prototype, { + fromRaw: function (rawData) { + return rawData['char']; + }, + // we don't need toRaw + toRaw: undefined, + }); + + Backform.KeyCodeControl = Backform.InputControl.extend({ + defaults: _.defaults({ + maxlength: 1, + }, Backform.InputControl.prototype.defaults), + + events: { + 'keydown input': 'onkeyDown', + 'keyup input': 'preventEvent', + 'focus select': 'clearInvalid', + }, + + formatter: Backform.KeyCodeControlFormatter, + + preventEvent: function(e) { + var key_code = e.which || e.keyCode; + + if ([16, 17, 18, 27].indexOf(key_code) != -1) { + // Shift, Ctrl, Alt/Option, Escape + return; + } + + e.preventDefault(); + e.stopImmediatePropagation(); + e.stopPropagation(); + }, + + template: _.template([ + '', + '
', + ' <%=required ? "required" : ""%> />', + ' <% if (helpMessage && helpMessage.length) { %>', + ' <%=helpMessage%>', + ' <% } %>', + '
', + ].join('\n')), + onkeyDown: function(e) { + var self = this, + model = this.model, + attrArr = this.field.get('name').split('.'), + name = attrArr.shift(), + changes = {}, + key_code = e.which || e.keyCode, + key; + + if ([16, 17, 18, 27].indexOf(key_code) != -1) { + // Shift, Ctrl, Alt/Option, Escape + return; + } + + if (this.model.errorModel instanceof Backbone.Model) { + this.model.errorModel.unset(name); + } + key = gettext(e.key); + if (key_code == 32) { + key = gettext('Space'); + } + changes = { + 'key_code': e.which || e.keyCode, + 'char': key, + }; + + this.stopListening(this.model, 'change:' + name, this.render); + model.set(name, changes); + this.listenTo(this.model, 'change:' + name, this.render); + setTimeout(function() { + self.$el.find('input').val(key); + }); + e.preventDefault(); + }, + keyPathAccessor: function(obj, path) { + var res = obj; + path = path.split('.'); + for (var i = 0; i < path.length; i++) { + if (_.isNull(res)) return null; + if (_.isEmpty(path[i])) continue; + if (!_.isUndefined(res[path[i]])) res = res[path[i]]; + } + return res; + }, + }); + + Backform.KeyboardshortcutControl = Backform.Control.extend({ + + initialize: function() { + + Backform.Control.prototype.initialize.apply(this, arguments); + + var self = this, + initial_value = {}, + value = self.model.get(self.field.get('name')); + + var fields = self.field.get('fields'); + + if (fields == null || fields == undefined) { + throw new ReferenceError('"fields" not found in keyboard shortcut'); + } + + _.each(fields, function(field) { + initial_value[field['name']] = value[field['name']]; + }); + + self.innerModel = new Backbone.Model(initial_value); + + self.innerModel.on('change', function() { + var val = _.clone(self.model.get('value')); + self.model.set('value', + $.extend(true, val, this.toJSON()) + ); + }); + + self.controls = []; + }, + cleanup: function() { + _.each(this.controls, function(c) { + c.remove(); + }); + this.controls.length = 0; + }, + template: _.template([ + '', + '
', + '
', + ].join('\n')), + render: function() { + this.cleanup(); + this.$el.empty(); + + var self = this, + field = _.defaults(this.field.toJSON(), this.defaults); + + this.$el.html(self.template(field)).addClass(field.name); + + var $container = $(self.$el.find('.pgadmin-controls')); + + var op = [], + label, + controlLabel; + + _.each(field['fields'], function(fld) { + // reset labels (of previous control If any) + label = undefined; + controlLabel = undefined; + + if (fld['type'] == 'checkbox') { + controlLabel = fld['label']; + } else { + label = fld['label']; + } + var f = new Backform.Field( + _.extend({}, { + id: fld['name'], + name: fld['name'], + control: fld['type'], + label: label, + controlLabel: controlLabel, + options: op, + }) + ), + cntr = new (f.get('control')) ({ + field: f, + model: self.innerModel, + }); + + cntr.render(); + $(cntr.$el.find('.control-label')).addClass('keyboard-shortcut-label'); + + if(fld['type'] == 'checkbox') { + $(cntr.$el.find('.control-label')) + .addClass('pg-el-sm-10') + .removeClass('pg-el-sm-3'); + + $(cntr.$el.find('.pgadmin-controls')) + .addClass('pg-el-sm-2') + .removeClass('pg-el-sm-9'); + $container.append($('
').append(cntr.$el)); + } else { + $container.append($('
').append(cntr.$el)); + } + + // We will keep track of all the controls rendered at the + // moment. + self.controls.push(cntr); + + }); + + return self; + }, + remove: function() { + /* First do the clean up */ + this.cleanup(); + Backform.Control.prototype.remove.apply(this, arguments); + }, + }); + return Backform; }); diff --git a/web/pgadmin/utils/preferences.py b/web/pgadmin/utils/preferences.py index 2598da1..c9a8bc2 100644 --- a/web/pgadmin/utils/preferences.py +++ b/web/pgadmin/utils/preferences.py @@ -13,6 +13,7 @@ module within the system. """ import decimal +import simplejson as json import dateutil.parser as dateutil_parser from flask import current_app @@ -31,7 +32,7 @@ class _Preference(object): def __init__( self, cid, name, label, _type, default, help_str=None, min_val=None, - max_val=None, options=None, select2=None + max_val=None, options=None, select2=None, fields=None ): """ __init__ @@ -54,6 +55,8 @@ class _Preference(object): :param max_val: maximum value :param options: options (Array of list objects) :param select2: select2 options (object) + :param fields: field schema (if preference has more than one field to + take input from user e.g. keyboardshortcut preference) :returns: nothing """ @@ -67,6 +70,7 @@ class _Preference(object): self.max_val = max_val self.options = options self.select2 = select2 + self.fields = fields # Look into the configuration table to find out the id of the specific # preference. @@ -137,6 +141,12 @@ class _Preference(object): if self._type == 'text': if res.value == '': return self.default + if self._type == 'keyboardshortcut': + try: + return json.loads(res.value) + except Exception as e: + current_app.logger.exeception(e) + return self.default return res.value @@ -196,6 +206,14 @@ class _Preference(object): if not has_value and self.select2 and not self.select2['tags']: return False, gettext("Invalid value for an options option.") + elif self._type == 'keyboardshortcut': + try: + value = json.dumps(value) + except Exception as e: + current_app.logger.exeception(e) + return False, gettext( + "Invalid value for a keyboardshortcut option." + ) pref = UserPrefTable.query.filter_by( pid=self.pid @@ -231,7 +249,8 @@ class _Preference(object): 'max_val': self.max_val, 'options': self.options, 'select2': self.select2, - 'value': self.get() + 'value': self.get(), + 'fields': self.fields, } return res @@ -371,7 +390,7 @@ class Preferences(object): def register( self, category, name, label, _type, default, min_val=None, max_val=None, options=None, help_str=None, category_label=None, - select2=None + select2=None, fields=None ): """ register @@ -393,6 +412,8 @@ class Preferences(object): :param help_str: :param category_label: :param select2: select2 control extra options + :param fields: field schema (if preference has more than one field to + take input from user e.g. keyboardshortcut preference) """ cat = self.__category(category, category_label) if name in cat['preferences']: @@ -402,12 +423,13 @@ class Preferences(object): assert _type is not None, "Type for a preference cannot be none!" assert _type in ( 'boolean', 'integer', 'numeric', 'date', 'datetime', - 'options', 'multiline', 'switch', 'node', 'text' + 'options', 'multiline', 'switch', 'node', 'text', + 'keyboardshortcut' ), "Type cannot be found in the defined list!" (cat['preferences'])[name] = res = _Preference( cat['id'], name, label, _type, default, help_str, min_val, - max_val, options, select2 + max_val, options, select2, fields ) return res