Why gtk css widget styling does not work? - gtk

I'm trying to style widget from inside of it's constructor in Vala but I don't get the result I want. Here is the original code sample:
public class MyWidget : Gtk.FlowBox {
public MyWidget () {
var css_provider = new Gtk.CssProvider ();
try {
css_provider.load_from_data (
"""
label {
color: blue;
}
"""
);
get_style_context ().add_provider (css_provider, Gtk.STYLE_PROVIDER_PRIORITY_APPLICATION);
} catch (Error e) {
message ("error");
}
}
public void add_entry (string s) {
var entry = new Gtk.Label (s);
add (entry);
}
}
I've tried all different variants of styling "flowbox", "flowboxchild", "label" etc. and only one that works is "*" for every element inside GtkFlowbox or assigning it to a class, thought I still can't style it's children. Priorities don't seem to change anything, adding global "flowbox" styles for screen context don't work too.
I've tried to do it using gtk inspector but no result here too.
So how do I style all children of widget? Or do I need to specify class for them?

Here is a working example:
public class MyWidget : Gtk.FlowBox {
public MyWidget () {
this.add_entry ("default text");
}
public void add_entry (string s) {
var entry = new Gtk.Label (s);
add (entry);
}
}
void main (string[] args) {
Gtk.init (ref args);
var window = new Gtk.Window ();
window.destroy.connect (Gtk.main_quit);
window.window_position = Gtk.WindowPosition.CENTER;
window.set_default_size (300, 80);
window.add (new MyWidget ());
var css_provider = new Gtk.CssProvider ();
try {
css_provider.load_from_data (
"""
GtkLabel {
color: blue;
}
"""
);
Gtk.StyleContext.add_provider_for_screen (
Gdk.Screen.get_default (),
css_provider,
Gtk.STYLE_PROVIDER_PRIORITY_APPLICATION
);
} catch (Error e) {
message ("error loading CSS provider");
}
window.show_all ();
Gtk.main ();
}
Compile with:
valac --pkg gtk+-3.0 css_provider_example.vala
add_provider is a provider for that widget only. add_provider_for_screen adds the stylesheet for all widgets in the application. I can understand for_screen can be a little misleading by making someone think it applies to all applications on that screen, but from what I can tell that is not the case.
I've moved the add_provider_for_screen section to the main () block to clarify it is for the whole application. If you want the style to only apply to GtkLabels inside GtkFlowBoxes then the selector GtkFlowBox GtkLabel should work.
Also the terminology 'node' and 'selector' seems a little confusing. I had to change label in the GTK+ CSS to GtkLabel. A 'node' is probably the part of the widget that can be affected by the CSS rather than an identifier for selection, but anyone clarifying that would be very helpful! I have found that .label does work. So label is treated like a class name in CSS and GtkLabel like an element. Note the dot before label. .flowbox doesn't seem to work though.

Related

Assertion Failed at method: gee_array_list_real_get

