Sign up

how to keep the focus

hi,

i have yet another issue i cannot figure out. it is probably purely GTK related, rather than gtkD, but i could not find it covered/explained anywhere. i would greatly appreciate a bit of help here.

so i want to create this dialog for checked input (see code example below), for starters just an int. i don't want to allow the user to leave the Entry field with invalid input, e.g. a string. even the [OK] and [Cancel] buttons shall be no way out. well, i managed to cope with (not) leaving using 'tab' key, but the [OK] and [Cancel] buttons will break my checked input - unless i display an error message as well. this is quite an ugly workaround and i rather do without.

module testfocus;

import gtk.Main, gtk.Widget, gtk.Dialog;
import gtk.MessageDialog, gtk.VBox, gtk.Entry;
import gdk.Event;
import std.stdio, std.conv;

int main(string[] args){

Main.init( args );

int i = 42;

auto dlg = new Dialog( "input values",	null,
	DialogFlags.MODAL, ["OK", "Cancel"],
	[ResponseType.ACCEPT, ResponseType.CANCEL]
);
auto entry = new Entry( "enter an int" );
bool test(Event e, Widget w){
	string test = entry.getText();
	try{
		auto tmp = to!(int)( test );
		i = tmp;
	}catch{
		// comment this back in and it works, i.e.
		// even [OK] and [Cancel] won't break checked input:
		//  auto msgbx = new MessageDialog(	dlg,
			//  DialogFlags.DESTROY_WITH_PARENT+DialogFlags.MODAL,
			//  MessageType.ERROR, ButtonsType.CLOSE, "invalid value"
		//  );
		//  msgbx.run();
		//  msgbx.destroy();
		w.grabFocus();		// works only when losing focus with tab, while
					// [OK] and [Cancel] break checked input!
		return true;
	}
	return false;
}
entry.addOnFocusOut(&test, ConnectFlags.AFTER);
auto vbox = dlg.getContentArea();
vbox.add(entry);
dlg.showAll();
if( ResponseType.ACCEPT == dlg.run() ){
	writeln( "i = "~to!string(i) );
}
dlg.destroy();

return 0;

}

Re: how to keep the focus

On Mon, 16 Sep 2013 06:24:18 GMT, det wrote:

hi,

i have yet another issue i cannot figure out. it is probably purely GTK related, rather than gtkD, but i could not find it covered/explained anywhere. i would greatly appreciate a bit of help here.

so i want to create this dialog for checked input (see code example below), for starters just an int. i don't want to allow the user to leave the Entry field with invalid input, e.g. a string. even the [OK] and [Cancel] buttons shall be no way out. well, i managed to cope with (not) leaving using 'tab' key, but the [OK] and [Cancel] buttons will break my checked input - unless i display an error message as well. this is quite an ugly workaround and i rather do without.

... code ...

For the simple cases like mandating that the entry only accepts integers you can set the input purpose.

entry.setInputPurpose(InputPurpose.DIGITS);

PS: code highlighting works better if the code is between triple back-ticks.

Re: how to keep the focus

On Mon, 16 Sep 2013 21:08:52 GMT, Mike Wey wrote:

For the simple cases like mandating that the entry only accepts integers you can set the input purpose.

thanks, mike!

guess i selected my example too simple ;) i will also need versions that check for floats and that the entered value falls within certain limits - so i do need the callback for d-side validation.

if there was a way to stop/kill any further propagation of the event or signal that made my Entry lose focus, then it should work (with grabFocus thereafter to reset the focus back to the Entry)... somehow running the error dialog has the positive side-effect of overriding/killing the [Ok]/[Cancel]-button-clicked-event. i would like to achieve the same without actually running the error dialog.

(it does not help that having both, events AND signals, confuses me endlessly.)

PS: code highlighting works better if the code is between triple back-ticks.

sorry. will try to remember next time.

cheers,
det

Re: how to keep the focus

On Mon, 16 Sep 2013 22:36:00 GMT, det wrote:

guess i selected my example too simple ;) i will also need versions that check for floats and that the entered value falls within certain limits - so i do need the callback for d-side validation.

That's not surprising ;)

if there was a way to stop/kill any further propagation of the event or signal that made my Entry lose focus, then it should work (with grabFocus thereafter to reset the focus back to the Entry)... somehow running the error dialog has the positive side-effect of overriding/killing the [Ok]/[Cancel]-button-clicked-event. i would like to achieve the same without actually running the error dialog.

Maybe try something like this:

import gtk.Main, gtk.Label, gtk.Dialog, gtk.Entry, gtk.EditableIF;
import std.conv, std.stdio;

