From ffcd546474ee8e2790be01b97f0a9b23d74c8653 Mon Sep 17 00:00:00 2001
From: Andy O'Neill <andy@scratch.mit.edu>
Date: Thu, 2 Mar 2023 13:46:07 -0500
Subject: [PATCH] feat: add quaternary color for selected dropdowns

---
 blocks_common/math.js                  | 15 +++++++----
 blocks_common/note.js                  |  3 ++-
 blocks_common/text.js                  |  3 ++-
 blocks_horizontal/control.js           | 15 +++++++----
 blocks_horizontal/event.js             | 15 +++++++----
 blocks_horizontal/wedo.js              | 27 ++++++++++++-------
 blocks_vertical/control.js             |  3 ++-
 blocks_vertical/data.js                |  8 ++----
 blocks_vertical/event.js               |  1 +
 blocks_vertical/looks.js               |  2 ++
 blocks_vertical/motion.js              |  3 +++
 blocks_vertical/procedures.js          |  2 ++
 blocks_vertical/sound.js               |  1 +
 blocks_vertical/vertical_extensions.js | 13 ++++-----
 core/block.js                          | 34 ++++++++++++++++++++---
 core/block_svg.js                      |  8 +++---
 core/colours.js                        | 36 ++++++++++++++++---------
 core/css.js                            |  2 +-
 core/field_colour.js                   |  4 +--
 core/field_colour_slider.js            |  5 ++--
 core/field_dropdown.js                 |  4 +--
 core/field_iconmenu.js                 |  6 +++--
 tests/jsunit/block_test.js             | 37 ++++++++++++++++++++++++++
 23 files changed, 180 insertions(+), 67 deletions(-)

diff --git a/blocks_common/math.js b/blocks_common/math.js
index 615228ff..63a95e98 100644
--- a/blocks_common/math.js
+++ b/blocks_common/math.js
@@ -51,7 +51,8 @@ Blockly.Blocks['math_number'] = {
       "outputShape": Blockly.OUTPUT_SHAPE_ROUND,
       "colour": Blockly.Colours.textField,
       "colourSecondary": Blockly.Colours.textField,
-      "colourTertiary": Blockly.Colours.textField
+      "colourTertiary": Blockly.Colours.textField,
+      "colourQuaternary": Blockly.Colours.textField
     });
   }
 };
@@ -75,7 +76,8 @@ Blockly.Blocks['math_integer'] = {
       "outputShape": Blockly.OUTPUT_SHAPE_ROUND,
       "colour": Blockly.Colours.textField,
       "colourSecondary": Blockly.Colours.textField,
-      "colourTertiary": Blockly.Colours.textField
+      "colourTertiary": Blockly.Colours.textField,
+      "colourQuaternary": Blockly.Colours.textField
     });
   }
 };
@@ -100,7 +102,8 @@ Blockly.Blocks['math_whole_number'] = {
       "outputShape": Blockly.OUTPUT_SHAPE_ROUND,
       "colour": Blockly.Colours.textField,
       "colourSecondary": Blockly.Colours.textField,
-      "colourTertiary": Blockly.Colours.textField
+      "colourTertiary": Blockly.Colours.textField,
+      "colourQuaternary": Blockly.Colours.textField
     });
   }
 };
@@ -124,7 +127,8 @@ Blockly.Blocks['math_positive_number'] = {
       "outputShape": Blockly.OUTPUT_SHAPE_ROUND,
       "colour": Blockly.Colours.textField,
       "colourSecondary": Blockly.Colours.textField,
-      "colourTertiary": Blockly.Colours.textField
+      "colourTertiary": Blockly.Colours.textField,
+      "colourQuaternary": Blockly.Colours.textField
     });
   }
 };
@@ -148,7 +152,8 @@ Blockly.Blocks['math_angle'] = {
       "outputShape": Blockly.OUTPUT_SHAPE_ROUND,
       "colour": Blockly.Colours.textField,
       "colourSecondary": Blockly.Colours.textField,
-      "colourTertiary": Blockly.Colours.textField
+      "colourTertiary": Blockly.Colours.textField,
+      "colourQuaternary": Blockly.Colours.textField
     });
   }
 };
diff --git a/blocks_common/note.js b/blocks_common/note.js
index 91ffffd5..f51ca40f 100644
--- a/blocks_common/note.js
+++ b/blocks_common/note.js
@@ -51,7 +51,8 @@ Blockly.Blocks['note'] = {
       "output": "Number",
       "colour": Blockly.Colours.textField,
       "colourSecondary": Blockly.Colours.textField,
-      "colourTertiary": Blockly.Colours.textField
+      "colourTertiary": Blockly.Colours.textField,
+      "colourQuaternary": Blockly.Colours.textField
     });
   }
 };
