commit 11e0332b849224e490337b199d2370f9d5df25d2 Author: Toshio Sekiya Date: Mon Dec 21 21:12:05 2020 +0900 Initialize git. Unstable version. diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..24b81f1 --- /dev/null +++ b/.gitignore @@ -0,0 +1,5 @@ +# .gitignore + +insertfunc.rb +makehtml.rb +pickoutfunc.rb diff --git a/Rakefile b/Rakefile new file mode 100755 index 0000000..4b9296c --- /dev/null +++ b/Rakefile @@ -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 diff --git a/Readme.md b/Readme.md new file mode 100644 index 0000000..8b6cfa8 --- /dev/null +++ b/Readme.md @@ -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) diff --git a/Readme_for_developers.md b/Readme_for_developers.md new file mode 100755 index 0000000..13cadce --- /dev/null +++ b/Readme_for_developers.md @@ -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 + + 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 + + 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 diff --git a/image/TfeTextView.png b/image/TfeTextView.png new file mode 100644 index 0000000..6c81eaa Binary files /dev/null and b/image/TfeTextView.png differ diff --git a/image/TfeTextViewClass.png b/image/TfeTextViewClass.png new file mode 100644 index 0000000..35cebea Binary files /dev/null and b/image/TfeTextViewClass.png differ diff --git a/image/box.png b/image/box.png new file mode 100644 index 0000000..7b31757 Binary files /dev/null and b/image/box.png differ diff --git a/image/child.png b/image/child.png new file mode 100644 index 0000000..8a733ed Binary files /dev/null and b/image/child.png differ diff --git a/image/dispose_handler.png b/image/dispose_handler.png new file mode 100644 index 0000000..4e2d713 Binary files /dev/null and b/image/dispose_handler.png differ diff --git a/image/menu.png b/image/menu.png new file mode 100644 index 0000000..bf20ecd Binary files /dev/null and b/image/menu.png differ diff --git a/image/menu1.png b/image/menu1.png new file mode 100644 index 0000000..8617334 Binary files /dev/null and b/image/menu1.png differ diff --git a/image/menu1_screenshot.png b/image/menu1_screenshot.png new file mode 100644 index 0000000..0fb9537 Binary files /dev/null and b/image/menu1_screenshot.png differ diff --git a/image/menu2.png b/image/menu2.png new file mode 100644 index 0000000..b3fa53a Binary files /dev/null and b/image/menu2.png differ diff --git a/image/menu3.png b/image/menu3.png new file mode 100644 index 0000000..261ef3c Binary files /dev/null and b/image/menu3.png differ diff --git a/image/menu_structure.png b/image/menu_structure.png new file mode 100644 index 0000000..1442abd Binary files /dev/null and b/image/menu_structure.png differ diff --git a/image/open.png b/image/open.png new file mode 100644 index 0000000..8f6eccd Binary files /dev/null and b/image/open.png differ diff --git a/image/refcount.png b/image/refcount.png new file mode 100644 index 0000000..407740a Binary files /dev/null and b/image/refcount.png differ diff --git a/image/saveas.png b/image/saveas.png new file mode 100644 index 0000000..c9c1775 Binary files /dev/null and b/image/saveas.png differ diff --git a/image/screenshot_gtk_notebook.png b/image/screenshot_gtk_notebook.png new file mode 100644 index 0000000..b61771f Binary files /dev/null and b/image/screenshot_gtk_notebook.png differ diff --git a/image/screenshot_lb1.png b/image/screenshot_lb1.png new file mode 100644 index 0000000..7452b94 Binary files /dev/null and b/image/screenshot_lb1.png differ diff --git a/image/screenshot_lb2.png b/image/screenshot_lb2.png new file mode 100644 index 0000000..858ae6e Binary files /dev/null and b/image/screenshot_lb2.png differ diff --git a/image/screenshot_lb4.png b/image/screenshot_lb4.png new file mode 100644 index 0000000..243ddd7 Binary files /dev/null and b/image/screenshot_lb4.png differ diff --git a/image/screenshot_pr3.png b/image/screenshot_pr3.png new file mode 100644 index 0000000..57a4a88 Binary files /dev/null and b/image/screenshot_pr3.png differ diff --git a/image/screenshot_pr4.png b/image/screenshot_pr4.png new file mode 100644 index 0000000..281ae5b Binary files /dev/null and b/image/screenshot_pr4.png differ diff --git a/image/screenshot_tfe2.png b/image/screenshot_tfe2.png new file mode 100644 index 0000000..e41f432 Binary files /dev/null and b/image/screenshot_tfe2.png differ diff --git a/image/screenshot_tfv1.png b/image/screenshot_tfv1.png new file mode 100644 index 0000000..73fed07 Binary files /dev/null and b/image/screenshot_tfv1.png differ diff --git a/image/screenshot_tfv3.png b/image/screenshot_tfv3.png new file mode 100644 index 0000000..0f59844 Binary files /dev/null and b/image/screenshot_tfv3.png differ diff --git a/image/window_widget.png b/image/window_widget.png new file mode 100644 index 0000000..713ff43 Binary files /dev/null and b/image/window_widget.png differ diff --git a/lib/lib_sec_file.rb b/lib/lib_sec_file.rb new file mode 100644 index 0000000..90515ee --- /dev/null +++ b/lib/lib_sec_file.rb @@ -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 diff --git a/lib/lib_src2md.rb b/lib/lib_src2md.rb new file mode 100755 index 0000000..2720cc0 --- /dev/null +++ b/lib/lib_src2md.rb @@ -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 + diff --git a/sec1.md b/sec1.md new file mode 100644 index 0000000..1ad9d4e --- /dev/null +++ b/sec1.md @@ -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 + 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 + 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) \ No newline at end of file diff --git a/sec10.md b/sec10.md new file mode 100644 index 0000000..311b014 --- /dev/null +++ b/sec10.md @@ -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 + 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) \ No newline at end of file diff --git a/sec11.md b/sec11.md new file mode 100644 index 0000000..5a6e89d --- /dev/null +++ b/sec11.md @@ -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", 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) \ No newline at end of file diff --git a/sec12.md b/sec12.md new file mode 100644 index 0000000..0508c4f --- /dev/null +++ b/sec12.md @@ -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) \ No newline at end of file diff --git a/sec13.md b/sec13.md new file mode 100644 index 0000000..e4eeab1 --- /dev/null +++ b/sec13.md @@ -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 + 2 + 3 + 4 tfe.ui + 5 + 6 + +## tfe.ui + + 1 + 2 + 3 file editor + 4 600 + 5 400 + 6 + 7 + 8 GTK_ORIENTATION_VERTICAL + 9 + 10 + 11 GTK_ORIENTATION_HORIZONTAL + 12 + 13 + 14 10 + 15 + 16 + 17 + 18 + 19 _New + 20 TRUE + 21 + 22 + 23 + 24 + 25 _Open + 26 TRUE + 27 + 28 + 29 + 30 + 31 TRUE + 32 + 33 + 34 + 35 + 36 _Save + 37 TRUE + 38 + 39 + 40 + 41 + 42 _Close + 43 TRUE + 44 + 45 + 46 + 47 + 48 10 + 49 + 50 + 51 + 52 + 53 + 54 + 55 TRUE + 56 TRUE + 57 TRUE + 58 + 59 + 60 + 61 + 62 + 63 + 64 + +## tfe.h + + 1 #include + 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) \ No newline at end of file diff --git a/sec14.md b/sec14.md new file mode 100644 index 0000000..2d6c08f --- /dev/null +++ b/sec14.md @@ -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 + 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) \ No newline at end of file diff --git a/sec15.md b/sec15.md new file mode 100644 index 0000000..d72d6b3 --- /dev/null +++ b/sec15.md @@ -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 + 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 + 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) \ No newline at end of file diff --git a/sec16.md b/sec16.md new file mode 100644 index 0000000..4e94dd6 --- /dev/null +++ b/sec16.md @@ -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. + + + + + + +`menu` tag corresponds to GMenu object. +`id` attribute defines the name of the object. +It will be refered by GtkBuilder. + + + File + + New + win.new + + + +`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. + + + File + + + New + win.new + + + + +`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 + 2 + 3 + 4 + 5 File + 6
+ 7 + 8 New + 9 win.new + 10 + 11 + 12 Open + 13 win.open + 14 + 15
+ 16
+ 17 + 18 Save + 19 win.save + 20 + 21 + 22 Save As… + 23 win.saveas + 24 + 25
+ 26
+ 27 + 28 Close + 29 win.close + 30 + 31
+ 32
+ 33 + 34 Quit + 35 app.quit + 36 + 37
+ 38
+ 39 + 40 Edit + 41
+ 42 + 43 Cut + 44 win.cut + 45 + 46 + 47 Copy + 48 win.copy + 49 + 50 + 51 Paste + 52 win.paste + 53 + 54
+ 55
+ 56 + 57 Select All + 58 win.selectall + 59 + 60
+ 61
+ 62 + 63 View + 64
+ 65 + 66 Full Screen + 67 win.fullscreen + 68 + 69
+ 70
+ 71
+ 72
+ +The ui file is converted to the resource by the resouce compiler `glib-compile-resouces` with xml file below. + + 1 + 2 + 3 + 4 menu3.ui + 5 + 6 + +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 + 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) \ No newline at end of file diff --git a/sec17.md b/sec17.md new file mode 100644 index 0000000..7a91618 --- /dev/null +++ b/sec17.md @@ -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) \ No newline at end of file diff --git a/sec2.md b/sec2.md new file mode 100644 index 0000000..ad37e11 --- /dev/null +++ b/sec2.md @@ -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 + 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 + 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 + 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) \ No newline at end of file diff --git a/sec3.md b/sec3.md new file mode 100644 index 0000000..1f9755b --- /dev/null +++ b/sec3.md @@ -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 + 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 + 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) \ No newline at end of file diff --git a/sec4.md b/sec4.md new file mode 100644 index 0000000..844bdf9 --- /dev/null +++ b/sec4.md @@ -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 + 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 + 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) \ No newline at end of file diff --git a/sec5.md b/sec5.md new file mode 100644 index 0000000..796613e --- /dev/null +++ b/sec5.md @@ -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 + 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) \ No newline at end of file diff --git a/sec6.md b/sec6.md new file mode 100644 index 0000000..6e9c082 --- /dev/null +++ b/sec6.md @@ -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 + 2 + 3 file editor + 4 600 + 5 400 + 6 + 7 + 8 GTK_ORIENTATION_VERTICAL + 9 + 10 + 11 GTK_ORIENTATION_HORIZONTAL + 12 + 13 + 14 10 + 15 + 16 + 17 + 18 + 19 New + 20 + 21 + 22 + 23 + 24 Open + 25 + 26 + 27 + 28 + 29 TRUE + 30 + 31 + 32 + 33 + 34 Save + 35 + 36 + 37 + 38 + 39 Close + 40 + 41 + 42 + 43 + 44 10 + 45 + 46 + 47 + 48 + 49 + 50 + 51 TRUE + 52 TRUE + 53 + 54 + 55 + 56 + 57 + 58 + 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, `` is a start tag and `` 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 = + "" + "" + "file editor" + "600" + "400" + "" + "" + "GTK_ORIENTATION_VERTICAL" + ... ... ... + ... ... ... + ""; + + 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 + 2 + 3 + 4 tfe3.ui + 5 + 6 + +- 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) \ No newline at end of file diff --git a/sec7.md b/sec7.md new file mode 100644 index 0000000..27922e0 --- /dev/null +++ b/sec7.md @@ -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 + 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 + 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 + 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 + 2 + 3 file editor + 4 600 + 5 400 + 6 + 7 + 8 GTK_ORIENTATION_VERTICAL + 9 + 10 + 11 GTK_ORIENTATION_HORIZONTAL + 12 + 13 + 14 10 + 15 + 16 + 17 + 18 + 19 New + 20 + 21 + 22 + 23 + 24 Open + 25 + 26 + 27 + 28 + 29 TRUE + 30 + 31 + 32 + 33 + 34 Save + 35 + 36 + 37 + 38 + 39 Close + 40 + 41 + 42 + 43 + 44 10 + 45 + 46 + 47 + 48 + 49 + 50 + 51 TRUE + 52 TRUE + 53 + 54 + 55 + 56 + 57 + 58 + 59 + +`tfe.gresource.xml` + + 1 + 2 + 3 + 4 tfe.ui + 5 + 6 + +## 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) \ No newline at end of file diff --git a/sec8.md b/sec8.md new file mode 100644 index 0000000..60b2a9f --- /dev/null +++ b/sec8.md @@ -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) \ No newline at end of file diff --git a/sec9.md b/sec9.md new file mode 100644 index 0000000..0c97a9d --- /dev/null +++ b/sec9.md @@ -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) \ No newline at end of file diff --git a/src/class_gobject.c b/src/class_gobject.c new file mode 100644 index 0000000..66288bd --- /dev/null +++ b/src/class_gobject.c @@ -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]; +}; diff --git a/src/classes.c b/src/classes.c new file mode 100644 index 0000000..6b3741d --- /dev/null +++ b/src/classes.c @@ -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; + diff --git a/src/gvarianttype_test.c b/src/gvarianttype_test.c new file mode 100644 index 0000000..7bd733b --- /dev/null +++ b/src/gvarianttype_test.c @@ -0,0 +1,8 @@ +#include + +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); +} diff --git a/src/lb1.c b/src/lb1.c new file mode 100644 index 0000000..adcb790 --- /dev/null +++ b/src/lb1.c @@ -0,0 +1,29 @@ +#include + +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; +} + diff --git a/src/lb2.c b/src/lb2.c new file mode 100644 index 0000000..a9a235c --- /dev/null +++ b/src/lb2.c @@ -0,0 +1,35 @@ +#include + +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; +} + diff --git a/src/lb3.c b/src/lb3.c new file mode 100644 index 0000000..ad155b9 --- /dev/null +++ b/src/lb3.c @@ -0,0 +1,36 @@ +#include + +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; +} + diff --git a/src/lb4.c b/src/lb4.c new file mode 100644 index 0000000..44d2948 --- /dev/null +++ b/src/lb4.c @@ -0,0 +1,58 @@ +#include + +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; +} + diff --git a/src/menu1.c b/src/menu1.c new file mode 100644 index 0000000..935b2e5 --- /dev/null +++ b/src/menu1.c @@ -0,0 +1,47 @@ +#include + +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; +} + diff --git a/src/menu2.c b/src/menu2.c new file mode 100644 index 0000000..63e5ffb --- /dev/null +++ b/src/menu2.c @@ -0,0 +1,105 @@ +#include + +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; +} + diff --git a/src/menu3/menu3.c b/src/menu3/menu3.c new file mode 100644 index 0000000..7c82763 --- /dev/null +++ b/src/menu3/menu3.c @@ -0,0 +1,106 @@ +#include + +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; +} + diff --git a/src/menu3/menu3.gresource.xml b/src/menu3/menu3.gresource.xml new file mode 100644 index 0000000..ff1192b --- /dev/null +++ b/src/menu3/menu3.gresource.xml @@ -0,0 +1,6 @@ + + + + menu3.ui + + diff --git a/src/menu3/menu3.ui b/src/menu3/menu3.ui new file mode 100644 index 0000000..366609c --- /dev/null +++ b/src/menu3/menu3.ui @@ -0,0 +1,72 @@ + + + + + File +
+ + New + win.new + + + Open + win.open + +
+
+ + Save + win.save + + + Save As… + win.saveas + +
+
+ + Close + win.close + +
+
+ + Quit + app.quit + +
+
+ + Edit +
+ + Cut + win.cut + + + Copy + win.copy + + + Paste + win.paste + +
+
+ + Select All + win.selectall + +
+
+ + View +
+ + Full Screen + win.fullscreen + +
+
+
+
diff --git a/src/menu3/meson.build b/src/menu3/meson.build new file mode 100644 index 0000000..1c3ceb0 --- /dev/null +++ b/src/menu3/meson.build @@ -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) diff --git a/src/misc/pr1.c b/src/misc/pr1.c new file mode 100644 index 0000000..a5e7539 --- /dev/null +++ b/src/misc/pr1.c @@ -0,0 +1,13 @@ +#include + +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; +} + diff --git a/src/misc/pr2.c b/src/misc/pr2.c new file mode 100644 index 0000000..eaf838d --- /dev/null +++ b/src/misc/pr2.c @@ -0,0 +1,19 @@ +#include + +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; +} + diff --git a/src/misc/pr3.c b/src/misc/pr3.c new file mode 100644 index 0000000..e20a865 --- /dev/null +++ b/src/misc/pr3.c @@ -0,0 +1,23 @@ +#include + +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; +} + diff --git a/src/misc/pr4.c b/src/misc/pr4.c new file mode 100644 index 0000000..229ab52 --- /dev/null +++ b/src/misc/pr4.c @@ -0,0 +1,24 @@ +#include + +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; +} + diff --git a/src/sec1.src.md b/src/sec1.src.md new file mode 100644 index 0000000..8e0ae36 --- /dev/null +++ b/src/sec1.src.md @@ -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) + diff --git a/src/sec10.src.md b/src/sec10.src.md new file mode 100644 index 0000000..7ebc2fa --- /dev/null +++ b/src/sec10.src.md @@ -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). diff --git a/src/sec11.src.md b/src/sec11.src.md new file mode 100644 index 0000000..616775d --- /dev/null +++ b/src/sec11.src.md @@ -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", 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`. + + diff --git a/src/sec12.src.md b/src/sec12.src.md new file mode 100644 index 0000000..5a35345 --- /dev/null +++ b/src/sec12.src.md @@ -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. + diff --git a/src/sec13.src.md b/src/sec13.src.md new file mode 100644 index 0000000..19ac0a9 --- /dev/null +++ b/src/sec13.src.md @@ -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 +$$$ diff --git a/src/sec14.src.md b/src/sec14.src.md new file mode 100644 index 0000000..0718f50 --- /dev/null +++ b/src/sec14.src.md @@ -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) + diff --git a/src/sec15.src.md b/src/sec15.src.md new file mode 100644 index 0000000..5b9047b --- /dev/null +++ b/src/sec15.src.md @@ -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. + diff --git a/src/sec16.src.md b/src/sec16.src.md new file mode 100644 index 0000000..56e580b --- /dev/null +++ b/src/sec16.src.md @@ -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. + + + + + + +`menu` tag corresponds to GMenu object. +`id` attribute defines the name of the object. +It will be refered by GtkBuilder. + + + File + + New + win.new + + + +`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. + + + File + + + New + win.new + + + + +`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 diff --git a/src/sec17.src.md b/src/sec17.src.md new file mode 100644 index 0000000..632e06c --- /dev/null +++ b/src/sec17.src.md @@ -0,0 +1,4 @@ +# GtkMenuButton + +before close + diff --git a/src/sec2.src.md b/src/sec2.src.md new file mode 100644 index 0000000..63322aa --- /dev/null +++ b/src/sec2.src.md @@ -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. + diff --git a/src/sec3.src.md b/src/sec3.src.md new file mode 100644 index 0000000..33e4d68 --- /dev/null +++ b/src/sec3.src.md @@ -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. + diff --git a/src/sec4.src.md b/src/sec4.src.md new file mode 100644 index 0000000..48653c7 --- /dev/null +++ b/src/sec4.src.md @@ -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. + diff --git a/src/sec5.src.md b/src/sec5.src.md new file mode 100644 index 0000000..554bc65 --- /dev/null +++ b/src/sec5.src.md @@ -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. diff --git a/src/sec6.src.md b/src/sec6.src.md new file mode 100644 index 0000000..424ba16 --- /dev/null +++ b/src/sec6.src.md @@ -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, `` is a start tag and `` 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 = + "" + "" + "file editor" + "600" + "400" + "" + "" + "GTK_ORIENTATION_VERTICAL" + ... ... ... + ... ... ... + ""; + + 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. + diff --git a/src/sec7.src.md b/src/sec7.src.md new file mode 100644 index 0000000..689aaac --- /dev/null +++ b/src/sec7.src.md @@ -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. + diff --git a/src/sec8.src.md b/src/sec8.src.md new file mode 100644 index 0000000..378f31e --- /dev/null +++ b/src/sec8.src.md @@ -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. + diff --git a/src/sec9.src.md b/src/sec9.src.md new file mode 100644 index 0000000..87b4ce6 --- /dev/null +++ b/src/sec9.src.md @@ -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. + diff --git a/src/tfe1.c b/src/tfe1.c new file mode 100644 index 0000000..cde4dda --- /dev/null +++ b/src/tfe1.c @@ -0,0 +1,136 @@ +#include + +/* 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; +} + diff --git a/src/tfe2.c b/src/tfe2.c new file mode 100644 index 0000000..c3053e2 --- /dev/null +++ b/src/tfe2.c @@ -0,0 +1,146 @@ +#include + +/* 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; +} + diff --git a/src/tfe3.c b/src/tfe3.c new file mode 100644 index 0000000..4b5b768 --- /dev/null +++ b/src/tfe3.c @@ -0,0 +1,107 @@ +#include + +/* 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; +} + diff --git a/src/tfe3.gresource.xml b/src/tfe3.gresource.xml new file mode 100644 index 0000000..0f61489 --- /dev/null +++ b/src/tfe3.gresource.xml @@ -0,0 +1,6 @@ + + + + tfe3.ui + + diff --git a/src/tfe3.ui b/src/tfe3.ui new file mode 100644 index 0000000..39b54b9 --- /dev/null +++ b/src/tfe3.ui @@ -0,0 +1,59 @@ + + + file editor + 600 + 400 + + + GTK_ORIENTATION_VERTICAL + + + GTK_ORIENTATION_HORIZONTAL + + + 10 + + + + + New + + + + + Open + + + + + TRUE + + + + + Save + + + + + Close + + + + + 10 + + + + + + + TRUE + TRUE + + + + + + + diff --git a/src/tfe4/Makefile b/src/tfe4/Makefile new file mode 100644 index 0000000..79c8c75 --- /dev/null +++ b/src/tfe4/Makefile @@ -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 diff --git a/src/tfe4/Rakefile b/src/tfe4/Rakefile new file mode 100644 index 0000000..959fb93 --- /dev/null +++ b/src/tfe4/Rakefile @@ -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 diff --git a/src/tfe4/meson.build b/src/tfe4/meson.build new file mode 100644 index 0000000..ae0c530 --- /dev/null +++ b/src/tfe4/meson.build @@ -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) diff --git a/src/tfe4/tfe.c b/src/tfe4/tfe.c new file mode 100644 index 0000000..6132959 --- /dev/null +++ b/src/tfe4/tfe.c @@ -0,0 +1,70 @@ +#include +#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; +} + diff --git a/src/tfe4/tfe.gresource.xml b/src/tfe4/tfe.gresource.xml new file mode 100644 index 0000000..70f351d --- /dev/null +++ b/src/tfe4/tfe.gresource.xml @@ -0,0 +1,6 @@ + + + + tfe.ui + + diff --git a/src/tfe4/tfe.ui b/src/tfe4/tfe.ui new file mode 100644 index 0000000..39b54b9 --- /dev/null +++ b/src/tfe4/tfe.ui @@ -0,0 +1,59 @@ + + + file editor + 600 + 400 + + + GTK_ORIENTATION_VERTICAL + + + GTK_ORIENTATION_HORIZONTAL + + + 10 + + + + + New + + + + + Open + + + + + TRUE + + + + + Save + + + + + Close + + + + + 10 + + + + + + + TRUE + TRUE + + + + + + + diff --git a/src/tfe4/tfetextview.c b/src/tfe4/tfetextview.c new file mode 100644 index 0000000..ef7dc0a --- /dev/null +++ b/src/tfe4/tfetextview.c @@ -0,0 +1,34 @@ +#include +#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); +} + diff --git a/src/tfe4/tfetextview.h b/src/tfe4/tfetextview.h new file mode 100644 index 0000000..103bd52 --- /dev/null +++ b/src/tfe4/tfetextview.h @@ -0,0 +1,14 @@ +#include + +#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); + diff --git a/src/tfe5/meson.build b/src/tfe5/meson.build new file mode 100644 index 0000000..376254b --- /dev/null +++ b/src/tfe5/meson.build @@ -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) diff --git a/src/tfe5/tfe.gresource.xml b/src/tfe5/tfe.gresource.xml new file mode 100644 index 0000000..988a36e --- /dev/null +++ b/src/tfe5/tfe.gresource.xml @@ -0,0 +1,6 @@ + + + + tfe.ui + + diff --git a/src/tfe5/tfe.h b/src/tfe5/tfe.h new file mode 100644 index 0000000..8223521 --- /dev/null +++ b/src/tfe5/tfe.h @@ -0,0 +1,4 @@ +#include + +#include "tfetextview.h" +#include "tfenotebook.h" diff --git a/src/tfe5/tfe.ui b/src/tfe5/tfe.ui new file mode 100644 index 0000000..15e63ff --- /dev/null +++ b/src/tfe5/tfe.ui @@ -0,0 +1,64 @@ + + + file editor + 600 + 400 + + + GTK_ORIENTATION_VERTICAL + + + GTK_ORIENTATION_HORIZONTAL + + + 10 + + + + + _New + TRUE + + + + + _Open + TRUE + + + + + TRUE + + + + + _Save + TRUE + + + + + _Close + TRUE + + + + + 10 + + + + + + + TRUE + TRUE + TRUE + + + + + + + diff --git a/src/tfe5/tfeapplication.c b/src/tfe5/tfeapplication.c new file mode 100644 index 0000000..3eb10a6 --- /dev/null +++ b/src/tfe5/tfeapplication.c @@ -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; +} + diff --git a/src/tfe5/tfenotebook.c b/src/tfe5/tfenotebook.c new file mode 100644 index 0000000..5b30be2 --- /dev/null +++ b/src/tfe5/tfenotebook.c @@ -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); +} + diff --git a/src/tfe5/tfenotebook.h b/src/tfe5/tfenotebook.h new file mode 100644 index 0000000..ab30489 --- /dev/null +++ b/src/tfe5/tfenotebook.h @@ -0,0 +1,12 @@ +void +notebook_page_save(GtkNotebook *nb); + +void +notebook_page_open (GtkNotebook *nb); + +void +notebook_page_new_with_file (GtkNotebook *nb, GFile *file); + +void +notebook_page_new (GtkNotebook *nb); + diff --git a/src/tfe5/tfetextview.c b/src/tfe5/tfetextview.c new file mode 100644 index 0000000..05149b8 --- /dev/null +++ b/src/tfe5/tfetextview.c @@ -0,0 +1,218 @@ +#include "tfe.h" + +struct _TfeTextView +{ + GtkTextView parent; + GtkTextBuffer *tb; + GFile *file; + gboolean changed; +}; + +G_DEFINE_TYPE (TfeTextView, tfe_text_view, GTK_TYPE_TEXT_VIEW); + +enum { + CHANGE_FILE, + OPEN_RESPONSE, + NUMBER_OF_SIGNALS +}; + +static guint tfe_text_view_signals[NUMBER_OF_SIGNALS]; + +/* Signal handler */ +static void +on_changed (GtkTextBuffer *tb, TfeTextView *tv) { + tv->changed=TRUE; +} + +static void +tfe_text_view_dispose (GObject *gobject) { + TfeTextView *tv = TFE_TEXT_VIEW (gobject); + + if (G_IS_FILE (tv->file)) + g_clear_object (&tv->file); + + G_OBJECT_CLASS (tfe_text_view_parent_class)->dispose (gobject); +} + +static void +tfe_text_view_init (TfeTextView *tv) { + tv->tb = gtk_text_view_get_buffer (GTK_TEXT_VIEW (tv)); + tv->file = NULL; + tv->changed = FALSE; + gtk_text_view_set_wrap_mode (GTK_TEXT_VIEW (tv), GTK_WRAP_WORD_CHAR); + g_signal_connect (tv->tb, "changed", G_CALLBACK (on_changed), tv); +} + +static void +tfe_text_view_class_init (TfeTextViewClass *class) { + GObjectClass *object_class = G_OBJECT_CLASS (class); + + object_class->dispose = tfe_text_view_dispose; + tfe_text_view_signals[CHANGE_FILE] = g_signal_newv ("change-file", + G_TYPE_FROM_CLASS (class), + G_SIGNAL_RUN_LAST | G_SIGNAL_NO_RECURSE | G_SIGNAL_NO_HOOKS, + NULL /* closure */, + NULL /* accumulator */, + NULL /* accumulator data */, + NULL /* C marshaller */, + G_TYPE_NONE /* return_type */, + 0 /* n_params */, + NULL /* param_types */); + GType param_types[] = {G_TYPE_INT}; + tfe_text_view_signals[OPEN_RESPONSE] = g_signal_newv ("open-response", + G_TYPE_FROM_CLASS (class), + G_SIGNAL_RUN_LAST | G_SIGNAL_NO_RECURSE | G_SIGNAL_NO_HOOKS, + NULL /* closure */, + NULL /* accumulator */, + NULL /* accumulator data */, + NULL /* C marshaller */, + G_TYPE_NONE /* return_type */, + 1 /* n_params */, + param_types); +} + +GFile * +tfe_text_view_get_file (TfeTextView *tv) { + g_return_val_if_fail (TFE_IS_TEXT_VIEW (tv), NULL); + + return g_file_dup (tv->file); +} + +static void +open_dialog_response(GtkWidget *dialog, gint response, TfeTextView *tv) { + GFile *file; + char *contents; + gsize length; + GtkWidget *message_dialog; + GError *err = NULL; + + if (response != GTK_RESPONSE_ACCEPT) + g_signal_emit (tv, tfe_text_view_signals[OPEN_RESPONSE], 0, TFE_OPEN_RESPONSE_CANCEL); + else if (! G_IS_FILE (file = gtk_file_chooser_get_file (GTK_FILE_CHOOSER (dialog)))) + g_signal_emit (tv, tfe_text_view_signals[OPEN_RESPONSE], 0, TFE_OPEN_RESPONSE_ERROR); + else if (! g_file_load_contents (file, NULL, &contents, &length, NULL, &err)) { /* read error */ + if (G_IS_FILE (file)) + g_object_unref (file); + message_dialog = gtk_message_dialog_new (GTK_WINDOW (dialog), GTK_DIALOG_MODAL, + GTK_MESSAGE_ERROR, GTK_BUTTONS_CLOSE, + "%s.\n", err->message); + g_signal_connect (message_dialog, "response", G_CALLBACK (gtk_window_destroy), NULL); + gtk_widget_show (message_dialog); + g_error_free (err); + g_signal_emit (tv, tfe_text_view_signals[OPEN_RESPONSE], 0, TFE_OPEN_RESPONSE_ERROR); + } else { + gtk_text_buffer_set_text (tv->tb, contents, length); + g_free (contents); + tv->file = file; +/* tv->changed = FALSE;*/ + g_signal_emit (tv, tfe_text_view_signals[OPEN_RESPONSE], 0, TFE_OPEN_RESPONSE_SUCCESS); + } + gtk_window_destroy (GTK_WINDOW (dialog)); +} + +void +tfe_text_view_open (TfeTextView *tv) { + g_return_if_fail (TFE_IS_TEXT_VIEW (tv)); + + GtkWidget *dialog; + + dialog = gtk_file_chooser_dialog_new ("Open file", NULL, GTK_FILE_CHOOSER_ACTION_OPEN, + "Cancel", GTK_RESPONSE_CANCEL, + "Open", GTK_RESPONSE_ACCEPT, + NULL); + g_signal_connect (dialog, "response", G_CALLBACK (open_dialog_response), tv); + gtk_widget_show (dialog); +} + +static void +saveas_dialog_response (GtkWidget *dialog, gint response, TfeTextView *tv) { + GFile *file; + + if (response == GTK_RESPONSE_ACCEPT) { + file = gtk_file_chooser_get_file (GTK_FILE_CHOOSER (dialog)); + if (G_IS_FILE(file)) { + tv->file = file; + tv->changed = TRUE; + g_signal_emit (tv, tfe_text_view_signals[CHANGE_FILE], 0); + tfe_text_view_save (TFE_TEXT_VIEW (tv)); + } + } + gtk_window_destroy (GTK_WINDOW (dialog)); +} + +void +tfe_text_view_save (TfeTextView *tv) { + g_return_if_fail (TFE_IS_TEXT_VIEW (tv)); + + GtkTextIter start_iter; + GtkTextIter end_iter; + gchar *contents; + GtkWidget *message_dialog; + GtkWidget *win = gtk_widget_get_ancestor (GTK_WIDGET (tv), GTK_TYPE_WINDOW); + GError *err = NULL; + + if (! tv->changed) + return; /* no necessary to save it */ + else if (tv->file == NULL) + tfe_text_view_saveas (tv); + else { + gtk_text_buffer_get_bounds (tv->tb, &start_iter, &end_iter); + contents = gtk_text_buffer_get_text (tv->tb, &start_iter, &end_iter, FALSE); + if (g_file_replace_contents (tv->file, contents, strlen (contents), NULL, TRUE, G_FILE_CREATE_NONE, NULL, NULL, &err)) + tv->changed = FALSE; + else { +/* It is possible that tv->file is broken. */ +/* It is a good idea to set tv->file to NULL. */ + if (G_IS_FILE (tv->file)) + g_object_unref (tv->file); + tv->file =NULL; + g_signal_emit (tv, tfe_text_view_signals[CHANGE_FILE], 0); + tv->changed = TRUE; + message_dialog = gtk_message_dialog_new (GTK_WINDOW (win), GTK_DIALOG_MODAL, + GTK_MESSAGE_ERROR, GTK_BUTTONS_CLOSE, + "%s.\n", err->message); + g_signal_connect (message_dialog, "response", G_CALLBACK (gtk_window_destroy), NULL); + gtk_widget_show (message_dialog); + g_error_free (err); + } + } +} + +void +tfe_text_view_saveas (TfeTextView *tv) { + g_return_if_fail (TFE_IS_TEXT_VIEW (tv)); + + GtkWidget *dialog; + GtkWidget *win = gtk_widget_get_ancestor (GTK_WIDGET (tv), GTK_TYPE_WINDOW); + + dialog = gtk_file_chooser_dialog_new ("Save file", GTK_WINDOW (win), GTK_FILE_CHOOSER_ACTION_SAVE, + "_Cancel", GTK_RESPONSE_CANCEL, + "_Save", GTK_RESPONSE_ACCEPT, + NULL); + g_signal_connect (dialog, "response", G_CALLBACK (saveas_dialog_response), tv); + gtk_widget_show (dialog); +} + +GtkWidget * +tfe_text_view_new_with_file (GFile *file) { + g_return_val_if_fail (G_IS_FILE (file), NULL); + + GtkWidget *tv; + char *contents; + gsize length; + + if (! g_file_load_contents (file, NULL, &contents, &length, NULL, NULL)) /* read error */ + return NULL; + + tv = tfe_text_view_new(); + gtk_text_buffer_set_text (TFE_TEXT_VIEW (tv)->tb, contents, length); + g_free (contents); + TFE_TEXT_VIEW (tv)->file = g_file_dup (file); + return tv; +} + +GtkWidget * +tfe_text_view_new (void) { + return gtk_widget_new (TFE_TYPE_TEXT_VIEW, NULL); +} + diff --git a/src/tfe5/tfetextview.h b/src/tfe5/tfetextview.h new file mode 100644 index 0000000..b48a412 --- /dev/null +++ b/src/tfe5/tfetextview.h @@ -0,0 +1,29 @@ +#define TFE_TYPE_TEXT_VIEW tfe_text_view_get_type () +G_DECLARE_FINAL_TYPE (TfeTextView, tfe_text_view, TFE, TEXT_VIEW, GtkTextView) + +/* "open-response" signal response */ +enum +{ + TFE_OPEN_RESPONSE_SUCCESS, + TFE_OPEN_RESPONSE_CANCEL, + TFE_OPEN_RESPONSE_ERROR +}; + +GFile * +tfe_text_view_get_file (TfeTextView *tv); + +void +tfe_text_view_open (TfeTextView *tv); + +void +tfe_text_view_save (TfeTextView *tv); + +void +tfe_text_view_saveas (TfeTextView *tv); + +GtkWidget * +tfe_text_view_new_with_file (GFile *file); + +GtkWidget * +tfe_text_view_new (void); + diff --git a/src/tfv1.c b/src/tfv1.c new file mode 100644 index 0000000..f6dd45e --- /dev/null +++ b/src/tfv1.c @@ -0,0 +1,46 @@ +#include + +static void +on_activate (GApplication *app, gpointer user_data) { + GtkWidget *win; + GtkWidget *tv; + GtkTextBuffer *tb; + gchar *text; + + text = +"Once upon a time, there was an old man who was called Taketori-no-Okina." +"It is a japanese word that means a man whose work is making bamboo baskets.\n" +"One day, he went into a mountain and found a shining bamboo." +"\"What a mysterious bamboo it is!,\" he said." +"He cuts it, then there was a small cute baby girl in it." +"The girl was shining faintly." +"He thought this baby girl is a gift from Heaven and took her home.\n" +"His wife was surprized at his tale." +"They were very happy because they had no children." +; + win = gtk_application_window_new (GTK_APPLICATION (app)); + gtk_window_set_title (GTK_WINDOW (win), "Taketori"); + gtk_window_set_default_size (GTK_WINDOW (win), 400, 300); + + tv = gtk_text_view_new (); + tb = gtk_text_view_get_buffer (GTK_TEXT_VIEW (tv)); + gtk_text_buffer_set_text (tb, text, -1); + gtk_text_view_set_wrap_mode (GTK_TEXT_VIEW (tv), GTK_WRAP_WORD_CHAR); + + gtk_window_set_child (GTK_WINDOW (win), tv); + + gtk_widget_show (win); +} + +int +main (int argc, char **argv) { + GtkApplication *app; + int stat; + + app = gtk_application_new ("com.github.ToshioCP.tfv1", 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; +} + diff --git a/src/tfv2.c b/src/tfv2.c new file mode 100644 index 0000000..6edd635 --- /dev/null +++ b/src/tfv2.c @@ -0,0 +1,50 @@ +#include + +static void +on_activate (GApplication *app, gpointer user_data) { + GtkWidget *win; + GtkWidget *scr; + GtkWidget *tv; + GtkTextBuffer *tb; + gchar *text; + + text = +"Once upon a time, there was an old man who was called Taketori-no-Okina." +"It is a japanese word that means a man whose work is making bamboo baskets.\n" +"One day, he went into a mountain and found a shining bamboo." +"\"What a mysterious bamboo it is!,\" he said." +"He cuts it, then there was a small cute baby girl in it." +"The girl was shining faintly." +"He thought this baby girl is a gift from Heaven and took her home.\n" +"His wife was surprized at his tale." +"They were very happy because they had no children." +; + win = gtk_application_window_new (GTK_APPLICATION (app)); + gtk_window_set_title (GTK_WINDOW (win), "Taketori"); + gtk_window_set_default_size (GTK_WINDOW (win), 400, 300); + + scr = gtk_scrolled_window_new (); + gtk_window_set_child (GTK_WINDOW (win), scr); + + tv = gtk_text_view_new (); + tb = gtk_text_view_get_buffer (GTK_TEXT_VIEW (tv)); + gtk_text_buffer_set_text (tb, text, -1); + gtk_text_view_set_wrap_mode (GTK_TEXT_VIEW (tv), GTK_WRAP_WORD_CHAR); + + gtk_scrolled_window_set_child (GTK_SCROLLED_WINDOW (scr), tv); + + gtk_widget_show (win); +} + +int +main (int argc, char **argv) { + GtkApplication *app; + int stat; + + app = gtk_application_new ("com.github.ToshioCP.tfv2", 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; +} + diff --git a/src/tfv3.c b/src/tfv3.c new file mode 100644 index 0000000..92fe1ac --- /dev/null +++ b/src/tfv3.c @@ -0,0 +1,56 @@ +#include + +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 *scr; + GtkWidget *tv; + GtkTextBuffer *tb; + char *contents; + gsize length; + char *filename; + + win = gtk_application_window_new (GTK_APPLICATION (app)); + gtk_window_set_default_size (GTK_WINDOW (win), 400, 300); + + scr = gtk_scrolled_window_new (); + gtk_window_set_child (GTK_WINDOW (win), scr); + + tv = gtk_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_text_view_set_editable (GTK_TEXT_VIEW (tv), FALSE); + gtk_scrolled_window_set_child (GTK_SCROLLED_WINDOW (scr), tv); + + 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)); + } +} + +int +main (int argc, char **argv) { + GtkApplication *app; + int stat; + + app = gtk_application_new ("com.github.ToshioCP.tfv3", 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; +} + diff --git a/src/tfv4.c b/src/tfv4.c new file mode 100644 index 0000000..c0b26f6 --- /dev/null +++ b/src/tfv4.c @@ -0,0 +1,71 @@ +#include + +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 viewer"); + 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 = gtk_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_text_view_set_editable (GTK_TEXT_VIEW (tv), FALSE); + gtk_scrolled_window_set_child (GTK_SCROLLED_WINDOW (scr), tv); + + 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.tfv4", 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; +} + diff --git a/src2md.rb b/src2md.rb new file mode 100755 index 0000000..29730f2 --- /dev/null +++ b/src2md.rb @@ -0,0 +1,21 @@ +#!/bin/sh +exec ruby -x "$0" "$@" +#!ruby + +# src2md.rb + +require_relative 'lib/lib_src2md.rb' + +def usage + $stderr.print "Usage: ruby srcd2md.rb src.md_file md_file\n" +end + +if ARGV.size != 2 + usage + exit 1 +end + +srcmd = ARGV[0] +md = ARGV[1] +src2md srcmd, md +