Ver código fonte

Ajout fonctionnalité Enregistrer sous

vetetix 1 mês atrás
pai
commit
40c6361e31

+ 12 - 0
src/shortcuts-dialog.ui

@@ -16,6 +16,18 @@
             <property name="action-name">app.quit</property>
           </object>
         </child>
+        <child>
+          <object class="AdwShortcutsItem">
+            <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>
       </object>
     </child>
   </object>

+ 19 - 7
src/text-viewer-application.c

@@ -113,11 +113,23 @@ static const GActionEntry app_actions[] = {
 static void
 text_viewer_application_init (TextViewerApplication *self)
 {
-	g_action_map_add_action_entries (G_ACTION_MAP (self),
-	                                 app_actions,
-	                                 G_N_ELEMENTS (app_actions),
-	                                 self);
-	gtk_application_set_accels_for_action (GTK_APPLICATION (self),
-	                                       "app.quit",
-	                                       (const char *[]) { "<control>q", NULL });
+  g_action_map_add_action_entries (G_ACTION_MAP (self),
+                                   app_actions,
+                                   G_N_ELEMENTS (app_actions),
+                                   self);
+  gtk_application_set_accels_for_action (GTK_APPLICATION (self),
+                                         "app.quit",
+                                         (const char *[]) { "<control>q", NULL });
+  gtk_application_set_accels_for_action (GTK_APPLICATION (self),
+                                         "win.open",
+                                         (const char *[]) {
+                                           "<Ctrl>o",
+                                           NULL,
+                                         });
+  gtk_application_set_accels_for_action (GTK_APPLICATION (self),
+                                         "win.save-as",
+                                         (const char *[]) {
+                                           "<Ctrl><Shift>s",
+                                           NULL,
+                                         });
 }

+ 278 - 7
src/text-viewer-window.c

@@ -24,10 +24,13 @@
 
 struct _TextViewerWindow
 {
-	AdwApplicationWindow  parent_instance;
+  AdwApplicationWindow  parent_instance;
 
-	/* Template widgets */
-	GtkLabel            *label;
+  /* Template widgets */
+  AdwHeaderBar  *header_bar;
+  GtkTextView   *main_text_view;
+  GtkButton     *open_button;
+  GtkLabel      *cursor_pos;
 };
 
 G_DEFINE_FINAL_TYPE (TextViewerWindow, text_viewer_window, ADW_TYPE_APPLICATION_WINDOW)
@@ -35,14 +38,282 @@ G_DEFINE_FINAL_TYPE (TextViewerWindow, text_viewer_window, ADW_TYPE_APPLICATION_
 static void
 text_viewer_window_class_init (TextViewerWindowClass *klass)
 {
-	GtkWidgetClass *widget_class = GTK_WIDGET_CLASS (klass);
+  GtkWidgetClass *widget_class = GTK_WIDGET_CLASS (klass);
 
-	gtk_widget_class_set_template_from_resource (widget_class, "/com/example/TextViewer/text-viewer-window.ui");
-	gtk_widget_class_bind_template_child (widget_class, TextViewerWindow, label);
+  gtk_widget_class_set_template_from_resource (
+        widget_class,
+        "/com/example/TextViewer/text-viewer-window.ui");
+  gtk_widget_class_bind_template_child (widget_class,
+                                        TextViewerWindow,
+                                        header_bar);
+  gtk_widget_class_bind_template_child (widget_class,
+                                        TextViewerWindow,
+                                        open_button);
+  gtk_widget_class_bind_template_child (widget_class,
+                                        TextViewerWindow,
+                                        main_text_view);
+  gtk_widget_class_bind_template_child (widget_class,
+                                        TextViewerWindow,
+                                        cursor_pos);
+}
+
+static void
+text_viewer_window__update_cursor_position (GtkTextBuffer    *buffer,
+                                            GParamSpec       *pspec G_GNUC_UNUSED,
+                                            TextViewerWindow *self)
+{
+  int cursor_pos = 0;
+
+  // Retrieve the value of the "cursor-position" property
+  g_object_get (buffer, "cursor-position", &cursor_pos, NULL);
+
+  // Construct the text iterator for the position of the cursor
+  GtkTextIter iter;
+  gtk_text_buffer_get_iter_at_offset (buffer, &iter, cursor_pos);
+
+  // Set the new contents of the label
+  g_autofree char *cursor_str =
+    g_strdup_printf ("Ln %d, Col %d",
+                     gtk_text_iter_get_line (&iter) + 1,
+                     gtk_text_iter_get_line_offset (&iter) + 1);
+
+  gtk_label_set_text (self->cursor_pos, cursor_str);
+}
+
+static void
+open_file_complete (GObject          *source_object,
+                    GAsyncResult     *result,
+                    TextViewerWindow *self)
+{
+  GFile *file = G_FILE (source_object);
+
+  g_autofree char *contents = NULL;
+  gsize length = 0;
+
+  g_autoptr (GError) error = NULL;
+
+  // Complete the asynchronous operation; this function will either
+  // give you the contents of the file as a byte array, or will
+  // set the error argument
+  g_file_load_contents_finish (file,
+                               result,
+                               &contents,
+                               &length,
+                               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);
+    }
+
+  // In case of error, print a warning to the standard error output
+  if (error != NULL)
+    {
+      g_printerr ("Unable to open “%s”: %s\n",
+                  g_file_peek_path (file),
+                  error->message);
+      return;
+    }
+
+  // Ensure that the file is encoded with UTF-8
+  if (!g_utf8_validate (contents, length, NULL))
+    {
+      g_printerr ("Unable to load the contents of “%s”: "
+                  "the file is not encoded with UTF-8\n",
+                  g_file_peek_path (file));
+      return;
+    }
+
+  // Retrieve the GtkTextBuffer instance that stores the
+  // text displayed by the GtkTextView widget
+  GtkTextBuffer *buffer = gtk_text_view_get_buffer (self->main_text_view);
+
+  // Set the text using the contents of the file
+  gtk_text_buffer_set_text (buffer, contents, length);
+
+  // Reposition the cursor so it's at the start of the text
+  GtkTextIter start;
+  gtk_text_buffer_get_start_iter (buffer, &start);
+  gtk_text_buffer_place_cursor (buffer, &start);
+
+  // Set the title using the display name
+  gtk_window_set_title (GTK_WINDOW (self), display_name);
+}
+
+static void
+open_file (TextViewerWindow *self,
+           GFile            *file)
+{
+  g_file_load_contents_async (file,
+                              NULL,
+                              (GAsyncReadyCallback) open_file_complete,
+                              self);
+}
+
+static void
+on_open_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_open_finish (dialog, result, NULL);
+
+  // If the user selected a file, open it
+  if (file != NULL)
+    open_file (self, file);
+}
+
+static void
+text_viewer_window__open_file_dialog (GAction           *action,
+                                      GVariant          *parameter,
+                                      TextViewerWindow  *self)
+{
+  g_autoptr (GtkFileDialog) dialog = gtk_file_dialog_new ();
+
+  gtk_file_dialog_open (dialog,
+                        GTK_WINDOW (self),
+                        NULL,
+                        on_open_response,
+                        self);
+}
+
+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);
+    }
+}
+
+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);
+}
+
+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);
+}
+
+static void
+text_viewer_window__save_file_dialog (GAction *action,
+                                      GVariant *param,
+                                      TextViewerWindow *self)
+{
+  g_autoptr (GtkFileDialog) dialog =
+    gtk_file_dialog_new ();
+
+  gtk_file_dialog_save (dialog,
+                        GTK_WINDOW (self),
+                        NULL,
+                        on_save_response,
+                        self);
 }
 
 static void
 text_viewer_window_init (TextViewerWindow *self)
 {
-	gtk_widget_init_template (GTK_WIDGET (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);
 }

+ 36 - 7
src/text-viewer-window.ui

@@ -3,13 +3,22 @@
   <requires lib="gtk" version="4.0"/>
   <requires lib="Adw" version="1.0"/>
   <template class="TextViewerWindow" parent="AdwApplicationWindow">
-    <property name="title" translatable="yes">Text-viewer</property>
+    <property name="title" translatable="yes">Text Viewer</property>
     <property name="default-width">800</property>
     <property name="default-height">600</property>
+    <style>
+      <class name="devel"/>
+    </style>
     <property name="content">
       <object class="AdwToolbarView">
         <child type="top">
-          <object class="AdwHeaderBar">
+          <object class="AdwHeaderBar" id="header_bar">
+            <child type="start">
+              <object class="GtkButton" id="open_button">
+                <property name="label">Open</property>
+                <property name="action-name">win.open</property>
+              </object>
+            </child>
             <child type="end">
               <object class="GtkMenuButton">
                 <property name="primary">True</property>
@@ -18,14 +27,30 @@
                 <property name="menu-model">primary_menu</property>
               </object>
             </child>
+            <child type="end">
+              <object class="GtkLabel" id="cursor_pos">
+                <property name="label">Ln 0, Col 0</property>
+                <style>
+                  <class name="dim-label"/>
+                  <class name="numeric"/>
+                </style>
+              </object>
+            </child>
           </object>
         </child>
         <property name="content">
-          <object class="GtkLabel" id="label">
-            <property name="label" translatable="yes">Hello, World!</property>
-            <style>
-              <class name="title-1"/>
-            </style>
+          <object class="GtkScrolledWindow">
+            <property name="hexpand">true</property>
+            <property name="vexpand">true</property>
+            <property name="margin-top">6</property>
+            <property name="margin-bottom">6</property>
+            <property name="margin-start">6</property>
+            <property name="margin-end">6</property>
+            <property name="child">
+              <object class="GtkTextView" id="main_text_view">
+                <property name="monospace">true</property>
+              </object>
+            </property>
           </object>
         </property>
       </object>
@@ -37,6 +62,10 @@
         <attribute name="label" translatable="yes">_Preferences</attribute>
         <attribute name="action">app.preferences</attribute>
       </item>
+      <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">app.shortcuts</attribute>