diff --git a/blocks_common/text.js b/blocks_common/text.js
index 1c2c57f0..484475d6 100644
--- a/blocks_common/text.js
+++ b/blocks_common/text.js
@@ -50,7 +50,8 @@ Blockly.Blocks['text'] = {
       "outputShape": Blockly.OUTPUT_SHAPE_ROUND,
       "colour": Blockly.Colours.textField,
       "colourSecondary": Blockly.Colours.textField,
-      "colourTertiary": Blockly.Colours.textField
+      "colourTertiary": Blockly.Colours.textField,
+      "colourQuaternary": Blockly.Colours.textField
     });
   }
 };
diff --git a/blocks_horizontal/control.js b/blocks_horizontal/control.js
index 8aa9418f..8fd7cf20 100644
--- a/blocks_horizontal/control.js
+++ b/blocks_horizontal/control.js
@@ -65,7 +65,8 @@ Blockly.Blocks['control_repeat'] = {
       "category": Blockly.Categories.control,
       "colour": Blockly.Colours.control.primary,
       "colourSecondary": Blockly.Colours.control.secondary,
-      "colourTertiary": Blockly.Colours.control.tertiary
+      "colourTertiary": Blockly.Colours.control.tertiary,
+      "colourQuaternary": Blockly.Colours.control.quaternary
     });
   }
 };
@@ -99,7 +100,8 @@ Blockly.Blocks['control_forever'] = {
       "category": Blockly.Categories.control,
       "colour": Blockly.Colours.control.primary,
       "colourSecondary": Blockly.Colours.control.secondary,
-      "colourTertiary": Blockly.Colours.control.tertiary
+      "colourTertiary": Blockly.Colours.control.tertiary,
+      "colourQuaternary": Blockly.Colours.control.quaternary
     });
   }
 };
@@ -139,7 +141,8 @@ Blockly.Blocks['control_repeat'] = {
       "category": Blockly.Categories.control,
       "colour": Blockly.Colours.control.primary,
       "colourSecondary": Blockly.Colours.control.secondary,
-      "colourTertiary": Blockly.Colours.control.tertiary
+      "colourTertiary": Blockly.Colours.control.tertiary,
+      "colourQuaternary": Blockly.Colours.control.quaternary
     });
   }
 };
@@ -167,7 +170,8 @@ Blockly.Blocks['control_stop'] = {
       "category": Blockly.Categories.control,
       "colour": Blockly.Colours.control.primary,
       "colourSecondary": Blockly.Colours.control.secondary,
-      "colourTertiary": Blockly.Colours.control.tertiary
+      "colourTertiary": Blockly.Colours.control.tertiary,
+      "colourQuaternary": Blockly.Colours.control.quaternary
     });
   }
 };
@@ -201,7 +205,8 @@ Blockly.Blocks['control_wait'] = {
       "category": Blockly.Categories.control,
       "colour": Blockly.Colours.control.primary,
       "colourSecondary": Blockly.Colours.control.secondary,
-      "colourTertiary": Blockly.Colours.control.tertiary
+      "colourTertiary": Blockly.Colours.control.tertiary,
+      "colourQuaternary": Blockly.Colours.control.quaternary
     });
   }
 };
diff --git a/blocks_horizontal/event.js b/blocks_horizontal/event.js
index 19a6ebab..faada4fb 100644
--- a/blocks_horizontal/event.js
+++ b/blocks_horizontal/event.js
@@ -54,7 +54,8 @@ Blockly.Blocks['event_whenflagclicked'] = {
       "category": Blockly.Categories.event,
       "colour": Blockly.Colours.event.primary,
       "colourSecondary": Blockly.Colours.event.secondary,
-      "colourTertiary": Blockly.Colours.event.tertiary
+      "colourTertiary": Blockly.Colours.event.tertiary,
+      "colourQuaternary": Blockly.Colours.event.quaternary
     });
   }
 };
@@ -84,7 +85,8 @@ Blockly.Blocks['dropdown_whenbroadcast'] = {
     this.setOutput(true);
     this.setColour(Blockly.Colours.event.primary,
         Blockly.Colours.event.secondary,
-        Blockly.Colours.event.tertiary
+        Blockly.Colours.event.tertiary,
+        Blockly.Colours.event.quaternary
     );
   }
 };
@@ -116,7 +118,8 @@ Blockly.Blocks['event_whenbroadcastreceived'] = {
       "category": Blockly.Categories.event,
       "colour": Blockly.Colours.event.primary,
       "colourSecondary": Blockly.Colours.event.secondary,
-      "colourTertiary": Blockly.Colours.event.tertiary
+      "colourTertiary": Blockly.Colours.event.tertiary,
+      "colourQuaternary": Blockly.Colours.event.quaternary
     });
   }
 };
