diff --git a/pd/nw/css/c64.css b/pd/nw/css/c64.css
index bca01f1a8d1c0cc396b173f22c0884c8eb50f62e..086afaba2caa5a05ed26f8af606cba74d643f966 100644
--- a/pd/nw/css/c64.css
+++ b/pd/nw/css/c64.css
@@ -220,6 +220,50 @@ text {
     fill: #3e32a2;
 }
 
+/* for dropdown box we want to visually distinguish boxes that output
+   the index from boxes that output the value. For now we do that by
+   stroking the arrow for boxes that output an index. For boxes that
+   output the value we don't need a CSS rule, as the arrow will be filled
+   black by default */
+.atom .index_arrow {
+    stroke: black;
+    stroke-width: 1;
+    fill: none;
+}
+
+/* gatom "activated" text (i.e., when it has the keyboard focus) */
+.atom.activated text {
+    fill: red;
+}
+
+#dropdown_list {
+    position: absolute;
+    border-width: 1px;
+    border-style: solid;
+    border-color: #7569d7;
+    cursor: pointer;
+    box-shadow: 2px 2px 0px #7569d7;
+}
+
+#dropdown_list ol {
+    list-style-position: inside;
+    margin: 0;
+    padding: 0;
+    background: #3e32a2;
+    outline: #7569d7;
+}
+
+#dropdown_list li {
+    color: #a49aea;
+    list-style-type: none;
+    padding: 5px;
+}
+
+#dropdown_list li.highlighted {
+    color: black;
+    background: #e87216;
+}
+
 .obj .border {
     fill: #3e32a2;
     stroke: #7569d7;
diff --git a/pd/nw/css/default.css b/pd/nw/css/default.css
index f0ed0040221c6061ececac8667173cdbb79cf880..21271136713a8a18af7ccd39f86c933094c50b5e 100644
--- a/pd/nw/css/default.css
+++ b/pd/nw/css/default.css
@@ -334,6 +334,47 @@ text {
     fill: #eee;
 }
 
+/* for dropdown box we want to visually distinguish boxes that output
+   the index from boxes that output the value. For now we do that by
+   stroking the arrow for boxes that output an index. For boxes that
+   output the value we don't need a CSS rule, as the arrow will be filled
+   black by default */
+.atom .index_arrow {
+    stroke: black;
+    stroke-width: 1;
+    fill: none;
+}
+
+/* gatom "activated" text (i.e., when it has the keyboard focus) */
+.atom.activated text {
+    fill: red;
+}
+
+#dropdown_list {
+    position: absolute;
+    border-width: 1px;
+    border-style: solid;
+    border-color: #c3c3c3;
+    cursor: pointer;
+    box-shadow: 5px 0 5px -5px #aaa, 0 5px 5px -5px #aaa, -5px 0 5px -5px #aaa;
+}
+
+#dropdown_list ol {
+    list-style-position: inside;
+    margin: 0;
+    padding: 0;
+    background: #eee;
+}
+
+#dropdown_list li {
+    list-style-type: none;
+    padding: 5px;
+}
+
+#dropdown_list li.highlighted {
+    background-color: #c3c3c3;
+}
+
 .obj .border {
     fill: #f6f8f8;
     stroke: #ccc;
@@ -410,11 +451,6 @@ text {
     fill: blue;
 }
 
-/* gatom "activated" text (i.e., when it has the keyboard focus) */
-.atom.activated text {
-    fill: red;
-}
-
 /* test of xlet hover animation... this should 
    probably use the web animation API instead. That
    way the animation won't get cut off when you
diff --git a/pd/nw/css/extended.css b/pd/nw/css/extended.css
index 2e320b165087c23648efe8fc52254557962cbb7e..53e0728fbc5facfd5b6b06f07de0be9a1825d379 100644
--- a/pd/nw/css/extended.css
+++ b/pd/nw/css/extended.css
@@ -214,6 +214,47 @@ text {
     fill: #e0e0e0;
 }
 
+/* for dropdown box we want to visually distinguish boxes that output
+   the index from boxes that output the value. For now we do that by
+   stroking the arrow for boxes that output an index. For boxes that
+   output the value we don't need a CSS rule, as the arrow will be filled
+   black by default */
+.atom .index_arrow {
+    stroke: black;
+    stroke-width: 1;
+    fill: none;
+}
+
+/* gatom "activated" text (i.e., when it has the keyboard focus) */
+.atom.activated text {
+    fill: red;
+}
+
+#dropdown_list {
+    position: absolute;
+    border-width: 1px;
+    border-style: solid;
+    border-color: #c3c3c3;
+    cursor: pointer;
+    box-shadow: 5px 0 5px -5px #888, 0 5px 5px -5px #888, -5px 0 5px -5px #888;
+}
+
+#dropdown_list ol {
+    list-style-position: inside;
+    margin: 0;
+    padding: 0;
+    background: #eee;
+}
+
+#dropdown_list li {
+    list-style-type: none;
+    padding: 5px;
+}
+
+#dropdown_list li.highlighted {
+    background: #c3c3c3;
+}
+
 .obj .border {
     fill: #f6f8f8;
     stroke: #c1c1c1;
diff --git a/pd/nw/css/inverted.css b/pd/nw/css/inverted.css
index 76a42c44db381461714e6658cff1be79ae0442c6..009384432b76bd783f2b9ad9cd2786e7847c46e3 100644
--- a/pd/nw/css/inverted.css
+++ b/pd/nw/css/inverted.css
@@ -232,6 +232,50 @@ text {
     fill: #111;
 }
 
+/* for dropdown box we want to visually distinguish boxes that output
+   the index from boxes that output the value. For now we do that by
+   stroking the arrow for boxes that output an index. */
+.atom .index_arrow {
+    stroke: #a294a2;
+    stroke-width: 1;
+    fill: none;
+}
+
+.atom .value_arrow {
+    fill: #a294a2;
+}
+
+/* gatom "activated" text (i.e., when it has the keyboard focus) */
+.atom.activated text {
+    fill: red;
+}
+
+#dropdown_list {
+    position: absolute;
+    border-width: 1px;
+    border-style: solid;
+    border-color: #999;
+    cursor: pointer;
+    color: white;
+    box-shadow: 5px 0 5px -5px #999, 0 5px 5px -5px #999, -5px 0 5px -5px #999;
+}
+
+#dropdown_list ol {
+    list-style-position: inside;
+    margin: 0;
+    padding: 0;
+    background: black;
+}
+
+#dropdown_list li {
+    list-style-type: none;
+    padding: 5px;
+}
+
+#dropdown_list li.highlighted {
+    background: #555;
+}
+
 .obj .border {
     fill: #090707;
     stroke: #3e3e3e;
diff --git a/pd/nw/css/strongbad.css b/pd/nw/css/strongbad.css
index c7e00cc25fddf70f886b6886f35591f307b10a24..fb26b42c62fd56a1310b271632c4f5db605737f3 100644
--- a/pd/nw/css/strongbad.css
+++ b/pd/nw/css/strongbad.css
@@ -222,6 +222,51 @@ text {
     fill: black;
 }
 
+/* for dropdown box we want to visually distinguish boxes that output
+   the index from boxes that output the value. For now we do that by
+   stroking the arrow for boxes that output an index. */
+.atom .index_arrow {
+    stroke: #4bd046;
+    stroke-width: 1;
+    fill: none;
+}
+
+.atom .value_arrow {
+   fill: #4bd046;
+}
+
+/* gatom "activated" text (i.e., when it has the keyboard focus) */
+.atom.activated text {
+    fill: red;
+}
+
+#dropdown_list {
+    position: absolute;
+    border-width: 1px;
+    border-style: solid;
+    border-color: #0b560b;
+    cursor: pointer;
+    color: #4bd046;
+    box-shadow: 1px 1px 1px 1px #0b560b;
+}
+
+#dropdown_list ol {
+    list-style-position: inside;
+    margin: 0;
+    padding: 0;
+    background: black;
+}
+
+#dropdown_list li {
+    list-style-type: none;
+    padding: 5px;
+}
+
+#dropdown_list li.highlighted {
+    background: #4bd046;
+    color: black;
+}
+
 .obj .border {
     fill: black;
     stroke: #0b560b;
diff --git a/pd/nw/css/subdued.css b/pd/nw/css/subdued.css
index f4966126425eb64da9a0e6c618007275c6d5d924..fd9d33413bfae848e770dd7cdb14a442d988b40b 100644
--- a/pd/nw/css/subdued.css
+++ b/pd/nw/css/subdued.css
@@ -221,6 +221,46 @@ text {
     fill: #9fc79f;
 }
 
+/* for dropdown box we want to visually distinguish boxes that output
+   the index from boxes that output the value. For now we do that by
+   stroking the arrow for boxes that output an index. For boxes that
+   output the value we don't need a CSS rule, as the arrow will be filled
+   black by default */
+.atom .index_arrow {
+    stroke: black;
+    stroke-width: 1;
+    fill: none;
+}
+
+/* gatom "activated" text (i.e., when it has the keyboard focus) */
+.atom.activated text {
+    fill: red;
+}
+
+#dropdown_list {
+    position: absolute;
+    border-width: 1px;
+    border-style: solid;
+    border-color: #b1d3b1;
+    cursor: pointer;
+}
+
+#dropdown_list ol {
+    list-style-position: inside;
+    margin: 0;
+    padding: 0;
+    background: #9fc79f;
+}
+
+#dropdown_list li {
+    list-style-type: none;
+    padding: 5px;
+}
+
+#dropdown_list li.highlighted {
+    background: #c3c3c3;
+}
+
 .obj .border {
     fill: #c0dcc0;
     stroke: #666666;
diff --git a/pd/nw/css/vanilla.css b/pd/nw/css/vanilla.css
index 1ac5e5792f9d0bf7e8dcc6a40e2f90470bedbc3b..2694e502c96a7ba84421fcf769004c1947b89a67 100644
--- a/pd/nw/css/vanilla.css
+++ b/pd/nw/css/vanilla.css
@@ -215,6 +215,46 @@ text {
     fill: none;
 }
 
+/* for dropdown box we want to visually distinguish boxes that output
+   the index from boxes that output the value. For now we do that by
+   stroking the arrow for boxes that output an index. For boxes that
+   output the value we don't need a CSS rule, as the arrow will be filled
+   black by default */
+.atom .index_arrow {
+    stroke: black;
+    stroke-width: 1;
+    fill: none;
+}
+
+/* gatom "activated" text (i.e., when it has the keyboard focus) */
+.atom.activated text {
+    fill: red;
+}
+
+#dropdown_list {
+    position: absolute;
+    border-width: 1px;
+    border-style: solid;
+    border-color: black;
+    cursor: pointer;
+}
+
+#dropdown_list ol {
+    list-style-position: inside;
+    margin: 0;
+    padding: 0;
+    background: white; 
+}
+
+#dropdown_list li {
+    list-style-type: none;
+    padding: 5px;
+}
+
+#dropdown_list li.highlighted {
+    background: #c3c3c3;
+}
+
 .obj .border {
     fill: none;
     stroke: black;
diff --git a/pd/nw/css/vanilla_inverted.css b/pd/nw/css/vanilla_inverted.css
index e1084e19148b1eb8961b319622b0eaca608eee61..aae6f878f51cd3fcb5f0147bb2ed7cdf17194722 100644
--- a/pd/nw/css/vanilla_inverted.css
+++ b/pd/nw/css/vanilla_inverted.css
@@ -226,6 +226,52 @@ text {
     fill: none;
 }
 
+/* for dropdown box we want to visually distinguish boxes that output
+   the index from boxes that output the value. For now we do that by
+   stroking the arrow for boxes that output an index. For boxes that
+   output the value we don't need a CSS rule, as the arrow will be filled
+   black by default */
+.atom .index_arrow {
+    stroke: white;
+    stroke-width: 1;
+    fill: none;
+}
+
+.atom .value_arrow {
+    fill: white;
+}
+
+/* gatom "activated" text (i.e., when it has the keyboard focus) */
+.atom.activated text {
+    fill: red;
+}
+
+#dropdown_list {
+    position: absolute;
+    border-width: 1px;
+    border-style: solid;
+    border-color: white;
+    cursor: pointer;
+    color: white;
+}
+
+#dropdown_list ol {
+    list-style-position: inside;
+    margin: 0;
+    padding: 0;
+    background: black;
+}
+
+#dropdown_list li {
+    list-style-type: none;
+    padding: 5px;
+}
+
+#dropdown_list li.highlighted {
+    background: #c3c3c3;
+    color: black;
+}
+
 .obj .border {
     fill: none;
     stroke: white;
diff --git a/pd/nw/dialog_gatom.html b/pd/nw/dialog_gatom.html
index 5a6be0ca11ec7fcc4ae945ffc30a3c57b57cc5f3..5d0e9fba4c197f45f6fd282d1e4ae39fb2a54e2c 100644
--- a/pd/nw/dialog_gatom.html
+++ b/pd/nw/dialog_gatom.html
@@ -7,7 +7,7 @@
     <div class="container">
     <form> 
       <fieldset> 
-        <legend data-i18n="gatom.prop.gatom"></legend> 
+        <legend id="dialog_header"></legend>
 
         <table class="pairs">
           <tr class="width prop">
@@ -45,6 +45,22 @@
                      onchange="update_attr(this)">
             </td>
           </tr>
+
+          <tr class="outtype prop">
+            <td>
+              <label data-i18n="[title]gatom.prop.dropdown_outtype_tt">
+                <span data-i18n="gatom.prop.dropdown_outtype"></span>
+              </label>
+            </td>
+            <td colspan="3"
+                data-i18n="[title]gatom.prop.dropdown_outtype_tt">
+              <select id="outtype_select"
+                      onchange="update_dropdown_outtype(this);">
+                <option>index</option>
+                <option>value</option>
+              </select>
+            </td>
+          </tr>
           <tr>
             <td>
               <label data-i18n="[title]iem.prop.send_tt">
@@ -215,12 +231,17 @@ function update_attr(elem) {
     new_attrs[elem.name] = elem.value;
 }
 
+function update_dropdown_outtype(elem) {
+    new_attrs.outtype = elem.selectedIndex;
+}
+
 function send_params(attrs, create_undo_point) {
     //pdgui.post("we're applying gatom changes!");
+    var gatom = attrs.name === "atom";
     pdgui.pdsend(pd_object_callback, "param",
         +attrs.width,
-        +attrs.draglo,
-        +attrs.draghi,
+        gatom ? +attrs.draglo : +attrs.outtype,
+        gatom ? +attrs.draghi : 0,
         gatom_escape(attrs.label),
         +attrs.labelpos,
         gatom_escape(attrs.receive_symbol),
@@ -266,9 +287,20 @@ function register_window_id(gfxstub, attributes) {
     var attr;
     pd_object_callback = gfxstub;
     add_events(gfxstub);
+
+    // before translating, set the header based on class name:
+    document.querySelector("#dialog_header")
+        .setAttribute("data-i18n", "gatom.prop." +
+            (attributes.name === "dropdown" ? "dropdown" : "gatom"));
+
     translate_form();
     populate_form(attributes);
 
+    // hide outtype select for "dropdown", or draglo/hi for "gatom"
+    document.querySelector(attributes.name === "atom" ? ".outtype" : ".draglo")
+        .style.setProperty("display", "none");
+
+
     // Hack... change incoming "-" to empty string
     if (attributes.label === "-") { attributes.label = ""; }
     if (attributes.send_symbol === "-") { attributes.send_symbol= ""; }
@@ -280,6 +312,7 @@ function register_window_id(gfxstub, attributes) {
             new_attrs[attr] = attributes[attr];
         }
     }
+
     old_attrs = attributes;
     // We don't turn on rendering of the "container" div until
     // We've finished displaying all the spans and populating the
@@ -320,8 +353,12 @@ function get_elem(name) {
 function populate_form(attributes) {
     var label, snd, rcv, labelpos, i, radios;
     get_elem("width").value = attributes.width;
-    get_elem("draglo").value = attributes.draglo;
-    get_elem("draghi").value = attributes.draghi;
+    if (attributes.name === "atom") {
+        get_elem("draglo").value = attributes.draglo;
+        get_elem("draghi").value = attributes.draghi;
+    } else {
+        get_elem("outtype_select").selectedIndex = attributes.outtype;
+    }
     label = attributes.label;
     get_elem("label").value = gatom_unescape(label);
     snd = attributes.send_symbol;
diff --git a/pd/nw/locales/de/translation.json b/pd/nw/locales/de/translation.json
index fa694938bcd3a9c6c8ce99b525683999c0a56071..8b51347374622bcb41d0cea9d289fb53bc47c7e2 100644
--- a/pd/nw/locales/de/translation.json
+++ b/pd/nw/locales/de/translation.json
@@ -79,6 +79,7 @@
   "gatom": {
     "prop": {
       "gatom": "Atom-Box",
+      "dropdown": "Ausgezähltwunderbox",
       "label": "Etikett",
       "label_left": "links",
       "label_right": "rechts",
@@ -89,6 +90,8 @@
       "label_bottom": "unten",
       "label_left": "links",
       "label_right": "rechts",
+      "dropdown_outtype": "output",
+      "dropdown_outtype_tt": "whether to output the index or the value",
       "width": "Breite",
       "width_tt": "Breite (in Zeichen)"
     }
@@ -195,6 +198,8 @@
     "symbol_tt": "Füge dem Patch ein Feld zur Eingabe und Anzeige von Symbolen hinzu",
     "comment": "Kommentar",
     "comment_tt": "Füge dem Patch einen Kommentar hinzu",
+    "dropdown": "Dropdown",
+    "dropdown_tt": "Dropdown menu",
     "bang": "Bang",
     "bang_tt": "Füge dem Patch einen Taster zum Senden von Bang-Nachrichten hinzu",
     "toggle": "Toggle",
diff --git a/pd/nw/locales/en/translation.json b/pd/nw/locales/en/translation.json
index 8e83ee1d7ead3b9337cb560bd74d85cc2333129c..44d4e12e5b3ebe575c136f044026bf0f56ca708e 100644
--- a/pd/nw/locales/en/translation.json
+++ b/pd/nw/locales/en/translation.json
@@ -79,6 +79,7 @@
   "gatom": {
     "prop": {
       "gatom": "atom box",
+      "dropdown": "dropdown menu",
       "label": "label",
       "label_left": "left",
       "label_right": "right",
@@ -89,6 +90,8 @@
       "label_bottom": "bottom",
       "label_left": "left",
       "label_right": "right",
+      "dropdown_outtype": "output",
+      "dropdown_outtype_tt": "whether to output the index or the value",
       "width": "width",
       "width_tt": "width (in characters)"
     }
@@ -195,6 +198,8 @@
     "symbol_tt": "Add a box to type and display a symbol on the canvas",
     "comment": "Comment",
     "comment_tt": "Write a comment on the canvas",
+    "dropdown": "Dropdown",
+    "dropdown_tt": "Dropdown menu",
     "bang": "Bang",
     "bang_tt": "Add a graphical button to the canvas for sending bang messages",
     "toggle": "Toggle",
diff --git a/pd/nw/pd_canvas.html b/pd/nw/pd_canvas.html
index 78460d810e6619ebcb819b64dcaac35d71252c3f..3d3d4d801adeec9efedc125850e9da4c20696a1e 100644
--- a/pd/nw/pd_canvas.html
+++ b/pd/nw/pd_canvas.html
@@ -47,6 +47,9 @@
         <span data-i18n="canvas.find.search"></span>
       </button>
     </div>
+    <div style="display:none;" id="dropdown_list" class="noselect">
+      <ol></ol>
+    </div>
     <dialog id="save_before_quit">
       <h4><span data-i18n="canvas.save_dialog.prompt"></span>
         <span id="save_before_quit_filename"></span>?
diff --git a/pd/nw/pd_canvas.js b/pd/nw/pd_canvas.js
index 8c066b8ad374a30b78f62bc3f003fd1adf91e4b7..106d56d5354de59bebe59475472e1e659b16cd02 100644
--- a/pd/nw/pd_canvas.js
+++ b/pd/nw/pd_canvas.js
@@ -197,6 +197,10 @@ var canvas_events = (function() {
             svg.setAttribute("width", w);
             svg.setAttribute("height", h);
         },
+        dropdown_index_to_pd = function(elem) {
+            pdgui.pdsend(elem.getAttribute("data-callback"),
+                elem.querySelector(".highlighted").getAttribute("data-index"));
+        },
         events = {
             mousemove: function(evt) {
                 //pdgui.post("x: " + evt.pageX + " y: " + evt.pageY +
@@ -460,6 +464,36 @@ var canvas_events = (function() {
                 // Set last state (none doesn't count as a state)
                 //pdgui.post("previous state is " + canvas_events.get_previous_state());
                 canvas_events[canvas_events.get_previous_state()]();
+            },
+            dropdown_menu_mousedown: function(evt) {
+                var select_elem = document.querySelector("#dropdown_list");
+                dropdown_index_to_pd(select_elem);
+                select_elem.style.setProperty("display", "none");
+                canvas_events.normal();
+            },
+            dropdown_menu_mouseup: function(evt) {
+                var i, select_elem;
+                if (evt.target.parentNode
+                    && evt.target.parentNode.parentNode
+                    && evt.target.parentNode.parentNode.id === "dropdown_list") {
+                    select_elem = document.querySelector("#dropdown_list");
+                    dropdown_index_to_pd(select_elem);
+                    select_elem.style.setProperty("display", "none");
+                    canvas_events.normal();
+                }
+            },
+            dropdown_menu_mouseover: function(evt) {
+                var li_array;
+                if (evt.target.parentNode
+                    && evt.target.parentNode.parentNode
+                    && evt.target.parentNode.parentNode.id === "dropdown_list") {
+                    li_array = evt.target.parentNode.querySelectorAll('li');
+                    li_array.forEach(function(e) {
+                        e.classList.remove("highlighted");
+                    });
+                    evt.target.classList.add("highlighted");
+                }
+                // hide the dropdown menu <div> thingy
             }
         },
         utils = {
@@ -701,6 +735,12 @@ var canvas_events = (function() {
             state = "floating_text";
             set_edit_menu_modals(false);
         },
+        dropdown_menu: function() {
+            this.none();
+            document.addEventListener("mousedown", events.dropdown_menu_mousedown, false);
+            document.addEventListener("mouseup", events.dropdown_menu_mouseup, false);
+            document.addEventListener("mouseover", events.dropdown_menu_mouseover, false);
+        },
         search: function() {
             this.none();
             document.addEventListener("keydown", events.find_keydown, false);
@@ -791,7 +831,7 @@ function register_window_id(cid, attr_array) {
     // Initialize the zoom level to the value retrieved from the patch, if any.
     nw.Window.get().zoomLevel = attr_array.zoom;
     pdgui.canvas_map(cid); // side-effect: triggers gui_canvas_get_scroll
-    set_editmode_checkbox(attr_array.editmode !== 0 ? true : false);
+    pdgui.canvas_set_editmode(cid, attr_array.editmod);
     // For now, there is no way for the cord inspector to be turned on by
     // default. But if this changes we need to set its menu item checkbox
     // accordingly here
@@ -1319,6 +1359,14 @@ function nw_create_patch_window_menus(gui, w, name) {
             pdgui.pdsend(name, "text 0");
         }
     });
+    minit(m.put.dropdown, {
+        enabled: true,
+        click: function() {
+            update_live_box();
+            pdgui.pdsend(name, "dirty 1");
+            pdgui.pdsend(name, "dropdown 0");
+        }
+    });
     minit(m.put.bang, {
         enabled: true,
         click: function(e) {
diff --git a/pd/nw/pd_menus.js b/pd/nw/pd_menus.js
index 85d8fce67d0dcd6f955293a1d81d2662da0abdca..abe2935ac313cab1b36a4b6e0dce165e6b1d3c13 100644
--- a/pd/nw/pd_menus.js
+++ b/pd/nw/pd_menus.js
@@ -425,6 +425,12 @@ function create_menu(gui, type) {
             modifiers: cmd_or_ctrl,
             tooltip: l("menu.comment_tt")
         }));
+        put_menu.append(m.put.dropdown = new gui.MenuItem({
+            label: l("menu.dropdown"),
+            //key: "6",
+            //modifiers: cmd_or_ctrl,
+            tooltip: l("menu.dropdown_tt")
+        }));
         put_menu.append(new gui.MenuItem({ type: "separator" }));
         put_menu.append(m.put.bang = new gui.MenuItem({
             label: l("menu.bang"),
diff --git a/pd/nw/pdgui.js b/pd/nw/pdgui.js
index 82643aec74b9aef7ce5f02b036ffe5e26d0a5b8f..002843fb915261382ee521fe10bf2f2e8336ff21 100644
--- a/pd/nw/pdgui.js
+++ b/pd/nw/pdgui.js
@@ -951,8 +951,20 @@ function menu_send(name) {
 }
 
 // requires nw.js API (Menuitem)
-function gui_canvas_set_editmode(cid, state) {
+function canvas_set_editmode(cid, state) {
+    var patchsvg = patchwin[cid].window.document.querySelector("#patchsvg");
     patchwin[cid].window.set_editmode_checkbox(state !== 0 ? true : false);
+    if (state !== 0) {
+        patchsvg.classList.add("editmode");
+    } else {
+        patchsvg.classList.remove("editmode");
+    }
+}
+
+exports.canvas_set_editmode = canvas_set_editmode;
+
+function gui_canvas_set_editmode(cid, state) {
+    canvas_set_editmode(cid, state);
 }
 
 // requires nw.js API (Menuitem)
@@ -1946,21 +1958,34 @@ function gui_message_redraw_border(cid, tag, width, height) {
     });
 }
 
-function atom_border_points(width, height) {
-    return [0, 0,
+function atom_border_points(width, height, is_dropdown) {
+    // For atom, angle the top-right corner.
+    // For dropdown, angle both top-right and bottom-right corners
+    var bottom_right_x = is_dropdown ? width - 4 : width;
+    return  [0, 0,
             width - 4, 0,
             width, 4,
-            width, height,
+            width, height - 4,
+            bottom_right_x, height,
             0, height,
             0, 0]
         .join(" ");
 }
 
-function gui_atom_draw_border(cid, tag, width, height) {
+function atom_arrow_points(width, height) {
+    var m = height < 20 ? 1 : height / 12;
+    return [width - (9 * m), height * 0.5 - Math.floor(1 * m),
+        width - (3 * m), height * 0.5 - Math.floor(1 * m),
+        width - (6 * m), height * 0.5 + Math.floor(4 * m),
+    ].join(" ");
+}
+
+
+function gui_atom_draw_border(cid, tag, type, width, height) {
     var g = get_gobj(cid, tag),
-        polygon;
+        polygon, arrow, m;
     polygon = create_item(cid, "polygon", {
-        points: atom_border_points(width, height),
+        points: atom_border_points(width, height, type !== 0),
         fill: "none",
         stroke: "gray",
         "stroke-width": 1,
@@ -1968,11 +1993,23 @@ function gui_atom_draw_border(cid, tag, width, height) {
         //id: tag + "border"
     });
     g.appendChild(polygon);
+    if (type !== 0) { // dropdown
+        // 1 = output index
+        // 2 = output value
+        // Let's make the two visually distinct so that the user can still
+        // reason about the patch functionality merely by reading the diagram
+        m = height < 20 ? 1 : height / 12;
+        arrow = create_item(cid, "polygon", {
+            points: atom_arrow_points(width, height),
+            "class": type === 1 ? "index_arrow" : "value_arrow"
+        });
+        g.appendChild(arrow);
+    }
 }
 
-function gui_atom_redraw_border(cid, tag, width, height) {
+function gui_atom_redraw_border(cid, tag, is_dropdown, width, height) {
     var g = get_gobj(cid, tag),
-        p;
+        p, a;
     // Unfortunately Pd will send updates for gui objects that
     // lie outside the bounding box of a graph-on-parent subpach.
     // We should refrain from sending such messages from Pd, but for
@@ -1986,6 +2023,12 @@ function gui_atom_redraw_border(cid, tag, width, height) {
             configure_item(p, {
                 points: atom_border_points(width, height)
             });
+            if (!!is_dropdown) {
+                a = g.querySelectorAll("polygon")[1];
+                configure_item(a , {
+                    points: atom_arrow_points(width, height)
+                });
+            }
         }
     }
 }
@@ -2305,6 +2348,14 @@ function elem_displace(elem, dx, dy) {
         t.matrix.f += dy;
 }
 
+function elem_get_coords(elem) {
+    var t = elem.transform.baseVal.getItem(0);
+    return {
+        x: t.matrix.e,
+        y: t.matrix.f
+    }
+}
+
 // used for tidy up
 function gui_text_displace(name, tag, dx, dy) {
     elem_displace(get_gobj(name, tag), dx, dy);
@@ -4383,6 +4434,61 @@ function gui_gatom_activate(cid, tag, state) {
     }
 }
 
+function gui_dropdown_dialog(did, attr_array) {
+    // Just reuse the "gatom" dialog
+    dialogwin[did] = create_window(did, "gatom", 265, 300,
+        popup_coords[2], popup_coords[3],
+        attr_array_to_object(attr_array));
+}
+
+function dropdown_populate(cid, label_array, current_index) {
+    var ol = patchwin[cid]
+        .window.document.querySelector("#dropdown_list ol");
+    // clear it out
+    ol.innerHTML = '';
+    label_array.forEach(function(text, i) {
+        var li = patchwin[cid].window.document.createElement("li");
+        li.textContent = text;
+        li.setAttribute("data-index", i);
+        if (i === current_index) {
+            li.classList.add("highlighted");
+        }
+        ol.appendChild(li);
+    });
+}
+
+function gui_dropdown_activate(cid, obj_tag, tag, current_index, font_size, state, label_array) {
+    var g, select_elem, svg_view;
+    // Annoying: obj_tag is just the "x"-prepended hex value for the object,
+    // and tag is the one from rtext_gettag that is used as our gobj id
+    if (patchwin[cid]) {
+        g = get_gobj(cid, tag);
+        if (state !== 0) {
+            svg_view = patchwin[cid].window.document.getElementById("patchsvg")
+                .viewBox.baseVal;
+            dropdown_populate(cid, label_array, current_index);
+            select_elem = patchwin[cid]
+                .window.document.querySelector("#dropdown_list");
+            // stick the obj_tag in a data field
+            select_elem.setAttribute("data-callback", obj_tag);
+            select_elem.style.setProperty("display", "inline");
+            select_elem.style.setProperty("left",
+                (elem_get_coords(g).x - svg_view.x) + "px");
+            select_elem.style.setProperty("top",
+                (elem_get_coords(g).y + g.getBBox().height - svg_view.y)
+                    + "px");
+            select_elem.style.setProperty("font-size",
+                pd_fontsize_to_gui_fontsize(font_size) + "px");
+            select_elem.style.setProperty("min-width", g.getBBox().width + "px");
+            patchwin[cid].window.canvas_events.dropdown_menu();
+        } else {
+            post("deactivating dropdown menu");
+            // Probably want to send this
+            pdsend(cid, "key 0 Control 0 1 0");
+        }
+    }
+}
+
 function gui_iemgui_dialog(did, attr_array) {
     //for (var i = 0; i < attr_array.length; i++) {
     //    attr_array[i] = '"' + attr_array[i] + '"';
diff --git a/pd/src/g_canvas.c b/pd/src/g_canvas.c
index aec7efd3ed82efb42e11ad296b53ed457bbd92a1..c0b10036b7baf97e43a5b940132a9764e778c246 100644
--- a/pd/src/g_canvas.c
+++ b/pd/src/g_canvas.c
@@ -2465,6 +2465,7 @@ extern void canvas_numbox(t_glist *gl, t_symbol *s, int argc, t_atom *argv);
 extern void canvas_msg(t_glist *gl, t_symbol *s, int argc, t_atom *argv);
 extern void canvas_floatatom(t_glist *gl, t_symbol *s, int argc, t_atom *argv);
 extern void canvas_symbolatom(t_glist *gl, t_symbol *s, int argc, t_atom *argv);
+extern void canvas_dropdown(t_glist *gl, t_symbol *s, int argc, t_atom *argv);
 extern void glist_scalar(t_glist *canvas, t_symbol *s, int argc, t_atom *argv);
 
 void g_graph_setup(void);
@@ -2499,6 +2500,8 @@ void g_canvas_setup(void)
         gensym("floatatom"), A_GIMME, A_NULL);
     class_addmethod(canvas_class, (t_method)canvas_symbolatom,
         gensym("symbolatom"), A_GIMME, A_NULL);
+    class_addmethod(canvas_class, (t_method)canvas_dropdown,
+        gensym("dropdown"), A_GIMME, A_NULL);
     class_addmethod(canvas_class, (t_method)glist_text,
         gensym("text"), A_GIMME, A_NULL);
     class_addmethod(canvas_class, (t_method)glist_glist, gensym("graph"),
diff --git a/pd/src/g_text.c b/pd/src/g_text.c
index 65af6bed58c65e77e13943973f278300c47fbde4..d04d0fd94134033b1269cea2e8880ca262773add 100644
--- a/pd/src/g_text.c
+++ b/pd/src/g_text.c
@@ -22,6 +22,7 @@
 t_class *text_class;
 t_class *message_class;
 static t_class *gatom_class;
+static t_class *dropdown_class;
 static void text_vis(t_gobj *z, t_glist *glist, int vis);
 static void text_displace(t_gobj *z, t_glist *glist,
     int dx, int dy);
@@ -1211,7 +1212,6 @@ static void gatom_getwherelabel(t_gatom *x, t_glist *glist, int *xp, int *yp)
     }
 }
 
-
 static void gatom_displace(t_gobj *z, t_glist *glist,
     int dx, int dy)
 {
@@ -1248,8 +1248,6 @@ static void gatom_vis(t_gobj *z, t_glist *glist, int vis)
                 canvas_realizedollar(x->a_glist, x->a_label)->s_name,
                 sys_hostfontsize(glist_getfont(glist))
             );
-
-
         }
         else
         {
@@ -1351,7 +1349,7 @@ void canvas_atom(t_glist *gl, t_atomtype type,
             (void *)canvas_undo_set_create(glist_getcanvas(gl)));
     }
     glob_preset_node_list_seek_hub();
-    glob_preset_node_list_check_loc_and_update();    
+    glob_preset_node_list_check_loc_and_update();
 }
 
 void canvas_floatatom(t_glist *gl, t_symbol *s, int argc, t_atom *argv)
@@ -1385,6 +1383,7 @@ static void gatom_properties(t_gobj *z, t_glist *owner)
     gui_start_vmess("gui_gatom_dialog", "s",
         gfxstub_new2(&x->a_text.te_pd, x));
     gui_start_array();
+    gui_s("name");     gui_s("atom");
     gui_s("width");    gui_i(x->a_text.te_width);
     gui_s("draglo");   gui_f(x->a_draglo);
     gui_s("draghi");   gui_f(x->a_draghi);
@@ -1396,6 +1395,444 @@ static void gatom_properties(t_gobj *z, t_glist *owner)
     gui_end_vmess();
 }
 
+/* ---------------------- the "dropdown" text item ------------------------ */
+
+typedef struct _dropdown
+{
+    t_text a_text;
+    t_binbuf *a_names;      /* names to be displayed */
+    int a_maxnamewidth;     /* when width = 0 this is used */
+    int a_index;            /* index */
+    t_glist *a_glist;       /* owning glist */
+    t_float a_dummy;        /* dummy value */
+    int a_outtype;           /* 0 = index, 1 = value */
+    int a_output;           /* 0 = index, 1 = value */
+    t_symbol *a_label;      /* symbol to show as label next to box */
+    t_symbol *a_symfrom;    /* "receive" name -- bind ourselvs to this */
+    t_symbol *a_symto;      /* "send" name -- send to this on output */
+    char a_wherelabel;      /* 0-3 for left, right, above, below */
+    t_symbol *a_expanded_to; /* a_symto after $0, $1, ...  expansion */
+} t_dropdown;
+
+static void dropdown_redraw(t_gobj *client, t_glist *glist)
+{
+    t_dropdown *x = (t_dropdown *)client;
+    glist_retext(x->a_glist, &x->a_text);
+}
+
+    /* recolor option offers    0 ignore recolor
+                                1 recolor */
+static void dropdown_retext(t_dropdown *x, int senditup, int recolor)
+{
+    t_canvas *canvas = glist_getcanvas(x->a_glist);
+    t_rtext *y = glist_findrtext(x->a_glist, &x->a_text);
+    if (recolor)
+    {
+        /* not sure if we need to activate dropdown */
+        //gui_vmess("gui_gatom_activate", "xsi",
+        //    canvas, rtext_gettag(y), 0);
+        post("note: dropdown is being activated!");
+    }
+    binbuf_clear(x->a_text.te_binbuf);
+    binbuf_add(x->a_text.te_binbuf, 1, binbuf_getvec(x->a_names) + x->a_index);
+
+    if (senditup && glist_isvisible(x->a_glist))
+        sys_queuegui(x, x->a_glist, dropdown_redraw);
+}
+
+/* use this to keep the index within the correct range */
+static int dropdown_clipindex(t_dropdown *x, int i)
+{
+    int ret = i, len = binbuf_getnatom(x->a_names);
+    if (ret < 0) ret = 0;
+    if (ret > len - 1) ret = len - 1;
+    return ret;
+}
+
+static void dropdown_set(t_dropdown *x, t_symbol *s, int argc, t_atom *argv)
+{
+    int oldindex = x->a_index;
+    if (!argc) return;
+    x->a_index = dropdown_clipindex(x, (int)atom_getfloat(argv));
+    if (oldindex != x->a_index)
+        dropdown_retext(x, 1, 0);
+}
+
+static int dropdown_names_getmaxwidth(t_dropdown *x) {
+    char buf[MAXPDSTRING];
+    t_binbuf *names = x->a_names, *b = binbuf_new();
+    int len = binbuf_getnatom(x->a_names), maxwidth = 0;
+    while (len--)
+    {
+        int width;
+        binbuf_clear(b);
+        binbuf_add(b, 1, binbuf_getvec(names) + len);
+        binbuf_gettext(b, &buf, &width);
+        if (width > maxwidth) maxwidth = width;
+    }
+    return maxwidth;
+}
+
+static void dropdown_names(t_dropdown *x, t_symbol *s, int argc, t_atom *argv)
+{
+    binbuf_clear(x->a_names);
+    if (argc)
+        binbuf_add(x->a_names, argc, argv);
+    else
+        binbuf_addv(x->a_names, "s", &s_);
+    /* nudge a_index back into range */
+    x->a_index = dropdown_clipindex(x, x->a_index);
+    post("max width is %d", dropdown_names_getmaxwidth(x));
+    x->a_maxnamewidth = dropdown_names_getmaxwidth(x);
+    //dropdown_max_namelength(x);
+    dropdown_retext(x, 1, 0);
+}
+
+static void dropdown_bang(t_dropdown *x)
+{
+    t_atom at;
+    if (x->a_outtype == 0)
+        SETFLOAT(&at, (t_float)x->a_index);
+    else
+    {
+        t_atom *atfrom = binbuf_getvec(x->a_names) + x->a_index;
+        if (atfrom->a_type == A_FLOAT)
+            SETFLOAT(&at, atom_getfloat(atfrom));
+        else if (atfrom->a_type == A_SYMBOL)
+            SETSYMBOL(&at, atom_getsymbol(atfrom));
+        else pd_error(x, "only float and symbol output supported");
+    }
+    if (x->a_text.te_outlet)
+        outlet_list(x->a_text.te_outlet, &s_list, 1, &at);
+    if (*x->a_expanded_to->s_name && x->a_expanded_to->s_thing)
+    {
+        if (x->a_symto == x->a_symfrom)
+            pd_error(x,
+                "%s: atom with same send/receive name (infinite loop)",
+                    x->a_symto->s_name);
+        else pd_list(x->a_expanded_to->s_thing, &s_list, 1, &at);
+    }
+}
+
+static void dropdown_float(t_dropdown *x, t_float f)
+{
+    /* this should output the atom at the relevant index
+       Let's do it js-style, negative indices for wrapping
+       back around and bring numbers greater than last index
+       down to last index */
+    t_atom at;
+    SETFLOAT(&at, f);
+    dropdown_set(x, 0, 1, &at);
+    dropdown_bang(x);
+}
+
+static void dropdown_symbol(t_dropdown *x, t_symbol *s)
+{
+    t_atom at;
+    SETSYMBOL(&at, s);
+    dropdown_set(x, 0, 1, &at);
+    dropdown_bang(x);
+}
+
+    /* We need a list method because, since there's both an "inlet" and a
+    "nofirstin" flag, the standard list behavior gets confused. */
+static void dropdown_list(t_dropdown *x, t_symbol *s, int argc, t_atom *argv)
+{
+    if (!argc)
+        dropdown_bang(x);
+    else if (argv->a_type == A_FLOAT)
+        dropdown_float(x, argv->a_w.w_float);
+    else if (argv->a_type == A_SYMBOL)
+        dropdown_symbol(x, argv->a_w.w_symbol);
+    else pd_error(x, "dropdown_list: need float or symbol");
+}
+
+/* this should send a message to the GUI triggering the dropdown
+   <div> to be displayed and its event listeners activated */
+static int dropdown_click(t_gobj *z, struct _glist *glist,
+    int xpix, int ypix, int shift, int alt, int dbl, int doit)
+{
+    t_dropdown *x = (t_dropdown *)z;
+    t_canvas *canvas = glist_getcanvas(glist);
+    t_rtext *y = glist_findrtext(glist, (t_text *)x);
+    if (doit)
+    {
+        int i, len = binbuf_getnatom(x->a_names);
+        t_atom *at = binbuf_getvec(x->a_names);
+        /* for gatom we turn the text red to indicate it as editable.
+           For dropdown we instead have the GUI create a menu with which
+           the user can choose an option */
+        gui_start_vmess("gui_dropdown_activate", "xxsiii",
+            canvas,
+            x,
+            rtext_gettag(y),
+            x->a_index,
+            sys_hostfontsize(glist_getfont(glist)),
+            1);
+        gui_start_array();
+        for (i = 0; i < len; i++)
+        {
+            if (at[i].a_type == A_FLOAT)
+                gui_f(at[i].a_w.w_float);
+            else if (at[i].a_type == A_SYMBOL)
+                gui_s(at[i].a_w.w_symbol->s_name);
+            else
+                gui_s("(pointer)");
+        }
+        gui_end_array();
+        gui_end_vmess();
+    }
+    return (1);
+}
+
+    /* message back from dialog window */
+static void dropdown_param(t_dropdown *x, t_symbol *sel, int argc, t_atom *argv)
+{
+    /* Check if we need to set an undo point. This happens if the user
+       clicks the "Ok" button, but not when clicking "Apply" or "Cancel" */
+    if (atom_getintarg(7, argc, argv))
+        canvas_apply_setundo(x->a_glist, (t_gobj *)x);
+
+    t_float width = atom_getfloatarg(0, argc, argv);
+
+    int output = (int)atom_getfloatarg(1, argc, argv);
+    t_float dummy = atom_getfloatarg(2, argc, argv);
+    t_symbol *label = gatom_unescapit(atom_getsymbolarg(3, argc, argv));
+    t_float wherelabel = atom_getfloatarg(4, argc, argv);
+    t_symbol *symfrom = gatom_unescapit(atom_getsymbolarg(5, argc, argv));
+    t_symbol *symto = gatom_unescapit(atom_getsymbolarg(6, argc, argv));
+
+    gobj_vis(&x->a_text.te_g, x->a_glist, 0);
+    if (!*symfrom->s_name && *x->a_symfrom->s_name)
+        inlet_new(&x->a_text, &x->a_text.te_pd, 0, 0);
+    else if (*symfrom->s_name && !*x->a_symfrom->s_name && x->a_text.te_inlet)
+    {
+        canvas_deletelinesforio(x->a_glist, &x->a_text,
+            x->a_text.te_inlet, 0);
+        inlet_free(x->a_text.te_inlet);
+    }
+    if (!*symto->s_name && *x->a_symto->s_name)
+        outlet_new(&x->a_text, 0);
+    else if (*symto->s_name && !*x->a_symto->s_name && x->a_text.te_outlet)
+    {
+        canvas_deletelinesforio(x->a_glist, &x->a_text,
+            0, x->a_text.te_outlet);
+        outlet_free(x->a_text.te_outlet);
+    }
+    x->a_outtype = output;
+    x->a_dummy = dummy;
+    if (width < 0)
+        width = 4;
+    else if (width > 80)
+        width = 80;
+    x->a_text.te_width = width;
+    x->a_wherelabel = ((int)wherelabel & 3);
+    x->a_label = label;
+    if (*x->a_symfrom->s_name)
+        pd_unbind(&x->a_text.te_pd,
+            canvas_realizedollar(x->a_glist, x->a_symfrom));
+    x->a_symfrom = symfrom;
+    if (*x->a_symfrom->s_name)
+        pd_bind(&x->a_text.te_pd,
+            canvas_realizedollar(x->a_glist, x->a_symfrom));
+    x->a_symto = symto;
+    x->a_expanded_to = canvas_realizedollar(x->a_glist, x->a_symto);
+    gobj_vis(&x->a_text.te_g, x->a_glist, 1);
+    gobj_select(&x->a_text.te_g, x->a_glist, 1);
+    canvas_dirty(x->a_glist, 1);
+    canvas_getscroll(x->a_glist);
+    /* glist_retext(x->a_glist, &x->a_text); */
+}
+
+    /* ---------------- dropdown-specific widget functions --------------- */
+
+/* this can be combined with gatom_getwherelabel */
+static void dropdown_getwherelabel(t_dropdown *x, t_glist *glist, int *xp, int *yp)
+{
+    int x1, y1, x2, y2;
+    text_getrect(&x->a_text.te_g, glist, &x1, &y1, &x2, &y2);
+    if (x->a_wherelabel == ATOM_LABELLEFT)
+    {
+        *xp = -3 -
+            strlen(canvas_realizedollar(x->a_glist, x->a_label)->s_name) *
+            sys_fontwidth(glist_getfont(glist));
+        *yp = y2 - y1 - 4;
+    }
+    else if (x->a_wherelabel == ATOM_LABELRIGHT)
+    {
+        *xp = x2 - x1 + 3;
+        *yp = y2 - y1 - 4;
+    }
+    else if (x->a_wherelabel == ATOM_LABELUP)
+    {
+        *xp = -1;
+        *yp = -3;
+    }
+    else
+    {
+        *xp = -1;
+        *yp = y2 - y1 + sys_fontheight(glist_getfont(glist));
+    }
+}
+
+/* for dropdown's label */
+static void dropdown_vis(t_gobj *z, t_glist *glist, int vis)
+{
+    //fprintf(stderr,"dropdown_vis\n");
+    t_dropdown *x = (t_dropdown *)z;
+    text_vis(z, glist, vis);
+    if (*x->a_label->s_name)
+    {
+        if (vis)
+        {
+            int x1, y1;
+            t_rtext *y = glist_findrtext(x->a_glist, &x->a_text);
+            dropdown_getwherelabel(x, glist, &x1, &y1);
+            gui_vmess("gui_text_new", "xssiiisi",
+                glist_getcanvas(glist),
+                rtext_gettag(y),
+                "dropdown",
+                0,
+                x1, // left margin
+                y1, // top margin
+                canvas_realizedollar(x->a_glist, x->a_label)->s_name,
+                sys_hostfontsize(glist_getfont(glist))
+            );
+        }
+        else
+        {
+            /* We're just deleting the parent gobj in the GUI, which takes
+               care of removing all the children. So we don't need to send
+               a message here */
+            //sys_vgui(".x%lx.c delete %lx.l\n", glist_getcanvas(glist), x);
+        }
+    }
+    if (!vis)
+        sys_unqueuegui(x);
+}
+
+/* a lot of this is duplicated from canvas_atom-- we should factor out the
+   common stuff from the copy/pasta here */
+void canvas_dropdown(t_glist *gl, t_symbol *s, int argc, t_atom *argv)
+{
+    char tagbuf[MAXPDSTRING];
+    if (canvas_hasarray(gl)) return;
+    //fprintf(stderr,"canvas_atom\n");
+    t_dropdown *x = (t_dropdown *)pd_new(dropdown_class);
+    x->a_text.te_width = 0;                        /* don't know it yet. */
+    x->a_text.te_type = T_ATOM;
+    x->a_text.te_iemgui = 0;
+    x->a_text.te_binbuf = binbuf_new();
+    x->a_glist = gl;
+    x->a_dummy = 0;
+    x->a_outtype = 1; /* output value by default */
+    x->a_wherelabel = 0;
+    x->a_label = &s_;
+    x->a_symfrom = &s_;
+    x->a_symto = x->a_expanded_to = &s_;
+    x->a_index = 0;
+    x->a_names = binbuf_new();
+    binbuf_addv(x->a_names, "ssss", &s_symbol, &s_float, &s_bang, &s_list);
+    x->a_maxnamewidth = dropdown_names_getmaxwidth(x);
+    x->a_text.te_width = 6;
+
+    /* bind symbol for sending index updates from the GUI */
+    sprintf(tagbuf, "x%lx", (long unsigned int)x);
+    pd_bind(&x->a_text.te_pd, gensym(tagbuf));
+
+    binbuf_add(x->a_text.te_binbuf, 1, binbuf_getvec(x->a_names));
+    if (argc > 1)
+        /* create from file. x, y, width, low-range, high-range, flags,
+            label, receive-name, send-name */
+    {
+        x->a_text.te_xpix = atom_getfloatarg(0, argc, argv);
+        x->a_text.te_ypix = atom_getfloatarg(1, argc, argv);
+        x->a_text.te_width = atom_getintarg(2, argc, argv);
+            /* sanity check because some very old patches have trash in this
+            field... remove this in 2003 or so: */
+        if (x->a_text.te_width < 0 || x->a_text.te_width > 500)
+            x->a_text.te_width = 4;
+        x->a_outtype = (int)atom_getfloatarg(3, argc, argv);
+        x->a_dummy = atom_getfloatarg(4, argc, argv);
+        x->a_wherelabel = (((int)atom_getfloatarg(5, argc, argv)) & 3);
+        x->a_label = gatom_unescapit(atom_getsymbolarg(6, argc, argv));
+        x->a_symfrom = gatom_unescapit(atom_getsymbolarg(7, argc, argv));
+        if (*x->a_symfrom->s_name)
+            pd_bind(&x->a_text.te_pd,
+                canvas_realizedollar(x->a_glist, x->a_symfrom));
+
+        x->a_symto = gatom_unescapit(atom_getsymbolarg(8, argc, argv));
+        x->a_expanded_to = canvas_realizedollar(x->a_glist, x->a_symto);
+        if (x->a_symto == &s_)
+            outlet_new(&x->a_text, &s_float);
+        if (x->a_symfrom == &s_)
+            inlet_new(&x->a_text, &x->a_text.te_pd, 0, 0);
+        glist_add(gl, &x->a_text.te_g);
+    }
+    else
+    {
+        int connectme, xpix, ypix, indx, nobj;
+        canvas_howputnew(gl, &connectme, &xpix, &ypix, &indx, &nobj);
+        outlet_new(&x->a_text, &s_float);
+        inlet_new(&x->a_text, &x->a_text.te_pd, 0, 0);
+        pd_vmess(&gl->gl_pd, gensym("editmode"), "i", 1);
+        x->a_text.te_xpix = xpix;
+        x->a_text.te_ypix = ypix;
+        glist_add(gl, &x->a_text.te_g);
+        glist_noselect(gl);
+        glist_select(gl, &x->a_text.te_g);
+        if (connectme == 1)
+            canvas_connect(gl, indx, 0, nobj, 0);
+        else if (connectme == 0)
+        {
+            canvas_displaceselection(glist_getcanvas(gl), -8, -8);
+            canvas_startmotion(glist_getcanvas(gl));
+        }
+        //canvas_setundo(glist_getcanvas(gl),
+        //    canvas_undo_create, canvas_undo_set_create(gl), "create");
+        canvas_undo_add(glist_getcanvas(gl), 9, "create",
+            (void *)canvas_undo_set_create(glist_getcanvas(gl)));
+    }
+    glob_preset_node_list_seek_hub();
+    glob_preset_node_list_check_loc_and_update();
+}
+
+static void dropdown_free(t_dropdown *x)
+{
+    char tagbuf[MAXPDSTRING];
+    sprintf(tagbuf, "x%lx", (long unsigned int)x);
+    pd_unbind(&x->a_text.te_pd, gensym(tagbuf));
+
+    if (*x->a_symfrom->s_name)
+        pd_unbind(&x->a_text.te_pd,
+            canvas_realizedollar(x->a_glist, x->a_symfrom));
+    gfxstub_deleteforkey(x);
+}
+
+static void dropdown_properties(t_gobj *z, t_glist *owner)
+{
+    t_dropdown *x = (t_dropdown *)z;
+    //char buf[200];
+    //sprintf(buf, "pdtk_dropdown_dialog %%s %d %g %g %d {%s} {%s} {%s}\n",
+    //    x->a_text.te_width, x->a_draglo, x->a_draghi,
+    //        x->a_wherelabel, gatom_escapit(x->a_label)->s_name,
+    //            gatom_escapit(x->a_symfrom)->s_name,
+    //                gatom_escapit(x->a_symto)->s_name);
+    //gfxstub_new(&x->a_text.te_pd, x, buf);
+    gui_start_vmess("gui_dropdown_dialog", "s",
+        gfxstub_new2(&x->a_text.te_pd, x));
+    gui_start_array();
+    gui_s("name");     gui_s("dropdown");
+    gui_s("width");    gui_i(x->a_text.te_width);
+    gui_s("outtype");   gui_f(x->a_outtype);
+    gui_s("labelpos"); gui_i(x->a_wherelabel);
+    gui_s("label");    gui_s(gatom_escapit(x->a_label)->s_name);
+    gui_s("receive_symbol");  gui_s(gatom_escapit(x->a_symfrom)->s_name);
+    gui_s("send_symbol");     gui_s(gatom_escapit(x->a_symto)->s_name);
+    gui_end_array();
+    gui_end_vmess();
+}
+
 /* -------------------- widget behavior for text objects ------------ */
 
 /* variant of the glist_findrtext found in g_rtext.c 
@@ -1419,6 +1856,9 @@ static void text_getrect(t_gobj *z, t_glist *glist,
         int font = glist_getfont(glist);
         int fontwidth = sys_fontwidth(font), fontheight = sys_fontheight(font);
         width = (x->te_width > 0 ? x->te_width : 6) * fontwidth + 2;
+        /* add an extra two characters for the dropdown box's arrow */
+        if (pd_class(&x->te_pd) == dropdown_class)
+            width += fontwidth * 2;
         height = fontheight + 3; /* borrowed from TMARGIN, etc, in g_rtext.c */
     }
     // jsarlo
@@ -1456,6 +1896,13 @@ static void text_getrect(t_gobj *z, t_glist *glist,
         if (y)
         {
             width = rtext_width(y);
+            if (pd_class(&x->te_pd) == dropdown_class)
+            {
+                int font = glist_getfont(glist);
+                int fontwidth = sys_fontwidth(font);
+//                width += fontwidth * 2;
+                width = fontwidth * (((t_dropdown *)x)->a_maxnamewidth + 2);
+            }
             height = rtext_height(y) - (iscomment << 1);
         }
 
@@ -1591,15 +2038,6 @@ static void text_displace_withtag(t_gobj *z, t_glist *glist,
     }    
 }
 
-static void gatom_displace_withtag(t_gobj *z, t_glist *glist,
-    int dx, int dy)
-{
-    //t_gatom *x = (t_gatom*)z;
-    text_displace_withtag(z, glist, dx, dy);
-    //sys_vgui(".x%lx.c move %lx.l %d %d\n", glist_getcanvas(glist), 
-    //    x, dx, dy);
-}
-
 static void text_select(t_gobj *z, t_glist *glist, int state)
 {
     t_text *x = (t_text *)z;
@@ -1723,7 +2161,8 @@ static void text_activate(t_gobj *z, t_glist *glist, int state)
 {
     t_text *x = (t_text *)z;
     t_rtext *y = glist_findrtext(glist, x);
-    if (z->g_pd != gatom_class) rtext_activate(y, state);
+    if (z->g_pd != gatom_class && z->g_pd != dropdown_class)
+        rtext_activate(y, state);
 }
 
 static void text_delete(t_gobj *z, t_glist *glist)
@@ -1825,6 +2264,7 @@ static int text_click(t_gobj *z, struct _glist *glist,
     }
     else if (x->te_type == T_ATOM)
     {
+        /* Note: dropdown has its own click handler */
         t_canvas *canvas = glist_getcanvas(glist);
         t_rtext *y = glist_findrtext(glist, x);
         if (doit)
@@ -1886,19 +2326,35 @@ void text_save(t_gobj *z, t_binbuf *b)
     else if (x->te_type == T_ATOM)
     {
         //fprintf(stderr, "atom\n");
-        t_atomtype t = ((t_gatom *)x)->a_atom.a_type;
-        t_symbol *sel = (t == A_SYMBOL ? gensym("symbolatom") :
-            (t == A_FLOAT ? gensym("floatatom") : gensym("intatom")));
-        t_symbol *label = gatom_escapit(((t_gatom *)x)->a_label);
-        t_symbol *symfrom = gatom_escapit(((t_gatom *)x)->a_symfrom);
-        t_symbol *symto = gatom_escapit(((t_gatom *)x)->a_symto);
-        binbuf_addv(b, "ssiiifffsss", gensym("#X"), sel,
-            (int)x->te_xpix, (int)x->te_ypix, (int)x->te_width,
-            (double)((t_gatom *)x)->a_draglo,
-            (double)((t_gatom *)x)->a_draghi,
-            (double)((t_gatom *)x)->a_wherelabel,
-            label, symfrom, symto);
-    }           
+        if (pd_class(&x->te_pd) == gatom_class)
+        {
+            t_atomtype t = ((t_gatom *)x)->a_atom.a_type;
+            t_symbol *sel = (t == A_SYMBOL ? gensym("symbolatom") :
+                (t == A_FLOAT ? gensym("floatatom") : gensym("intatom")));
+            t_symbol *label = gatom_escapit(((t_gatom *)x)->a_label);
+            t_symbol *symfrom = gatom_escapit(((t_gatom *)x)->a_symfrom);
+            t_symbol *symto = gatom_escapit(((t_gatom *)x)->a_symto);
+            binbuf_addv(b, "ssiiifffsss", gensym("#X"), sel,
+                (int)x->te_xpix, (int)x->te_ypix, (int)x->te_width,
+                (double)((t_gatom *)x)->a_draglo,
+                (double)((t_gatom *)x)->a_draghi,
+                (double)((t_gatom *)x)->a_wherelabel,
+                label, symfrom, symto);
+        }
+        else
+        {
+            t_symbol *sel = gensym("dropdown");
+            t_symbol *label = gatom_escapit(((t_dropdown *)x)->a_label);
+            t_symbol *symfrom = gatom_escapit(((t_dropdown *)x)->a_symfrom);
+            t_symbol *symto = gatom_escapit(((t_dropdown *)x)->a_symto);
+            binbuf_addv(b, "ssiiiiffsss", gensym("#X"), sel,
+                (int)x->te_xpix, (int)x->te_ypix, (int)x->te_width,
+                (int)((t_dropdown *)x)->a_outtype,
+                (double)((t_dropdown *)x)->a_dummy,
+                (double)((t_dropdown *)x)->a_wherelabel,
+                label, symfrom, symto);
+        }
+    }
     else    
     {
         //fprintf(stderr,"comment\n");
@@ -1959,7 +2415,19 @@ static t_widgetbehavior gatom_widgetbehavior =
     text_delete,
     gatom_vis,
     text_click,
-    gatom_displace_withtag,
+    text_displace_withtag,
+};
+
+static t_widgetbehavior dropdown_widgetbehavior =
+{
+    text_getrect,
+    text_displace,
+    text_select,
+    text_activate,
+    text_delete,
+    dropdown_vis,
+    dropdown_click,
+    text_displace_withtag,
 };
 
 /* -------------------- the "text" class  ------------ */
@@ -2138,7 +2606,6 @@ void glist_drawiofor_withtag(t_glist *glist, t_object *ob, int firsttime,
     }
 }
 
-
 void text_drawborder(t_text *x, t_glist *glist,
     char *tag, int width2, int height2, int firsttime)
 {
@@ -2259,9 +2726,11 @@ void text_drawborder(t_text *x, t_glist *glist,
             //    (selected ? "$pd_colors(selection)" : "$pd_colors(atom_box_border)"),
             //        tag, tag, (selected ? "selected" : ""));
             /* These coords can be greatly simplified... */
-            gui_vmess("gui_atom_draw_border", "xsii",
+            gui_vmess("gui_atom_draw_border", "xsiii",
                 glist_getcanvas(glist),
                 tag,
+                pd_class(&x->te_pd) == dropdown_class ?
+                    ((t_dropdown *)x)->a_outtype + 1 : 0,
                 x2 - x1,
                 y2 - y1);
         }
@@ -2272,9 +2741,10 @@ void text_drawborder(t_text *x, t_glist *glist,
             //         "%d %d %d %d %d %d %d %d %d %d %d %d\n",
             //    glist_getcanvas(glist), tag,
             //    x1, y1,  x2-4, y1,  x2, y1+4,  x2, y2,  x1, y2,  x1, y1);
-            gui_vmess("gui_atom_redraw_border", "xsii",
+            gui_vmess("gui_atom_redraw_border", "xsiii",
                 glist_getcanvas(glist),
                 tag,
+                pd_class(&x->te_pd) == dropdown_class ? 1 : 0,
                 x2 - x1,
                 y2 - y1);
         }
@@ -2432,7 +2902,6 @@ void text_drawborder_withtag(t_text *x, t_glist *glist,
     //    (long unsigned int)glist_getcanvas(glist));
 }
 
-
 void glist_eraseiofor(t_glist *glist, t_object *ob, char *tag)
 {
     //fprintf(stderr,"glist_eraseiofor\n");
@@ -2746,6 +3215,21 @@ void g_text_setup(void)
         A_GIMME, 0);
     class_setwidget(gatom_class, &gatom_widgetbehavior);
     class_setpropertiesfn(gatom_class, gatom_properties);
-}
-
 
+    dropdown_class = class_new(gensym("dropdown"), 0, (t_method)dropdown_free,
+        sizeof(t_dropdown), CLASS_NOINLET | CLASS_PATCHABLE, 0);
+    class_addbang(dropdown_class, dropdown_bang);
+    class_addfloat(dropdown_class, dropdown_float);
+    class_addsymbol(dropdown_class, dropdown_symbol);
+    class_addlist(dropdown_class, dropdown_list);
+    class_addmethod(dropdown_class, (t_method)dropdown_set, gensym("set"),
+        A_GIMME, 0);
+    class_addmethod(dropdown_class, (t_method)dropdown_names, gensym("names"),
+        A_GIMME, 0);
+    //class_addmethod(dropdown_class, (t_method)dropdown_click, gensym("click"),
+    //  A_FLOAT, A_FLOAT, A_FLOAT, A_FLOAT, A_FLOAT, 0);
+    class_addmethod(dropdown_class, (t_method)dropdown_param, gensym("param"),
+        A_GIMME, 0);
+    class_setwidget(dropdown_class, &dropdown_widgetbehavior);
+    class_setpropertiesfn(dropdown_class, dropdown_properties);
+}