Add TabletButtons demo
parent
09b52b174f
commit
6a5710aeae
@ -0,0 +1,139 @@
|
||||
/*
|
||||
Script to build INI file with database of button names / coordinates for TabletButtons.ahk to read
|
||||
For use with an Absolute Mode pointing device (eg a Wacom graphics tablet)
|
||||
|
||||
Usage:
|
||||
1) Find the VID/PID of your tablet using the Monitor.ahk demo and paste it where indicated below
|
||||
2) Mark up your tablet device with some buttons (eg tape a piece of paper to it, draw some boxes on it with names)
|
||||
3) Run this script
|
||||
4) Click "Create Box", give it a name, draw a line from top left to bottom right of one of the boxes
|
||||
5) Repeat (4) as needed
|
||||
6) Click "Save" at the bottom of the GUI to savce button coordinates to the INI file
|
||||
7) Close the GUI of this script, edit TabletButtons.ahk as appropriate and run it
|
||||
*/
|
||||
#SingleInstance force
|
||||
#include Lib\AutoHotInterception.ahk
|
||||
#include TabletLib\TabletLib.ahk
|
||||
|
||||
; Initialize AHI
|
||||
AHI := new AutoHotInterception()
|
||||
mouseId := AHI.GetMouseId(0x0B57, 0x9091) ; Get VID/PID of your device from the Monitor app and paste it in here
|
||||
AHI.SubscribeMouseButton(mouseId, 0, true, Func("ButtonEvent"))
|
||||
AHI.SubscribeMouseMoveAbsolute(mouseId, true, Func("OnMouseMove"))
|
||||
AHI.SetState(false)
|
||||
|
||||
; Build GUI
|
||||
guiW := 400
|
||||
Menu, BoxMenu, Add, Delete, DeleteBoxClicked
|
||||
Menu, BoxMenu, Add, Edit, EditBoxClicked
|
||||
Gui, Add, Button, % "w" guiW " Center gCreateBox", Create Box
|
||||
Gui, Add, Text, % "w" guiW " Center gCreateBox", Defined Boxes (Right Click to edit)
|
||||
Gui, Add, ListView, % "w" guiW " h400", Name|Start X|Start Y|End X|End Y
|
||||
LV_ModifyCol(1, 200)
|
||||
LV_ModifyCol(2, 45)
|
||||
LV_ModifyCol(3, 45)
|
||||
LV_ModifyCol(4, 45)
|
||||
LV_ModifyCol(5, 45)
|
||||
Gui, Add, Button, % "w" guiW " Center gSave", Save to disk
|
||||
Gui, Show, , Tablet button builder tool
|
||||
|
||||
boxArr := {}
|
||||
|
||||
; Load data from existing INI file
|
||||
bA := LoadBoxes("TabletButtons.ini")
|
||||
for name, box in bA {
|
||||
AddBox(box)
|
||||
}
|
||||
return
|
||||
|
||||
ButtonEvent(state){
|
||||
global boxMode, newBox
|
||||
if (state && boxMode == 1){
|
||||
boxMode := 2 ; Waiting for release
|
||||
} else if (!state && boxMode == 2){
|
||||
GoSub, EndBox
|
||||
}
|
||||
}
|
||||
|
||||
OnMouseMove(x, y){
|
||||
global boxMode, newBox
|
||||
if (boxMode == 1){
|
||||
newBox.StartX := x
|
||||
newBox.StartY := y
|
||||
} else if (boxMode == 2) {
|
||||
newBox.EndX := x
|
||||
newBox.EndY := y
|
||||
}
|
||||
}
|
||||
|
||||
CreateBox:
|
||||
InputBox, boxName, Create Box, Enter box name, , , 120
|
||||
if (ERRORLEVEL = 0){
|
||||
if (boxArr.HasKey(boxName)){
|
||||
msgbox % "Box " boxName " already exists"
|
||||
} else {
|
||||
StartCreateBox(new Box(boxName))
|
||||
}
|
||||
}
|
||||
return
|
||||
|
||||
StartCreateBox(box){
|
||||
global AHI, boxMode, newBox
|
||||
boxMode := 1 ; waiting for press
|
||||
AHI.SetState(true)
|
||||
newBox := box
|
||||
}
|
||||
|
||||
EndBox:
|
||||
AHI.SetState(false)
|
||||
AddBox(newBox)
|
||||
boxMode := 0
|
||||
return
|
||||
|
||||
GuiContextMenu: ; This is automatically called when the GUI is rightclicked
|
||||
If (A_EventInfo) { ; This will be the row number selected when right-clicked, if right clicking outside the list view will be 0 (False)
|
||||
rightClickedRow := A_EventInfo ; Save the selected row
|
||||
Menu, BoxMenu, Show ; Show the Rightclick menu
|
||||
}
|
||||
Return
|
||||
|
||||
DeleteBoxClicked(){
|
||||
global rightClickedRow
|
||||
DeleteBox(rightClickedRow)
|
||||
}
|
||||
|
||||
EditBoxClicked(){
|
||||
global rightClickedRow, boxArr
|
||||
newBox := boxArr[GetBoxName(rightClickedRow)].Clone()
|
||||
DeleteBox(rightClickedRow)
|
||||
StartCreateBox(newBox)
|
||||
}
|
||||
|
||||
Save(){
|
||||
global JSON, boxArr
|
||||
FileDelete, TabletButtons.ini
|
||||
FileAppend, % JSON.Dump(boxArr, ,true), TabletButtons.ini
|
||||
}
|
||||
|
||||
GuiClose:
|
||||
ExitApp
|
||||
|
||||
AddBox(box){
|
||||
global boxArr
|
||||
; ToDo: Validate if new box overlaps with any existing boxes
|
||||
LV_Add(, box.BoxName, box.StartX, box.StartY, box.EndX, box.EndY)
|
||||
boxArr[box.BoxName] := box.Clone()
|
||||
LV_ModifyCol(1, "Sort")
|
||||
}
|
||||
|
||||
DeleteBox(rowNum){
|
||||
global boxArr
|
||||
boxName := GetBoxName(rowNum)
|
||||
LV_Delete(rowNum)
|
||||
boxArr.Delete(boxName)
|
||||
}
|
||||
|
||||
GetBoxName(rowNum){
|
||||
LV_GetText(boxName, rowNum, 1)
|
||||
return boxName
|
||||
}
|
@ -0,0 +1,45 @@
|
||||
/*
|
||||
For use with an Absolute Mode pointing device (eg a Wacom graphics tablet)
|
||||
Turns a Absolute Mode device into a button box
|
||||
|
||||
To Use:
|
||||
1) Use the TabletButtonBuilder.ahk script to build a TabletButtons.ini file containing names and coordinates for your buttons
|
||||
2) Find the VID/PID of your tablet using the Monitor.ahk demo and paste it where indicated below
|
||||
3) Run this script
|
||||
*/
|
||||
|
||||
#SingleInstance force
|
||||
#Persistent ; If you have no hotkeys or GUI in the script, you need this else the script will instantly exit
|
||||
#include Lib\AutoHotInterception.ahk
|
||||
#include TabletLib\TabletLib.ahk
|
||||
|
||||
; Initialize AHI
|
||||
AHI := new AutoHotInterception()
|
||||
mouseId := AHI.GetMouseId(0x0B57, 0x9091) ; Get VID/PID of your device from the Monitor app and paste it in here
|
||||
AHI.SubscribeMouseButton(mouseId, 0, true, Func("ButtonEvent"))
|
||||
AHI.SubscribeMouseMoveAbsolute(mouseId, true, Func("OnMouseMove"))
|
||||
|
||||
boxArr := LoadBoxes("TabletButtons.ini")
|
||||
return
|
||||
|
||||
ButtonEvent(state){
|
||||
global lastX, lastY, boxArr
|
||||
if (state){ ; On button press...
|
||||
; Find name of box that was clicked (If any)
|
||||
name := FindBoxName(lastX, lastY, boxArr)
|
||||
if (name != ""){
|
||||
; Your code here to decide what action to take depending on which box was selected
|
||||
Tooltip % "You selected box " name
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
; Store coordinates on move, so they can be used in ButtonEvent
|
||||
OnMouseMove(x, y){
|
||||
global lastX, lastY
|
||||
lastX := x
|
||||
lastY := y
|
||||
}
|
||||
|
||||
^Esc::
|
||||
ExitApp
|
@ -0,0 +1,361 @@
|
||||
/**
|
||||
* Lib: JSON.ahk
|
||||
* JSON lib for AutoHotkey.
|
||||
* Version:
|
||||
* v2.1.0 [updated 01/28/2016 (MM/DD/YYYY)]
|
||||
* License:
|
||||
* WTFPL [http://wtfpl.net/]
|
||||
* Requirements:
|
||||
* Latest version of AutoHotkey (v1.1+ or v2.0-a+)
|
||||
* Installation:
|
||||
* Use #Include JSON.ahk or copy into a function library folder and then
|
||||
* use #Include <JSON>
|
||||
* Links:
|
||||
* GitHub: - https://github.com/cocobelgica/AutoHotkey-JSON
|
||||
* Forum Topic - http://goo.gl/r0zI8t
|
||||
* Email: - cocobelgica <at> gmail <dot> com
|
||||
*/
|
||||
|
||||
|
||||
/**
|
||||
* Class: JSON
|
||||
* The JSON object contains methods for parsing JSON and converting values
|
||||
* to JSON. Callable - NO; Instantiable - YES; Subclassable - YES;
|
||||
* Nestable(via #Include) - NO.
|
||||
* Methods:
|
||||
* Load() - see relevant documentation before method definition header
|
||||
* Dump() - see relevant documentation before method definition header
|
||||
*/
|
||||
class JSON
|
||||
{
|
||||
/**
|
||||
* Method: Load
|
||||
* Parses a JSON string into an AHK value
|
||||
* Syntax:
|
||||
* value := JSON.Load( text [, reviver ] )
|
||||
* Parameter(s):
|
||||
* value [retval] - parsed value
|
||||
* text [in, opt] - JSON formatted string
|
||||
* reviver [in, opt] - function object, similar to JavaScript's
|
||||
* JSON.parse() 'reviver' parameter
|
||||
*/
|
||||
class Load extends JSON.Functor
|
||||
{
|
||||
Call(self, text, reviver:="")
|
||||
{
|
||||
this.rev := IsObject(reviver) ? reviver : false
|
||||
this.keys := this.rev ? {} : false
|
||||
|
||||
static q := Chr(34)
|
||||
, json_value := q . "{[01234567890-tfn"
|
||||
, json_value_or_array_closing := q . "{[]01234567890-tfn"
|
||||
, object_key_or_object_closing := q . "}"
|
||||
|
||||
key := ""
|
||||
is_key := false
|
||||
root := {}
|
||||
stack := [root]
|
||||
next := json_value
|
||||
pos := 0
|
||||
|
||||
while ((ch := SubStr(text, ++pos, 1)) != "") {
|
||||
if InStr(" `t`r`n", ch)
|
||||
continue
|
||||
if !InStr(next, ch, 1)
|
||||
this.ParseError(next, text, pos)
|
||||
|
||||
holder := stack[1]
|
||||
is_array := holder.IsArray
|
||||
|
||||
if InStr(",:", ch) {
|
||||
next := (is_key := !is_array && ch == ",") ? q : json_value
|
||||
|
||||
} else if InStr("}]", ch) {
|
||||
ObjRemoveAt(stack, 1)
|
||||
next := stack[1]==root ? "" : stack[1].IsArray ? ",]" : ",}"
|
||||
|
||||
} else {
|
||||
if InStr("{[", ch) {
|
||||
; Check if Array() is overridden and if its return value has
|
||||
; the 'IsArray' property. If so, Array() will be called normally,
|
||||
; otherwise, use a custom base object for arrays
|
||||
static json_array := Func("Array").IsBuiltIn || ![].IsArray ? {IsArray: true} : 0
|
||||
|
||||
; sacrifice readability for minor(actually negligible) performance gain
|
||||
(ch == "{")
|
||||
? ( is_key := true
|
||||
, value := {}
|
||||
, next := object_key_or_object_closing )
|
||||
; ch == "["
|
||||
: ( value := json_array ? new json_array : []
|
||||
, next := json_value_or_array_closing )
|
||||
|
||||
ObjInsertAt(stack, 1, value)
|
||||
|
||||
if (this.keys)
|
||||
this.keys[value] := []
|
||||
|
||||
} else {
|
||||
if (ch == q) {
|
||||
i := pos
|
||||
while (i := InStr(text, q,, i+1)) {
|
||||
value := StrReplace(SubStr(text, pos+1, i-pos-1), "\\", "\u005c")
|
||||
|
||||
static ss_end := A_AhkVersion<"2" ? 0 : -1
|
||||
if (SubStr(value, ss_end) != "\")
|
||||
break
|
||||
}
|
||||
|
||||
if (!i)
|
||||
this.ParseError("'", text, pos)
|
||||
|
||||
value := StrReplace(value, "\/", "/")
|
||||
, value := StrReplace(value, "\" . q, q)
|
||||
, value := StrReplace(value, "\b", "`b")
|
||||
, value := StrReplace(value, "\f", "`f")
|
||||
, value := StrReplace(value, "\n", "`n")
|
||||
, value := StrReplace(value, "\r", "`r")
|
||||
, value := StrReplace(value, "\t", "`t")
|
||||
|
||||
pos := i ; update pos
|
||||
|
||||
i := 0
|
||||
while (i := InStr(value, "\",, i+1)) {
|
||||
if !(SubStr(value, i+1, 1) == "u")
|
||||
this.ParseError("\", text, pos - StrLen(SubStr(value, i+1)))
|
||||
|
||||
uffff := Abs("0x" . SubStr(value, i+2, 4))
|
||||
if (A_IsUnicode || uffff < 0x100)
|
||||
value := SubStr(value, 1, i-1) . Chr(uffff) . SubStr(value, i+6)
|
||||
}
|
||||
|
||||
if (is_key) {
|
||||
key := value, next := ":"
|
||||
continue
|
||||
}
|
||||
|
||||
} else {
|
||||
value := SubStr(text, pos, i := RegExMatch(text, "[\]\},\s]|$",, pos)-pos)
|
||||
|
||||
static number := "number"
|
||||
if value is %number%
|
||||
value += 0
|
||||
else if (value == "true" || value == "false")
|
||||
value := %value% + 0
|
||||
else if (value == "null")
|
||||
value := ""
|
||||
else
|
||||
; we can do more here to pinpoint the actual culprit
|
||||
; but that's just too much extra work.
|
||||
this.ParseError(next, text, pos, i)
|
||||
|
||||
pos += i-1
|
||||
}
|
||||
|
||||
next := holder==root ? "" : is_array ? ",]" : ",}"
|
||||
} ; If InStr("{[", ch) { ... } else
|
||||
|
||||
is_array? key := ObjPush(holder, value) : holder[key] := value
|
||||
|
||||
if (this.keys && this.keys.HasKey(holder))
|
||||
this.keys[holder].Push(key)
|
||||
}
|
||||
|
||||
} ; while ( ... )
|
||||
|
||||
return this.rev ? this.Walk(root, "") : root[""]
|
||||
}
|
||||
|
||||
ParseError(expect, text, pos, len:=1)
|
||||
{
|
||||
static q := Chr(34)
|
||||
|
||||
line := StrSplit(SubStr(text, 1, pos), "`n", "`r").Length()
|
||||
col := pos - InStr(text, "`n",, -(StrLen(text)-pos+1))
|
||||
msg := Format("{1}`n`nLine:`t{2}`nCol:`t{3}`nChar:`t{4}"
|
||||
, (expect == "") ? "Extra data"
|
||||
: (expect == "'") ? "Unterminated string starting at"
|
||||
: (expect == "\") ? "Invalid \escape"
|
||||
: (expect == ":") ? "Expecting ':' delimiter"
|
||||
: (expect == q) ? "Expecting object key enclosed in double quotes"
|
||||
: (expect == q . "}") ? "Expecting object key enclosed in double quotes or object closing '}'"
|
||||
: (expect == ",}") ? "Expecting ',' delimiter or object closing '}'"
|
||||
: (expect == ",]") ? "Expecting ',' delimiter or array closing ']'"
|
||||
: InStr(expect, "]") ? "Expecting JSON value or array closing ']'"
|
||||
: "Expecting JSON value(string, number, true, false, null, object or array)"
|
||||
, line, col, pos)
|
||||
|
||||
static offset := A_AhkVersion<"2" ? -3 : -4
|
||||
throw Exception(msg, offset, SubStr(text, pos, len))
|
||||
}
|
||||
|
||||
Walk(holder, key)
|
||||
{
|
||||
value := holder[key]
|
||||
if IsObject(value)
|
||||
for i, k in this.keys[value]
|
||||
value[k] := this.Walk.Call(this, value, k) ; bypass __Call
|
||||
|
||||
return this.rev.Call(holder, key, value)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Method: Dump
|
||||
* Converts an AHK value into a JSON string
|
||||
* Syntax:
|
||||
* str := JSON.Dump( value [, replacer, space ] )
|
||||
* Parameter(s):
|
||||
* str [retval] - JSON representation of an AHK value
|
||||
* value [in] - any value(object, string, number)
|
||||
* replacer [in, opt] - function object, similar to JavaScript's
|
||||
* JSON.stringify() 'replacer' parameter
|
||||
* space [in, opt] - similar to JavaScript's JSON.stringify()
|
||||
* 'space' parameter
|
||||
*/
|
||||
class Dump extends JSON.Functor
|
||||
{
|
||||
Call(self, value, replacer:="", space:="")
|
||||
{
|
||||
this.rep := IsObject(replacer) ? replacer : ""
|
||||
|
||||
this.gap := ""
|
||||
if (space) {
|
||||
static integer := "integer"
|
||||
if space is %integer%
|
||||
Loop, % ((n := Abs(space))>10 ? 10 : n)
|
||||
this.gap .= " "
|
||||
else
|
||||
this.gap := SubStr(space, 1, 10)
|
||||
|
||||
this.indent := "`n"
|
||||
}
|
||||
|
||||
return this.Str({"": value}, "")
|
||||
}
|
||||
|
||||
Str(holder, key)
|
||||
{
|
||||
value := holder[key]
|
||||
|
||||
if (this.rep)
|
||||
value := this.rep.Call(holder, key, value)
|
||||
|
||||
if IsObject(value) {
|
||||
; Check object type, skip serialization for other object types such as
|
||||
; ComObject, Func, BoundFunc, FileObject, RegExMatchObject, Property, etc.
|
||||
static type := A_AhkVersion<"2" ? "" : Func("Type")
|
||||
if (type ? type.Call(value) == "Object" : ObjGetCapacity(value) != "") {
|
||||
if (this.gap) {
|
||||
stepback := this.indent
|
||||
this.indent .= this.gap
|
||||
}
|
||||
|
||||
is_array := value.IsArray
|
||||
; Array() is not overridden, rollback to old method of
|
||||
; identifying array-like objects. Due to the use of a for-loop
|
||||
; sparse arrays such as '[1,,3]' are detected as objects({}).
|
||||
if (!is_array) {
|
||||
for i in value
|
||||
is_array := i == A_Index
|
||||
until !is_array
|
||||
}
|
||||
|
||||
str := ""
|
||||
if (is_array) {
|
||||
Loop, % value.Length() {
|
||||
if (this.gap)
|
||||
str .= this.indent
|
||||
|
||||
v := this.Str(value, A_Index)
|
||||
str .= (v != "") && value.HasKey(A_Index) ? v . "," : "null,"
|
||||
}
|
||||
} else {
|
||||
colon := this.gap ? ": " : ":"
|
||||
for k in value {
|
||||
v := this.Str(value, k)
|
||||
if (v != "") {
|
||||
if (this.gap)
|
||||
str .= this.indent
|
||||
|
||||
str .= this.Quote(k) . colon . v . ","
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (str != "") {
|
||||
str := RTrim(str, ",")
|
||||
if (this.gap)
|
||||
str .= stepback
|
||||
}
|
||||
|
||||
if (this.gap)
|
||||
this.indent := stepback
|
||||
|
||||
return is_array ? "[" . str . "]" : "{" . str . "}"
|
||||
}
|
||||
|
||||
} else ; is_number ? value : "value"
|
||||
return ObjGetCapacity([value], 1)=="" ? value : this.Quote(value)
|
||||
}
|
||||
|
||||
Quote(string)
|
||||
{
|
||||
static q := Chr(34)
|
||||
|
||||
if (string != "") {
|
||||
string := StrReplace(string, "\", "\\")
|
||||
; , string := StrReplace(string, "/", "\/") ; optional in ECMAScript
|
||||
, string := StrReplace(string, q, "\" . q)
|
||||
, string := StrReplace(string, "`b", "\b")
|
||||
, string := StrReplace(string, "`f", "\f")
|
||||
, string := StrReplace(string, "`n", "\n")
|
||||
, string := StrReplace(string, "`r", "\r")
|
||||
, string := StrReplace(string, "`t", "\t")
|
||||
|
||||
static rx_escapable := A_AhkVersion<"2" ? "O)[^\x20-\x7e]" : "[^\x20-\x7e]"
|
||||
while RegExMatch(string, rx_escapable, m)
|
||||
string := StrReplace(string, m.Value, Format("\u{1:04x}", Ord(m.Value)))
|
||||
}
|
||||
|
||||
return q . string . q
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Property: Undefined
|
||||
* Proxy for 'undefined' type
|
||||
* Syntax:
|
||||
* undefined := JSON.Undefined
|
||||
* Remarks:
|
||||
* For use with reviver and replacer functions since AutoHotkey does not
|
||||
* have an 'undefined' type. Returning blank("") or 0 won't work since these
|
||||
* can't be distnguished from actual JSON values. This leaves us with objects.
|
||||
* The caller may return a non-serializable AHK objects such as ComObject,
|
||||
* Func, BoundFunc, FileObject, RegExMatchObject, and Property to mimic the
|
||||
* behavior of returning 'undefined' in JavaScript but for the sake of code
|
||||
* readability and convenience, it's better to do 'return JSON.Undefined'.
|
||||
* Internally, the property returns a ComObject with the variant type of VT_EMPTY.
|
||||
*/
|
||||
Undefined[]
|
||||
{
|
||||
get {
|
||||
static empty := {}, vt_empty := ComObject(0, &empty, 1)
|
||||
return vt_empty
|
||||
}
|
||||
}
|
||||
|
||||
class Functor
|
||||
{
|
||||
__Call(method, args*)
|
||||
{
|
||||
; When casting to Call(), use a new instance of the "function object"
|
||||
; so as to avoid directly storing the properties(used across sub-methods)
|
||||
; into the "function object" itself.
|
||||
if IsObject(method)
|
||||
return (new this).Call(method, args*)
|
||||
else if (method == "")
|
||||
return (new this).Call(args*)
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,42 @@
|
||||
/*
|
||||
Common functions for Tablet Library
|
||||
*/
|
||||
#include %A_LineFile%\..\JSON.ahk ; JSON library is used to save / load data as it is easier than using INIRead / INIWrite
|
||||
|
||||
LoadBoxes(filename){
|
||||
global JSON
|
||||
boxes := {}
|
||||
FileRead, j, % filename
|
||||
if (ERRORLEVEL == 0){
|
||||
j := JSON.Load(j)
|
||||
for name, b in j {
|
||||
box := new Box(name)
|
||||
box.StartX := b.StartX
|
||||
box.StartY := b.StartY
|
||||
box.EndX := b.EndX
|
||||
box.EndY := b.EndY
|
||||
boxes[name] := box
|
||||
}
|
||||
}
|
||||
return boxes
|
||||
}
|
||||
|
||||
FindBoxName(x, y, boxes){
|
||||
for name, box in boxes {
|
||||
if (x >= box.StartX && x <= box.EndX && y >= box.StartY && y <= box.EndY){
|
||||
return name
|
||||
}
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
Class Box {
|
||||
StartX := 0
|
||||
StartY := 0
|
||||
EndX := 0
|
||||
EndY := 0
|
||||
|
||||
__New(name){
|
||||
this.BoxName := name
|
||||
}
|
||||
}
|
Loading…
Reference in New Issue