@@ -146,7 +149,8 @@ Blockly.Blocks['dropdown_broadcast'] = {
     this.setOutput(true);
     this.setColour(Blockly.Colours.event.primary,
         Blockly.Colours.event.secondary,
-        Blockly.Colours.event.tertiary
+        Blockly.Colours.event.tertiary,
+        Blockly.Colours.event.quaternary
     );
   }
 };
@@ -179,7 +183,8 @@ Blockly.Blocks['event_broadcast'] = {
       "category": Blockly.Categories.event,
       "colour": Blockly.Colours.event.primary,
       "colourSecondary": Blockly.Colours.event.secondary,
-      "colourTertiary": Blockly.Colours.event.tertiary
+      "colourTertiary": Blockly.Colours.event.tertiary,
+      "colourQuaternary": Blockly.Colours.event.quaternary
     });
   }
 };
diff --git a/blocks_horizontal/wedo.js b/blocks_horizontal/wedo.js
index f2052aa2..723fa9db 100644
--- a/blocks_horizontal/wedo.js
+++ b/blocks_horizontal/wedo.js
@@ -61,7 +61,8 @@ Blockly.Blocks['dropdown_wedo_setcolor'] = {
     this.setOutput(true);
     this.setColour(Blockly.Colours.looks.primary,
         Blockly.Colours.looks.secondary,
-        Blockly.Colours.looks.tertiary
+        Blockly.Colours.looks.tertiary,
+        Blockly.Colours.looks.quaternary
     );
   }
 };
@@ -94,7 +95,8 @@ Blockly.Blocks['wedo_setcolor'] = {
       "category": Blockly.Categories.looks,
       "colour": Blockly.Colours.looks.primary,
       "colourSecondary": Blockly.Colours.looks.secondary,
-      "colourTertiary": Blockly.Colours.looks.tertiary
+      "colourTertiary": Blockly.Colours.looks.tertiary,
+      "colourQuaternary": Blockly.Colours.looks.quaternary
     });
   }
 };
@@ -128,7 +130,8 @@ Blockly.Blocks['wedo_motorclockwise'] = {
       "category": Blockly.Categories.motion,
       "colour": Blockly.Colours.motion.primary,
       "colourSecondary": Blockly.Colours.motion.secondary,
-      "colourTertiary": Blockly.Colours.motion.tertiary
+      "colourTertiary": Blockly.Colours.motion.tertiary,
+      "colourQuaternary": Blockly.Colours.motion.quaternary
     });
   }
 };
@@ -162,7 +165,8 @@ Blockly.Blocks['wedo_motorcounterclockwise'] = {
       "category": Blockly.Categories.motion,
       "colour": Blockly.Colours.motion.primary,
       "colourSecondary": Blockly.Colours.motion.secondary,
-      "colourTertiary": Blockly.Colours.motion.tertiary
+      "colourTertiary": Blockly.Colours.motion.tertiary,
+      "colourQuaternary": Blockly.Colours.motion.quaternary
     });
   }
 };
@@ -186,7 +190,8 @@ Blockly.Blocks['dropdown_wedo_motorspeed'] = {
     this.setOutput(true);
     this.setColour(Blockly.Colours.motion.primary,
         Blockly.Colours.motion.secondary,
-        Blockly.Colours.motion.tertiary
+        Blockly.Colours.motion.tertiary,
+        Blockly.Colours.motion.quaternary
     );
   }
 };
@@ -219,7 +224,8 @@ Blockly.Blocks['wedo_motorspeed'] = {
       "category": Blockly.Categories.motion,
       "colour": Blockly.Colours.motion.primary,
       "colourSecondary": Blockly.Colours.motion.secondary,
-      "colourTertiary": Blockly.Colours.motion.tertiary
+      "colourTertiary": Blockly.Colours.motion.tertiary,
+      "colourQuaternary": Blockly.Colours.motion.quaternary
     });
   }
 };
@@ -250,7 +256,8 @@ Blockly.Blocks['dropdown_wedo_whentilt'] = {
     this.setOutput(true);
     this.setColour(Blockly.Colours.event.primary,
         Blockly.Colours.event.secondary,
-        Blockly.Colours.event.tertiary
+        Blockly.Colours.event.tertiary,
+        Blockly.Colours.event.quaternary
     );
   }
 };
