/* SPDX-License-Identifier: GPL-3.0-or-later */ /* This js file is meant to be used in Cycling'74 Max for the [jsui] object. It is designed to mimic [preset], but better. Copyright (C) 2024 Théophile Clet - https://tflcl.xyz. This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program. If not, see . */ autowatch = 0; // When developping, autowatch = 1 isn't enough. You also need to manually call the loadbang function, and then re-binding the pattrstorage. // A "loadbang, pattrstorage test" message does the trick. inlets = 1 setinletassist(0, "Connect to the linked pattrstorage"); outlets = 4; 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") mgraphics.init(); mgraphics.relative_coords = 0; mgraphics.autofill = 0; // LOOK var slot_size = 20; var slot_round = 0; var margin = 4; var spacing = 4; var font_size = 14; var font_name = "Arial"; var background_color = [0.2, 0.2, 0.2, 1]; var empty_slot_color = [0.349, 0.349, 0.349, 1]; var active_slot_color = [0.808, 0.898, 0.910, 1]; var stored_slot_color = [0.502, 0.502, 0.502, 1]; var interp_slot_color = [1.0, 1.0, 1.0, 0.8]; var text_bg_color = [1,1,1, 0.5]; var text_color = [0.129, 0.129, 0.129, 1]; // FEEL var layout = 1; var display_interp = 1; // Enable/disable the UI feedback when interpolating between presets var ignore_slot_zero = 1; // Makes previous_active_slot and interpolation display to ignore slot 0. Can be usefull when using slot 0 as a temporary step for interpolation. var auto_writeagain = 0; // When enabled, will send a "writeagain" to pattrstorage any time a preset is stored/deleted/moved/renamed/(un)locked var menu_number_only = 0; // Populates the umenu connected to 2nd outlet with stored preset number only, instead of number and name var scrollable = 1; // Defines weither the object can be scrolled or not var min_rows = 50; // Minimum number of rows to display is scrollable is enabled // (WORK) var pattrstorage_name, pattrstorage_obj = null; var columns, rows = 0; var slots = []; var slots_highest = 0; // Highest filled preset slot number var slots_count_display = 0; var filled_slots = []; var active_slot = 0; var previous_active_slot = 0; var previous_target = 0; var ui_width = box.rect[2] - box.rect[0]; var ui_height = box.rect[3] - box.rect[1]; var bg_width, bg_height = 0; var mg = new MGraphics(ui_width, ui_height); var base_drawing; var half_slot_size, half_margin, half_spacing; var last_x, last_y, last_hovered = -1; var y_offset = 0; // handle scrolling var shift_hold, option_hold = 0; var is_interpolating = 0; var is_dragging = 0; // Drag flag var drag_slot = -1; // Stores the slot that's being dragged if (jsarguments.length>1) { pattrstorage_name = jsarguments[1]; } function loadbang() { outlet(2, "set"); find_pattrstorage(pattrstorage_name); calc_rows_columns(); } // loadbang(); function calc_rows_columns() { half_margin = margin / 2; half_spacing = spacing / 2; half_slot_size = slot_size / 2; slots[0] = [0, 0, 0, 0, "(tmp)", 0, -1]; // Slot 0 is valid, but not represented in the GUI (and never saved by pattrstorage) if (layout == 0) { columns = Math.floor((ui_width - margin + spacing) / (slot_size + spacing)); rows = Math.floor((ui_height - margin + spacing) / (slot_size + spacing)); slots_count_display = columns * rows; } else { columns = 1; rows = Math.floor((ui_height - margin + spacing) / (slot_size + spacing)); if (scrollable) { rows = Math.max(rows, Math.max(min_rows, slots_highest)); } slots_count_display = columns * rows; } for (var i = 0; i < rows; i++) { var top = margin + i * (spacing+slot_size); var bottom = top + slot_size; for (var j = 0; j < columns; j++) { var left = margin + j * (spacing+slot_size); var right = left + slot_size; var cur = 1 + i * columns + j; var prev_name = null; var prev_lock = 0; var prev_interp = -1; if (typeof slots[cur] !== 'undefined') { prev_name = slots[cur][4]; prev_lock = slots[cur][5]; prev_interp = slots[cur][6]; } slots[cur] = [left, top, right, bottom, prev_name, prev_lock, prev_interp]; //0: left position //1: top position //2: right position //3: bottom position //4: name, null if nothing stored on that slot //5: lock state //6: is being interpolated (0 or 1) } } if (slots_count_display < slots_highest) { for (var i = slots_count_display + 1; i <= slots_highest; i++) { slots[i] = [0, 0, 0, 0, null, 0, -1]; } } // outlet(0, "init"); paint_base(); } calc_rows_columns.local = 1; function draw_slot(id, scale, cont) { scale = typeof cont !== 'undefined' ? scale : 1; // Sets scale to 1 by default if not passed as argument cont = typeof cont !== 'undefined' ? cont : mgraphics; // Sets drawing context to mgraphics by default if not passed as argument var offset = slot_size * (1 - scale); draw_slot_bubble(slots[id][0] + offset, slots[id][1] + offset, slot_size * scale, slot_size * scale, cont); cont.fill(); if (layout == 1) { // // Slot number // var nb = i.toString(); // var nb_dim = text_measure(nb); // var nb_pos_x = slots[i][0] + (slot_size - nb_dim[0]) / 2; // var nb_pos_y = slots[i][1] + (slot_size - spacing + nb_dim[1]) / 2 ; // set_source_rgba(text_color); // move_to(nb_pos_x, nb_pos_y); // show_text(nb); // slot text background var bg_txt_pos_x = margin + slot_size + spacing; var bg_txt_pos_y = slots[id][1]; var bg_txt_dim_w = ui_width - (2*margin + slot_size + spacing); var bg_txt_dim_h = slot_size; if (slots[id][4] != null) { cont.set_source_rgba(stored_slot_color); } else { cont.set_source_rgba(empty_slot_color); } cont.rectangle_rounded(bg_txt_pos_x, bg_txt_pos_y, bg_txt_dim_w, bg_txt_dim_h, 4, 4); cont.fill(); // slot name if (1) { // if (slots[id][4] != null) { var text = id; // If slot is locked, add brackets around its number if (slots[id][5] == 1) { text = '[' + text + ']'; } // If slot has a name, append it to the preset name if (slots[id][4] != null) { text += ': ' + slots[id][4]; } text = text.toString(); var text_dim = cont.text_measure(text); var txt_pos_x = margin + slot_size + 2 * spacing; var txt_pos_y = bg_txt_pos_y + (bg_txt_dim_h - spacing + text_dim[1]) / 2 ; cont.set_source_rgba(text_color); cont.move_to(txt_pos_x, txt_pos_y); cont.show_text(text.toString()); } } } draw_slot.local = 1; function draw_slot_bubble (x, y, w, h, cont) { cont = typeof cont !== 'undefined' ? cont : mgraphics; // I assume rectange is faster to draw than rectangle_rounded. Btw rectangle_rounded is wacky when showing interpolation. Maybe *interp on the first slot_round could solve this? if (slot_round) { cont.rectangle_rounded(x, y, w, h, slot_round, slot_round); } else { cont.rectangle(x, y, w, h); } } draw_slot_bubble.local = 1; function paint_base() { // We draw all slots (empty and stored ones) so we don't have to for every redraw // Background bg_width = layout == 0 ? columns * (slot_size + spacing) - spacing + 2 * margin : ui_width; bg_height = rows * (slot_size + spacing) - spacing + 2 * margin; mg = new MGraphics(ui_width, bg_height); with(mg) { set_source_rgba(background_color); rectangle(0, 0, bg_width, bg_height); fill(); select_font_face(font_name); set_font_size(font_size); // All slots for (var i = 1; i <= slots_count_display; i++) { if (slots[i][4] != null) { set_source_rgba(stored_slot_color); } else { set_source_rgba(empty_slot_color); } draw_slot(i, 1, mg); // fill(); } // if (layout == 0) { // for (var i = 1; i <= slots_count_display; i++) { // if (slots[i][4] != null) { // set_source_rgba(stored_slot_color); // } else { // set_source_rgba(empty_slot_color); // } // draw_slot_bubble(i, 1, mg); // fill(); // } // } else { // for (var i = 1; i <= slots_count_display; i++) { // if (slots[i][4] != null) { // set_source_rgba(stored_slot_color); // } else { // set_source_rgba(empty_slot_color); // } // draw_slot(i, 1, mg); // // fill(); // } // } } update_umenu(); base_drawing = new Image(mg); mgraphics.redraw(); } paint_base.local = 1; function paint() { // post("redraw\n"); with (mgraphics) { translate(0, y_offset); // Draw the base, which includes empty and filled slots image_surface_draw(base_drawing); set_line_width(1); // Active slot if (active_slot > 0 && active_slot <= slots_count_display) { set_source_rgba(active_slot_color); draw_slot_bubble(slots[active_slot][0], slots[active_slot][1], slot_size, slot_size); fill(); } // Previous active slot if (previous_active_slot > 0 && previous_active_slot <= slots_count_display) { set_source_rgba(active_slot_color); draw_slot_bubble(slots[previous_active_slot][0], slots[previous_active_slot][1], slot_size, slot_size); stroke(); } // Interpolated slots if (display_interp && is_interpolating) { for (var i = 1; i <= slots_count_display; i++) { var interp = slots[i][6]; if (interp >= 0) { set_source_rgba(interp_slot_color); draw_slot_bubble(slots[i][0], slots[i][1], slot_size, slot_size); stroke(); draw_slot_bubble(slots[i][0], slots[i][1] + slot_size * (1-interp), slot_size, slot_size * interp); fill(); } } } // Hovered slot if (last_hovered > -1) { if (shift_hold) { if (option_hold) { // About to delete set_source_rgba(empty_slot_color[0], empty_slot_color[1], empty_slot_color[2], 0.8); draw_slot_bubble(slots[last_hovered][0] + 1, slots[last_hovered][1] + 1, slot_size-2, slot_size-2); fill(); } else { // About to store set_source_rgba(active_slot_color[0], active_slot_color[1], active_slot_color[2], 0.7); draw_slot_bubble(slots[last_hovered][0] + 1, slots[last_hovered][1] + 1, slot_size-2, slot_size-2); fill(); } } // Slot border set_source_rgba(1, 1, 1, 0.8); draw_slot_bubble(slots[last_hovered][0], slots[last_hovered][1], slot_size, slot_size); stroke(); if (layout == 0) { //Text (slot number and name) var text = last_hovered; if (slots[last_hovered][5] == 1) { text = '[' + text + ']'; } if (slots[last_hovered][4] != null) { text += ': ' + slots[last_hovered][4]; } text = text.toString(); select_font_face(font_name); set_font_size(font_size); var text_dim = text_measure(text); // If the text is too big or a slot is being dragged, display the text on top of the next slot. // Otherwise, it gets displayed on the hovered slot. var bg_txt_pos_x = text_dim[0] > slot_size || is_dragging ? slots[last_hovered][0] + slot_size + half_spacing : slots[last_hovered][0] - half_spacing; var bg_txt_pos_y = text_dim[1] > slot_size || is_dragging ? slots[last_hovered][1] + (slot_size - text_dim[1]) / 2 - half_spacing : slots[last_hovered][1] - half_spacing; var bg_txt_dim_w = text_dim[0] > slot_size ? text_dim[0] + spacing : slot_size + spacing; var bg_txt_dim_h = text_dim[1] > slot_size ? text_dim[1] + spacing : slot_size + spacing; // If there is not enough place, text is displayed on the left if (bg_txt_pos_x + bg_txt_dim_w > ui_width) { bg_txt_pos_x = slots[last_hovered][0] - half_spacing - bg_txt_dim_w; } var txt_pos_x = text_dim[0] > slot_size ? bg_txt_pos_x + half_spacing : bg_txt_pos_x + (bg_txt_dim_w / 2) - (text_dim[0]/2); var txt_pos_y = bg_txt_pos_y + (bg_txt_dim_h - spacing + text_dim[1]) / 2 ; // Bubble background set_source_rgba(text_bg_color); rectangle_rounded(bg_txt_pos_x, bg_txt_pos_y, bg_txt_dim_w, bg_txt_dim_h, 4, 4); fill(); // Buble text set_source_rgba(text_color); move_to(txt_pos_x, txt_pos_y); show_text(text.toString()); } } // Drag slot if (is_dragging) { translate(last_x, last_y ); rotate(0.15); scale(1.1, 1.1); // scale(3, 3); // Shadow set_source_rgba(0, 0, 0, 0.15); for (var i = 0; i<4; i++) { draw_slot_bubble( i*0.4 + 1-slot_size/2, i*0.4 + 1-slot_size/2, slot_size + i*0.8, slot_size+i*0.8); fill(); } draw_slot_bubble( 2-slot_size/2, 2-slot_size/2, slot_size, slot_size); fill(); set_source_rgba(stored_slot_color); draw_slot_bubble( -slot_size/2, -slot_size/2, slot_size, slot_size); fill(); } } } paint.local = 1; function anything() { // Here just to avoid error messages in case pattrstorage sends unhandled message, like when using getstoredvalue, getsubscriptionlist, getalias, etc. // Handle the "delete" messages here because it is a reserevd word in js and cannot be used as a function name. if (messagename == "delete") { var v = arrayfromargs(arguments)[0]; v = Math.floor(v); if (v >= 0) { if (slots[v][5] > 0) { error('cannot delete locked slot ' + v + '\n'); } else { slots[v][4] = null; slots[v][6] = -1; if (active_slot == v) { active_slot = 0; } // to_pattrstorage("getslotname", v); to_pattrstorage("delete", v); to_pattrstorage("getslotlist"); paint_base(); set_active_slot(active_slot); outlet(0, "delete", v); trigger_writeagain(); } } } } function bang() { to_pattrstorage("recall", active_slot); } function msg_int(v) { to_pattrstorage("recall", v); } function msg_float(v) { var s = Math.floor(v); var i = v % 1; to_pattrstorage("recall", s, s+1, i); } function pattrstorage(v){ find_pattrstorage(v); paint_base(); } function slotlist() { filled_slots = arrayfromargs(arguments); if (filled_slots.length) { // If the highest numbered preset is above the maximum number of displayed presets, we need to extend slots[] slots_highest = filled_slots[filled_slots.length - 1]; if (slots_count_display < slots_highest) { for (var i = slots_count_display + 1; i <= slots_highest; i++) { slots[i] = [0, 0, 0, 0, null, 0, -1]; } } for (var i = 0; i < filled_slots.length; i++) { to_pattrstorage("getslotname", filled_slots[i]); } } // paint_base(); } function slotname() { var args = arrayfromargs(arguments); if (args[0] > 0 && args[1] != "(undefined)") { slots[args[0]][4] = args[1]; } } 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 (active_slot > 0) { var sname = arrayfromargs(arguments).join(' '); slotname(active_slot, sname); to_pattrstorage("slotname", active_slot, sname); update_umenu(); set_active_slot(active_slot); trigger_writeagain(); if (layout == 1) { paint_base(); } } } function text() { setslotname(arrayfromargs(arguments).join(' ')); } function recall() { var args = arrayfromargs(arguments); if (args.length == 1) { previous_active_slot = active_slot; is_interpolating = 0; set_active_slot(args[0]); outlet(0, 'recall', args[0]); } else { var src_slot = args[0]; var trg_slot = args[1]; for (var i = 0; i < filled_slots.length; i++) { slots[filled_slots[i]][6] = -1; } if (slots[src_slot][4] != null && slots[trg_slot][4] != null) { if (ignore_slot_zero == 1 && src_slot == 0) { // Set src_slot as if we were interpolating from the last recalled preset different than 0 // This way we can monitor which preset we come from even if we used preset 0 as intermediary preset if (previous_target != active_slot) { // If the last target preset was through interpollation or direct recall src_slot = previous_active_slot; } else { src_slot = active_slot; } } var interp = Math.min( 1, Math.max(0, args[2])); if (interp == 0.0) { slots[src_slot][6] = -1; slots[trg_slot][6] = -1; is_interpolating = 0; if (previous_target != active_slot) { previous_active_slot = active_slot; } else if (args[0] != 0) { previous_active_slot = args[0]; } else { previous_active_slot = previous_target; } set_active_slot(src_slot); } else if (interp == 1.0) { slots[src_slot][6] = -1; slots[trg_slot][6] = -1; is_interpolating = 0; previous_target = trg_slot; set_active_slot(trg_slot); } else { slots[src_slot][6] = 1 - interp; slots[trg_slot][6] = interp; is_interpolating = 1; active_slot = 0; // set_active_slot(0); } outlet(0, "recall", src_slot, trg_slot, interp); } } mgraphics.redraw(); } function recallmulti() { var args = arrayfromargs(arguments); var interp_slots = []; var summed_weight = 0; for (var i = 0; i < args.length; i++) { var weight = args[i] % 1.; if (weight == 0) weight = 1; summed_weight += weight; interp_slots.push([Math.floor(args[i]), weight]); } for (var i = 0; i < interp_slots.length; i++) { var nb = interp_slots[i][0]; if (slots[nb][4] != null) { interp_slots[i][1] /= summed_weight; } else { interp_slots[i][1] = -1; } slots[nb][6] = interp_slots[i][1] } is_interpolating = 1; mgraphics.redraw(); outlet(0, "recallmulti", args); } function store(v) { v = Math.floor(v); if (v >= 0) { if (slots[v][5] > 0) { error('cannot overwrite locked slot ' + v + '\n'); } else { var recalc_rows_flag = scrollable && v > slots_highest; to_pattrstorage("store", v); to_pattrstorage("getslotlist"); if (recalc_rows_flag) { calc_rows_columns(); } else { paint_base(); } if (!(ignore_slot_zero && v == 0)) { set_active_slot(v); } outlet(0, "store", v); if (v != 0) { trigger_writeagain(); } } } } function setlock(v) { lock(active_slot, v); } function lock() { var args = arrayfromargs(arguments); if (args.length == 2) { to_pattrstorage("lock", args[0], args[1]); to_pattrstorage("getlockedslots"); outlet(0, "lock", args[0], args[1]); trigger_writeagain(); if (layout == 1) { paint_base(); } } } function lockedslots() { var locked_slots = arrayfromargs(arguments); for (var i = 1; i < slots.length; i++) { slots[i][5] = 0; } if (locked_slots.length) { for (var i = 0; i < locked_slots.length; i++) { slots[locked_slots[i]][5] = 1; } } } function write() { var args = arrayfromargs(arguments); var filename = args[0]; var state = args[1]; if (state) { post(pattrstorage_name + ' pattrstorage: ' + filename + ' updated\n'); } else { error(pattrstorage_name + ': error while writing ' + filename + '\n'); } } function read() { var args = arrayfromargs(arguments); var state = args[1]; if (state) { pattrstorage(pattrstorage_name); } } function resync() { calc_rows_columns(); } function find_pattrstorage(name) { active_slot = 0; pattrstorage_obj = this.patcher.getnamed(name); if (pattrstorage_obj !== null) { pattrstorage_name = name; // slots_clear(); to_pattrstorage("getslotlist"); to_pattrstorage("getlockedslots"); } else { pattrstorage_name = null; slots_clear(); // error("Pattrstorage", name, "doesn't exist.\n"); } } find_pattrstorage.local = 1; function to_pattrstorage() { if (pattrstorage_obj !== null) { pattrstorage_obj.message(arrayfromargs(arguments)); } } function slots_clear() { slots[0] = [0, 0, 0, 0, "(tmp)", 0, -1]; for (var i = 1; i < slots.length; i++) { slots[i][4] = null; slots[i][5] = 0; slots[i][6] = -1; } } 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][1] - half_spacing) && y < (slots[i][3] + half_spacing) && x > (slots[i][0] - half_spacing) && x < (slots[i][2] + half_spacing)) { return i; } } return -1; } get_slot_index.local = 1; function set_active_slot(int) { if (int < 0) { active_slot = 0; } else { active_slot = int; } outlet(0, "previous", previous_active_slot); if (menu_number_only) { outlet(1, "setsymbol", active_slot); } else { outlet(1, "setsymbol", active_slot + ' ' + slots[active_slot][4]); } if (active_slot != 0) { outlet(2, "set", slots[active_slot][4]); } else { outlet(2, "set"); } outlet(3, "set", slots[active_slot][5]); } set_active_slot.local = 1; function update_umenu() { if (pattrstorage_obj !== null) { outlet(1, "clear"); for (var i=0; i < filled_slots.length; i++) { var txt = filled_slots[i].toString(); if (!menu_number_only) { txt += ' ' + slots[filled_slots[i]][4]; } outlet(1, "append", txt); } } } update_umenu.local = 1; function trigger_writeagain() { if (auto_writeagain && !is_dragging) { to_pattrstorage("writeagain"); } } trigger_writeagain.local = 1; // MOUSE EVENTS function onidle(x,y,but,cmd,shift,capslock,option,ctrl) { if (last_x != x || last_y != y - y_offset|| shift_hold != shift || option_hold != option) { last_x = x; last_y = y - y_offset; shift_hold = shift; option_hold = option; var cur = get_slot_index(x, y - y_offset); if (cur != last_hovered) { last_hovered = cur; mgraphics.redraw(); } } } onidle.local = 1; function onidleout() { last_hovered = -1; mgraphics.redraw(); } onidleout.local = 1; function onclick(x,y,but,cmd,shift,capslock,option,ctrl) { if (last_hovered > -1 && pattrstorage_name != null) { var output = "recall"; if (shift) { output = "store"; if (option) { output = "delete"; } } else if (slots[last_hovered][4] == null) { return; } if (output == "store") { store(last_hovered); } else { to_pattrstorage(output, last_hovered); } } last_x = x; last_y = y - y_offset; } onclick.local = 1; function ondrag(x,y,but,cmd,shift,capslock,option,ctrl) { y -= y_offset; if (is_dragging == 0 && last_hovered > 0 && slots[last_hovered][4] !== null) { var dist_from_start = Math.sqrt((x-last_x)*(x-last_x)+(y-last_y)*(y-last_y)); if (dist_from_start > 10) { is_dragging = 1; drag_slot = last_hovered; } } else if (is_dragging == 1) { last_hovered = get_slot_index(x, y); last_x = x; last_y = y; if (!but) { // Wehen to button is released, the dragging ceases if (last_hovered > 0 && last_hovered != drag_slot) { var cur_active_slot = active_slot; var offset = ((last_hovered <= drag_slot) && slots[last_hovered][4] != null) ? 1 : 0; var drag_slot_lock = slots[drag_slot][5]; // If the slot we wan to drag is locked, we need to temporarily unlock it. if (drag_slot_lock) { lock(drag_slot, 0); } // If new slot is empty we just move the drag preset here. If it's not, we move al next slots to the right if (slots[last_hovered][4] !== null) { to_pattrstorage("insert", last_hovered); } to_pattrstorage("copy", drag_slot + offset, last_hovered); to_pattrstorage("delete", drag_slot + offset); slots_clear(); to_pattrstorage("getslotlist"); to_pattrstorage("getlockedslots"); if (cur_active_slot == drag_slot) { active_slot = last_hovered; } // If the dragged slot was locked, relock it. if (drag_slot_lock) { lock(last_hovered, 1); } paint_base(); outlet(0, "drag", drag_slot, last_hovered, offset); set_active_slot(last_hovered); is_dragging = 0; trigger_writeagain(); } else { is_dragging = 0; mgraphics.redraw(); } } else { mgraphics.redraw(); } } } ondrag.local = 1; function onwheel(x, y, wheel_inc_x, wheel_inc_y, cmd, shift, caps, opt, ctrl) { if (scrollable) { y_offset += wheel_inc_y * 100.0; y_offset = Math.min(y_offset, 0); y_offset = Math.max(y_offset, -1 * (bg_height - ui_height)); mgraphics.redraw(); } } onwheel.local = 1; function onresize(w,h) { ui_width = w; ui_height = h; calc_rows_columns(); // loadbang(); to_pattrstorage("getslotlist"); paint_base(); } onresize.local = 1; // function ondblclick(x,y,but,cmd,shift,capslock,option,ctrl) // { // last_x = x; // last_y = y; // } // ondblclick.local = 1; // ATTRIBUTES DECLARATION declareattribute("bubblesize", "getslotsize", "setslotsize", 1); function getslotsize() { return slot_size; } function setslotsize(v){ slot_size = Math.max(2, v); calc_rows_columns(); } declareattribute("slot_round", "getslotround", "setslotround", 1); function getslotround() { return slot_round; } function setslotround(v){ slot_round = Math.max(0, Math.min(slot_size, v)); calc_rows_columns(); } declareattribute("margin", "getmargin", "setmargin", 1); function getmargin() { return margin; } function setmargin(v){ margin = Math.max(0, v); calc_rows_columns(); } declareattribute("spacing", "getspacing", "setspacing", 1); function getspacing() { return spacing; } function setspacing(v){ spacing = Math.max(1, v); calc_rows_columns(); } declareattribute("bgcolor", "getbgcolor", "setbgcolor", 1); function getbgcolor() { return background_color; } function setbgcolor(){ background_color = [arguments[0], arguments[1], arguments[2], arguments[3]]; paint_base(); } declareattribute("empty_slot_color", "getemptycolor", "setemptycolor", 1); function getemptycolor() { return empty_slot_color; } function setemptycolor(){ empty_slot_color = [arguments[0], arguments[1], arguments[2], arguments[3]]; paint_base(); } declareattribute("active_slot_color", "getactiveslotcolor", "setactiveslotcolor", 1); function getactiveslotcolor() { return active_slot_color; } function setactiveslotcolor(){ active_slot_color = [arguments[0], arguments[1], arguments[2], arguments[3]]; mgraphics.redraw(); } declareattribute("stored_slot_color", "getstoredslotcolor", "setstoredslotcolor", 1); function getstoredslotcolor() { return stored_slot_color; } function setstoredslotcolor(){ stored_slot_color = [arguments[0], arguments[1], arguments[2], arguments[3]]; paint_base(); } declareattribute("interp_slot_color", "getinterpslotcolor", "setinterpslotcolor", 1); function getinterpslotcolor() { return interp_slot_color; } function setinterpslotcolor(){ interp_slot_color = [arguments[0], arguments[1], arguments[2], arguments[3]]; mgraphics.redraw(); } declareattribute("text_bg_color", "gettextbgcolor", "settextbgcolor", 1); function gettextbgcolor() { return text_bg_color; } function settextbgcolor(){ text_bg_color = [arguments[0], arguments[1], arguments[2], arguments[3]]; mgraphics.redraw(); } declareattribute("text_color", "gettextcolor", "settextcolor", 1); function gettextcolor() { return text_color; } function settextcolor(){ text_color = [arguments[0], arguments[1], arguments[2], arguments[3]]; mgraphics.redraw(); } declareattribute("fontsize", "getfontsize", "setfontsize", 1); function getfontsize() { return font_size; } function setfontsize(v){ font_size = Math.max(2, v); mgraphics.redraw(); } declareattribute("fontname", "getfontname", "setfontname", 1); function getfontname() { return font_name; } function setfontname(v){ var fontlist = mgraphics.getfontlist(); if (fontlist.indexOf(v) > -1) { font_name = v.toString(); mgraphics.redraw(); } else { error("Font not found.\n"); } } declareattribute("autowriteagain", "getautowriteagain", "setautowriteagain", 1); function getautowriteagain() { return auto_writeagain; } function setautowriteagain(v){ if (v == 0) { auto_writeagain = 0; } else { auto_writeagain = 1; } } declareattribute("ignoreslotzero", "getignoreslotzero", "setignoreslotzero", 1); function getignoreslotzero() { return ignore_slot_zero; } function setignoreslotzero(v){ if (v == 0) { ignore_slot_zero = 0; } else { ignore_slot_zero = 1; } } declareattribute("displayinterp", "getdisplayinterp", "setdisplayinterp", 1); function getdisplayinterp() { return display_interp; } function setdisplayinterp(v){ if (v == 0) { display_interp = 0; } else { display_interp = 1; } } declareattribute("layout", "getlayout", "setlayout", 1); function getlayout() { return layout; } function setlayout(v){ if (v == 0) { layout = 0; } else { layout = 1; } y_offset = 0; calc_rows_columns(); } declareattribute("scrollable", "getscrollable", "setscrollable", 1); function getscrollable() { return scrollable; } function setscrollable(v){ if (v == 0) { scrollable = 0; } else { scrollable = 1; } y_offset = 0; calc_rows_columns(); } declareattribute("min_rows", "getmin_rows", "setmin_rows", 1); function getmin_rows() { return min_rows; } function setmin_rows(v){ if (v > 0) { min_rows = v; } if (scrollable) { calc_rows_columns(); } }