Tinderbox Forum

Feature suggestion: add "quick note color" to the context menu

Just as we have “Shapes” in the right-click menu that shows up for each note, please consider adding a “quick selection” of colors as well.

E.g. just have the same colors that are handy shortcuts for text colors right now (Red, Blue, Gray, Yellow, Black).

In the meanwhile, one option to experiment with might be a Keyboard Maestro macro:

Pick color for selected Tinderbox 8 notes.kmmacros.zip (11.0 KB)

Which I find I can also run (tho fractionally more slowly) as a stamp from the Tinderbox 8 QuickStamp inspector.

( Paste all of the source below into a new stamp, right down to

})();
JXA_END")

and give the stamp some name like Color Picker ).

runCommand version for Tinderbox 8 stamp, as above
runCommand("osascript -l JavaScript <<JXA_END 2>/dev/null
(() => {
    'use strict';

    // Pick color for selected notes in Tinderbox 8
    // Rob Trew 2020
    // Ver 0.01

    const main = () => {
        const docs = Application('Tinderbox 8').documents;
        return either(
            alert('Color of selected note(s)')
        )(
            x => x
        )(
            bindLR(
                0 < docs.length ? (
                    Right(docs.at(0))
                ) : Left('No documents open in Tinderbox 8.')
            )(
                doc => bindLR(
                    0 < doc.selections.length ? (
                        Right(doc.selections())
                    ) : Left('Nothing selected in ' + doc.name())
                )(notes => {
                    try {
                        const
                            hexColor = '#' + Object.assign(
                                Application.currentApplication(), {
                                    includeStandardAdditions: true
                                })
                            .chooseColor({
                                defaultColor: chunksOf(2)(
                                        notes[0].color().slice(1)
                                    )
                                    .map(s => parseInt(s, 16) / 255)
                            })
                            .map(
                                x => Math.round(255 * x)
                                .toString(16).padStart(2, '0')
                            ).join('');
                        return (
                            notes.forEach(
                                x => x.attributes.byName('Color')
                                .value = hexColor
                            ),
                            Right(hexColor)
                        );
                    } catch (e) {
                        return Left(e.message)
                    }
                })
            )
        );
    };

    // ------------------------JXA-------------------------

    // alert :: String -> String -> IO String
    const alert = title => s => {
        const
            sa = Object.assign(Application('System Events'), {
                includeStandardAdditions: true
            });
        return (
            sa.activate(),
            sa.displayDialog(s, {
                withTitle: title,
                buttons: ['OK'],
                defaultButton: 'OK'
            }),
            s
        );
    };

    // -----------------GENERIC FUNCTIONS------------------
    // https://github.com/RobTrew/prelude-jxa

    // Left :: a -> Either a b
    const Left = x => ({
        type: 'Either',
        Left: x
    });

    // Right :: b -> Either a b
    const Right = x => ({
        type: 'Either',
        Right: x
    });

    // bindLR (>>=) :: Either a ->
    // (a -> Either b) -> Either b
    const bindLR = m =>
        mf => undefined !== m.Left ? (
            m
        ) : mf(m.Right);

    // chunksOf :: Int -> [a] -> [[a]]
    const chunksOf = n =>
        xs => enumFromThenTo(0)(n)(
            xs.length - 1
        ).reduce(
            (a, i) => a.concat([xs.slice(i, (n + i))]),
            []
        );

    // either :: (a -> c) -> (b -> c) -> Either a b -> c
    const either = fl =>
        fr => e => 'Either' === e.type ? (
            undefined !== e.Left ? (
                fl(e.Left)
            ) : fr(e.Right)
        ) : undefined;

    // enumFromThenTo :: Int -> Int -> Int -> [Int]
    const enumFromThenTo = x1 =>
        x2 => y => {
            const d = x2 - x1;
            return Array.from({
                length: Math.floor(y - x2) / d + 2
            }, (_, i) => x1 + (d * i));
        };

    // MAIN ---
    return main();
})();
JXA_END")

You can also launch the slightly different version below from any context in which JavaScript for Automation scripts will run. For example macOS Script Editor, with the top left pull-down set to JavaScript rather than AppleScript:

JS for Script Editor etc
(() => {
    'use strict';

    // Pick color for selected notes in Tinderbox 8
    // Rob Trew 2020
    // Ver 0.01

    const main = () => {
        const docs = Application('Tinderbox 8').documents;
        return either(
            alert('Color of selected note(s)')
        )(
            x => x
        )(
            bindLR(
                0 < docs.length ? (
                    Right(docs.at(0))
                ) : Left('No documents open in Tinderbox 8.')
            )(
                doc => bindLR(
                    0 < doc.selections.length ? (
                        Right(doc.selections())
                    ) : Left('Nothing selected in ' + doc.name())
                )(notes => {
                    try {
                        const
                            hexColor = '#' + Object.assign(
                                Application.currentApplication(), {
                                    includeStandardAdditions: true
                                })
                            .chooseColor({
                                defaultColor: chunksOf(2)(
                                        notes[0].color().slice(1)
                                    )
                                    .map(s => parseInt(s, 16) / 255)
                            })
                            .map(
                                x => Math.round(255 * x)
                                .toString(16).padStart(2, '0')
                            ).join('');
                        return (
                            notes.forEach(
                                x => x.attributes.byName('Color')
                                .value = hexColor
                            ),
                            Right(hexColor)
                        );
                    } catch (e) {
                        return Left(e.message)
                    }
                })
            )
        );
    };

    // ------------------------JXA-------------------------

    // alert :: String -> String -> IO String
    const alert = title => s => {
        const
            sa = Object.assign(Application('System Events'), {
                includeStandardAdditions: true
            });
        return (
            sa.activate(),
            sa.displayDialog(s, {
                withTitle: title,
                buttons: ['OK'],
                defaultButton: 'OK'
            }),
            s
        );
    };

    // -----------------GENERIC FUNCTIONS------------------
    // https://github.com/RobTrew/prelude-jxa

    // Left :: a -> Either a b
    const Left = x => ({
        type: 'Either',
        Left: x
    });

    // Right :: b -> Either a b
    const Right = x => ({
        type: 'Either',
        Right: x
    });

    // bindLR (>>=) :: Either a ->
    // (a -> Either b) -> Either b
    const bindLR = m =>
        mf => undefined !== m.Left ? (
            m
        ) : mf(m.Right);

    // chunksOf :: Int -> [a] -> [[a]]
    const chunksOf = n =>
        xs => enumFromThenTo(0)(n)(
            xs.length - 1
        ).reduce(
            (a, i) => a.concat([xs.slice(i, (n + i))]),
            []
        );

    // either :: (a -> c) -> (b -> c) -> Either a b -> c
    const either = fl =>
        fr => e => 'Either' === e.type ? (
            undefined !== e.Left ? (
                fl(e.Left)
            ) : fr(e.Right)
        ) : undefined;

    // enumFromThenTo :: Int -> Int -> Int -> [Int]
    const enumFromThenTo = x1 =>
        x2 => y => {
            const d = x2 - x1;
            return Array.from({
                length: Math.floor(y - x2) / d + 2
            }, (_, i) => x1 + (d * i));
        };

    // MAIN ---
    return main();
})();

Something I have learned from the Stamp version of that experiment is that runCommand gets fired once for each selected note, so a Tinderbox stamp is probably not the best way to use that code for multiple selections.

To disable the ‘User cancelled’ alert (and other error messages), you can comment out

alert('Color of selected note(s)')

and replace it with x => x

so that the amended section (near the top of the code) reads:

return either(
    //alert('Color of selected note(s)')
    x => x
)(
    x => x
)(

Of course, you can also use regular stamps to set colors easily

stamp name: red
stamp action: $Color=“darker magenta”

I get the impulse, but respectfully disagree. Indeed, Tinderbox used to have such an option, which was actually removed in the v5 -> rewrite as the context menu was overloaded with options. Also, when you factor in colour schemes and OS dark-vs-light modes the implausibility of the idea becomes clear. A choice of blue is fine if it is actually a blue colour as intended; some colour schemes repurpose named colours to different ones.

Happily there is a simple way out of this. Cmd+1 opens the Inspector dialog and by default it opens on the Appearance pane which lets you easily select the desired colour.

If you have the Alfred Power Pack, you could also try the Alfred workflow for Tinderbox. There is a color choice menu, with 8 colors (that can be adjusted in the workflow preferences) plus a color picker. Looks like this:

(BTW, code was written by @ComplexPoint… )

1 Like

Thanks, I had no idea this existed!

Fair enough, the color scheme point is a good one, that would be hard. Thanks for responding!

1 Like

BTW, if you use hex codes for the color like #ff0000 instead of “red”, the color will not change, when you use different color schemes. (If I recall correctly…)

I think you’re confusing two things:

  • The assigned colour value of a named colour, normally defined as a string containing a hash-prefixed 6-digit hex number, i.e. colour “bright green” might be #00ff00.
  • An attribute value set for a Color-type attribute.

This is better explained visually. consdier this note with a $Pattern of bar(60) set so the map icon is part $AccentColor (what used to be $Color2) at left and default $Color at right:

Now I set that note to these values:

$Color = "#00ff00";
$AccentColor = "bright green";

Both attributes draw as bright green:

But if I now add a new colour scheme that redefines the named colour “bright green” as the value of a bright red (#ff0000) the result is the $Color still draws in the bright green whilst $AccentColor changes to red:

I think the bottom line is that if you want regularly swap schemes in your documents, then use named colours for colour values rather than hex values.

I hope that helps.

That’s exactly, what I wanted to say, but you found the better words, of course :grinning:

The #00ff00 in your example keeps green, while the “bright green” can be redefined (or is redifined by other color schemes).

So if I really want to keep the note green, no matter what the color scheme is, I would assign the #00ff00 to this note. Because you showed, that the “bright green” can get #ff0000. That confuses me, because it is - or at least it looks to me like - bright red…

Indeed, but defining one named colour as another value, i.e. making ‘blue’ a green colour, is exactly how Tinderbox colour scheme (TBC) files work. It’s not that crazy, for instance, someone with colour blindness might make themselves a scheme so that colours had more contrast within their discernible colour range. Using such a scheme doesn’t affect the colours and so another user can switch it back to their standard colours.

Also, ‘bright green’ only looks weird in the last grab above because of the colour chip next to the string value. On the Map itself it simply looks red, i.e. it doesn’t matter about its name.

Bottom line no one has to use colour schemes. Like many less common features in the Tinderbox toolbox, a few will use the feature a lots and the rest rarely if ever. So, we don’t need to worry unduly.

1 Like