Initialize git. Unstable version.

This commit is contained in:
Toshio Sekiya 2020-12-21 21:12:05 +09:00
commit 11e0332b84
108 changed files with 10519 additions and 0 deletions

5
.gitignore vendored Normal file
View file

@ -0,0 +1,5 @@
# .gitignore
insertfunc.rb
makehtml.rb
pickoutfunc.rb

55
Rakefile Executable file
View file

@ -0,0 +1,55 @@
require 'rake/clean'
require_relative 'lib/lib_sec_file.rb'
require_relative 'lib/lib_src2md.rb'
srcfiles = []
FileList['src/*.src.md'].each do |file|
srcfiles << Sec_file.new(file)
end
srcfiles = Sec_files.new srcfiles
srcfiles.renum
mdfilenames = srcfiles.map {|srcfile| srcfile.to_md}
CLEAN.append(*mdfilenames)
CLEAN << "Readme.md"
task default: :md
task md: mdfilenames
0.upto(srcfiles.size - 1) do |i|
file srcfiles[i].to_md => (srcfiles[i].c_files << srcfiles[i].path) do
src2md srcfiles[i].path, srcfiles[i].to_md
if srcfiles.size == 1
nav = "Up: [Readme.md](#{srcfiles[i].dirname}/Readme.md)"
elsif i == 0
nav = "Up: [Readme.md](#{srcfiles[i].dirname}/Readme.md), Next: [Section 2}](#{srcfiles[1].path})"
elsif i == srcfiles.size - 1
nav = "Up: [Readme.md](#{srcfiles[i].dirname}/Readme.md), Prev: [Section #{i}}](#{srcfiles[i-1].path})"
else
nav = "Up: [Readme.md](#{srcfiles[i].dirname}/Readme.md), Prev: [Section #{i}}](#{srcfiles[i-1].path}), Next: [Section #{i+2}[(#{srcfiles[i+1].path})"
end
buf = IO.readlines srcfiles[i].to_md
buf.insert(0, nav, "")
buf.append("", nav)
IO.write srcfiles[i].to_md, buf.join
end
end
task :md do
buf = [ "# Gtk4 TUtorial for beginners\n", "\n" ]
buf << "This tutorial is under development and unstable.\n"
buf << "You should be careful because there might exists bugs, errors or mistakes.\n"
buf << "\n"
0.upto(srcfiles.size-1) do |i|
h = File.open(srcfiles[i].path) { |file| file.readline }
h = h.gsub(/^#* */,"").chomp
buf << "- [#{h}](#{srcfiles[i].to_md})\n"
end
File.write("Readme.md", buf.join)
end
task :clean

22
Readme.md Normal file
View file

@ -0,0 +1,22 @@
# Gtk4 TUtorial for beginners
This tutorial is under development and unstable.
You should be careful because there might exists bugs, errors or mistakes.
- [GtkApplication and GtkApplicationWindow](sec1.md)
- [Widgets (1)](sec2.md)
- [Widgets (2)](sec3.md)
- [Widgets (3)](sec4.md)
- [Define Child object](sec5.md)
- [Ui file and GtkBuiler](sec6.md)
- [Build system](sec7.md)
- [Instance and class](sec8.md)
- [Signals](sec9.md)
- [Functions in TfeTextView](sec10.md)
- [Functions with GtkNotebook](sec11.md)
- [tfeapplication.c](sec12.md)
- [tfe5 source files](sec13.md)
- [Menu and action](sec14.md)
- [Stateful action](sec15.md)
- [Ui file for menu and action entries](sec16.md)
- [GtkMenuButton](sec17.md)

151
Readme_for_developers.md Executable file
View file

@ -0,0 +1,151 @@
# How to build Gtk4 Tutorial
## Src.md file and .md file (markdown file)
This tutorial uses 'github flavored markdown', which is often shortened as GFM.
We will call it `markdown' as a simple form in this document.
If you are not familiar with it, refer the website [github flavoer markdown spec](https://github.github.com/gfm/).
However, if you want to generate html or latex using pandoc, you need to use the markdown within the syntax common to GFM and pandoc.
### Definition
Src.md is similar to markdown but it has two commands which isn't included in markdown.
They are @@@ command and $$$ command.
@@@ C\_source\_file \[function_list\]
This command includes the C source file, but if a function list is given, only the functions in the C source file are included.
$$$
shell command
... ...
$$$
This command executes the shell command and substitutes the strings in the standard output for the lines between $$$ inclusive.
These two commands are carried out by scripts like src2md.rb, which is described in the next subsection.
### Conversion
A ruby script src2md converts src.md file to md file.
ruby src2md.rb src.md_file md_file
This script recognizes and carrys out the commands described in the previous subsection.
For example, it is assumed that there are two files sample.src.md and sample.c.
Their contents are as follows.
$ cat sample.src.md
The following is the contents of the file 'sample.c'.
@@@ sample.c
$ cat sample.c
#include <stdio.h>
int
main(int argc, char **argv) {
printf("Hello world.\n");
}
Now, convert sample.src.md to a markdown file sample.md with src2md.rb.
$ ruby src2md.rb sample.src.md sample.md
$ cat sample.md
The following is the contents of the file 'sample.c'.
#include <stdio.h>
int
main(int argc, char **argv) {
printf("Hello world.\n");
}
Compare sample.src.md and sample.md.
The contents of sample.c is substituted for the line `@@@ sample.c`.
These two commands have two advantages.
1. Less typing.
2. You don't need to modify your src.md file, even if the C sourcefile, which is included by @@@ command, is modified.
In the same way, any upgrade of the shell commands described between $$$ commands doesn't affect the src.md file.
There's a method src2md in the src2md.rb script.
This method converts src.md file into md file.
This method is also used in other ruby scripts like Rakefile.
## Directory structure
There are four directories under `gtk4_tutorial` directory.
They are `src`, `image`, `html` and `latex`.
-src: This directory contains src.md files.
-image: This directory contains image files like png or jpg.
-html: This directory is empty at first. A ruby script will convert md files to html files and store them in this directory.
-latex: This directory is empty at first. A ruby script will convert md files to latexl files and store them in this directory.
-lib: This directory includes ruby library files.
### Src and top directories
Src directory contains src.md files.
The top directory, which is gtk_tutorial directory, contains md files correspond to src.md files in src directory.
They are generated by scripts like src2md.rb.
However, usually they are generated by Rakefile.
Md files are generated from src.md files, so most of the lines in each md file corresponds to the lines in the original src.md file.
But some lines in md files don't have their original lines and they are newly generated by ruby scripts.
Those are mainly links to other md files.
In addition, readme.md file, which have title, table of contents and abstract, is generated by ruby script
and it doesn't have an original src.md file.
### The name of files in src directory
Each file in src directory is a section of the whole document.
The name of the files are "sec", number of the section and ".src.md" suffix.
For example, "sec1.src.md", "sec5.src.md" or "sec12.src.md".
They are the files correspond to section 1, section 5 and section 12 respectively.
### C source file directory
Src.md files might have @@@ commands and they include C source files.
Such C source files are located in the src directory or its subdirectories.
Usually, those C files are compiled and tested.
At that time, some auxiliary files and target file like a.out are generated.
If you locate the C source files under src directory, those temporary files make the directory messy.
Therefore, It is a good idea to make subdirectories under src directory and put each C source file under the corresponding subdirectory.
The name of the subdirectories should be independent of section names.
It is because of renumbering, which will be explained in the next subsection.
### Renumbering
Sometimes you want to insert a section.
For example, inserting it between section 4 and section 5.
You can make a temporary section 4.5, that is a rational number between 4 and 5.
However, section numbers are usually integer so it must change to section 5 and the following sections also must be added by one.
This renumbering is done by a ruby script `renumber.rb`.
- It changes file names.
- If there are references to sections in src.md files, the section numbers will be automatically renumbered.
## Rakefile
Rakefile is a similar file to Makefile, but controlled by rake, which is a make-like program written in ruby.
Rakefile has the following tasks.
- md: generate markdown files. This is the default.
- html: generate html files.
- latex: generate latex files and a pdf file, which is generated by latex.
- all: generate md, html, latex and pdf files.
If renumbering is necessary, rake does it before the tasks above.
### Generate markdown files
#### Readme.nd file
#### Section files
#### Cross reference
### Generate html files
### Generate latex files and a pdf file

BIN
image/TfeTextView.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 40 KiB

BIN
image/TfeTextViewClass.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 76 KiB

BIN
image/box.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.7 KiB

BIN
image/child.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 11 KiB

BIN
image/dispose_handler.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 26 KiB

BIN
image/menu.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 13 KiB

BIN
image/menu1.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 22 KiB

BIN
image/menu1_screenshot.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 8.2 KiB

BIN
image/menu2.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 15 KiB

BIN
image/menu3.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 15 KiB

BIN
image/menu_structure.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 25 KiB

BIN
image/open.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 45 KiB

BIN
image/refcount.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 13 KiB

BIN
image/saveas.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 18 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 52 KiB

BIN
image/screenshot_lb1.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.9 KiB

BIN
image/screenshot_lb2.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 20 KiB

BIN
image/screenshot_lb4.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.5 KiB

BIN
image/screenshot_pr3.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.8 KiB

BIN
image/screenshot_pr4.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.2 KiB

BIN
image/screenshot_tfe2.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 43 KiB

BIN
image/screenshot_tfv1.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 22 KiB

BIN
image/screenshot_tfv3.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 27 KiB

BIN
image/window_widget.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 17 KiB

142
lib/lib_sec_file.rb Normal file
View file

@ -0,0 +1,142 @@
class Sec_file < String
def initialize path
if path.instance_of?(String) && File.exist?(path)
@name = File.basename path
@dirname = File.dirname path
unless @name =~ /^sec\d+(\.\d+)?\.(src\.md|md|html|tex)$/
raise "Sec_file class initialization error: #{path} is not Sec_file object name."
end
super(path)
else
raise "Sec_file class initialization error: file #{path} is not exist."
end
end
def type
@name.match(/\.(src\.md|md|html|tex)$/)[1]
end
def path
self
end
def name
@name
end
def dirname
@dirname
end
def c_files
if self.type != "src.md"
return []
else
buf = IO.readlines(self)
files = []
buf.each do |line|
if line =~ /^@@@ (\S+)/
files << @dirname+"/"+$1
end
end
files
end
end
def to_srcmd
@name.gsub(/\.(src\.md|md|html|tex)$/, ".src.md")
end
def to_md
@name.gsub(/\.(src\.md|md|html|tex)$/, ".md")
end
def to_html
@name.gsub(/\.(src\.md|md|html|tex)$/, ".html")
end
def to_tex
@name.gsub(/\.(src\.md|md|html|tex)$/, ".tex")
end
def num
@name.match(/\d+(\.\d+)?/)[0].to_f
end
def <=> other
if other.instance_of?(Sec_file)
self.num <=> other.num
else
nil
end
end
def is_i?
self.num == self.num.floor
end
def renum n
if n.instance_of?(String)
n = n.to_i if n =~ /^\d+$/
n = n.to_f if n =~ /^\d+\.\d+/
end
if n.instance_of?(Integer) || n.instance_of?(Float)
old = self
new = self.gsub(/\d+(\.\d+)?(\.(src\.md|md|html|tex)$)/, "#{n}\\2")
File.rename old, new
self.replace new
@name = File.basename self
@dirname = File.dirname self
end
end
end
class Sec_files < Array
def initialize sec_files
if sec_files.instance_of? Array
sec_files.each do |sec_file|
unless sec_file.instance_of? Sec_file
raise "#{sec_file} is not an instance of Sec_file."
end
end
super(sec_files)
else
raise "#{sec_files} is not an array."
end
end
def renum
self.sort!
tbl = []
n = 1.0
self.each do |sec_file|
tbl << [ sec_file.num, n, sec_file.num == n ? true : false ]
n += 1.0
end
while any_diff?(tbl)
unless try_renum(tbl)
break
end
end
if any_diff?(tbl)
raise "Renumbering failed."
end
end
private
def any_diff? tbl
diff = false
tbl.each do |t|
diff = true if t[2] == false
end
diff
end
def try_renum tbl
changed = false
(self.size - 1).downto 0 do |i|
if tbl[i][2] == false
n = tbl[i][1] # number to substitute
found = false
tbl.each do |t|
if t[0] == n
found = true
break
end
end
unless found # OK to replace
self[i].renum n
tbl[i][0] = n.to_f
tbl[i][2] = true
changed = true
end
end
end
changed
end
end

70
lib/lib_src2md.rb Executable file
View file

@ -0,0 +1,70 @@
# lib_src2md.rb
def src2md srcmd, md
src_buf = IO.readlines srcmd
src_dir = File.dirname srcmd
md_buf = []
comflag = false
src_buf.each do |line|
if comflag
if line == "$$$\n"
comflag = false
else
md_buf << " $ "+line
`cd #{src_dir}; #{line.chomp}`.each_line do |l|
md_buf << l.gsub(/^/," ")
end
end
elsif line == "$$$\n"
comflag = true
elsif line =~ /^@@@\s+(\S+)\s*(.*)\s*$/
c_file = $1
c_functions = $2.split(" ")
if c_file =~ /^\// # absolute path
c_file_buf = IO.readlines(c_file)
else #relative path
c_file_buf = IO.readlines(src_dir+"/"+c_file)
end
if c_functions.empty? # no functions are specified
tmp_buf = c_file_buf
else
tmp_buf = []
spc = false
c_functions.each do |c_function|
from = c_file_buf.find_index { |line| line =~ /^#{c_function} *\(/ }
if ! from
warn "ERROR!!! --- Didn't find #{func} in #{filename}. ---"
break
end
to = from
while to < c_file_buf.size do
if c_file_buf[to] == "}\n"
break
end
to += 1
end
n = from-1
if spc
tmp_buf << "\n"
else
spc = true
end
while n <= to do
tmp_buf << c_file_buf[n]
n += 1
end
end
end
width = tmp_buf.size.to_s.length
n = 1
tmp_buf.each do |l|
md_buf << sprintf(" %#{width}d %s", n, l)
n += 1
end
else
md_buf << line
end
end
IO.write(md,md_buf.join)
end

284
sec1.md Normal file
View file

@ -0,0 +1,284 @@
Up: [Readme.md](src/Readme.md), Next: [Section 2}](src/sec2.src.md)# GtkApplication and GtkApplicationWindow
## GtkApplication
### GtkApplication and g\_application\_run
Usually people write a programming code to make an application.
What are appications?
Applications are software that runs using libraries, which includes OS, frameworks and so on.
In Gtk4 programming, GtkApplication is an object that runs on GTK libraries.
The basic way how to write GtkApplication is as follows.
- Generate a GtkApplication object
- Run it
That's all.
Very simple.
The following is the C code representing the scenario above.
1 #include <gtk/gtk.h>
2
3 int
4 main (int argc, char **argv) {
5 GtkApplication *app;
6 int stat;
7
8 app = gtk_application_new ("com.github.ToshioCP.pr1", G_APPLICATION_FLAGS_NONE);
9 stat =g_application_run (G_APPLICATION (app), argc, argv);
10 g_object_unref (app);
11 return stat;
12 }
13
The first line says that this program includes the GTK header libraries.
The function `main` above is a startup function in C language.
The variable `app` is defined as a pointer to GtkApplication, which is actually a structure in which information about the application is stored.
The function `gtk_application_new` generates a GtkApplication and sets its pointer to `app`.
The meaning of the arguments will be explained later.
The function `g_application_run` invokes the GtkApplication pointed by `app`.
(We often say that the function invokes `app`.
Actually, `app` is not an object but an pointer to the object.
However, it is simple and short, and probably no confusion occurs.)
To compile this, the following command needs to be run.
The string pr1.c is the filename of the C source code.
$ gcc `pkg-config --cflags gtk4` pr1.c `pkg-config --libs gtk4`
The C compiler gcc generates an executable file `a.out`.
Let's run it.
$ ./a.out
(a.out:13533): GLib-GIO-WARNING **: 15:30:17.449: Your application does not implement g_application_activate() and has no handlers connected to the "activate" signal. It should do one of these.
$
Oh, just an error message.
But this error message means that the GtkApplication object ran without a doubt.
Now, think about the message in the next section.
### signal
The message tells us that:
1. The application GtkApplication doesn't implement `g_application_activate()`.
2. And it has no handlers connected to the activate signal.
3. You need to solve at least one of this.
These two cause of the error are related to signals.
So, I will explain it to you first.
Signal is emitted when something happens.
For example, a window is generated, a window is destroyed and so on.
The signal "activate" is emitted when the application is activated.
If the signal is connected to a function, which is called signal handler or simply handler, then the function invokes when the signal emits.
The flow is like this:
1. Something happens.
2. If it's related to a certain signal, then the signal is emitted.
3. If the signal is connected to a handler in advance, then the handler is invoked.
Signals are defined in objects.
For example, "activate" signal belongs to GApplication object, which is a parent object of GtkApplication object.
GApplication object is a child object of GObject object.
GObject is the top object in the hierarchy of all the objects.
GObject -- GApplication -- GtkApplication
<---parent --->child
A child object derives signals, functions, properties and so on from its parent object.
So, Gtkapplication also has the "activate" signal.
Now we can solve the problem in `pr1.c`.
We need to connect the activate signal to a handler.
We use a function `g_signal_connect` which connects a signal to a handler.
1 #include <gtk/gtk.h>
2
3 static void
4 on_activate (GApplication *app, gpointer *user_data) {
5 g_print ("GtkApplication is activated.\n");
6 }
7
8 int
9 main (int argc, char **argv) {
10 GtkApplication *app;
11 int stat;
12
13 app = gtk_application_new ("com.github.ToshioCP.pr2", G_APPLICATION_FLAGS_NONE);
14 g_signal_connect (app, "activate", G_CALLBACK (on_activate), NULL);
15 stat =g_application_run (G_APPLICATION (app), argc, argv);
16 g_object_unref (app);
17 return stat;
18 }
19
First, we define the handler `on_activate` which simply displays a message.
In the function `main`, we add `g_signal_connect` before `g_application_run`.
The function `g_signal_connect` has four arguments.
1. An object to which the signal belongs.
2. The name of the signal.
3. A handler function (also called callback), which needs to be casted by `G_CALLBACK`.
4. Data to pass to the handler. If no data is necessary, NULL should be given.
You can find the description of each signal in API reference.
For example, "activate" signal is in GApplication subsection in GIO API reference.
The handler function is described in that subsection.
In addition, `g_signal_connect` is described in GObject API reference.
API reference is very important.
You should see and understand it to write GTK applications.
Let's compile the source file `pr2.c` above and run it.
$ gcc `pkg-config --cflags gtk4` pr2.c `pkg-config --libs gtk4`
$ ./a.out
GtkApplication is activated.
$
OK, well done.
However, you may have noticed that it's painful to type such a long line to compile.
It is a good idea to use shell script to solve this problem.
Make a text file which contains the following text.
gcc `pkg-config --cflags gtk4` $1.c `pkg-config --libs gtk4`
Then, save it in $HOME/bin, which is usually /home/(username)/bin.
(If your user name is James, then the directory is /home/james/bin).
And turn on the execute bit of the file.
Suppose the filename is comp, then the procedure is as follows.
$ chmod 755 $HOME/bin/comp
$ ls -log $HOME/bin
... ... ...
-rwxr-xr-x 1 62 May 23 08:21 comp
... ... ...
If this is the first time that you make a $HOME/bin directory and save a file in it, then you need to logout and login again.
$ comp pr2
$ ./a.out
GtkApplication is activated.
$
## GtkWindow and GtkApplicationWindow
### GtkWindow
A message "GtkApplication is activated." was printed out in the previous subsection.
It was good in terms of a test of GtkApplication.
However, it is insufficient because GTK is a framework for graphical user interface (GUI).
Now we go ahead with adding a window into this program.
What we need to do is:
1. Generate a GtkWindow.
2. Connect it to GtkApplication.
3. Show the window.
Now rewrite the function `on_activate`.
#### Generate a GtkWindow
1 static void
2 on_activate (GApplication *app, gpointer user_data) {
3 GtkWidget *win;
4
5 win = gtk_window_new ();
6 gtk_window_set_application (GTK_WINDOW (win), GTK_APPLICATION (app));
7 gtk_widget_show (win);
8 }
Widget is an abstract concept that includes all the GUI interfaces such as windows, dialogs, buttons, multiline text, containers and so on.
And GtkWidget is a base object from which all the GUI objects derive.
parent <-----> child
GtkWidget -- GtkWindow
GtkWindow includes GtkWidget at the top of its object.
![GtkWindow and GtkWidget](window_widget.png)
The function `gtk_window_new` is defined as follows.
GtkWidget *
gtk_window_new (void);
By this definition, it returns a pointer to GtkWidget, not GtkWindow.
It actually generates a new GtkWindow object (not GtkWidget) but returns a pointer to GtkWidget.
However,the pointer points the GtkWidget and at the same time it also points GtkWindow that contains GtkWidget in it.
If you want to use `win` as a pointer to the GtkWindow, you need to cast it.
(GtkWindow *) win
Or you can use `GTK_WINDOW` macro that performs a similar function.
GTK_WINDOW (win)
This is a recommended way.
#### Connect it to GtkApplication.
The function `gtk_window_set_application` is used to connect GtkWidow to GtkApplication.
gtk_window_set_application (GTK_WINDOW (win), GTK_APPLICATION (app));
You need to cast `win` to GtkWindow and `app` to GtkApplication.
`GTK_WINDOW` and `GTK_APPLICATION` macro is appropriate for that.
GtkApplication continues to run until the related window is destroyed.
If you didn't connect GtkWindow and GtkApplication, GtkApplication shutdowns soon.
Because no window is connected to GtkApplication, it doesn't need to wait anything.
As it shutdowns the generated window is also destroyed.
#### Show the window.
The function `gtk_widget_show` is used to show the window.
Gtk4 changed the default widget visibility to on, so every widget doesn't need this function to show itself.
But, there's an exception.
Top window (this term will be explained later) isn't visible when it is generated.
So you need to use the function above and show the window.
Save the program as `pr3.c` and compile and run it.
$ comp pr3
$ ./a.out
A small window appears.
![Screenshot of the window](screenshot_pr3.png)
Click on the close button then the window disappears and the program finishes.
### GtkApplicationWindow
GtkApplicationWindow is a child object of GtkWindow.
It has some extra functionality for better integration with GtkApplication.
It is recommended to use it instead of GtkWindow when you use GtkApplication.
Now rewrite the program and use GtkAppliction Window.
1 static void
2 on_activate (GApplication *app, gpointer user_data) {
3 GtkWidget *win;
4
5 win = gtk_application_window_new (GTK_APPLICATION (app));
6 gtk_window_set_title (GTK_WINDOW (win), "pr4");
7 gtk_window_set_default_size (GTK_WINDOW (win), 400, 300);
8 gtk_widget_show (win);
9 }
When you generate GtkApplicationWindow, you need to give GtkApplication object as an argument.
Then it automatically connect these two objects.
So you don't need to call `gtk_window_set_application` any more.
The program sets the title and the default size of the window.
Compile it and run `a.out`, then you will see a bigger window with its title "pr4".
![Screenshot of the window](screenshot_pr4.png)
Up: [Readme.md](src/Readme.md), Next: [Section 2}](src/sec2.src.md)

345
sec10.md Normal file
View file

@ -0,0 +1,345 @@
Up: [Readme.md](src/Readme.md), Prev: [Section 9}](src/sec9.src.md), Next: [Section 11[(src/sec11.src.md)# Functions in TfeTextView
In this section I will explain each function in TfeTextView object.
### tfe.h and tfetextview.h
`tfe.h` is a top header file and it includes `gtk.h` and all the header files.
Every C source files, which are `tfeapplication.c`, `tfenotebook.c` and `tfetextview.c`, include `tfe.h` at the beginning of each file.
1 #include <gtk/gtk.h>
2
3 #include "tfetextview.h"
4 #include "tfenotebook.h"
`tfetextview.h` is a header file which describes the public functions in `tfetextview.c`.
1 #define TFE_TYPE_TEXT_VIEW tfe_text_view_get_type ()
2 G_DECLARE_FINAL_TYPE (TfeTextView, tfe_text_view, TFE, TEXT_VIEW, GtkTextView)
3
4 /* "open-response" signal response */
5 enum
6 {
7 TFE_OPEN_RESPONSE_SUCCESS,
8 TFE_OPEN_RESPONSE_CANCEL,
9 TFE_OPEN_RESPONSE_ERROR
10 };
11
12 GFile *
13 tfe_text_view_get_file (TfeTextView *tv);
14
15 void
16 tfe_text_view_open (TfeTextView *tv);
17
18 void
19 tfe_text_view_save (TfeTextView *tv);
20
21 void
22 tfe_text_view_saveas (TfeTextView *tv);
23
24 GtkWidget *
25 tfe_text_view_new_with_file (GFile *file);
26
27 GtkWidget *
28 tfe_text_view_new (void);
29
- 1-2: These two lines are used to define TfeTextView.
- 4-10: Definitions of parameter used in the handler of "open-response" signal.
- 12-28: Public functions on GtkTextView.
Each function will be explained later in this section.
## Functions to generate TfeTextView object
TfeTextView Object is generated by `tfe_text_view_new` or `tfe_text_view_new_with_file`.
GtkWidget *tfe_text_view_new (void);
`tfe_text_view_new` just generates a new TfeTextView object and returns the pointer to the new object.
GtkWidget *tfe_text_view_new_with_file (GFile *file);
`tfe_text_view_new_with_file` is given a Gfile object as the argument and it loads the file into the GtkTextBuffer object, then returns the pointer to the new object.
Parameter:
- `file`: a pointer to the GFile object.
Return value:
- A pointer to the generated TfeTextView object but it is casted to a pointer to GtkWidget.
If an error occures during the genration process, NULL is returned.
Each function is defined as follows.
1 GtkWidget *
2 tfe_text_view_new_with_file (GFile *file) {
3 g_return_val_if_fail (G_IS_FILE (file), NULL);
4
5 GtkWidget *tv;
6 char *contents;
7 gsize length;
8
9 if (! g_file_load_contents (file, NULL, &contents, &length, NULL, NULL)) /* read error */
10 return NULL;
11
12 tv = tfe_text_view_new();
13 gtk_text_buffer_set_text (TFE_TEXT_VIEW (tv)->tb, contents, length);
14 g_free (contents);
15 TFE_TEXT_VIEW (tv)->file = g_file_dup (file);
16 return tv;
17 }
18
19 GtkWidget *
20 tfe_text_view_new (void) {
21 return gtk_widget_new (TFE_TYPE_TEXT_VIEW, NULL);
22 }
- 18-21: `tfe_text_view_new`.
Just returns the value from the function `gtk_widget_new`.
Initialization is done in `tfe_text_view_init` which is called in the process of `gtk_widget_new` function.
- 1-16: `tfe_text_view_new_with_file`
- 3: `g_return_val_if_fail` is described in [Glib API reference](https://developer.gnome.org/glib/stable/glib-Warnings-and-Assertions.html#g-return-val-if-fail).
It tests whether the argument `file` is a pointer to GFile.
If it's true, then the program goes on to the next line.
If it's false, then it returns NULL (the second argument) immediately.
And at the same time it logs out the error message (usually the log is outputted to stderr or stdout).
This function is used to check the programmer's error.
If an error occurs, the solution is usually to change the (caller) program and fix the bug.
You need to distinguish programmer's errors and runtime errors.
You shouldn't use this function to find runtime errors.
- 9-10: If an error occurs when reading the file, then return NULL.
- 11-15: Generate TfeTextView and set the pointer to it to `tv`.
Set the contents read from the file to GtkTextBuffer `tv->tb`.
Free the memories pointed by `contents`.
Duplicate `file` and set it to `tv->file`.
Return `tv`.
## Save and saveas functions
Save and saveas functions write the contents in GtkTextBuffer to a file.
void tfe_text_view_save (TfeTextView *tv)
`save` function writes the contents in GtkTextBuffer to a file specified by `tv->file`.
If `tv->file` is NULL, then it shows GtkFileChooserDialog and lets the user to give a file to the program. After that, it saves the contents to the specified file and set the file into `tv->file`.
void tfe_text_view_saveas (TfeTextView *tv)
`saveas` function uses GtkFileChooserDialog and lets the user to give a new file to the program. Then, the function changes `tv->file` and save the contents to the specified new file.
If an error occures, it is shown to the user through the message dialog.
The error is managed only in the object and no information is notified to the caller.
1 static void
2 saveas_dialog_response (GtkWidget *dialog, gint response, TfeTextView *tv) {
3 GFile *file;
4
5 if (response == GTK_RESPONSE_ACCEPT) {
6 file = gtk_file_chooser_get_file (GTK_FILE_CHOOSER (dialog));
7 if (G_IS_FILE(file)) {
8 tv->file = file;
9 tv->changed = TRUE;
10 g_signal_emit (tv, tfe_text_view_signals[CHANGE_FILE], 0);
11 tfe_text_view_save (TFE_TEXT_VIEW (tv));
12 }
13 }
14 gtk_window_destroy (GTK_WINDOW (dialog));
15 }
16
17 void
18 tfe_text_view_save (TfeTextView *tv) {
19 g_return_if_fail (TFE_IS_TEXT_VIEW (tv));
20
21 GtkTextIter start_iter;
22 GtkTextIter end_iter;
23 gchar *contents;
24 GtkWidget *message_dialog;
25 GtkWidget *win = gtk_widget_get_ancestor (GTK_WIDGET (tv), GTK_TYPE_WINDOW);
26 GError *err = NULL;
27
28 if (! tv->changed)
29 return; /* no necessary to save it */
30 else if (tv->file == NULL)
31 tfe_text_view_saveas (tv);
32 else {
33 gtk_text_buffer_get_bounds (tv->tb, &start_iter, &end_iter);
34 contents = gtk_text_buffer_get_text (tv->tb, &start_iter, &end_iter, FALSE);
35 if (g_file_replace_contents (tv->file, contents, strlen (contents), NULL, TRUE, G_FILE_CREATE_NONE, NULL, NULL, &err))
36 tv->changed = FALSE;
37 else {
38 /* It is possible that tv->file is broken. */
39 /* It is a good idea to set tv->file to NULL. */
40 if (G_IS_FILE (tv->file))
41 g_object_unref (tv->file);
42 tv->file =NULL;
43 g_signal_emit (tv, tfe_text_view_signals[CHANGE_FILE], 0);
44 tv->changed = TRUE;
45 message_dialog = gtk_message_dialog_new (GTK_WINDOW (win), GTK_DIALOG_MODAL,
46 GTK_MESSAGE_ERROR, GTK_BUTTONS_CLOSE,
47 "%s.\n", err->message);
48 g_signal_connect (message_dialog, "response", G_CALLBACK (gtk_window_destroy), NULL);
49 gtk_widget_show (message_dialog);
50 g_error_free (err);
51 }
52 }
53 }
54
55 void
56 tfe_text_view_saveas (TfeTextView *tv) {
57 g_return_if_fail (TFE_IS_TEXT_VIEW (tv));
58
59 GtkWidget *dialog;
60 GtkWidget *win = gtk_widget_get_ancestor (GTK_WIDGET (tv), GTK_TYPE_WINDOW);
61
62 dialog = gtk_file_chooser_dialog_new ("Save file", GTK_WINDOW (win), GTK_FILE_CHOOSER_ACTION_SAVE,
63 "_Cancel", GTK_RESPONSE_CANCEL,
64 "_Save", GTK_RESPONSE_ACCEPT,
65 NULL);
66 g_signal_connect (dialog, "response", G_CALLBACK (saveas_dialog_response), tv);
67 gtk_widget_show (dialog);
68 }
- 17-53: `Tfe_text_view_save` function.
- 19: If `tv` is not a pointer to TfeTextView, then it logs an error message and immediately returns.
This function is similar to `g_return_val_if_fail` function, but no value is returned because `tfe_text_view_save` doesn't return a value.
- 28-29: If the buffer hasn't modified, then it doesn't need to save it.
So the function returns.
- 30-31: If `tv->file` is NULL, no file has given yet.
It calls `tfe_text_view_save`, which lets the user to choose a file to save.
- 33-35: Save the buffer to the file.
If it succeeds, assigns FALSE to `tv->changed`.
- 38-50: If file writing fails, it assigns NULL to `tv->file`.
Emits "change-file" signal.
Shows the error message dialog (45-49).
Because the handler is `gtk_window_destroy`, the dialog disappears when user clicks on the button in the dialog.
- 55-68: `tfe_text_view_saveas` function.
It shows GtkFileChooserDialog and lets the user choose a file and give it to the signal handler.
- 62: Generate GtkFileChooserDialog.
The title is "Save file".
Transient parent of the dialog is `win`, which is the top level window.
The action is save mode.
The buttons are Cancel and Save.
- 63: connect the "response" signal of the dialog and `saveas_dialog_response` handler.
- 1-15: `saveas_dialog_response` signal handler.
- 5-13: If the response is `GTK_RESPONSE_ACCEPT`, which is set to the argument when the user has clicked on Save button, then gets a pointer to the GFile object, set it to `tv->file`, assign TRUE to `tv->changed`, emits "change-file" signal then call `tfe_text_view_save` to save the buffer to the file.
![Saveas process](saveas.png)
When you use GtkFileChooserDialog, you need to divide the program into two parts.
They are a function which generates GtkFileChooserDialog and the signal handler.
The function just generates and shows the dialog.
The rest is done by the handler.
It gets Gfile from GtkFileChooserDialog, save the buffer to the file and do some things necessary.
## Open function
Open function shows GtkFileChooserDialog to the user and let him/her choose a file.
Then read the file and set it to GtkTextBuffer.
void tfe_text_view_open (TfeTextView *tv)
TfeTextView object `tv` has to be generated in advance.
And it should be empty and `tv->file` is NULL.
If it is not empty, `tfe_text_view_open` doesn't treat it as an error.
If you want to revert the buffer, calling this function is apropreate.
Otherwise probably bad things will happen.
1 static void
2 open_dialog_response(GtkWidget *dialog, gint response, TfeTextView *tv) {
3 GFile *file;
4 char *contents;
5 gsize length;
6 GtkWidget *message_dialog;
7 GError *err = NULL;
8
9 if (response != GTK_RESPONSE_ACCEPT)
10 g_signal_emit (tv, tfe_text_view_signals[OPEN_RESPONSE], 0, TFE_OPEN_RESPONSE_CANCEL);
11 else if (! G_IS_FILE (file = gtk_file_chooser_get_file (GTK_FILE_CHOOSER (dialog))))
12 g_signal_emit (tv, tfe_text_view_signals[OPEN_RESPONSE], 0, TFE_OPEN_RESPONSE_ERROR);
13 else if (! g_file_load_contents (file, NULL, &contents, &length, NULL, &err)) { /* read error */
14 if (G_IS_FILE (file))
15 g_object_unref (file);
16 message_dialog = gtk_message_dialog_new (GTK_WINDOW (dialog), GTK_DIALOG_MODAL,
17 GTK_MESSAGE_ERROR, GTK_BUTTONS_CLOSE,
18 "%s.\n", err->message);
19 g_signal_connect (message_dialog, "response", G_CALLBACK (gtk_window_destroy), NULL);
20 gtk_widget_show (message_dialog);
21 g_error_free (err);
22 g_signal_emit (tv, tfe_text_view_signals[OPEN_RESPONSE], 0, TFE_OPEN_RESPONSE_ERROR);
23 } else {
24 gtk_text_buffer_set_text (tv->tb, contents, length);
25 g_free (contents);
26 tv->file = file;
27 /* tv->changed = FALSE;*/
28 g_signal_emit (tv, tfe_text_view_signals[OPEN_RESPONSE], 0, TFE_OPEN_RESPONSE_SUCCESS);
29 }
30 gtk_window_destroy (GTK_WINDOW (dialog));
31 }
32
33 void
34 tfe_text_view_open (TfeTextView *tv) {
35 g_return_if_fail (TFE_IS_TEXT_VIEW (tv));
36
37 GtkWidget *dialog;
38
39 dialog = gtk_file_chooser_dialog_new ("Open file", NULL, GTK_FILE_CHOOSER_ACTION_OPEN,
40 "Cancel", GTK_RESPONSE_CANCEL,
41 "Open", GTK_RESPONSE_ACCEPT,
42 NULL);
43 g_signal_connect (dialog, "response", G_CALLBACK (open_dialog_response), tv);
44 gtk_widget_show (dialog);
45 }
- 33-45: `tfe_text_view_open` function.
- 39: Generate GtkFileChooserDialog.
The title is "Open file".
No transient parent window.
The action is open mode.
The buttons are Cancel and Open.
- 43: connect the "reponse" signal of the dialog and `open_dialog_response` signal handler.
- 44: Show the dialog.
- 1-31: `open_dialog_response` signal handler.
- 9-10: If the response from GtkFileChooserDialog is not `GTK_RESPONSE_ACCEPT`, which means the user has clicked on the "Cancel" button or close button, then it emits "open-response" signal with the parameter `TFE_OPEN_RESPONSE_CANCEL`.
- 11-12: Get a pointer to Gfile by `gtk_file_chooser_get_file`.
If it is not GFile, maybe an error occured.
Then it emits "open-response" signal with the parameter `TFE_OPEN_RESPONSE_ERROR`.
- 13-22: If an error occurs when it has read the file, then it decreases the reference count of Gfile, shows a message dialog to report the error to the user and emits "open-response" signal with the parameter `TFE_OPEN_RESPONSE_ERROR`.
- 24-28: If the file has successfully read, then the text is set to GtkTextBuffer, free the temporary buffer pointed by `contents`, set file to `tv->file` (no duplication or unref is not necessary) and emits "open-response" signal with the parameter `TFE_OPEN_RESPONSE_SUCCESS`.
- 30: close GtkFileCooserDialog.
Now let's think about the whole process between the other object (caller) and TfeTextView.
It is shown in the following diagram and you would think that it is really complicated.
Because signal is the only way for GtkFileChooserDialog to communicate with others.
In Gtk3, `gtk_dialog_run` function is available.
It simplifies the process.
However, in Gtk4, `gtk_dialog_run`is unavailable any more.
![Caller and TfeTextView](open.png)
1. A caller get a pointer `tv` to TfeTextView by calling `tfe_text_view_new`.
2. The caller connects the handler (left bottom in the diagram) and the signal "open-response".
3. It calls `tfe_text_view_open` to let the user select a file from GtkFileChooserDialog.
4. The dialog emits a signal and it invokes the handler `open_dialog_response`.
5. The handler read the file and set it into GtkTextBuffer and emits a signal to inform the response status.
6. The handler outside TfeTextView recieves the signal.
## Get file function
`gtk_text_view_get_file` is a simple function show as follows.
1 GFile *
2 tfe_text_view_get_file (TfeTextView *tv) {
3 g_return_val_if_fail (TFE_IS_TEXT_VIEW (tv), NULL);
4
5 return g_file_dup (tv->file);
6 }
The important thing is duplicate `tv->file`.
Otherwise, if the caller free the GFile object, `tv->file` is no more guaranteed to point the GFile.
## Source file of tfetextview.c
All the source files are listed in [Section 13](ch13.html).
Up: [Readme.md](src/Readme.md), Prev: [Section 9}](src/sec9.src.md), Next: [Section 11[(src/sec11.src.md)

204
sec11.md Normal file
View file

@ -0,0 +1,204 @@
Up: [Readme.md](src/Readme.md), Prev: [Section 10}](src/sec10.src.md), Next: [Section 12[(src/sec12.src.md)# Functions with GtkNotebook
GtkNotebook is a very important object in the text file editor `tfe`.
It connects the application and TfeTextView objects.
`tfenotebook.h` and `tfenotebook.c` describe a set of functions related to GtkTextbook.
1 void
2 notebook_page_save(GtkNotebook *nb);
3
4 void
5 notebook_page_open (GtkNotebook *nb);
6
7 void
8 notebook_page_new_with_file (GtkNotebook *nb, GFile *file);
9
10 void
11 notebook_page_new (GtkNotebook *nb);
12
This header file shows the public functions in `tfenotebook.c`.
- `notebook_page_new` generates a new GtkNotebookPage and adds GtkScrolledWindow and TfeTextView under the page.
- `notebook_page_new_with_file` generates a new GtkNotebookPage and adds GtkScrolledWindow and TfeTextView under the page. `file` is set to the pointer to GFile in the TfeTextView object and the file is read and set into GtkTextBuffer.
- `notebook_page_open` lets the user select a file and sets it into GtkTextBuffer.
- `notebook_page_save` save the contents in GtkTextBuffer to a file, using the pointer `tv->file`.
You probably find that the functions above are higher level functions of `tfe_text_view_new`, `tfe_text_view_new_with_file`, `tef_text_view_open` and `tfe_text_view_save` respectively.
There are two layers.
One of them is `tfe_text_view ...`, which is the lower level layer.
The other is `note_book ...`, which is the higher level layer.
Now let's look at each program of the functions.
## notebook\_page\_new
1 static gchar*
2 get_untitled () {
3 static int c = -1;
4 if (++c == 0)
5 return g_strdup_printf("Untitled");
6 else
7 return g_strdup_printf ("Untitled%u", c);
8 }
9
10 static void
11 notebook_page_build (GtkNotebook *nb, GtkWidget *tv, char *filename) {
12 GtkWidget *scr;
13 GtkNotebookPage *nbp;
14 GtkWidget *lab;
15 gint i;
16 scr = gtk_scrolled_window_new ();
17
18 gtk_scrolled_window_set_child (GTK_SCROLLED_WINDOW (scr), tv);
19 lab = gtk_label_new (filename);
20 i = gtk_notebook_append_page (nb, scr, lab);
21 nbp = gtk_notebook_get_page (nb, scr);
22 g_object_set (nbp, "tab-expand", TRUE, NULL);
23 gtk_notebook_set_current_page (nb, i);
24 g_signal_connect (GTK_TEXT_VIEW (tv), "change-file", G_CALLBACK (file_changed), nb);
25 }
26
27 void
28 notebook_page_new (GtkNotebook *nb) {
29 g_return_if_fail(GTK_IS_NOTEBOOK (nb));
30
31 GtkWidget *tv;
32 char *filename;
33
34 tv = tfe_text_view_new ();
35 filename = get_untitled ();
36 notebook_page_build (nb, tv, filename);
37 }
- 27-37: `notebook_page_new` function.
- 29: `g_return_if_fail` is used because `notebook_page_new` is a public function.
- 34: Generate TfeTextView object.
- 35: Generate filename, which is "Untitled", "Untitled2", ... .
- 1-8: `get_untitled` function.
- 3: Static variable `c` is initialized at the first call of this function. After that `c` keeps its value except it is changed explicitly.
- 4-7: Increase `c` by one and if it is zero then the name is "Untitled". If it is a positive integer then the name is "Untitled<the integer>", for example, "Untitled1", "Untitled2", and so on.
It returns the name.
`g_strdup_printf` generates a string and it should be freed by `g_free` function.
The caller of `get_untitled` is in charge of freeing the memories of the string.
- 36: call `notebook_page_build` to build the contents of the page.
- 10- 25: `notebook_page_build` function.
- 17-18: Generate GtkScrolledWindow and set `tv` to its child.
- 19-20: Generate GtkLabel, then GtkNotebookPage.
- 21-22: Set "tab-expand" property to TRUE.
- 23: Set the page to the current page.
- 24: Connect "change-file" signal and `file_changed` handler.
## notebook\_page\_new\_with\_file
1 void
2 notebook_page_new_with_file (GtkNotebook *nb, GFile *file) {
3 g_return_if_fail(GTK_IS_NOTEBOOK (nb));
4 g_return_if_fail(G_IS_FILE (file));
5
6 GtkWidget *tv;
7 char *filename;
8
9 if ((tv = tfe_text_view_new_with_file (file)) == NULL)
10 return; /* read error */
11 filename = g_file_get_basename (file);
12 notebook_page_build (nb, tv, filename);
13 }
- 9-10: Call `tfe_text_view_new_with_file`.
If it returns NULL, then do nothing and return because of an error.
-11-13: Get the filename , build the contents of the page, then free `filename`.
## notebook\_page\_open
1 static void
2 open_response (TfeTextView *tv, gint response, GtkNotebook *nb) {
3 GFile *file;
4 char *filename;
5
6 if (response != TFE_OPEN_RESPONSE_SUCCESS)
7 g_object_unref (tv);
8 else if (! G_IS_FILE (file = tfe_text_view_get_file (tv)))
9 g_object_unref (tv);
10 else {
11 filename = g_file_get_basename (file);
12 g_object_unref (file);
13 notebook_page_build (nb, GTK_WIDGET (tv), filename);
14 }
15 }
16
17 void
18 notebook_page_open (GtkNotebook *nb) {
19 g_return_if_fail(GTK_IS_NOTEBOOK (nb));
20
21 GtkWidget *tv;
22
23 tv = tfe_text_view_new ();
24 g_signal_connect (TFE_TEXT_VIEW (tv), "open-response", G_CALLBACK (open_response), nb);
25 tfe_text_view_open (TFE_TEXT_VIEW (tv));
26 }
- 18-27: `notebook_page_open` function.
- 24: Generate TfeTextView object.
- 25: Connect the signal "open-response" and the handler `open_response`.
- 26: Call `tfe_text_view_open`.
It emits "open-response" signal to inform the status after the series of functions run.
- 1-16: `open_response` handler.
This is the postfunction of `notebook_page_open`.
- 6-7: It the status is NOT `TFE_OPEN_RESPONSE_SUCCESS`, cancel what we did in `notebook_page_open`.
Unref `tv`.
- 8-9: If `tfe_text_view_get_file` returns a pointer not to point GFile, then something bad happens. Cancel what we did. Unref `tv`.
- 10-14: Otherwise, everything was okay.
Get the filename, build the contents of the page, free `filename` and unref `tv`
## notebook\_page\_save
1 void
2 notebook_page_save(GtkNotebook *nb) {
3 gint i;
4 GtkWidget *scr;
5 GtkWidget *tv;
6
7 i = gtk_notebook_get_current_page (nb);
8 scr = gtk_notebook_get_nth_page (nb, i);
9 tv = gtk_scrolled_window_get_child (GTK_SCROLLED_WINDOW (scr));
10 tfe_text_view_save (TFE_TEXT_VIEW (tv));
11 }
- 7-9: Get TfeTextView belongs to the current notebook page.
- 10: Call `tfe_text_view_save`.
## file\_changed handler
`file_changed` is a handler connected to "change-file" signal.
If `tv->file` is changed, TfeTextView emits this signal.
This handler changes the label of GtkNotebookPage.
1 static void
2 file_changed (TfeTextView *tv, GtkNotebook *nb) {
3 GFile *file;
4 char *filename;
5 GtkWidget *scr;
6 GtkWidget *label;
7
8 file = tfe_text_view_get_file (tv);
9 scr = gtk_widget_get_parent (GTK_WIDGET (tv));
10 if (G_IS_FILE (file))
11 filename = g_file_get_basename (file);
12 else
13 filename = get_untitled ();
14 label = gtk_label_new (filename);
15 gtk_notebook_set_tab_label (nb, scr, label);
16 g_object_unref (file);
17 g_free (filename);
18 }
- 8: Get GFile from TfeTextView.
- 9: Get the parent (GkScrolledWindow) of `tv`.
- 10-13: If `file` points GFile, then assign the filename of the GFile into `filename`.
Otherwise (this is the case file is NULL), assign untitled string to `filename`.
- 14-15: Generate a label with the filename and set it into GtkNotebookPage.
- 16-17: Free `filename and unref `file`.
Up: [Readme.md](src/Readme.md), Prev: [Section 10}](src/sec10.src.md), Next: [Section 12[(src/sec12.src.md)

269
sec12.md Normal file
View file

@ -0,0 +1,269 @@
Up: [Readme.md](src/Readme.md), Prev: [Section 11}](src/sec11.src.md), Next: [Section 13[(src/sec13.src.md)# tfeapplication.c
`tfeapplication.c` includes all the code other than `tfetxtview.c` and `tfenotebook.c`.
It does following things.
- Application support, mainly handling command line arguments.
- Build widgets using ui file.
- Connect button signals and their handlers.
- Manage CSS.
## main
Th function `main` is the first invoked function in C language.
It connects the command line given by the user and GTK application.
1 int
2 main (int argc, char **argv) {
3 GtkApplication *app;
4 int stat;
5
6 app = gtk_application_new ("com.github.ToshioCP.tfe", G_APPLICATION_HANDLES_OPEN);
7
8 g_signal_connect (app, "startup", G_CALLBACK (tfe_startup), NULL);
9 g_signal_connect (app, "activate", G_CALLBACK (tfe_activate), NULL);
10 g_signal_connect (app, "open", G_CALLBACK (tfe_open), NULL);
11
12 stat =g_application_run (G_APPLICATION (app), argc, argv);
13 g_object_unref (app);
14 return stat;
15 }
- 6: Generate GtkApplication object.
- 8-10: Connect "startup", "activate" and "open signals to their handlers.
- 12: Run the application.
- 13-14: release the reference to the application and return the status.
## statup signal handler
"startup" signal is emitted just after the application is generated.
What the signal handler needs to do is initialization of the application.
- Build the widgets using ui file.
- Connect button signals and their handlers.
- Set CSS.
The handler is as follows.
1 static void
2 tfe_startup (GApplication *application) {
3 GtkApplication *app = GTK_APPLICATION (application);
4 GtkApplicationWindow *win;
5 GtkNotebook *nb;
6 GtkBuilder *build;
7 GtkButton *btno;
8 GtkButton *btnn;
9 GtkButton *btns;
10 GtkButton *btnc;
11
12 build = gtk_builder_new_from_resource ("/com/github/ToshioCP/tfe/tfe.ui");
13 win = GTK_APPLICATION_WINDOW (gtk_builder_get_object (build, "win"));
14 nb = GTK_NOTEBOOK (gtk_builder_get_object (build, "nb"));
15 gtk_window_set_application (GTK_WINDOW (win), app);
16 btno = GTK_BUTTON (gtk_builder_get_object (build, "btno"));
17 btnn = GTK_BUTTON (gtk_builder_get_object (build, "btnn"));
18 btns = GTK_BUTTON (gtk_builder_get_object (build, "btns"));
19 btnc = GTK_BUTTON (gtk_builder_get_object (build, "btnc"));
20 g_signal_connect (btno, "clicked", G_CALLBACK (open_clicked), nb);
21 g_signal_connect (btnn, "clicked", G_CALLBACK (new_clicked), nb);
22 g_signal_connect (btns, "clicked", G_CALLBACK (save_clicked), nb);
23 g_signal_connect (btnc, "clicked", G_CALLBACK (close_clicked), nb);
24 g_object_unref(build);
25
26 GdkDisplay *display;
27
28 display = gtk_widget_get_display (GTK_WIDGET (win));
29 GtkCssProvider *provider = gtk_css_provider_new ();
30 gtk_css_provider_load_from_data (provider, "textview {padding: 10px; font-family: monospace; font-size: 12pt;}", -1);
31 gtk_style_context_add_provider_for_display (display, GTK_STYLE_PROVIDER (provider), GTK_STYLE_PROVIDER_PRIORITY_USER);
32 }
- 12-15: Build widgets using ui file (resource).
Connect the top window and the application using `gtk_window_set_application`.
- 16-23: Get buttons and connect their signals and handlers.
- 24: Release the reference to GtkBuilder.
- 26-31: Set CSS.
CSS in GTK is similar to CSS in HTML.
You can set margin, border, padding, color, font and so on with CSS.
In this program CSS is in line 30.
It sets padding, font-family and font size of GtkTextView.
- 26-28: GdkDisplay is used to set CSS.
CSS will be explained in the next subsection.
## CSS in GTK
CSS is an abbretiation of Cascading Style Sheet.
It is originally used with HTML to describe the presentation semantics of a document.
You might have found that the widgets in GTK is simialr to the window in a browser.
It implies that CSS can also be apllied to GTK windowing system.
### CSS nodes, selectors
The syntax of CSS is as follws.
selector { color: yellow; padding-top: 10px; ...}
Every widget has CSS node.
For example GtkTextView has `textview` node.
If you want to set style to GtkTextView, set "textview" to the selector.
textview {color: yeallow; ...}
Class, ID and some other things can be applied to the selector like Web CSS. Refer GTK4 API reference for further information.
In line 30, the CSS is a string.
textview {padding: 10px; font-family: monospace; font-size: 12pt;}
- padding is a space between the border and contents.
This space makes the text easier to read.
- font-family is a name of font.
"monospace" is one of the generic family font keywords.
- font-size is set to 12pt.
It is a bit large, but easy on the eyes especially for elderly people.
### GtkStyleContext, GtkCSSProvider and GdkDisplay
GtkStyleContext is an object that stores styling information affecting a widget.
Each widget is connected to the corresponding GtkStyleContext.
You can get the context by `gtk_widget_get_style_context`.
GtkCssProvider is an object which parses CSS in order to style widgets.
To apply your CSS to wodgets, you need to add GtkStyleProvider (the interface of GtkCSSProvider) to GtkStyleContext.
However, instead, you can add it to GdkDisplay of the window (usually top level window).
Look at the source file of `startup` handler again.
- 28: The display is obtained by `gtk_widget_get_display`.
- 29: Generate GtkCssProvider.
- 30: Set the CSS into the provider.
- 31: Add the provider to the display.
It is possible to add the provider to the context of GtkTextView instead of GdkDiplay.
To do so, rewrite `tfe_text_view_new`.
GtkWidget *
tfe_text_view_new (void) {
GtkWidget *tv;
tv = gtk_widget_new (TFE_TYPE_TEXT_VIEW, NULL);
GtkStyleContext *context;
context = gtk_widget_get_style_context (GTK_WIDGET (tv));
GtkCssProvider *provider = gtk_css_provider_new ();
gtk_css_provider_load_from_data (provider, "textview {padding: 10px; font-family: monospace; font-size: 12pt;}", -1);
gtk_style_context_add_provider (context, GTK_STYLE_PROVIDER (provider), GTK_STYLE_PROVIDER_PRIORITY_USER);
return tv;
}
CSS set to the context takes precedence over the one set to the display.
## activate and open handler
The handler of "activate" and "open" signal are `tfe_activate` and `tfe_open` respectively.
They just generate a new GtkNotebookPage.
1 static void
2 tfe_activate (GApplication *application) {
3 GtkApplication *app = GTK_APPLICATION (application);
4 GtkWidget *win;
5 GtkWidget *boxv;
6 GtkNotebook *nb;
7
8 win = GTK_WIDGET (gtk_application_get_active_window (app));
9 boxv = gtk_window_get_child (GTK_WINDOW (win));
10 nb = GTK_NOTEBOOK (gtk_widget_get_last_child (boxv));
11
12 notebook_page_new (nb);
13 gtk_widget_show (GTK_WIDGET (win));
14 }
15
16 static void
17 tfe_open (GApplication *application, GFile ** files, gint n_files, const gchar *hint) {
18 GtkApplication *app = GTK_APPLICATION (application);
19 GtkWidget *win;
20 GtkWidget *boxv;
21 GtkNotebook *nb;
22 int i;
23
24 win = GTK_WIDGET (gtk_application_get_active_window (app));
25 boxv = gtk_window_get_child (GTK_WINDOW (win));
26 nb = GTK_NOTEBOOK (gtk_widget_get_last_child (boxv));
27
28 for (i = 0; i < n_files; i++)
29 notebook_page_new_with_file (nb, files[i]);
30 if (gtk_notebook_get_n_pages (nb) == 0)
31 notebook_page_new (nb);
32 gtk_widget_show (win);
33 }
- 1-14: `tfe_activate`.
- 8-10: Get GtkNotebook object.
- 12-13: Generate a new GtkNotebookPage and show the window.
- 16-33: `tfe_open`.
- 24-26: Get GtkNotebook object.
- 28-29: Generate GtkNotebookPage with files.
- 30-31: If opening and reading file failed and no GtkNotebookPage has generated, then generate a empty page.
- 32: Show the window.
These codes have become really simple thanks to tfenotebook.c and tfetextview.c.
## a series of handlers correspond to the button signals
1 static void
2 open_clicked (GtkWidget *btno, GtkNotebook *nb) {
3 notebook_page_open (nb);
4 }
5
6 static void
7 new_clicked (GtkWidget *btnn, GtkNotebook *nb) {
8 notebook_page_new (nb);
9 }
10
11 static void
12 save_clicked (GtkWidget *btns, GtkNotebook *nb) {
13 notebook_page_save (nb);
14 }
15
16 static void
17 close_clicked (GtkWidget *btnc, GtkNotebook *nb) {
18 GtkWidget *win;
19 GtkWidget *boxv;
20 gint i;
21
22 if (gtk_notebook_get_n_pages (nb) == 1) {
23 boxv = gtk_widget_get_parent (GTK_WIDGET (nb));
24 win = gtk_widget_get_parent (boxv);
25 gtk_window_destroy (GTK_WINDOW (win));
26 } else {
27 i = gtk_notebook_get_current_page (nb);
28 gtk_notebook_remove_page (GTK_NOTEBOOK (nb), i);
29 }
30 }
`open_clicked`, `new_clicked` and `save_clicked` just call corresponding notebook page functions.
`close_clicked` is a bit complicated.
- 22-25: If there's only one page, closing the last page is considered that it also close the top level window and quit the application.
Therefore, it gets the top level window and call `gtk_window_destroy`.
- 26-28: Otherwise, it removes the current page.
## meson.build
1 project('tfe', 'c')
2
3 gtkdep = dependency('gtk4')
4
5 gnome=import('gnome')
6 resources = gnome.compile_resources('resources','tfe.gresource.xml')
7
8 sourcefiles=files('tfeapplication.c', 'tfenotebook.c', 'tfetextview.c')
9
10 executable('tfe', sourcefiles, resources, dependencies: gtkdep)
This file is just modified the source file names.
Up: [Readme.md](src/Readme.md), Prev: [Section 11}](src/sec11.src.md), Next: [Section 13[(src/sec13.src.md)

619
sec13.md Normal file
View file

@ -0,0 +1,619 @@
Up: [Readme.md](src/Readme.md), Prev: [Section 12}](src/sec12.src.md), Next: [Section 14[(src/sec14.src.md)# tfe5 source files
The followings are the source files of tfe5.
## meson.buld
1 project('tfe', 'c')
2
3 gtkdep = dependency('gtk4')
4
5 gnome=import('gnome')
6 resources = gnome.compile_resources('resources','tfe.gresource.xml')
7
8 sourcefiles=files('tfeapplication.c', 'tfenotebook.c', 'tfetextview.c')
9
10 executable('tfe', sourcefiles, resources, dependencies: gtkdep)
## tfe.gresource.xml
1 <?xml version="1.0" encoding="UTF-8"?>
2 <gresources>
3 <gresource prefix="/com/github/ToshioCP/tfe">
4 <file>tfe.ui</file>
5 </gresource>
6 </gresources>
## tfe.ui
1 <interface>
2 <object class="GtkApplicationWindow" id="win">
3 <property name="title">file editor</property>
4 <property name="default-width">600</property>
5 <property name="default-height">400</property>
6 <child>
7 <object class="GtkBox" id="boxv">
8 <property name="orientation">GTK_ORIENTATION_VERTICAL</property>
9 <child>
10 <object class="GtkBox" id="boxh">
11 <property name="orientation">GTK_ORIENTATION_HORIZONTAL</property>
12 <child>
13 <object class="GtkLabel" id="dmy1">
14 <property name="width-chars">10</property>
15 </object>
16 </child>
17 <child>
18 <object class="GtkButton" id="btnn">
19 <property name="label">_New</property>
20 <property name="use-underline">TRUE</property>
21 </object>
22 </child>
23 <child>
24 <object class="GtkButton" id="btno">
25 <property name="label">_Open</property>
26 <property name="use-underline">TRUE</property>
27 </object>
28 </child>
29 <child>
30 <object class="GtkLabel" id="dmy2">
31 <property name="hexpand">TRUE</property>
32 </object>
33 </child>
34 <child>
35 <object class="GtkButton" id="btns">
36 <property name="label">_Save</property>
37 <property name="use-underline">TRUE</property>
38 </object>
39 </child>
40 <child>
41 <object class="GtkButton" id="btnc">
42 <property name="label">_Close</property>
43 <property name="use-underline">TRUE</property>
44 </object>
45 </child>
46 <child>
47 <object class="GtkLabel" id="dmy3">
48 <property name="width-chars">10</property>
49 </object>
50 </child>
51 </object>
52 </child>
53 <child>
54 <object class="GtkNotebook" id="nb">
55 <property name="scrollable">TRUE</property>
56 <property name="hexpand">TRUE</property>
57 <property name="vexpand">TRUE</property>
58 </object>
59 </child>
60 </object>
61 </child>
62 </object>
63 </interface>
64
## tfe.h
1 #include <gtk/gtk.h>
2
3 #include "tfetextview.h"
4 #include "tfenotebook.h"
## tfeapplication.c
1 #include "tfe.h"
2
3 static void
4 open_clicked (GtkWidget *btno, GtkNotebook *nb) {
5 notebook_page_open (nb);
6 }
7
8 static void
9 new_clicked (GtkWidget *btnn, GtkNotebook *nb) {
10 notebook_page_new (nb);
11 }
12
13 static void
14 save_clicked (GtkWidget *btns, GtkNotebook *nb) {
15 notebook_page_save (nb);
16 }
17
18 static void
19 close_clicked (GtkWidget *btnc, GtkNotebook *nb) {
20 GtkWidget *win;
21 GtkWidget *boxv;
22 gint i;
23
24 if (gtk_notebook_get_n_pages (nb) == 1) {
25 boxv = gtk_widget_get_parent (GTK_WIDGET (nb));
26 win = gtk_widget_get_parent (boxv);
27 gtk_window_destroy (GTK_WINDOW (win));
28 } else {
29 i = gtk_notebook_get_current_page (nb);
30 gtk_notebook_remove_page (GTK_NOTEBOOK (nb), i);
31 }
32 }
33
34 static void
35 tfe_activate (GApplication *application) {
36 GtkApplication *app = GTK_APPLICATION (application);
37 GtkWidget *win;
38 GtkWidget *boxv;
39 GtkNotebook *nb;
40
41 win = GTK_WIDGET (gtk_application_get_active_window (app));
42 boxv = gtk_window_get_child (GTK_WINDOW (win));
43 nb = GTK_NOTEBOOK (gtk_widget_get_last_child (boxv));
44
45 notebook_page_new (nb);
46 gtk_widget_show (GTK_WIDGET (win));
47 }
48
49 static void
50 tfe_open (GApplication *application, GFile ** files, gint n_files, const gchar *hint) {
51 GtkApplication *app = GTK_APPLICATION (application);
52 GtkWidget *win;
53 GtkWidget *boxv;
54 GtkNotebook *nb;
55 int i;
56
57 win = GTK_WIDGET (gtk_application_get_active_window (app));
58 boxv = gtk_window_get_child (GTK_WINDOW (win));
59 nb = GTK_NOTEBOOK (gtk_widget_get_last_child (boxv));
60
61 for (i = 0; i < n_files; i++)
62 notebook_page_new_with_file (nb, files[i]);
63 if (gtk_notebook_get_n_pages (nb) == 0)
64 notebook_page_new (nb);
65 gtk_widget_show (win);
66 }
67
68
69 static void
70 tfe_startup (GApplication *application) {
71 GtkApplication *app = GTK_APPLICATION (application);
72 GtkApplicationWindow *win;
73 GtkNotebook *nb;
74 GtkBuilder *build;
75 GtkButton *btno;
76 GtkButton *btnn;
77 GtkButton *btns;
78 GtkButton *btnc;
79
80 build = gtk_builder_new_from_resource ("/com/github/ToshioCP/tfe/tfe.ui");
81 win = GTK_APPLICATION_WINDOW (gtk_builder_get_object (build, "win"));
82 nb = GTK_NOTEBOOK (gtk_builder_get_object (build, "nb"));
83 gtk_window_set_application (GTK_WINDOW (win), app);
84 btno = GTK_BUTTON (gtk_builder_get_object (build, "btno"));
85 btnn = GTK_BUTTON (gtk_builder_get_object (build, "btnn"));
86 btns = GTK_BUTTON (gtk_builder_get_object (build, "btns"));
87 btnc = GTK_BUTTON (gtk_builder_get_object (build, "btnc"));
88 g_signal_connect (btno, "clicked", G_CALLBACK (open_clicked), nb);
89 g_signal_connect (btnn, "clicked", G_CALLBACK (new_clicked), nb);
90 g_signal_connect (btns, "clicked", G_CALLBACK (save_clicked), nb);
91 g_signal_connect (btnc, "clicked", G_CALLBACK (close_clicked), nb);
92 g_object_unref(build);
93
94 GdkDisplay *display;
95
96 display = gtk_widget_get_display (GTK_WIDGET (win));
97 GtkCssProvider *provider = gtk_css_provider_new ();
98 gtk_css_provider_load_from_data (provider, "textview {padding: 10px; font-family: monospace; font-size: 12pt;}", -1);
99 gtk_style_context_add_provider_for_display (display, GTK_STYLE_PROVIDER (provider), GTK_STYLE_PROVIDER_PRIORITY_USER);
100 }
101
102 int
103 main (int argc, char **argv) {
104 GtkApplication *app;
105 int stat;
106
107 app = gtk_application_new ("com.github.ToshioCP.tfe", G_APPLICATION_HANDLES_OPEN);
108
109 g_signal_connect (app, "startup", G_CALLBACK (tfe_startup), NULL);
110 g_signal_connect (app, "activate", G_CALLBACK (tfe_activate), NULL);
111 g_signal_connect (app, "open", G_CALLBACK (tfe_open), NULL);
112
113 stat =g_application_run (G_APPLICATION (app), argc, argv);
114 g_object_unref (app);
115 return stat;
116 }
117
### tfenotebook.h
1 void
2 notebook_page_save(GtkNotebook *nb);
3
4 void
5 notebook_page_open (GtkNotebook *nb);
6
7 void
8 notebook_page_new_with_file (GtkNotebook *nb, GFile *file);
9
10 void
11 notebook_page_new (GtkNotebook *nb);
12
## tfenotebook.c
1 #include "tfe.h"
2
3 /* The returned string should be freed with g_free() when no longer needed. */
4 static gchar*
5 get_untitled () {
6 static int c = -1;
7 if (++c == 0)
8 return g_strdup_printf("Untitled");
9 else
10 return g_strdup_printf ("Untitled%u", c);
11 }
12
13 static void
14 file_changed (TfeTextView *tv, GtkNotebook *nb) {
15 GFile *file;
16 char *filename;
17 GtkWidget *scr;
18 GtkWidget *label;
19
20 file = tfe_text_view_get_file (tv);
21 scr = gtk_widget_get_parent (GTK_WIDGET (tv));
22 if (G_IS_FILE (file))
23 filename = g_file_get_basename (file);
24 else
25 filename = get_untitled ();
26 label = gtk_label_new (filename);
27 gtk_notebook_set_tab_label (nb, scr, label);
28 g_object_unref (file);
29 g_free (filename);
30 }
31
32 /* Save the contents in the current page */
33 void
34 notebook_page_save(GtkNotebook *nb) {
35 gint i;
36 GtkWidget *scr;
37 GtkWidget *tv;
38
39 i = gtk_notebook_get_current_page (nb);
40 scr = gtk_notebook_get_nth_page (nb, i);
41 tv = gtk_scrolled_window_get_child (GTK_SCROLLED_WINDOW (scr));
42 tfe_text_view_save (TFE_TEXT_VIEW (tv));
43 }
44
45 static void
46 notebook_page_build (GtkNotebook *nb, GtkWidget *tv, char *filename) {
47 GtkWidget *scr;
48 GtkNotebookPage *nbp;
49 GtkWidget *lab;
50 gint i;
51 scr = gtk_scrolled_window_new ();
52
53 gtk_scrolled_window_set_child (GTK_SCROLLED_WINDOW (scr), tv);
54 lab = gtk_label_new (filename);
55 i = gtk_notebook_append_page (nb, scr, lab);
56 nbp = gtk_notebook_get_page (nb, scr);
57 g_object_set (nbp, "tab-expand", TRUE, NULL);
58 gtk_notebook_set_current_page (nb, i);
59 g_signal_connect (GTK_TEXT_VIEW (tv), "change-file", G_CALLBACK (file_changed), nb);
60 }
61
62 static void
63 open_response (TfeTextView *tv, gint response, GtkNotebook *nb) {
64 GFile *file;
65 char *filename;
66
67 if (response != TFE_OPEN_RESPONSE_SUCCESS)
68 g_object_unref (tv);
69 else if (! G_IS_FILE (file = tfe_text_view_get_file (tv)))
70 g_object_unref (tv);
71 else {
72 filename = g_file_get_basename (file);
73 g_object_unref (file);
74 notebook_page_build (nb, GTK_WIDGET (tv), filename);
75 }
76 }
77
78 void
79 notebook_page_open (GtkNotebook *nb) {
80 g_return_if_fail(GTK_IS_NOTEBOOK (nb));
81
82 GtkWidget *tv;
83
84 tv = tfe_text_view_new ();
85 g_signal_connect (TFE_TEXT_VIEW (tv), "open-response", G_CALLBACK (open_response), nb);
86 tfe_text_view_open (TFE_TEXT_VIEW (tv));
87 }
88
89 void
90 notebook_page_new_with_file (GtkNotebook *nb, GFile *file) {
91 g_return_if_fail(GTK_IS_NOTEBOOK (nb));
92 g_return_if_fail(G_IS_FILE (file));
93
94 GtkWidget *tv;
95 char *filename;
96
97 if ((tv = tfe_text_view_new_with_file (file)) == NULL)
98 return; /* read error */
99 filename = g_file_get_basename (file);
100 notebook_page_build (nb, tv, filename);
101 }
102
103 void
104 notebook_page_new (GtkNotebook *nb) {
105 g_return_if_fail(GTK_IS_NOTEBOOK (nb));
106
107 GtkWidget *tv;
108 char *filename;
109
110 tv = tfe_text_view_new ();
111 filename = get_untitled ();
112 notebook_page_build (nb, tv, filename);
113 }
114
## tfetextview.h
1 #define TFE_TYPE_TEXT_VIEW tfe_text_view_get_type ()
2 G_DECLARE_FINAL_TYPE (TfeTextView, tfe_text_view, TFE, TEXT_VIEW, GtkTextView)
3
4 /* "open-response" signal response */
5 enum
6 {
7 TFE_OPEN_RESPONSE_SUCCESS,
8 TFE_OPEN_RESPONSE_CANCEL,
9 TFE_OPEN_RESPONSE_ERROR
10 };
11
12 GFile *
13 tfe_text_view_get_file (TfeTextView *tv);
14
15 void
16 tfe_text_view_open (TfeTextView *tv);
17
18 void
19 tfe_text_view_save (TfeTextView *tv);
20
21 void
22 tfe_text_view_saveas (TfeTextView *tv);
23
24 GtkWidget *
25 tfe_text_view_new_with_file (GFile *file);
26
27 GtkWidget *
28 tfe_text_view_new (void);
29
## tfetextview.c
1 #include "tfe.h"
2
3 struct _TfeTextView
4 {
5 GtkTextView parent;
6 GtkTextBuffer *tb;
7 GFile *file;
8 gboolean changed;
9 };
10
11 G_DEFINE_TYPE (TfeTextView, tfe_text_view, GTK_TYPE_TEXT_VIEW);
12
13 enum {
14 CHANGE_FILE,
15 OPEN_RESPONSE,
16 NUMBER_OF_SIGNALS
17 };
18
19 static guint tfe_text_view_signals[NUMBER_OF_SIGNALS];
20
21 /* Signal handler */
22 static void
23 on_changed (GtkTextBuffer *tb, TfeTextView *tv) {
24 tv->changed=TRUE;
25 }
26
27 static void
28 tfe_text_view_dispose (GObject *gobject) {
29 TfeTextView *tv = TFE_TEXT_VIEW (gobject);
30
31 if (G_IS_FILE (tv->file))
32 g_clear_object (&tv->file);
33
34 G_OBJECT_CLASS (tfe_text_view_parent_class)->dispose (gobject);
35 }
36
37 static void
38 tfe_text_view_init (TfeTextView *tv) {
39 tv->tb = gtk_text_view_get_buffer (GTK_TEXT_VIEW (tv));
40 tv->file = NULL;
41 tv->changed = FALSE;
42 gtk_text_view_set_wrap_mode (GTK_TEXT_VIEW (tv), GTK_WRAP_WORD_CHAR);
43 g_signal_connect (tv->tb, "changed", G_CALLBACK (on_changed), tv);
44 }
45
46 static void
47 tfe_text_view_class_init (TfeTextViewClass *class) {
48 GObjectClass *object_class = G_OBJECT_CLASS (class);
49
50 object_class->dispose = tfe_text_view_dispose;
51 tfe_text_view_signals[CHANGE_FILE] = g_signal_newv ("change-file",
52 G_TYPE_FROM_CLASS (class),
53 G_SIGNAL_RUN_LAST | G_SIGNAL_NO_RECURSE | G_SIGNAL_NO_HOOKS,
54 NULL /* closure */,
55 NULL /* accumulator */,
56 NULL /* accumulator data */,
57 NULL /* C marshaller */,
58 G_TYPE_NONE /* return_type */,
59 0 /* n_params */,
60 NULL /* param_types */);
61 GType param_types[] = {G_TYPE_INT};
62 tfe_text_view_signals[OPEN_RESPONSE] = g_signal_newv ("open-response",
63 G_TYPE_FROM_CLASS (class),
64 G_SIGNAL_RUN_LAST | G_SIGNAL_NO_RECURSE | G_SIGNAL_NO_HOOKS,
65 NULL /* closure */,
66 NULL /* accumulator */,
67 NULL /* accumulator data */,
68 NULL /* C marshaller */,
69 G_TYPE_NONE /* return_type */,
70 1 /* n_params */,
71 param_types);
72 }
73
74 GFile *
75 tfe_text_view_get_file (TfeTextView *tv) {
76 g_return_val_if_fail (TFE_IS_TEXT_VIEW (tv), NULL);
77
78 return g_file_dup (tv->file);
79 }
80
81 static void
82 open_dialog_response(GtkWidget *dialog, gint response, TfeTextView *tv) {
83 GFile *file;
84 char *contents;
85 gsize length;
86 GtkWidget *message_dialog;
87 GError *err = NULL;
88
89 if (response != GTK_RESPONSE_ACCEPT)
90 g_signal_emit (tv, tfe_text_view_signals[OPEN_RESPONSE], 0, TFE_OPEN_RESPONSE_CANCEL);
91 else if (! G_IS_FILE (file = gtk_file_chooser_get_file (GTK_FILE_CHOOSER (dialog))))
92 g_signal_emit (tv, tfe_text_view_signals[OPEN_RESPONSE], 0, TFE_OPEN_RESPONSE_ERROR);
93 else if (! g_file_load_contents (file, NULL, &contents, &length, NULL, &err)) { /* read error */
94 if (G_IS_FILE (file))
95 g_object_unref (file);
96 message_dialog = gtk_message_dialog_new (GTK_WINDOW (dialog), GTK_DIALOG_MODAL,
97 GTK_MESSAGE_ERROR, GTK_BUTTONS_CLOSE,
98 "%s.\n", err->message);
99 g_signal_connect (message_dialog, "response", G_CALLBACK (gtk_window_destroy), NULL);
100 gtk_widget_show (message_dialog);
101 g_error_free (err);
102 g_signal_emit (tv, tfe_text_view_signals[OPEN_RESPONSE], 0, TFE_OPEN_RESPONSE_ERROR);
103 } else {
104 gtk_text_buffer_set_text (tv->tb, contents, length);
105 g_free (contents);
106 tv->file = file;
107 /* tv->changed = FALSE;*/
108 g_signal_emit (tv, tfe_text_view_signals[OPEN_RESPONSE], 0, TFE_OPEN_RESPONSE_SUCCESS);
109 }
110 gtk_window_destroy (GTK_WINDOW (dialog));
111 }
112
113 void
114 tfe_text_view_open (TfeTextView *tv) {
115 g_return_if_fail (TFE_IS_TEXT_VIEW (tv));
116
117 GtkWidget *dialog;
118
119 dialog = gtk_file_chooser_dialog_new ("Open file", NULL, GTK_FILE_CHOOSER_ACTION_OPEN,
120 "Cancel", GTK_RESPONSE_CANCEL,
121 "Open", GTK_RESPONSE_ACCEPT,
122 NULL);
123 g_signal_connect (dialog, "response", G_CALLBACK (open_dialog_response), tv);
124 gtk_widget_show (dialog);
125 }
126
127 static void
128 saveas_dialog_response (GtkWidget *dialog, gint response, TfeTextView *tv) {
129 GFile *file;
130
131 if (response == GTK_RESPONSE_ACCEPT) {
132 file = gtk_file_chooser_get_file (GTK_FILE_CHOOSER (dialog));
133 if (G_IS_FILE(file)) {
134 tv->file = file;
135 tv->changed = TRUE;
136 g_signal_emit (tv, tfe_text_view_signals[CHANGE_FILE], 0);
137 tfe_text_view_save (TFE_TEXT_VIEW (tv));
138 }
139 }
140 gtk_window_destroy (GTK_WINDOW (dialog));
141 }
142
143 void
144 tfe_text_view_save (TfeTextView *tv) {
145 g_return_if_fail (TFE_IS_TEXT_VIEW (tv));
146
147 GtkTextIter start_iter;
148 GtkTextIter end_iter;
149 gchar *contents;
150 GtkWidget *message_dialog;
151 GtkWidget *win = gtk_widget_get_ancestor (GTK_WIDGET (tv), GTK_TYPE_WINDOW);
152 GError *err = NULL;
153
154 if (! tv->changed)
155 return; /* no necessary to save it */
156 else if (tv->file == NULL)
157 tfe_text_view_saveas (tv);
158 else {
159 gtk_text_buffer_get_bounds (tv->tb, &start_iter, &end_iter);
160 contents = gtk_text_buffer_get_text (tv->tb, &start_iter, &end_iter, FALSE);
161 if (g_file_replace_contents (tv->file, contents, strlen (contents), NULL, TRUE, G_FILE_CREATE_NONE, NULL, NULL, &err))
162 tv->changed = FALSE;
163 else {
164 /* It is possible that tv->file is broken. */
165 /* It is a good idea to set tv->file to NULL. */
166 if (G_IS_FILE (tv->file))
167 g_object_unref (tv->file);
168 tv->file =NULL;
169 g_signal_emit (tv, tfe_text_view_signals[CHANGE_FILE], 0);
170 tv->changed = TRUE;
171 message_dialog = gtk_message_dialog_new (GTK_WINDOW (win), GTK_DIALOG_MODAL,
172 GTK_MESSAGE_ERROR, GTK_BUTTONS_CLOSE,
173 "%s.\n", err->message);
174 g_signal_connect (message_dialog, "response", G_CALLBACK (gtk_window_destroy), NULL);
175 gtk_widget_show (message_dialog);
176 g_error_free (err);
177 }
178 }
179 }
180
181 void
182 tfe_text_view_saveas (TfeTextView *tv) {
183 g_return_if_fail (TFE_IS_TEXT_VIEW (tv));
184
185 GtkWidget *dialog;
186 GtkWidget *win = gtk_widget_get_ancestor (GTK_WIDGET (tv), GTK_TYPE_WINDOW);
187
188 dialog = gtk_file_chooser_dialog_new ("Save file", GTK_WINDOW (win), GTK_FILE_CHOOSER_ACTION_SAVE,
189 "_Cancel", GTK_RESPONSE_CANCEL,
190 "_Save", GTK_RESPONSE_ACCEPT,
191 NULL);
192 g_signal_connect (dialog, "response", G_CALLBACK (saveas_dialog_response), tv);
193 gtk_widget_show (dialog);
194 }
195
196 GtkWidget *
197 tfe_text_view_new_with_file (GFile *file) {
198 g_return_val_if_fail (G_IS_FILE (file), NULL);
199
200 GtkWidget *tv;
201 char *contents;
202 gsize length;
203
204 if (! g_file_load_contents (file, NULL, &contents, &length, NULL, NULL)) /* read error */
205 return NULL;
206
207 tv = tfe_text_view_new();
208 gtk_text_buffer_set_text (TFE_TEXT_VIEW (tv)->tb, contents, length);
209 g_free (contents);
210 TFE_TEXT_VIEW (tv)->file = g_file_dup (file);
211 return tv;
212 }
213
214 GtkWidget *
215 tfe_text_view_new (void) {
216 return gtk_widget_new (TFE_TYPE_TEXT_VIEW, NULL);
217 }
218
## Total number of lines, words and charcters
$ wc tfe5/meson.build tfe5/tfeapplication.c tfe5/tfe.gresource.xml tfe5/tfe.h tfe5/tfenotebook.c tfe5/tfenotebook.h tfe5/tfetextview.c tfe5/tfetextview.h tfe5/tfe.ui
10 17 279 tfe5/meson.build
117 348 3576 tfe5/tfeapplication.c
6 9 153 tfe5/tfe.gresource.xml
4 6 72 tfe5/tfe.h
114 311 2870 tfe5/tfenotebook.c
12 17 196 tfe5/tfenotebook.h
218 622 7454 tfe5/tfetextview.c
29 47 545 tfe5/tfetextview.h
64 105 2266 tfe5/tfe.ui
574 1482 17411 合計
Up: [Readme.md](src/Readme.md), Prev: [Section 12}](src/sec12.src.md), Next: [Section 14[(src/sec14.src.md)

215
sec14.md Normal file
View file

@ -0,0 +1,215 @@
Up: [Readme.md](src/Readme.md), Prev: [Section 13}](src/sec13.src.md), Next: [Section 15[(src/sec15.src.md)# Menu and action
## Menu
Users often use menus to tell the command to the computer.
It is like this:
![Menu](menu.png)
Now let's analyze the menu above.
There are two types of object.
- "File", "Edit", "View", "Cut", "Copy", "Paste" and "Select All".
They are called "menu item" or simply "item".
When the user clicks one of these items, then something will happen.
- Menubar, submenu referenced by "Edit" item and two sections.
They are called "menu".
Menu is an ordered list of items.
They are similar to arrays.
![Menu structure](menu_structure.png)
- Menubar is a menu which has three items, which are "File", "Edit" and "View".
- The menu item labeled "Edit" has a link to the submenu which has two items.
These two items don't have labels.
Each item refers to a section.
- The first section is a menu which has three items -- "Cut", "Copy" and "Paste".
- The second section is a menu which has one item -- "Select All".
Menus can build a complicated structure thanks to the links of menu items.
## GMenuModel, GMenu and GMenuItem
GMenuModel is an abstact object which represents a menu.
GMenu is a simple implementation of GMenuModel and a child object of GMenuModel.
GObjct -- GMenuModel -- GMenu
Because GMenuModel is an abstract object, it doesn't have any functions to generate it.
Therefore, if you want to generate a menu, use `g_menu_new` function to generate GMenu object.
GMenu inherits all the functions of GMenuModel because of the child object.
GMenuItem is an object directly derived from GObject.
GMenuItem and Gmenu (or GMenuModel) don't have a parent-child relationship.
GObject -- GMenuModel -- GMenu
GObject -- GMenuItem
Usually, GMenuItem has attributes.
One of the attributes is label.
For example, there is a menu item which has "Edit" label in the first diagram in this section.
"Cut", "Copy", "Paste" and "Select All" are also the lables of menu items.
Other attributes will be explained later.
Some menu items have a link to another GMenu.
There are two types of links, submenu and section.
GMenuItem can be inserted, appended or prepended to GMenu.
When it is inserted, all of the attribute and link values of the item are copied and used to form a new item within the menu.
The GMenuItem itself is not really inserted.
Therefore, after the insertion, GMenuItem is useless and it should be freed.
The same goes for appending or prepending.
The following code shows how to append GMenuItem to GMenu.
GMenu *menu = g_menu_new ();
GMenuItem *menu_item_quit = g_menu_item_new ("Quit", "app.quit");
g_menu_append_item (menu, menu_item_quit);
g_object_unref (menu_item_quit);
## Menu and action
One of the attributes of menu items is an action.
This attribute points an action object.
There are two action objects, GSimpleAction and GPropertyAction.
GSimpleAction is often used.
And it is used with a menu item.
Only GSimpleAction is described in this section.
An action corresponds to a menu item will be activated when the menu item is clicked.
Then the action emits an activate signal.
1. menu item is clicked.
2. The corresponding action is activated.
3. The action emits a signal.
4. The connected handler is invoked.
The following code is an example.
static void
quit_activated(GSimpleAction *action, GVariant *parameter, gpointer app) { ... ... ...}
GSimpleAction *act_quit = g_simple_action_new ("quit", NULL);
g_signal_connect (act_quit, "activate", G_CALLBACK (quit_activated), app);
GMenuItem *menu_item_quit = g_menu_item_new ("Quit", "app.quit");
1. `menu_item_quit` is a menu item.
It has a label "Quit" and is connected to an action "app.quit".
"app" is a prefix and "quit" is the name of an action.
The prefix means that the action belongs to GtkApplication.
If the menu is clicked, then the corresponding action "quit" which belongs to GtkApplication will be activated.
2. `act_quit` is an action.
It has a name "quit".
It belongs to GtkApplication, but it is not obvious in the code above.
The function `g_simple_action_new` generates a stateless action.
So, `act_quit` is stateless.
The meaning of stateless will be explained later.
The argument `NULL` means that the action doesn't have an parameter.
Generally, most of the actions are stateless and have no parameter.
When `act_quit` is activated, it will emit "activate" signal.
3. "activate" signal of the action is connected to the handler `quit_activated`.
So, if the action is activated, the handler will be invoked.
## Simple example
The following is a simple example of menus and actions.
1 #include <gtk/gtk.h>
2
3 static void
4 quit_activated(GSimpleAction *action, GVariant *parameter, gpointer app)
5 {
6 g_application_quit (G_APPLICATION(app));
7 }
8
9 static void
10 on_activate (GApplication *app, gpointer user_data) {
11 GtkWidget *win = gtk_application_window_new (GTK_APPLICATION (app));
12 gtk_window_set_title (GTK_WINDOW (win), "menu1");
13 gtk_window_set_default_size (GTK_WINDOW (win), 400, 300);
14
15 GSimpleAction *act_quit = g_simple_action_new ("quit", NULL);
16 g_action_map_add_action (G_ACTION_MAP (app), G_ACTION (act_quit));
17 g_signal_connect (act_quit, "activate", G_CALLBACK (quit_activated), app);
18
19 GMenu *menubar = g_menu_new ();
20 GMenuItem *menu_item_menu = g_menu_item_new ("Menu", NULL);
21 GMenu *menu = g_menu_new ();
22 GMenuItem *menu_item_quit = g_menu_item_new ("Quit", "app.quit");
23 g_menu_append_item (menu, menu_item_quit);
24 g_object_unref (menu_item_quit);
25 g_menu_item_set_submenu (menu_item_menu, G_MENU_MODEL (menu));
26 g_menu_append_item (menubar, menu_item_menu);
27 g_object_unref (menu_item_menu);
28
29 gtk_application_set_menubar (GTK_APPLICATION (app), G_MENU_MODEL (menubar));
30 gtk_application_window_set_show_menubar (GTK_APPLICATION_WINDOW (win), TRUE);
31 gtk_window_present (GTK_WINDOW (win));
32 /* gtk_widget_show (win); is also OKay instead of gtk_window_present. */
33 }
34
35 int
36 main (int argc, char **argv) {
37 GtkApplication *app;
38 int stat;
39
40 app = gtk_application_new ("com.github.ToshioCP.menu1", G_APPLICATION_FLAGS_NONE);
41 g_signal_connect (app, "activate", G_CALLBACK (on_activate), NULL);
42
43 stat =g_application_run (G_APPLICATION (app), argc, argv);
44 g_object_unref (app);
45 return stat;
46 }
47
- 3-7: `quit_activated` is a handler of an action `act_quit`.
Handlers of actions have three parameters.
1. The action object which has emitted the signal.
2. Parameter.
In this example it is `NULL` because the second argument of `g_simple_action_new` (line 15) is `NULL`.
You don' t need to care about it.
3. User data.
It is the fourth parameter in the `g_signal_connect` (line 17) that has connected the action and the handler.
- 6: A function `g_application_quit` immediately quits the application.
- 9-33: `on_activate` is a handler of "activate" signal on GtkApplication.
- 11-13: Generate a GtkApplicationWindow and set a pointer to it to `win`. And set the title and default size.
- 15: Generate GSimpleAction `act_quit`.
It is stateless.
The first argument of `g_simple_action_new` is a name of the action and the second argument is a parameter.
If you don't need the parameter, set it `NULL`.
THerefore, `act_quit` has a name "quit" and no parameter.
- 16: Add the action to GtkApplication `app`.
GtkApplication implements an interface GActionMap and GActionGroup.
And GtkApplication can have a group of actions and actions are added by the function `g_action_map_add_action`.
This function is described in GMenuModel section in GIO API reference.
- 17: Connect "activate" signal of the action and the handler `quit_activated`.
- 19-22: Generate GMenu and GMenuItem.
`menubar` and `menu` are GMenu.
`menu_item_menu` and `menu_item_quit` are GMenuItem.
`menu_item_menu` has a label "Menu" and no action.
`menu_item_quit` has a label "Quit".
The second argument "app.quit" is a combination of "app" and "quit".
"app" is a prefix and it means that the action belongs to GtkApplication. "quit" is the name of the action.
Therefore, it points the action which belongs to GtkApplication and has the name "quit" -- it is `act_quit`.
- 23-24: Append `act_quit` to `menu`.
As I mentioned before, all the attribute and link values are copied and used to form a new item within `menu`.
Therefore after the appending, `menu` has a copy of `act_quit` in itself and `act_quit` is no longer needed.
It is freed by `g_object_unref`.
- 25: Set a submenu link to `menu_item_menu`.
And the link points the GMenu `menu`.
- 26-27: Append `menu_item_menu` to `menubar`.
Then free `menu_item_menu`.
GMenu and GMenuItem are connected and finally a menu is made up.
The structure of the menu is shown in the diagram below.
- 29: The menu is set to GtkApplication.
- 30: Set GtkApplicationWindow to show the menubar.
- 31: Show the window.
![menu and action](menu1.png)
![Screenshot of menu1](menu1_screenshot.png)
Up: [Readme.md](src/Readme.md), Prev: [Section 13}](src/sec13.src.md), Next: [Section 15[(src/sec15.src.md)

369
sec15.md Normal file
View file

@ -0,0 +1,369 @@
Up: [Readme.md](src/Readme.md), Prev: [Section 14}](src/sec14.src.md), Next: [Section 16[(src/sec16.src.md)# Stateful action
Some actions have states.
The values of states can be boolean or string.
Actions which have states are called stateful.
## Stateful action without a parameter
Some menus are called toggle menu.
For example, fullscreen menu has a state which has two values -- fullscreen and non-fullscreen.
The value of the state is changed every time the menu is clicked.
An action corresponds to the fullscreen menu also have a state.
Its value is TRUE or FALSE and it is called boolean value.
TRUE corresponds to fullscreen and FALSE to non-fullscreen.
The following is an example code to implement a fullscreen menu except the signal handler.
The signal handler will be described after the explanation of this code.
static void
on_activate (GApplication *app, gpointer user_data) {
... ... ...
GSimpleAction *act_fullscreen = g_simple_action_new_stateful ("fullscreen", NULL, g_variant_new_boolean (FALSE));
GMenuItem *menu_item_fullscreen = g_menu_item_new ("Full Screen", "win.fullscreen");
g_signal_connect (act_fullscreen, "change-state", G_CALLBACK (fullscreen_changed), win);
... ... ...
}
- `act_fullscreen` is GSimpleAction.
It is generated by `g_simple_action_new_stateful`.
The function has three arguments.
The first argument "fullscreen" is the name of the action.
The second argument is a parameter type.
`NULL` means the action doesn't have a parameter.
The third argument is the initial state of the action.
It is a GVariant value.
GVariant will be explained in the next subsection.
The function `g_variant_new_boolean (FALSE)` returns a GVariant value which is the boolean value `FALSE`.
- `menu_item_fullscreen` is GMenuItem.
There are two arguments.
The first argument "Full Screen" is a label which is one of the attributes of GMenuItem.
The second argument is called detailed action.
Detailed action has three parts, prefix, action name and target.
"win.fullscreen" means that the prefix is "win", the action name is "fullscreen" and there's no target.
The prefix says that the action belongs to the window.
- connect the action `act_fullscreen` and the "change-state" signal handler `fullscreen_`value2`changed`.
If the fullscreen menu is clicked, then the corresponding action `act_fullscreen` is activated.
But no handler is connected to "activate" signal.
Then, the default behaviour for boolean-stated actions with a NULL parameter type like `act_fullscreen` is to toggle them via the “change-state” signal.
The following is the "change-state" signal handler.
static void
fullscreen_changed(GSimpleAction *action, GVariant *value, gpointer win) {
if (g_variant_get_boolean (value))
gtk_window_maximize (GTK_WINDOW (win));
else
gtk_window_unmaximize (GTK_WINDOW (win));
g_simple_action_set_state (action, value);
}
- There are three parameters.
The first parameter is the action which emits the "change-state" signal.
The second parameter is the value of the state of the action.
But it is toggled because of no "activate" signal handler.
Ther third parameter is a user data which is set in `g_signal_connect`.
- If the value is boolean type and `TRUE`, then maximize the window.
Otherwise unmaximize.
- Set `value` to the state of the action.
Note: the second argument was the toggled state value, but at this stage the state of the action has the original value.
So, you need to set the new value by `g_simple_action_set_state`.
You can use "activate" signal instead ot "change-state" signal, or both signals.
But the way above is the simplest and best.
### GVariant
GVarient can contain boolean, string or other simple type values.
For example, the following program set TRUE to `value` whose type is GVariant.
GVariant *value = g_variant_new_boolean (TRUE);
Another example is:
GVariant *value2 = g_variant_new_string ("Hello");
`value2` is a GVariant and it has a string type value "Hello".
GVariant can contain other types like int16, int32, int64, double and so on.
If you want to get the boolean value, use g\_variant\_get series functions.
gboolean bool = g_variant_get_boolean (value);
Because `value` has been generated as a boolean type GVariant and `TRUE` value, `bool` equals `TRUE`.
In the same way, you can get a string from `value2`
const gchar *str = g_variant_get_string (value2, NULL);
The second parameter is a pointer to gsize type variable (gsize is defined as unsigned long).
If it isn't NULL, then the length of the string will be set by the function.
If it is NULL, nothing happens.
The returned string `str` can't be changed.
## Stateful action with a parameter
Another example of stateful actions is an action corresponds to color select menus.
For example, there are three menus and each menu has red, green or blue color respectively.
They determine the background color of a certain widget.
One action is connected to the three menus.
The action has a state which values are "red", "green" and "blue".
The values are string.
Those colors are given to the signal handler as a parameter.
static void
on_activate (GApplication *app, gpointer user_data) {
... ... ...
GSimpleAction *act_color = g_simple_action_new_stateful ("color", g_variant_type_new("s"), g_variant_new_string ("red"));
GMenuItem *menu_item_red = g_menu_item_new ("Red", "win.color::red");
GMenuItem *menu_item_green = g_menu_item_new ("Green", "win.color::green");
GMenuItem *menu_item_blue = g_menu_item_new ("Blue", "win.color::blue");
g_signal_connect (act_color, "activate", G_CALLBACK (color_activated), win);
... ... ...
}
- `act_color` is GSimpleAction.
It is generated by `g_simple_action_new_stateful`.
The function has three arguments.
The first argument "color" is the name of the action.
The second argument is a parameter type which is GVariantType.
`g_variant_type_new("s")` generates GVariantType which is a string type (G\_VARIANT\_TYPE\_STRING).
The third argument is the initial state of the action.
It is a GVariant.
GVariantType will be explained in the next subsection.
The function `g_variant_new_string ("red")` returns a GVariant value which has the string value "red".
- `menu_item_red` is GMenuItem.
There are two arguments.
The first argument "Red" is a label which is one of the attributes of GMenuItem.
The second argument is a detailed action.
Its prefix is "win", action name is "color" and target is "red".
Target is sent to the action as a parameter.
The same goes for `menu_item_green` and `menu_item_blue`.
- connect the action `act_color` and the "activate" signal handler `color_activate`.
If one of the three menus is clicked, then the action `act_color` is activated with a parameter to which the menu item gives its target.
No handler is connected to "change-state" signal.
Then the default behaviour is to call `g_simple_action_set_state()` to set the state to the requested value.
The following is the "activate" signal handler.
static void
color_activated(GSimpleAction *action, GVariant *parameter, gpointer win) {
gchar *color = g_strdup_printf ("label#lb {background-color: %s;}", g_variant_get_string (parameter, NULL));
gtk_css_provider_load_from_data (provider, color, -1);
g_free (color);
g_action_change_state (G_ACTION (action), parameter);
}
- There are three parameters.
The first parameter is the action which emits the "activate" signal.
The second parameter is the parameter given to the action.
It is a color specified by the menu.
The third parameter is a user data which is set in `g_signal_connect`.
- `color` is a CSS string generated by `g_strdup_printf`.
The parameter of `g_str_dup` is the same as printf C standard function.
`g_variant_get_string` get the string contained in `parameter`.
- Set the color to the css provider.
- Free the string `color`.
- Change the state by `g_action_change_state`.
The function just set the parameter to the state of the action by `g_simple_action_set_state`.
Therefore, you can use `g_simple_action_set_state` instead of `g_action_change_state`.
Note: If you have set a "change-state" signal handler, `g_action_change_state` will emit "change-state" signal instead of calling `g_simple_action_set_state`.
### GVariantType
GVariantType gives a type of GVariant.
GVariant can contain many kinds of types.
And the type often needs to be recognized at runtime.
GVariantType provides such functionality.
When GVariantType is generated, the type is expressed by the string.
- "b" means boolean type.
- "s" means string type.
The following program is a simple example.
It finally output the string "s".
1 #include <glib.h>
2
3 int
4 main (int argc, char **argv) {
5 GVariantType *vtype = g_variant_type_new ("s");
6 const gchar *type_string = g_variant_type_peek_string (vtype);
7 g_print ("%s\n",type_string);
8 }
- `g_variant_tpe_new` generates GVariantType.
It uses a type string "s" which means string.
- `g_variant_type_peek_string` takes a peek at `vtype`.
It is the string "s" given at the generation time.
- print the string to the terminal.
## Example code
The following code includes stateful actions above.
This program has menus like this:
![menu2](menu2.png)
- Fullscreen menu toggles the size of the window between maximum and non-maximum.
If the window is maximum size, which is called full screen, then a check mark is put before "fullscreen" label.
- Red, green and blue menu determines the back ground color of the label, which is the child widget of the window.
The menus have radio buttons on the left of each of the menus.
And the radio button of the selected menu turns on.
- Quit menu quits the application.
The code is as follows.
1 #include <gtk/gtk.h>
2
3 static GtkCssProvider *provider;
4
5 static void
6 fullscreen_changed(GSimpleAction *action, GVariant *value, gpointer win) {
7 if (g_variant_get_boolean (value))
8 gtk_window_maximize (GTK_WINDOW (win));
9 else
10 gtk_window_unmaximize (GTK_WINDOW (win));
11 g_simple_action_set_state (action, value);
12 }
13
14 static void
15 color_activated(GSimpleAction *action, GVariant *parameter, gpointer win) {
16 gchar *color = g_strdup_printf ("label#lb {background-color: %s;}", g_variant_get_string (parameter, NULL));
17 gtk_css_provider_load_from_data (provider, color, -1);
18 g_free (color);
19 g_action_change_state (G_ACTION (action), parameter);
20 }
21
22 static void
23 quit_activated(GSimpleAction *action, GVariant *parameter, gpointer app)
24 {
25 g_application_quit (G_APPLICATION(app));
26 }
27
28 static void
29 on_activate (GApplication *app, gpointer user_data) {
30 GtkWidget *win = gtk_application_window_new (GTK_APPLICATION (app));
31 gtk_window_set_title (GTK_WINDOW (win), "menu2");
32 gtk_window_set_default_size (GTK_WINDOW (win), 400, 300);
33
34 GtkWidget *lb = gtk_label_new (NULL);
35 gtk_widget_set_name (lb, "lb"); /* the name is used by CSS Selector */
36 gtk_window_set_child (GTK_WINDOW (win), lb);
37
38 GSimpleAction *act_fullscreen
39 = g_simple_action_new_stateful ("fullscreen", NULL, g_variant_new_boolean (FALSE));
40 GSimpleAction *act_color
41 = g_simple_action_new_stateful ("color", g_variant_type_new("s"), g_variant_new_string ("red"));
42 GSimpleAction *act_quit
43 = g_simple_action_new ("quit", NULL);
44
45 GMenu *menubar = g_menu_new ();
46 GMenu *menu = g_menu_new ();
47 GMenu *section1 = g_menu_new ();
48 GMenu *section2 = g_menu_new ();
49 GMenu *section3 = g_menu_new ();
50 GMenuItem *menu_item_fullscreen = g_menu_item_new ("Full Screen", "win.fullscreen");
51 GMenuItem *menu_item_red = g_menu_item_new ("Red", "win.color::red");
52 GMenuItem *menu_item_green = g_menu_item_new ("Green", "win.color::green");
53 GMenuItem *menu_item_blue = g_menu_item_new ("Blue", "win.color::blue");
54 GMenuItem *menu_item_quit = g_menu_item_new ("Quit", "app.quit");
55
56 g_signal_connect (act_fullscreen, "change-state", G_CALLBACK (fullscreen_changed), win);
57 g_signal_connect (act_color, "activate", G_CALLBACK (color_activated), win);
58 g_signal_connect (act_quit, "activate", G_CALLBACK (quit_activated), app);
59 g_action_map_add_action (G_ACTION_MAP (win), G_ACTION (act_fullscreen));
60 g_action_map_add_action (G_ACTION_MAP (win), G_ACTION (act_color));
61 g_action_map_add_action (G_ACTION_MAP (app), G_ACTION (act_quit));
62
63 g_menu_append_item (section1, menu_item_fullscreen);
64 g_menu_append_item (section2, menu_item_red);
65 g_menu_append_item (section2, menu_item_green);
66 g_menu_append_item (section2, menu_item_blue);
67 g_menu_append_item (section3, menu_item_quit);
68 g_object_unref (menu_item_red);
69 g_object_unref (menu_item_green);
70 g_object_unref (menu_item_blue);
71 g_object_unref (menu_item_fullscreen);
72 g_object_unref (menu_item_quit);
73
74 g_menu_append_section (menu, NULL, G_MENU_MODEL (section1));
75 g_menu_append_section (menu, "Color", G_MENU_MODEL (section2));
76 g_menu_append_section (menu, NULL, G_MENU_MODEL (section3));
77 g_menu_append_submenu (menubar, "Menu", G_MENU_MODEL (menu));
78
79 gtk_application_set_menubar (GTK_APPLICATION (app), G_MENU_MODEL (menubar));
80 gtk_application_window_set_show_menubar (GTK_APPLICATION_WINDOW (win), TRUE);
81
82 /* GtkCssProvider *provider = gtk_css_provider_new ();*/
83 provider = gtk_css_provider_new ();
84 GdkDisplay *display = gtk_widget_get_display (GTK_WIDGET (win));
85 gtk_css_provider_load_from_data (provider, "label#lb {background-color: red;}", -1);
86 gtk_style_context_add_provider_for_display (display, GTK_STYLE_PROVIDER (provider),
87 GTK_STYLE_PROVIDER_PRIORITY_USER);
88
89 /* gtk_widget_show (win);*/
90 gtk_window_present (GTK_WINDOW (win));
91 }
92
93 int
94 main (int argc, char **argv) {
95 GtkApplication *app;
96 int stat;
97
98 app = gtk_application_new ("com.github.ToshioCP.menu2", G_APPLICATION_FLAGS_NONE);
99 g_signal_connect (app, "activate", G_CALLBACK (on_activate), NULL);
100
101 stat =g_application_run (G_APPLICATION (app), argc, argv);
102 g_object_unref (app);
103 return stat;
104 }
105
- 5-26: Signal handlers.
They have been explained in this section.
- 30-36: `win` and `lb` are GtkApplicationWindow and GtkLabel respectively.
`win` has a title "menu2" and its defaust size is 400x300.
`lb` is named as "lb".
The name is used in CSS.
`lb` is set to `win` as a child.
- 38-43: Three actions are defined.
They are:
- stateful and has no parameter.
It has a toggle state.
- stateful and has a parameter.
Parameter is a string type.
- stateless and has no parameter.
- 45-54: Generate GMenu and GMenuItem.
There are three sections.
- 56-61: Signals are connected to handlers.
And actions are added to GActionMap.
Because `act_fullscreen` and `act_color` have "win" prefix and belong to GtkApplicationWindow,
they are added to `win`.
GtkApplicationWindow implements GActionModel interface like GtkApplication.
`act_quit` has "app" prefix and belongs to GtkApplication,
it is added to `app`.
- 63-77: Connect and build the menus.
Useless GMenuItem are freed.
- 79-80: GMenuModel `menubar` is set to `app`.
Set show menubar property to `TRUE` in `win`.
Note: `gtk_application_window_set_show_menubar` generates GtkPopoverMenubar from GMenuModel.
This is a different point between Gtk3 and Gtk4.
And you can use GtkPopoverMenubar directly and set it as a descendant widget of the window.
You may use GtkBox as a child widget of the window and set GtkPopoverMenubar as the first child of the box.
- 82-87: Set CSS.
`provider` is GtkCssProvider which is defined in line three as a static variable.
Its CSS data is:
`label#lb {background-color: red;}`.
"label#lb" is called selector.
"label" is the node of GtkLabel.
"#" precedes an ID which is an identiable name of the widget.
"lb" is the name of GtkLabel `lb`.
(See line 35).
The style is surrounded by open and close braces.
The style is applied to GtkLabel which has a name "lb".
Other GtkLabel have no effect from this.
The provider is added to GdkDisplay.
- 90: Show the window.
Up: [Readme.md](src/Readme.md), Prev: [Section 14}](src/sec14.src.md), Next: [Section 16[(src/sec16.src.md)

336
sec16.md Normal file
View file

@ -0,0 +1,336 @@
Up: [Readme.md](src/Readme.md), Prev: [Section 15}](src/sec15.src.md), Next: [Section 17[(src/sec17.src.md)# Ui file for menu and action entries
## Ui file for menu
You might have thought that building menus is really bothersome.
Yes, the program was complicated and it needs lots of time to code it.
The situation is similar to building widgets.
When we built widgets, using ui file was a good way to avoid such complicated coding.
The same goes for menus.
The ui file for menus has interface, menu tags.
The file starts and ends with interface tag.
<interface>
<menu id="menubar">
</menu>
</interface>
`menu` tag corresponds to GMenu object.
`id` attribute defines the name of the object.
It will be refered by GtkBuilder.
<submenu>
<attribute name="label">File</attribute>
<item>
<attribute name="label">New</attribute>
<attribute name="action">win.new</attribute>
</item>
</submenu>
`item` tag corresponds to item in GMenu which has the same structure as GMenuItem.
The item above has a label attribute.
Its value is "New".
The item also has an action attribute and its value is "win.new".
"win" is a prefix and "new" is an action name.
`submenu` tag corresponds to both GMenuItem and GMenu.
The GMenuItem has a link to GMenu.
The ui file above can be described as follows.
<item>
<attribute name="label">File</attribute>
<link name="submenu">
<item>
<attribute name="label">New</attribute>
<attribute name="action">win.new</attribute>
</item>
</link>
</item>
`link` tag expresses the link to submenu.
And at the same time it also expresses the submenu itself.
This file illustrates the relationship between the menus and items better than the prior ui file.
But `submenu` tag is simple and easy to understand.
So, we usually prefer the former ui file style.
The following is a screenshot of the sample program in this section.
Its name is `menu3`.
![menu3](menu3.png)
The following is the ui file of the menu in `menu3`.
1 <?xml version="1.0" encoding="UTF-8"?>
2 <interface>
3 <menu id="menubar">
4 <submenu>
5 <attribute name="label">File</attribute>
6 <section>
7 <item>
8 <attribute name="label">New</attribute>
9 <attribute name="action">win.new</attribute>
10 </item>
11 <item>
12 <attribute name="label">Open</attribute>
13 <attribute name="action">win.open</attribute>
14 </item>
15 </section>
16 <section>
17 <item>
18 <attribute name="label">Save</attribute>
19 <attribute name="action">win.save</attribute>
20 </item>
21 <item>
22 <attribute name="label">Save As…</attribute>
23 <attribute name="action">win.saveas</attribute>
24 </item>
25 </section>
26 <section>
27 <item>
28 <attribute name="label">Close</attribute>
29 <attribute name="action">win.close</attribute>
30 </item>
31 </section>
32 <section>
33 <item>
34 <attribute name="label">Quit</attribute>
35 <attribute name="action">app.quit</attribute>
36 </item>
37 </section>
38 </submenu>
39 <submenu>
40 <attribute name="label">Edit</attribute>
41 <section>
42 <item>
43 <attribute name="label">Cut</attribute>
44 <attribute name="action">win.cut</attribute>
45 </item>
46 <item>
47 <attribute name="label">Copy</attribute>
48 <attribute name="action">win.copy</attribute>
49 </item>
50 <item>
51 <attribute name="label">Paste</attribute>
52 <attribute name="action">win.paste</attribute>
53 </item>
54 </section>
55 <section>
56 <item>
57 <attribute name="label">Select All</attribute>
58 <attribute name="action">win.selectall</attribute>
59 </item>
60 </section>
61 </submenu>
62 <submenu>
63 <attribute name="label">View</attribute>
64 <section>
65 <item>
66 <attribute name="label">Full Screen</attribute>
67 <attribute name="action">win.fullscreen</attribute>
68 </item>
69 </section>
70 </submenu>
71 </menu>
72 </interface>
The ui file is converted to the resource by the resouce compiler `glib-compile-resouces` with xml file below.
1 <?xml version="1.0" encoding="UTF-8"?>
2 <gresources>
3 <gresource prefix="/com/github/ToshioCP/menu3">
4 <file>menu3.ui</file>
5 </gresource>
6 </gresources>
GtkBuilder builds menus from the resource.
GtkBuilder *builder = gtk_builder_new_from_resource ("/com/github/ToshioCP/menu3/menu3.ui");
GMenuModel *menubar = G_MENU_MODEL (gtk_builder_get_object (builder, "menubar"));
gtk_application_set_menubar (GTK_APPLICATION (app), menubar);
g_object_unref (builder);
It is important that `builder` is unreferred after the GMenuModel `menubar` is set to the application.
If you do it before setting, bad thing will happen -- your computer might freeze.
## Action entry
The coding for building actions and signal handlers is always the same.
Therefore, it can be automated.
You can implement them easily with GActionEntry `g_action_map_add_action_entries`.
GActionEntry is a strutcure.
It contains action name, signal handlers, parameter and state.
typedef struct _GActionEntry GActionEntry;
struct _GActionEntry
{
const gchar *name; /* action name */
void (* activate) (GSimpleAction *action, GVariant *parameter, gpointer user_data); /* activate handler */
const gchar *parameter_type; /* the type of the parameter given as a single GVariant type string */
const gchar *state; /* initial state given in GVariant text format */
void (* change_state) (GSimpleAction *action, GVariant *value, gpointer user_data); /* change-state handler */
/*< private >*/
gsize padding[3];
};
For example, the actions in the previous section are:
{ "fullscreen", NULL, NULL, "false", fullscreen_changed }
{ "color", color_activated, "s", "red", NULL }
{ "quit", quit_activated, NULL, NULL, NULL },
And `g_action_map_add_action_entries` does all the process instead of the functions you have needed.
const GActionEntry app_entries[] = {
{ "quit", quit_activated, NULL, NULL, NULL }
};
g_action_map_add_action_entries (G_ACTION_MAP (app), app_entries, G_N_ELEMENTS (app_entries), app);
The code above does:
- Build the "quit" action
- Connect the action and the "activate" signal handler `quit_activate`
- Add the action to the action map `app`.
const GActionEntry win_entries[] = {
{ "fullscreen", NULL, NULL, "false", fullscreen_changed },
{ "color", color_activated, "s", "red", NULL }
};
g_action_map_add_action_entries (G_ACTION_MAP (win), win_entries, G_N_ELEMENTS (win_entries), win);
The code above does:
- Build the "fullscreen" action and "color" action.
- Connect the "fullscreen" action and the "change-state" signal handler `fullscreen_changed`
- Its initial state is set to FALSE.
- Connect the "color" action and the "activate" signal handler `color_activate`
- Its parameter type is string and the initial value is "red".
- Add the action to the action map `win`.
## Example code
The C source code of `menu3` and `meson.build` is as follows.
1 #include <gtk/gtk.h>
2
3 static void
4 new_activated (GSimpleAction *action, GVariant *parameter, gpointer win) {
5 }
6
7 static void
8 open_activated (GSimpleAction *action, GVariant *parameter, gpointer win) {
9 }
10
11 static void
12 save_activated (GSimpleAction *action, GVariant *parameter, gpointer win) {
13 }
14
15 static void
16 saveas_activated (GSimpleAction *action, GVariant *parameter, gpointer win) {
17 }
18
19 static void
20 close_activated (GSimpleAction *action, GVariant *parameter, gpointer win) {
21 }
22
23 static void
24 cut_activated (GSimpleAction *action, GVariant *parameter, gpointer win) {
25 }
26
27 static void
28 copy_activated (GSimpleAction *action, GVariant *parameter, gpointer win) {
29 }
30
31 static void
32 paste_activated (GSimpleAction *action, GVariant *parameter, gpointer win) {
33 }
34
35 static void
36 selectall_activated (GSimpleAction *action, GVariant *parameter, gpointer win) {
37 }
38
39 static void
40 fullscreen_changed (GSimpleAction *action, GVariant *state, gpointer win) {
41 if (g_variant_get_boolean (state))
42 gtk_window_maximize (GTK_WINDOW (win));
43 else
44 gtk_window_unmaximize (GTK_WINDOW (win));
45 g_simple_action_set_state (action, state);
46 }
47
48 static void
49 quit_activated (GSimpleAction *action, GVariant *parameter, gpointer app)
50 {
51 g_application_quit (G_APPLICATION(app));
52 }
53
54 static void
55 on_activate (GApplication *app, gpointer user_data) {
56 GtkWidget *win = gtk_application_window_new (GTK_APPLICATION (app));
57
58 const GActionEntry win_entries[] = {
59 { "new", new_activated, NULL, NULL, NULL },
60 { "open", open_activated, NULL, NULL, NULL },
61 { "save", save_activated, NULL, NULL, NULL },
62 { "saveas", saveas_activated, NULL, NULL, NULL },
63 { "close", close_activated, NULL, NULL, NULL },
64 { "cut", cut_activated, NULL, NULL, NULL },
65 { "copy", copy_activated, NULL, NULL, NULL },
66 { "paste", paste_activated, NULL, NULL, NULL },
67 { "selectall", selectall_activated, NULL, NULL, NULL },
68 { "fullscreen", NULL, NULL, "false", fullscreen_changed }
69 };
70 g_action_map_add_action_entries (G_ACTION_MAP (win), win_entries, G_N_ELEMENTS (win_entries), win);
71
72 gtk_application_window_set_show_menubar (GTK_APPLICATION_WINDOW (win), TRUE);
73
74 gtk_window_set_title (GTK_WINDOW (win), "menu3");
75 gtk_window_set_default_size (GTK_WINDOW (win), 400, 300);
76 gtk_widget_show (win);
77 }
78
79 static void
80 on_startup (GApplication *app, gpointer user_data) {
81 GtkBuilder *builder = gtk_builder_new_from_resource ("/com/github/ToshioCP/menu3/menu3.ui");
82 GMenuModel *menubar = G_MENU_MODEL (gtk_builder_get_object (builder, "menubar"));
83
84 gtk_application_set_menubar (GTK_APPLICATION (app), menubar);
85 g_object_unref (builder);
86
87 const GActionEntry app_entries[] = {
88 { "quit", quit_activated, NULL, NULL, NULL }
89 };
90 g_action_map_add_action_entries (G_ACTION_MAP (app), app_entries, G_N_ELEMENTS (app_entries), app);
91 }
92
93 int
94 main (int argc, char **argv) {
95 GtkApplication *app;
96 int stat;
97
98 app = gtk_application_new ("com.github.ToshioCP.menu3", G_APPLICATION_FLAGS_NONE);
99 g_signal_connect (app, "startup", G_CALLBACK (on_startup), NULL);
100 g_signal_connect (app, "activate", G_CALLBACK (on_activate), NULL);
101
102 stat =g_application_run (G_APPLICATION (app), argc, argv);
103 g_object_unref (app);
104 return stat;
105 }
106
meson.build
1 project('menu3', 'c')
2
3 gtkdep = dependency('gtk4')
4
5 gnome=import('gnome')
6 resources = gnome.compile_resources('resources','menu3.gresource.xml')
7
8 sourcefiles=files('menu3.c')
9
10 executable('menu3', sourcefiles, resources, dependencies: gtkdep)
Up: [Readme.md](src/Readme.md), Prev: [Section 15}](src/sec15.src.md), Next: [Section 17[(src/sec17.src.md)

5
sec17.md Normal file
View file

@ -0,0 +1,5 @@
Up: [Readme.md](src/Readme.md), Prev: [Section 16}](src/sec16.src.md)# GtkMenuButton
before close
Up: [Readme.md](src/Readme.md), Prev: [Section 16}](src/sec16.src.md)

309
sec2.md Normal file
View file

@ -0,0 +1,309 @@
Up: [Readme.md](src/Readme.md), Prev: [Section 1}](src/sec1.src.md), Next: [Section 3[(src/sec3.src.md)# Widgets (1)
## GtkLabel, GtkButton and Gtkbox
### GtkLabel
We made an window and show it on the screen in the previous chapter.
Now we go on to the next topic, widgets in the window.
The simplest widget is GtkLabel.
It is a widget with a string in it.
1 #include <gtk/gtk.h>
2
3 static void
4 on_activate (GApplication *app, gpointer user_data) {
5 GtkWidget *win;
6 GtkWidget *lab;
7
8 win = gtk_application_window_new (GTK_APPLICATION (app));
9 gtk_window_set_title (GTK_WINDOW (win), "lb4");
10 gtk_window_set_default_size (GTK_WINDOW (win), 400, 300);
11
12 lab = gtk_label_new ("Hello.");
13 gtk_window_set_child (GTK_WINDOW (win), lab);
14
15 gtk_widget_show (win);
16 }
17
18 int
19 main (int argc, char **argv) {
20 GtkApplication *app;
21 int stat;
22
23 app = gtk_application_new ("com.github.ToshioCP.lb1", G_APPLICATION_FLAGS_NONE);
24 g_signal_connect (app, "activate", G_CALLBACK (on_activate), NULL);
25 stat =g_application_run (G_APPLICATION (app), argc, argv);
26 g_object_unref (app);
27 return stat;
28 }
29
Save this program to a file `lb1.c`.
Then compile and run it.
$ comp lb1
$ ./a.out
A window with a message "Hello." appears.
![Screenshot of the label](screenshot_lb1.png)
There's only a little change between `pr4.c` and `lb1.c`.
Diff is a good program to know the difference between two files.
$ diff misc/pr4.c lb1.c
5a6
> GtkWidget *lab;
8c9
< gtk_window_set_title (GTK_WINDOW (win), "pr4");
---
> gtk_window_set_title (GTK_WINDOW (win), "lb4");
9a11,14
>
> lab = gtk_label_new ("Hello.");
> gtk_window_set_child (GTK_WINDOW (win), lab);
>
18c23
< app = gtk_application_new ("com.github.ToshioCP.pr4", G_APPLICATION_FLAGS_NONE);
---
> app = gtk_application_new ("com.github.ToshioCP.lb1", G_APPLICATION_FLAGS_NONE);
This tells us:
- The definition of a variable lab is added.
- The title of the window is changed.
- A label is generated and connected to the window.
The function `gtk_window_set_child (GTK_WINDOW (win), lab)` makes the label `lab` a child widget of the window `win`.
Be careful.
A child widget is different from a child object.
Objects have parent-child relationship and Widgets also have parent-child relationship.
But these two relationships are totally different.
Don't be confused.
In the program `lb1.c`, `lab` is a child widget of `win`.
Child widgets are always located inside its parent widget in the screen.
See the window appeared on the screen.
The window includes the label.
The window `win` dosen't have any parents.
We call such a window top-level window.
One application can have two or more top-level windows.
### GtkButton
Next widget is GtkButton.
It has a label or icon on it.
In this subsection, we will make a button with a label.
When a button is clicked on, it emits a "clicked" signal.
The following program shows how to catch the signal and do something.
1 #include <gtk/gtk.h>
2
3 static void
4 on_clicked (GtkButton *btn, gpointer user_data) {
5 g_print ("Clicked.\n");
6 }
7
8 static void
9 on_activate (GApplication *app, gpointer user_data) {
10 GtkWidget *win;
11 GtkWidget *btn;
12
13 win = gtk_application_window_new (GTK_APPLICATION (app));
14 gtk_window_set_title (GTK_WINDOW (win), "lb4");
15 gtk_window_set_default_size (GTK_WINDOW (win), 400, 300);
16
17 btn = gtk_button_new_with_label ("Click me");
18 gtk_window_set_child (GTK_WINDOW (win), btn);
19 g_signal_connect (btn, "clicked", G_CALLBACK (on_clicked), NULL);
20
21 gtk_widget_show (win);
22 }
23
24 int
25 main (int argc, char **argv) {
26 GtkApplication *app;
27 int stat;
28
29 app = gtk_application_new ("com.github.ToshioCP.lb2", G_APPLICATION_FLAGS_NONE);
30 g_signal_connect (app, "activate", G_CALLBACK (on_activate), NULL);
31 stat =g_application_run (G_APPLICATION (app), argc, argv);
32 g_object_unref (app);
33 return stat;
34 }
35
Look at the line 17 to 19.
First, generate a GtkButton widget `btn` with a label "Click me".
Then, set it to the window `win` as a child.
Finally, connect a "clicked" signal of the button to a handler (function) `on_click`.
So, if `btn` is clicked, the function `on_click` is invoked.
Name the program `lb2.c` and save it.
Now compile and run it.
![Screenshot of the label](screenshot_lb2.png)
A window with the button appears.
Click the button (it is a large button, you can click everywhere inside the window), then a string "Clicked." appears on the shell terminal.
It shows the handler was invoked by clicking the button.
It's fairly good for us to make sure that the clicked signal was caught and the handler was invoked.
However, using g_print is out of harmony with GTK which is a GUI library.
So, we will change the handler.
The following code is `lb3.c`.
1 static void
2 on_clicked (GtkButton *btn, gpointer user_data) {
3 GtkWindow *win = GTK_WINDOW (user_data);
4 gtk_window_destroy (win);
5 }
6
7 static void
8 on_activate (GApplication *app, gpointer user_data) {
9 GtkWidget *win;
10 GtkWidget *btn;
11
12 win = gtk_application_window_new (GTK_APPLICATION (app));
13 gtk_window_set_title (GTK_WINDOW (win), "lb4");
14 gtk_window_set_default_size (GTK_WINDOW (win), 400, 300);
15
16 btn = gtk_button_new_with_label ("Quit");
17 gtk_window_set_child (GTK_WINDOW (win), btn);
18 g_signal_connect (btn, "clicked", G_CALLBACK (on_clicked), win);
19
20 gtk_widget_show (win);
21 }
And the difference between `lb2.c` and `lb3.c` is as follows.
$ diff lb2.c lb3.c
5c5,6
< g_print ("Clicked.\n");
---
> GtkWindow *win = GTK_WINDOW (user_data);
> gtk_window_destroy (win);
17c18
< btn = gtk_button_new_with_label ("Click me");
---
> btn = gtk_button_new_with_label ("Quit");
19c20
< g_signal_connect (btn, "clicked", G_CALLBACK (on_clicked), NULL);
---
> g_signal_connect (btn, "clicked", G_CALLBACK (on_clicked), win);
29c30
< app = gtk_application_new ("com.github.ToshioCP.lb2", G_APPLICATION_FLAGS_NONE);
---
> app = gtk_application_new ("com.github.ToshioCP.lb3", G_APPLICATION_FLAGS_NONE);
The change is:
- The function `g_print` in `lb2.c` was deleted and two lines above are inserted instead.
- The label of `btn` is changed from "Click me" to "Quit".
- The fourth argument of `g_signal_connect` is changed from `NULL` to `win`.
Most important is the fourth argument of `g_signal_connect`.
It is described as "data to pass to handler" in the definition of g\_signal\_connect in GObject API reference.
Therefore, `win` which is a pointer to GtkApplicationWindow is passed to the handler as a second parameter user_data.
Then, the handler cast it to a pointer to GtkWindow and call `gtk_window_destroy` and destroy the top window.
Then, the application quits.
### GtkBox
GtkWindow and GtkApplicationWindow can have only one child.
If you want to add two or more widgets inside a window, you need a container widget.
GtkBox is one of the containers.
It arranges two or more child widgets into a single row or column.
The following procedure shows the way to add two buttons in a window.
- Generate GtkApplicationWindow.
- Generate GtkBox and set it a child of GtkApplicationWindow.
- Generate GtkButton and append it to GtkBox.
- Generate another GtkButton and append it to GtkBox.
After this, the Widgets are connected as following diagram.
![Parent-child relationship](box.png)
Now, code it.
1 #include <gtk/gtk.h>
2
3 static void
4 on_clicked1 (GtkButton *btn, gpointer user_data) {
5 const gchar *s;
6
7 s = gtk_button_get_label (btn);
8 if (g_strcmp0 (s, "Hello.") == 0)
9 gtk_button_set_label (btn, "Good-bye.");
10 else
11 gtk_button_set_label (btn, "Hello.");
12 }
13
14 static void
15 on_clicked2 (GtkButton *btn, gpointer user_data) {
16 GtkWindow *win = GTK_WINDOW (user_data);
17 gtk_window_destroy (win);
18 }
19
20 static void
21 on_activate (GApplication *app, gpointer user_data) {
22 GtkWidget *win;
23 GtkWidget *box;
24 GtkWidget *btn1;
25 GtkWidget *btn2;
26
27 win = gtk_application_window_new (GTK_APPLICATION (app));
28 gtk_window_set_title (GTK_WINDOW (win), "lb4");
29 gtk_window_set_default_size (GTK_WINDOW (win), 400, 300);
30
31 box = gtk_box_new (GTK_ORIENTATION_VERTICAL, 5);
32 gtk_box_set_homogeneous (GTK_BOX (box), TRUE);
33 gtk_window_set_child (GTK_WINDOW (win), box);
34
35 btn1 = gtk_button_new_with_label ("Hello.");
36 g_signal_connect (btn1, "clicked", G_CALLBACK (on_clicked1), NULL);
37
38 btn2 = gtk_button_new_with_label ("Quit");
39 g_signal_connect (btn2, "clicked", G_CALLBACK (on_clicked2), win);
40
41 gtk_box_append (GTK_BOX (box), btn1);
42 gtk_box_append (GTK_BOX (box), btn2);
43
44 gtk_widget_show (win);
45 }
46
47 int
48 main (int argc, char **argv) {
49 GtkApplication *app;
50 int stat;
51
52 app = gtk_application_new ("com.github.ToshioCP.lb4", G_APPLICATION_FLAGS_NONE);
53 g_signal_connect (app, "activate", G_CALLBACK (on_activate), NULL);
54 stat =g_application_run (G_APPLICATION (app), argc, argv);
55 g_object_unref (app);
56 return stat;
57 }
58
Look at the function `on_activate`.
After the generation of GtkApplicationWindow, GtkBox is generated.
box = gtk_box_new(GTK_ORIENTATION_VERTICAL, 5);
gtk_box_set_homogeneous (GTK_BOX (box), TRUE);
The first argument arranges children vertically.
The second argument is sizes between children.
The next function fills a box with children, giving them equal space.
After that, two buttons `btn1` and `btn2` are generated and the signal handlers are set.
Then, these two buttons are appended to the box.
![Screenshot of the box](screenshot_lb4.png)
The handler corresponds to `btn1` changes its label.
The handler corresponds to `btn2` destroys the top-level window and the application quits.
Up: [Readme.md](src/Readme.md), Prev: [Section 1}](src/sec1.src.md), Next: [Section 3[(src/sec3.src.md)

164
sec3.md Normal file
View file

@ -0,0 +1,164 @@
Up: [Readme.md](src/Readme.md), Prev: [Section 2}](src/sec2.src.md), Next: [Section 4[(src/sec4.src.md)# Widgets (2)
## GtkTextView, GtkTextbuffer and GtkScrolledWindow
### GtkTextView and GtkTextBuffer
GtkTextview is a widget for multiline text editing.
GtkTextBuffer is a text buffer which is connected to GtkTextView.
See a sample program `tfv1.c` below.
1 #include <gtk/gtk.h>
2
3 static void
4 on_activate (GApplication *app, gpointer user_data) {
5 GtkWidget *win;
6 GtkWidget *tv;
7 GtkTextBuffer *tb;
8 gchar *text;
9
10 text =
11 "Once upon a time, there was an old man who was called Taketori-no-Okina."
12 "It is a japanese word that means a man whose work is making bamboo baskets.\n"
13 "One day, he went into a mountain and found a shining bamboo."
14 "\"What a mysterious bamboo it is!,\" he said."
15 "He cuts it, then there was a small cute baby girl in it."
16 "The girl was shining faintly."
17 "He thought this baby girl is a gift from Heaven and took her home.\n"
18 "His wife was surprized at his tale."
19 "They were very happy because they had no children."
20 ;
21 win = gtk_application_window_new (GTK_APPLICATION (app));
22 gtk_window_set_title (GTK_WINDOW (win), "Taketori");
23 gtk_window_set_default_size (GTK_WINDOW (win), 400, 300);
24
25 tv = gtk_text_view_new ();
26 tb = gtk_text_view_get_buffer (GTK_TEXT_VIEW (tv));
27 gtk_text_buffer_set_text (tb, text, -1);
28 gtk_text_view_set_wrap_mode (GTK_TEXT_VIEW (tv), GTK_WRAP_WORD_CHAR);
29
30 gtk_window_set_child (GTK_WINDOW (win), tv);
31
32 gtk_widget_show (win);
33 }
34
35 int
36 main (int argc, char **argv) {
37 GtkApplication *app;
38 int stat;
39
40 app = gtk_application_new ("com.github.ToshioCP.tfv1", G_APPLICATION_FLAGS_NONE);
41 g_signal_connect (app, "activate", G_CALLBACK (on_activate), NULL);
42 stat =g_application_run (G_APPLICATION (app), argc, argv);
43 g_object_unref (app);
44 return stat;
45 }
46
Look at line 25.
GtkTextView is generated and its pointer is assigned to `tv`.
When GtkTextView is generated, the connected GtkTextBuffer is also generated automatically.
In the next line, the pointer to the buffer is got and assigned to `tb`.
Then, the text from line 10 to 20 is assigned to the buffer.
GtkTextView has a wrap mode.
When `GTK_WRAP_WORD_CHAR` is set, text wraps in between words, or if that is not enough, also between graphemes.
In line 30, `tv` is set to `win` as a child.
Now compile and run it.
![GtkTextView](screenshot_tfv1.png)
There's an I-beam pointer in the window.
You can add or delete any character on GtkTextview.
And your change is kept in GtkTextBuffer.
If you add more characters than the limit of the window, the height of the window extends.
If the height gets bigger than the height of the display screen, you won't be able to control the size of the window back to the original size.
It's a problem.
You can solve it by putting GtkScrolledWindow between GtkApplicationWindow and GtkTextView.
### GtkScrolledWindow
What we need to do is:
- Generate GtkScrolledWindow and set it as a child of GtkApplicationWindow.
- Set GtkTextVies as a child of GtkScrolledWindow.
Modify `tfv1.c` and save it as `tfv2.c`.
The difference between these two files is very little.
$ diff tfv1.c tfv2.c
5a6
> GtkWidget *scr;
24a26,28
> scr = gtk_scrolled_window_new ();
> gtk_window_set_child (GTK_WINDOW (win), scr);
>
30c34
< gtk_window_set_child (GTK_WINDOW (win), tv);
---
> gtk_scrolled_window_set_child (GTK_SCROLLED_WINDOW (scr), tv);
40c44
< app = gtk_application_new ("com.github.ToshioCP.tfv1", G_APPLICATION_FLAGS_NONE);
---
> app = gtk_application_new ("com.github.ToshioCP.tfv2", G_APPLICATION_FLAGS_NONE);
Though you can modify the source file by this diff output, It's good for you to show `tfv2.c`.
1 #include <gtk/gtk.h>
2
3 static void
4 on_activate (GApplication *app, gpointer user_data) {
5 GtkWidget *win;
6 GtkWidget *scr;
7 GtkWidget *tv;
8 GtkTextBuffer *tb;
9 gchar *text;
10
11 text =
12 "Once upon a time, there was an old man who was called Taketori-no-Okina."
13 "It is a japanese word that means a man whose work is making bamboo baskets.\n"
14 "One day, he went into a mountain and found a shining bamboo."
15 "\"What a mysterious bamboo it is!,\" he said."
16 "He cuts it, then there was a small cute baby girl in it."
17 "The girl was shining faintly."
18 "He thought this baby girl is a gift from Heaven and took her home.\n"
19 "His wife was surprized at his tale."
20 "They were very happy because they had no children."
21 ;
22 win = gtk_application_window_new (GTK_APPLICATION (app));
23 gtk_window_set_title (GTK_WINDOW (win), "Taketori");
24 gtk_window_set_default_size (GTK_WINDOW (win), 400, 300);
25
26 scr = gtk_scrolled_window_new ();
27 gtk_window_set_child (GTK_WINDOW (win), scr);
28
29 tv = gtk_text_view_new ();
30 tb = gtk_text_view_get_buffer (GTK_TEXT_VIEW (tv));
31 gtk_text_buffer_set_text (tb, text, -1);
32 gtk_text_view_set_wrap_mode (GTK_TEXT_VIEW (tv), GTK_WRAP_WORD_CHAR);
33
34 gtk_scrolled_window_set_child (GTK_SCROLLED_WINDOW (scr), tv);
35
36 gtk_widget_show (win);
37 }
38
39 int
40 main (int argc, char **argv) {
41 GtkApplication *app;
42 int stat;
43
44 app = gtk_application_new ("com.github.ToshioCP.tfv2", G_APPLICATION_FLAGS_NONE);
45 g_signal_connect (app, "activate", G_CALLBACK (on_activate), NULL);
46 stat =g_application_run (G_APPLICATION (app), argc, argv);
47 g_object_unref (app);
48 return stat;
49 }
50
Now compile and run it.
This time the window doesn't extend even if you type a lot of characters.
It just scrolls.
Up: [Readme.md](src/Readme.md), Prev: [Section 2}](src/sec2.src.md), Next: [Section 4[(src/sec4.src.md)

304
sec4.md Normal file
View file

@ -0,0 +1,304 @@
Up: [Readme.md](src/Readme.md), Prev: [Section 3}](src/sec3.src.md), Next: [Section 5[(src/sec5.src.md)# Widgets (3)
## Open signal
### G\_APPLICATION\_HANDLES\_OPEN flag
GtkTextView, GtkTextBuffer and GtkScrolledWindow have given us a minimum editor in the previous section.
Next, we will add a read function to this program and remake it into a file viewer.
There are many way to implement the function.
However, because this is a tutorial for beginners, we take the simplest way.
When the program starts, we give a filename as an argument.
$ ./a.out filename
Then it opens the file and set it into GtkTextBuffer.
At the beginning of the implementation, we need to know how GtkApplication (or GApplication) recognizes arguments.
It is described in the GIO API reference.
When GtkApplication is generated, a flag (its type is GApplicationFlags) is given as an argument.
GtkApplication *
gtk_application_new (const gchar *application_id, GApplicationFlags flags);
This flag is described in the GApplication section in GIO API reference.
GApplicationFlags' Members
G_APPLICATION_FLAGS_NONE Default. (No argument allowed)
... ... ...
G_APPLICATION_HANDLES_OPEN This application handles opening files (in the primary instance).
... ... ...
There are ten flags.
But we only need two of them so far.
We've already used `G_APPLICATION_FLAGS_NONE`.
It is the simplest option.
No argument is allowed.
If you give arguments and run the application, then error occurs.
`G_APPLICATION_HANDLES_OPEN` is the second simplest option.
It allows arguments but only files.
The application assumes all the arguments are filenames.
Now we use this flag when generating GtkApplication.
app = gtk_application_new ("com.github.ToshioCP.tfv3", G_APPLICATION_HANDLES_OPEN);
### open signal
When the application starts, two signals are possible.
- activate signal --- This signal is emitted when there's no argument.
- open signal --- This signal is emitted when there is at least one argument.
The handler of open signal is called as follows.
void user_function (GApplication *application,
gpointer files,
gint n_files,
gchar *hint,
gpointer user_data)
The parameters are as follows:
- application --- the application (usually GtkApplication)
- files --- an array of GFiles. [array length=n_files] [element-type GFile]
- n_files --- the length of files
- hint --- a hint provided by the calling instance (usually it can be ignored)
- user_data --- user data set when the signal handler was connected.
The way how to read a file using GFiles will be described in the next section.
## Coding a file viewer
### What is a file viewer?
A file viewer is a program that shows a text file given as an argument.
It works as follows.
- If it is given arguments, it recognizes the first argument as a filename and open it.
- If opening the file succeeds, read and set it to GtkTextBuffer and show the window.
- If it fails to open the file, show an error message and quit.
- If there's no argument, show an error message and quit.
- If there are two or more arguments, the second one and after are ignored.
The program is as follows.
1 #include <gtk/gtk.h>
2
3 static void
4 on_activate (GApplication *app, gpointer user_data) {
5 g_print ("You need a filename argument.\n");
6 }
7
8 static void
9 on_open (GApplication *app, GFile ** files, gint n_files, gchar *hint, gpointer user_data) {
10 GtkWidget *win;
11 GtkWidget *scr;
12 GtkWidget *tv;
13 GtkTextBuffer *tb;
14 char *contents;
15 gsize length;
16 char *filename;
17
18 win = gtk_application_window_new (GTK_APPLICATION (app));
19 gtk_window_set_default_size (GTK_WINDOW (win), 400, 300);
20
21 scr = gtk_scrolled_window_new ();
22 gtk_window_set_child (GTK_WINDOW (win), scr);
23
24 tv = gtk_text_view_new ();
25 tb = gtk_text_view_get_buffer (GTK_TEXT_VIEW (tv));
26 gtk_text_view_set_wrap_mode (GTK_TEXT_VIEW (tv), GTK_WRAP_WORD_CHAR);
27 gtk_text_view_set_editable (GTK_TEXT_VIEW (tv), FALSE);
28 gtk_scrolled_window_set_child (GTK_SCROLLED_WINDOW (scr), tv);
29
30 if (g_file_load_contents (files[0], NULL, &contents, &length, NULL, NULL)) {
31 gtk_text_buffer_set_text (tb, contents, length);
32 g_free (contents);
33 filename = g_file_get_basename (files[0]);
34 gtk_window_set_title (GTK_WINDOW (win), filename);
35 g_free (filename);
36 gtk_widget_show (win);
37 } else {
38 filename = g_file_get_path (files[0]);
39 g_print ("No such file: %s.\n", filename);
40 gtk_window_destroy (GTK_WINDOW (win));
41 }
42 }
43
44 int
45 main (int argc, char **argv) {
46 GtkApplication *app;
47 int stat;
48
49 app = gtk_application_new ("com.github.ToshioCP.tfv3", G_APPLICATION_HANDLES_OPEN);
50 g_signal_connect (app, "activate", G_CALLBACK (on_activate), NULL);
51 g_signal_connect (app, "open", G_CALLBACK (on_open), NULL);
52 stat =g_application_run (G_APPLICATION (app), argc, argv);
53 g_object_unref (app);
54 return stat;
55 }
56
Save it as `tfv3.c`.
Then compile and run it.
$ comp tfv3
$ ./a.out tfv3.c
![File viewer](screenshot_tfv3.png)
Now I want to explain the program `tfv3.c`.
First, the function `main` changes in only two lines.
- `G_APPLICATION_FLAGS_NONE` is replaced with `G_APPLICATION_HANDLES_OPEN`.
- `g_signal_connect (app, "open", G_CALLBACK (on_open), NULL)` is added.
Next, the handler `on_activate` is now very simple.
Just output the error message.
The application quits immediately because no window is generated.
The point is the handler `on_open`.
- It generates GtkApplicationWindow, GtkScrolledWindow, GtkTextView and GtkTextBuffer and connect them.
- Set wrap mode to `GTK_WRAP_WORD_CHAR` in GtktextView.
- Set non-editable to GtkTextView because the program isn't an editor but only a viewer.
- Read the file and set it to GtkTextBuffer (this will be explained in detail later).
- If the file is not opened then output an error message and destroy the window. It makes the application quit.
The file reading part of the program is shown again below.
if (g_file_load_contents(files[0], NULL, &contents, &length, NULL, NULL)) {
gtk_text_buffer_set_text(tb, contents, length);
g_free(contents);
filename = g_file_get_basename(files[0]);
gtk_window_set_title (GTK_WINDOW (win), filename);
g_free(filename);
gtk_widget_show (win);
} else {
filename = g_file_get_path(files[0]);
g_print ("No such file: %s.\n", filename);
gtk_window_destroy (GTK_WINDOW (win));
}
The function `g_file_load_contents` loads the file contents into a buffer, which is automatically allocated, and set the pointer to the buffer into `contents`.
And the length of the buffer is set to `length`.
It returns `TRUE` if the file's contents were successfully loaded. `FALSE` if there were errors.
If the function succeeds, set the contents into GtkTextBuffer, free the buffer memories pointed by `contents`, set the filename to the title of the window,
free the memories pointed by `filename` and show the window.
If it fails, it outputs an error message and destroy the window.
## GtkNotebook
GtkNotebook is a container widget that contains multiple children with tabs in it.
![GtkNotebook](screenshot_gtk_notebook.png)
Look at the screenshots above.
The left one is a window at the startup.
It shows the file `pr1.c`.
The filename is in the left tab.
After clicking on the right tab, then the contents of `tfv1.c` appears.
It is shown in the right screenshot.
GtkNotebook widget is between GtkApplicationWindow and GtkScrolledWindow.
Now I want to show you the program `tfv4.c`.
1 #include <gtk/gtk.h>
2
3 static void
4 on_activate (GApplication *app, gpointer user_data) {
5 g_print ("You need a filename argument.\n");
6 }
7
8 static void
9 on_open (GApplication *app, GFile ** files, gint n_files, gchar *hint, gpointer user_data) {
10 GtkWidget *win;
11 GtkWidget *nb;
12 GtkWidget *lab;
13 GtkNotebookPage *nbp;
14 GtkWidget *scr;
15 GtkWidget *tv;
16 GtkTextBuffer *tb;
17 char *contents;
18 gsize length;
19 char *filename;
20 int i;
21
22 win = gtk_application_window_new (GTK_APPLICATION (app));
23 gtk_window_set_title (GTK_WINDOW (win), "file viewer");
24 gtk_window_set_default_size (GTK_WINDOW (win), 400, 300);
25 gtk_window_maximize (GTK_WINDOW (win));
26
27 nb = gtk_notebook_new ();
28 gtk_window_set_child (GTK_WINDOW (win), nb);
29
30 for (i = 0; i < n_files; i++) {
31 if (g_file_load_contents (files[i], NULL, &contents, &length, NULL, NULL)) {
32 scr = gtk_scrolled_window_new ();
33 tv = gtk_text_view_new ();
34 tb = gtk_text_view_get_buffer (GTK_TEXT_VIEW (tv));
35 gtk_text_view_set_wrap_mode (GTK_TEXT_VIEW (tv), GTK_WRAP_WORD_CHAR);
36 gtk_text_view_set_editable (GTK_TEXT_VIEW (tv), FALSE);
37 gtk_scrolled_window_set_child (GTK_SCROLLED_WINDOW (scr), tv);
38
39 gtk_text_buffer_set_text (tb, contents, length);
40 g_free (contents);
41 filename = g_file_get_basename (files[i]);
42 lab = gtk_label_new (filename);
43 gtk_notebook_append_page (GTK_NOTEBOOK (nb), scr, lab);
44 nbp = gtk_notebook_get_page (GTK_NOTEBOOK (nb), scr);
45 g_object_set (nbp, "tab-expand", TRUE, NULL);
46 g_free (filename);
47 } else {
48 filename = g_file_get_path (files[i]);
49 g_print ("No such file: %s.\n", filename);
50 g_free (filename);
51 }
52 }
53 if (gtk_notebook_get_n_pages (GTK_NOTEBOOK (nb)) > 0)
54 gtk_widget_show (win);
55 else
56 gtk_window_destroy (GTK_WINDOW (win));
57 }
58
59 int
60 main (int argc, char **argv) {
61 GtkApplication *app;
62 int stat;
63
64 app = gtk_application_new ("com.github.ToshioCP.tfv4", G_APPLICATION_HANDLES_OPEN);
65 g_signal_connect (app, "activate", G_CALLBACK (on_activate), NULL);
66 g_signal_connect (app, "open", G_CALLBACK (on_open), NULL);
67 stat =g_application_run (G_APPLICATION (app), argc, argv);
68 g_object_unref (app);
69 return stat;
70 }
71
Most of the change is in the function `on_open`.
The numbers at the left of the following items are line numbers in the source code.
- 11-13: Variables `nb`, `lab` and `nbp` are defined and point GtkNotebook, GtkLabel and GtkNotebookPage respectively.
- 23: The window's title is set to "file viewer".
- 25: The size of the window is set to maximum because a big window is appropriate for file viewers.
- 27-28 GtkNotebook is generated and set it as a child of GtkApplicationWindow.
- 30-52 For-loop. Each loop corresponds to an argument. And files[i] is GFile object with respect to the i-th argument.
- 32-37 GtkScrollledWindow, GtkTextView and GtkTextBuffer are generated and GtkTextView is connected to GtkScrolledWindow as a child.
They corresponds to each file, so they are generated inside the for-loop.
- 39-42 Set the contents of the file into GtkTextBuffer and free the memory pointed by `contents`. Get the filename and generate GtkLabel with the filename.
- 43: Append GtkScrolledWindow and GtkLabel to GtkNotebook. The appended objects are children of automatically generated GtkNotebookPage object. Therefore, the structure is like this:
GtkNotebook -- GtkNotebookPage -- (GtkScrolledWindow and GtkLabel)
- 44: Get GtkNotebookPage object and set its pointer to `nbp`.
- 45: GtkNotebookPage has a property "tab-expand". If it is set to TRUE then the tab expand horizontally as long as possible. If FALSE, then the width of the tab is determined by the size of the label. `g_object_set` is a general function to set properties in any objects.
- 46: free the memory pointed by `filename`
- 53-56: If at least one file was read, then the number of GtkNotebookPage is greater than zero. If it's true, then show the window. If it's false, then destroy the window.
Up: [Readme.md](src/Readme.md), Prev: [Section 3}](src/sec3.src.md), Next: [Section 5[(src/sec5.src.md)

347
sec5.md Normal file
View file

@ -0,0 +1,347 @@
Up: [Readme.md](src/Readme.md), Prev: [Section 4}](src/sec4.src.md), Next: [Section 6[(src/sec6.src.md)# Define Child object
## Very simple editor
We made a very simple file viewer in the previous section.
Now we go on to rewrite it and make a very simple editor.
Its source file name is tfe1.c (text file editor 1).
GtkTextView originally has a feature of multi line editing.
Therefore, we don't need to rewrite the program from scratch.
We just add two things to the file viewer.
- Static memory is needed to store a pointer to GFile.
- We need to implement file write function.
A couple of ways are possible to get memories to keep GFile.
- Use global variables.
- make a child widget object and extend the memories allocated to the widget.
Using global variables is easy to implement.
Define a sufficient size array of pointers to GFile.
For example,
GFile *f[20];
And `f[i]` corresponds to i-th GtkNotebookPage.
However, there are two problems.
One is the size of the array.
If a user gives arguments more than that, bad thing may happen.
The other is the difficulty of maintenance of the program.
It is a small program so far.
However, if you continue developing it, then its size grows bigger and bigger.
Generally speaking, the bigger the program size, the more difficult to maintain global variables.
Making child widget object is a good idea in terms of maintenance.
However, one thing you need to be careful is the difference between "child object" and "child widget".
What we are thinking about now is "child object".
A child object includes its parent object.
And a child widget object derives everything from the parent widget object.
![Child widget of GtkTwxtView](child.png)
We will define TfeTextView as a child widget object of GtkTextView.
It has everything that GtkTextView has.
For example, TfeTextView has GtkTextbuffer correspods to GtkTextView inside TfeTextView.
And important thing is that TfeTextView can have a memory to keep a pointer to GFile.
However, to understand the general theory about gobjects is very hard especially for beginners.
So, I will just show you the way how to write the code and avoid the theoretical side in the next section.
## How to define a child widget of GtkTextView
Let's define TfeTextView widget object which is a child object of GtkTextView.
First, look at the program below.
#define TFE_TYPE_TEXT_VIEW tfe_text_view_get_type ()
G_DECLARE_FINAL_TYPE (TfeTextView, tfe_text_view, TFE, TEXT_VIEW, GtkTextView)
struct _TfeTextView
{
GtkTextView parent;
GFile *file;
};
G_DEFINE_TYPE (TfeTextView, tfe_text_view, GTK_TYPE_TEXT_VIEW);
static void
tfe_text_view_init (TfeTextView *tv) {
}
static void
tfe_text_view_class_init (TfeTextViewClass *class) {
}
void
tfe_text_view_set_file (TfeTextView *tv, GFile *f) {
tv -> file = f;
}
GFile *
tfe_text_view_get_file (TfeTextView *tv) {
return tv -> file;
}
GtkWidget *
tfe_text_view_new (void) {
return gtk_widget_new (TFE_TYPE_TEXT_VIEW, NULL);
}
If you are curious about the background theory of this program, It's very good for you.
Because to know the theory is very important for you to program GTK applications.
Look at GObject API reference.
All you need is described in it.
However, it's a tough journey especially for beginners.
For now, you don't need to know such difficult theory.
Just remember the instructions below.
- TfeTextView is divided into two parts.
Tfe and TextView.
Tfe is called prefix, namespace or module.
TextView is called object.
- There are three patterns.
TfeTextView (camel case), tfe\_text\_view (this is used to write functions) and TFE\_TEXT\_VIEW (This is used to write casts).
- First, define TFE\_TYPE\_TEXT\_VIEW as tfe\_text\_view\_get\_type ().
The name is always (prefix)\_TYPE\_(object) and the letters are upper case.
And the replacement text is always (prefix)\_(object)\_get\_type () and the letters are lower case.
- Next, use G\_DECLARE\_FINAL\_TYPE macro.
The arguments are the child object name in camel case, lower case with underscore, prefix, object and parent object name.
- Declare the structure \_TfeTextView.
The underscore is necessary.
The first member is the parent object.
Notice this is not a pointer but the object itself.
The second member and after are members of the child object.
TfeTextView structure has a pointer to GFile as a member.
- Use G\_DEFINE\_TYPE macro.
The arguments are the child object name in camel case, lower case with underscore and parent object type (prefix)\_TYPE\_(module).
- Define instance init function (tfe\_text\_view\_init).
Usually you don't need to do anything.
- Define class init function (tfe\_text\_view\_class\_init).
You don't need to do anything in this widget.
- Write function codes you want to add (tfe\_text\_view\_set\_file and tfe\_text\_view\_get\_file).
`tv` is a pointer to TfeTextView object instance which is a C-struture declared with the tag \_TfeTextView.
So, the structure has a member `file` as a pointer to GFile.
`tv->file = f` is an assignment of `f` to a member `file` of the structure pointed by `tv`.
This is an example how to use the extended memory in a child widget.
- Write object generation function.
Its name is (prefix)\_(object)\_new.
If the parent object function needs parameters, this function also need them.
You sometimes might want to add some parameters.
It's your choice.
Use gtk\_widget\_new function to generate the child widget.
The arguments are (prefix)\_TYPE\_(object), a list to initialize properties and NULL.
In this code no property needs to be initialized.
This program is not perfect.
It has some problem.
But I don't discuss it now.
It will be modified later.
## Close-request signal
As a first step, `tfe1.c` writes files just before the window closes.
GtkWindow emits "close-request" signal before it closes.
We connect the signal and the handler `before_close`.
A handler is a C function.
When a function is connected to a certain signal, we call the function handler.
Then, the function `before_close` is invoked when the signal "close-request" is emittd.
g_signal_connect (win, "close-request", G_CALLBACK (before_close), NULL);
The argument win is GtkApplicationWindow, in which the signal "close-request" is defined, and before\_close is the handler.
`G_CALLBACK` cast is necessary before the handler.
The program of before\_close is as follows.
1 static gboolean
2 before_close (GtkWindow *win, GtkWidget *nb) {
3 GtkWidget *scr;
4 GtkWidget *tv;
5 GFile *file;
6 GtkTextBuffer *tb;
7 GtkTextIter start_iter;
8 GtkTextIter end_iter;
9 gchar *contents; /* gchar is the same as char ... typedef char gchar;*/
10 guint n;
11 guint i;
12
13 n = gtk_notebook_get_n_pages (GTK_NOTEBOOK (nb));
14 for (i = 0; i < n; ++i) {
15 scr = gtk_notebook_get_nth_page (GTK_NOTEBOOK (nb), i);
16 tv = gtk_scrolled_window_get_child (GTK_SCROLLED_WINDOW (scr));
17 file = tfe_text_view_get_file (TFE_TEXT_VIEW (tv));
18 tb = gtk_text_view_get_buffer (GTK_TEXT_VIEW (tv));
19 gtk_text_buffer_get_bounds (tb, &start_iter, &end_iter);
20 contents = gtk_text_buffer_get_text (tb, &start_iter, &end_iter, FALSE);
21 if (! g_file_replace_contents (file, contents, strlen (contents), NULL, TRUE, G_FILE_CREATE_NONE, NULL, NULL, NULL))
22 g_print ("ERROR : Can't save %s.", g_file_get_path (file));
23 }
24 return FALSE;
25 }
The numbers on the left of items are line numbers in the source code.
- 13: Get the number of pages `nb` has.
- 14-23: For loop with regard to the index to each pages.
- 15-17: Get GtkScrolledWindow, TfeTextView and a pointer to GFile. The pointer was stored when `on_open` handler ran. It will be shown later.
- 18-20: Get GtkTextBuffer and contents. start\_iter and end\_iter is iterators of the buffer. I don't want to explain them now because it would take a lot of time. Just remember these lines for the present.
- 21: Write the file.
## Source code of tfe1.c
Now I will show you all the source code of `tfe1`.c.
1 #include <gtk/gtk.h>
2
3 /* Define TfeTextView Widget which is the child object of GtkTextView */
4
5 #define TFE_TYPE_TEXT_VIEW tfe_text_view_get_type ()
6 G_DECLARE_FINAL_TYPE (TfeTextView, tfe_text_view, TFE, TEXT_VIEW, GtkTextView)
7
8 struct _TfeTextView
9 {
10 GtkTextView parent;
11 GFile *file;
12 };
13
14 G_DEFINE_TYPE (TfeTextView, tfe_text_view, GTK_TYPE_TEXT_VIEW);
15
16 static void
17 tfe_text_view_init (TfeTextView *tv) {
18 }
19
20 static void
21 tfe_text_view_class_init (TfeTextViewClass *class) {
22 }
23
24 void
25 tfe_text_view_set_file (TfeTextView *tv, GFile *f) {
26 tv -> file = f;
27 }
28
29 GFile *
30 tfe_text_view_get_file (TfeTextView *tv) {
31 return tv -> file;
32 }
33
34 GtkWidget *
35 tfe_text_view_new (void) {
36 return gtk_widget_new (TFE_TYPE_TEXT_VIEW, NULL);
37 }
38
39 /* ---------- end of the definition of TfeTextView ---------- */
40
41 static gboolean
42 before_close (GtkWindow *win, GtkWidget *nb) {
43 GtkWidget *scr;
44 GtkWidget *tv;
45 GFile *file;
46 GtkTextBuffer *tb;
47 GtkTextIter start_iter;
48 GtkTextIter end_iter;
49 gchar *contents; /* gchar is the same as char ... typedef char gchar;*/
50 guint n;
51 guint i;
52
53 n = gtk_notebook_get_n_pages (GTK_NOTEBOOK (nb));
54 for (i = 0; i < n; ++i) {
55 scr = gtk_notebook_get_nth_page (GTK_NOTEBOOK (nb), i);
56 tv = gtk_scrolled_window_get_child (GTK_SCROLLED_WINDOW (scr));
57 file = tfe_text_view_get_file (TFE_TEXT_VIEW (tv));
58 tb = gtk_text_view_get_buffer (GTK_TEXT_VIEW (tv));
59 gtk_text_buffer_get_bounds (tb, &start_iter, &end_iter);
60 contents = gtk_text_buffer_get_text (tb, &start_iter, &end_iter, FALSE);
61 if (! g_file_replace_contents (file, contents, strlen (contents), NULL, TRUE, G_FILE_CREATE_NONE, NULL, NULL, NULL))
62 g_print ("ERROR : Can't save %s.", g_file_get_path (file));
63 }
64 return FALSE;
65 }
66
67 static void
68 on_activate (GApplication *app, gpointer user_data) {
69 g_print ("You need a filename argument.\n");
70 }
71
72 static void
73 on_open (GApplication *app, GFile ** files, gint n_files, gchar *hint, gpointer user_data) {
74 GtkWidget *win;
75 GtkWidget *nb;
76 GtkWidget *lab;
77 GtkNotebookPage *nbp;
78 GtkWidget *scr;
79 GtkWidget *tv;
80 GtkTextBuffer *tb;
81 char *contents;
82 gsize length;
83 char *filename;
84 int i;
85
86 win = gtk_application_window_new (GTK_APPLICATION (app));
87 gtk_window_set_title (GTK_WINDOW (win), "file editor");
88 gtk_window_set_default_size (GTK_WINDOW (win), 400, 300);
89 gtk_window_maximize (GTK_WINDOW (win));
90
91 nb = gtk_notebook_new ();
92 gtk_window_set_child (GTK_WINDOW (win), nb);
93
94 for (i = 0; i < n_files; i++) {
95 if (g_file_load_contents (files[i], NULL, &contents, &length, NULL, NULL)) {
96 scr = gtk_scrolled_window_new ();
97 tv = tfe_text_view_new ();
98 tb = gtk_text_view_get_buffer (GTK_TEXT_VIEW (tv));
99 gtk_text_view_set_wrap_mode (GTK_TEXT_VIEW (tv), GTK_WRAP_WORD_CHAR);
100 gtk_scrolled_window_set_child (GTK_SCROLLED_WINDOW (scr), tv);
101
102 tfe_text_view_set_file (TFE_TEXT_VIEW (tv), g_file_dup (files[i]));
103 gtk_text_buffer_set_text (tb, contents, length);
104 g_free (contents);
105 filename = g_file_get_basename (files[i]);
106 lab = gtk_label_new (filename);
107 gtk_notebook_append_page (GTK_NOTEBOOK (nb), scr, lab);
108 nbp = gtk_notebook_get_page (GTK_NOTEBOOK (nb), scr);
109 g_object_set (nbp, "tab-expand", TRUE, NULL);
110 g_free (filename);
111 } else {
112 filename = g_file_get_path (files[i]);
113 g_print ("No such file: %s.\n", filename);
114 g_free (filename);
115 }
116 }
117 if (gtk_notebook_get_n_pages (GTK_NOTEBOOK (nb)) > 0) {
118 g_signal_connect (win, "close-request", G_CALLBACK (before_close), nb);
119 gtk_widget_show (win);
120 } else
121 gtk_window_destroy (GTK_WINDOW (win));
122 }
123
124 int
125 main (int argc, char **argv) {
126 GtkApplication *app;
127 int stat;
128
129 app = gtk_application_new ("com.github.ToshioCP.tfe1", G_APPLICATION_HANDLES_OPEN);
130 g_signal_connect (app, "activate", G_CALLBACK (on_activate), NULL);
131 g_signal_connect (app, "open", G_CALLBACK (on_open), NULL);
132 stat =g_application_run (G_APPLICATION (app), argc, argv);
133 g_object_unref (app);
134 return stat;
135 }
136
- 102: set the pointer to GFile into TfeTextView.
`files[i]` is a pointer to GFile structure.
It will be freed by the system. So you need to copy it.
`g_file_dup` duplicate the given GFile structure.
- 118: connect "close-request" signal and `before_close` handler.
The fourth argument is called user data and it is given to the signal handler.
So, `nb` is given to `before_close` as the second argument.
Now compile and run it.
Type `./a.out somefile` and make sure that the file is modified.
Now we got a very simple editor.
It's not smart.
We need more features like open, save, saveas, change font and so on.
We will add them in the next section and after.
Up: [Readme.md](src/Readme.md), Prev: [Section 4}](src/sec4.src.md), Next: [Section 6[(src/sec6.src.md)

450
sec6.md Normal file
View file

@ -0,0 +1,450 @@
Up: [Readme.md](src/Readme.md), Prev: [Section 5}](src/sec5.src.md), Next: [Section 7[(src/sec7.src.md)# Ui file and GtkBuiler
## New, open and save button
We made the simplest editor in the previous section.
It reads the files in `on_open` funciton at start-up and writes it at closing window.
It works but is not good.
It is better to make "New", "Open", "Save" and "Close" buttons.
This section describes how to put those buttons into the window.
Signals and handlers will be explained later.
![Screenshot of the file editor](screenshot_tfe2.png)
The screenshot above shows the layout.
The function `on_open` in the source code `tfe2.c` is as follows.
1 static void
2 on_open (GApplication *app, GFile ** files, gint n_files, gchar *hint, gpointer user_data) {
3 GtkWidget *win;
4 GtkWidget *nb;
5 GtkWidget *lab;
6 GtkNotebookPage *nbp;
7 GtkWidget *scr;
8 GtkWidget *tv;
9 GtkTextBuffer *tb;
10 char *contents;
11 gsize length;
12 char *filename;
13 int i;
14
15 GtkWidget *boxv;
16 GtkWidget *boxh;
17 GtkWidget *dmy1;
18 GtkWidget *dmy2;
19 GtkWidget *dmy3;
20 GtkWidget *btnn; /* button for new */
21 GtkWidget *btno; /* button for open */
22 GtkWidget *btns; /* button for save */
23 GtkWidget *btnc; /* button for close */
24
25
26 win = gtk_application_window_new (GTK_APPLICATION (app));
27 gtk_window_set_title (GTK_WINDOW (win), "file editor");
28 gtk_window_set_default_size (GTK_WINDOW (win), 600, 400);
29
30 boxv = gtk_box_new (GTK_ORIENTATION_VERTICAL, 0);
31 gtk_window_set_child (GTK_WINDOW (win), boxv);
32
33 boxh = gtk_box_new (GTK_ORIENTATION_HORIZONTAL, 0);
34 gtk_box_append (GTK_BOX (boxv), boxh);
35
36 dmy1 = gtk_label_new(NULL); /* dummy label for left space */
37 gtk_label_set_width_chars (GTK_LABEL (dmy1), 10);
38 dmy2 = gtk_label_new(NULL); /* dummy label for center space */
39 gtk_widget_set_hexpand (dmy2, TRUE);
40 dmy3 = gtk_label_new(NULL); /* dummy label for right space */
41 gtk_label_set_width_chars (GTK_LABEL (dmy3), 10);
42 btnn = gtk_button_new_with_label ("New");
43 btno = gtk_button_new_with_label ("Open");
44 btns = gtk_button_new_with_label ("Save");
45 btnc = gtk_button_new_with_label ("Close");
46
47 gtk_box_append (GTK_BOX (boxh), dmy1);
48 gtk_box_append (GTK_BOX (boxh), btnn);
49 gtk_box_append (GTK_BOX (boxh), btno);
50 gtk_box_append (GTK_BOX (boxh), dmy2);
51 gtk_box_append (GTK_BOX (boxh), btns);
52 gtk_box_append (GTK_BOX (boxh), btnc);
53 gtk_box_append (GTK_BOX (boxh), dmy3);
54
55 nb = gtk_notebook_new ();
56 gtk_widget_set_hexpand (nb, TRUE);
57 gtk_widget_set_vexpand (nb, TRUE);
58 gtk_box_append (GTK_BOX (boxv), nb);
59
60 for (i = 0; i < n_files; i++) {
61 if (g_file_load_contents (files[i], NULL, &contents, &length, NULL, NULL)) {
62 scr = gtk_scrolled_window_new ();
63 tv = tfe_text_view_new ();
64 tb = gtk_text_view_get_buffer (GTK_TEXT_VIEW (tv));
65 gtk_text_view_set_wrap_mode (GTK_TEXT_VIEW (tv), GTK_WRAP_WORD_CHAR);
66 gtk_scrolled_window_set_child (GTK_SCROLLED_WINDOW (scr), tv);
67
68 tfe_text_view_set_file (TFE_TEXT_VIEW (tv), g_file_dup (files[i]));
69 gtk_text_buffer_set_text (tb, contents, length);
70 g_free (contents);
71 filename = g_file_get_basename (files[i]);
72 lab = gtk_label_new (filename);
73 gtk_notebook_append_page (GTK_NOTEBOOK (nb), scr, lab);
74 nbp = gtk_notebook_get_page (GTK_NOTEBOOK (nb), scr);
75 g_object_set (nbp, "tab-expand", TRUE, NULL);
76 g_free (filename);
77 } else {
78 filename = g_file_get_path (files[i]);
79 g_print ("No such file: %s.\n", filename);
80 g_free (filename);
81 }
82 }
83 if (gtk_notebook_get_n_pages (GTK_NOTEBOOK (nb)) > 0) {
84 gtk_widget_show (win);
85 } else
86 gtk_window_destroy (GTK_WINDOW (win));
87 }
The point is how to build the window.
- 26-28: Generate GtkApplicationWindow and set its title and default size.
- 30-31: Generate GtkBox `boxv`.
It is a vertical box and a child of GtkApplicationWindow.
It has two children.
The first child is a horizontal box includes buttons.
The second child is GtkNotebook.
- 33-34: Generate GtkBox `boxh` and append it to 'boxv' as a first child.
- 36-41: Generate three dummy labels.
The labels `dmy1` and `dmy3` has a character width of ten.
The other label `dmy2` is set hexpand property TRUE.
This makes the label expands horizontally as long as possible.
- 42-45: Generate four buttons.
- 47-53: Append these GtkLabel and GtkButton to `boxh`.
- 55-58: Generate GtkNotebook and set hexpand and vexpand properties TRUE.
This makes it expands horizontally and vertically as big as possible.
It is appended to `boxv` as the second child.
The number of lines is 33(=58-26+1) to build the widgets.
And we needed many variables (boxv, boxh, dmy1 ...).
Most of them aren't necessary except building the widgets.
Are there any good solution to reduce these work?
Gtk provides GtkBuilder.
It reads ui data and builds a window.
It reduces the cumbersom work.
## Ui file
First, let's look at the ui file `tfe3.ui` that defines a structure of the widgets.
1 <interface>
2 <object class="GtkApplicationWindow" id="win">
3 <property name="title">file editor</property>
4 <property name="default-width">600</property>
5 <property name="default-height">400</property>
6 <child>
7 <object class="GtkBox" id="boxv">
8 <property name="orientation">GTK_ORIENTATION_VERTICAL</property>
9 <child>
10 <object class="GtkBox" id="boxh">
11 <property name="orientation">GTK_ORIENTATION_HORIZONTAL</property>
12 <child>
13 <object class="GtkLabel" id="dmy1">
14 <property name="width-chars">10</property>
15 </object>
16 </child>
17 <child>
18 <object class="GtkButton" id="btnn">
19 <property name="label">New</property>
20 </object>
21 </child>
22 <child>
23 <object class="GtkButton" id="btno">
24 <property name="label">Open</property>
25 </object>
26 </child>
27 <child>
28 <object class="GtkLabel" id="dmy2">
29 <property name="hexpand">TRUE</property>
30 </object>
31 </child>
32 <child>
33 <object class="GtkButton" id="btns">
34 <property name="label">Save</property>
35 </object>
36 </child>
37 <child>
38 <object class="GtkButton" id="btnc">
39 <property name="label">Close</property>
40 </object>
41 </child>
42 <child>
43 <object class="GtkLabel" id="dmy3">
44 <property name="width-chars">10</property>
45 </object>
46 </child>
47 </object>
48 </child>
49 <child>
50 <object class="GtkNotebook" id="nb">
51 <property name="hexpand">TRUE</property>
52 <property name="vexpand">TRUE</property>
53 </object>
54 </child>
55 </object>
56 </child>
57 </object>
58 </interface>
59
This is coded with XML structure.
Constructs begin with `<` and end with `>` is called tags.
And it is divided into two parts, start tag and end tag.
For example, `<interface>` is a start tag and `</interface>` is an end tag.
Ui file begins and ends with interface tags.
Some tags, for example, object tags can have a class and id attributes inside the start tag.
- 2-5: An object with `GtkApplicationWindow` class and `win` id is defined.
This is the top level window.
And the three properties of the window are defined.
`title` property is "file editor", `default-width` property is 400 and `default-height` property is 300.
- 6: child tag means a child of the object above.
For example, line 7 tells us that GtkBox object which id is "boxv" is a child of `win`.
Compare this ui file and the lines 26-58 in the source code of `on_open`.
Those two decribe the same structure of widgets.
## GtkBuilder
GtkBuilder builds widgets based on the ui file.
GtkBuilder *build;
build = gtk_builder_new_from_file ("tfe3.ui");
win = GTK_WIDGET (gtk_builder_get_object (build, "win"));
gtk_window_set_application (GTK_WINDOW (win), GTK_APPLICATION (app));
nb = GTK_WIDGET (gtk_builder_get_object (build, "nb"));
The function `gtk_builder_new_from_file` reads the file given as an argument, build the widgets, generate GtkBuilder object and set pointers to the widgets in it.
The function `gtk_builder_get_object (build, "win")` returns the pointer to the widget `win`, which is the id in the ui file.
All the widgets are connected based on the parent-children relationship described in the ui file.
We only need `win` and `nb` for the program after this, so we don't need to take out any other widgets.
This reduces lines in the C source file.
$ diff tfe2.c tfe3.c
58a59
> GtkBuilder *build;
60,104c61,65
< GtkWidget *boxv;
< GtkWidget *boxh;
< GtkWidget *dmy1;
< GtkWidget *dmy2;
< GtkWidget *dmy3;
< GtkWidget *btnn; /* button for new */
< GtkWidget *btno; /* button for open */
< GtkWidget *btns; /* button for save */
< GtkWidget *btnc; /* button for close */
<
<
< win = gtk_application_window_new (GTK_APPLICATION (app));
< gtk_window_set_title (GTK_WINDOW (win), "file editor");
< gtk_window_set_default_size (GTK_WINDOW (win), 600, 400);
<
< boxv = gtk_box_new (GTK_ORIENTATION_VERTICAL, 0);
< gtk_window_set_child (GTK_WINDOW (win), boxv);
<
< boxh = gtk_box_new (GTK_ORIENTATION_HORIZONTAL, 0);
< gtk_box_append (GTK_BOX (boxv), boxh);
<
< dmy1 = gtk_label_new(NULL); /* dummy label for left space */
< gtk_label_set_width_chars (GTK_LABEL (dmy1), 10);
< dmy2 = gtk_label_new(NULL); /* dummy label for center space */
< gtk_widget_set_hexpand (dmy2, TRUE);
< dmy3 = gtk_label_new(NULL); /* dummy label for right space */
< gtk_label_set_width_chars (GTK_LABEL (dmy3), 10);
< btnn = gtk_button_new_with_label ("New");
< btno = gtk_button_new_with_label ("Open");
< btns = gtk_button_new_with_label ("Save");
< btnc = gtk_button_new_with_label ("Close");
<
< gtk_box_append (GTK_BOX (boxh), dmy1);
< gtk_box_append (GTK_BOX (boxh), btnn);
< gtk_box_append (GTK_BOX (boxh), btno);
< gtk_box_append (GTK_BOX (boxh), dmy2);
< gtk_box_append (GTK_BOX (boxh), btns);
< gtk_box_append (GTK_BOX (boxh), btnc);
< gtk_box_append (GTK_BOX (boxh), dmy3);
<
< nb = gtk_notebook_new ();
< gtk_widget_set_hexpand (nb, TRUE);
< gtk_widget_set_vexpand (nb, TRUE);
< gtk_box_append (GTK_BOX (boxv), nb);
<
---
> build = gtk_builder_new_from_file ("tfe3.ui");
> win = GTK_WIDGET (gtk_builder_get_object (build, "win"));
> gtk_window_set_application (GTK_WINDOW (win), GTK_APPLICATION (app));
> nb = GTK_WIDGET (gtk_builder_get_object (build, "nb"));
> g_object_unref(build);
139c100
< app = gtk_application_new ("com.github.ToshioCP.tfe2", G_APPLICATION_HANDLES_OPEN);
---
> app = gtk_application_new ("com.github.ToshioCP.tfe3", G_APPLICATION_HANDLES_OPEN);
`65,104c61,65` means 40 (=104-65+1) lines change to 5 (=65-61+1) lines.
Therefore 35 lines are reduced.
Using ui file not only shortens C source files, but also makes the widgets' structure clear.
Now I'll show you the C source code `tfe3.c`.
Only functions `on_open` are shown as follows.
1 static void
2 on_open (GApplication *app, GFile ** files, gint n_files, gchar *hint, gpointer user_data) {
3 GtkWidget *win;
4 GtkWidget *nb;
5 GtkWidget *lab;
6 GtkNotebookPage *nbp;
7 GtkWidget *scr;
8 GtkWidget *tv;
9 GtkTextBuffer *tb;
10 char *contents;
11 gsize length;
12 char *filename;
13 int i;
14 GtkBuilder *build;
15
16 build = gtk_builder_new_from_file ("tfe3.ui");
17 win = GTK_WIDGET (gtk_builder_get_object (build, "win"));
18 gtk_window_set_application (GTK_WINDOW (win), GTK_APPLICATION (app));
19 nb = GTK_WIDGET (gtk_builder_get_object (build, "nb"));
20 g_object_unref(build);
21 for (i = 0; i < n_files; i++) {
22 if (g_file_load_contents (files[i], NULL, &contents, &length, NULL, NULL)) {
23 scr = gtk_scrolled_window_new ();
24 tv = tfe_text_view_new ();
25 tb = gtk_text_view_get_buffer (GTK_TEXT_VIEW (tv));
26 gtk_text_view_set_wrap_mode (GTK_TEXT_VIEW (tv), GTK_WRAP_WORD_CHAR);
27 gtk_scrolled_window_set_child (GTK_SCROLLED_WINDOW (scr), tv);
28
29 tfe_text_view_set_file (TFE_TEXT_VIEW (tv), g_file_dup (files[i]));
30 gtk_text_buffer_set_text (tb, contents, length);
31 g_free (contents);
32 filename = g_file_get_basename (files[i]);
33 lab = gtk_label_new (filename);
34 gtk_notebook_append_page (GTK_NOTEBOOK (nb), scr, lab);
35 nbp = gtk_notebook_get_page (GTK_NOTEBOOK (nb), scr);
36 g_object_set (nbp, "tab-expand", TRUE, NULL);
37 g_free (filename);
38 } else {
39 filename = g_file_get_path (files[i]);
40 g_print ("No such file: %s.\n", filename);
41 g_free (filename);
42 }
43 }
44 if (gtk_notebook_get_n_pages (GTK_NOTEBOOK (nb)) > 0) {
45 gtk_widget_show (win);
46 } else
47 gtk_window_destroy (GTK_WINDOW (win));
48 }
### Using ui string
GtkBuilder can build widgets using string.
Use the function gtk\_builder\_new\_from\_string instead of gtk\_builder\_new\_from\_file.
char *uistring;
uistring =
"<interface>"
"<object class="GtkApplicationWindow" id="win">"
"<property name=\"title\">file editor</property>"
"<property name=\"default-width\">600</property>"
"<property name=\"default-height\">400</property>"
"<child>"
"<object class=\"GtkBox\" id=\"boxv\">"
"<property name="orientation">GTK_ORIENTATION_VERTICAL</property>"
... ... ...
... ... ...
"</interface>";
build = gtk_builder_new_from_stringfile (uistring);
This method has an advantage and disadvantage.
The advantage is that the ui string is written in the source code.
So ui file is not necessary on runtime.
The disadvantage is that writing C string is a bit bothersome because of the double quotes.
If you want to use this method, you should write a script that transforms ui file into C-string.
- add backslash before each double quote.
- add double quote at the left and right.
### Using Gresource
Using Gresource is similar to using string.
But Gresource is compressed binary data, not text data.
And there's a compiler that compiles ui file into Gresource.
It can compile not only text files but also binary files such as images, sounds and so on.
And after compilation, it bundles them up into one Gresource object.
An xml file is necessary for the resource compiler `glib-compile-resources`.
It describes resource files.
1 <?xml version="1.0" encoding="UTF-8"?>
2 <gresources>
3 <gresource prefix="/com/github/ToshioCP/tfe3">
4 <file>tfe3.ui</file>
5 </gresource>
6 </gresources>
- 2: gresources tag can include mulitple gresources (gresource tags).
However, this xml has only one gresource.
- 3: The gresource has a prefix `/com/github/ToshioCP/tfe3`.
- 4: The gresource has tfe3.ui.
And it is pointed by `/com/github/ToshioCP/tfe3/tfe3.ui` because it needs prefix.
If you want to add more files, then insert them between line 4 and 5.
Save this xml text to `tfe3.gresource.xml`.
The gresource compiler `glib-compile-resources` shows its ussage with the argument `--help`.
$ LANG=C glib-compile-resources --help
Usage:
glib-compile-resources [OPTION?] FILE
Compile a resource specification into a resource file.
Resource specification files have the extension .gresource.xml,
and the resource file have the extension called .gresource.
Help Options:
-h, --help Show help options
Application Options:
--version Show program version and exit
--target=FILE Name of the output file
--sourcedir=DIRECTORY The directories to load files referenced in FILE from (default: current directory)
--generate Generate output in the format selected for by the target filename extension
--generate-header Generate source header
--generate-source Generate source code used to link in the resource file into your code
--generate-dependencies Generate dependency list
--dependency-file=FILE Name of the dependency file to generate
--generate-phony-targets Include phony targets in the generated dependency file
--manual-register Don?t automatically create and register resource
--internal Don?t export functions; declare them G_GNUC_INTERNAL
--external-data Don?t embed resource data in the C file; assume it's linked externally instead
--c-name C identifier name used for the generated source code
Now run the compiler.
$ glib-compile-resources tfe3.gresource.xml --target=resources.c --generate-source
Then a C source file `resources.c` is generated.
Modify tfe3.c and save it as tfe3_r.c
# include "resources.c"
... ... ...
... ... ...
build = gtk_builder_new_from_resource ("/com/github/ToshioCP/tfe3/tfe3.ui");
... ... ...
... ... ...
Then, compile and run it.
The window appears and it is the same as the screenshot at the beginning of this page.
Up: [Readme.md](src/Readme.md), Prev: [Section 5}](src/sec5.src.md), Next: [Section 7[(src/sec7.src.md)

421
sec7.md Normal file
View file

@ -0,0 +1,421 @@
Up: [Readme.md](src/Readme.md), Prev: [Section 6}](src/sec6.src.md), Next: [Section 8[(src/sec8.src.md)# Build system
## What do we need to think about building?
We've managed to compile a small editor so far.
But Some bad signs are beginning to appear.
- We have only one C source file and put everything into it.
We need to sort it out.
- There are two compilers, `gcc` and `glib-compile-resources`.
We want to control them by one building tool.
## Divide a C source file into two parts.
When you divide C source file into several parts, each file should contain only one thing.
For example, our source has two things, the definition of TfeTextView and functions related to GtkApplication and GtkApplicationWindow.
It is a good idea to separate them into two files, `tfetextview.c` and `tfe.c`.
- `tfetextview.c` includes the definition and functions of TfeTextView.
- `tfe.c` includes functions like `main`, `on_activate`, `on_open` and so on, which relate to GtkApplication and GtkApplicationWindow
Now we have three source files, `tfetextview.c`, `tfe.c` and `tfe3.ui`.
The `3` of `tfe3.ui` is like a version number.
Managing version with filenames is one possible idea but it may make bothersome complicated problem.
You need to rewrite filename in each version and it affects to contents of sourcefiles that refer to filenames.
So, we should take `3` away from the filename.
In `tfe.c` the function `tfe_text_view_new` is invoked to generate TfeTextView.
But it is defined in `tfetextview.c`, not `tfe.c`.
The lack of the declaration (not definition) of `tfe_text_view_new` makes error when `tfe.c` is compiled.
The declaration is necessary in `tfe.c`.
Those public information is usually written in header files.
It has `.h` suffix like `tfetextview.h`
And header files are included by C source files.
For example, `tfetextview.h` is included by `tfe.c`.
`tfetextview.h`
1 #include <gtk/gtk.h>
2
3 #define TFE_TYPE_TEXT_VIEW tfe_text_view_get_type ()
4 G_DECLARE_FINAL_TYPE (TfeTextView, tfe_text_view, TFE, TEXT_VIEW, GtkTextView)
5
6 void
7 tfe_text_view_set_file (TfeTextView *tv, GFile *f);
8
9 GFile *
10 tfe_text_view_get_file (TfeTextView *tv);
11
12 GtkWidget *
13 tfe_text_view_new (void);
14
`tfetextview.c`
1 #include <gtk/gtk.h>
2 #include "tfetextview.h"
3
4 struct _TfeTextView
5 {
6 GtkTextView parent;
7 GFile *file;
8 };
9
10 G_DEFINE_TYPE (TfeTextView, tfe_text_view, GTK_TYPE_TEXT_VIEW);
11
12 static void
13 tfe_text_view_init (TfeTextView *tv) {
14 }
15
16 static void
17 tfe_text_view_class_init (TfeTextViewClass *class) {
18 }
19
20 void
21 tfe_text_view_set_file (TfeTextView *tv, GFile *f) {
22 tv -> file = f;
23 }
24
25 GFile *
26 tfe_text_view_get_file (TfeTextView *tv) {
27 return tv -> file;
28 }
29
30 GtkWidget *
31 tfe_text_view_new (void) {
32 return gtk_widget_new (TFE_TYPE_TEXT_VIEW, NULL);
33 }
34
`tfe.c`
1 #include <gtk/gtk.h>
2 #include "tfetextview.h"
3
4 static void
5 on_activate (GApplication *app, gpointer user_data) {
6 g_print ("You need a filename argument.\n");
7 }
8
9 static void
10 on_open (GApplication *app, GFile ** files, gint n_files, gchar *hint, gpointer user_data) {
11 GtkWidget *win;
12 GtkWidget *nb;
13 GtkWidget *lab;
14 GtkNotebookPage *nbp;
15 GtkWidget *scr;
16 GtkWidget *tv;
17 GtkTextBuffer *tb;
18 char *contents;
19 gsize length;
20 char *filename;
21 int i;
22 GtkBuilder *build;
23
24 build = gtk_builder_new_from_resource ("/com/github/ToshioCP/tfe3/tfe.ui");
25 win = GTK_WIDGET (gtk_builder_get_object (build, "win"));
26 gtk_window_set_application (GTK_WINDOW (win), GTK_APPLICATION (app));
27 nb = GTK_WIDGET (gtk_builder_get_object (build, "nb"));
28 g_object_unref (build);
29 for (i = 0; i < n_files; i++) {
30 if (g_file_load_contents (files[i], NULL, &contents, &length, NULL, NULL)) {
31 scr = gtk_scrolled_window_new (NULL, NULL);
32 tv = tfe_text_view_new ();
33 tb = gtk_text_view_get_buffer (GTK_TEXT_VIEW (tv));
34 gtk_text_view_set_wrap_mode (GTK_TEXT_VIEW (tv), GTK_WRAP_WORD_CHAR);
35 gtk_scrolled_window_set_child (GTK_SCROLLED_WINDOW (scr), tv);
36
37 tfe_text_view_set_file (TFE_TEXT_VIEW (tv), g_file_dup (files[i]));
38 gtk_text_buffer_set_text (tb, contents, length);
39 g_free (contents);
40 filename = g_file_get_basename (files[i]);
41 lab = gtk_label_new (filename);
42 gtk_notebook_append_page (GTK_NOTEBOOK (nb), scr, lab);
43 nbp = gtk_notebook_get_page (GTK_NOTEBOOK (nb), scr);
44 g_object_set (nbp, "tab-expand", TRUE, NULL);
45 g_free (filename);
46 } else {
47 filename = g_file_get_path (files[i]);
48 g_print ("No such file: %s.\n", filename);
49 g_free (filename);
50 }
51 }
52 if (gtk_notebook_get_n_pages (GTK_NOTEBOOK (nb)) > 0) {
53 gtk_widget_show (win);
54 } else
55 gtk_window_destroy (GTK_WINDOW (win));
56 }
57
58 int
59 main (int argc, char **argv) {
60 GtkApplication *app;
61 int stat;
62
63 app = gtk_application_new ("com.github.ToshioCP.tfe3", G_APPLICATION_HANDLES_OPEN);
64 g_signal_connect (app, "activate", G_CALLBACK (on_activate), NULL);
65 g_signal_connect (app, "open", G_CALLBACK (on_open), NULL);
66 stat =g_application_run (G_APPLICATION (app), argc, argv);
67 g_object_unref (app);
68 return stat;
69 }
70
`tfe.ui`
1 <interface>
2 <object class="GtkApplicationWindow" id="win">
3 <property name="title">file editor</property>
4 <property name="default-width">600</property>
5 <property name="default-height">400</property>
6 <child>
7 <object class="GtkBox" id="boxv">
8 <property name="orientation">GTK_ORIENTATION_VERTICAL</property>
9 <child>
10 <object class="GtkBox" id="boxh">
11 <property name="orientation">GTK_ORIENTATION_HORIZONTAL</property>
12 <child>
13 <object class="GtkLabel" id="dmy1">
14 <property name="width-chars">10</property>
15 </object>
16 </child>
17 <child>
18 <object class="GtkButton" id="btnn">
19 <property name="label">New</property>
20 </object>
21 </child>
22 <child>
23 <object class="GtkButton" id="btno">
24 <property name="label">Open</property>
25 </object>
26 </child>
27 <child>
28 <object class="GtkLabel" id="dmy2">
29 <property name="hexpand">TRUE</property>
30 </object>
31 </child>
32 <child>
33 <object class="GtkButton" id="btns">
34 <property name="label">Save</property>
35 </object>
36 </child>
37 <child>
38 <object class="GtkButton" id="btnc">
39 <property name="label">Close</property>
40 </object>
41 </child>
42 <child>
43 <object class="GtkLabel" id="dmy3">
44 <property name="width-chars">10</property>
45 </object>
46 </child>
47 </object>
48 </child>
49 <child>
50 <object class="GtkNotebook" id="nb">
51 <property name="hexpand">TRUE</property>
52 <property name="vexpand">TRUE</property>
53 </object>
54 </child>
55 </object>
56 </child>
57 </object>
58 </interface>
59
`tfe.gresource.xml`
1 <?xml version="1.0" encoding="UTF-8"?>
2 <gresources>
3 <gresource prefix="/com/github/ToshioCP/tfe3">
4 <file>tfe.ui</file>
5 </gresource>
6 </gresources>
## Make
Dividing a file makes it easy to maintain source files.
But now we are faced with a new problem.
The building step increases.
- Compile the ui file `tfe.ui` into `resources.c`.
- Compile `tfe.c` into `tfe.o` (object file).
- Compile `tfetextview.c` into `tfetextview.o`.
- Compile `resources.c` into `resources.o`.
- Link all the object files into application `tfe`.
Now build tool is necessary to manage it.
Make is one of the build tools.
It was originally created in 1976.
So it is an old and widely used program.
Make analyzes Makefile and executes compilers.
All instructions are written in Makefile.
sample.o: sample.c
gcc -o sample.o sample.c
The sample of Malefile above consists of three elements, `sample.o`, `sample.c` and `gcc -0 sample.o sample.c`.
- `sample.o` is called target.
- `sample.c` is prerequisite.
- `gcc -0 sample.o sample.c` is recipe.
Recipes follow tab characters, not spaces.
(It is very important. Use tab not space, or make won't work as you expected).
The rule is:
If a prerequisite modified later than a target, then make executes the recipe.
In the example above, if `sample.c` is modified after the generation of `sample.o`, then make executes gcc and compile `sample.c` into `sample.o`.
If the modification time of `sample.c` is older then the generation of `sample.o`, then no compiling is necesarry, so make does nothing.
The Makefile for `tfe` is as follows.
1 all: tfe
2
3 tfe: tfe.o tfetextview.o resources.o
4 gcc -o tfe tfe.o tfetextview.o resources.o `pkg-config --libs gtk4`
5
6 tfe.o: tfe.c tfetextview.h
7 gcc -c -o tfe.o `pkg-config --cflags gtk4` tfe.c
8 tfetextview.o: tfetextview.c tfetextview.h
9 gcc -c -o tfetextview.o `pkg-config --cflags gtk4` tfetextview.c
10 resources.o: resources.c
11 gcc -c -o resources.o `pkg-config --cflags gtk4` resources.c
12
13 resources.c: tfe.gresource.xml tfe.ui
14 glib-compile-resources tfe.gresource.xml --target=resources.c --generate-source
15
16 .Phony: clean
17
18 clean:
19 rm -f tfe tfe.o tfetextview.o resources.o resources.c
Only you need is to type `make`.
$ make
gcc -c -o tfe.o `pkg-config --cflags gtk4` tfe.c
gcc -c -o tfetextview.o `pkg-config --cflags gtk4` tfetextview.c
glib-compile-resources tfe.gresource.xml --target=resources.c --generate-source
gcc -c -o resources.o `pkg-config --cflags gtk4` resources.c
gcc -o tfe tfe.o tfetextview.o resources.o `pkg-config --libs gtk4`
I used only very basic rules to write this Makefile.
There are many more convenient methods to make it more compact.
But it needs long story to explain.
So I want to finish the explanation about make.
## Rake
Rake is a similar program to make.
It is written in Ruby code.
If you don't use Ruby, you don't need to read this subsection.
However, Ruby is really sophisticated and recommendable script language.
- Rakefile controls the behavior of `rake`.
- You can write any ruby code in Rakefile.
Rake has task and file task, which is similar to target, prerequisite and recipe in make.
1 require 'rake/clean'
2
3 targetfile = "tfe"
4 srcfiles = FileList["tfe.c", "tfetextview.c", "resources.c"]
5 rscfile = srcfiles[2]
6 objfiles = srcfiles.gsub(/.c$/, '.o')
7
8 CLEAN.include(targetfile, objfiles, rscfile)
9
10 task default: targetfile
11
12 file targetfile => objfiles do |t|
13 sh "gcc -o #{t.name} #{t.prerequisites.join(' ')} `pkg-config --libs gtk4`"
14 end
15
16 objfiles.each do |obj|
17 src = obj.gsub(/.o$/,'.c')
18 file obj => src do |t|
19 sh "gcc -c -o #{t.name} `pkg-config --cflags gtk4` #{t.source} "
20 end
21 end
22
23 file rscfile => ["tfe.gresource.xml", "tfe.ui"] do |t|
24 sh "glib-compile-resources #{t.prerequisites[0]} --target=#{t.name} --generate-source"
25 end
What `Rakefile` describes is almost same as `Makefile` in the previous subsection.
- 3-6: define target file, source file and so on.
- 1, 8: Load clean library. And define CLEAN file list.
The files included by CLEAN will be removed when `rake clean` is typed on the command line.
- 10: default target depends on targetfile.
default is the final goal of tasks.
- 12-14: targetfile depends on objfiles.
The variable `t` is a task object.
- t.name is a target name
- t.prerequisites is an array of prerequisits.
- t.source is the first element of prerequisites.
- sh is a method to give the following string to shell as an argument and execute.
- 16-21: Loop by each element of the array of objfiles. Each object depends on corresponding source file.
- 23-25: resouce file depends on xml file and ui file.
Rakefile might seem to be difficult for beginners.
But, you can use any ruby syntax in Rakefile, so it is really flexible.
If you practice Ruby and Rakefile, it will be highly productive tools.
## Meson and ninja
Meson is one of the most popular building tool despite the developing version.
And ninja is similar to make but much faster than make.
Several years ago, most of the C developers used autotools and make.
But now the situation has changed.
Many developers are using meson and ninja now.
To use meson, you first need to write `meson.build` file.
1 project('tfe', 'c')
2
3 gtkdep = dependency('gtk4')
4
5 gnome=import('gnome')
6 resources = gnome.compile_resources('resources','tfe.gresource.xml')
7
8 sourcefiles=files('tfe.c', 'tfetextview.c')
9
10 executable('tfe', sourcefiles, resources, dependencies: gtkdep)
- 1: The function `project` defines things about the project.
The first parameter is the name of the project and the second is the programing language.
- 2: `dependency` function defines a dependency that is taken by `pkg-config`.
We put `gtk4` as an argument.
- 5: `import` function inports a module.
In line 5, gnome module is imported and assignd to the variable `gnome`.
gnome module provides helper tools to build GTK programs.
- 6: `.compile_resources` is a method of gnome module and compile files to resources under the instruction of xml file.
In line 6, the resource filename is `resources`, which means `resources.c` and `resources.h`, and xml file is `tfe.gresource.xml`.
This method generates C source file by default.
- 8: define source files.
- 10: executable function generates a target file by building source files.
The first parameter is the filename of the target. The following parameters are source files.
The last parameter has a option `dependencies`.
In line 10 it is `gtkdep` which is defined in line 3.
Now run meson and ninja.
$ meson _build
$ ninja -C _build
Then, the executable file `tfe` has been generated under the directory `_build`.
$ _build/tfe tfe.c tfetextview.c
Then the window appears.
I show you three build tools.
I think meson and ninja is the best choice for the present.
We divided a file into some categorized files and used a build tool.
This method is used by many developers.
Up: [Readme.md](src/Readme.md), Prev: [Section 6}](src/sec6.src.md), Next: [Section 8[(src/sec8.src.md)

426
sec8.md Normal file
View file

@ -0,0 +1,426 @@
Up: [Readme.md](src/Readme.md), Prev: [Section 7}](src/sec7.src.md), Next: [Section 9[(src/sec9.src.md)# Instance and class
This section and the following four sections are descriptions about next version of the text file editor (tfe).
It is tfe5.
It has many changes from the prior version.
All the sources are listed after the five sections.
## Encapsulation
We've divided C source file into two parts.
But it is not enough in terms of encapsulation.
- `tfe.c` includes everything other than TfeTextView.
It should be divided at least into two parts, `tfeapplication.c` and `tfenotebook.c`.
- Header files also need to be organized.
However, first of all, I'd like to focus on the object TfeTextView.
It is a child object of GtkTextView.
And important thing is it has newly added Gfile in it.
- What is necessary to GFile when generating (or initializing) TfeTextView?
- What is necessary to GFile when destructing TfeTextView?
- TfeTextView should read/write a file by itself or not?
- How it communicate with objects outside?
You need to know at least class/instance and signals before thinking about them.
I will explain them in this section and the next section.
After that I will explain:
- Organizing functions.
- How to use FileChooserDialog
## GObject and its children
GObject and its children are objects, which have both class and instance.
First, think about instance of objects.
Instance is structured memories and the structure is described using C language structure.
The following is a structure of TfeTextView.
/* This typedef statement is automaticaly generated by the macro G_DECLARE_FINAL_TYPE */
typedef struct _TfeTextView TfeTextView;
struct _TfeTextView {
GtkTextView parent;
GtkTextBuffer *tb;
GFile *file;
gboolean changed;
};
Each instance has similar structure as above.
- `parent` is the structure of GtkTextView which is the parent object of TfeTextView.
- `tb` is a pointer to GtkTextBuffer connected to GtkTextView.
- `file` is a pointer to GFile which is a file corresponds to `tb` (or NULL is available).
- `changed` is TRUE if the buffer has been modified, FALSE if not.
Comparing to the source file in the previous section, `tb` and `changed` are added.
Notice the program above is the declaration of the structure, not the definition.
So, no memories are allocated at this moment.
They are to be allocated when `tfe_text_view_new` function is invoked.
You can find the declaration of the ancestors of TfeTextView in the sourcefiles of GTK and GLib.
The following is extracts from the source files (not exactly the same).
typedef struct _GObject GObject;
typedef struct _GObject GInitiallyUnowned;
struct _GObject
{
GTypeInstance g_type_instance;
volatile guint ref_count;
GData *qdata;
};
typedef struct _GtkWidget GtkWidget;
struct _GtkWidget
{
GInitiallyUnowned parent_instance;
GtkWidgetPrivate *priv;
};
typedef struct _GtkTextView GtkTextView;
struct _GtkTextView
{
GtkWidget parent_instance;
GtkTextViewPrivate *priv;
};
In each structure, its parent instance is declared at the top of the members.
So, every ancestors is included in the child instance.
This is very important.
It guarantees a child widget to derive all the features from ancestors.
The structure of `TfeTextView` is like the following diagram.
![The structure of the instance TfeTextView](TfeTextView.png)
## Generate TfeTextView instance
The function `tfe_text_view_new` generates a new TfeTextView instance.
1 GtkWidget *
2 tfe_text_view_new (void) {
3 return gtk_widget_new (TFE_TYPE_TEXT_VIEW, NULL);
4 }
When this function is run, the following procedure is gone through.
1. Initialize GObject instance in TfeTextView instance.
2. Initialize GtkWidget instance in TfeTextView instance.
3. Initialize GtkTextView instance in TfeTextView instance.
4. Initialize TfeTextView instance.
Step one through three is done automatically.
Step four is done by the function `tfe_text_view_init`.
> (In the same way, `gtk_text_view_init`, `gtk_widget_init` and `g_object_init` is the initialization functions of GtkTextView, GtkWidget and GObject respectively.
> You can find them in the GTK or GLib source file.)
1 static void
2 on_changed (GtkTextBuffer *tb, TfeTextView *tv) {
3 tv->changed=TRUE;
4 }
5
6 static void
7 tfe_text_view_init (TfeTextView *tv) {
8 tv->tb = gtk_text_view_get_buffer (GTK_TEXT_VIEW (tv));
9 tv->file = NULL;
10 tv->changed = FALSE;
11 gtk_text_view_set_wrap_mode (GTK_TEXT_VIEW (tv), GTK_WRAP_WORD_CHAR);
12 g_signal_connect (tv->tb, "changed", G_CALLBACK (on_changed), tv);
13 }
`tfe_text_view_init` initializes the instance.
- 8-10: Initialize `tb`, `file` and `changed`.
- 11: Set the wrap mode of GtkTextView as GTK\_WRAP\_WORD\_CHAR.
- 12: Connect "changed" signal to a handler `on_changed`.
"changed" signal is defined in GtkTextBuffer.
It is emitted when the contents in the buffer is changed.
- 2-4: `on_changed` handler records TRUE to `tv->changed` when "changed" signal is emitted.
## Functions and Classes
In Gtk, all objects derived from GObject have class and instance.
Instance is memories which has a structure defined by C structure declaration as I mentioned in the previous two subsections.
And instance can be generated two or more.
Those instances have the same structure.
However, structured memories are insufficient to define its behavior.
We need at least two things.
One is functions and the other is class.
You've already seen many functions, for example, `tfe_text_view_new` is a function to generate TfeTextView instance.
These functions are similar to object methods in object oriented languages such as Java and Ruby.
Functions are public, which means that they are expected to be used by other objects.
Class comprises mainly pointers to functions.
And the functions are used by the object itself or its children objects.
For example, GObject class is declared in `gobject.h` in GLib source files.
1 typedef struct _GObjectClass GObjectClass;
2 typedef struct _GObjectClass GInitiallyUnownedClass;
3
4 struct _GObjectClass {
5 GTypeClass g_type_class;
6 /*< private >*/
7 GSList *construct_properties;
8 /*< public >*/
9 /* seldom overidden */
10 GObject* (*constructor) (GType type,
11 guint n_construct_properties,
12 GObjectConstructParam *construct_properties);
13 /* overridable methods */
14 void (*set_property) (GObject *object,
15 guint property_id,
16 const GValue *value,
17 GParamSpec *pspec);
18 void (*get_property) (GObject *object,
19 guint property_id,
20 GValue *value,
21 GParamSpec *pspec);
22 void (*dispose) (GObject *object);
23 void (*finalize) (GObject *object);
24 /* seldom overidden */
25 void (*dispatch_properties_changed) (GObject *object,
26 guint n_pspecs,
27 GParamSpec **pspecs);
28 /* signals */
29 void (*notify) (GObject *object,
30 GParamSpec *pspec);
31
32 /* called when done constructing */
33 void (*constructed) (GObject *object);
34 /*< private >*/
35 gsize flags;
36 /* padding */
37 gpointer pdummy[6];
38 };
I'd like to explain some of the members.
There's a pointer to the function `dispose` in line 22.
void (*dispose) (GObject *object);
The declaration is a bit complicated.
The asterisk before the identifier `dispose` means pointer.
So, the pointer `disopse` points a function which has one parameter , which points a GObject structure, and returns no value because of void type.
In the same way, line 23 says `finalize` is a pointer to the function which has one paremeter, which points a GObject structure, and returns no value.
void (*finalize) (GObject *object);
Look at the declaration of `_GObjectClass` so that you would find that most of the members are pointers to functions.
- 10: A function pointed by `constructor` is called when the instance is generated. It completes the initialization of the instance.
- 22: A function pointed by `dispose` is called when the instance destructs itself. Destruction process is divided into two phases. First is called disposing and the instance releases all the references to other instances. The second is finalizing.
- 23: A funtion pointed by `finalize` finishes the destruction process.
- The other pointers point functions which are called during the instance lives.
## TfeTextView class
TfeTextView class is a structure and it includes all its ancestors' class in it.
Let's look at all the classes from GObject, which is the top level object, to TfeTextView object, which is the lowest.
GObject -- GInitiallyUnowned -- GtkWidget -- GtkTextView -- TfeTextView
The following is extracts from the source files (not exactly the same).
1 struct _GtkWidgetClass {
2 GInitiallyUnownedClass parent_class;
3 /*< public >*/
4 guint activate_signal;
5 /* basics */
6 void (* show) (GtkWidget *widget);
7 void (* hide) (GtkWidget *widget);
8 void (* map) (GtkWidget *widget);
9 void (* unmap) (GtkWidget *widget);
10 void (* realize) (GtkWidget *widget);
11 void (* unrealize) (GtkWidget *widget);
12 void (* root) (GtkWidget *widget);
13 void (* unroot) (GtkWidget *widget);
14 void (* size_allocate) (GtkWidget *widget,
15 int width,
16 int height,
17 int baseline);
18 void (* state_flags_changed) (GtkWidget *widget,
19 GtkStateFlags previous_state_flags);
20 void (* direction_changed) (GtkWidget *widget,
21 GtkTextDirection previous_direction);
22 void (* grab_notify) (GtkWidget *widget,
23 gboolean was_grabbed);
24 /* size requests */
25 GtkSizeRequestMode (* get_request_mode) (GtkWidget *widget);
26 void (* measure) (GtkWidget *widget,
27 GtkOrientation orientation,
28 int for_size,
29 int *minimum,
30 int *natural,
31 int *minimum_baseline,
32 int *natural_baseline);
33 /* Mnemonics */
34 gboolean (* mnemonic_activate) (GtkWidget *widget,
35 gboolean group_cycling);
36 /* explicit focus */
37 gboolean (* grab_focus) (GtkWidget *widget);
38 gboolean (* focus) (GtkWidget *widget,
39 GtkDirectionType direction);
40 void (* set_focus_child) (GtkWidget *widget,
41 GtkWidget *child);
42 /* keyboard navigation */
43 void (* move_focus) (GtkWidget *widget,
44 GtkDirectionType direction);
45 gboolean (* keynav_failed) (GtkWidget *widget,
46 GtkDirectionType direction);
47 /* accessibility support
48 */
49 AtkObject * (* get_accessible) (GtkWidget *widget);
50 gboolean (* query_tooltip) (GtkWidget *widget,
51 gint x,
52 gint y,
53 gboolean keyboard_tooltip,
54 GtkTooltip *tooltip);
55 void (* compute_expand) (GtkWidget *widget,
56 gboolean *hexpand_p,
57 gboolean *vexpand_p);
58 void (* css_changed) (GtkWidget *widget,
59 GtkCssStyleChange *change);
60 void (* system_setting_changed) (GtkWidget *widget,
61 GtkSystemSetting settings);
62 void (* snapshot) (GtkWidget *widget,
63 GtkSnapshot *snapshot);
64 gboolean (* contains) (GtkWidget *widget,
65 gdouble x,
66 gdouble y);
67 /*< private >*/
68 GtkWidgetClassPrivate *priv;
69 gpointer padding[8];
70 };
71
72 struct _GtkTextViewClass {
73 GtkWidgetClass parent_class;
74 /*< public >*/
75 void (* move_cursor) (GtkTextView *text_view,
76 GtkMovementStep step,
77 gint count,
78 gboolean extend_selection);
79 void (* set_anchor) (GtkTextView *text_view);
80 void (* insert_at_cursor) (GtkTextView *text_view,
81 const gchar *str);
82 void (* delete_from_cursor) (GtkTextView *text_view,
83 GtkDeleteType type,
84 gint count);
85 void (* backspace) (GtkTextView *text_view);
86 void (* cut_clipboard) (GtkTextView *text_view);
87 void (* copy_clipboard) (GtkTextView *text_view);
88 void (* paste_clipboard) (GtkTextView *text_view);
89 void (* toggle_overwrite) (GtkTextView *text_view);
90 GtkTextBuffer * (* create_buffer) (GtkTextView *text_view);
91 void (* snapshot_layer) (GtkTextView *text_view,
92 GtkTextViewLayer layer,
93 GtkSnapshot *snapshot);
94 gboolean (* extend_selection) (GtkTextView *text_view,
95 GtkTextExtendSelection granularity,
96 const GtkTextIter *location,
97 GtkTextIter *start,
98 GtkTextIter *end);
99 void (* insert_emoji) (GtkTextView *text_view);
100 /*< private >*/
101 gpointer padding[8];
102 };
103
104 /* The following definition is generated by the macro G_DECLARE_FINAL_TYPE
105 typedef struct {
106 GtkTextView parent_class;
107 } TfeTextViewClass;
108
- 105-107: This three lines are generated by the macro G\_DECLARE\_FINAL\_TYPE.
So, they are not written in either `tfe_text_view.h` or `tfe_text_view.c`.
- 2, 73, 106: Each derived class puts its parent class at the first member of its structure.
It is the same as instance structures.
- Class members in ancesters are open to their child class.
So, they can be changed in `tfe_text_view_class_init` function.
For example, the `dispose` pointer in GObjectClass will be overridden later in `tfe_text_view_class_init`.
(Override is an object oriented programing terminology.
Override is rewriting ancestors' class methods in the child class.)
- Some class methods are often overridden.
`set_property`, `get_property`, `dispose`, `finalize` and `constructed` are such methods.
TfeTextViewClass includes its ancsestors' class in it.
It is illustrated in the following diagram.
![The structure of TfeTextView Class](TfeTextViewClass.png)
## Destruction of TfeTextView
Every Object derived from GObject has a reference count.
If an object A uses an object B, then A keeps a pointr to B in A and at the same time increaces the reference count of B by one with the function `g_object_ref (B)`.
If A doesn't need B any longer, then A discards the pointer to B (usually it is done by assigning NULL to the pointer) and decreaces the reference count of B by one with the function `g_object_unref (B)`.
If two objects A and B refer to C, then the reference count of C is two.
After A used C and if A no longer needs C, A discards the pointer to C and decreases the reference count in C by one.
Now the reference count of C is one.
In the same way, when B no longer needs C, B discards the pointer to C and decreases the reference count in C by one.
At this moment, no object refers C and the reference count of C is zero.
This means C is no longer useful.
Then C destructs itself and finally the memories allocated to C is freed.
![Reference count of B](refcount.png)
The idea above is based on an assumption that an object refered by nothing has reference count of zero.
When the reference count drops to zero, the object starts its destruction process.
The destruction process is split in two phases: disposing and finalizing.
In the disposing process, the object invokes the handler pointed by `dispose` in its class to release all references to other objects.
In the finalizing process, it invokes the handler pointed by `finalize` in its class to complete the destruction process.
In the destruction process of TfeTextView, the reference count of widgets related to TfeTextView is automatically decreased.
But GFile pointed by `tv->file` needs to decrease its reference count by one.
You must write the code in the dispose handler `tfe_text_view_dispose`.
1 static void
2 tfe_text_view_dispose (GObject *gobject) {
3 TfeTextView *tv = TFE_TEXT_VIEW (gobject);
4
5 if (G_IS_FILE (tv->file))
6 g_clear_object (&tv->file);
7
8 G_OBJECT_CLASS (tfe_text_view_parent_class)->dispose (gobject);
9 }
- 5,6: If `tv->file` points a GFile, decrease its reference count.
`g_clear_object` decreases the reference count and assigns NULL to `tv->file`. In dispose handlers, we usually use `g_clear_object` rather than `g_object_unref`.
- 8: invoke parent's despose handler. (This will be explained later.)
In the desposing process, the object uses the pointer in its class to call the handler.
Therefore, `tfe_text_view_dispose` needs to be registerd in the class when the TfeTextView class is initialized.
The function `tfe_text_view_class_init` is the class initialization function and it is declared in the replacement produced by `G_DEFINE_TYPE` macro.
static void
tfe_text_view_class_init (TfeTextViewClass *class) {
GObjectClass *object_class = G_OBJECT_CLASS (class);
object_class->dispose = tfe_text_view_dispose;
}
Each ancestors' class is generated before TfeTextViewClass.
Therefore, there are four classes and each class has a pointer to each dispose handler.
Look at the following diagram.
There are four classes -- GObjectClass (GInitiallyUnownedClass), GtkWidgetClass, GtkTextViewClass and TfeTextViewClass.
Each class has its own dispose handler -- `dh1`, `dh2`, `dh3` and `tfe_text_view_dispose`.
![dispose handers](dispose_handler.png)
Now, look at the `tfe_text_view_dispose` program above.
It first releases the reference to GFile object pointed by `tv->file`.
Then it invokes its parent's dispose handler in line 8.
G_OBJECT_CLASS (tfe_text_view_parent_class)->dispose (gobject);
`tfe_text_view_parent_class`,which is made by `G_DEFINE_TYPE` macro, is a pointer that points the parent object class.
Therefore, `G_OBJECT_CLASS (tfe_text_view_parent_class)->dispose` points the handler `dh3` in the diagram above.
And `gobject` is a pointer to TfeTextView object which is casted as a GObject instanse.
`dh3` releases all the references to objects in the GtkTextView part (it is actually the private area pointed by `prev`) in TfeTextView instance.
After that, `dh3` calls `dh2`, and `dh2` calls `dh1`.
Finally all the references are released.
Up: [Readme.md](src/Readme.md), Prev: [Section 7}](src/sec7.src.md), Next: [Section 9[(src/sec9.src.md)

162
sec9.md Normal file
View file

@ -0,0 +1,162 @@
Up: [Readme.md](src/Readme.md), Prev: [Section 8}](src/sec8.src.md), Next: [Section 10[(src/sec10.src.md)# Signals
## Signals
In GTK programming, each object is capsulated.
And it is not recommended to use global variables because they tend to make the program complicated.
So, we need something to communicate between objects.
There are two ways to do so.
- Functions.
For example, `tb = gtk_text_view_get_buffer (tv)`.
The function caller requests `tv`, which is a GtkTextView object, to give back `tb`, which is a GtkTextBuffer object connected to `tv`.
- Signals.
For example, `activate` signal on GApplication object.
When the application is activated, the signal is emitted.
Then the handler, which has been connected to the signal, is invoked.
The caller of the function or the handler connected to the signal is usually outside of the object.
One of the difference between these two is that the object is active or passive.
In functions the object responds to the caller.
In signals the object actively sends a signal to the handler.
GObject signal can be registered, connected and emitted.
1. A signal is registered with the object type on which it can be emitted.
This is done usually when the class is initialized.
2. It is connected to a handler by `g_connect_signal` or its family functions.
3. When it is emmitted, the connected handler is invoked.
Step one and three are done in the object on which the signal is emitted.
Step two is done outside the objects.
## Signal registration
In TfeTextView, two signals are registered.
- "change-file" signal.
This signal is emitted when `tv->file` is changed.
- "open-response" signal.
`tfe_text_view_open` function is not able to return the status because of using GtkFileChooserDialog.
This signal is emitted instead of the return value of the function.
Static variable is used to store the signal ID.
If you need to register two or more signals, static array is usually used.
enum {
CHANGE_FILE,
OPEN_RESPONSE,
NUMBER_OF_SIGNALS
};
static guint tfe_text_view_signals[NUMBER_OF_SIGNALS];
Signal registration codes are written in the class initialization function.
1 static void
2 tfe_text_view_class_init (TfeTextViewClass *class) {
3 GObjectClass *object_class = G_OBJECT_CLASS (class);
4
5 object_class->dispose = tfe_text_view_dispose;
6 tfe_text_view_signals[CHANGE_FILE] = g_signal_newv ("change-file",
7 G_TYPE_FROM_CLASS (class),
8 G_SIGNAL_RUN_LAST | G_SIGNAL_NO_RECURSE | G_SIGNAL_NO_HOOKS,
9 NULL /* closure */,
10 NULL /* accumulator */,
11 NULL /* accumulator data */,
12 NULL /* C marshaller */,
13 G_TYPE_NONE /* return_type */,
14 0 /* n_params */,
15 NULL /* param_types */);
16 GType param_types[] = {G_TYPE_INT};
17 tfe_text_view_signals[OPEN_RESPONSE] = g_signal_newv ("open-response",
18 G_TYPE_FROM_CLASS (class),
19 G_SIGNAL_RUN_LAST | G_SIGNAL_NO_RECURSE | G_SIGNAL_NO_HOOKS,
20 NULL /* closure */,
21 NULL /* accumulator */,
22 NULL /* accumulator data */,
23 NULL /* C marshaller */,
24 G_TYPE_NONE /* return_type */,
25 1 /* n_params */,
26 param_types);
27 }
- 6-15: Register "change-file"signal.
`g_signal_newv` function is used.
This signal has no default handler (object method handler).
I think you usually don't need to set a default handler in final type object.
If you need it, put the closure of the handler in line 9.
- The return value of `g_signal_newv` is the signal id.
The type of signal id is guint, which is the same as unsigned int.
It is used when the signal is emitted.
- 16-26: Register "open-response" signal.
This signal has a parameter.
- 25: Number of the parameter.
"open-response" signal has one parameter.
- 26: An array of types of parameters.
The array `param_types` is defined in line 16.
It has one element, which is `G_TYPE_INT`.
`G_TYPE_INT` is a type of integer.
Such fundamental types are described in [GObject API reference](https://developer.gnome.org/gobject/stable/gobject-Type-Information.html).
The handlers are as follows.
void change_file_handler (TfeTextView *tv, gpointer user_data);
void open_response_handler (TfeTextView *tv, guint parameter, gpointer user_data);
- Because "change-file" signal doesn't have parameter, the handler's parameter is TfeTextView object and user data.
- Because "open-response" signal has one parameter, the handler's parameter is TfeTextView object, the parameter and user data.
- `tv` is the object instance on which the signal is emitted.
- `user_data` comes from the fourth argument of `g_signal_connect`.
- `parameter` comes from the fourth argument of `g_signal_emit`.
The parameter is defined in `tfetextview.h` because it is public.
/* "open-response" signal response */
enum
{
TFE_OPEN_RESPONSE_SUCCESS,
TFE_OPEN_RESPONSE_CANCEL,
TFE_OPEN_RESPONSE_ERROR
};
- `TFE_OPEN_RESPONSE_SUCCESS` is set when `tfe_text_view_open` successfully has opend a file and loaded it.
- `TFE_OPEN_RESPONSE_CANCEL` is set when the user canceled to open a file.
- `TFE_OPEN_RESPONSE_ERROR` is set when error occured.
## Signal connection
A signal and a handler are connected by the function `g_signal_connect`.
There some similar functions like `g_signal_connect_after`, `g_signal_connect_swapped` and so on.
But I think `g_signal_connect` is the most common function.
The signals "change-file" is connected to a callback function `file_changed` outside of TfeTextView object.
In the same way, the signals "open-response" is connected to a callback function `open_response` outside of TfeTextView object.
The functions `file_changed` and `open_response` will be explained later.
g_signal_connect (GTK_TEXT_VIEW (tv), "change-file", G_CALLBACK (file_changed), nb);
g_signal_connect (TFE_TEXT_VIEW (tv), "open-response", G_CALLBACK (open_response), nb);
## Signal emission
Signals are emitted on the object.
The type of the object is the second argument of `g_signal_newv`.
The relationship between the signal and object (type) is made up when the signal is generated.
`g_signal_emit` is used to emit the signal.
The following is extract from `tfetexties.c`.
g_signal_emit (tv, tfe_text_view_signals[CHANGE_FILE], 0);
g_signal_emit (tv, tfe_text_view_signals[OPEN_RESPONSE], 0, TFE_OPEN_RESPONSE_SUCCESS);
g_signal_emit (tv, tfe_text_view_signals[OPEN_RESPONSE], 0, TFE_OPEN_RESPONSE_CANCEL);
g_signal_emit (tv, tfe_text_view_signals[OPEN_RESPONSE], 0, TFE_OPEN_RESPONSE_ERROR);
- The first argument is the object on which the signal is emitted.
- The second argument is the signal id.
- The third argument is the detail of the signal.
"change-file" signal and "open-response" signal doesn't have details and the argument is zero when no details.
- "change-file" signal doesn't have parameter, so no fourth parameter.
- "open-response" signal has one parameter.
The fourth parameter is the parameter.
Up: [Readme.md](src/Readme.md), Prev: [Section 8}](src/sec8.src.md), Next: [Section 10[(src/sec10.src.md)

38
src/class_gobject.c Normal file
View file

@ -0,0 +1,38 @@
typedef struct _GObjectClass GObjectClass;
typedef struct _GObjectClass GInitiallyUnownedClass;
struct _GObjectClass {
GTypeClass g_type_class;
/*< private >*/
GSList *construct_properties;
/*< public >*/
/* seldom overidden */
GObject* (*constructor) (GType type,
guint n_construct_properties,
GObjectConstructParam *construct_properties);
/* overridable methods */
void (*set_property) (GObject *object,
guint property_id,
const GValue *value,
GParamSpec *pspec);
void (*get_property) (GObject *object,
guint property_id,
GValue *value,
GParamSpec *pspec);
void (*dispose) (GObject *object);
void (*finalize) (GObject *object);
/* seldom overidden */
void (*dispatch_properties_changed) (GObject *object,
guint n_pspecs,
GParamSpec **pspecs);
/* signals */
void (*notify) (GObject *object,
GParamSpec *pspec);
/* called when done constructing */
void (*constructed) (GObject *object);
/*< private >*/
gsize flags;
/* padding */
gpointer pdummy[6];
};

108
src/classes.c Normal file
View file

@ -0,0 +1,108 @@
struct _GtkWidgetClass {
GInitiallyUnownedClass parent_class;
/*< public >*/
guint activate_signal;
/* basics */
void (* show) (GtkWidget *widget);
void (* hide) (GtkWidget *widget);
void (* map) (GtkWidget *widget);
void (* unmap) (GtkWidget *widget);
void (* realize) (GtkWidget *widget);
void (* unrealize) (GtkWidget *widget);
void (* root) (GtkWidget *widget);
void (* unroot) (GtkWidget *widget);
void (* size_allocate) (GtkWidget *widget,
int width,
int height,
int baseline);
void (* state_flags_changed) (GtkWidget *widget,
GtkStateFlags previous_state_flags);
void (* direction_changed) (GtkWidget *widget,
GtkTextDirection previous_direction);
void (* grab_notify) (GtkWidget *widget,
gboolean was_grabbed);
/* size requests */
GtkSizeRequestMode (* get_request_mode) (GtkWidget *widget);
void (* measure) (GtkWidget *widget,
GtkOrientation orientation,
int for_size,
int *minimum,
int *natural,
int *minimum_baseline,
int *natural_baseline);
/* Mnemonics */
gboolean (* mnemonic_activate) (GtkWidget *widget,
gboolean group_cycling);
/* explicit focus */
gboolean (* grab_focus) (GtkWidget *widget);
gboolean (* focus) (GtkWidget *widget,
GtkDirectionType direction);
void (* set_focus_child) (GtkWidget *widget,
GtkWidget *child);
/* keyboard navigation */
void (* move_focus) (GtkWidget *widget,
GtkDirectionType direction);
gboolean (* keynav_failed) (GtkWidget *widget,
GtkDirectionType direction);
/* accessibility support
*/
AtkObject * (* get_accessible) (GtkWidget *widget);
gboolean (* query_tooltip) (GtkWidget *widget,
gint x,
gint y,
gboolean keyboard_tooltip,
GtkTooltip *tooltip);
void (* compute_expand) (GtkWidget *widget,
gboolean *hexpand_p,
gboolean *vexpand_p);
void (* css_changed) (GtkWidget *widget,
GtkCssStyleChange *change);
void (* system_setting_changed) (GtkWidget *widget,
GtkSystemSetting settings);
void (* snapshot) (GtkWidget *widget,
GtkSnapshot *snapshot);
gboolean (* contains) (GtkWidget *widget,
gdouble x,
gdouble y);
/*< private >*/
GtkWidgetClassPrivate *priv;
gpointer padding[8];
};
struct _GtkTextViewClass {
GtkWidgetClass parent_class;
/*< public >*/
void (* move_cursor) (GtkTextView *text_view,
GtkMovementStep step,
gint count,
gboolean extend_selection);
void (* set_anchor) (GtkTextView *text_view);
void (* insert_at_cursor) (GtkTextView *text_view,
const gchar *str);
void (* delete_from_cursor) (GtkTextView *text_view,
GtkDeleteType type,
gint count);
void (* backspace) (GtkTextView *text_view);
void (* cut_clipboard) (GtkTextView *text_view);
void (* copy_clipboard) (GtkTextView *text_view);
void (* paste_clipboard) (GtkTextView *text_view);
void (* toggle_overwrite) (GtkTextView *text_view);
GtkTextBuffer * (* create_buffer) (GtkTextView *text_view);
void (* snapshot_layer) (GtkTextView *text_view,
GtkTextViewLayer layer,
GtkSnapshot *snapshot);
gboolean (* extend_selection) (GtkTextView *text_view,
GtkTextExtendSelection granularity,
const GtkTextIter *location,
GtkTextIter *start,
GtkTextIter *end);
void (* insert_emoji) (GtkTextView *text_view);
/*< private >*/
gpointer padding[8];
};
/* The following definition is generated by the macro G_DECLARE_FINAL_TYPE
typedef struct {
GtkTextView parent_class;
} TfeTextViewClass;

8
src/gvarianttype_test.c Normal file
View file

@ -0,0 +1,8 @@
#include <glib.h>
int
main (int argc, char **argv) {
GVariantType *vtype = g_variant_type_new ("s");
const gchar *type_string = g_variant_type_peek_string (vtype);
g_print ("%s\n",type_string);
}

29
src/lb1.c Normal file
View file

@ -0,0 +1,29 @@
#include <gtk/gtk.h>
static void
on_activate (GApplication *app, gpointer user_data) {
GtkWidget *win;
GtkWidget *lab;
win = gtk_application_window_new (GTK_APPLICATION (app));
gtk_window_set_title (GTK_WINDOW (win), "lb4");
gtk_window_set_default_size (GTK_WINDOW (win), 400, 300);
lab = gtk_label_new ("Hello.");
gtk_window_set_child (GTK_WINDOW (win), lab);
gtk_widget_show (win);
}
int
main (int argc, char **argv) {
GtkApplication *app;
int stat;
app = gtk_application_new ("com.github.ToshioCP.lb1", G_APPLICATION_FLAGS_NONE);
g_signal_connect (app, "activate", G_CALLBACK (on_activate), NULL);
stat =g_application_run (G_APPLICATION (app), argc, argv);
g_object_unref (app);
return stat;
}

35
src/lb2.c Normal file
View file

@ -0,0 +1,35 @@
#include <gtk/gtk.h>
static void
on_clicked (GtkButton *btn, gpointer user_data) {
g_print ("Clicked.\n");
}
static void
on_activate (GApplication *app, gpointer user_data) {
GtkWidget *win;
GtkWidget *btn;
win = gtk_application_window_new (GTK_APPLICATION (app));
gtk_window_set_title (GTK_WINDOW (win), "lb4");
gtk_window_set_default_size (GTK_WINDOW (win), 400, 300);
btn = gtk_button_new_with_label ("Click me");
gtk_window_set_child (GTK_WINDOW (win), btn);
g_signal_connect (btn, "clicked", G_CALLBACK (on_clicked), NULL);
gtk_widget_show (win);
}
int
main (int argc, char **argv) {
GtkApplication *app;
int stat;
app = gtk_application_new ("com.github.ToshioCP.lb2", G_APPLICATION_FLAGS_NONE);
g_signal_connect (app, "activate", G_CALLBACK (on_activate), NULL);
stat =g_application_run (G_APPLICATION (app), argc, argv);
g_object_unref (app);
return stat;
}

36
src/lb3.c Normal file
View file

@ -0,0 +1,36 @@
#include <gtk/gtk.h>
static void
on_clicked (GtkButton *btn, gpointer user_data) {
GtkWindow *win = GTK_WINDOW (user_data);
gtk_window_destroy (win);
}
static void
on_activate (GApplication *app, gpointer user_data) {
GtkWidget *win;
GtkWidget *btn;
win = gtk_application_window_new (GTK_APPLICATION (app));
gtk_window_set_title (GTK_WINDOW (win), "lb4");
gtk_window_set_default_size (GTK_WINDOW (win), 400, 300);
btn = gtk_button_new_with_label ("Quit");
gtk_window_set_child (GTK_WINDOW (win), btn);
g_signal_connect (btn, "clicked", G_CALLBACK (on_clicked), win);
gtk_widget_show (win);
}
int
main (int argc, char **argv) {
GtkApplication *app;
int stat;
app = gtk_application_new ("com.github.ToshioCP.lb3", G_APPLICATION_FLAGS_NONE);
g_signal_connect (app, "activate", G_CALLBACK (on_activate), NULL);
stat =g_application_run (G_APPLICATION (app), argc, argv);
g_object_unref (app);
return stat;
}

58
src/lb4.c Normal file
View file

@ -0,0 +1,58 @@
#include <gtk/gtk.h>
static void
on_clicked1 (GtkButton *btn, gpointer user_data) {
const gchar *s;
s = gtk_button_get_label (btn);
if (g_strcmp0 (s, "Hello.") == 0)
gtk_button_set_label (btn, "Good-bye.");
else
gtk_button_set_label (btn, "Hello.");
}
static void
on_clicked2 (GtkButton *btn, gpointer user_data) {
GtkWindow *win = GTK_WINDOW (user_data);
gtk_window_destroy (win);
}
static void
on_activate (GApplication *app, gpointer user_data) {
GtkWidget *win;
GtkWidget *box;
GtkWidget *btn1;
GtkWidget *btn2;
win = gtk_application_window_new (GTK_APPLICATION (app));
gtk_window_set_title (GTK_WINDOW (win), "lb4");
gtk_window_set_default_size (GTK_WINDOW (win), 400, 300);
box = gtk_box_new (GTK_ORIENTATION_VERTICAL, 5);
gtk_box_set_homogeneous (GTK_BOX (box), TRUE);
gtk_window_set_child (GTK_WINDOW (win), box);
btn1 = gtk_button_new_with_label ("Hello.");
g_signal_connect (btn1, "clicked", G_CALLBACK (on_clicked1), NULL);
btn2 = gtk_button_new_with_label ("Quit");
g_signal_connect (btn2, "clicked", G_CALLBACK (on_clicked2), win);
gtk_box_append (GTK_BOX (box), btn1);
gtk_box_append (GTK_BOX (box), btn2);
gtk_widget_show (win);
}
int
main (int argc, char **argv) {
GtkApplication *app;
int stat;
app = gtk_application_new ("com.github.ToshioCP.lb4", G_APPLICATION_FLAGS_NONE);
g_signal_connect (app, "activate", G_CALLBACK (on_activate), NULL);
stat =g_application_run (G_APPLICATION (app), argc, argv);
g_object_unref (app);
return stat;
}

47
src/menu1.c Normal file
View file

@ -0,0 +1,47 @@
#include <gtk/gtk.h>
static void
quit_activated(GSimpleAction *action, GVariant *parameter, gpointer app)
{
g_application_quit (G_APPLICATION(app));
}
static void
on_activate (GApplication *app, gpointer user_data) {
GtkWidget *win = gtk_application_window_new (GTK_APPLICATION (app));
gtk_window_set_title (GTK_WINDOW (win), "menu1");
gtk_window_set_default_size (GTK_WINDOW (win), 400, 300);
GSimpleAction *act_quit = g_simple_action_new ("quit", NULL);
g_action_map_add_action (G_ACTION_MAP (app), G_ACTION (act_quit));
g_signal_connect (act_quit, "activate", G_CALLBACK (quit_activated), app);
GMenu *menubar = g_menu_new ();
GMenuItem *menu_item_menu = g_menu_item_new ("Menu", NULL);
GMenu *menu = g_menu_new ();
GMenuItem *menu_item_quit = g_menu_item_new ("Quit", "app.quit");
g_menu_append_item (menu, menu_item_quit);
g_object_unref (menu_item_quit);
g_menu_item_set_submenu (menu_item_menu, G_MENU_MODEL (menu));
g_menu_append_item (menubar, menu_item_menu);
g_object_unref (menu_item_menu);
gtk_application_set_menubar (GTK_APPLICATION (app), G_MENU_MODEL (menubar));
gtk_application_window_set_show_menubar (GTK_APPLICATION_WINDOW (win), TRUE);
gtk_window_present (GTK_WINDOW (win));
/* gtk_widget_show (win); is also OKay instead of gtk_window_present. */
}
int
main (int argc, char **argv) {
GtkApplication *app;
int stat;
app = gtk_application_new ("com.github.ToshioCP.menu1", G_APPLICATION_FLAGS_NONE);
g_signal_connect (app, "activate", G_CALLBACK (on_activate), NULL);
stat =g_application_run (G_APPLICATION (app), argc, argv);
g_object_unref (app);
return stat;
}

105
src/menu2.c Normal file
View file

@ -0,0 +1,105 @@
#include <gtk/gtk.h>
static GtkCssProvider *provider;
static void
fullscreen_changed(GSimpleAction *action, GVariant *value, gpointer win) {
if (g_variant_get_boolean (value))
gtk_window_maximize (GTK_WINDOW (win));
else
gtk_window_unmaximize (GTK_WINDOW (win));
g_simple_action_set_state (action, value);
}
static void
color_activated(GSimpleAction *action, GVariant *parameter, gpointer win) {
gchar *color = g_strdup_printf ("label#lb {background-color: %s;}", g_variant_get_string (parameter, NULL));
gtk_css_provider_load_from_data (provider, color, -1);
g_free (color);
g_action_change_state (G_ACTION (action), parameter);
}
static void
quit_activated(GSimpleAction *action, GVariant *parameter, gpointer app)
{
g_application_quit (G_APPLICATION(app));
}
static void
on_activate (GApplication *app, gpointer user_data) {
GtkWidget *win = gtk_application_window_new (GTK_APPLICATION (app));
gtk_window_set_title (GTK_WINDOW (win), "menu2");
gtk_window_set_default_size (GTK_WINDOW (win), 400, 300);
GtkWidget *lb = gtk_label_new (NULL);
gtk_widget_set_name (lb, "lb"); /* the name is used by CSS Selector */
gtk_window_set_child (GTK_WINDOW (win), lb);
GSimpleAction *act_fullscreen
= g_simple_action_new_stateful ("fullscreen", NULL, g_variant_new_boolean (FALSE));
GSimpleAction *act_color
= g_simple_action_new_stateful ("color", g_variant_type_new("s"), g_variant_new_string ("red"));
GSimpleAction *act_quit
= g_simple_action_new ("quit", NULL);
GMenu *menubar = g_menu_new ();
GMenu *menu = g_menu_new ();
GMenu *section1 = g_menu_new ();
GMenu *section2 = g_menu_new ();
GMenu *section3 = g_menu_new ();
GMenuItem *menu_item_fullscreen = g_menu_item_new ("Full Screen", "win.fullscreen");
GMenuItem *menu_item_red = g_menu_item_new ("Red", "win.color::red");
GMenuItem *menu_item_green = g_menu_item_new ("Green", "win.color::green");
GMenuItem *menu_item_blue = g_menu_item_new ("Blue", "win.color::blue");
GMenuItem *menu_item_quit = g_menu_item_new ("Quit", "app.quit");
g_signal_connect (act_fullscreen, "change-state", G_CALLBACK (fullscreen_changed), win);
g_signal_connect (act_color, "activate", G_CALLBACK (color_activated), win);
g_signal_connect (act_quit, "activate", G_CALLBACK (quit_activated), app);
g_action_map_add_action (G_ACTION_MAP (win), G_ACTION (act_fullscreen));
g_action_map_add_action (G_ACTION_MAP (win), G_ACTION (act_color));
g_action_map_add_action (G_ACTION_MAP (app), G_ACTION (act_quit));
g_menu_append_item (section1, menu_item_fullscreen);
g_menu_append_item (section2, menu_item_red);
g_menu_append_item (section2, menu_item_green);
g_menu_append_item (section2, menu_item_blue);
g_menu_append_item (section3, menu_item_quit);
g_object_unref (menu_item_red);
g_object_unref (menu_item_green);
g_object_unref (menu_item_blue);
g_object_unref (menu_item_fullscreen);
g_object_unref (menu_item_quit);
g_menu_append_section (menu, NULL, G_MENU_MODEL (section1));
g_menu_append_section (menu, "Color", G_MENU_MODEL (section2));
g_menu_append_section (menu, NULL, G_MENU_MODEL (section3));
g_menu_append_submenu (menubar, "Menu", G_MENU_MODEL (menu));
gtk_application_set_menubar (GTK_APPLICATION (app), G_MENU_MODEL (menubar));
gtk_application_window_set_show_menubar (GTK_APPLICATION_WINDOW (win), TRUE);
/* GtkCssProvider *provider = gtk_css_provider_new ();*/
provider = gtk_css_provider_new ();
GdkDisplay *display = gtk_widget_get_display (GTK_WIDGET (win));
gtk_css_provider_load_from_data (provider, "label#lb {background-color: red;}", -1);
gtk_style_context_add_provider_for_display (display, GTK_STYLE_PROVIDER (provider),
GTK_STYLE_PROVIDER_PRIORITY_USER);
/* gtk_widget_show (win);*/
gtk_window_present (GTK_WINDOW (win));
}
int
main (int argc, char **argv) {
GtkApplication *app;
int stat;
app = gtk_application_new ("com.github.ToshioCP.menu2", G_APPLICATION_FLAGS_NONE);
g_signal_connect (app, "activate", G_CALLBACK (on_activate), NULL);
stat =g_application_run (G_APPLICATION (app), argc, argv);
g_object_unref (app);
return stat;
}

106
src/menu3/menu3.c Normal file
View file

@ -0,0 +1,106 @@
#include <gtk/gtk.h>
static void
new_activated (GSimpleAction *action, GVariant *parameter, gpointer win) {
}
static void
open_activated (GSimpleAction *action, GVariant *parameter, gpointer win) {
}
static void
save_activated (GSimpleAction *action, GVariant *parameter, gpointer win) {
}
static void
saveas_activated (GSimpleAction *action, GVariant *parameter, gpointer win) {
}
static void
close_activated (GSimpleAction *action, GVariant *parameter, gpointer win) {
}
static void
cut_activated (GSimpleAction *action, GVariant *parameter, gpointer win) {
}
static void
copy_activated (GSimpleAction *action, GVariant *parameter, gpointer win) {
}
static void
paste_activated (GSimpleAction *action, GVariant *parameter, gpointer win) {
}
static void
selectall_activated (GSimpleAction *action, GVariant *parameter, gpointer win) {
}
static void
fullscreen_changed (GSimpleAction *action, GVariant *state, gpointer win) {
if (g_variant_get_boolean (state))
gtk_window_maximize (GTK_WINDOW (win));
else
gtk_window_unmaximize (GTK_WINDOW (win));
g_simple_action_set_state (action, state);
}
static void
quit_activated (GSimpleAction *action, GVariant *parameter, gpointer app)
{
g_application_quit (G_APPLICATION(app));
}
static void
on_activate (GApplication *app, gpointer user_data) {
GtkWidget *win = gtk_application_window_new (GTK_APPLICATION (app));
const GActionEntry win_entries[] = {
{ "new", new_activated, NULL, NULL, NULL },
{ "open", open_activated, NULL, NULL, NULL },
{ "save", save_activated, NULL, NULL, NULL },
{ "saveas", saveas_activated, NULL, NULL, NULL },
{ "close", close_activated, NULL, NULL, NULL },
{ "cut", cut_activated, NULL, NULL, NULL },
{ "copy", copy_activated, NULL, NULL, NULL },
{ "paste", paste_activated, NULL, NULL, NULL },
{ "selectall", selectall_activated, NULL, NULL, NULL },
{ "fullscreen", NULL, NULL, "false", fullscreen_changed }
};
g_action_map_add_action_entries (G_ACTION_MAP (win), win_entries, G_N_ELEMENTS (win_entries), win);
gtk_application_window_set_show_menubar (GTK_APPLICATION_WINDOW (win), TRUE);
gtk_window_set_title (GTK_WINDOW (win), "menu3");
gtk_window_set_default_size (GTK_WINDOW (win), 400, 300);
gtk_widget_show (win);
}
static void
on_startup (GApplication *app, gpointer user_data) {
GtkBuilder *builder = gtk_builder_new_from_resource ("/com/github/ToshioCP/menu3/menu3.ui");
GMenuModel *menubar = G_MENU_MODEL (gtk_builder_get_object (builder, "menubar"));
gtk_application_set_menubar (GTK_APPLICATION (app), menubar);
g_object_unref (builder);
const GActionEntry app_entries[] = {
{ "quit", quit_activated, NULL, NULL, NULL }
};
g_action_map_add_action_entries (G_ACTION_MAP (app), app_entries, G_N_ELEMENTS (app_entries), app);
}
int
main (int argc, char **argv) {
GtkApplication *app;
int stat;
app = gtk_application_new ("com.github.ToshioCP.menu3", G_APPLICATION_FLAGS_NONE);
g_signal_connect (app, "startup", G_CALLBACK (on_startup), NULL);
g_signal_connect (app, "activate", G_CALLBACK (on_activate), NULL);
stat =g_application_run (G_APPLICATION (app), argc, argv);
g_object_unref (app);
return stat;
}

View file

@ -0,0 +1,6 @@
<?xml version="1.0" encoding="UTF-8"?>
<gresources>
<gresource prefix="/com/github/ToshioCP/menu3">
<file>menu3.ui</file>
</gresource>
</gresources>

72
src/menu3/menu3.ui Normal file
View file

@ -0,0 +1,72 @@
<?xml version="1.0" encoding="UTF-8"?>
<interface>
<menu id="menubar">
<submenu>
<attribute name="label">File</attribute>
<section>
<item>
<attribute name="label">New</attribute>
<attribute name="action">win.new</attribute>
</item>
<item>
<attribute name="label">Open</attribute>
<attribute name="action">win.open</attribute>
</item>
</section>
<section>
<item>
<attribute name="label">Save</attribute>
<attribute name="action">win.save</attribute>
</item>
<item>
<attribute name="label">Save As…</attribute>
<attribute name="action">win.saveas</attribute>
</item>
</section>
<section>
<item>
<attribute name="label">Close</attribute>
<attribute name="action">win.close</attribute>
</item>
</section>
<section>
<item>
<attribute name="label">Quit</attribute>
<attribute name="action">app.quit</attribute>
</item>
</section>
</submenu>
<submenu>
<attribute name="label">Edit</attribute>
<section>
<item>
<attribute name="label">Cut</attribute>
<attribute name="action">win.cut</attribute>
</item>
<item>
<attribute name="label">Copy</attribute>
<attribute name="action">win.copy</attribute>
</item>
<item>
<attribute name="label">Paste</attribute>
<attribute name="action">win.paste</attribute>
</item>
</section>
<section>
<item>
<attribute name="label">Select All</attribute>
<attribute name="action">win.selectall</attribute>
</item>
</section>
</submenu>
<submenu>
<attribute name="label">View</attribute>
<section>
<item>
<attribute name="label">Full Screen</attribute>
<attribute name="action">win.fullscreen</attribute>
</item>
</section>
</submenu>
</menu>
</interface>

10
src/menu3/meson.build Normal file
View file

@ -0,0 +1,10 @@
project('menu3', 'c')
gtkdep = dependency('gtk4')
gnome=import('gnome')
resources = gnome.compile_resources('resources','menu3.gresource.xml')
sourcefiles=files('menu3.c')
executable('menu3', sourcefiles, resources, dependencies: gtkdep)

13
src/misc/pr1.c Normal file
View file

@ -0,0 +1,13 @@
#include <gtk/gtk.h>
int
main (int argc, char **argv) {
GtkApplication *app;
int stat;
app = gtk_application_new ("com.github.ToshioCP.pr1", G_APPLICATION_FLAGS_NONE);
stat =g_application_run (G_APPLICATION (app), argc, argv);
g_object_unref (app);
return stat;
}

19
src/misc/pr2.c Normal file
View file

@ -0,0 +1,19 @@
#include <gtk/gtk.h>
static void
on_activate (GApplication *app, gpointer *user_data) {
g_print ("GtkApplication is activated.\n");
}
int
main (int argc, char **argv) {
GtkApplication *app;
int stat;
app = gtk_application_new ("com.github.ToshioCP.pr2", G_APPLICATION_FLAGS_NONE);
g_signal_connect (app, "activate", G_CALLBACK (on_activate), NULL);
stat =g_application_run (G_APPLICATION (app), argc, argv);
g_object_unref (app);
return stat;
}

23
src/misc/pr3.c Normal file
View file

@ -0,0 +1,23 @@
#include <gtk/gtk.h>
static void
on_activate (GApplication *app, gpointer user_data) {
GtkWidget *win;
win = gtk_window_new ();
gtk_window_set_application (GTK_WINDOW (win), GTK_APPLICATION (app));
gtk_widget_show (win);
}
int
main (int argc, char **argv) {
GtkApplication *app;
int stat;
app = gtk_application_new ("com.github.ToshioCP.pr3", G_APPLICATION_FLAGS_NONE);
g_signal_connect (app, "activate", G_CALLBACK (on_activate), NULL);
stat =g_application_run (G_APPLICATION (app), argc, argv);
g_object_unref (app);
return stat;
}

24
src/misc/pr4.c Normal file
View file

@ -0,0 +1,24 @@
#include <gtk/gtk.h>
static void
on_activate (GApplication *app, gpointer user_data) {
GtkWidget *win;
win = gtk_application_window_new (GTK_APPLICATION (app));
gtk_window_set_title (GTK_WINDOW (win), "pr4");
gtk_window_set_default_size (GTK_WINDOW (win), 400, 300);
gtk_widget_show (win);
}
int
main (int argc, char **argv) {
GtkApplication *app;
int stat;
app = gtk_application_new ("com.github.ToshioCP.pr4", G_APPLICATION_FLAGS_NONE);
g_signal_connect (app, "activate", G_CALLBACK (on_activate), NULL);
stat =g_application_run (G_APPLICATION (app), argc, argv);
g_object_unref (app);
return stat;
}

238
src/sec1.src.md Normal file
View file

@ -0,0 +1,238 @@
# GtkApplication and GtkApplicationWindow
## GtkApplication
### GtkApplication and g\_application\_run
Usually people write a programming code to make an application.
What are appications?
Applications are software that runs using libraries, which includes OS, frameworks and so on.
In Gtk4 programming, GtkApplication is an object that runs on GTK libraries.
The basic way how to write GtkApplication is as follows.
- Generate a GtkApplication object
- Run it
That's all.
Very simple.
The following is the C code representing the scenario above.
@@@ misc/pr1.c
The first line says that this program includes the GTK header libraries.
The function `main` above is a startup function in C language.
The variable `app` is defined as a pointer to GtkApplication, which is actually a structure in which information about the application is stored.
The function `gtk_application_new` generates a GtkApplication and sets its pointer to `app`.
The meaning of the arguments will be explained later.
The function `g_application_run` invokes the GtkApplication pointed by `app`.
(We often say that the function invokes `app`.
Actually, `app` is not an object but an pointer to the object.
However, it is simple and short, and probably no confusion occurs.)
To compile this, the following command needs to be run.
The string pr1.c is the filename of the C source code.
$ gcc `pkg-config --cflags gtk4` pr1.c `pkg-config --libs gtk4`
The C compiler gcc generates an executable file `a.out`.
Let's run it.
$ ./a.out
(a.out:13533): GLib-GIO-WARNING **: 15:30:17.449: Your application does not implement g_application_activate() and has no handlers connected to the "activate" signal. It should do one of these.
$
Oh, just an error message.
But this error message means that the GtkApplication object ran without a doubt.
Now, think about the message in the next section.
### signal
The message tells us that:
1. The application GtkApplication doesn't implement `g_application_activate()`.
2. And it has no handlers connected to the activate signal.
3. You need to solve at least one of this.
These two cause of the error are related to signals.
So, I will explain it to you first.
Signal is emitted when something happens.
For example, a window is generated, a window is destroyed and so on.
The signal "activate" is emitted when the application is activated.
If the signal is connected to a function, which is called signal handler or simply handler, then the function invokes when the signal emits.
The flow is like this:
1. Something happens.
2. If it's related to a certain signal, then the signal is emitted.
3. If the signal is connected to a handler in advance, then the handler is invoked.
Signals are defined in objects.
For example, "activate" signal belongs to GApplication object, which is a parent object of GtkApplication object.
GApplication object is a child object of GObject object.
GObject is the top object in the hierarchy of all the objects.
GObject -- GApplication -- GtkApplication
<---parent --->child
A child object derives signals, functions, properties and so on from its parent object.
So, Gtkapplication also has the "activate" signal.
Now we can solve the problem in `pr1.c`.
We need to connect the activate signal to a handler.
We use a function `g_signal_connect` which connects a signal to a handler.
@@@ misc/pr2.c
First, we define the handler `on_activate` which simply displays a message.
In the function `main`, we add `g_signal_connect` before `g_application_run`.
The function `g_signal_connect` has four arguments.
1. An object to which the signal belongs.
2. The name of the signal.
3. A handler function (also called callback), which needs to be casted by `G_CALLBACK`.
4. Data to pass to the handler. If no data is necessary, NULL should be given.
You can find the description of each signal in API reference.
For example, "activate" signal is in GApplication subsection in GIO API reference.
The handler function is described in that subsection.
In addition, `g_signal_connect` is described in GObject API reference.
API reference is very important.
You should see and understand it to write GTK applications.
Let's compile the source file `pr2.c` above and run it.
$ gcc `pkg-config --cflags gtk4` pr2.c `pkg-config --libs gtk4`
$ ./a.out
GtkApplication is activated.
$
OK, well done.
However, you may have noticed that it's painful to type such a long line to compile.
It is a good idea to use shell script to solve this problem.
Make a text file which contains the following text.
gcc `pkg-config --cflags gtk4` $1.c `pkg-config --libs gtk4`
Then, save it in $HOME/bin, which is usually /home/(username)/bin.
(If your user name is James, then the directory is /home/james/bin).
And turn on the execute bit of the file.
Suppose the filename is comp, then the procedure is as follows.
$ chmod 755 $HOME/bin/comp
$ ls -log $HOME/bin
... ... ...
-rwxr-xr-x 1 62 May 23 08:21 comp
... ... ...
If this is the first time that you make a $HOME/bin directory and save a file in it, then you need to logout and login again.
$ comp pr2
$ ./a.out
GtkApplication is activated.
$
## GtkWindow and GtkApplicationWindow
### GtkWindow
A message "GtkApplication is activated." was printed out in the previous subsection.
It was good in terms of a test of GtkApplication.
However, it is insufficient because GTK is a framework for graphical user interface (GUI).
Now we go ahead with adding a window into this program.
What we need to do is:
1. Generate a GtkWindow.
2. Connect it to GtkApplication.
3. Show the window.
Now rewrite the function `on_activate`.
#### Generate a GtkWindow
@@@ misc/pr3.c on_activate
Widget is an abstract concept that includes all the GUI interfaces such as windows, dialogs, buttons, multiline text, containers and so on.
And GtkWidget is a base object from which all the GUI objects derive.
parent <-----> child
GtkWidget -- GtkWindow
GtkWindow includes GtkWidget at the top of its object.
![GtkWindow and GtkWidget](window_widget.png)
The function `gtk_window_new` is defined as follows.
GtkWidget *
gtk_window_new (void);
By this definition, it returns a pointer to GtkWidget, not GtkWindow.
It actually generates a new GtkWindow object (not GtkWidget) but returns a pointer to GtkWidget.
However,the pointer points the GtkWidget and at the same time it also points GtkWindow that contains GtkWidget in it.
If you want to use `win` as a pointer to the GtkWindow, you need to cast it.
(GtkWindow *) win
Or you can use `GTK_WINDOW` macro that performs a similar function.
GTK_WINDOW (win)
This is a recommended way.
#### Connect it to GtkApplication.
The function `gtk_window_set_application` is used to connect GtkWidow to GtkApplication.
gtk_window_set_application (GTK_WINDOW (win), GTK_APPLICATION (app));
You need to cast `win` to GtkWindow and `app` to GtkApplication.
`GTK_WINDOW` and `GTK_APPLICATION` macro is appropriate for that.
GtkApplication continues to run until the related window is destroyed.
If you didn't connect GtkWindow and GtkApplication, GtkApplication shutdowns soon.
Because no window is connected to GtkApplication, it doesn't need to wait anything.
As it shutdowns the generated window is also destroyed.
#### Show the window.
The function `gtk_widget_show` is used to show the window.
Gtk4 changed the default widget visibility to on, so every widget doesn't need this function to show itself.
But, there's an exception.
Top window (this term will be explained later) isn't visible when it is generated.
So you need to use the function above and show the window.
Save the program as `pr3.c` and compile and run it.
$ comp pr3
$ ./a.out
A small window appears.
![Screenshot of the window](screenshot_pr3.png)
Click on the close button then the window disappears and the program finishes.
### GtkApplicationWindow
GtkApplicationWindow is a child object of GtkWindow.
It has some extra functionality for better integration with GtkApplication.
It is recommended to use it instead of GtkWindow when you use GtkApplication.
Now rewrite the program and use GtkAppliction Window.
@@@ misc/pr4.c on_activate
When you generate GtkApplicationWindow, you need to give GtkApplication object as an argument.
Then it automatically connect these two objects.
So you don't need to call `gtk_window_set_application` any more.
The program sets the title and the default size of the window.
Compile it and run `a.out`, then you will see a bigger window with its title "pr4".
![Screenshot of the window](screenshot_pr4.png)

176
src/sec10.src.md Normal file
View file

@ -0,0 +1,176 @@
# Functions in TfeTextView
In this section I will explain each function in TfeTextView object.
### tfe.h and tfetextview.h
`tfe.h` is a top header file and it includes `gtk.h` and all the header files.
Every C source files, which are `tfeapplication.c`, `tfenotebook.c` and `tfetextview.c`, include `tfe.h` at the beginning of each file.
@@@ tfe5/tfe.h
`tfetextview.h` is a header file which describes the public functions in `tfetextview.c`.
@@@ tfe5/tfetextview.h
- 1-2: These two lines are used to define TfeTextView.
- 4-10: Definitions of parameter used in the handler of "open-response" signal.
- 12-28: Public functions on GtkTextView.
Each function will be explained later in this section.
## Functions to generate TfeTextView object
TfeTextView Object is generated by `tfe_text_view_new` or `tfe_text_view_new_with_file`.
GtkWidget *tfe_text_view_new (void);
`tfe_text_view_new` just generates a new TfeTextView object and returns the pointer to the new object.
GtkWidget *tfe_text_view_new_with_file (GFile *file);
`tfe_text_view_new_with_file` is given a Gfile object as the argument and it loads the file into the GtkTextBuffer object, then returns the pointer to the new object.
Parameter:
- `file`: a pointer to the GFile object.
Return value:
- A pointer to the generated TfeTextView object but it is casted to a pointer to GtkWidget.
If an error occures during the genration process, NULL is returned.
Each function is defined as follows.
@@@ tfe5/tfetextview.c tfe_text_view_new_with_file tfe_text_view_new
- 18-21: `tfe_text_view_new`.
Just returns the value from the function `gtk_widget_new`.
Initialization is done in `tfe_text_view_init` which is called in the process of `gtk_widget_new` function.
- 1-16: `tfe_text_view_new_with_file`
- 3: `g_return_val_if_fail` is described in [Glib API reference](https://developer.gnome.org/glib/stable/glib-Warnings-and-Assertions.html#g-return-val-if-fail).
It tests whether the argument `file` is a pointer to GFile.
If it's true, then the program goes on to the next line.
If it's false, then it returns NULL (the second argument) immediately.
And at the same time it logs out the error message (usually the log is outputted to stderr or stdout).
This function is used to check the programmer's error.
If an error occurs, the solution is usually to change the (caller) program and fix the bug.
You need to distinguish programmer's errors and runtime errors.
You shouldn't use this function to find runtime errors.
- 9-10: If an error occurs when reading the file, then return NULL.
- 11-15: Generate TfeTextView and set the pointer to it to `tv`.
Set the contents read from the file to GtkTextBuffer `tv->tb`.
Free the memories pointed by `contents`.
Duplicate `file` and set it to `tv->file`.
Return `tv`.
## Save and saveas functions
Save and saveas functions write the contents in GtkTextBuffer to a file.
void tfe_text_view_save (TfeTextView *tv)
`save` function writes the contents in GtkTextBuffer to a file specified by `tv->file`.
If `tv->file` is NULL, then it shows GtkFileChooserDialog and lets the user to give a file to the program. After that, it saves the contents to the specified file and set the file into `tv->file`.
void tfe_text_view_saveas (TfeTextView *tv)
`saveas` function uses GtkFileChooserDialog and lets the user to give a new file to the program. Then, the function changes `tv->file` and save the contents to the specified new file.
If an error occures, it is shown to the user through the message dialog.
The error is managed only in the object and no information is notified to the caller.
@@@ tfe5/tfetextview.c saveas_dialog_response tfe_text_view_save tfe_text_view_saveas
- 17-53: `Tfe_text_view_save` function.
- 19: If `tv` is not a pointer to TfeTextView, then it logs an error message and immediately returns.
This function is similar to `g_return_val_if_fail` function, but no value is returned because `tfe_text_view_save` doesn't return a value.
- 28-29: If the buffer hasn't modified, then it doesn't need to save it.
So the function returns.
- 30-31: If `tv->file` is NULL, no file has given yet.
It calls `tfe_text_view_save`, which lets the user to choose a file to save.
- 33-35: Save the buffer to the file.
If it succeeds, assigns FALSE to `tv->changed`.
- 38-50: If file writing fails, it assigns NULL to `tv->file`.
Emits "change-file" signal.
Shows the error message dialog (45-49).
Because the handler is `gtk_window_destroy`, the dialog disappears when user clicks on the button in the dialog.
- 55-68: `tfe_text_view_saveas` function.
It shows GtkFileChooserDialog and lets the user choose a file and give it to the signal handler.
- 62: Generate GtkFileChooserDialog.
The title is "Save file".
Transient parent of the dialog is `win`, which is the top level window.
The action is save mode.
The buttons are Cancel and Save.
- 63: connect the "response" signal of the dialog and `saveas_dialog_response` handler.
- 1-15: `saveas_dialog_response` signal handler.
- 5-13: If the response is `GTK_RESPONSE_ACCEPT`, which is set to the argument when the user has clicked on Save button, then gets a pointer to the GFile object, set it to `tv->file`, assign TRUE to `tv->changed`, emits "change-file" signal then call `tfe_text_view_save` to save the buffer to the file.
![Saveas process](saveas.png)
When you use GtkFileChooserDialog, you need to divide the program into two parts.
They are a function which generates GtkFileChooserDialog and the signal handler.
The function just generates and shows the dialog.
The rest is done by the handler.
It gets Gfile from GtkFileChooserDialog, save the buffer to the file and do some things necessary.
## Open function
Open function shows GtkFileChooserDialog to the user and let him/her choose a file.
Then read the file and set it to GtkTextBuffer.
void tfe_text_view_open (TfeTextView *tv)
TfeTextView object `tv` has to be generated in advance.
And it should be empty and `tv->file` is NULL.
If it is not empty, `tfe_text_view_open` doesn't treat it as an error.
If you want to revert the buffer, calling this function is apropreate.
Otherwise probably bad things will happen.
@@@ tfe5/tfetextview.c open_dialog_response tfe_text_view_open
- 33-45: `tfe_text_view_open` function.
- 39: Generate GtkFileChooserDialog.
The title is "Open file".
No transient parent window.
The action is open mode.
The buttons are Cancel and Open.
- 43: connect the "reponse" signal of the dialog and `open_dialog_response` signal handler.
- 44: Show the dialog.
- 1-31: `open_dialog_response` signal handler.
- 9-10: If the response from GtkFileChooserDialog is not `GTK_RESPONSE_ACCEPT`, which means the user has clicked on the "Cancel" button or close button, then it emits "open-response" signal with the parameter `TFE_OPEN_RESPONSE_CANCEL`.
- 11-12: Get a pointer to Gfile by `gtk_file_chooser_get_file`.
If it is not GFile, maybe an error occured.
Then it emits "open-response" signal with the parameter `TFE_OPEN_RESPONSE_ERROR`.
- 13-22: If an error occurs when it has read the file, then it decreases the reference count of Gfile, shows a message dialog to report the error to the user and emits "open-response" signal with the parameter `TFE_OPEN_RESPONSE_ERROR`.
- 24-28: If the file has successfully read, then the text is set to GtkTextBuffer, free the temporary buffer pointed by `contents`, set file to `tv->file` (no duplication or unref is not necessary) and emits "open-response" signal with the parameter `TFE_OPEN_RESPONSE_SUCCESS`.
- 30: close GtkFileCooserDialog.
Now let's think about the whole process between the other object (caller) and TfeTextView.
It is shown in the following diagram and you would think that it is really complicated.
Because signal is the only way for GtkFileChooserDialog to communicate with others.
In Gtk3, `gtk_dialog_run` function is available.
It simplifies the process.
However, in Gtk4, `gtk_dialog_run`is unavailable any more.
![Caller and TfeTextView](open.png)
1. A caller get a pointer `tv` to TfeTextView by calling `tfe_text_view_new`.
2. The caller connects the handler (left bottom in the diagram) and the signal "open-response".
3. It calls `tfe_text_view_open` to let the user select a file from GtkFileChooserDialog.
4. The dialog emits a signal and it invokes the handler `open_dialog_response`.
5. The handler read the file and set it into GtkTextBuffer and emits a signal to inform the response status.
6. The handler outside TfeTextView recieves the signal.
## Get file function
`gtk_text_view_get_file` is a simple function show as follows.
@@@ tfe5/tfetextview.c tfe_text_view_get_file
The important thing is duplicate `tv->file`.
Otherwise, if the caller free the GFile object, `tv->file` is no more guaranteed to point the GFile.
## Source file of tfetextview.c
All the source files are listed in [Section 13](ch13.html).

92
src/sec11.src.md Normal file
View file

@ -0,0 +1,92 @@
# Functions with GtkNotebook
GtkNotebook is a very important object in the text file editor `tfe`.
It connects the application and TfeTextView objects.
`tfenotebook.h` and `tfenotebook.c` describe a set of functions related to GtkTextbook.
@@@ tfe5/tfenotebook.h
This header file shows the public functions in `tfenotebook.c`.
- `notebook_page_new` generates a new GtkNotebookPage and adds GtkScrolledWindow and TfeTextView under the page.
- `notebook_page_new_with_file` generates a new GtkNotebookPage and adds GtkScrolledWindow and TfeTextView under the page. `file` is set to the pointer to GFile in the TfeTextView object and the file is read and set into GtkTextBuffer.
- `notebook_page_open` lets the user select a file and sets it into GtkTextBuffer.
- `notebook_page_save` save the contents in GtkTextBuffer to a file, using the pointer `tv->file`.
You probably find that the functions above are higher level functions of `tfe_text_view_new`, `tfe_text_view_new_with_file`, `tef_text_view_open` and `tfe_text_view_save` respectively.
There are two layers.
One of them is `tfe_text_view ...`, which is the lower level layer.
The other is `note_book ...`, which is the higher level layer.
Now let's look at each program of the functions.
## notebook\_page\_new
@@@ tfe5/tfenotebook.c get_untitled notebook_page_build notebook_page_new
- 27-37: `notebook_page_new` function.
- 29: `g_return_if_fail` is used because `notebook_page_new` is a public function.
- 34: Generate TfeTextView object.
- 35: Generate filename, which is "Untitled", "Untitled2", ... .
- 1-8: `get_untitled` function.
- 3: Static variable `c` is initialized at the first call of this function. After that `c` keeps its value except it is changed explicitly.
- 4-7: Increase `c` by one and if it is zero then the name is "Untitled". If it is a positive integer then the name is "Untitled<the integer>", for example, "Untitled1", "Untitled2", and so on.
It returns the name.
`g_strdup_printf` generates a string and it should be freed by `g_free` function.
The caller of `get_untitled` is in charge of freeing the memories of the string.
- 36: call `notebook_page_build` to build the contents of the page.
- 10- 25: `notebook_page_build` function.
- 17-18: Generate GtkScrolledWindow and set `tv` to its child.
- 19-20: Generate GtkLabel, then GtkNotebookPage.
- 21-22: Set "tab-expand" property to TRUE.
- 23: Set the page to the current page.
- 24: Connect "change-file" signal and `file_changed` handler.
## notebook\_page\_new\_with\_file
@@@ tfe5/tfenotebook.c notebook_page_new_with_file
- 9-10: Call `tfe_text_view_new_with_file`.
If it returns NULL, then do nothing and return because of an error.
-11-13: Get the filename , build the contents of the page, then free `filename`.
## notebook\_page\_open
@@@ tfe5/tfenotebook.c open_response notebook_page_open
- 18-27: `notebook_page_open` function.
- 24: Generate TfeTextView object.
- 25: Connect the signal "open-response" and the handler `open_response`.
- 26: Call `tfe_text_view_open`.
It emits "open-response" signal to inform the status after the series of functions run.
- 1-16: `open_response` handler.
This is the postfunction of `notebook_page_open`.
- 6-7: It the status is NOT `TFE_OPEN_RESPONSE_SUCCESS`, cancel what we did in `notebook_page_open`.
Unref `tv`.
- 8-9: If `tfe_text_view_get_file` returns a pointer not to point GFile, then something bad happens. Cancel what we did. Unref `tv`.
- 10-14: Otherwise, everything was okay.
Get the filename, build the contents of the page, free `filename` and unref `tv`
## notebook\_page\_save
@@@ tfe5/tfenotebook.c notebook_page_save
- 7-9: Get TfeTextView belongs to the current notebook page.
- 10: Call `tfe_text_view_save`.
## file\_changed handler
`file_changed` is a handler connected to "change-file" signal.
If `tv->file` is changed, TfeTextView emits this signal.
This handler changes the label of GtkNotebookPage.
@@@ tfe5/tfenotebook.c file_changed
- 8: Get GFile from TfeTextView.
- 9: Get the parent (GkScrolledWindow) of `tv`.
- 10-13: If `file` points GFile, then assign the filename of the GFile into `filename`.
Otherwise (this is the case file is NULL), assign untitled string to `filename`.
- 14-15: Generate a label with the filename and set it into GtkNotebookPage.
- 16-17: Free `filename and unref `file`.

153
src/sec12.src.md Normal file
View file

@ -0,0 +1,153 @@
# tfeapplication.c
`tfeapplication.c` includes all the code other than `tfetxtview.c` and `tfenotebook.c`.
It does following things.
- Application support, mainly handling command line arguments.
- Build widgets using ui file.
- Connect button signals and their handlers.
- Manage CSS.
## main
Th function `main` is the first invoked function in C language.
It connects the command line given by the user and GTK application.
@@@ tfe5/tfeapplication.c main
- 6: Generate GtkApplication object.
- 8-10: Connect "startup", "activate" and "open signals to their handlers.
- 12: Run the application.
- 13-14: release the reference to the application and return the status.
## statup signal handler
"startup" signal is emitted just after the application is generated.
What the signal handler needs to do is initialization of the application.
- Build the widgets using ui file.
- Connect button signals and their handlers.
- Set CSS.
The handler is as follows.
@@@ tfe5/tfeapplication.c tfe_startup
- 12-15: Build widgets using ui file (resource).
Connect the top window and the application using `gtk_window_set_application`.
- 16-23: Get buttons and connect their signals and handlers.
- 24: Release the reference to GtkBuilder.
- 26-31: Set CSS.
CSS in GTK is similar to CSS in HTML.
You can set margin, border, padding, color, font and so on with CSS.
In this program CSS is in line 30.
It sets padding, font-family and font size of GtkTextView.
- 26-28: GdkDisplay is used to set CSS.
CSS will be explained in the next subsection.
## CSS in GTK
CSS is an abbretiation of Cascading Style Sheet.
It is originally used with HTML to describe the presentation semantics of a document.
You might have found that the widgets in GTK is simialr to the window in a browser.
It implies that CSS can also be apllied to GTK windowing system.
### CSS nodes, selectors
The syntax of CSS is as follws.
selector { color: yellow; padding-top: 10px; ...}
Every widget has CSS node.
For example GtkTextView has `textview` node.
If you want to set style to GtkTextView, set "textview" to the selector.
textview {color: yeallow; ...}
Class, ID and some other things can be applied to the selector like Web CSS. Refer GTK4 API reference for further information.
In line 30, the CSS is a string.
textview {padding: 10px; font-family: monospace; font-size: 12pt;}
- padding is a space between the border and contents.
This space makes the text easier to read.
- font-family is a name of font.
"monospace" is one of the generic family font keywords.
- font-size is set to 12pt.
It is a bit large, but easy on the eyes especially for elderly people.
### GtkStyleContext, GtkCSSProvider and GdkDisplay
GtkStyleContext is an object that stores styling information affecting a widget.
Each widget is connected to the corresponding GtkStyleContext.
You can get the context by `gtk_widget_get_style_context`.
GtkCssProvider is an object which parses CSS in order to style widgets.
To apply your CSS to wodgets, you need to add GtkStyleProvider (the interface of GtkCSSProvider) to GtkStyleContext.
However, instead, you can add it to GdkDisplay of the window (usually top level window).
Look at the source file of `startup` handler again.
- 28: The display is obtained by `gtk_widget_get_display`.
- 29: Generate GtkCssProvider.
- 30: Set the CSS into the provider.
- 31: Add the provider to the display.
It is possible to add the provider to the context of GtkTextView instead of GdkDiplay.
To do so, rewrite `tfe_text_view_new`.
GtkWidget *
tfe_text_view_new (void) {
GtkWidget *tv;
tv = gtk_widget_new (TFE_TYPE_TEXT_VIEW, NULL);
GtkStyleContext *context;
context = gtk_widget_get_style_context (GTK_WIDGET (tv));
GtkCssProvider *provider = gtk_css_provider_new ();
gtk_css_provider_load_from_data (provider, "textview {padding: 10px; font-family: monospace; font-size: 12pt;}", -1);
gtk_style_context_add_provider (context, GTK_STYLE_PROVIDER (provider), GTK_STYLE_PROVIDER_PRIORITY_USER);
return tv;
}
CSS set to the context takes precedence over the one set to the display.
## activate and open handler
The handler of "activate" and "open" signal are `tfe_activate` and `tfe_open` respectively.
They just generate a new GtkNotebookPage.
@@@ tfe5/tfeapplication.c tfe_activate tfe_open
- 1-14: `tfe_activate`.
- 8-10: Get GtkNotebook object.
- 12-13: Generate a new GtkNotebookPage and show the window.
- 16-33: `tfe_open`.
- 24-26: Get GtkNotebook object.
- 28-29: Generate GtkNotebookPage with files.
- 30-31: If opening and reading file failed and no GtkNotebookPage has generated, then generate a empty page.
- 32: Show the window.
These codes have become really simple thanks to tfenotebook.c and tfetextview.c.
## a series of handlers correspond to the button signals
@@@ tfe5/tfeapplication.c open_clicked new_clicked save_clicked close_clicked
`open_clicked`, `new_clicked` and `save_clicked` just call corresponding notebook page functions.
`close_clicked` is a bit complicated.
- 22-25: If there's only one page, closing the last page is considered that it also close the top level window and quit the application.
Therefore, it gets the top level window and call `gtk_window_destroy`.
- 26-28: Otherwise, it removes the current page.
## meson.build
@@@ tfe5/meson.build
This file is just modified the source file names.

45
src/sec13.src.md Normal file
View file

@ -0,0 +1,45 @@
# tfe5 source files
The followings are the source files of tfe5.
## meson.buld
@@@ tfe5/meson.build
## tfe.gresource.xml
@@@ tfe5/tfe.gresource.xml
## tfe.ui
@@@ tfe5/tfe.ui
## tfe.h
@@@ tfe5/tfe.h
## tfeapplication.c
@@@ tfe5/tfeapplication.c
### tfenotebook.h
@@@ tfe5/tfenotebook.h
## tfenotebook.c
@@@ tfe5/tfenotebook.c
## tfetextview.h
@@@ tfe5/tfetextview.h
## tfetextview.c
@@@ tfe5/tfetextview.c
## Total number of lines, words and charcters
$$$
wc tfe5/meson.build tfe5/tfeapplication.c tfe5/tfe.gresource.xml tfe5/tfe.h tfe5/tfenotebook.c tfe5/tfenotebook.h tfe5/tfetextview.c tfe5/tfetextview.h tfe5/tfe.ui
$$$

168
src/sec14.src.md Normal file
View file

@ -0,0 +1,168 @@
# Menu and action
## Menu
Users often use menus to tell the command to the computer.
It is like this:
![Menu](menu.png)
Now let's analyze the menu above.
There are two types of object.
- "File", "Edit", "View", "Cut", "Copy", "Paste" and "Select All".
They are called "menu item" or simply "item".
When the user clicks one of these items, then something will happen.
- Menubar, submenu referenced by "Edit" item and two sections.
They are called "menu".
Menu is an ordered list of items.
They are similar to arrays.
![Menu structure](menu_structure.png)
- Menubar is a menu which has three items, which are "File", "Edit" and "View".
- The menu item labeled "Edit" has a link to the submenu which has two items.
These two items don't have labels.
Each item refers to a section.
- The first section is a menu which has three items -- "Cut", "Copy" and "Paste".
- The second section is a menu which has one item -- "Select All".
Menus can build a complicated structure thanks to the links of menu items.
## GMenuModel, GMenu and GMenuItem
GMenuModel is an abstact object which represents a menu.
GMenu is a simple implementation of GMenuModel and a child object of GMenuModel.
GObjct -- GMenuModel -- GMenu
Because GMenuModel is an abstract object, it doesn't have any functions to generate it.
Therefore, if you want to generate a menu, use `g_menu_new` function to generate GMenu object.
GMenu inherits all the functions of GMenuModel because of the child object.
GMenuItem is an object directly derived from GObject.
GMenuItem and Gmenu (or GMenuModel) don't have a parent-child relationship.
GObject -- GMenuModel -- GMenu
GObject -- GMenuItem
Usually, GMenuItem has attributes.
One of the attributes is label.
For example, there is a menu item which has "Edit" label in the first diagram in this section.
"Cut", "Copy", "Paste" and "Select All" are also the lables of menu items.
Other attributes will be explained later.
Some menu items have a link to another GMenu.
There are two types of links, submenu and section.
GMenuItem can be inserted, appended or prepended to GMenu.
When it is inserted, all of the attribute and link values of the item are copied and used to form a new item within the menu.
The GMenuItem itself is not really inserted.
Therefore, after the insertion, GMenuItem is useless and it should be freed.
The same goes for appending or prepending.
The following code shows how to append GMenuItem to GMenu.
GMenu *menu = g_menu_new ();
GMenuItem *menu_item_quit = g_menu_item_new ("Quit", "app.quit");
g_menu_append_item (menu, menu_item_quit);
g_object_unref (menu_item_quit);
## Menu and action
One of the attributes of menu items is an action.
This attribute points an action object.
There are two action objects, GSimpleAction and GPropertyAction.
GSimpleAction is often used.
And it is used with a menu item.
Only GSimpleAction is described in this section.
An action corresponds to a menu item will be activated when the menu item is clicked.
Then the action emits an activate signal.
1. menu item is clicked.
2. The corresponding action is activated.
3. The action emits a signal.
4. The connected handler is invoked.
The following code is an example.
static void
quit_activated(GSimpleAction *action, GVariant *parameter, gpointer app) { ... ... ...}
GSimpleAction *act_quit = g_simple_action_new ("quit", NULL);
g_signal_connect (act_quit, "activate", G_CALLBACK (quit_activated), app);
GMenuItem *menu_item_quit = g_menu_item_new ("Quit", "app.quit");
1. `menu_item_quit` is a menu item.
It has a label "Quit" and is connected to an action "app.quit".
"app" is a prefix and "quit" is the name of an action.
The prefix means that the action belongs to GtkApplication.
If the menu is clicked, then the corresponding action "quit" which belongs to GtkApplication will be activated.
2. `act_quit` is an action.
It has a name "quit".
It belongs to GtkApplication, but it is not obvious in the code above.
The function `g_simple_action_new` generates a stateless action.
So, `act_quit` is stateless.
The meaning of stateless will be explained later.
The argument `NULL` means that the action doesn't have an parameter.
Generally, most of the actions are stateless and have no parameter.
When `act_quit` is activated, it will emit "activate" signal.
3. "activate" signal of the action is connected to the handler `quit_activated`.
So, if the action is activated, the handler will be invoked.
## Simple example
The following is a simple example of menus and actions.
@@@ menu1.c
- 3-7: `quit_activated` is a handler of an action `act_quit`.
Handlers of actions have three parameters.
1. The action object which has emitted the signal.
2. Parameter.
In this example it is `NULL` because the second argument of `g_simple_action_new` (line 15) is `NULL`.
You don' t need to care about it.
3. User data.
It is the fourth parameter in the `g_signal_connect` (line 17) that has connected the action and the handler.
- 6: A function `g_application_quit` immediately quits the application.
- 9-33: `on_activate` is a handler of "activate" signal on GtkApplication.
- 11-13: Generate a GtkApplicationWindow and set a pointer to it to `win`. And set the title and default size.
- 15: Generate GSimpleAction `act_quit`.
It is stateless.
The first argument of `g_simple_action_new` is a name of the action and the second argument is a parameter.
If you don't need the parameter, set it `NULL`.
THerefore, `act_quit` has a name "quit" and no parameter.
- 16: Add the action to GtkApplication `app`.
GtkApplication implements an interface GActionMap and GActionGroup.
And GtkApplication can have a group of actions and actions are added by the function `g_action_map_add_action`.
This function is described in GMenuModel section in GIO API reference.
- 17: Connect "activate" signal of the action and the handler `quit_activated`.
- 19-22: Generate GMenu and GMenuItem.
`menubar` and `menu` are GMenu.
`menu_item_menu` and `menu_item_quit` are GMenuItem.
`menu_item_menu` has a label "Menu" and no action.
`menu_item_quit` has a label "Quit".
The second argument "app.quit" is a combination of "app" and "quit".
"app" is a prefix and it means that the action belongs to GtkApplication. "quit" is the name of the action.
Therefore, it points the action which belongs to GtkApplication and has the name "quit" -- it is `act_quit`.
- 23-24: Append `act_quit` to `menu`.
As I mentioned before, all the attribute and link values are copied and used to form a new item within `menu`.
Therefore after the appending, `menu` has a copy of `act_quit` in itself and `act_quit` is no longer needed.
It is freed by `g_object_unref`.
- 25: Set a submenu link to `menu_item_menu`.
And the link points the GMenu `menu`.
- 26-27: Append `menu_item_menu` to `menubar`.
Then free `menu_item_menu`.
GMenu and GMenuItem are connected and finally a menu is made up.
The structure of the menu is shown in the diagram below.
- 29: The menu is set to GtkApplication.
- 30: Set GtkApplicationWindow to show the menubar.
- 31: Show the window.
![menu and action](menu1.png)
![Screenshot of menu1](menu1_screenshot.png)

257
src/sec15.src.md Normal file
View file

@ -0,0 +1,257 @@
# Stateful action
Some actions have states.
The values of states can be boolean or string.
Actions which have states are called stateful.
## Stateful action without a parameter
Some menus are called toggle menu.
For example, fullscreen menu has a state which has two values -- fullscreen and non-fullscreen.
The value of the state is changed every time the menu is clicked.
An action corresponds to the fullscreen menu also have a state.
Its value is TRUE or FALSE and it is called boolean value.
TRUE corresponds to fullscreen and FALSE to non-fullscreen.
The following is an example code to implement a fullscreen menu except the signal handler.
The signal handler will be described after the explanation of this code.
static void
on_activate (GApplication *app, gpointer user_data) {
... ... ...
GSimpleAction *act_fullscreen = g_simple_action_new_stateful ("fullscreen", NULL, g_variant_new_boolean (FALSE));
GMenuItem *menu_item_fullscreen = g_menu_item_new ("Full Screen", "win.fullscreen");
g_signal_connect (act_fullscreen, "change-state", G_CALLBACK (fullscreen_changed), win);
... ... ...
}
- `act_fullscreen` is GSimpleAction.
It is generated by `g_simple_action_new_stateful`.
The function has three arguments.
The first argument "fullscreen" is the name of the action.
The second argument is a parameter type.
`NULL` means the action doesn't have a parameter.
The third argument is the initial state of the action.
It is a GVariant value.
GVariant will be explained in the next subsection.
The function `g_variant_new_boolean (FALSE)` returns a GVariant value which is the boolean value `FALSE`.
- `menu_item_fullscreen` is GMenuItem.
There are two arguments.
The first argument "Full Screen" is a label which is one of the attributes of GMenuItem.
The second argument is called detailed action.
Detailed action has three parts, prefix, action name and target.
"win.fullscreen" means that the prefix is "win", the action name is "fullscreen" and there's no target.
The prefix says that the action belongs to the window.
- connect the action `act_fullscreen` and the "change-state" signal handler `fullscreen_`value2`changed`.
If the fullscreen menu is clicked, then the corresponding action `act_fullscreen` is activated.
But no handler is connected to "activate" signal.
Then, the default behaviour for boolean-stated actions with a NULL parameter type like `act_fullscreen` is to toggle them via the “change-state” signal.
The following is the "change-state" signal handler.
static void
fullscreen_changed(GSimpleAction *action, GVariant *value, gpointer win) {
if (g_variant_get_boolean (value))
gtk_window_maximize (GTK_WINDOW (win));
else
gtk_window_unmaximize (GTK_WINDOW (win));
g_simple_action_set_state (action, value);
}
- There are three parameters.
The first parameter is the action which emits the "change-state" signal.
The second parameter is the value of the state of the action.
But it is toggled because of no "activate" signal handler.
Ther third parameter is a user data which is set in `g_signal_connect`.
- If the value is boolean type and `TRUE`, then maximize the window.
Otherwise unmaximize.
- Set `value` to the state of the action.
Note: the second argument was the toggled state value, but at this stage the state of the action has the original value.
So, you need to set the new value by `g_simple_action_set_state`.
You can use "activate" signal instead ot "change-state" signal, or both signals.
But the way above is the simplest and best.
### GVariant
GVarient can contain boolean, string or other simple type values.
For example, the following program set TRUE to `value` whose type is GVariant.
GVariant *value = g_variant_new_boolean (TRUE);
Another example is:
GVariant *value2 = g_variant_new_string ("Hello");
`value2` is a GVariant and it has a string type value "Hello".
GVariant can contain other types like int16, int32, int64, double and so on.
If you want to get the boolean value, use g\_variant\_get series functions.
gboolean bool = g_variant_get_boolean (value);
Because `value` has been generated as a boolean type GVariant and `TRUE` value, `bool` equals `TRUE`.
In the same way, you can get a string from `value2`
const gchar *str = g_variant_get_string (value2, NULL);
The second parameter is a pointer to gsize type variable (gsize is defined as unsigned long).
If it isn't NULL, then the length of the string will be set by the function.
If it is NULL, nothing happens.
The returned string `str` can't be changed.
## Stateful action with a parameter
Another example of stateful actions is an action corresponds to color select menus.
For example, there are three menus and each menu has red, green or blue color respectively.
They determine the background color of a certain widget.
One action is connected to the three menus.
The action has a state which values are "red", "green" and "blue".
The values are string.
Those colors are given to the signal handler as a parameter.
static void
on_activate (GApplication *app, gpointer user_data) {
... ... ...
GSimpleAction *act_color = g_simple_action_new_stateful ("color", g_variant_type_new("s"), g_variant_new_string ("red"));
GMenuItem *menu_item_red = g_menu_item_new ("Red", "win.color::red");
GMenuItem *menu_item_green = g_menu_item_new ("Green", "win.color::green");
GMenuItem *menu_item_blue = g_menu_item_new ("Blue", "win.color::blue");
g_signal_connect (act_color, "activate", G_CALLBACK (color_activated), win);
... ... ...
}
- `act_color` is GSimpleAction.
It is generated by `g_simple_action_new_stateful`.
The function has three arguments.
The first argument "color" is the name of the action.
The second argument is a parameter type which is GVariantType.
`g_variant_type_new("s")` generates GVariantType which is a string type (G\_VARIANT\_TYPE\_STRING).
The third argument is the initial state of the action.
It is a GVariant.
GVariantType will be explained in the next subsection.
The function `g_variant_new_string ("red")` returns a GVariant value which has the string value "red".
- `menu_item_red` is GMenuItem.
There are two arguments.
The first argument "Red" is a label which is one of the attributes of GMenuItem.
The second argument is a detailed action.
Its prefix is "win", action name is "color" and target is "red".
Target is sent to the action as a parameter.
The same goes for `menu_item_green` and `menu_item_blue`.
- connect the action `act_color` and the "activate" signal handler `color_activate`.
If one of the three menus is clicked, then the action `act_color` is activated with a parameter to which the menu item gives its target.
No handler is connected to "change-state" signal.
Then the default behaviour is to call `g_simple_action_set_state()` to set the state to the requested value.
The following is the "activate" signal handler.
static void
color_activated(GSimpleAction *action, GVariant *parameter, gpointer win) {
gchar *color = g_strdup_printf ("label#lb {background-color: %s;}", g_variant_get_string (parameter, NULL));
gtk_css_provider_load_from_data (provider, color, -1);
g_free (color);
g_action_change_state (G_ACTION (action), parameter);
}
- There are three parameters.
The first parameter is the action which emits the "activate" signal.
The second parameter is the parameter given to the action.
It is a color specified by the menu.
The third parameter is a user data which is set in `g_signal_connect`.
- `color` is a CSS string generated by `g_strdup_printf`.
The parameter of `g_str_dup` is the same as printf C standard function.
`g_variant_get_string` get the string contained in `parameter`.
- Set the color to the css provider.
- Free the string `color`.
- Change the state by `g_action_change_state`.
The function just set the parameter to the state of the action by `g_simple_action_set_state`.
Therefore, you can use `g_simple_action_set_state` instead of `g_action_change_state`.
Note: If you have set a "change-state" signal handler, `g_action_change_state` will emit "change-state" signal instead of calling `g_simple_action_set_state`.
### GVariantType
GVariantType gives a type of GVariant.
GVariant can contain many kinds of types.
And the type often needs to be recognized at runtime.
GVariantType provides such functionality.
When GVariantType is generated, the type is expressed by the string.
- "b" means boolean type.
- "s" means string type.
The following program is a simple example.
It finally output the string "s".
@@@ gvarianttype_test.c
- `g_variant_tpe_new` generates GVariantType.
It uses a type string "s" which means string.
- `g_variant_type_peek_string` takes a peek at `vtype`.
It is the string "s" given at the generation time.
- print the string to the terminal.
## Example code
The following code includes stateful actions above.
This program has menus like this:
![menu2](menu2.png)
- Fullscreen menu toggles the size of the window between maximum and non-maximum.
If the window is maximum size, which is called full screen, then a check mark is put before "fullscreen" label.
- Red, green and blue menu determines the back ground color of the label, which is the child widget of the window.
The menus have radio buttons on the left of each of the menus.
And the radio button of the selected menu turns on.
- Quit menu quits the application.
The code is as follows.
@@@ menu2.c
- 5-26: Signal handlers.
They have been explained in this section.
- 30-36: `win` and `lb` are GtkApplicationWindow and GtkLabel respectively.
`win` has a title "menu2" and its defaust size is 400x300.
`lb` is named as "lb".
The name is used in CSS.
`lb` is set to `win` as a child.
- 38-43: Three actions are defined.
They are:
- stateful and has no parameter.
It has a toggle state.
- stateful and has a parameter.
Parameter is a string type.
- stateless and has no parameter.
- 45-54: Generate GMenu and GMenuItem.
There are three sections.
- 56-61: Signals are connected to handlers.
And actions are added to GActionMap.
Because `act_fullscreen` and `act_color` have "win" prefix and belong to GtkApplicationWindow,
they are added to `win`.
GtkApplicationWindow implements GActionModel interface like GtkApplication.
`act_quit` has "app" prefix and belongs to GtkApplication,
it is added to `app`.
- 63-77: Connect and build the menus.
Useless GMenuItem are freed.
- 79-80: GMenuModel `menubar` is set to `app`.
Set show menubar property to `TRUE` in `win`.
Note: `gtk_application_window_set_show_menubar` generates GtkPopoverMenubar from GMenuModel.
This is a different point between Gtk3 and Gtk4.
And you can use GtkPopoverMenubar directly and set it as a descendant widget of the window.
You may use GtkBox as a child widget of the window and set GtkPopoverMenubar as the first child of the box.
- 82-87: Set CSS.
`provider` is GtkCssProvider which is defined in line three as a static variable.
Its CSS data is:
`label#lb {background-color: red;}`.
"label#lb" is called selector.
"label" is the node of GtkLabel.
"#" precedes an ID which is an identiable name of the widget.
"lb" is the name of GtkLabel `lb`.
(See line 35).
The style is surrounded by open and close braces.
The style is applied to GtkLabel which has a name "lb".
Other GtkLabel have no effect from this.
The provider is added to GdkDisplay.
- 90: Show the window.

145
src/sec16.src.md Normal file
View file

@ -0,0 +1,145 @@
# Ui file for menu and action entries
## Ui file for menu
You might have thought that building menus is really bothersome.
Yes, the program was complicated and it needs lots of time to code it.
The situation is similar to building widgets.
When we built widgets, using ui file was a good way to avoid such complicated coding.
The same goes for menus.
The ui file for menus has interface, menu tags.
The file starts and ends with interface tag.
<interface>
<menu id="menubar">
</menu>
</interface>
`menu` tag corresponds to GMenu object.
`id` attribute defines the name of the object.
It will be refered by GtkBuilder.
<submenu>
<attribute name="label">File</attribute>
<item>
<attribute name="label">New</attribute>
<attribute name="action">win.new</attribute>
</item>
</submenu>
`item` tag corresponds to item in GMenu which has the same structure as GMenuItem.
The item above has a label attribute.
Its value is "New".
The item also has an action attribute and its value is "win.new".
"win" is a prefix and "new" is an action name.
`submenu` tag corresponds to both GMenuItem and GMenu.
The GMenuItem has a link to GMenu.
The ui file above can be described as follows.
<item>
<attribute name="label">File</attribute>
<link name="submenu">
<item>
<attribute name="label">New</attribute>
<attribute name="action">win.new</attribute>
</item>
</link>
</item>
`link` tag expresses the link to submenu.
And at the same time it also expresses the submenu itself.
This file illustrates the relationship between the menus and items better than the prior ui file.
But `submenu` tag is simple and easy to understand.
So, we usually prefer the former ui file style.
The following is a screenshot of the sample program in this section.
Its name is `menu3`.
![menu3](menu3.png)
The following is the ui file of the menu in `menu3`.
@@@ menu3/menu3.ui
The ui file is converted to the resource by the resouce compiler `glib-compile-resouces` with xml file below.
@@@ menu3/menu3.gresource.xml
GtkBuilder builds menus from the resource.
GtkBuilder *builder = gtk_builder_new_from_resource ("/com/github/ToshioCP/menu3/menu3.ui");
GMenuModel *menubar = G_MENU_MODEL (gtk_builder_get_object (builder, "menubar"));
gtk_application_set_menubar (GTK_APPLICATION (app), menubar);
g_object_unref (builder);
It is important that `builder` is unreferred after the GMenuModel `menubar` is set to the application.
If you do it before setting, bad thing will happen -- your computer might freeze.
## Action entry
The coding for building actions and signal handlers is always the same.
Therefore, it can be automated.
You can implement them easily with GActionEntry `g_action_map_add_action_entries`.
GActionEntry is a strutcure.
It contains action name, signal handlers, parameter and state.
typedef struct _GActionEntry GActionEntry;
struct _GActionEntry
{
const gchar *name; /* action name */
void (* activate) (GSimpleAction *action, GVariant *parameter, gpointer user_data); /* activate handler */
const gchar *parameter_type; /* the type of the parameter given as a single GVariant type string */
const gchar *state; /* initial state given in GVariant text format */
void (* change_state) (GSimpleAction *action, GVariant *value, gpointer user_data); /* change-state handler */
/*< private >*/
gsize padding[3];
};
For example, the actions in the previous section are:
{ "fullscreen", NULL, NULL, "false", fullscreen_changed }
{ "color", color_activated, "s", "red", NULL }
{ "quit", quit_activated, NULL, NULL, NULL },
And `g_action_map_add_action_entries` does all the process instead of the functions you have needed.
const GActionEntry app_entries[] = {
{ "quit", quit_activated, NULL, NULL, NULL }
};
g_action_map_add_action_entries (G_ACTION_MAP (app), app_entries, G_N_ELEMENTS (app_entries), app);
The code above does:
- Build the "quit" action
- Connect the action and the "activate" signal handler `quit_activate`
- Add the action to the action map `app`.
const GActionEntry win_entries[] = {
{ "fullscreen", NULL, NULL, "false", fullscreen_changed },
{ "color", color_activated, "s", "red", NULL }
};
g_action_map_add_action_entries (G_ACTION_MAP (win), win_entries, G_N_ELEMENTS (win_entries), win);
The code above does:
- Build the "fullscreen" action and "color" action.
- Connect the "fullscreen" action and the "change-state" signal handler `fullscreen_changed`
- Its initial state is set to FALSE.
- Connect the "color" action and the "activate" signal handler `color_activate`
- Its parameter type is string and the initial value is "red".
- Add the action to the action map `win`.
## Example code
The C source code of `menu3` and `meson.build` is as follows.
@@@ menu3/menu3.c
meson.build
@@@ menu3/meson.build

4
src/sec17.src.md Normal file
View file

@ -0,0 +1,4 @@
# GtkMenuButton
before close

141
src/sec2.src.md Normal file
View file

@ -0,0 +1,141 @@
# Widgets (1)
## GtkLabel, GtkButton and Gtkbox
### GtkLabel
We made an window and show it on the screen in the previous chapter.
Now we go on to the next topic, widgets in the window.
The simplest widget is GtkLabel.
It is a widget with a string in it.
@@@ lb1.c
Save this program to a file `lb1.c`.
Then compile and run it.
$ comp lb1
$ ./a.out
A window with a message "Hello." appears.
![Screenshot of the label](screenshot_lb1.png)
There's only a little change between `pr4.c` and `lb1.c`.
Diff is a good program to know the difference between two files.
$$$
diff misc/pr4.c lb1.c
$$$
This tells us:
- The definition of a variable lab is added.
- The title of the window is changed.
- A label is generated and connected to the window.
The function `gtk_window_set_child (GTK_WINDOW (win), lab)` makes the label `lab` a child widget of the window `win`.
Be careful.
A child widget is different from a child object.
Objects have parent-child relationship and Widgets also have parent-child relationship.
But these two relationships are totally different.
Don't be confused.
In the program `lb1.c`, `lab` is a child widget of `win`.
Child widgets are always located inside its parent widget in the screen.
See the window appeared on the screen.
The window includes the label.
The window `win` dosen't have any parents.
We call such a window top-level window.
One application can have two or more top-level windows.
### GtkButton
Next widget is GtkButton.
It has a label or icon on it.
In this subsection, we will make a button with a label.
When a button is clicked on, it emits a "clicked" signal.
The following program shows how to catch the signal and do something.
@@@ lb2.c
Look at the line 17 to 19.
First, generate a GtkButton widget `btn` with a label "Click me".
Then, set it to the window `win` as a child.
Finally, connect a "clicked" signal of the button to a handler (function) `on_click`.
So, if `btn` is clicked, the function `on_click` is invoked.
Name the program `lb2.c` and save it.
Now compile and run it.
![Screenshot of the label](screenshot_lb2.png)
A window with the button appears.
Click the button (it is a large button, you can click everywhere inside the window), then a string "Clicked." appears on the shell terminal.
It shows the handler was invoked by clicking the button.
It's fairly good for us to make sure that the clicked signal was caught and the handler was invoked.
However, using g_print is out of harmony with GTK which is a GUI library.
So, we will change the handler.
The following code is `lb3.c`.
@@@ lb3.c on_clicked on_activate
And the difference between `lb2.c` and `lb3.c` is as follows.
$$$
diff lb2.c lb3.c
$$$
The change is:
- The function `g_print` in `lb2.c` was deleted and two lines above are inserted instead.
- The label of `btn` is changed from "Click me" to "Quit".
- The fourth argument of `g_signal_connect` is changed from `NULL` to `win`.
Most important is the fourth argument of `g_signal_connect`.
It is described as "data to pass to handler" in the definition of g\_signal\_connect in GObject API reference.
Therefore, `win` which is a pointer to GtkApplicationWindow is passed to the handler as a second parameter user_data.
Then, the handler cast it to a pointer to GtkWindow and call `gtk_window_destroy` and destroy the top window.
Then, the application quits.
### GtkBox
GtkWindow and GtkApplicationWindow can have only one child.
If you want to add two or more widgets inside a window, you need a container widget.
GtkBox is one of the containers.
It arranges two or more child widgets into a single row or column.
The following procedure shows the way to add two buttons in a window.
- Generate GtkApplicationWindow.
- Generate GtkBox and set it a child of GtkApplicationWindow.
- Generate GtkButton and append it to GtkBox.
- Generate another GtkButton and append it to GtkBox.
After this, the Widgets are connected as following diagram.
![Parent-child relationship](box.png)
Now, code it.
@@@ lb4.c
Look at the function `on_activate`.
After the generation of GtkApplicationWindow, GtkBox is generated.
box = gtk_box_new(GTK_ORIENTATION_VERTICAL, 5);
gtk_box_set_homogeneous (GTK_BOX (box), TRUE);
The first argument arranges children vertically.
The second argument is sizes between children.
The next function fills a box with children, giving them equal space.
After that, two buttons `btn1` and `btn2` are generated and the signal handlers are set.
Then, these two buttons are appended to the box.
![Screenshot of the box](screenshot_lb4.png)
The handler corresponds to `btn1` changes its label.
The handler corresponds to `btn2` destroys the top-level window and the application quits.

57
src/sec3.src.md Normal file
View file

@ -0,0 +1,57 @@
# Widgets (2)
## GtkTextView, GtkTextbuffer and GtkScrolledWindow
### GtkTextView and GtkTextBuffer
GtkTextview is a widget for multiline text editing.
GtkTextBuffer is a text buffer which is connected to GtkTextView.
See a sample program `tfv1.c` below.
@@@ tfv1.c
Look at line 25.
GtkTextView is generated and its pointer is assigned to `tv`.
When GtkTextView is generated, the connected GtkTextBuffer is also generated automatically.
In the next line, the pointer to the buffer is got and assigned to `tb`.
Then, the text from line 10 to 20 is assigned to the buffer.
GtkTextView has a wrap mode.
When `GTK_WRAP_WORD_CHAR` is set, text wraps in between words, or if that is not enough, also between graphemes.
In line 30, `tv` is set to `win` as a child.
Now compile and run it.
![GtkTextView](screenshot_tfv1.png)
There's an I-beam pointer in the window.
You can add or delete any character on GtkTextview.
And your change is kept in GtkTextBuffer.
If you add more characters than the limit of the window, the height of the window extends.
If the height gets bigger than the height of the display screen, you won't be able to control the size of the window back to the original size.
It's a problem.
You can solve it by putting GtkScrolledWindow between GtkApplicationWindow and GtkTextView.
### GtkScrolledWindow
What we need to do is:
- Generate GtkScrolledWindow and set it as a child of GtkApplicationWindow.
- Set GtkTextVies as a child of GtkScrolledWindow.
Modify `tfv1.c` and save it as `tfv2.c`.
The difference between these two files is very little.
$$$
diff tfv1.c tfv2.c
$$$
Though you can modify the source file by this diff output, It's good for you to show `tfv2.c`.
@@@ tfv2.c
Now compile and run it.
This time the window doesn't extend even if you type a lot of characters.
It just scrolls.

178
src/sec4.src.md Normal file
View file

@ -0,0 +1,178 @@
# Widgets (3)
## Open signal
### G\_APPLICATION\_HANDLES\_OPEN flag
GtkTextView, GtkTextBuffer and GtkScrolledWindow have given us a minimum editor in the previous section.
Next, we will add a read function to this program and remake it into a file viewer.
There are many way to implement the function.
However, because this is a tutorial for beginners, we take the simplest way.
When the program starts, we give a filename as an argument.
$ ./a.out filename
Then it opens the file and set it into GtkTextBuffer.
At the beginning of the implementation, we need to know how GtkApplication (or GApplication) recognizes arguments.
It is described in the GIO API reference.
When GtkApplication is generated, a flag (its type is GApplicationFlags) is given as an argument.
GtkApplication *
gtk_application_new (const gchar *application_id, GApplicationFlags flags);
This flag is described in the GApplication section in GIO API reference.
GApplicationFlags' Members
G_APPLICATION_FLAGS_NONE Default. (No argument allowed)
... ... ...
G_APPLICATION_HANDLES_OPEN This application handles opening files (in the primary instance).
... ... ...
There are ten flags.
But we only need two of them so far.
We've already used `G_APPLICATION_FLAGS_NONE`.
It is the simplest option.
No argument is allowed.
If you give arguments and run the application, then error occurs.
`G_APPLICATION_HANDLES_OPEN` is the second simplest option.
It allows arguments but only files.
The application assumes all the arguments are filenames.
Now we use this flag when generating GtkApplication.
app = gtk_application_new ("com.github.ToshioCP.tfv3", G_APPLICATION_HANDLES_OPEN);
### open signal
When the application starts, two signals are possible.
- activate signal --- This signal is emitted when there's no argument.
- open signal --- This signal is emitted when there is at least one argument.
The handler of open signal is called as follows.
void user_function (GApplication *application,
gpointer files,
gint n_files,
gchar *hint,
gpointer user_data)
The parameters are as follows:
- application --- the application (usually GtkApplication)
- files --- an array of GFiles. [array length=n_files] [element-type GFile]
- n_files --- the length of files
- hint --- a hint provided by the calling instance (usually it can be ignored)
- user_data --- user data set when the signal handler was connected.
The way how to read a file using GFiles will be described in the next section.
## Coding a file viewer
### What is a file viewer?
A file viewer is a program that shows a text file given as an argument.
It works as follows.
- If it is given arguments, it recognizes the first argument as a filename and open it.
- If opening the file succeeds, read and set it to GtkTextBuffer and show the window.
- If it fails to open the file, show an error message and quit.
- If there's no argument, show an error message and quit.
- If there are two or more arguments, the second one and after are ignored.
The program is as follows.
@@@ tfv3.c
Save it as `tfv3.c`.
Then compile and run it.
$ comp tfv3
$ ./a.out tfv3.c
![File viewer](screenshot_tfv3.png)
Now I want to explain the program `tfv3.c`.
First, the function `main` changes in only two lines.
- `G_APPLICATION_FLAGS_NONE` is replaced with `G_APPLICATION_HANDLES_OPEN`.
- `g_signal_connect (app, "open", G_CALLBACK (on_open), NULL)` is added.
Next, the handler `on_activate` is now very simple.
Just output the error message.
The application quits immediately because no window is generated.
The point is the handler `on_open`.
- It generates GtkApplicationWindow, GtkScrolledWindow, GtkTextView and GtkTextBuffer and connect them.
- Set wrap mode to `GTK_WRAP_WORD_CHAR` in GtktextView.
- Set non-editable to GtkTextView because the program isn't an editor but only a viewer.
- Read the file and set it to GtkTextBuffer (this will be explained in detail later).
- If the file is not opened then output an error message and destroy the window. It makes the application quit.
The file reading part of the program is shown again below.
if (g_file_load_contents(files[0], NULL, &contents, &length, NULL, NULL)) {
gtk_text_buffer_set_text(tb, contents, length);
g_free(contents);
filename = g_file_get_basename(files[0]);
gtk_window_set_title (GTK_WINDOW (win), filename);
g_free(filename);
gtk_widget_show (win);
} else {
filename = g_file_get_path(files[0]);
g_print ("No such file: %s.\n", filename);
gtk_window_destroy (GTK_WINDOW (win));
}
The function `g_file_load_contents` loads the file contents into a buffer, which is automatically allocated, and set the pointer to the buffer into `contents`.
And the length of the buffer is set to `length`.
It returns `TRUE` if the file's contents were successfully loaded. `FALSE` if there were errors.
If the function succeeds, set the contents into GtkTextBuffer, free the buffer memories pointed by `contents`, set the filename to the title of the window,
free the memories pointed by `filename` and show the window.
If it fails, it outputs an error message and destroy the window.
## GtkNotebook
GtkNotebook is a container widget that contains multiple children with tabs in it.
![GtkNotebook](screenshot_gtk_notebook.png)
Look at the screenshots above.
The left one is a window at the startup.
It shows the file `pr1.c`.
The filename is in the left tab.
After clicking on the right tab, then the contents of `tfv1.c` appears.
It is shown in the right screenshot.
GtkNotebook widget is between GtkApplicationWindow and GtkScrolledWindow.
Now I want to show you the program `tfv4.c`.
@@@ tfv4.c
Most of the change is in the function `on_open`.
The numbers at the left of the following items are line numbers in the source code.
- 11-13: Variables `nb`, `lab` and `nbp` are defined and point GtkNotebook, GtkLabel and GtkNotebookPage respectively.
- 23: The window's title is set to "file viewer".
- 25: The size of the window is set to maximum because a big window is appropriate for file viewers.
- 27-28 GtkNotebook is generated and set it as a child of GtkApplicationWindow.
- 30-52 For-loop. Each loop corresponds to an argument. And files[i] is GFile object with respect to the i-th argument.
- 32-37 GtkScrollledWindow, GtkTextView and GtkTextBuffer are generated and GtkTextView is connected to GtkScrolledWindow as a child.
They corresponds to each file, so they are generated inside the for-loop.
- 39-42 Set the contents of the file into GtkTextBuffer and free the memory pointed by `contents`. Get the filename and generate GtkLabel with the filename.
- 43: Append GtkScrolledWindow and GtkLabel to GtkNotebook. The appended objects are children of automatically generated GtkNotebookPage object. Therefore, the structure is like this:
GtkNotebook -- GtkNotebookPage -- (GtkScrolledWindow and GtkLabel)
- 44: Get GtkNotebookPage object and set its pointer to `nbp`.
- 45: GtkNotebookPage has a property "tab-expand". If it is set to TRUE then the tab expand horizontally as long as possible. If FALSE, then the width of the tab is determined by the size of the label. `g_object_set` is a general function to set properties in any objects.
- 46: free the memory pointed by `filename`
- 53-56: If at least one file was read, then the number of GtkNotebookPage is greater than zero. If it's true, then show the window. If it's false, then destroy the window.

187
src/sec5.src.md Normal file
View file

@ -0,0 +1,187 @@
# Define Child object
## Very simple editor
We made a very simple file viewer in the previous section.
Now we go on to rewrite it and make a very simple editor.
Its source file name is tfe1.c (text file editor 1).
GtkTextView originally has a feature of multi line editing.
Therefore, we don't need to rewrite the program from scratch.
We just add two things to the file viewer.
- Static memory is needed to store a pointer to GFile.
- We need to implement file write function.
A couple of ways are possible to get memories to keep GFile.
- Use global variables.
- make a child widget object and extend the memories allocated to the widget.
Using global variables is easy to implement.
Define a sufficient size array of pointers to GFile.
For example,
GFile *f[20];
And `f[i]` corresponds to i-th GtkNotebookPage.
However, there are two problems.
One is the size of the array.
If a user gives arguments more than that, bad thing may happen.
The other is the difficulty of maintenance of the program.
It is a small program so far.
However, if you continue developing it, then its size grows bigger and bigger.
Generally speaking, the bigger the program size, the more difficult to maintain global variables.
Making child widget object is a good idea in terms of maintenance.
However, one thing you need to be careful is the difference between "child object" and "child widget".
What we are thinking about now is "child object".
A child object includes its parent object.
And a child widget object derives everything from the parent widget object.
![Child widget of GtkTwxtView](child.png)
We will define TfeTextView as a child widget object of GtkTextView.
It has everything that GtkTextView has.
For example, TfeTextView has GtkTextbuffer correspods to GtkTextView inside TfeTextView.
And important thing is that TfeTextView can have a memory to keep a pointer to GFile.
However, to understand the general theory about gobjects is very hard especially for beginners.
So, I will just show you the way how to write the code and avoid the theoretical side in the next section.
## How to define a child widget of GtkTextView
Let's define TfeTextView widget object which is a child object of GtkTextView.
First, look at the program below.
#define TFE_TYPE_TEXT_VIEW tfe_text_view_get_type ()
G_DECLARE_FINAL_TYPE (TfeTextView, tfe_text_view, TFE, TEXT_VIEW, GtkTextView)
struct _TfeTextView
{
GtkTextView parent;
GFile *file;
};
G_DEFINE_TYPE (TfeTextView, tfe_text_view, GTK_TYPE_TEXT_VIEW);
static void
tfe_text_view_init (TfeTextView *tv) {
}
static void
tfe_text_view_class_init (TfeTextViewClass *class) {
}
void
tfe_text_view_set_file (TfeTextView *tv, GFile *f) {
tv -> file = f;
}
GFile *
tfe_text_view_get_file (TfeTextView *tv) {
return tv -> file;
}
GtkWidget *
tfe_text_view_new (void) {
return gtk_widget_new (TFE_TYPE_TEXT_VIEW, NULL);
}
If you are curious about the background theory of this program, It's very good for you.
Because to know the theory is very important for you to program GTK applications.
Look at GObject API reference.
All you need is described in it.
However, it's a tough journey especially for beginners.
For now, you don't need to know such difficult theory.
Just remember the instructions below.
- TfeTextView is divided into two parts.
Tfe and TextView.
Tfe is called prefix, namespace or module.
TextView is called object.
- There are three patterns.
TfeTextView (camel case), tfe\_text\_view (this is used to write functions) and TFE\_TEXT\_VIEW (This is used to write casts).
- First, define TFE\_TYPE\_TEXT\_VIEW as tfe\_text\_view\_get\_type ().
The name is always (prefix)\_TYPE\_(object) and the letters are upper case.
And the replacement text is always (prefix)\_(object)\_get\_type () and the letters are lower case.
- Next, use G\_DECLARE\_FINAL\_TYPE macro.
The arguments are the child object name in camel case, lower case with underscore, prefix, object and parent object name.
- Declare the structure \_TfeTextView.
The underscore is necessary.
The first member is the parent object.
Notice this is not a pointer but the object itself.
The second member and after are members of the child object.
TfeTextView structure has a pointer to GFile as a member.
- Use G\_DEFINE\_TYPE macro.
The arguments are the child object name in camel case, lower case with underscore and parent object type (prefix)\_TYPE\_(module).
- Define instance init function (tfe\_text\_view\_init).
Usually you don't need to do anything.
- Define class init function (tfe\_text\_view\_class\_init).
You don't need to do anything in this widget.
- Write function codes you want to add (tfe\_text\_view\_set\_file and tfe\_text\_view\_get\_file).
`tv` is a pointer to TfeTextView object instance which is a C-struture declared with the tag \_TfeTextView.
So, the structure has a member `file` as a pointer to GFile.
`tv->file = f` is an assignment of `f` to a member `file` of the structure pointed by `tv`.
This is an example how to use the extended memory in a child widget.
- Write object generation function.
Its name is (prefix)\_(object)\_new.
If the parent object function needs parameters, this function also need them.
You sometimes might want to add some parameters.
It's your choice.
Use gtk\_widget\_new function to generate the child widget.
The arguments are (prefix)\_TYPE\_(object), a list to initialize properties and NULL.
In this code no property needs to be initialized.
This program is not perfect.
It has some problem.
But I don't discuss it now.
It will be modified later.
## Close-request signal
As a first step, `tfe1.c` writes files just before the window closes.
GtkWindow emits "close-request" signal before it closes.
We connect the signal and the handler `before_close`.
A handler is a C function.
When a function is connected to a certain signal, we call the function handler.
Then, the function `before_close` is invoked when the signal "close-request" is emittd.
g_signal_connect (win, "close-request", G_CALLBACK (before_close), NULL);
The argument win is GtkApplicationWindow, in which the signal "close-request" is defined, and before\_close is the handler.
`G_CALLBACK` cast is necessary before the handler.
The program of before\_close is as follows.
@@@ tfe1.c before_close
The numbers on the left of items are line numbers in the source code.
- 13: Get the number of pages `nb` has.
- 14-23: For loop with regard to the index to each pages.
- 15-17: Get GtkScrolledWindow, TfeTextView and a pointer to GFile. The pointer was stored when `on_open` handler ran. It will be shown later.
- 18-20: Get GtkTextBuffer and contents. start\_iter and end\_iter is iterators of the buffer. I don't want to explain them now because it would take a lot of time. Just remember these lines for the present.
- 21: Write the file.
## Source code of tfe1.c
Now I will show you all the source code of `tfe1`.c.
@@@ tfe1.c
- 102: set the pointer to GFile into TfeTextView.
`files[i]` is a pointer to GFile structure.
It will be freed by the system. So you need to copy it.
`g_file_dup` duplicate the given GFile structure.
- 118: connect "close-request" signal and `before_close` handler.
The fourth argument is called user data and it is given to the signal handler.
So, `nb` is given to `before_close` as the second argument.
Now compile and run it.
Type `./a.out somefile` and make sure that the file is modified.
Now we got a very simple editor.
It's not smart.
We need more features like open, save, saveas, change font and so on.
We will add them in the next section and after.

174
src/sec6.src.md Normal file
View file

@ -0,0 +1,174 @@
# Ui file and GtkBuiler
## New, open and save button
We made the simplest editor in the previous section.
It reads the files in `on_open` funciton at start-up and writes it at closing window.
It works but is not good.
It is better to make "New", "Open", "Save" and "Close" buttons.
This section describes how to put those buttons into the window.
Signals and handlers will be explained later.
![Screenshot of the file editor](screenshot_tfe2.png)
The screenshot above shows the layout.
The function `on_open` in the source code `tfe2.c` is as follows.
@@@ tfe2.c on_open
The point is how to build the window.
- 26-28: Generate GtkApplicationWindow and set its title and default size.
- 30-31: Generate GtkBox `boxv`.
It is a vertical box and a child of GtkApplicationWindow.
It has two children.
The first child is a horizontal box includes buttons.
The second child is GtkNotebook.
- 33-34: Generate GtkBox `boxh` and append it to 'boxv' as a first child.
- 36-41: Generate three dummy labels.
The labels `dmy1` and `dmy3` has a character width of ten.
The other label `dmy2` is set hexpand property TRUE.
This makes the label expands horizontally as long as possible.
- 42-45: Generate four buttons.
- 47-53: Append these GtkLabel and GtkButton to `boxh`.
- 55-58: Generate GtkNotebook and set hexpand and vexpand properties TRUE.
This makes it expands horizontally and vertically as big as possible.
It is appended to `boxv` as the second child.
The number of lines is 33(=58-26+1) to build the widgets.
And we needed many variables (boxv, boxh, dmy1 ...).
Most of them aren't necessary except building the widgets.
Are there any good solution to reduce these work?
Gtk provides GtkBuilder.
It reads ui data and builds a window.
It reduces the cumbersom work.
## Ui file
First, let's look at the ui file `tfe3.ui` that defines a structure of the widgets.
@@@ tfe3.ui
This is coded with XML structure.
Constructs begin with `<` and end with `>` is called tags.
And it is divided into two parts, start tag and end tag.
For example, `<interface>` is a start tag and `</interface>` is an end tag.
Ui file begins and ends with interface tags.
Some tags, for example, object tags can have a class and id attributes inside the start tag.
- 2-5: An object with `GtkApplicationWindow` class and `win` id is defined.
This is the top level window.
And the three properties of the window are defined.
`title` property is "file editor", `default-width` property is 400 and `default-height` property is 300.
- 6: child tag means a child of the object above.
For example, line 7 tells us that GtkBox object which id is "boxv" is a child of `win`.
Compare this ui file and the lines 26-58 in the source code of `on_open`.
Those two decribe the same structure of widgets.
## GtkBuilder
GtkBuilder builds widgets based on the ui file.
GtkBuilder *build;
build = gtk_builder_new_from_file ("tfe3.ui");
win = GTK_WIDGET (gtk_builder_get_object (build, "win"));
gtk_window_set_application (GTK_WINDOW (win), GTK_APPLICATION (app));
nb = GTK_WIDGET (gtk_builder_get_object (build, "nb"));
The function `gtk_builder_new_from_file` reads the file given as an argument, build the widgets, generate GtkBuilder object and set pointers to the widgets in it.
The function `gtk_builder_get_object (build, "win")` returns the pointer to the widget `win`, which is the id in the ui file.
All the widgets are connected based on the parent-children relationship described in the ui file.
We only need `win` and `nb` for the program after this, so we don't need to take out any other widgets.
This reduces lines in the C source file.
$$$
diff tfe2.c tfe3.c
$$$
`65,104c61,65` means 40 (=104-65+1) lines change to 5 (=65-61+1) lines.
Therefore 35 lines are reduced.
Using ui file not only shortens C source files, but also makes the widgets' structure clear.
Now I'll show you the C source code `tfe3.c`.
Only functions `on_open` are shown as follows.
@@@ tfe3.c on_open
### Using ui string
GtkBuilder can build widgets using string.
Use the function gtk\_builder\_new\_from\_string instead of gtk\_builder\_new\_from\_file.
char *uistring;
uistring =
"<interface>"
"<object class="GtkApplicationWindow" id="win">"
"<property name=\"title\">file editor</property>"
"<property name=\"default-width\">600</property>"
"<property name=\"default-height\">400</property>"
"<child>"
"<object class=\"GtkBox\" id=\"boxv\">"
"<property name="orientation">GTK_ORIENTATION_VERTICAL</property>"
... ... ...
... ... ...
"</interface>";
build = gtk_builder_new_from_stringfile (uistring);
This method has an advantage and disadvantage.
The advantage is that the ui string is written in the source code.
So ui file is not necessary on runtime.
The disadvantage is that writing C string is a bit bothersome because of the double quotes.
If you want to use this method, you should write a script that transforms ui file into C-string.
- add backslash before each double quote.
- add double quote at the left and right.
### Using Gresource
Using Gresource is similar to using string.
But Gresource is compressed binary data, not text data.
And there's a compiler that compiles ui file into Gresource.
It can compile not only text files but also binary files such as images, sounds and so on.
And after compilation, it bundles them up into one Gresource object.
An xml file is necessary for the resource compiler `glib-compile-resources`.
It describes resource files.
@@@ tfe3.gresource.xml
- 2: gresources tag can include mulitple gresources (gresource tags).
However, this xml has only one gresource.
- 3: The gresource has a prefix `/com/github/ToshioCP/tfe3`.
- 4: The gresource has tfe3.ui.
And it is pointed by `/com/github/ToshioCP/tfe3/tfe3.ui` because it needs prefix.
If you want to add more files, then insert them between line 4 and 5.
Save this xml text to `tfe3.gresource.xml`.
The gresource compiler `glib-compile-resources` shows its ussage with the argument `--help`.
$$$
LANG=C glib-compile-resources --help
$$$
Now run the compiler.
$ glib-compile-resources tfe3.gresource.xml --target=resources.c --generate-source
Then a C source file `resources.c` is generated.
Modify tfe3.c and save it as tfe3_r.c
# include "resources.c"
... ... ...
... ... ...
build = gtk_builder_new_from_resource ("/com/github/ToshioCP/tfe3/tfe3.ui");
... ... ...
... ... ...
Then, compile and run it.
The window appears and it is the same as the screenshot at the beginning of this page.

191
src/sec7.src.md Normal file
View file

@ -0,0 +1,191 @@
# Build system
## What do we need to think about building?
We've managed to compile a small editor so far.
But Some bad signs are beginning to appear.
- We have only one C source file and put everything into it.
We need to sort it out.
- There are two compilers, `gcc` and `glib-compile-resources`.
We want to control them by one building tool.
## Divide a C source file into two parts.
When you divide C source file into several parts, each file should contain only one thing.
For example, our source has two things, the definition of TfeTextView and functions related to GtkApplication and GtkApplicationWindow.
It is a good idea to separate them into two files, `tfetextview.c` and `tfe.c`.
- `tfetextview.c` includes the definition and functions of TfeTextView.
- `tfe.c` includes functions like `main`, `on_activate`, `on_open` and so on, which relate to GtkApplication and GtkApplicationWindow
Now we have three source files, `tfetextview.c`, `tfe.c` and `tfe3.ui`.
The `3` of `tfe3.ui` is like a version number.
Managing version with filenames is one possible idea but it may make bothersome complicated problem.
You need to rewrite filename in each version and it affects to contents of sourcefiles that refer to filenames.
So, we should take `3` away from the filename.
In `tfe.c` the function `tfe_text_view_new` is invoked to generate TfeTextView.
But it is defined in `tfetextview.c`, not `tfe.c`.
The lack of the declaration (not definition) of `tfe_text_view_new` makes error when `tfe.c` is compiled.
The declaration is necessary in `tfe.c`.
Those public information is usually written in header files.
It has `.h` suffix like `tfetextview.h`
And header files are included by C source files.
For example, `tfetextview.h` is included by `tfe.c`.
`tfetextview.h`
@@@ tfe4/tfetextview.h
`tfetextview.c`
@@@ tfe4/tfetextview.c
`tfe.c`
@@@ tfe4/tfe.c
`tfe.ui`
@@@ tfe4/tfe.ui
`tfe.gresource.xml`
@@@ tfe4/tfe.gresource.xml
## Make
Dividing a file makes it easy to maintain source files.
But now we are faced with a new problem.
The building step increases.
- Compile the ui file `tfe.ui` into `resources.c`.
- Compile `tfe.c` into `tfe.o` (object file).
- Compile `tfetextview.c` into `tfetextview.o`.
- Compile `resources.c` into `resources.o`.
- Link all the object files into application `tfe`.
Now build tool is necessary to manage it.
Make is one of the build tools.
It was originally created in 1976.
So it is an old and widely used program.
Make analyzes Makefile and executes compilers.
All instructions are written in Makefile.
sample.o: sample.c
gcc -o sample.o sample.c
The sample of Malefile above consists of three elements, `sample.o`, `sample.c` and `gcc -0 sample.o sample.c`.
- `sample.o` is called target.
- `sample.c` is prerequisite.
- `gcc -0 sample.o sample.c` is recipe.
Recipes follow tab characters, not spaces.
(It is very important. Use tab not space, or make won't work as you expected).
The rule is:
If a prerequisite modified later than a target, then make executes the recipe.
In the example above, if `sample.c` is modified after the generation of `sample.o`, then make executes gcc and compile `sample.c` into `sample.o`.
If the modification time of `sample.c` is older then the generation of `sample.o`, then no compiling is necesarry, so make does nothing.
The Makefile for `tfe` is as follows.
@@@ tfe4/Makefile
Only you need is to type `make`.
$ make
gcc -c -o tfe.o `pkg-config --cflags gtk4` tfe.c
gcc -c -o tfetextview.o `pkg-config --cflags gtk4` tfetextview.c
glib-compile-resources tfe.gresource.xml --target=resources.c --generate-source
gcc -c -o resources.o `pkg-config --cflags gtk4` resources.c
gcc -o tfe tfe.o tfetextview.o resources.o `pkg-config --libs gtk4`
I used only very basic rules to write this Makefile.
There are many more convenient methods to make it more compact.
But it needs long story to explain.
So I want to finish the explanation about make.
## Rake
Rake is a similar program to make.
It is written in Ruby code.
If you don't use Ruby, you don't need to read this subsection.
However, Ruby is really sophisticated and recommendable script language.
- Rakefile controls the behavior of `rake`.
- You can write any ruby code in Rakefile.
Rake has task and file task, which is similar to target, prerequisite and recipe in make.
@@@ tfe4/Rakefile
What `Rakefile` describes is almost same as `Makefile` in the previous subsection.
- 3-6: define target file, source file and so on.
- 1, 8: Load clean library. And define CLEAN file list.
The files included by CLEAN will be removed when `rake clean` is typed on the command line.
- 10: default target depends on targetfile.
default is the final goal of tasks.
- 12-14: targetfile depends on objfiles.
The variable `t` is a task object.
- t.name is a target name
- t.prerequisites is an array of prerequisits.
- t.source is the first element of prerequisites.
- sh is a method to give the following string to shell as an argument and execute.
- 16-21: Loop by each element of the array of objfiles. Each object depends on corresponding source file.
- 23-25: resouce file depends on xml file and ui file.
Rakefile might seem to be difficult for beginners.
But, you can use any ruby syntax in Rakefile, so it is really flexible.
If you practice Ruby and Rakefile, it will be highly productive tools.
## Meson and ninja
Meson is one of the most popular building tool despite the developing version.
And ninja is similar to make but much faster than make.
Several years ago, most of the C developers used autotools and make.
But now the situation has changed.
Many developers are using meson and ninja now.
To use meson, you first need to write `meson.build` file.
@@@ tfe4/meson.build
- 1: The function `project` defines things about the project.
The first parameter is the name of the project and the second is the programing language.
- 2: `dependency` function defines a dependency that is taken by `pkg-config`.
We put `gtk4` as an argument.
- 5: `import` function inports a module.
In line 5, gnome module is imported and assignd to the variable `gnome`.
gnome module provides helper tools to build GTK programs.
- 6: `.compile_resources` is a method of gnome module and compile files to resources under the instruction of xml file.
In line 6, the resource filename is `resources`, which means `resources.c` and `resources.h`, and xml file is `tfe.gresource.xml`.
This method generates C source file by default.
- 8: define source files.
- 10: executable function generates a target file by building source files.
The first parameter is the filename of the target. The following parameters are source files.
The last parameter has a option `dependencies`.
In line 10 it is `gtkdep` which is defined in line 3.
Now run meson and ninja.
$ meson _build
$ ninja -C _build
Then, the executable file `tfe` has been generated under the directory `_build`.
$ _build/tfe tfe.c tfetextview.c
Then the window appears.
I show you three build tools.
I think meson and ninja is the best choice for the present.
We divided a file into some categorized files and used a build tool.
This method is used by many developers.

258
src/sec8.src.md Normal file
View file

@ -0,0 +1,258 @@
# Instance and class
This section and the following four sections are descriptions about next version of the text file editor (tfe).
It is tfe5.
It has many changes from the prior version.
All the sources are listed after the five sections.
## Encapsulation
We've divided C source file into two parts.
But it is not enough in terms of encapsulation.
- `tfe.c` includes everything other than TfeTextView.
It should be divided at least into two parts, `tfeapplication.c` and `tfenotebook.c`.
- Header files also need to be organized.
However, first of all, I'd like to focus on the object TfeTextView.
It is a child object of GtkTextView.
And important thing is it has newly added Gfile in it.
- What is necessary to GFile when generating (or initializing) TfeTextView?
- What is necessary to GFile when destructing TfeTextView?
- TfeTextView should read/write a file by itself or not?
- How it communicate with objects outside?
You need to know at least class/instance and signals before thinking about them.
I will explain them in this section and the next section.
After that I will explain:
- Organizing functions.
- How to use FileChooserDialog
## GObject and its children
GObject and its children are objects, which have both class and instance.
First, think about instance of objects.
Instance is structured memories and the structure is described using C language structure.
The following is a structure of TfeTextView.
/* This typedef statement is automaticaly generated by the macro G_DECLARE_FINAL_TYPE */
typedef struct _TfeTextView TfeTextView;
struct _TfeTextView {
GtkTextView parent;
GtkTextBuffer *tb;
GFile *file;
gboolean changed;
};
Each instance has similar structure as above.
- `parent` is the structure of GtkTextView which is the parent object of TfeTextView.
- `tb` is a pointer to GtkTextBuffer connected to GtkTextView.
- `file` is a pointer to GFile which is a file corresponds to `tb` (or NULL is available).
- `changed` is TRUE if the buffer has been modified, FALSE if not.
Comparing to the source file in the previous section, `tb` and `changed` are added.
Notice the program above is the declaration of the structure, not the definition.
So, no memories are allocated at this moment.
They are to be allocated when `tfe_text_view_new` function is invoked.
You can find the declaration of the ancestors of TfeTextView in the sourcefiles of GTK and GLib.
The following is extracts from the source files (not exactly the same).
typedef struct _GObject GObject;
typedef struct _GObject GInitiallyUnowned;
struct _GObject
{
GTypeInstance g_type_instance;
volatile guint ref_count;
GData *qdata;
};
typedef struct _GtkWidget GtkWidget;
struct _GtkWidget
{
GInitiallyUnowned parent_instance;
GtkWidgetPrivate *priv;
};
typedef struct _GtkTextView GtkTextView;
struct _GtkTextView
{
GtkWidget parent_instance;
GtkTextViewPrivate *priv;
};
In each structure, its parent instance is declared at the top of the members.
So, every ancestors is included in the child instance.
This is very important.
It guarantees a child widget to derive all the features from ancestors.
The structure of `TfeTextView` is like the following diagram.
![The structure of the instance TfeTextView](TfeTextView.png)
## Generate TfeTextView instance
The function `tfe_text_view_new` generates a new TfeTextView instance.
@@@ tfe5/tfetextview.c tfe_text_view_new
When this function is run, the following procedure is gone through.
1. Initialize GObject instance in TfeTextView instance.
2. Initialize GtkWidget instance in TfeTextView instance.
3. Initialize GtkTextView instance in TfeTextView instance.
4. Initialize TfeTextView instance.
Step one through three is done automatically.
Step four is done by the function `tfe_text_view_init`.
> (In the same way, `gtk_text_view_init`, `gtk_widget_init` and `g_object_init` is the initialization functions of GtkTextView, GtkWidget and GObject respectively.
> You can find them in the GTK or GLib source file.)
@@@ tfe5/tfetextview.c on_changed tfe_text_view_init
`tfe_text_view_init` initializes the instance.
- 8-10: Initialize `tb`, `file` and `changed`.
- 11: Set the wrap mode of GtkTextView as GTK\_WRAP\_WORD\_CHAR.
- 12: Connect "changed" signal to a handler `on_changed`.
"changed" signal is defined in GtkTextBuffer.
It is emitted when the contents in the buffer is changed.
- 2-4: `on_changed` handler records TRUE to `tv->changed` when "changed" signal is emitted.
## Functions and Classes
In Gtk, all objects derived from GObject have class and instance.
Instance is memories which has a structure defined by C structure declaration as I mentioned in the previous two subsections.
And instance can be generated two or more.
Those instances have the same structure.
However, structured memories are insufficient to define its behavior.
We need at least two things.
One is functions and the other is class.
You've already seen many functions, for example, `tfe_text_view_new` is a function to generate TfeTextView instance.
These functions are similar to object methods in object oriented languages such as Java and Ruby.
Functions are public, which means that they are expected to be used by other objects.
Class comprises mainly pointers to functions.
And the functions are used by the object itself or its children objects.
For example, GObject class is declared in `gobject.h` in GLib source files.
@@@ class_gobject.c
I'd like to explain some of the members.
There's a pointer to the function `dispose` in line 22.
void (*dispose) (GObject *object);
The declaration is a bit complicated.
The asterisk before the identifier `dispose` means pointer.
So, the pointer `disopse` points a function which has one parameter , which points a GObject structure, and returns no value because of void type.
In the same way, line 23 says `finalize` is a pointer to the function which has one paremeter, which points a GObject structure, and returns no value.
void (*finalize) (GObject *object);
Look at the declaration of `_GObjectClass` so that you would find that most of the members are pointers to functions.
- 10: A function pointed by `constructor` is called when the instance is generated. It completes the initialization of the instance.
- 22: A function pointed by `dispose` is called when the instance destructs itself. Destruction process is divided into two phases. First is called disposing and the instance releases all the references to other instances. The second is finalizing.
- 23: A funtion pointed by `finalize` finishes the destruction process.
- The other pointers point functions which are called during the instance lives.
## TfeTextView class
TfeTextView class is a structure and it includes all its ancestors' class in it.
Let's look at all the classes from GObject, which is the top level object, to TfeTextView object, which is the lowest.
GObject -- GInitiallyUnowned -- GtkWidget -- GtkTextView -- TfeTextView
The following is extracts from the source files (not exactly the same).
@@@ classes.c
- 105-107: This three lines are generated by the macro G\_DECLARE\_FINAL\_TYPE.
So, they are not written in either `tfe_text_view.h` or `tfe_text_view.c`.
- 2, 73, 106: Each derived class puts its parent class at the first member of its structure.
It is the same as instance structures.
- Class members in ancesters are open to their child class.
So, they can be changed in `tfe_text_view_class_init` function.
For example, the `dispose` pointer in GObjectClass will be overridden later in `tfe_text_view_class_init`.
(Override is an object oriented programing terminology.
Override is rewriting ancestors' class methods in the child class.)
- Some class methods are often overridden.
`set_property`, `get_property`, `dispose`, `finalize` and `constructed` are such methods.
TfeTextViewClass includes its ancsestors' class in it.
It is illustrated in the following diagram.
![The structure of TfeTextView Class](TfeTextViewClass.png)
## Destruction of TfeTextView
Every Object derived from GObject has a reference count.
If an object A uses an object B, then A keeps a pointr to B in A and at the same time increaces the reference count of B by one with the function `g_object_ref (B)`.
If A doesn't need B any longer, then A discards the pointer to B (usually it is done by assigning NULL to the pointer) and decreaces the reference count of B by one with the function `g_object_unref (B)`.
If two objects A and B refer to C, then the reference count of C is two.
After A used C and if A no longer needs C, A discards the pointer to C and decreases the reference count in C by one.
Now the reference count of C is one.
In the same way, when B no longer needs C, B discards the pointer to C and decreases the reference count in C by one.
At this moment, no object refers C and the reference count of C is zero.
This means C is no longer useful.
Then C destructs itself and finally the memories allocated to C is freed.
![Reference count of B](refcount.png)
The idea above is based on an assumption that an object refered by nothing has reference count of zero.
When the reference count drops to zero, the object starts its destruction process.
The destruction process is split in two phases: disposing and finalizing.
In the disposing process, the object invokes the handler pointed by `dispose` in its class to release all references to other objects.
In the finalizing process, it invokes the handler pointed by `finalize` in its class to complete the destruction process.
In the destruction process of TfeTextView, the reference count of widgets related to TfeTextView is automatically decreased.
But GFile pointed by `tv->file` needs to decrease its reference count by one.
You must write the code in the dispose handler `tfe_text_view_dispose`.
@@@ tfe5/tfetextview.c tfe_text_view_dispose
- 5,6: If `tv->file` points a GFile, decrease its reference count.
`g_clear_object` decreases the reference count and assigns NULL to `tv->file`. In dispose handlers, we usually use `g_clear_object` rather than `g_object_unref`.
- 8: invoke parent's despose handler. (This will be explained later.)
In the desposing process, the object uses the pointer in its class to call the handler.
Therefore, `tfe_text_view_dispose` needs to be registerd in the class when the TfeTextView class is initialized.
The function `tfe_text_view_class_init` is the class initialization function and it is declared in the replacement produced by `G_DEFINE_TYPE` macro.
static void
tfe_text_view_class_init (TfeTextViewClass *class) {
GObjectClass *object_class = G_OBJECT_CLASS (class);
object_class->dispose = tfe_text_view_dispose;
}
Each ancestors' class is generated before TfeTextViewClass.
Therefore, there are four classes and each class has a pointer to each dispose handler.
Look at the following diagram.
There are four classes -- GObjectClass (GInitiallyUnownedClass), GtkWidgetClass, GtkTextViewClass and TfeTextViewClass.
Each class has its own dispose handler -- `dh1`, `dh2`, `dh3` and `tfe_text_view_dispose`.
![dispose handers](dispose_handler.png)
Now, look at the `tfe_text_view_dispose` program above.
It first releases the reference to GFile object pointed by `tv->file`.
Then it invokes its parent's dispose handler in line 8.
G_OBJECT_CLASS (tfe_text_view_parent_class)->dispose (gobject);
`tfe_text_view_parent_class`,which is made by `G_DEFINE_TYPE` macro, is a pointer that points the parent object class.
Therefore, `G_OBJECT_CLASS (tfe_text_view_parent_class)->dispose` points the handler `dh3` in the diagram above.
And `gobject` is a pointer to TfeTextView object which is casted as a GObject instanse.
`dh3` releases all the references to objects in the GtkTextView part (it is actually the private area pointed by `prev`) in TfeTextView instance.
After that, `dh3` calls `dh2`, and `dh2` calls `dh1`.
Finally all the references are released.

135
src/sec9.src.md Normal file
View file

@ -0,0 +1,135 @@
# Signals
## Signals
In GTK programming, each object is capsulated.
And it is not recommended to use global variables because they tend to make the program complicated.
So, we need something to communicate between objects.
There are two ways to do so.
- Functions.
For example, `tb = gtk_text_view_get_buffer (tv)`.
The function caller requests `tv`, which is a GtkTextView object, to give back `tb`, which is a GtkTextBuffer object connected to `tv`.
- Signals.
For example, `activate` signal on GApplication object.
When the application is activated, the signal is emitted.
Then the handler, which has been connected to the signal, is invoked.
The caller of the function or the handler connected to the signal is usually outside of the object.
One of the difference between these two is that the object is active or passive.
In functions the object responds to the caller.
In signals the object actively sends a signal to the handler.
GObject signal can be registered, connected and emitted.
1. A signal is registered with the object type on which it can be emitted.
This is done usually when the class is initialized.
2. It is connected to a handler by `g_connect_signal` or its family functions.
3. When it is emmitted, the connected handler is invoked.
Step one and three are done in the object on which the signal is emitted.
Step two is done outside the objects.
## Signal registration
In TfeTextView, two signals are registered.
- "change-file" signal.
This signal is emitted when `tv->file` is changed.
- "open-response" signal.
`tfe_text_view_open` function is not able to return the status because of using GtkFileChooserDialog.
This signal is emitted instead of the return value of the function.
Static variable is used to store the signal ID.
If you need to register two or more signals, static array is usually used.
enum {
CHANGE_FILE,
OPEN_RESPONSE,
NUMBER_OF_SIGNALS
};
static guint tfe_text_view_signals[NUMBER_OF_SIGNALS];
Signal registration codes are written in the class initialization function.
@@@ tfe5/tfetextview.c tfe_text_view_class_init
- 6-15: Register "change-file"signal.
`g_signal_newv` function is used.
This signal has no default handler (object method handler).
I think you usually don't need to set a default handler in final type object.
If you need it, put the closure of the handler in line 9.
- The return value of `g_signal_newv` is the signal id.
The type of signal id is guint, which is the same as unsigned int.
It is used when the signal is emitted.
- 16-26: Register "open-response" signal.
This signal has a parameter.
- 25: Number of the parameter.
"open-response" signal has one parameter.
- 26: An array of types of parameters.
The array `param_types` is defined in line 16.
It has one element, which is `G_TYPE_INT`.
`G_TYPE_INT` is a type of integer.
Such fundamental types are described in [GObject API reference](https://developer.gnome.org/gobject/stable/gobject-Type-Information.html).
The handlers are as follows.
void change_file_handler (TfeTextView *tv, gpointer user_data);
void open_response_handler (TfeTextView *tv, guint parameter, gpointer user_data);
- Because "change-file" signal doesn't have parameter, the handler's parameter is TfeTextView object and user data.
- Because "open-response" signal has one parameter, the handler's parameter is TfeTextView object, the parameter and user data.
- `tv` is the object instance on which the signal is emitted.
- `user_data` comes from the fourth argument of `g_signal_connect`.
- `parameter` comes from the fourth argument of `g_signal_emit`.
The parameter is defined in `tfetextview.h` because it is public.
/* "open-response" signal response */
enum
{
TFE_OPEN_RESPONSE_SUCCESS,
TFE_OPEN_RESPONSE_CANCEL,
TFE_OPEN_RESPONSE_ERROR
};
- `TFE_OPEN_RESPONSE_SUCCESS` is set when `tfe_text_view_open` successfully has opend a file and loaded it.
- `TFE_OPEN_RESPONSE_CANCEL` is set when the user canceled to open a file.
- `TFE_OPEN_RESPONSE_ERROR` is set when error occured.
## Signal connection
A signal and a handler are connected by the function `g_signal_connect`.
There some similar functions like `g_signal_connect_after`, `g_signal_connect_swapped` and so on.
But I think `g_signal_connect` is the most common function.
The signals "change-file" is connected to a callback function `file_changed` outside of TfeTextView object.
In the same way, the signals "open-response" is connected to a callback function `open_response` outside of TfeTextView object.
The functions `file_changed` and `open_response` will be explained later.
g_signal_connect (GTK_TEXT_VIEW (tv), "change-file", G_CALLBACK (file_changed), nb);
g_signal_connect (TFE_TEXT_VIEW (tv), "open-response", G_CALLBACK (open_response), nb);
## Signal emission
Signals are emitted on the object.
The type of the object is the second argument of `g_signal_newv`.
The relationship between the signal and object (type) is made up when the signal is generated.
`g_signal_emit` is used to emit the signal.
The following is extract from `tfetexties.c`.
g_signal_emit (tv, tfe_text_view_signals[CHANGE_FILE], 0);
g_signal_emit (tv, tfe_text_view_signals[OPEN_RESPONSE], 0, TFE_OPEN_RESPONSE_SUCCESS);
g_signal_emit (tv, tfe_text_view_signals[OPEN_RESPONSE], 0, TFE_OPEN_RESPONSE_CANCEL);
g_signal_emit (tv, tfe_text_view_signals[OPEN_RESPONSE], 0, TFE_OPEN_RESPONSE_ERROR);
- The first argument is the object on which the signal is emitted.
- The second argument is the signal id.
- The third argument is the detail of the signal.
"change-file" signal and "open-response" signal doesn't have details and the argument is zero when no details.
- "change-file" signal doesn't have parameter, so no fourth parameter.
- "open-response" signal has one parameter.
The fourth parameter is the parameter.

136
src/tfe1.c Normal file
View file

@ -0,0 +1,136 @@
#include <gtk/gtk.h>
/* Define TfeTextView Widget which is the child object of GtkTextView */
#define TFE_TYPE_TEXT_VIEW tfe_text_view_get_type ()
G_DECLARE_FINAL_TYPE (TfeTextView, tfe_text_view, TFE, TEXT_VIEW, GtkTextView)
struct _TfeTextView
{
GtkTextView parent;
GFile *file;
};
G_DEFINE_TYPE (TfeTextView, tfe_text_view, GTK_TYPE_TEXT_VIEW);
static void
tfe_text_view_init (TfeTextView *tv) {
}
static void
tfe_text_view_class_init (TfeTextViewClass *class) {
}
void
tfe_text_view_set_file (TfeTextView *tv, GFile *f) {
tv -> file = f;
}
GFile *
tfe_text_view_get_file (TfeTextView *tv) {
return tv -> file;
}
GtkWidget *
tfe_text_view_new (void) {
return gtk_widget_new (TFE_TYPE_TEXT_VIEW, NULL);
}
/* ---------- end of the definition of TfeTextView ---------- */
static gboolean
before_close (GtkWindow *win, GtkWidget *nb) {
GtkWidget *scr;
GtkWidget *tv;
GFile *file;
GtkTextBuffer *tb;
GtkTextIter start_iter;
GtkTextIter end_iter;
gchar *contents; /* gchar is the same as char ... typedef char gchar;*/
guint n;
guint i;
n = gtk_notebook_get_n_pages (GTK_NOTEBOOK (nb));
for (i = 0; i < n; ++i) {
scr = gtk_notebook_get_nth_page (GTK_NOTEBOOK (nb), i);
tv = gtk_scrolled_window_get_child (GTK_SCROLLED_WINDOW (scr));
file = tfe_text_view_get_file (TFE_TEXT_VIEW (tv));
tb = gtk_text_view_get_buffer (GTK_TEXT_VIEW (tv));
gtk_text_buffer_get_bounds (tb, &start_iter, &end_iter);
contents = gtk_text_buffer_get_text (tb, &start_iter, &end_iter, FALSE);
if (! g_file_replace_contents (file, contents, strlen (contents), NULL, TRUE, G_FILE_CREATE_NONE, NULL, NULL, NULL))
g_print ("ERROR : Can't save %s.", g_file_get_path (file));
}
return FALSE;
}
static void
on_activate (GApplication *app, gpointer user_data) {
g_print ("You need a filename argument.\n");
}
static void
on_open (GApplication *app, GFile ** files, gint n_files, gchar *hint, gpointer user_data) {
GtkWidget *win;
GtkWidget *nb;
GtkWidget *lab;
GtkNotebookPage *nbp;
GtkWidget *scr;
GtkWidget *tv;
GtkTextBuffer *tb;
char *contents;
gsize length;
char *filename;
int i;
win = gtk_application_window_new (GTK_APPLICATION (app));
gtk_window_set_title (GTK_WINDOW (win), "file editor");
gtk_window_set_default_size (GTK_WINDOW (win), 400, 300);
gtk_window_maximize (GTK_WINDOW (win));
nb = gtk_notebook_new ();
gtk_window_set_child (GTK_WINDOW (win), nb);
for (i = 0; i < n_files; i++) {
if (g_file_load_contents (files[i], NULL, &contents, &length, NULL, NULL)) {
scr = gtk_scrolled_window_new ();
tv = tfe_text_view_new ();
tb = gtk_text_view_get_buffer (GTK_TEXT_VIEW (tv));
gtk_text_view_set_wrap_mode (GTK_TEXT_VIEW (tv), GTK_WRAP_WORD_CHAR);
gtk_scrolled_window_set_child (GTK_SCROLLED_WINDOW (scr), tv);
tfe_text_view_set_file (TFE_TEXT_VIEW (tv), g_file_dup (files[i]));
gtk_text_buffer_set_text (tb, contents, length);
g_free (contents);
filename = g_file_get_basename (files[i]);
lab = gtk_label_new (filename);
gtk_notebook_append_page (GTK_NOTEBOOK (nb), scr, lab);
nbp = gtk_notebook_get_page (GTK_NOTEBOOK (nb), scr);
g_object_set (nbp, "tab-expand", TRUE, NULL);
g_free (filename);
} else {
filename = g_file_get_path (files[i]);
g_print ("No such file: %s.\n", filename);
g_free (filename);
}
}
if (gtk_notebook_get_n_pages (GTK_NOTEBOOK (nb)) > 0) {
g_signal_connect (win, "close-request", G_CALLBACK (before_close), nb);
gtk_widget_show (win);
} else
gtk_window_destroy (GTK_WINDOW (win));
}
int
main (int argc, char **argv) {
GtkApplication *app;
int stat;
app = gtk_application_new ("com.github.ToshioCP.tfe1", G_APPLICATION_HANDLES_OPEN);
g_signal_connect (app, "activate", G_CALLBACK (on_activate), NULL);
g_signal_connect (app, "open", G_CALLBACK (on_open), NULL);
stat =g_application_run (G_APPLICATION (app), argc, argv);
g_object_unref (app);
return stat;
}

146
src/tfe2.c Normal file
View file

@ -0,0 +1,146 @@
#include <gtk/gtk.h>
/* Define TfeTextView Widget which is the child object of GtkTextView */
#define TFE_TYPE_TEXT_VIEW tfe_text_view_get_type ()
G_DECLARE_FINAL_TYPE (TfeTextView, tfe_text_view, TFE, TEXT_VIEW, GtkTextView)
struct _TfeTextView
{
GtkTextView parent;
GFile *file;
};
G_DEFINE_TYPE (TfeTextView, tfe_text_view, GTK_TYPE_TEXT_VIEW);
static void
tfe_text_view_init (TfeTextView *tv) {
}
static void
tfe_text_view_class_init (TfeTextViewClass *class) {
}
void
tfe_text_view_set_file (TfeTextView *tv, GFile *f) {
tv -> file = f;
}
GFile *
tfe_text_view_get_file (TfeTextView *tv) {
return tv -> file;
}
GtkWidget *
tfe_text_view_new (void) {
return gtk_widget_new (TFE_TYPE_TEXT_VIEW, NULL);
}
/* ---------- end of the definition of TfeTextView ---------- */
static void
on_activate (GApplication *app, gpointer user_data) {
g_print ("You need a filename argument.\n");
}
static void
on_open (GApplication *app, GFile ** files, gint n_files, gchar *hint, gpointer user_data) {
GtkWidget *win;
GtkWidget *nb;
GtkWidget *lab;
GtkNotebookPage *nbp;
GtkWidget *scr;
GtkWidget *tv;
GtkTextBuffer *tb;
char *contents;
gsize length;
char *filename;
int i;
GtkWidget *boxv;
GtkWidget *boxh;
GtkWidget *dmy1;
GtkWidget *dmy2;
GtkWidget *dmy3;
GtkWidget *btnn; /* button for new */
GtkWidget *btno; /* button for open */
GtkWidget *btns; /* button for save */
GtkWidget *btnc; /* button for close */
win = gtk_application_window_new (GTK_APPLICATION (app));
gtk_window_set_title (GTK_WINDOW (win), "file editor");
gtk_window_set_default_size (GTK_WINDOW (win), 600, 400);
boxv = gtk_box_new (GTK_ORIENTATION_VERTICAL, 0);
gtk_window_set_child (GTK_WINDOW (win), boxv);
boxh = gtk_box_new (GTK_ORIENTATION_HORIZONTAL, 0);
gtk_box_append (GTK_BOX (boxv), boxh);
dmy1 = gtk_label_new(NULL); /* dummy label for left space */
gtk_label_set_width_chars (GTK_LABEL (dmy1), 10);
dmy2 = gtk_label_new(NULL); /* dummy label for center space */
gtk_widget_set_hexpand (dmy2, TRUE);
dmy3 = gtk_label_new(NULL); /* dummy label for right space */
gtk_label_set_width_chars (GTK_LABEL (dmy3), 10);
btnn = gtk_button_new_with_label ("New");
btno = gtk_button_new_with_label ("Open");
btns = gtk_button_new_with_label ("Save");
btnc = gtk_button_new_with_label ("Close");
gtk_box_append (GTK_BOX (boxh), dmy1);
gtk_box_append (GTK_BOX (boxh), btnn);
gtk_box_append (GTK_BOX (boxh), btno);
gtk_box_append (GTK_BOX (boxh), dmy2);
gtk_box_append (GTK_BOX (boxh), btns);
gtk_box_append (GTK_BOX (boxh), btnc);
gtk_box_append (GTK_BOX (boxh), dmy3);
nb = gtk_notebook_new ();
gtk_widget_set_hexpand (nb, TRUE);
gtk_widget_set_vexpand (nb, TRUE);
gtk_box_append (GTK_BOX (boxv), nb);
for (i = 0; i < n_files; i++) {
if (g_file_load_contents (files[i], NULL, &contents, &length, NULL, NULL)) {
scr = gtk_scrolled_window_new ();
tv = tfe_text_view_new ();
tb = gtk_text_view_get_buffer (GTK_TEXT_VIEW (tv));
gtk_text_view_set_wrap_mode (GTK_TEXT_VIEW (tv), GTK_WRAP_WORD_CHAR);
gtk_scrolled_window_set_child (GTK_SCROLLED_WINDOW (scr), tv);
tfe_text_view_set_file (TFE_TEXT_VIEW (tv), g_file_dup (files[i]));
gtk_text_buffer_set_text (tb, contents, length);
g_free (contents);
filename = g_file_get_basename (files[i]);
lab = gtk_label_new (filename);
gtk_notebook_append_page (GTK_NOTEBOOK (nb), scr, lab);
nbp = gtk_notebook_get_page (GTK_NOTEBOOK (nb), scr);
g_object_set (nbp, "tab-expand", TRUE, NULL);
g_free (filename);
} else {
filename = g_file_get_path (files[i]);
g_print ("No such file: %s.\n", filename);
g_free (filename);
}
}
if (gtk_notebook_get_n_pages (GTK_NOTEBOOK (nb)) > 0) {
gtk_widget_show (win);
} else
gtk_window_destroy (GTK_WINDOW (win));
}
int
main (int argc, char **argv) {
GtkApplication *app;
int stat;
app = gtk_application_new ("com.github.ToshioCP.tfe2", G_APPLICATION_HANDLES_OPEN);
g_signal_connect (app, "activate", G_CALLBACK (on_activate), NULL);
g_signal_connect (app, "open", G_CALLBACK (on_open), NULL);
stat =g_application_run (G_APPLICATION (app), argc, argv);
g_object_unref (app);
return stat;
}

107
src/tfe3.c Normal file
View file

@ -0,0 +1,107 @@
#include <gtk/gtk.h>
/* Define TfeTextView Widget which is the child object of GtkTextView */
#define TFE_TYPE_TEXT_VIEW tfe_text_view_get_type ()
G_DECLARE_FINAL_TYPE (TfeTextView, tfe_text_view, TFE, TEXT_VIEW, GtkTextView)
struct _TfeTextView
{
GtkTextView parent;
GFile *file;
};
G_DEFINE_TYPE (TfeTextView, tfe_text_view, GTK_TYPE_TEXT_VIEW);
static void
tfe_text_view_init (TfeTextView *tv) {
}
static void
tfe_text_view_class_init (TfeTextViewClass *class) {
}
void
tfe_text_view_set_file (TfeTextView *tv, GFile *f) {
tv -> file = f;
}
GFile *
tfe_text_view_get_file (TfeTextView *tv) {
return tv -> file;
}
GtkWidget *
tfe_text_view_new (void) {
return gtk_widget_new (TFE_TYPE_TEXT_VIEW, NULL);
}
/* ---------- end of the definition of TfeTextView ---------- */
static void
on_activate (GApplication *app, gpointer user_data) {
g_print ("You need a filename argument.\n");
}
static void
on_open (GApplication *app, GFile ** files, gint n_files, gchar *hint, gpointer user_data) {
GtkWidget *win;
GtkWidget *nb;
GtkWidget *lab;
GtkNotebookPage *nbp;
GtkWidget *scr;
GtkWidget *tv;
GtkTextBuffer *tb;
char *contents;
gsize length;
char *filename;
int i;
GtkBuilder *build;
build = gtk_builder_new_from_file ("tfe3.ui");
win = GTK_WIDGET (gtk_builder_get_object (build, "win"));
gtk_window_set_application (GTK_WINDOW (win), GTK_APPLICATION (app));
nb = GTK_WIDGET (gtk_builder_get_object (build, "nb"));
g_object_unref(build);
for (i = 0; i < n_files; i++) {
if (g_file_load_contents (files[i], NULL, &contents, &length, NULL, NULL)) {
scr = gtk_scrolled_window_new ();
tv = tfe_text_view_new ();
tb = gtk_text_view_get_buffer (GTK_TEXT_VIEW (tv));
gtk_text_view_set_wrap_mode (GTK_TEXT_VIEW (tv), GTK_WRAP_WORD_CHAR);
gtk_scrolled_window_set_child (GTK_SCROLLED_WINDOW (scr), tv);
tfe_text_view_set_file (TFE_TEXT_VIEW (tv), g_file_dup (files[i]));
gtk_text_buffer_set_text (tb, contents, length);
g_free (contents);
filename = g_file_get_basename (files[i]);
lab = gtk_label_new (filename);
gtk_notebook_append_page (GTK_NOTEBOOK (nb), scr, lab);
nbp = gtk_notebook_get_page (GTK_NOTEBOOK (nb), scr);
g_object_set (nbp, "tab-expand", TRUE, NULL);
g_free (filename);
} else {
filename = g_file_get_path (files[i]);
g_print ("No such file: %s.\n", filename);
g_free (filename);
}
}
if (gtk_notebook_get_n_pages (GTK_NOTEBOOK (nb)) > 0) {
gtk_widget_show (win);
} else
gtk_window_destroy (GTK_WINDOW (win));
}
int
main (int argc, char **argv) {
GtkApplication *app;
int stat;
app = gtk_application_new ("com.github.ToshioCP.tfe3", G_APPLICATION_HANDLES_OPEN);
g_signal_connect (app, "activate", G_CALLBACK (on_activate), NULL);
g_signal_connect (app, "open", G_CALLBACK (on_open), NULL);
stat =g_application_run (G_APPLICATION (app), argc, argv);
g_object_unref (app);
return stat;
}

6
src/tfe3.gresource.xml Normal file
View file

@ -0,0 +1,6 @@
<?xml version="1.0" encoding="UTF-8"?>
<gresources>
<gresource prefix="/com/github/ToshioCP/tfe3">
<file>tfe3.ui</file>
</gresource>
</gresources>

59
src/tfe3.ui Normal file
View file

@ -0,0 +1,59 @@
<interface>
<object class="GtkApplicationWindow" id="win">
<property name="title">file editor</property>
<property name="default-width">600</property>
<property name="default-height">400</property>
<child>
<object class="GtkBox" id="boxv">
<property name="orientation">GTK_ORIENTATION_VERTICAL</property>
<child>
<object class="GtkBox" id="boxh">
<property name="orientation">GTK_ORIENTATION_HORIZONTAL</property>
<child>
<object class="GtkLabel" id="dmy1">
<property name="width-chars">10</property>
</object>
</child>
<child>
<object class="GtkButton" id="btnn">
<property name="label">New</property>
</object>
</child>
<child>
<object class="GtkButton" id="btno">
<property name="label">Open</property>
</object>
</child>
<child>
<object class="GtkLabel" id="dmy2">
<property name="hexpand">TRUE</property>
</object>
</child>
<child>
<object class="GtkButton" id="btns">
<property name="label">Save</property>
</object>
</child>
<child>
<object class="GtkButton" id="btnc">
<property name="label">Close</property>
</object>
</child>
<child>
<object class="GtkLabel" id="dmy3">
<property name="width-chars">10</property>
</object>
</child>
</object>
</child>
<child>
<object class="GtkNotebook" id="nb">
<property name="hexpand">TRUE</property>
<property name="vexpand">TRUE</property>
</object>
</child>
</object>
</child>
</object>
</interface>

19
src/tfe4/Makefile Normal file
View file

@ -0,0 +1,19 @@
all: tfe
tfe: tfe.o tfetextview.o resources.o
gcc -o tfe tfe.o tfetextview.o resources.o `pkg-config --libs gtk4`
tfe.o: tfe.c tfetextview.h
gcc -c -o tfe.o `pkg-config --cflags gtk4` tfe.c
tfetextview.o: tfetextview.c tfetextview.h
gcc -c -o tfetextview.o `pkg-config --cflags gtk4` tfetextview.c
resources.o: resources.c
gcc -c -o resources.o `pkg-config --cflags gtk4` resources.c
resources.c: tfe.gresource.xml tfe.ui
glib-compile-resources tfe.gresource.xml --target=resources.c --generate-source
.Phony: clean
clean:
rm -f tfe tfe.o tfetextview.o resources.o resources.c

25
src/tfe4/Rakefile Normal file
View file

@ -0,0 +1,25 @@
require 'rake/clean'
targetfile = "tfe"
srcfiles = FileList["tfe.c", "tfetextview.c", "resources.c"]
rscfile = srcfiles[2]
objfiles = srcfiles.gsub(/.c$/, '.o')
CLEAN.include(targetfile, objfiles, rscfile)
task default: targetfile
file targetfile => objfiles do |t|
sh "gcc -o #{t.name} #{t.prerequisites.join(' ')} `pkg-config --libs gtk4`"
end
objfiles.each do |obj|
src = obj.gsub(/.o$/,'.c')
file obj => src do |t|
sh "gcc -c -o #{t.name} `pkg-config --cflags gtk4` #{t.source} "
end
end
file rscfile => ["tfe.gresource.xml", "tfe.ui"] do |t|
sh "glib-compile-resources #{t.prerequisites[0]} --target=#{t.name} --generate-source"
end

10
src/tfe4/meson.build Normal file
View file

@ -0,0 +1,10 @@
project('tfe', 'c')
gtkdep = dependency('gtk4')
gnome=import('gnome')
resources = gnome.compile_resources('resources','tfe.gresource.xml')
sourcefiles=files('tfe.c', 'tfetextview.c')
executable('tfe', sourcefiles, resources, dependencies: gtkdep)

70
src/tfe4/tfe.c Normal file
View file

@ -0,0 +1,70 @@
#include <gtk/gtk.h>
#include "tfetextview.h"
static void
on_activate (GApplication *app, gpointer user_data) {
g_print ("You need a filename argument.\n");
}
static void
on_open (GApplication *app, GFile ** files, gint n_files, gchar *hint, gpointer user_data) {
GtkWidget *win;
GtkWidget *nb;
GtkWidget *lab;
GtkNotebookPage *nbp;
GtkWidget *scr;
GtkWidget *tv;
GtkTextBuffer *tb;
char *contents;
gsize length;
char *filename;
int i;
GtkBuilder *build;
build = gtk_builder_new_from_resource ("/com/github/ToshioCP/tfe3/tfe.ui");
win = GTK_WIDGET (gtk_builder_get_object (build, "win"));
gtk_window_set_application (GTK_WINDOW (win), GTK_APPLICATION (app));
nb = GTK_WIDGET (gtk_builder_get_object (build, "nb"));
g_object_unref (build);
for (i = 0; i < n_files; i++) {
if (g_file_load_contents (files[i], NULL, &contents, &length, NULL, NULL)) {
scr = gtk_scrolled_window_new (NULL, NULL);
tv = tfe_text_view_new ();
tb = gtk_text_view_get_buffer (GTK_TEXT_VIEW (tv));
gtk_text_view_set_wrap_mode (GTK_TEXT_VIEW (tv), GTK_WRAP_WORD_CHAR);
gtk_scrolled_window_set_child (GTK_SCROLLED_WINDOW (scr), tv);
tfe_text_view_set_file (TFE_TEXT_VIEW (tv), g_file_dup (files[i]));
gtk_text_buffer_set_text (tb, contents, length);
g_free (contents);
filename = g_file_get_basename (files[i]);
lab = gtk_label_new (filename);
gtk_notebook_append_page (GTK_NOTEBOOK (nb), scr, lab);
nbp = gtk_notebook_get_page (GTK_NOTEBOOK (nb), scr);
g_object_set (nbp, "tab-expand", TRUE, NULL);
g_free (filename);
} else {
filename = g_file_get_path (files[i]);
g_print ("No such file: %s.\n", filename);
g_free (filename);
}
}
if (gtk_notebook_get_n_pages (GTK_NOTEBOOK (nb)) > 0) {
gtk_widget_show (win);
} else
gtk_window_destroy (GTK_WINDOW (win));
}
int
main (int argc, char **argv) {
GtkApplication *app;
int stat;
app = gtk_application_new ("com.github.ToshioCP.tfe3", G_APPLICATION_HANDLES_OPEN);
g_signal_connect (app, "activate", G_CALLBACK (on_activate), NULL);
g_signal_connect (app, "open", G_CALLBACK (on_open), NULL);
stat =g_application_run (G_APPLICATION (app), argc, argv);
g_object_unref (app);
return stat;
}

View file

@ -0,0 +1,6 @@
<?xml version="1.0" encoding="UTF-8"?>
<gresources>
<gresource prefix="/com/github/ToshioCP/tfe3">
<file>tfe.ui</file>
</gresource>
</gresources>

59
src/tfe4/tfe.ui Normal file
View file

@ -0,0 +1,59 @@
<interface>
<object class="GtkApplicationWindow" id="win">
<property name="title">file editor</property>
<property name="default-width">600</property>
<property name="default-height">400</property>
<child>
<object class="GtkBox" id="boxv">
<property name="orientation">GTK_ORIENTATION_VERTICAL</property>
<child>
<object class="GtkBox" id="boxh">
<property name="orientation">GTK_ORIENTATION_HORIZONTAL</property>
<child>
<object class="GtkLabel" id="dmy1">
<property name="width-chars">10</property>
</object>
</child>
<child>
<object class="GtkButton" id="btnn">
<property name="label">New</property>
</object>
</child>
<child>
<object class="GtkButton" id="btno">
<property name="label">Open</property>
</object>
</child>
<child>
<object class="GtkLabel" id="dmy2">
<property name="hexpand">TRUE</property>
</object>
</child>
<child>
<object class="GtkButton" id="btns">
<property name="label">Save</property>
</object>
</child>
<child>
<object class="GtkButton" id="btnc">
<property name="label">Close</property>
</object>
</child>
<child>
<object class="GtkLabel" id="dmy3">
<property name="width-chars">10</property>
</object>
</child>
</object>
</child>
<child>
<object class="GtkNotebook" id="nb">
<property name="hexpand">TRUE</property>
<property name="vexpand">TRUE</property>
</object>
</child>
</object>
</child>
</object>
</interface>

34
src/tfe4/tfetextview.c Normal file
View file

@ -0,0 +1,34 @@
#include <gtk/gtk.h>
#include "tfetextview.h"
struct _TfeTextView
{
GtkTextView parent;
GFile *file;
};
G_DEFINE_TYPE (TfeTextView, tfe_text_view, GTK_TYPE_TEXT_VIEW);
static void
tfe_text_view_init (TfeTextView *tv) {
}
static void
tfe_text_view_class_init (TfeTextViewClass *class) {
}
void
tfe_text_view_set_file (TfeTextView *tv, GFile *f) {
tv -> file = f;
}
GFile *
tfe_text_view_get_file (TfeTextView *tv) {
return tv -> file;
}
GtkWidget *
tfe_text_view_new (void) {
return gtk_widget_new (TFE_TYPE_TEXT_VIEW, NULL);
}

14
src/tfe4/tfetextview.h Normal file
View file

@ -0,0 +1,14 @@
#include <gtk/gtk.h>
#define TFE_TYPE_TEXT_VIEW tfe_text_view_get_type ()
G_DECLARE_FINAL_TYPE (TfeTextView, tfe_text_view, TFE, TEXT_VIEW, GtkTextView)
void
tfe_text_view_set_file (TfeTextView *tv, GFile *f);
GFile *
tfe_text_view_get_file (TfeTextView *tv);
GtkWidget *
tfe_text_view_new (void);

10
src/tfe5/meson.build Normal file
View file

@ -0,0 +1,10 @@
project('tfe', 'c')
gtkdep = dependency('gtk4')
gnome=import('gnome')
resources = gnome.compile_resources('resources','tfe.gresource.xml')
sourcefiles=files('tfeapplication.c', 'tfenotebook.c', 'tfetextview.c')
executable('tfe', sourcefiles, resources, dependencies: gtkdep)

View file

@ -0,0 +1,6 @@
<?xml version="1.0" encoding="UTF-8"?>
<gresources>
<gresource prefix="/com/github/ToshioCP/tfe">
<file>tfe.ui</file>
</gresource>
</gresources>

4
src/tfe5/tfe.h Normal file
View file

@ -0,0 +1,4 @@
#include <gtk/gtk.h>
#include "tfetextview.h"
#include "tfenotebook.h"

64
src/tfe5/tfe.ui Normal file
View file

@ -0,0 +1,64 @@
<interface>
<object class="GtkApplicationWindow" id="win">
<property name="title">file editor</property>
<property name="default-width">600</property>
<property name="default-height">400</property>
<child>
<object class="GtkBox" id="boxv">
<property name="orientation">GTK_ORIENTATION_VERTICAL</property>
<child>
<object class="GtkBox" id="boxh">
<property name="orientation">GTK_ORIENTATION_HORIZONTAL</property>
<child>
<object class="GtkLabel" id="dmy1">
<property name="width-chars">10</property>
</object>
</child>
<child>
<object class="GtkButton" id="btnn">
<property name="label">_New</property>
<property name="use-underline">TRUE</property>
</object>
</child>
<child>
<object class="GtkButton" id="btno">
<property name="label">_Open</property>
<property name="use-underline">TRUE</property>
</object>
</child>
<child>
<object class="GtkLabel" id="dmy2">
<property name="hexpand">TRUE</property>
</object>
</child>
<child>
<object class="GtkButton" id="btns">
<property name="label">_Save</property>
<property name="use-underline">TRUE</property>
</object>
</child>
<child>
<object class="GtkButton" id="btnc">
<property name="label">_Close</property>
<property name="use-underline">TRUE</property>
</object>
</child>
<child>
<object class="GtkLabel" id="dmy3">
<property name="width-chars">10</property>
</object>
</child>
</object>
</child>
<child>
<object class="GtkNotebook" id="nb">
<property name="scrollable">TRUE</property>
<property name="hexpand">TRUE</property>
<property name="vexpand">TRUE</property>
</object>
</child>
</object>
</child>
</object>
</interface>

117
src/tfe5/tfeapplication.c Normal file
View file

@ -0,0 +1,117 @@
#include "tfe.h"
static void
open_clicked (GtkWidget *btno, GtkNotebook *nb) {
notebook_page_open (nb);
}
static void
new_clicked (GtkWidget *btnn, GtkNotebook *nb) {
notebook_page_new (nb);
}
static void
save_clicked (GtkWidget *btns, GtkNotebook *nb) {
notebook_page_save (nb);
}
static void
close_clicked (GtkWidget *btnc, GtkNotebook *nb) {
GtkWidget *win;
GtkWidget *boxv;
gint i;
if (gtk_notebook_get_n_pages (nb) == 1) {
boxv = gtk_widget_get_parent (GTK_WIDGET (nb));
win = gtk_widget_get_parent (boxv);
gtk_window_destroy (GTK_WINDOW (win));
} else {
i = gtk_notebook_get_current_page (nb);
gtk_notebook_remove_page (GTK_NOTEBOOK (nb), i);
}
}
static void
tfe_activate (GApplication *application) {
GtkApplication *app = GTK_APPLICATION (application);
GtkWidget *win;
GtkWidget *boxv;
GtkNotebook *nb;
win = GTK_WIDGET (gtk_application_get_active_window (app));
boxv = gtk_window_get_child (GTK_WINDOW (win));
nb = GTK_NOTEBOOK (gtk_widget_get_last_child (boxv));
notebook_page_new (nb);
gtk_widget_show (GTK_WIDGET (win));
}
static void
tfe_open (GApplication *application, GFile ** files, gint n_files, const gchar *hint) {
GtkApplication *app = GTK_APPLICATION (application);
GtkWidget *win;
GtkWidget *boxv;
GtkNotebook *nb;
int i;
win = GTK_WIDGET (gtk_application_get_active_window (app));
boxv = gtk_window_get_child (GTK_WINDOW (win));
nb = GTK_NOTEBOOK (gtk_widget_get_last_child (boxv));
for (i = 0; i < n_files; i++)
notebook_page_new_with_file (nb, files[i]);
if (gtk_notebook_get_n_pages (nb) == 0)
notebook_page_new (nb);
gtk_widget_show (win);
}
static void
tfe_startup (GApplication *application) {
GtkApplication *app = GTK_APPLICATION (application);
GtkApplicationWindow *win;
GtkNotebook *nb;
GtkBuilder *build;
GtkButton *btno;
GtkButton *btnn;
GtkButton *btns;
GtkButton *btnc;
build = gtk_builder_new_from_resource ("/com/github/ToshioCP/tfe/tfe.ui");
win = GTK_APPLICATION_WINDOW (gtk_builder_get_object (build, "win"));
nb = GTK_NOTEBOOK (gtk_builder_get_object (build, "nb"));
gtk_window_set_application (GTK_WINDOW (win), app);
btno = GTK_BUTTON (gtk_builder_get_object (build, "btno"));
btnn = GTK_BUTTON (gtk_builder_get_object (build, "btnn"));
btns = GTK_BUTTON (gtk_builder_get_object (build, "btns"));
btnc = GTK_BUTTON (gtk_builder_get_object (build, "btnc"));
g_signal_connect (btno, "clicked", G_CALLBACK (open_clicked), nb);
g_signal_connect (btnn, "clicked", G_CALLBACK (new_clicked), nb);
g_signal_connect (btns, "clicked", G_CALLBACK (save_clicked), nb);
g_signal_connect (btnc, "clicked", G_CALLBACK (close_clicked), nb);
g_object_unref(build);
GdkDisplay *display;
display = gtk_widget_get_display (GTK_WIDGET (win));
GtkCssProvider *provider = gtk_css_provider_new ();
gtk_css_provider_load_from_data (provider, "textview {padding: 10px; font-family: monospace; font-size: 12pt;}", -1);
gtk_style_context_add_provider_for_display (display, GTK_STYLE_PROVIDER (provider), GTK_STYLE_PROVIDER_PRIORITY_USER);
}
int
main (int argc, char **argv) {
GtkApplication *app;
int stat;
app = gtk_application_new ("com.github.ToshioCP.tfe", G_APPLICATION_HANDLES_OPEN);
g_signal_connect (app, "startup", G_CALLBACK (tfe_startup), NULL);
g_signal_connect (app, "activate", G_CALLBACK (tfe_activate), NULL);
g_signal_connect (app, "open", G_CALLBACK (tfe_open), NULL);
stat =g_application_run (G_APPLICATION (app), argc, argv);
g_object_unref (app);
return stat;
}

114
src/tfe5/tfenotebook.c Normal file
View file

@ -0,0 +1,114 @@
#include "tfe.h"
/* The returned string should be freed with g_free() when no longer needed. */
static gchar*
get_untitled () {
static int c = -1;
if (++c == 0)
return g_strdup_printf("Untitled");
else
return g_strdup_printf ("Untitled%u", c);
}
static void
file_changed (TfeTextView *tv, GtkNotebook *nb) {
GFile *file;
char *filename;
GtkWidget *scr;
GtkWidget *label;
file = tfe_text_view_get_file (tv);
scr = gtk_widget_get_parent (GTK_WIDGET (tv));
if (G_IS_FILE (file))
filename = g_file_get_basename (file);
else
filename = get_untitled ();
label = gtk_label_new (filename);
gtk_notebook_set_tab_label (nb, scr, label);
g_object_unref (file);
g_free (filename);
}
/* Save the contents in the current page */
void
notebook_page_save(GtkNotebook *nb) {
gint i;
GtkWidget *scr;
GtkWidget *tv;
i = gtk_notebook_get_current_page (nb);
scr = gtk_notebook_get_nth_page (nb, i);
tv = gtk_scrolled_window_get_child (GTK_SCROLLED_WINDOW (scr));
tfe_text_view_save (TFE_TEXT_VIEW (tv));
}
static void
notebook_page_build (GtkNotebook *nb, GtkWidget *tv, char *filename) {
GtkWidget *scr;
GtkNotebookPage *nbp;
GtkWidget *lab;
gint i;
scr = gtk_scrolled_window_new ();
gtk_scrolled_window_set_child (GTK_SCROLLED_WINDOW (scr), tv);
lab = gtk_label_new (filename);
i = gtk_notebook_append_page (nb, scr, lab);
nbp = gtk_notebook_get_page (nb, scr);
g_object_set (nbp, "tab-expand", TRUE, NULL);
gtk_notebook_set_current_page (nb, i);
g_signal_connect (GTK_TEXT_VIEW (tv), "change-file", G_CALLBACK (file_changed), nb);
}
static void
open_response (TfeTextView *tv, gint response, GtkNotebook *nb) {
GFile *file;
char *filename;
if (response != TFE_OPEN_RESPONSE_SUCCESS)
g_object_unref (tv);
else if (! G_IS_FILE (file = tfe_text_view_get_file (tv)))
g_object_unref (tv);
else {
filename = g_file_get_basename (file);
g_object_unref (file);
notebook_page_build (nb, GTK_WIDGET (tv), filename);
}
}
void
notebook_page_open (GtkNotebook *nb) {
g_return_if_fail(GTK_IS_NOTEBOOK (nb));
GtkWidget *tv;
tv = tfe_text_view_new ();
g_signal_connect (TFE_TEXT_VIEW (tv), "open-response", G_CALLBACK (open_response), nb);
tfe_text_view_open (TFE_TEXT_VIEW (tv));
}
void
notebook_page_new_with_file (GtkNotebook *nb, GFile *file) {
g_return_if_fail(GTK_IS_NOTEBOOK (nb));
g_return_if_fail(G_IS_FILE (file));
GtkWidget *tv;
char *filename;
if ((tv = tfe_text_view_new_with_file (file)) == NULL)
return; /* read error */
filename = g_file_get_basename (file);
notebook_page_build (nb, tv, filename);
}
void
notebook_page_new (GtkNotebook *nb) {
g_return_if_fail(GTK_IS_NOTEBOOK (nb));
GtkWidget *tv;
char *filename;
tv = tfe_text_view_new ();
filename = get_untitled ();
notebook_page_build (nb, tv, filename);
}

Some files were not shown because too many files have changed in this diff Show more