diff --git a/javascript/tc.preset.js b/javascript/tc.preset.js index 23b59dd..a85d4b5 100644 --- a/javascript/tc.preset.js +++ b/javascript/tc.preset.js @@ -26,11 +26,13 @@ autowatch = 0; inlets = 1 setinletassist(0, "Connect to the linked pattrstorage"); -outlets = 4; +outlets = 5; setoutletassist(0, "Outputs last triggered action"); setoutletassist(1, "Connect to umenu to list stored presets"); setoutletassist(2, "Connect to textedit to edit preset name"); -setoutletassist(3, "Connect to toggle to show active presets lock state") +setoutletassist(3, "Connect to toggle to show active presets lock state"); +setoutletassist(4, "Dictionary out"); + mgraphics.init(); mgraphics.relative_coords = 0; @@ -75,7 +77,11 @@ var scrollable = 0; // Defines weither the object can be scrolled or not var min_rows = 10; // Minimum number of rows to display if scrollable is enabled var color_mode = 0; // Change the way the filled slots (stored presets) color is handeld. 0: stored_slot_color. 1: looping through color_1 to color_6. 2: Freely assign colors 1 to 6. 3: Set any color to any preset var select_mode = 0; // 0: single click to select and recall the slot. 1: single click to select the slot, double click to recall it. -var send_name = "none"; // The global name to send presets dict name to (received by the [receive] object) +var send_name = "none"; // The global name to send presets dict name to (received by the [receive] object) +var unique_names = false; // When enabled, force names to be unique when renaming slots by appending "bis" to them. Gets applied only to subsequently renamed presets. +var use_uid = 0; // Generating UID for each presets when enabled. Requires a [pattr preset_metadata] +var recall_passthrough = true; // By default (true), clicking a slot sends a recall message directly to [pattrstorage], and the jsui left outlet outputs a recall message once the recall is done. When disabled, clicking a slot will send a recall message straight from the jsui left outlet, so it's up to the user to forward the message to pattrstorage. It can be usefull for triggering interpolations with custom logic. +var ui_rename = false; // Use the attached textedit, if any, to edit slot names directly in the JSUI frame when clicking a slot while holding the control key. When disabled, the textedit remains untouched but gets focused when clicking a slot while holding the control key. // (WORK) var pattrstorage_name, pattrstorage_obj = null; @@ -95,6 +101,7 @@ var selected_slot = 0; //Last selected slot. Relevant especially when s var mess_with_presets = ['insert', 'lockall', 'read', 'readagain', 'remove', 'renumber']; // Received messages that can mess with presets and thus requiring a resync var to_ignore = ['active', 'alias', 'current', 'dump', 'edited', 'interp', 'priority', 'subscription', 'resolvealias']; // Messages that can come from psto and should be ignored to avoid feedback loops var to_passthrough = ['clear', 'client_close', 'client_window', 'fade', 'fillempty', 'getactive', 'getalias', 'getclientlist', 'getcurrent', 'getedited', 'getinterp', 'getlockedslots', 'getpriority', 'getslotlist', 'getslotnamelist', 'getstoredvalue', 'getsubscriptionlist', 'grab', 'locate', 'purge', 'readagain', 'setall', 'setstoredvalue', 'storage_close', 'storagewindow', 'storeagain', 'subscribe', 'unsubscribe', 'writeagain', 'writejson', 'writexml']; // Messages that can be passed to psto + var ui_width = box.rect[2] - box.rect[0]; var ui_height = box.rect[3] - box.rect[1]; var bg_width, bg_height = 0; @@ -114,11 +121,17 @@ var drag_slot = -1; // Stores the slot that's being dragged var is_writing = 0; // Keeping track of various variables for dealing with color modes -var requested_slot = -1; // Which slot we're waiting a value for (used in get_all_preset_colors) +var requested_slot = -1; // Which slot we're waiting a value for (used in get_all_presets_metadata) var color_mode_candidate = 0; // Which color mode we're aiming var is_listening_to_subscriptionlist = 0; //Filters out received subscriptionlist messages when not updating slot color values var is_listening_to_clientlist = 0; //Filters out received clientlist messages when not updating slot color values -var color_pattr; +var metadata_pattr; +var metadata_pattr_address; +var metadata_updated = false; // Flag to write presets file after filling possible empty preset_metadata + +var textedit_obj = null; +var textedit_initstate = {}; +var is_typing_name = false; var has_loaded = false; @@ -127,7 +140,7 @@ if (jsarguments.length>1) { // Depreciated, use "pattrstorage" attribute instead } // FUNCTIONS -function slot(left, top, right, bottom, name, lock, interp, color_index, color_custom) { +function slot(left, top, right, bottom, name, lock, interp, color_index, color_custom, uid) { this.left = left; this.top = top; this.right = right; @@ -137,6 +150,7 @@ function slot(left, top, right, bottom, name, lock, interp, color_index, color_c this.interp = interp; this.color_index = color_index; this.color_custom = color_custom; + this.uid = uid; this.init = function() { this.left = 0; @@ -147,6 +161,7 @@ function slot(left, top, right, bottom, name, lock, interp, color_index, color_c this.lock = 0; this.interp = -1; this.init_color(); + this.uid = 0; } this.init_color = function() { @@ -160,6 +175,7 @@ function slot(left, top, right, bottom, name, lock, interp, color_index, color_c this.interp = -1; this.color_index = 0; this.color_custom = stored_slot_color; + this.uid = 0; } } @@ -168,6 +184,8 @@ function loadbang() { has_loaded = true; find_pattrstorage(pattrstorage_name); calc_rows_columns(); + + find_textedit(); } function calc_rows_columns() { @@ -205,7 +223,14 @@ function calc_rows_columns() { if (typeof slots[cur] !== 'undefined') { prev_state = slots[cur]; } - slots[cur] = new slot(left, top, right, bottom, prev_state.name, prev_state.lock, prev_state.interp, prev_state.color_index, prev_state.color_custom); + slots[cur] = new slot(left, top, right, bottom, prev_state.name, prev_state.lock, prev_state.interp, prev_state.color_index, prev_state.color_custom, prev_state.uid); + } + } + + if (slots_count_display < slots_highest) { + for (var i = slots_count_display + 1; i <= slots_highest; i++) { + slots[i] = new slot(); + slots[i].init(); } } paint_base(); @@ -454,8 +479,8 @@ function paint() var bg_txt_pos_y = text_dim[1] > slot_size || is_dragging ? slots[last_hovered].top - 2 : slots[last_hovered].top - 2; - // If there is not enough place, text is displayed on the left - if (bg_txt_pos_x + bg_txt_dim_w > ui_width) { + // If there is not enough place on the right and if there is more available place on the left, text is displayed on the left + if (bg_txt_pos_x + bg_txt_dim_w > ui_width && slots[last_hovered].left - half_spacing > ui_width - slots[last_hovered].right) { bg_txt_pos_x = slots[last_hovered].left - half_spacing - bg_txt_dim_w; } @@ -568,7 +593,7 @@ function color_wheel() { } function setcolor() { - if (preset_color_pattr_exist()) { + if (metadata_pattr != undefined) { var args = arrayfromargs(arguments); var nb_args = args.length; var slot_nb = selected_slot; @@ -593,7 +618,7 @@ function setcolor() { slot_nb = Math.floor(args[0]); slots[slot_nb].color_custom = [args[1], args[2], args[3], args[4]]; } - update_preset_color_pattr(slot_nb); + update_preset_metadata_pattr(slot_nb); update_filled_slots_dict(); paint_base(); trigger_writeagain(); @@ -601,63 +626,101 @@ function setcolor() { } } -function preset_color_pattr_exist() { - var obj = this.patcher.getnamed("preset_color"); +function connect_to_metadata_pattr() { + var obj = this.patcher.getnamed("preset_metadata"); if (!obj) { - error("preset_color pattr not found.\n"); - color_pattr = 0; + error("preset_metadata pattr not found.\n"); + metadata_pattr = 0; return false; } else if (obj.maxclass != "pattr"){ - error("preset_color named object is not a pattr object.\n"); - color_pattr = 0; + error("preset_metadata named object is not a pattr object.\n"); + metadata_pattr = 0; return false; } else if (obj.getattr('invisible') == 1) { - error("preset_color has been found but has invisible attribute set to 1\n"); - color_pattr = 0; + error("preset_metadata has been found but has invisible attribute set to 1\n"); + metadata_pattr = 0; return false; } else { - color_pattr = obj; + metadata_pattr = obj; + get_metadata_pattr_address(); return true; } } -preset_color_pattr_exist.local = 1; +connect_to_metadata_pattr.local = 1; -function update_preset_color_pattr(s) { - var cstm = slots[s].color_custom; - to_pattrstorage("setstoredvalue", "preset_color", s, slots[s].color_index, cstm[0], cstm[1], cstm[2], cstm[3]); +function get_metadata_pattr_address() { + is_listening_to_clientlist = is_listening_to_clientlist == 2 ? 2 : 1; // 2 if setting color mode, 1 otherwise + to_pattrstorage("getclientlist"); } -update_preset_color_pattr.local = 1 +get_metadata_pattr_address.local = 1; -function get_all_preset_colors() { - if (filled_slots.length) { - for (var i = 0; i < filled_slots.length; i++) { - get_preset_color(filled_slots[i]); +function update_preset_metadata_pattr(s) { + var cstm = slots[s].color_custom; + var color_val = [slots[s].color_index, cstm[0], cstm[1], cstm[2], cstm[3]]; + to_pattrstorage("setstoredvalue", "preset_metadata", s, JSON.stringify({color: color_val, uid: slots[s].uid})); +} +update_preset_metadata_pattr.local = 1; + +function get_all_presets_metadata() { + if (metadata_pattr) { + metadata_updated = false; + if (filled_slots.length) { + for (var i = 0; i < filled_slots.length; i++) { + get_preset_metadata(filled_slots[i]); + } + requested_slot = -1; + } + if (metadata_updated) { + trigger_writeagain(); } - requested_slot = -1; } } -get_all_preset_colors.local = 1; +get_all_presets_metadata.local = 1; -function get_preset_color(s) { +function get_preset_metadata(s) { requested_slot = s; - to_pattrstorage("getstoredvalue", "preset_color", s); -} -get_preset_color.local = 1; + // Using 'getstoredvalue' doesn't return anything if the storage for a given client in a given preset is empty. + // Which is a problem when we want to know if the value is empty or not + // So instead we set the client to a known value, we recall the value for a given preset, and check f the value has changed. + metadata_pattr.message(0); + to_pattrstorage('recall', 'preset_metadata', s); -function preset_color() { - var args = arrayfromargs(arguments); - // post(pattrstorage_name, "preset_color", args, '----- args.length: ', args.length, '----- requested_slot: ', requested_slot,'\n'); - if (args.length == 5) { - var col = Math.max(0, Math.floor(args[0])) % color_wheel_size; - slots[requested_slot].color_index = col; - slots[requested_slot].color_custom = [args[1], args[2], args[3], args[4]]; - } else if (args.length == 4) { - slots[requested_slot].color_index = 0; - slots[requested_slot].color_custom = args; - } else if (args.length == 1) { - var col = Math.max(0, Math.floor(args)) % color_wheel_size; - slots[requested_slot].color_index = col; - slots[requested_slot].color_custom = stored_slot_color; + var metadata_val = metadata_pattr.getvalueof(); + if (metadata_val == 0) { + if (use_uid) { + var uid = generateUID(); + slots[s].uid = uid; + } + update_preset_metadata_pattr(s); + metadata_updated = true; + } else { + preset_metadata(metadata_val); + } +} +get_preset_metadata.local = 1; + +function preset_metadata(v) { + var meta_dict = {}; + if (requested_slot > -1 && v) { + meta_dict = JSON.parse(v); + if (meta_dict.hasOwnProperty('color')) { + var args = meta_dict.color; + if (args.length == 5) { + var col = Math.max(0, Math.floor(args[0])) % color_wheel_size; + slots[requested_slot].color_index = col; + slots[requested_slot].color_custom = [args[1], args[2], args[3], args[4]]; + } else if (args.length == 4) { + slots[requested_slot].color_index = 0; + slots[requested_slot].color_custom = args; + } else if (args.length == 1) { + var col = Math.max(0, Math.floor(args)) % color_wheel_size; + slots[requested_slot].color_index = col; + slots[requested_slot].color_custom = stored_slot_color; + } + } + if (use_uid && meta_dict.hasOwnProperty('uid') && meta_dict.uid) { + slots[requested_slot].uid = meta_dict.uid; + } } } @@ -738,6 +801,7 @@ function pattrstorage(v) { function slotlist() { filled_slots = arrayfromargs(arguments); + // post('get filled slots list for', pattrstorage_name, ' : ', filled_slots, '\n'); if (filled_slots.length) { // If the highest numbered preset is above the maximum number of displayed presets, we need to extend slots[] @@ -751,8 +815,7 @@ function slotlist() { for (var i = 0; i < filled_slots.length; i++) { to_pattrstorage("getslotname", filled_slots[i]); } - - get_all_preset_colors(); + get_all_presets_metadata(); update_filled_slots_dict(); } } @@ -768,15 +831,17 @@ function setslotname() { // Because [pattrstorage] doesn't output anything when renaming presets with "slotname", we use a custom "setslotname" instead, that will rename the active preset if (selected_slot > 0) { if (slots[selected_slot].lock == 0) { - var sname = arrayfromargs(arguments).join(' '); - slotname(selected_slot, sname); - to_pattrstorage("slotname", selected_slot, sname); - update_umenu(); - update_filled_slots_dict(); - select(selected_slot); - trigger_writeagain(); - if (layout == 1) { - paint_base(); + var sname = format_slotname(arrayfromargs(arguments).join(' ')); + if (sname != slots[selected_slot].name) { + slotname(selected_slot, sname); + to_pattrstorage("slotname", selected_slot, sname); + update_umenu(); + update_filled_slots_dict(); + select(selected_slot); + trigger_writeagain(); + if (layout == 1) { + paint_base(); + } } } else { error('Cannot set name of locked slot\n'); @@ -784,8 +849,29 @@ function setslotname() { } } +function format_slotname(name) { + return unique_names ? make_slotname_unique(name) : name; +} +format_slotname.local = 1; + +function make_slotname_unique(name) { + // Leftover from before using an uid. Found the bis bis bis presets funny (rather than bunch of (undefined) so I left this here + var current_name = name; + for (var i = 0; i < filled_slots.length; i++) { + if (slots[filled_slots[i]].name == current_name && filled_slots[i] != selected_slot) { + return make_slotname_unique(name + ' bis'); + } + } + return current_name +} +make_slotname_unique.local = 1; + function text() { - setslotname(arrayfromargs(arguments).join(' ')); + var input = arrayfromargs(arguments).join(' '); + if (is_typing_name && input != slots[selected_slot].name) { + restore_textedit(); + } + setslotname(input); } function recall() { @@ -801,7 +887,8 @@ function recall() { trg_slot = Math.abs(args[1]); interp = Math.min( 1, Math.max(0, args[2])); } - if (slots[src_slot].name != null && slots[trg_slot].name != null) { + + if ((src_slot == 0 || (filled_slots.indexOf(src_slot) > -1 && slots[src_slot].name != null)) && (trg_slot == 0 || (filled_slots.indexOf(trg_slot) > -1 && slots[trg_slot].name != null))) { for (var i = 0; i < filled_slots.length; i++) { slots[filled_slots[i]].interp = -1; } @@ -841,8 +928,9 @@ function recall() { is_interpolating = 1; active_slot = 0; } - - outlet(0, "recall", src_slot, trg_slot, interp); + if (recall_passthrough) { + outlet(0, "recall", src_slot, trg_slot, interp); + } } mgraphics.redraw(); @@ -894,9 +982,25 @@ function store(v) { } else { var recalc_rows_flag = scrollable && v > slots_highest; - if (color_pattr) { - //Initialize preset color pattr to default for new preset (otherwise, previously set color is used) - color_pattr.message(0); + if (metadata_pattr) { + if (filled_slots.indexOf(v) == -1) { + // If storing preset in empty slot + // Generating name and uid + if (use_uid) { + slots[v].uid = generateUID(); + } + slots[v].init_color(); + + var preset_name = format_slotname('Preset ' + v); + slotname(v, preset_name); + to_pattrstorage("slotname", v, preset_name); + } + + // Updating metadata in pattr object before storing in pattrstorage + var cstm = slots[v].color_custom; + var color_val = [slots[v].color_index, cstm[0], cstm[1], cstm[2], cstm[3]]; + var meta_dict = {color: color_val, uid: slots[v].uid}; + metadata_pattr.message(JSON.stringify(meta_dict)); } to_pattrstorage("store", v); @@ -935,6 +1039,8 @@ function lock() { trigger_writeagain(); if (layout == 1) { paint_base(); + } else if (layout == 0) { + mgraphics.redraw(); } } } @@ -970,7 +1076,6 @@ function write() { error("Send your write messages directly to the pattrstorage instead.\n"); } } - } function read() { @@ -986,32 +1091,26 @@ function readfile(f, s) { read(f, s); } -function subscriptionlist() { - var client = arrayfromargs(arguments)[0]; - if (is_listening_to_subscriptionlist) { - if (client == "preset_color") { - // [pattr preset_color] subscribed - // post("preset_color pattr object found and subscribed to bound pattrstorage. Switching to color mode", color_mode_candidate, '\n'); - is_listening_to_subscriptionlist = 0; - color_mode = color_mode_candidate; - paint_base(); - } else if (client == "done") { - error("A [pattr preset_color] object has been found but it isn't subscribed to your pattrstorage. Please add it to your subscribelist and try changing color mode again.\n") - is_listening_to_subscriptionlist = 0; - } - } -} - function clientlist() { var client = arrayfromargs(arguments)[0]; - if (is_listening_to_clientlist) { - if (client == "preset_color") { - // post("preset_color pattr object found and client to bound pattrstorage. Switching to color mode", color_mode_candidate, '\n'); + if (is_listening_to_clientlist > 0) { + var target = 'preset_metadata'; + // Here we look for a 'preset_metadata' client, which is can be in a sublevel of the pattr hierarchy... + if (client.indexOf(target, client.length - target.length) !== -1) { //...hence checking the end of the client address (same as endsWith()) + metadata_pattr_address = target; + if (is_listening_to_clientlist == 1) { + // Define an alias for preset_metadata in case it is not in the root pattrstorage level + to_pattrstorage('alias', client, 'preset_metadata'); + } + if (is_listening_to_clientlist == 2) { + // post("preset_metadata pattr object found and client to bound pattrstorage. Switching to color mode", color_mode_candidate, '\n'); + color_mode = color_mode_candidate; + } is_listening_to_clientlist = 0; - color_mode = color_mode_candidate; + paint_base(); } else if (client == "done") { - error("A [pattr preset_color] object has been found but seems to be invisible to the pattrstorage.\n") + error("A [pattr preset_metadata] object has been found but seems to be invisible to the pattrstorage.\n") is_listening_to_clientlist = 0; } } @@ -1029,10 +1128,18 @@ function resync() { function find_pattrstorage(name) { active_slot = 0; pattrstorage_obj = this.patcher.getnamed(name); - if (pattrstorage_obj !== null) { + if (pattrstorage_obj == null) { + var parent_patcher = this.patcher.parentpatcher; + pattrstorage_obj = parent_patcher == undefined ? null : this.patcher.parentpatcher.getnamed(name); + } + + if (pattrstorage_obj != null) { pattrstorage_name = name; + filled_slots_dict.name = pattrstorage_name + '_presets_dict'; slots_clear(); // this.patcher.hiddenconnect(pattrstorage_obj, 0, this.box, 0); + // post('lets find presets_metata pattr for', name, '\n'); + if (use_uid || color_mode > 1) connect_to_metadata_pattr(); to_pattrstorage("getslotlist"); to_pattrstorage("getlockedslots"); } else { @@ -1049,7 +1156,8 @@ function find_pattrstorage(name) { find_pattrstorage.local = 1; function to_pattrstorage() { - if (pattrstorage_obj !== null) { + if (pattrstorage_obj && pattrstorage_obj.maxclass === 'pattrstorage') { + // post('sending to pattrstorage: ', arrayfromargs(arguments), '\n'); pattrstorage_obj.message(arrayfromargs(arguments)); } } @@ -1079,9 +1187,15 @@ slots_clear.local = 1; function get_slot_index(x, y) { // Returns which slot is hovered by the mouse for (var i = 1; i <= slots_count_display; i++) { - if (y > (slots[i].top - half_spacing) && y < (slots[i].bottom + half_spacing) && x > (slots[i].left - half_spacing) && x < (slots[i].right + half_spacing)) { - return i; - } + if (layout === 0) { + if (y > (slots[i].top - half_spacing) && y < (slots[i].bottom + half_spacing) && x > (slots[i].left - half_spacing) && x < (slots[i].right + half_spacing)) { + return i; + } + } else if (layout === 1) { + if (y > (slots[i].top - half_spacing) && y < (slots[i].bottom + half_spacing)) { + return i; + } + } } return -1; @@ -1128,25 +1242,34 @@ function update_umenu() { update_umenu.local = 1; function update_filled_slots_dict() { - // Creates a coll-compatible dict containing slot id, name, lock, color index and color_custom for all existing presets + // Creates a dict containing slot id, uid, name, lock, color index and color_custom for all existing presets // And sends the dict name to a receive. // Best would be to allow for single slot updates, but for that we need to be able to know which at index of filled_slot is a slot id. filled_slots_dict.clear(); + var by_slot = {}; + var by_uid = {}; for (var i = 0; i < filled_slots.length; i++) { var slot_index = filled_slots[i]; - var tmp_color_custom = slots[slot_index].color_custom; - filled_slots_dict.set(slot_index, slots[slot_index].name, slots[slot_index].lock, slots[slot_index].color_index, tmp_color_custom[0], tmp_color_custom[1], tmp_color_custom[2], tmp_color_custom[3]); + var tmp_dict = { + index: slot_index, // Preset slot number (unique, non-persistent) + name: slots[slot_index].name, // Preset name (non unique, non-persistent) + uid: slots[slot_index].uid, // Preset uid (unique and persistent across preset renaming, overwriting and moving) Useful for keeping track of preset across changes + u_name: slot_index + ' | ' + slots[slot_index].name, // '3 | Third preset': (unique, non-persistent) Useful for easy preset listing and retrieving + lock: slots[slot_index].lock, // Preset lock state + color_index: slots[slot_index].color_index, // Preset color index (when color_mode = 2) + color_custom: slots[slot_index].color_custom, // Preset color (when color_mode = 3) + }; + // post('updating by_uid', slots[slot_index].uid, '\n'); + by_slot[slot_index] = tmp_dict; + if (use_uid) by_uid[slots[slot_index].uid] = tmp_dict; } + filled_slots_dict.setparse('by_slot', JSON.stringify(by_slot)); + if (use_uid) filled_slots_dict.setparse('by_uid', JSON.stringify(by_uid)); - // Non coll-compatible, but with proper keys: - // filled_slots_dict.set('filled_slots'); - // for (var i = 0; i < filled_slots.length; i++) { - // if (i > 0) filled_slots_dict.append('filled_slots', ''); - // var tmp_color_custom = slots[filled_slots[i]].color_custom; - // filled_slots_dict.setparse('filled_slots[' + i + ']', 'slot:', filled_slots[i], 'name:', '"' + slots[filled_slots[i]].name + '"', 'lock:', slots[filled_slots[i]].lock, 'color_index:', slots[filled_slots[i]].color_index, 'color_custom:', tmp_color_custom[0], tmp_color_custom[1], tmp_color_custom[2], tmp_color_custom[3]); - // } var tmp_send_name = send_name == "none" ? pattrstorage_name + '_presets_dict' : send_name; + outlet(4, 'dictionary', filled_slots_dict.name); messnamed(tmp_send_name, 'dictionary', filled_slots_dict.name); + } update_filled_slots_dict.local = 1; @@ -1166,6 +1289,88 @@ function trigger_writeagain() { } trigger_writeagain.local = 1; +function find_textedit() { + var cords = this.box.patchcords; + for (var c = 0; c < cords.outputs.length; c++) { + if (cords.outputs[c].srcoutlet == 2 && cords.outputs[c].dstobject.maxclass == "textedit") { + textedit_obj = cords.outputs[c].dstobject; + textedit_obj.setattr('keymode', 1); + textedit_obj.setattr('lines', 1); + break; + } + } +} +find_textedit.local = 1; + +function set_textedit(s) { + // Store initial textedit state + if (slots[s].lock == false && textedit_obj != null) { + if (ui_rename) { + textedit_initstate.presentation = textedit_obj.getattr('presentation'); + textedit_initstate.hidden = textedit_obj.getattr('hidden'); + textedit_initstate.patching_rect = textedit_obj.getattr('patching_rect'); + textedit_initstate.presentation_rect = textedit_obj.getattr('presentation_rect'); + textedit_initstate.bgcolor = textedit_obj.getattr('bgcolor'); + textedit_initstate.bordercolor = textedit_obj.getattr('bordercolor'); + textedit_initstate.textcolor = textedit_obj.getattr('textcolor'); + textedit_initstate.fontname = textedit_obj.getattr('fontname'); + textedit_initstate.fontsize = textedit_obj.getattr('fontsize'); + textedit_initstate.fontface = textedit_obj.getattr('fontface'); + textedit_initstate.textjustification = textedit_obj.getattr('textjustification'); + + // Apply new textedit state + var pos = this.patcher.getattr("presentation") ? this.box.getattr('presentation_rect') : this.box.getattr('patching_rect'); + textedit_obj.setattr('bgcolor', stored_slot_color); + textedit_obj.setattr('bordercolor', [0,0,0,0]); + textedit_obj.setattr('textcolor', text_color); + textedit_obj.setattr('fontname', font_name); + textedit_obj.setattr('fontsize', font_size-1); + textedit_obj.setattr('fontface', 0); + textedit_obj.setattr('textjustification', 0); + + if (layout == 0) { + textedit_obj.message('position', pos[0] + margin, pos[1] + slots[s].top + y_offset); + textedit_obj.message('size', ui_width - (2*margin), slot_size + 1); + // Only works in v8ui + // textedit_obj.position = [pos[0] + margin, pos[1] + slots[s].top + y_offset]; + // textedit_obj.size = [ui_width - (2*margin), slot_size + 1]; + } else if (layout == 1) { + textedit_obj.message('position', pos[0] + margin + slot_size + spacing, pos[1] + slots[s].top + y_offset); + textedit_obj.message('size', ui_width - (2*margin + slot_size + spacing), slot_size + 1); + // Only works in v8ui + // textedit_obj.position = [pos[0] + margin + slot_size + spacing, pos[1] + slots[s].top + y_offset]; + // textedit_obj.size = [ui_width - (2*margin + slot_size + spacing), slot_size + 1]; + } + textedit_obj.setattr('presentation', this.patcher.getattr("presentation")); + textedit_obj.setattr('hidden', 0); + } + textedit_obj.message("select"); + is_typing_name = s; + } +} + +function restore_textedit() { + // Restore [textedit] to initial state + if (textedit_obj != null) { + if (ui_rename) { + textedit_obj.setattr('bgcolor', textedit_initstate.bgcolor); + textedit_obj.setattr('bordercolor', textedit_initstate.bordercolor); + textedit_obj.setattr('textcolor', textedit_initstate.textcolor); + textedit_obj.setattr('fontname', textedit_initstate.fontname); + textedit_obj.setattr('fontsize', textedit_initstate.fontsize); + textedit_obj.setattr('fontface', textedit_initstate.fontface); + textedit_obj.setattr('textjustification', textedit_initstate.textjustification); + textedit_obj.setattr('presentation', textedit_initstate.presentation); + textedit_obj.setattr('hidden', textedit_initstate.hidden); + textedit_obj.setattr('patching_rect', textedit_initstate.patching_rect); + textedit_obj.setattr('presentation_rect', textedit_initstate.presentation_rect); + textedit_obj.message('set', slots[is_typing_name].name); + } + is_typing_name = false; + } +} +restore_textedit.local = 1; + // MOUSE EVENTS function onidle(x,y,but,cmd,shift,capslock,option,ctrl) { @@ -1190,8 +1395,13 @@ function onidle(x,y,but,cmd,shift,capslock,option,ctrl) } onidle.local = 1; -function onidleout() +function onidleout(x, y) { + if (is_typing_name) { + if (x <= 0 || y <= 0 || x >= ui_width || y >= ui_height) { + restore_textedit(); + } + } last_hovered = -1; mgraphics.redraw(); } @@ -1199,6 +1409,9 @@ onidleout.local = 1; function onclick(x,y,but,cmd,shift,capslock,option,ctrl) { + if (is_typing_name) { + restore_textedit(); + } if (last_hovered > -1 && pattrstorage_name != null) { var output = "recall"; if (select_mode) { @@ -1209,18 +1422,32 @@ function onclick(x,y,but,cmd,shift,capslock,option,ctrl) if (option) { output = "delete"; } - } else if (slots[last_hovered].name == null) { + if (ctrl) { + output = "lock"; + } + } else if (ctrl && filled_slots.indexOf(last_hovered) > -1) { + output = "rename"; + } else if (slots[last_hovered].name == null) { return; } - if (output == "store") { + if (output == "recall" && recall_passthrough == false) { + // if recall_passthrough == true, send the recall message to pattrstorage directly + outlet(0, 'recall', last_hovered); + } else if (output == "store") { store(last_hovered); - } else { - if (output == "select") { - select(last_hovered); - // mgraphics.redraw(); - } else { - to_pattrstorage(output, last_hovered); + } else if (output == "select") { + select(last_hovered); + // mgraphics.redraw(); + } else if (output == "lock") { + lock(last_hovered, 1 - slots[last_hovered].lock); + if (selected_slot == last_hovered) { + outlet(3, "set", slots[last_hovered].lock); } + } else if (output == "rename") { + select(last_hovered); + set_textedit(last_hovered); + } else { + to_pattrstorage(output, last_hovered); } } @@ -1231,8 +1458,12 @@ onclick.local = 1; function ondblclick(x,y,but,cmd,shift,capslock,option,ctrl) { - if (last_hovered > -1 && pattrstorage_name != null && filled_slots.indexOf(last_hovered) > -1) { - to_pattrstorage("recall", last_hovered); + if (select_mode && last_hovered > -1 && pattrstorage_name != null && filled_slots.indexOf(last_hovered) > -1) { + if (recall_passthrough) { + to_pattrstorage("recall", last_hovered); + } else { + outlet(0, 'recall', last_hovered); + } } last_x = x; @@ -1384,7 +1615,8 @@ function get_prect(prect) { get_prect.local = 1; // ATTRIBUTES DECLARATION -declareattribute("pattrstorage", "getpattrstorage", "setpattrstorage", 1); +// If loaded in JSUI, only the first 4 arguments passed in declareattribute() will be used (no attribute styling). V8UI will also use the latest object argument. +declareattribute("pattrstorage", "getpattrstorage","setpattrstorage", 1, {type: "symbol", label: "Pattrstorage"}); function getpattrstorage() { if (pattrstorage_name == null) { return @@ -1395,7 +1627,7 @@ function getpattrstorage() { function setpattrstorage(v){ // This method is called for the first time when the patch is loading, before the loadbang (not all objects are instanciated yet) // With v being the value stored whithin the patcher - if (v == null) { + if (v == null || v == 0 || v.lastIndexOf('#') === 0) { pattrstorage_name = null; pattrstorage_obj = null; } else { @@ -1422,7 +1654,7 @@ function delayed_init() { } delayed_init.local = 1; -declareattribute("bubblesize", "getslotsize", "setslotsize", 1); +declareattribute("bubblesize", "getslotsize", "setslotsize", 1, {type: "long", default: 14, label: "Slot Size", category: "Appearance"}); function getslotsize() { return slot_size; } @@ -1430,12 +1662,12 @@ function setslotsize(v){ if (arguments.length) { slot_size = Math.max(2, v); } else { - slot_size = 20; + slot_size = 14; } calc_rows_columns(); } -declareattribute("slot_round", "getslotround", "setslotround", 1); +declareattribute("slot_round", "getslotround", "setslotround", 1, {type: "long", default: 0, label: "Slot Round", category: "Appearance"}); function getslotround() { return slot_round; } @@ -1449,7 +1681,7 @@ function setslotround(v){ calc_rows_columns(); } -declareattribute("margin", "getmargin", "setmargin", 1); +declareattribute("margin", "getmargin", "setmargin", 1, {type: "long", default: 4, label: "Margin", category: "Appearance"}); function getmargin() { return margin; } @@ -1462,7 +1694,7 @@ function setmargin(v){ calc_rows_columns(); } -declareattribute("spacing", "getspacing", "setspacing", 1); +declareattribute("spacing", "getspacing", "setspacing", 1, {type: "long", default: 4, label: "Spacing", category: "Appearance"}); function getspacing() { return spacing; } @@ -1475,7 +1707,7 @@ function setspacing(v){ calc_rows_columns(); } -declareattribute("bgcolor", "getbgcolor", "setbgcolor", 1); +declareattribute("bgcolor", "getbgcolor", "setbgcolor", 1, {style: "rgba", label: "Background Color", category: "Appearance"}); function getbgcolor() { return background_color; } @@ -1490,7 +1722,7 @@ function setbgcolor(){ paint_base(); } -declareattribute("empty_slot_color", "getemptycolor", "setemptycolor", 1); +declareattribute("empty_slot_color", "getemptycolor", "setemptycolor", 1, {style: "rgba", label: "Empty Slot Color", category: "Appearance"}); function getemptycolor() { return empty_slot_color; } @@ -1505,7 +1737,7 @@ function setemptycolor(){ paint_base(); } -declareattribute("active_slot_color", "getactiveslotcolor", "setactiveslotcolor", 1); +declareattribute("active_slot_color", "getactiveslotcolor", "setactiveslotcolor", 1, {style: "rgba", label: "Active Slot Color", category: "Appearance"}); function getactiveslotcolor() { return active_slot_color; } @@ -1520,7 +1752,7 @@ function setactiveslotcolor(){ mgraphics.redraw(); } -declareattribute("stored_slot_color", "getstoredslotcolor", "setstoredslotcolor", 1); +declareattribute("stored_slot_color", "getstoredslotcolor", "setstoredslotcolor", 1, {style: "rgba", label: "Stored Slot Color", category: "Appearance"}); function getstoredslotcolor() { return stored_slot_color; } @@ -1535,7 +1767,7 @@ function setstoredslotcolor(){ paint_base(); } -declareattribute("interp_slot_color", "getinterpslotcolor", "setinterpslotcolor", 1); +declareattribute("interp_slot_color", "getinterpslotcolor", "setinterpslotcolor", 1, {style: "rgba", label: "Interpolating slot color", category: "Appearance"}); function getinterpslotcolor() { return interp_slot_color; } @@ -1550,7 +1782,7 @@ function setinterpslotcolor(){ mgraphics.redraw(); } -declareattribute("text_bg_color", "gettextbgcolor", "settextbgcolor", 1); +declareattribute("text_bg_color", "gettextbgcolor", "settextbgcolor", 1, {style: "rgba", label: "Text Background Color", category: "Appearance"}); function gettextbgcolor() { return text_bg_color; } @@ -1565,7 +1797,7 @@ function settextbgcolor(){ mgraphics.redraw(); } -declareattribute("text_color", "gettextcolor", "settextcolor", 1); +declareattribute("text_color", "gettextcolor", "settextcolor", 1, {style: "rgba", label: "Text Color", category: "Appearance"}); function gettextcolor() { return text_color; } @@ -1580,7 +1812,7 @@ function settextcolor(){ mgraphics.redraw(); } -declareattribute("fontsize", "getfontsize", "setfontsize", 1); +declareattribute("fontsize", "getfontsize", "setfontsize", 1, {type: "float", label: "Font Size", category: "Appearance"}); function getfontsize() { return font_size; } @@ -1597,7 +1829,7 @@ function setfontsize(v){ } } -declareattribute("fontname", "getfontname", "setfontname", 1); +declareattribute("fontname", "getfontname", "setfontname", 1, {type: "symbol", label: "Font Name", category: "Appearance"}); function getfontname() { return font_name; } @@ -1619,7 +1851,7 @@ function setfontname(v){ } } -declareattribute("menu_mode", "getmenu_mode", "setmenu_mode", 1); +declareattribute("menu_mode", "getmenu_mode", "setmenu_mode", 1, {style: "enumindex", enumvals: ["Preset number + name", "Preset number", "Preset name"], label: "Menu Mode"}); function getmenu_mode() { return menu_mode; } @@ -1630,7 +1862,7 @@ function setmenu_mode(v){ } } -declareattribute("autowriteagain", "getautowriteagain", "setautowriteagain", 1); +declareattribute("autowriteagain", "getautowriteagain", "setautowriteagain", 1, {style: "onoff", label: "Auto writeagain"}); function getautowriteagain() { return auto_writeagain; } @@ -1642,7 +1874,7 @@ function setautowriteagain(v){ } } -declareattribute("ignoreslotzero", "getignoreslotzero", "setignoreslotzero", 1); +declareattribute("ignoreslotzero", "getignoreslotzero", "setignoreslotzero", 1, {style: "onoff", label: "Ignore Slot 0", category: "Appearance"}); function getignoreslotzero() { return ignore_slot_zero; } @@ -1654,7 +1886,7 @@ function setignoreslotzero(v){ } } -declareattribute("display_interp", "getdisplayinterp", "setdisplayinterp", 1); +declareattribute("display_interp", "getdisplayinterp", "setdisplayinterp", 1, {style: "onoff", label: "Display Interpolations", category: "Appearance"}); function getdisplayinterp() { return display_interp; } @@ -1666,7 +1898,7 @@ function setdisplayinterp(v){ } } -declareattribute("layout", "getlayout", "setlayout", 1); +declareattribute("layout", "getlayout", "setlayout", 1, {style: "enumindex", enumvals: ["Grid", "List"], label: "Layout", category: "Appearance"}); function getlayout() { return layout; } @@ -1680,7 +1912,7 @@ function setlayout(v){ calc_rows_columns(); } -declareattribute("scrollable", "getscrollable", "setscrollable", 1); +declareattribute("scrollable", "getscrollable", "setscrollable", 1, {style: "onoff", label: "Scrollable"}); function getscrollable() { return scrollable; } @@ -1694,7 +1926,7 @@ function setscrollable(v){ calc_rows_columns(); } -declareattribute("min_rows", "getmin_rows", "setmin_rows", 1); +declareattribute("min_rows", "getmin_rows", "setmin_rows", 1, {type: "long", min: 1, label: "Minimum Rows"}); function getmin_rows() { return min_rows; } @@ -1707,7 +1939,7 @@ function setmin_rows(v){ } } -declareattribute("select_mode", "getselect_mode", "setselect_mode", 1); +declareattribute("select_mode", "getselect_mode", "setselect_mode", 1, {style: "onoff", label: "Select Mode"}); function getselect_mode() { return select_mode; } @@ -1720,7 +1952,7 @@ function setselect_mode(v){ mgraphics.redraw(); } -declareattribute("color_mode", "getcolor_mode", "setcolor_mode", 1); +declareattribute("color_mode", "getcolor_mode", "setcolor_mode", 1, {type: "long", min: 0, max: 3, style: "enumindex", enumvals: ["Same color", "Six colors cycling", "Six colors free", "Custom colors"], label: "Color Mode", category: "Appearance"}); function getcolor_mode() { return color_mode; } @@ -1728,26 +1960,16 @@ function setcolor_mode(v){ v = Math.floor(v); v = Math.max(0, Math.min(3, v)); // For color modes 2 and 3 (select and custom), - // we need to ensure there's a [pattr preset_color] somewhere to store the preset color + // we need to ensure there's a [pattr preset_metadata] somewhere to store the preset color if (v >= 2 ) { - if (!preset_color_pattr_exist()) { + if (metadata_pattr == undefined) { + is_listening_to_clientlist = 2; + color_mode_candidate = v; + connect_to_metadata_pattr(); + } else { v = 0; color_mode = v; paint_base(); - } else { - if (pattrstorage_obj != null && pattrstorage_obj.getattr('subscribemode') == 1) { - // If the pattrstorage is in subscribe mode, we need to query its subscription list, - // ...and wait for the result to continue (see function subscribelist) - post(pattrstorage_name, "subscribe mode detected. Checking for subscribed 'preset_color' client.\n"); - is_listening_to_subscriptionlist = 1; - color_mode_candidate = v; - to_pattrstorage("getsubscriptionlist"); - } else { - // If not in subscribe mode - is_listening_to_clientlist = 1; - color_mode_candidate = v; - to_pattrstorage("getclientlist"); - } } } else { color_mode = v; @@ -1755,7 +1977,7 @@ function setcolor_mode(v){ } } -declareattribute("color_1", "getcolor1", "setcolor1", 1); +declareattribute("color_1", "getcolor1", "setcolor1", 1, {style: "rgba", label: "Color 1", category: "Appearance"}); function getcolor1() { return color_1; } @@ -1769,7 +1991,7 @@ function setcolor1(){ } } -declareattribute("color_2", "getcolor2", "setcolor2", 1); +declareattribute("color_2", "getcolor2", "setcolor2", 1, {style: "rgba", label: "Color 2", category: "Appearance"}); function getcolor2() { return color_2; } @@ -1783,7 +2005,7 @@ function setcolor2(){ } } -declareattribute("color_3", "getcolor3", "setcolor3", 1); +declareattribute("color_3", "getcolor3", "setcolor3", 1, {style: "rgba", label: "Color 3", category: "Appearance"}); function getcolor3() { return color_3; } @@ -1797,7 +2019,7 @@ function setcolor3(){ } } -declareattribute("color_4", "getcolor4", "setcolor4", 1); +declareattribute("color_4", "getcolor4", "setcolor4", 1, {style: "rgba", label: "Color 4", category: "Appearance"}); function getcolor4() { return color_4; } @@ -1811,7 +2033,7 @@ function setcolor4(){ } } -declareattribute("color_5", "getcolor5", "setcolor5", 1); +declareattribute("color_5", "getcolor5", "setcolor5", 1, {style: "rgba", label: "Color 5", category: "Appearance"}); function getcolor5() { return color_5; } @@ -1825,8 +2047,7 @@ function setcolor5(){ } } - -declareattribute("color_6", "getcolor6", "setcolor6", 1); +declareattribute("color_6", "getcolor6", "setcolor6", 1, {style: "rgba", label: "Color 6", category: "Appearance"}); function getcolor6() { return color_6; } @@ -1840,7 +2061,7 @@ function setcolor6(){ } } -declareattribute("send_name", "getsendname", "setsendname", 1); +declareattribute("send_name", "getsendname", "setsendname", 1, {type: "symbol", label: "Send Dictionary To"}); function getsendname() { return send_name; } @@ -1852,9 +2073,67 @@ function setsendname(){ } } +declareattribute("unique_names", "getunique_names", "setunique_names", 1, {style: "onoff", label: "Force Unique Names"}); +function getunique_names() { + return unique_names; +} +function setunique_names(v){ + unique_names = v > 0; +} + +declareattribute("use_uid", "getuse_uid", "setuse_uid", 1, {style: "onoff", label: "Use UID"}); +function getuse_uid() { + return use_uid; +} +function setuse_uid(v){ + var new_val = v == 1 ? 1 : 0; + if (new_val != use_uid && new_val == 1) { + use_uid = 1; + connect_to_metadata_pattr(); + to_pattrstorage("getslotlist"); + } + use_uid = new_val; +} + +declareattribute("recall_passthrough", "getrecall_passthrough", "setrecall_passthrough", 1, {style: "onoff", label: "Recall Passthrough"}); +function getrecall_passthrough() { + return recall_passthrough; +} +function setrecall_passthrough(v){ + recall_passthrough = v > 0; +} + +declareattribute("ui_rename", "getui_rename", "setui_rename", 1, {style: "onoff", label: "Rename In UI"}); +function getui_rename() { + return ui_rename; +} +function setui_rename(v){ + ui_rename = v > 0; + if (ui_rename) { + find_textedit(); + } +} + // UTILITY function post_keys(obj) { post('Keys of obj: ', obj, '\n'); post(Object.keys(obj)); post('\n'); -} \ No newline at end of file +} + +// Generating (fast but not compliant but more than enough for us) UID without crypto.randomUUID() +// https://stackoverflow.com/a/21963136 +var lut = new Array(256); +for (var i=0; i<256; i++) { lut[i] = (i<16?'0':'')+(i).toString(16); } +function generateUID() +{ + var d0 = Math.random()*0xffffffff>>>0; + var d1 = Math.random()*0xffffffff>>>0; + var d2 = Math.random()*0xffffffff>>>0; + var d3 = Math.random()*0xffffffff>>>0; + return lut[d0&0xff]+lut[d0>>8&0xff]+lut[d0>>16&0xff]+lut[d0>>24&0xff]+'-'+ + lut[d1&0xff]+lut[d1>>8&0xff]+'-'+lut[d1>>16&0x0f|0x40]+lut[d1>>24&0xff]+'-'+ + lut[d2&0x3f|0x80]+lut[d2>>8&0xff]+'-'+lut[d2>>16&0xff]+lut[d2>>24&0xff]+ + lut[d3&0xff]+lut[d3>>8&0xff]+lut[d3>>16&0xff]+lut[d3>>24&0xff]; +} +generateUID.local = 1; \ No newline at end of file