How does JBOX_TEXTFIELD work?
Greetings! I'm trying to create a custom textfield object that will mask the user's input. Like when you type a password into an online form, and the input appears as "****".
I thought I would begin by trying to modify the uitextfield example that comes with the Max SDK. I'm fairly proficient with MIN Devkit. But making the leap into the deep end of Max SDK is proving a bit difficult.
I found the following method in the code:
long uitextfield_key(t_uitextfield *x, t_object *patcherview, long keycode, long modifiers, long textcharacter)
{
char buff[256];
buff[0] = textcharacter; // we know this is just a simple char
buff[1] = 0;
object_method(patcherview, gensym("insertboxtext"), x, buff);
jbox_redraw((t_jbox *)x);return 1;
}
This appears to be the method that takes the key input, appends it to the string, and redraws the box with the new string. If I want to append a * instead of the character the user typed, this would seem to be the method to modify.
I tried messing around here, just adding some print statements, etc. But it looks like this method doesn't actually fire when a key is pressed. My print statements don't appear, any breakpoints in here don't trigger, and any changes I make don't seem to have any effect.
Can anyone help me understand what this method is doing? Or what other methods I should look to modify to achieve my goal?
I also, I notice throughout this code, the function object_method() is being called with descriptive gensyms like "insertboxtext" and "selectbox," etc. These seem to calling additional, seemingly undocumented, functions that are available to the object.
Where are these functions defined? And how can I learn more about what they are doing?
Thanks for any insights!
This example is probably outdated, but you can utilize the keyfilter method for your needs, though I'm not really interessted to help with something that ends up as copy protection or something
Thanks 11OLSEN. I was starting to notice something was up when I commented out the "key" method entirely and nothing changed. Using "keyfilter" makes sense. Since "keyfilter" triggers on keypress, I can use that to call "set" and make sure the textfield is displaying stars, meanwhile I can store the actual string in a private variable.
I had started thinking down that route, and stopped because it didn't feel like the intended usage. But now that this approach has some sort of endorsement, I will pursue it.
This is not for copy protecting for anything in Max. The package will be accessing cloud services, which requires user auth. Creating and distributing Max patches that ask users to enter passwords into a stock [textedit] box and then passing sensitive data through patch chords seems like a bad thing to do.
I very much appreciate your input!
I think you need to use the textcharacter that is also passed instead of the keycode (keycode may generate a different character in other countries keyb layouts ?)
Thanks for the tip! I'm making good progress here at last.
One other question: What is the best way to convert the textcharacter long into a char so I can build my string and manage it?
From some brief investigation, it looks like there's nothing to do here, I can just append the textcharacter numbers directly to char array. But this is assuming textcharacter is typical ASCII, which apparently keycode is not?
Yes, use these numbers as chars should work. Sorry can't help with min devkit. Never got into that and will not. That's just a layer on the C-Api which doesn't give the same deep level of access.
And btw, seems like you need 2 textfields, name and password? Maybe don't use the textfield instead build a normal ui object and render 2 fields with paint method. User can click one of the fields to enter keys.
Thanks! I was thinking about going that route. It would increase the complexity a bit for me. But figuring out how to use the paint method might be a good exercise anyway.
But I'm arguing with myself about how much to invest, because whatever I do the solution will be suboptimal. My package depends on a C++ library. So all of my objects so far are built with MIN Devkit. But MIN seems to have limited support for UI objects. So I'm stuck with this jank where the UI Object (built with Max SDK) will always need to be deployed alongside a separate object that handles the business logic (built with Min). (there are plenty of ways for the objects to share data without patch cords)
If I could consolidate this to one Min Object I'd be more inclined to invest in a full-on UI object instead of the simpler solution of providing two textfields and building the rest of the UI in Max itself.
I know it's possible to call Max SDK functions in Min by putting the c74::max namespace in front of them. In fact I can probably just call functions from jgraphics.h directly in the paint method of the Min Object. (haven't tested this yet, but it seems possible in theory)
The place where I'm stuck is on how to enable the other necessary boxflags, especially JBOX_HILITE. The example Min object (min.textslider), only has a handful of flags enabled. And these flags are enabled by the ui_operator class that all ui objects in MIN must inherit from. Since the boxflags seem to be baked into the boilerplate for UI objects in Min, I don't know how I can add my own.
I inquired about this in this thread.
I'm totally open to ideas. I would LOVE to build a fully featured UI object in Min.
Fascinating. Wow! It looks like a number of the box flags can be set according to the return value of different member functions of m_instance. This is really encouraging. Thank you!
I'm having a hard time parsing c_min_operator_ui.h, though. I don't understand what it's expecting me to do in my Min file. Am I supposed to call m_instance->is_focusable()? How I do get that function to return true? And what is m_instance anyway?
That is something that's initialized in the constructor for the ui_operator class that my object inherits from. I'm not seeing how I would interact with it.
So this looks like it should be possible, but I'm not quite seeing how to do it.
Huh, it looks like it just works.
By following up on the definition of is_focusable() i eventually found this block in c74_min_object.h. The mention of there being a max message "focusgained" gave me the idea to try it. I created a max message "focusgained" and when the box is clicked on, that message is definitely triggered. So I guess JBOX_HILITE is set by default on MIN ui objects. So I think now I can play with jgraphics.h and/or MIN's c74_min_graphics.h to do what I need to do.
Lots of work ahead of me, but the path seems clear. Thanks a ton 11OLSEN!!
// SFINAE implementation used internally to determine if the Min class has a member named focusgained.
// NOTE: This relies on the C++ member name being "focusgained" -- not just the Max message name being "focusgained".
//
// To test this in isolation, for a class named slide, use the following code:
// static_assert(has_focusgained<slide>::value, "error");
template<typename min_class_type>
struct has_focusgained {
template<class, class>
class checker;
template<typename C>
static std::true_type test(checker<C, decltype(&C::focusgained)>*);
template<typename C>
static std::false_type test(...);
typedef decltype(test<min_class_type>(nullptr)) type;
static const bool value = is_same<std::true_type, decltype(test<min_class_type>(nullptr))>::value;
};
So I guess JBOX_HILITE is set by default on MIN ui objects
Not by default, rather by checking if you have defined a focusgained (member?) in your code.
remember you also need "focuslost" and "key" and you're good to go. BTW c++ doesn't mean you can't use the classic C-Api approach to write the external. Anyways, I'm happy that you see a path now :)
defined a focusgained (member?) in your code.
I'm not sure what you mean by this. I created a "focuslost", "focusedgained" and "key" message in the MIN Object. The focuslost and focusgained message worked beautifully. The "key" message seemed to work too. But it throws a "vector subscript out of range" error, upon returning. :(
there's only 4 args not 5 ? ptr,long,long,long
I don't think that's it. If I inspect the args vector at a breakpoint it shows there are 5 args.
also, I get the same error, if I do absolutely nothing in the MIN_FUNCTION and just return right away..
I also get a different error in c74_min_atom.h with a "keyup" message.
This happens after I lift the key, but before a breakpoint on the MIN_FUNCTION is hit.
To your previous point, it looks like I can just save a Max SDK file with a .cpp extension, and call my C++ functions and it will compile fine. I believe a recent version of the SDK makes this possible. But it still feels a bit risky. I'm happy to go that route if I have to. But my experience with MIN as a novice coder has been quite wonderful so far, and it would be excellent to figure out this tragically underdocumented area of Min Devkit.
Oh boy. I've really tried here.
I tried compiling the c object as a .cpp file. That seemed to go fine. But the next step is adding the functions from the third party C++ library I'm trying to integrate with Max. Declaring objects defined by the library seem to go fine, but as soon as I try to call a function that is defined inside the .dll of my library, the object fails to load. It compiles. But when I create an instance of it in Max, i get a "Error 126 loading external MaxSDKUITest"
These same functions work just fine, from an external written in Min. So it seems that changing the extension to .cpp does not suddenly give Max SDK support for Modern C++. I'm not taking this as a surprise.
But what happened next was a surprise.
MIN Devkit is just a wrapper about Max SDK. And Min Devkit creates the c74::max namespace. So how about we write a Max SDK UI object in Min.
Let's try:
#include "c74_min.h"
using namespace c74::max;
and then proceed to write a Max SDK Object.
In this case I just copied and pasted the contents of the uitextfield object from the max sdk examples. The linter was fine with this. So far so good. Min is amazing!
Now let's try to build the thing.
It says "Unresolved external on jpatcher_syms_init() referenced in function ext_main"
So.... something about the way Min wraps Max SDK causes a linkage failure for the function required to initialize UI objects.
I almost threw up my hands in fury. It seemed like all paths were blocked. Then I decided to check some of the other UI examples in Max Sdk. It turns out uitextfield is actually the only one that calls jpatch_syms_init().
The other UI examples, including 11OLSEN's excellent 11UILayer, do not require this function to initialize.
I tried copy-pasting a different example from max sdk into the Min object that is using namespace c74::max. It compiled just fine and behaved itself in Max. I quickly added some functions from my third party library. These performed as expected as well.
So it looks like if I want to paint my own rectangles, manage selection and deselection of each box, capture the keyboard input, draw characters in the right place, draw the cursor, move the cursor, make the cursor blink, I might be able to achieve that. And it will probably be a good exercise for someone who's never dabbled in computer graphics before.
It turns out the only thing I can't do is the simple solution I had originally planned, which was to use Max's existing textedit functionality to gather keyboard input from the user.
This has been a really strange journey, but I'm glad the next step is to start writing my own code.
For jpatcher_syms_init() to work, you need to include /common/jpatcher_syms.c. You see this line in the cmake of uitextfield sourcefolder: ${MAX_SDK_INCLUDES}/common/jpatcher_syms.c
But you can also add this line in your source file: #include "common/jpatcher_syms.c"
The same applies when you use other libraries. You probably have a .lib file for the dll which needs to be linked when building.
btw: jpatcher_syms.c and commonsyms.c are only pre-defining symbol ptrs. So you can reduce the use of gensym() which does a table lookup everytime. e.g. instead of executing gensym("patcher") you can just write _sym_patcher. You can also prepare your own symbols like you see in the UILayer eg in global scope: "static t_symbol* _sym_keyup;" and in the main method: _sym_keyup = gensym("keyup"); This is simply an efficiency measure to make the code running faster in the end..
I tried the key message in min-devkit. I think you need to return 1 or 0 which lets Max know if the key was cosumed I assume. This works for me:message<> key{ this, "key",
MIN_FUNCTION {
char text[256];
text[0] = int(args[4]);
text[1] = '\0';
cout << "number: " << args[4] << " textchar: " << text << endl;
return {1};
}
};
Thanks again 11OLSEN for all your help with this! I am learning a lot and feel like progress is coming. This seems like it may be doable now, and that's thanks to your help!
Regarding jpatcher_syms.c:
I tried loading it alongside the Min API
This:
Gives this:
Probably because some of these objects are in Max namespace, which is causing syntax confusion.
So I tried this:
Which gives this.
Probably because some of these objects are NOT in the Max namespace. And the blanket application of that namespace is causing syntax confusion.
This may be solvable by modifying jpatcher_syms.c, or being more clever with how I use the namespace. I'm not sure.
But at the moment I'm still stuck on building UI_textfield in C++. Though It looks like I finally found a list of all the secret messages max objects can receive. That's exciting!
Regarding linking the .lib file for my third party dll:
I'm pretty sure this is not the problem. I'm very familiar with the linkage requirements of the third party library. I double checked all my properties, it seems correct. The same properties work correctly with a Min object. It looks to me like the Max SDK Headers do not want to play well with Modern C++ libs... or at least they don't want to play with mine.
RE: use write_sym_patcher instead of gensym
Great tip! I will keep this in mind!
RE: The "Key" message in Min needs to return 1.
This worked!! That's huge actually. It means I can probably go ahead and build my object as a Min object. And use c74_min_graphics.h and possibly call functions from jgraphics.h with a c74::max prefix when necessary.
I still get an exception on "keyup", even if that returns 1. Not critical for my usecase, but there's still some ambiguity around what works and what doesn't work in Min.
Current status:
* It's still pretty unclear how we are expected to handle advanced UI objects in Min. Some things seem to work and some things don't. There's a lot of stumbling in the dark.
* Just using the Max SDK headers does not seem to play well with external c++ libraries.
* Using c74_min.h and then "using namespace c74::max" and writing a Max SDK object seems to work for everything except textfields.
This is probably enough for me to proceed with my project (i've said that before and been wrong). Textfields would be nice. And actually understanding Min would also be nice.
I don't see your pics, but all I described about jpatcher_syms and commonsyms is how I handle it in Max-Api. Don't know how much this applies to Min-Api as well..
regarding keyup in min-devkit:
that is not implemented but simply add in c74_min_object_wrapper.h
regarding "error loading external 126":
I had that in the past, the additional dll can't be found. In some cases the dlls have to be in the same folder as Max.exe.
Good luck with the next steps!
Error 126 on the Max SDK file is resolved. It was indeed a problem with my project configuration. And I can call my third party functions successfully. So if I get sick of trying to mix MIN and Max SDK, the option is available to build it strictly as a Max SDK external.
I now also recognize some advantages of min-devkit. In any case, the code is much leaner, requiring less writing. And if we really have unrestricted access to the c-level API at the same time, we could combine the best of both APIs. I will test this further
For sure. I think Min is pretty slick. I taught myself to code by working with Min.
I'm finally making good progress here. Min's c74_min_graphics.h wraps some, but not all, of jgraphics.h. What it does implement is very easy to use. Although the "image" class it defines seems like it might be unfinished? Or at least I can't figure out how it's supposed to work. But I can confirm jgraphics.h is fully accessible inside a a Min "paint" message. So creating image surfaces is still perfectly viable.
I'm happy I'll be able to do this in a Min object, so can I copy/paste my existing code instead of having to rewrite sections of it for Max SDK. Also, attributes will be much easier to handle.
I think the only thing that's still not clear is how to set the box flags. I discovered that it would be nice to use the JBOX_GROWY flag. But again, these flags seem to be hardcoded into the constructor of the class a Min UI object must inherit from.
If I modify c74_min_operator_ui.h I get the expected results. So it works... I just need to figure out how to set it from within my code.
Not without editing header file or adding your own header file with an alternative ui_operator class..
Do you know how to set the c74::max namespace for a block of code? Would like to avoid the prefix in every line;
Nevermind! Found the answer
using namespace c74::max
This will apply the namespace from where you declare it to the end of the file, or to the end of the block if you do it inside a block, which is probably recommended. If you found a different solution, do let me know.
Regarding setting the flags...
I decided to create my own header with an alternative class. So instead of having the Min Class inherit from ui_operator it inherits from a class called custom_ui_operator which is defined in my own header. custom_ui_operator is a copy/paste of ui_operator, except it takes a third template argument.
In addition to default height, and default width of the object, I provide a third argument for flags. I then modified the constructor to refer to that argument when setting the flags.
Now, in my object file, before instantiating my object class, I define a const long variable that contains my preferred flags. (defining it as const is important). Then pass that variable as an argument to the custom_ui_operator class template, and off we go.
To create the header I copy pasted c74_min_operator_ui.h. Removed lines 117-140 regarding wrap_as_max_external_ui. Then I replaced all instances of "ui_operator" with "custom_ui_operator" and "ui_operator_base" with "custom_ui_operator_base"
UPDATE: This didn't actually work. I was able to get the object to build, but when I create an instance of it in Max i get a fatal exception error in the constructor. It looks like Min is expecting the ui class to be named "ui_operator." Changing the name causes things to fail
This means we're stuck modifying c74s headers. This is a bit sad because I'd like to include Min as a submodule in my repo.
For me it works with a separate header file, just do not replace ui_operator_base class
Works for me now. I thought I tried that. I must have messed something up along the way. Thanks!
Hi all,
I'm finally getting back to this project after a bit of a pause. I'm happy to say I'm successfully typing into my custom UI object now. Thanks for all the assistance to get this far!!
I'm using key "key" message to capture the input. Accumulating the input to string variable. I'm feeding that variable to a a "text" method inside the "paint" message. By calling redraw() at the end of "key" I am able to update the string on the display in realtime. Great!
As soon as I got this running I saw the a limitation of this method. If I try to band select my text and delete a chunk of it, I can't. Because, after all, this isn't actually a text box. It's a smoke and mirrors show that looks like a text box.
I can filter on the backspace keycode and use mystring.popback() to handle backspaces. And I can try and paint a line that will blink and move like a cursor. But it will always feel a little strange, and not entirely native.
At the end of the day it would be very nice to be able to do a true textbox with Min Devkit. If anybody has had any luck with that since the last time I took a look I would love to hear about it! Thank you again as always!
I'm starting to take a closer look at uitextfield.c and thinking about how I can port it to MIN.
It looks like there's more to it than just setting the "textfield" jbox flag. That flag might not even be necessary for all I know.
It looks like there are a whole set of methods around creating a t_object for your textfield and then interacting with it. Beginning with the constructor.
textfield = jbox_get_textfield((t_object *) x);
In MaxSDK that x appears to be an the max class's pointer to itself. In MIN i thought it might be "this."I tried it my object's constructor:
textfield = c74::max::jbox_get_textfield((c74::max::t_object*)this);
The linter accepted it. But max crashed when I tried to create the object.
There are a couple similar constructs throughout the textfield object.
Like in the key method on line 206:
object_method(patcherview, gensym("insertboxtext"), x, buff);
If I can figure what MINs version of the object's pointer to itself is, I might be able to get something going here.
The other option, probably preferable, would be to use the Max SDK textfield example directly in my MIN project. But I wasn't quite able to make that happen. Those problems are documented in the November 17 post above.
Interesting stuff!