@@ -282,7 +289,8 @@ Blockly.Blocks['wedo_whentilt'] = {
       "category": Blockly.Categories.event,
       "colour": Blockly.Colours.event.primary,
       "colourSecondary": Blockly.Colours.event.secondary,
-      "colourTertiary": Blockly.Colours.event.tertiary
+      "colourTertiary": Blockly.Colours.event.tertiary,
+      "colourQuaternary": Blockly.Colours.event.quaternary
     });
   }
 };
@@ -310,7 +318,8 @@ Blockly.Blocks['wedo_whendistanceclose'] = {
       "category": Blockly.Categories.event,
       "colour": Blockly.Colours.event.primary,
       "colourSecondary": Blockly.Colours.event.secondary,
-      "colourTertiary": Blockly.Colours.event.tertiary
+      "colourTertiary": Blockly.Colours.event.tertiary,
+      "colourQuaternary": Blockly.Colours.event.quaternary
     });
   }
 };
diff --git a/blocks_vertical/control.js b/blocks_vertical/control.js
index e5bb20b5..34187458 100644
--- a/blocks_vertical/control.js
+++ b/blocks_vertical/control.js
@@ -209,7 +209,8 @@ Blockly.Blocks['control_stop'] = {
     this.setCategory(Blockly.Categories.control);
     this.setColour(Blockly.Colours.control.primary,
         Blockly.Colours.control.secondary,
-        Blockly.Colours.control.tertiary
+        Blockly.Colours.control.tertiary,
+        Blockly.Colours.control.quaternary
     );
     this.setPreviousStatement(true);
   },
diff --git a/blocks_vertical/data.js b/blocks_vertical/data.js
index a2e1df0e..726697d4 100644
--- a/blocks_vertical/data.js
+++ b/blocks_vertical/data.js
@@ -118,9 +118,7 @@ Blockly.Blocks['data_showvariable'] = {
       "previousStatement": null,
       "nextStatement": null,
       "category": Blockly.Categories.data,
-      "colour": Blockly.Colours.data.primary,
-      "colourSecondary": Blockly.Colours.data.secondary,
-      "colourTertiary": Blockly.Colours.data.tertiary
+      "extensions": ["colours_data"]
     });
   }
 };
@@ -142,9 +140,7 @@ Blockly.Blocks['data_hidevariable'] = {
       "previousStatement": null,
       "nextStatement": null,
       "category": Blockly.Categories.data,
-      "colour": Blockly.Colours.data.primary,
-      "colourSecondary": Blockly.Colours.data.secondary,
-      "colourTertiary": Blockly.Colours.data.tertiary
+      "extensions": ["colours_data"]
     });
   }
 };
diff --git a/blocks_vertical/event.js b/blocks_vertical/event.js
index c51a93cb..5694893c 100644
--- a/blocks_vertical/event.js
+++ b/blocks_vertical/event.js
@@ -216,6 +216,7 @@ Blockly.Blocks['event_broadcast_menu'] = {
       "colour": Blockly.Colours.event.secondary,
       "colourSecondary": Blockly.Colours.event.secondary,
       "colourTertiary": Blockly.Colours.event.tertiary,
+      "colourQuaternary": Blockly.Colours.event.quaternary,
       "extensions": ["output_string"]
     });
   }
diff --git a/blocks_vertical/looks.js b/blocks_vertical/looks.js
index 92629a6d..66482f0e 100644
--- a/blocks_vertical/looks.js
+++ b/blocks_vertical/looks.js
@@ -367,6 +367,7 @@ Blockly.Blocks['looks_costume'] = {
       "colour": Blockly.Colours.looks.secondary,
       "colourSecondary": Blockly.Colours.looks.secondary,
       "colourTertiary": Blockly.Colours.looks.tertiary,
+      "colourQuaternary": Blockly.Colours.looks.quaternary,
       "extensions": ["output_string"]
     });
   }
@@ -447,6 +448,7 @@ Blockly.Blocks['looks_backdrops'] = {
       "colour": Blockly.Colours.looks.secondary,
       "colourSecondary": Blockly.Colours.looks.secondary,
       "colourTertiary": Blockly.Colours.looks.tertiary,
+      "colourQuaternary": Blockly.Colours.looks.quaternary,
       "extensions": ["output_string"]
     });
   }
diff --git a/blocks_vertical/motion.js b/blocks_vertical/motion.js
index b40d2b03..b4b59692 100644
--- a/blocks_vertical/motion.js
+++ b/blocks_vertical/motion.js
@@ -141,6 +141,7 @@ Blockly.Blocks['motion_pointtowards_menu'] = {
       "colour": Blockly.Colours.motion.secondary,
       "colourSecondary": Blockly.Colours.motion.secondary,
       "colourTertiary": Blockly.Colours.motion.tertiary,
+      "colourQuaternary": Blockly.Colours.motion.quaternary,
       "extensions": ["output_string"]
     });
   }
