将内容保存到文件

在本课中,你将学习如何添加带有键盘快捷键的菜单项,提示用户选择一个文件来保存 GtkTextBuffer 的内容,并异步保存文件。

添加“另存为”菜单项

  1. 打开窗口的 UI 定义文件,并在文件的底部找到 primary_menu 菜单定义

  2. 删除“首选项”菜单项,因为我们不需要它

  3. 在删除的菜单项的位置,添加 “另存为” 菜单项的定义

<menu id="primary_menu">
  <section>
    <item>
      <attribute name="label" translatable="yes">_Save as...</attribute>
      <attribute name="action">win.save-as</attribute>
    </item>
    <item>
      <attribute name="label" translatable="yes">_Keyboard Shortcuts</attribute>
      <attribute name="action">win.show-help-overlay</attribute>
    </item>
    <item>
      <attribute name="label" translatable="yes">_About {{name}}</attribute>
      <attribute name="action">app.about</attribute>
    </item>
  </section>
</menu>

“另存为”菜单项绑定到 win.save-as 操作;这意味着激活菜单项将激活在 TextViewerWindow 窗口上注册的 save-as 操作。

添加“另存为”操作

  1. 打开 text_viewer-window.c 文件,找到 TextViewerWindow 部件的实例初始化函数 text_viewer_window_init

  2. 创建 save-as 操作,将其 activate 信号连接到一个回调函数,并将操作添加到窗口

static void
text_viewer_window__open_file_dialog (GAction *action,
                                      GVariant *param,
                                      TextViewerWindow *self);

static void
text_viewer_window__save_file_dialog (GAction *action,
                                      GVariant *param,
                                      TextViewerWindow *self);

static void
text_viewer_window_init (TextViewerWindow *self)
{
  gtk_widget_init_template (GTK_WIDGET (self));

  g_autoptr (GSimpleAction) open_action = g_simple_action_new ("open", NULL);
  g_signal_connect (open_action, "activate", G_CALLBACK (text_viewer_window__open_file_dialog), self);
  g_action_map_add_action (G_ACTION_MAP (self), G_ACTION (open_action));

  g_autoptr (GSimpleAction) save_action = g_simple_action_new ("save-as", NULL);
  g_signal_connect (save_action, "activate", G_CALLBACK (text_viewer_window__save_file_dialog), self);
  g_action_map_add_action (G_ACTION_MAP (self), G_ACTION (save_action));

  GtkTextBuffer *buffer = gtk_text_view_get_buffer (self->main_text_view);
  g_signal_connect (buffer,
                    "notify::cursor-position",
                    G_CALLBACK (text_viewer_window__update_cursor_position),
                    self);
}

选择一个文件

  1. save-as 操作的 activate 回调函数中,使用 GTK_FILE_CHOOSER_ACTION_SAVE 操作创建一个文件选择对话框,并连接到其 response 信号

static void
text_viewer_window__save_file_dialog (GAction          *action G_GNUC_UNUSED,
                                      GVariant         *param G_GNUC_UNUSED,
                                      TextViewerWindow *self)
{
  g_autoptr (GtkFileDialog) dialog =
    gtk_file_dialog_new ();

  gtk_file_dialog_save (dialog,
                        GTK_WINDOW (self),
                        NULL,
                        on_save_response,
                        self);
}
  1. 在回调函数中,检索用户选择的位置的 GFile,并调用 save_file() 函数

static void
on_save_response (GObject      *source,
                  GAsyncResult *result,
                  gpointer      user_data)
{
  GtkFileDialog *dialog = GTK_FILE_DIALOG (source);
  TextViewerWindow *self = user_data;

  g_autoptr (GFile) file =
    gtk_file_dialog_save_finish (dialog, result, NULL);

  if (file != NULL)
    save_file (self, file);
}

保存文本缓冲区的内容

  1. save_file 函数中,使用起始和结束 GtkTextIter 作为缓冲区的边界,检索 GtkTextBuffer 的内容,然后启动一个异步操作,将数据保存到 GFile 指向的位置

 static void
 save_file (TextViewerWindow *self,
            GFile            *file)
 {
   GtkTextBuffer *buffer = gtk_text_view_get_buffer (self->main_text_view);

   // Retrieve the iterator at the start of the buffer
   GtkTextIter start;
   gtk_text_buffer_get_start_iter (buffer, &start);

   // Retrieve the iterator at the end of the buffer
   GtkTextIter end;
   gtk_text_buffer_get_end_iter (buffer, &end);

   // Retrieve all the visible text between the two bounds
   char *text = gtk_text_buffer_get_text (buffer, &start, &end, FALSE);

   // If there is nothing to save, return early
   if (text == NULL)
     return;

   g_autoptr(GBytes) bytes = g_bytes_new_take (text, strlen (text));

   // Start the asynchronous operation to save the data into the file
   g_file_replace_contents_bytes_async (file,
                                        bytes,
                                        NULL,
                                        FALSE,
                                        G_FILE_CREATE_NONE,
                                        NULL,
                                        save_file_complete,
                                        self);
}
  1. save_file_complete 函数中,完成异步操作并报告任何错误

static void
save_file_complete (GObject      *source_object,
                    GAsyncResult *result,
                    gpointer      user_data)
{
  GFile *file = G_FILE (source_object);

  g_autoptr (GError) error =  NULL;
  g_file_replace_contents_finish (file, result, NULL, &error);

  // Query the display name for the file
  g_autofree char *display_name = NULL;
  g_autoptr (GFileInfo) info =
  g_file_query_info (file,
                     "standard::display-name",
                     G_FILE_QUERY_INFO_NONE,
                     NULL,
                     NULL);
  if (info != NULL)
    {
      display_name =
        g_strdup (g_file_info_get_attribute_string (info, "standard::display-name"));
    }
  else
    {
      display_name = g_file_get_basename (file);
    }

  if (error != NULL)
    {
      g_printerr ("Unable to save “%s”: %s\n",
                  display_name,
                  error->message);
    }
}

为“另存为”操作添加键盘快捷键

  1. 打开 text_viewer-application.c 源代码文件,找到 TextViewerApplication 实例初始化函数 text_viewer_application_init

  2. Ctrl + Shift + S 添加为 win.save-as 操作的加速器快捷键

static void
text_viewer_application_init (TextViewerApplication *self)
{
  // ...
  gtk_application_set_accels_for_action (GTK_APPLICATION (self),
                                         "win.save-as",
                                         (const char *[]) {
                                           "<Ctrl><Shift>s",
                                           NULL,
                                         });
}

将“另存为”快捷键添加到键盘快捷键帮助

  1. 在源代码目录中找到 help-overlay.ui 文件

  2. 找到 GtkShortcutsGroup 定义

  3. 在快捷键组中添加一个新的 GtkShortcutsShortcut 定义,用于 win.save 操作

<object class="GtkShortcutsGroup">
  <property name="title" translatable="yes" context="shortcut window">General</property>
  <child>
    <object class="GtkShortcutsShortcut">
      <property name="title" translatable="yes" context="shortcut window">Open</property>
      <property name="action-name">win.open</property>
    </object>
  </child>
  <child>
    <object class="GtkShortcutsShortcut">
      <property name="title" translatable="yes" context="shortcut window">Save As</property>
      <property name="action-name">win.save-as</property>
    </object>
  </child>

在本课结束时,你应该能够

  • 从主菜单中选择“另存为”菜单项

  • 从对话框中选择一个文件

  • 将文本查看器的内容保存到所选文件中