I think I fixed this error but I want to be certain I did it the right way.
Also I am not sure why is it happening this way.
Code before fix:
private Gee.ArrayList<Gtk.Widget> menuButtons;
// Some other code here
public override void remove (Gtk.Widget widget) {
if (widget in menuButtons) {
widget.unparent ();
menuButtons.remove ( widget ); // Look at this method call
if (this.get_visible () && widget.get_visible ()) {
this.queue_resize_no_redraw ();
}
}
}
The code causes:
ERROR:arraylist.c:957:gee_array_list_real_get: assertion failed: (index < _size)
./run: line 3: 11054 Aborted (core dumped) ./bin
Code after fix:
private Gee.ArrayList<Gtk.Widget> menuButtons;
// Some other code here
public override void remove (Gtk.Widget widget) {
if (widget in menuButtons) {
widget.unparent ();
if (this.get_visible () && widget.get_visible ()) {
this.queue_resize_no_redraw ();
menuButtons.remove ( widget ); // Moved method call here
}
}
}
And now it works, I am not sure but it might be something to do with remove method being called asynchronously, is it?
Any good explanation?
Is it a correct fix of a problem?
#After checking the code again I am certain that it is not a correct fix of my problem because menuButtons.remove ( widget ); never gets called in my case. The widget stays in the list and that is unwanted behaviour.
MVCE:
MyContainer.vala
public class MyContainer : Gtk.Container {
// ArrayList for storing menu buttons
private Gee.ArrayList<Gtk.Widget> menuButtons;
public MyContainer () {
base.set_has_window (false);
menuButtons = new Gee.ArrayList<Gtk.Widget> ();
}
public override void add (Gtk.Widget widget) {
widget.set_parent (this);
menuButtons.add (widget);
}
public override void remove (Gtk.Widget widget) {
if (widget in menuButtons) {
widget.unparent ();
menuButtons.remove ( widget ); // After removing the widget from the List I get "assertion failed error"
if (this.get_visible () && widget.get_visible ()) {
this.queue_resize_no_redraw ();
}
}
}
public override void forall_internal (bool include_internals, Gtk.Callback callback) {
foreach (var widget in menuButtons) {
callback (widget);
}
}
}
SimpleGtkApplication.vala
public class SimpleGtkApplication : Gtk.Application {
public SimpleGtkApplication () {
Object (application_id: "simple.gtk.application", flags: ApplicationFlags.FLAGS_NONE);
}
protected override void activate () {
Gtk.ApplicationWindow window = new Gtk.ApplicationWindow (this);
window.set_default_size (800, 600);
window.title = "SimpleGtkApplication";
Gtk.Container container = new MyContainer ();
container.add ( new Gtk.Button.with_label ("Button 1") );
container.add ( new Gtk.Button.with_label ("Button 2") );
window.add ( container );
window.show_all ();
}
public static int main (string[] args) {
SimpleGtkApplication app = new SimpleGtkApplication ();
return app.run (args);
}
}
Compile with: --pkg=gtk+-3.0 --pkg=gee-0.8
A couple of points:
You are overriding Gtk.Container::remove, but never chaining up to the parent class's implementation by calling base.remove(), which will cause you problems in the long run.
In MyContainer::remove you are calling widget.unparent(), which may be causing some kind of secondary invocation of MyContainer::remove. If so, both times the widget in menuButtons test evaluates to true, but when the original invocation tries to remove the widget from the list, it's already gone, hence the assertion failure.
TL;DR: Replace the call to widget.unparent() with base.remove(widget).
PS: I'd be really suprised if you need the explicit this.queue_resize_no_redraw() call either, GTK+ really should be managing that for you.
As Michael wrote, you are doing a lot of the things that Gtk could do for you yourself. You are also not calling the base methods in your overrides.
You are directly deriving from Gtk.Container, I have adapted your MVCE to use a Gtk.Box instead and get no warnings and no assertions with this code:
public class MyContainer : Gtk.Box {
private Gee.ArrayList<Gtk.Widget> menuButtons;
public MyContainer () {
menuButtons = new Gee.ArrayList<Gtk.Widget> ();
}
public override void add (Gtk.Widget widget) {
base.add (widget);
menuButtons.add (widget);
}
public override void remove (Gtk.Widget widget) {
if (widget in menuButtons) {
menuButtons.remove (widget);
}
base.remove (widget);
}
}

Wicket: tell the browser to scroll to certain tag (anchor)

