~alextee/zrythm

228c7ad526b354c9ba01bed71028b9cb422f73f0 — Alexandros Theodotou 5 months ago cc223a7 master
revamp project assistant dialog

Use AdwWindow instead of deprecated GtkDialog, and GtkFileDialog
instead of deprecated GtkFileChooserNative.

Requires libadwaita >= 1.4
M inc/gui/widgets/dialogs/project_assistant.h => inc/gui/widgets/dialogs/project_assistant.h +4 -20
@@ 1,21 1,5 @@
/*
 * Copyright (C) 2018-2019, 2022 Alexandros Theodotou <alex at zrythm dot org>
 *
 * This file is part of Zrythm
 *
 * Zrythm is free software: you can redistribute it and/or modify
 * it under the terms of the GNU Affero General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * Zrythm is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU Affero General Public License for more details.
 *
 * You should have received a copy of the GNU Affero General Public License
 * along with Zrythm.  If not, see <https://www.gnu.org/licenses/>.
 */
// SPDX-FileCopyrightText: © 2018-2019, 2022-2023 Alexandros Theodotou <alex@zrythm.org>
// SPDX-License-Identifier: LicenseRef-ZrythmLicense

/**
 * \file


@@ 48,7 32,7 @@ G_DECLARE_FINAL_TYPE (
  project_assistant_widget,
  Z,
  PROJECT_ASSISTANT_WIDGET,
  GtkDialog)
  AdwWindow)

/**
 * Project file information.


@@ 68,7 52,7 @@ typedef struct ProjectInfo
 */
