V-code Technical Reference ------------------------------------------------- == Introduction == V-code got its name from a void pointer. As bytecode is comprised of 'char', so V-code is of 'void *'. Therefore, "V". V-code engine runs mtPaint's GUI - and its commandline and scripts - but it is less a widget toolkit per se, than a technique of abstraction. The idea was to decouple the semantics of controls from the particulars of implementation - and to get rid of the mind-numbing boilerplate that invariably accompanies anything having to do with a GUI. And when I compare what it takes to create a new dialog now with V-code, to what it had taken without - I much prefer the new way. V-code is declarative. I describe an interface component, and leave it to the engine to make it - or to pretend convincingly enough, so that the rest of the program does not notice a difference. Whatever specialcasing is needed, is done within the engine, never to poke its ugly snout outside. While the declarative part is quite extensive, the runtime API is simplistic. It does not need complexity; when creation is firmly separated into its own phase, only a few tweaks remain to be done at runtime. Another reason for the simplicity is that I made things interchangeable. There are no special accessor functions for any type of widget, and callback signatures never differ without a very compelling reason. This way, one event handler can serve a number of different widgets with minimal effort. This absence of widget functions/methods is an intentional feature and the core difference of V-code from usual GUI toolkits. All actions are done through a compact set of call points, and while internally tasks get routed to specific effector modules, calling code takes no part in the decisions what to use, how, and when. Interdependences of some operations arising in some cases are thus easily tracked and handled, entirely within the engine. Equally easy are conditional replacements of a component with a differently implemented, or a simulated one. V-code is not told what to do; it receives commands as to what it should make happen. It is the principle of the thing. The function handlers are minimalistic. No sanity checks; if something is done wrong, code signals that by crashing. But no strictness either; if a function is called on an unhandled widget type, it simply does nothing. Default values are sometimes substituted, but only where doing it once in the handler made more sense than repeating the same at several call sites. Neither declarative nor runtime part is providing complete coverage; only the widgets and operations needed by mtPaint. When for a new feature I need something that is not yet there, I extend the V-code engine. == Overview == The pegasus-eye view of how V-code is used, is this. - You prepare a V-code description of, say, a dialog window. - You fill a struct with initial values for it. - You hand off both to **run_create_()**. - It executes the description: allocates memory, copies the struct in there, creates widgets, setups callbacks, does initializations, and shows the finished result to the user. - User does some things to controls in the dialog, changing some values, then presses "OK". - The callback sees that it was "OK", calls **run_query()** to read back values from all the controls. - The new values in the copy of struct and/or in global variables are used to do something. - The callback calls **run_destroy()** to finish off the window, and returns. - Done. There are ways and means to do more complicated things, but this simple scenario is done this simply; in exactly 3 function calls. This is how it can work. Every V-code command describing a control - say, a spinbutton or a text entry - has a memory reference; either to a global variable, or to a field in a struct. When the control is created, it is initialized from that memory; when it is read, the result goes to that memory. When **run_create()** makes that control, it saves the address of that V-code command, and the widget it created, into its "tape" (a flat array of "slots"). When you call **run_query()**, it iterates over slots in the "tape" and looks at commands; it reads back values from every control it finds, each into the place that its command refers to. When you call **run_destroy()**, after the widgets get destroyed it again iterates over slots and looks at commands, to do cleanup actions if needed; after that, the attached struct and "tape" are freed. When you need to add a callback, you put an //EVENT()// command, with a type ID and a function reference, after a control; this command also gets a slot in the "tape". The function gets called when the control sitting before it in the "tape" wants to raise an event of that type. With the //TRIGGER// command, you can raise the preceding //EVENT()// just after **run_create()** finishes creating things; doing it to a list control's //SELECT// event is the natural way to make other controls reflect initial selection in the list. Some commands that create controls, have //EVENT()// built in, some even two of them; for them, function references go as parameters to the commands. A //TRIGGER// command after a two-event control triggers its second builtin //EVENT()//. When you need to, say, hide a control, you pass address of its slot to **cmd_showhide()**. To get that address, you put a //REF()// command right before the command that creates the control. //REF()//, again, has a memory reference (to a field of type **void**** ; for a variable, you use //REFv()//); into that memory, **run_create()** will put the current position in the "tape". For a container widget, you create it with a command such as //VBOX//, and everything you create after it will go into it, till you close it with //WEND//. One-place containers, such as //FRAME()//, close by themselves after accepting a widget (or you can close them by //WEND// leaving them empty). One command can also create more than one container; //DOCK()// has two panes, each filled separately and finished by its own //WEND//. Most container widgets do not get slots of their own, but are fill-and-forget. If you need to interact with them, say hide a box with all its contents, you need a version with a slot; those are tagged with "r", like //VBOXr// vs //VBOX//. When you want to include some commands only if a condition holds (say, with an RGB image you offer to select red, green, or blue channel, and with indexed ones you don't), you can use simple conditionals //IF()// and //UNLESS()//, affecting one command, or extended ones //IFx()// and //UNLESSx()// that affect everything till the matching //ENDIF()//. They get a memory reference (to an **int**), and check it for being zero or nonzero. When you need to allocate some memory (say, a buffer for a histogram image to be displayed), you use the //TALLOC()// command; it takes two fields - from one it takes the size, into the other it puts the resulting pointer. Such memory is allocated as part of the same memory block that holds the struct and the "tape", so if there isn't enough memory for it, then nothing at all gets allocated and **run_create()** itself fails (returning NULL). If you put a pointer to some separately allocated object into the struct, you can even tell **run_destroy()** to free it along with everything else; for that, you use the //CLEANUP()// command. A complete command sequence is one that creates a self-contained interface element (usually a toplevel window, but not necessarily). There are two things required from it. It has to define something in where all the controls will go; a //WINDOW()//, //TOPVBOX// etc. And it has to terminate the sequence, telling **run_create()** what to do with it; use //WDIALOG()// command to run it as a dialog, //WSHOW// to show it to the user, or //WEND// to hand it off to the program to be shown later. == V-code as written == Declarative V-code is an array of pointers to void: **void *whatever_code[] = { ... };** The array is initialized by a sequence of command macros, with their parameters. In case the commands are referring to **ddata** fields, the struct type to use must be set before that, as the //WBbase// macro: **#define WBbase whatever_dd** So nearly all V-code chunks in mtPaint look like this: ``` #define WBbase whatever_dd void *whatever_code[] = { ... }; #undef WBbase ``` In a simplest case, one V-code chunk creates something from beginning to end, but there are also other possibilities. The most complex part of mtPaint, the main window, is defined part by part in several chunks in several source files, which the main chunk, fittingly named **main_code[]**, calls one by one using the //CALL()// command; some then call yet other chunks in their turn. The least complex parts, the lowly filter windows, use a reverse approach; a small filter-specific code chunk which is a V-code subroutine, indirectly called (with //CALLp()// command) by **filterwindow_code[]** chunk between it building the generic top part and the equally generic bottom part of the window. == In-memory structure == The memory block allocated by **run_create_()** is structured this way: | ddata | User-provided backing struct | | vdata | Internal window-info struct | | wdata | "Tape" of "slots" referring to widgets, events, etc. | | dtail | Extra memory allocated to widgets and to/by other V-code commands | All this is allocated as one single block, which not only greatly simplifies memory management, but also, with all references to anything of note sitting in a flat array of "slots", makes all kinds of reflection trivial. This is by virtue of V-code being processed in two passes; first, **predict_size()** counts how many "slots" and how much extra memory the V-code sequence wants, then memory for all that is allocated and partitioned up, the user-provided struct copied in, and only then the commands actually get executed one after another; creating and setting up widgets, taking up chunks of dtail area, and filling "slots". A canonical reference to a block is the address of its **wdata**; i.e. it points into middle of the block. The reason for that is purely historical; nothing prevents setting up **ddata** and **vdata** after **dtail** area instead, but given that a block should never be deallocated in any other way than by calling **run_destroy()**, the arrangement is not relevant to anything. A "slot" in **wdata** consists of 3 pointers: | 0 | Widget/something else | | 1 | V-code command that set up the slot | | 2 | Dtail chunk | Slot where "something else" is its **dtail** chunk (i.e. 0th cell equals 2nd) is considered "unreal"; such slots are set up by pseudo-widgets used to simulate real ones when running scripts, thus the name. With the V-code command obviously being the same in either case (real and simulated), this is the only way to tell the two apart. The first slot of **wdata** is a fake one, used for linking all this together: | 0 | **ddata** | | 1 | **vdata** | | 2 | Dtail chunk | The **vdata** structure masquerades as a V-code command //WDONE//; as no regular //WDONE// gets a slot of its own, the slot is thus unique. The **wdata_slot()** function just steps through the "tape" before a slot till it finds this marker, and returns its address. The second slot must belong to a toplevel widget. No other thing should be put into the "tape" before it. And the last slot in **wdata**, marking its end, is all NULLs; all functions iterating over slots, depend on it being there. //WEND//, //WSHOW// and //WDIALOG()// put it into the "tape", before doing all other finishing touches. Macro **GET_DDATA()** gets from **wdata** to **ddata** (just reads the 0th cell of the first slot). The macro GET_VDATA(), internal to vcode.c, gets from **wdata** to **vdata** (by reading the 1st cell). == Structure of V-codes == As said above, V-code is an array of pointers to void. The array contains a sequence of commands - each consisting of one or more pointer values: an instruction header, and a specified number of parameters. The parameters can be either constants, or memory references. The latter may be either addresses of globals (string constants, variables, cells in global arrays, functions), or offsets in **ddata** struct, of the type designated by the //WBbase// macro. Encoding the field references is done with the //WBfield()// macro: **..., WBfield(field), ...** Other things are encoded as per usual; variables with the "&" operator, strings, arrays and functions as is, constants with a "(void *)" cast: **&(variable), "string", array, array + 2, function, (void *)1** The responsibility of a high-level command macro is to pack its parameters in the way and order that the underlying instruction code expects them. Like this: **OKBTN(_("OK"), conf_done)** translates to this: **WBr2h_x(OKBTN, 1 + 2), (_("OK")), EVENT(OK, conf_done)** Here you see a command macro expanding into an instruction header macro, a string constant (wrapped in a no-op translation marker), and a nested command macro which in its turn expands to another header and a function constant: **WBrh(EVT_OK, 1), (conf_done)** Instruction headers are in fact 31-bit integer values, to be able to comfortably reside in a pointer on a 32-bit system, and to avoid the signed/unsigned hassle. As of mtPaint 3.50, the bits are allocated this way: || Bits | Function | Values | | 0-11 | Instruction code | op_* | | 12-15 | Packing mode | pk_* | | 16-17 | Number of slots | WB_REF* | | 18 | Indirection flag | WB_NFLAG | | 19 | Field flag | WB_FFLAG | | 20 | Script flag | WB_SFLAG | | 21-23 | Reserved | 0 | | 24-30 | Number of parameters | 0-127 | The number of parameters is how many pointers after the header are part of the command in question; i.e. how many to skip to get to the next header. In the example above it is 1 + 2: 1 for the string, and 2 more for the nested //EVENT()// command (which in its own header has 1, for the function). The first pointer after the header (given the number of parameters is nonzero) is intended to be interpreted as pointer to data; this is in no way a hard requirement, and many instructions use it for constants instead, but the prologue code in V-code functions pre-interprets this field in accordance to field flag and indirection flag, so if an instruction refers to in-memory data, it is easiest to place the most used (or only) location in there. - Initially, the first pointer is read as, well, a pointer (and by default, left unmodified); - With the field flag on, it is reinterpreted as an integer offset from the start of **ddata**, and the current **ddata** base address gets added to it; - With the indirection flag on, it is interpreted as a pointer to a pointer, and that pointer is read in. The script flag, if set, selects nondefault scripting-related behaviour for some few commands (specifics depend on command). Not even looked at for all the rest of them. The number of slots (from 0 to 3) tells //run_create_()// how many slots in the "tape" this command needs. The first slot refers to the command itself, the second and third are set up for its builtin //EVENT()// subcommands (each one a pair of pointers at end of the parameters area). A command not taking any slots, is executed and forgotten; this fits the widgets that do not need any interaction (such as most containers) and modifier commands. The example command above takes two slots; it is what the "r2" in its header macro means. The //EVENT// command on its own takes one, thus the plain "r" in its. The packing mode denotes how the widget the command creates should be packed into its container, in case the container supports this mode of packing. The small assortment of choices is this: | pk_NONE | not intended to be packed (modifier, event, etc.) | | pk_PACK | from beginning of container, not expanding | | pk_XPACK | from beginning, expanding | | pk_EPACK | from end, expanding | | pk_PACKEND | from end, not expanding | | pk_TABLE1x | into first free cell in first/second column of a table, not expanding | | pk_TABLE | into specified row and column(s) of a table, not expanding | | pk_TABLEx | into specified row and column(s) of a table, expanding | - //pk_PACK// and //pk_XPACK// are allowed for any kind of container, other modes require a matching container type. - //pk_TABLE// and //pk_TABLEx// take an extra pointer value, from the very end of command's parameters, and interpret it as 3 byte values denoting X, Y, and length, as packed by the //WBxyl()// macro. Like in this command macro for a horizontal box that can take several table columns: **#define TLHBOXl(X,Y,L) WBh_t(HBOX, 1), WBxyl(X, Y, L)** Returning to the example, //OKBTN()// command does //pk_XPACK//, as denoted by the "_x" in its header macro; the //EVENT()// command does //pk_NONE//, as denoted by the lack of "_" in its. And finally, the instruction code tells //run_create_()// what to do with it all (and later, is used to recognize the objects to which the slots in the "tape" refer). In the example, it is //op_OKBTN// for the command itself, and //op_EVT_OK// for its builtin //EVENT()// command. On the preprocessor side of things, instruction codes are assembled in stages. First, a set of macros is used as shorthand representations for every combination of flags and slots that ever arises in some command. For example, //WB_R2F// denotes two slots asked for, and the field flag set: **#define WB_R2F (WB_REF2 + WB_FFLAG)** Then goes the basic macro encoding the instruction header: //WBp_()//, taking instruction code, length, packing mode, and a flags and slots combination. However, for brevity, actual command macros use one of the many pre-encoded //WB*h*()// macros that need take only instruction name and length. Like the one from the example: **#define WBr2h_x(NM,L) WBp_(op_##NM, L, XPACK, R2)** In these macros, the part between "WB" and "h" denotes the slots and flags, and the part after "h", packing mode. Additionally, there is a set of helper macros that some commands use to pack two byte values into a single pointer: //WBwh()//, //WBnh()//, and //WBbs()//; the difference is purely semantic, they do the same thing. And another set for three values: //WBpbs()// and //WBppa()//, again doing the same. == V-code commands == All high-level command macros are listed below. The list is not final, for new commands can be derived from opcodes and lower-level macros any time when existing combinations of parameters do not cover a new use case. In the descriptions below, unless noted differently, the string parameters are constants or global **char** arrays, the numeric parameters are constants. Packing mode for the commands producing widgets (controls and containers), unless noted differently, is the default one, //pk_PACK//. Naming guidelines (not 100% followed) for the macros: - "**u**" prefixed to "unreal" (script-only, simulated) version of a widget: //uSPIN()// - "**a**" affixed to a version that takes an array instead of several constant parameters: //TSPINa()// - "**c**" affixed to a version that is centered: //MLABELc()// - "**e**" affixed to a version that has a builtin event: //RPACKe()// - "**p**" affixed to a version that takes string parameter by reference instead of a string constant: //WINDOWp()// - "**r**" affixed to a version that has a slot in the "tape": //VBOXr// - "**s**" affixed to a version that is scriptable: //MENUITEMs()// - "**v**" affixed to a version that refers to a variable/array instead of a field: //CSCROLLv()// - "**x**" affixed to a version that has extra parameters: //TLABELx()// - "**T**" or "**T1**" prefixed to a version packed in first/second table column: //TLABEL()//, //TSPIN()//, //T1SPIN()// - "**TL**" prefixed to a version packed into table at (//x, y//): //TLLABEL()// - "**TL...l**" circumfixed to a version packed into table at (//x, y//) taking //length// cells: //TLLABELl()// - "**X**" prefixed to a version that is packed expanding: //XTABLE()// - "**F**" prefixed to a version that has a frame around: //FTABLE()// === Finalizers === All these commands finish element creation and cause **run_create()** to return. They differ in what else they do between those two points. : //WDONE// : do nothing extra : //WSHOW// : show the toplevel : //WDIALOG(field)// : show, then process input till the **void**** field changes to non-NULL === Toplevels === All toplevels are one-place containers, unless noted differently. All have a slot in the "tape" (and extra slots for builtin events, if have any). : //MAINWINDOW(title, icon, width, height)// : program's main window, with specified title, XPM icon, default width and height : //WINDOW(title)// : a regular non-modal window with specified title : //WINDOWp(field)// : as above, with title passed in a **char*** field : //WINDOWm(title)// : a regular modal window with title : //WINDOWpm(field)// : as above, with title in a **char*** field : //DIALOGpm(field)// : a modal dialog, with title in a **char*** field; is a double container: the top (the first accessible) is dialog's "content area" (a vertical box), the bottom/second is its "action area" (the one with buttons, a horizontal box) : //FPICKpm(title_field, mode_field, filename_field, OK_handler, CANCEL_handler)// : a file selector window, with **char*** title, **int** mode, and **char[]** filename (in system encoding) in fields, and handlers for //OK// and //CANCEL// events; is a box container (horizontal) for extra controls : //POPUP(title)// : a popup window with specified title (which, while not shown, is still useful for identifying the window in window manager's lists) : //TOPVBOX// : a pseudo-toplevel, for elements created to sit in //MOUNT// containers or purely for scripting; is a box container (vertical) : //TOPVBOXV// : same thing but with different sizing: fills space vertically, but horizontally is only centered, not stretched : //IDENT(id)// : set a non-default identifying string for the toplevel; is needed when one toplevel has separate V-code descriptions for different parts: the case in point is file selector, where the //FPICKpm()// command builds its fixed part through a nested **run_create()** using an //IDENT()// === Containers === Table containers have a border area by default; its size can be set by //BORDER(TABLE)// (see "Sizing and placement" category below). Other containers do not, unless specifically noted. : //WEND// : close the current container : //TABLE(columns, rows)// : a table of given dimensions (those currently are initial, not a hard limit) : //TABLE2(rows)// : a two-column table : //TABLEs(columns, rows, spacing)// : a table with specified spacing (same for rows and columns) : //TABLEr(columns, rows)// : a table that gets a slot in the "tape" : //XTABLE(columns, rows)// : a table that is packed expanding (//pk_XPACK//) : //ETABLE(columns, rows)// : a table that is packed from the end (//pk_PACKEND//) : //VBOX// : a vertical box : //VBOXr// : a vertical box with slot in the "tape" : //VBOXbp(spacing, border, padding)// : a vertical box with specified spacing, border, and padding : //VBOXP// : a vertical box with padding of default size (5 pixels) : //VBOXB// : a vertical box with border of default size (5 pixels) : //VBOXS// : a vertical box with spacing of default size (5 pixels) : //VBOXPS// : a vertical box with padding and spacing of default size (5 pixels each) : //VBOXBS// : a vertical box with border and spacing of default size (5 pixels each) : //VBOXPBS// : a vertical box with padding, border and spacing of default size (5 pixels each) : //XVBOX// : a vertical box that is packed expanding : //XVBOXbp(spacing, border, padding)// : same, with specified spacing, border, and padding : //XVBOXP// : a vertical box, packed expanding, with default padding : //XVBOXB// : a vertical box, packed expanding, with default border : //XVBOXBS// : a vertical box, packed expanding, with default border and spacing : //EVBOX// : a vertical box, packed from the end : //HBOX// : a horizontal box : //HBOXbp(spacing, border, padding)// : a horizontal box with specified spacing, border, and padding : //HBOXP// : a horizontal box, with default padding : //HBOXPr// : same, with slot in the "tape" : //HBOXB// : a horizontal box, with default border : //XHBOX// : a horizontal box, packed expanding : //XHBOXbp(spacing, border, padding)// : same, with specified spacing, border, and padding : //XHBOXP// : a horizontal box, packed expanding, with default padding : //XHBOXS// : a horizontal box, packed expanding, with default spacing : //XHBOXBS// : a horizontal box, packed expanding, with default border and spacing : //TLHBOXl(x, y, length)// : a horizontal box, packed into //length// columns in a table, starting at column //x//, row //y// : //TLHBOXpl(x, y, length)// : same, with default padding : //EQBOX// : a horizontal box with equal space allocated to contents : //EQBOXbp(spacing, border, padding)// : same, with specified spacing, border, and padding : //EQBOXP// : a horizontal box with equal space, with default padding : //EQBOXB// : a horizontal box with equal space, with default border : //EQBOXS// : a horizontal box with equal space, with default spacing : //EEQBOX// : a horizontal box with equal space, packed from the end ==== Frames ==== Frames have an empty border area around them by default, its size can be set by //BORDER(FRAME)//. A container sitting inside a frame can also have a border of its own, even if both are created by one command. : //FRAME(name)// : a frame with specified name : //XFRAME(name)// : same, packed expanding : //XFRAMEp(field)// : a frame, packed expanding, with name passed in a **char*** field : //EFRAME// : a frame without name, with an "etched out" look : //FTABLE(name, columns, rows)// : a table of given dimensions sitting in a named frame : //FVBOX(name)// : a vertical box sitting in a named frame : //FVBOXB(name)// : a vertical box in a named frame, with default border : //FVBOXBS(name)// : a vertical box in a named frame, with default border and spacing : //FXVBOX(name)// : a vertical box in a named frame, packed expanding : //FXVBOXB(name)// : same, with default border : //EFVBOX// : a vertical box with twice the default border (10 pixels), sitting in an "etched out" nameless frame : //FHBOXB(name)// : a horizontal box in a named frame, with default border ==== Scrolling ==== Scrolling containers have a default border area: //BORDER(SCROLL)//. : //SCROLL(horiz_mode, vert_mode)// : a scrolling container, with display modes for its horizontal and vertical scrollbar: 0 - do not show, 1 - show when needed, 2 - show always : //XSCROLL(horiz_mode, vert_mode)// : same, packed expanding : //FSCROLL(horiz_mode, vert_mode)// : a scrolling container in an unnamed frame, packed expanding : //CSCROLLv(array)// : a scrolling container acting as a control: with its own slot, and with horizontal and vertical positions read into/reset from **int[2]** array (zeroed out by **run_create()**); horizontal and vertical scrollbars are shown when needed ==== Notebook ==== Regular notebook widgets have a default border area: //BORDER(NBOOK)//. : //NBOOK// : a notebook with tabs on top, packed expanding; is a container for //PAGE//'s : //NBOOKr// : same, with a slot in the "tape" : //NBOOKl// : a notebook with tabs on the left, packed expanding : //PAGE(name)// : a page for a notebook, with a name; is a box container (vertical) : //PAGEvp(var)// : same, with name passed in a **char*** variable : //PAGEi(icon, spacing)// : a page for a notebook, with an icon instead of name, and specified spacing : //PAGEir(icon, spacing)// : same, with a slot in the "tape" : //PLAINBOOK// : a "notebook" without any visible trappings, just to show either of two boxes in same place; has a slot in the "tape", through which to command it to switch pages; is a double container - page 0 is the top, all pages vertical boxes : //PLAINBOOKn(num_pages)// : same but with specified number of pages (3 or more); a multiple container, page 0 is the top : //BOOKBTN(name, field)// : a toggle button with a name, for switching pages of a //PLAINBOOK// whose slot is in the **void**** field: show page 1 if toggled, page 0 if not ==== Special ==== : //DOCK(ini_var_name)// : a splitter adding a dock pane on the right, that can be hidden/shown; the specified inifile variable (integer) holds the pane's width when it is shown; has a slot in the "tape"; is a double container, with left (main) pane the top, and dock pane the bottom, both panes are vertical boxes : //HVSPLIT// : a splitter that can be switched between single-pane, horizontal, and vertical; packed expanding, has a slot; is a container for two widgets, the first for single/left/top pane, the second for hidden/right/bottom one; still needs //WEND// after them : //VSPLIT// : a regular vertical splitter, packed expanding, has a slot; a container for two widgets, first is for the top pane, the second for the bottom, needs //WEND// after them : //TWOBOX// : a container for two widgets, holding them in one row if enough horizontal space, or in two rows if not : //MOUNT(field, create_func, CHANGE_handler)// : a container holding and "leasing" an element created by a specified **mnt_fn** function (returns **wdata**), with **int** field set to TRUE while holding it, and a handler for //CHANGE// event, triggered when leasing/unleasing; has a slot for itself and another for the event : //PMOUNT(field, create_func, CHANGE_handler, ini_var_name, def_height)// : same, but with the element held in a resizable vertical pane with specified default height, with specified inifile variable (integer) storing modified height : //REMOUNTv(var)// : a container that, when created, "leases" the element from the //MOUNT// whose slot is in the **void**** variable, and when destroyed, returns it back; is packed expanding, has a slot : //HEIGHTBAR// : a modifier that requests the max height of all the widgets following it in the same container, whether visible or not; used to prevent jitter when those widgets get shown/hidden later ==== Statusbar ==== : //STATUSBAR// : a statusbar, packed from the end, has a slot, is a box container (horizontal) : //STLABEL(width, align)// : a statusbar label, with specified width and alignment: 0 - left, 1 - center, 2 - right; has a slot : //STLABELe(width, align)// : same, packed from the end === Separators === : //HSEP// : a horizontal separator : //HSEPl(width)// : same, with specified minimum width : //HSEPt// : a thin (minimum height) horizontal separator === Labels === All labels, when packed into boxes and tables, have padding by default; its size can be set by //BORDER(LABEL)//. Labels are left aligned by default. Labels identify adjacent controls to scripts, unless prevented from it or have no control to attach to. : //MLABEL(text)// : a regular label with text : //MLABELr(text)// : same, with a slot of its own : //MLABELc(text)// : a centered label with text : //MLABELcp(field)// : same, with text passed in a **char*** field : //MLABELxr(text, x_padding, y_padding, x_align)// : a label with text, with specified extra horizontal and vertical padding, and horizontal alignment at //x_align/10//: 0 - left, 5 - center, 10 - right; has a slot : //MLABELpx(field, x_padding, y_padding, x_align)// : same, but with text passed in a **char*** field, and without a slot of its own : //WLABELp(field)// : a centered label with text passed in a **char*** field, packed expanding, ignored by scripts : //XLABELcr(text)// : a centered label with text, packed expanding, has a slot : //TLABEL(text)// : a label with text, packed into the first free cell of the first column of a table : //TLABELr(text)// : same, with a slot of its own : //TLABELx(text, x_padding, y_padding, x_align)// : same, with specified padding and alignment, and without a slot : //TLLABELl(text, x, y, length)// : a label with text, packed into //length// columns in a table, starting at column //x//, row //y// : //TLLABEL(text, x, y)// : same, packed into one column : //TLLABELx(text, x, y, x_padding, y_padding, x_align)// : same, with specified padding and alignment : //TLLABELxr(text, x, y, x_padding, y_padding, x_align)// : same, with a slot of its own : //TLLABELp(field, x, y)// : a label with text passed in a **char*** field, packed into a table at column //x//, row //y// : //TLLABELpx(field, x, y, x_padding, y_padding, x_align)// : same, with specified padding and alignment : //TXLABEL(text, x, y)// : a label with text, packed expanding into a table at column //x//, row //y//, aligned at 0.3 of its width : //HLABELp(field)// : a helptext label with text passed in a **char*** field : //HLABELmp(field)// : same with monospace font : //TLTEXT(text, x, y)// : text, separated into columns by tabs and into rows by newlines, is placed into a table starting at column //x//, row //y// : //TLTEXTf(field, x, y)// : same, with text stored in a **char** array field : //TLTEXTp(field, x, y)// : same, but with text passed in a **char*** field === Image display === All widgets in this group have slots in the "tape". : //COLORPATCHv(rgb_array, width, height)// : areŠ° of specified size, filled with RGB color passed in an **unsigned char[3]** array (or a string constant), packed expanding : //RGBIMAGE(image_field, w_h_array_field)// : area of size passed in **int[2]** array field, displaying an image pointed to by **unsigned char*** field : //TLRGBIMAGE(image_field, w_h_array_field, x, y)// : same, packed into a table at column //x//, row //y// : //RGBIMAGEP(array_field, width, height)// : area of specified size, displaying an image stored in **unsigned char[]** array field : //CANVASIMGv(array, width, height)// : framed canvas widget (one able to properly receive clicks, releases, and movement, and efficiently handle scrolling) of specified size, displaying an image stored in **unsigned char[]** array : //CCANVASIMGv(array, width, height)// : same, packed from the end, expanding (//pk_EPACK//) : //CANVASIMGB(image_field, w_h_bkg_array_field)// : framed canvas widget of minimum size passed in first 2 cells of **int[3]** array field, displaying an image pointed to by **unsigned char*** field, and filling the extra space, if any, with RGB color packed in an **int** in the cell 2 of the **int[3]** array : //FCIMAGEP(image_field, x_y_array_field, w_h_array_field)// : focusable widget displaying an image pointed to by **unsigned char*** field, of size passed in **int[2]** array field, contoured by a frame, with a ring-shaped location marker on it at coordinates given in another **int[2]** array field : //TLFCIMAGEP(image_field, x_y_array_field, w_h_array_field, x, y)// : same, packed into a table at column //x//, row //y// : //TLFCIMAGEPn(image_field, w_h_array_field, x, y)// : same, but without a location marker : //CANVAS(init_width, init_height, cost, EXT_handler)// : framed canvas widget of specified initial size, that triggers //EXT// event to redraw an area (handled by an **evtxr_fn**); the //cost// value is defined as how many pixels likely could be redrawn in the time needed to call the event once more (i.e. its init+teardown cost), and is used for deciding whether to redraw multiple subregions one by one, or their encompassing region once; has a slot for itself and an extra one for the event The //EXT// handler of //CANVAS()// receives in its //xdata// parameter a pointer to **rgbcontext** (see mygtk.h). Its task is to render into the buffer there an RGB image of the denoted area of canvas, and return TRUE. === Spinbuttons and spinsliders === All spinbuttons and spinsliders, when packed into boxes and tables, have padding by default; its size can be set by //BORDER(SPIN)// and //BORDER(SPINSLIDE)//. They all have slots in the "tape", unless specifically noted. : //NOSPINv(var)// : displays an **int** variable in a non-modifiable spinbutton, has no slot : //TLNOSPIN(field, x, y)// : displays an **int** field value in a non-modifiable spinbutton, packed into a table at column //x//, row //y//, has no slot : //TLNOSPINr(field, x, y)// : same but has a slot : //TLSPIN(field, min, max, x, y)// : a spinbutton with **int** field, range //min// to //max//, packed into a table at column //x//, row //y// : //TLXSPIN(field, min, max, x, y)// : same, packed there expanding : //TLXSPINv(var, min, max, x, y)// : same, with **int** variable : //T1SPIN(field, min, max)// : a spinbutton with **int** field, packed into the first free cell of the second column of a table : //TSPIN(text, field, min, max)// : same combined with label that goes into the first column : //TSPINv(text, var, min, max)// : same, with **int** variable : //TSPINa(text, array_field)// : same, but with value, min and max in **int[3]** array field : //SPIN(field, min, max)// : a spinbutton with **int** field, range //min// to //max// : //SPINv(var, min, max)// : same, with **int** variable : //SPINc(field, min, max)// : a spinbutton with **int** field, centering the value : //XSPIN(field, min, max)// : a spinbutton with **int** field, packed expanding : //FSPIN(field, min, max)// : a fixedpoint spinbutton with **int** field holding value * 100, range //min// to //max// (both * 100) : //FSPINv(field, min, max)// : same, with **int** variable : //TFSPIN(text, field, min, max)// : a fixedpoint spinbutton with **int** field, packed into the second column of a table, combined with label that goes into the first column : //FSPIN(field, min, max, x, y)// : a fixedpoint spinbutton with **int** field, packed into a table at column //x//, row //y// : //SPINa(array_field)// : a spinbutton with value, min and max in **int[3]** array field : //XSPINa(array_field)// : same, packed expanding : //uSPIN(field, min, max)// : a script-only simulated spinbutton with **int** field, range //min// to //max// : //uSPINv(var, min, max)// : same, with **int** variable : //uFSPINv(var, min, max)// : same, but fixedpoint : //uSPINa(array_field)// : a script-only simulated spinbutton with value, min and max in **int[3]** array field : //uSCALE(field, min, max)// : a script-only simulated spinbutton with **int** field, with extended syntax: interprets values like "x1.5" as "original value multiplied by 1.5" : //TLSPINPACKv(array, count, CHANGE_handler, width, x, y)// : a grid of //count// spinbuttons arranged in //width// columns, packed into a table starting at column //x//, row //y//, with values, min and max for each in **int[][3]** array which is automatically updated when any of values changes, with a handler for //CHANGE// event that is triggered after (handled by an **evtx_fn**); has a slot for itself and another for the event : //T1SPINSLIDE(field, min, max)// : a spinslider with **int** field, range //min// to //max//, packed into the first free cell of the second column of a table, with preset size (255 x 20 pixels) : //TSPINSLIDE(text, field, min, max)// : same combined with label that goes into the first column : //TSPINSLIDEa(text, array_field)// : same, but with value, min and max in **int[3]** array field : //TLSPINSLIDE(field, min, max, x, y)// : a spinslider with **int** field, packed into a table at column //x//, row //y// : //TLSPINSLIDEvs(var, min, max, x, y)// : same, with **int** variable and preset width (150 pixels) : //TLSPINSLIDExl(field, min, max, x, y, length)// : a spinslider with **int** field, packed expanding into //length// columns in a table, starting at column //x//, row //y// : //TLSPINSLIDEx(field, min, max, x, y)// : same, packed into one column : //SPINSLIDEa(array_field)// : a spinslider with value, min and max in **int[3]** array field : //XSPINSLIDEa(array_field)// : same, packed expanding The //CHANGE// handler of //TLSPINPACKv()// receives in its //xdata// parameter an **int** index of the spinbutton that changed, cast into **void***. === Checkbuttons === All checkbuttons have a default border area: //BORDER(CHECK)//. They all have slots in the "tape". : //CHECK(name, field)// : a named checkbutton with **int** field : //CHECKv(name, var)// : same, with **int** variable : //CHECKb(name, field, ini_var_name)// : a named checkbutton with **int** field, with specified inifile variable (integer) storing its value : //XCHECK(name, field)// : a named checkbutton with **int** field, packed expanding : //TLCHECKl(name, field, x, y, length)// : same, packed into //length// columns in a table, starting at column //x//, row //y// : //TLCHECK(name, field, x, y)// : same, packed into one column : //TLCHECKvl(name, var, x, y, length)// : a named checkbutton with **int** variable, packed into //length// columns in a table, starting at column //x//, row //y// : //TLCHECKv(name, var, x, y)// : same, packed into one column : //uCHECK(name, field)// : a script-only simulated named checkbutton with **int** field : //uCHECKv(name, var)// : same, with **int** variable === Radiobutton packs === All radiobutton packs have a default border area: //BORDER(RPACK)//. They all have slots in the "tape", those with a builtin event have an extra slot for the event. : //RPACK(names_array, count, height, field)// : a box of radiobuttons with **int** field that stores the index of the active one, with names in **char*[]** array, //count// of them in total (0 if array of names is NULL-terminated), arranged in columns of no more than //height// (0 if all in a single column), packed expanding : //RPACKv(names_array, count, height, var)// : same, with **int** variable : //FRPACK(frame_name, names_array, count, height, field)// : a box of radiobuttons with **int** field, sitting in a named frame : //FRPACKv(frame_name, names_array, count, height, var)// : same, with **int** variable : //RPACKe(names_array, count, height, field, SELECT_handler)// : a box of radiobuttons with **int** field and a handler for //SELECT// event, packed expanding : //FRPACKe(frame_name, names_array, count, height, field, SELECT_handler)// : same, sitting in a named frame : //RPACKD(names_field, height, field)// : a box of radiobuttons with **int** field, with **char**** field pointing to a NULL-terminated **char*[]** array of names, packed expanding : //RPACKDv(names_field, height, var)// : same, with **int** variable : //RPACKDve(names_field, height, var, SELECT_handler)// : same, with a handler for //SELECT// event In case a radiobutton's name is an empty string, the corresponding index is just skipped. === Option menus and comboboxes === All option menus and comboboxes have a default border area: //BORDER(OPT)//. They all have slots in the "tape", those with a builtin event have an extra slot for the event. : //OPT(names_array, count, field)// : an option menu with **int** field, with list of choices' names in **char*[]** array, //count// of them in total (0 if array of choices is NULL-terminated) : //OPTv(names_array, count, var)// : same, with **int** variable : //TOPTv(text, names_array, count, var)// : same, packed into the second column of a table, combined with label that goes into the first column : //TLOPT(names_array, count, field, x, y)// : an option menu with **int** field, packed into a table at column //x//, row //y// : //TLOPTv(names_array, count, var, x, y)// : same, with **int** variable : //OPTe(names_array, count, field, SELECT_handler)// : an option menu with **int** field and a handler for //SELECT// event : //OPTve(names_array, count, var, SELECT_handler)// : same, with **int** variable : //XOPTe(names_array, count, field, SELECT_handler)// : an option menu with **int** field and a handler for //SELECT// event, packed expanding : //TLOPTle(names_array, count, field, SELECT_handler, x, y, length)// : same, packed into //length// columns in a table, starting at column //x//, row //y// : //TLOPTvle(names_array, count, var, SELECT_handler, x, y, length)// : same, with **int** variable : //TLOPTve(names_array, count, var, SELECT_handler, x, y)// : same, packed into one column : //OPTD(names_field, field)// : an option menu with **int** field, with **char**** field pointing to a NULL-terminated **char*[]** array of choices' names : //XOPTD(names_field, field)// : same, packed expanding : //TOPTDv(text, names_field, field)// : same with **int** variable, packed into the second column of a table, combined with label that goes into the first column : //OPTDe(names_field, field, SELECT_handler)// : an option menu with **int** field, **char**** field pointing to choices' names, and a handler for //SELECT// event : //XOPTDe(names_field, field, SELECT_handler)// : same, packed expanding : //TOPTDe(text, names_field, field, SELECT_handler)// : same, packed into the second column of a table, combined with label that goes into the first column : //COMBO(names_array, count, field)// : a combobox with **int** field, with list of choices' names in **char*[]** array, //count// of them in total (0 if array of choices is NULL-terminated) : //PCTCOMBOv(var, array, CHANGE_handler)// : a combobox displaying percent zoom values, with **int** variable, an **int[]** array (0-terminated) of some values, and a handler for //CHANGE// event; has no border In case a choice's name is an empty string, the corresponding index is just skipped. === Specialized controls === All widgets in this group have slots in the "tape", those with a builtin event have an extra slot for the event. : //PROGRESSp(text_field)// : a progressbar, with text for it passed in a **char*** field : //GRADBAR(chan_field, idx_field, len_field, max, map_array_field, cmap_array_field, SELECT_handler)// : a "gradient bar" for displaying and selecting points in a gradient, with currently selected index in **int** //idx_field//, gradient's length in //len_field//, gradient's channel in **int** //chan_field//, colormap for non-RGB cases in **unsigned char[768]** //cmap_array_field//, gradient's values/colors at points in **unsigned char[]** //map_array_field//, and a handler for //SELECT// event : //KEYBUTTON(field)// : a button for choosing key combos, with **char*** field where it places keyname string : //TABLETBTN(name)// : a named button for calling up tablet configuration dialog : //FONTSEL(array_field)// : a font chooser, with font description string and the text to render in a **char*[2]** array field, packed expanding : //EYEDROPPER(field, CHANGE_handler, x, y)// : a button calling up eyedropper, with color it picked up in an **int** field, and a handler for //CHANGE// event, packed into a table at column //x//, row //y// : //COLOR(array_field)// : a color chooser for opaque colors, with RGBA in **unsigned char[4]** array field : //TCOLOR(array_field)// : same for colors with alpha === Lists === The //LISTCC*// widgets have a default border area: //BORDER(LISTCC)//. All list widgets and columns have slots in the "tape", those with a builtin event have an extra slot for the event, those with two, extra two slots. : //COLORLIST(names_field, idx_field, rgb_array_field, SELECT_handler, EXT_handler)// : a list of named colors, with currently selected index in **int** field, a **char**** field pointing to a NULL-terminated **char*[]** array of names, colors themselves in **unsigned char[]** array field, a handler for //SELECT// event, and a handler for //EXT// event (an **evtx_fn**) : //COLORLISTN(cnt_field, idx_field, rgb_array_field, SELECT_handler, EXT_handler)// : same for numbered colors, with count of them in **int** field : //LISTCCHr(idx_field, len_field, max, SELECT_handler)// : a simpler (headerless, unsorted) columned list, with index and length (dynamic, limited to //max// items) in **int** fields, and a handler for //SELECT// event : //LISTCCHr(idx_field, len_field, SELECT_handler)// : same with static (unchanging) length : //LISTC(idx_field, len_field, SELECT_handler)// : a complex (with column headers and sorting) columned list, with index and length in **int** fields, and a handler for //SELECT// event : //LISTCu(idx_field, len_field, SELECT_handler)// : same, unsorted : //LISTCd(idx_field, len_field, SELECT_handler)// : same, with draggable rows : //LISTCS(idx_field, len_field, sortmode_field, SELECT_handler)// : a complex columned list, with selectable sort column and direction; the sort mode in **int** field is column+1 if sorted ascending, and the same negated if descending : //LISTCX(idx_field, len_field, sortmode_field, map_field, SELECT_handler, EXT_handler)// : same, with user-resizable columns and filtering: **int**** field points to mapping array (row indices to display, in order; also used for sorting); and with a handler for //EXT// event (an **evtx_fn**) Index in //LISTC()// and its brethren refers to raw (unsorted and unfiltered) data. The //EXT// handler of //LISTCX()// is triggered by right click on a row, and receives in its //xdata// parameter an **int** index of that row. The //EXT// handler of //COLORLIST()// and //COLORLISTN()// is triggered by click on a color, and receives in its //xdata// parameter a pointer to **colorlist_ext**. The columns of a columned list sit between //WLIST// command and the list itself, and describe what, how, and where from goes into each. They can refer either to an array, or to a structure and a field in it; if the latter, the //COLUMNDATA()// command is needed, describing an array of those structures. : //WLIST// : start a group of columns : //COLUMNDATA(field, step)// : set a **void*** field as pointer to the array of data structures for columns in this group : //IDXCOLUMN(init, step, width, align)// : add a column of indices, starting at //init// and changing by //step//, //width// pixels wide, aligned per //align//: 0 - left, 1 - center, 2 - right : //TXTCOLUMNv(array, step, width, align)// : add a column of text strings from **char[]** array (buffers, not pointers), with specified step, width and alignment : //XTXTCOLUMNv(array, step, width, align)// : same, expanding : //NTXTCOLUMNv(name, array, step, width, align)// : add a column of text strings from **char[]** array, with column name/title : //NTXTCOLUMND(name, struct_type, struct_field, width, align)// : same, from a **char[]** array field (buffer) in //COLUMNDATA// : //PTXTCOLUMN(array_field, step, width, align)// : add a column of text strings from **char*[]** array field (pointers) : //PTXTCOLUMNp(field, step, width, align)// : same, from **char*[]** array pointed to by //field// : //RTXTCOLUMNDi(width, align)// : add a column of text strings relative to elements of **int[]** array that is //COLUMNDATA//: each element holds an offset **from itself** to the string : //RTXTCOLUMND(struct_type, struct_field, width, align)// : same, relative to **int** field in //COLUMNDATA// : //NRTXTCOLUMND(name, struct_type, struct_field, width, align)// : same, with column name/title : //NRTXTCOLUMNDax(name, index, width, align, ini_var_name)// : same, relative to //index//-th element of **int[]** sub-array in //COLUMNDATA//, with inifile variable (integer) storing modified width : //NRTXTCOLUMNDaxx(name, index, width, align, ini_var_name, test_string)// : same, at least wide enough for //test_string// : //NRFILECOLUMNDax(name, index, width, align, ini_var_name)// : add a column of filenames relative to element of sub-array: the 0th character denotes the type: 'F' for file, 'D' for directory, ' ' for ".." : //CHKCOLUMNv(array, step, width, align, CHANGE_handler)// : add a column of checkbuttons from **int[]** array, with a handler for //CHANGE// event (an **evtxr_fn**) The //CHANGE// handler of //CHKCOLUMNv()// receives in its //xdata// parameter an **int** index of the row that was toggled. === Text entry fields === All entry widgets, when packed into boxes and tables, have padding by default; its size can be set by //BORDER(ENTRY)//. All pathboxes have a default border area: //BORDER(PATH)//. All widgets in this group have slots in the "tape", those with a builtin event have an extra slot for the event. : //XENTRY(field)// : an entry with **char*** field, packed expanding : //XLENTRY(field, max)// : same, with max length in chars : //TLENTRY(field, max, x, y, length)// : same, packed into //length// columns in a table, starting at column //x//, row //y// : //MLENTRY(field)// : a multiline entry (accepts **Ctrl+Enter** for a newline) with **char*** field : //XPENTRY(field, max)// : an entry for filenames (in system encoding) with **char*** field, with max length in chars, packed expanding : //TPENTRYv(text, var, max)// : an entry for filenames with **char*** variable, with max length, packed into the second column of a table, combined with label that goes into the first column : //PATH(name, fsel_name, mode, field)// : a pathbox (a named frame with entry in system encoding and button calling up file selector) with **char*** field, with name and mode for the fileselector : //PATHv(name, fsel_name, mode, var)// : same, with **char*** variable : //PATHv(name, fsel_name, mode, ini_var_name)// : same, with inifile variable (string) : //TPATHv(name, fsel_name, mode, var)// : a frameless pathbox with **char*** variable, packed into the second column of a table, combined with label that goes into the first column : //uPATHSTR(field)// : a script-only simulated entry for filenames with **char*** field : //TEXT(field)// : a text widget with **char*** field, packed expanding : //COMBOENTRY(field, list_field, OK_handler)// : an entry with dropdown list with **char*** field, with **char**** //list_field// pointing to NULL-terminated **char*[]** array of choices, and a handler for //OK// event, packed expanding : //HEXENTRY(field, CHANGE_handler, x, y)// : an entry for color hex code / name, with **int** field (packed RGB), and a handler for //CHANGE// event, packed into a table at column //x//, row //y// The //XENTRY//, //*LENTRY// and //COMBOENTRY// widgets immediately replace the string values used for initialization with copies owned by the widgets, so that there is no chance of incidental access if the original strings are freed after **run_create()**. The //TEXT// widget just replaces it with NULL, for the same reason. === Buttons === All buttons have a default border area: //BORDER(BUTTON)//. They all have two slots in the "tape": for themselves and for their builtin event. Buttons, except toggle buttons, are not scriptable by default, unless explicitly tagged by the "script flag"; some of macros below are for such buttons. : //OKBTN(name, OK_handler)// : a named button reacting to **Enter** key, with //OK// event handler, packed expanding : //uOKBTN(OK_handler)// : a script-only simulated button with //OK// event handler : //CANCELBTN(name, CANCEL_handler)// : a named button reacting to **Esc** key, with //CANCEL// event handler, packed expanding : //CANCELBTNp(field, CANCEL_handler)// : same, with name in a **char*** field : //UCANCELBTN(name, CANCEL_handler)// : a cancel button packed the default way (//pk_PACK//) : //ECANCELBTN(name, CANCEL_handler)// : same, packed from the end : //UDONEBTN(name, OK_handler)// : a named button reacting to **Enter** and **Esc** keys, with //OK// event handler : //TOGGLE(name, field, CHANGE_handler)// : a named toggle button with an **int** field and //CHANGE// event handler, packed expanding : //UTOGGLEv(name, var, CHANGE_handler)// : same with an **int** variable, packed the default way : //BUTTON(name, CLICK_handler)// : a named button with //CLICK// event handler, packed expanding : //BUTTONs(name, CLICK_handler)// : same, scriptable : //BUTTONp(field, CLICK_handler)// : same, with name in a **char*** field, not scriptable : //UBUTTON(name, CLICK_handler)// : a named button with //CLICK// event handler, packed the default way : //EBUTTON(name, CLICK_handler)// : same, packed from the end : //EBUTTONs(name, CLICK_handler)// : same, scriptable : //TLBUTTON(name, CLICK_handler, x, y)// : a named button with //CLICK// event handler, packed into a table at column //x//, row //y// : //TLBUTTONs(name, CLICK_handler, x, y)// : same, scriptable : //uBUTTONs(name, CLICK_handler)// : a script-only simulated named button with //CLICK// event handler, scriptable (obviously) : //OKBOX(ok_name, OK_handler, cancel_name, CANCEL_handler)// : a convenience macro creating an //EQBOX// with //CANCELBTN// and //OKBTN//; the box is left unclosed (without //WEND//) but no practical reason to add more things into it, and it is normally the last thing in a dialog anyway : //OKBOXP(ok_name, OK_handler, cancel_name, CANCEL_handler)// : same, with a box with padding (//EQBOXP//) : //OKBOXB(ok_name, OK_handler, cancel_name, CANCEL_handler)// : same, with a box with border (//EQBOXB//) : //OKBOX3(ok_name, OK_handler, cancel_name, CANCEL_handler, button_name, CLICK_handler)// : an //EQBOX// with //CANCELBTN//, regular //BUTTON//, and //OKBTN// : //OKBOX3B(ok_name, OK_handler, cancel_name, CANCEL_handler, button_name, CLICK_handler)// : same, with a box with border === Toolbar === Toolbars are containers for their buttons, and need //WEND// to close. //TOOLBAR*// widgets have a default border area: //BORDER(TOOLBAR)//. All toolbars and toolbar buttons have slots in the "tape"; the toolbars have an extra slot or two for their builtin events. Toolbar buttons call their toolbar's event handlers when things happen to them, so that they do not need //EVENT//s of their own. : //TOOLBAR(CHANGE_handler)// : a regular toolbar with //CHANGE// event handler (to which, despite the name, regular toolbuttons getting pressed is routed too) : //TOOLBARx(CHANGE_handler, CLICK_handler)// : same, with also a //CLICK// event handler for right-clicking on a button : //SMARTTBAR(CHANGE_handler)// : a toolbar that, if buttons do not fit, adds a dropdown with the overflow ones; with //CHANGE// event handler : //SMARTTBARx(CHANGE_handler, CLICK_handler)// : same, with also a //CLICK// event handler : //SMARTTBMORE(name)// : a button that shows said dropdown; after it go regular widgets (not toolbuttons) that will stay displayed regardless : //TBBUTTON(name, icon, action)// : a toolbar button with icon, tooltip (also used for scripting), and action code (two numbers packed into one by //ACTMOD()// macro) : //TBBUTTON(name, icon, action, rclick_action)// : same, with another action code for right click : //TBTOGGLE(name, icon, action, field)// : a toolbar toggle button with an **int** field, icon, tooltip, and action code : //TBTOGGLEv(name, icon, action, var)// : same, with an **int** variable : //TBTOGGLExv(name, icon, action, rclick_action, var)// : same, with another action code for right click : //TBBOXTOGxv(name, icon, action, rclick_action, var)// : a regular toggle button that actually goes into the container that the toolbar sits in, but still acts like //TBTOGGLExv// : //TBRBUTTONv(name, icon, action, var)// : a toolbar radiobutton with an **int** variable, icon, tooltip, and action code : //TBRBUTTONxv(name, icon, action, rclick_action, var)// : same, with another action code for right click : //TBSPACE// : a toolbar separator === Menu === Menubars and submenus are containers for menuitems and submenus, and need //WEND// to close. They and menuitems all have slots in the "tape"; the menubars have an extra slot for their builtin event. Menuitems call their menubar's event handler when they activate. Menuitems are not scriptable by default; those that are (tagged by "script flag") have their macros' names ending in "s". : //MENUBAR(CHANGE_handler)// : a regular menubar with //CHANGE// event handler, to which all its menuitems' activations get routed : //SMARTMENU(CHANGE_handler)// : a menubar that, if not wide enough, moves some of regular submenus into an overflow submenu; with //CHANGE// event handler : //SUBMENU(name)// : a submenu : //ESUBMENU(name)// : a submenu placed at the end of menubar : //SSUBMENU(name)// : the overflow submenu : //MENUITEM(name, action)// : a menuitem with action code (//ACTMOD()// made) : //MENUITEMs(name, action)// : same, scriptable : //MENUITEMi(name, action, icon)// : a menuitem with an icon, with action code : //MENUITEMis(name, action, icon)// : same, scriptable : //MENUCHECKv(name, action, var)// : a check menuitem with an **int** variable, with action code : //MENUCHECKvs(name, action, var)// : same, scriptable : //MENURITEMv(name, action, var)// : a radio menuitem with an **int** variable, with action code : //MENURITEMvs(name, action, var)// : same, scriptable : //MENUTEAR// : a tearoff menuitem : //MENUSEP// : a menu separator : //MENUSEPr// : same, with a slot of its own (to hide/show it) : //uMENUBAR(CHANGE_handler)// : a script-only simulated menubar with //CHANGE// event handler : //uMENUITEM(name, action)// : a script-only simulated menuitem; not scriptable, is used for keybindings : //uMENUITEMs(name, action)// : same, scriptable === Control flow === The commands in this group control what other commands get executed after. : //GOTO(array)// : go execute specified V-code array : //CALL(array)// : push current execution address onto return stack, and go execute specified V-code array : //CALLp(field)// : same, with address of the array given in **void**** field : //RET// : pop execution address from return stack, and continue from there : //IF(field)// : if **int** field is 0, skip the next command (caution: do not put before those macros that are internally multiple commands) : //IFv(var)// same, if **int** variable is 0 : //IFx(field, id)// : if **int** field is 0, skip the following commands till an //ENDIF()// with the same id number : //IFx(var, id)// : same, if **int** variable is 0 : //UNLESS(field)// : if **int** field isn't 0, skip the next command : //UNLESSv(field)// : same, if **int** variable isn't 0 : //UNLESSx(field, id)// : if **int** field isn't 0, skip the following commands till an //ENDIF()// with the same id : //UNLESSbt(ini_var_name)// : if inifile variable (boolean) isn't FALSE, skip the next command : //ENDIF(id)// : mark the point for //IFx/UNLESSx// with corresponding id, otherwise do nothing Complex conditions can be implemented in three ways; either the flag variable/field itself can be calculated from the condition, or //IFx/UNLESSx// can be wrapped around another (sharing the same //ENDIF//), or //IF//UNLESS// can precede another conditional to skip it: "//IF(A), IF(B), command//" results in "if (!a || b) command". === Memory allocation and referencing === : //REF(field)// : put address of current slot in the "tape" into **void**** field : //REFv(var)// : same, into **void**** variable : //CLEANUP(field)// : in **run_destroy()** free the memory block pointed to by the **void*** field : //TALLOC(field, length_field)// : in **run_create()** allocate **int** //length_field// bytes (**void*** aligned) below current **dtail** and put their address (new **dtail**) into **void*** field : //TCOPY(field, length_field)// : same but copy whatever the **void*** field was pointing to into the allocated block before overwriting the field with its (now the copy's) address In case //TALLOC()// is used to allocate **double** arrays on a 32-bit architecture, the block will need be allocated with one **double** extra, and properly aligned on **double** afterwards. === Keymap and action map === Action map serves to easily enable/disable or show/hide multiple widgets in response to combinations of various conditions. : //VISMASK(mask)// : choose which bits of action map will control visibility instead of sensitivity (default is none) : //ACTMAP(mask)// : the widget after whose slot in the "tape" this goes, will be sensitive/shown when any bits in the mask are set in the action map, and insensitive/hidden when none are Keymap serves to dynamically map keys to widgets (represented as their slots in the "tape"). The default mappings are set in V-code, and keymap allows to modify them afterwards and saves/restores the modifications using the inifile. : //KEYMAP(field, name)// : add a named keymap, with **void**** field to put the found slot into : //SHORTCUTs(string)// : add a shortcut represented in GTK+ string format, like "F1" : //SHORTCUT(keyval, mods)// : add a shortcut representes as keyname and modifiers, like //SHORTCUT(F2, CS)// for Ctrl+Shift+F2 : //SHORTCUT0// add no shortcut but tell the keymap to allow user to add one (or more) All //SHORTCUT*// commands affect the last widget with a slot in the "tape" before them, as does //ACTMAP// and other "postfix modifier" commands. === Grouping === Group marks serve to direct **cmd_reset()** to a range of widgets all at once, and to differentiate groups of like-named widgets for scripting. They all have a slot in the "tape". : //GROUPR// : begin a resetting-group : //GROUPN// : begin a scripting-group, taking its name from the preceding label : //GROUP(name)// : begin a scripting-group with specified name : //GROUP0// : end the current group === Sizing and placement === : //BORDER(category, num)// : set border/spacing for specified category of widgets to specified number of pixels : //DEFBORDER(category)// : set border/spacing for specified category of widgets to the default (5 pixels) : The categories are: | TABLE | tables | NBOOK | notebooks | SCROLL | scrolledwindows | SPIN | spinbuttons | SPINSLIDE | spinsliders | LABEL | labels | OPT | option menus | BUTTON | buttons | TOOLBAR | toolbars | POPUP | popup windows | TOPVBOX | toplevel vboxes | CHECK | checkbuttons | FRAME | frames | RPACK | radiobutton packs | ENTRY | text entries | LISTCC | simple lists | PATH | pathboxes : //MKSHRINK// : make toplevel window shrinkable (to prevent it becoming larger than screen) : //NORESIZE// : make toplevel window not user-resizable (to make it automatically shrink when widgets get hidden) : //WANTMAX// : tell scrolledwindow that goes //after// it to request full size of its contents : //WANTMAXW// : same, for width only : //WXYWH(prefix, width, height)// make the toplevel save/restore its size and position using inifile variables with specified prefix, and set default width and height for it : //DEFW(width)// : set default width for toplevel : //DEFH(height)// : set default height for toplevel : //DEFSIZE(width, height)// : set default width and height for toplevel : //WPMOUSE// : tell window manager to show the toplevel at the mouse cursor location : //WPWHEREVER// : tell window manager to show the toplevel wherever it chooses : //WIDTH(width)// : set width for the next widget : HEIGHT(height)// : set height for the next widget : //MINWIDTH(width)// : set minimum width for the next widget : //KEEPWIDTH// : make the //preceding// widget to never shrink in width : //KEEPHEIGHT// : same in height : //ONTOP(field)// : make the toplevel sit above the one whose **wdata** is passed in **void**** field (set it as its transient parent) : //ONTOP0// : let the toplevel sit either over or under the main window (which is default transient parent of everything else **run_create()** makes); i.e. unset transient parent By default, **run_create()** tells window manager to show the toplevels in the center of the screen. === Initial state === : //HIDDEN// : make the preceding widget initially hidden : //INSENS// : make it initially insensitive : //FOCUS// : make it initially focused : //RAISED// : initially raise the toplevel above other windows === Cursors === These commands make cursors for later use. They all have slots in the "tape", by which the cursors can later be referred to. : //XBMCURSOR(xbm_name, x, y)// : a 21x21 cursor from two XBM bitmaps (image and mask) specified by the variable part of their filenames ("select" for xbm_select.xbm and xbm_select_mask.xbm, for example), with specified hotspot coordinates : //SYSCURSOR(id)// : a builtin cursor, identified as per GdkCursorType === Events === Event handlers, in V-code, get a slot in the "tape" after the widget they are attached to. There should be only one handler for a given event type per widget. : //EVENT(type, handler)// : add a handler for specified event type to the preceding widget : //TRIGGER// : trigger the preceding event handler after **run_create()** finishes creating widgets (but before the toplevel gets shown); has a slot of its own : //MTRIGGER(handler)// : trigger specified event handler (should be the same as on the menubar) for the preceding menuitem; has a slot of its own and another for the //CHANGE// event : //WANTKEYS(handler)// : install priority //KEY// event handler for when the preceding widget is focused; has a slot of its own and another for the //KEY// event The event types and their causes (widgets denoted by underlying opcodes): : OK : - pressing //OKBTN// or //DONEBTN// - pressing //Enter// in text entry or pathbox's entry - choosing from list or pressing //Enter// in //COMBOENTRY// - double click or pressing //Enter// in a complex list : CANCEL : - pressing //CANCELBTN// - closing a window (call **run_destroy()** if agreeing to it, return without doing that if ignoring the request) : CLICK : - pressing //BUTTON// - right click on a toolbar button : SELECT : - changing selection in radiobutton pack, option menu, list, or //GRADBAR// : CHANGE : - changing value in spinbutton or spinslider - changing value in //TLSPINPACK//, //PCTCOMBO//, //HEXENTRY//, //EYEDROPPER// - changing text in text entry, pathbox, or //TEXT// - changing color in color selector - changing state of //TOGGLE// or checkbutton - pressing a toolbar button - selecting a menuitem - leasing/unleasing the element from //MOUNT// - scrolling a //CSCROLL// - resizing //CANVAS// - changing sort mode in //LISTCX// (should re-sort its map array) : DESTROY : - running **run_destroy()** (after the toplevel is unrealized but before most everything else) : SCRIPT : - setting value in the widget from a script : MULTI : - setting a list of values to the widget from a script - **evtxr_fn** : KEY : - pressing a key when focus is in the widget - **evtxr_fn** : MOUSE and XMOUSE: - pressing a mouse button in the widget - **evtxr_fn** : MMOUSE and MXMOUSE: - moving mouse in the widget - **evtxr_fn** : RMOUSE and RXMOUSE: - releasing a mouse button after pressing it in the widget - **evtxr_fn** : CROSS : - cursor entering or leaving the widget - **evtx_fn** : SCROLL : - rotating mouse scroll wheel in the widget - **evtx_fn** : EXT : - needing to render an area of //CANVAS// - **evtxr_fn** - right click in //LISTCX// - **evtx_fn** - click in //COLORLIST// - **evtx_fn** : DRAGFROM : - dragging from //DRAGDROP//'s widget - **evtxr_fn** : DROP : - dropping to //DRAGDROP//'s widget - **evtx_fn** : COPY : - data request from //CLIPBOARD// - **evtx_fn** : PASTE : - data received by //CLIPBOARD// - **evtxr_fn** Unless otherwise noted, the event handlers are of type **evt_fn**. For those of types **evtx_fn** and **evtxr_fn**, what they get in the extra parameter and what their return value does, is described under "Callbacks" below. The difference between //MOUSE/MMOUSE/RMOUSE// and //XMOUSE/MXMOUSE/RXMOUSE// events is, the latter 3 ask for, and report, tablet pressure. Outside //EVENT()// commands, event types are referred to by their opcodes, which have "//op_EVT_//" prefix; like //op_EVT_OK// for //OK// event. === Clipboard and drag-drop === Clipboard and drag-drop use the same type of data format descriptions. The //CLIPFORM// command prepares those descriptions for them; its slot in the "tape" is then passed to the commands that use the formats. : //CLIPFORM(array, count)// : a group of formats from **clipform_dd[]** array; has a slot in the "tape" : //DRAGDROP(field, DRAGFROM_handler, DROP_handler)// : add handlers for drag and/or drop to the preceding widgets, with formats in **void**** field; has a slot for itself and two more for the events : //DRAGDROPm(field, DRAGFROM_handler, DROP_handler)// : the same but allows the "move" type of drop in addition to "copy" (some programs use the "move" type when they really should not) : //CLIPBOARD(field, which, COPY_handler, PASTE_handler)// : add handlers for copy and paste for specified clipboards (//which//: 1 if the regular clipboard, 2 if the "primary selection" i.e. what is highlighted, 3 if both at once), with formats in **void**** field; has a slot for itself and two more for the events The //DROP// event handler receives in its //xdata// parameter a pointer to **drag_ext**; the //PASTE// event, to **copy_ext** and returns TRUE if successfully imported the data. The //DRAGFROM// and //COPY// events are handled differently from most others; their handlers receive in their //xdata// parameter a slot (dynamically created) with which they then interact to send or receive the data. //DRAGFROM// handler also should return TRUE if it agrees to initiate drag, FALSE otherwise. === Scripting === The below commands exist solely to facilitate scripting. : //ALTNAME(name)// : add another name (identifier) for the preceding widget; has a slot in the "tape" : //FLATTEN// : make option names from the preceding option menu or radiobutton pack be directly referrable from script like widget names (in addition to being referrable as values); has a slot in the "tape" : //OPNAME(name)// : set (override) name for the preceding widget : //OPNAME0// : unset the current name (the initial empty-string one, or the preceding unattached label) to prevent it mis-attaching to the next widget : //UNNAME// : hide the preceding widget from scripts (by setting it an impossible name) : //SCRIPTED// : tell **run_create()** to start a range of "live-scriptable" widgets (those get extra //ALTNAME// slots automatically added, to make them visible from script despite being real widgets) : //ENDSCRIPT// : end a range of "live-scriptable" widgets; has a slot in the "tape" (to tell script interpreter where the range ends) == Functions and macros == There are two main groups of functions; the **run_*()** that affect the entire "tape", and the **cmd_*()** affecting a single slot. Also a few assorted helper functions and macros. : **void **run_create_(void **ifcode, void *ddata, int ddsize, char **script);** build a dialog window out of V-code decription; takes an array with that description, address and size of the struct that will be copied into its **ddata**, and an array of script commands or NULL; returns **wdata** of what got built (possibly already freed, if script was passed in), or NULL if failed : **void **run_create(void **ifcode, void *ddata, int ddsize);** same without script commands (a convenience macro) : **void run_query(void **wdata);** query dialog contents using its "tape"; takes dialog's **wdata** : **void run_destroy(void **wdata);** destroy a dialog; takes dialog's **wdata** : **void cmd_event(void **slot, int op);** raise event (specified by opcode) on slot : **void cmd_sensitive(void **slot, int state);** set sensitive state on slot : **void cmd_showhide(void **slot, int state);** set visible state on slot : **void cmd_set(void **slot, int v);** set value on slot : **int cmd_setstr(void **slot, char *s);** set text-encoded value on slot; returns -1 if failed, 0 if value was left unused (when only the fact of setting something mattered), 1 if set : **void *cmd_read(void **slot, void *ddata);** read back slot value (as is) given **ddata** (to calculate fields' addresses faster); returns the value's storage location : **void cmd_repaint(void **slot);** repaint slot : **void cmd_reset(void **slot, void *ddata);** reset slot or group given **ddata**; causes the widget(s) to reinitialize : **void cmd_cursor(void **slot, void **cursor);** set cursor on slot (for its widget's window) given the cursor's slot : **int cmd_run_script(void **slot, char **strs);** run script on slot; returns -1 if error happened, 0 if //OK// event handler got activated, 1 if not : **void **get_wdata(GtkWidget *widget, char *id);** from widget to its **wdata** : **void **wdata_slot(void **slot);** from slot to its **wdata** : **void **origin_slot(void **slot);** from event to its originator : **void *slot_data(void **slot, void *ddata);** from slot to its storage location (its own, //not// originator's), given **ddata** : **void **find_slot(void **slot, char *id, int l, int mlevel);** find slot by text name (with explicit length) and menu level (//MLEVEL_*//) : **void do_evt_1_d(void **slot);** run event handler in slot, defaulting to run_destroy() if NULL there : **void dialog_event(void *ddata, void **wdata, int what, void **where);** handle dialog buttons' events (from //OKBTN// and //CANCELBTN//) The next three are for those (widget-type-specific) actions that the above functions do not cover. Such actions were not made into functions to avoid the interface ballooning up with functions that are applicable to very few widgets each. Sending requests and receiving responses through a generic interface instead, is convenient enough in practice. : **void cmd_peekv(void **slot, void *res, int size, int idx);** read extra data from slot, given data's index/id, destination, and size : **void cmd_setv(void **slot, void *res, int idx);** set extra data on slot, given data's index/id and source; for some functions, //res// itself is the value (**int** converted to pointer) : **int cmd_checkv(void **slot, int idx);** check extra state on slot, given state's index/id Now, the indices/ids per slot type, what each does, and what type of data expects. Types are denoted by representative opcodes. Most actions are either get or set, only very few are valid for both; the check actions are not valid for either get or set and vice versa, as in totally unrelated. - //FPICK// : : //FPICK_VALUE// get/set the filename with full path, in system encoding: **char[]** : //FPICK_RAW// get/set the raw filename; only the file part without path, in UTF8: **char[]** - //PATH// and //PENTRY// : : //PATH_VALUE// get/set the filename: **char[]** : //PATH_RAW// get/set the raw filename: **char[]** - //LISTCC// : : //LISTCC_RESET_ROW// set the row index to reset: **int** - //LISTC// : : //LISTC_RESET_ROW// set the row index to reset: **int** : //LISTC_ORDER// get sort order: **int[]** : //LISTC_SORT// set sort mode: **int** - //LABEL// and //MENUITEM// : : //LABEL_VALUE// set label text: **char*** - //NBOOK// : : //NBOOK_TABS// set show/hide tabs: **int** - //ENTRY// and //COMBOENTRY// : : //ENTRY_VALUE// set value: **char*** - //TEXT// : : //TEXT_VALUE// set value: **char*** - **wdata** : : //WDATA_ACTMAP// set action map: **unsigned int** : //WDATA_TABLET// get name of tablet device: **char**** - //WINDOW// : : //WINDOW_TITLE// set window title: **char*** : //WINDOW_ESC_BTN// set slot to react (get "clicked") to //Esc// keypress in window: **void**** : //WINDOW_FOCUS// set focus to slot (or to nowhere if NULL): **void**** : //WINDOW_RAISE// set window to raise; value is ignored: **void** : //WINDOW_DISAPPEAR// set window to hide from view (to allow a screenshot), or to unhide: **int** : //WINDOW_DPI// get DPI: **int*** : //WINDOW_TEXTENG// set to render text: **texteng_dd*** - //COLOR// : : //COLOR_RGBA// set current color and alpha: **int[2]** : //COLOR_ALL// set current and previous color and alpha: **int[4]** - //SPIN// and //SPINSLIDE// : : //SPIN_ALL// set value, min and max: **int[3]** - //COLORLIST// : : //COLORLIST_RESET_ROW// set the row index to reset: **int** - //PROGRESS// : : //PROGRESS_PERCENT// set progress percentage: **int** - //CSCROLL// : : //CSCROLL_XYSIZE// get x offset, y offset, onscreen width, onscreen height: **int[4]** : //CSCROLL_LIMITS// get full width minus onscreen width, and full height minus onscreen height: **int[2]** : //CSCROLL_XYRANGE// set x offset, y offset, full width, and full height, for a delayed resize: **int[4]** - //CANVASIMG//: : CANVAS_SIZE set full size: **int[2]** - //CANVAS// : : CANVAS_SIZE get onscreen size / set full size: **int[2]** : CANVAS_VPORT get viewport (x, y, x + width, y + height): **int[4]** : CANVAS_REPAINT set area that needs repaint (x, y, x + width, y + height): **int[4]** : CANVAS_PAINT set area you want to repaint (//rgb// = NULL) - bounds will be modified and buffer allocated / set image data (//rgb// != NULL) - buffer will be painted into the bounded area: **rgbcontext*** : CANVAS_FIND_MOUSE get mouse state: **mouse_ext*** : CANVAS_BMOVE_MOUSE set to nudge mouse cursor by dx, dy: **int[2]** - //FCIMAGE// : : //FCIMAGE_XY// set location marker to x, y: **int[2]** - //KEYMAP// : : //KEYMAP_KEY// set key to map to slot: **key_ext*** : //KEYMAP_MAP// get/set keymap: **keymap_dd*** - //EV_MOUSE// : : //MOUSE_BOUND// check to keep cursor inside canvas' visible area, by scrolling the canvas & moving the cursor back in; return TRUE if moved it - //EV_DRAGFROM// : : //DRAG_DATA// set start & end of data exported through drag: **char*[2]** : //DRAG_ICON_RGB// set RGB color (packed) for drag icon (color patch): **int** - //EV_COPY// : : //COPY_DATA// set start & end of data exported through copying: **char*[2]** - //CLIPBOARD// : : //CLIP_TEXT// set text to export through clipboard: **char*** : //CLIP_OFFER// check to offer data through clipboard; return TRUE if successful : //CLIP_PROCESS// check to request data from clipboard; return TRUE if successful - all regular widgets : : //SLOT_SENSITIVE// check sensitive state; return TRUE if sensitive : //SLOT_FOCUSED// check focused state; return TRUE if focus in toplevel window is inside the widget : //SLOT_SCRIPTABLE// check scriptability; return TRUE if script flag is set in opcode : //SLOT_UNREAL// check unreality; return TRUE if the widget is simulated : //SLOT_RADIO// check radio-ness; return TRUE if the widget is radio toolbutton or radio menuitem - //FONTSEL// : : //FONTSEL_DPI// set DPI: **int** Finally, the macros. : //VSLOT_SIZE// : size of a slot, in void pointers: 3 : //GET_DDATA(wdata)// : from **wdata** to **ddata**: **void*** : //GET_WINDOW(wdata)// : from **wdata** to its toplevel widget's slot: **void**** : //GET_REAL_WINDOW(wdata)// : from **wdata** to actual toplevel widget: **void***, in fact **GtkWidget*** : //NEXT_SLOT(slot)// : from slot to the next one: **void**** : //PREV_SLOT(slot)// : from slot to the previous one: **void**** : //SLOT_N(slot, n)// : from slot to the n-th one after: **void**** : //TOOL_ID(slot)// : get toolbutton's action code: **int** : //TOOL_IR(slot)// : get toolbutton's right-click action code: **int** : //ACTMOD(action, mode)// : combine action and mode into action code == Callbacks == Most events are handled by **evt_fn** function (the default one): **void (*evt_fn)(void *ddata, void **wdata, int what, void **where);** The //ddata// and //wdata// parameters are precisely that; the backing struct, and the "tape". The //what// parameter is the event's opcode, and the //where// is its slot. One handler for all (or most) events happening in the dialog, is the usual pattern when using V-code. When inside it need do different things depending on what is happening to what, there are 3 ways to make the decision. One is to use the "//what//" parameter; it is mostly done to separate out final button presses (the //op_EVT_OK// and //op_EVT_CANCEL//) from everything else. Another is to do "**void *cause = cmd_read(where, ddata);**" and compare the "//cause//" to addresses of variables and/or fields that V-code commands refer to. This serves for most types of controls, and with them **cmd_read()** is usually needed anyway, to get the newly-modified value (a few are "self-reading" but that is rarely significant; only affects when to save the previous value if you need it). The third is to do "**void **slot = origin_slot(where);**" and compare the slot with controls' slots stored using //REF()// commands. This in practice is only needed when several //BUTTON()//s are in one dialog; with them all having //CLICK// events and not referring to any memory locations, their slots are the only thing differentiating them. Now, some events need send some extra data to their handler. Those are handled by **evtx_fn** function: **void (*evtx_fn)(void *ddata, void **wdata, int what, void **where, void *xdata);** What, specifically, the handler receives in //xdata//, depends on the event. : //CROSS// : TRUE if entering, FALSE if leaving: **int** converted to pointer : //SCROLL// : pointer to **scroll_ext** : //EXT// from //LISTCX// : row index where right click happened: **int** converted to pointer : //EXT// from //COLORLIST//: pointer to **colorlist_ext** : //DROP// : pointer to **drag_ext** : //COPY// : pointer to **copy_ext** Yet other events not only send extra data to the handler, but also await a return value from it. The value, for nearly all, is simply TRUE or FALSE, and the function handling them is **evtxr_fn**: **int (*evtxr_fn)(void *ddata, void **wdata, int what, void **where, void *xdata);** What the handler receives in //xdata//, and what its return value means, again depends on the event. : //KEY// : pointer to **key_ext**; return TRUE if the key was handled, FALSE otherwise : //MOUSE//, //XMOUSE//, //MMOUSE//, //MXMOUSE//, //RMOUSE//, //RXMOUSE//: pointer to **mouse_ext**; return TRUE if the mouse event was handled : //EXT// from //CANVAS//: pointer to **rgbcontext**; return TRUE if something was rendered into buffer : //DRAGFROM// : pointer to **drag_ext**; return TRUE to initiate drag : //PASTE// : pointer to **copy_ext**; return TRUE if data successfully imported : //MULTI// : pointer to **multi_ext**; return 0 if error, 1 if success and the //xdata// not needed anymore, -1 if success and the struct should be kept (not freed) Handling some events can include system-dependent interactions with their //xdata//. Instead of saddling some poor unsuspecting widget with those, an "intercessor" capable of processing that is passed as the originating event, in the "//where//" parameter. The intercessors' opcodes are //op_EV_*// to their events' //op_EVT_*//; their functions are described under the headings //EV_MOUSE//, //EV_DRAGFROM//, and //EV_COPY// in the "Functions and macros" chapter above. They are accessed like this: "**cmd_setv(where, pp, DRAG_DATA);**". == THE END ==