void main(string[] args){ 
	float f;

	Main.init(args); 

	auto dlg = new Dialog(
		"test",
		null,
		DialogFlags.MODAL,
		["OK", "Cancel"],
		[ResponseType.ACCEPT, ResponseType.CANCEL]
	);

	auto q = new Entry();
	q.setPlaceholderText("Some float");
	
	q.addOnChanged(delegate void(EditableIF e){
		try
		{
			f = to!(float)( q.getText() );
			dlg.getWidgetForResponse(ResponseType.ACCEPT).setSensitive(true);
			q.setStockId(EntryIconPosition.SECONDARY, StockID.DISCARD);
		}
		catch
		{
			dlg.getWidgetForResponse(ResponseType.ACCEPT).setSensitive(false);
			q.setStockId(EntryIconPosition.SECONDARY, StockID.DIALOG_ERROR);
		}
	});

	auto vbox = dlg.getContentArea();
	vbox.add( new Label("Entry:") );
	vbox.add( q );
	vbox.showAll();

	if( ResponseType.ACCEPT == dlg.run() ){
		writeln(f);
	}

	dlg.destroy();
}

(it does not help that having both, events AND signals, confuses me endlessly.)

Do you have an example?

Re: how to keep the focus

thanks a lot, mike!

On Tue, 17 Sep 2013 21:22:21 GMT, Mike Wey wrote:

Maybe try something like this:

this looks very promising. originally i did not want to give the user the option to cancel out of the dialog, but i am slowly warming up to it.

the main rational in my case is that i start with a valid value, give the user a chance to change it to another valid value, and move on. ideally with no boilerplate code at this point. it is important to me that the solution is 'modular', i.e. encapsulated in a new widget class (agnostic to the hosting dialog) as in my test code pasted in at the end. as you can see it would be really awkward if i would have to supply a reference to the hosting Dialog, so that the widget can enable or disable the OK and CANCEL buttons.

i tried using getParent(), getToplevel() or getAncestor(GType ?) to get a reference to the host Dialog but i failed miserably. if i had this reference your suggestion is probably the way to go. so can the reference to the host dialog be retrieved from e.g. an Entry or Box at all? how? (where do i find the GType used in gtkD for Dialog, will this do the trick with getAncestor()?)

(it does not help that having both, events AND signals, confuses me endlessly.)

Do you have an example?

oh, just in general terms. GTK documentation is scattered around and tends to be either introductory only, or highly specific reference material. (and then on top of it it is often not straightforward how this translates to gtkD usage.) anyhow, sometimes the terms event and signal are used basically interchangeably, in other cases a doc/tutorial tries to make a point that they are completely different things. very confusing and convoluted. the best and almost sound explanation i have found so far is this one: http://zetcode.com/tutorials/gtktutorial/gtkevents (still makes me only wonder what a X server is on windows)

i think i mostly get now what signals are, and that i have ways to stop their further propagation if i need to. but how to deal with events is still very blurry to me. it seems in my case i have to kill the root event itself (mouse click on OK button, ESC key), so that no other/more/new signals come out of this event. i even tried connecting my Entry widget to the Delete signal, hoping i could prevent the action arising from a pressed OK button by returning TRUE, but it did not do anything. guess i only stopped signal propagation within the Entry widget, while i would need to get rid of the root Event. is there no Event.kill or destroy or discard or ...?

cheers,
det

ok, here is my silly code so far:

module testinputdlg;

import gtk.Main, gtk.Window, gtk.Widget;
import gtk.Dialog, gtk.Box;
import gdk.Event;
import std.stdio, std.conv;

ubyte b = 16;
string s = "rubbish";
int i = 4;
double d = 3.14;
float f = 7.0;


int main(string[] args){
	import gtk.Entry, gtk.Label;
	import gobject.Signals;
	Main.init( args );

	auto dlg = new Dialog( "update settings",
		null, DialogFlags.MODAL,
		["OK"],
		[ResponseType.ACCEPT]
	);
	with( dlg.getContentArea() ){
		add( new CheckedInput!ubyte( "unsigned byte:", b ) );
		add( new CheckedInput!string( "string:", s, ((string ss)=>' '==ss[0]?false:true) ) );
		add( new CheckedInput!int( "even int:", i, ((int ii)=>ii%2?false:true) ) );
		add( new CheckedInput!double( "positive double:", d, 0.0, double.max_10_exp ) );
		add( new CheckedInput!float( "float:", f, -10.0f, +10.0f, ((float ff)=>(ff>-5.0 && ff<5.0)?false:true) ) );
		add( new Label( "[-10.0;-5.0] or [+5.0;+10.0]" ) );
	}
	dlg.showAll();
	dlg.setResizable(false);
	dlg.run();
	dlg.destroy();

	writeln( "b = "~to!string(b) );
	writeln( s );
	writeln( "i = "~to!string(i) );
	writeln( "d = "~to!string(d) );
	writeln( "f = "~to!string(f) );

	return 0;
}


