diff --git a/meson.build b/meson.build index ae6bb0b225..d9b5443f47 100644 --- a/meson.build +++ b/meson.build @@ -110,7 +110,7 @@ endif subdir('data') subdir('src') if get_option('plugins') - subdir('plugins') + # subdir('plugins') endif subdir('po') diff --git a/plugins/fuzzy-search/fuzzy-search.vala b/plugins/fuzzy-search/fuzzy-search.vala index 169fd5a801..e4e5699a78 100644 --- a/plugins/fuzzy-search/fuzzy-search.vala +++ b/plugins/fuzzy-search/fuzzy-search.vala @@ -126,7 +126,7 @@ public class Scratch.Plugins.FuzzySearch: Peas.ExtensionBase, Scratch.Services.A var popover = new Scratch.FuzzySearchPopover (indexer, window); popover.open_file.connect ((filepath) => { - var file = new Scratch.FolderManager.File (filepath); + var file = new Code.File (filepath); var doc = new Scratch.Services.Document (window.actions, file.file); window.open_document.begin (doc); diff --git a/src/Application.vala b/src/Application.vala index ee7b260081..593da2eab0 100644 --- a/src/Application.vala +++ b/src/Application.vala @@ -178,16 +178,18 @@ namespace Scratch { var window = get_last_window (); foreach (var file in files) { bool is_folder; + var path = file.get_uri (); if (Scratch.Services.FileHandler.can_open_file (file, out is_folder)) { if (is_folder) { - window.open_folder (file); + window.open_folder_as_project (file); // Opens in sidebar not as documents } else { debug ("Files length: %d\n", files.length); - var doc = new Scratch.Services.Document (window.actions, file); + // var doc = new Scratch.Services.Document (window.actions, file); if (location_jump_manager.has_selection_range != null && files.length == 1) { - window.open_document_at_selected_range.begin (doc, true, location_jump_manager.range); + // We know file exists so we do not need to check again + window.document_view.open_document.begin (path, true, 0, location_jump_manager.range); } else { - window.open_document.begin (doc); + window.document_view.open_document.begin (path); } } } @@ -231,6 +233,24 @@ namespace Scratch { return true; } + public bool can_rename_file (Code.File file) { + // folder_manager_view.rename_request.connect ((file) => { + var allow = true; + foreach (var window in get_windows ()) { + var win = (MainWindow)window; + foreach (var doc in win.document_view.docs) { + if (doc.file.equal (file.file)) { + // Only allow sidebar to rename docs that are in sync with their file in + // all windows + allow = allow && !doc.locked && doc.saved; + } + } + } + + return allow; + // }); + } + public static int main (string[] args) { // By default, profile whole app when profiling is enabled in meson_options.txt // These conditional statements can be moved to profile sections of code diff --git a/src/Dialogs/BranchActions/BranchActionDialog.vala b/src/Dialogs/BranchActions/BranchActionDialog.vala index 4c1a55e80d..d29d20fda1 100644 --- a/src/Dialogs/BranchActions/BranchActionDialog.vala +++ b/src/Dialogs/BranchActions/BranchActionDialog.vala @@ -43,11 +43,11 @@ public class Scratch.Dialogs.BranchActionDialog : Granite.MessageDialog { } public bool can_apply { get; set; default = false; } - public FolderManager.ProjectFolderItem project { get; construct; } + public Code.ProjectFolderItem project { get; construct; } private Gtk.Stack stack; - public BranchActionDialog (FolderManager.ProjectFolderItem project) { + public BranchActionDialog (Code.ProjectFolderItem project) { Object ( project: project, modal: true diff --git a/src/FolderManager/File.vala b/src/FolderManager/File.vala index 8bef07dee7..58ddffdf77 100644 --- a/src/FolderManager/File.vala +++ b/src/FolderManager/File.vala @@ -1,216 +1,209 @@ /*- - * Copyright (c) 2017-2025 elementary LLC. (https://elementary.io), - * 2013 Julien Spautz - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU Lesser General Public License version 3 - * as published by the Free Software Foundation, either version 3 of the - * License, or (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranties of - * MERCHANTABILITY, SATISFACTORY QUALITY, or FITNESS FOR A PARTICULAR - * PURPOSE. See the GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - * - * Authored by: Julien Spautz , Andrei-Costin Zisu - */ - -namespace Scratch.FolderManager { - - /** - * Class for easily dealing with files. - */ - public class File : GLib.Object { - public GLib.File file { get; private set; } - private GLib.FileInfo? info = null; // Non-null after loading - - public File (string path) { - Object (path: path); - } +* Copyright (c) 2017-2025 elementary LLC. (https://elementary.io), +* 2013 Julien Spautz +* +* This program is free software: you can redistribute it and/or modify +* it under the terms of the GNU Lesser General Public License version 3 +* as published by the Free Software Foundation, either version 3 of the +* License, or (at your option) any later version. +* +* This program is distributed in the hope that it will be useful, +* but WITHOUT ANY WARRANTY; without even the implied warranties of +* MERCHANTABILITY, SATISFACTORY QUALITY, or FITNESS FOR A PARTICULAR +* PURPOSE. See the GNU General Public License for more details. +* +* You should have received a copy of the GNU General Public License +* along with this program. If not, see . +* +* Authored by: Julien Spautz , Andrei-Costin Zisu +*/ + +public class Code.File : GLib.Object { + public GLib.File file { get; set construct; } + private GLib.FileInfo? info = null; // Non-null after loading + + public File (string path) { + Object (path: path); + } - // returns the path the file - public string path { - owned get { - return file.get_path (); - } - set construct { - load_file_for_path (value); - } + construct { + file = GLib.File.new_for_path (path); + info = new FileInfo (); + try { + var query_string = GLib.FileAttribute.STANDARD_CONTENT_TYPE + "," + + GLib.FileAttribute.STANDARD_IS_BACKUP + "," + + GLib.FileAttribute.STANDARD_IS_HIDDEN + "," + + GLib.FileAttribute.STANDARD_DISPLAY_NAME + "," + + GLib.FileAttribute.STANDARD_TYPE; + + info = file.query_info (query_string, FileQueryInfoFlags.NONE); + } catch (GLib.Error error) { + info = null; + warning (error.message); } - - // returns the basename of the file - private string _name; - public string name { - get { - if (_name != null) { - return _name; - } - - if (info == null) { - _name = file.get_basename (); - } else { - _name = info.get_display_name (); - } - + } + // returns the path the file + public string path { get; construct; } + // owned get { + // return file.get_path (); + // } + // set construct { + // load_file_for_path (value); + // } + // } + + // returns the basename of the file + private string _name; + public string name { + get { + if (_name != null) { return _name; } - } - - // returns the icon of the file's content type - private GLib.Icon? _icon = null; - public GLib.Icon icon { - get { - if (_icon != null) { - return _icon; - } - if (info != null) { - _icon = GLib.ContentType.get_icon (info.get_content_type ()); - } else { - _icon = new ThemedIcon ("missing-image"); - } - - return _icon; + if (info == null) { + _name = file.get_basename (); + } else { + _name = info.get_display_name (); } - } - // checks if file exists - public bool exists { - get { return file.query_exists (); } + return _name; } + } - // Checks if we're dealing with a non-backup directory - // If parent is hidden then inherit validity from parent - private bool? _is_valid_directory = null; - public bool is_valid_directory { - get { - if (_is_valid_directory == null) { - _is_valid_directory = info != null && - !info.get_is_backup () && - info.get_file_type () == FileType.DIRECTORY; - } - - return _is_valid_directory; + // returns the icon of the file's content type + private GLib.Icon? _icon = null; + public GLib.Icon icon { + get { + if (_icon != null) { + return _icon; } - } - // checks if we're dealing with a textfile - private bool? _is_valid_textfile = null; - public bool is_valid_textfile { - get { - if (_is_valid_textfile == null) { - _is_valid_textfile = !path.has_suffix ("~") && Utils.check_if_valid_text_file (path, info); - } - - return _is_valid_textfile; + if (info != null) { + _icon = GLib.ContentType.get_icon (info.get_content_type ()); + } else { + _icon = new ThemedIcon ("missing-image"); } - } - // Files can be executed and folders can be cd'd into - public bool is_executable { - get { - try { - return get_boolean_file_attribute (GLib.FileAttribute.ACCESS_CAN_EXECUTE); - } catch (GLib.Error error) { - return false; - } - } + return _icon; } + } - // returns a list of all children of a directory - private bool children_valid = false; - private Gee.ArrayList _children = new Gee.ArrayList (); - public Gee.Collection children { - owned get { - if (children_valid) { - return _children; - } - - _children.clear (); - - try { - var enumerator = file.enumerate_children ( - GLib.FileAttribute.STANDARD_NAME, - FileQueryInfoFlags.NONE - ); - - var file_info = new FileInfo (); - while ((file_info = enumerator.next_file ()) != null) { - var child = file.get_child (file_info.get_name ()); - var child_file = new File (child.get_path ()); - if (child_file.is_valid_directory || child_file.is_valid_textfile) { - _children.add (child_file); - } - } - - children_valid = true; - } catch (GLib.Error error) { - warning (error.message); - } + // checks if file exists + public bool exists { + get { return file.query_exists (); } + } - return _children; + // Checks if we're dealing with a non-backup directory + // If parent is hidden then inherit validity from parent + private bool? _is_valid_directory = null; + public bool is_valid_directory { + get { + if (_is_valid_directory == null) { + _is_valid_directory = info != null && + !info.get_is_backup () && + info.get_file_type () == FileType.DIRECTORY; } + + return _is_valid_directory; } + } - private bool get_boolean_file_attribute (string attribute) throws GLib.Error { - var info = file.query_info (attribute, GLib.FileQueryInfoFlags.NONE); + // checks if we're dealing with a textfile + private bool? _is_valid_textfile = null; + public bool is_valid_textfile { + get { + if (_is_valid_textfile == null) { + _is_valid_textfile = !path.has_suffix ("~") && Scratch.Utils.check_if_valid_text_file (path, info); + } - return info.get_attribute_boolean (attribute); + return _is_valid_textfile; } + } - private void load_file_for_path (string path) { - file = GLib.File.new_for_path (path); - - info = new FileInfo (); + // Files can be executed and folders can be cd'd into + public bool is_executable { + get { try { - var query_string = GLib.FileAttribute.STANDARD_CONTENT_TYPE + "," + - GLib.FileAttribute.STANDARD_IS_BACKUP + "," + - GLib.FileAttribute.STANDARD_IS_HIDDEN + "," + - GLib.FileAttribute.STANDARD_DISPLAY_NAME + "," + - GLib.FileAttribute.STANDARD_TYPE; - - info = file.query_info (query_string, FileQueryInfoFlags.NONE); + return get_boolean_file_attribute (GLib.FileAttribute.ACCESS_CAN_EXECUTE); } catch (GLib.Error error) { - info = null; - warning (error.message); + return false; } } + } - public void rename (string name) { - try { - if (exists) { - file.set_display_name (name); - } - } catch (GLib.Error error) { - warning (error.message); + // returns a list of all children of a directory + private bool children_valid = false; + private Gee.ArrayList _children = new Gee.ArrayList (); + public Gee.Collection children { + owned get { + if (children_valid) { + return _children; } - } - public void trash () { + _children.clear (); + try { - file.trash (); + var enumerator = file.enumerate_children ( + GLib.FileAttribute.STANDARD_NAME, + FileQueryInfoFlags.NONE + ); + + var file_info = new FileInfo (); + while ((file_info = enumerator.next_file ()) != null) { + var child = file.get_child (file_info.get_name ()); + var child_file = new File (child.get_path ()); + if (child_file.is_valid_directory || child_file.is_valid_textfile) { + _children.add (child_file); + } + } + + children_valid = true; } catch (GLib.Error error) { warning (error.message); } + + return _children; } + } - public static int compare (File a, File b) { - if (a.is_valid_directory && b.is_valid_textfile) { - return -1; - } - if (a.is_valid_textfile && b.is_valid_directory) { - return 1; + private bool get_boolean_file_attribute (string attribute) throws GLib.Error { + var info = file.query_info (attribute, GLib.FileQueryInfoFlags.NONE); + + return info.get_attribute_boolean (attribute); + } + + public void rename (string name) { + try { + if (exists) { + file.set_display_name (name); } + } catch (GLib.Error error) { + warning (error.message); + } + } - return strcmp (a.path.collate_key_for_filename (), - b.path.collate_key_for_filename ()); + public void trash () { + try { + file.trash (); + } catch (GLib.Error error) { + warning (error.message); } + } - public void invalidate_cache () { - children_valid = false; + public static int compare (File a, File b) { + if (a.is_valid_directory && b.is_valid_textfile) { + return -1; + } + if (a.is_valid_textfile && b.is_valid_directory) { + return 1; } + + return strcmp (a.path.collate_key_for_filename (), + b.path.collate_key_for_filename ()); + } + + public void invalidate_cache () { + children_valid = false; } } + diff --git a/src/FolderManager/FileItem.vala b/src/FolderManager/FileItem.vala index d75ff5841b..dffdf57278 100644 --- a/src/FolderManager/FileItem.vala +++ b/src/FolderManager/FileItem.vala @@ -18,101 +18,104 @@ * Authored by: Julien Spautz , Andrei-Costin Zisu */ -namespace Scratch.FolderManager { - /** - * Normal item in the source list, represents a textfile. - */ - public class FileItem : Item { - public FileItem (File file, FileView view) { - Object (file: file, view: view); - } - - // The ListView has an activate signal - // public override void activated () { - // view.activate (file.path); - // } - - public override GLib.Menu? get_context_menu () { - GLib.FileInfo info = null; - - try { - info = file.file.query_info (GLib.FileAttribute.STANDARD_CONTENT_TYPE, 0); - } catch (Error e) { - warning (e.message); - } - - var file_type = info.get_attribute_string (GLib.FileAttribute.STANDARD_CONTENT_TYPE); - var contractor_items = Utils.create_contract_items_for_file (file.file); - var external_actions_section = new GLib.Menu (); - external_actions_section.append_item (create_submenu_for_open_in (file_type)); - if (contractor_items.get_n_items () > 0) { - external_actions_section.append_submenu ( - _("Other Actions"), - contractor_items - ); - } - - var rename_item = new GLib.MenuItem ( - _("Rename"), - GLib.Action.print_detailed_name ( - FileView.ACTION_PREFIX + FileView.ACTION_RENAME_FILE, - new Variant.string (file.path) - ) - ); - var rename_action = Utils.action_from_group (FileView.ACTION_RENAME_FILE, view.actions); - rename_action.set_enabled (view.rename_request (file)); +public class Code.FileItem : FolderManagerItem, Code.FolderManagerItemInterface { + public FileItem (File file, FolderTree view) { + Object (file: file, view: view); + } - var delete_item = new GLib.MenuItem ( - _("Move to Trash"), - GLib.Action.print_detailed_name ( - FileView.ACTION_PREFIX + FileView.ACTION_DELETE, - new Variant.string (file.path) - ) - ); + // The ListView has an activate signal + // public override void activated () { + // view.activate (file.path); + // } - var direct_actions_section = new GLib.Menu (); - direct_actions_section.append_item (rename_item); - direct_actions_section.append_item (delete_item); + // public override GLib.Menu? get_context_menu () { + // GLib.FileInfo info = null; - var menu_model = new GLib.Menu (); - menu_model.append_section (null, external_actions_section); - menu_model.append_section (null, direct_actions_section); + // try { + // info = file.file.query_info (GLib.FileAttribute.STANDARD_CONTENT_TYPE, 0); + // } catch (Error e) { + // warning (e.message); + // } - return menu_model; - } + // var file_type = info.get_attribute_string (GLib.FileAttribute.STANDARD_CONTENT_TYPE); + // var contractor_items = create_contract_items_for_file (file.file); // Interface method + // var external_actions_section = new GLib.Menu (); + // external_actions_section.append_item (create_submenu_for_open_item_in (file_type)); + // if (contractor_items.get_n_items () > 0) { + // external_actions_section.append_submenu ( + // _("Other Actions"), + // contractor_items + // ); + // } - private GLib.MenuItem create_submenu_for_open_in (string? file_type) { + // var rename_item = new GLib.MenuItem ( + // _("Rename"), + // GLib.Action.print_detailed_name ( + // ITEM_ACTION_PREFIX + ACTION_RENAME_FILE, + // new Variant.string (file.path) + // ) + // ); + // var rename_action = Scratch.Utils.action_from_group (ACTION_RENAME_FILE, view.actions); + // var app = (Scratch.Application) (Application.get_default ()); + // rename_action.set_enabled (app.can_rename_file (file)); + // rename_action.set_enabled (view.rename_request (file)); + + // var delete_item = new GLib.MenuItem ( + // _("Move to Trash"), + // GLib.Action.print_detailed_name ( + // ITEM_ACTION_PREFIX + ACTION_DELETE, + // new Variant.string (file.path) + // ) + // ); + + // var direct_actions_section = new GLib.Menu (); + // direct_actions_section.append_item (rename_item); + // direct_actions_section.append_item (delete_item); + + // var menu_model = new GLib.Menu (); + // menu_model.append_section (null, external_actions_section); + // menu_model.append_section (null, direct_actions_section); + + // return menu_model; + // } + + public override GLib.MenuItem create_submenu_for_open_item_in (GLib.File gfile, string? file_type) { + var menu_item = base.create_submenu_for_open_item_in (gfile, file_type); + var sub_menu = (GLib.Menu) (menu_item.get_link ("submenu")); + + if (file.is_valid_textfile) { + var top_section = new GLib.Menu (); var new_window_menu_item = new GLib.MenuItem ( _("New Window"), GLib.Action.print_detailed_name ( - MainWindow.ACTION_PREFIX + MainWindow.ACTION_OPEN_IN_NEW_WINDOW, - file.path + Scratch.MainWindow.ACTION_PREFIX + Scratch.MainWindow.ACTION_OPEN_IN_NEW_WINDOW, + path ) ); + top_section.append_item (new_window_menu_item); + sub_menu.prepend_section (null, top_section); + } - var top_section = new GLib.Menu (); - if (file.is_valid_textfile) { - top_section.append_item (new_window_menu_item); - } + //TODO Add section for regular file only actions - var other_menu_item = new GLib.MenuItem ( - _("Other Application…"), - GLib.Action.print_detailed_name ( - FileView.ACTION_PREFIX + FileView.ACTION_SHOW_APP_CHOOSER, - file.path - ) - ); + // var other_menu_item = new GLib.MenuItem ( + // _("Other Application…"), + // GLib.Action.print_detailed_name ( + // ITEM_ACTION_PREFIX + ACTION_SHOW_APP_CHOOSER, + // file.path + // ) + // ); - var extra_section = new GLib.Menu (); - extra_section.append_item (other_menu_item); + // var extra_section = new GLib.Menu (); + // extra_section.append_item (other_menu_item); - var open_in_menu = new GLib.Menu (); - open_in_menu.append_section (null, top_section); - open_in_menu.append_section (null, Utils.create_executable_app_items_for_file (file.file, file_type)); - open_in_menu.append_section (null, extra_section); + // var open_in_menu = new GLib.Menu (); + // open_in_menu.append_section (null, top_section); + // open_in_menu.append_section (null, create_executable_app_items_for_file (file.file, file_type)); + // open_in_menu.append_section (null, extra_section); - var open_in_menu_item = new GLib.MenuItem.submenu (_("Open In"), open_in_menu); - return open_in_menu_item; - } + // var open_in_menu_item = new GLib.MenuItem.submenu (_("Open In"), open_in_menu); + return menu_item; } } + diff --git a/src/FolderManager/FileView.vala b/src/FolderManager/FileView.vala deleted file mode 100644 index fb9a814d84..0000000000 --- a/src/FolderManager/FileView.vala +++ /dev/null @@ -1,768 +0,0 @@ -/*- - * Copyright (c) 2017-2026 elementary LLC. (https://elementary.io), - * 2013 Julien Spautz - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU Lesser General Public License version 3 - * as published by the Free Software Foundation, either version 3 of the - * License, or (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranties of - * MERCHANTABILITY, SATISFACTORY QUALITY, or FITNESS FOR A PARTICULAR - * PURPOSE. See the GNU General Public License for more details. - - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - * - * Authored by: Julien Spautz , Andrei-Costin Zisu - */ - -/** - * SourceList that displays folders and their contents. - */ -public class Scratch.FolderManager.FileView : Code.TreeList, Code.PaneSwitcher { - public const string ACTION_GROUP = "file-view"; - public const string ACTION_PREFIX = ACTION_GROUP + "."; - public const string ACTION_LAUNCH_APP_WITH_FILE_PATH = "launch-app-with-file-path"; - public const string ACTION_SHOW_APP_CHOOSER = "show-app-chooser"; - public const string ACTION_EXECUTE_CONTRACT_WITH_FILE_PATH = "execute-contract-with-file-path"; - public const string ACTION_RENAME_FILE = "rename-file"; - public const string ACTION_RENAME_FOLDER = "rename-folder"; - public const string ACTION_DELETE = "delete"; - public const string ACTION_NEW_FILE = "new-file"; - public const string ACTION_NEW_FROM_TEMPLATE = "new-from-template"; - public const string ACTION_NEW_FOLDER = "new-folder"; - public const string ACTION_CLOSE_FOLDER = "close-folder"; - public const string ACTION_CLOSE_OTHER_FOLDERS = "close-other-folders"; - - private const ActionEntry[] ACTION_ENTRIES = { - { ACTION_LAUNCH_APP_WITH_FILE_PATH, action_launch_app_with_file_path, "as" }, - { ACTION_SHOW_APP_CHOOSER, action_show_app_chooser, "s" }, - { ACTION_EXECUTE_CONTRACT_WITH_FILE_PATH, action_execute_contract_with_file_path, "as" }, - { ACTION_RENAME_FILE, action_rename_file, "s" }, - { ACTION_RENAME_FOLDER, action_rename_folder, "s" }, - { ACTION_DELETE, action_delete, "s" }, - { ACTION_NEW_FILE, add_new_file, "s" }, - { ACTION_NEW_FROM_TEMPLATE, add_new_from_template, "(ss)" }, - { ACTION_NEW_FOLDER, add_new_folder, "s"}, - { ACTION_CLOSE_FOLDER, action_close_folder, "s"}, - { ACTION_CLOSE_OTHER_FOLDERS, action_close_other_folders, "s"} - }; - - private GLib.Settings settings; - private Scratch.Services.GitManager git_manager; - private Scratch.Services.PluginsManager plugins; - - public signal void file_activate (File file); - public signal bool rename_request (File file); - - public SimpleActionGroup actions { get; private set; } - public string icon_name { get; set; } - public string title { get; set; } - - public FileView (Scratch.Services.PluginsManager plugins_manager) { - plugins = plugins_manager; - } - - construct { - icon_name = "folder-symbolic"; - title = _("Folders"); - - settings = new GLib.Settings ("io.elementary.code.folder-manager"); - - git_manager = Scratch.Services.GitManager.get_instance (); - - actions = new SimpleActionGroup (); - actions.add_action_entries (ACTION_ENTRIES, this); - insert_action_group (ACTION_GROUP, actions); - - Scratch.saved_state.changed["order-folders"].connect (() => { - order_folders (); - }); - - // Convert ListView signal into file_activate - item_activated.connect ((item) => { - if (item is FileItem) { - file_activate (((FileItem) item).file); - } - }); - } - - private void action_close_folder (SimpleAction action, GLib.Variant? parameter) { - var path = parameter.get_string (); - if (path == null || path == "") { - return; - } - - var project_item = find_path (null, path) as ProjectFolderItem; - if (project_item == null) { - return; - } - - project_item.closed (); - } - - private void action_close_other_folders (SimpleAction action, GLib.Variant? parameter) { - var path = parameter.get_string (); - if (path == null || path == "") { - return; - } - - var folder_root = find_path (null, path) as ProjectFolderItem; - if (folder_root == null) { - return; - } - - List to_remove = null; - iterate_children (null, (child) => { - var project_folder_item = (ProjectFolderItem) child; - if (project_folder_item != folder_root) { - activate_action ( - MainWindow.ACTION_PREFIX + MainWindow.ACTION_CLOSE_PROJECT_DOCS, - "s", - project_folder_item.path - ); - to_remove.prepend (project_folder_item); - git_manager.remove_project (project_folder_item); - } - - return Code.TreeList.ITERATE_CONTINUE; - }); - - remove_root_children (to_remove); - //Make remaining project the active one - set_project_active (path); - } - - private void action_set_active_project (SimpleAction action, GLib.Variant? parameter) { - var path = parameter.get_string (); - if (path == null || path == "") { - return; - } - - set_active_project (path); - } - - private ProjectFolderItem? set_active_project (string path) { - var folder_root = find_path (null, path) as ProjectFolderItem; - if (folder_root == null) { - return null; - } - - git_manager.active_project_path = path; - - write_settings (); - - return folder_root; - } - - private void set_project_active (string path) { - activate_action ( - MainWindow.ACTION_PREFIX + MainWindow.ACTION_SET_ACTIVE_PROJECT, - "s", - path - ); - } - - public async void restore_saved_state () { - foreach (unowned string path in settings.get_strv ("opened-folders")) { - yield add_folder (new File (path), false, true); - } - } - - public void open_folder (File folder) { - if (is_open (folder)) { - var existing = find_path (null, folder.path); - if (existing is Code.Widgets.SourceList.ExpandableItem) { - ((Code.Widgets.SourceList.ExpandableItem)existing).expanded = true; - } - - return; - } - - add_folder.begin (folder, true); - } - - public void collapse_all () { - iterate_children (null, (child) => { - child.collapse_all (true, true); - return Code.TreeList.ITERATE_CONTINUE; - }); - } - - public void order_folders () { - if (!Scratch.saved_state.get_boolean ("order-folders")) { - return; - } - - sort_root_children ((a, b) => { - return strcmp ( - ((ProjectFolderItem)a).name.down (), - ((ProjectFolderItem)b).name.down () - ); - }); - } - - public void select_path (string path) { - find_path (null, path); - } - - public void unselect_file (GLib.File file) { - //TODO Complete this - } - - public void unselect_all () { - selection_model.unselect_all (); - } - - public void collapse_other_projects () { - unowned string path; - path = git_manager.active_project_path; - - iterate_children (null, (child) => { - var project_folder = ((ProjectFolderItem) child); - if (project_folder.path != path) { - project_folder.is_expanded = false; - activate_action ( - MainWindow.ACTION_PREFIX + MainWindow.ACTION_HIDE_PROJECT_DOCS, - "s", - project_folder.path - ); - } else if (project_folder.path == path) { - project_folder.is_expanded = true; - activate_action ( - MainWindow.ACTION_PREFIX + MainWindow.ACTION_RESTORE_PROJECT_DOCS, - "s", - project_folder.path - ); - } - - return Code.TreeList.ITERATE_CONTINUE; - }); - } - - public void branch_actions (string path) { - // Must only carry out branch actions on active project so switch if necessary. - //TODO Warn before switching active project? - var active_project = set_active_project (path); - if (active_project == null || !active_project.is_git_repo) { - Gdk.Display.get_default ().beep (); - return; - } - - var dialog = new Dialogs.BranchActionDialog (active_project); - dialog.response.connect ((res) => { - if (res == Gtk.ResponseType.APPLY) { - perform_branch_action (dialog); - } - - dialog.destroy (); - }); - - dialog.present (); - } - - private void perform_branch_action ( - Scratch.Dialogs.BranchActionDialog dialog - ) { - switch (dialog.action) { - case CHECKOUT: - dialog.project.checkout_branch_ref (dialog.branch_ref); - break; - case COMMIT: - break; - case PUSH: - break; - case PULL: - break; - case MERGE: - break; - case DELETE: - break; - case CREATE: - dialog.project.new_branch (dialog.new_branch_name); - break; - default: - assert_not_reached (); - } - } - - private Code.TreeListItem? find_path ( - Code.TreeListItem? list, // Starting point for search - string path, // File path to search fod - bool expand = false, // Whether to expsnd to show found item - GLib.File? target_file = null // Alternatively find this file - ) { - - var model = list != null ? list.child_model : root_model; - var target = target_file ?? GLib.File.new_for_path (path); - Code.TreeListItem? matched_item = null; - - iterate_children (null, (item) => { - if ((item is Item) && item.path == path) { - matched_item = item; - return Code.TreeList.ITERATE_STOP; - } - - if (item is FolderItem) { - var folder = item as FolderItem; - var folder_root = folder.file.file; - if (folder_root.get_relative_path (target) == null) { - return Code.TreeList.ITERATE_CONTINUE; - } - - if (!folder.is_expanded) { - if (expand) { - folder.load_children (); //Synchronous - folder.is_expanded = true; - } else { - return Code.TreeList.ITERATE_CONTINUE; - } - } - - var recurse_item = find_path (folder, path, expand, target); - if (recurse_item != null) { - matched_item = recurse_item; - return Code.TreeList.ITERATE_STOP; - } - } - - return Code.TreeList.ITERATE_CONTINUE; - }); - - return matched_item; - } - - public bool project_is_open (string project_path) { - return get_project_for_file (GLib.File.new_for_path (project_path)) != null; - } - - public ProjectFolderItem? get_project_for_file (GLib.File file) { - ProjectFolderItem? matched_project = null; - iterate_children (null, (item) => { - if (item is ProjectFolderItem) { - var folder = (ProjectFolderItem) item; - if (folder.file.file.equal (file) || folder.contains_file (file)) { - matched_project = folder; - return Code.TreeList.ITERATE_STOP; - } - } - - return Code.TreeList.ITERATE_CONTINUE; - }); - - return matched_project; - } - - public Code.TreeListItem? expand_to_path (string path) { - return find_path (null, path, true); - } - - /* Do global search on project containing the file path supplied in parameter */ - public void search_global (string path, string? term = null) { - var item_for_path = (Item?)(expand_to_path (path)); - if (item_for_path != null) { - var search_root = item_for_path.get_root_folder (); - if (search_root is ProjectFolderItem) { - GLib.File start_folder = (item_for_path is FolderItem) - ? item_for_path.file.file - : search_root.file.file; - - bool is_explicit = !(item_for_path is ProjectFolderItem); - search_root.global_search.begin (start_folder, term, is_explicit); - } - } - } - - public void clear_badges () { - iterate_children (null, (child) => { - if (child is ProjectFolderItem) { - ((FolderItem)child).remove_all_badges (); - } - - return Code.TreeList.ITERATE_CONTINUE; - }); - } - - public void folder_item_update_hook (GLib.File source, GLib.File? dest, GLib.FileMonitorEvent event) { - plugins.hook_folder_item_change (source, dest, event); - } - - // This only works when the list is stable (nothing being added, expanded etc) - private void rename_file (string path) { - // this.select_path (path); - // if (this.start_editing_item (selected)) { - // ulong once = 0; - // once = selected.edited.connect ((new_name) => { - // selected.disconnect (once); - // var new_path = Path.get_dirname (path) + Path.DIR_SEPARATOR_S + new_name; - // activate_action ( - // MainWindow.ACTION_PREFIX + MainWindow.ACTION_CLOSE_TAB, - // "s", - // path - // ); - - // // RecentManager requires valid URI - // var new_uri = "file://" + new_path; // Code only edits local files - // Gtk.RecentManager.get_default ().add_item (new_uri); - - // activate (new_path); - // }); - // } - - // // Handle cancelled rename (which does not produce signal) - // Timeout.add (200, () => { - // if (this.editing) { - // return Source.CONTINUE; - // } else { - // // Avoid selected but unopened item if rename cancelled (they would not open if clicked on) - // this.unselect_all (); - // return Source.REMOVE; - // } - // }); - } - - private void rename_folder (string path) { - // var folder_to_rename = find_path (null, path) as FolderItem; - // if (folder_to_rename == null) { - // critical ("Could not find folder from given path to rename: %s", path); - // return; - // } - - // folder_to_rename.selectable = true; - // if (start_editing_item (folder_to_rename)) { - // // Need to poll view as no signal emited when editing cancelled and need to set - // // selectable to false anyway. - // Timeout.add (200, () => { - // if (editing) { - // return Source.CONTINUE; - // } else { - // unselect_all (); - // // Must do this *after* unselecting all else sourcelist breaks - // folder_to_rename.selectable = false; - // } - - // return Source.REMOVE; - // }); - // } else { - // critical ("Could not rename %s", path); - // folder_to_rename.selectable = false; - // } - } - - private void rename_items_with_same_name (Item item) { - // string item_name = item.file.name; - // iterate_children (null, (child) => { - // string new_other_item_name, new_item_name; - // var other_item = (ProjectFolderItem) child; - - // if (Utils.find_unique_path ( - // item.file.file, - // other_item.file.file, - // out new_item_name, - // out new_other_item_name - // ) - // ) { - // if (item_name.length < new_item_name.length) { - // item_name = new_item_name; - // } - - // if (other_item.name.length < new_other_item_name.length) { - // other_item.name = new_other_item_name; - // } - // } - - // return Code.TreeList.ITERATE_CONTINUE; - // }); - - // item.name = item_name; - } - - private void add_new_folder (SimpleAction action, Variant? param) { - // Using "path" of parent folder from params, call `on_add_new (true)` on `FolderItem` - var path = param.get_string (); - - if (path == null || path == "") { - return; - } - - var folder = find_path (null, path) as FolderItem; - if (folder == null) { - return; - } - - folder.on_add_new (true); - } - - private void add_new_file (SimpleAction action, Variant? param) { - // Using "path" of parent folder from params, call `on_add_new (false)` on `FolderItem` - var path = param != null ? param.get_string () : null; - - if (path == null || path == "") { - critical ("No path"); - return; - } - - var folder = find_path (null, path) as FolderItem; - if (folder == null) { - return; - } - - folder.on_add_new (false); - } - - private void add_new_from_template (SimpleAction action, Variant? param) { - // Using "path" of parent folder from params, call `on_add_new (false)` on `FolderItem` - // var path = param.get_string (); - string? parent_path = null, template_path = null; - param.@get ("(ss)", out parent_path, out template_path); - - //Do we need this check? - if (parent_path == null || parent_path == "") { - return; - } - - var folder = find_path (null, parent_path) as FolderItem; - if (folder == null) { - return; - } - - folder.on_add_template (template_path); - } - - private void action_launch_app_with_file_path (SimpleAction action, Variant? param) { - var params = param.get_strv (); - Utils.launch_app_with_file (params[1], params[0]); - } - - private void action_show_app_chooser (SimpleAction action, Variant? param) { - var path = param.get_string (); - - if (path == null || path == "") { - return; - } - - var file = GLib.File.new_for_path (path); - var dialog = new Gtk.AppChooserDialog (new Gtk.Window (), Gtk.DialogFlags.MODAL, file); - dialog.deletable = false; - - dialog.response.connect ((res) => { - if (res == Gtk.ResponseType.OK) { - var app_info = dialog.get_app_info (); - if (app_info != null) { - Utils.launch_app_with_file (app_info.get_id (), path); - } - } - - dialog.destroy (); - }); - - dialog.show (); - } - - private void action_execute_contract_with_file_path (SimpleAction action, Variant? param) { - var params = param.get_strv (); - var path = params[0]; - if (path == null || path == "") { - return; - } - - var contract_name = params[1]; - if (contract_name == null || contract_name == "") { - return; - } - - Utils.execute_contract_with_file_path (path, contract_name); - } - - private void action_rename_file (SimpleAction action, Variant? param) { - var path = param.get_string (); - - if (path == null || path == "") { - return; - } - - rename_file (path); - } - - private void action_rename_folder (SimpleAction action, Variant? param) { - var path = param.get_string (); - - if (path == null || path == "") { - return; - } - - rename_folder (path); - } - - - private void action_delete (SimpleAction action, Variant? param) { - var path = param.get_string (); - - if (path == null || path == "") { - return; - } - - var item = find_path (null, path); - if (item != null) { - var item_to_delete = item as Scratch.FolderManager.Item; - - // Wait for ProjectFolderItem closed signal handle logic to run before moving item to trash - if (item_to_delete is Scratch.FolderManager.ProjectFolderItem) { - item_to_delete.closed.connect_after (() => { - item_to_delete.trash (); - }); - item_to_delete.closed (); - return; - } - - item_to_delete.trash (); - } - } - - private async void add_folder (File folder, bool expand, bool restoring = false) { - if (is_open (folder)) { - warning ("Folder '%s' is already open.", folder.path); - return; - } else if (!folder.is_valid_directory) { - warning ("Cannot open invalid directory."); - return; - } - - var add_file = folder.file; - // Need to deal with case where folder is parent or child of an existing project - var parents = new List (); - var children = new List (); - - iterate_children (null, (child) => { - var item = (ProjectFolderItem) child; - if (add_file.get_relative_path (item.file.file) != null) { - debug ("Trying to add parent of existing project"); - children.append (item); - } else if (item.file.file.get_relative_path (add_file) != null) { - debug ("Trying to add child of existing project"); - parents.append (item); - } - - return Code.TreeList.ITERATE_CONTINUE; - }); - - if (parents.length () > 0 || children.length () > 0) { - assert (parents.length () <= 1); - assert (parents.length () == 0 || children.length () == 0); - var dialog = new Scratch.Dialogs.CloseProjectsConfirmationDialog ( - (MainWindow) get_root (), - parents.length (), - children.length () - ); - - var close_projects = false; - dialog.response.connect ((res) => { - if (res == Gtk.ResponseType.ACCEPT) { - close_projects = true; - } - - dialog.destroy (); - add_folder.callback (); - }); - - dialog.show (); - yield; - - if (close_projects) { - foreach (var item in parents) { - item.closed (); - } - - foreach (var item in children) { - item.closed (); - } - } else { - return; - } - } - - // Process any closed signals emitted before proceeding - Idle.add (() => { - var folder_root = new ProjectFolderItem (folder, this); // Constructor adds project to GitManager - add_root_item (folder_root); - rename_items_with_same_name (folder_root); - - folder_root.is_expanded = expand; - folder_root.closed.connect (() => { - activate_action ( - MainWindow.ACTION_PREFIX + MainWindow.ACTION_CLOSE_PROJECT_DOCS, - "s", - folder_root.path - ); - - remove_root_item (folder_root); - - iterate_children (null, (child) => { - var child_folder = (ProjectFolderItem) child; - if (child_folder.name != child_folder.file.name) { - rename_items_with_same_name (child_folder); - } - - return Code.TreeList.ITERATE_CONTINUE; - }); - - Scratch.Services.GitManager.get_instance ().remove_project (folder_root); - write_settings (); - }); - - // We do not want to rewrite settings while restoring from settings - // This interferes with fuzzy-finder plugins_manager - // See https://github.com/elementary/code/issues/1533 - if (!restoring) { - write_settings (); - } - - add_folder.callback (); - return Source.REMOVE; - }); - - yield; - - order_folders (); - } - - private bool is_open (File folder) { - bool open = false; - iterate_children (null, (child) => { - if (folder.path == ((Item) child).path) { - open = true; - return Code.TreeList.ITERATE_STOP; - } - - return Code.TreeList.ITERATE_CONTINUE; - }); - - return open; - } - - private void write_settings () { - string[] to_save = {}; - iterate_children (null, (item) => { - var saved = false; - var folder_path = ((Item) item).path; - - //Do we need to de-duplicate? Not possible to open a project twice? - foreach (var saved_folder in to_save) { - if (folder_path == saved_folder) { - saved = true; - break; - } - } - - if (!saved) { - to_save += folder_path; - } - - return Code.TreeList.ITERATE_CONTINUE; - }); - - settings.set_strv ("opened-folders", to_save); - } -} diff --git a/src/FolderManager/FolderItem.vala b/src/FolderManager/FolderItem.vala index bcd0e81fd2..039750ae3e 100644 --- a/src/FolderManager/FolderItem.vala +++ b/src/FolderManager/FolderItem.vala @@ -18,644 +18,616 @@ * Authored by: Julien Spautz , Andrei-Costin Zisu */ -namespace Scratch.FolderManager { - /** - * Expandable item in the source list, represents a folder. - * Monitored for changes inside the directory. - */ - public class FolderItem : Item { - private const uint RENAME_AFTER_NEW_DELAY_MSEC = 500; - private GLib.FileMonitor monitor; - private bool children_loaded = false; - private bool has_dummy; - private Code.TreeListItem dummy; /* Blank item for expanded empty folders */ - - public bool loading_required { - get { - return !children_loaded && file.children.size > 0; - } +// Folder interface shared with Project folder items used in ListBox +public interface Code.FolderInterface : Object { + public abstract bool is_expanded { get; set; } + public abstract ListStore? child_model { get; set; } + public abstract FileMonitor monitor { get; set; } + public virtual void on_changed ( + GLib.File source, + GLib.File? dest, + GLib.FileMonitorEvent event + ) {} + + public virtual void init_monitor_directory (GLib.File gfile) { + try { + monitor = gfile.monitor_directory (GLib.FileMonitorFlags.NONE); + monitor.changed.connect (on_changed); + } catch (GLib.Error e) { + warning (e.message); } + } - public signal void children_finished_loading (); - - public FolderItem (File file, FileView view) { - Object ( - file: file, - view: view + // Used by both ProjectListItem and FolderItem + public virtual GLib.MenuItem create_submenu_for_new (string file_path) { + var new_folder_item = new GLib.MenuItem ( + _("Folder"), + GLib.Action.print_detailed_name ( + ITEM_ACTION_PREFIX + ACTION_NEW_FOLDER, + new Variant.string (file_path) + ) + ); + + var new_file_item = new GLib.MenuItem ( + _("Empty File"), + GLib.Action.print_detailed_name ( + ITEM_ACTION_PREFIX + ACTION_NEW_FILE, + new Variant.string (file_path) + ) + ); + + var new_menu = new GLib.Menu (); + new_menu.append_item (new_folder_item); + new_menu.append_item (new_file_item); + + // Append any templates/template folders. + unowned string? template_path = GLib.Environment.get_user_special_dir (GLib.UserDirectory.TEMPLATES); + if (template_path != null) { + var template_submenu = new Menu (); + uint template_count = load_templates_from_folder ( + file_path, + GLib.File.new_for_path (template_path), + template_submenu ); - } - - ~FolderItem () { - monitor.cancel (); - } - - construct { - is_selectable = false; - is_expandable = true; - - dummy = new Code.TreeListItem.dummy (); - // Must add dummy on unexpanded folders else expander will not show - add_child (dummy); - has_dummy = true; - - // Signal when expanded - notify["is-expanded"].connect (on_toggled); + if (template_count > 0) { + if (template_count > MAX_TEMPLATES) { + template_submenu.append_item (new MenuItem (_("…too many templates"), null)); + } - try { - monitor = file.file.monitor_directory (GLib.FileMonitorFlags.NONE); - monitor.changed.connect (on_changed); - } catch (GLib.Error e) { - warning (e.message); + new_menu.append_submenu (_("Templates"), template_submenu); } } - private void create_child_items (File file) { - foreach (var child_file in file.children) { - if (child_file.is_valid_directory) { - add_child (new FolderItem (child_file, view)); - } else if (child_file.is_valid_textfile) { - add_child (new FileItem (child_file, view)); - } - } - } + var new_item = new GLib.MenuItem.submenu (_("Add New"), new_menu); + new_item.set_submenu (new_menu); - public void load_children () { - if (loading_required) { - create_child_items (file); - after_children_loaded (); - } - } + return new_item; + } - private async void load_children_async () { - if (loading_required) { - // Do we need to yield after each child with dynamic view?? - Idle.add (() => { - create_child_items (file); - load_children_async.callback (); - return Source.REMOVE; - }); - yield; - } + public delegate bool IterateChildrenCallback (Object obj); + public virtual void iterate_children (IterateChildrenCallback cb) {} - after_children_loaded (); - } + // Recursively load templates from folder and subfolders keeping count of total menuitems + const int MAX_TEMPLATES = 2048; + private uint load_templates_from_folder ( + string target_path, + GLib.File template_folder, + Menu template_submenu + ) { + GLib.List template_list = null; + GLib.List folder_list = null; + + GLib.FileEnumerator enumerator; + var flags = GLib.FileQueryInfoFlags.NOFOLLOW_SYMLINKS; + uint count = 0; + try { + enumerator = template_folder.enumerate_children ("standard::*", flags, null); + GLib.File location; + GLib.FileInfo? info = enumerator.next_file (null); + + while (count < MAX_TEMPLATES && (info != null)) { + if (!info.get_attribute_boolean (GLib.FileAttribute.STANDARD_IS_BACKUP)) { + location = template_folder.get_child (info.get_name ()); + if (info.get_file_type () == GLib.FileType.DIRECTORY) { + folder_list.prepend (location); + } else { + template_list.prepend (location); + } - private void add_file (File child) { - Code.TreeListItem item = null; - if (child.is_valid_directory) { - item = new FolderItem (child, view); - } else if (child.is_valid_textfile) { - item = new FileItem (child, view); - } + count ++; + } - if (item != null) { - add_child (item); + info = enumerator.next_file (null); } + } catch (GLib.Error error) { + return 0; } - private void after_children_loaded () { - children_loaded = true; - var root = get_root_folder (); - if (root != null) { - root.after_child_folder_loaded (this); //Updates child status emblens - } - - children_finished_loading (); - } + folder_list.sort ((a, b) => { + return strcmp (a.get_basename ().down (), b.get_basename ().down ()); + }); + + unowned List fl = folder_list; + while (fl != null && count < MAX_TEMPLATES) { + var folder = fl.data; + var folder_submenu = new Menu (); + var folder_submenuitem = new MenuItem.submenu ( + folder.get_basename (), + folder_submenu + ); - private void on_toggled () { - if (is_expanded) { - load_children_async.begin (); + var sub_count = load_templates_from_folder (target_path, folder, folder_submenu); + if (sub_count > 0) { + template_submenu.append_item (folder_submenuitem); + count += sub_count; } else { - var root = get_root_folder (); - if (root != null && - root.monitored_repo != null) { - //When toggled closed, update status to reflect hidden contents - root.update_item_status (this); - } + count -= 1; // Adjust count for ignored folder } - } - public override GLib.Menu? get_context_menu () { - GLib.FileInfo info = null; - try { - info = file.file.query_info (GLib.FileAttribute.STANDARD_CONTENT_TYPE, GLib.FileQueryInfoFlags.NONE); - } catch (Error e) { - warning (e.message); - } - - var file_type = info.get_content_type (); - - var contractor_items = Utils.create_contract_items_for_file (file.file); + fl = fl.next; + } - var rename_menu_item = new GLib.MenuItem ( - _("Rename"), - GLib.Action.print_detailed_name ( - FileView.ACTION_PREFIX + FileView.ACTION_RENAME_FOLDER, - new Variant.string (file.path) - ) - ); + if (count > MAX_TEMPLATES) { + warning ("too many templates! %u", count); + return count; + } - var delete_item = new GLib.MenuItem ( - _("Move to Trash"), - GLib.Action.print_detailed_name ( - FileView.ACTION_PREFIX + FileView.ACTION_DELETE, - new Variant.string (file.path) - ) - ); + template_list.sort ((a, b) => { + return strcmp (a.get_basename ().down (), b.get_basename ().down ()); + }); - var search_item = new GLib.MenuItem ( - _("Find in Folder…"), + template_list.@foreach ((template) => { + var template_menuitem = new MenuItem ( + template.get_basename (), GLib.Action.print_detailed_name ( - MainWindow.ACTION_PREFIX + MainWindow.ACTION_FIND_GLOBAL, - new Variant.string (file.file.get_path ()) + ITEM_ACTION_PREFIX + ACTION_NEW_FROM_TEMPLATE, + new Variant ("(ss)", target_path, template.get_path ()) ) ); - var external_actions_section = new GLib.Menu (); - external_actions_section.append_item (create_submenu_for_open_in (file_type)); - if (contractor_items.get_n_items () > 0) { - external_actions_section.append_submenu (_("Other Actions"), contractor_items); - } + template_submenu.append_item (template_menuitem); + }); - var direct_actions_section = new GLib.Menu (); - direct_actions_section.append_item (create_submenu_for_new ()); - direct_actions_section.append_item (rename_menu_item); - direct_actions_section.append_item (delete_item); + return count; + } - var search_section = new GLib.Menu (); - search_section.append_item (search_item); +} - var menu_model = new GLib.Menu (); - menu_model.append_section (null, external_actions_section); - menu_model.append_section (null, direct_actions_section); - menu_model.append_section (null, search_section); +public class Code.FolderItem : FolderManagerItem, Code.FolderInterface, Code.FolderManagerItemInterface { // Ultimately a Code.TreeListItem + public signal void children_finished_loading (); - return menu_model; + public bool loading_required { + get { + return !children_loaded && file.children.size > 0; } + } - protected GLib.MenuItem create_submenu_for_open_in (string? file_type) { - var open_in_terminal_pane_item = new GLib.MenuItem ( - (_("Terminal Pane")), - GLib.Action.print_detailed_name ( - MainWindow.ACTION_PREFIX + MainWindow.ACTION_OPEN_IN_TERMINAL, - new Variant.string (file.path) - ) - ); - open_in_terminal_pane_item.set_icon (new ThemedIcon ("panel-bottom-symbolic")); - - var other_menu_item = new GLib.MenuItem ( - _("Other Application…"), - GLib.Action.print_detailed_name ( - FileView.ACTION_PREFIX + FileView.ACTION_SHOW_APP_CHOOSER, - file.path - ) - ); + // Code.FolderInterface + // protected bool is_expanded { get; set; } + protected FileMonitor monitor { get; set; } - var extra_section = new GLib.Menu (); - extra_section.append_item (other_menu_item); + private const uint RENAME_AFTER_NEW_DELAY_MSEC = 500; + private bool children_loaded = false; + private bool has_dummy; + private Code.TreeListItem dummy; /* Blank item for expanded empty folders */ - var terminal_pane_section = new Menu (); - terminal_pane_section.append_item (open_in_terminal_pane_item); + public FolderItem (File file, FolderTree view) { + Object ( + file: file, + view: view + ); + } - file_type = file_type ?? "inode/directory"; + ~FolderItem () { + monitor.cancel (); + } - var open_in_menu = new GLib.Menu (); - open_in_menu.append_section (null, terminal_pane_section); - open_in_menu.append_section (null, Utils.create_executable_app_items_for_file (file.file, file_type)); - open_in_menu.append_section (null, extra_section); + construct { + is_selectable = false; + is_expandable = true; - var open_in_menu_item = new GLib.MenuItem.submenu (_("Open In"), open_in_menu); - return open_in_menu_item; - } + dummy = new Code.TreeListItem.dummy (); + // Must add dummy on unexpanded folders else expander will not show + add_child (dummy); + has_dummy = true; - protected GLib.MenuItem create_submenu_for_new () { - var new_folder_item = new GLib.MenuItem ( - _("Folder"), - GLib.Action.print_detailed_name ( - FileView.ACTION_PREFIX + FileView.ACTION_NEW_FOLDER, - new Variant.string (file.path) - ) - ); + init_monitor_directory (file.file); - var new_file_item = new GLib.MenuItem ( - _("Empty File"), - GLib.Action.print_detailed_name ( - FileView.ACTION_PREFIX + FileView.ACTION_NEW_FILE, - new Variant.string (file.path) - ) - ); + // Signal when expanded + notify["is-expanded"].connect (on_toggled); + } - var new_menu = new GLib.Menu (); - new_menu.append_item (new_folder_item); - new_menu.append_item (new_file_item); - - //Append any templates/template folders. - unowned string? template_path = GLib.Environment.get_user_special_dir (GLib.UserDirectory.TEMPLATES); - if (template_path != null) { - var template_submenu = new Menu (); - uint template_count = load_templates_from_folder (GLib.File.new_for_path (template_path), template_submenu); - if (template_count > 0) { - if (template_count > MAX_TEMPLATES) { - template_submenu.append_item (new MenuItem (_("…too many templates"), null)); - } + public override void iterate_children (IterateChildrenCallback cb) { + uint pos = 0; + Object? child = null; + do { + child = child_model.get_object (pos++); + } while (child != null && cb (child)); + } - new_menu.append_submenu (_("Templates"), template_submenu); - } + private void create_child_items (File file) { + foreach (var child_file in file.children) { + if (child_file.is_valid_directory) { + add_child (new FolderItem (child_file, view)); + } else if (child_file.is_valid_textfile) { + add_child (new FileItem (child_file, view)); } + } + } - var new_item = new GLib.MenuItem.submenu (_("New"), new_menu); - new_item.set_submenu (new_menu); - - return new_item; + public void load_children () { + if (loading_required) { + create_child_items (file); + after_children_loaded (); } + } - // Recursively load templates from folder and subfolders keeping count of total menuitems - const int MAX_TEMPLATES = 2048; - private uint load_templates_from_folder ( - GLib.File template_folder, - Menu template_submenu - ) { - GLib.List template_list = null; - GLib.List folder_list = null; - - GLib.FileEnumerator enumerator; - var flags = GLib.FileQueryInfoFlags.NOFOLLOW_SYMLINKS; - uint count = 0; - try { - enumerator = template_folder.enumerate_children ("standard::*", flags, null); - GLib.File location; - GLib.FileInfo? info = enumerator.next_file (null); - - while (count < MAX_TEMPLATES && (info != null)) { - if (!info.get_attribute_boolean (GLib.FileAttribute.STANDARD_IS_BACKUP)) { - location = template_folder.get_child (info.get_name ()); - if (info.get_file_type () == GLib.FileType.DIRECTORY) { - folder_list.prepend (location); - } else { - template_list.prepend (location); - } + private async void load_children_async () { + if (loading_required) { + // Do we need to yield after each child with dynamic view?? + Idle.add (() => { + create_child_items (file); + load_children_async.callback (); + return Source.REMOVE; + }); - count ++; - } + yield; + } - info = enumerator.next_file (null); - } - } catch (GLib.Error error) { - return 0; - } + after_children_loaded (); + } - folder_list.sort ((a, b) => { - return strcmp (a.get_basename ().down (), b.get_basename ().down ()); - }); + private void add_file (File child) { + Code.TreeListItem item = null; + if (child.is_valid_directory) { + item = new FolderItem (child, view); + } else if (child.is_valid_textfile) { + item = new FileItem (child, view); + } - unowned List fl = folder_list; - while (fl != null && count < MAX_TEMPLATES) { - var folder = fl.data; - var folder_submenu = new Menu (); - var folder_submenuitem = new MenuItem.submenu ( - folder.get_basename (), - folder_submenu - ); - - var sub_count = load_templates_from_folder (folder, folder_submenu); - if (sub_count > 0) { - template_submenu.append_item (folder_submenuitem); - count += sub_count; - } else { - count -= 1; // Adjust count for ignored folder - } + if (item != null) { + add_child (item); + } + } - fl = fl.next; - } + private void after_children_loaded () { + children_loaded = true; + // var root = get_root_folder (); + // if (root != null) { + // root.after_child_folder_loaded (this); //Updates child status emblens + // } - if (count > MAX_TEMPLATES) { - warning ("too many templates! %u", count); - return count; - } + children_finished_loading (); + } - template_list.sort ((a, b) => { - return strcmp (a.get_basename ().down (), b.get_basename ().down ()); - }); + private void on_toggled () { + // if (is_expanded) { + // warning ("load children"); + // load_children_async.begin (); + // } + // else { + // var root = get_root_folder (); + // if (root != null) { + // //When toggled closed, update status to reflect hidden contents + // root.update_item_status (this); + // } + // } + } - template_list.@foreach ((template) => { - var template_menuitem = new MenuItem ( - template.get_basename (), - GLib.Action.print_detailed_name ( - FileView.ACTION_PREFIX + FileView.ACTION_NEW_FROM_TEMPLATE, - new Variant ("(ss)", this.path, template.get_path ()) - ) - ); + public override GLib.Menu? get_context_menu () { + warning ("Folder item get context menu"); + var menu_model = base.get_context_menu (); + + // Add items related only to folders + var search_item = new GLib.MenuItem ( + _("Find in Folder…"), + GLib.Action.print_detailed_name ( + Scratch.MainWindow.ACTION_PREFIX + Scratch.MainWindow.ACTION_FIND_GLOBAL, + new Variant.string (file.file.get_path ()) + ) + ); + var search_section = new GLib.Menu (); + search_section.append_item (search_item); + menu_model.append_section (null, search_section); + // var external_actions_section = new GLib.Menu (); + // external_actions_section.append_item (create_submenu_for_open_item_in (file.path, file_type)); + + var direct_actions_section = new GLib.Menu (); + direct_actions_section.append_item (create_submenu_for_new (file.path)); + menu_model.prepend_section (null, direct_actions_section); + // direct_actions_section.append_item (rename_menu_item); + // direct_actions_section.append_item (delete_item); + + return menu_model; + } - template_submenu.append_item (template_menuitem); - }); - return count; - } + // public void remove_all_badges () { + // view.iterate_children (this, (child) => { + // remove_badge (child); + // return Code.TreeList.ITERATE_CONTINUE; + // }); + // } - public void remove_all_badges () { - view.iterate_children (this, (child) => { - remove_badge (child); - return Code.TreeList.ITERATE_CONTINUE; - }); - } + // private void remove_badge (Code.TreeListItem item) { - private void remove_badge (Code.TreeListItem item) { + // if (item is FolderItem) { + // ((FolderItem) item).remove_all_badges (); + // } - if (item is FolderItem) { - ((FolderItem) item).remove_all_badges (); - } + // item.badge = ""; + // } - item.badge = ""; + public override void add_child (Code.TreeListItem item) { + if (has_dummy) { + base.remove_child (dummy); + has_dummy = false; } - public override void add_child (Code.TreeListItem item) { - if (has_dummy) { - base.remove_child (dummy); - has_dummy = false; - } + base.add_child (item); + } - base.add_child (item); + // Only for removing "real" items; dummy is added/removed directly + public override void remove_child (Code.TreeListItem item) requires (has_dummy == false) { + if (item is FolderItem) { + var folder = (FolderItem) item; + folder.remove_all_children (); } - // Only for removing "real" items; dummy is added/removed directly - public override void remove_child (Code.TreeListItem item) requires (has_dummy == false) { - if (item is FolderItem) { - var folder = (FolderItem) item; - folder.remove_all_children (); - } + base.remove_child (item); - base.remove_child (item); + // Add back dummy if empty + if (has_no_children ()) { + base.add_child (dummy); + has_dummy = true; + } + } - // Add back dummy if empty - if (has_no_children ()) { - base.add_child (dummy); - has_dummy = true; - } + // public new void clear () { + // ((Code.Widgets.SourceList.ExpandableItem)this).clear (); + // has_dummy = false; + // } + + protected virtual void on_changed ( + GLib.File source, + GLib.File? dest, + GLib.FileMonitorEvent event + ) { + if (source.get_basename ().has_prefix (".goutputstream")) { + return; // Ignore changes due to temp files and streams } - // public new void clear () { - // ((Code.Widgets.SourceList.ExpandableItem)this).clear (); - // has_dummy = false; - // } + view.folder_item_update_hook (source, dest, event); - protected virtual void on_changed ( - GLib.File source, - GLib.File? dest, - GLib.FileMonitorEvent event - ) { - if (source.get_basename ().has_prefix (".goutputstream")) { - return; // Ignore changes due to temp files and streams + if (!children_loaded) { // No child items except dummy, child never expanded + /* Empty folder with dummy item will come here even if expanded */ + switch (event) { + case GLib.FileMonitorEvent.DELETED: + file.invalidate_cache (); //TODO Throttle if required + if (is_expanded) { + // Force refresh of child model? + notify_property ("is-expanded"); + } + break; + case GLib.FileMonitorEvent.CREATED: + file.invalidate_cache (); //TODO Throttle if required + if (is_expanded) { + notify_property ("is-expanded"); + } + break; + case FileMonitorEvent.RENAMED: + case FileMonitorEvent.PRE_UNMOUNT: + case FileMonitorEvent.UNMOUNTED: + case FileMonitorEvent.CHANGED: + case FileMonitorEvent.CHANGES_DONE_HINT: + case FileMonitorEvent.MOVED: + case FileMonitorEvent.MOVED_IN: + case FileMonitorEvent.MOVED_OUT: + case FileMonitorEvent.ATTRIBUTE_CHANGED: + + break; } + } else { + // Child has been expanded ( but could be closed now) and + // items loaded (or dummy). + // No cache invalidation is needed here because the entire state + // is kept in the tree + //TODO Test for TreeList + switch (event) { + case GLib.FileMonitorEvent.DELETED: + // Find item corresponding to deleted file + // Note may not be found if deleted file is not valid for display + var path_item = get_child_for_path (source.get_path ()); + if (path_item != null) { + remove_child (path_item); + } - view.folder_item_update_hook (source, dest, event); - - if (!children_loaded) { // No child items except dummy, child never expanded - /* Empty folder with dummy item will come here even if expanded */ - switch (event) { - case GLib.FileMonitorEvent.DELETED: - file.invalidate_cache (); //TODO Throttle if required - if (is_expanded) { - // Force refresh of child model? - notify_property ("is-expanded"); - } - break; - case GLib.FileMonitorEvent.CREATED: - file.invalidate_cache (); //TODO Throttle if required - if (is_expanded) { - notify_property ("is-expanded"); - } - break; - case FileMonitorEvent.RENAMED: - case FileMonitorEvent.PRE_UNMOUNT: - case FileMonitorEvent.UNMOUNTED: - case FileMonitorEvent.CHANGED: - case FileMonitorEvent.CHANGES_DONE_HINT: - case FileMonitorEvent.MOVED: - case FileMonitorEvent.MOVED_IN: - case FileMonitorEvent.MOVED_OUT: - case FileMonitorEvent.ATTRIBUTE_CHANGED: - - break; - } - } else { - // Child has been expanded ( but could be closed now) and - // items loaded (or dummy). - // No cache invalidation is needed here because the entire state - // is kept in the tree - //TODO Test for TreeList - switch (event) { - case GLib.FileMonitorEvent.DELETED: - // Find item corresponding to deleted file - // Note may not be found if deleted file is not valid for display - var path_item = find_item_for_path (source.get_path ()); - if (path_item != null) { - remove_child (path_item); - } + break; + case GLib.FileMonitorEvent.CREATED: + if (source.query_exists () == false) { + return; + } - break; - case GLib.FileMonitorEvent.CREATED: - if (source.query_exists () == false) { - return; + var path_item = get_child_for_path (source.get_path ()); + if (path_item == null) { + var file = new File (source.get_path ()); + if (file.is_valid_directory) { + path_item = new FolderItem (file, view); + } else if (file.is_valid_textfile) { + path_item = new FileItem (file, view); } - var path_item = find_item_for_path (source.get_path ()); - if (path_item == null) { - var file = new File (source.get_path ()); - if (file.is_valid_directory) { - path_item = new FolderItem (file, view); - } else if (file.is_valid_textfile) { - path_item = new FileItem (file, view); - } - - add_child (path_item); // null parameter is silently ignored - } + add_child (path_item); // null parameter is silently ignored + } - break; - case FileMonitorEvent.RENAMED: - case FileMonitorEvent.PRE_UNMOUNT: - case FileMonitorEvent.UNMOUNTED: - case FileMonitorEvent.CHANGED: - case FileMonitorEvent.CHANGES_DONE_HINT: - case FileMonitorEvent.MOVED: - case FileMonitorEvent.MOVED_IN: - case FileMonitorEvent.MOVED_OUT: - case FileMonitorEvent.ATTRIBUTE_CHANGED: - break; - } + break; + case FileMonitorEvent.RENAMED: + case FileMonitorEvent.PRE_UNMOUNT: + case FileMonitorEvent.UNMOUNTED: + case FileMonitorEvent.CHANGED: + case FileMonitorEvent.CHANGES_DONE_HINT: + case FileMonitorEvent.MOVED: + case FileMonitorEvent.MOVED_IN: + case FileMonitorEvent.MOVED_OUT: + case FileMonitorEvent.ATTRIBUTE_CHANGED: + break; } + } - // Reduce spamming of root (still results in multiple signals per change in file being edited - //TODO Throttle this signal? - if (event == FileMonitorEvent.CHANGES_DONE_HINT) { - //TODO Get root folder once as it will not change for the life of this folder - var root = get_root_folder (this); - if (root != null) { - root.child_folder_changed (this); - } + // Reduce spamming of root (still results in multiple signals per change in file being edited + //TODO Throttle this signal? + if (event == FileMonitorEvent.CHANGES_DONE_HINT) { + //TODO Get root folder once as it will not change for the life of this folder + var root = get_root_folder (this); + if (root != null) { + root.child_folder_changed (this); } } + } - private FolderManager.Item? find_item_for_path (string path) { - FolderManager.Item? found_item = null; - view.iterate_children (this, (item) => { - // Item could be dummy - if (item is FolderManager.Item) { - var child = (FolderManager.Item) item; - if (child.path == path) { - found_item = child; - return Code.TreeList.ITERATE_STOP; - } + // Called on MonitorEvent so only concerns direct children + private Code.FolderManagerItem? get_child_for_path (string path) { + Code.FolderManagerItem? found_item = null; + iterate_children ((obj) => { + // Item could be dummy + if (obj is Code.FolderManagerItem) { + var child = (Code.FolderManagerItem) obj; + if (child.path == path) { + found_item = child; + return Code.TreeList.ITERATE_STOP; } + } - return Code.TreeList.ITERATE_CONTINUE; - }); + return Code.TreeList.ITERATE_CONTINUE; + }); - return found_item; - } + return found_item; + } - public void on_add_template (string template_path) { - // Expand folder before trying to copy template so that child appears for renaming - if (!is_expanded) { - is_expanded = true; // causes async loading of children - ulong once = 0; - once = children_finished_loading.connect (() => { - this.disconnect (once); - copy_template (template_path); - }); - } else { + public void on_add_template (string template_path) { + warning ("add template"); + // Expand folder before trying to copy template so that child appears for renaming + if (!is_expanded) { + is_expanded = true; // causes async loading of children + ulong once = 0; + once = children_finished_loading.connect (() => { + this.disconnect (once); copy_template (template_path); - } + }); + } else { + copy_template (template_path); } + } - private void copy_template (string template_path) { - if (!file.is_executable) { - // This is necessary to avoid infinite loop below - warning ("Unable to open parent folder"); - return; - } - - var template_file = GLib.File.new_for_path (template_path); - name = template_file.get_basename (); - var new_file = file.file.get_child (name); - var n = 1; - while (new_file.query_exists ()) { - new_file = file.file.get_child (("%s %d").printf (name, n)); - n++; - } + private void copy_template (string template_path) { + if (!file.is_executable) { + // This is necessary to avoid infinite loop below + warning ("Unable to open parent folder"); + return; + } - name = new_file.get_basename (); - - //Assume templates are small and can be copied without problems - try { - template_file.copy ( - new_file, - TARGET_DEFAULT_MODIFIED_TIME | TARGET_DEFAULT_PERMS | NOFOLLOW_SYMLINKS, - null, //No cancellable - null //No progress - ); - } catch (Error e) { - warning ("Error copying template %s", e.message); - return; - } + var template_file = GLib.File.new_for_path (template_path); + name = template_file.get_basename (); + var new_file = file.file.get_child (name); + var n = 1; + while (new_file.query_exists ()) { + new_file = file.file.get_child (("%s %d").printf (name, n)); + n++; + } - // Wait for monitor to pickup file creation and add new item - ulong once = 0; - once = child_added.connect (() => { - this.disconnect (once); - var path = new_file.get_path (); - // Still need to wait for sourcelist to become stable and editable - //TODO Find a better way - Timeout.add (RENAME_AFTER_NEW_DELAY_MSEC, () => { - var rename_action = Utils.action_from_group (FileView.ACTION_RENAME_FILE, view.actions); - if (rename_action != null && rename_action.enabled) { - rename_action.activate (path); - } else { - critical ("Rename action not available"); - } + name = new_file.get_basename (); - return Source.REMOVE; - }); - }); + //Assume templates are small and can be copied without problems + try { + template_file.copy ( + new_file, + TARGET_DEFAULT_MODIFIED_TIME | TARGET_DEFAULT_PERMS | NOFOLLOW_SYMLINKS, + null, //No cancellable + null //No progress + ); + } catch (Error e) { + warning ("Error copying template %s", e.message); + return; } - public void on_add_new (bool is_folder) { - if (!file.is_executable) { - // This is necessary to avoid infinite loop below - warning ("Unable to open parent folder"); - return; - } + // Wait for monitor to pickup file creation and add new item + ulong once = 0; + once = child_added.connect (() => { + this.disconnect (once); + var path = new_file.get_path (); + // Still need to wait for sourcelist to become stable and editable + //TODO Find a better way + Timeout.add (RENAME_AFTER_NEW_DELAY_MSEC, () => { + var rename_action = Scratch.Utils.action_from_group (ACTION_RENAME_FILE, view.actions); + if (rename_action != null && rename_action.enabled) { + rename_action.activate (path); + } else { + critical ("Rename action not available"); + } + return Source.REMOVE; + }); + }); + } - var name = is_folder ? _("untitled folder") : _("new file"); - var new_file = file.file.get_child (name); - var n = 1; - while (new_file.query_exists ()) { - new_file = file.file.get_child (("%s %d").printf (name, n)); - n++; - } + public void on_add_new (bool is_folder) { + warning ("add new"); + if (!file.is_executable) { + // This is necessary to avoid infinite loop below + warning ("Unable to open parent folder"); + return; + } - name = new_file.get_basename (); - // Expand folder before trying to rename - if (!is_expanded) { - ulong once = 0; - once = children_finished_loading.connect (() => { - this.disconnect (once); - rename_new (name, is_folder); - }); + var name = is_folder ? _("untitled folder") : _("new file"); + var new_file = file.file.get_child (name); + var n = 1; + while (new_file.query_exists ()) { + new_file = file.file.get_child (("%s %d").printf (name, n)); + n++; + } - is_expanded = true; // causes async loading of children - } else { + name = new_file.get_basename (); + + // Expand folder before trying to rename + if (!is_expanded) { + ulong once = 0; + once = children_finished_loading.connect (() => { + this.disconnect (once); rename_new (name, is_folder); - } - } + }); - private void rename_new (string name, bool is_folder) { - // assert (!view.editing); - // var rename_item = new RenameItem (name, is_folder); - // add_child (rename_item); - - // // Wait until can start editing - // // For some reason using an Idle does not work properly here - the editable gets drawn in the wrong place - // //TODO Find a way to detect when the sourcelist is stable and can be edited - // Timeout.add (RENAME_AFTER_NEW_DELAY_MSEC, () => { - // if (view.start_editing_item (rename_item)) { - // ulong once = 0; - // once = rename_item.edited.connect (() => { - // rename_item.disconnect (once); - // // A name was accepted so create the corresponding file - // var new_name = rename_item.name; - // try { - // var gfile = file.file.get_child_for_display_name (new_name); - // if (rename_item.is_folder) { - // gfile.make_directory (); - // } else { - // gfile.create (FileCreateFlags.NONE); - // view.activate (gfile.get_path ()); - // } - // } catch (Error e) { - // warning (e.message); - // } - // }); - - // /* Need to remove rename item even when editing cancelled so cannot use "edited" signal */ - // Timeout.add (200, () => { - // if (view.editing) { - // return Source.CONTINUE; - // } else { - // remove_child (rename_item); - // return Source.REMOVE; - // } - // }); - // } else { - // critical ("Failed to rename new item"); - // remove_child (rename_item); - // } - - // return Source.REMOVE; - // }); + is_expanded = true; // causes async loading of children + } else { + rename_new (name, is_folder); } } + + private void rename_new (string name, bool is_folder) { + // assert (!view.editing); + // var rename_item = new RenameItem (name, is_folder); + // add_child (rename_item); + + // // Wait until can start editing + // // For some reason using an Idle does not work properly here - the editable gets drawn in the wrong place + // //TODO Find a way to detect when the sourcelist is stable and can be edited + // Timeout.add (RENAME_AFTER_NEW_DELAY_MSEC, () => { + // if (view.start_editing_item (rename_item)) { + // ulong once = 0; + // once = rename_item.edited.connect (() => { + // rename_item.disconnect (once); + // // A name was accepted so create the corresponding file + // var new_name = rename_item.name; + // try { + // var gfile = file.file.get_child_for_display_name (new_name); + // if (rename_item.is_folder) { + // gfile.make_directory (); + // } else { + // gfile.create (FileCreateFlags.NONE); + // view.activate (gfile.get_path ()); + // } + // } catch (Error e) { + // warning (e.message); + // } + // }); + + // /* Need to remove rename item even when editing cancelled so cannot use "edited" signal */ + // Timeout.add (200, () => { + // if (view.editing) { + // return Source.CONTINUE; + // } else { + // remove_child (rename_item); + // return Source.REMOVE; + // } + // }); + // } else { + // critical ("Failed to rename new item"); + // remove_child (rename_item); + // } + + // return Source.REMOVE; + // }); + } } + diff --git a/src/FolderManager/FolderTree.vala b/src/FolderManager/FolderTree.vala new file mode 100644 index 0000000000..68b05286bd --- /dev/null +++ b/src/FolderManager/FolderTree.vala @@ -0,0 +1,808 @@ +/* + * SPDX-License-Identifier: GPL-3.0-or-later + * SPDX-FileCopyrightText: 2026 elementary, Inc. + */ + +/* FolderTree shows a single folder and its children as a tree */ +public class Code.FolderTreeItem : Code.TreeListItem { + public FolderManagerItem? item { get; construct; } //Either File or Folder Item + + public FolderTreeItem (FolderManagerItem item) { + Object ( item: item ); + } + + construct { + item.file.bind_property ("name", this, text); + } +} + +public class Code.FolderTree : Gtk.Box, Code.PaneSwitcher { + public signal void file_activate (File file); + // All file related actions handled here + + public SimpleActionGroup actions { get; private set; } + public string icon_name { get; set; } + public string title { get; set; } + public string root_path { get; construct; } + public bool is_empty { get { return tree_list.n_root_items () == 0; } } + + private const ActionEntry[] ACTION_ENTRIES = { + { ACTION_RENAME_FILE, action_rename_file, "s" }, + { ACTION_RENAME_FOLDER, action_rename_folder, "s" }, + { ACTION_LAUNCH_APP_WITH_FILE_PATH, action_launch_app_with_file_path, "as" }, + { ACTION_SHOW_APP_CHOOSER, action_show_app_chooser, "s" }, + { ACTION_DELETE, action_delete, "s" }, + { ACTION_EXECUTE_CONTRACT_WITH_FILE_PATH, action_execute_contract_with_file_path, "as" }, + { ACTION_NEW_FILE, add_new_file, "s" }, + { ACTION_NEW_FROM_TEMPLATE, add_new_from_template, "(ss)" }, + { ACTION_NEW_FOLDER, add_new_folder, "s"}, + + }; + + private Code.TreeList tree_list; + private Gtk.PopoverMenu context_menu; + // private GLib.Settings settings; + // private Scratch.Services.GitManager git_manager; + // private Scratch.Services.PluginsManager plugins; + + // public FolderTree (Scratch.Services.PluginsManager plugins_manager) { + // plugins = plugins_manager; + // } + + // Root_path is path of the associated //TODO Needed? + public FolderTree (string root_path) { + Object ( + root_path: root_path + ); + } + + construct { + actions = new SimpleActionGroup (); + actions.add_action_entries (ACTION_ENTRIES, this); + insert_action_group (ITEM_ACTION_GROUP, actions); + + tree_list = new Code.TreeList (); + // icon_name = "folder-symbolic"; + // title = _("Folders"); + + // settings = new GLib.Settings ("io.elementary.code.folder-manager"); + + // git_manager = Scratch.Services.GitManager.get_instance (); + + actions = new SimpleActionGroup (); + actions.add_action_entries (ACTION_ENTRIES, this); + insert_action_group (ITEM_ACTION_PREFIX, actions); + + // Scratch.saved_state.changed["order-folders"].connect (() => { + // order_folders (); + // }); + + // Convert ListView signal into file_activate + tree_list.item_activated.connect ((item) => { + if (item is FileItem) { + tree_list.activate_action ( + "win.show-document", + "s", + item.path + ); + file_activate (((FileItem) item).file); + } else if (item.is_expandable) { + item.is_expanded = !item.is_expanded; + } + }); + + context_menu = new Gtk.PopoverMenu.from_model (null) { + position = BOTTOM + }; + this.append (context_menu); + tree_list.popup_context_menu.connect (on_popup_context_menu); + + append (tree_list); + + var cfile = new Code.File (root_path); + foreach (var child_file in cfile.children) { + if (child_file.is_valid_directory) { + tree_list.add_root_item (new FolderItem (child_file, this)); + } else if (child_file.is_valid_textfile) { + tree_list.add_root_item (new FileItem (child_file, this)); + } + } + } + + public bool contains_file (GLib.File gfile) { + //TODO Fill out + return false; + } + + // private void action_close_folder (SimpleAction action, GLib.Variant? parameter) { + // var path = parameter.get_string (); + // if (path == null || path == "") { + // return; + // } + + // var project_item = find_path (null, path) as ProjectFolderItem; + // if (project_item == null) { + // return; + // } + + // project_item.closed (); + // } + + // private void action_close_other_folders (SimpleAction action, GLib.Variant? parameter) { + // var path = parameter.get_string (); + // if (path == null || path == "") { + // return; + // } + + // var folder_root = find_path (null, path) as ProjectFolderItem; + // if (folder_root == null) { + // return; + // } + + // List to_remove = null; + // tree_list.iterate_children (null, (child) => { + // var project_folder_item = (ProjectFolderItem) child; + // if (project_folder_item != folder_root) { + // activate_action ( + // MainWindow.ACTION_PREFIX + MainWindow.ACTION_CLOSE_PROJECT_DOCS, + // "s", + // project_folder_item.path + // ); + // to_remove.prepend (project_folder_item); + // git_manager.remove_project (project_folder_item); + // } + + // return Code.TreeList.ITERATE_CONTINUE; + // }); + + // tree_list.remove_root_children (to_remove); + // //Make remaining project the active one + // set_project_active (path); + // } + + // private void action_set_active_project (SimpleAction action, GLib.Variant? parameter) { + // var path = parameter.get_string (); + // if (path == null || path == "") { + // return; + // } + + // set_active_project (path); + // } + + // private ProjectFolderItem? set_active_project (string path) { + // var folder_root = find_path (null, path) as ProjectFolderItem; + // if (folder_root == null) { + // return null; + // } + + // git_manager.active_project_path = path; + + // write_settings (); + + // return folder_root; + // } + + // private void set_project_active (string path) { + // activate_action ( + // MainWindow.ACTION_PREFIX + MainWindow.ACTION_SET_ACTIVE_PROJECT, + // "s", + // path + // ); + // } + + // public async void restore_saved_state () { + // foreach (unowned string path in settings.get_strv ("opened-folders")) { + // yield add_folder (new File (path), false, true); + // } + // } + + // public void open_folder (File folder) { + // if (is_open (folder)) { + // var existing = find_path (null, folder.path); + // if (existing is Code.TreeListItem) { + // ((Code.TreeListItem)existing).is_expanded = true; + // } + + // return; + // } + + // add_folder.begin (folder, true); + // } + + public void collapse_all () { + tree_list.iterate_children (null, (child) => { + child.collapse_all (true, true); + return Code.TreeList.ITERATE_CONTINUE; + }); + } + + // public void order_folders () { + // if (!Scratch.saved_state.get_boolean ("order-folders")) { + // return; + // } + + // tree_list.sort_root_children ((a, b) => { + // return strcmp ( + // ((ProjectFolderItem)a).name.down (), + // ((ProjectFolderItem)b).name.down () + // ); + // }); + // } + + public void select_path (string path) { + // find_path (null, path); + } + + public void unselect_file (GLib.File file) { + //TODO Complete this + } + + public void unselect_all () { + tree_list.unselect_all (); + } + + public void remove_all () { + tree_list.remove_all (); + } + + private void on_popup_context_menu (Graphene.Point vp, Code.TreeListItem treelistitem) { + var foldermanageritem = (Code.FolderManagerItem) treelistitem; + var model = foldermanageritem.get_context_menu (); + context_menu.menu_model = model; + context_menu.pointing_to = Gdk.Rectangle () {x = (int) vp.x, y = (int) vp.y, height = 1, width = 1}; + context_menu.popup (); + } + // public void collapse_other_projects () { + // unowned string path; + // path = git_manager.active_project_path; + + // tree_list.iterate_children (null, (child) => { + // var project_folder = ((ProjectFolderItem) child); + // if (project_folder.path != path) { + // project_folder.is_expanded = false; + // activate_action ( + // Scratch.MainWindow.ACTION_PREFIX + Scratch.MainWindow.ACTION_HIDE_PROJECT_DOCS, + // "s", + // project_folder.path + // ); + // } else if (project_folder.path == path) { + // project_folder.is_expanded = true; + // activate_action ( + // MainWindow.ACTION_PREFIX + MainWindow.ACTION_RESTORE_PROJECT_DOCS, + // "s", + // project_folder.path + // ); + // } + + // return Code.TreeList.ITERATE_CONTINUE; + // }); + // } + + // public void branch_actions (string path) { + // // Must only carry out branch actions on active project so switch if necessary. + // //TODO Warn before switching active project? + // var active_project = set_active_project (path); + // if (active_project == null || !active_project.is_git_repo) { + // Gdk.Display.get_default ().beep (); + // return; + // } + + // var dialog = new Dialogs.BranchActionDialog (active_project); + // dialog.response.connect ((res) => { + // if (res == Gtk.ResponseType.APPLY) { + // perform_branch_action (dialog); + // } + + // dialog.destroy (); + // }); + + // dialog.present (); + // } + + // private void perform_branch_action ( + // Scratch.Dialogs.BranchActionDialog dialog + // ) { + // switch (dialog.action) { + // case CHECKOUT: + // dialog.project.checkout_branch_ref (dialog.branch_ref); + // break; + // case COMMIT: + // break; + // case PUSH: + // break; + // case PULL: + // break; + // case MERGE: + // break; + // case DELETE: + // break; + // case CREATE: + // dialog.project.new_branch (dialog.new_branch_name); + // break; + // default: + // assert_not_reached (); + // } + // } + + public FolderManagerItem? find_path ( + Code.TreeListItem? start, + string path, // File path to search fod + bool expand = false // Whether to expsnd to show found item + ) { + +warning ("Folder tree find path"); + FolderManagerItem? matched_item = null; + var target = GLib.File.new_for_path (path); + + tree_list.iterate_children (start, (item) => { + if ((item is FolderManagerItem) && item.path == path) { + matched_item = (FolderManagerItem) item; + return Code.TreeList.ITERATE_STOP; + } + + if (item is FolderItem) { + var folder = item as FolderItem; + var folder_root = folder.file.file; + if (folder_root.get_relative_path (target) == null) { + return Code.TreeList.ITERATE_CONTINUE; + } + + if (!folder.is_expanded) { + if (expand) { + folder.load_children (); //Synchronous + folder.is_expanded = true; + } else { + return Code.TreeList.ITERATE_CONTINUE; + } + } + + var recurse_item = find_path (folder, path, expand); + if (recurse_item != null) { + matched_item = recurse_item; + return Code.TreeList.ITERATE_STOP; + } + } + + return Code.TreeList.ITERATE_CONTINUE; + }); + + return matched_item; + } + + // public bool project_is_open (string project_path) { + // return get_project_for_file (GLib.File.new_for_path (project_path)) != null; + // } + + // public ProjectFolderItem? get_project_for_file (GLib.File file) { + // ProjectFolderItem? matched_project = null; + // tree_list.iterate_children (null, (item) => { + // if (item is ProjectFolderItem) { + // var folder = (ProjectFolderItem) item; + // if (folder.file.file.equal (file) || folder.contains_file (file)) { + // matched_project = folder; + // return Code.TreeList.ITERATE_STOP; + // } + // } + + // return Code.TreeList.ITERATE_CONTINUE; + // }); + + // return matched_project; + // } + + public Code.TreeListItem? expand_to_path (string path) { + return find_path (null, path, true); + } + + // /* Do global search on project containing the file path supplied in parameter */ + // public void search_global (string path, string? term = null) { + // var item_for_path = (FolderManagerItem?)(expand_to_path (path)); + // if (item_for_path != null) { + // var search_root = item_for_path.get_root_folder (); + // if (search_root is ProjectFolderItem) { + // GLib.File start_folder = (item_for_path is FolderItem) + // ? item_for_path.file.file + // : search_root.file.file; + + // bool is_explicit = !(item_for_path is ProjectFolderItem); + // search_root.global_search.begin (start_folder, term, is_explicit); + // } + // } + // } + + public void clear_badges () { + // tree_list.iterate_children (null, (child) => { + // if (child is ProjectFolderItem) { + // ((FolderItem)child).remove_all_badges (); + // } + + // return Code.TreeList.ITERATE_CONTINUE; + // }); + } + + public void folder_item_update_hook (GLib.File source, GLib.File? dest, GLib.FileMonitorEvent event) { + // plugins.hook_folder_item_change (source, dest, event); + //TODO Make plugins a singleton + } + + private void iterate_children (Code.TreeListItem? start, Code.TreeList.ListIteratorCallback cb) { + tree_list.iterate_children (start, cb); + } + + // This only works when the list is stable (nothing being added, expanded etc) + private void rename_file (string path) { + // this.select_path (path); + // if (this.start_editing_item (selected)) { + // ulong once = 0; + // once = selected.edited.connect ((new_name) => { + // selected.disconnect (once); + // var new_path = Path.get_dirname (path) + Path.DIR_SEPARATOR_S + new_name; + // activate_action ( + // Scratch.MainWindow.ACTION_PREFIX + Scratch.MainWindow.ACTION_CLOSE_TAB, + // "s", + // path + // ); + + // // RecentManager requires valid URI + // var new_uri = "file://" + new_path; // Code only edits local files + // Gtk.RecentManager.get_default ().add_item (new_uri); + + // activate (new_path); + // }); + // } + + // // Handle cancelled rename (which does not produce signal) + // Timeout.add (200, () => { + // if (this.editing) { + // return Source.CONTINUE; + // } else { + // // Avoid selected but unopened item if rename cancelled (they would not open if clicked on) + // this.unselect_all (); + // return Source.REMOVE; + // } + // }); + } + + private void rename_folder (string path) { + // var folder_to_rename = find_path (null, path) as FolderItem; + // if (folder_to_rename == null) { + // critical ("Could not find folder from given path to rename: %s", path); + // return; + // } + + // folder_to_rename.selectable = true; + // if (start_editing_item (folder_to_rename)) { + // // Need to poll view as no signal emited when editing cancelled and need to set + // // selectable to false anyway. + // Timeout.add (200, () => { + // if (editing) { + // return Source.CONTINUE; + // } else { + // unselect_all (); + // // Must do this *after* unselecting all else sourcelist breaks + // folder_to_rename.selectable = false; + // } + + // return Source.REMOVE; + // }); + // } else { + // critical ("Could not rename %s", path); + // folder_to_rename.selectable = false; + // } + } + + private void rename_items_with_same_name (FolderManagerItem item) { + // string item_name = item.file.name; + // tree_list.iterate_children (null, (child) => { + // string new_other_item_name, new_item_name; + // var other_item = (ProjectFolderItem) child; + + // if (Utils.find_unique_path ( + // item.file.file, + // other_item.file.file, + // out new_item_name, + // out new_other_item_name + // ) + // ) { + // if (item_name.length < new_item_name.length) { + // item_name = new_item_name; + // } + + // if (other_item.name.length < new_other_item_name.length) { + // other_item.name = new_other_item_name; + // } + // } + + // return Code.TreeList.ITERATE_CONTINUE; + // }); + + // item.name = item_name; + } + + private void add_new_folder (SimpleAction action, Variant? param) { + // // Using "path" of parent folder from params, call `on_add_new (true)` on `FolderItem` + // var path = param.get_string (); + + // if (path == null || path == "") { + // return; + // } + + // var folder = find_path (null, path) as FolderItem; + // if (folder == null) { + // return; + // } + + // folder.on_add_new (true); + } + + private void add_new_file (SimpleAction action, Variant? param) { + // // Using "path" of parent folder from params, call `on_add_new (false)` on `FolderItem` + // var path = param != null ? param.get_string () : null; + + // if (path == null || path == "") { + // critical ("No path"); + // return; + // } + + // var folder = find_path (null, path) as FolderItem; + // if (folder == null) { + // return; + // } + + // folder.on_add_new (false); + } + + private void add_new_from_template (SimpleAction action, Variant? param) { + // // Using "path" of parent folder from params, call `on_add_new (false)` on `FolderItem` + // // var path = param.get_string (); + // string? parent_path = null, template_path = null; + // param.@get ("(ss)", out parent_path, out template_path); + + // //Do we need this check? + // if (parent_path == null || parent_path == "") { + // return; + // } + + // var folder = find_path (null, parent_path) as FolderItem; + // if (folder == null) { + // return; + // } + + // folder.on_add_template (template_path); + } + + private void action_launch_app_with_file_path (SimpleAction action, Variant? param) { + // var params = param.get_strv (); + // Utils.launch_app_with_file (params[1], params[0]); + } + + private void action_show_app_chooser (SimpleAction action, Variant? param) { + // var path = param.get_string (); + + // if (path == null || path == "") { + // return; + // } + + // var file = GLib.File.new_for_path (path); + // var dialog = new Gtk.AppChooserDialog (new Gtk.Window (), Gtk.DialogFlags.MODAL, file); + // dialog.deletable = false; + + // dialog.response.connect ((res) => { + // if (res == Gtk.ResponseType.OK) { + // var app_info = dialog.get_app_info (); + // if (app_info != null) { + // Utils.launch_app_with_file (app_info.get_id (), path); + // } + // } + + // dialog.destroy (); + // }); + + // dialog.show (); + } + + private void action_execute_contract_with_file_path (SimpleAction action, Variant? param) { + // var params = param.get_strv (); + // var path = params[0]; + // if (path == null || path == "") { + // return; + // } + + // var contract_name = params[1]; + // if (contract_name == null || contract_name == "") { + // return; + // } + + // Utils.execute_contract_with_file_path (path, contract_name); + } + + private void action_rename_file (SimpleAction action, Variant? param) { + // var path = param.get_string (); + + // if (path == null || path == "") { + // return; + // } + + // rename_file (path); + } + + private void action_rename_folder (SimpleAction action, Variant? param) { + // var path = param.get_string (); + + // if (path == null || path == "") { + // return; + // } + + // rename_folder (path); + } + + + private void action_delete (SimpleAction action, Variant? param) { + // var path = param.get_string (); + + // if (path == null || path == "") { + // return; + // } + + // var item = find_path (null, path); + // if (item != null) { + // var item_to_delete = item as Code.FolderManagerItem; + + // // Wait for ProjectFolderItem closed signal handle logic to run before moving item to trash + // if (item_to_delete is Code.ProjectFolderItem) { + // item_to_delete.closed.connect_after (() => { + // item_to_delete.trash (); + // }); + // item_to_delete.closed (); + // return; + // } + + // item_to_delete.trash (); + // } + } + + // private async void add_folder (File folder, bool expand, bool restoring = false) { + // if (is_open (folder)) { + // warning ("Folder '%s' is already open.", folder.path); + // return; + // } else if (!folder.is_valid_directory) { + // warning ("Cannot open invalid directory."); + // return; + // } + + // var add_file = folder.file; + // // Need to deal with case where folder is parent or child of an existing project + // var parents = new List (); + // var children = new List (); + + // tree_list.iterate_children (null, (child) => { + // var item = (ProjectFolderItem) child; + // if (add_file.get_relative_path (item.file.file) != null) { + // debug ("Trying to add parent of existing project"); + // children.append (item); + // } else if (item.file.file.get_relative_path (add_file) != null) { + // debug ("Trying to add child of existing project"); + // parents.append (item); + // } + + // return Code.TreeList.ITERATE_CONTINUE; + // }); + + // if (parents.length () > 0 || children.length () > 0) { + // assert (parents.length () <= 1); + // assert (parents.length () == 0 || children.length () == 0); + // var dialog = new Scratch.Dialogs.CloseProjectsConfirmationDialog ( + // (Scratch.MainWindow) get_root (), + // parents.length (), + // children.length () + // ); + + // var close_projects = false; + // dialog.response.connect ((res) => { + // if (res == Gtk.ResponseType.ACCEPT) { + // close_projects = true; + // } + + // dialog.destroy (); + // add_folder.callback (); + // }); + + // dialog.show (); + // yield; + + // if (close_projects) { + // foreach (var item in parents) { + // item.closed (); + // } + + // foreach (var item in children) { + // item.closed (); + // } + // } else { + // return; + // } + // } + + // // Process any closed signals emitted before proceeding + // Idle.add (() => { + // var folder_root = new ProjectFolderItem (folder, this); // Constructor adds project to GitManager + // tree_list.add_root_item (folder_root); + // rename_items_with_same_name (folder_root); + + // folder_root.is_expanded = expand; + // folder_root.closed.connect (() => { + // activate_action ( + // MainWindow.ACTION_PREFIX + MainWindow.ACTION_CLOSE_PROJECT_DOCS, + // "s", + // folder_root.path + // ); + + // tree_list.remove_root_item (folder_root); + + // tree_list.iterate_children (null, (child) => { + // var child_folder = (ProjectFolderItem) child; + // if (child_folder.name != child_folder.file.name) { + // rename_items_with_same_name (child_folder); + // } + + // return Code.TreeList.ITERATE_CONTINUE; + // }); + + // Scratch.Services.GitManager.get_instance ().remove_project (folder_root); + // write_settings (); + // }); + + // // We do not want to rewrite settings while restoring from settings + // // This interferes with fuzzy-finder plugins_manager + // // See https://github.com/elementary/code/issues/1533 + // if (!restoring) { + // write_settings (); + // } + + // add_folder.callback (); + // return Source.REMOVE; + // }); + + // yield; + + // order_folders (); + // } + + // private bool is_open (File folder) { + // bool open = false; + // tree_list.iterate_children (null, (child) => { + // if (folder.path == ((FolderManagerItem) child).path) { + // open = true; + // return Code.TreeList.ITERATE_STOP; + // } + + // return Code.TreeList.ITERATE_CONTINUE; + // }); + + // return open; + // } + + // private void write_settings () { + // string[] to_save = {}; + // tree_list.iterate_children (null, (item) => { + // var saved = false; + // var folder_path = ((FolderManagerItem) item).path; + + // //Do we need to de-duplicate? Not possible to open a project twice? + // foreach (var saved_folder in to_save) { + // if (folder_path == saved_folder) { + // saved = true; + // break; + // } + // } + + // if (!saved) { + // to_save += folder_path; + // } + + // return Code.TreeList.ITERATE_CONTINUE; + // }); + + // settings.set_strv ("opened-folders", to_save); + // } +} diff --git a/src/FolderManager/Item.vala b/src/FolderManager/Item.vala index f4621a1bf2..1220e3c229 100644 --- a/src/FolderManager/Item.vala +++ b/src/FolderManager/Item.vala @@ -18,128 +18,296 @@ * Authored by: Julien Spautz , Andrei-Costin Zisu */ -namespace Scratch.FolderManager { - /** - * Common abstract class for file and folder items. - */ - public abstract class Item: Code.TreeListItem { - public File file { get; construct; } - public FileView view { get; construct; } - - public string name { - get { - return text; - } +public interface Code.FolderManagerItemInterface : Object { + public abstract string path { get; set; } + public abstract string name { get; set; } + public abstract string badge { get; set; } + public bool equal (FolderManagerItemInterface b) { + return path == b.path; + } - set { - text = value; - } + public virtual GLib.Menu? get_context_menu () { + GLib.FileInfo info = null; + var gfile = GLib.File.new_for_path (path); + try { + info = gfile.query_info (GLib.FileAttribute.STANDARD_CONTENT_TYPE, GLib.FileQueryInfoFlags.NONE); + } catch (Error e) { + warning (e.message); } - public string path { - owned get { return file.path; } - set { file.path = value; } + var file_type = info.get_content_type (); + + // external_actions_section holds submenus for "Open In" and "Other Actions" + // which involve other apps + var external_actions_section = new GLib.Menu (); + external_actions_section.append_item (create_submenu_for_open_item_in (gfile, file_type)); + var contractor_items = create_contract_items_for_file (gfile); + if (contractor_items.get_n_items () > 0) { + external_actions_section.append_submenu (_("Other Actions"), contractor_items); } - public signal void edited (string new_name); + var menu_model = new GLib.Menu (); + menu_model.append_section (null, external_actions_section); + var rename_menu_item = new GLib.MenuItem ( + _("Rename"), + GLib.Action.print_detailed_name ( + ITEM_ACTION_PREFIX + ACTION_RENAME_FOLDER, + new Variant.string (path) + ) + ); + + var delete_item = new GLib.MenuItem ( + _("Move to Trash"), + GLib.Action.print_detailed_name ( + ITEM_ACTION_PREFIX + ACTION_DELETE, + new Variant.string (path) + ) + ); + + return menu_model; + } + + public virtual GLib.MenuItem create_submenu_for_open_item_in (GLib.File gfile, string? file_type) { + //TODO Open in Terminal Pane only for projects + // var open_in_terminal_pane_item = new GLib.MenuItem ( + // (_("Terminal Pane")), + // GLib.Action.print_detailed_name ( + // Scratch.MainWindow.ACTION_PREFIX + Scratch.MainWindow.ACTION_OPEN_IN_TERMINAL, + // new Variant.string (file_path) + // ) + // ); + // open_in_terminal_pane_item.set_icon (new ThemedIcon ("panel-bottom-symbolic")); + + var file_path = gfile.get_path (); + var other_menu_item = new GLib.MenuItem ( + _("Other Application…"), + GLib.Action.print_detailed_name ( + ITEM_ACTION_PREFIX + ACTION_SHOW_APP_CHOOSER, + file_path + ) + ); + + var extra_section = new GLib.Menu (); + extra_section.append_item (other_menu_item); + + // var terminal_pane_section = new Menu (); + // terminal_pane_section.append_item (open_in_terminal_pane_item); + + file_type = file_type ?? "inode/directory"; + + var open_in_menu = new GLib.Menu (); + // open_in_menu.append_section (null, terminal_pane_section); + open_in_menu.append_section (null, create_executable_app_items_for_file ( + gfile, + file_type + )); + open_in_menu.append_section (null, extra_section); + + var open_in_menu_item = new GLib.MenuItem.submenu (_("Open In"), open_in_menu); + open_in_menu_item.set_link ("submenu", open_in_menu); // So overriding methods can modify + return open_in_menu_item; + } + + public GLib.Menu? create_executable_app_items_for_file (GLib.File file, string file_type) { + var scratch_app = (Scratch.Application) (GLib.Application.get_default ()); + var this_id = scratch_app.application_id + ".desktop"; + var menu = new GLib.Menu (); + + if (scratch_app.is_running_in_flatpak) { + var menu_item = new MenuItem ( + ///TRANSLATORS '%s' represents the quoted basename of a uri to be opened with the default app + _("Show '%s' with default app").printf (file.get_basename ()), + GLib.Action.print_detailed_name ( + ITEM_ACTION_PREFIX + ACTION_LAUNCH_APP_WITH_FILE_PATH, + new GLib.Variant.array ( + GLib.VariantType.STRING, + { file.get_path (), "" } + ) + ) + ); + menu.append_item (menu_item); + } else { + List external_apps = null; + if (file_type == "") { + var files_appinfo = AppInfo.get_default_for_type ("inode/directory", true); + external_apps.prepend (files_appinfo); + } else { + external_apps = GLib.AppInfo.get_all_for_type (file_type); + external_apps.sort ((a, b) => { + return a.get_name ().collate (b.get_name ()); + }); + } - construct { - is_selectable = true; - is_editable = true; - text = file.name; - icon = file.icon; - edited.connect (rename); - tooltip = Scratch.Utils.replace_home_with_tilde (file.path); + foreach (AppInfo app_info in external_apps) { + string app_id = app_info.get_id (); + if (app_id == this_id) { + continue; + } - notify["activatable-tooltip"].connect (() => { - tooltip = ("%s\n" + Granite.TOOLTIP_SECONDARY_TEXT_MARKUP).printf ( - Scratch.Utils.replace_home_with_tilde (file.path), - secondary_icon_tooltip + var menu_item = new MenuItem ( + app_info.get_name (), + GLib.Action.print_detailed_name ( + ITEM_ACTION_PREFIX + ACTION_LAUNCH_APP_WITH_FILE_PATH, + new GLib.Variant.array ( + GLib.VariantType.STRING, + { file.get_path (), app_id } + ) + ) ); - }); + menu_item.set_icon (app_info.get_icon ()); + menu.append_item (menu_item); + } } - protected void rename (string new_name) { - file.rename (new_name); - } + return menu; + } - public bool equal (Item b) { - return path == b.path; - } + public GLib.Menu create_contract_items_for_file (GLib.File file) { + var menu = new GLib.Menu (); - public void trash () { - file.trash (); - } + try { + var contracts = Granite.Services.ContractorProxy.get_contracts_for_file (file); + foreach (var contract in contracts) { + string contract_name = contract.get_display_name (); + var menu_item = new GLib.MenuItem ( + contract_name, + GLib.Action.print_detailed_name ( + ITEM_ACTION_PREFIX + ACTION_EXECUTE_CONTRACT_WITH_FILE_PATH, + new GLib.Variant.array ( + GLib.VariantType.STRING, + { file.get_path (), contract_name } + ) + ) + ); - public int compare (Code.TreeListItem a, Code.TreeListItem b) { - // if (a is RenameItem) { - // return -1; - // } else - // if (b is RenameItem) { - // return 1; - // } - - if (a is FolderItem && b is FileItem) { - return -1; - } else if (a is FileItem && b is FolderItem) { - return 1; + menu.append_item (menu_item); } + } catch (Error e) { + warning (e.message); + } + + return menu; + } +} + +public abstract class Code.FolderManagerItem: Code.TreeListItem, Code.FolderManagerItemInterface { + public signal void edited (string new_name); - assert (a is Item && b is Item); //Ensure more informative error message + public Code.File file { get; construct; } + public Code.FolderTree view { get; construct; } - return File.compare (((Item)a).file, ((Item)b).file); + // interface Code.FolderManagerItemInterface + public string name { + get { + return text; } - public bool allow_dnd_sorting () { - return false; + set { + text = value; } + } - public ProjectFolderItem? get_root_folder (Code.TreeListItem start = null) { - if (start == null) { - start = this; - } + public string path { get; set; } + public string badge { get; set; } - if (start is ProjectFolderItem) { - return start as ProjectFolderItem; - } else if (start.parent is ProjectFolderItem) { - return start.parent as ProjectFolderItem; - } else if (start.parent != null) { - return get_root_folder (start.parent); - } else { - return null; - } - } - // protected class RenameItem : Code.TreeListItem { - // public bool is_folder { get; construct; } - // public string name { get; set construct; } - - // public RenameItem (string name, bool is_folder) { - // Object ( - // name: name - // is_folder: is_folder - // ); - // } - - // construct { - // is_editable = true; - // is_selectable = true; - // edited.connect (on_edited); - - // if (is_folder) { - // icon = GLib.ContentType.get_icon ("inode/directory"); - // } else { - // icon = GLib.ContentType.get_icon ("text"); - // } - // } - - // private void on_edited (string new_name) { - // if (new_name != "") { - // name = new_name; - // } - // } + construct { + is_selectable = true; + is_editable = true; + text = file.name; + icon = file.icon; + edited.connect (rename); + tooltip = Scratch.Utils.replace_home_with_tilde (file.path); + + notify["activatable-tooltip"].connect (() => { + tooltip = ("%s\n" + Granite.TOOLTIP_SECONDARY_TEXT_MARKUP).printf ( + Scratch.Utils.replace_home_with_tilde (file.path), + secondary_icon_tooltip + ); + }); + + file.bind_property ("path", this, "path", SYNC_CREATE); + } + + protected void rename (string new_name) { + file.rename (new_name); + } + + public bool equal (FolderManagerItem b) { + return path == b.path; + } + + public void trash () { + file.trash (); + } + + public int compare (Code.TreeListItem a, Code.TreeListItem b) { + // if (a is RenameItem) { + // return -1; + // } else + // if (b is RenameItem) { + // return 1; // } + warning ("compare item"); + if (a is FolderItem && b is FileItem) { + return -1; + } else if (a is FileItem && b is FolderItem) { + return 1; + } + + assert (a is FolderManagerItem && b is FolderManagerItem); //Ensure more informative error message + + return File.compare (((FolderManagerItem)a).file, ((FolderManagerItem)b).file); + } - public virtual GLib.Menu? get_context_menu () { return null; } + public bool allow_dnd_sorting () { + return false; } + + public ProjectFolderItem? get_root_folder (Code.TreeListItem start = null) { + if (start == null) { + start = this; + } + + if (start is ProjectFolderItem) { + return start as ProjectFolderItem; + } else if (start.parent is ProjectFolderItem) { + return start.parent as ProjectFolderItem; + } else if (start.parent != null) { + return get_root_folder (start.parent); + } else { + return null; + } + } + + // protected class RenameItem : Code.TreeListItem { + // public bool is_folder { get; construct; } + // public string name { get; set construct; } + + // public RenameItem (string name, bool is_folder) { + // Object ( + // name: name + // is_folder: is_folder + // ); + // } + + // construct { + // is_editable = true; + // is_selectable = true; + // edited.connect (on_edited); + + // if (is_folder) { + // icon = GLib.ContentType.get_icon ("inode/directory"); + // } else { + // icon = GLib.ContentType.get_icon ("text"); + // } + // } + + // private void on_edited (string new_name) { + // if (new_name != "") { + // name = new_name; + // } + // } + // } } + diff --git a/src/FolderManager/ProjectFolderItem.vala b/src/FolderManager/ProjectFolderItem.vala index 3f9437c06c..68eb1d13de 100644 --- a/src/FolderManager/ProjectFolderItem.vala +++ b/src/FolderManager/ProjectFolderItem.vala @@ -1,688 +1,747 @@ /*- - * Copyright (c) 2018-2026 elementary LLC. (https://elementary.io), - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU Lesser General Public License version 3 - * as published by the Free Software Foundation, either version 3 of the - * License, or (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranties of - * MERCHANTABILITY, SATISFACTORY QUALITY, or FITNESS FOR A PARTICULAR - * PURPOSE. See the GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - * - * Authored by: David Hewitt - */ - -namespace Scratch.FolderManager { - public class ProjectFolderItem : FolderItem { - struct VisibleItem { - public string rel_path; - public Item item; +* Copyright (c) 2018-2026 elementary LLC. (https://elementary.io), +* +* This program is free software: you can redistribute it and/or modify +* it under the terms of the GNU Lesser General Public License version 3 +* as published by the Free Software Foundation, either version 3 of the +* License, or (at your option) any later version. +* +* This program is distributed in the hope that it will be useful, +* but WITHOUT ANY WARRANTY; without even the implied warranties of +* MERCHANTABILITY, SATISFACTORY QUALITY, or FITNESS FOR A PARTICULAR +* PURPOSE. See the GNU General Public License for more details. +* +* You should have received a copy of the GNU General Public License +* along with this program. If not, see . +* +* Authored by: David Hewitt +*/ + +// Top level items for ProjectList not derived from TreeListItem but sharing some interfaces +// public class Code.ProjectFolderItem : Object, Code.FolderInterface, Code.FolderManagerItemInterface { +public class Code.ProjectFolderItem : Object, Code.FolderInterface, Code.FolderManagerItemInterface { + public signal void deleted (); + public Scratch.Services.MonitoredRepository? monitored_repo { get; private set; default = null; } + public bool is_git_repo { + get { + return monitored_repo != null; } + } - private static Icon added_icon; - private static Icon modified_icon; - - - - public signal void closed (); + // Code.FolderManagerItemInterface + public string path { get; set; } + public string name { get; set; } + public string badge { get; set; } + + // Code.FolderItemInterface + public bool is_expanded { get; set; } + public FileMonitor monitor { get; set; } + public ListStore? child_model { get; set; default = null;} + + public Code.FolderTree folder_tree; + public Code.File file { get; construct; } + public Code.ProjectList view { get; construct; } + // For convenience + public GLib.File gfile { get { return file.file; }} + public string secondary_text { get; private set; } + + // private static Icon added_icon; + // private static Icon modified_icon; + // static construct { + // added_icon = new ThemedIcon ("emblem-git-new-symbolic"); + // modified_icon = new ThemedIcon ("emblem-git-modified-symbolic"); + // } + + private struct VisibleItem { + public string rel_path; + public FolderManagerItem item; + } - public Scratch.Services.MonitoredRepository? monitored_repo { get; private set; default = null; } - // Cache the visible item in the project. - private List visible_item_list = null; + private Scratch.Services.GitManager git_manager; - public bool is_git_repo { - get { - return monitored_repo != null; - } - } + // Cache the visible item in the project. (Excludes items loaded but in collapsed folder) + private List visible_item_list = null; + // Child widget shows tree of project files and folders - private Ggit.Repository? git_repo { - get { - return (is_git_repo ? monitored_repo.git_repo : null); - } + private Ggit.Repository? git_repo { + get { + return (is_git_repo ? monitored_repo.git_repo : null); } + } - public ProjectFolderItem (File file, FileView view) requires (file.is_valid_directory) { - Object (file: file, view: view); - } + public ProjectFolderItem (File file, Code.ProjectList view) requires (file.is_valid_directory) { + Object (file: file, view: view); + } - static construct { - added_icon = new ThemedIcon ("emblem-git-new-symbolic"); - modified_icon = new ThemedIcon ("emblem-git-modified-symbolic"); - } - - private void branch_or_name_changed () { - if (monitored_repo != null) { - //As SourceList items are not widgets we have to use markup to change appearance of text. - if (monitored_repo.head_is_branch) { - text = "%s\n%s".printf ( - name, monitored_repo.branch_name - ); - } else { //Distinguish detached heads visually - text = "%s\n %s".printf ( - name, monitored_repo.branch_name - ); - } - } + construct { + path = file.path; + name = file.name; + monitored_repo = Scratch.Services.GitManager.get_instance ().add_project (this); + git_manager = Scratch.Services.GitManager.get_instance (); + folder_tree = new Code.FolderTree (path); + file.notify["name"].connect (branch_or_name_changed); + if (monitored_repo != null) { + monitored_repo.branch_changed.connect (branch_or_name_changed); + // monitored_repo.ignored_changed.connect ((deprioritize_git_ignored)); + // monitored_repo.file_status_change.connect (() => update_item_status (null)); + monitored_repo.update_status_map (); + monitored_repo.branch_changed (); } + } - construct { - monitored_repo = Scratch.Services.GitManager.get_instance ().add_project (this); - notify["name"].connect (branch_or_name_changed); - if (monitored_repo != null) { - monitored_repo.branch_changed.connect (branch_or_name_changed); - monitored_repo.ignored_changed.connect ((deprioritize_git_ignored)); - monitored_repo.file_status_change.connect (() => update_item_status (null)); - monitored_repo.update_status_map (); - monitored_repo.branch_changed (); - } - } + public void refresh_diff ( + ref Gee.HashMap line_status_map, + string doc_path + ) { + monitored_repo.refresh_diff (doc_path, ref line_status_map); + } - protected override void on_changed (GLib.File source, GLib.File? dest, GLib.FileMonitorEvent event) { - if (source.equal (file.file) && event == DELETED) { - closed (); - } else { - base.on_changed (source, dest, event); - } - } - public void child_folder_changed (FolderItem folder) { - if (monitored_repo != null) { - monitored_repo.update_status_map (); - } + public void child_folder_changed (FolderItem folder) { + if (monitored_repo != null) { + monitored_repo.update_status_map (); } + } - public void after_child_folder_loaded (FolderItem folder) { - view.iterate_children (folder, (child) => { - if (child is Item) { - var item = (Item)child; - var rel_path = this.file.file.get_relative_path (item.file.file); - if (rel_path != null && rel_path != "") { - visible_item_list.prepend ({rel_path, item}); - } - } - - return Code.TreeList.ITERATE_CONTINUE; - }); - + //TODO Move to FolderTree + public void after_child_folder_loaded (FolderItem folder) { + // view.iterate_children (folder, (child) => { + // if (child is FolderManagerItem) { + // var item = (FolderManagerItem)child; + // var rel_path = this.file.file.get_relative_path (item.file.file); + // if (rel_path != null && rel_path != "") { + // visible_item_list.prepend ({rel_path, item}); + // } + // } + + // return Code.TreeList.ITERATE_CONTINUE; + // }); + + + // if (monitored_repo != null) { + // monitored_repo.update_status_map (); + // update_item_status (folder); + // deprioritize_git_ignored (); + // } + } - if (monitored_repo != null) { - monitored_repo.update_status_map (); - update_item_status (folder); - deprioritize_git_ignored (); + // warning ("returns a menu model"); + public override Menu? get_context_menu () { + string file_type = ""; + try { + var info = gfile.query_info (GLib.FileAttribute.STANDARD_CONTENT_TYPE, GLib.FileQueryInfoFlags.NONE); + if (info.has_attribute (GLib.FileAttribute.STANDARD_CONTENT_TYPE)) { + file_type = info.get_content_type (); } + } catch (Error e) { + warning (e.message); } - public override GLib.Menu? get_context_menu () { - string file_type = ""; - try { - var info = file.file.query_info (GLib.FileAttribute.STANDARD_CONTENT_TYPE, GLib.FileQueryInfoFlags.NONE); - if (info.has_attribute (GLib.FileAttribute.STANDARD_CONTENT_TYPE)) { - file_type = info.get_content_type (); - } - } catch (Error e) { - warning (e.message); - } - - MenuItem set_active_folder_item; - if (is_git_repo) { - set_active_folder_item = new GLib.MenuItem ( - _("Set as Active Project"), - GLib.Action.print_detailed_name ( - MainWindow.ACTION_PREFIX + MainWindow.ACTION_SET_ACTIVE_PROJECT, - new Variant.string (file.path) - ) - ); - } else { - set_active_folder_item = new GLib.MenuItem ( - _("Open in Terminal Pane"), - GLib.Action.print_detailed_name ( - MainWindow.ACTION_PREFIX + MainWindow.ACTION_OPEN_IN_TERMINAL, - new Variant.string ( - Services.GitManager.get_instance ().get_default_build_dir (path) - ) - ) - ); - } - - set_active_folder_item.set_attribute_value ( - "accel", - Utils.get_accel_for_action ( - GLib.Action.print_detailed_name ( - MainWindow.ACTION_PREFIX + MainWindow.ACTION_OPEN_IN_TERMINAL, - "" - ) - ) - ); - - var external_actions_section = new GLib.Menu (); - external_actions_section.append_item (set_active_folder_item); - external_actions_section.append_item (create_submenu_for_open_in (file_type)); - - var folder_actions_section = new GLib.Menu (); - folder_actions_section.append_item (create_submenu_for_new ()); - if (monitored_repo != null) { - var branch_action_item = new MenuItem ( - _("Branch Actions…"), - GLib.Action.print_detailed_name ( - MainWindow.ACTION_PREFIX + MainWindow.ACTION_BRANCH_ACTIONS, - new Variant.string (file.path) - ) - ); - folder_actions_section.append_item (branch_action_item); - } - - var close_folder_item = new GLib.MenuItem ( - _("Close Folder"), - GLib.Action.print_detailed_name ( - FileView.ACTION_PREFIX + FileView.ACTION_CLOSE_FOLDER, - new Variant.string (file.path) - ) - ); - - var close_all_except_item = new GLib.MenuItem ( - _("Close Other Folders"), + MenuItem set_active_folder_item; + if (is_git_repo) { + set_active_folder_item = new GLib.MenuItem ( + _("Set as Active Project"), GLib.Action.print_detailed_name ( - FileView.ACTION_PREFIX + FileView.ACTION_CLOSE_OTHER_FOLDERS, + Scratch.MainWindow.ACTION_PREFIX + Scratch.MainWindow.ACTION_SET_ACTIVE_PROJECT, new Variant.string (file.path) ) ); - var close_other_folders_action = Utils.action_from_group ( - FileView.ACTION_CLOSE_OTHER_FOLDERS, - view.actions - ); - close_other_folders_action.set_enabled (view.n_root_items () > 1); - - var close_actions_section = new GLib.Menu (); - close_actions_section.append_item (close_folder_item); - close_actions_section.append_item (close_all_except_item); - - var n_open = Scratch.Services.DocumentManager.get_instance ().open_for_project (path); - var open_text = ngettext ("Close %u Open Document", - "Close %u Open Documents", - n_open).printf (n_open); - - var close_item = new GLib.MenuItem ( - open_text, + } else { + set_active_folder_item = new GLib.MenuItem ( + _("Open in Terminal Pane"), GLib.Action.print_detailed_name ( - MainWindow.ACTION_PREFIX + MainWindow.ACTION_CLOSE_PROJECT_DOCS, - new Variant.string (file.file.get_path ()) - ) - ); - - var hide_text = ngettext ("Hide %u Open Document", - "Hide %u Open Documents", - n_open).printf (n_open); - - var hide_item = new GLib.MenuItem ( - hide_text, - GLib.Action.print_detailed_name ( - MainWindow.ACTION_PREFIX + MainWindow.ACTION_HIDE_PROJECT_DOCS, - new Variant.string (file.file.get_path ()) - ) - ); - - hide_item.set_attribute_value ( - "accel", - Utils.get_accel_for_action ( - GLib.Action.print_detailed_name ( - MainWindow.ACTION_PREFIX + MainWindow.ACTION_HIDE_PROJECT_DOCS, - "" + Scratch.MainWindow.ACTION_PREFIX + Scratch.MainWindow.ACTION_OPEN_IN_TERMINAL, + new Variant.string ( + Scratch.Services.GitManager.get_instance ().get_default_build_dir (path) ) ) ); + } - var n_restorable = Scratch.Services.DocumentManager.get_instance ().restorable_for_project (path); - var restore_text = ngettext ("Restore %u Hidden Document", - "Restore %u Hidden Documents", - n_restorable).printf (n_restorable); - - var restore_item = new GLib.MenuItem ( - restore_text, + set_active_folder_item.set_attribute_value ( + "accel", + Scratch.Utils.get_accel_for_action ( GLib.Action.print_detailed_name ( - MainWindow.ACTION_PREFIX + MainWindow.ACTION_RESTORE_PROJECT_DOCS, - new Variant.string (file.file.get_path ()) + Scratch.MainWindow.ACTION_PREFIX + Scratch.MainWindow.ACTION_OPEN_IN_TERMINAL, + "" ) - ); + ) + ); + + var new_window_menu_item = new GLib.MenuItem ( + _("Open in New Window"), + GLib.Action.print_detailed_name ( + Scratch.MainWindow.ACTION_PREFIX + Scratch.MainWindow.ACTION_OPEN_IN_NEW_WINDOW, + path + ) + ); + + var external_actions_section = new GLib.Menu (); + external_actions_section.append_item (set_active_folder_item); + external_actions_section.append_item (new_window_menu_item); + + var folder_actions_section = new GLib.Menu (); + folder_actions_section.append_item (create_submenu_for_new (path)); + + // if (monitored_repo != null) { + // var branch_action_item = new MenuItem ( + // _("Branch Actions…"), + // GLib.Action.print_detailed_name ( + // Scratch.MainWindow.ACTION_PREFIX + Scratch.MainWindow.ACTION_BRANCH_ACTIONS, + // new Variant.string (file.path) + // ) + // ); + // folder_actions_section.append_item (branch_action_item); + // } + + var close_folder_item = new GLib.MenuItem ( + _("Close Folder"), + GLib.Action.print_detailed_name ( + PROJECT_ACTION_PREFIX + ACTION_CLOSE_PROJECT_FOLDER, + new Variant.string (file.path) + ) + ); + + var close_all_except_item = new GLib.MenuItem ( + _("Close Other Folders"), + GLib.Action.print_detailed_name ( + PROJECT_ACTION_PREFIX + ACTION_CLOSE_OTHER_PROJECT_FOLDERS, + new Variant.string (file.path) + ) + ); + var close_other_folders_action = Scratch.Utils.action_from_group ( + ACTION_CLOSE_OTHER_PROJECT_FOLDERS, + view.actions + ); + close_other_folders_action.set_enabled (!view.is_empty); + + var close_actions_section = new GLib.Menu (); + close_actions_section.append_item (close_folder_item); + close_actions_section.append_item (close_all_except_item); + + // var n_open = Scratch.Services.DocumentManager.get_instance ().open_for_project (path); + // var open_text = ngettext ("Close %u Open Document", + // "Close %u Open Documents", + // n_open).printf (n_open); + + // var close_item = new GLib.MenuItem ( + // open_text, + // GLib.Action.print_detailed_name ( + // Scratch.MainWindow.ACTION_PREFIX + Scratch.MainWindow.ACTION_CLOSE_PROJECT_DOCS, + // new Variant.string (file.file.get_path ()) + // ) + // ); + + // var hide_text = ngettext ("Hide %u Open Document", + // "Hide %u Open Documents", + // n_open).printf (n_open); + + // var hide_item = new GLib.MenuItem ( + // hide_text, + // GLib.Action.print_detailed_name ( + // Scratch.MainWindow.ACTION_PREFIX + Scratch.MainWindow.ACTION_HIDE_PROJECT_DOCS, + // new Variant.string (file.file.get_path ()) + // ) + // ); + + // hide_item.set_attribute_value ( + // "accel", + // Scratch.Utils.get_accel_for_action ( + // GLib.Action.print_detailed_name ( + // Scratch.MainWindow.ACTION_PREFIX + Scratch.MainWindow.ACTION_HIDE_PROJECT_DOCS, + // "" + // ) + // ) + // ); + + // var n_restorable = Scratch.Services.DocumentManager.get_instance ().restorable_for_project (path); + // var restore_text = ngettext ("Restore %u Hidden Document", + // "Restore %u Hidden Documents", + // n_restorable).printf (n_restorable); + + // var restore_item = new GLib.MenuItem ( + // restore_text, + // GLib.Action.print_detailed_name ( + // Scratch.MainWindow.ACTION_PREFIX + Scratch.MainWindow.ACTION_RESTORE_PROJECT_DOCS, + // new Variant.string (file.file.get_path ()) + // ) + // ); + + // restore_item.set_attribute_value ( + // "accel", + // Scratch.Utils.get_accel_for_action ( + // GLib.Action.print_detailed_name ( + // Scratch.MainWindow.ACTION_PREFIX + Scratch.MainWindow.ACTION_RESTORE_PROJECT_DOCS, + // "" + // ) + // ) + // ); + + // var direct_actions_section = new GLib.Menu (); + // if (n_restorable > 0) { + // direct_actions_section.append_item (restore_item); + // } + + // if (n_open > 0) { + // direct_actions_section.append_item (hide_item); + // direct_actions_section.append_item (close_item); + // } + + // var search_item = new GLib.MenuItem ( + // _("Find in Project…"), + // GLib.Action.print_detailed_name ( + // Scratch.MainWindow.ACTION_PREFIX + Scratch.MainWindow.ACTION_FIND_GLOBAL, + // new Variant.string (file.file.get_path ()) + // ) + // ); + + // search_item.set_attribute_value ( + // "accel", + // Scratch.Utils.get_accel_for_action ( + // GLib.Action.print_detailed_name ( + // Scratch.MainWindow.ACTION_PREFIX + Scratch.MainWindow.ACTION_FIND_GLOBAL, + // "" + // ) + // ) + // ); + + // var search_actions_section = new GLib.Menu (); + // search_actions_section.append_item (search_item); + + var menu_model = new GLib.Menu (); + menu_model.append_section (null, external_actions_section); + menu_model.append_section (null, folder_actions_section); + menu_model.append_section (null, close_actions_section); + // menu_model.append_section (null, direct_actions_section); + // menu_model.append_section (null, search_actions_section); + + return menu_model; + + // return null; + } - restore_item.set_attribute_value ( - "accel", - Utils.get_accel_for_action ( - GLib.Action.print_detailed_name ( - MainWindow.ACTION_PREFIX + MainWindow.ACTION_RESTORE_PROJECT_DOCS, - "" - ) - ) - ); + public void update_item_status () { + // var status = folder_tree.update_status (); + //TODO update appearance according to global status + } + // public void update_item_status (FolderItem? start_folder) { + // if (monitored_repo == null) { + // debug ("Ignore non-git folders"); + // return; + // } + // bool is_new = false; + // string start_path = start_folder != null ? start_folder.path : ""; + // visible_item_list.@foreach ((visible_item) => { + // if (start_path.has_prefix (visible_item.rel_path)) { + // return; //Only need to update status for start_folder and its children + // } + + // var item = visible_item.item; + // // item.secondary_icon = null; + // monitored_repo.non_current_entries.@foreach ((entry) => { + // // Match non_current_path with parent folder as well as itself + // var match = entry.key.has_prefix (visible_item.rel_path); + // if (match) { + // is_new = (entry.@value & (Ggit.StatusFlags.WORKING_TREE_NEW | Ggit.StatusFlags.INDEX_NEW)) > 0; + // // Only mark folders new if only contains new items otherwise mark modified + // if (item is FolderItem && + // is_new && item.secondary_icon == null) { + + // item.secondary_icon = added_icon; + // item.secondary_icon_tooltip = _("New"); + // return true; // scan all children + // } + + // if (!(item is FolderItem) || !item.is_expanded) { //No need to show status when children shown + // item.secondary_icon = is_new ? added_icon : modified_icon; + // item.secondary_icon_tooltip = is_new ? _("New") : _("Modified"); + // } + // return false; + // } else { + // return true; + // } + // }); + // }); + // } + + // This assumes the descendant exists + public bool contains_file (GLib.File descendant) { + return file.file.get_relative_path (descendant) != null; + } - var direct_actions_section = new GLib.Menu (); - if (n_restorable > 0) { - direct_actions_section.append_item (restore_item); + // private void deprioritize_git_ignored () requires (monitored_repo != null) { + // visible_item_list.@foreach ((visible_item) => { + // var item = visible_item.item; + // try { + // if (monitored_repo.path_is_ignored (visible_item.rel_path)) { + // deprioritize = true; + // } else { + + // } + // } catch (Error e) { + // warning ("An error occurred while checking if item '%s' is git-ignored: %s", item.name, e.message); + // } + // }); + // } + + public bool checkout_branch_ref (Ggit.Ref branch_ref) { + if (branch_ref.is_branch ()) { + try { + var branch_name = ((Ggit.Branch)branch_ref).get_name (); + if (branch_name != null) { + return monitored_repo.change_local_branch (branch_name); + } else { + return false; + } + } catch (Error e) { + warning ("Failed to get branch name from ref. %s", e.message); + return false; } - - if (n_open > 0) { - direct_actions_section.append_item (hide_item); - direct_actions_section.append_item (close_item); + } else { + var target_shorthand = branch_ref.get_shorthand (); + if (target_shorthand != null) { + return monitored_repo.checkout_remote_branch (target_shorthand); + } else { + return false; } - - var search_item = new GLib.MenuItem ( - _("Find in Project…"), - GLib.Action.print_detailed_name ( - MainWindow.ACTION_PREFIX + MainWindow.ACTION_FIND_GLOBAL, - new Variant.string (file.file.get_path ()) - ) - ); - - search_item.set_attribute_value ( - "accel", - Utils.get_accel_for_action ( - GLib.Action.print_detailed_name ( - MainWindow.ACTION_PREFIX + MainWindow.ACTION_FIND_GLOBAL, - "" - ) - ) - ); - - var search_actions_section = new GLib.Menu (); - search_actions_section.append_item (search_item); - - var menu_model = new GLib.Menu (); - menu_model.append_section (null, external_actions_section); - menu_model.append_section (null, folder_actions_section); - menu_model.append_section (null, close_actions_section); - menu_model.append_section (null, direct_actions_section); - menu_model.append_section (null, search_actions_section); - - return menu_model; } + } - public void update_item_status (FolderItem? start_folder) { - if (monitored_repo == null) { - debug ("Ignore non-git folders"); - return; + public void new_branch (string branch_name) { + try { + if (monitored_repo.head_is_branch) { + monitored_repo.create_new_branch (branch_name); + } else { + throw new IOError.NOT_FOUND ("Cannot create a new branch when head is detached"); } - bool is_new = false; - string start_path = start_folder != null ? start_folder.path : ""; - visible_item_list.@foreach ((visible_item) => { - if (start_path.has_prefix (visible_item.rel_path)) { - return; //Only need to update status for start_folder and its children - } - - var item = visible_item.item; - item.secondary_icon = null; - monitored_repo.non_current_entries.@foreach ((entry) => { - // Match non_current_path with parent folder as well as itself - var match = entry.key.has_prefix (visible_item.rel_path); - if (match) { - is_new = (entry.@value & (Ggit.StatusFlags.WORKING_TREE_NEW | Ggit.StatusFlags.INDEX_NEW)) > 0; - // Only mark folders new if only contains new items otherwise mark modified - if (item is FolderItem && - is_new && item.secondary_icon == null) { - - item.secondary_icon = added_icon; - item.secondary_icon_tooltip = _("New"); - return true; // scan all children - } - - if (!(item is FolderItem) || !item.is_expanded) { //No need to show status when children shown - item.secondary_icon = is_new ? added_icon : modified_icon; - item.secondary_icon_tooltip = is_new ? _("New") : _("Modified"); - } - return false; - } else { - return true; - } - }); - }); - } - - public bool contains_file (GLib.File descendant) { - return file.file.get_relative_path (descendant) != null; - } - - private void deprioritize_git_ignored () requires (monitored_repo != null) { - visible_item_list.@foreach ((visible_item) => { - var item = visible_item.item; - try { - if (monitored_repo.path_is_ignored (visible_item.rel_path)) { - item.text = Markup.printf_escaped ("%s", item.name); - } else { - item.text = item.name; - } - } catch (Error e) { - warning ("An error occurred while checking if item '%s' is git-ignored: %s", item.name, e.message); - } + } catch (Error e) { + var dialog = new Granite.MessageDialog ( + _("Error while creating new branch: “%s”").printf (branch_name), + e.message, + new ThemedIcon ("git"), + Gtk.ButtonsType.CLOSE + ) { + badge_icon = new ThemedIcon ("dialog-error"), + modal = true + }; + dialog.transient_for = (Gtk.Window)(view.get_root ()); + dialog.response.connect (() => { + dialog.destroy (); }); + dialog.show (); } + } - public bool checkout_branch_ref (Ggit.Ref branch_ref) { - if (branch_ref.is_branch ()) { - try { - var branch_name = ((Ggit.Branch)branch_ref).get_name (); - if (branch_name != null) { - return monitored_repo.change_local_branch (branch_name); - } else { - return false; - } - } catch (Error e) { - warning ("Failed to get branch name from ref. %s", e.message); - return false; - } - } else { - var target_shorthand = branch_ref.get_shorthand (); - if (target_shorthand != null) { - return monitored_repo.checkout_remote_branch (target_shorthand); - } else { - return false; - } - } - } + public bool is_recent_ref (Ggit.Ref bref) { + return monitored_repo.is_recent_ref (bref); + } - public void new_branch (string branch_name) { - try { - if (monitored_repo.head_is_branch) { - monitored_repo.create_new_branch (branch_name); - } else { - throw new IOError.NOT_FOUND ("Cannot create a new branch when head is detached"); - } - } catch (Error e) { - var dialog = new Granite.MessageDialog ( - _("Error while creating new branch: “%s”").printf (branch_name), - e.message, - new ThemedIcon ("git"), - Gtk.ButtonsType.CLOSE - ) { - badge_icon = new ThemedIcon ("dialog-error"), - modal = true - }; - dialog.transient_for = (Gtk.Window)(view.get_root ()); - dialog.response.connect (() => { - dialog.destroy (); - }); - dialog.show (); - } - } + public Gee.ArrayList get_all_branch_refs () requires (is_git_repo) { + return monitored_repo.get_all_branch_refs (); + } - public bool is_recent_ref (Ggit.Ref bref) { - return monitored_repo.is_recent_ref (bref); - } + public bool has_local_branch_name (string name) { + return is_git_repo ? monitored_repo.has_local_branch_name (name) : false; + } - public Gee.ArrayList get_all_branch_refs () requires (is_git_repo) { - return monitored_repo.get_all_branch_refs (); - } + public bool has_remote_branch_name (string name) { + return is_git_repo ? monitored_repo.has_remote_branch_name (name) : false; + } - public bool has_local_branch_name (string name) { - return is_git_repo ? monitored_repo.has_local_branch_name (name) : false; + public bool has_branch_name (string name, out bool found_is_remote) { + found_is_remote = false; + // var is_remote = false; + var found = false; + if (!has_local_branch_name (name)) { + found = has_remote_branch_name (name); + found_is_remote = found; + } else { + found = true; } - public bool has_remote_branch_name (string name) { - return is_git_repo ? monitored_repo.has_remote_branch_name (name) : false; - } + return found; + } - public bool has_branch_name (string name, out bool found_is_remote) { - found_is_remote = false; - // var is_remote = false; - var found = false; - if (!has_local_branch_name (name)) { - found = has_remote_branch_name (name); - found_is_remote = found; - } else { - found = true; - } + public string get_current_branch_name () { + return is_git_repo ? monitored_repo.branch_name : ""; + } - return found; - } + public bool is_valid_new_branch_name (string new_name) { + return is_git_repo ? monitored_repo.is_valid_new_local_branch_name (new_name) : false; + } - public string get_current_branch_name () { - return is_git_repo ? monitored_repo.branch_name : ""; + // The parameter "is_explicit" indicates whether a global search was requested + // via a context menu on an explicitly chosen folder, in which case everything in that + // folder will be searched, or whether the hot-key was used in which case the search will + // take place on the active project and will omit certain folders + public async void global_search ( + GLib.File start_folder = this.file.file, + string? term = null, + bool is_explicit = false + ) { + /* For now set all options to the most inclusive (except case). + * The ability to set these in the dialog (or by parameter) may be added later. */ + string? search_term = null; + bool search_tracked_only = false; + bool recurse_subfolders = true; + bool check_is_text = true; + string[] path_spec = {"*.*"}; + bool modified_only = false; + bool case_sensitive = false; + Regex? pattern = null; + + var wholeword_search = Scratch.settings.get_boolean ("wholeword-search"); + var case_mode = (Scratch.CaseSensitiveMode)(Scratch.settings.get_enum ("case-sensitive-search")); + var use_regex = Scratch.settings.get_boolean ("regex-search"); + + switch (case_mode) { + case NEVER: + case_sensitive = false; + break; + case MIXED: + case_sensitive = (term != term.ascii_up () && term != term.ascii_down ()); + break; + case ALWAYS: + case_sensitive = true; + break; + default: + assert_not_reached (); } - public bool is_valid_new_branch_name (string new_name) { - return is_git_repo ? monitored_repo.is_valid_new_local_branch_name (new_name) : false; + var folder_name = start_folder.get_basename (); + if (this.file.file.equal (start_folder)) { + folder_name = name; } - // The parameter "is_explicit" indicates whether a global search was requested - // via a context menu on an explicitly chosen folder, in which case everything in that - // folder will be searched, or whether the hot-key was used in which case the search will - // take place on the active project and will omit certain folders - public async void global_search ( - GLib.File start_folder = this.file.file, - string? term = null, - bool is_explicit = false + var dialog = new Scratch.Dialogs.GlobalSearchDialog ( + folder_name, + monitored_repo != null && monitored_repo.git_repo != null, + case_sensitive, + wholeword_search, + use_regex ) { - /* For now set all options to the most inclusive (except case). - * The ability to set these in the dialog (or by parameter) may be added later. */ - string? search_term = null; - bool search_tracked_only = false; - bool recurse_subfolders = true; - bool check_is_text = true; - string[] path_spec = {"*.*"}; - bool modified_only = false; - bool case_sensitive = false; - Regex? pattern = null; - - var wholeword_search = Scratch.settings.get_boolean ("wholeword-search"); - var case_mode = (CaseSensitiveMode)(Scratch.settings.get_enum ("case-sensitive-search")); - var use_regex = Scratch.settings.get_boolean ("regex-search"); - - switch (case_mode) { - case NEVER: - case_sensitive = false; - break; - case MIXED: - case_sensitive = (term != term.ascii_up () && term != term.ascii_down ()); - break; - case ALWAYS: - case_sensitive = true; + search_term = term + }; + + dialog.response.connect ((response) => { + switch (response) { + case Gtk.ResponseType.ACCEPT: + search_term = dialog.search_term; break; - default: - assert_not_reached (); - } - var folder_name = start_folder.get_basename (); - if (this.file.file.equal (start_folder)) { - folder_name = name; + default: + term = null; + break; } - var dialog = new Scratch.Dialogs.GlobalSearchDialog ( - folder_name, - monitored_repo != null && monitored_repo.git_repo != null, - case_sensitive, - wholeword_search, - use_regex - ) { - search_term = term - }; - - dialog.response.connect ((response) => { - switch (response) { - case Gtk.ResponseType.ACCEPT: - search_term = dialog.search_term; - break; + dialog.destroy (); + global_search.callback (); + }); + + dialog.show (); + yield; + + if (search_term != null) { + // Remove results of previous search before attempting a new one + folder_tree.clear_badges (); + // Collapse everything + collapse (); + + /* Put search term in search bar to help user locate the position of the matches in each doc */ + var search_variant = new Variant.string (search_term); + var app = (Gtk.Application)GLib.Application.get_default (); + var win = (Scratch.MainWindow)(app.get_active_window ()); + win.actions.lookup_action ("action-find").activate (search_variant); + + if (!use_regex) { + search_term = Regex.escape_string (search_term); + if (wholeword_search) { + search_term = "\\b%s\\b".printf (search_term); + } + } // else use search_term as is - TODO do we need to escape it? - default: - term = null; - break; + try { + var flags = RegexCompileFlags.MULTILINE; + if (!case_sensitive) { + flags |= RegexCompileFlags.CASELESS; } - dialog.destroy (); - global_search.callback (); - }); + pattern = new Regex (search_term, flags); + } catch (Error e) { + critical ("Error creating regex from '%s': %s", search_term, e.message); + return; + } + } else { + return; + } - dialog.show (); - yield; - - if (search_term != null) { - // Remove results of previous search before attempting a new one - remove_all_badges (); - // Collapse everything - collapse_all (true, true); - - /* Put search term in search bar to help user locate the position of the matches in each doc */ - var search_variant = new Variant.string (search_term); - var app = (Gtk.Application)GLib.Application.get_default (); - var win = (Scratch.MainWindow)(app.get_active_window ()); - win.actions.lookup_action ("action-find").activate (search_variant); - - if (!use_regex) { - search_term = Regex.escape_string (search_term); - if (wholeword_search) { - search_term = "\\b%s\\b".printf (search_term); - } - } // else use search_term as is - TODO do we need to escape it? + var status_scope = Ggit.StatusOption.DEFAULT; + if (!modified_only) { + status_scope |= Ggit.StatusOption.INCLUDE_UNMODIFIED; + } + if (!search_tracked_only) { + status_scope |= Ggit.StatusOption.INCLUDE_UNTRACKED; + } + var status_options = new Ggit.StatusOptions ( + status_scope, + Ggit.StatusShow.WORKDIR_ONLY, + path_spec + ); - try { - var flags = RegexCompileFlags.MULTILINE; - if (!case_sensitive) { - flags |= RegexCompileFlags.CASELESS; + if (monitored_repo != null && !is_explicit) { + try { + monitored_repo.git_repo.file_status_foreach (status_options, (rel_path, status) => { + var target = file.file.resolve_relative_path (rel_path); + if (check_is_text && rel_path.has_prefix ("po/")) { // Ignore translation files + return 0; } - pattern = new Regex (search_term, flags); - } catch (Error e) { - critical ("Error creating regex from '%s': %s", search_term, e.message); - return; - } - } else { - return; - } + if ((recurse_subfolders && start_folder.get_relative_path (target) != null) || + start_folder.equal (target.get_parent ())) { - var status_scope = Ggit.StatusOption.DEFAULT; - if (!modified_only) { - status_scope |= Ggit.StatusOption.INCLUDE_UNMODIFIED; - } - if (!search_tracked_only) { - status_scope |= Ggit.StatusOption.INCLUDE_UNTRACKED; + perform_match (target, pattern, check_is_text); + } + + return 0; //TODO Allow cancelling? + }); + } catch (Error err) { + warning ("Error getting file status: %s", err.message); } - var status_options = new Ggit.StatusOptions ( - status_scope, - Ggit.StatusShow.WORKDIR_ONLY, - path_spec - ); + } else { + search_folder_children (start_folder, pattern, recurse_subfolders); + } - if (monitored_repo != null && !is_explicit) { - try { - monitored_repo.git_repo.file_status_foreach (status_options, (rel_path, status) => { - var target = file.file.resolve_relative_path (rel_path); - if (check_is_text && rel_path.has_prefix ("po/")) { // Ignore translation files - return 0; - } + return; + } - if ((recurse_subfolders && start_folder.get_relative_path (target) != null) || - start_folder.equal (target.get_parent ())) { + public void set_as_active_project () { + git_manager.active_project_path = path; + } - perform_match (target, pattern, check_is_text); - } + public void collapse () { + is_expanded = false; + } - return 0; //TODO Allow cancelling? - }); - } catch (Error err) { - warning ("Error getting file status: %s", err.message); - } - } else { - search_folder_children (start_folder, pattern, recurse_subfolders); - } + public void expand () { + is_expanded = true; + } - return; + public FolderManagerItem? find_path (string path, bool expand) { + return folder_tree.find_path (null, path, expand); + } + // Does not remove from liststore - that is up to ProjectList + public void close () { + folder_tree.remove_all (); + git_manager.remove_project (this); // Takes care of active_project? + } + protected override void on_changed (GLib.File source, GLib.File? dest, GLib.FileMonitorEvent event) { + if (source.equal (file.file) && event == DELETED) { + deleted (); } + } - private void search_folder_children (GLib.File start_folder, Regex pattern, bool recurse_subfolders) { - try { - var enumerator = start_folder.enumerate_children ( - FileAttribute.STANDARD_CONTENT_TYPE + "," + FileAttribute.STANDARD_TYPE, - FileQueryInfoFlags.NOFOLLOW_SYMLINKS, - null - ); - - unowned FileInfo info = null; - unowned GLib.File child = null; - while (enumerator.iterate (out info, out child, null) && info != null) { - if (info != null && info.has_attribute (FileAttribute.STANDARD_TYPE)) { - if (info.get_file_type () == FileType.DIRECTORY) { - if (recurse_subfolders) { - search_folder_children (child, pattern, recurse_subfolders); - } - } else { - perform_match (child, pattern, true, info); + private void search_folder_children (GLib.File start_folder, Regex pattern, bool recurse_subfolders) { + try { + var enumerator = start_folder.enumerate_children ( + FileAttribute.STANDARD_CONTENT_TYPE + "," + FileAttribute.STANDARD_TYPE, + FileQueryInfoFlags.NOFOLLOW_SYMLINKS, + null + ); + + unowned FileInfo info = null; + unowned GLib.File child = null; + while (enumerator.iterate (out info, out child, null) && info != null) { + if (info != null && info.has_attribute (FileAttribute.STANDARD_TYPE)) { + if (info.get_file_type () == FileType.DIRECTORY) { + if (recurse_subfolders) { + search_folder_children (child, pattern, recurse_subfolders); } + } else { + perform_match (child, pattern, true, info); } } - } catch (Error enumerate_error) { - warning ("Error enumerating children of %s: %s", start_folder.get_path (), enumerate_error.message); } + } catch (Error enumerate_error) { + warning ("Error enumerating children of %s: %s", start_folder.get_path (), enumerate_error.message); } + } - private void perform_match (GLib.File target, - Regex search_regex, - bool check_is_text = false, - FileInfo? target_info = null) { - string contents; - string target_path = target.get_path (); - if (check_is_text) { - FileInfo? info = null; - if (target_info == null) { - try { - info = target.query_info ( - FileAttribute.STANDARD_CONTENT_TYPE, - FileQueryInfoFlags.NOFOLLOW_SYMLINKS, - null - ); - } catch (Error query_error) { - warning ( - "Error getting file info for %s: %s. Ignoring.", target.get_path (), query_error.message - ); - } - } else { - info = target_info; - } - - if (info == null) { - return; + private void perform_match (GLib.File target, + Regex search_regex, + bool check_is_text = false, + FileInfo? target_info = null) { + string contents; + string target_path = target.get_path (); + if (check_is_text) { + FileInfo? info = null; + if (target_info == null) { + try { + info = target.query_info ( + FileAttribute.STANDARD_CONTENT_TYPE, + FileQueryInfoFlags.NOFOLLOW_SYMLINKS, + null + ); + } catch (Error query_error) { + warning ( + "Error getting file info for %s: %s. Ignoring.", target.get_path (), query_error.message + ); } + } else { + info = target_info; + } - var type = info.get_content_type (); - if (!ContentType.is_mime_type (type, "text/*") || - ContentType.is_mime_type (type, "image/*")) { //Do not search svg images - return; - } + if (info == null) { + return; } - try { - FileUtils.get_contents (target_path, out contents); - } catch (Error e) { - warning ("error getting contents: %s", e.message); + var type = info.get_content_type (); + if (!ContentType.is_mime_type (type, "text/*") || + ContentType.is_mime_type (type, "image/*")) { //Do not search svg images return; } + } - MatchInfo? match_info = null; - int match_count = 0; - try { - for (search_regex.match (contents, RegexMatchFlags.NOTEMPTY, out match_info); - match_info.matches (); - match_info.next ()) { + try { + FileUtils.get_contents (target_path, out contents); + } catch (Error e) { + warning ("error getting contents: %s", e.message); + return; + } - match_count++; - } - } catch (RegexError next_error) { - critical ("Error getting next match: %s", next_error.message); - } + MatchInfo? match_info = null; + int match_count = 0; + try { + for (search_regex.match (contents, RegexMatchFlags.NOTEMPTY, out match_info); + match_info.matches (); + match_info.next ()) { - if (match_count > 0) { - var item = view.expand_to_path (target_path); - if (item != null) { - item.badge = match_count.to_string (); - } + match_count++; } + } catch (RegexError next_error) { + critical ("Error getting next match: %s", next_error.message); + } - return; + if (match_count > 0) { + var item = view.expand_to_path (target_path); + if (item != null) { + item.badge = match_count.to_string (); + } } - public void refresh_diff (ref Gee.HashMap line_status_map, string doc_path) { - monitored_repo.refresh_diff (doc_path, ref line_status_map); + return; + } + + private void branch_or_name_changed () { + name = file.name; + if (monitored_repo != null) { + //As SourceList items are not widgets we have to use markup to change appearance of text. + if (monitored_repo.head_is_branch) { + secondary_text = monitored_repo.branch_name; + } else { //Distinguish detached heads visually + secondary_text = _("%s (detached)").printf (monitored_repo.branch_name); + } } } } + diff --git a/src/FolderManager/ProjectList.vala b/src/FolderManager/ProjectList.vala new file mode 100644 index 0000000000..aaeab360fc --- /dev/null +++ b/src/FolderManager/ProjectList.vala @@ -0,0 +1,852 @@ +/* + * SPDX-License-Identifier: GPL-3.0-or-later + * SPDX-FileCopyrightText: 2026 elementary, Inc. + */ + +// //TODO Can we just use ProjectFolderItems direct? +// public class Code.ProjectListItem : Object { +// public ProjectFolderItem project_folder { get; set construct; } +// public ProjectList view { get; set construct; } // Needed to activate actions +// public string path { get; construct; } +// public GLib.File gfile { get { return project_folder.file.file; }} + +// private Code.FolderTree folder_tree; +// private Scratch.Services.GitManager git_manager; +// private bool is_expanded { get; set; } +// private bool is_active_project { get { return git_manager.active_project_path == path; } } + +// public ProjectListItem (string path, ProjectList view) { +// Object ( +// path: path, +// view: view +// ); +// } + +// construct { +// folder_tree = new FolderTree (path); +// git_manager = Scratch.Services.GitManager.get_instance (); + +// // ProjectFolderItem constructor adds project to GitManager +// var project_folder = new ProjectFolderItem (new Code.File (path), folder_tree); + +// // Closed signal emitted when project folder is externally deleted +// project_folder.deleted.connect (() => { +// folder_tree.remove_all (); +// view.item_deleted (this); +// }); + +// } + +// public bool is_or_contains_file (GLib.File gfile) { +// return path == gfile.get_path () || folder_tree.contains_file (gfile); +// } + +// public void collapse_all () { +// // folder_tree.collapse_all (); +// is_expanded = false; +// } + +// public void expand_all () { +// // folder_tree.expand_all (); +// is_expanded = true; +// } + +// // Does not remove from liststore - that is up to ProjectList +// public void close () { +// folder_tree.remove_all (); +// git_manager.remove_project (project_folder); // Takes care of active_project? +// } + +// public void set_as_active_project () { +// git_manager.active_project_path = path; +// } + +// public FolderManagerItem? find_path ( +// string path, // File path to search fod +// bool expand = false // Whether to expsnd to show found item +// // GLib.File? target_file = null // Alternatively find this file +// ) { +// return folder_tree.find_path (null, path, expand); +// } +// } + +namespace Code { + // All project related actions handled in ProjectFolderItem + public const string PROJECT_ACTION_GROUP = "project"; + public const string PROJECT_ACTION_PREFIX = "project."; + public const string ACTION_CLOSE_PROJECT_FOLDER = "close-project-folder"; + public const string ACTION_CLOSE_OTHER_PROJECT_FOLDERS = "close-other-project-folders"; + public const string ACTION_EXECUTE_CONTRACT_WITH_FILE_PATH = "execute-contract-with-file-path"; + public const string ACTION_NEW_FILE = "new-file"; + public const string ACTION_NEW_FROM_TEMPLATE = "new-from-template"; + public const string ACTION_NEW_FOLDER = "new-folder"; + + // All fileitem related actions handled in FolderTree + public const string ITEM_ACTION_GROUP = "item"; + public const string ITEM_ACTION_PREFIX = "item."; + public const string ACTION_RENAME_FILE = "rename-file"; + public const string ACTION_RENAME_FOLDER = "rename-folder"; + public const string ACTION_LAUNCH_APP_WITH_FILE_PATH = "launch-app-with-file-path"; + public const string ACTION_SHOW_APP_CHOOSER = "show-app-chooser"; + public const string ACTION_DELETE = "delete"; +} +/* ProjectList is a flat list of FolderTrees each representing a single project */ +public class Code.ProjectList : Granite.Bin, Code.PaneSwitcher { + // Moved from folder tree to shared top widget + // public signal bool rename_request (Code.File file); + + private const ActionEntry[] ACTION_ENTRIES = { + { ACTION_CLOSE_PROJECT_FOLDER, action_close_project_folder, "s"}, + { ACTION_CLOSE_OTHER_PROJECT_FOLDERS, action_close_other_project_folders, "s"} + }; + + public const string CLOSE_PROJECT_DOCS_ACTION_NAME = Scratch.MainWindow.ACTION_PREFIX + Scratch.MainWindow.ACTION_CLOSE_PROJECT_DOCS; + + public SimpleActionGroup actions { get; private set; } + public bool is_empty { get { return list_store.n_items == 0; } } + + // PaneSwitcher interface + public string icon_name { get; set; } + public string title { get; set; } + + private Gtk.ScrolledWindow scrolled_window; + private Gtk.ListBox list_box; + private GLib.ListStore list_store; + private Gtk.NoSelection selection_model; + + private GLib.Settings settings; + private Scratch.Services.PluginsManager plugins; + + + // public ProjectList (Scratch.Services.PluginsManager plugins_manager) { + // plugins = plugins_manager; + // } + + construct { + actions = new SimpleActionGroup (); + actions.add_action_entries (ACTION_ENTRIES, this); + insert_action_group (PROJECT_ACTION_GROUP, actions); + + //For Code.PaneSwitcher iterface + icon_name = "not-exist"; + title = "Projects"; + settings = new GLib.Settings ("io.elementary.code.folder-manager"); + list_store = new ListStore (typeof (ProjectFolderItem)); + selection_model = new Gtk.NoSelection (list_store); + list_box = new Gtk.ListBox (); + list_box.add_css_class (Granite.STYLE_CLASS_SIDEBAR); + list_box.bind_model ( + selection_model, + (obj) => { + var project_item = (ProjectFolderItem) obj; + return new ProjectListRowWidget (project_item); + } + ); + + Scratch.saved_state.changed["order-folders"].connect (() => { + order_folders (); + }); + + scrolled_window = new Gtk.ScrolledWindow () { + child = list_box, + }; + child = scrolled_window; + + list_box.activate_on_single_click = true; + list_box.row_activated.connect ((row) => { + var project_widget = (ProjectListRowWidget) (row.child); + project_widget.toggle_expanded (); + }); + } + + public async void restore_saved_state () { + foreach (unowned string path in settings.get_strv ("opened-folders")) { + yield add_new_project_folder (path, false, true); + } + } + + public void open_project_folder (File folder) { + warning ("open project folder"); + ProjectFolderItem? listitem; + if (is_existing_project_path (folder.path, out listitem)) { + warning ("exists - expanding"); + listitem.expand (); + return; //TODO Should we expand here? + } + + add_new_project_folder.begin (folder.path, true, false); + } + + public void collapse_all () { + iterate_children ((listitem) => { + // For now just collapse the top level + listitem.is_expanded = false; + return Code.TreeList.ITERATE_CONTINUE; + }); + } + + public void order_folders () { + if (!Scratch.saved_state.get_boolean ("order-folders")) { + return; + } + + //TODO Sorting + } + + public void select_path (string path) { + //TODO write find_path + // find_path (null, path); + } + + public void unselect_file (GLib.File file) { // Needed? + //TODO Complete this + } + + public void unselect_all () { // Needed?? + //TODO Call unselect all on all children. + // tree_list.unselect_all (); + } + + public void collapse_other_projects (string active_project_path) { + iterate_children ((listitem) => { + if (listitem.path != active_project_path) { + listitem.collapse (); + activate_action ( + Scratch.MainWindow.ACTION_PREFIX + Scratch.MainWindow.ACTION_HIDE_PROJECT_DOCS, + "s", + listitem.path + ); + } else { + listitem.expand (); + activate_action ( + Scratch.MainWindow.ACTION_PREFIX + Scratch.MainWindow.ACTION_RESTORE_PROJECT_DOCS, + "s", + listitem.path + ); + } + + return Code.TreeList.ITERATE_CONTINUE; + }); + } + + public void branch_actions (string path) { + // // Must only carry out branch actions on active project so switch if necessary. + // //TODO Warn before switching active project? + // var active_project = set_active_project (path); + // if (active_project == null || !active_project.is_git_repo) { + // Gdk.Display.get_default ().beep (); + // return; + // } + + // var dialog = new Dialogs.BranchActionDialog (active_project); + // dialog.response.connect ((res) => { + // if (res == Gtk.ResponseType.APPLY) { + // perform_branch_action (dialog); + // } + + // dialog.destroy (); + // }); + + // dialog.present (); + } + + private void perform_branch_action ( + Scratch.Dialogs.BranchActionDialog dialog + ) { + // switch (dialog.action) { + // case CHECKOUT: + // dialog.project.checkout_branch_ref (dialog.branch_ref); + // break; + // case COMMIT: + // break; + // case PUSH: + // break; + // case PULL: + // break; + // case MERGE: + // break; + // case DELETE: + // break; + // case CREATE: + // dialog.project.new_branch (dialog.new_branch_name); + // break; + // default: + // assert_not_reached (); + // } + } + + //TODO do we need to return anything? + // Call to find top level foldertree and then call find path on that. + private Object? find_path ( + string path, // File path to search fod + bool expand = false // Whether to expsnd to show found item + // GLib.File? target_file = null // Alternatively find this file + ) { +warning ("Project list find path %s", path); + // var target = target_file ?? GLib.File.new_for_path (path); + ProjectFolderItem? matched_project = null; + iterate_children ((listitem) => { + if (listitem.path == path) { + matched_project = listitem; + return Code.TreeList.ITERATE_STOP; + } + + return Code.TreeList.ITERATE_CONTINUE; + }); + + if (matched_project != null) { + return matched_project; + } + + FolderManagerItem? matched_item = null; + iterate_children ((listitem) => { + if (path.has_prefix (listitem.path)) { //TODO Ensure paths are compatible + matched_item = listitem.find_path (path, expand); + return Code.TreeList.ITERATE_STOP; + } + + return Code.TreeList.ITERATE_CONTINUE; + }); + + + return matched_item; + // if (listitem is FolderItem) { + // var folder = item as FolderItem; + // var folder_root = folder.file.file; + // if (folder_root.get_relative_path (target) == null) { + // return Code.TreeList.ITERATE_CONTINUE; + // } + + // if (!folder.is_expanded) { + // if (expand) { + // folder.load_children (); //Synchronous + // folder.is_expanded = true; + // } else { + // return Code.TreeList.ITERATE_CONTINUE; + // } + // } + + // var recurse_item = find_path (folder, path, expand, target); + // if (recurse_item != null) { + // matched_item = recurse_item; + // return Code.TreeList.ITERATE_STOP; + // } + // } + + // return Code.TreeList.ITERATE_CONTINUE; + // }); + + } + + //TODO Is this needed? + // // Which project any file is in + // public ProjectFolderItem? get_project_for_file (GLib.File file) { + // ProjectFolderItem? matched_project_item = null; + // iterate_children ((listitem) => { + // if (listitem.is_or_contains_file (file)) { + // matched_project_item = listitem; + // return Code.TreeList.ITERATE_STOP; + // } + + // return Code.TreeList.ITERATE_CONTINUE; + // }); + + // return matched_project_item; + // } + + public FolderManagerItem? expand_to_path (string path) { + var object = find_path (path, true); + if (object is FolderManagerItem) { + return (FolderManagerItem) object; + } + + return null; + } + + /* Do global search on project containing the file path supplied in parameter */ + public void search_global (string path, string? term = null) { + var item_for_path = expand_to_path (path); + if (item_for_path != null) { + var search_root = item_for_path.get_root_folder (); + if (search_root is ProjectFolderItem) { + GLib.File start_folder = (item_for_path is FolderItem) + ? item_for_path.file.file + : search_root.file.file; + + bool is_explicit = !(item_for_path is ProjectFolderItem); + search_root.global_search.begin (start_folder, term, is_explicit); + } + } + } + + // public void clear_badges () { + // tree_list.iterate_children (null, (child) => { + // if (child is ProjectFolderItem) { + // ((FolderItem)child).remove_all_badges (); + // } + + // return Code.TreeList.ITERATE_CONTINUE; + // }); + // } + + // public void folder_item_update_hook (GLib.File source, GLib.File? dest, GLib.FileMonitorEvent event) { + // plugins.hook_folder_item_change (source, dest, event); + // } + + + public void item_deleted (ProjectFolderItem listitem) { + // Just remove it for now + remove_project_item (listitem); + } + + + // private void add_new_folder (SimpleAction action, Variant? param) { + // // Using "path" of parent folder from params, call `on_add_new (true)` on `FolderItem` + // var path = param.get_string (); + + // if (path == null || path == "") { + // return; + // } + + // var folder = find_path (null, path); + // if (folder == null) { + // return; + // } + + + // folder.on_add_new (true); + // } + + // private void add_new_file (SimpleAction action, Variant? param) { + // // Using "path" of parent folder from params, call `on_add_new (false)` on `FolderItem` + // var path = param != null ? param.get_string () : null; + + // if (path == null || path == "") { + // critical ("No path"); + // return; + // } + + // var folder = find_path (null, path) as FolderItem; + // if (folder == null) { + // return; + // } + + // folder.on_add_new (false); + // } + + // private void add_new_from_template (SimpleAction action, Variant? param) { + // // Using "path" of parent folder from params, call `on_add_new (false)` on `FolderItem` + // // var path = param.get_string (); + // string? parent_path = null, template_path = null; + // param.@get ("(ss)", out parent_path, out template_path); + + // //Do we need this check? + // if (parent_path == null || parent_path == "") { + // return; + // } + + // var folder = find_path (null, parent_path) as FolderItem; + // if (folder == null) { + // return; + // } + + // folder.on_add_template (template_path); + // } + + // private void action_launch_app_with_file_path (SimpleAction action, Variant? param) { + // var params = param.get_strv (); + // Utils.launch_app_with_file (params[1], params[0]); + // } + + // private void action_show_app_chooser (SimpleAction action, Variant? param) { + // var path = param.get_string (); + + // if (path == null || path == "") { + // return; + // } + + // var file = GLib.File.new_for_path (path); + // var dialog = new Gtk.AppChooserDialog (new Gtk.Window (), Gtk.DialogFlags.MODAL, file); + // dialog.deletable = false; + + // dialog.response.connect ((res) => { + // if (res == Gtk.ResponseType.OK) { + // var app_info = dialog.get_app_info (); + // if (app_info != null) { + // Utils.launch_app_with_file (app_info.get_id (), path); + // } + // } + + // dialog.destroy (); + // }); + + // dialog.show (); + // } + + // private void action_execute_contract_with_file_path (SimpleAction action, Variant? param) { + // var params = param.get_strv (); + // var path = params[0]; + // if (path == null || path == "") { + // return; + // } + + // var contract_name = params[1]; + // if (contract_name == null || contract_name == "") { + // return; + // } + + // Utils.execute_contract_with_file_path (path, contract_name); + // } + + + + + // private void action_delete (SimpleAction action, Variant? param) { + // var path = param.get_string (); + + // if (path == null || path == "") { + // return; + // } + + // var item = find_path (null, path); + // if (item != null) { + // var item_to_delete = item as Code.FolderManagerItem; + + // // Wait for ProjectFolderItem closed signal handle logic to run before moving item to trash + // if (item_to_delete is Code.ProjectFolderItem) { + // item_to_delete.closed.connect_after (() => { + // item_to_delete.trash (); + // }); + // item_to_delete.closed (); + // return; + // } + + // item_to_delete.trash (); + // } + // } + + // Only call when path is known to be a new project + private async void add_new_project_folder (string path, bool expand, bool restoring = false) { + var folder = new File (path); + if (!folder.is_valid_directory) { + warning ("Cannot open invalid directory."); + return; + } + + var add_file = folder.file; + // Need to deal with case where folder is parent or child of an existing project + var parents = new List (); + var children = new List (); + + iterate_children ((listitem) => { + // var item = (ProjectFolderItem) child; + if (add_file.get_relative_path (listitem.gfile) != null) { + debug ("Trying to add parent of existing project"); + children.append (listitem); + } else if (listitem.gfile.get_relative_path (add_file) != null) { + debug ("Trying to add child of existing project"); + parents.append (listitem); + } + + return Code.TreeList.ITERATE_CONTINUE; + }); + + if (parents.length () > 0 || children.length () > 0) { + assert (parents.length () <= 1); + assert (parents.length () == 0 || children.length () == 0); + var dialog = new Scratch.Dialogs.CloseProjectsConfirmationDialog ( + (Scratch.MainWindow) get_root (), + parents.length (), + children.length () + ); + + var close_projects = false; + dialog.response.connect ((res) => { + if (res == Gtk.ResponseType.ACCEPT) { + close_projects = true; + } + + dialog.destroy (); + add_new_project_folder.callback (); + }); + + dialog.show (); + yield; + + if (close_projects) { + foreach (var listitem in parents) { + listitem.close (); + remove_project_item (listitem); + } + + foreach (var listitem in children) { + listitem.close (); + remove_project_item (listitem); + } + } else { + return; + } + } + + // Process any closed signals emitted before proceeding + Idle.add (() => { + var new_item = new ProjectFolderItem (folder, this); + list_store.append (new_item); + if (expand) { + new_item.expand (); + } + // rename_items_with_same_name (new_project); //TODO do this later + + // We do not want to rewrite settings while restoring from settings + // This interferes with fuzzy-finder plugins_manager + // See https://github.com/elementary/code/issues/1533 + if (!restoring) { + write_open_folders_setting (); + } + + add_new_project_folder.callback (); + return Source.REMOVE; + }); + + yield; + + // order_folders (); //TODO do later + } + + public bool is_existing_project_path (string path, out ProjectFolderItem? list_item) { + bool open = false; + list_item = null; + ProjectFolderItem? matched_item = null; + // Only iterate this model + iterate_children ((listitem) => { + if (path == listitem.path) { + open = true; + matched_item = listitem; + return Code.TreeList.ITERATE_STOP; + } + + return Code.TreeList.ITERATE_CONTINUE; + }); + + list_item = matched_item; + return open; + } + + private void write_open_folders_setting () { + string[] to_save = {}; + iterate_children ((listitem) => { + var saved = false; + //Do we need to de-duplicate? Not possible to open a project twice? + foreach (var saved_folder in to_save) { + if (listitem.path == saved_folder) { + saved = true; + break; + } + } + + if (!saved) { + to_save += listitem.path; + } + + return Code.TreeList.ITERATE_CONTINUE; + }); + + settings.set_strv ("opened-folders", to_save); + } + + private void remove_project_item (ProjectFolderItem listitem) { + activate_action ( + CLOSE_PROJECT_DOCS_ACTION_NAME, + "s", + listitem.path + ); + + uint pos; + list_store.find (listitem, out pos); + list_store.remove (pos); + + } + private void action_close_project_folder (SimpleAction action, GLib.Variant? parameter) { + var path = parameter.get_string (); + if (path == null || path == "") { + return; + } + + iterate_children ((listitem) => { + if (listitem.path == path) { + listitem.close (); + remove_project_item (listitem); // OK to remove as we stop iterating + return TreeList.ITERATE_STOP; + } + + return TreeList.ITERATE_CONTINUE; + }); + } + + private void action_close_other_project_folders (SimpleAction action, GLib.Variant? parameter) { + var path = parameter.get_string (); + if (path == null || path == "") { + return; + } + + List to_remove = null; + iterate_children ((listitem) => { + if (listitem.path != path) { + listitem.close (); + to_remove.prepend (listitem); // Delay removal during iteration + } + + return TreeList.ITERATE_CONTINUE; + }); + + // List to_remove = null; + // tree_list.iterate_children (null, (child) => { + // var project_folder_item = (ProjectFolderItem) child; + // if (project_folder_item != folder_root) { + // activate_action ( + // MainWindow.ACTION_PREFIX + MainWindow.ACTION_CLOSE_PROJECT_DOCS, + // "s", + // project_folder_item.path + // ); + // to_remove.prepend (project_folder_item); + // git_manager.remove_project (project_folder_item); + // } + + // return Code.TreeList.ITERATE_CONTINUE; + // }); + + foreach (ProjectFolderItem listitem in to_remove) { + remove_project_item (listitem); + } + } + + private void action_set_active_project (SimpleAction action, GLib.Variant? parameter) { + var path = parameter.get_string (); + if (path == null || path == "") { + return; + } + + set_active_project (path); + } + + //TODO Do we need both these functions??? + private ProjectFolderItem? set_active_project (string path) { + ProjectFolderItem? project_item; + is_existing_project_path (path, out project_item); + project_item.set_as_active_project (); + return project_item; + } + + // private void set_project_active (string path) { + // activate_action ( + // MainWindow.ACTION_PREFIX + MainWindow.ACTION_SET_ACTIVE_PROJECT, + // "s", + // path + // ); + // } + + delegate bool ProjectListIteratorCallback (ProjectFolderItem item); + // This iterates the model objects not the listrows + private void iterate_children (ProjectListIteratorCallback cb) { + ProjectFolderItem? item = null; + uint pos = 0; + do { + item = (ProjectFolderItem?) (list_store.get_object (pos++)); + } while (item != null && cb (item)); + } + + private void execute_contract_with_file_path (string path, string contract_name) { + var file = GLib.File.new_for_path (path); + + try { + var contracts = Granite.Services.ContractorProxy.get_contracts_for_file (file); + int length = contracts.size; + for (int i = 0; i < length; i++) { + var contract = contracts[i]; + if (contract.get_display_name () == contract_name) { + contract.execute_with_file (file); + break; + } + } + } catch (Error e) { + warning (e.message); + } + } + + private class ProjectListRowWidget : Gtk.Box { + public ProjectFolderItem project_item { get; construct; } + + private Gtk.Expander expander; + private bool expander_locked = false; + private Gtk.Revealer revealer; + + public ProjectListRowWidget (ProjectFolderItem project) { + Object ( + project_item: project + ); + + } + + construct { + var label = new Granite.HeaderLabel ("") { + label = project_item.name, + secondary_text = "Branch name goes here", + hexpand = true + }; + + expander = new Gtk.Expander ("") { + hexpand = false, + expanded = false, + sensitive = false, // We do not want expander interfering with listboxrow activate + valign = CENTER + }; + + var label_box = new Gtk.Box (HORIZONTAL, 6); + label_box.append (label); + label_box.append (expander); + + revealer = new Gtk.Revealer () { + child = project_item.folder_tree, + reveal_child = false + }; + + // We want expander arrow on right so separate expander and revealer + // We put the label and the expander in one box (horizontal) which also + // receives menu requests. The revealer with folder tree goes below + orientation = VERTICAL; + append (label_box); + append (revealer); + + project_item.bind_property ("is-expanded", revealer, "reveal-child", BIDIRECTIONAL | SYNC_CREATE); + revealer.bind_property ("reveal-child", expander, "expanded", BIDIRECTIONAL | SYNC_CREATE); + + var button_controller = new Gtk.GestureClick () { + propagation_phase = CAPTURE, + button = 0 + }; + + // Assume menu is static for now + var menu_model = project_item.get_context_menu (); + var menu = new Gtk.PopoverMenu.from_model (menu_model) { + position = BOTTOM + }; + + this.append (menu); + + label_box.add_controller (button_controller); + button_controller.pressed.connect ((n_press, x, y) => { + var event = button_controller.get_last_event (null); + if (event.triggers_context_menu ()) { // Only true for press events + menu.pointing_to = Gdk.Rectangle () {x = (int)x, y = (int)y, height = 1, width = 1}; + menu.popup (); + } + }); + } + + public void toggle_expanded () { + var show = !revealer.child_revealed; + revealer.reveal_child = show; + } + } + +} diff --git a/src/MainWindow.vala b/src/MainWindow.vala index 36e36bc74c..59023bbf89 100644 --- a/src/MainWindow.vala +++ b/src/MainWindow.vala @@ -39,17 +39,18 @@ namespace Scratch { } } - public Scratch.Widgets.DocumentView document_view; + public Scratch.Widgets.DocumentView document_view { get; private set; } - // Widgets + // These Widgets may be accessed by plugins so public public Scratch.HeaderBar toolbar; public Scratch.Widgets.SearchBar search_bar; + // Not available to plugins private Code.WelcomeView welcome_view; private Code.Terminal terminal; - private FolderManager.FileView folder_manager_view; + private Code.ProjectList folder_manager_view; private Scratch.Services.DocumentManager document_manager; // Plugins - private Scratch.Services.PluginsManager plugins; + public Scratch.Services.PluginsManager plugins { get; private set;} // Widgets for Plugins public Code.Sidebar sidebar; @@ -73,6 +74,7 @@ namespace Scratch { public const string ACTION_FIND_PREVIOUS = "action-find-previous"; public const string ACTION_FIND_GLOBAL = "action-find-global"; public const string ACTION_OPEN = "action-open"; + public const string ACTION_SHOW_OR_OPEN_DOCUMENT = "action-document"; public const string ACTION_OPEN_FOLDER = "action-open-folder"; public const string ACTION_OPEN_PROJECT = "action-open-project"; public const string ACTION_COLLAPSE_ALL_FOLDERS = "action-collapse-all-folders"; @@ -132,7 +134,8 @@ namespace Scratch { { ACTION_FIND_PREVIOUS, action_find_previous }, { ACTION_FIND_GLOBAL, action_find_global, "s" }, { ACTION_OPEN, action_open }, - { ACTION_OPEN_FOLDER, action_open_folder, "s" }, + { ACTION_SHOW_OR_OPEN_DOCUMENT,action_document, "s" }, + { ACTION_OPEN_FOLDER, action_open_folder_as_project, "s" }, { ACTION_OPEN_PROJECT, action_open_project }, { ACTION_COLLAPSE_ALL_FOLDERS, action_collapse_all_folders }, { ACTION_PREFERENCES, action_preferences }, @@ -463,36 +466,38 @@ namespace Scratch { sidebar = new Code.Sidebar (); - folder_manager_view = new FolderManager.FileView (plugins); + folder_manager_view = new Code.ProjectList (); sidebar.add_tab (folder_manager_view); + // sidebar.add_tab (new Granite.HeaderLabel ("Dummy Sidebar")); - folder_manager_view.file_activate.connect ((file) => { - // var file = new Scratch.FolderManager.File (a); - var doc = new Scratch.Services.Document (actions, file.file); + // folder_manager_view.file_activate.connect ((file) => { + // warning ("file activate %s", file.path); + // // var file = new Code.File (a); + // var doc = new Scratch.Services.Document (actions, file.file); - if (file.is_valid_textfile) { - open_document.begin (doc); - } else { - open_binary (file.file); - } - }); + // if (file.is_valid_textfile) { + // open_document.begin (doc); + // } else { + // open_binary (file.file); + // } + // }); - folder_manager_view.rename_request.connect ((file) => { - var allow = true; - foreach (var window in app.get_windows ()) { - var win = (MainWindow)window; - foreach (var doc in win.document_view.docs) { - if (doc.file.equal (file.file)) { - // Only allow sidebar to rename docs that are in sync with their file in - // all windows - allow = allow && !doc.locked && doc.saved; - } - } - } + // folder_manager_view.rename_request.connect ((file) => { + // var allow = true; + // foreach (var window in app.get_windows ()) { + // var win = (MainWindow)window; + // foreach (var doc in win.document_view.docs) { + // if (doc.file.equal (file.file)) { + // // Only allow sidebar to rename docs that are in sync with their file in + // // all windows + // allow = allow && !doc.locked && doc.saved; + // } + // } + // } - return allow; - }); + // return allow; + // }); terminal = new Code.Terminal () { visible = false @@ -582,7 +587,7 @@ namespace Scratch { document_view.tab_removed.connect ((doc) => { update_find_actions (); - folder_manager_view.unselect_file (doc.file); + // folder_manager_view.unselect_file (doc.file); }); document_view.document_change.connect ((doc) => { @@ -594,7 +599,7 @@ namespace Scratch { toolbar.set_document_focus (doc); - folder_manager_view.select_path (doc.file.get_path ()); + // folder_manager_view.select_path (doc.file.get_path ()); // Must follow setting focus document for editorconfig plug plugins.hook_document (doc); @@ -612,6 +617,7 @@ namespace Scratch { set_widgets_sensitive (false); } + private void open_binary (File file) { if (!file.query_exists ()) { return; @@ -647,18 +653,26 @@ namespace Scratch { if (file.query_exists ()) { var is_focused = uri == focused_uri; if (is_focused) { - focused_file = file; + focused_file = file; // remember focused file for focusing after all docs loaded } //TODO Check files valid (settings could have been manually altered) - var doc = new Scratch.Services.Document (actions, file); - if (doc.exists () || !doc.is_file_temporary) { - if (restore_override != null && (file.get_path () == restore_override.file.get_path ())) { - yield open_document_at_selected_range (doc, true, restore_override.range, true); + // var doc = new Scratch.Services.Document (actions, file); + // if (doc.exists () || !doc.is_file_temporary) { + if (restore_override != null && + (file.get_path () == restore_override.file.get_path ())) { + + // yield open_document_at_selected_range (doc, true, restore_override.range, true); + yield document_view.open_document (uri, true, 0, restore_override.range); was_restore_overriden = true; } else { - yield open_document (doc, was_restore_overriden ? false : is_focused, pos); + yield document_view.open_document ( + uri, + //Prevent focus being grabbed if there was an override + was_restore_overriden ? false : is_focused, + pos + ); } - } + // } } } } @@ -668,7 +682,7 @@ namespace Scratch { document_view.update_outline_visible (); restore_override = null; if (focused_file != null) { - folder_manager_view.expand_to_path (focused_file.get_path ()); + // folder_manager_view.expand_to_path (focused_file.get_path ()); } } @@ -743,31 +757,36 @@ namespace Scratch { return search_bar.search_occurrences > 0; } - public void open_folder (File folder) { - var foldermanager_file = new FolderManager.File (folder.get_path ()); - folder_manager_view.open_folder (foldermanager_file); - } - - public async void open_document (Scratch.Services.Document doc, - bool focus = true, - int cursor_position = 0) { - - FolderManager.ProjectFolderItem? project = folder_manager_view.get_project_for_file (doc.file); - doc.source_view.project = project; - yield document_view.open_document (doc, focus, cursor_position); - } - - public async void open_document_at_selected_range (Scratch.Services.Document doc, - bool focus = true, - SelectionRange range = SelectionRange.EMPTY, - bool is_override = false) { - if (restore_override != null && is_override == false) { - return; - } - - doc.source_view.project = folder_manager_view.get_project_for_file (doc.file); - yield document_view.open_document (doc, focus, 0, range); - } + public void open_folder_as_project (File folder) { + warning ("opening %s as project", folder.get_path ()); + var foldermanager_file = new Code.File (folder.get_path ()); + folder_manager_view.open_project_folder (foldermanager_file); + } + + // public async void open_document ( + // string path, + // bool focus = true, + // int cursor_position = 0 + // ) { + // // Code.ProjectFolderItem? project = folder_manager_view.get_project_for_file (doc.file); + // // doc.source_view.project = project; + // yield document_view.open_document (path, focus, cursor_position); + // } + + // public async void open_document_at_selected_range ( + // // Scratch.Services.Document doc, + // string doc_path, + // bool focus = true, + // SelectionRange range = SelectionRange.EMPTY, + // bool is_override = false + // ) { + // if (restore_override != null && is_override == false) { + // return; + // } + + // // doc.source_view.project = folder_manager_view.get_project_for_file (doc.file); + // yield document_view.open_document (doc_path, focus, 0, range); + // } // Close a document public void close_document (Scratch.Services.Document doc) { @@ -938,6 +957,7 @@ namespace Scratch { } private void action_open () { + warning ("action open"); var all_files_filter = new Gtk.FileFilter (); all_files_filter.set_filter_name (_("All files")); all_files_filter.add_pattern ("*"); @@ -966,19 +986,22 @@ namespace Scratch { } file_chooser.response.connect ((res) => { + warning ("got response %s", res.to_string ()); var files = file_chooser.get_files (); // Returns ListModel of GFile objects // var uris = file_chooser.get_uris (); file_chooser.destroy (); // Close now so it does not stay open during lengthy or failed loading if (res == Gtk.ResponseType.ACCEPT) { + warning ("ACCEPT"); var index = 0; var obj = files.get_item (index++); while (obj != null) { - var file = (GLib.File) obj; + var gfile = (GLib.File) obj; // Update last visited path - Utils.last_path = Path.get_dirname (file.get_uri ()); + var path = gfile.get_uri (); + Utils.last_path = Path.get_dirname (path); // Open the file - var doc = new Scratch.Services.Document (actions, file); - open_document.begin (doc); + // var doc = new Scratch.Services.Document (actions, gfile); + document_view.open_document.begin (path); obj = files.get_item (index++); } } @@ -987,6 +1010,11 @@ namespace Scratch { file_chooser.show (); } + // Shows document if alreadu open else open it + private void action_document (SimpleAction action, Variant? param) requires (param != null) { + document_view.open_document.begin (param.get_string ()); + } + private void action_open_in_new_window (SimpleAction action, Variant? param) { var path = param.get_string (); if (path == "") { @@ -995,9 +1023,9 @@ namespace Scratch { var new_window = new MainWindow (false); var file = File.new_for_path (path); - var doc = new Scratch.Services.Document (new_window.actions, file); + // var doc = new Scratch.Services.Document (new_window.actions, file); - new_window.open_document.begin (doc, true); + new_window.document_view.open_document.begin (path, true); } @@ -1006,6 +1034,7 @@ namespace Scratch { } private void choose_folder () { + warning ("choose folder"); var chooser = new Gtk.FileChooserNative ( "Select a folder.", this, Gtk.FileChooserAction.SELECT_FOLDER, _("_Open"), @@ -1013,17 +1042,16 @@ namespace Scratch { ); chooser.select_multiple = true; - chooser.response.connect ((res) => { var files = chooser.get_files (); chooser.destroy (); if (res == Gtk.ResponseType.ACCEPT) { -var index = 0; + var index = 0; var obj = files.get_item (index++); while (obj != null) { var file = (GLib.File) obj; - var foldermanager_file = new FolderManager.File (file.get_path ()); - folder_manager_view.open_folder (foldermanager_file); + var foldermanager_file = new Code.File (file.get_path ()); + folder_manager_view.open_project_folder (foldermanager_file); obj = files.get_item (index++); } } @@ -1032,12 +1060,12 @@ var index = 0; chooser.show (); } - private void action_open_folder (SimpleAction action, Variant? param) { + private void action_open_folder_as_project (SimpleAction action, Variant? param) { var path = param.get_string (); if (path == "") { choose_folder (); } else { - folder_manager_view.open_folder (new FolderManager.File (path)); + folder_manager_view.open_project_folder (new Code.File (path)); } } @@ -1080,7 +1108,7 @@ var index = 0; sidebar.cloning_in_progress = false; if (success) { - open_folder (workdir); + open_folder_as_project (workdir); if (this.is_active) { sidebar.notify_cloning_success (); } else { @@ -1252,8 +1280,7 @@ var index = 0; private void restore_project_docs (string project_path) { document_manager.take_restorable_paths (project_path).@foreach ((doc_path) => { - var doc = new Scratch.Services.Document (actions, File.new_for_path (doc_path)); - open_document.begin (doc); // Use this to reassociate project and document. + document_view.open_document.begin (doc_path); // Use this to reassociate project and document. return true; }); } @@ -1319,7 +1346,7 @@ var index = 0; } if (search_path != "") { - folder_manager_view.search_global (search_path, search_bar.entry_text); + // folder_manager_view.search_global (search_path, search_bar.entry_text); } else { // Fallback to standard search warning ("Unable to perform global search - search document instead"); @@ -1450,10 +1477,12 @@ var index = 0; } private void action_set_active_project (SimpleAction action, Variant? param) { +warning ("set active project"); var project_path = param.get_string (); - if (folder_manager_view.project_is_open (project_path)) { + Code.ProjectFolderItem? project = null; + if (folder_manager_view.is_existing_project_path (project_path, out project)) { git_manager.active_project_path = project_path; - folder_manager_view.collapse_other_projects (); + folder_manager_view.collapse_other_projects (project_path); //The opened folders are not changed so no need to update "opened-folders" setting } else { warning ("Attempt to set folder path %s which is not opened as active project ignored", project_path); @@ -1490,7 +1519,7 @@ var index = 0; } private void action_branch_actions (SimpleAction action, Variant? param) { - folder_manager_view.branch_actions (get_target_path_for_actions (param)); + // folder_manager_view.branch_actions (get_target_path_for_actions (param)); } private void action_move_tab_to_new_window () { diff --git a/src/Services/Document.vala b/src/Services/Document.vala index 67ae2c9a2d..6ad3cf5aa7 100644 --- a/src/Services/Document.vala +++ b/src/Services/Document.vala @@ -283,7 +283,7 @@ namespace Scratch.Services { var fc = new Gtk.EventControllerFocus (); source_view.add_controller (fc); fc.enter.connect (() => { - return_if_fail (!locked); + if (locked) { return; } // Not an error if (fc.is_focus) { if (!is_file_temporary) { check_undoable_actions (); diff --git a/src/Services/FileHandler.vala b/src/Services/FileHandler.vala index 6db312455e..e7d4db22c0 100644 --- a/src/Services/FileHandler.vala +++ b/src/Services/FileHandler.vala @@ -29,10 +29,10 @@ namespace Scratch.Services { } public class FileHandler : GLib.Object { - public static bool can_open_file (File file, out bool is_folder) { + public static bool can_open_file (GLib.File file, out bool is_folder) { is_folder = false; - if (file == null || file.get_path () == null) { - warning ("Ignoring file %s. Cannot determine path", + if (file == null || file.get_path () == null || !file.query_exists ()) { + warning ("Ignoring file %s. Cannot determine path or does not exist", file != null ? file.get_uri () ?? "null" : "null" ); diff --git a/src/Services/GitManager.vala b/src/Services/GitManager.vala index 9827d65832..1e6f3c28e1 100644 --- a/src/Services/GitManager.vala +++ b/src/Services/GitManager.vala @@ -46,24 +46,39 @@ namespace Scratch.Services { construct { // Used to populate the ChooseProject popover in sorted order - project_liststore = new ListStore (typeof (FolderManager.ProjectFolderItem)); + //TODO This seems to duplicate the sidebar store - can we combine? + project_liststore = new ListStore (typeof (Code.ProjectFolderItem)); settings.bind ("active-project-path", this, "active-project-path", DEFAULT); } - public MonitoredRepository? add_project (FolderManager.ProjectFolderItem root_folder) { + public MonitoredRepository? add_project (Code.ProjectFolderItem root_folder) { var root_path = root_folder.path; MonitoredRepository? monitored_repo = null; uint position; if (project_liststore.find_with_equal_func ( root_folder, - (a, b) => { return ((FolderManager.Item) a).equal ((FolderManager.Item) b); }, + (oa, ob) => { + warning ("compare"); + if (oa == null || ob == null) { + return false; + } + + warning ("neither null"); + var a = (Code.ProjectFolderItem) oa; + var b = (Code.ProjectFolderItem) ob; + + warning ("a.name %s", a.name); + warning ("b.name %s", b.name); + return str_equal (a.name, b.name); + }, out position )) { +warning ("found"); var repo = project_gitrepo_map.@get (root_path); return repo; } - +warning ("not found"); try { var git_repo = Ggit.Repository.open (root_folder.file.file); if (!project_gitrepo_map.has_key (root_path)) { @@ -89,13 +104,13 @@ namespace Scratch.Services { } [CCode (instance_pos = -1)] - private int project_sort_func (FolderManager.ProjectFolderItem a, FolderManager.ProjectFolderItem b) { + private int project_sort_func (Code.ProjectFolderItem a, Code.ProjectFolderItem b) { GLib.File file_a = a.file.file; GLib.File file_b = b.file.file; return Path.get_basename (file_a.get_path ()).collate (Path.get_basename (file_b.get_path ())); } - public void remove_project (FolderManager.ProjectFolderItem root_folder) { + public void remove_project (Code.ProjectFolderItem root_folder) { var root_path = root_folder.file.file.get_path (); uint position; @@ -119,6 +134,7 @@ namespace Scratch.Services { build_path = build_file.get_path (); } else { warning ("build path not found %s", build_file.get_path ()); + // Just returns the project path } return build_path; diff --git a/src/Services/LocationJumpManager.vala b/src/Services/LocationJumpManager.vala index 4b4814472b..8cb3d8b495 100644 --- a/src/Services/LocationJumpManager.vala +++ b/src/Services/LocationJumpManager.vala @@ -7,9 +7,13 @@ namespace Scratch { public class LocationJumpManager : GLib.Object { + // This is created by the Application and manages parsing of commandline for a range + // and creating a restoreoverride object to be passed to the MainWindow + // for a particular file public GLib.File file { get; set; } public SelectionRange range { get; set; } + // Check the settings to see whether file was previously open and will be restored public bool has_override_target () { if (file == null) { return false; diff --git a/src/Services/PluginManager.vala b/src/Services/PluginManager.vala index 3324a47c45..fc8860c3f2 100644 --- a/src/Services/PluginManager.vala +++ b/src/Services/PluginManager.vala @@ -30,17 +30,18 @@ public class Scratch.Services.Interface : GLib.Object { ); } - public Scratch.Services.Document open_file (File file) { - var doc = new Scratch.Services.Document (manager.window.actions, file); - manager.window.open_document.begin (doc); - return doc; - } + // public Scratch.Services.Document open_file (File file) { + // var doc = new Scratch.Services.Document (manager.window.actions, file); + // manager.window.open_document.begin (doc); + // return doc; + // } public void close_document (Scratch.Services.Document doc) { manager.window.close_document (doc); } } +//TODO Make singleton public class Scratch.Services.PluginsManager : GLib.Object { public signal void hook_window (Scratch.MainWindow window); public signal void hook_share_menu (GLib.MenuModel menu); diff --git a/src/Services/RestoreOverride.vala b/src/Services/RestoreOverride.vala index 08887df62d..0aa0bf9c4e 100644 --- a/src/Services/RestoreOverride.vala +++ b/src/Services/RestoreOverride.vala @@ -9,6 +9,7 @@ public class RestoreOverride : GLib.Object { public GLib.File file { get; construct; } public SelectionRange range { get; construct; } + // This is used to override the cursor position that may be restored from settings public RestoreOverride (GLib.File file, SelectionRange range) { Object ( file: file, diff --git a/src/SymbolPane/C/CtagsSymbol.vala b/src/SymbolPane/C/CtagsSymbol.vala index 1f7d82e415..c26add1ff7 100644 --- a/src/SymbolPane/C/CtagsSymbol.vala +++ b/src/SymbolPane/C/CtagsSymbol.vala @@ -16,7 +16,7 @@ * */ -public class Scratch.Services.CtagsSymbol : Code.Widgets.SourceList.ExpandableItem, Scratch.Services.SymbolItem { +public class Scratch.Services.CtagsSymbol : Code.TreeListItem, Scratch.Services.SymbolItem { public Scratch.Services.Document doc { get; construct set; } public SymbolType symbol_type { get; set; default = SymbolType.OTHER; } public int line { get; construct set; } @@ -30,7 +30,7 @@ public class Scratch.Services.CtagsSymbol : Code.Widgets.SourceList.ExpandableIt Object ( doc: doc, - name: name, + text: name, line: line ); diff --git a/src/SymbolPane/C/CtagsSymbolOutline.vala b/src/SymbolPane/C/CtagsSymbolOutline.vala index e3c600ddad..7d5c6402fe 100644 --- a/src/SymbolPane/C/CtagsSymbolOutline.vala +++ b/src/SymbolPane/C/CtagsSymbolOutline.vala @@ -40,13 +40,10 @@ public class Scratch.Services.CtagsSymbolOutline : Scratch.Services.SymbolOutlin SymbolType.CONSTANT }; } + construct { - store.item_selected.connect ((selected) => { - if (selected == null) { - return; - } - doc.goto (((CtagsSymbol)selected).line); - store.selected = null; + tree_list.item_activated.connect ((item) => { + doc.goto (((CtagsSymbol)item).line); }); } @@ -75,8 +72,8 @@ public class Scratch.Services.CtagsSymbolOutline : Scratch.Services.SymbolOutlin } private async void parse_output (GLib.Subprocess subprocess) { - var parent_dependent = new Gee.LinkedList (); - var new_root = new Code.Widgets.SourceList.ExpandableItem (_("Symbols")); + var symboliter_list = new Gee.LinkedList (); + var new_root = new Code.TreeListItem () { text = _("Symbols") }; var datainput = new GLib.DataInputStream (subprocess.get_stdout_pipe ()); try { @@ -174,9 +171,9 @@ public class Scratch.Services.CtagsSymbolOutline : Scratch.Services.SymbolOutlin icon, s_type ); - new_root.add (s); + new_root.add_child (s); } else { - parent_dependent.add (new CtagsSymbolIter ( + symboliter_list.add (new CtagsSymbolIter ( name, parent, line, @@ -191,9 +188,9 @@ public class Scratch.Services.CtagsSymbolOutline : Scratch.Services.SymbolOutlin } var found_something = true; - while (found_something && parent_dependent.size > 0) { + while (found_something && symboliter_list.size > 0) { found_something = false; - var iter = parent_dependent.iterator (); + var iter = symboliter_list.iterator (); while (iter.has_next ()) { iter.next (); var i = iter.get (); @@ -201,7 +198,7 @@ public class Scratch.Services.CtagsSymbolOutline : Scratch.Services.SymbolOutlin var parent = find_existing (i.parent, new_root); if (parent != null) { found_something = true; - parent.add (new CtagsSymbol ( + parent.add_child (new CtagsSymbol ( doc, i.name, i.line, @@ -214,7 +211,7 @@ public class Scratch.Services.CtagsSymbolOutline : Scratch.Services.SymbolOutlin var parent_parts = i.parent.split (":", 2); parent = find_existing (parent_parts[1], new_root); if (parent != null) { - parent.name = i.name; + parent.text = i.name; switch (parent_parts[0]) { case "class": parent.icon = new ThemedIcon ("lang-class"); @@ -239,9 +236,9 @@ public class Scratch.Services.CtagsSymbolOutline : Scratch.Services.SymbolOutlin new ThemedIcon ("lang-enum"), SymbolType.ENUM ); - new_root.add (e); + new_root.add_child (e); - e.add (new CtagsSymbol ( + e.add_child (new CtagsSymbol ( doc, i.name, i.line, @@ -255,8 +252,8 @@ public class Scratch.Services.CtagsSymbolOutline : Scratch.Services.SymbolOutlin } // just add the rest - foreach (var symbol in parent_dependent) { - new_root.add (new CtagsSymbol ( + foreach (var symbol in symboliter_list) { + new_root.add_child (new CtagsSymbol ( doc, symbol.name, symbol.line, @@ -266,29 +263,28 @@ public class Scratch.Services.CtagsSymbolOutline : Scratch.Services.SymbolOutlin } Idle.add (() => { - double adjustment_value = store.vadjustment.value; - store.root.clear (); - store.root.add (new_root); - store.root.expand_all (); - store.vadjustment.set_value (adjustment_value); + double adjustment_value = vadj.value; + tree_list.remove_all (); + tree_list.add_root_item (new_root); + // store.root.expand_all (); + vadj.set_value (adjustment_value); destroy_root (root); root = new_root; - add_tooltips (store.root); + add_tooltips (null); return false; }); } - protected override void add_tooltips (Code.Widgets.SourceList.ExpandableItem root) { - foreach (var parent in root.children) { - if (parent is Code.Widgets.SourceList.ExpandableItem) { - add_tooltip ((Code.Widgets.SourceList.ExpandableItem) parent); - } - } + protected override void add_tooltips (Code.TreeListItem? root) { + tree_list.iterate_children (root, (child) => { + add_tooltip (child); + return Code.TreeList.ITERATE_CONTINUE; + }); } - private void add_tooltip (Code.Widgets.SourceList.ExpandableItem parent) { + private void add_tooltip (Code.TreeListItem parent) { if (parent is CtagsSymbol) { var item = ((CtagsSymbol)parent); var start = item.line; @@ -311,39 +307,45 @@ public class Scratch.Services.CtagsSymbolOutline : Scratch.Services.SymbolOutlin add_tooltips (parent); } - private void destroy_root (Code.Widgets.SourceList.ExpandableItem to_destroy) { - var children = iterate_children (to_destroy); - to_destroy.clear (); + private void destroy_root (Code.TreeListItem to_destroy) { + var children = collect_children (to_destroy); + to_destroy.remove_all_children (); foreach (var item in children) { - item.clear (); + item.remove_all_children (); var parent = item.parent; if (parent != null) { - parent.remove (item); + parent.remove_child (item); } } } - private Gee.TreeSet iterate_children (Code.Widgets.SourceList.ExpandableItem parent) { + private Gee.TreeSet collect_children (Code.TreeListItem? parent) { var result = new Gee.TreeSet (); - foreach (var child in parent.children) { - result.add_all (iterate_children ((CtagsSymbol)child)); - } + tree_list.iterate_children (parent, (child) => { + result.add_all (collect_children ((CtagsSymbol)child)); + return Code.TreeList.ITERATE_CONTINUE; + }); return result; } - CtagsSymbol? find_existing (string name, Code.Widgets.SourceList.ExpandableItem parent) { + CtagsSymbol? find_existing (string name, Code.TreeListItem parent) { CtagsSymbol match = null; - foreach (var child in parent.children) { - var child_symbol = child as CtagsSymbol; - if (child_symbol.name == name) { + // foreach (var child in parent.children) { + tree_list.iterate_children (parent, (child) => { + var child_symbol = (CtagsSymbol) child; + if (child_symbol.text == name) { match = child_symbol; - break; + return Code.TreeList.ITERATE_STOP; } else { var res = find_existing (name, child_symbol); - if (res != null) - return res; + if (res != null) { + match = res; + return Code.TreeList.ITERATE_STOP; + } } - } + + return Code.TreeList.ITERATE_CONTINUE; + }); return match; } diff --git a/src/SymbolPane/SymbolOutline.vala b/src/SymbolPane/SymbolOutline.vala index ae9e7374ef..dc1a9926d2 100644 --- a/src/SymbolPane/SymbolOutline.vala +++ b/src/SymbolPane/SymbolOutline.vala @@ -59,26 +59,12 @@ public enum Scratch.Services.SymbolType { } } -public interface Scratch.Services.SymbolItem : Code.Widgets.SourceList.ExpandableItem { +public interface Scratch.Services.SymbolItem : Code.TreeListItem { public abstract SymbolType symbol_type { get; set; default = SymbolType.OTHER;} } public class Scratch.Services.SymbolOutline : Gtk.Box { - protected static SymbolType[] filters; //Initialized by derived classes - const string ACTION_GROUP = "symbol"; - const string ACTION_PREFIX = ACTION_GROUP + "."; - const string ACTION_SELECT = "action-select"; - const string ACTION_TOGGLE = "toggle-"; - const uint SPINNER_DELAY_MSEC = 300; - SimpleActionGroup symbol_action_group; - public Scratch.Services.Document doc { get; construct; } - - protected Gee.HashMap checks; - protected Gtk.SearchEntry search_entry; - protected Code.Widgets.SourceList store; - protected Code.Widgets.SourceList.ExpandableItem root; - protected Gtk.CssProvider source_list_style_provider; public Gtk.Widget get_widget () { return this; } public bool tool_box_sensitive { set { @@ -87,8 +73,18 @@ public class Scratch.Services.SymbolOutline : Gtk.Box { } } + public virtual void parse_symbols () {} + public virtual void add_tooltips (Code.TreeListItem? root) {} + + protected static SymbolType[] filters; //Initialized by derived classes + protected Gee.HashMap checks; + protected Gtk.SearchEntry search_entry; + protected Code.TreeList tree_list; + protected Gtk.ScrolledWindow scrolled_window; + protected Gtk.Adjustment vadj { get { return scrolled_window.vadjustment; }} + protected Code.TreeListItem root; + protected Gtk.CssProvider source_list_style_provider; protected bool took_too_long; - private uint show_spinner_timeout_id = 0; protected void before_parse () { tool_box_sensitive = true; took_too_long = false; @@ -111,21 +107,33 @@ public class Scratch.Services.SymbolOutline : Gtk.Box { tool_box_sensitive = !took_too_long; } - public virtual void parse_symbols () {} - public virtual void add_tooltips (Code.Widgets.SourceList.ExpandableItem root) {} - + private const string ACTION_GROUP = "symbol"; + private const string ACTION_PREFIX = ACTION_GROUP + "."; + private const string ACTION_SELECT = "action-select"; + private const string ACTION_TOGGLE = "toggle-"; + private const uint SPINNER_DELAY_MSEC = 300; private Gtk.MenuButton filter_button; private Gtk.Spinner spinner; private Gtk.Stack stack; + private uint show_spinner_timeout_id = 0; + + SimpleActionGroup symbol_action_group; construct { + tree_list = new Code.TreeList (); + scrolled_window = new Gtk.ScrolledWindow () { + child = tree_list + }; symbol_action_group = new SimpleActionGroup (); insert_action_group (ACTION_GROUP, symbol_action_group); checks = new Gee.HashMap (); - store = new Code.Widgets.SourceList (); - root = new Code.Widgets.SourceList.ExpandableItem (_("Symbols")); - store.root.add (root); + // store = new Code.TreeList (); + // root = new Code.TreeListItem () { + // text = _("Symbols") + // }; + + // tree_list.add_root_item (root); search_entry = new Gtk.SearchEntry () { placeholder_text = _("Find Symbol"), @@ -179,11 +187,11 @@ public class Scratch.Services.SymbolOutline : Gtk.Box { tool_box.append (search_entry); tool_box.append (stack); append (tool_box); - append (store); + append (scrolled_window); set_up_css (); realize.connect (() => { - store.set_filter_func (filter_func, false); + // store.set_filter_func (filter_func, false); search_entry.changed.connect (schedule_refilter); }); } @@ -229,13 +237,13 @@ public class Scratch.Services.SymbolOutline : Gtk.Box { // Do not exclude text search misses on Item with children as may // hide hits on its children - if (item is Code.Widgets.SourceList.ExpandableItem) { - var expandable = (Code.Widgets.SourceList.ExpandableItem)item; + if (item is Code.TreeListItem) { + var expandable = (Code.TreeListItem) item; if (expandable.n_children > 0) { return true; } - return ((SymbolItem)item).name.contains (search_entry.text); + return ((SymbolItem)item).text.contains (search_entry.text); } return true; @@ -256,10 +264,10 @@ public class Scratch.Services.SymbolOutline : Gtk.Box { delay_refilter = false; return Source.CONTINUE; } else { - refilter_timeout_id = 0; - store.refilter (); + // refilter_timeout_id = 0; + // store.refilter (); // Ensure new visible items shown when filter removed - store.root.expand_all (true, true); + tree_list.expand_all (null); return Source.REMOVE; } }); diff --git a/src/SymbolPane/Vala/ValaComparisonHelper.vala b/src/SymbolPane/Vala/ValaComparisonHelper.vala index 4c38d32058..eba4b1f9f2 100644 --- a/src/SymbolPane/Vala/ValaComparisonHelper.vala +++ b/src/SymbolPane/Vala/ValaComparisonHelper.vala @@ -17,9 +17,9 @@ */ namespace Scratch.Services.ValaComparison { - int sort_function (Code.Widgets.SourceList.Item str1, Code.Widgets.SourceList.Item str2) { + int sort_function (Code.TreeListItem str1, Code.TreeListItem str2) { if (!(str1 is ValaSymbolItem && str2 is ValaSymbolItem)) - return str1.name.collate (str2.name); + return str1.text.collate (str2.text); var a = (ValaSymbolItem) str1; var b = (ValaSymbolItem) str2; var sa = a.symbol; @@ -52,7 +52,7 @@ namespace Scratch.Services.ValaComparison { return (compare_signal ((Vala.Signal) sa, sb)); else if (sa is Vala.Struct) return (compare_struct ((Vala.Struct) sa, sb)); - return str1.name.collate (str2.name); + return str1.text.collate (str2.text); } int compare_class (Vala.Class s, Vala.Symbol s2) { diff --git a/src/SymbolPane/Vala/ValaSymbolItem.vala b/src/SymbolPane/Vala/ValaSymbolItem.vala index ac3bb20f70..62b4008222 100644 --- a/src/SymbolPane/Vala/ValaSymbolItem.vala +++ b/src/SymbolPane/Vala/ValaSymbolItem.vala @@ -16,7 +16,7 @@ * */ -public class Scratch.Services.ValaSymbolItem : Code.Widgets.SourceList.ExpandableItem, Code.Widgets.SourceListSortable, Scratch.Services.SymbolItem { +public class Scratch.Services.ValaSymbolItem : Code.TreeListItem, Scratch.Services.SymbolItem { public Vala.Symbol symbol { get; construct; } public SymbolType symbol_type { get; set; default = SymbolType.OTHER; } public ValaSymbolItem (Vala.Symbol symbol, string _tooltip) { @@ -30,12 +30,12 @@ public class Scratch.Services.ValaSymbolItem : Code.Widgets.SourceList.Expandabl if (symbol is Vala.CreationMethod) { var klass = ((Vala.CreationMethod)symbol).class_name; if (symbol.name == ".new") { - name = klass; + text = klass; } else { - name = "%s.%s".printf (klass, symbol.name); + text = "%s.%s".printf (klass, symbol.name); } } else { - name = symbol.name; + text = symbol.name; } if (symbol is Vala.Struct) { @@ -99,13 +99,15 @@ public class Scratch.Services.ValaSymbolItem : Code.Widgets.SourceList.Expandabl } else { warning (symbol.type_name); } + + warning ("new symbol text %s", text); } ~ValaSymbolItem () { debug ("Destroy Vala symbol"); } - public int compare (Code.Widgets.SourceList.Item a, Code.Widgets.SourceList.Item b) { + public int compare (Code.TreeListItem a, Code.TreeListItem b) { return ValaComparison.sort_function (a, b); } diff --git a/src/SymbolPane/Vala/ValaSymbolOutline.vala b/src/SymbolPane/Vala/ValaSymbolOutline.vala index 4399302ccd..b2ba2c5c90 100644 --- a/src/SymbolPane/Vala/ValaSymbolOutline.vala +++ b/src/SymbolPane/Vala/ValaSymbolOutline.vala @@ -48,13 +48,8 @@ public class Scratch.Services.ValaSymbolOutline : Scratch.Services.SymbolOutline parser = new Vala.Parser (); resolver = new Code.Plugins.ValaSymbolResolver (); - store.item_selected.connect ((selected) => { - if (selected == null) { - return; - } - - doc.goto (((ValaSymbolItem)selected).symbol.source_reference.begin.line); - store.selected = null; + tree_list.item_activated.connect ((item) => { + doc.goto (((ValaSymbolItem)item).symbol.source_reference.begin.line); }); doc.doc_closed.connect (doc_closed); @@ -109,33 +104,32 @@ public class Scratch.Services.ValaSymbolOutline : Scratch.Services.SymbolOutline if (!cancellable.is_cancelled () || took_too_long) { Idle.add (() => { - double adjustment_value = store.vadjustment.value; - var root_children = store.root.children; // Keep reference to children for later destruction - store.root.clear (); // This does not destroy children but disconnects signals - avoids terminal warnings - foreach (var child in root_children) { // Destroy items after clearing list to avoid memory leak - destroy_all_children ((Code.Widgets.SourceList.ExpandableItem)child); - } - - if (took_too_long) { - var warning_item = new Code.Widgets.SourceList.Item () { - icon = new ThemedIcon ("dialog-warning"), - markup = "%s".printf (_("Too Many Symbols")), - tooltip = _("%s contains too many Vala symbols.\nParsing and showing them took too long.").printf (doc.file.get_basename ()), - selectable = false - }; - - store.root.add (warning_item); - } else { - store.root.add (new_root); - } - - store.root.expand_all (); - add_tooltips (store.root); - store.vadjustment.set_value (adjustment_value); + double adjustment_value = vadj.value; + // var root_children = root.children; // Keep reference to children for later destruction + // tree_list.clear (); // This does not destroy children but disconnects signals - avoids terminal warnings + // foreach (var child in root_children) { // Destroy items after clearing list to avoid memory leak + // destroy_all_children ((Code.TreeListItem)child); + // } + + tree_list.remove_all (); + // if (took_too_long) { + // var warning_item = new Code.Widgets.SourceList.Item () { + // icon = new ThemedIcon ("dialog-warning"), + // markup = "%s".printf (_("Too Many Symbols")), + // tooltip = _("%s contains too many Vala symbols.\nParsing and showing them took too long.").printf (doc.file.get_basename ()), + // selectable = false + // }; + + // root.add (warning_item); + // } else { + tree_list.add_root_item (new_root); + // } + + tree_list.expand_all (null); + add_tooltips (root); + vadj.set_value (adjustment_value); return Source.REMOVE; }); - } else { - destroy_all_children (new_root); } after_parse (); @@ -143,15 +137,14 @@ public class Scratch.Services.ValaSymbolOutline : Scratch.Services.SymbolOutline }); } - protected override void add_tooltips (Code.Widgets.SourceList.ExpandableItem root) { - foreach (var parent in root.children) { - if (parent is Code.Widgets.SourceList.ExpandableItem) { - add_tooltip ((Code.Widgets.SourceList.ExpandableItem) parent); - } - } + protected override void add_tooltips (Code.TreeListItem? root = null) { + tree_list.iterate_children (root, (child) => { + add_tooltip ((Code.TreeListItem) parent); + return Code.TreeList.ITERATE_CONTINUE; + }); } - private void add_tooltip (Code.Widgets.SourceList.ExpandableItem parent) { + private void add_tooltip (Code.TreeListItem parent) { if (parent is ValaSymbolItem) { var item = ((ValaSymbolItem)parent); var symbol = item.symbol; @@ -169,28 +162,29 @@ public class Scratch.Services.ValaSymbolOutline : Scratch.Services.SymbolOutline add_tooltips (parent); } - private void destroy_all_children (Code.Widgets.SourceList.ExpandableItem parent) { - foreach (var child in parent.children) { - remove (child, parent); - } - } + // private void destroy_all_children (Code.TreeListItem parent) { + // foreach (var child in parent.children) { + // remove (child, parent); + // } + // } - private new void remove (Code.Widgets.SourceList.Item item, Code.Widgets.SourceList.ExpandableItem parent) { - if (item is Code.Widgets.SourceList.ExpandableItem) { - destroy_all_children ((Code.Widgets.SourceList.ExpandableItem)item); - } + // private new void remove (Code.Widgets.SourceList.Item item, Code.TreeListItem parent) { + // if (item is Code.TreeListItem) { + // destroy_all_children ((Code.TreeListItem)item); + // } - parent.remove (item); - } + // parent.remove (item); + // } // Called from separate thread - private Code.Widgets.SourceList.ExpandableItem construct_tree (GLib.Cancellable cancellable) { + private Code.TreeListItem construct_tree (GLib.Cancellable cancellable) { + warning ("construct tree"); var fields = resolver.get_properties_fields (); var symbols = resolver.get_symbols (); // Remove fake fields created by the vala parser. symbols.remove_all (fields); - var new_root = new Code.Widgets.SourceList.ExpandableItem (_("Symbols")); + var new_root = new Code.TreeListItem () { text = _("Construct Tree Symbols") }; new_root.tooltip = _("Vala symbols found in %s").printf (doc.file.get_basename ()); foreach (var symbol in symbols) { if (cancellable.is_cancelled ()) @@ -211,48 +205,55 @@ public class Scratch.Services.ValaSymbolOutline : Scratch.Services.SymbolOutline private ValaSymbolItem construct_child ( Vala.Symbol symbol, - Code.Widgets.SourceList.ExpandableItem given_parent, + Code.TreeListItem given_parent, GLib.Cancellable cancellable ) { - - Code.Widgets.SourceList.ExpandableItem parent; - if (symbol.scope.parent_scope.owner.name == null) +warning ("construct child"); + Code.TreeListItem parent; + if (symbol.scope.parent_scope.owner.name == null) { parent = given_parent; - else + } else { parent = find_existing (symbol.scope.parent_scope.owner, given_parent, cancellable); + } if (parent == null) { parent = construct_child (symbol.scope.parent_scope.owner, given_parent, cancellable); } - var tree_child = new ValaSymbolItem ( symbol, "" ); - parent.add (tree_child); + + parent.add_child (tree_child); return tree_child; } - ValaSymbolItem? find_existing (Vala.Symbol symbol, Code.Widgets.SourceList.ExpandableItem parent, GLib.Cancellable cancellable) { + ValaSymbolItem? find_existing (Vala.Symbol symbol, Code.TreeListItem parent, GLib.Cancellable cancellable) { ValaSymbolItem match = null; - foreach (var _child in parent.children) { - if (cancellable.is_cancelled ()) - break; + tree_list.iterate_children (null, (_child) => { + if (cancellable.is_cancelled ()) { + return Code.TreeList.ITERATE_STOP; + } var child = _child as ValaSymbolItem; - if (child == null) - continue; + if (child == null) { + return Code.TreeList.ITERATE_CONTINUE; + } if (child.symbol == symbol) { match = child; - break; + return Code.TreeList.ITERATE_STOP; } else { var res = find_existing (symbol, child, cancellable); - if (res != null) - return res; + if (res != null) { + match = res; + return Code.TreeList.ITERATE_STOP; + } } - } + + return Code.TreeList.ITERATE_CONTINUE; + }); return match; } diff --git a/src/Utils.vala b/src/Utils.vala index 9307da5a23..ee4570659e 100644 --- a/src/Utils.vala +++ b/src/Utils.vala @@ -201,62 +201,6 @@ namespace Scratch.Utils { return false; } - public GLib.Menu? create_executable_app_items_for_file (GLib.File file, string file_type) { - var scratch_app = (Scratch.Application) (GLib.Application.get_default ()); - var this_id = scratch_app.application_id + ".desktop"; - var menu = new GLib.Menu (); - - if (scratch_app.is_running_in_flatpak) { - var menu_item = new MenuItem ( - ///TRANSLATORS '%s' represents the quoted basename of a uri to be opened with the default app - _("Show '%s' with default app").printf (file.get_basename ()), - GLib.Action.print_detailed_name ( - Scratch.FolderManager.FileView.ACTION_PREFIX - + Scratch.FolderManager.FileView.ACTION_LAUNCH_APP_WITH_FILE_PATH, - new GLib.Variant.array ( - GLib.VariantType.STRING, - { file.get_path (), "" } - ) - ) - ); - menu.append_item (menu_item); - } else { - List external_apps = null; - if (file_type == "") { - var files_appinfo = AppInfo.get_default_for_type ("inode/directory", true); - external_apps.prepend (files_appinfo); - } else { - external_apps = GLib.AppInfo.get_all_for_type (file_type); - external_apps.sort ((a, b) => { - return a.get_name ().collate (b.get_name ()); - }); - } - - foreach (AppInfo app_info in external_apps) { - string app_id = app_info.get_id (); - if (app_id == this_id) { - continue; - } - - var menu_item = new MenuItem ( - app_info.get_name (), - GLib.Action.print_detailed_name ( - Scratch.FolderManager.FileView.ACTION_PREFIX - + Scratch.FolderManager.FileView.ACTION_LAUNCH_APP_WITH_FILE_PATH, - new GLib.Variant.array ( - GLib.VariantType.STRING, - { file.get_path (), app_id } - ) - ) - ); - menu_item.set_icon (app_info.get_icon ()); - menu.append_item (menu_item); - } - } - - return menu; - } - public void launch_app_with_file (string app_id, string path) { var scratch_app = (Scratch.Application) (GLib.Application.get_default ()); if (scratch_app.is_running_in_flatpak || app_id == "") { @@ -281,51 +225,51 @@ namespace Scratch.Utils { } } - public GLib.Menu create_contract_items_for_file (GLib.File file) { - var menu = new GLib.Menu (); - - try { - var contracts = Granite.Services.ContractorProxy.get_contracts_for_file (file); - foreach (var contract in contracts) { - string contract_name = contract.get_display_name (); - var menu_item = new GLib.MenuItem ( - contract_name, - GLib.Action.print_detailed_name ( - Scratch.FolderManager.FileView.ACTION_PREFIX - + Scratch.FolderManager.FileView.ACTION_EXECUTE_CONTRACT_WITH_FILE_PATH, - new GLib.Variant.array ( - GLib.VariantType.STRING, - { file.get_path (), contract_name } - ) - ) - ); - - menu.append_item (menu_item); - } - } catch (Error e) { - warning (e.message); - } - - return menu; - } - - public void execute_contract_with_file_path (string path, string contract_name) { - var file = GLib.File.new_for_path (path); - - try { - var contracts = Granite.Services.ContractorProxy.get_contracts_for_file (file); - int length = contracts.size; - for (int i = 0; i < length; i++) { - var contract = contracts[i]; - if (contract.get_display_name () == contract_name) { - contract.execute_with_file (file); - break; - } - } - } catch (Error e) { - warning (e.message); - } - } + // public GLib.Menu create_contract_items_for_file (GLib.File file) { + // var menu = new GLib.Menu (); + + // try { + // var contracts = Granite.Services.ContractorProxy.get_contracts_for_file (file); + // foreach (var contract in contracts) { + // string contract_name = contract.get_display_name (); + // var menu_item = new GLib.MenuItem ( + // contract_name, + // GLib.Action.print_detailed_name ( + // Code.FolderTree.ACTION_PREFIX + // + Code.ProjectList.ACTION_EXECUTE_CONTRACT_WITH_FILE_PATH, + // new GLib.Variant.array ( + // GLib.VariantType.STRING, + // { file.get_path (), contract_name } + // ) + // ) + // ); + + // menu.append_item (menu_item); + // } + // } catch (Error e) { + // warning (e.message); + // } + + // return menu; + // } + + // public void execute_contract_with_file_path (string path, string contract_name) { + // var file = GLib.File.new_for_path (path); + + // try { + // var contracts = Granite.Services.ContractorProxy.get_contracts_for_file (file); + // int length = contracts.size; + // for (int i = 0; i < length; i++) { + // var contract = contracts[i]; + // if (contract.get_display_name () == contract_name) { + // contract.execute_with_file (file); + // break; + // } + // } + // } catch (Error e) { + // warning (e.message); + // } + // } public string get_accel_for_action (string detailed_action_name) { var app_instance = (Gtk.Application) GLib.Application.get_default (); diff --git a/src/Widgets/ChooseProjectButton.vala b/src/Widgets/ChooseProjectButton.vala index 70ae55a05e..4cbf30c4a2 100644 --- a/src/Widgets/ChooseProjectButton.vala +++ b/src/Widgets/ChooseProjectButton.vala @@ -51,6 +51,7 @@ public class Code.ChooseProjectButton : Gtk.Box { box.append (label_widget); box.append (cloning_spinner); menu_button = new Gtk.MenuButton () { + hexpand = true, child = box }; menu_button.set_parent (this); @@ -115,8 +116,8 @@ public class Code.ChooseProjectButton : Gtk.Box { var src = git_manager.project_liststore; for (int index = 0; index < src.n_items; index++) { var item = src.get_object (index); - if (item is Scratch.FolderManager.ProjectFolderItem) { - var row = create_project_row ((Scratch.FolderManager.ProjectFolderItem)item); + if (item is Code.ProjectFolderItem) { + var row = create_project_row ((Code.ProjectFolderItem)item); project_listbox.insert (row, index); } } @@ -125,8 +126,8 @@ public class Code.ChooseProjectButton : Gtk.Box { project_listbox.remove_all (); for (int index = (int)pos; index < pos + n_added; index++) { var item = src.get_object (index); - if (item is Scratch.FolderManager.ProjectFolderItem) { - var row = create_project_row ((Scratch.FolderManager.ProjectFolderItem)item); + if (item is Code.ProjectFolderItem) { + var row = create_project_row ((Code.ProjectFolderItem)item); project_listbox.insert (row, index); } } @@ -161,7 +162,7 @@ public class Code.ChooseProjectButton : Gtk.Box { } } - private Gtk.Widget create_project_row (Scratch.FolderManager.ProjectFolderItem project_folder) { + private Gtk.Widget create_project_row (Code.ProjectFolderItem project_folder) { var project_path = project_folder.file.file.get_path (); return new ProjectRow (project_path); } diff --git a/src/Widgets/DocumentView.vala b/src/Widgets/DocumentView.vala index 828e3981a9..7618e57d42 100644 --- a/src/Widgets/DocumentView.vala +++ b/src/Widgets/DocumentView.vala @@ -314,43 +314,46 @@ public class Scratch.Widgets.DocumentView : Gtk.Box { } public void new_document () { - var file = File.new_for_path (unsaved_file_path_builder ()); + var path = unsaved_file_path_builder (); + var file = File.new_for_path (path); try { file.create (FileCreateFlags.PRIVATE); - - var doc = new Services.Document (window.actions, file); // Must open document in order to unlock it. - open_document.begin (doc); + open_document.begin (path); } catch (Error e) { critical (e.message); } } public void new_document_from_clipboard (string clipboard) { - var file = File.new_for_path (unsaved_file_path_builder ()); + var path = unsaved_file_path_builder (); + var file = File.new_for_path (path); // Set clipboard content try { file.create (FileCreateFlags.PRIVATE); file.replace_contents (clipboard.data, null, false, 0, null); - var doc = new Services.Document (window.actions, file); - - open_document.begin (doc); - - + open_document.begin (path); } catch (Error e) { critical ("Cannot insert clipboard: %s", clipboard); } } - public async void open_document (Services.Document doc, bool focus = true, int cursor_position = 0, SelectionRange range = SelectionRange.EMPTY) { + // public async void open_document (Services.Document doc, bool focus = true, int cursor_position = 0, SelectionRange range = SelectionRange.EMPTY) { + public async void open_document ( + string doc_path, + bool focus = true, + int cursor_position = 0, + SelectionRange range = SelectionRange.EMPTY + ) { for (int n = 0; n <= docs.length (); n++) { var nth_doc = docs.nth_data (n); if (nth_doc == null) { continue; } - if (nth_doc.file != null && nth_doc.file.get_uri () == doc.file.get_uri ()) { + // if (nth_doc.file != null && nth_doc.file.get_uri () == doc.file.get_uri ()) { + if (nth_doc.file != null && nth_doc.file.get_uri () == doc_path) { if (focus) { current_document = nth_doc; } @@ -369,6 +372,10 @@ public class Scratch.Widgets.DocumentView : Gtk.Box { } } + var doc = new Scratch.Services.Document ( + window.actions, + GLib.File.new_for_path (doc_path) + ); insert_document (doc, (int) docs.length ()); if (focus) { current_document = doc; @@ -538,8 +545,7 @@ public class Scratch.Widgets.DocumentView : Gtk.Box { public void restore_closed_tab (string path) { var file = File.new_for_path (path); - var doc = new Services.Document (window.actions, file); - open_document.begin (doc); + open_document.begin (path); var menu = (Menu) tab_history_button.menu_model; for (var i = 0; i < menu.get_n_items (); i++) { diff --git a/src/Widgets/GitGutterRenderer.vala b/src/Widgets/GitGutterRenderer.vala index 3e3f12ea5a..419af28f76 100644 --- a/src/Widgets/GitGutterRenderer.vala +++ b/src/Widgets/GitGutterRenderer.vala @@ -17,7 +17,7 @@ public class Scratch.Widgets.GitGutterRenderer : GtkSource.GutterRenderer { public Gee.HashMap line_status_map; public Gee.HashMap status_color_map; - public FolderManager.ProjectFolderItem? project { get; set; default = null; } + public Code.ProjectFolderItem? project { get; set; default = null; } static construct { fallback_scheme = GtkSource.StyleSchemeManager.get_default ().get_scheme ("classic"); // We can assume this always exists @@ -62,23 +62,22 @@ public class Scratch.Widgets.GitGutterRenderer : GtkSource.GutterRenderer { status_color_map.set (status, color); } - public override void snapshot_line (Gtk.Snapshot snapshot, GtkSource.GutterLines lines, uint line) { - warning ("snapshot_line"); - //Gutter and diff lines numbers start at one, source lines start at 0 - var gutter_line_no = (int) lines.get_first (); - Gdk.RGBA? color; - if (line_status_map.has_key (gutter_line_no)) { - color = status_color_map[line_status_map[gutter_line_no]]; - } else { - color = status_color_map [Services.VCStatus.NONE]; - } + // public override void snapshot_line (Gtk.Snapshot snapshot, GtkSource.GutterLines lines, uint line) { + // //Gutter and diff lines numbers start at one, source lines start at 0 + // var gutter_line_no = (int) lines.get_first (); + // Gdk.RGBA? color; + // if (line_status_map.has_key (gutter_line_no)) { + // color = status_color_map[line_status_map[gutter_line_no]]; + // } else { + // color = status_color_map [Services.VCStatus.NONE]; + // } - var rect = Graphene.Rect () { - origin = Graphene.Point () { x = 0.0f, y = 0.0f }, - size = Graphene.Size () { width = 6.0f , height = 12.0f } - }; - snapshot.append_color (color, rect); - } + // var rect = Graphene.Rect () { + // origin = Graphene.Point () { x = 0.0f, y = 0.0f }, + // size = Graphene.Size () { width = 6.0f , height = 12.0f } + // }; + // snapshot.append_color (color, rect); + // } // public override void draw (Cairo.Context cr, // Gdk.Rectangle bg, diff --git a/src/Widgets/SearchBar.vala b/src/Widgets/SearchBar.vala index 8f5ddd06ba..0ec50bf3a8 100644 --- a/src/Widgets/SearchBar.vala +++ b/src/Widgets/SearchBar.vala @@ -26,19 +26,14 @@ public enum Scratch.CaseSensitiveMode { } namespace Scratch.Widgets { - public class SearchBar : Gtk.Box { //TODO In Gtk4 use a BinLayout Widget + public class SearchBar : Granite.Bin { public weak MainWindow window { get; construct; } + private Gtk.Button tool_arrow_up; private Gtk.Button tool_arrow_down; - - /** - * Is the search cyclic? e.g., when you are at the bottom, if you press - * "Down", it will go at the start of the file to search for the content - * of the search entry. - **/ private Granite.SwitchModelButton cycle_search_button ; private Gtk.ComboBoxText case_sensitive_search_button; private Granite.SwitchModelButton regex_search_button; @@ -116,15 +111,13 @@ namespace Scratch.Widgets { } construct { - this.orientation = HORIZONTAL; - search_entry = new Gtk.SearchEntry () { hexpand = true, placeholder_text = _("Find"), }; entry_for_search = new Gtk.Entry (); - entry_for_search.set_parent (search_entry); + // entry_for_search.set_parent (search_entry); search_entry.set_key_capture_widget (entry_for_search); search_occurence_count_label = new Gtk.Label (_("No Results")); @@ -214,7 +207,8 @@ namespace Scratch.Widgets { margin_start = 6 }; search_box.add_css_class (Granite.STYLE_CLASS_LINKED); - search_box.append (search_entry); + // search_box.append (search_entry); + search_box.append (entry_for_search); search_box.append (tool_arrow_down); search_box.append (tool_arrow_up); search_box.append (search_menubutton); @@ -229,7 +223,8 @@ namespace Scratch.Widgets { placeholder_text = _("Replace With") }; entry_for_replace = new Gtk.Entry (); - entry_for_replace.set_parent (replace_search_entry); + replace_search_entry.set_key_capture_widget (entry_for_replace); + // entry_for_replace.set_parent (replace_search_entry); entry_for_replace.set_icon_from_icon_name (Gtk.EntryIconPosition.PRIMARY, "edit-symbolic"); replace_tool_button = new Gtk.Button.with_label (_("Replace")); @@ -245,7 +240,8 @@ namespace Scratch.Widgets { margin_start = 3 }; replace_grid.add_css_class (Granite.STYLE_CLASS_LINKED); - replace_grid.append (replace_search_entry); + replace_grid.append (entry_for_replace); + // replace_grid.append (replace_search_entry); replace_grid.append (replace_tool_button); replace_grid.append (replace_all_tool_button); @@ -290,7 +286,7 @@ namespace Scratch.Widgets { reveal_child = false }; - append (revealer); + this.child = revealer; update_search_widgets (); var key_controller = new Gtk.EventControllerKey () { diff --git a/src/Widgets/Sidebar.vala b/src/Widgets/Sidebar.vala index 500cec6873..3ee1d14c9f 100644 --- a/src/Widgets/Sidebar.vala +++ b/src/Widgets/Sidebar.vala @@ -44,6 +44,9 @@ public class Code.Sidebar : Gtk.Box { construct { orientation = Gtk.Orientation.VERTICAL; + vexpand = true; + hexpand = true; + add_css_class (Granite.STYLE_CLASS_SIDEBAR); choose_project_button = new Code.ChooseProjectButton () { @@ -62,7 +65,9 @@ public class Code.Sidebar : Gtk.Box { }; headerbar.add_css_class (Granite.STYLE_CLASS_FLAT); - stack = new Gtk.Stack (); + stack = new Gtk.Stack () { + vexpand = true + }; stack.transition_type = Gtk.StackTransitionType.SLIDE_LEFT_RIGHT; var overlay = new Gtk.Overlay () { @@ -74,7 +79,9 @@ public class Code.Sidebar : Gtk.Box { stack_switcher.visible = false; stack_switcher.stack = stack; - var actionbar = new Gtk.ActionBar (); + var actionbar = new Gtk.ActionBar () { + valign = END + }; actionbar.add_css_class (Granite.STYLE_CLASS_FLAT); var collapse_all_menu_item = new GLib.MenuItem (_("Collapse All"), Scratch.MainWindow.ACTION_PREFIX @@ -96,7 +103,8 @@ public class Code.Sidebar : Gtk.Box { project_menu_model = project_menu; var label = new Gtk.Label ( _("Manage project folders…")) { - halign = START + halign = START, + valign = CENTER }; var project_menu_button = new Gtk.MenuButton () { hexpand = true, @@ -176,8 +184,8 @@ public class Code.Sidebar : Gtk.Box { } public void focus_sidebar () { - if (stack.visible_child is Code.Widgets.SourceList) { - ((Code.Widgets.SourceList) stack.visible_child).grab_focus (); - } + // if (stack.visible_child is Code.TreeList) { + // ((Code.Widgets.SourceList) stack.visible_child).grab_focus (); + // } } } diff --git a/src/Widgets/SourceView.vala b/src/Widgets/SourceView.vala index df88dd3f15..7101aa5130 100644 --- a/src/Widgets/SourceView.vala +++ b/src/Widgets/SourceView.vala @@ -28,7 +28,7 @@ namespace Scratch.Widgets { public Gtk.TextTag error_tag; public GLib.File location { get; set; } - public FolderManager.ProjectFolderItem project { get; set; default = null; } + public Code.ProjectFolderItem project { get; set; default = null; } public SimpleActionGroup actions { get; construct; } private string font; @@ -109,12 +109,12 @@ namespace Scratch.Widgets { draw_spaces_tag.draw_spaces = true; source_buffer.tag_table.add (draw_spaces_tag); - // Make the gutter renderer and insert into the left side of the source view. - git_diff_gutter_renderer = new GitGutterRenderer (); - navmark_gutter_renderer = new NavMarkGutterRenderer (source_buffer); + // // Make the gutter renderer and insert into the left side of the source view. + // git_diff_gutter_renderer = new GitGutterRenderer (); + // navmark_gutter_renderer = new NavMarkGutterRenderer (source_buffer); - get_gutter (Gtk.TextWindowType.LEFT).insert (git_diff_gutter_renderer, 10); - get_gutter (Gtk.TextWindowType.LEFT).insert (navmark_gutter_renderer, -48); + // get_gutter (Gtk.TextWindowType.LEFT).insert (git_diff_gutter_renderer, 10); + // get_gutter (Gtk.TextWindowType.LEFT).insert (navmark_gutter_renderer, -48); smart_home_end = GtkSource.SmartHomeEndType.AFTER; @@ -216,11 +216,11 @@ namespace Scratch.Widgets { buffer.notify_property ("has-selection"); buffer.notify_property ("language"); - navmark_gutter_renderer.notify["has-marks"].connect (() => { - next_mark_action.set_enabled (navmark_gutter_renderer.has_marks); - prev_mark_action.set_enabled (next_mark_action.get_enabled ()); - }); - navmark_gutter_renderer.notify_property ("has-marks"); + // navmark_gutter_renderer.notify["has-marks"].connect (() => { + // next_mark_action.set_enabled (navmark_gutter_renderer.has_marks); + // prev_mark_action.set_enabled (next_mark_action.get_enabled ()); + // }); + // navmark_gutter_renderer.notify_property ("has-marks"); // // For Gtk3 we need to convert extra_menu to additional Gtk.MenuItems. This is omitted in Gtk4 // populate_popup.connect_after ((menu) => { @@ -383,7 +383,7 @@ namespace Scratch.Widgets { source_buffer.style_scheme = scheme ?? style_scheme_manager.get_scheme ("classic"); } - git_diff_gutter_renderer.set_style_scheme (source_buffer.style_scheme); + // git_diff_gutter_renderer.set_style_scheme (source_buffer.style_scheme); style_changed (source_buffer.style_scheme); } @@ -620,7 +620,7 @@ namespace Scratch.Widgets { Gtk.TextIter? cur_iter = null; buffer.get_iter_at_offset (out cur_iter, cursor_position); var cur_line = cur_iter.get_line (); - navmark_gutter_renderer.add_mark_at_line (cur_line); + // navmark_gutter_renderer.add_mark_at_line (cur_line); } public void goto_previous_mark () { @@ -635,12 +635,12 @@ namespace Scratch.Widgets { Gtk.TextIter? start, end; int line; if (get_current_line (out start, out end)) { - navmark_gutter_renderer.get_nearest_marked_line (start.get_line (), before, out line); - Gtk.TextIter? iter; - buffer.get_iter_at_line (out iter, line); - if (iter != null) { - cursor_position = iter.get_offset (); - } + // navmark_gutter_renderer.get_nearest_marked_line (start.get_line (), before, out line); + // Gtk.TextIter? iter; + // buffer.get_iter_at_line (out iter, line); + // if (iter != null) { + // cursor_position = iter.get_offset (); + // } } } @@ -709,7 +709,7 @@ namespace Scratch.Widgets { var name = mark.get_name (); if (name != null && name.has_prefix ("NavMark")) { warning ("NavMark deleted"); - navmark_gutter_renderer.remove_mark (mark); + // navmark_gutter_renderer.remove_mark (mark); } } @@ -736,14 +736,14 @@ namespace Scratch.Widgets { refresh_timeout_id = Timeout.add (250, () => { refresh_timeout_id = 0; if (project != null && project.is_git_repo) { - git_diff_gutter_renderer.line_status_map.clear (); + // git_diff_gutter_renderer.line_status_map.clear (); project.refresh_diff (ref git_diff_gutter_renderer.line_status_map, location.get_path ()); - git_diff_gutter_renderer.queue_draw (); + // git_diff_gutter_renderer.queue_draw (); } - if (navmark_gutter_renderer.has_marks) { - navmark_gutter_renderer.queue_draw (); - } + // if (navmark_gutter_renderer.has_marks) { + // navmark_gutter_renderer.queue_draw (); + // } return Source.REMOVE; }); diff --git a/src/Widgets/TreeList/TreeList.vala b/src/Widgets/TreeList/TreeList.vala index b5830733f4..5370c7210c 100644 --- a/src/Widgets/TreeList/TreeList.vala +++ b/src/Widgets/TreeList/TreeList.vala @@ -3,31 +3,50 @@ * SPDX-FileCopyrightText: 20126 elementary, Inc. */ -public class Code.TreeList : Gtk.Box { - private Gtk.ListView list_view; - protected GLib.ListStore root_model; - private Gtk.TreeListModel tree_model; - protected Gtk.SelectionModel selection_model; - - public bool activate_on_single_click { get; set; } +public sealed class Code.TreeList : Granite.Bin { public signal void item_activated (TreeListItem item); + // Signals context menu button event giving coords on list view and the associated treelistitem + public signal void popup_context_menu (Graphene.Point view_point, Code.TreeListItem treelistitem); + + // public Gtk.Adjustment vadjustment { + // get { + // return scrolled_window.vadjustment; + // } + // } + + public bool activate_on_single_click { get; set; default = true;} + // public TreeListItem? selected { get; set; } // Selection handled by SelectionModel + // private Gtk.ScrolledWindow scrolled_window; + private Gtk.ListView list_view; + private GLib.ListStore root_model; + private Gtk.TreeListModel tree_model; + private Gtk.SingleSelection selection_model; + construct { root_model = new GLib.ListStore (typeof (TreeListItem)); // Passthrough false (create Gtk.TreeListRows), autoexpand false tree_model = new Gtk.TreeListModel (root_model, false, false, create_model_func); selection_model = new Gtk.SingleSelection (tree_model); + selection_model.sections_changed.connect ((pos, n_items) => { + warning ("sections changed pos %u, n items %u", pos, n_items); + }); var tree_list_factory = new Gtk.SignalListItemFactory (); - var tree_header_factory = new Gtk.SignalListItemFactory (); - list_view = new Gtk.ListView (selection_model, tree_list_factory) { - header_factory = tree_header_factory - }; + // var tree_header_factory = new Gtk.SignalListItemFactory (); + // list_view = new Gtk.ListView (selection_model, tree_list_factory) { + // header_factory = tree_header_factory + // }; + list_view = new Gtk.ListView (selection_model, tree_list_factory); + list_view.add_css_class ("compact"); - bind_property ("activate-on-single-click", list_view, "activate-on-single-click", BIDIRECTIONAL | SYNC_CREATE); + bind_property ("activate-on-single-click", list_view, "single-click-activate", BIDIRECTIONAL | SYNC_CREATE); list_view.activate.connect ((pos) => { - item_activated ((TreeListItem) selection_model.get_item (pos)); + var tree_row = ((Gtk.TreeListRow) selection_model.get_item (pos)); + var data = (Code.TreeListItem) (tree_row.item); + //Always activate regardless of whether expandable - symbol pane works differently to foldermanager + item_activated (data); }); // LIST ITEM FACTORY HANDLERS tree_list_factory.setup.connect ((obj) => { @@ -52,99 +71,141 @@ public class Code.TreeList : Gtk.Box { unbind_data_from_row (data, treelistrow, listitem); }); - // HEADER FACTORY HANDLERS - tree_header_factory.setup.connect ((obj) => { - // By default create header and subheader, expandable - var listitem = (Gtk.ListItem) obj; - create_headeritem_child (listitem); - }); - tree_header_factory.bind.connect ((obj) => { - var listitem = (Gtk.ListItem) obj; - var treelistrow = (Gtk.TreeListRow) listitem.item; - var data = (Code.TreeListItem) treelistrow.item; - }); - tree_header_factory.unbind.connect (() => {}); - tree_header_factory.teardown.connect (() => {}); - - append (list_view); + child = list_view; } - protected virtual void create_listitem_child (Gtk.ListItem item) { + protected virtual void create_listitem_child (Gtk.ListItem listitem) { var label = new Gtk.Label ("") { halign = START, + hexpand = true + }; + // label.add_css_class (Granite.STYLE_CLASS_H4_LABEL); + label.add_css_class (Granite.STYLE_CLASS_SMALL_LABEL); + var primary_image = new Gtk.Image.from_icon_name (null); + var secondary_image = new Gtk.Image.from_icon_name (null); + var badge_label = new Gtk.Label (""); + badge_label.add_css_class (Granite.STYLE_CLASS_MENUITEM); + var box = new Gtk.Box (HORIZONTAL, 6) { + hexpand = true }; - label.add_css_class (Granite.STYLE_CLASS_H4_LABEL); - item.child = label; + box.append (primary_image); + box.append (label); + box.append (secondary_image); + box.append (badge_label); + badge_label.add_css_class (Granite.STYLE_CLASS_SMALL_LABEL); + var expander = new Gtk.TreeExpander () { + child = box + }; + + listitem.child = expander; + + var button_controller = new Gtk.GestureClick () { + propagation_phase = CAPTURE, + button = 0 + }; + + box.add_controller (button_controller); + button_controller.pressed.connect ((n_press, bx, by) => { + var event = button_controller.get_last_event (null); + if (event.triggers_context_menu ()) { // Only true for press events + var treelistrow = (Gtk.TreeListRow) (listitem.get_item ()); + var data = (Code.TreeListItem) (treelistrow.get_item ()); + var button_point = Graphene.Point () {x = (float) bx, y = (float) by}; + var view_point = Graphene.Point (); + listitem.get_child ().compute_point (list_view, button_point, out view_point); + popup_context_menu (view_point, data); + } + }); } + protected virtual void teardown_listitem_child (Gtk.ListItem item) { // Must be paired with create_listitem child + // Assuming controller will be removed automatically on teardown } protected virtual void bind_data_to_row ( TreeListItem data, Gtk.TreeListRow row, Gtk.ListItem item) { // Must be matched with create item widget when overriding - var label = (Gtk.Label)(item.child); - label.label = data.text; - } - protected virtual void unbind_data_from_row ( - TreeListItem data, - Gtk.TreeListRow row, - Gtk.ListItem item - ) { - //Must undo any signal connections etc made in bind_data_to_row - } + var expander = (Gtk.TreeExpander) (item.child); + expander.set_list_row (row); + expander.hide_expander = !data.is_expandable; + data.expanded_binding = data.bind_property ("is-expanded", row, "expanded", BIDIRECTIONAL | SYNC_CREATE); + var box = (Gtk.Box)(expander.child); //TODO use expander? + var primary_image = (Gtk.Image)(box.get_first_child ()); + var name_label = (Gtk.Label)(primary_image.get_next_sibling ()); + var secondary_image = (Gtk.Image) (name_label.get_next_sibling ()); + var badge_label = (Gtk.Label) secondary_image.get_next_sibling (); + primary_image.icon_name = data.is_expandable ? "folder" : "text-x-vala"; //TODO Icon based on file type + secondary_image.icon_name = "emblem-default"; //TODO Different icon + badge_label.label = "32"; //TODO Different icon + name_label.label = data.text; - protected virtual void create_headeritem_child (Gtk.ListItem item) { - var text_label = new Gtk.Label ("") { - halign = START, - }; - text_label.add_css_class (Granite.STYLE_CLASS_H3_LABEL); - var subtext_label = new Gtk.Label ("") { - halign = START - }; - subtext_label.add_css_class (Granite.STYLE_CLASS_SMALL_LABEL); - var box = new Gtk.Box (VERTICAL, 0); - box.append (text_label); - box.append (subtext_label); - var expander = new Gtk.TreeExpander () { - child = box, - }; - item.child = expander; - } - protected virtual void teardown_headeritem_child () { - // Must be paired with create_listitem child - } - protected virtual void bind_data_to_headeritem ( - TreeListItem data, - Gtk.TreeListRow row, - Gtk.ListItem item - ) { - var expander = (Gtk.TreeExpander) item.child; - var box = (Gtk.Box) expander.get_child (); - var text_label = (Gtk.Label) box.get_first_child (); - var subtext_label = (Gtk.Label) text_label.get_next_sibling (); - text_label.label = data.text; - subtext_label.label = data.sub_text; - } - protected virtual void unbind_data_from_headerrow ( + } + protected virtual void unbind_data_from_row ( TreeListItem data, Gtk.TreeListRow row, Gtk.ListItem item ) { //Must undo any signal connections etc made in bind_data_to_row + data.expanded_binding.unbind (); } public ListModel? create_model_func (Object item) { var data = (TreeListItem) item; if (data.is_expandable && data.child_model == null) { + warning ("data %s is expandable and no model", data.text); data.child_model = new GLib.ListStore (typeof (TreeListItem)); } return data.child_model; } + // protected virtual void create_headeritem_child (Gtk.ListHeader item) { + // warning ("create headeritem child pos"); + // var text_label = new Gtk.Label ("") { + // halign = START, + // }; + // text_label.add_css_class (Granite.STYLE_CLASS_H3_LABEL); + // var subtext_label = new Gtk.Label ("") { + // halign = START + // }; + // subtext_label.add_css_class (Granite.STYLE_CLASS_SMALL_LABEL); + // var box = new Gtk.Box (VERTICAL, 0); + // box.append (text_label); + // box.append (subtext_label); + // var expander = new Gtk.TreeExpander () { + // child = box, + // }; + // item.child = expander; + // } + // protected virtual void teardown_headeritem_child () { + // // Must be paired with create_listitem child + // } + // protected virtual void bind_data_to_header ( + // TreeListItem data, + // Gtk.TreeListRow row, + // Gtk.ListHeader item + // ) { + + // var expander = (Gtk.TreeExpander) item.child; + // var box = (Gtk.Box) expander.get_child (); + // var text_label = (Gtk.Label) box.get_first_child (); + // var subtext_label = (Gtk.Label) text_label.get_next_sibling (); + // text_label.label = data.text; + // subtext_label.label = data.sub_text; + // warning ("bind data to header %s", text_label.label); + // } + + // protected virtual void unbind_data_from_headerrow ( + // TreeListItem data, + // Gtk.TreeListRow row, + // Gtk.ListItem item + // ) { + // //Must undo any signal connections etc made in bind_data_to_row + // } + // Root items must generally be expandable public TreeListItem add_root_item ( TreeListItem item @@ -169,6 +230,10 @@ public class Code.TreeList : Gtk.Box { } } + public void remove_all () { + root_model.remove_all (); + } + public void sort_root_children (CompareDataFunc sort_func) { root_model.sort (sort_func); } @@ -194,4 +259,20 @@ public class Code.TreeList : Gtk.Box { item = (TreeListItem?) (model.get_object (pos++)); } while (item != null && cb (item)); } + + public void expand_all (TreeListItem? start) { + iterate_children (start, expand_callback); + } + + public void unselect_all () { + selection_model.unselect_all (); + } + + private bool expand_callback (TreeListItem item) { + if (item.is_expandable) { + iterate_children (item, expand_callback); + } + + return TreeList.ITERATE_CONTINUE; + } } diff --git a/src/Widgets/TreeList/TreeListItem.vala b/src/Widgets/TreeList/TreeListItem.vala index 296ba47bc5..0b5282be1b 100644 --- a/src/Widgets/TreeList/TreeListItem.vala +++ b/src/Widgets/TreeList/TreeListItem.vala @@ -13,29 +13,32 @@ public class Code.TreeListItem : Object { public string secondary_icon_tooltip { get; set; default = ""; } public string badge = ""; // Use label styled with Granite.STYLE_CLASS_BADGE? - public ListStore? child_model { get; set; default = null; } + public ListStore? child_model { get; set; } public TreeListItem? parent { get; set; default = null; } - public bool is_expandable { get; construct; } + public bool is_expandable { get; set construct; } public bool is_expanded { get; set; } // gets bound to the ListItem (temporarily) public Binding expanded_binding { get; set; } // Need to save bind so we can unbind public bool is_activatable { get; set; default = true; } public bool is_selectable { get; set; default = true; } public bool is_editable { get; set; default = false; } public bool is_dummy { get; construct; } + public uint n_children { + get { + return child_model == null ? 0 : child_model.get_n_items (); + } + } public signal void child_added (TreeListItem item); // To emulate source list public TreeListItem () { Object ( - is_dummy: false, - is_expandable: false + is_dummy: false ); } public TreeListItem.dummy () { Object ( - is_dummy: true, - is_expandable: false + is_dummy: true ); } @@ -115,4 +118,13 @@ public class Code.TreeListItem : Object { level--; } } + + public delegate bool IterateChildrenCallback (Object obj); + public void iterate_children (IterateChildrenCallback cb) { + uint pos = 0; + Object? child = null; + do { + child = child_model.get_object (pos++); + } while (child != null && cb (child)); + } } diff --git a/src/meson.build b/src/meson.build index 66d547b2ee..c0255ac944 100644 --- a/src/meson.build +++ b/src/meson.build @@ -31,10 +31,12 @@ code_files = files( 'Dialogs/BranchActions/BranchNameRow.vala', 'FolderManager/File.vala', 'FolderManager/FileItem.vala', - 'FolderManager/FileView.vala', + # 'FolderManager/FileView.vala', 'FolderManager/FolderItem.vala', 'FolderManager/Item.vala', 'FolderManager/ProjectFolderItem.vala', + 'FolderManager/FolderTree.vala', + 'FolderManager/ProjectList.vala', 'Services/CommentToggler.vala', 'Services/Document.vala', 'Services/DocumentManager.vala', @@ -57,9 +59,9 @@ code_files = files( 'Widgets/PaneSwitcher.vala', 'Widgets/PopoverMenuItem.vala', 'Widgets/SearchBar.vala', - 'Widgets/SourceList/CellRendererBadge.vala', - 'Widgets/SourceList/CellRendererExpander.vala', - 'Widgets/SourceList/SourceList.vala', + # 'Widgets/SourceList/CellRendererBadge.vala', + # 'Widgets/SourceList/CellRendererExpander.vala', + # 'Widgets/SourceList/SourceList.vala', 'Widgets/SourceView.vala', 'Widgets/Terminal.vala', 'Widgets/WelcomeView.vala', @@ -83,30 +85,30 @@ executable( install: true ) -codecore = library( - 'codecore', - code_files, - config_header, - dependencies: dependencies, - install: true, - install_dir: [true, true, true], - version: '0.0' -) +# codecore = library( +# 'codecore', +# code_files, +# config_header, +# dependencies: dependencies, +# install: true, +# install_dir: [true, true, true], +# version: '0.0' +# ) -pkg = import('pkgconfig') +# pkg = import('pkgconfig') -pkg.generate( - version: '0.1', - libraries: codecore, - description: 'elementary Code headers', - name: 'codecore', - filebase: 'codecore' -) +# pkg.generate( +# version: '0.1', +# libraries: codecore, +# description: 'elementary Code headers', +# name: 'codecore', +# filebase: 'codecore' +# ) -install_data ('codecore.deps', install_dir: get_option('prefix') / get_option('datadir') / 'vala' / 'vapi') +# install_data ('codecore.deps', install_dir: get_option('prefix') / get_option('datadir') / 'vala' / 'vapi') -codecore_dep = declare_dependency( - link_with: codecore, - dependencies: dependencies, - include_directories: [include_directories('.')] -) +# codecore_dep = declare_dependency( +# link_with: codecore, +# dependencies: dependencies, +# include_directories: [include_directories('.')] +# )