@@ -187,6 +188,7 @@ Blockly.Blocks['motion_goto_menu'] = {
       "colour": Blockly.Colours.motion.secondary,
       "colourSecondary": Blockly.Colours.motion.secondary,
       "colourTertiary": Blockly.Colours.motion.tertiary,
+      "colourQuaternary": Blockly.Colours.motion.quaternary,
       "extensions": ["output_string"]
     });
   }
@@ -285,6 +287,7 @@ Blockly.Blocks['motion_glideto_menu'] = {
       "colour": Blockly.Colours.motion.secondary,
       "colourSecondary": Blockly.Colours.motion.secondary,
       "colourTertiary": Blockly.Colours.motion.tertiary,
+      "colourQuaternary": Blockly.Colours.motion.quaternary,
       "extensions": ["output_string"]
     });
   }
diff --git a/blocks_vertical/procedures.js b/blocks_vertical/procedures.js
index fc7df290..1d2d2153 100644
--- a/blocks_vertical/procedures.js
+++ b/blocks_vertical/procedures.js
@@ -943,6 +943,7 @@ Blockly.Blocks['argument_editor_boolean'] = {
       "colour": Blockly.Colours.textField,
       "colourSecondary": Blockly.Colours.textField,
       "colourTertiary": Blockly.Colours.textField,
+      "colourQuaternary": Blockly.Colours.textField,
       "extensions": ["output_boolean"]
     });
   },
@@ -963,6 +964,7 @@ Blockly.Blocks['argument_editor_string_number'] = {
       "colour": Blockly.Colours.textField,
       "colourSecondary": Blockly.Colours.textField,
       "colourTertiary": Blockly.Colours.textField,
+      "colourQuaternary": Blockly.Colours.textField,
       "extensions": ["output_number", "output_string"]
     });
   },
diff --git a/blocks_vertical/sound.js b/blocks_vertical/sound.js
index d4e4e824..03c33630 100644
--- a/blocks_vertical/sound.js
+++ b/blocks_vertical/sound.js
@@ -59,6 +59,7 @@ Blockly.Blocks['sound_sounds_menu'] = {
       "colour": Blockly.Colours.sounds.secondary,
       "colourSecondary": Blockly.Colours.sounds.secondary,
       "colourTertiary": Blockly.Colours.sounds.tertiary,
+      "colourQuaternary": Blockly.Colours.sounds.quaternary,
       "extensions": ["output_string"]
     });
   }
diff --git a/blocks_vertical/vertical_extensions.js b/blocks_vertical/vertical_extensions.js
index 76fe3aa5..2d6c13ad 100644
--- a/blocks_vertical/vertical_extensions.js
+++ b/blocks_vertical/vertical_extensions.js
@@ -35,25 +35,26 @@ goog.require('Blockly.constants');
 
 /**
  * Helper function that generates an extension based on a category name.
- * The generated function will set primary, secondary, and tertiary colours
- * based on the category name.
+ * The generated function will set primary, secondary, tertiary, and quaternary
+ * colours based on the category name.
  * @param {String} category The name of the category to set colours for.
  * @return {function} An extension function that sets colours based on the given
  *     category.
  */
 Blockly.ScratchBlocks.VerticalExtensions.colourHelper = function(category) {
   var colours = Blockly.Colours[category];
-  if (!(colours && colours.primary && colours.secondary && colours.tertiary)) {
+  if (!(colours && colours.primary && colours.secondary && colours.tertiary &&
+    colours.quaternary)) {
     throw new Error('Could not find colours for category "' + category + '"');
   }
   /**
-   * Set the primary, secondary, and tertiary colours on this block for the
-   * given category.
+   * Set the primary, secondary, tertiary, and quaternary colours on this block for
+   * the given category.
    * @this {Blockly.Block}
    */
   return function() {
     this.setColourFromRawValues_(colours.primary, colours.secondary,
-        colours.tertiary);
+        colours.tertiary, colours.quaternary);
   };
 };
 
diff --git a/core/block.js b/core/block.js
index 88c11fb9..176cbcc5 100644
--- a/core/block.js
+++ b/core/block.js
@@ -238,6 +238,13 @@ Blockly.Block.prototype.colourSecondary_ = '#FF0000';
  */
 Blockly.Block.prototype.colourTertiary_ = '#FF0000';
 