class CheckedInput(T) : Box
if( is(T==string) || __traits( isArithmetic, T ) && !is(T==dchar) && !is(T==wchar) && !is(T==char) && !is(T==bool)
		 && !is(T==ifloat) && !is(T==idouble) && !is(T==ireal) && !is(T==cfloat) && !is(T==cdouble) && !is(T==creal)){
	import gtk.MessageDialog;
	static import gtk.Entry, gtk.Label;
	import std.exception;

	T* valptr;
	static if( __traits( isIntegral, T ) ){
		T min = T.min;
		T max = T.max;
	}else static if( __traits( isFloating, T ) ){
		T min = -T.max_10_exp;
		T max = T.max_10_exp;
	}else static if( is( T==string ) ){
		// for potential future use
	}else static assert( 0, "Unsupported type");
	gtk.Label.Label Label;
	gtk.Entry.Entry Entry;
	bool delegate(T) condition;

	static if( __traits( isArithmetic, T ) ){
		private bool _withinLimits(T i){
			return i>=this.min && i <=this.max;
		}
	}
	private bool _isValid(T i){
		static if( !is( T==string ) ){
			if( !_withinLimits(i) ) return false;
		}
		if( condition ){
			if( !condition(i) ) return false;
		}
		return true;
	}

	private bool _checkInput(Event e, Widget w){
		bool valid = false;
		try{
			//  auto tmp = to!(T)( (cast(gtk.Entry.Entry)w).getText() );
			auto tmp = to!(T)( Entry.getText() );
			if( _isValid(tmp) ){
				*valptr = tmp;
				valid = true;
			}
		}catch{}
		if( !valid ){
			// should not need this but does not work without,
			// [OK] and [Cancel] break checked input!
			auto msgbx = new MessageDialog(	null,
				DialogFlags.DESTROY_WITH_PARENT+DialogFlags.MODAL,
				MessageType.ERROR, ButtonsType.CLOSE, "Invalid value!"
			);
			msgbx.run();
			msgbx.destroy();
			// end of workaround, replace with better way once known
			w.grabFocus();
			return true;
		}
		return false;
	}

	this(string query, ref T val, bool delegate(T) condition = null){
		super( Orientation.HORIZONTAL, 1 );
		this.valptr = &val;
		if( condition ) this.condition = condition;
		enforce( _isValid(val), "Checked value out of bounds" );
		Label = new gtk.Label.Label(query);
		Entry = new gtk.Entry.Entry( to!string(val) );
		Entry.setWidthChars(12);
		Entry.addOnFocusOut(&_checkInput, ConnectFlags.AFTER);	// gtk error if not after
		packEnd(Entry,false,false,0);
		packEnd(Label,false,false,0);
	}

	static if( __traits( isArithmetic, T ) ){
		this(string query, ref T val, T min, T max, bool delegate(T) condition = null){
			super( Orientation.HORIZONTAL, 1 );
			this.valptr = &val;
			this.min = min;
			this.max = max;
			if( condition ) this.condition = condition;
			enforce( _isValid(val), "Checked value out of bounds" );
			Label = new gtk.Label.Label(query);
			Entry = new gtk.Entry.Entry( to!string(val) );
			Entry.setWidthChars(12);
			Entry.addOnFocusOut(&_checkInput, ConnectFlags.AFTER);	// gtk error if not after
			packEnd(Entry,false,false,0);
			packEnd(Label,false,false,0);
		}
	}
}

Re: how to keep the focus

On Fri, 20 Sep 2013 00:07:11 GMT, det wrote:

i tried using getParent(), getToplevel() or getAncestor(GType ?) to get a reference to the host Dialog but i failed miserably. if i had this reference your suggestion is probably the way to go. so can the reference to the host dialog be retrieved from e.g. an Entry or Box at all? how? (where do i find the GType used in gtkD for Dialog, will this do the trick with getAncestor()?)

oh, i have overlooked (not the first time) that i have to cast the returned Widget to Dialog first, this seems to work:

assert( dlg is cast(Dialog)q.getToplevel() );
( cast(Dialog)q.getToplevel() ).getWidgetForResponse(ResponseType.ACCEPT).setSensitive(true);

with appropiate casting i got even a version using getAncestor(...) and gobject.Type.fromName() working, using:

assert( dlg is cast(Dialog)q.getAncestor( Type.fromName("GtkDialog") ) );
( cast(Dialog)q.getAncestor( Type.fromName("GtkDialog") ) ).getWidgetForResponse(ResponseType.ACCEPT).setSensitive(true);

despite being more verbose this is probably better as i can verify that my widget lives in a Dialog widget. it seems a bit inconsistent, though, that i have to use "GtkDialog" instead of "Dialog".

so this way my widget finds its host Dialog and doesn't need a reference. now i can experiment a bit more with your suggestions, mike, thanks again for your very useful help.

det