How to use the ToolButton clicked signal (in a HeaderBar)? - gtk

While creating a simple ToolButton, one can use the clicked.connect interface as in the Toolbar example from the Vala guide. The interface of adding a button to a HeaderBar is similar to what is shown in that example. However, the way of handling the clicked connection seems to be different (or there is something that I am missing).
The following example is of a small text editor, where a open dialog button is packed into the HeaderBar. The clicked.connection syntax, however returns an error.
Here is the code:
[indent=4]
uses
Gtk
init
Gtk.init (ref args)
var app = new Application ()
app.show_all ()
Gtk.main ()
// This class holds all the elements from the GUI
class Application : Gtk.Window
_view:Gtk.TextView
construct ()
// Prepare Gtk.Window:
this.window_position = Gtk.WindowPosition.CENTER
this.destroy.connect (Gtk.main_quit)
this.set_default_size (400, 400)
// Headerbar definition
headerbar:Gtk.HeaderBar = new Gtk.HeaderBar()
headerbar.show_close_button = true
headerbar.set_title("My text editor")
// Headerbar buttons
open_button:Gtk.ToolButton = new ToolButton.from_stock(Stock.OPEN)
open_button.clicked.connect (openfile)
// Add everything to the toolbar
headerbar.pack_start (open_button)
show_all ()
this.set_titlebar(headerbar)
// Box:
box:Gtk.Box = new Gtk.Box (Gtk.Orientation.VERTICAL, 1)
this.add (box)
// A ScrolledWindow:
scrolled:Gtk.ScrolledWindow = new Gtk.ScrolledWindow (null, null)
box.pack_start (scrolled, true, true, 0)
// The TextView:
_view = new Gtk.TextView ()
_view.set_wrap_mode (Gtk.WrapMode.WORD)
_view.buffer.text = "Lorem Ipsum"
scrolled.add (_view)
The open_button.clicked.connect returns at compilation:
text_editor-exercise_7_1.gs:134.32-134.39: error: Argument 1: Cannot convert from `Application.openfile' to `Gtk.ToolButton.clicked'
open_button.clicked.connect (openfile)
Does the way of handling that signal changes when one uses the HeaderBar widget?
The code is working as long as the line is commented (you may want to add a stub for the openfile function).
Thanks
UPDATE
This question deserves an update, because the error was actually not in the body I attached above.
The error was at the definition of the function. I wrote:
def openfile (self:Button)
When I should've instead:
def openfile (self:ToolButton)
Or simply:
def openfile ()

You didn't include the click handler in your code, using this example stub it works just fine:
def openfile ()
warning ("Button clicked")
So I guess that the type signature of your click handler is wrong and that's why the compiler is complaining here.

Related

scalafx custom binding race condition