+/**
+ * Quaternary colour of the block in '#RRGGBB' format.
+ * @type {string}
+ * @private
+ */
+Blockly.Block.prototype.colourQuaternary = '#FF0000';
+
 /**
  * Fill colour used to override default shadow colour behaviour.
  * @type {string}
@@ -798,6 +805,14 @@ Blockly.Block.prototype.getColourTertiary = function() {
   return this.colourTertiary_;
 };
 
+/**
+ * Get the quaternary colour of a block.
+ * @return {string} #RRGGBB string.
+ */
+Blockly.Block.prototype.getColourQuaternary = function() {
+  return this.colourQuaternary_;
+};
+
 /**
  * Get the shadow colour of a block.
  * @return {string} #RRGGBB string.
@@ -849,8 +864,10 @@ Blockly.Block.prototype.makeColour_ = function(colour) {
  * @param {number|string} colour HSV hue value, or #RRGGBB string.
  * @param {number|string} colourSecondary HSV hue value, or #RRGGBB string.
  * @param {number|string} colourTertiary HSV hue value, or #RRGGBB string.
+ * @param {number|string} colourQuaternary HSV hue value, or #RRGGBB string.
  */
-Blockly.Block.prototype.setColour = function(colour, colourSecondary, colourTertiary) {
+Blockly.Block.prototype.setColour = function(colour, colourSecondary, colourTertiary,
+    colourQuaternary) {
   this.colour_ = this.makeColour_(colour);
   if (colourSecondary !== undefined) {
     this.colourSecondary_ = this.makeColour_(colourSecondary);
@@ -864,6 +881,11 @@ Blockly.Block.prototype.setColour = function(colour, colourSecondary, colourTert
     this.colourTertiary_ = goog.color.rgbArrayToHex(
         goog.color.darken(goog.color.hexToRgb(this.colour_), 0.2));
   }
+  if (colourQuaternary !== undefined) {
+    this.colourQuaternary_ = this.makeColour_(colourQuaternary);
+  } else {
+    this.colourQuaternary_ = this.colourTertiary_;
+  }
   if (this.rendered) {
     this.updateColour();
   }
@@ -1364,18 +1386,22 @@ Blockly.Block.prototype.mixin = function(mixinObj, opt_disableCheck) {
  *     contains string table references.
  * @param {string|?} tertiary Tertiary colour, which may be a string that
  *     contains string table references.
+ * @param {string|?} quaternary Quaternary colour, which may be a string that
+ *     contains string table references.
  * @private
  */
 Blockly.Block.prototype.setColourFromRawValues_ = function(primary, secondary,
-    tertiary) {
+    tertiary, quaternary) {
   primary = goog.isString(primary) ?
       Blockly.utils.replaceMessageReferences(primary) : primary;
   secondary = goog.isString(secondary) ?
       Blockly.utils.replaceMessageReferences(secondary) : secondary;
   tertiary = goog.isString(tertiary) ?
       Blockly.utils.replaceMessageReferences(tertiary) : tertiary;
+  quaternary = goog.isString(quaternary) ?
+      Blockly.utils.replaceMessageReferences(quaternary) : quaternary;
 
-  this.setColour(primary, secondary, tertiary);
+  this.setColour(primary, secondary, tertiary, quaternary);
 };
 
 /**
@@ -1386,7 +1412,7 @@ Blockly.Block.prototype.setColourFromRawValues_ = function(primary, secondary,
  */
 Blockly.Block.prototype.setColourFromJson_ = function(json) {
   this.setColourFromRawValues_(json['colour'], json['colourSecondary'],
-      json['colourTertiary']);
+      json['colourTertiary'], json['colourQuaternary']);
 };
 
 /**
diff --git a/core/block_svg.js b/core/block_svg.js
index 73c0133c..6e24dbe8 100644
--- a/core/block_svg.js
+++ b/core/block_svg.js
@@ -309,7 +309,7 @@ Blockly.BlockSvg.prototype.setParent = function(newParent) {
     // If we are a shadow block, inherit tertiary colour.
     if (this.isShadow()) {
       this.setColour(this.getColour(), this.getColourSecondary(),
-          newParent.getColourTertiary());
+          newParent.getColourTertiary(), this.getColourQuaternary());
     }
   }
   // If we are losing a parent, we want to move our DOM element to the
@@ -1060,11 +1060,13 @@ Blockly.BlockSvg.prototype.setDeleteStyle = function(enable) {
  *    string.
  * @param {number|string} colourTertiary Tertiary HSV hue value, or #RRGGBB
  *    string.
+ * @param {number|string} colourQuaternary Quaternary HSV hue value, or #RRGGBB
+ *    string.
  */
 Blockly.BlockSvg.prototype.setColour = function(colour, colourSecondary,
-    colourTertiary) {
+    colourTertiary, colourQuaternary) {
   Blockly.BlockSvg.superClass_.setColour.call(this, colour, colourSecondary,
-      colourTertiary);
+      colourTertiary, colourQuaternary);
 
   if (this.rendered) {
     this.updateColour();
diff --git a/core/colours.js b/core/colours.js
index 58e4f96d..78b259c8 100644
--- a/core/colours.js
+++ b/core/colours.js
@@ -28,59 +28,70 @@ Blockly.Colours = {
   "motion": {
     "primary": "#4C97FF",
     "secondary": "#4280D7",
-    "tertiary": "#3373CC"
+    "tertiary": "#3373CC",
+    "quaternary": "#3373CC"
   },
   "looks": {
     "primary": "#9966FF",
     "secondary": "#855CD6",
-    "tertiary": "#774DCB"
+    "tertiary": "#774DCB",
+    "quaternary": "#774DCB"
   },
   "sounds": {
     "primary": "#CF63CF",
     "secondary": "#C94FC9",
-    "tertiary": "#BD42BD"
+    "tertiary": "#BD42BD",
+    "quaternary": "#BD42BD"
   },
   "control": {
     "primary": "#FFAB19",
     "secondary": "#EC9C13",
-    "tertiary": "#CF8B17"
+    "tertiary": "#CF8B17",
+    "quaternary": "#CF8B17"
   },
   "event": {
     "primary": "#FFBF00",
     "secondary": "#E6AC00",
-    "tertiary": "#CC9900"
+    "tertiary": "#CC9900",
+    "quaternary": "#CC9900"
   },
   "sensing": {
     "primary": "#5CB1D6",
     "secondary": "#47A8D1",
-    "tertiary": "#2E8EB8"
+    "tertiary": "#2E8EB8",
+    "quaternary": "#2E8EB8"
   },
   "pen": {
     "primary": "#0fBD8C",
     "secondary": "#0DA57A",
-    "tertiary": "#0B8E69"
+    "tertiary": "#0B8E69",
+    "quaternary": "#0B8E69"
   },
   "operators": {
     "primary": "#59C059",
     "secondary": "#46B946",
-    "tertiary": "#389438"
+    "tertiary": "#389438",
+    "quaternary": "#389438"
   },
   "data": {
     "primary": "#FF8C1A",
     "secondary": "#FF8000",
-    "tertiary": "#DB6E00"
+    "tertiary": "#DB6E00",
+    "quaternary": "#DB6E00"
   },
   // This is not a new category, but rather for differentiation
   // between lists and scalar variables.
   "data_lists": {
     "primary": "#FF661A",
     "secondary": "#FF5500",
-    "tertiary": "#E64D00"
+    "tertiary": "#E64D00",
+    "quaternary": "#E64D00"
   },
   "more": {
     "primary": "#FF6680",
     "secondary": "#FF4D6A",
-    "tertiary": "#FF3355"
+    "tertiary": "#FF3355",
+    "quaternary": "#FF3355"
   },
   "text": "#FFFFFF",
   "workspace": "#F9F9F9",
@@ -111,7 +122,8 @@ Blockly.Colours = {
   "numPadActiveBackground": "#435F91",
   "numPadText": "white", // Do not use hex here, it cannot be inlined with data-uri SVG
   "valueReportBackground": "#FFFFFF",
-  "valueReportBorder": "#AAAAAA"
+  "valueReportBorder": "#AAAAAA",
+  "menuHover": "rgba(0, 0, 0, 0.2)"
 };
 
 /**
diff --git a/core/css.js b/core/css.js
index e46579d5..03c6bab0 100644
--- a/core/css.js
+++ b/core/css.js
@@ -1169,7 +1169,7 @@ Blockly.Css.CONTENT = [
 
   '.blocklyDropDownDiv .goog-menuitem-highlight,',
   '.blocklyDropDownDiv .goog-menuitem-hover {',
-    'background-color: rgba(0, 0, 0, 0.2);',
+    'background-color: $colour_menuHover;',
   '}',
 
   /* State: selected/checked. */
diff --git a/core/field_colour.js b/core/field_colour.js
index 58afad24..6dab2e42 100644
--- a/core/field_colour.js
+++ b/core/field_colour.js
@@ -123,9 +123,9 @@ Blockly.FieldColour.prototype.setValue = function(colour) {
   }
   this.colour_ = colour;
   if (this.sourceBlock_) {
-    // Set the primary, secondary and tertiary colour to this value.
+    // Set the primary, secondary, tertiary, and quaternary colour to this value.
     // The renderer expects to be able to use the secondary color as the fill for a shadow.
-    this.sourceBlock_.setColour(colour, colour, colour);
+    this.sourceBlock_.setColour(colour, colour, colour, colour);
   }
 };
 
diff --git a/core/field_colour_slider.js b/core/field_colour_slider.js
index d8560573..331de697 100644
--- a/core/field_colour_slider.js
+++ b/core/field_colour_slider.js
@@ -112,9 +112,10 @@ Blockly.FieldColourSlider.prototype.setValue = function(colour) {
   }
   this.colour_ = colour;
   if (this.sourceBlock_) {
-    // Set the primary, secondary and tertiary colour to this value.
+    // Set the colours to this value.
     // The renderer expects to be able to use the secondary colour as the fill for a shadow.
-    this.sourceBlock_.setColour(colour, colour, this.sourceBlock_.getColourTertiary());
+    this.sourceBlock_.setColour(colour, colour, this.sourceBlock_.getColourTertiary(),
+        this.sourceBlock_.getColourQuaternary());
   }
   this.updateSliderHandles_();
   this.updateDom_();
diff --git a/core/field_dropdown.js b/core/field_dropdown.js
index 8d833c7f..627fbf89 100644
--- a/core/field_dropdown.js
+++ b/core/field_dropdown.js
@@ -250,9 +250,9 @@ Blockly.FieldDropdown.prototype.showEditor_ = function() {
   // Update colour to look selected.
   if (!this.disableColourChange_) {
     if (this.sourceBlock_.isShadow()) {
-      this.sourceBlock_.setShadowColour(this.sourceBlock_.getColourTertiary());
+      this.sourceBlock_.setShadowColour(this.sourceBlock_.getColourQuaternary());
     } else if (this.box_) {
-      this.box_.setAttribute('fill', this.sourceBlock_.getColourTertiary());
+      this.box_.setAttribute('fill', this.sourceBlock_.getColourQuaternary());
     }
   }
 };
diff --git a/core/field_iconmenu.js b/core/field_iconmenu.js
index 351f8891..f5d2a83b 100644
--- a/core/field_iconmenu.js
+++ b/core/field_iconmenu.js
@@ -254,7 +254,8 @@ Blockly.FieldIconMenu.prototype.showEditor_ = function() {
   this.savedPrimary_ = this.sourceBlock_.getColour();
   this.sourceBlock_.setColour(this.sourceBlock_.getColourSecondary(),
       this.sourceBlock_.getColourSecondary(),
-      this.sourceBlock_.getColourTertiary());
+      this.sourceBlock_.getColourTertiary(),
+      this.sourceBlock_.getColourQuaternary());
 
   var scale = this.sourceBlock_.workspace.scale;
   // Offset for icon-type horizontal blocks.
@@ -295,7 +296,8 @@ Blockly.FieldIconMenu.prototype.onHide_ = function() {
   if (this.sourceBlock_) {
     this.sourceBlock_.setColour(this.savedPrimary_,
         this.sourceBlock_.getColourSecondary(),
-        this.sourceBlock_.getColourTertiary());
+        this.sourceBlock_.getColourTertiary(),
+        this.sourceBlock_.getColourQuaternary());
   }
   Blockly.DropDownDiv.content_.removeAttribute('role');
   Blockly.DropDownDiv.content_.removeAttribute('aria-haspopup');
diff --git a/tests/jsunit/block_test.js b/tests/jsunit/block_test.js
index bb794069..d01a1e95 100644
--- a/tests/jsunit/block_test.js
+++ b/tests/jsunit/block_test.js
@@ -67,3 +67,40 @@ function test_jsonInit_FieldIconMenu() {
   assertTrue('IconMenu field not added to block by jsonInit',
       block.getField(field_name) instanceof Blockly.FieldIconMenu);
 }
+
+function test_jsonInit_colors() {
+  var workspace = new Blockly.Workspace();
+  var block_name = 'test_jsonInit_FieldDropdown_colors';
+  var field_name = 'TEST_FIELD';
+  var dropdown_options = [
+    ['value', 'VALUE']
+  ];
+
+  Blockly.Blocks[block_name] = {
+    init: function() {
+      this.jsonInit({
+        message0: '%1',
+        args0: [{
+          type: 'field_dropdown',
+          name: field_name,
+          options: dropdown_options
+        }],
+        output: null,
+        colour: '#111111',
+        colourSecondary: '#222222',
+        colourTertiary: '#333333',
+        colourQuaternary: '#444444'
+      });
+    }
+  };
+
+  var block = workspace.newBlock(block_name);
+  var field =  block.getField(field_name);
+
+  assertEquals('Block primary colour not set', block.getColour(), '#111111');
+  assertEquals('Block secondary colour not set', block.getColourSecondary(), '#222222');
+  assertEquals('Block tertiary colour not set', block.getColourTertiary(), '#333333');
+  assertEquals('Block quaternary colour not set', block.getColourQuaternary(), '#444444');
+
+  assertEquals('Source block is not correct', field.sourceBlock_, block);
+}