G_APPLICATION_HANDLES_OPEN flag
ในบทความที่ผ่านมาได้กล่าวถึง GtkTextView GtkTextBuffer และ GtkScrolledWindow เพราะว่ามุ่งหวังจะสร้างโปรแกรมอ่านไฟล์ ซึ่งจะมีวิธีการหลากหลาย แต่สำหรับมือใหม่ก็จะกล่าวในเนื้อหาที่ง่ายๆ แต่ก่อนอื่นก็ต้องทำความเข้าใจเรื่อง OpenSignal เสียก่อน
ต่อไปจะทำโปรแกรมให้รับพารามิเตอร์เพื่อเปิดอ่านไฟล์ไปเก็บไว้ใน GtkTextBuffer ตัวอย่างเช่น
$ ./a.out filename
วิธีการนี้เราต้องรู้ก่อนว่า GtkApplication หรือ GApplication เข้าใจอาร์กิวเมนต์หรือรับพารามิเตอร์อย่างไร
เมื่อ GtkApplication ถูกสร้างขึ้น จะมี flag ถูกกำหนดขึ้นในฐานะอาร์กิวเมนท์ คือ
GtkApplication *
gtk_application_new (const gchar *application_id, GApplicationFlags flags);
ในบทความนี้จะอธิบาย flags สองอัน คือ G_APPLICATION_FLAGS_NONE และ G_APPLICATION_HANDLES_OPEN
และหากสังเกต อาร์กิวเมนต์ นี้จะถูกส่งไปพร้อมกับการสร้าง application จาก GtkApplicaton และทุกครั้งเราส่ง G_APPLICATION_FLAGS_NONE เป็นค่าดีฟอลท์ นั่นคือ โปรแกรมจะไม่รับพารามิเตอร์ใดๆ ดังนั้นถ้าเราส่งอาร์กิวเมนต์ไปพร้อมกับโปรแกรม ระบบก็จะแสดงข้อความ error
แต่ถ้าเราต้องการส่งอาร์กิวเมนต์ไปขณะที่รันโปรแกรมจากคอมมานด์ไลน์นั้น เราต้องใช้ G_APPLICATION_HANDLES_COMMAND_LINE มาช่วย
โดย flags ที่จะใช้นั้น คือ
GApplicationFlags' Members
G_APPLICATION_FLAGS_NONE Default. (ไม่มีการส่งผ่านอาร์กิวเมนต์)
... ... ...
G_APPLICATION_HANDLES_OPEN ส่งผ่านอาร์กิวเมนต์ตอนเรียกใช้โปรแกรม
... ... ...
ตัวอย่างการใช้งาน เช่น
app = gtk_application_new ("com.github.ToshioCP.tfv3", G_APPLICATION_HANDLES_OPEN);
คำสั่งนี้จะยอมให้เราส่งอาร์กิวเมนต์ไปพร้อมกับรันโปรแกรม ซึ่งก็จะเป็นชื่อไฟล์ที่เราต้องการเปิดอ่านนั่นเอง
open signal
วกกลับมาเรื่องโปรแกรม เมื่อ Application ถูกรัน จะมี signal ที่เราสามารถส่งไป 2 อย่าง คือ
- activate signal --- เป็น signal ที่ถูกเรียกใช้เมื่อไม่มีอาร์กิวเมนต์
- open signal --- เป็น signal ที่เรียกใช้เมื่อมีอย่างน้อย 1 อาร์กิวเมนต์
แฮนเดลอร์ที่ชื่อ open (handler คือ ตัวที่รองรับหลัง signal ถูกเรียก) จะมีรูปแบบดังนี้
void user_function (GApplication *application,
gpointer files,
gint n_files,
gchar *hint,
gpointer user_data)
โดยพารามิเตอร์ที่รับมา คือ
application
--- อินสแตนซ์ของ application ปกติเป็น GtkApplicationfiles
--- อะเรย์ของ GFiles ประกอบด้วย [array length=n_files] [element-type GFile]n_files
--- เป็นจำนวนของอิลิเมนต์ของ fileshint
--- เป็น hint ที่จะเรียกโดย อินสแตนซ์ (ไม่ใส่ก็ได้)user_data
--- ข้อมูลที่ผู้ใช้ส่งไปให้กับ handler
Making a file viewer
What is a file viewer?
ตอนนี้เราจะสร้างโปรแกรมเปิดอ่านไฟล์ โดยจะแสดงข้อความในไฟล์ที่ระบุชื่อไฟล์ตอนเริ่มต้นโปรแกรม โดยมีหลักการดังนี้
- When arguments are given, it treats the first argument as a filename and opens it.
- If opening the file succeeds, it reads the contents of the file and inserts it to GtkTextBuffer and then shows the window.
- If it fails to open the file, it will show an error message and quit.
- If there's no argument, it will shows an error message and quit.
- If there are two or more arguments, the second one and any others are ignored.
The program which does this is shown below.
1 #include <gtk/gtk.h> 2 3 static void 4 app_activate (GApplication *app, gpointer user_data) { 5 g_print ("You need a filename argument.\n"); 6 } 7 8 static void 9 app_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 if ((filename = g_file_get_basename (files[0])) != NULL) { 34 gtk_window_set_title (GTK_WINDOW (win), filename); 35 g_free (filename); 36 } 37 gtk_widget_show (win); 38 } else { 39 if ((filename = g_file_get_path (files[0])) != NULL) { 40 g_print ("No such file: %s.\n", filename); 41 g_free (filename); 42 } 43 gtk_window_destroy (GTK_WINDOW (win)); 44 } 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.tfv3", G_APPLICATION_HANDLES_OPEN); 53 g_signal_connect (app, "activate", G_CALLBACK (app_activate), NULL); 54 g_signal_connect (app, "open", G_CALLBACK (app_open), NULL); 55 stat = g_application_run (G_APPLICATION (app), argc, argv); 56 g_object_unref (app); 57 return stat; 58 }
เมื่อสั่งรันโปรแกรมจากหน้าจอ Terminal พร้อมกับกำหนดชื่อไฟล์ที่จะเปิดก็จะได้หน้าจอดังนี้
โปรแกรมจะมีรายละเอียดดังนี้
ก่อนอื่นเราต้องแก้ไขอาร์กิวเมนท์ในฟังค์ชัน main ก่อนโดยเปลี่ยนดังนี้
G_APPLICATION_FLAGS_NONE
เปลี่ยนเป็นG_APPLICATION_HANDLES_OPEN
; และ- เพิ่ม
g_signal_connect (app, "open", G_CALLBACK (on_open), NULL)
จากนั้นลบโค้ดออกจาก app_activate แล้วให้มีเฉพาะ พิมพ์ข้อความเตือน กรณีไม่ระบุชื่อไฟล์ที่จะเปิดอ่านในโปรแกรม และโปรแกรมก็จะยกเลิกโดยอัตโนมัติเพราะใน app_activate ไม่มีการสร้างและแสดงวินโดว์ ซึ่งไม่เกิดการวนลูปในระบบโปรแกรม
หลักๆ แล้วการทำงานจะอยู่ที่ app_open ภายในฟังค์ชันนี้จะมีหน้าที่ดังนี้
- สร้าง GtkApplicationWindow, GtkScrolledWindow, GtkTextView และ GtkTextBuffer พร้อมกับเชื่อม Widget ทั้งหมดเข้าด้วยกัน
- กำหนดการตัดบรรทัดเป็น
GTK_WRAP_WORD_CHAR
ใน GtktextView; - กำหนด GtkTextView ให้อ่านได้เพียงอย่างเดียว เพราะว่าโปรแกรมนี้สำหรับอ่าน ไม่ใช่โปรแกรมแก้ไขไฟล์
- อ่านไฟล์แล้วเพิ่มข้อความไว้ใน GtkTextBuffer และ
- ถ้าไม่มีไฟล์ที่จะเปิดหรือมีปัญหาเกี่ยวกับการเปิดไฟล์ให้ออกจากโปรแกรม พร้อมกับลบอินสแตนซ์ของ window ออกไปด้วย
ในโปรแกรมนี้ส่วนสำคัญในการเปิดอ่านไฟล์อยู่ตรงนี้
if (g_file_load_contents (files[0], NULL, &contents, &length, NULL, NULL)) { gtk_text_buffer_set_text (tb, contents, length); g_free (contents); if ((filename = g_file_get_basename (files[0])) != NULL) { gtk_window_set_title (GTK_WINDOW (win), filename); g_free (filename); } gtk_widget_show (win); } else { if ((filename = g_file_get_path (files[0])) != NULL) { g_print ("No such file: %s.\n", filename); g_free (filename); } gtk_window_destroy (GTK_WINDOW (win)); }
ฟังค์ชัน g_file_load_contents จะโหลดไฟล์ไปไว้ใน buffer โดยจะจองหน่วยความจำ (allocated) contents ขึ้นมาโดยจะอ้างอิงเป็น pointer (ต้องมีเครื่องหมาย &) และความยาวของ buffer จะกำหนดไว้ใน length แล้วก็จะส่งค่า TRUE กลับ กรณีที่เปิดไฟล์สำเร็จ
ดังนั้นหลังจากที่อ่านข้อมูลไปเก็บใน buffer เสร็จแล้ว ต้องคืนหน่วยความจำตำแหน่งของ contents ด้วย มิฉะนั้นจะทำให้เกิด memory leak (ตรงนี้ดูจะขัดใจเล็กน้อยตรงที่เราต้องศึกษาให้ละเอียดว่าฟังค์ชันไหนสร้างแล้วจองหน่วยความจำบ้าง ต้องคืนให้หมด หากปล่อยไว้จะกลายเป็นบัก ทำให้โปรแกรมหยุดทำงานหรือช้าลงในภายหลัง)
อีกฟังค์ชันหนึ่งที่ต้องคืนหน่วยความจำ คือ g_file_get_basename และ g_file_get_path เพราะระบบจะจองหน่วยความสำหรับเก็บข้อมูลที่ส่งกลับจากฟังค์ชัน ในตัวอย่างนี้ คือ filename
GtkNotebook
ในระบบจะมี Widget หนึ่งชื่อว่า GtkNotebook หรือที่รู้จักกันอีกชื่อหนึ่งว่า Tabs โดยในหนึ่งหน้าจอจะมีหลายๆ tab ซึ่งเป็น Multiple child เหมือนตัวอย่างในรูปด้านล่างนี้
ดูจากภาพด้านบน ภาพแรก tab ถูกเลือกจะแสดงในกรอบด้านล่างเป็นไฟล์ pr1.c และเมื่อคลิก tab ชื่อ tfv1.c ก็จะได้เหมือนภาพขวา โดยข้อมูลจะเปลี่ยนเป็นรายละเอียดของไฟล์ tfv1.c
ในตัวอย่างต่อไปเราจะใช้ GtkNotebook มาแทรกเข้าเป็น child ของ GtkApplicationWindow และในแต่ละ tab ของ GtkNotebook ก็จะเพิ่ม GtkScrolledWindow เข้าไปเป็น child เพื่อใช้แสดงผลในไฟล์ที่เปิด
ตัวอย่างโปรแกรม
1 #include <gtk/gtk.h> 2 3 static void 4 app_activate (GApplication *app, gpointer user_data) { 5 g_print ("You need a filename argument.\n"); 6 } 7 8 static void 9 app_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 if ((filename = g_file_get_basename (files[i])) != NULL) { 42 lab = gtk_label_new (filename); 43 g_free (filename); 44 } else 45 lab = gtk_label_new (""); 46 gtk_notebook_append_page (GTK_NOTEBOOK (nb), scr, lab); 47 nbp = gtk_notebook_get_page (GTK_NOTEBOOK (nb), scr); 48 g_object_set (nbp, "tab-expand", TRUE, NULL); 49 } else if ((filename = g_file_get_path (files[i])) != NULL) { 50 g_print ("No such file: %s.\n", filename); 51 g_free (filename); 52 } else 53 g_print ("No valid file is given\n"); 54 } 55 if (gtk_notebook_get_n_pages (GTK_NOTEBOOK (nb)) > 0) 56 gtk_widget_show (win); 57 else 58 gtk_window_destroy (GTK_WINDOW (win)); 59 } 60 61 int 62 main (int argc, char **argv) { 63 GtkApplication *app; 64 int stat; 65 66 app = gtk_application_new ("com.github.ToshioCP.tfv4", G_APPLICATION_HANDLES_OPEN); 67 g_signal_connect (app, "activate", G_CALLBACK (app_activate), NULL); 68 g_signal_connect (app, "open", G_CALLBACK (app_open), NULL); 69 stat = g_application_run (G_APPLICATION (app), argc, argv); 70 g_object_unref (app); 71 return stat; 72 }
ตัวอย่างหน้าจอที่รันโปรแกรม
ทีนี้มาดูสิ่งที่เปลี่ยนแปลงและเพิ่มเติมใน app_open โดยจะอธิบายแต่ละบรรทัดดังนี้
- 11-13: ตัวแปร
nb
,lab
และnbp
ถูกกำหนดขึ้นเพื่อใช้ใน GtkNotebook, GtkLabel และ GtkNotebookPage - 23: ชื่อวินโดว์เปลี่ยนเป็น "file viewer".
- 25: กำหนดให้ขนาดวินโดว์ขยายใหญ่เต็มหน้าจอ
- 27-28 GtkNotebook ถูกสร้างขึ้นและเพิ่มเข้าไปเป็น chile ใน GtkApplicationWindow
- 30-59 For-loop. โดยในลูปนี้จะอ่านชื่อไฟล์ที่ถูกส่งมาตอนรันโปรแกรม (arguments) และ
files[i]
เป็น GFile object บรรจุอาร์กิวเมนต์ลำดับที่ i - 32-37 GtkScrollledWindow, GtkTextView ถูกสร้างขึ้น และ GtkTextBuffer ถูกใช้ใน GtkTextView โดย GtkTextView จะถูกเพิ่มเข้าไปเป็น child ของ GtkScrolledWindow ในแต่ละไฟล์ก็จะถูกสร้างแบบนี้ซ้ำๆ จนกว่าจะหมดทุกไฟล์
- 39-40 inserts the contents of the file into GtkTextBuffer and frees the memory pointed by
contents
. - 41-43: Gets the filename and creates GtkLabel with the filename and then frees
filename
. - 44-45: If
filename
is NULL, creates GtkLabel with the empty string. - 46: Appends GtkScrolledWindow as a page, with the tab GtkLabel, to GtkNotebook. At this time a GtkNoteBookPage widget is created automatically. The GtkScrolledWindow widget is connected to the GtkNotebookPage. Therefore, the structure is like this:
GtkNotebook -- GtkNotebookPage -- GtkScrolledWindow
- 47: Gets GtkNotebookPage widget and sets
nbp
to point to this GtkNotebookPage. - 48: GtkNotebookPage has a property set called "tab-expand". If it is set to TRUE then the tab expands horizontally as long as possible. If it is 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 objects. See GObject API Reference, g_object_set. - 49-51: If the file cannot be read, "No such file" message is displayed and the
filename
buffer is freed. - 52-53: If
filename
is NULL, the "No valid file is given" message is outputted. - 55-58: If at least one file was read, then the number of GtkNotebookPage is greater than zero. If it's true, it shows the window. If it's false, it destroys the window, which causes the program to quit.
ไม่มีความคิดเห็น:
แสดงความคิดเห็น