I'm trying to use Amazon Rekognition to draw boxes around detected text in an image.
Here's a stripped-down JavaFX app that is supposed to do that:
object TextRekognizeGui extends JFXApp {
lazy val rekognition: Option[AmazonRekognition] =
RekogClient.get(config.AwsConfig.rekogEndpoint, config.AwsConfig.region)
private val config = MainConfig.loadConfig()
stage = new PrimaryStage {
scene = new Scene {
var imgFile: Option[File] = None
content = new HBox {
val imgFile: ObjectProperty[Option[File]] = ObjectProperty(None)
val img: ObjectProperty[Option[Image]] = ObjectProperty(None)
val lines: ObjectProperty[Seq[TextDetection]] = ObjectProperty(Seq.empty)
// These two print statements are always executed when we press the appropriate button.
img.onChange((_, _, newValue) => println("New image"))
lines.onChange((_, _, newValue) => println(s"${newValue.length} new text detections"))
children = Seq(
new VBox {
children = Seq(
new OpenButton(img, imgFile, lines, stage),
new RekogButton(img, imgFile, lines, rekognition)
)
},
new ResultPane(675, 425, img, lines))
}
}
}
stage.show
}
class ResultPane(width: Double, height: Double, img: ObjectProperty[Option[Image]],
textDetections: ObjectProperty[Seq[TextDetection]]) extends AnchorPane { private val emptyPane: Node = Rectangle(0, 0, width, height)
// This print statement, and the one below, are often not executed even when their dependencies change.
private val backdrop = Bindings.createObjectBinding[Node](
() => {println("new backdrop"); img.value.map(new MainImageView(_)).getOrElse(emptyPane)},
img
)
private val outlines = Bindings.createObjectBinding[Seq[Node]](
() => {println("new outlines"); textDetections.value map getTextOutline},
textDetections
)
This looks to me as though I followed the ScalaFX documentation's directions for creating a custom binding. When I click my "RekogButton", I expect to see the message " new text detections" along with the message "new outlines", but more often than not I see only the " new text detections" message.
The behavior appears to be the same throughout the lifetime of the program, but it only manifests on some runs. The same problem happens with the img -> background binding, but it manifests less often.
Why do my properties sometimes fail to call the bindings when they change?
edit 1
JavaFX stores the listeners associated with bindings as a weak reference, meaning they can still be garbage-collected unless another object holds a strong or soft reference to them. So it seems my app fails if the garbage collector happens to run between app initialization and the time the outlines are drawn.
This is odd, because I seem to have a strong reference to the binding -- the "delegate" field of the value "outlines" in the result pane. Looking into it more.

How do I connect a custom function to the clicked action of a GTK Button?

I am working my way through the Vala GTK+3 tutorial provided by Elementary OS. I understand that this code:
var button_hello = new Gtk.Button.with_label ("Click me!");
button_hello.clicked.connect (() => {
button_hello.label = "Hello World!";
button_hello.set_sensitive (false);
});
uses a Lambda function to change the button's label when it's clicked. What I want to do is call this function instead:
void clicked_button(Gtk.Button sender) {
sender.label = "Clicked. Yippee!";
sender.set_sensitive(false);
}
I've tried this:
button.clicked.connect(clicked_button(button));
But I get this error from the Vala compile when I try to compile:
hello-packaging.vala:16.25-16.46: error: invocation of void method not allowed as expression
button.clicked.connect(clicked_button(button));
^^^^^^^^^^^^^^^^^^^^^^
Compilation failed: 1 error(s), 0 warning(s)
I'm new to both Vala and Linux so please be gentle but can someone point me in the right direction?
You need to pass a reference to the function, rather than the result of the function. So it should be:
button.clicked.connect (clicked_button);
When the button is clicked GTK+ will invoke the clicked_button function with the button as an argument.
The error message invocation of void method not allowed as expression is telling you you are calling (invoking) the method and it has no result (void). Adding parentheses, (), to the end of a function name invokes that function.
Managed to get it working. Here's the code in case others need it:
int main(string[] args) {
// Initialise GTK
Gtk.init(ref args);
// Configure our window
var window = new Gtk.Window();
window.set_default_size(350, 70);
window.title = "Hello Packaging App";
window.set_position(Gtk.WindowPosition.CENTER);
window.set_border_width(12);
window.destroy.connect(Gtk.main_quit);
// Create our button
var button = new Gtk.Button.with_label("Click Me!");
button.clicked.connect(clicked_button);
// Add the button to the window
window.add(button);
window.show_all();
// Start the main application loop
Gtk.main();
return 0;
}
// Handled the clicking of the button
void clicked_button(Gtk.Button sender) {
sender.label = "Clicked. Yippee!";
sender.set_sensitive(false);
}

How to add menu items to WebKitContextMenu (javascript, gjs)?

When I try to add menuitems to a webview with this code:
my_webview.connect('context-menu', Lang.bind(this, function(webview, c_menu, event, hit_test){
var action = new Gtk.Action({name:"some_name", label:"Some Label"});
action.connect('activate', Lang.bind(this, function(){
print("Your Menu Item !");
}))
var m_item = new WebKit.ContextMenuItem(action);
c_menu.append(m_item);
m_item.show();
return false;
}));
the app exit with the error "Segmentation fault (core dumped)"
What's the correct way to add a menuitem to the context menu of a WebView ?
Small correction:
var m_item = WebKit.ContextMenuItem.new(action);
This is confusing to say the least, it's because WebKit.ContextMenuItem has a custom constructor and not the usual GObject constructor that would be invoked with new WebKit.ContextMenuItem. The segmentation fault is due to WebKit choking when its context menu item doesn't receive an action; however, it would be good if GJS at least warned you what was going on here.
I've opened a bug report for GJS here.

How to pack a button in a HeaderBar using Genie?

Background
My aim is to improve a little text editor as an exercise. It is running fine after the HeaderBar was added, however I can't find a way to pack buttons in it.
Code
uses
Granite.Widgets
Gtk
init
Gtk.init (ref args)
var app = new Application ()
app.show_all ()
Gtk.main ()
// This class holds all the elements from the GUI
class Application : Gtk.Window
_view:Gtk.TextView
construct ()
// Prepare Gtk.Window:
this.window_position = Gtk.WindowPosition.CENTER
this.destroy.connect (Gtk.main_quit)
this.set_default_size (400, 400)
// Headerbar definition
headerbar:Gtk.HeaderBar = new Gtk.HeaderBar()
headerbar.show_close_button = true
headerbar.set_title("My text editor")
// Headerbar buttons
var open_button = new ToolButton.from_stock(Stock.Open)
// Add everything to the toolbar
open_button.pack_start ()
show_all ()
this.set_titlebar(headerbar)
// Box:
box:Gtk.Box = new Gtk.Box (Gtk.Orientation.VERTICAL, 1)
this.add (box)
// A ScrolledWindow:
scrolled:Gtk.ScrolledWindow = new Gtk.ScrolledWindow (null, null)
box.pack_start (scrolled, true, true, 0)
// The TextView:
_view = new Gtk.TextView ()
_view.set_wrap_mode (Gtk.WrapMode.WORD)
_view.buffer.text = "Lorem Ipsum"
scrolled.add (_view)
// A Button:
button:Gtk.Button = new Gtk.Button.with_label ("Print content to
stdout")
box.pack_start (button, false, true, 0)
button.clicked.connect (clicked)
// This is a simple stub function to take care of the click
def clicked ()
stdout.puts (_view.buffer.text)
stdout.putc ('\n')
Error
When pack_start (see below) is used, I get the error:
text_editor-exercise_7_1.gs:136.3-136.39: error: Access to instance member `Gtk.HeaderBar.pack_start' denied
Similar errors occur when I use HeaderBar.pack_start or button_name.pack_start.
Question
Am I wrong to believe that pack_start should be used with HeaderBars?
A second smaller problem is regarding the use of Stock icons. For some reason I can't access Stock.Open.
Finally, is there any other source of information on HeaderBar? Valadoc is sparse in this subject (there are no examples or templates).
You are trying to call pack_start on the button, not on the headerbar:
// Add everything to the toolbar
open_button.pack_start ()
The correct code is:
headerbar.pack_start (open_button)

How to load an accelerators map from a file using GTK3 in Vala?

I'm making a text editor using GTK3 in Vala. I have a Gtk.MenuBar in a Gtk.Window and I want to use accelerators to easily activate its Gtk.MenuItems. But I want the user to be able to change the key combinations, so I'm loading the accelerators specifications from a file using the method Gtk.AccelMap.load("accels"). However, after calling this method, the accelerators are not loaded: the menu items don't have AccelLabels and are not activated when I press the key combinations. Here are the two files I'm working on. The first file contains a small version of my application (to show what I'm trying to do) and the second one is the accels file from which I load the accels specifications, and they must be in the same directory.
main.vala
// Compile me with: valac main.vala -o main --pkg gtk+-3.0
public class MyWindow: Gtk.Window {
public MyWindow() {
this.set_default_size(500, 500);
var main_box = new Gtk.VBox(false, 0);
this.add(main_box);
var accel_group = new Gtk.AccelGroup();
this.add_accel_group(accel_group);
// Load the accelerators from the file
Gtk.AccelMap.load("accels");
// Create the action
var quit_action = new Gtk.Action("file-quit", "Quit", "Quit the application", null);
quit_action.activate.connect(()=>{
Gtk.main_quit();
});
quit_action.set_accel_group(accel_group);
quit_action.set_accel_path("<MyWindow>/File/Quit");
// Menubar
var menubar = new Gtk.MenuBar();
main_box.pack_start(menubar, false, false, 0);
var file = new Gtk.MenuItem.with_label("File");
menubar.add(file);
var file_menu = new Gtk.Menu();
file.set_submenu(file_menu);
var quit_mi = (Gtk.MenuItem)quit_action.create_menu_item();
file_menu.append(quit_mi);
// Label
var label = new Gtk.Label("My Window");
main_box.pack_start(label, true, true, 0);
this.destroy.connect(Gtk.main_quit);
}
}
int main(string[] args) {
Gtk.init(ref args);
var win = new MyWindow();
win.show_all();
Gtk.main();
return 0;
}
"accels" file
; main GtkAccelMap rc-file -*- scheme -*-
; this file is an automated accelerator map dump
;
; (gtk_accel_path "<MyWindow>/File/Quit" "<Control>q")
So, why is this not working? What do I have to do before or after loading the accel file?
PS: I don't want to use a Gtk.UIManager.
See : https://docs.xfce.org/faq#keyboard_related
"
This functionality has been disabled since GTK3 which means that Xfce apps that have migrated to GTK3 (such as xfce4-terminal) do not support it.
Refer to specific app's documentation to learn how to configure its shortcuts.
"