We are using Wicket and our generated pages are quiet long (a lot of vertical scrolling). Some links or form's onSubmit methods invoke just perform some actions on the database and show the same page again:
public class MyPage extends WebPage {
public MyPage(PageParameters parameters) {
....
final Form<Void> form = new StatelessForm<Void>("formId") {
protected void onSubmit() {
// some database stuff
...
setResponsePage(getClass(), getPageParameters());
}
};
...
}
}
How can I make the setResponsePage invocation cause the browser scroll to the form, so the page is not just showing the top? Maybe some JavaScript-injection?
I think a nice Wicket-y solution combines stuff that is already in Michael's answer, with a Behavior, so you can just add this to your form.
form.add( new ScrollToTopBehavior());
The behaviour itself would like something like this:
public class ScrollToTopBehavior extends Behavior
{
#Override
public void renderHead( Component component, IHeaderResponse response )
{
super.renderHead( component, response );
response.render( JavaScriptHeaderItem.forReference( Application.get().getJavaScriptLibrarySettings().getJQueryReference() ) );
component.setOutputMarkupId( true );
String script = String.format("doSomeJavaScriptStuff('%s')", component.getMarkupId());
response.render( OnDomReadyHeaderItem.forScript( script ) );
}
}
UPDATE:
For scrolling to a specific ID / ANCHOR only once, you can follow this answer:
https://stackoverflow.com/a/3163635/461499
JS of course.
This would be something like (with JQuery usage):
var scrollPosition = $('#scrollToMarkupId').offset().top;
$('html, body').animate({ scrollTop: " + scrollPosition + " }, 'slow');
where scrollToMarkupId is wicket component's markup id, which could be obtained by calling component.getMarkupId() method.
I'm not pro in JS, so you can try to google better impl may be.
Now, about wicket:
1) As for me, I prefer AJAX invocations for such behavior ( note that if you use such approach your page won't be stateless ):
// do not override your form's `onSubmit()` method
final Form<Void> form = new Form<Void>("formId");
// adding ajax behavior with `onSubmit()` method overriding.
form.add ( new AjaxFormSubmitBehavior ("submit")
{
protected void onSubmit ( AjaxRequestTarget target )
{
// your submit logic
// then insert js, descriped above:
target.appendJavaScript ("..." + componentToScroll.getMarkupId() + "..");
}
});
This approach won't reload your page at all but also post your data.
/----------------------------------------------------------------------------------------------------------------------------------/
2) You also could execute JS after page loading, by overriding renderHead method:
public class YourPage extends WebPage
{
...
#Override
public void renderHead ( final IHeaderResponse response )
{
//replace `...` by your script.
response.render ( OnDomReadyHeaderItem.forScript ( "..." );
}
...
}
Such script will be invoked after page is renedered (and setResponsePage method will render your page). You can use this approach for any components and panels too.
I've now use following JavaScript injecting code:
add(new Behavior() {
#Override
public void renderHead(Component component, IHeaderResponse response) {
super.renderHead(component, response);
response.render(new HeaderItem() {
#Override
public Iterable<?> getRenderTokens() {
return Collections.singletonList("javascript-anchor");
}
#Override
public void render(Response response) {
response.write("<script type=\"text/javascript\">\n");
response.write("window.location.href='#rules';\n");
response.write("</script>\n");
}
});
}
});
Feel free to comment (I'm a complete JS-noob with only very limited experience in Wicket).

Adding button to SimplePanel results in error

I am performing the following action in GWT
public class FooPanel extends SimplePanel {
private String url;
public FooPanel () {
super(DOM.createAnchor());
Button button = new Button();
button.addClickHandler(new ClickHandler() {
public void onClick(ClickEvent event) {
foo();
}
});
add(button);
}
}
however when I run the code I get the following error
SimplePanel can only contain one child widget
However Button is a single widget so I am not sure what the problem is? The problem doesn't occur if i don't add the button
Remove this line:
super(DOM.createAnchor());
You don't need it.
You can simply use your Button in your code, or extend a Button widget. Adding a Button to a SimplePanel does not offer any benefits.
Have a look at source code of SimplePanel#add() to analyze this error.
#Override
public void add(Widget w) {
// Can't add() more than one widget to a SimplePanel.
if (getWidget() != null) {
throw new IllegalStateException("SimplePanel can only contain one child widget");
}
setWidget(w);
}
Now its clear from the source code that you have already added a widget in SimplePanel.
Call SimplePanel#getWidget() to get the already added widget.
Look at the source code of default constructor if SimplePanel class. It might help you to understand that how SimplePanel enclose the widget inside it.
/**
* Creates an empty panel that uses a DIV for its contents.
*/
public SimplePanel() {
this(DOM.createDiv());
}
Try with setWidget(button); instead of add(button);

change moved widget on drop with gwt-dnd 3.3.0

situation
I'm writing a admintool to change a GWT based GUI via Browser.
I want the Admin to use drag and drop to create and change the GUI.
To realize the dnd I use gwt-dnd 3.3.0
I have a toolbar in my admintool, from which I can drag the different objects.
Every object in the toolbar is a plain HTML widget with text inside.
I would like to change the Widget when it is droped.It should change from the HTML widget to the original widget I like to use.
problem & tried solution
At the moment I can change the widget on Drop, then it throws an exception and the "moving widget" don't get removed from the page. The "moving widget" is still dragable and shows the "move designe"
I think this happens, because the Drag or Drophandler do not know the moving widget, because i changed the drop widget...
Here is the code:
dragController.addDragHandler(new DragHandler(){
public void onDragEnd(DragEndEvent event) {
...
}
public void onDragStart(DragStartEvent event) {
...
}
public void onPreviewDragEnd(DragEndEvent event)
throws VetoDragException {
final DragContext mycontent = event.getContext();
List<Widget> mywl = mycontent.selectedWidgets;
for(int i = 0; i < mywl.size(); i++)
{
String stemp = ((HTML)mywl.get(i)).getText();
if(stemp.contains("Container")&&!stemp.contains("SubContainer"))
{
FlowPanel mypanel = new FlowPanel();
HTML htmltemp = new HTML("Label");
htmltemp.setStyleName("edit-dndcontainer");
mypanel.add(htmltemp);
mypanel.setStyleName("edit-dndcontainer");
mywl.add(i, mypanel);
dragController.makeDraggable(mypanel);
mywl.get(i).removeFromParent();
mywl.remove(i+1);
mycontent.selectedWidgets = mywl;
}
else if(stemp.contains("Label"))
{
...
}
else
{
...
throw new VetoDragException();
}
}
}
public void onPreviewDragStart(DragStartEvent event)
throws VetoDragException
{
...
}
});
question
Is gwt-dnd the correct lib to use for this behaviour, or should I use native dnd of GWT?
How can I change the dnd-widget on drop with gwt-dnd?
Thanks for your help
My approach was not that bad, but instead of working with the DragContext, I have to work with the getSelectedWidgets().iterator() from the DragController.
edit
I switched my code to onDragEnd(), in this case I don't screw up the moving widget. And I changed the Draggable widget to a Panel. I just have to add and remove widgets from it.
answer
Samplecode:
dragController.addDragHandler(new DragHandler(){
public void onDragEnd(DragEndEvent event) {
Iterable<Widget> myiterable = myDragCTRL_subcont.getSelectedWidgets();
Widget mywidget = myiterable.iterator().next();
FlowPanel flowtemp = ((FlowPanel)mywidget);
flowtemp.add(new HTML("label"));
...
...
}
public void onDragStart(DragStartEvent event) {
...
}
public void onPreviewDragEnd(DragEndEvent event)
throws VetoDragException {
...
}
public void onPreviewDragStart(DragStartEvent event)
throws VetoDragException {
...
}
});

popuppanel show up beneath the widget

I am new to GWT and the web stuff.
I am working out my own project based on
http://code.google.com/p/cloud-tasks-io/source/browse/#svn%2Ftrunk%2FCloudTasks-AppEngine%2Fsrc%2Fcom%2Fcloudtasks%2Fclient
I am trying to use popup/dialog. The popup and dialog always show behind the widget. I keep googling around and the most relevant I found is this
http://groups.google.com/group/gwt-google-apis/browse_thread/thread/40db4fcbe10d2060 which does not provide any answer. Anyway, I have 3rd party library, bst-player 1.3, which uses flash. So I disabled it(later remove it too), the popup just won't come to the top! It is still hiding behind the widget.
I have learned that popuppanel/dialogpanel alikes do not need to get added to another widget. A different way of saying is that it is not a normal widget in a sense that it cannot attach to a parent but it attaches itself to the dom to guarantee being on top (from GWT composite widget )
I am at my wit end and I am here at SO ...
UPDATE
Here is my Popup class
public class PopUp {
static PopupPanel simplePopup;
public static void init() {
simplePopup = new PopupPanel(true);
simplePopup.hide();
simplePopup.setVisible(false);
// DOM.setIntStyleAttribute(simplePopup.getElement(), "zIndex", 3);
}
public static void showpopupmsg(String msg, int left, int top) {
if (simplePopup == null) {
init();
}
if (msg != null && !msg.equalsIgnoreCase("")) {
simplePopup.ensureDebugId("cwBasicPopup-simplePopup");
simplePopup.setWidget(new HTML(msg));
simplePopup.setVisible(true);
simplePopup.setPopupPosition(left, top);
simplePopup.setWidth("475px"); //575
simplePopup.setGlassEnabled(true);
simplePopup.show();
}
}
public static void show(String message){
if (simplePopup == null) {
init();
}
simplePopup.setGlassEnabled(true);
simplePopup.setTitle(message);
simplePopup.center();
}
}
Here is how I am calling
tasksTable.doneColumn.setFieldUpdater(new FieldUpdater<TaskProxy, Boolean>() {
public void update(int index, TaskProxy task, Boolean value) {
String msg = "Here is the popup. All the way underneath";
Widget source = tasksTable;
int left = source.getAbsoluteLeft() - 50;
// source.getAbsoluteLeft() + 25;
int top = source.getAbsoluteTop() - 25;
PopUp.showpopupmsg(msg, left, top); //Here is the calling method
TaskRequest request = requestFactory.taskRequest();
TaskProxy updatedTask = request.edit(task);
updatedTask.setDone(value);
request.updateTask(updatedTask).fire();
}
});
Here is how the Popup is beneath the widget.
The source of the problem has been quite elusive since I am still new to the webapp, yet, I finally solve it myself. The culprit is the CSS. It is defining the z-index for the whole thing to quite high as seen in the following code line 1333.
http://code.google.com/p/cloud-tasks-io/source/browse/trunk/CloudTasks-AppEngine/war/CloudTasks.css#1333
I have doubted about the z-index before and try it out with a paltry value 3 as seen in the commented out code segment of Popup class in question. I have to uncomment it and set it to 101.
DOM.setIntStyleAttribute(simplePopup.getElement(), "zIndex", 101);
I was , you know, #$%###$*.
z-index is only decides which widget should show on top..
the widget popup is under benath might be having z-index value high.
set the z-index for popup thru css (recomended) or DOM will work for you
According to my feeling, using static methods of your "PopUp" object is a bit strange...
In that way, I think things a relative to the top rather than caller object.
Maybe you could consider make your class 'Popup' extending 'popupanel'
and in your calling code, just make
new PopUp(msg,left,top).show() ;
I recently wrote my own solution for a popup panel that needs to be aligned with its caller. The solution consists out of an PopupPanel extension and a Button extension.
The button extension has an instance of the panel extension, and the moment it is clicked it gives its coordinates and width and height to its panel.
#Override
public void onClick(ClickEvent event) {
if (optionsPanel.isShowing()) {
optionsPanel.hide();
} else {
optionsPanel.setButtonBounds(new Bbox(
getAbsoluteLeft(), getAbsoluteTop(), getOffsetWidth(), getOffsetHeight()));
optionsPanel.show();
}
}
(The Bbox class is just a convenience class I could use for wrapping coordinates; write your own class or 4 methods for that matter)
The main work is then done in the onLoad() override of the PopupPanel, in which the coordinates of the button are used to position the panel;
#Override
protected void onLoad() {
super.onLoad();
if (null == bounds) {
super.hide();
} else {
left = (int) bounds.getX();
top = (int) bounds.getMaxY();
setPopupPosition(left, top);
}
}
(bounds are the coordinates of the button; getMaxY() == bottom coordinate of button)