typedef struct _ProjectAssistantWidget
{
  GtkDialog parent_instance;
  AdwWindow parent_instance;

  AdwViewStack * stack;


M inc/utils/gtk.h => inc/utils/gtk.h +3 -0
@@ 779,6 779,9 @@ z_gtk_get_first_focusable_child (GtkWidget * parent);
bool
z_gtk_descendant_has_focus (GtkWidget * parent);

void
z_gtk_window_make_escapable (GtkWindow * self);

/**
 * @}
 */

M meson.build => meson.build +1 -2
@@ 520,7 520,6 @@ test_cflags = [
  '-DG_LOG_USE_STRUCTURED=1',
  '-DG_LOG_DOMAIN="' + prog_name_lowercase + '"',
  '-DREALTIME=' + (is_clang ? '__attribute__((annotate("realtime")))' : ''),
  #'-DDEPRECATED_MSG(x)=__attribute__((deprecated(x)))',
  '-DOPTIMIZE(x)=' + (is_gcc ? '__attribute__((optimize(#x)))' : ''),
  '-DOPTIMIZE_O0=OPTIMIZE(O0)',
  '-DOPTIMIZE_O1=OPTIMIZE(O1)',


@@ 899,7 898,7 @@ gtk_dep = dependency (
  static: all_static)

libadwaita_dep = dependency (
  'libadwaita-1', version: '>=1.2',
  'libadwaita-1', version: '>=1.4',
  fallback: ['libadwaita', 'libadwaita_dep'],
  default_options: [
    'vapi=false', 'tests=false',

M resources/ui/export_dialog.ui => resources/ui/export_dialog.ui +1 -1
@@ 1,6 1,6 @@
<?xml version="1.0" encoding="UTF-8"?>
<interface>
  <requires lib="Adw" version="4.0"/>
  <requires lib="Adw" version="1.0"/>
  <requires lib="gtk" version="4.0"/>
  <template class="ExportDialogWidget" parent="GtkDialog">
    <property name="title" translatable="yes">Export As...</property>

M resources/ui/project_assistant.ui => resources/ui/project_assistant.ui +83 -82
@@ 1,19 1,18 @@
<?xml version="1.0" encoding="UTF-8"?>
<interface>
  <requires lib="gtk" version="4.0"/>
  <template class="ProjectAssistantWidget" parent="GtkDialog">
  <requires lib="Adw" version="1.0"/>
  <template class="ProjectAssistantWidget" parent="AdwWindow">
    <property name="title" translatable="yes">Select a Project</property>
    <property name="resizable">1</property>
    <property name="icon_name">zrythm</property>
    <property name="decorated">0</property>
    <property name="modal">True</property>
    <property name="use-header-bar">True</property>
    <property name="height-request">280</property>
    <property name="height-request">256</property>
    <property name="width-request">680</property>
    <child internal-child="content_area">
      <object class="GtkBox">
        <property name="orientation">vertical</property>
        <child>
    <property name="content">
      <object class="AdwToolbarView">
        <child type="top">
          <object class="AdwHeaderBar">
            <property name="centering-policy">strict</property>
            <child type="title">


@@ 23,93 22,95 @@
            </child>
          </object>
        </child>
        <child>
          <object class="AdwViewStack" id="stack">
            <property name="vexpand">1</property>
        <property name="content">
          <object class="GtkBox">
            <property name="orientation">vertical</property>
            <child>
              <object class="AdwViewStackPage">
                <property name="name">open-recent</property>
                <property name="title" translatable="yes">Open Project</property>
                <property name="icon-name">folder-recent</property>
                <property name="child">
                  <object class="GtkBox">
                    <property name="orientation">vertical</property>
                    <child>
                      <object class="GtkScrolledWindow">
                        <property name="hscrollbar-policy">never</property>
                        <property name="vexpand">1</property>
                        <property name="child">
                          <object class="GtkColumnView" id="recent_projects_column_view">
                            <style>
                              <class name="data-table"/>
                            </style>
              <object class="AdwViewStack" id="stack">
                <property name="vexpand">1</property>
                <child>
                  <object class="AdwViewStackPage">
                    <property name="name">open-recent</property>
                    <property name="title" translatable="yes">Open Project</property>
                    <property name="icon-name">folder-recent</property>
                    <property name="child">
                      <object class="GtkBox">
                        <property name="orientation">vertical</property>
                        <child>
                          <object class="GtkScrolledWindow">
                            <property name="hscrollbar-policy">never</property>
                            <property name="vexpand">1</property>
                            <property name="child">
                              <object class="GtkColumnView" id="recent_projects_column_view">
                                <property name="tab-behavior">GTK_LIST_TAB_ITEM</property>
                                <style>
                                  <class name="data-table"/>
                                </style>
                              </object>
                            </property>
                          </object>
                        </property>
                        </child>
                      </object>
                    </child>
                    </property>
                  </object>
                </property>
              </object>
            </child>
            <child>
              <object class="AdwViewStackPage">
                <property name="name">create-new</property>
                <property name="title" translatable="yes">Create New</property>
                <property name="icon-name">document-new</property>
                <property name="child">
                  <object class="GtkBox">
                    <property name="orientation">vertical</property>
                    <child>
                      <object class="GtkScrolledWindow">
                        <property name="hscrollbar-policy">never</property>
                        <property name="vexpand">1</property>
                        <property name="child">
                          <object class="GtkColumnView" id="templates_column_view">
                            <style>
                              <class name="data-table"/>
                            </style>
                </child>
                <child>
                  <object class="AdwViewStackPage">
                    <property name="name">create-new</property>
                    <property name="title" translatable="yes">Create New</property>
                    <property name="icon-name">document-new</property>
                    <property name="child">
                      <object class="GtkBox">
                        <property name="orientation">vertical</property>
                        <child>
                          <object class="GtkScrolledWindow">
                            <property name="hscrollbar-policy">never</property>
                            <property name="vexpand">1</property>
                            <property name="child">
                              <object class="GtkColumnView" id="templates_column_view">
                                <style>
                                  <class name="data-table"/>
                                </style>
                              </object>
                            </property>
                          </object>
                        </property>
                        </child>
                      </object>
                    </child>
                    </property>
                  </object>
                </property>
                </child>
              </object>
            </child>
            <child>
              <object class="AdwViewSwitcherBar">
                <property name="stack">stack</property>
                <binding name="reveal">
                  <lookup name="title-visible">title</lookup>
                </binding>
              </object>
            </child>
          </object>
        </child>
        <child>
          <object class="AdwViewSwitcherBar">
            <property name="stack">stack</property>
            <binding name="reveal">
              <lookup name="title-visible">title</lookup>
            </binding>
        </property>
        <child type="bottom">
          <object class="GtkActionBar">
            <child type="end">
              <object class="GtkButton" id="ok_btn">
                <style>
                  <class name="suggested-action"/>
                </style>
                <property name="label" translatable="yes">_Open Selected</property>
                <property name="use-underline">1</property>
              </object>
            </child>
            <child type="end">
              <object class="GtkButton" id="open_from_path_btn">
                <property name="label" translatable="yes">Open from _Path</property>
                <property name="use-underline">1</property>
              </object>
            </child>
          </object>
        </child>
      </object>
    </child>
    <child type="action">
      <object class="GtkButton" id="cancel_btn">
        <property name="label" translatable="yes">_Cancel</property>
        <property name="use-underline">1</property>
      </object>
    </child>
    <child type="action">
      <object class="GtkButton" id="open_from_path_btn">
        <property name="label" translatable="yes">Open from _Path</property>
        <property name="use-underline">1</property>
      </object>
    </child>
    <child type="action">
      <object class="GtkButton" id="ok_btn">
        <property name="label" translatable="yes">_Open Selected</property>
        <property name="use-underline">1</property>
      </object>
    </child>
    <action-widgets>
      <action-widget response="cancel">cancel_btn</action-widget>
      <action-widget response="256">open_from_path_btn</action-widget>
      <action-widget response="ok" default="true">ok_btn</action-widget>
    </action-widgets>
    </property>
  </template>
</interface>

M src/gui/widgets/dialogs/midi_function_dialog.c => src/gui/widgets/dialogs/midi_function_dialog.c +2 -12
@@ 5,6 5,7 @@
#include "project.h"
#include "settings/settings.h"
#include "utils/error.h"
#include "utils/gtk.h"
#include "utils/io.h"
#include "utils/objects.h"
#include "utils/resources.h"


@@ 421,16 422,5 @@ midi_function_dialog_widget_init (
  gtk_box_append (self->vbox, GTK_WIDGET (self->page));

  /* close on escape */
  GtkShortcutTrigger * trigger =
    gtk_shortcut_trigger_parse_string ("Escape|<Control>w");
  GtkShortcutAction * action =
    gtk_named_action_new ("window.close");
  GtkShortcut * esc_shortcut =
    gtk_shortcut_new (trigger, action);
  GtkShortcutController * sc =
    GTK_SHORTCUT_CONTROLLER (gtk_shortcut_controller_new ());
  gtk_shortcut_controller_add_shortcut (sc, esc_shortcut);
  gtk_widget_add_controller (
    GTK_WIDGET (self), GTK_EVENT_CONTROLLER (sc));
  /* note: can also use gtk_widget_class_add_binding_action (widget_class, GDK_KEY_Escape, 0, "window.close", NULL); */
  z_gtk_window_make_escapable (GTK_WINDOW (self));
}

M src/gui/widgets/dialogs/project_assistant.c => src/gui/widgets/dialogs/project_assistant.c +149 -128
@@ 26,7 26,7 @@
G_DEFINE_TYPE (
  ProjectAssistantWidget,
  project_assistant_widget,
  GTK_TYPE_DIALOG)
  ADW_TYPE_WINDOW)

static ProjectInfo *
project_info_new (const char * name, const char * filename)


@@ 315,27 315,6 @@ on_key_release (
    }
}

static void
on_project_activate (
  GtkColumnView * column_view,
  guint           position,
  gpointer        user_data)
{
  ProjectAssistantWidget * self =
    (ProjectAssistantWidget *) user_data;

  GListStore * list_store =
    z_gtk_column_view_get_list_store (column_view);
  WrappedObjectWithChangeSignal * wrapped_obj =
    Z_WRAPPED_OBJECT_WITH_CHANGE_SIGNAL (g_list_model_get_item (
      G_LIST_MODEL (list_store), position));
  ProjectInfo * nfo = (ProjectInfo *) wrapped_obj->obj;

  g_debug ("activated %s", nfo->filename);

  gtk_dialog_response (GTK_DIALOG (self), GTK_RESPONSE_OK);
}

/**
 * Runs the project assistant.
 *


@@ 389,36 368,45 @@ project_assistant_widget_present (
}

static void
on_file_chooser_response (
  GtkNativeDialog *        native,
  int                      response,
run_create_project_dialog (
  ProjectAssistantWidget * self,
  GtkWindow *              transient_parent)
{
  CreateProjectDialogWidget * create_prj_dialog =
    create_project_dialog_widget_new ();
  g_signal_connect (
    G_OBJECT (create_prj_dialog), "response",
    G_CALLBACK (create_project_dialog_response_cb),
    GINT_TO_POINTER (self->zrythm_already_running));

  gtk_window_set_transient_for (
    GTK_WINDOW (create_prj_dialog),
    GTK_WINDOW (transient_parent));

  gtk_window_present (GTK_WINDOW (create_prj_dialog));
}

static bool
on_close_request (
  GtkWindow *              window,
  ProjectAssistantWidget * self)
{
  if (response == GTK_RESPONSE_ACCEPT)
    {
      GtkFileChooser * chooser = GTK_FILE_CHOOSER (native);
      GFile * file = gtk_file_chooser_get_file (chooser);
      char *  path = g_file_get_path (file);
      g_return_if_fail (path);
      g_object_unref (file);
  g_debug ("close request");

      ZRYTHM->open_filename = path;
      g_message ("Loading project: %s", ZRYTHM->open_filename);
  ZRYTHM->creating_project = true;
  ZRYTHM->open_filename = NULL;

      post_finish (self, self->zrythm_already_running, false);
    }
  /* window already destroyed, set transient to splash screen
   * instead */
  run_create_project_dialog (self, GTK_WINDOW (self->parent));

  g_object_unref (native);
  /* close normally */
  return false;
}

static void
on_response (
  GtkDialog *              dialog,
  gint                     response_id,
  ProjectAssistantWidget * self)
on_ok_clicked (GtkButton * btn, ProjectAssistantWidget * self)
{
  g_debug ("response %d", response_id);

  ZRYTHM->creating_project = true;

  const char * child_name =


@@ 428,106 416,124 @@ on_response (
  ProjectInfo * selected_template =
    get_selected_template (self);

  switch (response_id)
  if (string_is_equal (child_name, "create-new"))
    {
    case GTK_RESPONSE_DELETE_EVENT:
    case GTK_RESPONSE_CLOSE:
    case GTK_RESPONSE_CANCEL:
      ZRYTHM->open_filename = NULL;
      break;
    case GTK_RESPONSE_OK:
      if (string_is_equal (child_name, "create-new"))
        {
          g_return_if_fail (selected_template);
      g_return_if_fail (selected_template);

          /* if we are loading a blank template */
          if (selected_template->filename[0] == '-')
            {
              ZRYTHM->open_filename = NULL;
              g_message ("Creating blank project");
            }
          else
            {
              ZRYTHM->open_filename =
                selected_template->filename;
              g_message (
                "Creating project from template: %s",
                ZRYTHM->open_filename);
              ZRYTHM->opening_template = true;
            }
      /* if we are loading a blank template */
      if (selected_template->filename[0] == '-')
        {
          ZRYTHM->open_filename = NULL;
          g_message ("Creating blank project");
        }
      /* else if we are loading a project */
      else if (string_is_equal (child_name, "open-recent"))
      else
        {
          if (!selected_project)
            {
              ui_show_error_message (
                false, _ ("No project selected"));
              return;
            }
          ZRYTHM->open_filename = selected_project->filename;
          g_return_if_fail (ZRYTHM->open_filename);
          ZRYTHM->open_filename = selected_template->filename;
          g_message (
            "Loading project: %s", ZRYTHM->open_filename);
          ZRYTHM->creating_project = false;
            "Creating project from template: %s",
            ZRYTHM->open_filename);
          ZRYTHM->opening_template = true;
        }
      break;
    /* open from path */
    case 256:
      {
        GtkFileChooserNative * native =
          gtk_file_chooser_native_new (
            _ ("Select Project File"), GTK_WINDOW (self),
            GTK_FILE_CHOOSER_ACTION_OPEN, _ ("_Open"),
            _ ("_Cancel"));
        GtkFileFilter * filter = gtk_file_filter_new ();
        gtk_file_filter_add_mime_type (
          filter, "application/x-zrythm-project");
        gtk_file_filter_add_suffix (filter, "zpj");
        gtk_file_chooser_add_filter (
          GTK_FILE_CHOOSER (native), filter);
        g_signal_connect (
          native, "response",
          G_CALLBACK (on_file_chooser_response), self);
        gtk_native_dialog_show (GTK_NATIVE_DIALOG (native));
        return;
      }
      break;
    }
  /* else if we are loading a project */
  else if (string_is_equal (child_name, "open-recent"))
    {
      if (!selected_project)
        {
          ui_show_error_message (
            false, _ ("No project selected"));
          return;
        }
      ZRYTHM->open_filename = selected_project->filename;
      g_return_if_fail (ZRYTHM->open_filename);
      g_message ("Loading project: %s", ZRYTHM->open_filename);
      ZRYTHM->creating_project = false;
    }

  /* if not loading a project, show dialog to
   * select directory and name */
  /* if not loading a project, show dialog to select directory
   * and name */
  if (ZRYTHM->creating_project)
    {
      CreateProjectDialogWidget * create_prj_dialog =
        create_project_dialog_widget_new ();
      g_signal_connect (
        G_OBJECT (create_prj_dialog), "response",
        G_CALLBACK (create_project_dialog_response_cb),
        GINT_TO_POINTER (self->zrythm_already_running));
      run_create_project_dialog (self, GTK_WINDOW (self));
    }
  else
    {
      post_finish (self, self->zrythm_already_running, false);
    }
}

      /* if window already destroyed, set transient
       * to splash screen instead */
      if (
        response_id == GTK_RESPONSE_DELETE_EVENT
        || response_id == GTK_RESPONSE_CLOSE)
        {
          gtk_window_set_transient_for (
            GTK_WINDOW (create_prj_dialog),
            GTK_WINDOW (self->parent));
        }
      else
        {
          gtk_window_set_transient_for (
            GTK_WINDOW (create_prj_dialog), GTK_WINDOW (self));
        }
      gtk_window_present (GTK_WINDOW (create_prj_dialog));
static void
open_ready_cb (
  GtkFileDialog *          dialog,
  GAsyncResult *           res,
  ProjectAssistantWidget * self)
{
  GError * err = NULL;
  GFile *  file =
    gtk_file_dialog_open_finish (dialog, res, &err);
  if (!file)
    {
      g_message ("no project selected: %s", err->message);
      g_error_free (err);
      return;
    }

  char * path = g_file_get_path (file);
  g_return_if_fail (path);
  g_object_unref (file);

  ZRYTHM->open_filename = path;
  g_message ("Loading project: %s", ZRYTHM->open_filename);

  post_finish (self, self->zrythm_already_running, false);
}

static void
on_open_from_path_clicked (
  GtkButton *              btn,
  ProjectAssistantWidget * self)
{
  ZRYTHM->creating_project = true;

  GtkFileDialog * dialog = gtk_file_dialog_new ();
  gtk_file_dialog_set_title (dialog, _ ("Select Project"));
  gtk_file_dialog_set_modal (dialog, true);
  GtkFileFilter * filter = gtk_file_filter_new ();
  gtk_file_filter_add_mime_type (
    filter, "application/x-zrythm-project");
  gtk_file_filter_add_suffix (filter, "zpj");
  GListStore * list_store =
    g_list_store_new (GTK_TYPE_FILE_FILTER);
  g_list_store_append (list_store, filter);
  gtk_file_dialog_set_filters (
    dialog, G_LIST_MODEL (list_store));
  g_object_unref (list_store);
  gtk_file_dialog_open (
    dialog, GTK_WINDOW (self), NULL,
    (GAsyncReadyCallback) open_ready_cb, self);
}

static void
on_project_activate (
  GtkColumnView * column_view,
  guint           position,
  gpointer        user_data)
{
  ProjectAssistantWidget * self =
    (ProjectAssistantWidget *) user_data;

  GListStore * list_store =
    z_gtk_column_view_get_list_store (column_view);
  WrappedObjectWithChangeSignal * wrapped_obj =
    Z_WRAPPED_OBJECT_WITH_CHANGE_SIGNAL (g_list_model_get_item (
      G_LIST_MODEL (list_store), position));
  ProjectInfo * nfo = (ProjectInfo *) wrapped_obj->obj;

  g_debug ("activated %s", nfo->filename);

  on_ok_clicked (self->ok_btn, self);
}

static char *
get_prj_name (void * data)
{


@@ 646,7 652,7 @@ project_assistant_widget_class_init (
  BIND_CHILD (templates_column_view);
  BIND_CHILD (ok_btn);
  BIND_CHILD (open_from_path_btn);
  BIND_CHILD (cancel_btn);
  /*BIND_CHILD (cancel_btn);*/

#undef BIND_CHILD



@@ 658,6 664,8 @@ project_assistant_widget_class_init (
static void
project_assistant_widget_init (ProjectAssistantWidget * self)
{
  g_type_ensure (ADW_TYPE_TOOLBAR_VIEW);

  gtk_widget_init_template (GTK_WIDGET (self));

  gtk_widget_set_size_request (GTK_WIDGET (self), 380, 180);


@@ 756,9 764,22 @@ project_assistant_widget_init (ProjectAssistantWidget * self)
    G_CALLBACK (on_visible_child_name_changed), self);

  g_signal_connect (
    self, "response", G_CALLBACK (on_response), self);
    self->ok_btn, "clicked", G_CALLBACK (on_ok_clicked), self);
  g_signal_connect (
    self->open_from_path_btn, "clicked",
    G_CALLBACK (on_open_from_path_clicked), self);
  g_signal_connect (
    self, "close-request", G_CALLBACK (on_close_request),
    self);

  gtk_window_set_focus (
    GTK_WINDOW (self),
    GTK_WIDGET (self->recent_projects_column_view));

  /* close on escape */
  z_gtk_window_make_escapable (GTK_WINDOW (self));

  gtk_accessible_update_property (
    GTK_ACCESSIBLE (self->recent_projects_column_view),
    GTK_ACCESSIBLE_PROPERTY_LABEL, "Recent projects", -1);
}

M src/utils/gtk.c => src/utils/gtk.c +26 -0
@@ 2296,3 2296,29 @@ z_gtk_descendant_has_focus (GtkWidget * parent)
  return get_first_focused_or_focusable_child (parent, true)
         != NULL;
}

void
z_gtk_window_make_escapable (GtkWindow * self)
{
  /* close on escape */
#if 0
  /* for reference */
  GtkShortcutTrigger * trigger =
    gtk_shortcut_trigger_parse_string ("Escape|<Control>w");
  GtkShortcutAction * action =
    gtk_named_action_new ("window.close");
  GtkShortcut * esc_shortcut =
    gtk_shortcut_new (trigger, action);
  GtkShortcutController * sc =
    GTK_SHORTCUT_CONTROLLER (gtk_shortcut_controller_new ());
  gtk_shortcut_controller_add_shortcut (sc, esc_shortcut);
  gtk_widget_add_controller (
    GTK_WIDGET (self), GTK_EVENT_CONTROLLER (sc));
#endif
  GtkWidgetClass * wklass =
    GTK_WIDGET_CLASS (G_OBJECT_GET_CLASS (self));
  gtk_widget_class_add_binding_action (
    wklass, GDK_KEY_Escape, 0, "window.close", NULL);
  gtk_widget_class_add_binding_action (
    wklass, GDK_KEY_w, GDK_CONTROL_MASK, "window.close", NULL);
}

M subprojects/libadwaita.wrap => subprojects/libadwaita.wrap +2 -2
@@ 1,6 1,6 @@
[wrap-git]
directory=libadwaita
url=https://gitlab.gnome.org/GNOME/libadwaita
# 1.3.2
revision=dfaf404704a6e4a15d9881fc39ec8a607cd47701
# 1.4.beta
revision=dbe9da35ed611b907efb4a671f6ab57342946581
depth=1