From 3ade071f273aaa973e44bf95d6b1d4913a18f03b Mon Sep 17 00:00:00 2001 From: Daniel Baumann Date: Sun, 7 Apr 2024 19:39:48 +0200 Subject: Adding upstream version 43.2. Signed-off-by: Daniel Baumann --- src/check-nautilus | 2 + src/gtk/.editorconfig | 30 + src/gtk/nautilusgtkbookmarksmanager.c | 643 ++ src/gtk/nautilusgtkbookmarksmanagerprivate.h | 89 + src/gtk/nautilusgtkplacessidebar.c | 5142 ++++++++++ src/gtk/nautilusgtkplacessidebarprivate.h | 148 + src/gtk/nautilusgtkplacesview.c | 2635 ++++++ src/gtk/nautilusgtkplacesview.ui | 261 + src/gtk/nautilusgtkplacesviewprivate.h | 55 + src/gtk/nautilusgtkplacesviewrow.c | 508 + src/gtk/nautilusgtkplacesviewrow.ui | 83 + src/gtk/nautilusgtkplacesviewrowprivate.h | 59 + src/gtk/nautilusgtksidebarrow.c | 692 ++ src/gtk/nautilusgtksidebarrow.ui | 69 + src/gtk/nautilusgtksidebarrowprivate.h | 60 + src/meson.build | 345 + src/nautilus-app-chooser.c | 320 + src/nautilus-app-chooser.h | 22 + src/nautilus-application.c | 1542 +++ src/nautilus-application.h | 87 + src/nautilus-autorun-software.c | 285 + src/nautilus-batch-rename-dialog.c | 2040 ++++ src/nautilus-batch-rename-dialog.h | 232 + src/nautilus-batch-rename-utilities.c | 1186 +++ src/nautilus-batch-rename-utilities.h | 70 + src/nautilus-bookmark-list.c | 670 ++ src/nautilus-bookmark-list.h | 47 + src/nautilus-bookmark.c | 788 ++ src/nautilus-bookmark.h | 54 + src/nautilus-clipboard.c | 349 + src/nautilus-clipboard.h | 47 + src/nautilus-column-chooser.c | 597 ++ src/nautilus-column-chooser.h | 38 + src/nautilus-column-utilities.c | 416 + src/nautilus-column-utilities.h | 34 + src/nautilus-compress-dialog-controller.c | 612 ++ src/nautilus-compress-dialog-controller.h | 34 + src/nautilus-dbus-launcher.c | 231 + src/nautilus-dbus-launcher.h | 39 + src/nautilus-dbus-manager.c | 660 ++ src/nautilus-dbus-manager.h | 36 + src/nautilus-debug.c | 180 + src/nautilus-debug.h | 78 + src/nautilus-directory-async.c | 4755 ++++++++++ src/nautilus-directory-notify.h | 57 + src/nautilus-directory-private.h | 230 + src/nautilus-directory.c | 2085 +++++ src/nautilus-directory.h | 252 + src/nautilus-dnd.c | 317 + src/nautilus-dnd.h | 26 + src/nautilus-enum-types.c.template | 37 + src/nautilus-enum-types.h.template | 25 + src/nautilus-enums.h | 81 + src/nautilus-error-reporting.c | 446 + src/nautilus-error-reporting.h | 53 + src/nautilus-file-changes-queue.c | 344 + src/nautilus-file-changes-queue.h | 31 + src/nautilus-file-conflict-dialog.c | 307 + src/nautilus-file-conflict-dialog.h | 62 + src/nautilus-file-name-widget-controller.c | 522 ++ src/nautilus-file-name-widget-controller.h | 52 + src/nautilus-file-operations-dbus-data.c | 75 + src/nautilus-file-operations-dbus-data.h | 34 + src/nautilus-file-operations.c | 9183 ++++++++++++++++++ src/nautilus-file-operations.h | 166 + src/nautilus-file-private.h | 283 + src/nautilus-file-queue.c | 130 + src/nautilus-file-queue.h | 46 + src/nautilus-file-undo-manager.c | 270 + src/nautilus-file-undo-manager.h | 55 + src/nautilus-file-undo-operations.c | 2639 ++++++ src/nautilus-file-undo-operations.h | 230 + src/nautilus-file-utilities.c | 1515 +++ src/nautilus-file-utilities.h | 145 + src/nautilus-file.c | 9586 +++++++++++++++++++ src/nautilus-file.h | 590 ++ src/nautilus-files-view-dnd.c | 362 + src/nautilus-files-view-dnd.h | 51 + src/nautilus-files-view.c | 9880 ++++++++++++++++++++ src/nautilus-files-view.h | 315 + src/nautilus-floating-bar.c | 549 ++ src/nautilus-floating-bar.h | 48 + src/nautilus-freedesktop-dbus.c | 329 + src/nautilus-freedesktop-dbus.h | 37 + src/nautilus-global-preferences.c | 67 + src/nautilus-global-preferences.h | 145 + src/nautilus-grid-cell.c | 282 + src/nautilus-grid-cell.h | 29 + src/nautilus-grid-view.c | 604 ++ src/nautilus-grid-view.h | 20 + src/nautilus-history-controls.c | 313 + src/nautilus-history-controls.h | 20 + src/nautilus-icon-info.c | 505 + src/nautilus-icon-info.h | 29 + src/nautilus-icon-names.h | 26 + src/nautilus-keyfile-metadata.c | 343 + src/nautilus-keyfile-metadata.h | 43 + src/nautilus-label-cell.c | 158 + src/nautilus-label-cell.h | 23 + src/nautilus-lib-self-check-functions.c | 35 + src/nautilus-lib-self-check-functions.h | 48 + src/nautilus-list-base-private.h | 33 + src/nautilus-list-base.c | 1892 ++++ src/nautilus-list-base.h | 27 + src/nautilus-list-view.c | 1258 +++ src/nautilus-list-view.h | 22 + src/nautilus-location-entry.c | 845 ++ src/nautilus-location-entry.h | 51 + src/nautilus-main.c | 89 + src/nautilus-metadata.c | 55 + src/nautilus-metadata.h | 45 + src/nautilus-mime-actions.c | 2325 +++++ src/nautilus-mime-actions.h | 54 + src/nautilus-module.c | 332 + src/nautilus-module.h | 39 + src/nautilus-monitor.c | 182 + src/nautilus-monitor.h | 33 + src/nautilus-name-cell.c | 340 + src/nautilus-name-cell.h | 23 + src/nautilus-new-folder-dialog-controller.c | 191 + src/nautilus-new-folder-dialog-controller.h | 36 + src/nautilus-operations-ui-manager.c | 688 ++ src/nautilus-operations-ui-manager.h | 34 + src/nautilus-pathbar.c | 1216 +++ src/nautilus-pathbar.h | 34 + src/nautilus-places-view.c | 413 + src/nautilus-places-view.h | 32 + src/nautilus-preferences-window.c | 411 + src/nautilus-preferences-window.h | 34 + src/nautilus-previewer.c | 207 + src/nautilus-previewer.h | 42 + src/nautilus-profile.c | 69 + src/nautilus-profile.h | 53 + src/nautilus-program-choosing.c | 611 ++ src/nautilus-program-choosing.h | 59 + src/nautilus-progress-indicator.c | 548 ++ src/nautilus-progress-indicator.h | 20 + src/nautilus-progress-info-manager.c | 239 + src/nautilus-progress-info-manager.h | 44 + src/nautilus-progress-info-widget.c | 223 + src/nautilus-progress-info-widget.h | 57 + src/nautilus-progress-info.c | 760 ++ src/nautilus-progress-info.h | 82 + src/nautilus-progress-persistence-handler.c | 384 + src/nautilus-progress-persistence-handler.h | 37 + src/nautilus-properties-window.c | 4457 +++++++++ src/nautilus-properties-window.h | 41 + src/nautilus-query-editor.c | 844 ++ src/nautilus-query-editor.h | 79 + src/nautilus-query.c | 692 ++ src/nautilus-query.h | 89 + src/nautilus-rename-file-popover-controller.c | 439 + src/nautilus-rename-file-popover-controller.h | 37 + src/nautilus-search-directory-file.c | 313 + src/nautilus-search-directory-file.h | 32 + src/nautilus-search-directory.c | 1080 +++ src/nautilus-search-directory.h | 43 + src/nautilus-search-engine-model.c | 391 + src/nautilus-search-engine-model.h | 32 + src/nautilus-search-engine-private.h | 31 + src/nautilus-search-engine-recent.c | 465 + src/nautilus-search-engine-recent.h | 33 + src/nautilus-search-engine-simple.c | 614 ++ src/nautilus-search-engine-simple.h | 34 + src/nautilus-search-engine-tracker.c | 627 ++ src/nautilus-search-engine-tracker.h | 29 + src/nautilus-search-engine.c | 588 ++ src/nautilus-search-engine.h | 48 + src/nautilus-search-hit.c | 482 + src/nautilus-search-hit.h | 50 + src/nautilus-search-popover.c | 1092 +++ src/nautilus-search-popover.h | 52 + src/nautilus-search-provider.c | 145 + src/nautilus-search-provider.h | 101 + src/nautilus-self-check-functions.c | 37 + src/nautilus-self-check-functions.h | 46 + src/nautilus-shell-search-provider.c | 875 ++ src/nautilus-shell-search-provider.h | 39 + src/nautilus-signaller.c | 93 + src/nautilus-signaller.h | 41 + src/nautilus-special-location-bar.c | 324 + src/nautilus-special-location-bar.h | 39 + src/nautilus-star-cell.c | 173 + src/nautilus-star-cell.h | 19 + src/nautilus-starred-directory.c | 573 ++ src/nautilus-starred-directory.h | 33 + src/nautilus-tag-manager.c | 1019 ++ src/nautilus-tag-manager.h | 61 + src/nautilus-thumbnails.c | 598 ++ src/nautilus-thumbnails.h | 35 + src/nautilus-toolbar-menu-sections.h | 30 + src/nautilus-toolbar.c | 644 ++ src/nautilus-toolbar.h | 54 + src/nautilus-tracker-utilities.c | 148 + src/nautilus-tracker-utilities.h | 31 + src/nautilus-trash-monitor.c | 262 + src/nautilus-trash-monitor.h | 35 + src/nautilus-types.h | 47 + src/nautilus-ui-utilities.c | 420 + src/nautilus-ui-utilities.h | 59 + src/nautilus-undo-private.h | 30 + src/nautilus-vfs-directory.c | 121 + src/nautilus-vfs-directory.h | 49 + src/nautilus-vfs-file.c | 734 ++ src/nautilus-vfs-file.h | 49 + src/nautilus-video-mime-types.h | 65 + src/nautilus-view-cell.c | 190 + src/nautilus-view-cell.h | 31 + src/nautilus-view-controls.c | 187 + src/nautilus-view-controls.h | 20 + src/nautilus-view-item.c | 280 + src/nautilus-view-item.h | 40 + src/nautilus-view-model.c | 422 + src/nautilus-view-model.h | 34 + src/nautilus-view.c | 369 + src/nautilus-view.h | 121 + src/nautilus-window-slot-dnd.c | 346 + src/nautilus-window-slot-dnd.h | 37 + src/nautilus-window-slot.c | 3417 +++++++ src/nautilus-window-slot.h | 120 + src/nautilus-window.c | 2328 +++++ src/nautilus-window.h | 121 + src/nautilus-x-content-bar.c | 357 + src/nautilus-x-content-bar.h | 38 + src/resources/Checkerboard.png | Bin 0 -> 184 bytes src/resources/gtk/help-overlay.ui | 406 + src/resources/icons/external-link-symbolic.svg | 2 + src/resources/icons/funnel-symbolic.svg | 2 + src/resources/icons/quotation-symbolic.svg | 2 + src/resources/nautilus.gresource.xml | 45 + src/resources/style-hc.css | 16 + src/resources/style.css | 301 + src/resources/text-x-preview.png | Bin 0 -> 923 bytes src/resources/ui/nautilus-app-chooser.ui | 120 + src/resources/ui/nautilus-batch-rename-dialog.ui | 395 + src/resources/ui/nautilus-column-chooser.ui | 105 + src/resources/ui/nautilus-compress-dialog.ui | 98 + src/resources/ui/nautilus-create-folder-dialog.ui | 58 + src/resources/ui/nautilus-file-conflict-dialog.ui | 147 + .../nautilus-file-properties-change-permissions.ui | 157 + .../ui/nautilus-files-view-context-menus.ui | 260 + .../ui/nautilus-files-view-select-items.ui | 54 + src/resources/ui/nautilus-files-view.ui | 42 + src/resources/ui/nautilus-grid-cell.ui | 109 + src/resources/ui/nautilus-history-controls.ui | 28 + .../ui/nautilus-list-view-column-editor.ui | 41 + src/resources/ui/nautilus-name-cell.ui | 112 + ...lus-operations-ui-manager-request-passphrase.ui | 49 + src/resources/ui/nautilus-pathbar-context-menu.ui | 73 + src/resources/ui/nautilus-preferences-window.ui | 164 + src/resources/ui/nautilus-progress-indicator.ui | 70 + src/resources/ui/nautilus-progress-info-widget.ui | 75 + src/resources/ui/nautilus-properties-window.ui | 913 ++ src/resources/ui/nautilus-rename-file-popover.ui | 54 + src/resources/ui/nautilus-search-popover.ui | 336 + src/resources/ui/nautilus-toolbar-view-menu.ui | 63 + src/resources/ui/nautilus-toolbar.ui | 227 + src/resources/ui/nautilus-view-controls.ui | 42 + src/resources/ui/nautilus-window.ui | 107 + 259 files changed, 117448 insertions(+) create mode 100755 src/check-nautilus create mode 100644 src/gtk/.editorconfig create mode 100644 src/gtk/nautilusgtkbookmarksmanager.c create mode 100644 src/gtk/nautilusgtkbookmarksmanagerprivate.h create mode 100644 src/gtk/nautilusgtkplacessidebar.c create mode 100644 src/gtk/nautilusgtkplacessidebarprivate.h create mode 100644 src/gtk/nautilusgtkplacesview.c create mode 100644 src/gtk/nautilusgtkplacesview.ui create mode 100644 src/gtk/nautilusgtkplacesviewprivate.h create mode 100644 src/gtk/nautilusgtkplacesviewrow.c create mode 100644 src/gtk/nautilusgtkplacesviewrow.ui create mode 100644 src/gtk/nautilusgtkplacesviewrowprivate.h create mode 100644 src/gtk/nautilusgtksidebarrow.c create mode 100644 src/gtk/nautilusgtksidebarrow.ui create mode 100644 src/gtk/nautilusgtksidebarrowprivate.h create mode 100644 src/meson.build create mode 100644 src/nautilus-app-chooser.c create mode 100644 src/nautilus-app-chooser.h create mode 100644 src/nautilus-application.c create mode 100644 src/nautilus-application.h create mode 100644 src/nautilus-autorun-software.c create mode 100644 src/nautilus-batch-rename-dialog.c create mode 100644 src/nautilus-batch-rename-dialog.h create mode 100644 src/nautilus-batch-rename-utilities.c create mode 100644 src/nautilus-batch-rename-utilities.h create mode 100644 src/nautilus-bookmark-list.c create mode 100644 src/nautilus-bookmark-list.h create mode 100644 src/nautilus-bookmark.c create mode 100644 src/nautilus-bookmark.h create mode 100644 src/nautilus-clipboard.c create mode 100644 src/nautilus-clipboard.h create mode 100644 src/nautilus-column-chooser.c create mode 100644 src/nautilus-column-chooser.h create mode 100644 src/nautilus-column-utilities.c create mode 100644 src/nautilus-column-utilities.h create mode 100644 src/nautilus-compress-dialog-controller.c create mode 100644 src/nautilus-compress-dialog-controller.h create mode 100644 src/nautilus-dbus-launcher.c create mode 100644 src/nautilus-dbus-launcher.h create mode 100644 src/nautilus-dbus-manager.c create mode 100644 src/nautilus-dbus-manager.h create mode 100644 src/nautilus-debug.c create mode 100644 src/nautilus-debug.h create mode 100644 src/nautilus-directory-async.c create mode 100644 src/nautilus-directory-notify.h create mode 100644 src/nautilus-directory-private.h create mode 100644 src/nautilus-directory.c create mode 100644 src/nautilus-directory.h create mode 100644 src/nautilus-dnd.c create mode 100644 src/nautilus-dnd.h create mode 100644 src/nautilus-enum-types.c.template create mode 100644 src/nautilus-enum-types.h.template create mode 100644 src/nautilus-enums.h create mode 100644 src/nautilus-error-reporting.c create mode 100644 src/nautilus-error-reporting.h create mode 100644 src/nautilus-file-changes-queue.c create mode 100644 src/nautilus-file-changes-queue.h create mode 100644 src/nautilus-file-conflict-dialog.c create mode 100644 src/nautilus-file-conflict-dialog.h create mode 100644 src/nautilus-file-name-widget-controller.c create mode 100644 src/nautilus-file-name-widget-controller.h create mode 100644 src/nautilus-file-operations-dbus-data.c create mode 100644 src/nautilus-file-operations-dbus-data.h create mode 100644 src/nautilus-file-operations.c create mode 100644 src/nautilus-file-operations.h create mode 100644 src/nautilus-file-private.h create mode 100644 src/nautilus-file-queue.c create mode 100644 src/nautilus-file-queue.h create mode 100644 src/nautilus-file-undo-manager.c create mode 100644 src/nautilus-file-undo-manager.h create mode 100644 src/nautilus-file-undo-operations.c create mode 100644 src/nautilus-file-undo-operations.h create mode 100644 src/nautilus-file-utilities.c create mode 100644 src/nautilus-file-utilities.h create mode 100644 src/nautilus-file.c create mode 100644 src/nautilus-file.h create mode 100644 src/nautilus-files-view-dnd.c create mode 100644 src/nautilus-files-view-dnd.h create mode 100644 src/nautilus-files-view.c create mode 100644 src/nautilus-files-view.h create mode 100644 src/nautilus-floating-bar.c create mode 100644 src/nautilus-floating-bar.h create mode 100644 src/nautilus-freedesktop-dbus.c create mode 100644 src/nautilus-freedesktop-dbus.h create mode 100644 src/nautilus-global-preferences.c create mode 100644 src/nautilus-global-preferences.h create mode 100644 src/nautilus-grid-cell.c create mode 100644 src/nautilus-grid-cell.h create mode 100644 src/nautilus-grid-view.c create mode 100644 src/nautilus-grid-view.h create mode 100644 src/nautilus-history-controls.c create mode 100644 src/nautilus-history-controls.h create mode 100644 src/nautilus-icon-info.c create mode 100644 src/nautilus-icon-info.h create mode 100644 src/nautilus-icon-names.h create mode 100644 src/nautilus-keyfile-metadata.c create mode 100644 src/nautilus-keyfile-metadata.h create mode 100644 src/nautilus-label-cell.c create mode 100644 src/nautilus-label-cell.h create mode 100644 src/nautilus-lib-self-check-functions.c create mode 100644 src/nautilus-lib-self-check-functions.h create mode 100644 src/nautilus-list-base-private.h create mode 100644 src/nautilus-list-base.c create mode 100644 src/nautilus-list-base.h create mode 100644 src/nautilus-list-view.c create mode 100644 src/nautilus-list-view.h create mode 100644 src/nautilus-location-entry.c create mode 100644 src/nautilus-location-entry.h create mode 100644 src/nautilus-main.c create mode 100644 src/nautilus-metadata.c create mode 100644 src/nautilus-metadata.h create mode 100644 src/nautilus-mime-actions.c create mode 100644 src/nautilus-mime-actions.h create mode 100644 src/nautilus-module.c create mode 100644 src/nautilus-module.h create mode 100644 src/nautilus-monitor.c create mode 100644 src/nautilus-monitor.h create mode 100644 src/nautilus-name-cell.c create mode 100644 src/nautilus-name-cell.h create mode 100644 src/nautilus-new-folder-dialog-controller.c create mode 100644 src/nautilus-new-folder-dialog-controller.h create mode 100644 src/nautilus-operations-ui-manager.c create mode 100644 src/nautilus-operations-ui-manager.h create mode 100644 src/nautilus-pathbar.c create mode 100644 src/nautilus-pathbar.h create mode 100644 src/nautilus-places-view.c create mode 100644 src/nautilus-places-view.h create mode 100644 src/nautilus-preferences-window.c create mode 100644 src/nautilus-preferences-window.h create mode 100644 src/nautilus-previewer.c create mode 100644 src/nautilus-previewer.h create mode 100644 src/nautilus-profile.c create mode 100644 src/nautilus-profile.h create mode 100644 src/nautilus-program-choosing.c create mode 100644 src/nautilus-program-choosing.h create mode 100644 src/nautilus-progress-indicator.c create mode 100644 src/nautilus-progress-indicator.h create mode 100644 src/nautilus-progress-info-manager.c create mode 100644 src/nautilus-progress-info-manager.h create mode 100644 src/nautilus-progress-info-widget.c create mode 100644 src/nautilus-progress-info-widget.h create mode 100644 src/nautilus-progress-info.c create mode 100644 src/nautilus-progress-info.h create mode 100644 src/nautilus-progress-persistence-handler.c create mode 100644 src/nautilus-progress-persistence-handler.h create mode 100644 src/nautilus-properties-window.c create mode 100644 src/nautilus-properties-window.h create mode 100644 src/nautilus-query-editor.c create mode 100644 src/nautilus-query-editor.h create mode 100644 src/nautilus-query.c create mode 100644 src/nautilus-query.h create mode 100644 src/nautilus-rename-file-popover-controller.c create mode 100644 src/nautilus-rename-file-popover-controller.h create mode 100644 src/nautilus-search-directory-file.c create mode 100644 src/nautilus-search-directory-file.h create mode 100644 src/nautilus-search-directory.c create mode 100644 src/nautilus-search-directory.h create mode 100644 src/nautilus-search-engine-model.c create mode 100644 src/nautilus-search-engine-model.h create mode 100644 src/nautilus-search-engine-private.h create mode 100644 src/nautilus-search-engine-recent.c create mode 100644 src/nautilus-search-engine-recent.h create mode 100644 src/nautilus-search-engine-simple.c create mode 100644 src/nautilus-search-engine-simple.h create mode 100644 src/nautilus-search-engine-tracker.c create mode 100644 src/nautilus-search-engine-tracker.h create mode 100644 src/nautilus-search-engine.c create mode 100644 src/nautilus-search-engine.h create mode 100644 src/nautilus-search-hit.c create mode 100644 src/nautilus-search-hit.h create mode 100644 src/nautilus-search-popover.c create mode 100644 src/nautilus-search-popover.h create mode 100644 src/nautilus-search-provider.c create mode 100644 src/nautilus-search-provider.h create mode 100644 src/nautilus-self-check-functions.c create mode 100644 src/nautilus-self-check-functions.h create mode 100644 src/nautilus-shell-search-provider.c create mode 100644 src/nautilus-shell-search-provider.h create mode 100644 src/nautilus-signaller.c create mode 100644 src/nautilus-signaller.h create mode 100644 src/nautilus-special-location-bar.c create mode 100644 src/nautilus-special-location-bar.h create mode 100644 src/nautilus-star-cell.c create mode 100644 src/nautilus-star-cell.h create mode 100644 src/nautilus-starred-directory.c create mode 100644 src/nautilus-starred-directory.h create mode 100644 src/nautilus-tag-manager.c create mode 100644 src/nautilus-tag-manager.h create mode 100644 src/nautilus-thumbnails.c create mode 100644 src/nautilus-thumbnails.h create mode 100644 src/nautilus-toolbar-menu-sections.h create mode 100644 src/nautilus-toolbar.c create mode 100644 src/nautilus-toolbar.h create mode 100644 src/nautilus-tracker-utilities.c create mode 100644 src/nautilus-tracker-utilities.h create mode 100644 src/nautilus-trash-monitor.c create mode 100644 src/nautilus-trash-monitor.h create mode 100644 src/nautilus-types.h create mode 100644 src/nautilus-ui-utilities.c create mode 100644 src/nautilus-ui-utilities.h create mode 100644 src/nautilus-undo-private.h create mode 100644 src/nautilus-vfs-directory.c create mode 100644 src/nautilus-vfs-directory.h create mode 100644 src/nautilus-vfs-file.c create mode 100644 src/nautilus-vfs-file.h create mode 100644 src/nautilus-video-mime-types.h create mode 100644 src/nautilus-view-cell.c create mode 100644 src/nautilus-view-cell.h create mode 100644 src/nautilus-view-controls.c create mode 100644 src/nautilus-view-controls.h create mode 100644 src/nautilus-view-item.c create mode 100644 src/nautilus-view-item.h create mode 100644 src/nautilus-view-model.c create mode 100644 src/nautilus-view-model.h create mode 100644 src/nautilus-view.c create mode 100644 src/nautilus-view.h create mode 100644 src/nautilus-window-slot-dnd.c create mode 100644 src/nautilus-window-slot-dnd.h create mode 100644 src/nautilus-window-slot.c create mode 100644 src/nautilus-window-slot.h create mode 100644 src/nautilus-window.c create mode 100644 src/nautilus-window.h create mode 100644 src/nautilus-x-content-bar.c create mode 100644 src/nautilus-x-content-bar.h create mode 100644 src/resources/Checkerboard.png create mode 100644 src/resources/gtk/help-overlay.ui create mode 100644 src/resources/icons/external-link-symbolic.svg create mode 100644 src/resources/icons/funnel-symbolic.svg create mode 100644 src/resources/icons/quotation-symbolic.svg create mode 100644 src/resources/nautilus.gresource.xml create mode 100644 src/resources/style-hc.css create mode 100644 src/resources/style.css create mode 100644 src/resources/text-x-preview.png create mode 100644 src/resources/ui/nautilus-app-chooser.ui create mode 100644 src/resources/ui/nautilus-batch-rename-dialog.ui create mode 100644 src/resources/ui/nautilus-column-chooser.ui create mode 100644 src/resources/ui/nautilus-compress-dialog.ui create mode 100644 src/resources/ui/nautilus-create-folder-dialog.ui create mode 100644 src/resources/ui/nautilus-file-conflict-dialog.ui create mode 100644 src/resources/ui/nautilus-file-properties-change-permissions.ui create mode 100644 src/resources/ui/nautilus-files-view-context-menus.ui create mode 100644 src/resources/ui/nautilus-files-view-select-items.ui create mode 100644 src/resources/ui/nautilus-files-view.ui create mode 100644 src/resources/ui/nautilus-grid-cell.ui create mode 100644 src/resources/ui/nautilus-history-controls.ui create mode 100644 src/resources/ui/nautilus-list-view-column-editor.ui create mode 100644 src/resources/ui/nautilus-name-cell.ui create mode 100644 src/resources/ui/nautilus-operations-ui-manager-request-passphrase.ui create mode 100644 src/resources/ui/nautilus-pathbar-context-menu.ui create mode 100644 src/resources/ui/nautilus-preferences-window.ui create mode 100644 src/resources/ui/nautilus-progress-indicator.ui create mode 100644 src/resources/ui/nautilus-progress-info-widget.ui create mode 100644 src/resources/ui/nautilus-properties-window.ui create mode 100644 src/resources/ui/nautilus-rename-file-popover.ui create mode 100644 src/resources/ui/nautilus-search-popover.ui create mode 100644 src/resources/ui/nautilus-toolbar-view-menu.ui create mode 100644 src/resources/ui/nautilus-toolbar.ui create mode 100644 src/resources/ui/nautilus-view-controls.ui create mode 100644 src/resources/ui/nautilus-window.ui (limited to 'src') diff --git a/src/check-nautilus b/src/check-nautilus new file mode 100755 index 0000000..96e4d56 --- /dev/null +++ b/src/check-nautilus @@ -0,0 +1,2 @@ +#!/bin/sh +G_DEBUG=fatal-warnings ./nautilus --check diff --git a/src/gtk/.editorconfig b/src/gtk/.editorconfig new file mode 100644 index 0000000..e1ee505 --- /dev/null +++ b/src/gtk/.editorconfig @@ -0,0 +1,30 @@ +# SPDX-FileCopyrightText: 2021 The GTK Authors +# SPDX-License-Identifier: CC0-1.0 + +[*] +charset = utf-8 +end_of_line = lf +trim_trailing_whitespace = true + +[*.[ch]] +indent_size = 2 +indent_style = space +insert_final_newline = true +# For the legacy tabs which still exist in the code: +tab_width = 8 + +[*.ui] +indent_size = 2 +indent_style = space +insert_final_newline = true + +[*.xml] +indent_size = 2 +indent_style = space + +[meson.build] +indent_size = 2 +indent_style = space + +[*.md] +max_line_length = 80 diff --git a/src/gtk/nautilusgtkbookmarksmanager.c b/src/gtk/nautilusgtkbookmarksmanager.c new file mode 100644 index 0000000..86f3bb4 --- /dev/null +++ b/src/gtk/nautilusgtkbookmarksmanager.c @@ -0,0 +1,643 @@ +/* -*- Mode: C; c-file-style: "gnu"; tab-width: 8 -*- */ +/* GTK - The GIMP Toolkit + * nautilusgtkbookmarksmanager.c: Utilities to manage and monitor ~/.gtk-bookmarks + * Copyright (C) 2003, Red Hat, Inc. + * Copyright (C) 2007-2008 Carlos Garnacho + * Copyright (C) 2011 Suse + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as + * published by the Free Software Foundation; either version 2.1 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 warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program; if not, see . + * + * Authors: Federico Mena Quintero + */ + +#include "config.h" +#include +#include +#include "nautilus-enum-types.h" + +#include + +#include "nautilusgtkbookmarksmanagerprivate.h" + +static void +_gtk_bookmark_free (gpointer data) +{ + GtkBookmark *bookmark = data; + + g_object_unref (bookmark->file); + g_free (bookmark->label); + g_slice_free (GtkBookmark, bookmark); +} + +static void +set_error_bookmark_doesnt_exist (GFile *file, GError **error) +{ + char *uri = g_file_get_uri (file); + + g_set_error (error, + GTK_FILE_CHOOSER_ERROR, + GTK_FILE_CHOOSER_ERROR_NONEXISTENT, + _("%s does not exist in the bookmarks list"), + uri); + + g_free (uri); +} + +static GFile * +get_legacy_bookmarks_file (void) +{ + GFile *file; + char *filename; + + filename = g_build_filename (g_get_home_dir (), ".gtk-bookmarks", NULL); + file = g_file_new_for_path (filename); + g_free (filename); + + return file; +} + +static GFile * +get_bookmarks_file (void) +{ + GFile *file; + char *filename; + + /* Use gtk-3.0's bookmarks file as the format didn't change. + * Add the 3.0 file format to get_legacy_bookmarks_file() when + * the format does change. + */ + filename = g_build_filename (g_get_user_config_dir (), "gtk-3.0", "bookmarks", NULL); + file = g_file_new_for_path (filename); + g_free (filename); + + return file; +} + +static GSList * +parse_bookmarks (const char *contents) +{ + char **lines, *space; + GSList *bookmarks = NULL; + int i; + + lines = g_strsplit (contents, "\n", -1); + + for (i = 0; lines[i]; i++) + { + GtkBookmark *bookmark; + + if (!*lines[i]) + continue; + + if (!g_utf8_validate (lines[i], -1, NULL)) + continue; + + bookmark = g_slice_new0 (GtkBookmark); + + if ((space = strchr (lines[i], ' ')) != NULL) + { + space[0] = '\0'; + bookmark->label = g_strdup (space + 1); + } + + bookmark->file = g_file_new_for_uri (lines[i]); + bookmarks = g_slist_prepend (bookmarks, bookmark); + } + + bookmarks = g_slist_reverse (bookmarks); + g_strfreev (lines); + + return bookmarks; +} + +static GSList * +read_bookmarks (GFile *file) +{ + char *contents; + GSList *bookmarks = NULL; + + if (!g_file_load_contents (file, NULL, &contents, + NULL, NULL, NULL)) + return NULL; + + bookmarks = parse_bookmarks (contents); + + g_free (contents); + + return bookmarks; +} + +static void +notify_changed (NautilusGtkBookmarksManager *manager) +{ + if (manager->changed_func) + manager->changed_func (manager->changed_func_data); +} + +static void +read_bookmarks_finish (GObject *source, + GAsyncResult *result, + gpointer data) +{ + GFile *file = G_FILE (source); + NautilusGtkBookmarksManager *manager = data; + char *contents = NULL; + GError *error = NULL; + + if (!g_file_load_contents_finish (file, result, &contents, NULL, NULL, &error)) + { + if (!g_error_matches (error, G_IO_ERROR, G_IO_ERROR_CANCELLED)) + g_warning ("Failed to load '%s': %s", g_file_peek_path (file), error->message); + g_error_free (error); + return; + } + + g_slist_free_full (manager->bookmarks, _gtk_bookmark_free); + manager->bookmarks = parse_bookmarks (contents); + + g_free (contents); + + notify_changed (manager); +} + +static void +save_bookmarks (GFile *bookmarks_file, + GSList *bookmarks) +{ + GError *error = NULL; + GString *contents; + GSList *l; + GFile *parent = NULL; + + contents = g_string_new (""); + + for (l = bookmarks; l; l = l->next) + { + GtkBookmark *bookmark = l->data; + char *uri; + + uri = g_file_get_uri (bookmark->file); + if (!uri) + continue; + + g_string_append (contents, uri); + + if (bookmark->label && g_utf8_validate (bookmark->label, -1, NULL)) + g_string_append_printf (contents, " %s", bookmark->label); + + g_string_append_c (contents, '\n'); + g_free (uri); + } + + parent = g_file_get_parent (bookmarks_file); + if (!g_file_make_directory_with_parents (parent, NULL, &error)) + { + if (g_error_matches (error, G_IO_ERROR, G_IO_ERROR_EXISTS)) + g_clear_error (&error); + else + goto out; + } + if (!g_file_replace_contents (bookmarks_file, + contents->str, + contents->len, + NULL, FALSE, 0, NULL, + NULL, &error)) + goto out; + + out: + if (error) + { + g_critical ("%s", error->message); + g_error_free (error); + } + g_clear_object (&parent); + g_string_free (contents, TRUE); +} + +static void +bookmarks_file_changed (GFileMonitor *monitor, + GFile *file, + GFile *other_file, + GFileMonitorEvent event, + gpointer data) +{ + NautilusGtkBookmarksManager *manager = data; + + switch (event) + { + case G_FILE_MONITOR_EVENT_CHANGED: + case G_FILE_MONITOR_EVENT_CHANGES_DONE_HINT: + case G_FILE_MONITOR_EVENT_CREATED: + g_file_load_contents_async (file, manager->cancellable, read_bookmarks_finish, manager); + break; + + case G_FILE_MONITOR_EVENT_DELETED: + case G_FILE_MONITOR_EVENT_ATTRIBUTE_CHANGED: + case G_FILE_MONITOR_EVENT_PRE_UNMOUNT: + case G_FILE_MONITOR_EVENT_UNMOUNTED: + case G_FILE_MONITOR_EVENT_MOVED: + case G_FILE_MONITOR_EVENT_RENAMED: + case G_FILE_MONITOR_EVENT_MOVED_IN: + case G_FILE_MONITOR_EVENT_MOVED_OUT: + default: + /* ignore at the moment */ + break; + } +} + +NautilusGtkBookmarksManager * +_nautilus_gtk_bookmarks_manager_new (GtkBookmarksChangedFunc changed_func, gpointer changed_func_data) +{ + NautilusGtkBookmarksManager *manager; + GFile *bookmarks_file; + GError *error; + + manager = g_new0 (NautilusGtkBookmarksManager, 1); + + manager->changed_func = changed_func; + manager->changed_func_data = changed_func_data; + + manager->cancellable = g_cancellable_new (); + + bookmarks_file = get_bookmarks_file (); + if (!g_file_query_exists (bookmarks_file, NULL)) + { + GFile *legacy_bookmarks_file; + + /* Read the legacy one and write it to the new one */ + legacy_bookmarks_file = get_legacy_bookmarks_file (); + manager->bookmarks = read_bookmarks (legacy_bookmarks_file); + if (manager->bookmarks) + save_bookmarks (bookmarks_file, manager->bookmarks); + + g_object_unref (legacy_bookmarks_file); + } + else + g_file_load_contents_async (bookmarks_file, manager->cancellable, read_bookmarks_finish, manager); + + error = NULL; + manager->bookmarks_monitor = g_file_monitor_file (bookmarks_file, + G_FILE_MONITOR_NONE, + NULL, &error); + if (error) + { + g_warning ("%s", error->message); + g_error_free (error); + } + else + manager->bookmarks_monitor_changed_id = g_signal_connect (manager->bookmarks_monitor, "changed", + G_CALLBACK (bookmarks_file_changed), manager); + + + g_object_unref (bookmarks_file); + + return manager; +} + +void +_nautilus_gtk_bookmarks_manager_free (NautilusGtkBookmarksManager *manager) +{ + g_return_if_fail (manager != NULL); + + g_cancellable_cancel (manager->cancellable); + g_object_unref (manager->cancellable); + + if (manager->bookmarks_monitor) + { + g_file_monitor_cancel (manager->bookmarks_monitor); + g_signal_handler_disconnect (manager->bookmarks_monitor, manager->bookmarks_monitor_changed_id); + manager->bookmarks_monitor_changed_id = 0; + g_object_unref (manager->bookmarks_monitor); + } + + g_slist_free_full (manager->bookmarks, _gtk_bookmark_free); + + g_free (manager); +} + +GSList * +_nautilus_gtk_bookmarks_manager_list_bookmarks (NautilusGtkBookmarksManager *manager) +{ + GSList *bookmarks, *files = NULL; + + g_return_val_if_fail (manager != NULL, NULL); + + bookmarks = manager->bookmarks; + + while (bookmarks) + { + GtkBookmark *bookmark; + + bookmark = bookmarks->data; + bookmarks = bookmarks->next; + + files = g_slist_prepend (files, g_object_ref (bookmark->file)); + } + + return g_slist_reverse (files); +} + +static GSList * +find_bookmark_link_for_file (GSList *bookmarks, GFile *file, int *position_ret) +{ + int pos; + + pos = 0; + for (; bookmarks; bookmarks = bookmarks->next) + { + GtkBookmark *bookmark = bookmarks->data; + + if (g_file_equal (file, bookmark->file)) + { + if (position_ret) + *position_ret = pos; + + return bookmarks; + } + + pos++; + } + + if (position_ret) + *position_ret = -1; + + return NULL; +} + +gboolean +_nautilus_gtk_bookmarks_manager_has_bookmark (NautilusGtkBookmarksManager *manager, + GFile *file) +{ + GSList *link; + + link = find_bookmark_link_for_file (manager->bookmarks, file, NULL); + return (link != NULL); +} + +gboolean +_nautilus_gtk_bookmarks_manager_insert_bookmark (NautilusGtkBookmarksManager *manager, + GFile *file, + int position, + GError **error) +{ + GSList *link; + GtkBookmark *bookmark; + GFile *bookmarks_file; + + g_return_val_if_fail (manager != NULL, FALSE); + g_return_val_if_fail (error == NULL || *error == NULL, FALSE); + + link = find_bookmark_link_for_file (manager->bookmarks, file, NULL); + + if (link) + { + char *uri; + bookmark = link->data; + uri = g_file_get_uri (bookmark->file); + + g_set_error (error, + GTK_FILE_CHOOSER_ERROR, + GTK_FILE_CHOOSER_ERROR_ALREADY_EXISTS, + _("%s already exists in the bookmarks list"), + uri); + + g_free (uri); + + return FALSE; + } + + bookmark = g_slice_new0 (GtkBookmark); + bookmark->file = g_object_ref (file); + + manager->bookmarks = g_slist_insert (manager->bookmarks, bookmark, position); + + bookmarks_file = get_bookmarks_file (); + save_bookmarks (bookmarks_file, manager->bookmarks); + g_object_unref (bookmarks_file); + + notify_changed (manager); + + return TRUE; +} + +gboolean +_nautilus_gtk_bookmarks_manager_remove_bookmark (NautilusGtkBookmarksManager *manager, + GFile *file, + GError **error) +{ + GSList *link; + GFile *bookmarks_file; + + g_return_val_if_fail (manager != NULL, FALSE); + g_return_val_if_fail (error == NULL || *error == NULL, FALSE); + + if (!manager->bookmarks) + return FALSE; + + link = find_bookmark_link_for_file (manager->bookmarks, file, NULL); + if (link) + { + GtkBookmark *bookmark = link->data; + + manager->bookmarks = g_slist_remove_link (manager->bookmarks, link); + _gtk_bookmark_free (bookmark); + g_slist_free_1 (link); + } + else + { + set_error_bookmark_doesnt_exist (file, error); + return FALSE; + } + + bookmarks_file = get_bookmarks_file (); + save_bookmarks (bookmarks_file, manager->bookmarks); + g_object_unref (bookmarks_file); + + notify_changed (manager); + + return TRUE; +} + +gboolean +_nautilus_gtk_bookmarks_manager_reorder_bookmark (NautilusGtkBookmarksManager *manager, + GFile *file, + int new_position, + GError **error) +{ + GSList *link; + GFile *bookmarks_file; + int old_position; + + g_return_val_if_fail (manager != NULL, FALSE); + g_return_val_if_fail (error == NULL || *error == NULL, FALSE); + g_return_val_if_fail (new_position >= 0, FALSE); + + if (!manager->bookmarks) + return FALSE; + + link = find_bookmark_link_for_file (manager->bookmarks, file, &old_position); + if (new_position == old_position) + return TRUE; + + if (link) + { + GtkBookmark *bookmark = link->data; + + manager->bookmarks = g_slist_remove_link (manager->bookmarks, link); + g_slist_free_1 (link); + + if (new_position > old_position) + new_position--; + + manager->bookmarks = g_slist_insert (manager->bookmarks, bookmark, new_position); + } + else + { + set_error_bookmark_doesnt_exist (file, error); + return FALSE; + } + + bookmarks_file = get_bookmarks_file (); + save_bookmarks (bookmarks_file, manager->bookmarks); + g_object_unref (bookmarks_file); + + notify_changed (manager); + + return TRUE; +} + +char * +_nautilus_gtk_bookmarks_manager_get_bookmark_label (NautilusGtkBookmarksManager *manager, + GFile *file) +{ + GSList *bookmarks; + char *label = NULL; + + g_return_val_if_fail (manager != NULL, NULL); + g_return_val_if_fail (file != NULL, NULL); + + bookmarks = manager->bookmarks; + + while (bookmarks) + { + GtkBookmark *bookmark; + + bookmark = bookmarks->data; + bookmarks = bookmarks->next; + + if (g_file_equal (file, bookmark->file)) + { + label = g_strdup (bookmark->label); + break; + } + } + + return label; +} + +gboolean +_nautilus_gtk_bookmarks_manager_set_bookmark_label (NautilusGtkBookmarksManager *manager, + GFile *file, + const char *label, + GError **error) +{ + GFile *bookmarks_file; + GSList *link; + + g_return_val_if_fail (manager != NULL, FALSE); + g_return_val_if_fail (file != NULL, FALSE); + + link = find_bookmark_link_for_file (manager->bookmarks, file, NULL); + if (link) + { + GtkBookmark *bookmark = link->data; + + g_free (bookmark->label); + bookmark->label = g_strdup (label); + } + else + { + set_error_bookmark_doesnt_exist (file, error); + return FALSE; + } + + bookmarks_file = get_bookmarks_file (); + save_bookmarks (bookmarks_file, manager->bookmarks); + g_object_unref (bookmarks_file); + + notify_changed (manager); + + return TRUE; +} + +static gboolean +_nautilus_gtk_bookmarks_manager_get_xdg_type (NautilusGtkBookmarksManager *manager, + GFile *file, + GUserDirectory *directory) +{ + GSList *link; + gboolean match; + GFile *location; + const char *path; + GUserDirectory dir; + GtkBookmark *bookmark; + + link = find_bookmark_link_for_file (manager->bookmarks, file, NULL); + if (!link) + return FALSE; + + match = FALSE; + bookmark = link->data; + + for (dir = 0; dir < G_USER_N_DIRECTORIES; dir++) + { + path = g_get_user_special_dir (dir); + if (!path) + continue; + + location = g_file_new_for_path (path); + match = g_file_equal (location, bookmark->file); + g_object_unref (location); + + if (match) + break; + } + + if (match && directory != NULL) + *directory = dir; + + return match; +} + +gboolean +_nautilus_gtk_bookmarks_manager_get_is_builtin (NautilusGtkBookmarksManager *manager, + GFile *file) +{ + GUserDirectory xdg_type; + + /* if this is not an XDG dir, it's never builtin */ + if (!_nautilus_gtk_bookmarks_manager_get_xdg_type (manager, file, &xdg_type)) + return FALSE; + + /* exclude XDG locations we don't display by default */ + return _nautilus_gtk_bookmarks_manager_get_is_xdg_dir_builtin (xdg_type); +} + +gboolean +_nautilus_gtk_bookmarks_manager_get_is_xdg_dir_builtin (GUserDirectory xdg_type) +{ + return (xdg_type != G_USER_DIRECTORY_DESKTOP) && + (xdg_type != G_USER_DIRECTORY_TEMPLATES) && + (xdg_type != G_USER_DIRECTORY_PUBLIC_SHARE); +} diff --git a/src/gtk/nautilusgtkbookmarksmanagerprivate.h b/src/gtk/nautilusgtkbookmarksmanagerprivate.h new file mode 100644 index 0000000..99890a3 --- /dev/null +++ b/src/gtk/nautilusgtkbookmarksmanagerprivate.h @@ -0,0 +1,89 @@ +/* -*- Mode: C; c-file-style: "gnu"; tab-width: 8 -*- */ +/* GTK - The GIMP Toolkit + * nautilusgtkbookmarksmanager.h: Utilities to manage and monitor ~/.gtk-bookmarks + * Copyright (C) 2003, Red Hat, Inc. + * Copyright (C) 2007-2008 Carlos Garnacho + * Copyright (C) 2011 Suse + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as + * published by the Free Software Foundation; either version 2.1 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 warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program; if not, see . + * + * Authors: Federico Mena Quintero + */ + +#ifndef __NAUTILUS_GTK_BOOKMARKS_MANAGER_H__ +#define __NAUTILUS_GTK_BOOKMARKS_MANAGER_H__ + +#include + +typedef void (* GtkBookmarksChangedFunc) (gpointer data); + +typedef struct +{ + /* This list contains GtkBookmark structs */ + GSList *bookmarks; + + GFileMonitor *bookmarks_monitor; + gulong bookmarks_monitor_changed_id; + + gpointer changed_func_data; + GtkBookmarksChangedFunc changed_func; + + GCancellable *cancellable; +} NautilusGtkBookmarksManager; + +typedef struct +{ + GFile *file; + char *label; +} GtkBookmark; + +NautilusGtkBookmarksManager *_nautilus_gtk_bookmarks_manager_new (GtkBookmarksChangedFunc changed_func, + gpointer changed_func_data); + + +void _nautilus_gtk_bookmarks_manager_free (NautilusGtkBookmarksManager *manager); + +GSList *_nautilus_gtk_bookmarks_manager_list_bookmarks (NautilusGtkBookmarksManager *manager); + +gboolean _nautilus_gtk_bookmarks_manager_insert_bookmark (NautilusGtkBookmarksManager *manager, + GFile *file, + int position, + GError **error); + +gboolean _nautilus_gtk_bookmarks_manager_remove_bookmark (NautilusGtkBookmarksManager *manager, + GFile *file, + GError **error); + +gboolean _nautilus_gtk_bookmarks_manager_reorder_bookmark (NautilusGtkBookmarksManager *manager, + GFile *file, + int new_position, + GError **error); + +gboolean _nautilus_gtk_bookmarks_manager_has_bookmark (NautilusGtkBookmarksManager *manager, + GFile *file); + +char * _nautilus_gtk_bookmarks_manager_get_bookmark_label (NautilusGtkBookmarksManager *manager, + GFile *file); + +gboolean _nautilus_gtk_bookmarks_manager_set_bookmark_label (NautilusGtkBookmarksManager *manager, + GFile *file, + const char *label, + GError **error); + +gboolean _nautilus_gtk_bookmarks_manager_get_is_builtin (NautilusGtkBookmarksManager *manager, + GFile *file); + +gboolean _nautilus_gtk_bookmarks_manager_get_is_xdg_dir_builtin (GUserDirectory xdg_type); + +#endif /* __NAUTILUS_GTK_BOOKMARKS_MANAGER_H__ */ diff --git a/src/gtk/nautilusgtkplacessidebar.c b/src/gtk/nautilusgtkplacessidebar.c new file mode 100644 index 0000000..c536e89 --- /dev/null +++ b/src/gtk/nautilusgtkplacessidebar.c @@ -0,0 +1,5142 @@ +/* NautilusGtkPlacesSidebar - sidebar widget for places in the filesystem + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library. If not, see . + * + * This code is originally from Nautilus. + * + * Authors : Mr Jamie McCracken (jamiemcc at blueyonder dot co dot uk) + * Cosimo Cecchi + * Federico Mena Quintero + * Carlos Soriano + */ + +#include "config.h" +#include +#include +#include "nautilus-enum-types.h" + +#include +#include + +#include "nautilusgtkplacessidebarprivate.h" +#include "nautilusgtksidebarrowprivate.h" +#include "gdk/gdkkeysyms.h" +#include "nautilusgtkbookmarksmanagerprivate.h" +#include "nautilus-dnd.h" +#include "nautilus-dbus-launcher.h" +#include "nautilus-file.h" +#include "nautilus-file-operations.h" +#include "nautilus-global-preferences.h" +#include "nautilus-properties-window.h" +#include "nautilus-trash-monitor.h" + +#ifdef GDK_WINDOWING_X11 +#include +#endif + +#pragma GCC diagnostic ignored "-Wshadow" + +/*< private > + * NautilusGtkPlacesSidebar: + * + * NautilusGtkPlacesSidebar is a widget that displays a list of frequently-used places in the + * file system: the user’s home directory, the user’s bookmarks, and volumes and drives. + * This widget is used as a sidebar in GtkFileChooser and may be used by file managers + * and similar programs. + * + * The places sidebar displays drives and volumes, and will automatically mount + * or unmount them when the user selects them. + * + * Applications can hook to various signals in the places sidebar to customize + * its behavior. For example, they can add extra commands to the context menu + * of the sidebar. + * + * While bookmarks are completely in control of the user, the places sidebar also + * allows individual applications to provide extra shortcut folders that are unique + * to each application. For example, a Paint program may want to add a shortcut + * for a Clipart folder. You can do this with nautilus_gtk_places_sidebar_add_shortcut(). + * + * To make use of the places sidebar, an application at least needs to connect + * to the NautilusGtkPlacesSidebar::open-location signal. This is emitted when the + * user selects in the sidebar a location to open. The application should also + * call nautilus_gtk_places_sidebar_set_location() when it changes the currently-viewed + * location. + * + * # CSS nodes + * + * NautilusGtkPlacesSidebar uses a single CSS node with name placessidebar and style + * class .sidebar. + * + * Among the children of the places sidebar, the following style classes can + * be used: + * - .sidebar-new-bookmark-row for the 'Add new bookmark' row + * - .sidebar-placeholder-row for a row that is a placeholder + * - .has-open-popup when a popup is open for a row + */ + +/* These are used when a destination-side DND operation is taking place. + * Normally, when a common drag action is taking place, the state will be + * DROP_STATE_NEW_BOOKMARK_ARMED, however, if the client of NautilusGtkPlacesSidebar + * wants to show hints about the valid targets, we sill set it as + * DROP_STATE_NEW_BOOKMARK_ARMED_PERMANENT, so the sidebar will show drop hints + * until the client says otherwise + */ +typedef enum { + DROP_STATE_NORMAL, + DROP_STATE_NEW_BOOKMARK_ARMED, + DROP_STATE_NEW_BOOKMARK_ARMED_PERMANENT, +} DropState; + +struct _NautilusGtkPlacesSidebar { + GtkWidget parent; + + GtkWidget *swin; + GtkWidget *list_box; + GtkWidget *new_bookmark_row; + + NautilusGtkBookmarksManager *bookmarks_manager; + + GActionGroup *row_actions; + + CloudProvidersCollector *cloud_manager; + GList *unready_accounts; + + GVolumeMonitor *volume_monitor; + GtkSettings *gtk_settings; + GFile *current_location; + + GtkWidget *rename_popover; + GtkWidget *rename_entry; + GtkWidget *rename_button; + GtkWidget *rename_error; + char *rename_uri; + + GtkWidget *trash_row; + + /* DND */ + gboolean dragging_over; + GtkWidget *drag_row; + int drag_row_height; + int drag_row_x; + int drag_row_y; + GtkWidget *row_placeholder; + DropState drop_state; + guint hover_timer_id; + graphene_point_t hover_start_point; + GtkListBoxRow *hover_row; + + /* volume mounting - delayed open process */ + NautilusGtkPlacesOpenFlags go_to_after_mount_open_flags; + GCancellable *cancellable; + + GtkWidget *popover; + NautilusGtkSidebarRow *context_row; + GListStore *shortcuts; + + GDBusProxy *hostnamed_proxy; + GCancellable *hostnamed_cancellable; + char *hostname; + + NautilusGtkPlacesOpenFlags open_flags; + + guint mounting : 1; + guint show_recent_set : 1; + guint show_recent : 1; + guint show_desktop_set : 1; + guint show_desktop : 1; + guint show_enter_location : 1; + guint show_other_locations : 1; + guint show_trash : 1; + guint show_starred_location : 1; +}; + +struct _NautilusGtkPlacesSidebarClass { + GtkWidgetClass parent_class; + + void (* open_location) (NautilusGtkPlacesSidebar *sidebar, + GFile *location, + NautilusGtkPlacesOpenFlags open_flags); + void (* show_error_message) (NautilusGtkPlacesSidebar *sidebar, + const char *primary, + const char *secondary); + GdkDragAction (* drag_action_requested) (NautilusGtkPlacesSidebar *sidebar, + GFile *dest_file, + GSList *source_file_list); + GdkDragAction (* drag_action_ask) (NautilusGtkPlacesSidebar *sidebar, + GdkDragAction actions); + void (* drag_perform_drop) (NautilusGtkPlacesSidebar *sidebar, + GFile *dest_file, + GList *source_file_list, + GdkDragAction action); + void (* show_enter_location) (NautilusGtkPlacesSidebar *sidebar); + + void (* show_other_locations_with_flags) (NautilusGtkPlacesSidebar *sidebar, + NautilusGtkPlacesOpenFlags open_flags); + + void (* show_starred_location) (NautilusGtkPlacesSidebar *sidebar); + + void (* mount) (NautilusGtkPlacesSidebar *sidebar, + GMountOperation *mount_operation); + void (* unmount) (NautilusGtkPlacesSidebar *sidebar, + GMountOperation *unmount_operation); +}; + +enum { + OPEN_LOCATION, + SHOW_ERROR_MESSAGE, + SHOW_ENTER_LOCATION, + DRAG_ACTION_REQUESTED, + DRAG_ACTION_ASK, + DRAG_PERFORM_DROP, + SHOW_OTHER_LOCATIONS_WITH_FLAGS, + SHOW_STARRED_LOCATION, + MOUNT, + UNMOUNT, + LAST_SIGNAL +}; + +enum { + PROP_LOCATION = 1, + PROP_OPEN_FLAGS, + PROP_SHOW_RECENT, + PROP_SHOW_DESKTOP, + PROP_SHOW_ENTER_LOCATION, + PROP_SHOW_TRASH, + PROP_SHOW_STARRED_LOCATION, + PROP_SHOW_OTHER_LOCATIONS, + NUM_PROPERTIES +}; + +/* Names for themed icons */ +#define ICON_NAME_HOME "user-home-symbolic" +#define ICON_NAME_DESKTOP "user-desktop-symbolic" +#define ICON_NAME_FILESYSTEM "drive-harddisk-symbolic" +#define ICON_NAME_EJECT "media-eject-symbolic" +#define ICON_NAME_NETWORK "network-workgroup-symbolic" +#define ICON_NAME_NETWORK_SERVER "network-server-symbolic" +#define ICON_NAME_FOLDER_NETWORK "folder-remote-symbolic" +#define ICON_NAME_OTHER_LOCATIONS "list-add-symbolic" + +#define ICON_NAME_FOLDER "folder-symbolic" +#define ICON_NAME_FOLDER_DESKTOP "user-desktop-symbolic" +#define ICON_NAME_FOLDER_DOCUMENTS "folder-documents-symbolic" +#define ICON_NAME_FOLDER_DOWNLOAD "folder-download-symbolic" +#define ICON_NAME_FOLDER_MUSIC "folder-music-symbolic" +#define ICON_NAME_FOLDER_PICTURES "folder-pictures-symbolic" +#define ICON_NAME_FOLDER_PUBLIC_SHARE "folder-publicshare-symbolic" +#define ICON_NAME_FOLDER_TEMPLATES "folder-templates-symbolic" +#define ICON_NAME_FOLDER_VIDEOS "folder-videos-symbolic" +#define ICON_NAME_FOLDER_SAVED_SEARCH "folder-saved-search-symbolic" + +static guint places_sidebar_signals [LAST_SIGNAL] = { 0 }; +static GParamSpec *properties[NUM_PROPERTIES] = { NULL, }; + +static gboolean eject_or_unmount_bookmark (NautilusGtkSidebarRow *row); +static gboolean eject_or_unmount_selection (NautilusGtkPlacesSidebar *sidebar); +static void check_unmount_and_eject (GMount *mount, + GVolume *volume, + GDrive *drive, + gboolean *show_unmount, + gboolean *show_eject); +static void on_row_pressed (GtkGestureClick *gesture, + int n_press, + double x, + double y, + NautilusGtkSidebarRow *row); +static void on_row_released (GtkGestureClick *gesture, + int n_press, + double x, + double y, + NautilusGtkSidebarRow *row); +static void on_row_dragged (GtkGestureDrag *gesture, + double x, + double y, + NautilusGtkSidebarRow *row); + +static void popup_menu_cb (NautilusGtkSidebarRow *row); +static void long_press_cb (GtkGesture *gesture, + double x, + double y, + NautilusGtkPlacesSidebar *sidebar); +static void stop_drop_feedback (NautilusGtkPlacesSidebar *sidebar); +static GMountOperation * get_mount_operation (NautilusGtkPlacesSidebar *sidebar); +static GMountOperation * get_unmount_operation (NautilusGtkPlacesSidebar *sidebar); + + +G_DEFINE_TYPE (NautilusGtkPlacesSidebar, nautilus_gtk_places_sidebar, GTK_TYPE_WIDGET); + +static void +emit_open_location (NautilusGtkPlacesSidebar *sidebar, + GFile *location, + NautilusGtkPlacesOpenFlags open_flags) +{ + if ((open_flags & sidebar->open_flags) == 0) + open_flags = NAUTILUS_GTK_PLACES_OPEN_NORMAL; + + g_signal_emit (sidebar, places_sidebar_signals[OPEN_LOCATION], 0, + location, open_flags); +} + +static void +emit_show_error_message (NautilusGtkPlacesSidebar *sidebar, + const char *primary, + const char *secondary) +{ + g_signal_emit (sidebar, places_sidebar_signals[SHOW_ERROR_MESSAGE], 0, + primary, secondary); +} + +static void +emit_show_enter_location (NautilusGtkPlacesSidebar *sidebar) +{ + g_signal_emit (sidebar, places_sidebar_signals[SHOW_ENTER_LOCATION], 0); +} + +static void +emit_show_other_locations_with_flags (NautilusGtkPlacesSidebar *sidebar, + NautilusGtkPlacesOpenFlags open_flags) +{ + g_signal_emit (sidebar, places_sidebar_signals[SHOW_OTHER_LOCATIONS_WITH_FLAGS], + 0, open_flags); +} + +static void +emit_show_starred_location (NautilusGtkPlacesSidebar *sidebar, + NautilusGtkPlacesOpenFlags open_flags) +{ + g_signal_emit (sidebar, places_sidebar_signals[SHOW_STARRED_LOCATION], 0, + open_flags); +} + + +static void +emit_mount_operation (NautilusGtkPlacesSidebar *sidebar, + GMountOperation *mount_op) +{ + g_signal_emit (sidebar, places_sidebar_signals[MOUNT], 0, mount_op); +} + +static void +emit_unmount_operation (NautilusGtkPlacesSidebar *sidebar, + GMountOperation *mount_op) +{ + g_signal_emit (sidebar, places_sidebar_signals[UNMOUNT], 0, mount_op); +} + +static GdkDragAction +emit_drag_action_requested (NautilusGtkPlacesSidebar *sidebar, + NautilusFile *dest_file, + GSList *source_file_list) +{ + GdkDragAction ret_action = 0; + + g_signal_emit (sidebar, places_sidebar_signals[DRAG_ACTION_REQUESTED], 0, + dest_file, source_file_list, &ret_action); + + return ret_action; +} + +static void +emit_drag_perform_drop (NautilusGtkPlacesSidebar *sidebar, + GFile *dest_file, + GSList *source_file_list, + GdkDragAction action) +{ + g_signal_emit (sidebar, places_sidebar_signals[DRAG_PERFORM_DROP], 0, + dest_file, source_file_list, action); +} +static void +list_box_header_func (GtkListBoxRow *row, + GtkListBoxRow *before, + gpointer user_data) +{ + NautilusGtkPlacesSectionType row_section_type; + NautilusGtkPlacesSectionType before_section_type; + GtkWidget *separator; + + gtk_list_box_row_set_header (row, NULL); + + g_object_get (row, "section-type", &row_section_type, NULL); + if (before) + { + g_object_get (before, "section-type", &before_section_type, NULL); + } + else + { + before_section_type = NAUTILUS_GTK_PLACES_SECTION_INVALID; + } + + if (before && before_section_type != row_section_type) + { + separator = gtk_separator_new (GTK_ORIENTATION_HORIZONTAL); + gtk_list_box_row_set_header (row, separator); + } +} + +static GtkWidget* +add_place (NautilusGtkPlacesSidebar *sidebar, + NautilusGtkPlacesPlaceType place_type, + NautilusGtkPlacesSectionType section_type, + const char *name, + GIcon *start_icon, + GIcon *end_icon, + const char *uri, + GDrive *drive, + GVolume *volume, + GMount *mount, + CloudProvidersAccount *cloud_provider_account, + const int index, + const char *tooltip) +{ + gboolean show_eject, show_unmount; + gboolean show_eject_button; + GtkWidget *row; + GtkWidget *eject_button; + GtkGesture *gesture; + char *eject_tooltip; + + check_unmount_and_eject (mount, volume, drive, + &show_unmount, &show_eject); + + if (show_unmount || show_eject) + g_assert (place_type != NAUTILUS_GTK_PLACES_BOOKMARK); + + show_eject_button = (show_unmount || show_eject); + if (mount != NULL && volume == NULL && drive == NULL) + eject_tooltip = _("Disconnect"); + else if (show_eject) + eject_tooltip = _("Eject"); + else + eject_tooltip = _("Unmount"); + + row = g_object_new (NAUTILUS_TYPE_GTK_SIDEBAR_ROW, + "sidebar", sidebar, + "start-icon", start_icon, + "end-icon", end_icon, + "label", name, + "tooltip", tooltip, + "ejectable", show_eject_button, + "eject-tooltip", eject_tooltip, + "order-index", index, + "section-type", section_type, + "place-type", place_type, + "uri", uri, + "drive", drive, + "volume", volume, + "mount", mount, + "cloud-provider-account", cloud_provider_account, + NULL); + + eject_button = nautilus_gtk_sidebar_row_get_eject_button (NAUTILUS_GTK_SIDEBAR_ROW (row)); + + g_signal_connect_swapped (eject_button, "clicked", + G_CALLBACK (eject_or_unmount_bookmark), row); + + gesture = gtk_gesture_click_new (); + gtk_gesture_single_set_button (GTK_GESTURE_SINGLE (gesture), 0); + g_signal_connect (gesture, "pressed", + G_CALLBACK (on_row_pressed), row); + g_signal_connect (gesture, "released", + G_CALLBACK (on_row_released), row); + gtk_widget_add_controller (row, GTK_EVENT_CONTROLLER (gesture)); + + gesture = gtk_gesture_drag_new (); + g_signal_connect (gesture, "drag-update", + G_CALLBACK (on_row_dragged), row); + gtk_widget_add_controller (row, GTK_EVENT_CONTROLLER (gesture)); + + gtk_list_box_insert (GTK_LIST_BOX (sidebar->list_box), GTK_WIDGET (row), -1); + + return row; +} + +static GIcon * +special_directory_get_gicon (GUserDirectory directory) +{ +#define ICON_CASE(x) \ + case G_USER_DIRECTORY_ ## x: \ + return g_themed_icon_new_with_default_fallbacks (ICON_NAME_FOLDER_ ## x); + + switch (directory) + { + + ICON_CASE (DESKTOP); + ICON_CASE (DOCUMENTS); + ICON_CASE (DOWNLOAD); + ICON_CASE (MUSIC); + ICON_CASE (PICTURES); + ICON_CASE (PUBLIC_SHARE); + ICON_CASE (TEMPLATES); + ICON_CASE (VIDEOS); + + case G_USER_N_DIRECTORIES: + default: + return g_themed_icon_new_with_default_fallbacks (ICON_NAME_FOLDER); + } + +#undef ICON_CASE +} + +static gboolean +recent_files_setting_is_enabled (NautilusGtkPlacesSidebar *sidebar) +{ + GtkSettings *settings; + gboolean enabled; + + settings = gtk_widget_get_settings (GTK_WIDGET (sidebar)); + g_object_get (settings, "gtk-recent-files-enabled", &enabled, NULL); + + return enabled; +} + +static gboolean +recent_scheme_is_supported (void) +{ + const char * const *supported; + + supported = g_vfs_get_supported_uri_schemes (g_vfs_get_default ()); + if (supported != NULL) + return g_strv_contains (supported, "recent"); + + return FALSE; +} + +static gboolean +should_show_recent (NautilusGtkPlacesSidebar *sidebar) +{ + return recent_files_setting_is_enabled (sidebar) && + ((sidebar->show_recent_set && sidebar->show_recent) || + (!sidebar->show_recent_set && recent_scheme_is_supported ())); +} + +static gboolean +path_is_home_dir (const char *path) +{ + GFile *home_dir; + GFile *location; + const char *home_path; + gboolean res; + + home_path = g_get_home_dir (); + if (!home_path) + return FALSE; + + home_dir = g_file_new_for_path (home_path); + location = g_file_new_for_path (path); + res = g_file_equal (home_dir, location); + + g_object_unref (home_dir); + g_object_unref (location); + + return res; +} + +static void +open_home (NautilusGtkPlacesSidebar *sidebar) +{ + const char *home_path; + GFile *home_dir; + + home_path = g_get_home_dir (); + if (!home_path) + return; + + home_dir = g_file_new_for_path (home_path); + emit_open_location (sidebar, home_dir, 0); + + g_object_unref (home_dir); +} + +static void +add_special_dirs (NautilusGtkPlacesSidebar *sidebar) +{ + GList *dirs; + int index; + + dirs = NULL; + for (index = 0; index < G_USER_N_DIRECTORIES; index++) + { + const char *path; + GFile *root; + GIcon *start_icon; + char *name; + char *mount_uri; + char *tooltip; + + if (!_nautilus_gtk_bookmarks_manager_get_is_xdg_dir_builtin (index)) + continue; + + path = g_get_user_special_dir (index); + + /* XDG resets special dirs to the home directory in case + * it's not finiding what it expects. We don't want the home + * to be added multiple times in that weird configuration. + */ + if (path == NULL || + path_is_home_dir (path) || + g_list_find_custom (dirs, path, (GCompareFunc) g_strcmp0) != NULL) + continue; + + root = g_file_new_for_path (path); + + name = _nautilus_gtk_bookmarks_manager_get_bookmark_label (sidebar->bookmarks_manager, root); + if (!name) + name = g_file_get_basename (root); + + start_icon = special_directory_get_gicon (index); + mount_uri = g_file_get_uri (root); + tooltip = g_file_get_parse_name (root); + + add_place (sidebar, NAUTILUS_GTK_PLACES_XDG_DIR, + NAUTILUS_GTK_PLACES_SECTION_COMPUTER, + name, start_icon, NULL, mount_uri, + NULL, NULL, NULL, NULL, 0, + tooltip); + g_free (name); + g_object_unref (root); + g_object_unref (start_icon); + g_free (mount_uri); + g_free (tooltip); + + dirs = g_list_prepend (dirs, (char *)path); + } + + g_list_free (dirs); +} + +static char * +get_home_directory_uri (void) +{ + const char *home; + + home = g_get_home_dir (); + if (!home) + return NULL; + + return g_filename_to_uri (home, NULL, NULL); +} + +static char * +get_desktop_directory_uri (void) +{ + const char *name; + + name = g_get_user_special_dir (G_USER_DIRECTORY_DESKTOP); + + /* "To disable a directory, point it to the homedir." + * See http://freedesktop.org/wiki/Software/xdg-user-dirs + */ + if (path_is_home_dir (name)) + return NULL; + + return g_filename_to_uri (name, NULL, NULL); +} + +static gboolean +file_is_shown (NautilusGtkPlacesSidebar *sidebar, + GFile *file) +{ + char *uri; + GtkWidget *row; + gboolean found = FALSE; + + for (row = gtk_widget_get_first_child (GTK_WIDGET (sidebar->list_box)); + row != NULL && !found; + row = gtk_widget_get_next_sibling (row)) + { + if (!GTK_IS_LIST_BOX_ROW (row)) + continue; + + g_object_get (row, "uri", &uri, NULL); + if (uri) + { + GFile *other; + other = g_file_new_for_uri (uri); + found = g_file_equal (file, other); + g_object_unref (other); + g_free (uri); + } + } + + return found; +} + +typedef struct +{ + NautilusGtkPlacesSidebar *sidebar; + guint position; +} ShortcutData; + +static void +on_app_shortcuts_query_complete (GObject *source, + GAsyncResult *result, + gpointer data) +{ + ShortcutData *sdata = data; + NautilusGtkPlacesSidebar *sidebar = sdata->sidebar; + guint pos = sdata->position; + GFile *file = G_FILE (source); + GFileInfo *info; + + g_free (sdata); + + info = g_file_query_info_finish (file, result, NULL); + + if (info) + { + char *uri; + char *tooltip; + const char *name; + GIcon *start_icon; + + name = g_file_info_get_display_name (info); + start_icon = g_file_info_get_symbolic_icon (info); + uri = g_file_get_uri (file); + tooltip = g_file_get_parse_name (file); + + add_place (sidebar, NAUTILUS_GTK_PLACES_BUILT_IN, + NAUTILUS_GTK_PLACES_SECTION_COMPUTER, + name, start_icon, NULL, uri, + NULL, NULL, NULL, NULL, + pos, + tooltip); + + g_free (uri); + g_free (tooltip); + + g_object_unref (info); + } +} + +static void +add_application_shortcuts (NautilusGtkPlacesSidebar *sidebar) +{ + guint i, n; + + n = g_list_model_get_n_items (G_LIST_MODEL (sidebar->shortcuts)); + for (i = 0; i < n; i++) + { + GFile *file = g_list_model_get_item (G_LIST_MODEL (sidebar->shortcuts), i); + ShortcutData *data; + + g_object_unref (file); + + if (file_is_shown (sidebar, file)) + continue; + + data = g_new (ShortcutData, 1); + data->sidebar = sidebar; + data->position = i; + g_file_query_info_async (file, + "standard::display-name,standard::symbolic-icon", + G_FILE_QUERY_INFO_NONE, + G_PRIORITY_DEFAULT, + sidebar->cancellable, + on_app_shortcuts_query_complete, + data); + } +} + +typedef struct { + NautilusGtkPlacesSidebar *sidebar; + int index; + gboolean is_native; +} BookmarkQueryClosure; + +static void +on_bookmark_query_info_complete (GObject *source, + GAsyncResult *result, + gpointer data) +{ + BookmarkQueryClosure *clos = data; + NautilusGtkPlacesSidebar *sidebar = clos->sidebar; + GFile *root = G_FILE (source); + GError *error = NULL; + GFileInfo *info; + char *bookmark_name; + char *mount_uri; + char *tooltip; + GIcon *start_icon; + + info = g_file_query_info_finish (root, result, &error); + if (g_error_matches (error, G_IO_ERROR, G_IO_ERROR_CANCELLED)) + goto out; + + bookmark_name = _nautilus_gtk_bookmarks_manager_get_bookmark_label (sidebar->bookmarks_manager, root); + if (bookmark_name == NULL && info != NULL) + bookmark_name = g_strdup (g_file_info_get_display_name (info)); + else if (bookmark_name == NULL) + { + /* Don't add non-UTF-8 bookmarks */ + bookmark_name = g_file_get_basename (root); + if (bookmark_name == NULL) + goto out; + + if (!g_utf8_validate (bookmark_name, -1, NULL)) + { + g_free (bookmark_name); + goto out; + } + } + + if (info) + start_icon = g_object_ref (g_file_info_get_symbolic_icon (info)); + else + start_icon = g_themed_icon_new_with_default_fallbacks (clos->is_native ? ICON_NAME_FOLDER : ICON_NAME_FOLDER_NETWORK); + + mount_uri = g_file_get_uri (root); + tooltip = clos->is_native ? g_file_get_path (root) : g_uri_unescape_string (mount_uri, NULL); + + add_place (sidebar, NAUTILUS_GTK_PLACES_BOOKMARK, + NAUTILUS_GTK_PLACES_SECTION_BOOKMARKS, + bookmark_name, start_icon, NULL, mount_uri, + NULL, NULL, NULL, NULL, clos->index, + tooltip); + + g_free (mount_uri); + g_free (tooltip); + g_free (bookmark_name); + g_object_unref (start_icon); + +out: + g_clear_object (&info); + g_clear_error (&error); + g_slice_free (BookmarkQueryClosure, clos); +} + +static gboolean +is_external_volume (GVolume *volume) +{ + gboolean is_external; + GDrive *drive; + char *id; + + drive = g_volume_get_drive (volume); + id = g_volume_get_identifier (volume, G_VOLUME_IDENTIFIER_KIND_CLASS); + + is_external = g_volume_can_eject (volume); + + /* NULL volume identifier only happens on removable devices */ + is_external |= !id; + + if (drive) + is_external |= g_drive_is_removable (drive); + + g_clear_object (&drive); + g_free (id); + + return is_external; +} + +static void +update_trash_icon (NautilusGtkPlacesSidebar *sidebar) +{ + if (sidebar->trash_row) + { + GIcon *icon; + + icon = nautilus_trash_monitor_get_symbolic_icon (); + nautilus_gtk_sidebar_row_set_start_icon (NAUTILUS_GTK_SIDEBAR_ROW (sidebar->trash_row), icon); + g_object_unref (icon); + } +} + +static gboolean +create_cloud_provider_account_row (NautilusGtkPlacesSidebar *sidebar, + CloudProvidersAccount *account) +{ + GIcon *end_icon; + GIcon *start_icon; + const char *mount_path; + const char *name; + char *mount_uri; + char *tooltip; + guint provider_account_status; + + start_icon = cloud_providers_account_get_icon (account); + name = cloud_providers_account_get_name (account); + provider_account_status = cloud_providers_account_get_status (account); + mount_path = cloud_providers_account_get_path (account); + if (start_icon != NULL + && name != NULL + && provider_account_status != CLOUD_PROVIDERS_ACCOUNT_STATUS_INVALID + && mount_path != NULL) + { + switch (provider_account_status) + { + case CLOUD_PROVIDERS_ACCOUNT_STATUS_IDLE: + end_icon = NULL; + break; + + case CLOUD_PROVIDERS_ACCOUNT_STATUS_SYNCING: + end_icon = g_themed_icon_new ("emblem-synchronizing-symbolic"); + break; + + case CLOUD_PROVIDERS_ACCOUNT_STATUS_ERROR: + end_icon = g_themed_icon_new ("dialog-warning-symbolic"); + break; + + default: + return FALSE; + } + + mount_uri = g_strconcat ("file://", mount_path, NULL); + + /* translators: %s is the name of a cloud provider for files */ + tooltip = g_strdup_printf (_("Open %s"), name); + + add_place (sidebar, NAUTILUS_GTK_PLACES_BUILT_IN, + NAUTILUS_GTK_PLACES_SECTION_CLOUD, + name, start_icon, end_icon, mount_uri, + NULL, NULL, NULL, account, 0, + tooltip); + + g_free (tooltip); + g_free (mount_uri); + g_clear_object (&end_icon); + return TRUE; + } + else + { + return FALSE; + } +} + +static void +on_account_updated (GObject *object, + GParamSpec *pspec, + gpointer user_data) +{ + CloudProvidersAccount *account = CLOUD_PROVIDERS_ACCOUNT (object); + NautilusGtkPlacesSidebar *sidebar = NAUTILUS_GTK_PLACES_SIDEBAR (user_data); + + if (create_cloud_provider_account_row (sidebar, account)) + { + g_signal_handlers_disconnect_by_data (account, sidebar); + sidebar->unready_accounts = g_list_remove (sidebar->unready_accounts, account); + g_object_unref (account); + } +} + +static void +update_places (NautilusGtkPlacesSidebar *sidebar) +{ + GList *mounts, *l, *ll; + GMount *mount; + GList *drives; + GDrive *drive; + GList *volumes; + GVolume *volume; + GSList *bookmarks, *sl; + int index; + char *original_uri, *name, *identifier; + GtkListBoxRow *selected; + char *home_uri; + GIcon *start_icon; + GFile *root; + char *tooltip; + GList *network_mounts, *network_volumes; + GIcon *new_bookmark_icon; + GtkWidget *child; + GList *cloud_providers; + GList *cloud_providers_accounts; + CloudProvidersAccount *cloud_provider_account; + CloudProvidersProvider *cloud_provider; + + /* save original selection */ + selected = gtk_list_box_get_selected_row (GTK_LIST_BOX (sidebar->list_box)); + if (selected) + g_object_get (selected, "uri", &original_uri, NULL); + else + original_uri = NULL; + + g_cancellable_cancel (sidebar->cancellable); + + g_object_unref (sidebar->cancellable); + sidebar->cancellable = g_cancellable_new (); + + /* Reset drag state, just in case we update the places while dragging or + * ending a drag */ + stop_drop_feedback (sidebar); + while ((child = gtk_widget_get_first_child (GTK_WIDGET (sidebar->list_box)))) + gtk_list_box_remove (GTK_LIST_BOX (sidebar->list_box), child); + + network_mounts = network_volumes = NULL; + + /* add built-in places */ + if (should_show_recent (sidebar)) + { + start_icon = g_themed_icon_new_with_default_fallbacks ("document-open-recent-symbolic"); + add_place (sidebar, NAUTILUS_GTK_PLACES_BUILT_IN, + NAUTILUS_GTK_PLACES_SECTION_COMPUTER, + _("Recent"), start_icon, NULL, "recent:///", + NULL, NULL, NULL, NULL, 0, + _("Recent files")); + g_object_unref (start_icon); + } + + if (sidebar->show_starred_location) + { + start_icon = g_themed_icon_new_with_default_fallbacks ("starred-symbolic"); + add_place (sidebar, NAUTILUS_GTK_PLACES_STARRED_LOCATION, + NAUTILUS_GTK_PLACES_SECTION_COMPUTER, + _("Starred"), start_icon, NULL, "starred:///", + NULL, NULL, NULL, NULL, 0, + _("Starred files")); + g_object_unref (start_icon); + } + + /* home folder */ + home_uri = get_home_directory_uri (); + start_icon = g_themed_icon_new_with_default_fallbacks (ICON_NAME_HOME); + add_place (sidebar, NAUTILUS_GTK_PLACES_BUILT_IN, + NAUTILUS_GTK_PLACES_SECTION_COMPUTER, + _("Home"), start_icon, NULL, home_uri, + NULL, NULL, NULL, NULL, 0, + _("Open your personal folder")); + g_object_unref (start_icon); + g_free (home_uri); + + /* desktop */ + if (sidebar->show_desktop) + { + char *mount_uri = get_desktop_directory_uri (); + if (mount_uri) + { + start_icon = g_themed_icon_new_with_default_fallbacks (ICON_NAME_DESKTOP); + add_place (sidebar, NAUTILUS_GTK_PLACES_BUILT_IN, + NAUTILUS_GTK_PLACES_SECTION_COMPUTER, + _("Desktop"), start_icon, NULL, mount_uri, + NULL, NULL, NULL, NULL, 0, + _("Open the contents of your desktop in a folder")); + g_object_unref (start_icon); + g_free (mount_uri); + } + } + + /* XDG directories */ + add_special_dirs (sidebar); + + if (sidebar->show_enter_location) + { + start_icon = g_themed_icon_new_with_default_fallbacks (ICON_NAME_NETWORK_SERVER); + add_place (sidebar, NAUTILUS_GTK_PLACES_ENTER_LOCATION, + NAUTILUS_GTK_PLACES_SECTION_COMPUTER, + _("Enter Location"), start_icon, NULL, NULL, + NULL, NULL, NULL, NULL, 0, + _("Manually enter a location")); + g_object_unref (start_icon); + } + + /* Trash */ + if (sidebar->show_trash) + { + start_icon = nautilus_trash_monitor_get_symbolic_icon (); + sidebar->trash_row = add_place (sidebar, NAUTILUS_GTK_PLACES_BUILT_IN, + NAUTILUS_GTK_PLACES_SECTION_COMPUTER, + _("Trash"), start_icon, NULL, "trash:///", + NULL, NULL, NULL, NULL, 0, + _("Open the trash")); + g_object_add_weak_pointer (G_OBJECT (sidebar->trash_row), + (gpointer *) &sidebar->trash_row); + g_object_unref (start_icon); + } + + /* Application-side shortcuts */ + add_application_shortcuts (sidebar); + + /* Cloud providers */ + cloud_providers = cloud_providers_collector_get_providers (sidebar->cloud_manager); + for (l = sidebar->unready_accounts; l != NULL; l = l->next) + { + g_signal_handlers_disconnect_by_data (l->data, sidebar); + } + g_list_free_full (sidebar->unready_accounts, g_object_unref); + sidebar->unready_accounts = NULL; + for (l = cloud_providers; l != NULL; l = l->next) + { + cloud_provider = CLOUD_PROVIDERS_PROVIDER (l->data); + g_signal_connect_swapped (cloud_provider, "accounts-changed", + G_CALLBACK (update_places), sidebar); + cloud_providers_accounts = cloud_providers_provider_get_accounts (cloud_provider); + for (ll = cloud_providers_accounts; ll != NULL; ll = ll->next) + { + cloud_provider_account = CLOUD_PROVIDERS_ACCOUNT (ll->data); + if (!create_cloud_provider_account_row (sidebar, cloud_provider_account)) + { + + g_signal_connect (cloud_provider_account, "notify::name", + G_CALLBACK (on_account_updated), sidebar); + g_signal_connect (cloud_provider_account, "notify::status", + G_CALLBACK (on_account_updated), sidebar); + g_signal_connect (cloud_provider_account, "notify::status-details", + G_CALLBACK (on_account_updated), sidebar); + g_signal_connect (cloud_provider_account, "notify::path", + G_CALLBACK (on_account_updated), sidebar); + sidebar->unready_accounts = g_list_append (sidebar->unready_accounts, + g_object_ref (cloud_provider_account)); + continue; + } + + } + } + + /* go through all connected drives */ + drives = g_volume_monitor_get_connected_drives (sidebar->volume_monitor); + + for (l = drives; l != NULL; l = l->next) + { + drive = l->data; + + volumes = g_drive_get_volumes (drive); + if (volumes != NULL) + { + for (ll = volumes; ll != NULL; ll = ll->next) + { + volume = ll->data; + identifier = g_volume_get_identifier (volume, G_VOLUME_IDENTIFIER_KIND_CLASS); + + if (g_strcmp0 (identifier, "network") == 0) + { + g_free (identifier); + network_volumes = g_list_prepend (network_volumes, volume); + continue; + } + g_free (identifier); + + if (sidebar->show_other_locations && !is_external_volume (volume)) + { + g_object_unref (volume); + continue; + } + + mount = g_volume_get_mount (volume); + if (mount != NULL) + { + char *mount_uri; + + /* Show mounted volume in the sidebar */ + start_icon = g_mount_get_symbolic_icon (mount); + root = g_mount_get_default_location (mount); + mount_uri = g_file_get_uri (root); + name = g_mount_get_name (mount); + tooltip = g_file_get_parse_name (root); + + add_place (sidebar, NAUTILUS_GTK_PLACES_MOUNTED_VOLUME, + NAUTILUS_GTK_PLACES_SECTION_MOUNTS, + name, start_icon, NULL, mount_uri, + drive, volume, mount, NULL, 0, tooltip); + g_object_unref (root); + g_object_unref (mount); + g_object_unref (start_icon); + g_free (tooltip); + g_free (name); + g_free (mount_uri); + } + else + { + /* Do show the unmounted volumes in the sidebar; + * this is so the user can mount it (in case automounting + * is off). + * + * Also, even if automounting is enabled, this gives a visual + * cue that the user should remember to yank out the media if + * he just unmounted it. + */ + start_icon = g_volume_get_symbolic_icon (volume); + name = g_volume_get_name (volume); + tooltip = g_strdup_printf (_("Mount and open “%s”"), name); + + add_place (sidebar, NAUTILUS_GTK_PLACES_MOUNTED_VOLUME, + NAUTILUS_GTK_PLACES_SECTION_MOUNTS, + name, start_icon, NULL, NULL, + drive, volume, NULL, NULL, 0, tooltip); + g_object_unref (start_icon); + g_free (name); + g_free (tooltip); + } + g_object_unref (volume); + } + g_list_free (volumes); + } + else + { + if (g_drive_is_media_removable (drive) && !g_drive_is_media_check_automatic (drive)) + { + /* If the drive has no mountable volumes and we cannot detect media change.. we + * display the drive in the sidebar so the user can manually poll the drive by + * right clicking and selecting "Rescan..." + * + * This is mainly for drives like floppies where media detection doesn't + * work.. but it's also for human beings who like to turn off media detection + * in the OS to save battery juice. + */ + start_icon = g_drive_get_symbolic_icon (drive); + name = g_drive_get_name (drive); + tooltip = g_strdup_printf (_("Mount and open “%s”"), name); + + add_place (sidebar, NAUTILUS_GTK_PLACES_BUILT_IN, + NAUTILUS_GTK_PLACES_SECTION_MOUNTS, + name, start_icon, NULL, NULL, + drive, NULL, NULL, NULL, 0, tooltip); + g_object_unref (start_icon); + g_free (tooltip); + g_free (name); + } + } + } + g_list_free_full (drives, g_object_unref); + + /* add all network volumes that are not associated with a drive, and + * loop devices + */ + volumes = g_volume_monitor_get_volumes (sidebar->volume_monitor); + for (l = volumes; l != NULL; l = l->next) + { + gboolean is_loop = FALSE; + volume = l->data; + drive = g_volume_get_drive (volume); + if (drive != NULL) + { + g_object_unref (volume); + g_object_unref (drive); + continue; + } + + identifier = g_volume_get_identifier (volume, G_VOLUME_IDENTIFIER_KIND_CLASS); + + if (g_strcmp0 (identifier, "network") == 0) + { + g_free (identifier); + network_volumes = g_list_prepend (network_volumes, volume); + continue; + } + else if (g_strcmp0 (identifier, "loop") == 0) + is_loop = TRUE; + g_free (identifier); + + if (sidebar->show_other_locations && + !is_external_volume (volume) && + !is_loop) + { + g_object_unref (volume); + continue; + } + + mount = g_volume_get_mount (volume); + if (mount != NULL) + { + char *mount_uri; + + start_icon = g_mount_get_symbolic_icon (mount); + root = g_mount_get_default_location (mount); + mount_uri = g_file_get_uri (root); + tooltip = g_file_get_parse_name (root); + name = g_mount_get_name (mount); + add_place (sidebar, NAUTILUS_GTK_PLACES_MOUNTED_VOLUME, + NAUTILUS_GTK_PLACES_SECTION_MOUNTS, + name, start_icon, NULL, mount_uri, + NULL, volume, mount, NULL, 0, tooltip); + g_object_unref (mount); + g_object_unref (root); + g_object_unref (start_icon); + g_free (name); + g_free (tooltip); + g_free (mount_uri); + } + else + { + /* see comment above in why we add an icon for an unmounted mountable volume */ + start_icon = g_volume_get_symbolic_icon (volume); + name = g_volume_get_name (volume); + add_place (sidebar, NAUTILUS_GTK_PLACES_MOUNTED_VOLUME, + NAUTILUS_GTK_PLACES_SECTION_MOUNTS, + name, start_icon, NULL, NULL, + NULL, volume, NULL, NULL, 0, name); + g_object_unref (start_icon); + g_free (name); + } + g_object_unref (volume); + } + g_list_free (volumes); + + /* file system root */ + if (!sidebar->show_other_locations) + { + start_icon = g_themed_icon_new_with_default_fallbacks (ICON_NAME_FILESYSTEM); + add_place (sidebar, NAUTILUS_GTK_PLACES_BUILT_IN, + NAUTILUS_GTK_PLACES_SECTION_MOUNTS, + sidebar->hostname, start_icon, NULL, "file:///", + NULL, NULL, NULL, NULL, 0, + _("Open the contents of the file system")); + g_object_unref (start_icon); + } + + /* add mounts that has no volume (/etc/mtab mounts, ftp, sftp,...) */ + mounts = g_volume_monitor_get_mounts (sidebar->volume_monitor); + + for (l = mounts; l != NULL; l = l->next) + { + char *mount_uri; + + mount = l->data; + if (g_mount_is_shadowed (mount)) + { + g_object_unref (mount); + continue; + } + volume = g_mount_get_volume (mount); + if (volume != NULL) + { + g_object_unref (volume); + g_object_unref (mount); + continue; + } + root = g_mount_get_default_location (mount); + + if (!g_file_is_native (root)) + { + network_mounts = g_list_prepend (network_mounts, mount); + g_object_unref (root); + continue; + } + + start_icon = g_mount_get_symbolic_icon (mount); + mount_uri = g_file_get_uri (root); + name = g_mount_get_name (mount); + tooltip = g_file_get_parse_name (root); + add_place (sidebar, NAUTILUS_GTK_PLACES_MOUNTED_VOLUME, + NAUTILUS_GTK_PLACES_SECTION_COMPUTER, + name, start_icon, NULL, mount_uri, + NULL, NULL, mount, NULL, 0, tooltip); + g_object_unref (root); + g_object_unref (mount); + g_object_unref (start_icon); + g_free (name); + g_free (mount_uri); + g_free (tooltip); + } + g_list_free (mounts); + + /* add bookmarks */ + bookmarks = _nautilus_gtk_bookmarks_manager_list_bookmarks (sidebar->bookmarks_manager); + + for (sl = bookmarks, index = 0; sl; sl = sl->next, index++) + { + gboolean is_native; + BookmarkQueryClosure *clos; + + root = sl->data; + is_native = g_file_is_native (root); + + if (_nautilus_gtk_bookmarks_manager_get_is_builtin (sidebar->bookmarks_manager, root)) + continue; + + clos = g_slice_new (BookmarkQueryClosure); + clos->sidebar = sidebar; + clos->index = index; + clos->is_native = is_native; + g_file_query_info_async (root, + "standard::display-name,standard::symbolic-icon", + G_FILE_QUERY_INFO_NONE, + G_PRIORITY_DEFAULT, + sidebar->cancellable, + on_bookmark_query_info_complete, + clos); + } + + g_slist_free_full (bookmarks, g_object_unref); + + /* Add new bookmark row */ + new_bookmark_icon = g_themed_icon_new ("bookmark-new-symbolic"); + sidebar->new_bookmark_row = add_place (sidebar, NAUTILUS_GTK_PLACES_DROP_FEEDBACK, + NAUTILUS_GTK_PLACES_SECTION_BOOKMARKS, + _("New bookmark"), new_bookmark_icon, NULL, NULL, + NULL, NULL, NULL, NULL, 0, + _("Add a new bookmark")); + gtk_widget_add_css_class (sidebar->new_bookmark_row, "sidebar-new-bookmark-row"); + g_object_unref (new_bookmark_icon); + + /* network */ + network_volumes = g_list_reverse (network_volumes); + for (l = network_volumes; l != NULL; l = l->next) + { + volume = l->data; + mount = g_volume_get_mount (volume); + + if (mount != NULL) + { + network_mounts = g_list_prepend (network_mounts, mount); + continue; + } + else + { + start_icon = g_volume_get_symbolic_icon (volume); + name = g_volume_get_name (volume); + tooltip = g_strdup_printf (_("Mount and open “%s”"), name); + + add_place (sidebar, NAUTILUS_GTK_PLACES_MOUNTED_VOLUME, + NAUTILUS_GTK_PLACES_SECTION_MOUNTS, + name, start_icon, NULL, NULL, + NULL, volume, NULL, NULL, 0, tooltip); + g_object_unref (start_icon); + g_free (name); + g_free (tooltip); + } + } + + network_mounts = g_list_reverse (network_mounts); + for (l = network_mounts; l != NULL; l = l->next) + { + char *mount_uri; + + mount = l->data; + root = g_mount_get_default_location (mount); + start_icon = g_mount_get_symbolic_icon (mount); + mount_uri = g_file_get_uri (root); + name = g_mount_get_name (mount); + tooltip = g_file_get_parse_name (root); + add_place (sidebar, NAUTILUS_GTK_PLACES_MOUNTED_VOLUME, + NAUTILUS_GTK_PLACES_SECTION_MOUNTS, + name, start_icon, NULL, mount_uri, + NULL, NULL, mount, NULL, 0, tooltip); + g_object_unref (root); + g_object_unref (start_icon); + g_free (name); + g_free (mount_uri); + g_free (tooltip); + } + + + g_list_free_full (network_volumes, g_object_unref); + g_list_free_full (network_mounts, g_object_unref); + + /* Other locations */ + if (sidebar->show_other_locations) + { + start_icon = g_themed_icon_new_with_default_fallbacks (ICON_NAME_OTHER_LOCATIONS); + + add_place (sidebar, NAUTILUS_GTK_PLACES_OTHER_LOCATIONS, + NAUTILUS_GTK_PLACES_SECTION_OTHER_LOCATIONS, + _("Other Locations"), start_icon, NULL, "other-locations:///", + NULL, NULL, NULL, NULL, 0, _("Show other locations")); + + g_object_unref (start_icon); + } + + gtk_widget_show (GTK_WIDGET (sidebar)); + /* We want this hidden by default, but need to do it after the show_all call */ + nautilus_gtk_sidebar_row_hide (NAUTILUS_GTK_SIDEBAR_ROW (sidebar->new_bookmark_row), TRUE); + + /* restore original selection */ + if (original_uri) + { + GFile *restore; + + restore = g_file_new_for_uri (original_uri); + nautilus_gtk_places_sidebar_set_location (sidebar, restore); + g_object_unref (restore); + g_free (original_uri); + } +} + +static gboolean +hover_timer (gpointer user_data) +{ + NautilusGtkPlacesSidebar *sidebar = user_data; + gboolean open_folder_on_hover; + g_autofree gchar *uri = NULL; + g_autoptr (GFile) location = NULL; + + open_folder_on_hover = g_settings_get_boolean (nautilus_preferences, + NAUTILUS_PREFERENCES_OPEN_FOLDER_ON_DND_HOVER); + sidebar->hover_timer_id = 0; + + if (open_folder_on_hover && sidebar->hover_row != NULL) + { + g_object_get (sidebar->hover_row, "uri", &uri, NULL); + if (uri != NULL) + { + location = g_file_new_for_uri (uri); + emit_open_location (sidebar, location, 0); + } + } + + return G_SOURCE_REMOVE; +} + +static gboolean +check_valid_drop_target (NautilusGtkPlacesSidebar *sidebar, + NautilusGtkSidebarRow *row, + const GValue *value) +{ + NautilusGtkPlacesPlaceType place_type; + NautilusGtkPlacesSectionType section_type; + g_autoptr (NautilusFile) dest_file = NULL; + gboolean valid = FALSE; + char *uri; + int drag_action; + + g_return_val_if_fail (value != NULL, TRUE); + + if (row == NULL) + return FALSE; + + g_object_get (row, + "place-type", &place_type, + "section_type", §ion_type, + "uri", &uri, + "file", &dest_file, + NULL); + + if (place_type == NAUTILUS_GTK_PLACES_CONNECT_TO_SERVER) + { + g_free (uri); + return FALSE; + } + + if (place_type == NAUTILUS_GTK_PLACES_OTHER_LOCATIONS) + { + g_free (uri); + return FALSE; + } + + if (place_type == NAUTILUS_GTK_PLACES_DROP_FEEDBACK) + { + g_free (uri); + return TRUE; + } + + /* Disallow drops on recent:/// */ + if (place_type == NAUTILUS_GTK_PLACES_BUILT_IN) + { + if (g_strcmp0 (uri, "recent:///") == 0) + { + g_free (uri); + return FALSE; + } + } + + /* Dragging a bookmark? */ + if (G_VALUE_HOLDS (value, NAUTILUS_TYPE_GTK_SIDEBAR_ROW)) + { + /* Don't allow reordering bookmarks into non-bookmark areas */ + valid = section_type == NAUTILUS_GTK_PLACES_SECTION_BOOKMARKS; + } + else if (G_VALUE_HOLDS (value, GDK_TYPE_FILE_LIST)) + { + /* Dragging a file */ + if (uri != NULL) + { + drag_action = emit_drag_action_requested (sidebar, dest_file, g_value_get_boxed (value)); + valid = drag_action > 0; + } + else + { + valid = FALSE; + } + } + else + { + g_assert_not_reached (); + valid = TRUE; + } + + g_free (uri); + return valid; +} + +static void +update_possible_drop_targets (NautilusGtkPlacesSidebar *sidebar, + const GValue *value) +{ + GtkWidget *row; + + for (row = gtk_widget_get_first_child (GTK_WIDGET (sidebar->list_box)); + row != NULL; + row = gtk_widget_get_next_sibling (row)) + { + gboolean sensitive; + + if (!GTK_IS_LIST_BOX_ROW (row)) + continue; + + sensitive = value == NULL || + check_valid_drop_target (sidebar, NAUTILUS_GTK_SIDEBAR_ROW (row), value); + gtk_widget_set_sensitive (row, sensitive); + } +} + +static void +start_drop_feedback (NautilusGtkPlacesSidebar *sidebar, + const GValue *value) +{ + if (value != NULL && G_VALUE_HOLDS (value, GDK_TYPE_FILE_LIST)) + { + GSList *source_list = g_value_get_boxed (value); + if (g_slist_length (source_list) == 1) + { + g_autoptr (NautilusFile) file = NULL; + file = nautilus_file_get (source_list->data); + if (nautilus_file_is_directory (file)) + nautilus_gtk_sidebar_row_reveal (NAUTILUS_GTK_SIDEBAR_ROW (sidebar->new_bookmark_row)); + } + } + if (value && !G_VALUE_HOLDS (value, NAUTILUS_TYPE_GTK_SIDEBAR_ROW)) + { + /* If the state is permanent, don't change it. The application controls it. */ + if (sidebar->drop_state != DROP_STATE_NEW_BOOKMARK_ARMED_PERMANENT) + sidebar->drop_state = DROP_STATE_NEW_BOOKMARK_ARMED; + } + + update_possible_drop_targets (sidebar, value); +} + +static void +stop_drop_feedback (NautilusGtkPlacesSidebar *sidebar) +{ + update_possible_drop_targets (sidebar, NULL); + + if (sidebar->drop_state != DROP_STATE_NEW_BOOKMARK_ARMED_PERMANENT && + sidebar->new_bookmark_row != NULL) + { + nautilus_gtk_sidebar_row_hide (NAUTILUS_GTK_SIDEBAR_ROW (sidebar->new_bookmark_row), FALSE); + sidebar->drop_state = DROP_STATE_NORMAL; + } + + if (sidebar->drag_row != NULL) + { + gtk_widget_show (sidebar->drag_row); + sidebar->drag_row = NULL; + } + + if (sidebar->row_placeholder != NULL) + { + if (gtk_widget_get_parent (sidebar->row_placeholder) != NULL) + gtk_list_box_remove (GTK_LIST_BOX (sidebar->list_box), sidebar->row_placeholder); + sidebar->row_placeholder = NULL; + } + + sidebar->dragging_over = FALSE; +} + +static GtkWidget * +create_placeholder_row (NautilusGtkPlacesSidebar *sidebar) +{ + return g_object_new (NAUTILUS_TYPE_GTK_SIDEBAR_ROW, "placeholder", TRUE, NULL); +} + +static GdkDragAction +drag_motion_callback (GtkDropTarget *target, + double x, + double y, + NautilusGtkPlacesSidebar *sidebar) +{ + GdkDragAction action; + GtkListBoxRow *row; + NautilusGtkPlacesPlaceType place_type; + char *drop_target_uri = NULL; + int row_index; + int row_placeholder_index; + const GValue *value; + graphene_point_t start; + + sidebar->dragging_over = TRUE; + action = 0; + row = gtk_list_box_get_row_at_y (GTK_LIST_BOX (sidebar->list_box), y); + + start = sidebar->hover_start_point; + if (row != sidebar->hover_row || + gtk_drag_check_threshold (GTK_WIDGET (sidebar), start.x, start.y, x, y)) + { + g_clear_handle_id (&sidebar->hover_timer_id, g_source_remove); + sidebar->hover_row = row; + sidebar->hover_timer_id = g_timeout_add (HOVER_TIMEOUT, hover_timer, sidebar); + sidebar->hover_start_point.x = x; + sidebar->hover_start_point.y = y; + } + + /* Workaround https://gitlab.gnome.org/GNOME/gtk/-/issues/5023 */ + gtk_list_box_drag_unhighlight_row (GTK_LIST_BOX (sidebar->list_box)); + + /* Nothing to do if no value yet */ + value = gtk_drop_target_get_value (target); + if (value == NULL) + goto out; + + /* Nothing to do if the target is not valid drop destination */ + if (!check_valid_drop_target (sidebar, NAUTILUS_GTK_SIDEBAR_ROW (row), value)) + goto out; + + if (G_VALUE_HOLDS (value, NAUTILUS_TYPE_GTK_SIDEBAR_ROW)) + { + /* Dragging bookmarks always moves them to another position in the bookmarks list */ + action = GDK_ACTION_MOVE; + if (sidebar->row_placeholder == NULL) + { + sidebar->row_placeholder = create_placeholder_row (sidebar); + g_object_ref_sink (sidebar->row_placeholder); + } + else if (GTK_WIDGET (row) == sidebar->row_placeholder) + { + goto out; + } + + if (gtk_widget_get_parent (sidebar->row_placeholder) != NULL) + gtk_list_box_remove (GTK_LIST_BOX (sidebar->list_box), sidebar->row_placeholder); + + if (row != NULL) + { + g_object_get (row, "order-index", &row_index, NULL); + g_object_get (sidebar->row_placeholder, "order-index", &row_placeholder_index, NULL); + /* We order the bookmarks sections based on the bookmark index that we + * set on the row as order-index property, but we have to deal with + * the placeholder row wanting to be between two consecutive bookmarks, + * with two consecutive order-index values which is the usual case. + * For that, in the list box sort func we give priority to the placeholder row, + * that means that if the index-order is the same as another bookmark + * the placeholder row goes before. However if we want to show it after + * the current row, for instance when the cursor is in the lower half + * of the row, we need to increase the order-index. + */ + row_placeholder_index = row_index; + gtk_widget_translate_coordinates (GTK_WIDGET (sidebar), GTK_WIDGET (row), + x, y, + &x, &y); + + if (y > sidebar->drag_row_height / 2 && row_index > 0) + row_placeholder_index++; + } + else + { + /* If the user is dragging over an area that has no row, place the row + * placeholder in the last position + */ + row_placeholder_index = G_MAXINT32; + } + + g_object_set (sidebar->row_placeholder, "order-index", row_placeholder_index, NULL); + + gtk_list_box_prepend (GTK_LIST_BOX (sidebar->list_box), + sidebar->row_placeholder); + } + else if (G_VALUE_HOLDS (value, GDK_TYPE_FILE_LIST)) + { + NautilusFile *file; + gtk_list_box_drag_highlight_row (GTK_LIST_BOX (sidebar->list_box), row); + + g_object_get (row, + "place-type", &place_type, + "uri", &drop_target_uri, + "file", &file, + NULL); + /* URIs are being dragged. See if the caller wants to handle a + * file move/copy operation itself, or if we should only try to + * create bookmarks out of the dragged URIs. + */ + if (place_type == NAUTILUS_GTK_PLACES_DROP_FEEDBACK) + { + action = GDK_ACTION_COPY; + } + else + { + /* uri may be NULL for unmounted volumes, for example, so we don't allow drops there */ + if (drop_target_uri != NULL) + { + GFile *dest_file = g_file_new_for_uri (drop_target_uri); + + action = emit_drag_action_requested (sidebar, file, g_value_get_boxed (value)); + + g_object_unref (dest_file); + } + } + + nautilus_file_unref (file); + g_free (drop_target_uri); + } + else + { + g_assert_not_reached (); + } + + out: + start_drop_feedback (sidebar, value); + return action; +} + +/* Reorders the bookmark to the specified position */ +static void +reorder_bookmarks (NautilusGtkPlacesSidebar *sidebar, + NautilusGtkSidebarRow *row, + int new_position) +{ + char *uri; + GFile *file; + + g_object_get (row, "uri", &uri, NULL); + file = g_file_new_for_uri (uri); + _nautilus_gtk_bookmarks_manager_reorder_bookmark (sidebar->bookmarks_manager, file, new_position, NULL); + + g_object_unref (file); + g_free (uri); +} + +/* Creates bookmarks for the specified files at the given position in the bookmarks list */ +static void +drop_files_as_bookmarks (NautilusGtkPlacesSidebar *sidebar, + GSList *files, + int position) +{ + GSList *l; + + for (l = files; l; l = l->next) + { + GFile *f = G_FILE (l->data); + GFileInfo *info = g_file_query_info (f, + G_FILE_ATTRIBUTE_STANDARD_TYPE, + G_FILE_QUERY_INFO_NOFOLLOW_SYMLINKS, + NULL, + NULL); + + if (info) + { + if ((g_file_info_get_file_type (info) == G_FILE_TYPE_DIRECTORY || + g_file_info_get_file_type (info) == G_FILE_TYPE_MOUNTABLE || + g_file_info_get_file_type (info) == G_FILE_TYPE_SHORTCUT || + g_file_info_get_file_type (info) == G_FILE_TYPE_SYMBOLIC_LINK)) + _nautilus_gtk_bookmarks_manager_insert_bookmark (sidebar->bookmarks_manager, f, position++, NULL); + + g_object_unref (info); + } + } +} + +static gboolean +drag_drop_callback (GtkDropTarget *target, + const GValue *value, + double x, + double y, + NautilusGtkPlacesSidebar *sidebar) +{ + int target_order_index; + NautilusGtkPlacesPlaceType target_place_type; + NautilusGtkPlacesSectionType target_section_type; + char *target_uri; + GtkListBoxRow *target_row; + gboolean result; + + target_row = gtk_list_box_get_row_at_y (GTK_LIST_BOX (sidebar->list_box), y); + if (target_row == NULL) + return FALSE; + + if (!check_valid_drop_target (sidebar, NAUTILUS_GTK_SIDEBAR_ROW (target_row), value)) + return FALSE; + + g_object_get (target_row, + "place-type", &target_place_type, + "section-type", &target_section_type, + "order-index", &target_order_index, + "uri", &target_uri, + NULL); + result = FALSE; + + if (G_VALUE_HOLDS (value, NAUTILUS_TYPE_GTK_SIDEBAR_ROW)) + { + GtkWidget *source_row; + /* A bookmark got reordered */ + if (target_section_type != NAUTILUS_GTK_PLACES_SECTION_BOOKMARKS) + goto out; + + source_row = g_value_get_object (value); + + if (sidebar->row_placeholder != NULL) + g_object_get (sidebar->row_placeholder, "order-index", &target_order_index, NULL); + + reorder_bookmarks (sidebar, NAUTILUS_GTK_SIDEBAR_ROW (source_row), target_order_index); + result = TRUE; + } + else if (G_VALUE_HOLDS (value, GDK_TYPE_FILE_LIST)) + { + /* Dropping URIs! */ + if (target_place_type == NAUTILUS_GTK_PLACES_DROP_FEEDBACK) + { + drop_files_as_bookmarks (sidebar, g_value_get_boxed (value), target_order_index); + } + else + { + GFile *dest_file = g_file_new_for_uri (target_uri); + GdkDragAction actions; + + actions = gdk_drop_get_actions (gtk_drop_target_get_current_drop (target)); + + #ifdef GDK_WINDOWING_X11 + if (GDK_IS_X11_DISPLAY (gtk_widget_get_display (GTK_WIDGET (sidebar)))) + { + /* Temporary workaround until the below GTK MR (or equivalend fix) + * is merged. Without this fix, the preferred action isn't set correctly. + * https://gitlab.gnome.org/GNOME/gtk/-/merge_requests/4982 */ + GdkDrag *drag = gdk_drop_get_drag (gtk_drop_target_get_current_drop (target)); + actions = gdk_drag_get_selected_action (drag); + } + #endif + + emit_drag_perform_drop (sidebar, + dest_file, + g_value_get_boxed (value), + actions); + + g_object_unref (dest_file); + } + result = TRUE; + } + else + { + g_assert_not_reached (); + } + +out: + stop_drop_feedback (sidebar); + g_free (target_uri); + return result; +} + +static void +dnd_finished_cb (GdkDrag *drag, + NautilusGtkPlacesSidebar *sidebar) +{ + stop_drop_feedback (sidebar); +} + +static void +dnd_cancel_cb (GdkDrag *drag, + GdkDragCancelReason reason, + NautilusGtkPlacesSidebar *sidebar) +{ + stop_drop_feedback (sidebar); +} + +/* This functions is called every time the drag source leaves + * the sidebar widget. + * The problem is that, we start showing hints for drop when the source + * start being above the sidebar or when the application request so show + * drop hints, but at some moment we need to restore to normal + * state. + * One could think that here we could simply call stop_drop_feedback, + * but that's not true, because this function is called also before drag_drop, + * which needs the data from the drag so we cannot free the drag data here. + * So now one could think we could just do nothing here, and wait for + * drag-end or drag-cancel signals and just stop_drop_feedback there. But that + * is also not true, since when the drag comes from a different widget than the + * sidebar, when the drag stops the last drag signal we receive is drag-leave. + * So here what we will do is restore the state of the sidebar as if no drag + * is being done (and if the application didn't request for permanent hints with + * nautilus_gtk_places_sidebar_show_drop_hints) and we will free the drag data next time + * we build new drag data in drag_data_received. + */ +static void +drag_leave_callback (GtkDropTarget *dest, + gpointer user_data) +{ + NautilusGtkPlacesSidebar *sidebar = NAUTILUS_GTK_PLACES_SIDEBAR (user_data); + + gtk_list_box_drag_unhighlight_row (GTK_LIST_BOX (sidebar->list_box)); + + if (sidebar->drop_state != DROP_STATE_NEW_BOOKMARK_ARMED_PERMANENT) + { + update_possible_drop_targets (sidebar, FALSE); + nautilus_gtk_sidebar_row_hide (NAUTILUS_GTK_SIDEBAR_ROW (sidebar->new_bookmark_row), FALSE); + sidebar->drop_state = DROP_STATE_NORMAL; + } + + g_clear_handle_id (&sidebar->hover_timer_id, g_source_remove); + sidebar->dragging_over = FALSE; +} + +static void +check_unmount_and_eject (GMount *mount, + GVolume *volume, + GDrive *drive, + gboolean *show_unmount, + gboolean *show_eject) +{ + *show_unmount = FALSE; + *show_eject = FALSE; + + if (drive != NULL) + *show_eject = g_drive_can_eject (drive); + + if (volume != NULL) + *show_eject |= g_volume_can_eject (volume); + + if (mount != NULL) + { + *show_eject |= g_mount_can_eject (mount); + *show_unmount = g_mount_can_unmount (mount) && !*show_eject; + } +} + +static void +drive_start_from_bookmark_cb (GObject *source_object, + GAsyncResult *res, + gpointer user_data) +{ + NautilusGtkPlacesSidebar *sidebar; + GError *error; + char *primary; + char *name; + + sidebar = NAUTILUS_GTK_PLACES_SIDEBAR (user_data); + + error = NULL; + if (!g_drive_poll_for_media_finish (G_DRIVE (source_object), res, &error)) + { + if (error->code != G_IO_ERROR_FAILED_HANDLED) + { + name = g_drive_get_name (G_DRIVE (source_object)); + primary = g_strdup_printf (_("Unable to start “%s”"), name); + g_free (name); + emit_show_error_message (sidebar, primary, error->message); + g_free (primary); + } + g_error_free (error); + } +} + +static void +volume_mount_cb (GObject *source_object, + GAsyncResult *result, + gpointer user_data) +{ + NautilusGtkSidebarRow *row = NAUTILUS_GTK_SIDEBAR_ROW (user_data); + NautilusGtkPlacesSidebar *sidebar; + GVolume *volume; + GError *error; + char *primary; + char *name; + GMount *mount; + + volume = G_VOLUME (source_object); + g_object_get (row, "sidebar", &sidebar, NULL); + + error = NULL; + if (!g_volume_mount_finish (volume, result, &error)) + { + if (error->code != G_IO_ERROR_FAILED_HANDLED && + error->code != G_IO_ERROR_ALREADY_MOUNTED) + { + name = g_volume_get_name (G_VOLUME (source_object)); + if (g_str_has_prefix (error->message, "Error unlocking")) + /* Translators: This means that unlocking an encrypted storage + * device failed. %s is the name of the device. + */ + primary = g_strdup_printf (_("Error unlocking “%s”"), name); + else + primary = g_strdup_printf (_("Unable to access “%s”"), name); + g_free (name); + emit_show_error_message (sidebar, primary, error->message); + g_free (primary); + } + g_error_free (error); + } + + sidebar->mounting = FALSE; + nautilus_gtk_sidebar_row_set_busy (row, FALSE); + + mount = g_volume_get_mount (volume); + if (mount != NULL) + { + GFile *location; + + location = g_mount_get_default_location (mount); + emit_open_location (sidebar, location, sidebar->go_to_after_mount_open_flags); + + g_object_unref (G_OBJECT (location)); + g_object_unref (G_OBJECT (mount)); + } + + g_object_unref (row); + g_object_unref (sidebar); +} + +static void +mount_volume (NautilusGtkSidebarRow *row, + GVolume *volume) +{ + NautilusGtkPlacesSidebar *sidebar; + GMountOperation *mount_op; + + g_object_get (row, "sidebar", &sidebar, NULL); + + mount_op = get_mount_operation (sidebar); + g_mount_operation_set_password_save (mount_op, G_PASSWORD_SAVE_FOR_SESSION); + + g_object_ref (row); + g_object_ref (sidebar); + g_volume_mount (volume, 0, mount_op, NULL, volume_mount_cb, row); +} + +static void +open_drive (NautilusGtkSidebarRow *row, + GDrive *drive, + NautilusGtkPlacesOpenFlags open_flags) +{ + NautilusGtkPlacesSidebar *sidebar; + + g_object_get (row, "sidebar", &sidebar, NULL); + + if (drive != NULL && + (g_drive_can_start (drive) || g_drive_can_start_degraded (drive))) + { + GMountOperation *mount_op; + + nautilus_gtk_sidebar_row_set_busy (row, TRUE); + mount_op = get_mount_operation (sidebar); + g_drive_start (drive, G_DRIVE_START_NONE, mount_op, NULL, drive_start_from_bookmark_cb, NULL); + g_object_unref (mount_op); + } +} + +static void +open_volume (NautilusGtkSidebarRow *row, + GVolume *volume, + NautilusGtkPlacesOpenFlags open_flags) +{ + NautilusGtkPlacesSidebar *sidebar; + + g_object_get (row, "sidebar", &sidebar, NULL); + + if (volume != NULL && !sidebar->mounting) + { + sidebar->mounting = TRUE; + sidebar->go_to_after_mount_open_flags = open_flags; + nautilus_gtk_sidebar_row_set_busy (row, TRUE); + mount_volume (row, volume); + } +} + +static void +open_uri (NautilusGtkPlacesSidebar *sidebar, + const char *uri, + NautilusGtkPlacesOpenFlags open_flags) +{ + GFile *location; + + location = g_file_new_for_uri (uri); + emit_open_location (sidebar, location, open_flags); + g_object_unref (location); +} + +static void +open_row (NautilusGtkSidebarRow *row, + NautilusGtkPlacesOpenFlags open_flags) +{ + char *uri; + GDrive *drive; + GVolume *volume; + NautilusGtkPlacesPlaceType place_type; + NautilusGtkPlacesSidebar *sidebar; + + g_object_get (row, + "sidebar", &sidebar, + "uri", &uri, + "place-type", &place_type, + "drive", &drive, + "volume", &volume, + NULL); + + if (place_type == NAUTILUS_GTK_PLACES_OTHER_LOCATIONS) + { + emit_show_other_locations_with_flags (sidebar, open_flags); + } + else if (place_type == NAUTILUS_GTK_PLACES_STARRED_LOCATION) + { + emit_show_starred_location (sidebar, open_flags); + } + else if (uri != NULL) + { + open_uri (sidebar, uri, open_flags); + } + else if (place_type == NAUTILUS_GTK_PLACES_ENTER_LOCATION) + { + emit_show_enter_location (sidebar); + } + else if (volume != NULL) + { + open_volume (row, volume, open_flags); + } + else if (drive != NULL) + { + open_drive (row, drive, open_flags); + } + + g_object_unref (sidebar); + if (drive) + g_object_unref (drive); + if (volume) + g_object_unref (volume); + g_free (uri); +} + +/* Callback used for the "Open" menu items in the context menu */ +static void +open_shortcut_cb (GSimpleAction *action, + GVariant *parameter, + gpointer data) +{ + NautilusGtkPlacesSidebar *sidebar = data; + NautilusGtkPlacesOpenFlags flags; + + flags = (NautilusGtkPlacesOpenFlags)g_variant_get_int32 (parameter); + open_row (sidebar->context_row, flags); +} + +/* Add bookmark for the selected item - just used from mount points */ +static void +add_shortcut_cb (GSimpleAction *action, + GVariant *parameter, + gpointer data) +{ + NautilusGtkPlacesSidebar *sidebar = data; + char *uri; + char *name; + GFile *location; + + g_object_get (sidebar->context_row, + "uri", &uri, + "label", &name, + NULL); + + if (uri != NULL) + { + location = g_file_new_for_uri (uri); + if (_nautilus_gtk_bookmarks_manager_insert_bookmark (sidebar->bookmarks_manager, location, -1, NULL)) + _nautilus_gtk_bookmarks_manager_set_bookmark_label (sidebar->bookmarks_manager, location, name, NULL); + g_object_unref (location); + } + + g_free (uri); + g_free (name); +} + +static void +rename_entry_changed (GtkEntry *entry, + NautilusGtkPlacesSidebar *sidebar) +{ + NautilusGtkPlacesPlaceType type; + char *name; + char *uri; + const char *new_name; + gboolean found = FALSE; + GtkWidget *row; + + new_name = gtk_editable_get_text (GTK_EDITABLE (sidebar->rename_entry)); + + if (strcmp (new_name, "") == 0) + { + gtk_widget_set_sensitive (sidebar->rename_button, FALSE); + gtk_label_set_label (GTK_LABEL (sidebar->rename_error), ""); + return; + } + + for (row = gtk_widget_get_first_child (GTK_WIDGET (sidebar->list_box)); + row != NULL && !found; + row = gtk_widget_get_next_sibling (row)) + { + if (!GTK_IS_LIST_BOX_ROW (row)) + continue; + + g_object_get (row, + "place-type", &type, + "uri", &uri, + "label", &name, + NULL); + + if ((type == NAUTILUS_GTK_PLACES_XDG_DIR || type == NAUTILUS_GTK_PLACES_BOOKMARK) && + strcmp (uri, sidebar->rename_uri) != 0 && + strcmp (new_name, name) == 0) + found = TRUE; + + g_free (uri); + g_free (name); + } + + gtk_widget_set_sensitive (sidebar->rename_button, !found); + gtk_label_set_label (GTK_LABEL (sidebar->rename_error), + found ? _("This name is already taken") : ""); +} + +static void +do_rename (GtkButton *button, + NautilusGtkPlacesSidebar *sidebar) +{ + char *new_text; + GFile *file; + + new_text = g_strdup (gtk_editable_get_text (GTK_EDITABLE (sidebar->rename_entry))); + + file = g_file_new_for_uri (sidebar->rename_uri); + if (!_nautilus_gtk_bookmarks_manager_has_bookmark (sidebar->bookmarks_manager, file)) + _nautilus_gtk_bookmarks_manager_insert_bookmark (sidebar->bookmarks_manager, file, -1, NULL); + + if (sidebar->rename_popover) + { + gtk_popover_popdown (GTK_POPOVER (sidebar->rename_popover)); + } + + _nautilus_gtk_bookmarks_manager_set_bookmark_label (sidebar->bookmarks_manager, file, new_text, NULL); + + g_object_unref (file); + g_free (new_text); + + g_clear_pointer (&sidebar->rename_uri, g_free); + +} + +static void +on_rename_popover_destroy (GtkWidget *rename_popover, + NautilusGtkPlacesSidebar *sidebar) +{ + if (sidebar) + { + sidebar->rename_popover = NULL; + sidebar->rename_entry = NULL; + sidebar->rename_button = NULL; + sidebar->rename_error = NULL; + } +} + +static void +create_rename_popover (NautilusGtkPlacesSidebar *sidebar) +{ + GtkWidget *popover; + GtkWidget *grid; + GtkWidget *label; + GtkWidget *entry; + GtkWidget *button; + GtkWidget *error; + char *str; + + if (sidebar->rename_popover) + return; + + popover = gtk_popover_new (); + gtk_widget_set_parent (popover, GTK_WIDGET (sidebar)); + /* Clean sidebar pointer when its destroyed, most of the times due to its + * relative_to associated row being destroyed */ + g_signal_connect (popover, "destroy", G_CALLBACK (on_rename_popover_destroy), sidebar); + gtk_popover_set_position (GTK_POPOVER (popover), GTK_POS_RIGHT); + grid = gtk_grid_new (); + gtk_popover_set_child (GTK_POPOVER (popover), grid); + g_object_set (grid, + "margin-start", 10, + "margin-end", 10, + "margin-top", 10, + "margin-bottom", 10, + "row-spacing", 6, + "column-spacing", 6, + NULL); + entry = gtk_entry_new (); + gtk_entry_set_activates_default (GTK_ENTRY (entry), TRUE); + g_signal_connect (entry, "changed", G_CALLBACK (rename_entry_changed), sidebar); + str = g_strdup_printf ("%s", _("Name")); + label = gtk_label_new (str); + gtk_widget_set_halign (label, GTK_ALIGN_START); + gtk_label_set_use_markup (GTK_LABEL (label), TRUE); + gtk_label_set_mnemonic_widget (GTK_LABEL (label), entry); + g_free (str); + button = gtk_button_new_with_mnemonic (_("_Rename")); + gtk_widget_add_css_class (button, "suggested-action"); + g_signal_connect (button, "clicked", G_CALLBACK (do_rename), sidebar); + error = gtk_label_new (""); + gtk_widget_set_halign (error, GTK_ALIGN_START); + gtk_grid_attach (GTK_GRID (grid), label, 0, 0, 2, 1); + gtk_grid_attach (GTK_GRID (grid), entry, 0, 1, 1, 1); + gtk_grid_attach (GTK_GRID (grid), button,1, 1, 1, 1); + gtk_grid_attach (GTK_GRID (grid), error, 0, 2, 2, 1); + gtk_popover_set_default_widget (GTK_POPOVER (popover), button); + + sidebar->rename_popover = popover; + sidebar->rename_entry = entry; + sidebar->rename_button = button; + sidebar->rename_error = error; +} + +/* Style the row differently while we show a popover for it. + * Otherwise, the popover is 'pointing to nothing'. Since the + * main popover and the rename popover interleave their hiding + * and showing, we have to count to ensure that we don't loose + * the state before the last popover is gone. + * + * This would be nicer as a state, but reusing hover for this + * interferes with the normal handling of this state, so just + * use a style class. + */ +static void +update_popover_shadowing (GtkWidget *row, + gboolean shown) +{ + int count; + + count = GPOINTER_TO_INT (g_object_get_data (G_OBJECT (row), "popover-count")); + count = shown ? count + 1 : count - 1; + g_object_set_data (G_OBJECT (row), "popover-count", GINT_TO_POINTER (count)); + + if (count > 0) + gtk_widget_add_css_class (row, "has-open-popup"); + else + gtk_widget_remove_css_class (row, "has-open-popup"); +} + +static void +set_prelight (NautilusGtkPlacesSidebar *sidebar) +{ + update_popover_shadowing (GTK_WIDGET (sidebar->context_row), TRUE); +} + +static void +unset_prelight (NautilusGtkPlacesSidebar *sidebar) +{ + update_popover_shadowing (GTK_WIDGET (sidebar->context_row), FALSE); +} + +static void +setup_popover_shadowing (GtkWidget *popover, + NautilusGtkPlacesSidebar *sidebar) +{ + g_signal_connect_swapped (popover, "map", G_CALLBACK (set_prelight), sidebar); + g_signal_connect_swapped (popover, "unmap", G_CALLBACK (unset_prelight), sidebar); +} + +static void +_popover_set_pointing_to_widget (GtkPopover *popover, + GtkWidget *target) +{ + GtkWidget *parent; + double x, y, w, h; + + parent = gtk_widget_get_parent (GTK_WIDGET (popover)); + + if (!gtk_widget_translate_coordinates (target, parent, 0, 0, &x, &y)) + return; + + w = gtk_widget_get_allocated_width (GTK_WIDGET (target)); + h = gtk_widget_get_allocated_height (GTK_WIDGET (target)); + + gtk_popover_set_pointing_to (popover, &(GdkRectangle){x, y, w, h}); +} + +static void +show_rename_popover (NautilusGtkSidebarRow *row) +{ + char *name; + char *uri; + NautilusGtkPlacesSidebar *sidebar; + + g_object_get (row, + "sidebar", &sidebar, + "label", &name, + "uri", &uri, + NULL); + + create_rename_popover (sidebar); + + if (sidebar->rename_uri) + g_free (sidebar->rename_uri); + sidebar->rename_uri = g_strdup (uri); + + gtk_editable_set_text (GTK_EDITABLE (sidebar->rename_entry), name); + + _popover_set_pointing_to_widget (GTK_POPOVER (sidebar->rename_popover), + GTK_WIDGET (row)); + + setup_popover_shadowing (sidebar->rename_popover, sidebar); + + gtk_popover_popup (GTK_POPOVER (sidebar->rename_popover)); + gtk_widget_grab_focus (sidebar->rename_entry); + + g_free (name); + g_free (uri); + g_object_unref (sidebar); +} + +static void +rename_bookmark (NautilusGtkSidebarRow *row) +{ + NautilusGtkPlacesPlaceType type; + + g_object_get (row, "place-type", &type, NULL); + + if (type != NAUTILUS_GTK_PLACES_BOOKMARK && type != NAUTILUS_GTK_PLACES_XDG_DIR) + return; + + show_rename_popover (row); +} + +static void +rename_shortcut_cb (GSimpleAction *action, + GVariant *parameter, + gpointer data) +{ + NautilusGtkPlacesSidebar *sidebar = data; + + rename_bookmark (sidebar->context_row); +} + +static void +properties_cb (GSimpleAction *action, + GVariant *parameter, + gpointer data) +{ + NautilusGtkPlacesSidebar *sidebar = data; + GList *list; + NautilusFile *file; + + g_object_get (sidebar->context_row, "file", &file, NULL); + + list = g_list_append (NULL, file); + nautilus_properties_window_present (list, GTK_WIDGET (sidebar), NULL, NULL, NULL); + + nautilus_file_list_free (list); +} + +static void +empty_trash_cb (GSimpleAction *action, + GVariant *parameter, + gpointer data) +{ + NautilusGtkPlacesSidebar *sidebar = data; + nautilus_file_operations_empty_trash (GTK_WIDGET (sidebar), TRUE, NULL); +} + +static void +remove_bookmark (NautilusGtkSidebarRow *row) +{ + NautilusGtkPlacesPlaceType type; + char *uri; + GFile *file; + NautilusGtkPlacesSidebar *sidebar; + + g_object_get (row, + "sidebar", &sidebar, + "place-type", &type, + "uri", &uri, + NULL); + + if (type == NAUTILUS_GTK_PLACES_BOOKMARK) + { + file = g_file_new_for_uri (uri); + _nautilus_gtk_bookmarks_manager_remove_bookmark (sidebar->bookmarks_manager, file, NULL); + g_object_unref (file); + } + + g_free (uri); + g_object_unref (sidebar); +} + +static void +remove_shortcut_cb (GSimpleAction *action, + GVariant *parameter, + gpointer data) +{ + NautilusGtkPlacesSidebar *sidebar = data; + + remove_bookmark (sidebar->context_row); +} + +static void +mount_shortcut_cb (GSimpleAction *action, + GVariant *parameter, + gpointer data) +{ + NautilusGtkPlacesSidebar *sidebar = data; + GVolume *volume; + + g_object_get (sidebar->context_row, + "volume", &volume, + NULL); + + if (volume != NULL) + mount_volume (sidebar->context_row, volume); + + g_object_unref (volume); +} + +static GMountOperation * +get_mount_operation (NautilusGtkPlacesSidebar *sidebar) +{ + GMountOperation *mount_op; + + mount_op = gtk_mount_operation_new (GTK_WINDOW (gtk_widget_get_root (GTK_WIDGET (sidebar)))); + + emit_mount_operation (sidebar, mount_op); + + return mount_op; +} + +static GMountOperation * +get_unmount_operation (NautilusGtkPlacesSidebar *sidebar) +{ + GMountOperation *mount_op; + + mount_op = gtk_mount_operation_new (GTK_WINDOW (gtk_widget_get_root (GTK_WIDGET (sidebar)))); + + emit_unmount_operation (sidebar, mount_op); + + return mount_op; +} + +/* Returns TRUE if file1 is prefix of file2 or if both files have the + * same path + */ +static gboolean +file_prefix_or_same (GFile *file1, + GFile *file2) +{ + return g_file_has_prefix (file1, file2) || + g_file_equal (file1, file2); +} + +static gboolean +is_current_location_on_volume (NautilusGtkPlacesSidebar *sidebar, + GMount *mount, + GVolume *volume, + GDrive *drive) +{ + gboolean current_location_on_volume; + GFile *mount_default_location; + GMount *mount_for_volume; + GList *volumes_for_drive; + GList *volume_for_drive; + + current_location_on_volume = FALSE; + + if (sidebar->current_location != NULL) + { + if (mount != NULL) + { + mount_default_location = g_mount_get_default_location (mount); + current_location_on_volume = file_prefix_or_same (sidebar->current_location, + mount_default_location); + + g_object_unref (mount_default_location); + } + /* This code path is probably never reached since mount always exists, + * and if it doesn't exists we don't offer a way to eject a volume or + * drive in the UI. Do it for defensive programming + */ + else if (volume != NULL) + { + mount_for_volume = g_volume_get_mount (volume); + if (mount_for_volume != NULL) + { + mount_default_location = g_mount_get_default_location (mount_for_volume); + current_location_on_volume = file_prefix_or_same (sidebar->current_location, + mount_default_location); + + g_object_unref (mount_default_location); + g_object_unref (mount_for_volume); + } + } + /* This code path is probably never reached since mount always exists, + * and if it doesn't exists we don't offer a way to eject a volume or + * drive in the UI. Do it for defensive programming + */ + else if (drive != NULL) + { + volumes_for_drive = g_drive_get_volumes (drive); + for (volume_for_drive = volumes_for_drive; volume_for_drive != NULL; volume_for_drive = volume_for_drive->next) + { + mount_for_volume = g_volume_get_mount (volume_for_drive->data); + if (mount_for_volume != NULL) + { + mount_default_location = g_mount_get_default_location (mount_for_volume); + current_location_on_volume = file_prefix_or_same (sidebar->current_location, + mount_default_location); + + g_object_unref (mount_default_location); + g_object_unref (mount_for_volume); + + if (current_location_on_volume) + break; + } + } + g_list_free_full (volumes_for_drive, g_object_unref); + } + } + + return current_location_on_volume; +} + +static void +do_unmount (GMount *mount, + NautilusGtkPlacesSidebar *sidebar) +{ + if (mount != NULL) + { + GMountOperation *mount_op; + GtkWindow *parent; + + if (is_current_location_on_volume (sidebar, mount, NULL, NULL)) + open_home (sidebar); + + mount_op = get_unmount_operation (sidebar); + parent = gtk_mount_operation_get_parent (GTK_MOUNT_OPERATION (mount_op)); + nautilus_file_operations_unmount_mount_full (parent, mount, mount_op, + FALSE, TRUE, NULL, NULL); + g_object_unref (mount_op); + } +} + +static void +unmount_shortcut_cb (GSimpleAction *action, + GVariant *parameter, + gpointer data) +{ + NautilusGtkPlacesSidebar *sidebar = data; + GMount *mount; + + g_object_get (sidebar->context_row, + "mount", &mount, + NULL); + + do_unmount (mount, sidebar); + + if (mount) + g_object_unref (mount); +} + +static void +drive_stop_cb (GObject *source_object, + GAsyncResult *res, + gpointer user_data) +{ + NautilusGtkPlacesSidebar *sidebar; + GError *error; + char *primary; + char *name; + + sidebar = user_data; + + error = NULL; + if (!g_drive_stop_finish (G_DRIVE (source_object), res, &error)) + { + if (error->code != G_IO_ERROR_FAILED_HANDLED) + { + name = g_drive_get_name (G_DRIVE (source_object)); + primary = g_strdup_printf (_("Unable to stop “%s”"), name); + g_free (name); + emit_show_error_message (sidebar, primary, error->message); + g_free (primary); + } + g_error_free (error); + } + + g_object_unref (sidebar); +} + +static void +drive_eject_cb (GObject *source_object, + GAsyncResult *res, + gpointer user_data) +{ + NautilusGtkPlacesSidebar *sidebar; + GError *error; + char *primary; + char *name; + + sidebar = user_data; + + error = NULL; + if (!g_drive_eject_with_operation_finish (G_DRIVE (source_object), res, &error)) + { + if (error->code != G_IO_ERROR_FAILED_HANDLED) + { + name = g_drive_get_name (G_DRIVE (source_object)); + primary = g_strdup_printf (_("Unable to eject “%s”"), name); + g_free (name); + emit_show_error_message (sidebar, primary, error->message); + g_free (primary); + } + g_error_free (error); + } + + g_object_unref (sidebar); +} + +static void +volume_eject_cb (GObject *source_object, + GAsyncResult *res, + gpointer user_data) +{ + NautilusGtkPlacesSidebar *sidebar; + GError *error; + char *primary; + char *name; + + sidebar = user_data; + + error = NULL; + if (!g_volume_eject_with_operation_finish (G_VOLUME (source_object), res, &error)) + { + if (error->code != G_IO_ERROR_FAILED_HANDLED) + { + name = g_volume_get_name (G_VOLUME (source_object)); + primary = g_strdup_printf (_("Unable to eject %s"), name); + g_free (name); + emit_show_error_message (sidebar, primary, error->message); + g_free (primary); + } + g_error_free (error); + } + + g_object_unref (sidebar); +} + +static void +do_eject (GMount *mount, + GVolume *volume, + GDrive *drive, + NautilusGtkPlacesSidebar *sidebar) +{ + GMountOperation *mount_op; + GtkWindow *parent; + + mount_op = get_unmount_operation (sidebar); + + if (is_current_location_on_volume (sidebar, mount, volume, drive)) + open_home (sidebar); + + if (mount != NULL) + { + parent = gtk_mount_operation_get_parent (GTK_MOUNT_OPERATION (mount_op)); + nautilus_file_operations_unmount_mount_full (parent, mount, mount_op, + TRUE, TRUE, NULL, NULL); + } + /* This code path is probably never reached since mount always exists, + * and if it doesn't exists we don't offer a way to eject a volume or + * drive in the UI. Do it for defensive programming + */ + else if (volume != NULL) + g_volume_eject_with_operation (volume, 0, mount_op, NULL, volume_eject_cb, + g_object_ref (sidebar)); + /* This code path is probably never reached since mount always exists, + * and if it doesn't exists we don't offer a way to eject a volume or + * drive in the UI. Do it for defensive programming + */ + else if (drive != NULL) + { + if (g_drive_can_stop (drive)) + g_drive_stop (drive, 0, mount_op, NULL, drive_stop_cb, + g_object_ref (sidebar)); + else + g_drive_eject_with_operation (drive, 0, mount_op, NULL, drive_eject_cb, + g_object_ref (sidebar)); + } + g_object_unref (mount_op); +} + +static void +eject_shortcut_cb (GSimpleAction *action, + GVariant *parameter, + gpointer data) +{ + NautilusGtkPlacesSidebar *sidebar = data; + GMount *mount; + GVolume *volume; + GDrive *drive; + + g_object_get (sidebar->context_row, + "mount", &mount, + "volume", &volume, + "drive", &drive, + NULL); + + do_eject (mount, volume, drive, sidebar); + + if (mount) + g_object_unref (mount); + if (volume) + g_object_unref (volume); + if (drive) + g_object_unref (drive); +} + +static gboolean +eject_or_unmount_bookmark (NautilusGtkSidebarRow *row) +{ + gboolean can_unmount, can_eject; + GMount *mount; + GVolume *volume; + GDrive *drive; + gboolean ret; + NautilusGtkPlacesSidebar *sidebar; + + g_object_get (row, + "sidebar", &sidebar, + "mount", &mount, + "volume", &volume, + "drive", &drive, + NULL); + ret = FALSE; + + check_unmount_and_eject (mount, volume, drive, &can_unmount, &can_eject); + /* if we can eject, it has priority over unmount */ + if (can_eject) + { + do_eject (mount, volume, drive, sidebar); + ret = TRUE; + } + else if (can_unmount) + { + do_unmount (mount, sidebar); + ret = TRUE; + } + + g_object_unref (sidebar); + if (mount) + g_object_unref (mount); + if (volume) + g_object_unref (volume); + if (drive) + g_object_unref (drive); + + return ret; +} + +static gboolean +eject_or_unmount_selection (NautilusGtkPlacesSidebar *sidebar) +{ + gboolean ret; + GtkListBoxRow *row; + + row = gtk_list_box_get_selected_row (GTK_LIST_BOX (sidebar->list_box)); + ret = eject_or_unmount_bookmark (NAUTILUS_GTK_SIDEBAR_ROW (row)); + + return ret; +} + +static void +drive_poll_for_media_cb (GObject *source_object, + GAsyncResult *res, + gpointer user_data) +{ + NautilusGtkPlacesSidebar *sidebar; + GError *error; + char *primary; + char *name; + + sidebar = NAUTILUS_GTK_PLACES_SIDEBAR (user_data); + + error = NULL; + if (!g_drive_poll_for_media_finish (G_DRIVE (source_object), res, &error)) + { + if (error->code != G_IO_ERROR_FAILED_HANDLED) + { + name = g_drive_get_name (G_DRIVE (source_object)); + primary = g_strdup_printf (_("Unable to poll “%s” for media changes"), name); + g_free (name); + emit_show_error_message (sidebar, primary, error->message); + g_free (primary); + } + g_error_free (error); + } + + g_object_unref (sidebar); +} + +static void +rescan_shortcut_cb (GSimpleAction *action, + GVariant *parameter, + gpointer data) +{ + NautilusGtkPlacesSidebar *sidebar = data; + GDrive *drive; + + g_object_get (sidebar->context_row, + "drive", &drive, + NULL); + + if (drive != NULL) + { + g_drive_poll_for_media (drive, NULL, drive_poll_for_media_cb, g_object_ref (sidebar)); + g_object_unref (drive); + } +} + +static void +drive_start_cb (GObject *source_object, + GAsyncResult *res, + gpointer user_data) +{ + NautilusGtkPlacesSidebar *sidebar; + GError *error; + char *primary; + char *name; + + sidebar = NAUTILUS_GTK_PLACES_SIDEBAR (user_data); + + error = NULL; + if (!g_drive_start_finish (G_DRIVE (source_object), res, &error)) + { + if (error->code != G_IO_ERROR_FAILED_HANDLED) + { + name = g_drive_get_name (G_DRIVE (source_object)); + primary = g_strdup_printf (_("Unable to start “%s”"), name); + g_free (name); + emit_show_error_message (sidebar, primary, error->message); + g_free (primary); + } + g_error_free (error); + } + + g_object_unref (sidebar); +} + +static void +start_shortcut_cb (GSimpleAction *action, + GVariant *parameter, + gpointer data) +{ + NautilusGtkPlacesSidebar *sidebar = data; + GDrive *drive; + + g_object_get (sidebar->context_row, + "drive", &drive, + NULL); + + if (drive != NULL) + { + GMountOperation *mount_op; + + mount_op = get_mount_operation (sidebar); + + g_drive_start (drive, G_DRIVE_START_NONE, mount_op, NULL, drive_start_cb, g_object_ref (sidebar)); + + g_object_unref (mount_op); + g_object_unref (drive); + } +} + +static void +stop_shortcut_cb (GSimpleAction *action, + GVariant *parameter, + gpointer data) +{ + NautilusGtkPlacesSidebar *sidebar = data; + GDrive *drive; + + g_object_get (sidebar->context_row, + "drive", &drive, + NULL); + + if (drive != NULL) + { + GMountOperation *mount_op; + + mount_op = get_unmount_operation (sidebar); + g_drive_stop (drive, G_MOUNT_UNMOUNT_NONE, mount_op, NULL, drive_stop_cb, + g_object_ref (sidebar)); + + g_object_unref (mount_op); + g_object_unref (drive); + } +} + +static gboolean +on_key_pressed (GtkEventControllerKey *controller, + guint keyval, + guint keycode, + GdkModifierType state, + NautilusGtkPlacesSidebar *sidebar) +{ + guint modifiers; + GtkListBoxRow *row; + + row = gtk_list_box_get_selected_row (GTK_LIST_BOX (sidebar->list_box)); + if (row) + { + modifiers = gtk_accelerator_get_default_mod_mask (); + + if (keyval == GDK_KEY_Return || + keyval == GDK_KEY_KP_Enter || + keyval == GDK_KEY_ISO_Enter || + keyval == GDK_KEY_space) + { + NautilusGtkPlacesOpenFlags open_flags = NAUTILUS_GTK_PLACES_OPEN_NORMAL; + + if ((state & modifiers) == GDK_SHIFT_MASK) + open_flags = NAUTILUS_GTK_PLACES_OPEN_NEW_TAB; + else if ((state & modifiers) == GDK_CONTROL_MASK) + open_flags = NAUTILUS_GTK_PLACES_OPEN_NEW_WINDOW; + + open_row (NAUTILUS_GTK_SIDEBAR_ROW (row), open_flags); + + return TRUE; + } + + if (keyval == GDK_KEY_Down && + (state & modifiers) == GDK_ALT_MASK) + return eject_or_unmount_selection (sidebar); + + if ((keyval == GDK_KEY_Delete || + keyval == GDK_KEY_KP_Delete) && + (state & modifiers) == 0) + { + remove_bookmark (NAUTILUS_GTK_SIDEBAR_ROW (row)); + return TRUE; + } + + if ((keyval == GDK_KEY_F2) && + (state & modifiers) == 0) + { + rename_bookmark (NAUTILUS_GTK_SIDEBAR_ROW (row)); + return TRUE; + } + + if ((keyval == GDK_KEY_Menu) || + ((keyval == GDK_KEY_F10) && + (state & modifiers) == GDK_SHIFT_MASK)) + { + popup_menu_cb (NAUTILUS_GTK_SIDEBAR_ROW (row)); + return TRUE; + } + } + + return FALSE; +} + +static void +format_cb (GSimpleAction *action, + GVariant *variant, + gpointer data) +{ + NautilusGtkPlacesSidebar *sidebar = data; + g_autoptr (GVolume) volume = NULL; + g_autofree gchar *device_identifier = NULL; + GVariant *parameters; + + g_object_get (sidebar->context_row, "volume", &volume, NULL); + device_identifier = g_volume_get_identifier (volume, + G_VOLUME_IDENTIFIER_KIND_UNIX_DEVICE); + + parameters = g_variant_new_parsed ("(objectpath '/org/gnome/DiskUtility', @aay [], " + "{'options': <{'block-device': <%s>, " + "'format-device': }> })", device_identifier); + + nautilus_dbus_launcher_call (nautilus_dbus_launcher_get(), + NAUTILUS_DBUS_LAUNCHER_DISKS, + "CommandLine", + parameters, + GTK_WINDOW (gtk_widget_get_root (GTK_WIDGET (sidebar)))); + +} + +static GActionEntry entries[] = { + { "open", open_shortcut_cb, "i", NULL, NULL }, + { "open-other", open_shortcut_cb, "i", NULL, NULL }, + { "bookmark", add_shortcut_cb, NULL, NULL, NULL }, + { "remove", remove_shortcut_cb, NULL, NULL, NULL }, + { "rename", rename_shortcut_cb, NULL, NULL, NULL }, + { "mount", mount_shortcut_cb, NULL, NULL, NULL }, + { "unmount", unmount_shortcut_cb, NULL, NULL, NULL }, + { "eject", eject_shortcut_cb, NULL, NULL, NULL }, + { "rescan", rescan_shortcut_cb, NULL, NULL, NULL }, + { "start", start_shortcut_cb, NULL, NULL, NULL }, + { "stop", stop_shortcut_cb, NULL, NULL, NULL }, + { "properties", properties_cb, NULL, NULL, NULL }, + { "empty-trash", empty_trash_cb, NULL, NULL, NULL }, + { "format", format_cb, NULL, NULL, NULL }, +}; + +static gboolean +should_show_format_command (GVolume *volume, + gchar *uri) +{ + g_autofree gchar *unix_device_id = NULL; + gboolean disks_available; + + if (volume == NULL || !G_IS_VOLUME (volume) || g_str_has_prefix (uri, "mtp://")) + return FALSE; + + unix_device_id = g_volume_get_identifier (volume, G_VOLUME_IDENTIFIER_KIND_UNIX_DEVICE); + disks_available = nautilus_dbus_launcher_is_available (nautilus_dbus_launcher_get(), + NAUTILUS_DBUS_LAUNCHER_DISKS); + + return unix_device_id != NULL && disks_available; +} + +static void +on_row_popover_destroy (GtkWidget *row_popover, + NautilusGtkPlacesSidebar *sidebar) +{ + if (sidebar) + sidebar->popover = NULL; +} + +static void +build_popup_menu_using_gmenu (NautilusGtkSidebarRow *row) +{ + CloudProvidersAccount *cloud_provider_account; + NautilusGtkPlacesSidebar *sidebar; + GMenuModel *cloud_provider_menu; + GActionGroup *cloud_provider_action_group; + + g_object_get (row, + "sidebar", &sidebar, + "cloud-provider-account", &cloud_provider_account, + NULL); + + /* Cloud provider account */ + if (cloud_provider_account) + { + GMenu *menu = g_menu_new (); + GMenuItem *item; + item = g_menu_item_new (_("_Open"), "row.open"); + g_menu_item_set_action_and_target_value (item, "row.open", + g_variant_new_int32 (NAUTILUS_GTK_PLACES_OPEN_NORMAL)); + g_menu_append_item (menu, item); + g_object_unref (item); + + if (sidebar->open_flags & NAUTILUS_GTK_PLACES_OPEN_NEW_TAB) + { + item = g_menu_item_new (_("Open in New _Tab"), "row.open-other"); + g_menu_item_set_action_and_target_value (item, "row.open-other", g_variant_new_int32(NAUTILUS_GTK_PLACES_OPEN_NEW_TAB)); + g_menu_append_item (menu, item); + g_object_unref (item); + } + if (sidebar->open_flags & NAUTILUS_GTK_PLACES_OPEN_NEW_WINDOW) + { + item = g_menu_item_new (_("Open in New _Window"), "row.open-other"); + g_menu_item_set_action_and_target_value (item, "row.open-other", g_variant_new_int32(NAUTILUS_GTK_PLACES_OPEN_NEW_WINDOW)); + g_menu_append_item (menu, item); + g_object_unref (item); + } + cloud_provider_menu = cloud_providers_account_get_menu_model (cloud_provider_account); + cloud_provider_action_group = cloud_providers_account_get_action_group (cloud_provider_account); + if (cloud_provider_menu != NULL && cloud_provider_action_group != NULL) + { + g_menu_append_section (menu, NULL, cloud_provider_menu); + gtk_widget_insert_action_group (GTK_WIDGET (sidebar), + "cloudprovider", + G_ACTION_GROUP (cloud_provider_action_group)); + } + if (sidebar->popover) + gtk_widget_unparent (sidebar->popover); + + sidebar->popover = gtk_popover_menu_new_from_model_full (G_MENU_MODEL (menu), + GTK_POPOVER_MENU_NESTED); + g_object_unref (menu); + gtk_widget_set_parent (sidebar->popover, GTK_WIDGET (sidebar)); + gtk_widget_set_halign (sidebar->popover, GTK_ALIGN_START); + gtk_popover_set_has_arrow (GTK_POPOVER (sidebar->popover), FALSE); + g_signal_connect (sidebar->popover, "destroy", + G_CALLBACK (on_row_popover_destroy), sidebar); + + setup_popover_shadowing (sidebar->popover, sidebar); + + g_object_unref (sidebar); + g_object_unref (cloud_provider_account); + } +} + +/* Constructs the popover for the sidebar row if needed */ +static void +create_row_popover (NautilusGtkPlacesSidebar *sidebar, + NautilusGtkSidebarRow *row) +{ + NautilusGtkPlacesPlaceType type; + GMenu *menu, *section; + GMenuItem *item; + GMount *mount; + GVolume *volume; + GDrive *drive; + GAction *action; + gboolean show_unmount, show_eject; + gboolean show_stop; + g_autofree gchar *uri = NULL; + g_autoptr (GFile) file = NULL; + gboolean show_properties; + g_autoptr (GFile) trash = NULL; + gboolean is_trash; + CloudProvidersAccount *cloud_provider_account; + + g_object_get (row, + "place-type", &type, + "drive", &drive, + "volume", &volume, + "mount", &mount, + "uri", &uri, + NULL); + + check_unmount_and_eject (mount, volume, drive, &show_unmount, &show_eject); + if (uri != NULL) + { + file = g_file_new_for_uri (uri); + trash = g_file_new_for_uri("trash:///"); + is_trash = g_file_equal (trash, file); + show_properties = (g_file_is_native (file) || is_trash || mount != NULL); + } + else + { + show_properties = FALSE; + is_trash = FALSE; + } + + g_object_get (row, "cloud-provider-account", &cloud_provider_account, NULL); + + if (cloud_provider_account) + { + build_popup_menu_using_gmenu (row); + return; + } + + action = g_action_map_lookup_action (G_ACTION_MAP (sidebar->row_actions), "remove"); + g_simple_action_set_enabled (G_SIMPLE_ACTION (action), (type == NAUTILUS_GTK_PLACES_BOOKMARK)); + action = g_action_map_lookup_action (G_ACTION_MAP (sidebar->row_actions), "rename"); + g_simple_action_set_enabled (G_SIMPLE_ACTION (action), (type == NAUTILUS_GTK_PLACES_BOOKMARK || + type == NAUTILUS_GTK_PLACES_XDG_DIR)); + action = g_action_map_lookup_action (G_ACTION_MAP (sidebar->row_actions), "bookmark"); + g_simple_action_set_enabled (G_SIMPLE_ACTION (action), (type == NAUTILUS_GTK_PLACES_MOUNTED_VOLUME)); + action = g_action_map_lookup_action (G_ACTION_MAP (sidebar->row_actions), "open"); + g_simple_action_set_enabled (G_SIMPLE_ACTION (action), !gtk_list_box_row_is_selected (GTK_LIST_BOX_ROW (row))); + action = g_action_map_lookup_action (G_ACTION_MAP (sidebar->row_actions), "empty-trash"); + g_simple_action_set_enabled (G_SIMPLE_ACTION (action), !nautilus_trash_monitor_is_empty()); + + menu = g_menu_new (); + section = g_menu_new (); + + item = g_menu_item_new (_("_Open"), "row.open"); + g_menu_item_set_action_and_target_value (item, "row.open", + g_variant_new_int32 (NAUTILUS_GTK_PLACES_OPEN_NORMAL)); + g_menu_append_item (section, item); + g_object_unref (item); + + if (sidebar->open_flags & NAUTILUS_GTK_PLACES_OPEN_NEW_TAB) + { + item = g_menu_item_new (_("Open in New _Tab"), "row.open-other"); + g_menu_item_set_action_and_target_value (item, "row.open-other", + g_variant_new_int32 (NAUTILUS_GTK_PLACES_OPEN_NEW_TAB)); + g_menu_append_item (section, item); + g_object_unref (item); + } + + if (sidebar->open_flags & NAUTILUS_GTK_PLACES_OPEN_NEW_WINDOW) + { + item = g_menu_item_new (_("Open in New _Window"), "row.open-other"); + g_menu_item_set_action_and_target_value (item, "row.open-other", + g_variant_new_int32 (NAUTILUS_GTK_PLACES_OPEN_NEW_WINDOW)); + g_menu_append_item (section, item); + g_object_unref (item); + } + + g_menu_append_section (menu, NULL, G_MENU_MODEL (section)); + g_object_unref (section); + + section = g_menu_new (); + item = g_menu_item_new (_("Add to _Bookmarks"), "row.bookmark"); + g_menu_item_set_attribute (item, "hidden-when", "s", "action-disabled"); + g_menu_append_item (section, item); + g_object_unref (item); + + item = g_menu_item_new (_("_Remove from Bookmarks"), "row.remove"); + g_menu_item_set_attribute (item, "hidden-when", "s", "action-disabled"); + g_menu_append_item (section, item); + g_object_unref (item); + + item = g_menu_item_new (_("_Rename"), "row.rename"); + g_menu_append_item (section, item); + g_object_unref (item); + + g_menu_append_section (menu, NULL, G_MENU_MODEL (section)); + g_object_unref (section); + + if (is_trash) { + section = g_menu_new (); + item = g_menu_item_new (_("Empty Trash"), "row.empty-trash"); + g_menu_append_item (section, item); + g_object_unref (item); + + g_menu_append_section (menu, NULL, G_MENU_MODEL (section)); + g_object_unref (section); + } + + section = g_menu_new (); + + if (volume != NULL && mount == NULL && + g_volume_can_mount (volume)) + { + item = g_menu_item_new (_("_Mount"), "row.mount"); + g_menu_append_item (section, item); + g_object_unref (item); + } + + show_stop = (drive != NULL && g_drive_can_stop (drive)); + + if (show_unmount && !show_stop) + { + item = g_menu_item_new (_("_Unmount"), "row.unmount"); + g_menu_append_item (section, item); + g_object_unref (item); + } + + if (show_eject) + { + item = g_menu_item_new (_("_Eject"), "row.eject"); + g_menu_append_item (section, item); + g_object_unref (item); + } + + if (drive != NULL && + g_drive_is_media_removable (drive) && + !g_drive_is_media_check_automatic (drive) && + g_drive_can_poll_for_media (drive)) + { + item = g_menu_item_new (_("_Detect Media"), "row.rescan"); + g_menu_append_item (section, item); + g_object_unref (item); + } + + if (drive != NULL && + (g_drive_can_start (drive) || g_drive_can_start_degraded (drive))) + { + const guint ss_type = g_drive_get_start_stop_type (drive); + const char *start_label = _("_Start"); + + if (ss_type == G_DRIVE_START_STOP_TYPE_SHUTDOWN) start_label = _("_Power On"); + else if (ss_type == G_DRIVE_START_STOP_TYPE_NETWORK) start_label = _("_Connect Drive"); + else if (ss_type == G_DRIVE_START_STOP_TYPE_MULTIDISK) start_label = _("_Start Multi-disk Device"); + else if (ss_type == G_DRIVE_START_STOP_TYPE_PASSWORD) start_label = _("_Unlock Device"); + + item = g_menu_item_new (start_label, "row.start"); + g_menu_append_item (section, item); + g_object_unref (item); + } + + if (show_stop && !show_unmount) + { + const guint ss_type = g_drive_get_start_stop_type (drive); + const char *stop_label = _("_Stop"); + + if (ss_type == G_DRIVE_START_STOP_TYPE_SHUTDOWN) stop_label = _("_Safely Remove Drive"); + else if (ss_type == G_DRIVE_START_STOP_TYPE_NETWORK) stop_label = _("_Disconnect Drive"); + else if (ss_type == G_DRIVE_START_STOP_TYPE_MULTIDISK) stop_label = _("_Stop Multi-disk Device"); + else if (ss_type == G_DRIVE_START_STOP_TYPE_PASSWORD) stop_label = _("_Lock Device"); + + item = g_menu_item_new (stop_label, "row.stop"); + g_menu_append_item (section, item); + g_object_unref (item); + } + + if (should_show_format_command (volume, uri)) + { + item = g_menu_item_new (_("Format…"), "row.format"); + g_menu_append_item (section, item); + g_object_unref (item); + } + + g_menu_append_section (menu, NULL, G_MENU_MODEL (section)); + g_object_unref (section); + + if (show_properties) { + section = g_menu_new (); + item = g_menu_item_new (_("Properties"), "row.properties"); + g_menu_append_item (section, item); + g_object_unref (item); + + g_menu_append_section (menu, NULL, G_MENU_MODEL (section)); + g_object_unref (section); + } + + sidebar->popover = gtk_popover_menu_new_from_model (G_MENU_MODEL (menu)); + g_object_unref (menu); + gtk_widget_set_parent (sidebar->popover, GTK_WIDGET (sidebar)); + gtk_widget_set_halign (sidebar->popover, GTK_ALIGN_START); + gtk_popover_set_has_arrow (GTK_POPOVER (sidebar->popover), FALSE); + g_signal_connect (sidebar->popover, "destroy", G_CALLBACK (on_row_popover_destroy), sidebar); + + setup_popover_shadowing (sidebar->popover, sidebar); +} + +static void +show_row_popover (NautilusGtkSidebarRow *row, + double x, + double y) +{ + NautilusGtkPlacesSidebar *sidebar; + double x_in_sidebar, y_in_sidebar; + + g_object_get (row, "sidebar", &sidebar, NULL); + + g_clear_pointer (&sidebar->popover, gtk_widget_unparent); + + create_row_popover (sidebar, row); + + if (x == -1 && y == -1) + _popover_set_pointing_to_widget (GTK_POPOVER (sidebar->popover), GTK_WIDGET (row)); + else + { + gtk_widget_translate_coordinates (GTK_WIDGET (row), GTK_WIDGET (sidebar), + x, y, &x_in_sidebar, &y_in_sidebar); + gtk_popover_set_pointing_to (GTK_POPOVER (sidebar->popover), + &(GdkRectangle){x_in_sidebar, y_in_sidebar, 0, 0}); + } + + sidebar->context_row = row; + gtk_popover_popup (GTK_POPOVER (sidebar->popover)); + + g_object_unref (sidebar); +} + +static void +on_row_activated (GtkListBox *list_box, + GtkListBoxRow *row, + gpointer user_data) +{ + NautilusGtkSidebarRow *selected_row; + + /* Avoid to open a location if the user is dragging. Changing the location + * while dragging usually makes clients changing the view of the files, which + * is confusing while the user has the attention on the drag + */ + if (NAUTILUS_GTK_PLACES_SIDEBAR (user_data)->dragging_over) + return; + + selected_row = NAUTILUS_GTK_SIDEBAR_ROW (gtk_list_box_get_selected_row (list_box)); + open_row (selected_row, 0); +} + +static void +on_row_pressed (GtkGestureClick *gesture, + int n_press, + double x, + double y, + NautilusGtkSidebarRow *row) +{ + NautilusGtkPlacesSidebar *sidebar; + NautilusGtkPlacesSectionType section_type; + NautilusGtkPlacesPlaceType row_type; + + g_object_get (row, + "sidebar", &sidebar, + "section_type", §ion_type, + "place-type", &row_type, + NULL); + + if (section_type == NAUTILUS_GTK_PLACES_SECTION_BOOKMARKS) + { + sidebar->drag_row = GTK_WIDGET (row); + sidebar->drag_row_x = (int)x; + sidebar->drag_row_y = (int)y; + } + + g_object_unref (sidebar); +} + +static void +on_row_released (GtkGestureClick *gesture, + int n_press, + double x, + double y, + NautilusGtkSidebarRow *row) +{ + NautilusGtkPlacesSidebar *sidebar; + NautilusGtkPlacesSectionType section_type; + NautilusGtkPlacesPlaceType row_type; + guint button, state; + + g_object_get (row, + "sidebar", &sidebar, + "section_type", §ion_type, + "place-type", &row_type, + NULL); + + button = gtk_gesture_single_get_current_button (GTK_GESTURE_SINGLE (gesture)); + state = gtk_event_controller_get_current_event_state (GTK_EVENT_CONTROLLER (gesture)); + + if (row) + { + if (button == 2) + { + NautilusGtkPlacesOpenFlags open_flags = NAUTILUS_GTK_PLACES_OPEN_NORMAL; + + open_flags = (state & GDK_CONTROL_MASK) ? + NAUTILUS_GTK_PLACES_OPEN_NEW_WINDOW : + NAUTILUS_GTK_PLACES_OPEN_NEW_TAB; + + open_row (NAUTILUS_GTK_SIDEBAR_ROW (row), open_flags); + gtk_gesture_set_state (GTK_GESTURE (gesture), + GTK_EVENT_SEQUENCE_CLAIMED); + } + else if (button == 3) + { + if (row_type != NAUTILUS_GTK_PLACES_CONNECT_TO_SERVER) + show_row_popover (NAUTILUS_GTK_SIDEBAR_ROW (row), x, y); + } + } +} + +static void +on_row_dragged (GtkGestureDrag *gesture, + double x, + double y, + NautilusGtkSidebarRow *row) +{ + NautilusGtkPlacesSidebar *sidebar; + + g_object_get (row, "sidebar", &sidebar, NULL); + + if (sidebar->drag_row == NULL || sidebar->dragging_over) + { + g_object_unref (sidebar); + return; + } + + if (gtk_drag_check_threshold (GTK_WIDGET (row), 0, 0, x, y)) + { + double start_x, start_y; + double drag_x, drag_y; + GdkContentProvider *content; + GdkSurface *surface; + GdkDevice *device; + GtkAllocation allocation; + GtkWidget *drag_widget; + GdkDrag *drag; + + gtk_gesture_drag_get_start_point (gesture, &start_x, &start_y); + gtk_widget_translate_coordinates (GTK_WIDGET (row), + GTK_WIDGET (sidebar), + start_x, start_y, + &drag_x, &drag_y); + + sidebar->dragging_over = TRUE; + + content = gdk_content_provider_new_typed (NAUTILUS_TYPE_GTK_SIDEBAR_ROW, sidebar->drag_row); + + surface = gtk_native_get_surface (gtk_widget_get_native (GTK_WIDGET (sidebar))); + device = gtk_gesture_get_device (GTK_GESTURE (gesture)); + + drag = gdk_drag_begin (surface, device, content, GDK_ACTION_MOVE, drag_x, drag_y); + + g_object_unref (content); + + g_signal_connect (drag, "dnd-finished", G_CALLBACK (dnd_finished_cb), sidebar); + g_signal_connect (drag, "cancel", G_CALLBACK (dnd_cancel_cb), sidebar); + + gtk_widget_get_allocation (sidebar->drag_row, &allocation); + gtk_widget_hide (sidebar->drag_row); + + drag_widget = GTK_WIDGET (nautilus_gtk_sidebar_row_clone (NAUTILUS_GTK_SIDEBAR_ROW (sidebar->drag_row))); + sidebar->drag_row_height = allocation.height; + gtk_widget_set_size_request (drag_widget, allocation.width, allocation.height); + gtk_widget_set_opacity (drag_widget, 0.8); + + gtk_drag_icon_set_child (GTK_DRAG_ICON (gtk_drag_icon_get_for_drag (drag)), drag_widget); + + g_object_unref (drag); + } + + g_object_unref (sidebar); +} + +static void +popup_menu_cb (NautilusGtkSidebarRow *row) +{ + NautilusGtkPlacesPlaceType row_type; + + g_object_get (row, "place-type", &row_type, NULL); + + if (row_type != NAUTILUS_GTK_PLACES_CONNECT_TO_SERVER) + show_row_popover (row, -1, -1); +} + +static void +long_press_cb (GtkGesture *gesture, + double x, + double y, + NautilusGtkPlacesSidebar *sidebar) +{ + GtkWidget *row; + + row = GTK_WIDGET (gtk_list_box_get_row_at_y (GTK_LIST_BOX (sidebar->list_box), y)); + if (NAUTILUS_IS_GTK_SIDEBAR_ROW (row)) + popup_menu_cb (NAUTILUS_GTK_SIDEBAR_ROW (row)); +} + +static int +list_box_sort_func (GtkListBoxRow *row1, + GtkListBoxRow *row2, + gpointer user_data) +{ + NautilusGtkPlacesSectionType section_type_1, section_type_2; + NautilusGtkPlacesPlaceType place_type_1, place_type_2; + char *label_1, *label_2; + int index_1, index_2; + int retval = 0; + + g_object_get (row1, + "label", &label_1, + "place-type", &place_type_1, + "section-type", §ion_type_1, + "order-index", &index_1, + NULL); + g_object_get (row2, + "label", &label_2, + "place-type", &place_type_2, + "section-type", §ion_type_2, + "order-index", &index_2, + NULL); + + /* Always last position for "connect to server" */ + if (place_type_1 == NAUTILUS_GTK_PLACES_CONNECT_TO_SERVER) + { + retval = 1; + } + else if (place_type_2 == NAUTILUS_GTK_PLACES_CONNECT_TO_SERVER) + { + retval = -1; + } + else + { + if (section_type_1 == section_type_2) + { + if ((section_type_1 == NAUTILUS_GTK_PLACES_SECTION_COMPUTER && + place_type_1 == place_type_2 && + place_type_1 == NAUTILUS_GTK_PLACES_XDG_DIR) || + section_type_1 == NAUTILUS_GTK_PLACES_SECTION_MOUNTS) + { + retval = g_utf8_collate (label_1, label_2); + } + else if ((place_type_1 == NAUTILUS_GTK_PLACES_BOOKMARK || place_type_2 == NAUTILUS_GTK_PLACES_DROP_FEEDBACK) && + (place_type_1 == NAUTILUS_GTK_PLACES_DROP_FEEDBACK || place_type_2 == NAUTILUS_GTK_PLACES_BOOKMARK)) + { + retval = index_1 - index_2; + } + /* We order the bookmarks sections based on the bookmark index that we + * set on the row as an order-index property, but we have to deal with + * the placeholder row wanted to be between two consecutive bookmarks, + * with two consecutive order-index values which is the usual case. + * For that, in the list box sort func we give priority to the placeholder row, + * that means that if the index-order is the same as another bookmark + * the placeholder row goes before. However if we want to show it after + * the current row, for instance when the cursor is in the lower half + * of the row, we need to increase the order-index. + */ + else if (place_type_1 == NAUTILUS_GTK_PLACES_BOOKMARK_PLACEHOLDER && place_type_2 == NAUTILUS_GTK_PLACES_BOOKMARK) + { + if (index_1 == index_2) + retval = index_1 - index_2 - 1; + else + retval = index_1 - index_2; + } + else if (place_type_1 == NAUTILUS_GTK_PLACES_BOOKMARK && place_type_2 == NAUTILUS_GTK_PLACES_BOOKMARK_PLACEHOLDER) + { + if (index_1 == index_2) + retval = index_1 - index_2 + 1; + else + retval = index_1 - index_2; + } + } + else + { + /* Order by section. That means the order in the enum of section types + * define the actual order of them in the list */ + retval = section_type_1 - section_type_2; + } + } + + g_free (label_1); + g_free (label_2); + + return retval; +} + +static void +update_hostname (NautilusGtkPlacesSidebar *sidebar) +{ + GVariant *variant; + gsize len; + const char *hostname; + + if (sidebar->hostnamed_proxy == NULL) + return; + + variant = g_dbus_proxy_get_cached_property (sidebar->hostnamed_proxy, + "PrettyHostname"); + if (variant == NULL) + return; + + hostname = g_variant_get_string (variant, &len); + if (len > 0 && + g_strcmp0 (sidebar->hostname, hostname) != 0) + { + g_free (sidebar->hostname); + sidebar->hostname = g_strdup (hostname); + update_places (sidebar); + } + + g_variant_unref (variant); +} + +static void +hostname_proxy_new_cb (GObject *source_object, + GAsyncResult *res, + gpointer user_data) +{ + NautilusGtkPlacesSidebar *sidebar = user_data; + GError *error = NULL; + GDBusProxy *proxy; + + proxy = g_dbus_proxy_new_for_bus_finish (res, &error); + if (g_error_matches (error, G_IO_ERROR, G_IO_ERROR_CANCELLED)) + { + g_error_free (error); + return; + } + + sidebar->hostnamed_proxy = proxy; + g_clear_object (&sidebar->hostnamed_cancellable); + + if (error != NULL) + { + g_debug ("Failed to create D-Bus proxy: %s", error->message); + g_error_free (error); + return; + } + + g_signal_connect_swapped (sidebar->hostnamed_proxy, + "g-properties-changed", + G_CALLBACK (update_hostname), + sidebar); + update_hostname (sidebar); +} + +static void +create_volume_monitor (NautilusGtkPlacesSidebar *sidebar) +{ + g_assert (sidebar->volume_monitor == NULL); + + sidebar->volume_monitor = g_volume_monitor_get (); + + g_signal_connect_object (sidebar->volume_monitor, "volume_added", + G_CALLBACK (update_places), sidebar, G_CONNECT_SWAPPED); + g_signal_connect_object (sidebar->volume_monitor, "volume_removed", + G_CALLBACK (update_places), sidebar, G_CONNECT_SWAPPED); + g_signal_connect_object (sidebar->volume_monitor, "volume_changed", + G_CALLBACK (update_places), sidebar, G_CONNECT_SWAPPED); + g_signal_connect_object (sidebar->volume_monitor, "mount_added", + G_CALLBACK (update_places), sidebar, G_CONNECT_SWAPPED); + g_signal_connect_object (sidebar->volume_monitor, "mount_removed", + G_CALLBACK (update_places), sidebar, G_CONNECT_SWAPPED); + g_signal_connect_object (sidebar->volume_monitor, "mount_changed", + G_CALLBACK (update_places), sidebar, G_CONNECT_SWAPPED); + g_signal_connect_object (sidebar->volume_monitor, "drive_disconnected", + G_CALLBACK (update_places), sidebar, G_CONNECT_SWAPPED); + g_signal_connect_object (sidebar->volume_monitor, "drive_connected", + G_CALLBACK (update_places), sidebar, G_CONNECT_SWAPPED); + g_signal_connect_object (sidebar->volume_monitor, "drive_changed", + G_CALLBACK (update_places), sidebar, G_CONNECT_SWAPPED); +} + +static void +shell_shows_desktop_changed (GtkSettings *settings, + GParamSpec *pspec, + gpointer user_data) +{ + NautilusGtkPlacesSidebar *sidebar = user_data; + gboolean show_desktop; + + g_assert (settings == sidebar->gtk_settings); + + /* Check if the user explicitly set this and, if so, don't change it. */ + if (sidebar->show_desktop_set) + return; + + g_object_get (settings, "gtk-shell-shows-desktop", &show_desktop, NULL); + + if (show_desktop != sidebar->show_desktop) + { + sidebar->show_desktop = show_desktop; + update_places (sidebar); + g_object_notify_by_pspec (G_OBJECT (sidebar), properties[PROP_SHOW_DESKTOP]); + } +} + +static void +nautilus_gtk_places_sidebar_init (NautilusGtkPlacesSidebar *sidebar) +{ + GtkDropTarget *target; + gboolean show_desktop; + GtkEventController *controller; + GtkGesture *gesture; + + sidebar->cancellable = g_cancellable_new (); + + sidebar->show_trash = TRUE; + sidebar->show_other_locations = TRUE; + sidebar->show_recent = TRUE; + sidebar->show_desktop = TRUE; + + sidebar->shortcuts = g_list_store_new (G_TYPE_FILE); + + create_volume_monitor (sidebar); + + sidebar->open_flags = NAUTILUS_GTK_PLACES_OPEN_NORMAL; + + sidebar->bookmarks_manager = _nautilus_gtk_bookmarks_manager_new ((GtkBookmarksChangedFunc)update_places, sidebar); + + g_signal_connect_object (nautilus_trash_monitor_get (), "trash-state-changed", + G_CALLBACK (update_trash_icon), sidebar, + G_CONNECT_SWAPPED); + + sidebar->swin = gtk_scrolled_window_new (); + gtk_widget_set_parent (sidebar->swin, GTK_WIDGET (sidebar)); + gtk_widget_set_size_request (sidebar->swin, 140, 280); + + gtk_scrolled_window_set_policy (GTK_SCROLLED_WINDOW (sidebar->swin), + GTK_POLICY_NEVER, + GTK_POLICY_AUTOMATIC); + + /* list box */ + sidebar->list_box = gtk_list_box_new (); + gtk_widget_add_css_class (sidebar->list_box, "navigation-sidebar"); + + gtk_list_box_set_header_func (GTK_LIST_BOX (sidebar->list_box), + list_box_header_func, sidebar, NULL); + gtk_list_box_set_sort_func (GTK_LIST_BOX (sidebar->list_box), + list_box_sort_func, NULL, NULL); + gtk_list_box_set_selection_mode (GTK_LIST_BOX (sidebar->list_box), GTK_SELECTION_SINGLE); + gtk_list_box_set_activate_on_single_click (GTK_LIST_BOX (sidebar->list_box), TRUE); + + g_signal_connect (sidebar->list_box, "row-activated", + G_CALLBACK (on_row_activated), sidebar); + + controller = gtk_event_controller_key_new (); + g_signal_connect (controller, "key-pressed", + G_CALLBACK (on_key_pressed), sidebar); + gtk_widget_add_controller (sidebar->list_box, controller); + + gesture = gtk_gesture_long_press_new (); + gtk_gesture_single_set_touch_only (GTK_GESTURE_SINGLE (gesture), TRUE); + g_signal_connect (gesture, "pressed", + G_CALLBACK (long_press_cb), sidebar); + gtk_widget_add_controller (GTK_WIDGET (sidebar), GTK_EVENT_CONTROLLER (gesture)); + + /* DND support */ + target = gtk_drop_target_new (G_TYPE_INVALID, GDK_ACTION_ALL); + gtk_drop_target_set_preload (target, TRUE); + gtk_drop_target_set_gtypes (target, (GType[2]) { NAUTILUS_TYPE_GTK_SIDEBAR_ROW, GDK_TYPE_FILE_LIST }, 2); + g_signal_connect (target, "enter", G_CALLBACK (drag_motion_callback), sidebar); + g_signal_connect (target, "motion", G_CALLBACK (drag_motion_callback), sidebar); + g_signal_connect (target, "drop", G_CALLBACK (drag_drop_callback), sidebar); + g_signal_connect (target, "leave", G_CALLBACK (drag_leave_callback), sidebar); + gtk_widget_add_controller (sidebar->list_box, GTK_EVENT_CONTROLLER (target)); + + sidebar->drag_row = NULL; + sidebar->row_placeholder = NULL; + sidebar->dragging_over = FALSE; + + gtk_scrolled_window_set_child (GTK_SCROLLED_WINDOW (sidebar->swin), sidebar->list_box); + + sidebar->hostname = g_strdup (_("Computer")); + sidebar->hostnamed_cancellable = g_cancellable_new (); + g_dbus_proxy_new_for_bus (G_BUS_TYPE_SYSTEM, + G_DBUS_PROXY_FLAGS_GET_INVALIDATED_PROPERTIES, + NULL, + "org.freedesktop.hostname1", + "/org/freedesktop/hostname1", + "org.freedesktop.hostname1", + sidebar->hostnamed_cancellable, + hostname_proxy_new_cb, + sidebar); + + sidebar->drop_state = DROP_STATE_NORMAL; + + /* Don't bother trying to trace this across hierarchy changes... */ + sidebar->gtk_settings = gtk_settings_get_default (); + g_signal_connect (sidebar->gtk_settings, "notify::gtk-shell-shows-desktop", + G_CALLBACK (shell_shows_desktop_changed), sidebar); + g_object_get (sidebar->gtk_settings, "gtk-shell-shows-desktop", &show_desktop, NULL); + sidebar->show_desktop = show_desktop; + + /* Cloud providers */ + sidebar->cloud_manager = cloud_providers_collector_dup_singleton (); + g_signal_connect_swapped (sidebar->cloud_manager, + "providers-changed", + G_CALLBACK (update_places), + sidebar); + + /* populate the sidebar */ + update_places (sidebar); + + sidebar->row_actions = G_ACTION_GROUP (g_simple_action_group_new ()); + g_action_map_add_action_entries (G_ACTION_MAP (sidebar->row_actions), + entries, G_N_ELEMENTS (entries), + sidebar); + gtk_widget_insert_action_group (GTK_WIDGET (sidebar), "row", sidebar->row_actions); + + gtk_accessible_update_property (GTK_ACCESSIBLE (sidebar), + GTK_ACCESSIBLE_PROPERTY_LABEL, _("Sidebar"), + GTK_ACCESSIBLE_PROPERTY_DESCRIPTION, + _("List of common shortcuts, mountpoints, and bookmarks."), -1); +} + +static void +nautilus_gtk_places_sidebar_set_property (GObject *obj, + guint property_id, + const GValue *value, + GParamSpec *pspec) +{ + NautilusGtkPlacesSidebar *sidebar = NAUTILUS_GTK_PLACES_SIDEBAR (obj); + + switch (property_id) + { + case PROP_LOCATION: + nautilus_gtk_places_sidebar_set_location (sidebar, g_value_get_object (value)); + break; + + case PROP_OPEN_FLAGS: + nautilus_gtk_places_sidebar_set_open_flags (sidebar, g_value_get_flags (value)); + break; + + case PROP_SHOW_RECENT: + nautilus_gtk_places_sidebar_set_show_recent (sidebar, g_value_get_boolean (value)); + break; + + case PROP_SHOW_DESKTOP: + nautilus_gtk_places_sidebar_set_show_desktop (sidebar, g_value_get_boolean (value)); + break; + + case PROP_SHOW_ENTER_LOCATION: + nautilus_gtk_places_sidebar_set_show_enter_location (sidebar, g_value_get_boolean (value)); + break; + + case PROP_SHOW_OTHER_LOCATIONS: + nautilus_gtk_places_sidebar_set_show_other_locations (sidebar, g_value_get_boolean (value)); + break; + + case PROP_SHOW_TRASH: + nautilus_gtk_places_sidebar_set_show_trash (sidebar, g_value_get_boolean (value)); + break; + + case PROP_SHOW_STARRED_LOCATION: + nautilus_gtk_places_sidebar_set_show_starred_location (sidebar, g_value_get_boolean (value)); + break; + + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (obj, property_id, pspec); + break; + } +} + +static void +nautilus_gtk_places_sidebar_get_property (GObject *obj, + guint property_id, + GValue *value, + GParamSpec *pspec) +{ + NautilusGtkPlacesSidebar *sidebar = NAUTILUS_GTK_PLACES_SIDEBAR (obj); + + switch (property_id) + { + case PROP_LOCATION: + g_value_take_object (value, nautilus_gtk_places_sidebar_get_location (sidebar)); + break; + + case PROP_OPEN_FLAGS: + g_value_set_flags (value, nautilus_gtk_places_sidebar_get_open_flags (sidebar)); + break; + + case PROP_SHOW_RECENT: + g_value_set_boolean (value, nautilus_gtk_places_sidebar_get_show_recent (sidebar)); + break; + + case PROP_SHOW_DESKTOP: + g_value_set_boolean (value, nautilus_gtk_places_sidebar_get_show_desktop (sidebar)); + break; + + case PROP_SHOW_ENTER_LOCATION: + g_value_set_boolean (value, nautilus_gtk_places_sidebar_get_show_enter_location (sidebar)); + break; + + case PROP_SHOW_OTHER_LOCATIONS: + g_value_set_boolean (value, nautilus_gtk_places_sidebar_get_show_other_locations (sidebar)); + break; + + case PROP_SHOW_TRASH: + g_value_set_boolean (value, nautilus_gtk_places_sidebar_get_show_trash (sidebar)); + break; + + case PROP_SHOW_STARRED_LOCATION: + g_value_set_boolean (value, nautilus_gtk_places_sidebar_get_show_starred_location (sidebar)); + break; + + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (obj, property_id, pspec); + break; + } +} + +static void +nautilus_gtk_places_sidebar_dispose (GObject *object) +{ + NautilusGtkPlacesSidebar *sidebar; + GList *l; + + sidebar = NAUTILUS_GTK_PLACES_SIDEBAR (object); + + if (sidebar->cancellable) + { + g_cancellable_cancel (sidebar->cancellable); + g_object_unref (sidebar->cancellable); + sidebar->cancellable = NULL; + } + + if (sidebar->bookmarks_manager != NULL) + { + _nautilus_gtk_bookmarks_manager_free (sidebar->bookmarks_manager); + sidebar->bookmarks_manager = NULL; + } + + g_clear_pointer (&sidebar->popover, gtk_widget_unparent); + + if (sidebar->rename_popover) + { + gtk_widget_unparent (sidebar->rename_popover); + sidebar->rename_popover = NULL; + sidebar->rename_entry = NULL; + sidebar->rename_button = NULL; + sidebar->rename_error = NULL; + } + + if (sidebar->trash_row) + { + g_object_remove_weak_pointer (G_OBJECT (sidebar->trash_row), + (gpointer *) &sidebar->trash_row); + sidebar->trash_row = NULL; + } + + if (sidebar->volume_monitor != NULL) + { + g_signal_handlers_disconnect_by_func (sidebar->volume_monitor, + update_places, sidebar); + g_clear_object (&sidebar->volume_monitor); + } + + if (sidebar->hostnamed_cancellable != NULL) + { + g_cancellable_cancel (sidebar->hostnamed_cancellable); + g_clear_object (&sidebar->hostnamed_cancellable); + } + + g_clear_object (&sidebar->hostnamed_proxy); + g_free (sidebar->hostname); + sidebar->hostname = NULL; + + if (sidebar->gtk_settings) + { + g_signal_handlers_disconnect_by_func (sidebar->gtk_settings, shell_shows_desktop_changed, sidebar); + sidebar->gtk_settings = NULL; + } + + g_clear_object (&sidebar->current_location); + g_clear_pointer (&sidebar->rename_uri, g_free); + g_clear_object (&sidebar->shortcuts); + + g_clear_handle_id (&sidebar->hover_timer_id, g_source_remove); + + for (l = sidebar->unready_accounts; l != NULL; l = l->next) + { + g_signal_handlers_disconnect_by_data (l->data, sidebar); + } + g_list_free_full (sidebar->unready_accounts, g_object_unref); + sidebar->unready_accounts = NULL; + if (sidebar->cloud_manager) + { + g_signal_handlers_disconnect_by_data (sidebar->cloud_manager, sidebar); + for (l = cloud_providers_collector_get_providers (sidebar->cloud_manager); + l != NULL; l = l->next) + { + g_signal_handlers_disconnect_by_data (l->data, sidebar); + } + g_object_unref (sidebar->cloud_manager); + sidebar->cloud_manager = NULL; + } + + G_OBJECT_CLASS (nautilus_gtk_places_sidebar_parent_class)->dispose (object); +} + +static void +nautilus_gtk_places_sidebar_finalize (GObject *object) +{ + NautilusGtkPlacesSidebar *sidebar = NAUTILUS_GTK_PLACES_SIDEBAR (object); + + g_clear_object (&sidebar->row_actions); + + g_clear_pointer (&sidebar->swin, gtk_widget_unparent); + + G_OBJECT_CLASS (nautilus_gtk_places_sidebar_parent_class)->finalize (object); +} + +static void +nautilus_gtk_places_sidebar_measure (GtkWidget *widget, + GtkOrientation orientation, + int for_size, + int *minimum, + int *natural, + int *minimum_baseline, + int *natural_baseline) +{ + NautilusGtkPlacesSidebar *sidebar = NAUTILUS_GTK_PLACES_SIDEBAR (widget); + + gtk_widget_measure (sidebar->swin, + orientation, + for_size, + minimum, natural, + minimum_baseline, natural_baseline); +} + +static void +nautilus_gtk_places_sidebar_size_allocate (GtkWidget *widget, + int width, + int height, + int baseline) +{ + NautilusGtkPlacesSidebar *sidebar = NAUTILUS_GTK_PLACES_SIDEBAR (widget); + + gtk_widget_size_allocate (sidebar->swin, + &(GtkAllocation) { 0, 0, width, height }, + baseline); + + if (sidebar->popover) + gtk_popover_present (GTK_POPOVER (sidebar->popover)); + + if (sidebar->rename_popover) + gtk_popover_present (GTK_POPOVER (sidebar->rename_popover)); +} + +static void +nautilus_gtk_places_sidebar_class_init (NautilusGtkPlacesSidebarClass *class) +{ + GObjectClass *gobject_class = G_OBJECT_CLASS (class); + GtkWidgetClass *widget_class = GTK_WIDGET_CLASS (class); + + + gobject_class->dispose = nautilus_gtk_places_sidebar_dispose; + gobject_class->finalize = nautilus_gtk_places_sidebar_finalize; + gobject_class->set_property = nautilus_gtk_places_sidebar_set_property; + gobject_class->get_property = nautilus_gtk_places_sidebar_get_property; + + widget_class->measure = nautilus_gtk_places_sidebar_measure; + widget_class->size_allocate = nautilus_gtk_places_sidebar_size_allocate; + + /* + * NautilusGtkPlacesSidebar::open-location: + * @sidebar: the object which received the signal. + * @location: (type Gio.File): GFile to which the caller should switch. + * @open_flags: a single value from NautilusGtkPlacesOpenFlags specifying how the @location should be opened. + * + * The places sidebar emits this signal when the user selects a location + * in it. The calling application should display the contents of that + * location; for example, a file manager should show a list of files in + * the specified location. + */ + places_sidebar_signals [OPEN_LOCATION] = + g_signal_new ("open-location", + G_OBJECT_CLASS_TYPE (gobject_class), + G_SIGNAL_RUN_FIRST, + G_STRUCT_OFFSET (NautilusGtkPlacesSidebarClass, open_location), + NULL, NULL, + NULL, + G_TYPE_NONE, 2, + G_TYPE_OBJECT, + NAUTILUS_TYPE_OPEN_FLAGS); + + /* + * NautilusGtkPlacesSidebar::show-error-message: + * @sidebar: the object which received the signal. + * @primary: primary message with a summary of the error to show. + * @secondary: secondary message with details of the error to show. + * + * The places sidebar emits this signal when it needs the calling + * application to present an error message. Most of these messages + * refer to mounting or unmounting media, for example, when a drive + * cannot be started for some reason. + */ + places_sidebar_signals [SHOW_ERROR_MESSAGE] = + g_signal_new ("show-error-message", + G_OBJECT_CLASS_TYPE (gobject_class), + G_SIGNAL_RUN_FIRST, + G_STRUCT_OFFSET (NautilusGtkPlacesSidebarClass, show_error_message), + NULL, NULL, + NULL, + G_TYPE_NONE, 2, + G_TYPE_STRING, + G_TYPE_STRING); + + /* + * NautilusGtkPlacesSidebar::show-enter-location: + * @sidebar: the object which received the signal. + * + * The places sidebar emits this signal when it needs the calling + * application to present a way to directly enter a location. + * For example, the application may bring up a dialog box asking for + * a URL like "http://http.example.com". + */ + places_sidebar_signals [SHOW_ENTER_LOCATION] = + g_signal_new ("show-enter-location", + G_OBJECT_CLASS_TYPE (gobject_class), + G_SIGNAL_RUN_FIRST, + G_STRUCT_OFFSET (NautilusGtkPlacesSidebarClass, show_enter_location), + NULL, NULL, + NULL, + G_TYPE_NONE, 0); + + /* + * NautilusGtkPlacesSidebar::drag-action-requested: + * @sidebar: the object which received the signal. + * @drop: (type Gdk.Drop): GdkDrop with information about the drag operation + * @dest_file: (type Gio.File): GFile with the tentative location that is being hovered for a drop + * @source_file_list: (type GLib.SList) (element-type GFile) (transfer none): + * List of GFile that are being dragged + * + * When the user starts a drag-and-drop operation and the sidebar needs + * to ask the application for which drag action to perform, then the + * sidebar will emit this signal. + * + * The application can evaluate the @context for customary actions, or + * it can check the type of the files indicated by @source_file_list against the + * possible actions for the destination @dest_file. + * + * The drag action to use must be the return value of the signal handler. + * + * Returns: The drag action to use, for example, GDK_ACTION_COPY + * or GDK_ACTION_MOVE, or 0 if no action is allowed here (i.e. drops + * are not allowed in the specified @dest_file). + */ + places_sidebar_signals [DRAG_ACTION_REQUESTED] = + g_signal_new ("drag-action-requested", + G_OBJECT_CLASS_TYPE (gobject_class), + G_SIGNAL_RUN_LAST, + G_STRUCT_OFFSET (NautilusGtkPlacesSidebarClass, drag_action_requested), + NULL, NULL, + NULL, + GDK_TYPE_DRAG_ACTION, 2, + G_TYPE_OBJECT, + GDK_TYPE_FILE_LIST); + + /* + * NautilusGtkPlacesSidebar::drag-action-ask: + * @sidebar: the object which received the signal. + * @actions: Possible drag actions that need to be asked for. + * + * The places sidebar emits this signal when it needs to ask the application + * to pop up a menu to ask the user for which drag action to perform. + * + * Returns: the final drag action that the sidebar should pass to the drag side + * of the drag-and-drop operation. + */ + places_sidebar_signals [DRAG_ACTION_ASK] = + g_signal_new ("drag-action-ask", + G_OBJECT_CLASS_TYPE (gobject_class), + G_SIGNAL_RUN_LAST, + G_STRUCT_OFFSET (NautilusGtkPlacesSidebarClass, drag_action_ask), + NULL, NULL, + NULL, + GDK_TYPE_DRAG_ACTION, 1, + GDK_TYPE_DRAG_ACTION); + + /* + * NautilusGtkPlacesSidebar::drag-perform-drop: + * @sidebar: the object which received the signal. + * @dest_file: (type Gio.File): Destination GFile. + * @source_file_list: (type GLib.SList) (element-type GFile) (transfer none): + * GSList of GFile that got dropped. + * @action: Drop action to perform. + * + * The places sidebar emits this signal when the user completes a + * drag-and-drop operation and one of the sidebar's items is the + * destination. This item is in the @dest_file, and the + * @source_file_list has the list of files that are dropped into it and + * which should be copied/moved/etc. based on the specified @action. + */ + places_sidebar_signals [DRAG_PERFORM_DROP] = + g_signal_new ("drag-perform-drop", + G_OBJECT_CLASS_TYPE (gobject_class), + G_SIGNAL_RUN_FIRST, + G_STRUCT_OFFSET (NautilusGtkPlacesSidebarClass, drag_perform_drop), + NULL, NULL, + NULL, + G_TYPE_NONE, 3, + G_TYPE_OBJECT, + GDK_TYPE_FILE_LIST, + GDK_TYPE_DRAG_ACTION); + + /* + * NautilusGtkPlacesSidebar::show-other-locations-with-flags: + * @sidebar: the object which received the signal. + * @open_flags: a single value from NautilusGtkPlacesOpenFlags specifying how it should be opened. + * + * The places sidebar emits this signal when it needs the calling + * application to present a way to show other locations e.g. drives + * and network access points. + * For example, the application may bring up a page showing persistent + * volumes and discovered network addresses. + */ + places_sidebar_signals [SHOW_OTHER_LOCATIONS_WITH_FLAGS] = + g_signal_new ("show-other-locations-with-flags", + G_OBJECT_CLASS_TYPE (gobject_class), + G_SIGNAL_RUN_FIRST, + G_STRUCT_OFFSET (NautilusGtkPlacesSidebarClass, show_other_locations_with_flags), + NULL, NULL, + NULL, + G_TYPE_NONE, 1, + NAUTILUS_TYPE_OPEN_FLAGS); + + /* + * NautilusGtkPlacesSidebar::mount: + * @sidebar: the object which received the signal. + * @mount_operation: the GMountOperation that is going to start. + * + * The places sidebar emits this signal when it starts a new operation + * because the user clicked on some location that needs mounting. + * In this way the application using the NautilusGtkPlacesSidebar can track the + * progress of the operation and, for example, show a notification. + */ + places_sidebar_signals [MOUNT] = + g_signal_new ("mount", + G_OBJECT_CLASS_TYPE (gobject_class), + G_SIGNAL_RUN_FIRST, + G_STRUCT_OFFSET (NautilusGtkPlacesSidebarClass, mount), + NULL, NULL, + NULL, + G_TYPE_NONE, + 1, + G_TYPE_MOUNT_OPERATION); + /* + * NautilusGtkPlacesSidebar::unmount: + * @sidebar: the object which received the signal. + * @mount_operation: the GMountOperation that is going to start. + * + * The places sidebar emits this signal when it starts a new operation + * because the user for example ejected some drive or unmounted a mount. + * In this way the application using the NautilusGtkPlacesSidebar can track the + * progress of the operation and, for example, show a notification. + */ + places_sidebar_signals [UNMOUNT] = + g_signal_new ("unmount", + G_OBJECT_CLASS_TYPE (gobject_class), + G_SIGNAL_RUN_FIRST, + G_STRUCT_OFFSET (NautilusGtkPlacesSidebarClass, unmount), + NULL, NULL, + NULL, + G_TYPE_NONE, + 1, + G_TYPE_MOUNT_OPERATION); + + /* + * NautilusGtkPlacesSidebar::show-starred-location: + * @sidebar: the object which received the signal + * @flags: the flags for the operation + * + * The places sidebar emits this signal when it needs the calling + * application to present a way to show the starred files. In GNOME, + * starred files are implemented by setting the nao:predefined-tag-favorite + * tag in the tracker database. + */ + places_sidebar_signals [SHOW_STARRED_LOCATION] = + g_signal_new ("show-starred-location", + G_OBJECT_CLASS_TYPE (gobject_class), + G_SIGNAL_RUN_FIRST, + G_STRUCT_OFFSET (NautilusGtkPlacesSidebarClass, show_starred_location), + NULL, NULL, + NULL, + G_TYPE_NONE, 1, + NAUTILUS_TYPE_OPEN_FLAGS); + + properties[PROP_LOCATION] = + g_param_spec_object ("location", + "Location to Select", + "The location to highlight in the sidebar", + G_TYPE_FILE, + G_PARAM_READWRITE|G_PARAM_STATIC_NAME|G_PARAM_STATIC_NICK|G_PARAM_STATIC_BLURB); + properties[PROP_OPEN_FLAGS] = + g_param_spec_flags ("open-flags", + "Open Flags", + "Modes in which the calling application can open locations selected in the sidebar", + NAUTILUS_TYPE_OPEN_FLAGS, + NAUTILUS_GTK_PLACES_OPEN_NORMAL, + G_PARAM_READWRITE|G_PARAM_STATIC_NAME|G_PARAM_STATIC_NICK|G_PARAM_STATIC_BLURB); + properties[PROP_SHOW_RECENT] = + g_param_spec_boolean ("show-recent", + "Show recent files", + "Whether the sidebar includes a builtin shortcut for recent files", + TRUE, + G_PARAM_READWRITE|G_PARAM_STATIC_NAME|G_PARAM_STATIC_NICK|G_PARAM_STATIC_BLURB); + properties[PROP_SHOW_DESKTOP] = + g_param_spec_boolean ("show-desktop", + "Show “Desktop”", + "Whether the sidebar includes a builtin shortcut to the Desktop folder", + TRUE, + G_PARAM_READWRITE|G_PARAM_STATIC_NAME|G_PARAM_STATIC_NICK|G_PARAM_STATIC_BLURB); + properties[PROP_SHOW_ENTER_LOCATION] = + g_param_spec_boolean ("show-enter-location", + "Show “Enter Location”", + "Whether the sidebar includes a builtin shortcut to manually enter a location", + FALSE, + G_PARAM_READWRITE|G_PARAM_STATIC_NAME|G_PARAM_STATIC_NICK|G_PARAM_STATIC_BLURB); + properties[PROP_SHOW_TRASH] = + g_param_spec_boolean ("show-trash", + "Show “Trash”", + "Whether the sidebar includes a builtin shortcut to the Trash location", + TRUE, + G_PARAM_READWRITE|G_PARAM_STATIC_NAME|G_PARAM_STATIC_NICK|G_PARAM_STATIC_BLURB); + properties[PROP_SHOW_OTHER_LOCATIONS] = + g_param_spec_boolean ("show-other-locations", + "Show “Other locations”", + "Whether the sidebar includes an item to show external locations", + TRUE, + G_PARAM_READWRITE|G_PARAM_STATIC_NAME|G_PARAM_STATIC_NICK|G_PARAM_STATIC_BLURB); + properties[PROP_SHOW_STARRED_LOCATION] = + g_param_spec_boolean ("show-starred-location", + "Show “Starred Location”", + "Whether the sidebar includes an item to show starred files", + FALSE, + G_PARAM_READWRITE|G_PARAM_STATIC_NAME|G_PARAM_STATIC_NICK|G_PARAM_STATIC_BLURB); + + g_object_class_install_properties (gobject_class, NUM_PROPERTIES, properties); + + gtk_widget_class_set_css_name (widget_class, "placessidebar"); + gtk_widget_class_set_accessible_role (widget_class, GTK_ACCESSIBLE_ROLE_LIST); +} + +/* + * nautilus_gtk_places_sidebar_new: + * + * Creates a new NautilusGtkPlacesSidebar widget. + * + * The application should connect to at least the + * NautilusGtkPlacesSidebar::open-location signal to be notified + * when the user makes a selection in the sidebar. + * + * Returns: a newly created NautilusGtkPlacesSidebar + */ +GtkWidget * +nautilus_gtk_places_sidebar_new (void) +{ + return GTK_WIDGET (g_object_new (nautilus_gtk_places_sidebar_get_type (), NULL)); +} + +/* + * nautilus_gtk_places_sidebar_set_open_flags: + * @sidebar: a places sidebar + * @flags: Bitmask of modes in which the calling application can open locations + * + * Sets the way in which the calling application can open new locations from + * the places sidebar. For example, some applications only open locations + * “directly” into their main view, while others may support opening locations + * in a new notebook tab or a new window. + * + * This function is used to tell the places @sidebar about the ways in which the + * application can open new locations, so that the sidebar can display (or not) + * the “Open in new tab” and “Open in new window” menu items as appropriate. + * + * When the NautilusGtkPlacesSidebar::open-location signal is emitted, its flags + * argument will be set to one of the @flags that was passed in + * nautilus_gtk_places_sidebar_set_open_flags(). + * + * Passing 0 for @flags will cause NAUTILUS_GTK_PLACES_OPEN_NORMAL to always be sent + * to callbacks for the “open-location” signal. + */ +void +nautilus_gtk_places_sidebar_set_open_flags (NautilusGtkPlacesSidebar *sidebar, + NautilusGtkPlacesOpenFlags flags) +{ + g_return_if_fail (NAUTILUS_IS_GTK_PLACES_SIDEBAR (sidebar)); + + if (sidebar->open_flags != flags) + { + sidebar->open_flags = flags; + g_object_notify_by_pspec (G_OBJECT (sidebar), properties[PROP_OPEN_FLAGS]); + } +} + +/* + * nautilus_gtk_places_sidebar_get_open_flags: + * @sidebar: a NautilusGtkPlacesSidebar + * + * Gets the open flags. + * + * Returns: the NautilusGtkPlacesOpenFlags of @sidebar + */ +NautilusGtkPlacesOpenFlags +nautilus_gtk_places_sidebar_get_open_flags (NautilusGtkPlacesSidebar *sidebar) +{ + g_return_val_if_fail (NAUTILUS_IS_GTK_PLACES_SIDEBAR (sidebar), 0); + + return sidebar->open_flags; +} + +/* + * nautilus_gtk_places_sidebar_set_location: + * @sidebar: a places sidebar + * @location: (nullable): location to select, or %NULL for no current path + * + * Sets the location that is being shown in the widgets surrounding the + * @sidebar, for example, in a folder view in a file manager. In turn, the + * @sidebar will highlight that location if it is being shown in the list of + * places, or it will unhighlight everything if the @location is not among the + * places in the list. + */ +void +nautilus_gtk_places_sidebar_set_location (NautilusGtkPlacesSidebar *sidebar, + GFile *location) +{ + GtkWidget *row; + char *row_uri; + char *uri; + gboolean found = FALSE; + + g_return_if_fail (NAUTILUS_IS_GTK_PLACES_SIDEBAR (sidebar)); + + gtk_list_box_unselect_all (GTK_LIST_BOX (sidebar->list_box)); + + if (sidebar->current_location != NULL) + g_object_unref (sidebar->current_location); + sidebar->current_location = location; + if (sidebar->current_location != NULL) + g_object_ref (sidebar->current_location); + + if (location == NULL) + goto out; + + uri = g_file_get_uri (location); + + for (row = gtk_widget_get_first_child (GTK_WIDGET (sidebar->list_box)); + row != NULL && !found; + row = gtk_widget_get_next_sibling (row)) + { + if (!GTK_IS_LIST_BOX_ROW (row)) + continue; + + g_object_get (row, "uri", &row_uri, NULL); + if (row_uri != NULL && g_strcmp0 (row_uri, uri) == 0) + { + gtk_list_box_select_row (GTK_LIST_BOX (sidebar->list_box), + GTK_LIST_BOX_ROW (row)); + found = TRUE; + } + + g_free (row_uri); + } + + g_free (uri); + + out: + g_object_notify_by_pspec (G_OBJECT (sidebar), properties[PROP_LOCATION]); +} + +/* + * nautilus_gtk_places_sidebar_get_location: + * @sidebar: a places sidebar + * + * Gets the currently selected location in the @sidebar. This can be %NULL when + * nothing is selected, for example, when nautilus_gtk_places_sidebar_set_location() has + * been called with a location that is not among the sidebar’s list of places to + * show. + * + * You can use this function to get the selection in the @sidebar. + * + * Returns: (nullable) (transfer full): a GFile with the selected location, or + * %NULL if nothing is visually selected. + */ +GFile * +nautilus_gtk_places_sidebar_get_location (NautilusGtkPlacesSidebar *sidebar) +{ + GtkListBoxRow *selected; + GFile *file; + + g_return_val_if_fail (sidebar != NULL, NULL); + + file = NULL; + selected = gtk_list_box_get_selected_row (GTK_LIST_BOX (sidebar->list_box)); + + if (selected) + { + char *uri; + + g_object_get (selected, "uri", &uri, NULL); + file = g_file_new_for_uri (uri); + g_free (uri); + } + + return file; +} + +char * +nautilus_gtk_places_sidebar_get_location_title (NautilusGtkPlacesSidebar *sidebar) +{ + GtkListBoxRow *selected; + char *title; + + g_return_val_if_fail (sidebar != NULL, NULL); + + title = NULL; + selected = gtk_list_box_get_selected_row (GTK_LIST_BOX (sidebar->list_box)); + + if (selected) + g_object_get (selected, "label", &title, NULL); + + return title; +} + +/* + * nautilus_gtk_places_sidebar_set_show_recent: + * @sidebar: a places sidebar + * @show_recent: whether to show an item for recent files + * + * Sets whether the @sidebar should show an item for recent files. + * The default value for this option is determined by the desktop + * environment, but this function can be used to override it on a + * per-application basis. + */ +void +nautilus_gtk_places_sidebar_set_show_recent (NautilusGtkPlacesSidebar *sidebar, + gboolean show_recent) +{ + g_return_if_fail (NAUTILUS_IS_GTK_PLACES_SIDEBAR (sidebar)); + + sidebar->show_recent_set = TRUE; + + show_recent = !!show_recent; + if (sidebar->show_recent != show_recent) + { + sidebar->show_recent = show_recent; + update_places (sidebar); + g_object_notify_by_pspec (G_OBJECT (sidebar), properties[PROP_SHOW_RECENT]); + } +} + +/* + * nautilus_gtk_places_sidebar_get_show_recent: + * @sidebar: a places sidebar + * + * Returns the value previously set with nautilus_gtk_places_sidebar_set_show_recent() + * + * Returns: %TRUE if the sidebar will display a builtin shortcut for recent files + */ +gboolean +nautilus_gtk_places_sidebar_get_show_recent (NautilusGtkPlacesSidebar *sidebar) +{ + g_return_val_if_fail (NAUTILUS_IS_GTK_PLACES_SIDEBAR (sidebar), FALSE); + + return sidebar->show_recent; +} + +/* + * nautilus_gtk_places_sidebar_set_show_desktop: + * @sidebar: a places sidebar + * @show_desktop: whether to show an item for the Desktop folder + * + * Sets whether the @sidebar should show an item for the Desktop folder. + * The default value for this option is determined by the desktop + * environment and the user’s configuration, but this function can be + * used to override it on a per-application basis. + */ +void +nautilus_gtk_places_sidebar_set_show_desktop (NautilusGtkPlacesSidebar *sidebar, + gboolean show_desktop) +{ + g_return_if_fail (NAUTILUS_IS_GTK_PLACES_SIDEBAR (sidebar)); + + /* Don't bother disconnecting from the GtkSettings -- it will just + * complicate things. Besides, it's highly unlikely that this will + * change while we're running, but we can ignore it if it does. + */ + sidebar->show_desktop_set = TRUE; + + show_desktop = !!show_desktop; + if (sidebar->show_desktop != show_desktop) + { + sidebar->show_desktop = show_desktop; + update_places (sidebar); + g_object_notify_by_pspec (G_OBJECT (sidebar), properties[PROP_SHOW_DESKTOP]); + } +} + +/* + * nautilus_gtk_places_sidebar_get_show_desktop: + * @sidebar: a places sidebar + * + * Returns the value previously set with nautilus_gtk_places_sidebar_set_show_desktop() + * + * Returns: %TRUE if the sidebar will display a builtin shortcut to the desktop folder. + */ +gboolean +nautilus_gtk_places_sidebar_get_show_desktop (NautilusGtkPlacesSidebar *sidebar) +{ + g_return_val_if_fail (NAUTILUS_IS_GTK_PLACES_SIDEBAR (sidebar), FALSE); + + return sidebar->show_desktop; +} + +/* + * nautilus_gtk_places_sidebar_set_show_enter_location: + * @sidebar: a places sidebar + * @show_enter_location: whether to show an item to enter a location + * + * Sets whether the @sidebar should show an item for entering a location; + * this is off by default. An application may want to turn this on if manually + * entering URLs is an expected user action. + * + * If you enable this, you should connect to the + * NautilusGtkPlacesSidebar::show-enter-location signal. + */ +void +nautilus_gtk_places_sidebar_set_show_enter_location (NautilusGtkPlacesSidebar *sidebar, + gboolean show_enter_location) +{ + g_return_if_fail (NAUTILUS_IS_GTK_PLACES_SIDEBAR (sidebar)); + + show_enter_location = !!show_enter_location; + if (sidebar->show_enter_location != show_enter_location) + { + sidebar->show_enter_location = show_enter_location; + update_places (sidebar); + g_object_notify_by_pspec (G_OBJECT (sidebar), properties[PROP_SHOW_ENTER_LOCATION]); + } +} + +/* + * nautilus_gtk_places_sidebar_get_show_enter_location: + * @sidebar: a places sidebar + * + * Returns the value previously set with nautilus_gtk_places_sidebar_set_show_enter_location() + * + * Returns: %TRUE if the sidebar will display an “Enter Location” item. + */ +gboolean +nautilus_gtk_places_sidebar_get_show_enter_location (NautilusGtkPlacesSidebar *sidebar) +{ + g_return_val_if_fail (NAUTILUS_IS_GTK_PLACES_SIDEBAR (sidebar), FALSE); + + return sidebar->show_enter_location; +} + +/* + * nautilus_gtk_places_sidebar_set_show_other_locations: + * @sidebar: a places sidebar + * @show_other_locations: whether to show an item for the Other Locations view + * + * Sets whether the @sidebar should show an item for the application to show + * an Other Locations view; this is off by default. When set to %TRUE, persistent + * devices such as hard drives are hidden, otherwise they are shown in the sidebar. + * An application may want to turn this on if it implements a way for the user to + * see and interact with drives and network servers directly. + * + * If you enable this, you should connect to the + * NautilusGtkPlacesSidebar::show-other-locations-with-flags signal. + */ +void +nautilus_gtk_places_sidebar_set_show_other_locations (NautilusGtkPlacesSidebar *sidebar, + gboolean show_other_locations) +{ + g_return_if_fail (NAUTILUS_IS_GTK_PLACES_SIDEBAR (sidebar)); + + show_other_locations = !!show_other_locations; + if (sidebar->show_other_locations != show_other_locations) + { + sidebar->show_other_locations = show_other_locations; + update_places (sidebar); + g_object_notify_by_pspec (G_OBJECT (sidebar), properties[PROP_SHOW_OTHER_LOCATIONS]); + } + } + +/* + * nautilus_gtk_places_sidebar_get_show_other_locations: + * @sidebar: a places sidebar + * + * Returns the value previously set with nautilus_gtk_places_sidebar_set_show_other_locations() + * + * Returns: %TRUE if the sidebar will display an “Other Locations” item. + */ +gboolean +nautilus_gtk_places_sidebar_get_show_other_locations (NautilusGtkPlacesSidebar *sidebar) +{ + g_return_val_if_fail (NAUTILUS_IS_GTK_PLACES_SIDEBAR (sidebar), FALSE); + + return sidebar->show_other_locations; +} + +/* + * nautilus_gtk_places_sidebar_set_show_trash: + * @sidebar: a places sidebar + * @show_trash: whether to show an item for the Trash location + * + * Sets whether the @sidebar should show an item for the Trash location. + */ +void +nautilus_gtk_places_sidebar_set_show_trash (NautilusGtkPlacesSidebar *sidebar, + gboolean show_trash) +{ + g_return_if_fail (NAUTILUS_IS_GTK_PLACES_SIDEBAR (sidebar)); + + show_trash = !!show_trash; + if (sidebar->show_trash != show_trash) + { + sidebar->show_trash = show_trash; + update_places (sidebar); + g_object_notify_by_pspec (G_OBJECT (sidebar), properties[PROP_SHOW_TRASH]); + } +} + +/* + * nautilus_gtk_places_sidebar_get_show_trash: + * @sidebar: a places sidebar + * + * Returns the value previously set with nautilus_gtk_places_sidebar_set_show_trash() + * + * Returns: %TRUE if the sidebar will display a “Trash” item. + */ +gboolean +nautilus_gtk_places_sidebar_get_show_trash (NautilusGtkPlacesSidebar *sidebar) +{ + g_return_val_if_fail (NAUTILUS_IS_GTK_PLACES_SIDEBAR (sidebar), TRUE); + + return sidebar->show_trash; +} + +/* + * nautilus_gtk_places_sidebar_add_shortcut: + * @sidebar: a places sidebar + * @location: location to add as an application-specific shortcut + * + * Applications may want to present some folders in the places sidebar if + * they could be immediately useful to users. For example, a drawing + * program could add a “/usr/share/clipart” location when the sidebar is + * being used in an “Insert Clipart” dialog box. + * + * This function adds the specified @location to a special place for immutable + * shortcuts. The shortcuts are application-specific; they are not shared + * across applications, and they are not persistent. If this function + * is called multiple times with different locations, then they are added + * to the sidebar’s list in the same order as the function is called. + */ +void +nautilus_gtk_places_sidebar_add_shortcut (NautilusGtkPlacesSidebar *sidebar, + GFile *location) +{ + g_return_if_fail (NAUTILUS_IS_GTK_PLACES_SIDEBAR (sidebar)); + g_return_if_fail (G_IS_FILE (location)); + + g_list_store_append (sidebar->shortcuts, location); + + update_places (sidebar); +} + +/* + * nautilus_gtk_places_sidebar_remove_shortcut: + * @sidebar: a places sidebar + * @location: location to remove + * + * Removes an application-specific shortcut that has been previously been + * inserted with nautilus_gtk_places_sidebar_add_shortcut(). If the @location is not a + * shortcut in the sidebar, then nothing is done. + */ +void +nautilus_gtk_places_sidebar_remove_shortcut (NautilusGtkPlacesSidebar *sidebar, + GFile *location) +{ + guint i, n; + + g_return_if_fail (NAUTILUS_IS_GTK_PLACES_SIDEBAR (sidebar)); + g_return_if_fail (G_IS_FILE (location)); + + n = g_list_model_get_n_items (G_LIST_MODEL (sidebar->shortcuts)); + for (i = 0; i < n; i++) + { + GFile *shortcut = g_list_model_get_item (G_LIST_MODEL (sidebar->shortcuts), i); + + if (shortcut == location) + { + g_list_store_remove (sidebar->shortcuts, i); + g_object_unref (shortcut); + update_places (sidebar); + return; + } + + g_object_unref (shortcut); + } +} + +/* + * nautilus_gtk_places_sidebar_list_shortcuts: + * @sidebar: a places sidebar + * + * Gets the list of shortcuts, as a list model containing GFile objects. + * + * You should not modify the returned list model. Future changes to + * @sidebar may or may not affect the returned model. + * + * Returns: (transfer full): a list model of GFiles that have been added as + * application-specific shortcuts with nautilus_gtk_places_sidebar_add_shortcut() + */ +GListModel * +nautilus_gtk_places_sidebar_get_shortcuts (NautilusGtkPlacesSidebar *sidebar) +{ + g_return_val_if_fail (NAUTILUS_IS_GTK_PLACES_SIDEBAR (sidebar), NULL); + + return G_LIST_MODEL (g_object_ref (sidebar->shortcuts)); +} + +/* + * nautilus_gtk_places_sidebar_get_nth_bookmark: + * @sidebar: a places sidebar + * @n: index of the bookmark to query + * + * This function queries the bookmarks added by the user to the places sidebar, + * and returns one of them. This function is used by GtkFileChooser to implement + * the “Alt-1”, “Alt-2”, etc. shortcuts, which activate the corresponding bookmark. + * + * Returns: (nullable) (transfer full): The bookmark specified by the index @n, or + * %NULL if no such index exist. Note that the indices start at 0, even though + * the file chooser starts them with the keyboard shortcut "Alt-1". + */ +GFile * +nautilus_gtk_places_sidebar_get_nth_bookmark (NautilusGtkPlacesSidebar *sidebar, + int n) +{ + GtkWidget *row; + int k; + GFile *file; + + g_return_val_if_fail (NAUTILUS_IS_GTK_PLACES_SIDEBAR (sidebar), NULL); + + file = NULL; + k = 0; + for (row = gtk_widget_get_first_child (GTK_WIDGET (sidebar->list_box)); + row != NULL; + row = gtk_widget_get_next_sibling (row)) + { + NautilusGtkPlacesPlaceType place_type; + char *uri; + + if (!GTK_IS_LIST_BOX_ROW (row)) + continue; + + g_object_get (row, + "place-type", &place_type, + "uri", &uri, + NULL); + if (place_type == NAUTILUS_GTK_PLACES_BOOKMARK) + { + if (k == n) + { + file = g_file_new_for_uri (uri); + g_free (uri); + break; + } + k++; + } + g_free (uri); + } + + return file; +} + +/* + * nautilus_gtk_places_sidebar_set_drop_targets_visible: + * @sidebar: a places sidebar. + * @visible: whether to show the valid targets or not. + * + * Make the NautilusGtkPlacesSidebar show drop targets, so it can show the available + * drop targets and a "new bookmark" row. This improves the Drag-and-Drop + * experience of the user and allows applications to show all available + * drop targets at once. + * + * This needs to be called when the application is aware of an ongoing drag + * that might target the sidebar. The drop-targets-visible state will be unset + * automatically if the drag finishes in the NautilusGtkPlacesSidebar. You only need + * to unset the state when the drag ends on some other widget on your application. + */ +void +nautilus_gtk_places_sidebar_set_drop_targets_visible (NautilusGtkPlacesSidebar *sidebar, + gboolean visible) +{ + g_return_if_fail (NAUTILUS_IS_GTK_PLACES_SIDEBAR (sidebar)); + + if (visible) + { + sidebar->drop_state = DROP_STATE_NEW_BOOKMARK_ARMED_PERMANENT; + start_drop_feedback (sidebar, NULL); + } + else + { + if (sidebar->drop_state == DROP_STATE_NEW_BOOKMARK_ARMED_PERMANENT || + sidebar->drop_state == DROP_STATE_NEW_BOOKMARK_ARMED) + { + if (!sidebar->dragging_over) + { + sidebar->drop_state = DROP_STATE_NORMAL; + stop_drop_feedback (sidebar); + } + else + { + /* In case this is called while we are dragging we need to mark the + * drop state as no permanent so the leave timeout can do its job. + * This will only happen in applications that call this in a wrong + * time */ + sidebar->drop_state = DROP_STATE_NEW_BOOKMARK_ARMED; + } + } + } +} + +/* + * nautilus_gtk_places_sidebar_set_show_starred_location: + * @sidebar: a places sidebar + * @show_starred_location: whether to show an item for Starred files + * + * If you enable this, you should connect to the + * NautilusGtkPlacesSidebar::show-starred-location signal. + */ +void +nautilus_gtk_places_sidebar_set_show_starred_location (NautilusGtkPlacesSidebar *sidebar, + gboolean show_starred_location) +{ + g_return_if_fail (NAUTILUS_IS_GTK_PLACES_SIDEBAR (sidebar)); + + show_starred_location = !!show_starred_location; + if (sidebar->show_starred_location != show_starred_location) + { + sidebar->show_starred_location = show_starred_location; + update_places (sidebar); + g_object_notify_by_pspec (G_OBJECT (sidebar), properties[PROP_SHOW_STARRED_LOCATION]); + } +} + +/* + * nautilus_gtk_places_sidebar_get_show_starred_location: + * @sidebar: a places sidebar + * + * Returns the value previously set with nautilus_gtk_places_sidebar_set_show_starred_location() + * + * Returns: %TRUE if the sidebar will display a Starred item. + */ +gboolean +nautilus_gtk_places_sidebar_get_show_starred_location (NautilusGtkPlacesSidebar *sidebar) +{ + g_return_val_if_fail (NAUTILUS_IS_GTK_PLACES_SIDEBAR (sidebar), FALSE); + + return sidebar->show_starred_location; +} diff --git a/src/gtk/nautilusgtkplacessidebarprivate.h b/src/gtk/nautilusgtkplacessidebarprivate.h new file mode 100644 index 0000000..c1503ad --- /dev/null +++ b/src/gtk/nautilusgtkplacessidebarprivate.h @@ -0,0 +1,148 @@ +/* nautilusgtkplacessidebarprivate.h + * + * Copyright (C) 2015 Red Hat + * + * This file is free software; you can redistribute it and/or modify it + * under the terms of the GNU Lesser General Public License as + * published by the Free Software Foundation; either version 2.1 of the + * License, or (at your option) any later version. + * + * This file is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program. If not, see . + * + * Authors: Carlos Soriano + */ + +#ifndef __NAUTILUS_GTK_PLACES_SIDEBAR_PRIVATE_H__ +#define __NAUTILUS_GTK_PLACES_SIDEBAR_PRIVATE_H__ + +#include +#include + +G_BEGIN_DECLS + +#define NAUTILUS_TYPE_GTK_PLACES_SIDEBAR (nautilus_gtk_places_sidebar_get_type ()) +#define NAUTILUS_GTK_PLACES_SIDEBAR(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), NAUTILUS_TYPE_GTK_PLACES_SIDEBAR, NautilusGtkPlacesSidebar)) +#define NAUTILUS_GTK_PLACES_SIDEBAR_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), NAUTILUS_TYPE_GTK_PLACES_SIDEBAR, NautilusGtkPlacesSidebarClass)) +#define NAUTILUS_IS_GTK_PLACES_SIDEBAR(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), NAUTILUS_TYPE_GTK_PLACES_SIDEBAR)) +#define NAUTILUS_IS_GTK_PLACES_SIDEBAR_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), NAUTILUS_TYPE_GTK_PLACES_SIDEBAR)) +#define NAUTILUS_GTK_PLACES_SIDEBAR_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), NAUTILUS_TYPE_GTK_PLACES_SIDEBAR, NautilusGtkPlacesSidebarClass)) + +typedef struct _NautilusGtkPlacesSidebar NautilusGtkPlacesSidebar; +typedef struct _NautilusGtkPlacesSidebarClass NautilusGtkPlacesSidebarClass; + +/* + * NautilusGtkPlacesOpenFlags: + * @NAUTILUS_GTK_PLACES_OPEN_NORMAL: This is the default mode that NautilusGtkPlacesSidebar uses if no other flags + * are specified. It indicates that the calling application should open the selected location + * in the normal way, for example, in the folder view beside the sidebar. + * @NAUTILUS_GTK_PLACES_OPEN_NEW_TAB: When passed to nautilus_gtk_places_sidebar_set_open_flags(), this indicates + * that the application can open folders selected from the sidebar in new tabs. This value + * will be passed to the NautilusGtkPlacesSidebar::open-location signal when the user selects + * that a location be opened in a new tab instead of in the standard fashion. + * @NAUTILUS_GTK_PLACES_OPEN_NEW_WINDOW: Similar to @NAUTILUS_GTK_PLACES_OPEN_NEW_TAB, but indicates that the application + * can open folders in new windows. + * + * These flags serve two purposes. First, the application can call nautilus_gtk_places_sidebar_set_open_flags() + * using these flags as a bitmask. This tells the sidebar that the application is able to open + * folders selected from the sidebar in various ways, for example, in new tabs or in new windows in + * addition to the normal mode. + * + * Second, when one of these values gets passed back to the application in the + * NautilusGtkPlacesSidebar::open-location signal, it means that the application should + * open the selected location in the normal way, in a new tab, or in a new + * window. The sidebar takes care of determining the desired way to open the location, + * based on the modifier keys that the user is pressing at the time the selection is made. + * + * If the application never calls nautilus_gtk_places_sidebar_set_open_flags(), then the sidebar will only + * use NAUTILUS_GTK_PLACES_OPEN_NORMAL in the NautilusGtkPlacesSidebar::open-location signal. This is the + * default mode of operation. + */ +typedef enum { + NAUTILUS_GTK_PLACES_OPEN_NORMAL = 1 << 0, + NAUTILUS_GTK_PLACES_OPEN_NEW_TAB = 1 << 1, + NAUTILUS_GTK_PLACES_OPEN_NEW_WINDOW = 1 << 2 +} NautilusGtkPlacesOpenFlags; + +GType nautilus_gtk_places_sidebar_get_type (void) G_GNUC_CONST; +GtkWidget * nautilus_gtk_places_sidebar_new (void); + +NautilusGtkPlacesOpenFlags nautilus_gtk_places_sidebar_get_open_flags (NautilusGtkPlacesSidebar *sidebar); +void nautilus_gtk_places_sidebar_set_open_flags (NautilusGtkPlacesSidebar *sidebar, + NautilusGtkPlacesOpenFlags flags); + +GFile * nautilus_gtk_places_sidebar_get_location (NautilusGtkPlacesSidebar *sidebar); +void nautilus_gtk_places_sidebar_set_location (NautilusGtkPlacesSidebar *sidebar, + GFile *location); + +gboolean nautilus_gtk_places_sidebar_get_show_recent (NautilusGtkPlacesSidebar *sidebar); +void nautilus_gtk_places_sidebar_set_show_recent (NautilusGtkPlacesSidebar *sidebar, + gboolean show_recent); + +gboolean nautilus_gtk_places_sidebar_get_show_desktop (NautilusGtkPlacesSidebar *sidebar); +void nautilus_gtk_places_sidebar_set_show_desktop (NautilusGtkPlacesSidebar *sidebar, + gboolean show_desktop); + +gboolean nautilus_gtk_places_sidebar_get_show_enter_location (NautilusGtkPlacesSidebar *sidebar); +void nautilus_gtk_places_sidebar_set_show_enter_location (NautilusGtkPlacesSidebar *sidebar, + gboolean show_enter_location); + +void nautilus_gtk_places_sidebar_add_shortcut (NautilusGtkPlacesSidebar *sidebar, + GFile *location); +void nautilus_gtk_places_sidebar_remove_shortcut (NautilusGtkPlacesSidebar *sidebar, + GFile *location); +GListModel * nautilus_gtk_places_sidebar_get_shortcuts (NautilusGtkPlacesSidebar *sidebar); + +GFile * nautilus_gtk_places_sidebar_get_nth_bookmark (NautilusGtkPlacesSidebar *sidebar, + int n); +void nautilus_gtk_places_sidebar_set_drop_targets_visible (NautilusGtkPlacesSidebar *sidebar, + gboolean visible); +gboolean nautilus_gtk_places_sidebar_get_show_trash (NautilusGtkPlacesSidebar *sidebar); +void nautilus_gtk_places_sidebar_set_show_trash (NautilusGtkPlacesSidebar *sidebar, + gboolean show_trash); + +void nautilus_gtk_places_sidebar_set_show_other_locations (NautilusGtkPlacesSidebar *sidebar, + gboolean show_other_locations); +gboolean nautilus_gtk_places_sidebar_get_show_other_locations (NautilusGtkPlacesSidebar *sidebar); + +void nautilus_gtk_places_sidebar_set_show_starred_location (NautilusGtkPlacesSidebar *sidebar, + gboolean show_starred_location); +gboolean nautilus_gtk_places_sidebar_get_show_starred_location (NautilusGtkPlacesSidebar *sidebar); + +/* Keep order, since it's used for the sort functions */ +typedef enum { + NAUTILUS_GTK_PLACES_SECTION_INVALID, + NAUTILUS_GTK_PLACES_SECTION_COMPUTER, + NAUTILUS_GTK_PLACES_SECTION_MOUNTS, + NAUTILUS_GTK_PLACES_SECTION_CLOUD, + NAUTILUS_GTK_PLACES_SECTION_BOOKMARKS, + NAUTILUS_GTK_PLACES_SECTION_OTHER_LOCATIONS, + NAUTILUS_GTK_PLACES_N_SECTIONS +} NautilusGtkPlacesSectionType; + +typedef enum { + NAUTILUS_GTK_PLACES_INVALID, + NAUTILUS_GTK_PLACES_BUILT_IN, + NAUTILUS_GTK_PLACES_XDG_DIR, + NAUTILUS_GTK_PLACES_MOUNTED_VOLUME, + NAUTILUS_GTK_PLACES_BOOKMARK, + NAUTILUS_GTK_PLACES_HEADING, + NAUTILUS_GTK_PLACES_CONNECT_TO_SERVER, + NAUTILUS_GTK_PLACES_ENTER_LOCATION, + NAUTILUS_GTK_PLACES_DROP_FEEDBACK, + NAUTILUS_GTK_PLACES_BOOKMARK_PLACEHOLDER, + NAUTILUS_GTK_PLACES_OTHER_LOCATIONS, + NAUTILUS_GTK_PLACES_STARRED_LOCATION, + NAUTILUS_GTK_PLACES_N_PLACES +} NautilusGtkPlacesPlaceType; + +char *nautilus_gtk_places_sidebar_get_location_title (NautilusGtkPlacesSidebar *sidebar); + +G_END_DECLS + +#endif /* __NAUTILUS_GTK_PLACES_SIDEBAR_PRIVATE_H__ */ diff --git a/src/gtk/nautilusgtkplacesview.c b/src/gtk/nautilusgtkplacesview.c new file mode 100644 index 0000000..0d062c9 --- /dev/null +++ b/src/gtk/nautilusgtkplacesview.c @@ -0,0 +1,2635 @@ +/* nautilusgtkplacesview.c + * + * Copyright (C) 2015 Georges Basile Stavracas Neto + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation, either version 2.1 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 warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program. If not, see . + */ + +#include "config.h" +#include +#include +#include "nautilus-enum-types.h" + +#include +#include +#include + +#include "nautilusgtkplacesviewprivate.h" +#include "nautilusgtkplacesviewrowprivate.h" +#include "nautilus-file.h" +#include "nautilus-file-utilities.h" +#include "nautilus-properties-window.h" + +/*< private > + * NautilusGtkPlacesView: + * + * NautilusGtkPlacesView is a widget that displays a list of persistent drives + * such as harddisk partitions and networks. NautilusGtkPlacesView does not monitor + * removable devices. + * + * The places view displays drives and networks, and will automatically mount + * them when the user activates. Network addresses are stored even if they fail + * to connect. When the connection is successful, the connected network is + * shown at the network list. + * + * To make use of the places view, an application at least needs to connect + * to the NautilusGtkPlacesView::open-location signal. This is emitted when the user + * selects a location to open in the view. + */ + +struct _NautilusGtkPlacesViewClass +{ + GtkBoxClass parent_class; + + void (* open_location) (NautilusGtkPlacesView *view, + GFile *location, + NautilusGtkPlacesOpenFlags open_flags); + + void (* show_error_message) (NautilusGtkPlacesSidebar *sidebar, + const char *primary, + const char *secondary); +}; + +struct _NautilusGtkPlacesView +{ + GtkBox parent_instance; + + GVolumeMonitor *volume_monitor; + NautilusGtkPlacesOpenFlags open_flags; + NautilusGtkPlacesOpenFlags current_open_flags; + + GFile *server_list_file; + GFileMonitor *server_list_monitor; + GFileMonitor *network_monitor; + + GCancellable *cancellable; + + char *search_query; + + GtkWidget *actionbar; + GtkWidget *address_entry; + GtkWidget *connect_button; + GtkWidget *listbox; + GtkWidget *popup_menu; + GtkWidget *recent_servers_listbox; + GtkWidget *recent_servers_popover; + GtkWidget *recent_servers_stack; + GtkWidget *stack; + GtkWidget *server_adresses_popover; + GtkWidget *available_protocols_grid; + GtkWidget *network_placeholder; + GtkWidget *network_placeholder_label; + + GtkSizeGroup *path_size_group; + GtkSizeGroup *space_size_group; + + GtkEntryCompletion *address_entry_completion; + GtkListStore *completion_store; + + GCancellable *networks_fetching_cancellable; + + NautilusGtkPlacesViewRow *row_for_action; + + guint should_open_location : 1; + guint should_pulse_entry : 1; + guint entry_pulse_timeout_id; + guint connecting_to_server : 1; + guint mounting_volume : 1; + guint unmounting_mount : 1; + guint fetching_networks : 1; + guint loading : 1; + guint destroyed : 1; +}; + +static void mount_volume (NautilusGtkPlacesView *view, + GVolume *volume); + +static void on_eject_button_clicked (GtkWidget *widget, + NautilusGtkPlacesViewRow *row); + +static gboolean on_row_popup_menu (GtkWidget *widget, + GVariant *args, + gpointer user_data); + +static void click_cb (GtkGesture *gesture, + int n_press, + double x, + double y, + gpointer user_data); + +static void populate_servers (NautilusGtkPlacesView *view); + +static gboolean nautilus_gtk_places_view_get_fetching_networks (NautilusGtkPlacesView *view); + +static void nautilus_gtk_places_view_set_fetching_networks (NautilusGtkPlacesView *view, + gboolean fetching_networks); + +static void nautilus_gtk_places_view_set_loading (NautilusGtkPlacesView *view, + gboolean loading); + +static void update_loading (NautilusGtkPlacesView *view); + +G_DEFINE_TYPE (NautilusGtkPlacesView, nautilus_gtk_places_view, GTK_TYPE_BOX) + +/* NautilusGtkPlacesView properties & signals */ +enum { + PROP_0, + PROP_OPEN_FLAGS, + PROP_FETCHING_NETWORKS, + PROP_LOADING, + LAST_PROP +}; + +enum { + OPEN_LOCATION, + SHOW_ERROR_MESSAGE, + LAST_SIGNAL +}; + +const char *unsupported_protocols [] = +{ + "file", "afc", "obex", "http", + "trash", "burn", "computer", + "archive", "recent", "localtest", + NULL +}; + +static guint places_view_signals [LAST_SIGNAL] = { 0 }; +static GParamSpec *properties [LAST_PROP]; + +static void +emit_open_location (NautilusGtkPlacesView *view, + GFile *location, + NautilusGtkPlacesOpenFlags open_flags) +{ + if ((open_flags & view->open_flags) == 0) + open_flags = NAUTILUS_GTK_PLACES_OPEN_NORMAL; + + g_signal_emit (view, places_view_signals[OPEN_LOCATION], 0, location, open_flags); +} + +static void +emit_show_error_message (NautilusGtkPlacesView *view, + char *primary_message, + char *secondary_message) +{ + g_signal_emit (view, places_view_signals[SHOW_ERROR_MESSAGE], + 0, primary_message, secondary_message); +} + +static void +server_file_changed_cb (NautilusGtkPlacesView *view) +{ + populate_servers (view); +} + +static GBookmarkFile * +server_list_load (NautilusGtkPlacesView *view) +{ + GBookmarkFile *bookmarks; + GError *error = NULL; + char *datadir; + char *filename; + + bookmarks = g_bookmark_file_new (); + datadir = g_build_filename (g_get_user_config_dir (), "gtk-4.0", NULL); + filename = g_build_filename (datadir, "servers", NULL); + + g_mkdir_with_parents (datadir, 0700); + g_bookmark_file_load_from_file (bookmarks, filename, &error); + + if (error) + { + if (!g_error_matches (error, G_FILE_ERROR, G_FILE_ERROR_NOENT)) + { + /* only warn if the file exists */ + g_warning ("Unable to open server bookmarks: %s", error->message); + g_clear_pointer (&bookmarks, g_bookmark_file_free); + } + + g_clear_error (&error); + } + + /* Monitor the file in case it's modified outside this code */ + if (!view->server_list_monitor) + { + view->server_list_file = g_file_new_for_path (filename); + + if (view->server_list_file) + { + view->server_list_monitor = g_file_monitor_file (view->server_list_file, + G_FILE_MONITOR_NONE, + NULL, + &error); + + if (error) + { + g_warning ("Cannot monitor server file: %s", error->message); + g_clear_error (&error); + } + else + { + g_signal_connect_swapped (view->server_list_monitor, + "changed", + G_CALLBACK (server_file_changed_cb), + view); + } + } + + g_clear_object (&view->server_list_file); + } + + g_free (datadir); + g_free (filename); + + return bookmarks; +} + +static void +server_list_save (GBookmarkFile *bookmarks) +{ + char *filename; + + filename = g_build_filename (g_get_user_config_dir (), "gtk-4.0", "servers", NULL); + g_bookmark_file_to_file (bookmarks, filename, NULL); + g_free (filename); +} + +static void +server_list_add_server (NautilusGtkPlacesView *view, + GFile *file) +{ + GBookmarkFile *bookmarks; + GFileInfo *info; + GError *error; + char *title; + char *uri; + GDateTime *now; + + error = NULL; + bookmarks = server_list_load (view); + + if (!bookmarks) + return; + + uri = g_file_get_uri (file); + + info = g_file_query_info (file, + G_FILE_ATTRIBUTE_STANDARD_DISPLAY_NAME, + G_FILE_QUERY_INFO_NONE, + NULL, + &error); + title = g_file_info_get_attribute_as_string (info, G_FILE_ATTRIBUTE_STANDARD_DISPLAY_NAME); + + g_bookmark_file_set_title (bookmarks, uri, title); + now = g_date_time_new_now_utc (); + g_bookmark_file_set_visited_date_time (bookmarks, uri, now); + g_date_time_unref (now); + g_bookmark_file_add_application (bookmarks, uri, NULL, NULL); + + server_list_save (bookmarks); + + g_bookmark_file_free (bookmarks); + g_clear_object (&info); + g_free (title); + g_free (uri); +} + +static void +server_list_remove_server (NautilusGtkPlacesView *view, + const char *uri) +{ + GBookmarkFile *bookmarks; + + bookmarks = server_list_load (view); + + if (!bookmarks) + return; + + g_bookmark_file_remove_item (bookmarks, uri, NULL); + server_list_save (bookmarks); + + g_bookmark_file_free (bookmarks); +} + +/* Returns a toplevel GtkWindow, or NULL if none */ +static GtkWindow * +get_toplevel (GtkWidget *widget) +{ + GtkWidget *toplevel; + + toplevel = GTK_WIDGET (gtk_widget_get_root (widget)); + if (GTK_IS_WINDOW (toplevel)) + return GTK_WINDOW (toplevel); + else + return NULL; +} + +static void +set_busy_cursor (NautilusGtkPlacesView *view, + gboolean busy) +{ + GtkWidget *widget; + GtkWindow *toplevel; + + toplevel = get_toplevel (GTK_WIDGET (view)); + widget = GTK_WIDGET (toplevel); + if (!toplevel || !gtk_widget_get_realized (widget)) + return; + + if (busy) + gtk_widget_set_cursor_from_name (widget, "progress"); + else + gtk_widget_set_cursor (widget, NULL); +} + +/* Activates the given row, with the given flags as parameter */ +static void +activate_row (NautilusGtkPlacesView *view, + NautilusGtkPlacesViewRow *row, + NautilusGtkPlacesOpenFlags flags) +{ + GVolume *volume; + GMount *mount; + GFile *file; + + mount = nautilus_gtk_places_view_row_get_mount (row); + volume = nautilus_gtk_places_view_row_get_volume (row); + file = nautilus_gtk_places_view_row_get_file (row); + + if (file) + { + emit_open_location (view, file, flags); + } + else if (mount) + { + GFile *location = g_mount_get_default_location (mount); + + emit_open_location (view, location, flags); + + g_object_unref (location); + } + else if (volume && g_volume_can_mount (volume)) + { + /* + * When the row is activated, the unmounted volume shall + * be mounted and opened right after. + */ + view->should_open_location = TRUE; + + nautilus_gtk_places_view_row_set_busy (row, TRUE); + mount_volume (view, volume); + } +} + +static void update_places (NautilusGtkPlacesView *view); + +static void +nautilus_gtk_places_view_finalize (GObject *object) +{ + NautilusGtkPlacesView *view = (NautilusGtkPlacesView *)object; + + if (view->entry_pulse_timeout_id > 0) + g_source_remove (view->entry_pulse_timeout_id); + + g_clear_pointer (&view->search_query, g_free); + g_clear_object (&view->server_list_file); + g_clear_object (&view->server_list_monitor); + g_clear_object (&view->volume_monitor); + g_clear_object (&view->network_monitor); + g_clear_object (&view->cancellable); + g_clear_object (&view->networks_fetching_cancellable); + g_clear_object (&view->path_size_group); + g_clear_object (&view->space_size_group); + + G_OBJECT_CLASS (nautilus_gtk_places_view_parent_class)->finalize (object); +} + +static void +nautilus_gtk_places_view_dispose (GObject *object) +{ + NautilusGtkPlacesView *view = (NautilusGtkPlacesView *)object; + + view->destroyed = 1; + + g_signal_handlers_disconnect_by_func (view->volume_monitor, update_places, object); + + if (view->network_monitor) + g_signal_handlers_disconnect_by_func (view->network_monitor, update_places, object); + + if (view->server_list_monitor) + g_signal_handlers_disconnect_by_func (view->server_list_monitor, server_file_changed_cb, object); + + g_cancellable_cancel (view->cancellable); + g_cancellable_cancel (view->networks_fetching_cancellable); + g_clear_pointer (&view->popup_menu, gtk_widget_unparent); + + G_OBJECT_CLASS (nautilus_gtk_places_view_parent_class)->dispose (object); +} + +static void +nautilus_gtk_places_view_get_property (GObject *object, + guint prop_id, + GValue *value, + GParamSpec *pspec) +{ + NautilusGtkPlacesView *self = NAUTILUS_GTK_PLACES_VIEW (object); + + switch (prop_id) + { + case PROP_LOADING: + g_value_set_boolean (value, nautilus_gtk_places_view_get_loading (self)); + break; + + case PROP_OPEN_FLAGS: + g_value_set_flags (value, nautilus_gtk_places_view_get_open_flags (self)); + break; + + case PROP_FETCHING_NETWORKS: + g_value_set_boolean (value, nautilus_gtk_places_view_get_fetching_networks (self)); + break; + + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); + } +} + +static void +nautilus_gtk_places_view_set_property (GObject *object, + guint prop_id, + const GValue *value, + GParamSpec *pspec) +{ + NautilusGtkPlacesView *self = NAUTILUS_GTK_PLACES_VIEW (object); + + switch (prop_id) + { + case PROP_OPEN_FLAGS: + nautilus_gtk_places_view_set_open_flags (self, g_value_get_flags (value)); + break; + + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); + } +} + +static gboolean +is_external_volume (GVolume *volume) +{ + gboolean is_external; + GDrive *drive; + char *id; + + drive = g_volume_get_drive (volume); + id = g_volume_get_identifier (volume, G_VOLUME_IDENTIFIER_KIND_CLASS); + + is_external = g_volume_can_eject (volume); + + /* NULL volume identifier only happens on removable devices */ + is_external |= !id; + + if (drive) + is_external |= g_drive_is_removable (drive); + + g_clear_object (&drive); + g_free (id); + + return is_external; +} + +typedef struct +{ + char *uri; + NautilusGtkPlacesView *view; +} RemoveServerData; + +static void +on_remove_server_button_clicked (RemoveServerData *data) +{ + server_list_remove_server (data->view, data->uri); + + populate_servers (data->view); +} + +static void +populate_servers (NautilusGtkPlacesView *view) +{ + GBookmarkFile *server_list; + GtkWidget *child; + char **uris; + gsize num_uris; + int i; + + server_list = server_list_load (view); + + if (!server_list) + return; + + uris = g_bookmark_file_get_uris (server_list, &num_uris); + + gtk_stack_set_visible_child_name (GTK_STACK (view->recent_servers_stack), + num_uris > 0 ? "list" : "empty"); + + if (!uris) + { + g_bookmark_file_free (server_list); + return; + } + + /* clear previous items */ + while ((child = gtk_widget_get_first_child (GTK_WIDGET (view->recent_servers_listbox)))) + gtk_list_box_remove (GTK_LIST_BOX (view->recent_servers_listbox), child); + + gtk_list_store_clear (view->completion_store); + + for (i = 0; i < num_uris; i++) + { + RemoveServerData *data; + GtkTreeIter iter; + GtkWidget *row; + GtkWidget *grid; + GtkWidget *button; + GtkWidget *label; + char *name; + char *dup_uri; + + name = g_bookmark_file_get_title (server_list, uris[i], NULL); + dup_uri = g_strdup (uris[i]); + + /* add to the completion list */ + gtk_list_store_append (view->completion_store, &iter); + gtk_list_store_set (view->completion_store, + &iter, + 0, name, + 1, uris[i], + -1); + + /* add to the recent servers listbox */ + row = gtk_list_box_row_new (); + + grid = g_object_new (GTK_TYPE_GRID, + "orientation", GTK_ORIENTATION_VERTICAL, + NULL); + + /* name of the connected uri, if any */ + label = gtk_label_new (name); + gtk_widget_set_hexpand (label, TRUE); + gtk_label_set_xalign (GTK_LABEL (label), 0.0); + gtk_label_set_ellipsize (GTK_LABEL (label), PANGO_ELLIPSIZE_END); + gtk_grid_attach (GTK_GRID (grid), label, 0, 0, 1, 1); + + /* the uri itself */ + label = gtk_label_new (uris[i]); + gtk_widget_set_hexpand (label, TRUE); + gtk_label_set_xalign (GTK_LABEL (label), 0.0); + gtk_label_set_ellipsize (GTK_LABEL (label), PANGO_ELLIPSIZE_END); + gtk_widget_add_css_class (label, "dim-label"); + gtk_grid_attach (GTK_GRID (grid), label, 0, 1, 1, 1); + + /* remove button */ + button = gtk_button_new_from_icon_name ("window-close-symbolic"); + gtk_widget_set_halign (button, GTK_ALIGN_END); + gtk_widget_set_valign (button, GTK_ALIGN_CENTER); + gtk_button_set_has_frame (GTK_BUTTON (button), FALSE); + gtk_widget_add_css_class (button, "sidebar-button"); + gtk_grid_attach (GTK_GRID (grid), button, 1, 0, 1, 2); + + gtk_list_box_row_set_child (GTK_LIST_BOX_ROW (row), grid); + gtk_list_box_insert (GTK_LIST_BOX (view->recent_servers_listbox), row, -1); + + /* custom data */ + data = g_new0 (RemoveServerData, 1); + data->view = view; + data->uri = dup_uri; + + g_object_set_data_full (G_OBJECT (row), "uri", dup_uri, g_free); + g_object_set_data_full (G_OBJECT (row), "remove-server-data", data, g_free); + + g_signal_connect_swapped (button, + "clicked", + G_CALLBACK (on_remove_server_button_clicked), + data); + + g_free (name); + } + + g_strfreev (uris); + g_bookmark_file_free (server_list); +} + +static void +update_view_mode (NautilusGtkPlacesView *view) +{ + GtkWidget *child; + gboolean show_listbox; + + show_listbox = FALSE; + + /* drives */ + for (child = gtk_widget_get_first_child (GTK_WIDGET (view->listbox)); + child != NULL; + child = gtk_widget_get_next_sibling (child)) + { + /* GtkListBox filter rows by changing their GtkWidget::child-visible property */ + if (gtk_widget_get_child_visible (child)) + { + show_listbox = TRUE; + break; + } + } + + if (!show_listbox && + view->search_query && + view->search_query[0] != '\0') + { + gtk_stack_set_visible_child_name (GTK_STACK (view->stack), "empty-search"); + } + else + { + gtk_stack_set_visible_child_name (GTK_STACK (view->stack), "browse"); + } +} + +static void +insert_row (NautilusGtkPlacesView *view, + GtkWidget *row, + gboolean is_network) +{ + GtkEventController *controller; + GtkShortcutTrigger *trigger; + GtkShortcutAction *action; + GtkShortcut *shortcut; + GtkGesture *gesture; + + g_object_set_data (G_OBJECT (row), "is-network", GINT_TO_POINTER (is_network)); + + controller = gtk_shortcut_controller_new (); + trigger = gtk_alternative_trigger_new (gtk_keyval_trigger_new (GDK_KEY_F10, GDK_SHIFT_MASK), + gtk_keyval_trigger_new (GDK_KEY_Menu, 0)); + action = gtk_callback_action_new (on_row_popup_menu, row, NULL); + shortcut = gtk_shortcut_new (trigger, action); + gtk_shortcut_controller_add_shortcut (GTK_SHORTCUT_CONTROLLER (controller), shortcut); + gtk_widget_add_controller (GTK_WIDGET (row), controller); + + gesture = gtk_gesture_click_new (); + gtk_gesture_single_set_button (GTK_GESTURE_SINGLE (gesture), GDK_BUTTON_SECONDARY); + g_signal_connect (gesture, "pressed", G_CALLBACK (click_cb), row); + gtk_widget_add_controller (row, GTK_EVENT_CONTROLLER (gesture)); + + g_signal_connect (nautilus_gtk_places_view_row_get_eject_button (NAUTILUS_GTK_PLACES_VIEW_ROW (row)), + "clicked", + G_CALLBACK (on_eject_button_clicked), + row); + + nautilus_gtk_places_view_row_set_path_size_group (NAUTILUS_GTK_PLACES_VIEW_ROW (row), view->path_size_group); + nautilus_gtk_places_view_row_set_space_size_group (NAUTILUS_GTK_PLACES_VIEW_ROW (row), view->space_size_group); + + gtk_list_box_insert (GTK_LIST_BOX (view->listbox), row, -1); +} + +static void +add_volume (NautilusGtkPlacesView *view, + GVolume *volume) +{ + gboolean is_network; + GMount *mount; + GFile *root; + GIcon *icon; + char *identifier; + char *name; + char *path; + + if (is_external_volume (volume)) + return; + + identifier = g_volume_get_identifier (volume, G_VOLUME_IDENTIFIER_KIND_CLASS); + is_network = g_strcmp0 (identifier, "network") == 0; + + mount = g_volume_get_mount (volume); + root = mount ? g_mount_get_default_location (mount) : NULL; + icon = g_volume_get_icon (volume); + name = g_volume_get_name (volume); + path = !is_network ? g_volume_get_identifier (volume, G_VOLUME_IDENTIFIER_KIND_UNIX_DEVICE) : NULL; + + if (!mount || !g_mount_is_shadowed (mount)) + { + GtkWidget *row; + + row = g_object_new (NAUTILUS_TYPE_GTK_PLACES_VIEW_ROW, + "icon", icon, + "name", name, + "path", path ? path : "", + "volume", volume, + "mount", mount, + "file", NULL, + "is-network", is_network, + NULL); + + insert_row (view, row, is_network); + } + + g_clear_object (&root); + g_clear_object (&icon); + g_clear_object (&mount); + g_free (identifier); + g_free (name); + g_free (path); +} + +static void +add_mount (NautilusGtkPlacesView *view, + GMount *mount) +{ + gboolean is_network; + GFile *root; + GIcon *icon; + char *name; + char *path; + char *uri; + char *schema; + + icon = g_mount_get_icon (mount); + name = g_mount_get_name (mount); + root = g_mount_get_default_location (mount); + path = root ? g_file_get_parse_name (root) : NULL; + uri = g_file_get_uri (root); + schema = g_uri_parse_scheme (uri); + is_network = g_strcmp0 (schema, "file") != 0; + + if (is_network) + g_clear_pointer (&path, g_free); + + if (!g_mount_is_shadowed (mount)) + { + GtkWidget *row; + + row = g_object_new (NAUTILUS_TYPE_GTK_PLACES_VIEW_ROW, + "icon", icon, + "name", name, + "path", path ? path : "", + "volume", NULL, + "mount", mount, + "file", NULL, + "is-network", is_network, + NULL); + + insert_row (view, row, is_network); + } + + g_clear_object (&root); + g_clear_object (&icon); + g_free (name); + g_free (path); + g_free (uri); + g_free (schema); +} + +static void +add_drive (NautilusGtkPlacesView *view, + GDrive *drive) +{ + GList *volumes; + GList *l; + + volumes = g_drive_get_volumes (drive); + + for (l = volumes; l != NULL; l = l->next) + add_volume (view, l->data); + + g_list_free_full (volumes, g_object_unref); +} + +static void +add_file (NautilusGtkPlacesView *view, + GFile *file, + GIcon *icon, + const char *display_name, + const char *path, + gboolean is_network) +{ + GtkWidget *row; + row = g_object_new (NAUTILUS_TYPE_GTK_PLACES_VIEW_ROW, + "icon", icon, + "name", display_name, + "path", path, + "volume", NULL, + "mount", NULL, + "file", file, + "is_network", is_network, + NULL); + + insert_row (view, row, is_network); +} + +static gboolean +has_networks (NautilusGtkPlacesView *view) +{ + GtkWidget *child; + gboolean has_network = FALSE; + + for (child = gtk_widget_get_first_child (GTK_WIDGET (view->listbox)); + child != NULL; + child = gtk_widget_get_next_sibling (child)) + { + if (GPOINTER_TO_INT (g_object_get_data (G_OBJECT (child), "is-network")) && + g_object_get_data (G_OBJECT (child), "is-placeholder") == NULL) + { + has_network = TRUE; + break; + } + } + + return has_network; +} + +static void +update_network_state (NautilusGtkPlacesView *view) +{ + if (view->network_placeholder == NULL) + { + view->network_placeholder = gtk_list_box_row_new (); + view->network_placeholder_label = gtk_label_new (""); + gtk_label_set_xalign (GTK_LABEL (view->network_placeholder_label), 0.0); + gtk_widget_set_margin_start (view->network_placeholder_label, 12); + gtk_widget_set_margin_end (view->network_placeholder_label, 12); + gtk_widget_set_margin_top (view->network_placeholder_label, 6); + gtk_widget_set_margin_bottom (view->network_placeholder_label, 6); + gtk_widget_set_hexpand (view->network_placeholder_label, TRUE); + gtk_widget_set_sensitive (view->network_placeholder, FALSE); + gtk_list_box_row_set_child (GTK_LIST_BOX_ROW (view->network_placeholder), + view->network_placeholder_label); + g_object_set_data (G_OBJECT (view->network_placeholder), + "is-network", GINT_TO_POINTER (TRUE)); + /* mark the row as placeholder, so it always goes first */ + g_object_set_data (G_OBJECT (view->network_placeholder), + "is-placeholder", GINT_TO_POINTER (TRUE)); + gtk_list_box_insert (GTK_LIST_BOX (view->listbox), view->network_placeholder, -1); + } + + if (nautilus_gtk_places_view_get_fetching_networks (view)) + { + /* only show a placeholder with a message if the list is empty. + * otherwise just show the spinner in the header */ + if (!has_networks (view)) + { + gtk_widget_show (view->network_placeholder); + gtk_label_set_text (GTK_LABEL (view->network_placeholder_label), + _("Searching for network locations")); + } + } + else if (!has_networks (view)) + { + gtk_widget_show (view->network_placeholder); + gtk_label_set_text (GTK_LABEL (view->network_placeholder_label), + _("No network locations found")); + } + else + { + gtk_widget_hide (view->network_placeholder); + } +} + +static void +monitor_network (NautilusGtkPlacesView *view) +{ + GFile *network_file; + GError *error; + + if (view->network_monitor) + return; + + error = NULL; + network_file = g_file_new_for_uri ("network:///"); + view->network_monitor = g_file_monitor (network_file, + G_FILE_MONITOR_NONE, + NULL, + &error); + + g_clear_object (&network_file); + + if (error) + { + g_warning ("Error monitoring network: %s", error->message); + g_clear_error (&error); + return; + } + + g_signal_connect_swapped (view->network_monitor, + "changed", + G_CALLBACK (update_places), + view); +} + +static void +populate_networks (NautilusGtkPlacesView *view, + GFileEnumerator *enumerator, + GList *detected_networks) +{ + GList *l; + GFile *file; + GFile *activatable_file; + char *uri; + GFileType type; + GIcon *icon; + char *display_name; + + for (l = detected_networks; l != NULL; l = l->next) + { + file = g_file_enumerator_get_child (enumerator, l->data); + type = g_file_info_get_file_type (l->data); + if (type == G_FILE_TYPE_SHORTCUT || type == G_FILE_TYPE_MOUNTABLE) + uri = g_file_info_get_attribute_as_string (l->data, G_FILE_ATTRIBUTE_STANDARD_TARGET_URI); + else + uri = g_file_get_uri (file); + activatable_file = g_file_new_for_uri (uri); + display_name = g_file_info_get_attribute_as_string (l->data, G_FILE_ATTRIBUTE_STANDARD_DISPLAY_NAME); + icon = g_file_info_get_icon (l->data); + + add_file (view, activatable_file, icon, display_name, NULL, TRUE); + + g_free (uri); + g_free (display_name); + g_clear_object (&file); + g_clear_object (&activatable_file); + } +} + +static void +network_enumeration_next_files_finished (GObject *source_object, + GAsyncResult *res, + gpointer user_data) +{ + NautilusGtkPlacesView *view; + GList *detected_networks; + GError *error; + + view = NAUTILUS_GTK_PLACES_VIEW (user_data); + error = NULL; + + detected_networks = g_file_enumerator_next_files_finish (G_FILE_ENUMERATOR (source_object), + res, &error); + + if (error) + { + if (g_error_matches (error, G_IO_ERROR, G_IO_ERROR_CANCELLED)) + { + g_clear_error (&error); + g_object_unref (view); + return; + } + + g_warning ("Failed to fetch network locations: %s", error->message); + g_clear_error (&error); + } + else + { + nautilus_gtk_places_view_set_fetching_networks (view, FALSE); + populate_networks (view, G_FILE_ENUMERATOR (source_object), detected_networks); + + g_list_free_full (detected_networks, g_object_unref); + } + + update_network_state (view); + monitor_network (view); + update_loading (view); + + g_object_unref (view); +} + +static void +network_enumeration_finished (GObject *source_object, + GAsyncResult *res, + gpointer user_data) +{ + NautilusGtkPlacesView *view = NAUTILUS_GTK_PLACES_VIEW (user_data); + GFileEnumerator *enumerator; + GError *error; + + error = NULL; + enumerator = g_file_enumerate_children_finish (G_FILE (source_object), res, &error); + + if (error) + { + if (!g_error_matches (error, G_IO_ERROR, G_IO_ERROR_CANCELLED) && + !g_error_matches (error, G_IO_ERROR, G_IO_ERROR_NOT_SUPPORTED)) + g_warning ("Failed to fetch network locations: %s", error->message); + + g_clear_error (&error); + g_object_unref (view); + } + else + { + g_file_enumerator_next_files_async (enumerator, + G_MAXINT32, + G_PRIORITY_DEFAULT, + view->networks_fetching_cancellable, + network_enumeration_next_files_finished, + user_data); + g_object_unref (enumerator); + } +} + +static void +fetch_networks (NautilusGtkPlacesView *view) +{ + GFile *network_file; + const char * const *supported_uris; + gboolean found; + + supported_uris = g_vfs_get_supported_uri_schemes (g_vfs_get_default ()); + + for (found = FALSE; !found && supported_uris && supported_uris[0]; supported_uris++) + if (g_strcmp0 (supported_uris[0], "network") == 0) + found = TRUE; + + if (!found) + return; + + network_file = g_file_new_for_uri ("network:///"); + + g_cancellable_cancel (view->networks_fetching_cancellable); + g_clear_object (&view->networks_fetching_cancellable); + view->networks_fetching_cancellable = g_cancellable_new (); + nautilus_gtk_places_view_set_fetching_networks (view, TRUE); + update_network_state (view); + + g_object_ref (view); + g_file_enumerate_children_async (network_file, + "standard::type,standard::target-uri,standard::name,standard::display-name,standard::icon", + G_FILE_QUERY_INFO_NONE, + G_PRIORITY_DEFAULT, + view->networks_fetching_cancellable, + network_enumeration_finished, + view); + + g_clear_object (&network_file); +} + +static void +update_places (NautilusGtkPlacesView *view) +{ + GList *mounts; + GList *volumes; + GList *drives; + GList *l; + GIcon *icon; + GFile *file; + GtkWidget *child; + gchar *osname; + + /* Clear all previously added items */ + while ((child = gtk_widget_get_first_child (GTK_WIDGET (view->listbox)))) + gtk_list_box_remove (GTK_LIST_BOX (view->listbox), child); + + view->network_placeholder = NULL; + /* Inform clients that we started loading */ + nautilus_gtk_places_view_set_loading (view, TRUE); + + /* Add "Computer" row */ + file = g_file_new_for_path ("/"); + icon = g_themed_icon_new_with_default_fallbacks ("drive-harddisk"); + osname = g_get_os_info (G_OS_INFO_KEY_NAME); + + add_file (view, file, icon, ((osname != NULL) ? osname : _("Operating System")), "/", FALSE); + + g_clear_object (&file); + g_clear_object (&icon); + g_free (osname); + + /* Add currently connected drives */ + drives = g_volume_monitor_get_connected_drives (view->volume_monitor); + + for (l = drives; l != NULL; l = l->next) + add_drive (view, l->data); + + g_list_free_full (drives, g_object_unref); + + /* + * Since all volumes with an associated GDrive were already added with + * add_drive before, add all volumes that aren't associated with a + * drive. + */ + volumes = g_volume_monitor_get_volumes (view->volume_monitor); + + for (l = volumes; l != NULL; l = l->next) + { + GVolume *volume; + GDrive *drive; + + volume = l->data; + drive = g_volume_get_drive (volume); + + if (drive) + { + g_object_unref (drive); + continue; + } + + add_volume (view, volume); + } + + g_list_free_full (volumes, g_object_unref); + + /* + * Now that all necessary drives and volumes were already added, add mounts + * that have no volume, such as /etc/mtab mounts, ftp, sftp, etc. + */ + mounts = g_volume_monitor_get_mounts (view->volume_monitor); + + for (l = mounts; l != NULL; l = l->next) + { + GMount *mount; + GVolume *volume; + + mount = l->data; + volume = g_mount_get_volume (mount); + + if (volume) + { + g_object_unref (volume); + continue; + } + + add_mount (view, mount); + } + + g_list_free_full (mounts, g_object_unref); + + /* load saved servers */ + populate_servers (view); + + /* fetch networks and add them asynchronously */ + fetch_networks (view); + + update_view_mode (view); + /* Check whether we still are in a loading state */ + update_loading (view); +} + +static void +server_mount_ready_cb (GObject *source_file, + GAsyncResult *res, + gpointer user_data) +{ + NautilusGtkPlacesView *view = NAUTILUS_GTK_PLACES_VIEW (user_data); + gboolean should_show; + GError *error; + GFile *location; + + location = G_FILE (source_file); + should_show = TRUE; + error = NULL; + + g_file_mount_enclosing_volume_finish (location, res, &error); + if (error) + { + should_show = FALSE; + + if (error->code == G_IO_ERROR_ALREADY_MOUNTED) + { + /* + * Already mounted volume is not a critical error + * and we can still continue with the operation. + */ + should_show = TRUE; + } + else if (error->domain != G_IO_ERROR || + (error->code != G_IO_ERROR_CANCELLED && + error->code != G_IO_ERROR_FAILED_HANDLED)) + { + /* if it wasn't cancelled show a dialog */ + emit_show_error_message (view, _("Unable to access location"), error->message); + } + + /* The operation got cancelled by the user and or the error + has been handled already. */ + g_clear_error (&error); + } + + if (view->destroyed) + { + g_object_unref (view); + return; + } + + view->should_pulse_entry = FALSE; + gtk_entry_set_progress_fraction (GTK_ENTRY (view->address_entry), 0); + + /* Restore from Cancel to Connect */ + gtk_button_set_label (GTK_BUTTON (view->connect_button), _("Con_nect")); + gtk_widget_set_sensitive (view->address_entry, TRUE); + view->connecting_to_server = FALSE; + + if (should_show) + { + server_list_add_server (view, location); + + /* + * Only clear the entry if it successfully connects to the server. + * Otherwise, the user would lost the typed address even if it fails + * to connect. + */ + gtk_editable_set_text (GTK_EDITABLE (view->address_entry), ""); + + if (view->should_open_location) + { + GMount *mount; + GFile *root; + + /* + * If the mount is not found at this point, it is probably user- + * invisible, which happens e.g for smb-browse, but the location + * should be opened anyway... + */ + mount = g_file_find_enclosing_mount (location, view->cancellable, NULL); + if (mount) + { + root = g_mount_get_default_location (mount); + + emit_open_location (view, root, view->open_flags); + + g_object_unref (root); + g_object_unref (mount); + } + else + { + emit_open_location (view, location, view->open_flags); + } + } + } + + update_places (view); + g_object_unref (view); +} + +static void +volume_mount_ready_cb (GObject *source_volume, + GAsyncResult *res, + gpointer user_data) +{ + NautilusGtkPlacesView *view = NAUTILUS_GTK_PLACES_VIEW (user_data); + gboolean should_show; + GVolume *volume; + GError *error; + + volume = G_VOLUME (source_volume); + should_show = TRUE; + error = NULL; + + g_volume_mount_finish (volume, res, &error); + + if (error) + { + should_show = FALSE; + + if (error->code == G_IO_ERROR_ALREADY_MOUNTED) + { + /* + * If the volume was already mounted, it's not a hard error + * and we can still continue with the operation. + */ + should_show = TRUE; + } + else if (error->domain != G_IO_ERROR || + (error->code != G_IO_ERROR_CANCELLED && + error->code != G_IO_ERROR_FAILED_HANDLED)) + { + /* if it wasn't cancelled show a dialog */ + emit_show_error_message (NAUTILUS_GTK_PLACES_VIEW (user_data), _("Unable to access location"), error->message); + should_show = FALSE; + } + + /* The operation got cancelled by the user and or the error + has been handled already. */ + g_clear_error (&error); + } + + if (view->destroyed) + { + g_object_unref(view); + return; + } + + view->mounting_volume = FALSE; + update_loading (view); + + if (should_show) + { + GMount *mount; + GFile *root; + + mount = g_volume_get_mount (volume); + root = g_mount_get_default_location (mount); + + if (view->should_open_location) + emit_open_location (NAUTILUS_GTK_PLACES_VIEW (user_data), root, view->open_flags); + + g_object_unref (mount); + g_object_unref (root); + } + + update_places (view); + g_object_unref (view); +} + +static void +unmount_ready_cb (GObject *source_mount, + GAsyncResult *res, + gpointer user_data) +{ + NautilusGtkPlacesView *view; + GMount *mount; + GError *error; + + view = NAUTILUS_GTK_PLACES_VIEW (user_data); + mount = G_MOUNT (source_mount); + error = NULL; + + g_mount_unmount_with_operation_finish (mount, res, &error); + + if (error) + { + if (error->domain != G_IO_ERROR || + (error->code != G_IO_ERROR_CANCELLED && + error->code != G_IO_ERROR_FAILED_HANDLED)) + { + /* if it wasn't cancelled show a dialog */ + emit_show_error_message (view, _("Unable to unmount volume"), error->message); + } + + g_clear_error (&error); + } + + if (view->destroyed) { + g_object_unref (view); + return; + } + + view->row_for_action = NULL; + view->unmounting_mount = FALSE; + update_loading (view); + + g_object_unref (view); +} + +static gboolean +pulse_entry_cb (gpointer user_data) +{ + NautilusGtkPlacesView *view = NAUTILUS_GTK_PLACES_VIEW (user_data); + + if (view->destroyed) + { + view->entry_pulse_timeout_id = 0; + + return G_SOURCE_REMOVE; + } + else if (view->should_pulse_entry) + { + gtk_entry_progress_pulse (GTK_ENTRY (view->address_entry)); + + return G_SOURCE_CONTINUE; + } + else + { + gtk_entry_set_progress_fraction (GTK_ENTRY (view->address_entry), 0); + view->entry_pulse_timeout_id = 0; + + return G_SOURCE_REMOVE; + } +} + +static void +unmount_mount (NautilusGtkPlacesView *view, + GMount *mount) +{ + GMountOperation *operation; + GtkWidget *toplevel; + + toplevel = GTK_WIDGET (gtk_widget_get_root (GTK_WIDGET (view))); + + g_cancellable_cancel (view->cancellable); + g_clear_object (&view->cancellable); + view->cancellable = g_cancellable_new (); + + view->unmounting_mount = TRUE; + update_loading (view); + + g_object_ref (view); + + operation = gtk_mount_operation_new (GTK_WINDOW (toplevel)); + g_mount_unmount_with_operation (mount, + 0, + operation, + view->cancellable, + unmount_ready_cb, + view); + g_object_unref (operation); +} + +static void +mount_server (NautilusGtkPlacesView *view, + GFile *location) +{ + GMountOperation *operation; + GtkWidget *toplevel; + + g_cancellable_cancel (view->cancellable); + g_clear_object (&view->cancellable); + /* User cliked when the operation was ongoing, so wanted to cancel it */ + if (view->connecting_to_server) + return; + + view->cancellable = g_cancellable_new (); + toplevel = GTK_WIDGET (gtk_widget_get_root (GTK_WIDGET (view))); + operation = gtk_mount_operation_new (GTK_WINDOW (toplevel)); + + view->should_pulse_entry = TRUE; + gtk_entry_set_progress_pulse_step (GTK_ENTRY (view->address_entry), 0.1); + gtk_entry_set_progress_fraction (GTK_ENTRY (view->address_entry), 0.1); + /* Allow to cancel the operation */ + gtk_button_set_label (GTK_BUTTON (view->connect_button), _("Cance_l")); + gtk_widget_set_sensitive (view->address_entry, FALSE); + view->connecting_to_server = TRUE; + update_loading (view); + + if (view->entry_pulse_timeout_id == 0) + view->entry_pulse_timeout_id = g_timeout_add (100, (GSourceFunc) pulse_entry_cb, view); + + g_mount_operation_set_password_save (operation, G_PASSWORD_SAVE_FOR_SESSION); + + /* make sure we keep the view around for as long as we are running */ + g_object_ref (view); + + g_file_mount_enclosing_volume (location, + 0, + operation, + view->cancellable, + server_mount_ready_cb, + view); + + /* unref operation here - g_file_mount_enclosing_volume() does ref for itself */ + g_object_unref (operation); +} + +static void +mount_volume (NautilusGtkPlacesView *view, + GVolume *volume) +{ + GMountOperation *operation; + GtkWidget *toplevel; + + toplevel = GTK_WIDGET (gtk_widget_get_root (GTK_WIDGET (view))); + operation = gtk_mount_operation_new (GTK_WINDOW (toplevel)); + + g_cancellable_cancel (view->cancellable); + g_clear_object (&view->cancellable); + view->cancellable = g_cancellable_new (); + + view->mounting_volume = TRUE; + update_loading (view); + + g_mount_operation_set_password_save (operation, G_PASSWORD_SAVE_FOR_SESSION); + + /* make sure we keep the view around for as long as we are running */ + g_object_ref (view); + + g_volume_mount (volume, + 0, + operation, + view->cancellable, + volume_mount_ready_cb, + view); + + /* unref operation here - g_file_mount_enclosing_volume() does ref for itself */ + g_object_unref (operation); +} + +static void +open_cb (GtkWidget *widget, + const char *action_name, + GVariant *parameter) +{ + NautilusGtkPlacesView *view = NAUTILUS_GTK_PLACES_VIEW (widget); + NautilusGtkPlacesOpenFlags flags = NAUTILUS_GTK_PLACES_OPEN_NORMAL; + + if (view->row_for_action == NULL) + return; + + if (strcmp (action_name, "location.open") == 0) + flags = NAUTILUS_GTK_PLACES_OPEN_NORMAL; + else if (strcmp (action_name, "location.open-tab") == 0) + flags = NAUTILUS_GTK_PLACES_OPEN_NEW_TAB; + else if (strcmp (action_name, "location.open-window") == 0) + flags = NAUTILUS_GTK_PLACES_OPEN_NEW_WINDOW; + + activate_row (view, view->row_for_action, flags); +} + +static void +properties_cb (GtkWidget *widget, + const char *action_name, + GVariant *parameter) +{ + NautilusGtkPlacesView *view = NAUTILUS_GTK_PLACES_VIEW (widget); + GList *list; + GMount *mount; + g_autoptr (GFile) location = NULL; + NautilusFile *file; + + if (view->row_for_action == NULL) + return; + + file = NULL; + mount = nautilus_gtk_places_view_row_get_mount (view->row_for_action); + + if (mount) + { + location = g_mount_get_root (mount); + file = nautilus_file_get (location); + } + else + file = nautilus_file_get (nautilus_gtk_places_view_row_get_file (view->row_for_action)); + + if (file) + { + list = g_list_append (NULL, file); + nautilus_properties_window_present (list, widget, NULL, NULL, NULL); + + nautilus_file_list_unref (list); + } +} + +static void +mount_cb (GtkWidget *widget, + const char *action_name, + GVariant *parameter) +{ + NautilusGtkPlacesView *view = NAUTILUS_GTK_PLACES_VIEW (widget); + GVolume *volume; + + if (view->row_for_action == NULL) + return; + + volume = nautilus_gtk_places_view_row_get_volume (view->row_for_action); + + /* + * When the mount item is activated, it's expected that + * the volume only gets mounted, without opening it after + * the operation is complete. + */ + view->should_open_location = FALSE; + + nautilus_gtk_places_view_row_set_busy (view->row_for_action, TRUE); + mount_volume (view, volume); +} + +static void +unmount_cb (GtkWidget *widget, + const char *action_name, + GVariant *parameter) +{ + NautilusGtkPlacesView *view = NAUTILUS_GTK_PLACES_VIEW (widget); + GMount *mount; + + if (view->row_for_action == NULL) + return; + + mount = nautilus_gtk_places_view_row_get_mount (view->row_for_action); + + nautilus_gtk_places_view_row_set_busy (view->row_for_action, TRUE); + + unmount_mount (view, mount); +} + +static void +attach_protocol_row_to_grid (GtkGrid *grid, + const char *protocol_name, + const char *protocol_prefix) +{ + GtkWidget *name_label; + GtkWidget *prefix_label; + + name_label = gtk_label_new (protocol_name); + gtk_widget_set_halign (name_label, GTK_ALIGN_START); + gtk_grid_attach_next_to (grid, name_label, NULL, GTK_POS_BOTTOM, 1, 1); + + prefix_label = gtk_label_new (protocol_prefix); + gtk_widget_set_halign (prefix_label, GTK_ALIGN_START); + gtk_grid_attach_next_to (grid, prefix_label, name_label, GTK_POS_RIGHT, 1, 1); +} + +static void +populate_available_protocols_grid (GtkGrid *grid) +{ + const char * const *supported_protocols; + gboolean has_any = FALSE; + + supported_protocols = g_vfs_get_supported_uri_schemes (g_vfs_get_default ()); + + if (g_strv_contains (supported_protocols, "afp")) + { + attach_protocol_row_to_grid (grid, _("AppleTalk"), "afp://"); + has_any = TRUE; + } + + if (g_strv_contains (supported_protocols, "ftp")) + { + attach_protocol_row_to_grid (grid, _("File Transfer Protocol"), + /* Translators: do not translate ftp:// and ftps:// */ + _("ftp:// or ftps://")); + has_any = TRUE; + } + + if (g_strv_contains (supported_protocols, "nfs")) + { + attach_protocol_row_to_grid (grid, _("Network File System"), "nfs://"); + has_any = TRUE; + } + + if (g_strv_contains (supported_protocols, "smb")) + { + attach_protocol_row_to_grid (grid, _("Samba"), "smb://"); + has_any = TRUE; + } + + if (g_strv_contains (supported_protocols, "ssh")) + { + attach_protocol_row_to_grid (grid, _("SSH File Transfer Protocol"), + /* Translators: do not translate sftp:// and ssh:// */ + _("sftp:// or ssh://")); + has_any = TRUE; + } + + if (g_strv_contains (supported_protocols, "dav")) + { + attach_protocol_row_to_grid (grid, _("WebDAV"), + /* Translators: do not translate dav:// and davs:// */ + _("dav:// or davs://")); + has_any = TRUE; + } + + if (!has_any) + gtk_widget_hide (GTK_WIDGET (grid)); +} + +static GMenuModel * +get_menu_model (void) +{ + GMenu *menu; + GMenu *section; + GMenuItem *item; + + menu = g_menu_new (); + section = g_menu_new (); + item = g_menu_item_new (_("_Open"), "location.open"); + g_menu_append_item (section, item); + g_object_unref (item); + + item = g_menu_item_new (_("Open in New _Tab"), "location.open-tab"); + g_menu_item_set_attribute (item, "hidden-when", "s", "action-disabled"); + g_menu_append_item (section, item); + g_object_unref (item); + + item = g_menu_item_new (_("Open in New _Window"), "location.open-window"); + g_menu_item_set_attribute (item, "hidden-when", "s", "action-disabled"); + g_menu_append_item (section, item); + g_object_unref (item); + + g_menu_append_section (menu, NULL, G_MENU_MODEL (section)); + g_object_unref (section); + + section = g_menu_new (); + item = g_menu_item_new (_("_Disconnect"), "location.disconnect"); + g_menu_item_set_attribute (item, "hidden-when", "s", "action-disabled"); + g_menu_append_item (section, item); + g_object_unref (item); + + item = g_menu_item_new (_("_Unmount"), "location.unmount"); + g_menu_item_set_attribute (item, "hidden-when", "s", "action-disabled"); + g_menu_append_item (section, item); + g_object_unref (item); + + + item = g_menu_item_new (_("_Connect"), "location.connect"); + g_menu_item_set_attribute (item, "hidden-when", "s", "action-disabled"); + g_menu_append_item (section, item); + g_object_unref (item); + + item = g_menu_item_new (_("_Mount"), "location.mount"); + g_menu_item_set_attribute (item, "hidden-when", "s", "action-disabled"); + g_menu_append_item (section, item); + g_object_unref (item); + + item = g_menu_item_new (_("_Properties"), "location.properties"); + g_menu_item_set_attribute (item, "hidden-when", "s", "action-disabled"); + g_menu_append_item (section, item); + g_object_unref (item); + + g_menu_append_section (menu, NULL, G_MENU_MODEL (section)); + g_object_unref (section); + + return G_MENU_MODEL (menu); +} + +static void +_popover_set_pointing_to_widget (GtkPopover *popover, + GtkWidget *target) +{ + GtkWidget *parent; + double x, y, w, h; + + parent = gtk_widget_get_parent (GTK_WIDGET (popover)); + + if (!gtk_widget_translate_coordinates (target, parent, 0, 0, &x, &y)) + return; + + w = gtk_widget_get_allocated_width (GTK_WIDGET (target)); + h = gtk_widget_get_allocated_height (GTK_WIDGET (target)); + + gtk_popover_set_pointing_to (popover, &(GdkRectangle){x, y, w, h}); +} + +static gboolean +real_popup_menu (GtkWidget *widget, + double x, + double y) +{ + NautilusGtkPlacesViewRow *row = NAUTILUS_GTK_PLACES_VIEW_ROW (widget); + NautilusGtkPlacesView *view; + GMount *mount; + GFile *file; + gboolean is_root, is_network; + double x_in_view, y_in_view; + + view = NAUTILUS_GTK_PLACES_VIEW (gtk_widget_get_ancestor (GTK_WIDGET (row), NAUTILUS_TYPE_GTK_PLACES_VIEW)); + + mount = nautilus_gtk_places_view_row_get_mount (row); + file = nautilus_gtk_places_view_row_get_file (row); + is_root = file && nautilus_is_root_directory (file); + is_network = nautilus_gtk_places_view_row_get_is_network (row); + + gtk_widget_action_set_enabled (GTK_WIDGET (view), "location.disconnect", + !file && mount && is_network); + gtk_widget_action_set_enabled (GTK_WIDGET (view), "location.unmount", + !file && mount && !is_network); + gtk_widget_action_set_enabled (GTK_WIDGET (view), "location.connect", + !file && !mount && is_network); + gtk_widget_action_set_enabled (GTK_WIDGET (view), "location.mount", + !file && !mount && !is_network); + gtk_widget_action_set_enabled (GTK_WIDGET (view), "location.properties", + (is_root || + (mount && !(file && is_network)))); + + if (!view->popup_menu) + { + GMenuModel *model = get_menu_model (); + + view->popup_menu = gtk_popover_menu_new_from_model (model); + + gtk_popover_set_has_arrow (GTK_POPOVER (view->popup_menu), FALSE); + gtk_widget_set_parent (view->popup_menu, GTK_WIDGET (view)); + gtk_widget_set_halign (view->popup_menu, GTK_ALIGN_START); + + g_object_unref (model); + } + + if (view->row_for_action) + g_object_set_data (G_OBJECT (view->row_for_action), "menu", NULL); + + if (x == -1 && y == -1) + _popover_set_pointing_to_widget (GTK_POPOVER (view->popup_menu), widget); + else + { + gtk_widget_translate_coordinates (widget, GTK_WIDGET (view), + x, y, &x_in_view, &y_in_view); + gtk_popover_set_pointing_to (GTK_POPOVER (view->popup_menu), + &(GdkRectangle){x_in_view, y_in_view, 0, 0}); + } + + view->row_for_action = row; + if (view->row_for_action) + g_object_set_data (G_OBJECT (view->row_for_action), "menu", view->popup_menu); + + gtk_popover_popup (GTK_POPOVER (view->popup_menu)); + + return TRUE; +} + +static gboolean +on_row_popup_menu (GtkWidget *widget, + GVariant *args, + gpointer user_data) +{ + return real_popup_menu (widget, -1, -1); +} + +static void +click_cb (GtkGesture *gesture, + int n_press, + double x, + double y, + gpointer user_data) +{ + real_popup_menu (GTK_WIDGET (user_data), x, y); +} + +static gboolean +on_key_press_event (GtkEventController *controller, + guint keyval, + guint keycode, + GdkModifierType state, + NautilusGtkPlacesView *view) +{ + GdkModifierType modifiers; + + modifiers = gtk_accelerator_get_default_mod_mask (); + + if (keyval == GDK_KEY_Return || + keyval == GDK_KEY_KP_Enter || + keyval == GDK_KEY_ISO_Enter || + keyval == GDK_KEY_space) + { + GtkWidget *focus_widget; + GtkWindow *toplevel; + + view->current_open_flags = NAUTILUS_GTK_PLACES_OPEN_NORMAL; + toplevel = get_toplevel (GTK_WIDGET (view)); + + if (!toplevel) + return FALSE; + + focus_widget = gtk_root_get_focus (GTK_ROOT (toplevel)); + + if (!NAUTILUS_IS_GTK_PLACES_VIEW_ROW (focus_widget)) + return FALSE; + + if ((state & modifiers) == GDK_SHIFT_MASK) + view->current_open_flags = NAUTILUS_GTK_PLACES_OPEN_NEW_TAB; + else if ((state & modifiers) == GDK_CONTROL_MASK) + view->current_open_flags = NAUTILUS_GTK_PLACES_OPEN_NEW_WINDOW; + + activate_row (view, NAUTILUS_GTK_PLACES_VIEW_ROW (focus_widget), view->current_open_flags); + + return TRUE; + } + + return FALSE; +} + +static void +on_middle_click_row_event (GtkGestureClick *gesture, + guint n_press, + double x, + double y, + NautilusGtkPlacesView *view) +{ + GtkListBoxRow *row; + + if (n_press != 1) + return; + + row = gtk_list_box_get_row_at_y (GTK_LIST_BOX (view->listbox), y); + if (row != NULL && gtk_widget_is_sensitive (GTK_WIDGET (row))) + activate_row (view, NAUTILUS_GTK_PLACES_VIEW_ROW (row), NAUTILUS_GTK_PLACES_OPEN_NEW_TAB); +} + + +static void +on_eject_button_clicked (GtkWidget *widget, + NautilusGtkPlacesViewRow *row) +{ + if (row) + { + GtkWidget *view = gtk_widget_get_ancestor (GTK_WIDGET (row), NAUTILUS_TYPE_GTK_PLACES_VIEW); + + unmount_mount (NAUTILUS_GTK_PLACES_VIEW (view), nautilus_gtk_places_view_row_get_mount (row)); + } +} + +static void +on_connect_button_clicked (NautilusGtkPlacesView *view) +{ + const char *uri; + GFile *file; + + file = NULL; + + /* + * Since the 'Connect' button is updated whenever the typed + * address changes, it is sufficient to check if it's sensitive + * or not, in order to determine if the given address is valid. + */ + if (!gtk_widget_get_sensitive (view->connect_button)) + return; + + uri = gtk_editable_get_text (GTK_EDITABLE (view->address_entry)); + + if (uri != NULL && uri[0] != '\0') + file = g_file_new_for_commandline_arg (uri); + + if (file) + { + view->should_open_location = TRUE; + + mount_server (view, file); + } + else + { + emit_show_error_message (view, _("Unable to get remote server location"), NULL); + } +} + +static void +on_address_entry_text_changed (NautilusGtkPlacesView *view) +{ + const char * const *supported_protocols; + char *address, *scheme; + gboolean supported; + + supported = FALSE; + supported_protocols = g_vfs_get_supported_uri_schemes (g_vfs_get_default ()); + address = g_strdup (gtk_editable_get_text (GTK_EDITABLE (view->address_entry))); + scheme = g_uri_parse_scheme (address); + + if (!supported_protocols) + goto out; + + if (!scheme) + goto out; + + supported = g_strv_contains (supported_protocols, scheme) && + !g_strv_contains (unsupported_protocols, scheme); + +out: + gtk_widget_set_sensitive (view->connect_button, supported); + if (scheme && !supported) + gtk_widget_add_css_class (view->address_entry, "error"); + else + gtk_widget_remove_css_class (view->address_entry, "error"); + + g_free (address); + g_free (scheme); +} + +static void +on_address_entry_show_help_pressed (NautilusGtkPlacesView *view, + GtkEntryIconPosition icon_pos, + GtkEntry *entry) +{ + GdkRectangle rect; + double x, y; + + /* Setup the auxiliary popover's rectangle */ + gtk_entry_get_icon_area (GTK_ENTRY (view->address_entry), + GTK_ENTRY_ICON_SECONDARY, + &rect); + gtk_widget_translate_coordinates (view->address_entry, GTK_WIDGET (view), + rect.x, rect.y, &x, &y); + + rect.x = x; + rect.y = y; + gtk_popover_set_pointing_to (GTK_POPOVER (view->server_adresses_popover), &rect); + gtk_widget_set_visible (view->server_adresses_popover, TRUE); +} + +static void +on_recent_servers_listbox_row_activated (NautilusGtkPlacesView *view, + NautilusGtkPlacesViewRow *row, + GtkWidget *listbox) +{ + char *uri; + + uri = g_object_get_data (G_OBJECT (row), "uri"); + + gtk_editable_set_text (GTK_EDITABLE (view->address_entry), uri); + + gtk_widget_hide (view->recent_servers_popover); +} + +static void +on_listbox_row_activated (NautilusGtkPlacesView *view, + NautilusGtkPlacesViewRow *row, + GtkWidget *listbox) +{ + activate_row (view, row, view->current_open_flags); +} + +static gboolean +listbox_filter_func (GtkListBoxRow *row, + gpointer user_data) +{ + NautilusGtkPlacesView *view = NAUTILUS_GTK_PLACES_VIEW (user_data); + gboolean is_placeholder; + gboolean retval; + gboolean searching; + char *name; + char *path; + + retval = FALSE; + searching = view->search_query && view->search_query[0] != '\0'; + + is_placeholder = GPOINTER_TO_INT (g_object_get_data (G_OBJECT (row), "is-placeholder")); + + if (is_placeholder && searching) + return FALSE; + + if (!searching) + return TRUE; + + g_object_get (row, + "name", &name, + "path", &path, + NULL); + + if (name) + { + char *lowercase_name = g_utf8_strdown (name, -1); + + retval |= strstr (lowercase_name, view->search_query) != NULL; + + g_free (lowercase_name); + } + + if (path) + { + char *lowercase_path = g_utf8_strdown (path, -1); + + retval |= strstr (lowercase_path, view->search_query) != NULL; + + g_free (lowercase_path); + } + + g_free (name); + g_free (path); + + return retval; +} + +static void +listbox_header_func (GtkListBoxRow *row, + GtkListBoxRow *before, + gpointer user_data) +{ + gboolean row_is_network; + char *text; + + text = NULL; + row_is_network = GPOINTER_TO_INT (g_object_get_data (G_OBJECT (row), "is-network")); + + if (!before) + { + text = g_strdup_printf ("%s", row_is_network ? _("Networks") : _("On This Computer")); + } + else + { + gboolean before_is_network; + + before_is_network = GPOINTER_TO_INT (g_object_get_data (G_OBJECT (before), "is-network")); + + if (before_is_network != row_is_network) + text = g_strdup_printf ("%s", row_is_network ? _("Networks") : _("On This Computer")); + } + + if (text) + { + GtkWidget *header; + GtkWidget *label; + GtkWidget *separator; + + header = gtk_box_new (GTK_ORIENTATION_VERTICAL, 6); + gtk_widget_set_margin_top (header, 6); + + separator = gtk_separator_new (GTK_ORIENTATION_HORIZONTAL); + + label = g_object_new (GTK_TYPE_LABEL, + "use_markup", TRUE, + "margin-start", 12, + "label", text, + "xalign", 0.0f, + NULL); + if (row_is_network) + { + GtkWidget *header_name; + GtkWidget *network_header_spinner; + + gtk_widget_set_margin_end (label, 6); + + header_name = gtk_box_new (GTK_ORIENTATION_HORIZONTAL, 0); + network_header_spinner = gtk_spinner_new (); + gtk_widget_set_margin_end (network_header_spinner, 12); + g_object_bind_property (NAUTILUS_GTK_PLACES_VIEW (user_data), + "fetching-networks", + network_header_spinner, + "spinning", + G_BINDING_SYNC_CREATE); + + gtk_box_append (GTK_BOX (header_name), label); + gtk_box_append (GTK_BOX (header_name), network_header_spinner); + gtk_box_append (GTK_BOX (header), header_name); + } + else + { + gtk_widget_set_hexpand (label, TRUE); + gtk_widget_set_margin_end (label, 12); + gtk_box_append (GTK_BOX (header), label); + } + + gtk_box_append (GTK_BOX (header), separator); + + gtk_list_box_row_set_header (row, header); + + g_free (text); + } + else + { + gtk_list_box_row_set_header (row, NULL); + } +} + +static int +listbox_sort_func (GtkListBoxRow *row1, + GtkListBoxRow *row2, + gpointer user_data) +{ + gboolean row1_is_network; + gboolean row2_is_network; + char *path1; + char *path2; + gboolean *is_placeholder1; + gboolean *is_placeholder2; + int retval; + + row1_is_network = GPOINTER_TO_INT (g_object_get_data (G_OBJECT (row1), "is-network")); + row2_is_network = GPOINTER_TO_INT (g_object_get_data (G_OBJECT (row2), "is-network")); + + retval = row1_is_network - row2_is_network; + + if (retval != 0) + return retval; + + is_placeholder1 = g_object_get_data (G_OBJECT (row1), "is-placeholder"); + is_placeholder2 = g_object_get_data (G_OBJECT (row2), "is-placeholder"); + + /* we can't have two placeholders for the same section */ + g_assert (!(is_placeholder1 != NULL && is_placeholder2 != NULL)); + + if (is_placeholder1) + return -1; + if (is_placeholder2) + return 1; + + g_object_get (row1, "path", &path1, NULL); + g_object_get (row2, "path", &path2, NULL); + + retval = g_utf8_collate (path1, path2); + + g_free (path1); + g_free (path2); + + return retval; +} + +static void +nautilus_gtk_places_view_constructed (GObject *object) +{ + NautilusGtkPlacesView *view = NAUTILUS_GTK_PLACES_VIEW (object); + + G_OBJECT_CLASS (nautilus_gtk_places_view_parent_class)->constructed (object); + + gtk_list_box_set_sort_func (GTK_LIST_BOX (view->listbox), + listbox_sort_func, + object, + NULL); + gtk_list_box_set_filter_func (GTK_LIST_BOX (view->listbox), + listbox_filter_func, + object, + NULL); + gtk_list_box_set_header_func (GTK_LIST_BOX (view->listbox), + listbox_header_func, + object, + NULL); + + /* load drives */ + update_places (view); + + g_signal_connect_swapped (view->volume_monitor, + "mount-added", + G_CALLBACK (update_places), + object); + g_signal_connect_swapped (view->volume_monitor, + "mount-changed", + G_CALLBACK (update_places), + object); + g_signal_connect_swapped (view->volume_monitor, + "mount-removed", + G_CALLBACK (update_places), + object); + g_signal_connect_swapped (view->volume_monitor, + "volume-added", + G_CALLBACK (update_places), + object); + g_signal_connect_swapped (view->volume_monitor, + "volume-changed", + G_CALLBACK (update_places), + object); + g_signal_connect_swapped (view->volume_monitor, + "volume-removed", + G_CALLBACK (update_places), + object); +} + +static void +nautilus_gtk_places_view_map (GtkWidget *widget) +{ + NautilusGtkPlacesView *view = NAUTILUS_GTK_PLACES_VIEW (widget); + + gtk_editable_set_text (GTK_EDITABLE (view->address_entry), ""); + + GTK_WIDGET_CLASS (nautilus_gtk_places_view_parent_class)->map (widget); +} + +static void +nautilus_gtk_places_view_class_init (NautilusGtkPlacesViewClass *klass) +{ + GObjectClass *object_class = G_OBJECT_CLASS (klass); + GtkWidgetClass *widget_class = GTK_WIDGET_CLASS (klass); + + object_class->finalize = nautilus_gtk_places_view_finalize; + object_class->dispose = nautilus_gtk_places_view_dispose; + object_class->constructed = nautilus_gtk_places_view_constructed; + object_class->get_property = nautilus_gtk_places_view_get_property; + object_class->set_property = nautilus_gtk_places_view_set_property; + + widget_class->map = nautilus_gtk_places_view_map; + + /* + * NautilusGtkPlacesView::open-location: + * @view: the object which received the signal. + * @location: (type Gio.File): GFile to which the caller should switch. + * @open_flags: a single value from NautilusGtkPlacesOpenFlags specifying how the @location + * should be opened. + * + * The places view emits this signal when the user selects a location + * in it. The calling application should display the contents of that + * location; for example, a file manager should show a list of files in + * the specified location. + */ + places_view_signals [OPEN_LOCATION] = + g_signal_new ("open-location", + G_OBJECT_CLASS_TYPE (object_class), + G_SIGNAL_RUN_FIRST, + G_STRUCT_OFFSET (NautilusGtkPlacesViewClass, open_location), + NULL, NULL, + NULL, + G_TYPE_NONE, 2, + G_TYPE_OBJECT, + NAUTILUS_TYPE_OPEN_FLAGS); + + /* + * NautilusGtkPlacesView::show-error-message: + * @view: the object which received the signal. + * @primary: primary message with a summary of the error to show. + * @secondary: secondary message with details of the error to show. + * + * The places view emits this signal when it needs the calling + * application to present an error message. Most of these messages + * refer to mounting or unmounting media, for example, when a drive + * cannot be started for some reason. + */ + places_view_signals [SHOW_ERROR_MESSAGE] = + g_signal_new ("show-error-message", + G_OBJECT_CLASS_TYPE (object_class), + G_SIGNAL_RUN_FIRST, + G_STRUCT_OFFSET (NautilusGtkPlacesViewClass, show_error_message), + NULL, NULL, + NULL, + G_TYPE_NONE, 2, + G_TYPE_STRING, + G_TYPE_STRING); + + properties[PROP_LOADING] = + g_param_spec_boolean ("loading", + "Loading", + "Whether the view is loading locations", + FALSE, + G_PARAM_READABLE|G_PARAM_STATIC_NAME|G_PARAM_STATIC_NICK|G_PARAM_STATIC_BLURB); + + properties[PROP_FETCHING_NETWORKS] = + g_param_spec_boolean ("fetching-networks", + "Fetching networks", + "Whether the view is fetching networks", + FALSE, + G_PARAM_READABLE|G_PARAM_STATIC_NAME|G_PARAM_STATIC_NICK|G_PARAM_STATIC_BLURB); + + properties[PROP_OPEN_FLAGS] = + g_param_spec_flags ("open-flags", + "Open Flags", + "Modes in which the calling application can open locations selected in the sidebar", + NAUTILUS_TYPE_OPEN_FLAGS, + NAUTILUS_GTK_PLACES_OPEN_NORMAL, + G_PARAM_READWRITE|G_PARAM_STATIC_NAME|G_PARAM_STATIC_NICK|G_PARAM_STATIC_BLURB); + + g_object_class_install_properties (object_class, LAST_PROP, properties); + + /* Bind class to template */ + gtk_widget_class_set_template_from_resource (widget_class, "/org/gnome/nautilus/gtk/ui/nautilusgtkplacesview.ui"); + + gtk_widget_class_bind_template_child (widget_class, NautilusGtkPlacesView, actionbar); + gtk_widget_class_bind_template_child (widget_class, NautilusGtkPlacesView, address_entry); + gtk_widget_class_bind_template_child (widget_class, NautilusGtkPlacesView, address_entry_completion); + gtk_widget_class_bind_template_child (widget_class, NautilusGtkPlacesView, completion_store); + gtk_widget_class_bind_template_child (widget_class, NautilusGtkPlacesView, connect_button); + gtk_widget_class_bind_template_child (widget_class, NautilusGtkPlacesView, listbox); + gtk_widget_class_bind_template_child (widget_class, NautilusGtkPlacesView, recent_servers_listbox); + gtk_widget_class_bind_template_child (widget_class, NautilusGtkPlacesView, recent_servers_popover); + gtk_widget_class_bind_template_child (widget_class, NautilusGtkPlacesView, recent_servers_stack); + gtk_widget_class_bind_template_child (widget_class, NautilusGtkPlacesView, stack); + gtk_widget_class_bind_template_child (widget_class, NautilusGtkPlacesView, server_adresses_popover); + gtk_widget_class_bind_template_child (widget_class, NautilusGtkPlacesView, available_protocols_grid); + + gtk_widget_class_bind_template_callback (widget_class, on_address_entry_text_changed); + gtk_widget_class_bind_template_callback (widget_class, on_address_entry_show_help_pressed); + gtk_widget_class_bind_template_callback (widget_class, on_connect_button_clicked); + gtk_widget_class_bind_template_callback (widget_class, on_listbox_row_activated); + gtk_widget_class_bind_template_callback (widget_class, on_recent_servers_listbox_row_activated); + + /** + * NautilusGtkPlacesView|location.open: + * + * Opens the location in the current window. + */ + gtk_widget_class_install_action (widget_class, "location.open", NULL, open_cb); + + /** + * NautilusGtkPlacesView|location.open-tab: + * + * Opens the location in a new tab. + */ + gtk_widget_class_install_action (widget_class, "location.open-tab", NULL, open_cb); + + /** + * NautilusGtkPlacesView|location.open-window: + * + * Opens the location in a new window. + */ + gtk_widget_class_install_action (widget_class, "location.open-window", NULL, open_cb); + + /** + * NautilusGtkPlacesView|location.mount: + * + * Mount the location. + */ + gtk_widget_class_install_action (widget_class, "location.mount", NULL, mount_cb); + + /** + * NautilusGtkPlacesView|location.connect: + * + * Connect the location. + */ + gtk_widget_class_install_action (widget_class, "location.connect", NULL, mount_cb); + + /** + * NautilusGtkPlacesView|location.unmount: + * + * Unmount the location. + */ + gtk_widget_class_install_action (widget_class, "location.unmount", NULL, unmount_cb); + + /** + * NautilusGtkPlacesView|location.disconnect: + * + * Disconnect the location. + */ + gtk_widget_class_install_action (widget_class, "location.disconnect", NULL, unmount_cb); + + /** + * NautilusGtkPlacesView|location.properties: + * + * Show location properties. + */ + gtk_widget_class_install_action (widget_class, "location.properties", NULL, properties_cb); + + gtk_widget_class_set_css_name (widget_class, "placesview"); + gtk_widget_class_set_accessible_role (widget_class, GTK_ACCESSIBLE_ROLE_LIST); +} + +static void +nautilus_gtk_places_view_init (NautilusGtkPlacesView *self) +{ + GtkEventController *controller; + + self->volume_monitor = g_volume_monitor_get (); + self->open_flags = NAUTILUS_GTK_PLACES_OPEN_NORMAL; + self->path_size_group = gtk_size_group_new (GTK_SIZE_GROUP_HORIZONTAL); + self->space_size_group = gtk_size_group_new (GTK_SIZE_GROUP_HORIZONTAL); + + gtk_widget_action_set_enabled (GTK_WIDGET (self), "location.open-tab", FALSE); + gtk_widget_action_set_enabled (GTK_WIDGET (self), "location.open-window", FALSE); + + gtk_widget_init_template (GTK_WIDGET (self)); + + gtk_widget_set_parent (self->server_adresses_popover, GTK_WIDGET (self)); + controller = gtk_event_controller_key_new (); + g_signal_connect (controller, "key-pressed", G_CALLBACK (on_key_press_event), self); + gtk_widget_add_controller (GTK_WIDGET (self), controller); + + /* We need an additional controller because GtkListBox only + * activates rows for GDK_BUTTON_PRIMARY clicks + */ + controller = (GtkEventController *) gtk_gesture_click_new (); + gtk_event_controller_set_propagation_phase (controller, GTK_PHASE_BUBBLE); + gtk_gesture_single_set_button (GTK_GESTURE_SINGLE (controller), GDK_BUTTON_MIDDLE); + g_signal_connect (controller, "released", + G_CALLBACK (on_middle_click_row_event), self); + gtk_widget_add_controller (self->listbox, controller); + + populate_available_protocols_grid (GTK_GRID (self->available_protocols_grid)); +} + +/* + * nautilus_gtk_places_view_new: + * + * Creates a new NautilusGtkPlacesView widget. + * + * The application should connect to at least the + * NautilusGtkPlacesView::open-location signal to be notified + * when the user makes a selection in the view. + * + * Returns: a newly created NautilusGtkPlacesView + */ +GtkWidget * +nautilus_gtk_places_view_new (void) +{ + return g_object_new (NAUTILUS_TYPE_GTK_PLACES_VIEW, NULL); +} + +/* + * nautilus_gtk_places_view_set_open_flags: + * @view: a NautilusGtkPlacesView + * @flags: Bitmask of modes in which the calling application can open locations + * + * Sets the way in which the calling application can open new locations from + * the places view. For example, some applications only open locations + * “directly” into their main view, while others may support opening locations + * in a new notebook tab or a new window. + * + * This function is used to tell the places @view about the ways in which the + * application can open new locations, so that the view can display (or not) + * the “Open in new tab” and “Open in new window” menu items as appropriate. + * + * When the NautilusGtkPlacesView::open-location signal is emitted, its flags + * argument will be set to one of the @flags that was passed in + * nautilus_gtk_places_view_set_open_flags(). + * + * Passing 0 for @flags will cause NAUTILUS_GTK_PLACES_OPEN_NORMAL to always be sent + * to callbacks for the “open-location” signal. + */ +void +nautilus_gtk_places_view_set_open_flags (NautilusGtkPlacesView *view, + NautilusGtkPlacesOpenFlags flags) +{ + g_return_if_fail (NAUTILUS_IS_GTK_PLACES_VIEW (view)); + + if (view->open_flags == flags) + return; + + view->open_flags = flags; + + gtk_widget_action_set_enabled (GTK_WIDGET (view), "location.open-tab", + (flags & NAUTILUS_GTK_PLACES_OPEN_NEW_TAB) != 0); + gtk_widget_action_set_enabled (GTK_WIDGET (view), "location.open-window", + (flags & NAUTILUS_GTK_PLACES_OPEN_NEW_WINDOW) != 0); + + g_object_notify_by_pspec (G_OBJECT (view), properties[PROP_OPEN_FLAGS]); +} + +/* + * nautilus_gtk_places_view_get_open_flags: + * @view: a NautilusGtkPlacesSidebar + * + * Gets the open flags. + * + * Returns: the NautilusGtkPlacesOpenFlags of @view + */ +NautilusGtkPlacesOpenFlags +nautilus_gtk_places_view_get_open_flags (NautilusGtkPlacesView *view) +{ + g_return_val_if_fail (NAUTILUS_IS_GTK_PLACES_VIEW (view), 0); + + return view->open_flags; +} + +/* + * nautilus_gtk_places_view_get_search_query: + * @view: a NautilusGtkPlacesView + * + * Retrieves the current search query from @view. + * + * Returns: (transfer none): the current search query. + */ +const char * +nautilus_gtk_places_view_get_search_query (NautilusGtkPlacesView *view) +{ + g_return_val_if_fail (NAUTILUS_IS_GTK_PLACES_VIEW (view), NULL); + + return view->search_query; +} + +/* + * nautilus_gtk_places_view_set_search_query: + * @view: a NautilusGtkPlacesView + * @query_text: the query, or NULL. + * + * Sets the search query of @view. The search is immediately performed + * once the query is set. + */ +void +nautilus_gtk_places_view_set_search_query (NautilusGtkPlacesView *view, + const char *query_text) +{ + g_return_if_fail (NAUTILUS_IS_GTK_PLACES_VIEW (view)); + + if (g_strcmp0 (view->search_query, query_text) != 0) + { + g_clear_pointer (&view->search_query, g_free); + view->search_query = g_utf8_strdown (query_text, -1); + + gtk_list_box_invalidate_filter (GTK_LIST_BOX (view->listbox)); + gtk_list_box_invalidate_headers (GTK_LIST_BOX (view->listbox)); + + update_view_mode (view); + } +} + +/* + * nautilus_gtk_places_view_get_loading: + * @view: a NautilusGtkPlacesView + * + * Returns %TRUE if the view is loading locations. + */ +gboolean +nautilus_gtk_places_view_get_loading (NautilusGtkPlacesView *view) +{ + g_return_val_if_fail (NAUTILUS_IS_GTK_PLACES_VIEW (view), FALSE); + + return view->loading; +} + +static void +update_loading (NautilusGtkPlacesView *view) +{ + gboolean loading; + + g_return_if_fail (NAUTILUS_IS_GTK_PLACES_VIEW (view)); + + loading = view->fetching_networks || view->connecting_to_server || + view->mounting_volume || view->unmounting_mount; + + set_busy_cursor (view, loading); + nautilus_gtk_places_view_set_loading (view, loading); +} + +static void +nautilus_gtk_places_view_set_loading (NautilusGtkPlacesView *view, + gboolean loading) +{ + g_return_if_fail (NAUTILUS_IS_GTK_PLACES_VIEW (view)); + + if (view->loading != loading) + { + view->loading = loading; + g_object_notify_by_pspec (G_OBJECT (view), properties [PROP_LOADING]); + } +} + +static gboolean +nautilus_gtk_places_view_get_fetching_networks (NautilusGtkPlacesView *view) +{ + g_return_val_if_fail (NAUTILUS_IS_GTK_PLACES_VIEW (view), FALSE); + + return view->fetching_networks; +} + +static void +nautilus_gtk_places_view_set_fetching_networks (NautilusGtkPlacesView *view, + gboolean fetching_networks) +{ + g_return_if_fail (NAUTILUS_IS_GTK_PLACES_VIEW (view)); + + if (view->fetching_networks != fetching_networks) + { + view->fetching_networks = fetching_networks; + g_object_notify_by_pspec (G_OBJECT (view), properties [PROP_FETCHING_NETWORKS]); + } +} diff --git a/src/gtk/nautilusgtkplacesview.ui b/src/gtk/nautilusgtkplacesview.ui new file mode 100644 index 0000000..fbedf70 --- /dev/null +++ b/src/gtk/nautilusgtkplacesview.ui @@ -0,0 +1,261 @@ + + + + + + + + + + completion_store + 1 + 1 + 0 + + + 2 + + + 1 + 6 + 18 + 18 + 18 + 18 + + + 1 + Server Addresses + + + + + + + + + 1 + Server addresses are made up of a protocol prefix and an address. Examples: + 1 + 40 + 40 + 0 + + + + + 1 + smb://gnome.org, ssh://192.168.0.1, ftp://[2001:db8::1] + 1 + 40 + 40 + 0 + + + + + 12 + 1 + 6 + 12 + + + 1 + Available Protocols + 0 + + + + + 0 + 0 + + + + + + Prefix + 0 + + + + + 1 + 0 + + + + + + + + + + + + + + empty + + + network-server-symbolic + No Recent Servers + + + + + + + + list + + + 1 + 12 + 12 + 12 + 12 + 12 + + + Recent Servers + + + + + + + + 1 + 1 + 250 + 200 + + + + + 0 + + + + + + + + + + + + + + + + diff --git a/src/gtk/nautilusgtkplacesviewprivate.h b/src/gtk/nautilusgtkplacesviewprivate.h new file mode 100644 index 0000000..4cf6e3e --- /dev/null +++ b/src/gtk/nautilusgtkplacesviewprivate.h @@ -0,0 +1,55 @@ +/* nautilusgtkplacesview.h + * + * Copyright (C) 2015 Georges Basile Stavracas Neto + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published + * by the Free Software Foundation, either version 2.1 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 warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program. If not, see . + */ + +#ifndef NAUTILUS_GTK_PLACES_VIEW_H +#define NAUTILUS_GTK_PLACES_VIEW_H + +#if !defined (__GTK_H_INSIDE__) && !defined (GTK_COMPILATION) +#endif + +#include "nautilusgtkplacessidebarprivate.h" + +G_BEGIN_DECLS + +#define NAUTILUS_TYPE_GTK_PLACES_VIEW (nautilus_gtk_places_view_get_type ()) +#define NAUTILUS_GTK_PLACES_VIEW(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), NAUTILUS_TYPE_GTK_PLACES_VIEW, NautilusGtkPlacesView)) +#define NAUTILUS_GTK_PLACES_VIEW_CLASS(klass)(G_TYPE_CHECK_CLASS_CAST ((klass), NAUTILUS_TYPE_GTK_PLACES_VIEW, NautilusGtkPlacesViewClass)) +#define NAUTILUS_IS_GTK_PLACES_VIEW(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), NAUTILUS_TYPE_GTK_PLACES_VIEW)) +#define NAUTILUS_IS_GTK_PLACES_VIEW_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), NAUTILUS_TYPE_GTK_PLACES_VIEW)) +#define NAUTILUS_GTK_PLACES_VIEW_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), NAUTILUS_TYPE_GTK_PLACES_VIEW, NautilusGtkPlacesViewClass)) + +typedef struct _NautilusGtkPlacesView NautilusGtkPlacesView; +typedef struct _NautilusGtkPlacesViewClass NautilusGtkPlacesViewClass; + +GType nautilus_gtk_places_view_get_type (void) G_GNUC_CONST; + +NautilusGtkPlacesOpenFlags nautilus_gtk_places_view_get_open_flags (NautilusGtkPlacesView *view); +void nautilus_gtk_places_view_set_open_flags (NautilusGtkPlacesView *view, + NautilusGtkPlacesOpenFlags flags); + +const char * nautilus_gtk_places_view_get_search_query (NautilusGtkPlacesView *view); +void nautilus_gtk_places_view_set_search_query (NautilusGtkPlacesView *view, + const char *query_text); + +gboolean nautilus_gtk_places_view_get_loading (NautilusGtkPlacesView *view); + +GtkWidget * nautilus_gtk_places_view_new (void); + +G_END_DECLS + +#endif /* NAUTILUS_GTK_PLACES_VIEW_H */ diff --git a/src/gtk/nautilusgtkplacesviewrow.c b/src/gtk/nautilusgtkplacesviewrow.c new file mode 100644 index 0000000..64d8896 --- /dev/null +++ b/src/gtk/nautilusgtkplacesviewrow.c @@ -0,0 +1,508 @@ +/* nautilusgtkplacesviewrow.c + * + * Copyright (C) 2015 Georges Basile Stavracas Neto + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation, either version 2.1 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 warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program. If not, see . + */ + +#include "config.h" +#include +#include +#include "nautilus-application.h" +#include "nautilus-enum-types.h" + +#include + +#include "nautilusgtkplacesviewrowprivate.h" + +/* As this widget is shared with Nautilus, we use this guard to + * ensure that internally we only include the files that we need + * instead of including gtk.h + */ +#ifdef GTK_COMPILATION +#else +#include +#endif + +struct _NautilusGtkPlacesViewRow +{ + GtkListBoxRow parent_instance; + + GtkLabel *available_space_label; + GtkStack *mount_stack; + GtkSpinner *busy_spinner; + GtkButton *eject_button; + GtkImage *eject_icon; + GtkImage *icon_image; + GtkLabel *name_label; + GtkLabel *path_label; + + GVolume *volume; + GMount *mount; + GFile *file; + + GCancellable *cancellable; + + int is_network : 1; +}; + +G_DEFINE_TYPE (NautilusGtkPlacesViewRow, nautilus_gtk_places_view_row, GTK_TYPE_LIST_BOX_ROW) + +enum { + PROP_0, + PROP_ICON, + PROP_NAME, + PROP_PATH, + PROP_VOLUME, + PROP_MOUNT, + PROP_FILE, + PROP_IS_NETWORK, + LAST_PROP +}; + +static GParamSpec *properties [LAST_PROP]; + +static void +measure_available_space_finished (GObject *object, + GAsyncResult *res, + gpointer user_data) +{ + NautilusGtkPlacesViewRow *row = user_data; + GFileInfo *info; + GError *error; + guint64 free_space; + guint64 total_space; + char *formatted_free_size; + char *formatted_total_size; + char *label; + guint plural_form; + + error = NULL; + + info = g_file_query_filesystem_info_finish (G_FILE (object), + res, + &error); + + if (error) + { + if (!g_error_matches (error, G_IO_ERROR, G_IO_ERROR_CANCELLED) && + !g_error_matches (error, G_IO_ERROR, G_IO_ERROR_NOT_MOUNTED)) + { + g_warning ("Failed to measure available space: %s", error->message); + } + + g_clear_error (&error); + goto out; + } + + if (!g_file_info_has_attribute (info, G_FILE_ATTRIBUTE_FILESYSTEM_FREE) || + !g_file_info_has_attribute (info, G_FILE_ATTRIBUTE_FILESYSTEM_SIZE)) + { + g_object_unref (info); + goto out; + } + + free_space = g_file_info_get_attribute_uint64 (info, G_FILE_ATTRIBUTE_FILESYSTEM_FREE); + total_space = g_file_info_get_attribute_uint64 (info, G_FILE_ATTRIBUTE_FILESYSTEM_SIZE); + + formatted_free_size = g_format_size (free_space); + formatted_total_size = g_format_size (total_space); + + /* read g_format_size code in glib for further understanding */ + plural_form = free_space < 1000 ? free_space : free_space % 1000 + 1000; + + /* Translators: respectively, free and total space of the drive. The plural form + * should be based on the free space available. + * i.e. 1 GB / 24 GB available. + */ + label = g_strdup_printf (dngettext (GETTEXT_PACKAGE, "%s / %s available", "%s / %s available", plural_form), + formatted_free_size, formatted_total_size); + + gtk_label_set_label (row->available_space_label, label); + + g_object_unref (info); + g_free (formatted_total_size); + g_free (formatted_free_size); + g_free (label); +out: + g_object_unref (object); +} + +static void +measure_available_space (NautilusGtkPlacesViewRow *row) +{ + gboolean skip_measure; + gboolean should_measure; + g_autoptr (GFile) root = NULL; + + skip_measure = FALSE; + if (nautilus_application_is_sandboxed ()) + { + root = g_file_new_for_uri ("file:///"); + if (row->file != NULL) + skip_measure = g_file_equal (root, row->file); + } + + should_measure = ((row->volume || row->mount || row->file) && + !row->is_network && !skip_measure); + + gtk_label_set_label (row->available_space_label, ""); + gtk_widget_set_visible (GTK_WIDGET (row->available_space_label), should_measure); + + if (should_measure) + { + GFile *file = NULL; + + if (row->file) + { + file = g_object_ref (row->file); + } + else if (row->mount) + { + file = g_mount_get_root (row->mount); + } + else if (row->volume) + { + GMount *mount; + + mount = g_volume_get_mount (row->volume); + + if (mount) + file = g_mount_get_root (row->mount); + + g_clear_object (&mount); + } + + if (file) + { + g_cancellable_cancel (row->cancellable); + g_clear_object (&row->cancellable); + row->cancellable = g_cancellable_new (); + + g_file_query_filesystem_info_async (file, + G_FILE_ATTRIBUTE_FILESYSTEM_FREE "," G_FILE_ATTRIBUTE_FILESYSTEM_SIZE, + G_PRIORITY_DEFAULT, + row->cancellable, + measure_available_space_finished, + row); + } + } +} + +static void +nautilus_gtk_places_view_row_finalize (GObject *object) +{ + NautilusGtkPlacesViewRow *self = NAUTILUS_GTK_PLACES_VIEW_ROW (object); + + g_cancellable_cancel (self->cancellable); + + g_clear_object (&self->volume); + g_clear_object (&self->mount); + g_clear_object (&self->file); + g_clear_object (&self->cancellable); + + G_OBJECT_CLASS (nautilus_gtk_places_view_row_parent_class)->finalize (object); +} + +static void +nautilus_gtk_places_view_row_get_property (GObject *object, + guint prop_id, + GValue *value, + GParamSpec *pspec) +{ + NautilusGtkPlacesViewRow *self; + + self = NAUTILUS_GTK_PLACES_VIEW_ROW (object); + + switch (prop_id) + { + case PROP_ICON: + g_value_set_object (value, gtk_image_get_gicon (self->icon_image)); + break; + + case PROP_NAME: + g_value_set_string (value, gtk_label_get_label (self->name_label)); + break; + + case PROP_PATH: + g_value_set_string (value, gtk_label_get_label (self->path_label)); + break; + + case PROP_VOLUME: + g_value_set_object (value, self->volume); + break; + + case PROP_MOUNT: + g_value_set_object (value, self->mount); + break; + + case PROP_FILE: + g_value_set_object (value, self->file); + break; + + case PROP_IS_NETWORK: + g_value_set_boolean (value, self->is_network); + break; + + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); + } +} + +static void +nautilus_gtk_places_view_row_set_property (GObject *object, + guint prop_id, + const GValue *value, + GParamSpec *pspec) +{ + NautilusGtkPlacesViewRow *self = NAUTILUS_GTK_PLACES_VIEW_ROW (object); + + switch (prop_id) + { + case PROP_ICON: + gtk_image_set_from_gicon (self->icon_image, g_value_get_object (value)); + break; + + case PROP_NAME: + gtk_label_set_label (self->name_label, g_value_get_string (value)); + break; + + case PROP_PATH: + gtk_label_set_label (self->path_label, g_value_get_string (value)); + break; + + case PROP_VOLUME: + g_set_object (&self->volume, g_value_get_object (value)); + break; + + case PROP_MOUNT: + g_set_object (&self->mount, g_value_get_object (value)); + if (self->mount != NULL) + { + gtk_stack_set_visible_child (self->mount_stack, GTK_WIDGET (self->eject_button)); + gtk_widget_set_child_visible (GTK_WIDGET (self->mount_stack), TRUE); + } + else + { + gtk_widget_set_child_visible (GTK_WIDGET (self->mount_stack), FALSE); + } + measure_available_space (self); + break; + + case PROP_FILE: + g_set_object (&self->file, g_value_get_object (value)); + measure_available_space (self); + break; + + case PROP_IS_NETWORK: + nautilus_gtk_places_view_row_set_is_network (self, g_value_get_boolean (value)); + measure_available_space (self); + break; + + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); + } +} + +static void +nautilus_gtk_places_view_row_size_allocate (GtkWidget *widget, + int width, + int height, + int baseline) +{ + GtkWidget *menu = GTK_WIDGET (g_object_get_data (G_OBJECT (widget), "menu")); + + GTK_WIDGET_CLASS (nautilus_gtk_places_view_row_parent_class)->size_allocate (widget, width, height, baseline); + if (menu) + gtk_popover_present (GTK_POPOVER (menu)); +} + +static void +nautilus_gtk_places_view_row_class_init (NautilusGtkPlacesViewRowClass *klass) +{ + GObjectClass *object_class = G_OBJECT_CLASS (klass); + GtkWidgetClass *widget_class = GTK_WIDGET_CLASS (klass); + + object_class->finalize = nautilus_gtk_places_view_row_finalize; + object_class->get_property = nautilus_gtk_places_view_row_get_property; + object_class->set_property = nautilus_gtk_places_view_row_set_property; + + widget_class->size_allocate = nautilus_gtk_places_view_row_size_allocate; + + properties[PROP_ICON] = + g_param_spec_object ("icon", + "Icon of the row", + "The icon representing the volume", + G_TYPE_ICON, + G_PARAM_READWRITE); + + properties[PROP_NAME] = + g_param_spec_string ("name", + "Name of the volume", + "The name of the volume", + "", + G_PARAM_READWRITE); + + properties[PROP_PATH] = + g_param_spec_string ("path", + "Path of the volume", + "The path of the volume", + "", + G_PARAM_READWRITE); + + properties[PROP_VOLUME] = + g_param_spec_object ("volume", + "Volume represented by the row", + "The volume represented by the row", + G_TYPE_VOLUME, + G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY); + + properties[PROP_MOUNT] = + g_param_spec_object ("mount", + "Mount represented by the row", + "The mount point represented by the row, if any", + G_TYPE_MOUNT, + G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY); + + properties[PROP_FILE] = + g_param_spec_object ("file", + "File represented by the row", + "The file represented by the row, if any", + G_TYPE_FILE, + G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY); + + properties[PROP_IS_NETWORK] = + g_param_spec_boolean ("is-network", + "Whether the row represents a network location", + "Whether the row represents a network location", + FALSE, + G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY); + + g_object_class_install_properties (object_class, LAST_PROP, properties); + + gtk_widget_class_set_template_from_resource (widget_class, "/org/gnome/nautilus/gtk/ui/nautilusgtkplacesviewrow.ui"); + + gtk_widget_class_bind_template_child (widget_class, NautilusGtkPlacesViewRow, available_space_label); + gtk_widget_class_bind_template_child (widget_class, NautilusGtkPlacesViewRow, mount_stack); + gtk_widget_class_bind_template_child (widget_class, NautilusGtkPlacesViewRow, busy_spinner); + gtk_widget_class_bind_template_child (widget_class, NautilusGtkPlacesViewRow, eject_button); + gtk_widget_class_bind_template_child (widget_class, NautilusGtkPlacesViewRow, eject_icon); + gtk_widget_class_bind_template_child (widget_class, NautilusGtkPlacesViewRow, icon_image); + gtk_widget_class_bind_template_child (widget_class, NautilusGtkPlacesViewRow, name_label); + gtk_widget_class_bind_template_child (widget_class, NautilusGtkPlacesViewRow, path_label); +} + +static void +nautilus_gtk_places_view_row_init (NautilusGtkPlacesViewRow *self) +{ + gtk_widget_init_template (GTK_WIDGET (self)); +} + +GtkWidget* +nautilus_gtk_places_view_row_new (GVolume *volume, + GMount *mount) +{ + return g_object_new (NAUTILUS_TYPE_GTK_PLACES_VIEW_ROW, + "volume", volume, + "mount", mount, + NULL); +} + +GMount* +nautilus_gtk_places_view_row_get_mount (NautilusGtkPlacesViewRow *row) +{ + g_return_val_if_fail (NAUTILUS_IS_GTK_PLACES_VIEW_ROW (row), NULL); + + return row->mount; +} + +GVolume* +nautilus_gtk_places_view_row_get_volume (NautilusGtkPlacesViewRow *row) +{ + g_return_val_if_fail (NAUTILUS_IS_GTK_PLACES_VIEW_ROW (row), NULL); + + return row->volume; +} + +GFile* +nautilus_gtk_places_view_row_get_file (NautilusGtkPlacesViewRow *row) +{ + g_return_val_if_fail (NAUTILUS_IS_GTK_PLACES_VIEW_ROW (row), NULL); + + return row->file; +} + +GtkWidget* +nautilus_gtk_places_view_row_get_eject_button (NautilusGtkPlacesViewRow *row) +{ + g_return_val_if_fail (NAUTILUS_IS_GTK_PLACES_VIEW_ROW (row), NULL); + + return GTK_WIDGET (row->eject_button); +} + +void +nautilus_gtk_places_view_row_set_busy (NautilusGtkPlacesViewRow *row, + gboolean is_busy) +{ + g_return_if_fail (NAUTILUS_IS_GTK_PLACES_VIEW_ROW (row)); + + if (is_busy) + { + gtk_stack_set_visible_child (row->mount_stack, GTK_WIDGET (row->busy_spinner)); + gtk_widget_set_child_visible (GTK_WIDGET (row->mount_stack), TRUE); + gtk_spinner_start (row->busy_spinner); + } + else + { + gtk_widget_set_child_visible (GTK_WIDGET (row->mount_stack), FALSE); + gtk_spinner_stop (row->busy_spinner); + } +} + +gboolean +nautilus_gtk_places_view_row_get_is_network (NautilusGtkPlacesViewRow *row) +{ + g_return_val_if_fail (NAUTILUS_IS_GTK_PLACES_VIEW_ROW (row), FALSE); + + return row->is_network; +} + +void +nautilus_gtk_places_view_row_set_is_network (NautilusGtkPlacesViewRow *row, + gboolean is_network) +{ + if (row->is_network != is_network) + { + row->is_network = is_network; + + gtk_image_set_from_icon_name (row->eject_icon, "media-eject-symbolic"); + gtk_widget_set_tooltip_text (GTK_WIDGET (row->eject_button), is_network ? _("Disconnect") : _("Unmount")); + } +} + +void +nautilus_gtk_places_view_row_set_path_size_group (NautilusGtkPlacesViewRow *row, + GtkSizeGroup *group) +{ + if (group) + gtk_size_group_add_widget (group, GTK_WIDGET (row->path_label)); +} + +void +nautilus_gtk_places_view_row_set_space_size_group (NautilusGtkPlacesViewRow *row, + GtkSizeGroup *group) +{ + if (group) + gtk_size_group_add_widget (group, GTK_WIDGET (row->available_space_label)); +} diff --git a/src/gtk/nautilusgtkplacesviewrow.ui b/src/gtk/nautilusgtkplacesviewrow.ui new file mode 100644 index 0000000..06c8041 --- /dev/null +++ b/src/gtk/nautilusgtkplacesviewrow.ui @@ -0,0 +1,83 @@ + + + + diff --git a/src/gtk/nautilusgtkplacesviewrowprivate.h b/src/gtk/nautilusgtkplacesviewrowprivate.h new file mode 100644 index 0000000..d54b918 --- /dev/null +++ b/src/gtk/nautilusgtkplacesviewrowprivate.h @@ -0,0 +1,59 @@ +/* nautilusgtkplacesviewrow.h + * + * Copyright (C) 2015 Georges Basile Stavracas Neto + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation, either version 2.1 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 warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program. If not, see . + */ + +#ifndef NAUTILUS_GTK_PLACES_VIEW_ROW_H +#define NAUTILUS_GTK_PLACES_VIEW_ROW_H + +#if !defined (__GTK_H_INSIDE__) && !defined (GTK_COMPILATION) +#endif + + +G_BEGIN_DECLS + +#define NAUTILUS_TYPE_GTK_PLACES_VIEW_ROW (nautilus_gtk_places_view_row_get_type()) + + G_DECLARE_FINAL_TYPE (NautilusGtkPlacesViewRow, nautilus_gtk_places_view_row, NAUTILUS, GTK_PLACES_VIEW_ROW, GtkListBoxRow) + +GtkWidget* nautilus_gtk_places_view_row_new (GVolume *volume, + GMount *mount); + +GtkWidget* nautilus_gtk_places_view_row_get_eject_button (NautilusGtkPlacesViewRow *row); + +GMount* nautilus_gtk_places_view_row_get_mount (NautilusGtkPlacesViewRow *row); + +GVolume* nautilus_gtk_places_view_row_get_volume (NautilusGtkPlacesViewRow *row); + +GFile* nautilus_gtk_places_view_row_get_file (NautilusGtkPlacesViewRow *row); + +void nautilus_gtk_places_view_row_set_busy (NautilusGtkPlacesViewRow *row, + gboolean is_busy); + +gboolean nautilus_gtk_places_view_row_get_is_network (NautilusGtkPlacesViewRow *row); + +void nautilus_gtk_places_view_row_set_is_network (NautilusGtkPlacesViewRow *row, + gboolean is_network); + +void nautilus_gtk_places_view_row_set_path_size_group (NautilusGtkPlacesViewRow *row, + GtkSizeGroup *group); + +void nautilus_gtk_places_view_row_set_space_size_group (NautilusGtkPlacesViewRow *row, + GtkSizeGroup *group); + +G_END_DECLS + +#endif /* NAUTILUS_GTK_PLACES_VIEW_ROW_H */ diff --git a/src/gtk/nautilusgtksidebarrow.c b/src/gtk/nautilusgtksidebarrow.c new file mode 100644 index 0000000..9b6ebaf --- /dev/null +++ b/src/gtk/nautilusgtksidebarrow.c @@ -0,0 +1,692 @@ +/* nautilusgtksidebarrow.c + * + * Copyright (C) 2015 Carlos Soriano + * + * This file is free software; you can redistribute it and/or modify it + * under the terms of the GNU Lesser General Public License as + * published by the Free Software Foundation; either version 2.1 of the + * License, or (at your option) any later version. + * + * This file is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program. If not, see . + */ + +#include "config.h" +#include +#include +#include "nautilus-enum-types.h" +#include "nautilus-file.h" + +#include "nautilusgtksidebarrowprivate.h" +/* For section and place type enums */ +#include "nautilusgtkplacessidebarprivate.h" + +#include + +struct _NautilusGtkSidebarRow +{ + GtkListBoxRow parent_instance; + GIcon *start_icon; + GIcon *end_icon; + GtkWidget *start_icon_widget; + GtkWidget *end_icon_widget; + char *label; + char *tooltip; + char *eject_tooltip; + GtkWidget *label_widget; + gboolean ejectable; + GtkWidget *eject_button; + int order_index; + NautilusGtkPlacesSectionType section_type; + NautilusGtkPlacesPlaceType place_type; + char *uri; + NautilusFile *file; + GDrive *drive; + GVolume *volume; + GMount *mount; + GObject *cloud_provider_account; + gboolean placeholder; + NautilusGtkPlacesSidebar *sidebar; + GtkWidget *revealer; + GtkWidget *busy_spinner; +}; + +G_DEFINE_TYPE (NautilusGtkSidebarRow, nautilus_gtk_sidebar_row, GTK_TYPE_LIST_BOX_ROW) + +enum +{ + PROP_0, + PROP_START_ICON, + PROP_END_ICON, + PROP_LABEL, + PROP_TOOLTIP, + PROP_EJECT_TOOLTIP, + PROP_EJECTABLE, + PROP_SIDEBAR, + PROP_ORDER_INDEX, + PROP_SECTION_TYPE, + PROP_PLACE_TYPE, + PROP_URI, + PROP_NAUTILUS_FILE, + PROP_DRIVE, + PROP_VOLUME, + PROP_MOUNT, + PROP_CLOUD_PROVIDER_ACCOUNT, + PROP_PLACEHOLDER, + LAST_PROP +}; + +static GParamSpec *properties [LAST_PROP]; + +static void +cloud_row_update (NautilusGtkSidebarRow *self) +{ + CloudProvidersAccount *account; + GIcon *end_icon; + int provider_status; + + account = CLOUD_PROVIDERS_ACCOUNT (self->cloud_provider_account); + provider_status = cloud_providers_account_get_status (account); + switch (provider_status) + { + case CLOUD_PROVIDERS_ACCOUNT_STATUS_IDLE: + end_icon = NULL; + break; + + case CLOUD_PROVIDERS_ACCOUNT_STATUS_SYNCING: + end_icon = g_themed_icon_new ("emblem-synchronizing-symbolic"); + break; + + case CLOUD_PROVIDERS_ACCOUNT_STATUS_ERROR: + end_icon = g_themed_icon_new ("dialog-warning-symbolic"); + break; + + default: + return; + } + + g_object_set (self, + "label", cloud_providers_account_get_name (account), + NULL); + g_object_set (self, + "tooltip", cloud_providers_account_get_status_details (account), + NULL); + g_object_set (self, + "end-icon", end_icon, + NULL); + + if (end_icon != NULL) + g_object_unref (end_icon); +} + +static void +nautilus_gtk_sidebar_row_get_property (GObject *object, + guint prop_id, + GValue *value, + GParamSpec *pspec) +{ + NautilusGtkSidebarRow *self = NAUTILUS_GTK_SIDEBAR_ROW (object); + + switch (prop_id) + { + case PROP_SIDEBAR: + g_value_set_object (value, self->sidebar); + break; + + case PROP_START_ICON: + g_value_set_object (value, self->start_icon); + break; + + case PROP_END_ICON: + g_value_set_object (value, self->end_icon); + break; + + case PROP_LABEL: + g_value_set_string (value, self->label); + break; + + case PROP_TOOLTIP: + g_value_set_string (value, self->tooltip); + break; + + case PROP_EJECT_TOOLTIP: + g_value_set_string (value, self->eject_tooltip); + break; + + case PROP_EJECTABLE: + g_value_set_boolean (value, self->ejectable); + break; + + case PROP_ORDER_INDEX: + g_value_set_int (value, self->order_index); + break; + + case PROP_SECTION_TYPE: + g_value_set_enum (value, self->section_type); + break; + + case PROP_PLACE_TYPE: + g_value_set_enum (value, self->place_type); + break; + + case PROP_URI: + g_value_set_string (value, self->uri); + break; + + case PROP_NAUTILUS_FILE: + g_value_set_object (value, self->file); + break; + + case PROP_DRIVE: + g_value_set_object (value, self->drive); + break; + + case PROP_VOLUME: + g_value_set_object (value, self->volume); + break; + + case PROP_MOUNT: + g_value_set_object (value, self->mount); + break; + + case PROP_CLOUD_PROVIDER_ACCOUNT: + g_value_set_object (value, self->cloud_provider_account); + break; + + case PROP_PLACEHOLDER: + g_value_set_boolean (value, self->placeholder); + break; + + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); + } +} + +static void +nautilus_gtk_sidebar_row_set_property (GObject *object, + guint prop_id, + const GValue *value, + GParamSpec *pspec) +{ + NautilusGtkSidebarRow *self = NAUTILUS_GTK_SIDEBAR_ROW (object); + + switch (prop_id) + { + case PROP_SIDEBAR: + self->sidebar = g_value_get_object (value); + break; + + case PROP_START_ICON: + { + g_clear_object (&self->start_icon); + object = g_value_get_object (value); + if (object != NULL) + { + self->start_icon = G_ICON (g_object_ref (object)); + gtk_image_set_from_gicon (GTK_IMAGE (self->start_icon_widget), self->start_icon); + } + else + { + gtk_image_clear (GTK_IMAGE (self->start_icon_widget)); + } + break; + } + + case PROP_END_ICON: + { + g_clear_object (&self->end_icon); + object = g_value_get_object (value); + if (object != NULL) + { + self->end_icon = G_ICON (g_object_ref (object)); + gtk_image_set_from_gicon (GTK_IMAGE (self->end_icon_widget), self->end_icon); + gtk_widget_show (self->end_icon_widget); + } + else + { + gtk_image_clear (GTK_IMAGE (self->end_icon_widget)); + gtk_widget_hide (self->end_icon_widget); + } + break; + } + + case PROP_LABEL: + g_free (self->label); + self->label = g_strdup (g_value_get_string (value)); + gtk_label_set_text (GTK_LABEL (self->label_widget), self->label); + break; + + case PROP_TOOLTIP: + g_free (self->tooltip); + self->tooltip = g_strdup (g_value_get_string (value)); + gtk_widget_set_tooltip_text (GTK_WIDGET (self), self->tooltip); + break; + + case PROP_EJECT_TOOLTIP: + g_free (self->eject_tooltip); + self->eject_tooltip = g_strdup (g_value_get_string (value)); + gtk_widget_set_tooltip_text (GTK_WIDGET (self->eject_button), self->eject_tooltip); + break; + + case PROP_EJECTABLE: + self->ejectable = g_value_get_boolean (value); + if (self->ejectable) + gtk_widget_show (self->eject_button); + else + gtk_widget_hide (self->eject_button); + break; + + case PROP_ORDER_INDEX: + self->order_index = g_value_get_int (value); + break; + + case PROP_SECTION_TYPE: + self->section_type = g_value_get_enum (value); + if (self->section_type == NAUTILUS_GTK_PLACES_SECTION_COMPUTER || + self->section_type == NAUTILUS_GTK_PLACES_SECTION_OTHER_LOCATIONS) + gtk_label_set_ellipsize (GTK_LABEL (self->label_widget), PANGO_ELLIPSIZE_NONE); + else + gtk_label_set_ellipsize (GTK_LABEL (self->label_widget), PANGO_ELLIPSIZE_END); + break; + + case PROP_PLACE_TYPE: + self->place_type = g_value_get_enum (value); + break; + + case PROP_URI: + g_free (self->uri); + self->uri = g_strdup (g_value_get_string (value)); + if (self->uri != NULL) + { + self->file = nautilus_file_get_by_uri (self->uri); + if (self->file != NULL) + nautilus_file_call_when_ready (self->file, NAUTILUS_FILE_ATTRIBUTE_MOUNT, NULL, NULL); + } + break; + + case PROP_DRIVE: + g_set_object (&self->drive, g_value_get_object (value)); + break; + + case PROP_VOLUME: + g_set_object (&self->volume, g_value_get_object (value)); + break; + + case PROP_MOUNT: + g_set_object (&self->mount, g_value_get_object (value)); + break; + + case PROP_CLOUD_PROVIDER_ACCOUNT: + if (self->cloud_provider_account != NULL) + g_signal_handlers_disconnect_by_data (self->cloud_provider_account, self); + + self->cloud_provider_account = g_value_dup_object (value); + + if (self->cloud_provider_account != NULL) + { + g_signal_connect_swapped (self->cloud_provider_account, "notify::name", + G_CALLBACK (cloud_row_update), self); + g_signal_connect_swapped (self->cloud_provider_account, "notify::status", + G_CALLBACK (cloud_row_update), self); + g_signal_connect_swapped (self->cloud_provider_account, "notify::status-details", + G_CALLBACK (cloud_row_update), self); + } + break; + + case PROP_PLACEHOLDER: + { + self->placeholder = g_value_get_boolean (value); + if (self->placeholder) + { + g_clear_object (&self->start_icon); + g_clear_object (&self->end_icon); + g_free (self->label); + self->label = NULL; + g_free (self->tooltip); + self->tooltip = NULL; + self->eject_tooltip = NULL; + gtk_widget_set_tooltip_text (GTK_WIDGET (self), NULL); + self->ejectable = FALSE; + self->section_type = NAUTILUS_GTK_PLACES_SECTION_BOOKMARKS; + self->place_type = NAUTILUS_GTK_PLACES_BOOKMARK_PLACEHOLDER; + g_free (self->uri); + self->uri = NULL; + g_clear_object (&self->drive); + g_clear_object (&self->volume); + g_clear_object (&self->mount); + g_clear_object (&self->cloud_provider_account); + + gtk_list_box_row_set_child (GTK_LIST_BOX_ROW (self), NULL); + + gtk_widget_add_css_class (GTK_WIDGET (self), "sidebar-placeholder-row"); + } + + break; + } + + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); + } +} + +static void +on_child_revealed (GObject *self, + GParamSpec *pspec, + gpointer user_data) +{ + /* We need to hide the actual widget because if not the GtkListBoxRow will + * still allocate the paddings, even if the revealer is not revealed, and + * therefore the row will be still somewhat visible. */ + if (!gtk_revealer_get_reveal_child (GTK_REVEALER (self))) + gtk_widget_hide (GTK_WIDGET (NAUTILUS_GTK_SIDEBAR_ROW (user_data))); +} + +void +nautilus_gtk_sidebar_row_reveal (NautilusGtkSidebarRow *self) +{ + gtk_widget_show (GTK_WIDGET (self)); + gtk_revealer_set_reveal_child (GTK_REVEALER (self->revealer), TRUE); +} + +void +nautilus_gtk_sidebar_row_hide (NautilusGtkSidebarRow *self, + gboolean immediate) +{ + guint transition_duration; + + transition_duration = gtk_revealer_get_transition_duration (GTK_REVEALER (self->revealer)); + if (immediate) + gtk_revealer_set_transition_duration (GTK_REVEALER (self->revealer), 0); + + gtk_revealer_set_reveal_child (GTK_REVEALER (self->revealer), FALSE); + + gtk_revealer_set_transition_duration (GTK_REVEALER (self->revealer), transition_duration); +} + +void +nautilus_gtk_sidebar_row_set_start_icon (NautilusGtkSidebarRow *self, + GIcon *icon) +{ + g_return_if_fail (NAUTILUS_IS_GTK_SIDEBAR_ROW (self)); + + if (self->start_icon != icon) + { + g_set_object (&self->start_icon, icon); + if (self->start_icon != NULL) + gtk_image_set_from_gicon (GTK_IMAGE (self->start_icon_widget), self->start_icon); + else + gtk_image_clear (GTK_IMAGE (self->start_icon_widget)); + + g_object_notify_by_pspec (G_OBJECT (self), properties [PROP_START_ICON]); + } +} + +void +nautilus_gtk_sidebar_row_set_end_icon (NautilusGtkSidebarRow *self, + GIcon *icon) +{ + g_return_if_fail (NAUTILUS_IS_GTK_SIDEBAR_ROW (self)); + + if (self->end_icon != icon) + { + g_set_object (&self->end_icon, icon); + if (self->end_icon != NULL) + gtk_image_set_from_gicon (GTK_IMAGE (self->end_icon_widget), self->end_icon); + else + if (self->end_icon_widget != NULL) + gtk_image_clear (GTK_IMAGE (self->end_icon_widget)); + + g_object_notify_by_pspec (G_OBJECT (self), properties [PROP_END_ICON]); + } +} + +static void +nautilus_gtk_sidebar_row_finalize (GObject *object) +{ + NautilusGtkSidebarRow *self = NAUTILUS_GTK_SIDEBAR_ROW (object); + + g_clear_object (&self->start_icon); + g_clear_object (&self->end_icon); + g_free (self->label); + self->label = NULL; + g_free (self->tooltip); + self->tooltip = NULL; + g_free (self->eject_tooltip); + self->eject_tooltip = NULL; + g_free (self->uri); + self->uri = NULL; + nautilus_file_unref (self->file); + g_clear_object (&self->drive); + g_clear_object (&self->volume); + g_clear_object (&self->mount); + if (self->cloud_provider_account != NULL) + g_signal_handlers_disconnect_by_data (self->cloud_provider_account, self); + g_clear_object (&self->cloud_provider_account); + + G_OBJECT_CLASS (nautilus_gtk_sidebar_row_parent_class)->finalize (object); +} + +static void +nautilus_gtk_sidebar_row_init (NautilusGtkSidebarRow *self) +{ + gtk_widget_init_template (GTK_WIDGET (self)); + + gtk_widget_set_focus_on_click (GTK_WIDGET (self), FALSE); + + self->file = NULL; +} + +static void +nautilus_gtk_sidebar_row_class_init (NautilusGtkSidebarRowClass *klass) +{ + GObjectClass *object_class = G_OBJECT_CLASS (klass); + GtkWidgetClass *widget_class = GTK_WIDGET_CLASS (klass); + + object_class->get_property = nautilus_gtk_sidebar_row_get_property; + object_class->set_property = nautilus_gtk_sidebar_row_set_property; + object_class->finalize = nautilus_gtk_sidebar_row_finalize; + + properties [PROP_SIDEBAR] = + g_param_spec_object ("sidebar", + "Sidebar", + "Sidebar", + NAUTILUS_TYPE_GTK_PLACES_SIDEBAR, + (G_PARAM_READWRITE | + G_PARAM_CONSTRUCT_ONLY | + G_PARAM_STATIC_STRINGS)); + + properties [PROP_START_ICON] = + g_param_spec_object ("start-icon", + "start-icon", + "The start icon.", + G_TYPE_ICON, + (G_PARAM_READWRITE | + G_PARAM_STATIC_STRINGS)); + + properties [PROP_END_ICON] = + g_param_spec_object ("end-icon", + "end-icon", + "The end icon.", + G_TYPE_ICON, + (G_PARAM_READWRITE | + G_PARAM_STATIC_STRINGS)); + + properties [PROP_LABEL] = + g_param_spec_string ("label", + "label", + "The label text.", + NULL, + (G_PARAM_READWRITE | + G_PARAM_STATIC_STRINGS)); + + properties [PROP_TOOLTIP] = + g_param_spec_string ("tooltip", + "Tooltip", + "Tooltip", + NULL, + (G_PARAM_READWRITE | + G_PARAM_STATIC_STRINGS)); + + properties [PROP_EJECT_TOOLTIP] = + g_param_spec_string ("eject-tooltip", + "Eject Tooltip", + "Eject Tooltip", + NULL, + (G_PARAM_READWRITE | + G_PARAM_STATIC_STRINGS)); + + properties [PROP_EJECTABLE] = + g_param_spec_boolean ("ejectable", + "Ejectable", + "Ejectable", + FALSE, + (G_PARAM_READWRITE | + G_PARAM_STATIC_STRINGS)); + + properties [PROP_ORDER_INDEX] = + g_param_spec_int ("order-index", + "OrderIndex", + "Order Index", + 0, G_MAXINT, 0, + (G_PARAM_READWRITE | + G_PARAM_STATIC_STRINGS)); + + properties [PROP_SECTION_TYPE] = + g_param_spec_enum ("section-type", + "section type", + "The section type.", + NAUTILUS_TYPE_GTK_PLACES_SECTION_TYPE, + NAUTILUS_GTK_PLACES_SECTION_INVALID, + (G_PARAM_READWRITE | + G_PARAM_STATIC_STRINGS | + G_PARAM_CONSTRUCT_ONLY)); + + properties [PROP_PLACE_TYPE] = + g_param_spec_enum ("place-type", + "place type", + "The place type.", + NAUTILUS_TYPE_GTK_PLACES_PLACE_TYPE, + NAUTILUS_GTK_PLACES_INVALID, + (G_PARAM_READWRITE | + G_PARAM_STATIC_STRINGS | + G_PARAM_CONSTRUCT_ONLY)); + + properties [PROP_URI] = + g_param_spec_string ("uri", + "Uri", + "Uri", + NULL, + (G_PARAM_READWRITE | + G_PARAM_CONSTRUCT_ONLY | + G_PARAM_STATIC_STRINGS)); + + properties [PROP_NAUTILUS_FILE] = + g_param_spec_object ("file", + "File", + "Nautilus File", + NAUTILUS_TYPE_FILE, + (G_PARAM_READABLE | + G_PARAM_STATIC_STRINGS )); + + properties [PROP_DRIVE] = + g_param_spec_object ("drive", + "Drive", + "Drive", + G_TYPE_DRIVE, + (G_PARAM_READWRITE | + G_PARAM_CONSTRUCT_ONLY | + G_PARAM_STATIC_STRINGS)); + + properties [PROP_VOLUME] = + g_param_spec_object ("volume", + "Volume", + "Volume", + G_TYPE_VOLUME, + (G_PARAM_READWRITE | + G_PARAM_CONSTRUCT_ONLY | + G_PARAM_STATIC_STRINGS)); + + properties [PROP_MOUNT] = + g_param_spec_object ("mount", + "Mount", + "Mount", + G_TYPE_MOUNT, + (G_PARAM_READWRITE | + G_PARAM_CONSTRUCT_ONLY | + G_PARAM_STATIC_STRINGS)); + + properties [PROP_CLOUD_PROVIDER_ACCOUNT] = + g_param_spec_object ("cloud-provider-account", + "CloudProvidersAccount", + "CloudProvidersAccount", + G_TYPE_OBJECT, + (G_PARAM_READWRITE | + G_PARAM_STATIC_STRINGS)); + + properties [PROP_PLACEHOLDER] = + g_param_spec_boolean ("placeholder", + "Placeholder", + "Placeholder", + FALSE, + (G_PARAM_READWRITE | + G_PARAM_CONSTRUCT_ONLY | + G_PARAM_STATIC_STRINGS)); + + g_object_class_install_properties (object_class, LAST_PROP, properties); + + gtk_widget_class_set_template_from_resource (widget_class, + "/org/gnome/nautilus/gtk/ui/nautilusgtksidebarrow.ui"); + + gtk_widget_class_bind_template_child (widget_class, NautilusGtkSidebarRow, start_icon_widget); + gtk_widget_class_bind_template_child (widget_class, NautilusGtkSidebarRow, end_icon_widget); + gtk_widget_class_bind_template_child (widget_class, NautilusGtkSidebarRow, label_widget); + gtk_widget_class_bind_template_child (widget_class, NautilusGtkSidebarRow, eject_button); + gtk_widget_class_bind_template_child (widget_class, NautilusGtkSidebarRow, revealer); + gtk_widget_class_bind_template_child (widget_class, NautilusGtkSidebarRow, busy_spinner); + + gtk_widget_class_bind_template_callback (widget_class, on_child_revealed); + gtk_widget_class_set_css_name (widget_class, "row"); +} + +NautilusGtkSidebarRow* +nautilus_gtk_sidebar_row_clone (NautilusGtkSidebarRow *self) +{ + return g_object_new (NAUTILUS_TYPE_GTK_SIDEBAR_ROW, + "sidebar", self->sidebar, + "start-icon", self->start_icon, + "end-icon", self->end_icon, + "label", self->label, + "tooltip", self->tooltip, + "eject-tooltip", self->eject_tooltip, + "ejectable", self->ejectable, + "order-index", self->order_index, + "section-type", self->section_type, + "place-type", self->place_type, + "uri", self->uri, + "file", self->file, + "drive", self->drive, + "volume", self->volume, + "mount", self->mount, + "cloud-provider-account", self->cloud_provider_account, + NULL); +} + +GtkWidget* +nautilus_gtk_sidebar_row_get_eject_button (NautilusGtkSidebarRow *self) +{ + return self->eject_button; +} + +void +nautilus_gtk_sidebar_row_set_busy (NautilusGtkSidebarRow *row, + gboolean is_busy) +{ + g_return_if_fail (NAUTILUS_IS_GTK_SIDEBAR_ROW (row)); + + gtk_widget_set_visible (row->busy_spinner, is_busy); +} diff --git a/src/gtk/nautilusgtksidebarrow.ui b/src/gtk/nautilusgtksidebarrow.ui new file mode 100644 index 0000000..95d91f9 --- /dev/null +++ b/src/gtk/nautilusgtksidebarrow.ui @@ -0,0 +1,69 @@ + + + + diff --git a/src/gtk/nautilusgtksidebarrowprivate.h b/src/gtk/nautilusgtksidebarrowprivate.h new file mode 100644 index 0000000..0bd9355 --- /dev/null +++ b/src/gtk/nautilusgtksidebarrowprivate.h @@ -0,0 +1,60 @@ +/* nautilusgtksidebarrowprivate.h + * + * Copyright (C) 2015 Carlos Soriano + * + * This file is free software; you can redistribute it and/or modify it + * under the terms of the GNU Lesser General Public License as + * published by the Free Software Foundation; either version 2.1 of the + * License, or (at your option) any later version. + * + * This file is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program. If not, see . + */ +#ifndef NAUTILUS_GTK_SIDEBAR_ROW_PRIVATE_H +#define NAUTILUS_GTK_SIDEBAR_ROW_PRIVATE_H + +#include + +G_BEGIN_DECLS + +#define NAUTILUS_TYPE_GTK_SIDEBAR_ROW (nautilus_gtk_sidebar_row_get_type()) +#define NAUTILUS_GTK_SIDEBAR_ROW(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), NAUTILUS_TYPE_GTK_SIDEBAR_ROW, NautilusGtkSidebarRow)) +#define NAUTILUS_GTK_SIDEBAR_ROW_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), NAUTILUS_TYPE_GTK_SIDEBAR_ROW, NautilusGtkSidebarRowClass)) +#define NAUTILUS_IS_GTK_SIDEBAR_ROW(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), NAUTILUS_TYPE_GTK_SIDEBAR_ROW)) +#define NAUTILUS_IS_GTK_SIDEBAR_ROW_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), NAUTILUS_TYPE_GTK_SIDEBAR_ROW)) +#define NAUTILUS_GTK_SIDEBAR_ROW_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), NAUTILUS_TYPE_GTK_SIDEBAR_ROW, NautilusGtkSidebarRowClass)) + +typedef struct _NautilusGtkSidebarRow NautilusGtkSidebarRow; +typedef struct _NautilusGtkSidebarRowClass NautilusGtkSidebarRowClass; + +struct _NautilusGtkSidebarRowClass +{ + GtkListBoxRowClass parent; +}; + +GType nautilus_gtk_sidebar_row_get_type (void) G_GNUC_CONST; + +NautilusGtkSidebarRow *nautilus_gtk_sidebar_row_new (void); +NautilusGtkSidebarRow *nautilus_gtk_sidebar_row_clone (NautilusGtkSidebarRow *self); + +/* Use these methods instead of gtk_widget_hide/show to use an animation */ +void nautilus_gtk_sidebar_row_hide (NautilusGtkSidebarRow *self, + gboolean immediate); +void nautilus_gtk_sidebar_row_reveal (NautilusGtkSidebarRow *self); + +GtkWidget *nautilus_gtk_sidebar_row_get_eject_button (NautilusGtkSidebarRow *self); +void nautilus_gtk_sidebar_row_set_start_icon (NautilusGtkSidebarRow *self, + GIcon *icon); +void nautilus_gtk_sidebar_row_set_end_icon (NautilusGtkSidebarRow *self, + GIcon *icon); +void nautilus_gtk_sidebar_row_set_busy (NautilusGtkSidebarRow *row, + gboolean is_busy); + +G_END_DECLS + +#endif /* NAUTILUS_GTK_SIDEBAR_ROW_PRIVATE_H */ diff --git a/src/meson.build b/src/meson.build new file mode 100644 index 0000000..383920c --- /dev/null +++ b/src/meson.build @@ -0,0 +1,345 @@ +resources = gnome.compile_resources( + 'nautilus-resources', + join_paths( + 'resources', 'nautilus.gresource.xml' + ), + source_dir: 'resources', + c_name: 'nautilus', + extra_args: '--manual-register' +) + +libnautilus_sources = [ + gnome.mkenums( + 'nautilus-enum-types', + c_template: 'nautilus-enum-types.c.template', + h_template: 'nautilus-enum-types.h.template', + sources: [ + 'gtk/nautilusgtkplacessidebarprivate.h', + 'nautilus-enums.h', + 'nautilus-search-popover.h', + 'nautilus-special-location-bar.h', + 'nautilus-query.h', + 'nautilus-search-provider.h' + ] + ), + resources, + gnome.gdbus_codegen( + 'nautilus-freedesktop-generated', + join_paths( + meson.project_source_root(), 'data', 'freedesktop-dbus-interfaces.xml' + ), + interface_prefix: 'org.freedesktop', + namespace: 'NautilusFreedesktop' + ), + gnome.gdbus_codegen( + 'nautilus-generated', + join_paths( + meson.project_source_root(), 'data', 'dbus-interfaces.xml' + ), + interface_prefix: 'org.gnome.Nautilus', + namespace: 'NautilusDBus' + ), + gnome.gdbus_codegen( + 'nautilus-generated2', + join_paths( + meson.project_source_root(), 'data', 'dbus-interfaces2.xml' + ), + interface_prefix: 'org.gnome.Nautilus', + namespace: 'NautilusDBus' + ), + gnome.gdbus_codegen( + 'nautilus-shell-search-provider-generated', + join_paths( + meson.project_source_root(), 'data', 'shell-search-provider-dbus-interfaces.xml' + ), + interface_prefix: 'org.gnome', + namespace: 'Nautilus' + ), + 'gtk/nautilusgtkbookmarksmanager.c', + 'gtk/nautilusgtkbookmarksmanagerprivate.h', + 'gtk/nautilusgtkplacessidebar.c', + 'gtk/nautilusgtkplacessidebarprivate.h', + 'gtk/nautilusgtksidebarrow.c', + 'gtk/nautilusgtksidebarrowprivate.h', + 'gtk/nautilusgtkplacesview.c', + 'gtk/nautilusgtkplacesviewprivate.h', + 'gtk/nautilusgtkplacesviewrow.c', + 'gtk/nautilusgtkplacesviewrowprivate.h', + 'nautilus-application.c', + 'nautilus-application.h', + 'nautilus-app-chooser.c', + 'nautilus-app-chooser.h', + 'nautilus-bookmark-list.c', + 'nautilus-bookmark-list.h', + 'nautilus-dbus-manager.c', + 'nautilus-dbus-manager.h', + 'nautilus-error-reporting.c', + 'nautilus-error-reporting.h', + 'nautilus-preferences-window.c', + 'nautilus-preferences-window.h', + 'nautilus-files-view.c', + 'nautilus-files-view.h', + 'nautilus-files-view-dnd.c', + 'nautilus-files-view-dnd.h', + 'nautilus-floating-bar.c', + 'nautilus-floating-bar.h', + 'nautilus-freedesktop-dbus.c', + 'nautilus-freedesktop-dbus.h', + 'nautilus-grid-cell.c', + 'nautilus-grid-cell.h', + 'nautilus-grid-view.c', + 'nautilus-grid-view.h', + 'nautilus-history-controls.c', + 'nautilus-history-controls.h', + 'nautilus-label-cell.c', + 'nautilus-label-cell.h', + 'nautilus-list-base.c', + 'nautilus-list-base.h', + 'nautilus-list-view.c', + 'nautilus-list-view.h', + 'nautilus-location-entry.c', + 'nautilus-location-entry.h', + 'nautilus-mime-actions.c', + 'nautilus-mime-actions.h', + 'nautilus-name-cell.c', + 'nautilus-name-cell.h', + 'nautilus-pathbar.c', + 'nautilus-pathbar.h', + 'nautilus-places-view.c', + 'nautilus-places-view.h', + 'nautilus-previewer.c', + 'nautilus-previewer.h', + 'nautilus-progress-indicator.c', + 'nautilus-progress-indicator.h', + 'nautilus-progress-info-widget.c', + 'nautilus-progress-info-widget.h', + 'nautilus-progress-persistence-handler.c', + 'nautilus-progress-persistence-handler.h', + 'nautilus-properties-window.c', + 'nautilus-properties-window.h', + 'nautilus-query-editor.c', + 'nautilus-query-editor.h', + 'nautilus-search-popover.c', + 'nautilus-self-check-functions.c', + 'nautilus-self-check-functions.h', + 'nautilus-shell-search-provider.c', + 'nautilus-special-location-bar.c', + 'nautilus-star-cell.c', + 'nautilus-star-cell.h', + 'nautilus-toolbar.c', + 'nautilus-toolbar.h', + 'nautilus-toolbar-menu-sections.h', + 'nautilus-view.c', + 'nautilus-view.h', + 'nautilus-view-cell.c', + 'nautilus-view-cell.h', + 'nautilus-view-controls.c', + 'nautilus-view-controls.h', + 'nautilus-view-item.c', + 'nautilus-view-item.h', + 'nautilus-view-model.c', + 'nautilus-view-model.h', + 'nautilus-window-slot.c', + 'nautilus-window-slot.h', + 'nautilus-window-slot-dnd.c', + 'nautilus-window-slot-dnd.h', + 'nautilus-window.c', + 'nautilus-window.h', + 'nautilus-x-content-bar.c', + 'nautilus-x-content-bar.h', + 'nautilus-bookmark.c', + 'nautilus-bookmark.h', + 'nautilus-clipboard.c', + 'nautilus-clipboard.h', + 'nautilus-column-chooser.c', + 'nautilus-column-chooser.h', + 'nautilus-column-utilities.c', + 'nautilus-column-utilities.h', + 'nautilus-dbus-launcher.c', + 'nautilus-dbus-launcher.h', + 'nautilus-debug.c', + 'nautilus-debug.h', + 'nautilus-directory-async.c', + 'nautilus-directory-notify.h', + 'nautilus-directory-private.h', + 'nautilus-directory.c', + 'nautilus-directory.h', + 'nautilus-dnd.c', + 'nautilus-dnd.h', + 'nautilus-file-changes-queue.c', + 'nautilus-file-changes-queue.h', + 'nautilus-file-conflict-dialog.c', + 'nautilus-file-conflict-dialog.h', + 'nautilus-file-name-widget-controller.c', + 'nautilus-file-name-widget-controller.h', + 'nautilus-rename-file-popover-controller.c', + 'nautilus-rename-file-popover-controller.h', + 'nautilus-new-folder-dialog-controller.c', + 'nautilus-new-folder-dialog-controller.h', + 'nautilus-compress-dialog-controller.c', + 'nautilus-compress-dialog-controller.h', + 'nautilus-operations-ui-manager.c', + 'nautilus-operations-ui-manager.h', + 'nautilus-file-operations.c', + 'nautilus-file-operations.h', + 'nautilus-file-operations-dbus-data.c', + 'nautilus-file-operations-dbus-data.h', + 'nautilus-file-private.h', + 'nautilus-file-queue.c', + 'nautilus-file-queue.h', + 'nautilus-file-utilities.c', + 'nautilus-file-utilities.h', + 'nautilus-file.c', + 'nautilus-file.h', + 'nautilus-global-preferences.c', + 'nautilus-global-preferences.h', + 'nautilus-icon-info.c', + 'nautilus-icon-info.h', + 'nautilus-icon-names.h', + 'nautilus-keyfile-metadata.c', + 'nautilus-keyfile-metadata.h', + 'nautilus-lib-self-check-functions.c', + 'nautilus-lib-self-check-functions.h', + 'nautilus-metadata.h', + 'nautilus-metadata.c', + 'nautilus-module.c', + 'nautilus-module.h', + 'nautilus-monitor.c', + 'nautilus-monitor.h', + 'nautilus-profile.c', + 'nautilus-profile.h', + 'nautilus-progress-info.c', + 'nautilus-progress-info.h', + 'nautilus-progress-info-manager.c', + 'nautilus-progress-info-manager.h', + 'nautilus-program-choosing.c', + 'nautilus-program-choosing.h', + 'nautilus-search-directory.c', + 'nautilus-search-directory.h', + 'nautilus-search-directory-file.c', + 'nautilus-search-directory-file.h', + 'nautilus-search-provider.c', + 'nautilus-search-provider.h', + 'nautilus-search-engine.c', + 'nautilus-search-engine.h', + 'nautilus-search-engine-private.h', + 'nautilus-search-engine-model.c', + 'nautilus-search-engine-model.h', + 'nautilus-search-engine-recent.c', + 'nautilus-search-engine-recent.h', + 'nautilus-search-engine-simple.c', + 'nautilus-search-engine-simple.h', + 'nautilus-search-hit.c', + 'nautilus-search-hit.h', + 'nautilus-signaller.h', + 'nautilus-signaller.c', + 'nautilus-query.c', + 'nautilus-thumbnails.c', + 'nautilus-thumbnails.h', + 'nautilus-trash-monitor.c', + 'nautilus-trash-monitor.h', + 'nautilus-ui-utilities.c', + 'nautilus-ui-utilities.h', + 'nautilus-video-mime-types.h', + 'nautilus-vfs-directory.c', + 'nautilus-vfs-directory.h', + 'nautilus-vfs-file.c', + 'nautilus-vfs-file.h', + 'nautilus-file-undo-operations.c', + 'nautilus-file-undo-operations.h', + 'nautilus-file-undo-manager.c', + 'nautilus-file-undo-manager.h', + 'nautilus-batch-rename-dialog.c', + 'nautilus-batch-rename-dialog.h', + 'nautilus-batch-rename-utilities.c', + 'nautilus-batch-rename-utilities.h', + 'nautilus-search-engine-tracker.c', + 'nautilus-search-engine-tracker.h', + 'nautilus-tag-manager.c', + 'nautilus-tag-manager.h', + 'nautilus-starred-directory.c', + 'nautilus-starred-directory.h', + 'nautilus-enums.h', + 'nautilus-types.h', + 'nautilus-tracker-utilities.c', + 'nautilus-tracker-utilities.h' +] + +nautilus_deps = [ + config_h, + eel_2, + gio_unix, + gmodule, + gnome_autoar, + gnome_desktop, + libadwaita, + libportal, + libportal_gtk4, + nautilus_extension, + selinux, + tracker_sparql, + xml, + cloudproviders, +] + +libnautilus = static_library( + 'nautilus', + libnautilus_sources, + dependencies: nautilus_deps, + include_directories: nautilus_include_dirs +) + +libnautilus_include_dirs = include_directories('.') + +libnautilus_dep = declare_dependency( + link_with: libnautilus, + include_directories: [ + nautilus_include_dirs, + libnautilus_include_dirs + ], + dependencies: nautilus_deps, + # nautilus-main.c, which is part of the main Nautilus executable, uses + # the header, generated by glib-compile-resources. Passing it on from here + # will ensure that an internal compile-time dependency is placed on this file, + # thus avoiding failures that are difficult to reproduce. + sources: resources +) + +nautilus = executable( + 'nautilus', + 'nautilus-main.c', + dependencies: libnautilus_dep, + install: true +) + +if get_option('tests') == 'all' + test( + 'nautilus', nautilus, + args: [ + '--check', + ], + env: [ + 'G_DEBUG=fatal-warnings', + 'GSETTINGS_SCHEMA_DIR=@0@'.format(join_paths(meson.project_build_root(), 'data')), + 'RUNNING_TESTS=@0@'.format('TRUE') + ] + ) +endif + +nautilus_autorun_software_sources = [ + 'nautilus-autorun-software.c', + 'nautilus-icon-info.c', + 'nautilus-icon-info.h' +] + +executable( + 'nautilus-autorun-software', + nautilus_autorun_software_sources, + include_directories: nautilus_include_dirs, + dependencies: [ + config_h, + gtk, + libadwaita, + ], + install: true +) diff --git a/src/nautilus-app-chooser.c b/src/nautilus-app-chooser.c new file mode 100644 index 0000000..e15abc3 --- /dev/null +++ b/src/nautilus-app-chooser.c @@ -0,0 +1,320 @@ +/* + * Copyright (C) 2022 António Fernandes + * + * SPDX-License-Identifier: GPL-3.0-or-later + */ + +#include "nautilus-app-chooser.h" + +#include +#include + +#include + +#include "nautilus-file.h" +#include "nautilus-signaller.h" + +struct _NautilusAppChooser +{ + GtkDialog parent_instance; + + gchar *content_type; + gchar *file_name; + gboolean single_content_type; + + GtkWidget *app_chooser_widget_box; + GtkWidget *label_description; + GtkWidget *set_default_row; + GtkWidget *set_as_default_switch; + GtkWidget *set_default_box; + + GtkWidget *app_chooser_widget; +}; + +G_DEFINE_TYPE (NautilusAppChooser, nautilus_app_chooser, GTK_TYPE_DIALOG) + +enum +{ + PROP_0, + PROP_CONTENT_TYPE, + PROP_SINGLE_CONTENT_TYPE, + PROP_FILE_NAME, + LAST_PROP +}; + +static void +open_cb (NautilusAppChooser *self) +{ + gboolean set_new_default = FALSE; + g_autoptr (GAppInfo) info = NULL; + g_autoptr (GError) error = NULL; + + if (!self->single_content_type) + { + /* Don't attempt to set an association with multiple content types */ + return; + } + + /* The switch is insensitive if the selected app is already default */ + if (gtk_widget_get_sensitive (self->set_as_default_switch)) + { + set_new_default = gtk_switch_get_active (GTK_SWITCH (self->set_as_default_switch)); + } + + if (set_new_default) + { + info = gtk_app_chooser_get_app_info (GTK_APP_CHOOSER (self->app_chooser_widget)); + g_app_info_set_as_default_for_type (info, self->content_type, + &error); + g_signal_emit_by_name (nautilus_signaller_get_current (), "mime-data-changed"); + } + + if (error != NULL) + { + g_autofree gchar *message = NULL; + GtkWidget *message_dialog; + + message = g_strdup_printf (_("Error while setting “%s” as default application: %s"), + g_app_info_get_display_name (info), error->message); + message_dialog = adw_message_dialog_new (GTK_WINDOW (self), + _("Could not set as default"), + message); + adw_message_dialog_add_response (ADW_MESSAGE_DIALOG (message_dialog), "close", _("OK")); + gtk_window_present (GTK_WINDOW (message_dialog)); + } +} + +static void +on_application_activated (NautilusAppChooser *self) +{ + open_cb (self); + gtk_dialog_response (GTK_DIALOG (self), GTK_RESPONSE_OK); +} + +static void +on_application_selected (GtkAppChooserWidget *widget, + GAppInfo *info, + gpointer user_data) +{ + NautilusAppChooser *self = NAUTILUS_APP_CHOOSER (user_data); + g_autoptr (GAppInfo) default_app = NULL; + gboolean is_default; + + gtk_dialog_set_response_sensitive (GTK_DIALOG (self), GTK_RESPONSE_OK, info != NULL); + + default_app = g_app_info_get_default_for_type (self->content_type, FALSE); + is_default = default_app != NULL && g_app_info_equal (info, default_app); + + gtk_switch_set_state (GTK_SWITCH (self->set_as_default_switch), is_default); + gtk_widget_set_sensitive (GTK_WIDGET (self->set_as_default_switch), !is_default); +} + +static void +nautilus_app_chooser_set_property (GObject *object, + guint param_id, + const GValue *value, + GParamSpec *pspec) +{ + NautilusAppChooser *self = NAUTILUS_APP_CHOOSER (object); + + switch (param_id) + { + case PROP_CONTENT_TYPE: + { + self->content_type = g_value_dup_string (value); + } + break; + + case PROP_SINGLE_CONTENT_TYPE: + { + self->single_content_type = g_value_get_boolean (value); + } + break; + + case PROP_FILE_NAME: + { + self->file_name = g_value_dup_string (value); + } + break; + + default: + { + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, param_id, pspec); + } + break; + } +} + +static void +nautilus_app_chooser_init (NautilusAppChooser *self) +{ + gtk_widget_init_template (GTK_WIDGET (self)); + + gtk_widget_set_name (GTK_WIDGET (self), "NautilusAppChooser"); +} + +static gboolean +content_type_is_folder (NautilusAppChooser *self) +{ + return g_strcmp0 (self->content_type, "inode/directory") == 0; +} + +static void +nautilus_app_chooser_constructed (GObject *object) +{ + NautilusAppChooser *self = NAUTILUS_APP_CHOOSER (object); + g_autoptr (GAppInfo) info = NULL; + g_autofree gchar *content_type_description = NULL; + g_autofree gchar *description = NULL; + gchar *title; + + G_OBJECT_CLASS (nautilus_app_chooser_parent_class)->constructed (object); + + self->app_chooser_widget = gtk_app_chooser_widget_new (self->content_type); + gtk_widget_set_vexpand (self->app_chooser_widget, TRUE); + gtk_widget_add_css_class (self->app_chooser_widget, "lowres-icon"); + gtk_box_append (GTK_BOX (self->app_chooser_widget_box), self->app_chooser_widget); + + gtk_app_chooser_widget_set_show_default (GTK_APP_CHOOSER_WIDGET (self->app_chooser_widget), TRUE); + gtk_app_chooser_widget_set_show_fallback (GTK_APP_CHOOSER_WIDGET (self->app_chooser_widget), TRUE); + gtk_app_chooser_widget_set_show_other (GTK_APP_CHOOSER_WIDGET (self->app_chooser_widget), TRUE); + + /* initialize sensitivity */ + info = gtk_app_chooser_get_app_info (GTK_APP_CHOOSER (self->app_chooser_widget)); + if (info != NULL) + { + on_application_selected (GTK_APP_CHOOSER_WIDGET (self->app_chooser_widget), + info, self); + } + + g_signal_connect_object (self->app_chooser_widget, "application-selected", + G_CALLBACK (on_application_selected), self, 0); + g_signal_connect_object (self->app_chooser_widget, "application-activated", + G_CALLBACK (on_application_activated), self, G_CONNECT_SWAPPED); + + if (self->file_name != NULL) + { + /* Translators: %s is the filename. i.e. "Choose an application to open test.jpg" */ + description = g_strdup_printf (_("Choose an application to open %s."), self->file_name); + gtk_label_set_markup (GTK_LABEL (self->label_description), description); + } + + if (!self->single_content_type) + { + title = _("Open Items"); + } + else if (content_type_is_folder (self)) + { + title = _("Open Folder"); + } + else + { + title = _("Open File"); + } + + gtk_header_bar_set_title_widget (GTK_HEADER_BAR (gtk_dialog_get_header_bar (GTK_DIALOG (self))), + adw_window_title_new (title, NULL)); + + if (self->single_content_type && !content_type_is_folder (self)) + { + content_type_description = g_content_type_get_description (self->content_type); + if (content_type_description != NULL) + { + g_autofree gchar *capitalized = NULL; + capitalized = eel_str_capitalize (content_type_description); + adw_action_row_set_subtitle (ADW_ACTION_ROW (self->set_default_row), capitalized); + } + } + else + { + gtk_widget_set_visible (self->set_default_box, FALSE); + } +} + +static void +nautilus_app_chooser_finalize (GObject *object) +{ + NautilusAppChooser *self = (NautilusAppChooser *) object; + + g_clear_pointer (&self->content_type, g_free); + g_clear_pointer (&self->file_name, g_free); + + G_OBJECT_CLASS (nautilus_app_chooser_parent_class)->finalize (object); +} + +static void +nautilus_app_chooser_class_init (NautilusAppChooserClass *klass) +{ + GObjectClass *object_class = G_OBJECT_CLASS (klass); + GtkWidgetClass *widget_class = GTK_WIDGET_CLASS (klass); + + object_class->finalize = nautilus_app_chooser_finalize; + object_class->constructed = nautilus_app_chooser_constructed; + object_class->set_property = nautilus_app_chooser_set_property; + + gtk_widget_class_set_template_from_resource (widget_class, "/org/gnome/nautilus/ui/nautilus-app-chooser.ui"); + + gtk_widget_class_bind_template_child (widget_class, NautilusAppChooser, app_chooser_widget_box); + gtk_widget_class_bind_template_child (widget_class, NautilusAppChooser, set_as_default_switch); + gtk_widget_class_bind_template_child (widget_class, NautilusAppChooser, label_description); + gtk_widget_class_bind_template_child (widget_class, NautilusAppChooser, set_default_row); + gtk_widget_class_bind_template_child (widget_class, NautilusAppChooser, set_default_box); + + gtk_widget_class_bind_template_callback (widget_class, open_cb); + + g_object_class_install_property (object_class, + PROP_CONTENT_TYPE, + g_param_spec_string ("content-type", "", "", + NULL, + G_PARAM_CONSTRUCT_ONLY | G_PARAM_WRITABLE)); + + g_object_class_install_property (object_class, + PROP_FILE_NAME, + g_param_spec_string ("file-name", "", "", + NULL, + G_PARAM_CONSTRUCT_ONLY | G_PARAM_WRITABLE)); + + g_object_class_install_property (object_class, + PROP_SINGLE_CONTENT_TYPE, + g_param_spec_boolean ("single-content-type", "", "", + TRUE, + G_PARAM_CONSTRUCT_ONLY | G_PARAM_WRITABLE)); +} + +NautilusAppChooser * +nautilus_app_chooser_new (GList *files, + GtkWindow *parent_window) +{ + gboolean single_content_type = TRUE; + g_autofree gchar *content_type = NULL; + g_autofree gchar *file_name = NULL; + + content_type = nautilus_file_get_mime_type (files->data); + + file_name = files->next ? NULL : nautilus_file_get_display_name (files->data); + + for (GList *l = files; l != NULL; l = l->next) + { + g_autofree gchar *temp_mime_type = NULL; + temp_mime_type = nautilus_file_get_mime_type (l->data); + if (g_strcmp0 (content_type, temp_mime_type) != 0) + { + single_content_type = FALSE; + break; + } + } + + return NAUTILUS_APP_CHOOSER (g_object_new (NAUTILUS_TYPE_APP_CHOOSER, + "transient-for", parent_window, + "content-type", content_type, + "use-header-bar", TRUE, + "file-name", file_name, + "single-content-type", single_content_type, + NULL)); +} + +GAppInfo * +nautilus_app_chooser_get_app_info (NautilusAppChooser *self) +{ + return gtk_app_chooser_get_app_info (GTK_APP_CHOOSER (self->app_chooser_widget)); +} diff --git a/src/nautilus-app-chooser.h b/src/nautilus-app-chooser.h new file mode 100644 index 0000000..3131f49 --- /dev/null +++ b/src/nautilus-app-chooser.h @@ -0,0 +1,22 @@ +/* + * Copyright (C) 2022 António Fernandes + * + * SPDX-License-Identifier: GPL-3.0-or-later + */ + +#pragma once + +#include + +G_BEGIN_DECLS + +#define NAUTILUS_TYPE_APP_CHOOSER (nautilus_app_chooser_get_type()) + +G_DECLARE_FINAL_TYPE (NautilusAppChooser, nautilus_app_chooser, NAUTILUS, APP_CHOOSER, GtkDialog) + +NautilusAppChooser *nautilus_app_chooser_new (GList *files, + GtkWindow *parent_window); + +GAppInfo *nautilus_app_chooser_get_app_info (NautilusAppChooser *self); + +G_END_DECLS diff --git a/src/nautilus-application.c b/src/nautilus-application.c new file mode 100644 index 0000000..22bfdb7 --- /dev/null +++ b/src/nautilus-application.c @@ -0,0 +1,1542 @@ +/* + * nautilus-application: main Nautilus application class. + * + * Copyright (C) 1999, 2000 Red Hat, Inc. + * Copyright (C) 2000, 2001 Eazel, Inc. + * Copyright (C) 2010, Cosimo Cecchi + * + * Nautilus is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License as + * published by the Free Software Foundation; either version 2 of the + * License, or (at your option) any later version. + * + * Nautilus is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * 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 . + * + * Authors: Elliot Lee , + * Darin Adler + * Cosimo Cecchi + * + */ + +#include "nautilus-application.h" + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#define DEBUG_FLAG NAUTILUS_DEBUG_APPLICATION +#include "nautilus-debug.h" + +#include "nautilus-bookmark-list.h" +#include "nautilus-clipboard.h" +#include "nautilus-dbus-launcher.h" +#include "nautilus-dbus-manager.h" +#include "nautilus-directory-private.h" +#include "nautilus-file.h" +#include "nautilus-file-operations.h" +#include "nautilus-file-undo-manager.h" +#include "nautilus-file-utilities.h" +#include "nautilus-freedesktop-dbus.h" +#include "nautilus-global-preferences.h" +#include "nautilus-icon-info.h" +#include "nautilus-lib-self-check-functions.h" +#include "nautilus-module.h" +#include "nautilus-preferences-window.h" +#include "nautilus-previewer.h" +#include "nautilus-profile.h" +#include "nautilus-progress-persistence-handler.h" +#include "nautilus-self-check-functions.h" +#include "nautilus-shell-search-provider.h" +#include "nautilus-signaller.h" +#include "nautilus-tag-manager.h" +#include "nautilus-tracker-utilities.h" +#include "nautilus-ui-utilities.h" +#include "nautilus-view.h" +#include "nautilus-window-slot.h" +#include "nautilus-window.h" + +typedef struct +{ + NautilusProgressPersistenceHandler *progress_handler; + NautilusDBusManager *dbus_manager; + NautilusFreedesktopDBus *fdb_manager; + + NautilusBookmarkList *bookmark_list; + + NautilusShellSearchProvider *search_provider; + + GList *windows; + + GHashTable *notifications; + + NautilusFileUndoManager *undo_manager; + + NautilusTagManager *tag_manager; + + NautilusDBusLauncher *dbus_launcher; + + guint previewer_selection_id; +} NautilusApplicationPrivate; + +G_DEFINE_TYPE_WITH_PRIVATE (NautilusApplication, nautilus_application, ADW_TYPE_APPLICATION); + +void +nautilus_application_set_accelerator (GApplication *app, + const gchar *action_name, + const gchar *accel) +{ + const gchar *vaccels[] = + { + accel, + NULL + }; + + gtk_application_set_accels_for_action (GTK_APPLICATION (app), action_name, vaccels); +} + +void +nautilus_application_set_accelerators (GApplication *app, + const gchar *action_name, + const gchar **accels) +{ + gtk_application_set_accels_for_action (GTK_APPLICATION (app), action_name, accels); +} + +GList * +nautilus_application_get_windows (NautilusApplication *self) +{ + NautilusApplicationPrivate *priv; + + priv = nautilus_application_get_instance_private (self); + + return priv->windows; +} + +NautilusBookmarkList * +nautilus_application_get_bookmarks (NautilusApplication *self) +{ + NautilusApplicationPrivate *priv; + + priv = nautilus_application_get_instance_private (self); + + if (!priv->bookmark_list) + { + priv->bookmark_list = nautilus_bookmark_list_new (); + } + + return priv->bookmark_list; +} + +static gboolean +check_required_directories (NautilusApplication *self) +{ + char *user_directory; + GSList *directories; + gboolean ret; + + g_assert (NAUTILUS_IS_APPLICATION (self)); + + nautilus_profile_start (NULL); + + ret = TRUE; + + user_directory = nautilus_get_user_directory (); + + directories = NULL; + + if (!g_file_test (user_directory, G_FILE_TEST_IS_DIR)) + { + directories = g_slist_prepend (directories, user_directory); + } + + if (directories != NULL) + { + int failed_count; + GString *directories_as_string; + GSList *l; + char *error_string; + g_autofree char *detail_string = NULL; + AdwMessageDialog *dialog; + + ret = FALSE; + + failed_count = g_slist_length (directories); + + directories_as_string = g_string_new ((const char *) directories->data); + for (l = directories->next; l != NULL; l = l->next) + { + g_string_append_printf (directories_as_string, ", %s", (const char *) l->data); + } + + error_string = _("Oops! Something went wrong."); + if (failed_count == 1) + { + detail_string = g_strdup_printf (_("Unable to create a required folder. " + "Please create the following folder, or " + "set permissions such that it can be created:\n%s"), + directories_as_string->str); + } + else + { + detail_string = g_strdup_printf (_("Unable to create required folders. " + "Please create the following folders, or " + "set permissions such that they can be created:\n%s"), + directories_as_string->str); + } + + dialog = show_dialog (error_string, detail_string, NULL, GTK_MESSAGE_ERROR); + /* We need the main event loop so the user has a chance to see the dialog. */ + gtk_application_add_window (GTK_APPLICATION (self), + GTK_WINDOW (dialog)); + + g_string_free (directories_as_string, TRUE); + } + + g_slist_free (directories); + g_free (user_directory); + nautilus_profile_end (NULL); + + return ret; +} + +static void +menu_provider_items_updated_handler (NautilusMenuProvider *provider, + GtkWidget *parent_window, + gpointer data) +{ + g_signal_emit_by_name (nautilus_signaller_get_current (), + "popup-menu-changed"); +} + +static void +menu_provider_init_callback (void) +{ + GList *providers; + GList *l; + + providers = nautilus_module_get_extensions_for_type (NAUTILUS_TYPE_MENU_PROVIDER); + + for (l = providers; l != NULL; l = l->next) + { + NautilusMenuProvider *provider = NAUTILUS_MENU_PROVIDER (l->data); + + g_signal_connect_after (G_OBJECT (provider), "items-updated", + (GCallback) menu_provider_items_updated_handler, + NULL); + } + + nautilus_module_extension_list_free (providers); +} + +NautilusWindow * +nautilus_application_create_window (NautilusApplication *self) +{ + NautilusWindow *window; + gboolean maximized; + g_autoptr (GVariant) default_size = NULL; + gint default_width = 0; + gint default_height = 0; + + g_return_val_if_fail (NAUTILUS_IS_APPLICATION (self), NULL); + nautilus_profile_start (NULL); + + window = nautilus_window_new (); + + maximized = g_settings_get_boolean + (nautilus_window_state, NAUTILUS_WINDOW_STATE_MAXIMIZED); + if (maximized) + { + gtk_window_maximize (GTK_WINDOW (window)); + } + else + { + gtk_window_unmaximize (GTK_WINDOW (window)); + } + default_size = g_settings_get_value (nautilus_window_state, + NAUTILUS_WINDOW_STATE_INITIAL_SIZE); + + g_variant_get (default_size, "(ii)", &default_width, &default_height); + + gtk_window_set_default_size (GTK_WINDOW (window), + MAX (NAUTILUS_WINDOW_MIN_WIDTH, default_width), + MAX (NAUTILUS_WINDOW_MIN_HEIGHT, default_height)); + + if (g_strcmp0 (PROFILE, "") != 0) + { + GtkStyleContext *style_context; + + style_context = gtk_widget_get_style_context (GTK_WIDGET (window)); + + gtk_style_context_add_class (style_context, "devel"); + } + + DEBUG ("Creating a new navigation window"); + nautilus_profile_end (NULL); + + return window; +} + +static NautilusWindowSlot * +get_window_slot_for_location (NautilusApplication *self, + GFile *location) +{ + NautilusApplicationPrivate *priv; + NautilusWindowSlot *slot; + NautilusWindow *window; + NautilusFile *file; + GList *l, *sl; + + priv = nautilus_application_get_instance_private (self); + slot = NULL; + file = nautilus_file_get (location); + + if (!nautilus_file_is_directory (file) && !nautilus_file_is_other_locations (file) && + g_file_has_parent (location, NULL)) + { + location = g_file_get_parent (location); + } + else + { + g_object_ref (location); + } + + for (l = priv->windows; l != NULL; l = l->next) + { + window = l->data; + + for (sl = nautilus_window_get_slots (window); sl; sl = sl->next) + { + NautilusWindowSlot *current = sl->data; + GFile *slot_location = nautilus_window_slot_get_location (current); + + if (slot_location && g_file_equal (slot_location, location)) + { + slot = current; + break; + } + } + + if (slot) + { + break; + } + } + + nautilus_file_unref (file); + g_object_unref (location); + + return slot; +} + +void +nautilus_application_open_location_full (NautilusApplication *self, + GFile *location, + NautilusOpenFlags flags, + GList *selection, + NautilusWindow *target_window, + NautilusWindowSlot *target_slot) +{ + NAUTILUS_APPLICATION_CLASS (G_OBJECT_GET_CLASS (self))->open_location_full (self, + location, + flags, + selection, + target_window, + target_slot); +} + +static void +real_open_location_full (NautilusApplication *self, + GFile *location, + NautilusOpenFlags flags, + GList *selection, + NautilusWindow *target_window, + NautilusWindowSlot *target_slot) +{ + NautilusWindowSlot *active_slot = NULL; + NautilusWindow *active_window; + GFile *old_location = NULL; + char *old_uri, *new_uri; + gboolean use_same; + GdkDisplay *display; + + use_same = TRUE; + /* FIXME: We are having problems on getting the current focused window with + * gtk_application_get_active_window, see https://bugzilla.gnome.org/show_bug.cgi?id=756499 + * so what we do is never rely on this on the callers, but would be cool to + * make it work withouth explicitly setting the active window on the callers. */ + active_window = NAUTILUS_WINDOW (gtk_application_get_active_window (GTK_APPLICATION (self))); + /* There is no active window if the application is run with + * --gapplication-service + */ + if (active_window) + { + active_slot = nautilus_window_get_active_slot (active_window); + /* Just for debug.*/ + if (active_slot != NULL) + { + old_location = nautilus_window_slot_get_location (active_slot); + } + } + + + /* this happens at startup */ + if (old_location == NULL) + { + old_uri = g_strdup ("(none)"); + } + else + { + old_uri = g_file_get_uri (old_location); + } + + new_uri = g_file_get_uri (location); + + DEBUG ("Application opening location, old: %s, new: %s", old_uri, new_uri); + nautilus_profile_start ("Application opening location, old: %s, new: %s", old_uri, new_uri); + + g_free (old_uri); + g_free (new_uri); + /* end debug */ + + /* In case a target slot is provided, we can use it's associated window. + * In case a target window were given as well, we give preference to the + * slot we target at */ + if (target_slot != NULL) + { + target_window = nautilus_window_slot_get_window (target_slot); + } + + g_assert (!((flags & NAUTILUS_OPEN_FLAG_NEW_WINDOW) != 0 && + (flags & NAUTILUS_OPEN_FLAG_NEW_TAB) != 0)); + + /* and if the flags specify so, this is overridden */ + if ((flags & NAUTILUS_OPEN_FLAG_NEW_WINDOW) != 0) + { + use_same = FALSE; + } + + /* now get/create the window */ + if (use_same) + { + if (!target_window) + { + if (!target_slot) + { + target_window = active_window; + } + else + { + target_window = nautilus_window_slot_get_window (target_slot); + } + } + } + else + { + display = active_window != NULL ? + gtk_root_get_display (GTK_ROOT (active_window)) : + gdk_display_get_default (); + + target_window = nautilus_application_create_window (self); + /* Whatever the caller says, the slot won't be the same */ + gtk_window_set_display (GTK_WINDOW (target_window), display); + target_slot = NULL; + } + + g_assert (target_window != NULL); + + /* Application is the one that manages windows, so this flag shouldn't use + * it anymore by any client */ + flags &= ~NAUTILUS_OPEN_FLAG_NEW_WINDOW; + nautilus_window_open_location_full (target_window, location, flags, selection, target_slot); +} + +static NautilusWindow * +open_window (NautilusApplication *self, + GFile *location) +{ + NautilusWindow *window; + + nautilus_profile_start (NULL); + window = nautilus_application_create_window (self); + + if (location != NULL) + { + nautilus_application_open_location_full (self, location, 0, NULL, window, NULL); + } + else + { + GFile *home; + home = g_file_new_for_path (g_get_home_dir ()); + nautilus_application_open_location_full (self, home, 0, NULL, window, NULL); + + g_object_unref (home); + } + + nautilus_profile_end (NULL); + + return window; +} + +void +nautilus_application_open_location (NautilusApplication *self, + GFile *location, + GFile *selection, + const char *startup_id) +{ + NautilusWindow *window; + NautilusWindowSlot *slot; + GList *sel_list = NULL; + + nautilus_profile_start (NULL); + + if (selection != NULL) + { + sel_list = g_list_prepend (sel_list, nautilus_file_get (selection)); + } + + slot = get_window_slot_for_location (self, location); + + if (!slot) + { + window = nautilus_application_create_window (self); + } + else + { + window = nautilus_window_slot_get_window (slot); + } + + nautilus_application_open_location_full (self, location, 0, sel_list, window, slot); + + if (sel_list != NULL) + { + nautilus_file_list_free (sel_list); + } + + nautilus_profile_end (NULL); +} + +/* Note: when launched from command line we do not reach this method + * since we manually handle the command line parameters in order to + * parse --version, --check, etc. + * However this method is called when open () is called via dbus, for + * instance when gtk_uri_open () is called from outside. + */ +static void +nautilus_application_open (GApplication *app, + GFile **files, + gint n_files, + const gchar *hint) +{ + NautilusApplication *self = NAUTILUS_APPLICATION (app); + gboolean force_new = (g_strcmp0 (hint, "new-window") == 0); + NautilusWindowSlot *slot = NULL; + GFile *file; + gint idx; + + DEBUG ("Open called on the GApplication instance; %d files", n_files); + + /* Open windows at each requested location. */ + for (idx = 0; idx < n_files; idx++) + { + file = files[idx]; + + if (!force_new) + { + slot = get_window_slot_for_location (self, file); + } + + if (!slot) + { + open_window (self, file); + } + else + { + /* We open the location again to update any possible selection */ + nautilus_application_open_location_full (NAUTILUS_APPLICATION (app), file, 0, NULL, NULL, slot); + } + } +} + +static void +nautilus_application_finalize (GObject *object) +{ + NautilusApplication *self; + NautilusApplicationPrivate *priv; + + self = NAUTILUS_APPLICATION (object); + priv = nautilus_application_get_instance_private (self); + + g_clear_object (&priv->progress_handler); + g_clear_object (&priv->bookmark_list); + + g_clear_object (&priv->fdb_manager); + + g_list_free (priv->windows); + + g_hash_table_destroy (priv->notifications); + + g_clear_object (&priv->undo_manager); + + g_clear_object (&priv->tag_manager); + + g_clear_object (&priv->dbus_launcher); + + G_OBJECT_CLASS (nautilus_application_parent_class)->finalize (object); +} + +static gboolean +do_cmdline_sanity_checks (NautilusApplication *self, + GVariantDict *options) +{ + gboolean retval = FALSE; + + if (g_variant_dict_contains (options, "check") && + (g_variant_dict_contains (options, G_OPTION_REMAINING) || + g_variant_dict_contains (options, "quit"))) + { + g_printerr ("%s\n", + _("--check cannot be used with other options.")); + goto out; + } + + if (g_variant_dict_contains (options, "quit") && + g_variant_dict_contains (options, G_OPTION_REMAINING)) + { + g_printerr ("%s\n", + _("--quit cannot be used with URIs.")); + goto out; + } + + + if (g_variant_dict_contains (options, "select") && + !g_variant_dict_contains (options, G_OPTION_REMAINING)) + { + g_printerr ("%s\n", + _("--select must be used with at least an URI.")); + goto out; + } + + retval = TRUE; + +out: + return retval; +} + +static int +do_perform_self_checks (void) +{ +#ifndef NAUTILUS_OMIT_SELF_CHECK + gtk_init (); + + nautilus_profile_start (NULL); + /* Run the checks (each twice) for nautilus and libnautilus-private. */ + + nautilus_run_self_checks (); + nautilus_run_lib_self_checks (); + eel_exit_if_self_checks_failed (); + + nautilus_run_self_checks (); + nautilus_run_lib_self_checks (); + eel_exit_if_self_checks_failed (); + nautilus_profile_end (NULL); +#endif + + return EXIT_SUCCESS; +} + +static void +nautilus_application_select (NautilusApplication *self, + GFile **files, + gint len) +{ + int i; + GFile *file; + GFile *parent; + + for (i = 0; i < len; i++) + { + file = files[i]; + parent = g_file_get_parent (file); + if (parent != NULL) + { + nautilus_application_open_location (self, parent, file, NULL); + g_object_unref (parent); + } + else + { + nautilus_application_open_location (self, file, NULL, NULL); + } + } +} + +static void +action_new_window (GSimpleAction *action, + GVariant *parameter, + gpointer user_data) +{ + NautilusApplication *application; + g_autoptr (GFile) home = NULL; + + application = NAUTILUS_APPLICATION (user_data); + home = g_file_new_for_path (g_get_home_dir ()); + + nautilus_application_open_location_full (application, home, + NAUTILUS_OPEN_FLAG_NEW_WINDOW, + NULL, NULL, NULL); +} + +static void +action_clone_window (GSimpleAction *action, + GVariant *parameter, + gpointer user_data) +{ + NautilusWindowSlot *active_slot = NULL; + NautilusWindow *active_window = NULL; + GtkApplication *application = user_data; + g_autoptr (GFile) location = NULL; + NautilusView *current_view; + + active_window = NAUTILUS_WINDOW (gtk_application_get_active_window (application)); + active_slot = nautilus_window_get_active_slot (active_window); + current_view = nautilus_window_slot_get_current_view (active_slot); + + if (current_view != NULL && + nautilus_view_is_searching (current_view)) + { + location = g_file_new_for_path (g_get_home_dir ()); + } + else + { + /* If the user happens to fall asleep while holding ctrl-n, or very + * unfortunately opens a new window at a remote location, the current + * location will be null, leading to criticals and/or failed assertions. + * + * Another sad thing is that checking if the view/slot is loading will + * not work, as the loading process only really begins after the attributes + * for the file have been fetched. + */ + location = nautilus_window_slot_get_location (active_slot); + if (location == NULL) + { + location = nautilus_window_slot_get_pending_location (active_slot); + } + } + + nautilus_application_open_location_full (NAUTILUS_APPLICATION (application), location, + NAUTILUS_OPEN_FLAG_NEW_WINDOW, NULL, NULL, NULL); +} + +static void +action_preferences (GSimpleAction *action, + GVariant *parameter, + gpointer user_data) +{ + GtkApplication *application = user_data; + nautilus_preferences_window_show (gtk_application_get_active_window (application)); +} + +static void +action_about (GSimpleAction *action, + GVariant *parameter, + gpointer user_data) +{ + GtkApplication *application = user_data; + + nautilus_window_show_about_dialog (NAUTILUS_WINDOW (gtk_application_get_active_window (application))); +} + +static void +action_help (GSimpleAction *action, + GVariant *parameter, + gpointer user_data) +{ + GtkWindow *window; + GtkWidget *dialog; + GtkApplication *application = user_data; + GError *error = NULL; + + window = gtk_application_get_active_window (application); + gtk_show_uri (window, "help:gnome-help/files", GDK_CURRENT_TIME); + + if (error) + { + dialog = adw_message_dialog_new (window ? GTK_WINDOW (window) : NULL, + NULL, NULL); + adw_message_dialog_format_heading (ADW_MESSAGE_DIALOG (dialog), + _("There was an error displaying help: \n%s"), + error->message); + adw_message_dialog_add_response (ADW_MESSAGE_DIALOG (dialog), "ok", _("_OK")); + adw_message_dialog_set_default_response (ADW_MESSAGE_DIALOG (dialog), "ok"); + + gtk_window_present (GTK_WINDOW (dialog)); + g_error_free (error); + } +} + +static void +action_kill (GSimpleAction *action, + GVariant *parameter, + gpointer user_data) +{ + AdwApplication *application = user_data; + + /* we have been asked to force quit */ + g_application_quit (G_APPLICATION (application)); +} + +static void +action_quit (GSimpleAction *action, + GVariant *parameter, + gpointer user_data) +{ + NautilusApplication *self = NAUTILUS_APPLICATION (user_data); + GList *windows, *l; + + windows = nautilus_application_get_windows (self); + /* make a copy, since the original list will be modified when destroying + * a window, making this list invalid */ + windows = g_list_copy (windows); + for (l = windows; l != NULL; l = l->next) + { + nautilus_window_close (l->data); + } + + g_list_free (windows); +} + +static void +action_show_help_overlay (GSimpleAction *action, + GVariant *state, + gpointer user_data) +{ + GtkApplication *application = user_data; + GtkWindow *window = gtk_application_get_active_window (application); + + g_action_group_activate_action (G_ACTION_GROUP (window), "show-help-overlay", NULL); +} + +const static GActionEntry app_entries[] = +{ + { "new-window", action_new_window, NULL, NULL, NULL }, + { "clone-window", action_clone_window, NULL, NULL, NULL }, + { "preferences", action_preferences, NULL, NULL, NULL }, + { "about", action_about, NULL, NULL, NULL }, + { "help", action_help, NULL, NULL, NULL }, + { "quit", action_quit, NULL, NULL, NULL }, + { "kill", action_kill, NULL, NULL, NULL }, + { "show-help-overlay", action_show_help_overlay, NULL, NULL, NULL }, +}; + +static void +nautilus_init_application_actions (NautilusApplication *app) +{ + g_action_map_add_action_entries (G_ACTION_MAP (app), + app_entries, G_N_ELEMENTS (app_entries), + app); + + + nautilus_application_set_accelerator (G_APPLICATION (app), + "app.clone-window", "n"); + nautilus_application_set_accelerator (G_APPLICATION (app), + "app.help", "F1"); + nautilus_application_set_accelerator (G_APPLICATION (app), + "app.quit", "q"); +} + +static void +nautilus_application_activate (GApplication *app) +{ + GFile **files; + + DEBUG ("Calling activate"); + + files = g_malloc0 (2 * sizeof (GFile *)); + files[0] = g_file_new_for_path (g_get_home_dir ()); + nautilus_application_open (app, files, 1, NULL); + + g_object_unref (files[0]); + g_free (files); +} + +static gint +nautilus_application_handle_file_args (NautilusApplication *self, + GVariantDict *options) +{ + GFile **files; + GFile *file; + gint idx, len; + g_autofree const gchar **remaining = NULL; + GPtrArray *file_array; + + g_variant_dict_lookup (options, G_OPTION_REMAINING, "^a&s", &remaining); + + /* Convert args to GFiles */ + file_array = g_ptr_array_new_full (0, g_object_unref); + + if (remaining) + { + for (idx = 0; remaining[idx] != NULL; idx++) + { + gchar *cwd; + + g_variant_dict_lookup (options, "cwd", "s", &cwd); + if (cwd == NULL) + { + file = g_file_new_for_commandline_arg (remaining[idx]); + } + else + { + file = g_file_new_for_commandline_arg_and_cwd (remaining[idx], cwd); + g_free (cwd); + } + + if (nautilus_is_search_directory (file)) + { + g_autofree char *error_string = NULL; + error_string = g_strdup_printf (_("“%s” is an internal protocol. " + "Opening this location directly is not supported."), + EEL_SEARCH_URI); + + g_printerr ("%s\n", error_string); + } + else + { + g_ptr_array_add (file_array, file); + } + } + } + else if (g_variant_dict_contains (options, "new-window")) + { + file = g_file_new_for_path (g_get_home_dir ()); + g_ptr_array_add (file_array, file); + } + else + { + g_ptr_array_unref (file_array); + + /* No command line options or files, just activate the application */ + nautilus_application_activate (G_APPLICATION (self)); + return EXIT_SUCCESS; + } + + len = file_array->len; + files = (GFile **) file_array->pdata; + + if (g_variant_dict_contains (options, "select")) + { + nautilus_application_select (self, files, len); + } + else + { + /* Create new windows */ + nautilus_application_open (G_APPLICATION (self), files, len, + g_variant_dict_contains (options, "new-window") ? "new-window" : ""); + } + + g_ptr_array_unref (file_array); + + return EXIT_SUCCESS; +} + +static gint +nautilus_application_command_line (GApplication *application, + GApplicationCommandLine *command_line) +{ + NautilusApplication *self = NAUTILUS_APPLICATION (application); + gint retval = -1; + GVariantDict *options; + + nautilus_profile_start (NULL); + + options = g_application_command_line_get_options_dict (command_line); + + if (g_variant_dict_contains (options, "version")) + { + g_application_command_line_print (command_line, + "GNOME nautilus " PACKAGE_VERSION "\n"); + retval = EXIT_SUCCESS; + goto out; + } + + if (!do_cmdline_sanity_checks (self, options)) + { + retval = EXIT_FAILURE; + goto out; + } + + if (g_variant_dict_contains (options, "check")) + { + retval = do_perform_self_checks (); + goto out; + } + + if (g_variant_dict_contains (options, "quit")) + { + DEBUG ("Killing application, as requested"); + g_action_group_activate_action (G_ACTION_GROUP (application), + "kill", NULL); + goto out; + } + + retval = nautilus_application_handle_file_args (self, options); + +out: + nautilus_profile_end (NULL); + + return retval; +} + +static void +nautilus_application_init (NautilusApplication *self) +{ + static const GOptionEntry options[] = + { +#ifndef NAUTILUS_OMIT_SELF_CHECK + { "check", 'c', 0, G_OPTION_ARG_NONE, NULL, + N_("Perform a quick set of self-check tests."), NULL }, +#endif + { "version", '\0', 0, G_OPTION_ARG_NONE, NULL, + N_("Show the version of the program."), NULL }, + { "new-window", 'w', 0, G_OPTION_ARG_NONE, NULL, + N_("Always open a new window for browsing specified URIs"), NULL }, + { "quit", 'q', 0, G_OPTION_ARG_NONE, NULL, + N_("Quit Nautilus."), NULL }, + { "select", 's', 0, G_OPTION_ARG_NONE, NULL, + N_("Select specified URI in parent folder."), NULL }, + { G_OPTION_REMAINING, 0, 0, G_OPTION_ARG_STRING_ARRAY, NULL, NULL, N_("[URI…]") }, + + /* The following are old options which have no effect anymore. We keep + * them around for compatibility reasons, e.g. not breaking old scripts. + */ + { "browser", '\0', G_OPTION_FLAG_HIDDEN, G_OPTION_ARG_NONE, NULL, + NULL, NULL }, + { "geometry", 'g', G_OPTION_FLAG_HIDDEN, G_OPTION_ARG_STRING, NULL, + NULL, NULL }, + { "no-default-window", 'n', G_OPTION_FLAG_HIDDEN, G_OPTION_ARG_NONE, NULL, + NULL, NULL }, + { "no-desktop", '\0', G_OPTION_FLAG_HIDDEN, G_OPTION_ARG_NONE, NULL, + NULL, NULL }, + + { NULL } + }; + NautilusApplicationPrivate *priv; + + priv = nautilus_application_get_instance_private (self); + + priv->notifications = g_hash_table_new_full (g_str_hash, + g_str_equal, + g_free, + NULL); + + priv->undo_manager = nautilus_file_undo_manager_new (); + priv->tag_manager = nautilus_tag_manager_new (); + + priv->dbus_launcher = nautilus_dbus_launcher_new (); + + nautilus_tracker_setup_miner_fs_connection (); + + g_application_add_main_option_entries (G_APPLICATION (self), options); + + nautilus_ensure_extension_points (); + nautilus_ensure_extension_builtins (); + + nautilus_clipboard_register (); +} + +NautilusApplication * +nautilus_application_get_default (void) +{ + NautilusApplication *self; + + self = NAUTILUS_APPLICATION (g_application_get_default ()); + + return self; +} + +void +nautilus_application_send_notification (NautilusApplication *self, + const gchar *notification_id, + GNotification *notification) +{ + NautilusApplicationPrivate *priv; + + priv = nautilus_application_get_instance_private (self); + + g_hash_table_add (priv->notifications, g_strdup (notification_id)); + g_application_send_notification (G_APPLICATION (self), notification_id, notification); +} + +void +nautilus_application_withdraw_notification (NautilusApplication *self, + const gchar *notification_id) +{ + NautilusApplicationPrivate *priv; + + priv = nautilus_application_get_instance_private (self); + if (!g_hash_table_contains (priv->notifications, notification_id)) + { + return; + } + + g_hash_table_remove (priv->notifications, notification_id); + g_application_withdraw_notification (G_APPLICATION (self), notification_id); +} + +static void +on_application_shutdown (GApplication *application, + gpointer user_data) +{ + NautilusApplication *self = NAUTILUS_APPLICATION (application); + NautilusApplicationPrivate *priv; + GList *notification_ids; + GList *l; + gchar *notification_id; + + priv = nautilus_application_get_instance_private (self); + notification_ids = g_hash_table_get_keys (priv->notifications); + for (l = notification_ids; l != NULL; l = l->next) + { + notification_id = l->data; + + g_application_withdraw_notification (application, notification_id); + } + + g_list_free (notification_ids); + + nautilus_icon_info_clear_caches (); +} + +static void +icon_theme_changed_callback (GtkIconTheme *icon_theme, + gpointer user_data) +{ + /* Clear all pixmap caches as the icon => pixmap lookup changed */ + nautilus_icon_info_clear_caches (); + + /* Tell the world that icons might have changed. We could invent a narrower-scope + * signal to mean only "thumbnails might have changed" if this ends up being slow + * for some reason. + */ + emit_change_signals_for_all_files_in_all_directories (); +} + +static void +maybe_migrate_gtk_filechooser_preferences (void) +{ + if (!g_settings_get_boolean (nautilus_preferences, NAUTILUS_PREFERENCES_MIGRATED_GTK_SETTINGS)) + { + g_autoptr (GSettingsSchema) schema = NULL; + + /* We don't depend on GTK 3. Check whether its schema is installed. */ + schema = g_settings_schema_source_lookup (g_settings_schema_source_get_default (), + "org.gtk.Settings.FileChooser", + FALSE); + if (schema != NULL) + { + g_autoptr (GSettings) gtk3_settings = NULL; + + gtk3_settings = g_settings_new_with_path ("org.gtk.Settings.FileChooser", + "/org/gtk/settings/file-chooser/"); + g_settings_set_boolean (gtk_filechooser_preferences, + NAUTILUS_PREFERENCES_SORT_DIRECTORIES_FIRST, + g_settings_get_boolean (gtk3_settings, + NAUTILUS_PREFERENCES_SORT_DIRECTORIES_FIRST)); + g_settings_set_boolean (gtk_filechooser_preferences, + NAUTILUS_PREFERENCES_SHOW_HIDDEN_FILES, + g_settings_get_boolean (gtk3_settings, + NAUTILUS_PREFERENCES_SHOW_HIDDEN_FILES)); + } + g_settings_set_boolean (nautilus_preferences, + NAUTILUS_PREFERENCES_MIGRATED_GTK_SETTINGS, + TRUE); + } +} + +void +nautilus_application_startup_common (NautilusApplication *self) +{ + NautilusApplicationPrivate *priv; + + nautilus_profile_start (NULL); + priv = nautilus_application_get_instance_private (self); + + g_application_set_resource_base_path (G_APPLICATION (self), "/org/gnome/nautilus"); + + /* chain up to the GTK+ implementation early, so gtk_init() + * is called for us. + */ + G_APPLICATION_CLASS (nautilus_application_parent_class)->startup (G_APPLICATION (self)); + + gtk_window_set_default_icon_name (APPLICATION_ID); + + /* initialize preferences and create the global GSettings objects */ + nautilus_global_preferences_init (); + + /* initialize nautilus modules */ + nautilus_profile_start ("Modules"); + nautilus_module_setup (); + nautilus_profile_end ("Modules"); + + /* attach menu-provider module callback */ + menu_provider_init_callback (); + + /* Initialize the UI handler singleton for file operations */ + priv->progress_handler = nautilus_progress_persistence_handler_new (G_OBJECT (self)); + + /* Check the user's .nautilus directories and post warnings + * if there are problems. + */ + check_required_directories (self); + + nautilus_init_application_actions (self); + + if (g_strcmp0 (g_getenv ("RUNNING_TESTS"), "TRUE") != 0) + { + maybe_migrate_gtk_filechooser_preferences (); + nautilus_tag_manager_maybe_migrate_tracker2_data (priv->tag_manager); + } + + nautilus_profile_end (NULL); + + g_signal_connect (self, "shutdown", G_CALLBACK (on_application_shutdown), NULL); + + g_signal_connect_object (gtk_icon_theme_get_for_display (gdk_display_get_default ()), + "changed", + G_CALLBACK (icon_theme_changed_callback), + NULL, 0); +} + +static void +nautilus_application_startup (GApplication *app) +{ + NautilusApplication *self = NAUTILUS_APPLICATION (app); + NautilusApplicationPrivate *priv; + + nautilus_profile_start (NULL); + priv = nautilus_application_get_instance_private (self); + + /* create DBus manager */ + priv->fdb_manager = nautilus_freedesktop_dbus_new (); + nautilus_application_startup_common (self); + + nautilus_profile_end (NULL); +} + +static gboolean +nautilus_application_dbus_register (GApplication *app, + GDBusConnection *connection, + const gchar *object_path, + GError **error) +{ + NautilusApplication *self = NAUTILUS_APPLICATION (app); + NautilusApplicationPrivate *priv; + + priv = nautilus_application_get_instance_private (self); + priv->dbus_manager = nautilus_dbus_manager_new (); + if (!nautilus_dbus_manager_register (priv->dbus_manager, connection, error)) + { + return FALSE; + } + + priv->search_provider = nautilus_shell_search_provider_new (); + if (!nautilus_shell_search_provider_register (priv->search_provider, connection, error)) + { + return FALSE; + } + + priv->previewer_selection_id = nautilus_previewer_connect_selection_event (connection); + + return TRUE; +} + +static void +nautilus_application_dbus_unregister (GApplication *app, + GDBusConnection *connection, + const gchar *object_path) +{ + NautilusApplication *self = NAUTILUS_APPLICATION (app); + NautilusApplicationPrivate *priv; + + priv = nautilus_application_get_instance_private (self); + if (priv->dbus_manager) + { + nautilus_dbus_manager_unregister (priv->dbus_manager); + g_clear_object (&priv->dbus_manager); + } + + if (priv->search_provider) + { + nautilus_shell_search_provider_unregister (priv->search_provider); + g_clear_object (&priv->search_provider); + } + + if (priv->previewer_selection_id != 0) + { + nautilus_previewer_disconnect_selection_event (connection, + priv->previewer_selection_id); + priv->previewer_selection_id = 0; + } +} + +static void +update_dbus_opened_locations (NautilusApplication *self) +{ + NautilusApplicationPrivate *priv; + gint i; + GList *l, *sl; + GList *locations = NULL; + gsize locations_size = 0; + gchar **locations_array; + NautilusWindow *window; + GFile *location; + const gchar *dbus_object_path = NULL; + + g_autoptr (GVariant) windows_to_locations = NULL; + GVariantBuilder windows_to_locations_builder; + + g_return_if_fail (NAUTILUS_IS_APPLICATION (self)); + + priv = nautilus_application_get_instance_private (self); + + /* Children of nautilus application could not handle the dbus, so don't + * do anything in that case */ + if (!priv->fdb_manager) + { + return; + } + + dbus_object_path = g_application_get_dbus_object_path (G_APPLICATION (self)); + + g_return_if_fail (dbus_object_path); + + g_variant_builder_init (&windows_to_locations_builder, G_VARIANT_TYPE ("a{sas}")); + + for (l = priv->windows; l != NULL; l = l->next) + { + guint32 id; + g_autofree gchar *path = NULL; + GVariantBuilder locations_in_window_builder; + + window = l->data; + + g_variant_builder_init (&locations_in_window_builder, G_VARIANT_TYPE ("as")); + + for (sl = nautilus_window_get_slots (window); sl; sl = sl->next) + { + NautilusWindowSlot *slot = sl->data; + location = nautilus_window_slot_get_location (slot); + + if (location != NULL) + { + gchar *uri = g_file_get_uri (location); + GList *found = g_list_find_custom (locations, uri, (GCompareFunc) g_strcmp0); + + g_variant_builder_add (&locations_in_window_builder, "s", uri); + + if (!found) + { + locations = g_list_prepend (locations, uri); + ++locations_size; + } + else + { + g_free (uri); + } + } + } + + id = gtk_application_window_get_id (GTK_APPLICATION_WINDOW (window)); + path = g_strdup_printf ("%s/window/%u", dbus_object_path, id); + g_variant_builder_add (&windows_to_locations_builder, "{sas}", path, &locations_in_window_builder); + g_variant_builder_clear (&locations_in_window_builder); + } + + locations_array = g_new (gchar *, locations_size + 1); + + for (i = 0, l = locations; l; l = l->next, ++i) + { + /* We reuse the locations string locations saved on list */ + locations_array[i] = l->data; + } + + locations_array[locations_size] = NULL; + + nautilus_freedesktop_dbus_set_open_locations (priv->fdb_manager, + (const gchar **) locations_array); + + windows_to_locations = g_variant_ref_sink (g_variant_builder_end (&windows_to_locations_builder)); + nautilus_freedesktop_dbus_set_open_windows_with_locations (priv->fdb_manager, + windows_to_locations); + + g_free (locations_array); + g_list_free_full (locations, g_free); +} + +static void +on_slot_location_changed (NautilusWindowSlot *slot, + GParamSpec *pspec, + NautilusApplication *self) +{ + update_dbus_opened_locations (self); +} + +static void +on_slot_added (NautilusWindow *window, + NautilusWindowSlot *slot, + NautilusApplication *self) +{ + if (nautilus_window_slot_get_location (slot)) + { + update_dbus_opened_locations (self); + } + + g_signal_connect (slot, "notify::location", G_CALLBACK (on_slot_location_changed), self); +} + +static void +on_slot_removed (NautilusWindow *window, + NautilusWindowSlot *slot, + NautilusApplication *self) +{ + update_dbus_opened_locations (self); + + g_signal_handlers_disconnect_by_func (slot, on_slot_location_changed, self); +} + +static void +nautilus_application_window_added (GtkApplication *app, + GtkWindow *window) +{ + NautilusApplication *self = NAUTILUS_APPLICATION (app); + NautilusApplicationPrivate *priv; + + priv = nautilus_application_get_instance_private (self); + GTK_APPLICATION_CLASS (nautilus_application_parent_class)->window_added (app, window); + + if (NAUTILUS_IS_WINDOW (window)) + { + priv->windows = g_list_prepend (priv->windows, window); + g_signal_connect (window, "slot-added", G_CALLBACK (on_slot_added), app); + g_signal_connect (window, "slot-removed", G_CALLBACK (on_slot_removed), app); + } +} + +static void +nautilus_application_window_removed (GtkApplication *app, + GtkWindow *window) +{ + NautilusApplication *self = NAUTILUS_APPLICATION (app); + NautilusApplicationPrivate *priv; + + priv = nautilus_application_get_instance_private (self); + + GTK_APPLICATION_CLASS (nautilus_application_parent_class)->window_removed (app, window); + + if (NAUTILUS_IS_WINDOW (window)) + { + priv->windows = g_list_remove_all (priv->windows, window); + g_signal_handlers_disconnect_by_func (window, on_slot_added, app); + g_signal_handlers_disconnect_by_func (window, on_slot_removed, app); + } + + /* if this was the last window, close the previewer */ + if (g_list_length (priv->windows) == 0) + { + nautilus_previewer_call_close (); + nautilus_progress_persistence_handler_make_persistent (priv->progress_handler); + } +} + +/* Manage the local instance command line options. This is only necessary to + * resolve correctly relative paths, since if the main instance resolve them in + * open(), it will do it with its current cwd, which may not be correct for the + * non main GApplication instance */ +static gint +nautilus_application_handle_local_options (GApplication *app, + GVariantDict *options) +{ + gchar *cwd; + + cwd = g_get_current_dir (); + g_variant_dict_insert (options, "cwd", "s", cwd); + g_free (cwd); + + return -1; +} + +static void +nautilus_application_class_init (NautilusApplicationClass *class) +{ + GObjectClass *object_class; + GApplicationClass *application_class; + GtkApplicationClass *gtkapp_class; + + object_class = G_OBJECT_CLASS (class); + object_class->finalize = nautilus_application_finalize; + + application_class = G_APPLICATION_CLASS (class); + application_class->startup = nautilus_application_startup; + application_class->activate = nautilus_application_activate; + application_class->dbus_register = nautilus_application_dbus_register; + application_class->dbus_unregister = nautilus_application_dbus_unregister; + application_class->open = nautilus_application_open; + application_class->command_line = nautilus_application_command_line; + application_class->handle_local_options = nautilus_application_handle_local_options; + + class->open_location_full = real_open_location_full; + + gtkapp_class = GTK_APPLICATION_CLASS (class); + gtkapp_class->window_added = nautilus_application_window_added; + gtkapp_class->window_removed = nautilus_application_window_removed; +} + +NautilusApplication * +nautilus_application_new (void) +{ + return g_object_new (NAUTILUS_TYPE_APPLICATION, + "application-id", APPLICATION_ID, + "flags", G_APPLICATION_HANDLES_COMMAND_LINE | G_APPLICATION_HANDLES_OPEN, + "inactivity-timeout", 12000, + NULL); +} + +void +nautilus_application_search (NautilusApplication *self, + NautilusQuery *query) +{ + g_autoptr (GFile) location = NULL; + NautilusWindow *window; + + location = nautilus_query_get_location (query); + window = open_window (self, location); + nautilus_window_search (window, query); +} + +gboolean +nautilus_application_is_sandboxed (void) +{ + static gboolean ret; + + static gsize init = 0; + if (g_once_init_enter (&init)) + { + ret = g_file_test ("/.flatpak-info", G_FILE_TEST_EXISTS); + g_once_init_leave (&init, 1); + } + + return ret; +} diff --git a/src/nautilus-application.h b/src/nautilus-application.h new file mode 100644 index 0000000..50bcf4d --- /dev/null +++ b/src/nautilus-application.h @@ -0,0 +1,87 @@ +/* + * nautilus-application: main Nautilus application class. + * + * Copyright (C) 2000 Red Hat, Inc. + * Copyright (C) 2010 Cosimo Cecchi + * + * Nautilus is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License as + * published by the Free Software Foundation; either version 2 of the + * License, or (at your option) any later version. + * + * Nautilus is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * 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 . + */ + +#pragma once + +#include + +#include "nautilus-types.h" + +G_BEGIN_DECLS +#define NAUTILUS_TYPE_APPLICATION (nautilus_application_get_type()) +G_DECLARE_DERIVABLE_TYPE (NautilusApplication, nautilus_application, NAUTILUS, APPLICATION, AdwApplication) + +struct _NautilusApplicationClass { + AdwApplicationClass parent_class; + + void (*open_location_full) (NautilusApplication *application, + GFile *location, + NautilusOpenFlags flags, + GList *selection, + NautilusWindow *target_window, + NautilusWindowSlot *target_slot); +}; + +NautilusApplication * nautilus_application_new (void); + +NautilusWindow * nautilus_application_create_window (NautilusApplication *application); + +void nautilus_application_set_accelerator (GApplication *app, + const gchar *action_name, + const gchar *accel); + +void nautilus_application_set_accelerators (GApplication *app, + const gchar *action_name, + const gchar **accels); + +GList * nautilus_application_get_windows (NautilusApplication *application); + +void nautilus_application_open_location (NautilusApplication *application, + GFile *location, + GFile *selection, + const char *startup_id); + +void nautilus_application_open_location_full (NautilusApplication *application, + GFile *location, + NautilusOpenFlags flags, + GList *selection, + NautilusWindow *target_window, + NautilusWindowSlot *target_slot); + +NautilusApplication *nautilus_application_get_default (void); +void nautilus_application_send_notification (NautilusApplication *self, + const gchar *notification_id, + GNotification *notification); +void nautilus_application_withdraw_notification (NautilusApplication *self, + const gchar *notification_id); + +NautilusBookmarkList * + nautilus_application_get_bookmarks (NautilusApplication *application); +void nautilus_application_edit_bookmarks (NautilusApplication *application, + NautilusWindow *window); + +GtkWidget * nautilus_application_connect_server (NautilusApplication *application, + NautilusWindow *window); + +void nautilus_application_search (NautilusApplication *application, + NautilusQuery *query); +void nautilus_application_startup_common (NautilusApplication *application); +gboolean nautilus_application_is_sandboxed (void); +G_END_DECLS diff --git a/src/nautilus-autorun-software.c b/src/nautilus-autorun-software.c new file mode 100644 index 0000000..6d8553f --- /dev/null +++ b/src/nautilus-autorun-software.c @@ -0,0 +1,285 @@ +/* Nautilus + * + * Copyright (C) 2008 Red Hat, Inc. + * + * The Gnome Library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Library General Public License as + * published by the Free Software Foundation; either version 2 of the + * License, or (at your option) any later version. + * + * The Gnome Library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Library General Public License for more details. + * + * You should have received a copy of the GNU Library General Public + * License along with the Gnome Library; see the file COPYING.LIB. If not, + * see . + * + * Author: David Zeuthen + */ + + +#include + +#include +#include +#include +#include +#include +#include +#include + +#include + +typedef struct +{ + GtkWidget *dialog; + GMount *mount; +} AutorunSoftwareDialogData; + +static void autorun_software_dialog_mount_unmounted (GMount *mount, + AutorunSoftwareDialogData *data); + +static void +autorun_software_dialog_destroy (AutorunSoftwareDialogData *data) +{ + g_signal_handlers_disconnect_by_func (G_OBJECT (data->mount), + G_CALLBACK (autorun_software_dialog_mount_unmounted), + data); + + gtk_window_destroy (GTK_WINDOW (data->dialog)); + g_object_unref (data->mount); + g_free (data); +} + +static void +autorun_software_dialog_mount_unmounted (GMount *mount, + AutorunSoftwareDialogData *data) +{ + autorun_software_dialog_destroy (data); +} + +static gboolean +_check_file (GFile *mount_root, + const char *file_path, + gboolean must_be_executable) +{ + g_autoptr (GFile) file = NULL; + g_autoptr (GFileInfo) file_info = NULL; + + file = g_file_get_child (mount_root, file_path); + file_info = g_file_query_info (file, + G_FILE_ATTRIBUTE_ACCESS_CAN_EXECUTE, + G_FILE_QUERY_INFO_NONE, + NULL, + NULL); + + if (file_info == NULL) + { + return FALSE; + } + + if (must_be_executable && + !g_file_info_get_attribute_boolean (file_info, G_FILE_ATTRIBUTE_ACCESS_CAN_EXECUTE)) + { + return FALSE; + } + + return TRUE; +} + +static void +autorun (GMount *mount) +{ + g_autoptr (GFile) root = NULL; + g_autoptr (GFile) program_to_spawn = NULL; + g_autoptr (GFile) program_parameter_file = NULL; + g_autofree char *error_string = NULL; + g_autofree char *path_to_spawn = NULL; + g_autofree char *cwd_for_program = NULL; + g_autofree char *program_parameter = NULL; + + root = g_mount_get_root (mount); + + /* Careful here, according to + * + * http://standards.freedesktop.org/autostart-spec/autostart-spec-latest.html + * + * the ordering does matter. + */ + + if (_check_file (root, ".autorun", TRUE)) + { + program_to_spawn = g_file_get_child (root, ".autorun"); + } + else if (_check_file (root, "autorun", TRUE)) + { + program_to_spawn = g_file_get_child (root, "autorun"); + } + else if (_check_file (root, "autorun.sh", FALSE)) + { + program_to_spawn = g_file_new_for_path ("/bin/sh"); + program_parameter_file = g_file_get_child (root, "autorun.sh"); + } + + if (program_to_spawn != NULL) + { + path_to_spawn = g_file_get_path (program_to_spawn); + } + if (program_parameter_file != NULL) + { + program_parameter = g_file_get_path (program_parameter_file); + } + + cwd_for_program = g_file_get_path (root); + + if (path_to_spawn != NULL && cwd_for_program != NULL) + { + if (chdir (cwd_for_program) == 0) + { + execl (path_to_spawn, path_to_spawn, program_parameter, NULL); + error_string = g_strdup_printf (_("Unable to start the program:\n%s"), strerror (errno)); + goto out; + } + error_string = g_strdup_printf (_("Unable to start the program:\n%s"), strerror (errno)); + goto out; + } + error_string = g_strdup_printf (_("Unable to locate the program")); + +out: + if (error_string != NULL) + { + GtkWidget *dialog; + dialog = adw_message_dialog_new (NULL, + _("Oops! There was a problem running this software."), + error_string); + adw_message_dialog_add_response (ADW_MESSAGE_DIALOG (dialog), "ok", _("_OK")); + adw_message_dialog_set_default_response (ADW_MESSAGE_DIALOG (dialog), "ok"); + + gtk_window_present (GTK_WINDOW (dialog)); + } +} + +static void +autorun_software_dialog_response (GtkDialog *dialog, + gchar *response, + GMount *mount) +{ + if (g_strcmp0 (response, "run") == 0) + { + autorun (mount); + } +} + +static void +present_autorun_for_software_dialog (GMount *mount) +{ + GIcon *icon; + g_autofree char *mount_name = NULL; + GtkWidget *dialog; + AutorunSoftwareDialogData *data; + + mount_name = g_mount_get_name (mount); + + dialog = adw_message_dialog_new (NULL, NULL, _("If you don’t trust this location or aren’t sure, press Cancel.")); + adw_message_dialog_format_heading (ADW_MESSAGE_DIALOG (dialog), + _("“%s” contains software intended to be automatically started. Would you like to run it?"), + mount_name); + + /* TODO: in a star trek future add support for verifying + * software on media (e.g. if it has a certificate, check it + * etc.) + */ + + + icon = g_mount_get_icon (mount); + if (G_IS_THEMED_ICON (icon)) + { + const gchar * const *names; + + names = g_themed_icon_get_names (G_THEMED_ICON (icon)); + + if (names != NULL) + { + gtk_window_set_icon_name (GTK_WINDOW (dialog), names[0]); + } + } + + data = g_new0 (AutorunSoftwareDialogData, 1); + data->dialog = dialog; + data->mount = g_object_ref (mount); + + g_signal_connect (G_OBJECT (mount), + "unmounted", + G_CALLBACK (autorun_software_dialog_mount_unmounted), + data); + + adw_message_dialog_add_responses (ADW_MESSAGE_DIALOG (dialog), + "cancel", _("_Cancel"), + "run", _("_Run"), + NULL); + adw_message_dialog_set_default_response (ADW_MESSAGE_DIALOG (dialog), "cancel"); + + g_signal_connect (dialog, + "response", + G_CALLBACK (autorun_software_dialog_response), + mount); + + gtk_window_present (GTK_WINDOW (dialog)); +} + +int +main (int argc, + char *argv[]) +{ + g_autoptr (GVolumeMonitor) monitor = NULL; + g_autoptr (GFile) file = NULL; + g_autoptr (GMount) mount = NULL; + g_autoptr (GError) error = NULL; + + bindtextdomain (GETTEXT_PACKAGE, LOCALEDIR); + bind_textdomain_codeset (GETTEXT_PACKAGE, "UTF-8"); + textdomain (GETTEXT_PACKAGE); + + gtk_init (); + adw_init (); + + if (argc != 2) + { + g_print ("Usage: %s mount-uri\n", argv[0]); + goto out; + } + + /* instantiate monitor so we get the "unmounted" signal properly */ + monitor = g_volume_monitor_get (); + if (monitor == NULL) + { + g_warning ("Unable to connect to the volume monitor"); + goto out; + } + + file = g_file_new_for_commandline_arg (argv[1]); + if (file == NULL) + { + g_warning ("Unable to parse mount URI"); + goto out; + } + + mount = g_file_find_enclosing_mount (file, NULL, &error); + if (mount == NULL) + { + g_warning ("Unable to find device for URI: %s", error->message); + goto out; + } + + present_autorun_for_software_dialog (mount); + + while (g_list_model_get_n_items (gtk_window_get_toplevels ()) > 0) + { + g_main_context_iteration (NULL, TRUE); + } + +out: + return 0; +} diff --git a/src/nautilus-batch-rename-dialog.c b/src/nautilus-batch-rename-dialog.c new file mode 100644 index 0000000..14f142b --- /dev/null +++ b/src/nautilus-batch-rename-dialog.c @@ -0,0 +1,2040 @@ +/* nautilus-batch-rename-dialog.c + * + * Copyright (C) 2016 Alexandru Pandelea + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 2 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 warranty of + * MERCHANTABILITY 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 . + */ + +#include + +#include "nautilus-batch-rename-dialog.h" +#include "nautilus-file.h" +#include "nautilus-error-reporting.h" +#include "nautilus-batch-rename-utilities.h" + +#include +#include +#include +#include + +#define ROW_MARGIN_START 6 +#define ROW_MARGIN_TOP_BOTTOM 4 + +struct _NautilusBatchRenameDialog +{ + GtkDialog parent; + + GtkWidget *grid; + NautilusWindow *window; + + GtkWidget *cancel_button; + GtkWidget *original_name_listbox; + GtkWidget *arrow_listbox; + GtkWidget *result_listbox; + GtkWidget *name_entry; + GtkWidget *rename_button; + GtkWidget *find_entry; + GtkWidget *mode_stack; + GtkWidget *replace_entry; + GtkWidget *format_mode_button; + GtkWidget *replace_mode_button; + GtkWidget *numbering_order_button; + GtkWidget *numbering_label; + GtkWidget *scrolled_window; + GtkWidget *numbering_revealer; + GtkWidget *conflict_box; + GtkWidget *conflict_label; + GtkWidget *conflict_down; + GtkWidget *conflict_up; + + GList *listbox_labels_new; + GList *listbox_labels_old; + GList *listbox_icons; + GtkSizeGroup *size_group; + + GList *selection; + GList *new_names; + NautilusBatchRenameDialogMode mode; + NautilusDirectory *directory; + + GActionGroup *action_group; + + GMenu *numbering_order_menu; + + GHashTable *create_date; + GList *selection_metadata; + + /* the index of the currently selected conflict */ + gint selected_conflict; + /* total conflicts number */ + gint conflicts_number; + + GList *duplicates; + GList *distinct_parent_directories; + GList *directories_pending_conflict_check; + + /* this hash table has information about the status + * of all tags: availability, if it's currently used + * and position */ + GHashTable *tag_info_table; + + GtkWidget *preselected_row1; + GtkWidget *preselected_row2; + + gint row_height; + gboolean rename_clicked; + + GCancellable *metadata_cancellable; +}; + +typedef struct +{ + gboolean available; + gboolean set; + gint position; + /* if the tag was just added, then we shouldn't update it's position */ + gboolean just_added; + TagConstants tag_constants; +} TagData; + + +static void update_display_text (NautilusBatchRenameDialog *dialog); +static void cancel_conflict_check (NautilusBatchRenameDialog *self); + +G_DEFINE_TYPE (NautilusBatchRenameDialog, nautilus_batch_rename_dialog, GTK_TYPE_DIALOG); + +static void +change_numbering_order (GSimpleAction *action, + GVariant *value, + gpointer user_data) +{ + NautilusBatchRenameDialog *dialog; + const gchar *target_name; + guint i; + + dialog = NAUTILUS_BATCH_RENAME_DIALOG (user_data); + + target_name = g_variant_get_string (value, NULL); + + for (i = 0; i < G_N_ELEMENTS (sorts_constants); i++) + { + if (g_strcmp0 (sorts_constants[i].action_target_name, target_name) == 0) + { + gtk_menu_button_set_label (GTK_MENU_BUTTON (dialog->numbering_order_button), + gettext (sorts_constants[i].label)); + dialog->selection = nautilus_batch_rename_dialog_sort (dialog->selection, + sorts_constants[i].sort_mode, + dialog->create_date); + break; + } + } + + g_simple_action_set_state (action, value); + + update_display_text (dialog); +} + +static void +enable_action (NautilusBatchRenameDialog *self, + const gchar *action_name) +{ + GAction *action; + + action = g_action_map_lookup_action (G_ACTION_MAP (self->action_group), + action_name); + g_simple_action_set_enabled (G_SIMPLE_ACTION (action), TRUE); +} + +static void +disable_action (NautilusBatchRenameDialog *self, + const gchar *action_name) +{ + GAction *action; + + action = g_action_map_lookup_action (G_ACTION_MAP (self->action_group), + action_name); + g_simple_action_set_enabled (G_SIMPLE_ACTION (action), FALSE); +} + +static void +add_tag (NautilusBatchRenameDialog *self, + TagConstants tag_constants) +{ + g_autofree gchar *tag_text_representation = NULL; + gint cursor_position; + TagData *tag_data; + + g_object_get (self->name_entry, "cursor-position", &cursor_position, NULL); + + tag_text_representation = batch_rename_get_tag_text_representation (tag_constants); + tag_data = g_hash_table_lookup (self->tag_info_table, tag_text_representation); + tag_data->available = TRUE; + tag_data->set = TRUE; + tag_data->just_added = TRUE; + tag_data->position = cursor_position; + + /* FIXME: We can add a tag when the cursor is inside a tag, which breaks this. + * We need to check the cursor movement and update the actions acordingly or + * even better add the tag at the end of the previous tag if this happens. + */ + gtk_editable_insert_text (GTK_EDITABLE (self->name_entry), + tag_text_representation, + strlen (tag_text_representation), + &cursor_position); + tag_data->just_added = FALSE; + gtk_editable_set_position (GTK_EDITABLE (self->name_entry), cursor_position); + + gtk_entry_grab_focus_without_selecting (GTK_ENTRY (self->name_entry)); +} + +static void +add_metadata_tag (GSimpleAction *action, + GVariant *value, + gpointer user_data) +{ + NautilusBatchRenameDialog *self; + const gchar *action_name; + guint i; + + self = NAUTILUS_BATCH_RENAME_DIALOG (user_data); + action_name = g_action_get_name (G_ACTION (action)); + + for (i = 0; i < G_N_ELEMENTS (metadata_tags_constants); i++) + { + if (g_strcmp0 (metadata_tags_constants[i].action_name, action_name) == 0) + { + add_tag (self, metadata_tags_constants[i]); + disable_action (self, metadata_tags_constants[i].action_name); + + break; + } + } +} + +static void +add_numbering_tag (GSimpleAction *action, + GVariant *value, + gpointer user_data) +{ + NautilusBatchRenameDialog *self; + const gchar *action_name; + guint i; + + self = NAUTILUS_BATCH_RENAME_DIALOG (user_data); + action_name = g_action_get_name (G_ACTION (action)); + + for (i = 0; i < G_N_ELEMENTS (numbering_tags_constants); i++) + { + if (g_strcmp0 (numbering_tags_constants[i].action_name, action_name) == 0) + { + add_tag (self, numbering_tags_constants[i]); + } + /* We want to allow only one tag of numbering type, so we disable all + * of them */ + disable_action (self, numbering_tags_constants[i].action_name); + } +} + +const GActionEntry dialog_entries[] = +{ + { "numbering-order-changed", NULL, "s", "'name-ascending'", change_numbering_order }, + { "add-numbering-no-zero-pad-tag", add_numbering_tag }, + { "add-numbering-one-zero-pad-tag", add_numbering_tag }, + { "add-numbering-two-zero-pad-tag", add_numbering_tag }, + { "add-original-file-name-tag", add_metadata_tag }, + { "add-creation-date-tag", add_metadata_tag }, + { "add-equipment-tag", add_metadata_tag }, + { "add-season-number-tag", add_metadata_tag }, + { "add-episode-number-tag", add_metadata_tag }, + { "add-video-album-tag", add_metadata_tag }, + { "add-track-number-tag", add_metadata_tag }, + { "add-artist-name-tag", add_metadata_tag }, + { "add-title-tag", add_metadata_tag }, + { "add-album-name-tag", add_metadata_tag }, +}; + +static gint +compare_int (gconstpointer a, + gconstpointer b) +{ + int *number1 = (int *) a; + int *number2 = (int *) b; + + return *number1 - *number2; +} + +/* This function splits the entry text into a list of regular text and tags. + * For instance, "[1, 2, 3]Paris[Creation date]" would result in: + * "[1, 2, 3]", "Paris", "[Creation date]" */ +static GList * +split_entry_text (NautilusBatchRenameDialog *self, + gchar *entry_text) +{ + GString *normal_text; + GString *tag; + GArray *tag_positions; + g_autoptr (GList) tag_info_keys = NULL; + GList *l; + gint tags; + gint i; + gchar *substring; + gint tag_end_position; + GList *result = NULL; + TagData *tag_data; + + tags = 0; + tag_end_position = 0; + tag_positions = g_array_new (FALSE, FALSE, sizeof (gint)); + + tag_info_keys = g_hash_table_get_keys (self->tag_info_table); + + for (l = tag_info_keys; l != NULL; l = l->next) + { + tag_data = g_hash_table_lookup (self->tag_info_table, l->data); + if (tag_data->set) + { + g_array_append_val (tag_positions, tag_data->position); + tags++; + } + } + + g_array_sort (tag_positions, compare_int); + + for (i = 0; i < tags; i++) + { + tag = g_string_new (""); + + substring = g_utf8_substring (entry_text, tag_end_position, + g_array_index (tag_positions, gint, i)); + normal_text = g_string_new (substring); + g_free (substring); + + if (g_strcmp0 (normal_text->str, "")) + { + result = g_list_prepend (result, normal_text); + } + else + { + g_string_free (normal_text, TRUE); + } + + for (l = tag_info_keys; l != NULL; l = l->next) + { + g_autofree gchar *tag_text_representation = NULL; + + tag_data = g_hash_table_lookup (self->tag_info_table, l->data); + if (tag_data->set && g_array_index (tag_positions, gint, i) == tag_data->position) + { + tag_text_representation = batch_rename_get_tag_text_representation (tag_data->tag_constants); + tag_end_position = g_array_index (tag_positions, gint, i) + + g_utf8_strlen (tag_text_representation, -1); + tag = g_string_append (tag, tag_text_representation); + + break; + } + } + + result = g_list_prepend (result, tag); + } + + normal_text = g_string_new (g_utf8_offset_to_pointer (entry_text, tag_end_position)); + + if (g_strcmp0 (normal_text->str, "") != 0) + { + result = g_list_prepend (result, normal_text); + } + else + { + g_string_free (normal_text, TRUE); + } + + result = g_list_reverse (result); + + g_array_free (tag_positions, TRUE); + return result; +} + +static GList * +batch_rename_dialog_get_new_names (NautilusBatchRenameDialog *dialog) +{ + GList *result = NULL; + GList *selection; + GList *text_chunks; + g_autofree gchar *entry_text = NULL; + g_autofree gchar *replace_text = NULL; + + selection = dialog->selection; + text_chunks = NULL; + + if (dialog->mode == NAUTILUS_BATCH_RENAME_DIALOG_REPLACE) + { + entry_text = g_strdup (gtk_editable_get_text (GTK_EDITABLE (dialog->find_entry))); + } + else + { + entry_text = g_strdup (gtk_editable_get_text (GTK_EDITABLE (dialog->name_entry))); + } + + replace_text = g_strdup (gtk_editable_get_text (GTK_EDITABLE (dialog->replace_entry))); + + if (dialog->mode == NAUTILUS_BATCH_RENAME_DIALOG_REPLACE) + { + result = batch_rename_dialog_get_new_names_list (dialog->mode, + selection, + NULL, + NULL, + entry_text, + replace_text); + } + else + { + text_chunks = split_entry_text (dialog, entry_text); + + result = batch_rename_dialog_get_new_names_list (dialog->mode, + selection, + text_chunks, + dialog->selection_metadata, + entry_text, + replace_text); + g_list_free_full (text_chunks, string_free); + } + + result = g_list_reverse (result); + + return result; +} + +static void +begin_batch_rename (NautilusBatchRenameDialog *dialog, + GList *new_names) +{ + batch_rename_sort_lists_for_rename (&dialog->selection, &new_names, NULL, NULL, NULL, FALSE); + + /* do the actual rename here */ + nautilus_file_batch_rename (dialog->selection, new_names, NULL, NULL); + + gtk_widget_set_cursor (GTK_WIDGET (dialog->window), NULL); +} + +static void +listbox_header_func (GtkListBoxRow *row, + GtkListBoxRow *before, + NautilusBatchRenameDialog *dialog) +{ + GtkWidget *separator; + + if (before == NULL) + { + /* First row needs no separator */ + gtk_list_box_row_set_header (row, NULL); + return; + } + + separator = gtk_list_box_row_get_header (row); + if (separator == NULL) + { + separator = gtk_separator_new (GTK_ORIENTATION_HORIZONTAL); + gtk_widget_show (separator); + + gtk_list_box_row_set_header (row, separator); + } +} + +/* This is manually done instead of using GtkSizeGroup because of the computational + * complexity of the later.*/ +static void +update_rows_height (NautilusBatchRenameDialog *dialog) +{ + GList *l; + GtkRequisition current_row_natural_size; + gint maximum_height; + + maximum_height = -1; + + /* check if maximum height has changed */ + for (l = dialog->listbox_labels_new; l != NULL; l = l->next) + { + gtk_widget_get_preferred_size (GTK_WIDGET (l->data), + NULL, + ¤t_row_natural_size); + + if (current_row_natural_size.height > maximum_height) + { + maximum_height = current_row_natural_size.height; + } + } + + for (l = dialog->listbox_labels_old; l != NULL; l = l->next) + { + gtk_widget_get_preferred_size (GTK_WIDGET (l->data), + NULL, + ¤t_row_natural_size); + + if (current_row_natural_size.height > maximum_height) + { + maximum_height = current_row_natural_size.height; + } + } + + for (l = dialog->listbox_icons; l != NULL; l = l->next) + { + gtk_widget_get_preferred_size (GTK_WIDGET (l->data), + NULL, + ¤t_row_natural_size); + + if (current_row_natural_size.height > maximum_height) + { + maximum_height = current_row_natural_size.height; + } + } + + if (maximum_height != dialog->row_height) + { + dialog->row_height = maximum_height + ROW_MARGIN_TOP_BOTTOM * 2; + + for (l = dialog->listbox_icons; l != NULL; l = l->next) + { + g_object_set (G_OBJECT (l->data), "height-request", dialog->row_height, NULL); + } + + for (l = dialog->listbox_labels_new; l != NULL; l = l->next) + { + g_object_set (G_OBJECT (l->data), "height-request", dialog->row_height, NULL); + } + + for (l = dialog->listbox_labels_old; l != NULL; l = l->next) + { + g_object_set (G_OBJECT (l->data), "height-request", dialog->row_height, NULL); + } + } +} + +static GtkWidget * +create_original_name_label (NautilusBatchRenameDialog *dialog, + const gchar *old_text) +{ + GtkWidget *label_old; + + label_old = gtk_label_new (old_text); + gtk_label_set_xalign (GTK_LABEL (label_old), 0.0); + gtk_widget_set_hexpand (label_old, TRUE); + gtk_widget_set_margin_start (label_old, ROW_MARGIN_START); + gtk_label_set_ellipsize (GTK_LABEL (label_old), PANGO_ELLIPSIZE_END); + + dialog->listbox_labels_old = g_list_prepend (dialog->listbox_labels_old, label_old); + + gtk_widget_show (label_old); + + return label_old; +} + +static GtkWidget * +create_result_label (NautilusBatchRenameDialog *dialog, + const gchar *new_text) +{ + GtkWidget *label_new; + + label_new = gtk_label_new (new_text); + gtk_label_set_xalign (GTK_LABEL (label_new), 0.0); + gtk_widget_set_hexpand (label_new, TRUE); + gtk_widget_set_margin_start (label_new, ROW_MARGIN_START); + gtk_label_set_ellipsize (GTK_LABEL (label_new), PANGO_ELLIPSIZE_END); + + dialog->listbox_labels_new = g_list_prepend (dialog->listbox_labels_new, label_new); + + gtk_widget_show (label_new); + + return label_new; +} + +static GtkWidget * +create_arrow (NautilusBatchRenameDialog *dialog, + GtkTextDirection text_direction) +{ + GtkWidget *icon; + + if (text_direction == GTK_TEXT_DIR_RTL) + { + icon = gtk_label_new ("←"); + } + else + { + icon = gtk_label_new ("→"); + } + + gtk_label_set_xalign (GTK_LABEL (icon), 1.0); + gtk_widget_set_hexpand (icon, FALSE); + gtk_widget_set_margin_start (icon, ROW_MARGIN_START); + + dialog->listbox_icons = g_list_prepend (dialog->listbox_icons, icon); + + gtk_widget_show (icon); + + return icon; +} + +static void +prepare_batch_rename (NautilusBatchRenameDialog *dialog) +{ + /* wait for checking conflicts to finish, to be sure that + * the rename can actually take place */ + if (dialog->directories_pending_conflict_check != NULL) + { + dialog->rename_clicked = TRUE; + return; + } + + if (!gtk_widget_is_sensitive (dialog->rename_button)) + { + return; + } + + gtk_widget_set_cursor_from_name (GTK_WIDGET (dialog->window), "progress"); + + gtk_widget_set_cursor_from_name (GTK_WIDGET (dialog), "progress"); + + gtk_widget_hide (GTK_WIDGET (dialog)); + begin_batch_rename (dialog, dialog->new_names); + + gtk_window_destroy (GTK_WINDOW (dialog)); +} + +static void +batch_rename_dialog_on_response (NautilusBatchRenameDialog *dialog, + gint response_id, + gpointer user_data) +{ + if (response_id == GTK_RESPONSE_OK) + { + prepare_batch_rename (dialog); + } + else + { + if (dialog->directories_pending_conflict_check != NULL) + { + cancel_conflict_check (dialog); + } + + gtk_window_destroy (GTK_WINDOW (dialog)); + } +} + +static void +fill_display_listbox (NautilusBatchRenameDialog *dialog) +{ + GtkWidget *row_child; + GList *l1; + GList *l2; + NautilusFile *file; + GString *new_name; + gchar *name; + GtkTextDirection text_direction; + + gtk_size_group_add_widget (dialog->size_group, dialog->result_listbox); + gtk_size_group_add_widget (dialog->size_group, dialog->original_name_listbox); + + text_direction = gtk_widget_get_direction (GTK_WIDGET (dialog)); + + for (l1 = dialog->new_names, l2 = dialog->selection; l1 != NULL && l2 != NULL; l1 = l1->next, l2 = l2->next) + { + file = NAUTILUS_FILE (l2->data); + new_name = l1->data; + + name = nautilus_file_get_name (file); + row_child = create_original_name_label (dialog, name); + gtk_list_box_insert (GTK_LIST_BOX (dialog->original_name_listbox), row_child, -1); + + row_child = create_arrow (dialog, text_direction); + gtk_list_box_insert (GTK_LIST_BOX (dialog->arrow_listbox), row_child, -1); + + row_child = create_result_label (dialog, new_name->str); + gtk_list_box_insert (GTK_LIST_BOX (dialog->result_listbox), row_child, -1); + + g_free (name); + } + + dialog->listbox_labels_old = g_list_reverse (dialog->listbox_labels_old); + dialog->listbox_labels_new = g_list_reverse (dialog->listbox_labels_new); + dialog->listbox_icons = g_list_reverse (dialog->listbox_icons); +} + +static void +select_nth_conflict (NautilusBatchRenameDialog *dialog) +{ + GList *l; + GString *conflict_file_name; + GString *display_text; + GString *new_name; + gint nth_conflict_index; + gint nth_conflict; + gint name_occurences; + GtkAdjustment *adjustment; + GtkAllocation allocation; + ConflictData *conflict_data; + GtkListBoxRow *list_box_row; + + nth_conflict = dialog->selected_conflict; + l = g_list_nth (dialog->duplicates, nth_conflict); + conflict_data = l->data; + + /* the conflict that has to be selected */ + conflict_file_name = g_string_new (conflict_data->name); + display_text = g_string_new (""); + + nth_conflict_index = conflict_data->index; + + l = g_list_nth (dialog->listbox_labels_new, nth_conflict_index); + list_box_row = GTK_LIST_BOX_ROW (gtk_widget_get_parent (l->data)); + gtk_list_box_select_row (GTK_LIST_BOX (dialog->original_name_listbox), + list_box_row); + + l = g_list_nth (dialog->listbox_labels_old, nth_conflict_index); + list_box_row = GTK_LIST_BOX_ROW (gtk_widget_get_parent (l->data)); + gtk_list_box_select_row (GTK_LIST_BOX (dialog->arrow_listbox), + list_box_row); + + l = g_list_nth (dialog->listbox_icons, nth_conflict_index); + list_box_row = GTK_LIST_BOX_ROW (gtk_widget_get_parent (l->data)); + gtk_list_box_select_row (GTK_LIST_BOX (dialog->result_listbox), + list_box_row); + + /* scroll to the selected row */ + adjustment = gtk_scrolled_window_get_vadjustment (GTK_SCROLLED_WINDOW (dialog->scrolled_window)); + gtk_widget_get_allocation (GTK_WIDGET (l->data), &allocation); + gtk_adjustment_set_value (adjustment, (allocation.height + 1) * nth_conflict_index); + + name_occurences = 0; + for (l = dialog->new_names; l != NULL; l = l->next) + { + new_name = l->data; + if (g_string_equal (new_name, conflict_file_name)) + { + name_occurences++; + } + } + if (name_occurences > 1) + { + g_string_append_printf (display_text, + _("“%s” would not be a unique new name."), + conflict_file_name->str); + } + else + { + g_string_append_printf (display_text, + _("“%s” would conflict with an existing file."), + conflict_file_name->str); + } + + gtk_label_set_label (GTK_LABEL (dialog->conflict_label), + display_text->str); + + g_string_free (conflict_file_name, TRUE); + g_string_free (display_text, TRUE); +} + +static void +select_next_conflict_down (NautilusBatchRenameDialog *dialog) +{ + dialog->selected_conflict++; + + if (dialog->selected_conflict == 1) + { + gtk_widget_set_sensitive (dialog->conflict_up, TRUE); + } + + if (dialog->selected_conflict == dialog->conflicts_number - 1) + { + gtk_widget_set_sensitive (dialog->conflict_down, FALSE); + } + + select_nth_conflict (dialog); +} + +static void +select_next_conflict_up (NautilusBatchRenameDialog *dialog) +{ + dialog->selected_conflict--; + + if (dialog->selected_conflict == 0) + { + gtk_widget_set_sensitive (dialog->conflict_up, FALSE); + } + + if (dialog->selected_conflict == dialog->conflicts_number - 2) + { + gtk_widget_set_sensitive (dialog->conflict_down, TRUE); + } + + select_nth_conflict (dialog); +} + +static void +update_conflict_row_background (NautilusBatchRenameDialog *dialog) +{ + GList *l1; + GList *l2; + GList *l3; + GList *duplicates; + gint index; + GtkStyleContext *context; + ConflictData *conflict_data; + + index = 0; + + duplicates = dialog->duplicates; + + for (l1 = dialog->listbox_labels_new, + l2 = dialog->listbox_labels_old, + l3 = dialog->listbox_icons; + l1 != NULL && l2 != NULL && l3 != NULL; + l1 = l1->next, l2 = l2->next, l3 = l3->next) + { + GtkWidget *row1 = gtk_widget_get_parent (l1->data); + GtkWidget *row2 = gtk_widget_get_parent (l2->data); + GtkWidget *row3 = gtk_widget_get_parent (l3->data); + + context = gtk_widget_get_style_context (row1); + + if (gtk_style_context_has_class (context, "conflict-row")) + { + gtk_style_context_remove_class (context, "conflict-row"); + + context = gtk_widget_get_style_context (row2); + gtk_style_context_remove_class (context, "conflict-row"); + + context = gtk_widget_get_style_context (row3); + gtk_style_context_remove_class (context, "conflict-row"); + } + + if (duplicates != NULL) + { + conflict_data = duplicates->data; + if (conflict_data->index == index) + { + context = gtk_widget_get_style_context (row1); + gtk_style_context_add_class (context, "conflict-row"); + + context = gtk_widget_get_style_context (row2); + gtk_style_context_add_class (context, "conflict-row"); + + context = gtk_widget_get_style_context (row3); + gtk_style_context_add_class (context, "conflict-row"); + + duplicates = duplicates->next; + } + } + index++; + } +} + +static void +update_listbox (NautilusBatchRenameDialog *dialog) +{ + GList *l1; + GList *l2; + NautilusFile *file; + gchar *old_name; + GtkLabel *label; + GString *new_name; + gboolean empty_name = FALSE; + + for (l1 = dialog->new_names, l2 = dialog->listbox_labels_new; l1 != NULL && l2 != NULL; l1 = l1->next, l2 = l2->next) + { + label = GTK_LABEL (l2->data); + new_name = l1->data; + + gtk_label_set_label (label, new_name->str); + gtk_widget_set_tooltip_text (GTK_WIDGET (label), new_name->str); + + if (g_strcmp0 (new_name->str, "") == 0) + { + empty_name = TRUE; + } + } + + for (l1 = dialog->selection, l2 = dialog->listbox_labels_old; l1 != NULL && l2 != NULL; l1 = l1->next, l2 = l2->next) + { + label = GTK_LABEL (l2->data); + file = NAUTILUS_FILE (l1->data); + + old_name = nautilus_file_get_name (file); + gtk_widget_set_tooltip_text (GTK_WIDGET (label), old_name); + + if (dialog->mode == NAUTILUS_BATCH_RENAME_DIALOG_FORMAT) + { + gtk_label_set_label (label, old_name); + } + else + { + new_name = batch_rename_replace_label_text (old_name, + gtk_editable_get_text (GTK_EDITABLE (dialog->find_entry))); + gtk_label_set_markup (GTK_LABEL (label), new_name->str); + + g_string_free (new_name, TRUE); + } + + g_free (old_name); + } + + update_rows_height (dialog); + + if (empty_name) + { + gtk_widget_set_sensitive (dialog->rename_button, FALSE); + + return; + } + + /* check if there are name conflicts and display them if they exist */ + if (dialog->duplicates != NULL) + { + update_conflict_row_background (dialog); + + gtk_widget_set_sensitive (dialog->rename_button, FALSE); + + gtk_widget_show (dialog->conflict_box); + + dialog->selected_conflict = 0; + dialog->conflicts_number = g_list_length (dialog->duplicates); + + select_nth_conflict (dialog); + + gtk_widget_set_sensitive (dialog->conflict_up, FALSE); + + if (g_list_length (dialog->duplicates) == 1) + { + gtk_widget_set_sensitive (dialog->conflict_down, FALSE); + } + else + { + gtk_widget_set_sensitive (dialog->conflict_down, TRUE); + } + } + else + { + gtk_widget_hide (dialog->conflict_box); + + /* re-enable the rename button if there are no more name conflicts */ + if (dialog->duplicates == NULL && !gtk_widget_is_sensitive (dialog->rename_button)) + { + update_conflict_row_background (dialog); + gtk_widget_set_sensitive (dialog->rename_button, TRUE); + } + } + + /* if the rename button was clicked and there's no conflict, then start renaming */ + if (dialog->rename_clicked && dialog->duplicates == NULL) + { + prepare_batch_rename (dialog); + } + + if (dialog->rename_clicked && dialog->duplicates != NULL) + { + dialog->rename_clicked = FALSE; + } +} + +static void +check_conflict_for_files (NautilusBatchRenameDialog *dialog, + NautilusDirectory *directory, + GList *files) +{ + gchar *current_directory; + gchar *parent_uri; + gchar *name; + NautilusFile *file; + GString *new_name; + GString *file_name; + GList *l1, *l2; + GHashTable *directory_files_table; + GHashTable *new_names_table; + GHashTable *names_conflicts_table; + gboolean exists; + gboolean have_conflict; + gboolean tag_present; + gboolean same_parent_directory; + ConflictData *conflict_data; + + current_directory = nautilus_directory_get_uri (directory); + + directory_files_table = g_hash_table_new_full (g_str_hash, + g_str_equal, + (GDestroyNotify) g_free, + NULL); + new_names_table = g_hash_table_new_full (g_str_hash, + g_str_equal, + (GDestroyNotify) g_free, + (GDestroyNotify) g_free); + names_conflicts_table = g_hash_table_new_full (g_str_hash, + g_str_equal, + (GDestroyNotify) g_free, + (GDestroyNotify) g_free); + + /* names_conflicts_table is used for knowing which names from the list are not unique, + * so that they can easily be reached when needed */ + for (l1 = dialog->new_names, l2 = dialog->selection; + l1 != NULL && l2 != NULL; + l1 = l1->next, l2 = l2->next) + { + new_name = l1->data; + file = NAUTILUS_FILE (l2->data); + parent_uri = nautilus_file_get_parent_uri (file); + + tag_present = g_hash_table_lookup (new_names_table, new_name->str) != NULL; + same_parent_directory = g_strcmp0 (parent_uri, current_directory) == 0; + + if (same_parent_directory) + { + if (!tag_present) + { + g_hash_table_insert (new_names_table, + g_strdup (new_name->str), + nautilus_file_get_parent_uri (file)); + } + else + { + g_hash_table_insert (names_conflicts_table, + g_strdup (new_name->str), + nautilus_file_get_parent_uri (file)); + } + } + + g_free (parent_uri); + } + + for (l1 = files; l1 != NULL; l1 = l1->next) + { + file = NAUTILUS_FILE (l1->data); + g_hash_table_insert (directory_files_table, + nautilus_file_get_name (file), + GINT_TO_POINTER (TRUE)); + } + + for (l1 = dialog->selection, l2 = dialog->new_names; l1 != NULL && l2 != NULL; l1 = l1->next, l2 = l2->next) + { + file = NAUTILUS_FILE (l1->data); + + name = nautilus_file_get_name (file); + file_name = g_string_new (name); + g_free (name); + + parent_uri = nautilus_file_get_parent_uri (file); + + new_name = l2->data; + + have_conflict = FALSE; + + /* check for duplicate only if the parent of the current file is + * the current directory and the name of the file has changed */ + if (g_strcmp0 (parent_uri, current_directory) == 0 && + !g_string_equal (new_name, file_name)) + { + exists = GPOINTER_TO_INT (g_hash_table_lookup (directory_files_table, new_name->str)); + + if (exists == TRUE && + !file_name_conflicts_with_results (dialog->selection, dialog->new_names, new_name, parent_uri)) + { + conflict_data = g_new (ConflictData, 1); + conflict_data->name = g_strdup (new_name->str); + conflict_data->index = g_list_index (dialog->selection, l1->data); + dialog->duplicates = g_list_prepend (dialog->duplicates, + conflict_data); + + have_conflict = TRUE; + } + } + + if (!have_conflict) + { + tag_present = g_hash_table_lookup (names_conflicts_table, new_name->str) != NULL; + same_parent_directory = g_strcmp0 (parent_uri, current_directory) == 0; + + if (tag_present && same_parent_directory) + { + conflict_data = g_new (ConflictData, 1); + conflict_data->name = g_strdup (new_name->str); + conflict_data->index = g_list_index (dialog->selection, l1->data); + dialog->duplicates = g_list_prepend (dialog->duplicates, + conflict_data); + + have_conflict = TRUE; + } + } + + g_string_free (file_name, TRUE); + g_free (parent_uri); + } + + g_free (current_directory); + g_hash_table_destroy (directory_files_table); + g_hash_table_destroy (new_names_table); + g_hash_table_destroy (names_conflicts_table); +} + +static void +on_directory_attributes_ready_for_conflicts_check (NautilusDirectory *conflict_directory, + GList *files, + gpointer callback_data) +{ + NautilusBatchRenameDialog *self; + + self = NAUTILUS_BATCH_RENAME_DIALOG (callback_data); + + check_conflict_for_files (self, conflict_directory, files); + + g_assert (g_list_find (self->directories_pending_conflict_check, conflict_directory) != NULL); + + self->directories_pending_conflict_check = g_list_remove (self->directories_pending_conflict_check, conflict_directory); + + nautilus_directory_unref (conflict_directory); + + if (self->directories_pending_conflict_check == NULL) + { + self->duplicates = g_list_reverse (self->duplicates); + + update_listbox (self); + } +} + +static void +cancel_conflict_check (NautilusBatchRenameDialog *self) +{ + GList *l; + NautilusDirectory *directory; + + for (l = self->directories_pending_conflict_check; l != NULL; l = l->next) + { + directory = l->data; + + nautilus_directory_cancel_callback (directory, + on_directory_attributes_ready_for_conflicts_check, + self); + } + + g_clear_list (&self->directories_pending_conflict_check, g_object_unref); +} + +static void +file_names_list_has_duplicates_async (NautilusBatchRenameDialog *self) +{ + GList *l; + + if (self->directories_pending_conflict_check != NULL) + { + cancel_conflict_check (self); + } + + self->directories_pending_conflict_check = nautilus_directory_list_copy (self->distinct_parent_directories); + self->duplicates = NULL; + + for (l = self->distinct_parent_directories; l != NULL; l = l->next) + { + nautilus_directory_call_when_ready (l->data, + NAUTILUS_FILE_ATTRIBUTE_INFO, + TRUE, + on_directory_attributes_ready_for_conflicts_check, + self); + } +} + +static gboolean +have_unallowed_character (NautilusBatchRenameDialog *dialog) +{ + GList *names; + GString *new_name; + const gchar *entry_text; + gboolean have_empty_name; + gboolean have_unallowed_character_slash; + gboolean have_unallowed_character_dot; + gboolean have_unallowed_character_dotdot; + + have_empty_name = FALSE; + have_unallowed_character_slash = FALSE; + have_unallowed_character_dot = FALSE; + have_unallowed_character_dotdot = FALSE; + + if (dialog->mode == NAUTILUS_BATCH_RENAME_DIALOG_FORMAT) + { + entry_text = gtk_editable_get_text (GTK_EDITABLE (dialog->name_entry)); + } + else + { + entry_text = gtk_editable_get_text (GTK_EDITABLE (dialog->replace_entry)); + } + + if (strstr (entry_text, "/") != NULL) + { + have_unallowed_character_slash = TRUE; + } + + if (dialog->mode == NAUTILUS_BATCH_RENAME_DIALOG_FORMAT && g_strcmp0 (entry_text, ".") == 0) + { + have_unallowed_character_dot = TRUE; + } + else if (dialog->mode == NAUTILUS_BATCH_RENAME_DIALOG_REPLACE) + { + for (names = dialog->new_names; names != NULL; names = names->next) + { + new_name = names->data; + + if (g_strcmp0 (new_name->str, ".") == 0) + { + have_unallowed_character_dot = TRUE; + break; + } + } + } + + if (dialog->mode == NAUTILUS_BATCH_RENAME_DIALOG_FORMAT && g_strcmp0 (entry_text, "..") == 0) + { + have_unallowed_character_dotdot = TRUE; + } + else if (dialog->mode == NAUTILUS_BATCH_RENAME_DIALOG_REPLACE) + { + for (names = dialog->new_names; names != NULL; names = names->next) + { + new_name = names->data; + + if (g_strcmp0 (new_name->str, "") == 0) + { + have_empty_name = TRUE; + break; + } + + if (g_strcmp0 (new_name->str, "..") == 0) + { + have_unallowed_character_dotdot = TRUE; + break; + } + } + } + + if (have_empty_name) + { + gtk_label_set_label (GTK_LABEL (dialog->conflict_label), + _("Name cannot be empty.")); + } + + if (have_unallowed_character_slash) + { + gtk_label_set_label (GTK_LABEL (dialog->conflict_label), + _("Name cannot contain “/”.")); + } + + if (have_unallowed_character_dot) + { + gtk_label_set_label (GTK_LABEL (dialog->conflict_label), + _("“.” is not a valid name.")); + } + + if (have_unallowed_character_dotdot) + { + gtk_label_set_label (GTK_LABEL (dialog->conflict_label), + _("“..” is not a valid name.")); + } + + if (have_unallowed_character_slash || have_unallowed_character_dot || have_unallowed_character_dotdot + || have_empty_name) + { + gtk_widget_set_sensitive (dialog->rename_button, FALSE); + gtk_widget_set_sensitive (dialog->conflict_down, FALSE); + gtk_widget_set_sensitive (dialog->conflict_up, FALSE); + + gtk_widget_show (dialog->conflict_box); + + return TRUE; + } + else + { + gtk_widget_hide (dialog->conflict_box); + + return FALSE; + } +} + +static gboolean +numbering_tag_is_some_added (NautilusBatchRenameDialog *self) +{ + guint i; + TagData *tag_data; + + for (i = 0; i < G_N_ELEMENTS (numbering_tags_constants); i++) + { + g_autofree gchar *tag_text_representation = NULL; + + tag_text_representation = batch_rename_get_tag_text_representation (numbering_tags_constants[i]); + tag_data = g_hash_table_lookup (self->tag_info_table, tag_text_representation); + if (tag_data->set) + { + return TRUE; + } + } + + return FALSE; +} + +static void +update_display_text (NautilusBatchRenameDialog *dialog) +{ + if (dialog->selection == NULL) + { + return; + } + + if (dialog->duplicates != NULL) + { + g_list_free_full (dialog->duplicates, conflict_data_free); + dialog->duplicates = NULL; + } + + if (dialog->new_names != NULL) + { + g_list_free_full (dialog->new_names, string_free); + } + + if (!numbering_tag_is_some_added (dialog)) + { + gtk_revealer_set_reveal_child (GTK_REVEALER (dialog->numbering_revealer), FALSE); + } + else + { + gtk_revealer_set_reveal_child (GTK_REVEALER (dialog->numbering_revealer), TRUE); + } + + dialog->new_names = batch_rename_dialog_get_new_names (dialog); + + if (have_unallowed_character (dialog)) + { + return; + } + + file_names_list_has_duplicates_async (dialog); +} + +static void +batch_rename_dialog_mode_changed (NautilusBatchRenameDialog *dialog) +{ + if (gtk_check_button_get_active (GTK_CHECK_BUTTON (dialog->format_mode_button))) + { + gtk_stack_set_visible_child_name (GTK_STACK (dialog->mode_stack), "format"); + + dialog->mode = NAUTILUS_BATCH_RENAME_DIALOG_FORMAT; + + gtk_entry_grab_focus_without_selecting (GTK_ENTRY (dialog->name_entry)); + } + else + { + gtk_stack_set_visible_child_name (GTK_STACK (dialog->mode_stack), "replace"); + + dialog->mode = NAUTILUS_BATCH_RENAME_DIALOG_REPLACE; + + gtk_entry_grab_focus_without_selecting (GTK_ENTRY (dialog->find_entry)); + } + + update_display_text (dialog); +} + +void +nautilus_batch_rename_dialog_query_finished (NautilusBatchRenameDialog *dialog, + GHashTable *hash_table, + GList *selection_metadata) +{ + GMenuItem *first_created; + GMenuItem *last_created; + FileMetadata *file_metadata; + MetadataType metadata_type; + gboolean is_metadata; + TagData *tag_data; + g_autoptr (GList) tag_info_keys = NULL; + GList *l; + + /* for files with no metadata */ + if (hash_table != NULL && g_hash_table_size (hash_table) == 0) + { + g_hash_table_destroy (hash_table); + + hash_table = NULL; + } + + if (hash_table == NULL) + { + dialog->create_date = NULL; + } + else + { + dialog->create_date = hash_table; + } + + if (dialog->create_date != NULL) + { + first_created = g_menu_item_new ("First Created", + "dialog.numbering-order-changed('first-created')"); + + g_menu_append_item (dialog->numbering_order_menu, first_created); + + last_created = g_menu_item_new ("Last Created", + "dialog.numbering-order-changed('last-created')"); + + g_menu_append_item (dialog->numbering_order_menu, last_created); + } + + dialog->selection_metadata = selection_metadata; + file_metadata = selection_metadata->data; + tag_info_keys = g_hash_table_get_keys (dialog->tag_info_table); + for (l = tag_info_keys; l != NULL; l = l->next) + { + /* Only metadata has to be handled here. */ + tag_data = g_hash_table_lookup (dialog->tag_info_table, l->data); + is_metadata = tag_data->tag_constants.is_metadata; + if (!is_metadata) + { + continue; + } + + metadata_type = tag_data->tag_constants.metadata_type; + if (file_metadata->metadata[metadata_type] == NULL || + file_metadata->metadata[metadata_type]->len <= 0) + { + disable_action (dialog, tag_data->tag_constants.action_name); + tag_data->available = FALSE; + } + } +} + +static void +update_row_shadowing (GtkWidget *row, + gboolean shown) +{ + GtkStyleContext *context; + GtkStateFlags flags; + + if (!GTK_IS_LIST_BOX_ROW (row)) + { + return; + } + + context = gtk_widget_get_style_context (row); + flags = gtk_style_context_get_state (context); + + if (shown) + { + flags |= GTK_STATE_FLAG_PRELIGHT; + } + else + { + flags &= ~GTK_STATE_FLAG_PRELIGHT; + } + + gtk_style_context_set_state (context, flags); +} + +static void +on_event_controller_motion_motion (GtkEventControllerMotion *controller, + double x, + double y, + gpointer user_data) +{ + GtkWidget *widget; + NautilusBatchRenameDialog *dialog; + GtkListBoxRow *row; + + widget = gtk_event_controller_get_widget (GTK_EVENT_CONTROLLER (controller)); + dialog = NAUTILUS_BATCH_RENAME_DIALOG (user_data); + + if (dialog->preselected_row1 && dialog->preselected_row2) + { + update_row_shadowing (dialog->preselected_row1, FALSE); + update_row_shadowing (dialog->preselected_row2, FALSE); + } + + if (widget == dialog->result_listbox) + { + row = gtk_list_box_get_row_at_y (GTK_LIST_BOX (dialog->original_name_listbox), y); + update_row_shadowing (GTK_WIDGET (row), TRUE); + dialog->preselected_row1 = GTK_WIDGET (row); + + row = gtk_list_box_get_row_at_y (GTK_LIST_BOX (dialog->arrow_listbox), y); + update_row_shadowing (GTK_WIDGET (row), TRUE); + dialog->preselected_row2 = GTK_WIDGET (row); + } + + if (widget == dialog->arrow_listbox) + { + row = gtk_list_box_get_row_at_y (GTK_LIST_BOX (dialog->original_name_listbox), y); + update_row_shadowing (GTK_WIDGET (row), TRUE); + dialog->preselected_row1 = GTK_WIDGET (row); + + row = gtk_list_box_get_row_at_y (GTK_LIST_BOX (dialog->result_listbox), y); + update_row_shadowing (GTK_WIDGET (row), TRUE); + dialog->preselected_row2 = GTK_WIDGET (row); + } + + if (widget == dialog->original_name_listbox) + { + row = gtk_list_box_get_row_at_y (GTK_LIST_BOX (dialog->result_listbox), y); + update_row_shadowing (GTK_WIDGET (row), TRUE); + dialog->preselected_row1 = GTK_WIDGET (row); + + row = gtk_list_box_get_row_at_y (GTK_LIST_BOX (dialog->arrow_listbox), y); + update_row_shadowing (GTK_WIDGET (row), TRUE); + dialog->preselected_row2 = GTK_WIDGET (row); + } +} + +static void +on_event_controller_motion_leave (GtkEventControllerMotion *controller, + gpointer user_data) +{ + NautilusBatchRenameDialog *dialog; + + dialog = NAUTILUS_BATCH_RENAME_DIALOG (user_data); + + update_row_shadowing (dialog->preselected_row1, FALSE); + update_row_shadowing (dialog->preselected_row2, FALSE); + + dialog->preselected_row1 = NULL; + dialog->preselected_row2 = NULL; +} + +static void +nautilus_batch_rename_dialog_initialize_actions (NautilusBatchRenameDialog *dialog) +{ + GAction *action; + + dialog->action_group = G_ACTION_GROUP (g_simple_action_group_new ()); + + g_action_map_add_action_entries (G_ACTION_MAP (dialog->action_group), + dialog_entries, + G_N_ELEMENTS (dialog_entries), + dialog); + gtk_widget_insert_action_group (GTK_WIDGET (dialog), + "dialog", + G_ACTION_GROUP (dialog->action_group)); + + action = g_action_map_lookup_action (G_ACTION_MAP (dialog->action_group), + metadata_tags_constants[ORIGINAL_FILE_NAME].action_name); + g_simple_action_set_enabled (G_SIMPLE_ACTION (action), FALSE); + + check_metadata_for_selection (dialog, dialog->selection, + dialog->metadata_cancellable); + + /* Make sure that the state is initialized to name-ascending */ + action = g_action_map_lookup_action (G_ACTION_MAP (dialog->action_group), + "numbering-order-changed"); + g_action_change_state (action, g_variant_new_string ("name-ascending")); +} + +static void +file_names_widget_on_activate (NautilusBatchRenameDialog *dialog) +{ + prepare_batch_rename (dialog); +} + +static void +remove_tag (NautilusBatchRenameDialog *dialog, + TagData *tag_data) +{ + GAction *action; + + if (!tag_data->set) + { + g_warning ("Trying to remove an already removed tag"); + + return; + } + + tag_data->set = FALSE; + tag_data->position = -1; + action = g_action_map_lookup_action (G_ACTION_MAP (dialog->action_group), + tag_data->tag_constants.action_name); + g_simple_action_set_enabled (G_SIMPLE_ACTION (action), TRUE); +} + +static gint +compare_tag_position (gconstpointer a, + gconstpointer b) +{ + const TagData *tag_data1 = a; + const TagData *tag_data2 = b; + + return tag_data1->position - tag_data2->position; +} + +typedef enum +{ + TEXT_WAS_DELETED, + TEXT_WAS_INSERTED +} TextChangedMode; + +static GList * +get_tags_intersecting_sorted (NautilusBatchRenameDialog *self, + gint start_position, + gint end_position, + TextChangedMode text_changed_mode) +{ + g_autoptr (GList) tag_info_keys = NULL; + TagData *tag_data; + GList *l; + GList *intersecting_tags = NULL; + gint tag_end_position; + + tag_info_keys = g_hash_table_get_keys (self->tag_info_table); + for (l = tag_info_keys; l != NULL; l = l->next) + { + g_autofree gchar *tag_text_representation = NULL; + + tag_data = g_hash_table_lookup (self->tag_info_table, l->data); + tag_text_representation = batch_rename_get_tag_text_representation (tag_data->tag_constants); + tag_end_position = tag_data->position + g_utf8_strlen (tag_text_representation, -1); + if (tag_data->set && !tag_data->just_added) + { + gboolean selection_intersects_tag_start; + gboolean selection_intersects_tag_end; + gboolean tag_is_contained_in_selection; + + if (text_changed_mode == TEXT_WAS_DELETED) + { + selection_intersects_tag_start = end_position > tag_data->position && + end_position <= tag_end_position; + selection_intersects_tag_end = start_position >= tag_data->position && + start_position < tag_end_position; + tag_is_contained_in_selection = start_position <= tag_data->position && + end_position >= tag_end_position; + } + else + { + selection_intersects_tag_start = start_position > tag_data->position && + start_position < tag_end_position; + selection_intersects_tag_end = FALSE; + tag_is_contained_in_selection = FALSE; + } + if (selection_intersects_tag_end || selection_intersects_tag_start || tag_is_contained_in_selection) + { + intersecting_tags = g_list_prepend (intersecting_tags, tag_data); + } + } + } + + return g_list_sort (intersecting_tags, compare_tag_position); +} + +static void +update_tags_positions (NautilusBatchRenameDialog *self, + gint start_position, + gint end_position, + TextChangedMode text_changed_mode) +{ + g_autoptr (GList) tag_info_keys = NULL; + TagData *tag_data; + GList *l; + + tag_info_keys = g_hash_table_get_keys (self->tag_info_table); + for (l = tag_info_keys; l != NULL; l = l->next) + { + tag_data = g_hash_table_lookup (self->tag_info_table, l->data); + if (tag_data->set && !tag_data->just_added && tag_data->position >= start_position) + { + if (text_changed_mode == TEXT_WAS_DELETED) + { + tag_data->position -= end_position - start_position; + } + else + { + tag_data->position += end_position - start_position; + } + } + } +} + +static void +on_delete_text (GtkEditable *editable, + gint start_position, + gint end_position, + gpointer user_data) +{ + NautilusBatchRenameDialog *self; + g_autoptr (GList) intersecting_tags = NULL; + gint final_start_position; + gint final_end_position; + GList *l; + + self = NAUTILUS_BATCH_RENAME_DIALOG (user_data); + intersecting_tags = get_tags_intersecting_sorted (self, start_position, + end_position, TEXT_WAS_DELETED); + if (intersecting_tags) + { + gint last_tag_end_position; + g_autofree gchar *tag_text_representation = NULL; + TagData *first_tag = g_list_first (intersecting_tags)->data; + TagData *last_tag = g_list_last (intersecting_tags)->data; + + tag_text_representation = batch_rename_get_tag_text_representation (last_tag->tag_constants); + last_tag_end_position = last_tag->position + + g_utf8_strlen (tag_text_representation, -1); + final_start_position = MIN (start_position, first_tag->position); + final_end_position = MAX (end_position, last_tag_end_position); + } + else + { + final_start_position = start_position; + final_end_position = end_position; + } + + g_signal_handlers_block_by_func (editable, (gpointer) on_delete_text, user_data); + gtk_editable_delete_text (editable, final_start_position, final_end_position); + g_signal_handlers_unblock_by_func (editable, (gpointer) on_delete_text, user_data); + + /* Mark the tags as removed */ + for (l = intersecting_tags; l != NULL; l = l->next) + { + remove_tag (self, l->data); + } + + /* If we removed the numbering tag, we want to enable all numbering actions */ + if (!numbering_tag_is_some_added (self)) + { + guint i; + + for (i = 0; i < G_N_ELEMENTS (numbering_tags_constants); i++) + { + enable_action (self, numbering_tags_constants[i].action_name); + } + } + + update_tags_positions (self, final_start_position, + final_end_position, TEXT_WAS_DELETED); + update_display_text (self); + + g_signal_stop_emission_by_name (editable, "delete-text"); +} + +static void +on_insert_text (GtkEditable *editable, + const gchar *new_text, + gint new_text_length, + gpointer position, + gpointer user_data) +{ + NautilusBatchRenameDialog *self; + gint start_position; + gint end_position; + g_autoptr (GList) intersecting_tags = NULL; + + self = NAUTILUS_BATCH_RENAME_DIALOG (user_data); + start_position = *(int *) position; + end_position = start_position + g_utf8_strlen (new_text, -1); + intersecting_tags = get_tags_intersecting_sorted (self, start_position, + end_position, TEXT_WAS_INSERTED); + if (!intersecting_tags) + { + g_signal_handlers_block_by_func (editable, (gpointer) on_insert_text, user_data); + gtk_editable_insert_text (editable, new_text, new_text_length, position); + g_signal_handlers_unblock_by_func (editable, (gpointer) on_insert_text, user_data); + + update_tags_positions (self, start_position, end_position, TEXT_WAS_INSERTED); + update_display_text (self); + } + + g_signal_stop_emission_by_name (editable, "insert-text"); +} + +static void +file_names_widget_entry_on_changed (NautilusBatchRenameDialog *self) +{ + update_display_text (self); +} + +static void +nautilus_batch_rename_dialog_finalize (GObject *object) +{ + NautilusBatchRenameDialog *dialog; + GList *l; + guint i; + + dialog = NAUTILUS_BATCH_RENAME_DIALOG (object); + + if (dialog->directories_pending_conflict_check != NULL) + { + cancel_conflict_check (dialog); + } + + g_list_free (dialog->listbox_labels_new); + g_list_free (dialog->listbox_labels_old); + g_list_free (dialog->listbox_icons); + + for (l = dialog->selection_metadata; l != NULL; l = l->next) + { + FileMetadata *file_metadata; + + file_metadata = l->data; + for (i = 0; i < G_N_ELEMENTS (file_metadata->metadata); i++) + { + if (file_metadata->metadata[i]) + { + g_string_free (file_metadata->metadata[i], TRUE); + } + } + + g_string_free (file_metadata->file_name, TRUE); + g_free (file_metadata); + } + + if (dialog->create_date != NULL) + { + g_hash_table_destroy (dialog->create_date); + } + + g_list_free_full (dialog->new_names, string_free); + g_list_free_full (dialog->duplicates, conflict_data_free); + + nautilus_file_list_free (dialog->selection); + nautilus_directory_unref (dialog->directory); + nautilus_directory_list_free (dialog->distinct_parent_directories); + + g_object_unref (dialog->size_group); + + g_hash_table_destroy (dialog->tag_info_table); + + g_cancellable_cancel (dialog->metadata_cancellable); + g_clear_object (&dialog->metadata_cancellable); + + G_OBJECT_CLASS (nautilus_batch_rename_dialog_parent_class)->finalize (object); +} + +static void +nautilus_batch_rename_dialog_class_init (NautilusBatchRenameDialogClass *klass) +{ + GtkWidgetClass *widget_class; + GObjectClass *oclass; + + widget_class = GTK_WIDGET_CLASS (klass); + oclass = G_OBJECT_CLASS (klass); + + oclass->finalize = nautilus_batch_rename_dialog_finalize; + + gtk_widget_class_set_template_from_resource (widget_class, "/org/gnome/nautilus/ui/nautilus-batch-rename-dialog.ui"); + + gtk_widget_class_bind_template_child (widget_class, NautilusBatchRenameDialog, grid); + gtk_widget_class_bind_template_child (widget_class, NautilusBatchRenameDialog, cancel_button); + gtk_widget_class_bind_template_child (widget_class, NautilusBatchRenameDialog, original_name_listbox); + gtk_widget_class_bind_template_child (widget_class, NautilusBatchRenameDialog, arrow_listbox); + gtk_widget_class_bind_template_child (widget_class, NautilusBatchRenameDialog, result_listbox); + gtk_widget_class_bind_template_child (widget_class, NautilusBatchRenameDialog, name_entry); + gtk_widget_class_bind_template_child (widget_class, NautilusBatchRenameDialog, rename_button); + gtk_widget_class_bind_template_child (widget_class, NautilusBatchRenameDialog, find_entry); + gtk_widget_class_bind_template_child (widget_class, NautilusBatchRenameDialog, replace_entry); + gtk_widget_class_bind_template_child (widget_class, NautilusBatchRenameDialog, mode_stack); + gtk_widget_class_bind_template_child (widget_class, NautilusBatchRenameDialog, replace_mode_button); + gtk_widget_class_bind_template_child (widget_class, NautilusBatchRenameDialog, format_mode_button); + gtk_widget_class_bind_template_child (widget_class, NautilusBatchRenameDialog, numbering_order_button); + gtk_widget_class_bind_template_child (widget_class, NautilusBatchRenameDialog, scrolled_window); + gtk_widget_class_bind_template_child (widget_class, NautilusBatchRenameDialog, numbering_order_menu); + gtk_widget_class_bind_template_child (widget_class, NautilusBatchRenameDialog, numbering_revealer); + gtk_widget_class_bind_template_child (widget_class, NautilusBatchRenameDialog, conflict_box); + gtk_widget_class_bind_template_child (widget_class, NautilusBatchRenameDialog, conflict_label); + gtk_widget_class_bind_template_child (widget_class, NautilusBatchRenameDialog, conflict_up); + gtk_widget_class_bind_template_child (widget_class, NautilusBatchRenameDialog, conflict_down); + gtk_widget_class_bind_template_child (widget_class, NautilusBatchRenameDialog, numbering_label); + + gtk_widget_class_bind_template_callback (widget_class, file_names_widget_on_activate); + gtk_widget_class_bind_template_callback (widget_class, file_names_widget_entry_on_changed); + gtk_widget_class_bind_template_callback (widget_class, batch_rename_dialog_mode_changed); + gtk_widget_class_bind_template_callback (widget_class, select_next_conflict_up); + gtk_widget_class_bind_template_callback (widget_class, select_next_conflict_down); + gtk_widget_class_bind_template_callback (widget_class, batch_rename_dialog_on_response); +} + +GtkWidget * +nautilus_batch_rename_dialog_new (GList *selection, + NautilusDirectory *directory, + NautilusWindow *window) +{ + NautilusBatchRenameDialog *dialog; + GString *dialog_title; + GList *l; + gboolean all_targets_are_folders; + gboolean all_targets_are_regular_files; + + dialog = g_object_new (NAUTILUS_TYPE_BATCH_RENAME_DIALOG, "use-header-bar", TRUE, NULL); + + dialog->selection = nautilus_file_list_copy (selection); + dialog->directory = nautilus_directory_ref (directory); + dialog->window = window; + + gtk_window_set_transient_for (GTK_WINDOW (dialog), + GTK_WINDOW (window)); + + all_targets_are_folders = TRUE; + for (l = selection; l != NULL; l = l->next) + { + if (!nautilus_file_is_directory (NAUTILUS_FILE (l->data))) + { + all_targets_are_folders = FALSE; + break; + } + } + + all_targets_are_regular_files = TRUE; + for (l = selection; l != NULL; l = l->next) + { + if (!nautilus_file_is_regular_file (NAUTILUS_FILE (l->data))) + { + all_targets_are_regular_files = FALSE; + break; + } + } + + dialog_title = g_string_new (""); + if (all_targets_are_folders) + { + g_string_append_printf (dialog_title, + ngettext ("Rename %d Folder", + "Rename %d Folders", + g_list_length (selection)), + g_list_length (selection)); + } + else if (all_targets_are_regular_files) + { + g_string_append_printf (dialog_title, + ngettext ("Rename %d File", + "Rename %d Files", + g_list_length (selection)), + g_list_length (selection)); + } + else + { + g_string_append_printf (dialog_title, + /* To translators: %d is the total number of files and folders. + * Singular case of the string is never used */ + ngettext ("Rename %d File and Folder", + "Rename %d Files and Folders", + g_list_length (selection)), + g_list_length (selection)); + } + + gtk_window_set_title (GTK_WINDOW (dialog), dialog_title->str); + + dialog->distinct_parent_directories = batch_rename_files_get_distinct_parents (selection); + + add_tag (dialog, metadata_tags_constants[ORIGINAL_FILE_NAME]); + + nautilus_batch_rename_dialog_initialize_actions (dialog); + + update_display_text (dialog); + + fill_display_listbox (dialog); + + gtk_widget_set_cursor (GTK_WIDGET (window), NULL); + + g_string_free (dialog_title, TRUE); + + return GTK_WIDGET (dialog); +} + +static void +connect_to_pointer_motion_events (NautilusBatchRenameDialog *self, + GtkWidget *listbox) +{ + GtkEventController *controller; + + controller = gtk_event_controller_motion_new (); + gtk_widget_add_controller (listbox, controller); + gtk_event_controller_set_propagation_phase (controller, GTK_PHASE_CAPTURE); + g_signal_connect (controller, "leave", + G_CALLBACK (on_event_controller_motion_leave), self); + g_signal_connect (controller, "motion", + G_CALLBACK (on_event_controller_motion_motion), self); +} + +static void +nautilus_batch_rename_dialog_init (NautilusBatchRenameDialog *self) +{ + TagData *tag_data; + guint i; + + gtk_widget_init_template (GTK_WIDGET (self)); + + gtk_list_box_set_header_func (GTK_LIST_BOX (self->original_name_listbox), + (GtkListBoxUpdateHeaderFunc) listbox_header_func, + self, + NULL); + gtk_list_box_set_header_func (GTK_LIST_BOX (self->arrow_listbox), + (GtkListBoxUpdateHeaderFunc) listbox_header_func, + self, + NULL); + gtk_list_box_set_header_func (GTK_LIST_BOX (self->result_listbox), + (GtkListBoxUpdateHeaderFunc) listbox_header_func, + self, + NULL); + + + self->mode = NAUTILUS_BATCH_RENAME_DIALOG_FORMAT; + + gtk_label_set_ellipsize (GTK_LABEL (self->conflict_label), PANGO_ELLIPSIZE_END); + gtk_label_set_max_width_chars (GTK_LABEL (self->conflict_label), 1); + + self->duplicates = NULL; + self->distinct_parent_directories = NULL; + self->directories_pending_conflict_check = NULL; + self->new_names = NULL; + self->rename_clicked = FALSE; + + self->tag_info_table = g_hash_table_new_full (g_str_hash, + g_str_equal, + (GDestroyNotify) g_free, + (GDestroyNotify) g_free); + + for (i = 0; i < G_N_ELEMENTS (numbering_tags_constants); i++) + { + g_autofree gchar *tag_text_representation = NULL; + + tag_text_representation = batch_rename_get_tag_text_representation (numbering_tags_constants[i]); + tag_data = g_new (TagData, 1); + tag_data->available = TRUE; + tag_data->set = FALSE; + tag_data->position = -1; + tag_data->tag_constants = numbering_tags_constants[i]; + g_hash_table_insert (self->tag_info_table, g_strdup (tag_text_representation), tag_data); + } + + for (i = 0; i < G_N_ELEMENTS (metadata_tags_constants); i++) + { + g_autofree gchar *tag_text_representation = NULL; + + /* Only the original name is available and set at the start */ + tag_text_representation = batch_rename_get_tag_text_representation (metadata_tags_constants[i]); + + tag_data = g_new (TagData, 1); + tag_data->available = FALSE; + tag_data->set = FALSE; + tag_data->position = -1; + tag_data->tag_constants = metadata_tags_constants[i]; + g_hash_table_insert (self->tag_info_table, g_strdup (tag_text_representation), tag_data); + } + + self->row_height = -1; + + g_signal_connect_object (gtk_editable_get_delegate (GTK_EDITABLE (self->name_entry)), + "delete-text", G_CALLBACK (on_delete_text), self, 0); + g_signal_connect_object (gtk_editable_get_delegate (GTK_EDITABLE (self->name_entry)), + "insert-text", G_CALLBACK (on_insert_text), self, 0); + + self->size_group = gtk_size_group_new (GTK_SIZE_GROUP_HORIZONTAL); + + connect_to_pointer_motion_events (self, self->original_name_listbox); + connect_to_pointer_motion_events (self, self->result_listbox); + connect_to_pointer_motion_events (self, self->arrow_listbox); + + self->metadata_cancellable = g_cancellable_new (); +} diff --git a/src/nautilus-batch-rename-dialog.h b/src/nautilus-batch-rename-dialog.h new file mode 100644 index 0000000..0749fa3 --- /dev/null +++ b/src/nautilus-batch-rename-dialog.h @@ -0,0 +1,232 @@ +/* nautilus-batch-rename-utilities.c + * + * Copyright (C) 2016 Alexandru Pandelea + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 2 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 warranty of + * MERCHANTABILITY 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 . + */ + +#pragma once + +#include +#include +#include +#include +#include "nautilus-files-view.h" + +G_BEGIN_DECLS + +typedef enum +{ + EQUIPMENT, + CREATION_DATE, + SEASON_NUMBER, + EPISODE_NUMBER, + TRACK_NUMBER, + ARTIST_NAME, + TITLE, + ALBUM_NAME, + ORIGINAL_FILE_NAME, + METADATA_INVALID, +} MetadataType; + +typedef enum +{ + NUMBERING_NO_ZERO_PAD, + NUMBERING_ONE_ZERO_PAD, + NUMBERING_TWO_ZERO_PAD, + NUMBERING_INVALID, +} NumberingType; + +typedef enum { + NAUTILUS_BATCH_RENAME_DIALOG_APPEND = 0, + NAUTILUS_BATCH_RENAME_DIALOG_PREPEND = 1, + NAUTILUS_BATCH_RENAME_DIALOG_REPLACE = 2, + NAUTILUS_BATCH_RENAME_DIALOG_FORMAT = 3, +} NautilusBatchRenameDialogMode; + +typedef enum { + ORIGINAL_ASCENDING = 0, + ORIGINAL_DESCENDING = 1, + FIRST_MODIFIED = 2, + LAST_MODIFIED = 3, + FIRST_CREATED = 4, + LAST_CREATED = 5, +} SortMode; + +typedef struct +{ + const gchar *action_name; + const gchar *label; + MetadataType metadata_type; + NumberingType numbering_type; + gboolean is_metadata; +} TagConstants; + +typedef struct +{ + const gchar *action_target_name; + const gchar *label; + const SortMode sort_mode; +} SortConstants; + +static const SortConstants sorts_constants[] = +{ + { + "name-ascending", + N_("Original Name (Ascending)"), + ORIGINAL_ASCENDING, + }, + { + "name-descending", + N_("Original Name (Descending)"), + ORIGINAL_DESCENDING, + }, + { + "first-modified", + N_("First Modified"), + FIRST_MODIFIED, + }, + { + "last-modified", + N_("Last Modified"), + LAST_MODIFIED, + }, + { + "first-created", + N_("First Created"), + FIRST_CREATED, + }, + { + "last-created", + N_("Last Created"), + LAST_CREATED, + }, +}; + +static const TagConstants metadata_tags_constants[] = +{ + { + "add-equipment-tag", + N_("Camera model"), + EQUIPMENT, + NUMBERING_INVALID, + TRUE, + }, + { + "add-creation-date-tag", + N_("Creation date"), + CREATION_DATE, + NUMBERING_INVALID, + TRUE, + }, + { + "add-season-number-tag", + N_("Season number"), + SEASON_NUMBER, + NUMBERING_INVALID, + TRUE, + }, + { + "add-episode-number-tag", + N_("Episode number"), + EPISODE_NUMBER, + NUMBERING_INVALID, + TRUE, + }, + { + "add-track-number-tag", + N_("Track number"), + TRACK_NUMBER, + NUMBERING_INVALID, + TRUE, + }, + { + "add-artist-name-tag", + N_("Artist name"), + ARTIST_NAME, + NUMBERING_INVALID, + TRUE, + }, + { + "add-title-tag", + N_("Title"), + TITLE, + NUMBERING_INVALID, + TRUE, + }, + { + "add-album-name-tag", + N_("Album name"), + ALBUM_NAME, + NUMBERING_INVALID, + TRUE, + }, + { + "add-original-file-name-tag", + N_("Original file name"), + ORIGINAL_FILE_NAME, + NUMBERING_INVALID, + TRUE, + }, +}; + +static const TagConstants numbering_tags_constants[] = +{ + { + "add-numbering-no-zero-pad-tag", + N_("1, 2, 3"), + METADATA_INVALID, + NUMBERING_NO_ZERO_PAD, + FALSE, + }, + { + "add-numbering-one-zero-pad-tag", + N_("01, 02, 03"), + METADATA_INVALID, + NUMBERING_ONE_ZERO_PAD, + FALSE, + }, + { + "add-numbering-two-zero-pad-tag", + N_("001, 002, 003"), + METADATA_INVALID, + NUMBERING_TWO_ZERO_PAD, + FALSE, + }, +}; + +typedef struct +{ + gchar *name; + gint index; +} ConflictData; + +typedef struct { + GString *file_name; + GString *metadata [G_N_ELEMENTS (metadata_tags_constants)]; +} FileMetadata; + +#define NAUTILUS_TYPE_BATCH_RENAME_DIALOG (nautilus_batch_rename_dialog_get_type()) + +G_DECLARE_FINAL_TYPE (NautilusBatchRenameDialog, nautilus_batch_rename_dialog, NAUTILUS, BATCH_RENAME_DIALOG, GtkDialog); + +GtkWidget* nautilus_batch_rename_dialog_new (GList *selection, + NautilusDirectory *directory, + NautilusWindow *window); + +void nautilus_batch_rename_dialog_query_finished (NautilusBatchRenameDialog *dialog, + GHashTable *hash_table, + GList *selection_metadata); + +G_END_DECLS \ No newline at end of file diff --git a/src/nautilus-batch-rename-utilities.c b/src/nautilus-batch-rename-utilities.c new file mode 100644 index 0000000..a81fc2e --- /dev/null +++ b/src/nautilus-batch-rename-utilities.c @@ -0,0 +1,1186 @@ +/* nautilus-batch-rename-utilities.c + * + * Copyright (C) 2016 Alexandru Pandelea + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 2 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 warranty of + * MERCHANTABILITY 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 . + */ + +#include "nautilus-batch-rename-dialog.h" +#include "nautilus-batch-rename-utilities.h" +#include "nautilus-file.h" +#include "nautilus-tracker-utilities.h" + +#include +#include +#include +#include +#include + +typedef struct +{ + NautilusFile *file; + gint position; +} CreateDateElem; + +typedef struct +{ + NautilusBatchRenameDialog *dialog; + GHashTable *date_order_hash_table; + + GList *selection_metadata; + + gboolean has_metadata[G_N_ELEMENTS (metadata_tags_constants)]; + + GCancellable *cancellable; +} QueryData; + +enum +{ + FILE_NAME_INDEX, + CREATION_DATE_INDEX, + YEAR_INDEX, + MONTH_INDEX, + DAY_INDEX, + HOURS_INDEX, + MINUTES_INDEX, + SECONDS_INDEX, + CAMERA_MODEL_INDEX, + SEASON_INDEX, + EPISODE_NUMBER_INDEX, + TRACK_NUMBER_INDEX, + ARTIST_NAME_INDEX, + TITLE_INDEX, + ALBUM_NAME_INDEX, +} QueryMetadata; + +static void on_cursor_callback (GObject *object, + GAsyncResult *result, + gpointer user_data); + +void +string_free (gpointer mem) +{ + if (mem != NULL) + { + g_string_free (mem, TRUE); + } +} + +void +conflict_data_free (gpointer mem) +{ + ConflictData *conflict_data = mem; + + g_free (conflict_data->name); + g_free (conflict_data); +} + +gchar * +batch_rename_get_tag_text_representation (TagConstants tag_constants) +{ + return g_strdup_printf ("[%s]", gettext (tag_constants.label)); +} + +static GString * +batch_rename_replace (gchar *string, + gchar *substring, + gchar *replacement) +{ + GString *new_string; + gchar **splitted_string; + gint i, n_splits; + + new_string = g_string_new (""); + + if (substring == NULL || replacement == NULL) + { + g_string_append (new_string, string); + + return new_string; + } + + if (g_utf8_strlen (substring, -1) == 0) + { + g_string_append (new_string, string); + + return new_string; + } + + splitted_string = g_strsplit (string, substring, -1); + if (splitted_string == NULL) + { + g_string_append (new_string, string); + + return new_string; + } + + n_splits = g_strv_length (splitted_string); + + for (i = 0; i < n_splits; i++) + { + g_string_append (new_string, splitted_string[i]); + + if (i != n_splits - 1) + { + g_string_append (new_string, replacement); + } + } + + g_strfreev (splitted_string); + + return new_string; +} + +void +batch_rename_sort_lists_for_rename (GList **selection, + GList **new_names, + GList **old_names, + GList **new_files, + GList **old_files, + gboolean is_undo_redo) +{ + GList *new_names_list; + GList *new_names_list2; + GList *files; + GList *files2; + GList *old_names_list = NULL; + GList *new_files_list = NULL; + GList *old_files_list = NULL; + GList *old_names_list2 = NULL; + GList *new_files_list2 = NULL; + GList *old_files_list2 = NULL; + GString *new_file_name; + GString *new_name; + GString *old_name; + GFile *new_file; + GFile *old_file; + NautilusFile *file; + gboolean order_changed = TRUE; + + /* in the following case: + * file1 -> file2 + * file2 -> file3 + * file2 must be renamed first, so because of that, the list has to be reordered + */ + while (order_changed) + { + order_changed = FALSE; + + if (is_undo_redo) + { + old_names_list = *old_names; + new_files_list = *new_files; + old_files_list = *old_files; + } + + for (new_names_list = *new_names, files = *selection; + new_names_list != NULL && files != NULL; + new_names_list = new_names_list->next, files = files->next) + { + g_autofree gchar *old_file_name = NULL; + g_autoptr (NautilusFile) parent = NULL; + + old_file_name = nautilus_file_get_name (NAUTILUS_FILE (files->data)); + new_file_name = new_names_list->data; + parent = nautilus_file_get_parent (NAUTILUS_FILE (files->data)); + + if (is_undo_redo) + { + old_names_list2 = old_names_list; + new_files_list2 = new_files_list; + old_files_list2 = old_files_list; + } + + for (files2 = files, new_names_list2 = new_names_list; + files2 != NULL && new_names_list2 != NULL; + files2 = files2->next, new_names_list2 = new_names_list2->next) + { + g_autofree gchar *file_name = NULL; + g_autoptr (NautilusFile) parent2 = NULL; + + file_name = nautilus_file_get_name (NAUTILUS_FILE (files2->data)); + new_name = new_names_list2->data; + + parent2 = nautilus_file_get_parent (NAUTILUS_FILE (files2->data)); + + if (files2 != files && g_strcmp0 (file_name, new_file_name->str) == 0 && + parent == parent2) + { + file = NAUTILUS_FILE (files2->data); + + *selection = g_list_remove_link (*selection, files2); + *new_names = g_list_remove_link (*new_names, new_names_list2); + + *selection = g_list_prepend (*selection, file); + *new_names = g_list_prepend (*new_names, new_name); + + if (is_undo_redo) + { + old_name = old_names_list2->data; + new_file = new_files_list2->data; + old_file = old_files_list2->data; + + *old_names = g_list_remove_link (*old_names, old_names_list2); + *new_files = g_list_remove_link (*new_files, new_files_list2); + *old_files = g_list_remove_link (*old_files, old_files_list2); + + *old_names = g_list_prepend (*old_names, old_name); + *new_files = g_list_prepend (*new_files, new_file); + *old_files = g_list_prepend (*old_files, old_file); + } + + order_changed = TRUE; + break; + } + + if (is_undo_redo) + { + old_names_list2 = old_names_list2->next; + new_files_list2 = new_files_list2->next; + old_files_list2 = old_files_list2->next; + } + } + + if (is_undo_redo) + { + old_names_list = old_names_list->next; + new_files_list = new_files_list->next; + old_files_list = old_files_list->next; + } + } + } +} + +/* This function changes the background color of the replaced part of the name */ +GString * +batch_rename_replace_label_text (gchar *label, + const gchar *substring) +{ + GString *new_label; + gchar **splitted_string; + gchar *token; + gint i, n_splits; + + new_label = g_string_new (""); + + if (substring == NULL || g_strcmp0 (substring, "") == 0) + { + token = g_markup_escape_text (label, -1); + new_label = g_string_append (new_label, token); + g_free (token); + + return new_label; + } + + splitted_string = g_strsplit (label, substring, -1); + if (splitted_string == NULL) + { + token = g_markup_escape_text (label, -1); + new_label = g_string_append (new_label, token); + g_free (token); + + return new_label; + } + + n_splits = g_strv_length (splitted_string); + + for (i = 0; i < n_splits; i++) + { + token = g_markup_escape_text (splitted_string[i], -1); + new_label = g_string_append (new_label, token); + + g_free (token); + + if (i != n_splits - 1) + { + token = g_markup_escape_text (substring, -1); + g_string_append_printf (new_label, + "%s", + token); + + g_free (token); + } + } + + g_strfreev (splitted_string); + + return new_label; +} + +static gchar * +get_metadata (GList *selection_metadata, + gchar *file_name, + MetadataType metadata_type) +{ + GList *l; + FileMetadata *file_metadata; + gchar *metadata = NULL; + + for (l = selection_metadata; l != NULL; l = l->next) + { + file_metadata = l->data; + if (g_strcmp0 (file_name, file_metadata->file_name->str) == 0) + { + if (file_metadata->metadata[metadata_type] && + file_metadata->metadata[metadata_type]->len > 0) + { + metadata = file_metadata->metadata[metadata_type]->str; + } + + break; + } + } + + return metadata; +} + +static GString * +batch_rename_format (NautilusFile *file, + GList *text_chunks, + GList *selection_metadata, + gint count) +{ + GList *l; + GString *tag_string; + GString *new_name; + gboolean added_tag; + MetadataType metadata_type; + g_autofree gchar *file_name = NULL; + g_autofree gchar *extension = NULL; + gint i; + gchar *metadata; + + file_name = nautilus_file_get_display_name (file); + if (!nautilus_file_is_directory (file)) + { + extension = nautilus_file_get_extension (file); + } + + new_name = g_string_new (""); + + for (l = text_chunks; l != NULL; l = l->next) + { + added_tag = FALSE; + tag_string = l->data; + + for (i = 0; i < G_N_ELEMENTS (numbering_tags_constants); i++) + { + g_autofree gchar *tag_text_representation = NULL; + + tag_text_representation = batch_rename_get_tag_text_representation (numbering_tags_constants[i]); + if (g_strcmp0 (tag_string->str, tag_text_representation) == 0) + { + switch (numbering_tags_constants[i].numbering_type) + { + case NUMBERING_NO_ZERO_PAD: + { + g_string_append_printf (new_name, "%d", count); + } + break; + + case NUMBERING_ONE_ZERO_PAD: + { + g_string_append_printf (new_name, "%02d", count); + } + break; + + case NUMBERING_TWO_ZERO_PAD: + { + g_string_append_printf (new_name, "%03d", count); + } + break; + + default: + { + g_warn_if_reached (); + } + break; + } + + added_tag = TRUE; + break; + } + } + + if (added_tag) + { + continue; + } + + for (i = 0; i < G_N_ELEMENTS (metadata_tags_constants); i++) + { + g_autofree gchar *tag_text_representation = NULL; + + tag_text_representation = batch_rename_get_tag_text_representation (metadata_tags_constants[i]); + if (g_strcmp0 (tag_string->str, tag_text_representation) == 0) + { + metadata_type = metadata_tags_constants[i].metadata_type; + metadata = get_metadata (selection_metadata, file_name, metadata_type); + + /* TODO: This is a hack, we should provide a cancellable for checking + * the metadata, and if that is happening don't enter here. We can + * special case original file name upper in the call stack */ + if (!metadata && metadata_type != ORIGINAL_FILE_NAME) + { + g_warning ("Metadata not present in one file, it shouldn't have been added. File name: %s, Metadata: %s", + file_name, metadata_tags_constants[i].label); + continue; + } + + switch (metadata_type) + { + case ORIGINAL_FILE_NAME: + { + if (nautilus_file_is_directory (file)) + { + new_name = g_string_append (new_name, file_name); + } + else + { + g_autofree gchar *base_name = NULL; + base_name = eel_filename_strip_extension (file_name); + new_name = g_string_append (new_name, base_name); + } + } + break; + + case TRACK_NUMBER: + { + g_string_append_printf (new_name, "%02d", atoi (metadata)); + } + break; + + default: + { + new_name = g_string_append (new_name, metadata); + } + break; + } + + added_tag = TRUE; + break; + } + } + + if (!added_tag) + { + new_name = g_string_append (new_name, tag_string->str); + } + } + + if (g_strcmp0 (new_name->str, "") == 0) + { + new_name = g_string_append (new_name, file_name); + } + else + { + if (extension != NULL) + { + new_name = g_string_append (new_name, extension); + } + } + + return new_name; +} + +GList * +batch_rename_dialog_get_new_names_list (NautilusBatchRenameDialogMode mode, + GList *selection, + GList *text_chunks, + GList *selection_metadata, + gchar *entry_text, + gchar *replace_text) +{ + GList *l; + GList *result; + GString *file_name; + GString *new_name; + NautilusFile *file; + gchar *name; + gint count; + + result = NULL; + count = 1; + + for (l = selection; l != NULL; l = l->next) + { + file = NAUTILUS_FILE (l->data); + + name = nautilus_file_get_name (file); + file_name = g_string_new (name); + + /* get the new name here and add it to the list*/ + if (mode == NAUTILUS_BATCH_RENAME_DIALOG_FORMAT) + { + new_name = batch_rename_format (file, + text_chunks, + selection_metadata, + count++); + result = g_list_prepend (result, new_name); + } + + if (mode == NAUTILUS_BATCH_RENAME_DIALOG_REPLACE) + { + new_name = batch_rename_replace (file_name->str, + entry_text, + replace_text); + result = g_list_prepend (result, new_name); + } + + g_string_free (file_name, TRUE); + g_free (name); + } + + return result; +} + +/* There is a case that a new name for a file conflicts with an existing file name + * in the directory but it's not a problem because the file in the directory that + * conflicts is part of the batch renaming selection and it's going to change the name anyway. */ +gboolean +file_name_conflicts_with_results (GList *selection, + GList *new_names, + GString *old_name, + gchar *parent_uri) +{ + GList *l1; + GList *l2; + NautilusFile *selection_file; + GString *new_name; + + for (l1 = selection, l2 = new_names; l1 != NULL && l2 != NULL; l1 = l1->next, l2 = l2->next) + { + g_autofree gchar *name1 = NULL; + g_autofree gchar *selection_parent_uri = NULL; + + selection_file = NAUTILUS_FILE (l1->data); + name1 = nautilus_file_get_name (selection_file); + + selection_parent_uri = nautilus_file_get_parent_uri (selection_file); + + if (g_strcmp0 (name1, old_name->str) == 0) + { + new_name = l2->data; + + /* if the name didn't change, then there's a conflict */ + if (g_string_equal (old_name, new_name) && + (parent_uri == NULL || g_strcmp0 (parent_uri, selection_parent_uri) == 0)) + { + return FALSE; + } + + + /* if this file exists and it changed it's name, then there's no + * conflict */ + return TRUE; + } + } + + /* the case this function searched for doesn't exist, so the file + * has a conlfict */ + return FALSE; +} + +static gint +compare_files_by_name_ascending (gconstpointer a, + gconstpointer b) +{ + NautilusFile *file1; + NautilusFile *file2; + + file1 = NAUTILUS_FILE (a); + file2 = NAUTILUS_FILE (b); + + return nautilus_file_compare_for_sort (file1, file2, + NAUTILUS_FILE_SORT_BY_DISPLAY_NAME, + FALSE, FALSE); +} + +static gint +compare_files_by_name_descending (gconstpointer a, + gconstpointer b) +{ + NautilusFile *file1; + NautilusFile *file2; + + file1 = NAUTILUS_FILE (a); + file2 = NAUTILUS_FILE (b); + + return nautilus_file_compare_for_sort (file1, file2, + NAUTILUS_FILE_SORT_BY_DISPLAY_NAME, + FALSE, TRUE); +} + +static gint +compare_files_by_first_modified (gconstpointer a, + gconstpointer b) +{ + NautilusFile *file1; + NautilusFile *file2; + + file1 = NAUTILUS_FILE (a); + file2 = NAUTILUS_FILE (b); + + return nautilus_file_compare_for_sort (file1, file2, + NAUTILUS_FILE_SORT_BY_MTIME, + FALSE, FALSE); +} + +static gint +compare_files_by_last_modified (gconstpointer a, + gconstpointer b) +{ + NautilusFile *file1; + NautilusFile *file2; + + file1 = NAUTILUS_FILE (a); + file2 = NAUTILUS_FILE (b); + + return nautilus_file_compare_for_sort (file1, file2, + NAUTILUS_FILE_SORT_BY_MTIME, + FALSE, TRUE); +} + +static gint +compare_files_by_first_created (gconstpointer a, + gconstpointer b) +{ + CreateDateElem *elem1; + CreateDateElem *elem2; + + elem1 = (CreateDateElem *) a; + elem2 = (CreateDateElem *) b; + + return elem1->position - elem2->position; +} + +static gint +compare_files_by_last_created (gconstpointer a, + gconstpointer b) +{ + CreateDateElem *elem1; + CreateDateElem *elem2; + + elem1 = (CreateDateElem *) a; + elem2 = (CreateDateElem *) b; + + return elem2->position - elem1->position; +} + +GList * +nautilus_batch_rename_dialog_sort (GList *selection, + SortMode mode, + GHashTable *creation_date_table) +{ + GList *l, *l2; + NautilusFile *file; + GList *create_date_list; + GList *create_date_list_sorted; + gchar *name; + + if (mode == ORIGINAL_ASCENDING) + { + return g_list_sort (selection, compare_files_by_name_ascending); + } + + if (mode == ORIGINAL_DESCENDING) + { + return g_list_sort (selection, compare_files_by_name_descending); + } + + if (mode == FIRST_MODIFIED) + { + return g_list_sort (selection, compare_files_by_first_modified); + } + + if (mode == LAST_MODIFIED) + { + return g_list_sort (selection, compare_files_by_last_modified); + } + + if (mode == FIRST_CREATED || mode == LAST_CREATED) + { + create_date_list = NULL; + + for (l = selection; l != NULL; l = l->next) + { + CreateDateElem *elem; + elem = g_new (CreateDateElem, 1); + + file = NAUTILUS_FILE (l->data); + + name = nautilus_file_get_name (file); + elem->file = file; + elem->position = GPOINTER_TO_INT (g_hash_table_lookup (creation_date_table, name)); + g_free (name); + + create_date_list = g_list_prepend (create_date_list, elem); + } + + if (mode == FIRST_CREATED) + { + create_date_list_sorted = g_list_sort (create_date_list, + compare_files_by_first_created); + } + else + { + create_date_list_sorted = g_list_sort (create_date_list, + compare_files_by_last_created); + } + + for (l = selection, l2 = create_date_list_sorted; l2 != NULL; l = l->next, l2 = l2->next) + { + CreateDateElem *elem = l2->data; + l->data = elem->file; + } + + g_list_free_full (create_date_list, g_free); + } + + return selection; +} + +static void +cursor_next (QueryData *query_data, + TrackerSparqlCursor *cursor) +{ + tracker_sparql_cursor_next_async (cursor, + query_data->cancellable, + on_cursor_callback, + query_data); +} + +static void +remove_metadata (QueryData *query_data, + MetadataType metadata_type) +{ + GList *l; + FileMetadata *metadata_to_delete; + + for (l = query_data->selection_metadata; l != NULL; l = l->next) + { + metadata_to_delete = l->data; + if (metadata_to_delete->metadata[metadata_type]) + { + g_string_free (metadata_to_delete->metadata[metadata_type], TRUE); + metadata_to_delete->metadata[metadata_type] = NULL; + } + } + + query_data->has_metadata[metadata_type] = FALSE; +} + +static GString * +format_date_time (GDateTime *date_time) +{ + g_autofree gchar *date = NULL; + GString *formated_date; + + date = g_date_time_format (date_time, "%x"); + if (strstr (date, "/") != NULL) + { + formated_date = batch_rename_replace (date, "/", "-"); + } + else + { + formated_date = g_string_new (date); + } + + return formated_date; +} + +static void +on_cursor_callback (GObject *object, + GAsyncResult *result, + gpointer user_data) +{ + TrackerSparqlCursor *cursor; + gboolean success; + QueryData *query_data; + MetadataType metadata_type; + g_autoptr (GError) error = NULL; + GList *l; + FileMetadata *file_metadata; + GDateTime *date_time; + guint i; + const gchar *current_metadata; + const gchar *file_name; + const gchar *creation_date; + const gchar *year; + const gchar *month; + const gchar *day; + const gchar *hours; + const gchar *minutes; + const gchar *seconds; + const gchar *equipment; + const gchar *season_number; + const gchar *episode_number; + const gchar *track_number; + const gchar *artist_name; + const gchar *title; + const gchar *album_name; + + file_metadata = NULL; + + cursor = TRACKER_SPARQL_CURSOR (object); + query_data = user_data; + + success = tracker_sparql_cursor_next_finish (cursor, result, &error); + if (!success) + { + if (error != NULL) + { + g_warning ("Error on batch rename tracker query cursor: %s", error->message); + } + + g_clear_object (&cursor); + + /* The dialog is going away at the time of cancellation */ + if (error == NULL || + (error != NULL && error->code != G_IO_ERROR_CANCELLED)) + { + nautilus_batch_rename_dialog_query_finished (query_data->dialog, + query_data->date_order_hash_table, + query_data->selection_metadata); + } + + g_free (query_data); + + return; + } + + creation_date = tracker_sparql_cursor_get_string (cursor, CREATION_DATE_INDEX, NULL); + + year = tracker_sparql_cursor_get_string (cursor, YEAR_INDEX, NULL); + month = tracker_sparql_cursor_get_string (cursor, MONTH_INDEX, NULL); + day = tracker_sparql_cursor_get_string (cursor, DAY_INDEX, NULL); + hours = tracker_sparql_cursor_get_string (cursor, HOURS_INDEX, NULL); + minutes = tracker_sparql_cursor_get_string (cursor, MINUTES_INDEX, NULL); + seconds = tracker_sparql_cursor_get_string (cursor, SECONDS_INDEX, NULL); + equipment = tracker_sparql_cursor_get_string (cursor, CAMERA_MODEL_INDEX, NULL); + season_number = tracker_sparql_cursor_get_string (cursor, SEASON_INDEX, NULL); + episode_number = tracker_sparql_cursor_get_string (cursor, EPISODE_NUMBER_INDEX, NULL); + track_number = tracker_sparql_cursor_get_string (cursor, TRACK_NUMBER_INDEX, NULL); + artist_name = tracker_sparql_cursor_get_string (cursor, ARTIST_NAME_INDEX, NULL); + title = tracker_sparql_cursor_get_string (cursor, TITLE_INDEX, NULL); + album_name = tracker_sparql_cursor_get_string (cursor, ALBUM_NAME_INDEX, NULL); + + /* Search for the metadata object corresponding to the file name */ + file_name = tracker_sparql_cursor_get_string (cursor, FILE_NAME_INDEX, NULL); + for (l = query_data->selection_metadata; l != NULL; l = l->next) + { + file_metadata = l->data; + + if (g_strcmp0 (file_name, file_metadata->file_name->str) == 0) + { + break; + } + } + + /* Set metadata when available, and delete for the whole selection when not */ + for (i = 0; i < G_N_ELEMENTS (metadata_tags_constants); i++) + { + if (query_data->has_metadata[i]) + { + metadata_type = metadata_tags_constants[i].metadata_type; + current_metadata = NULL; + switch (metadata_type) + { + case ORIGINAL_FILE_NAME: + { + current_metadata = file_name; + } + break; + + case CREATION_DATE: + { + current_metadata = creation_date; + } + break; + + case EQUIPMENT: + { + current_metadata = equipment; + } + break; + + case SEASON_NUMBER: + { + current_metadata = season_number; + } + break; + + case EPISODE_NUMBER: + { + current_metadata = episode_number; + } + break; + + case ARTIST_NAME: + { + current_metadata = artist_name; + } + break; + + case ALBUM_NAME: + { + current_metadata = album_name; + } + break; + + case TITLE: + { + current_metadata = title; + } + break; + + case TRACK_NUMBER: + { + current_metadata = track_number; + } + break; + + default: + { + g_warn_if_reached (); + } + break; + } + + /* TODO: Figure out how to inform the user of why the metadata is + * unavailable when one or more contains the unallowed character "/" + */ + if (!current_metadata || g_strrstr (current_metadata, "/")) + { + remove_metadata (query_data, + metadata_type); + + if (metadata_type == CREATION_DATE && + query_data->date_order_hash_table) + { + g_hash_table_destroy (query_data->date_order_hash_table); + query_data->date_order_hash_table = NULL; + } + } + else + { + if (metadata_type == CREATION_DATE) + { + /* Add the sort order to the order hash table */ + g_hash_table_insert (query_data->date_order_hash_table, + g_strdup (tracker_sparql_cursor_get_string (cursor, 0, NULL)), + GINT_TO_POINTER (g_hash_table_size (query_data->date_order_hash_table))); + + date_time = g_date_time_new_local (atoi (year), + atoi (month), + atoi (day), + atoi (hours), + atoi (minutes), + atoi (seconds)); + + file_metadata->metadata[metadata_type] = format_date_time (date_time); + } + else + { + file_metadata->metadata[metadata_type] = g_string_new (current_metadata); + } + } + } + } + + /* Get next */ + cursor_next (query_data, cursor); +} + +static void +batch_rename_dialog_query_callback (GObject *object, + GAsyncResult *result, + gpointer user_data) +{ + TrackerSparqlConnection *connection; + TrackerSparqlCursor *cursor; + QueryData *query_data; + g_autoptr (GError) error = NULL; + + connection = TRACKER_SPARQL_CONNECTION (object); + query_data = user_data; + + cursor = tracker_sparql_connection_query_finish (connection, + result, + &error); + + if (error != NULL) + { + g_warning ("Error on batch rename query for metadata: %s", error->message); + + /* The dialog is being finalized at this point */ + if (error->code != G_IO_ERROR_CANCELLED) + { + nautilus_batch_rename_dialog_query_finished (query_data->dialog, + query_data->date_order_hash_table, + query_data->selection_metadata); + } + + g_free (query_data); + } + else + { + cursor_next (query_data, cursor); + } +} + +void +check_metadata_for_selection (NautilusBatchRenameDialog *dialog, + GList *selection, + GCancellable *cancellable) +{ + TrackerSparqlConnection *connection; + GString *query; + GList *l; + NautilusFile *file; + GError *error; + QueryData *query_data; + gchar *file_name; + FileMetadata *file_metadata; + GList *selection_metadata; + guint i; + g_autofree gchar *parent_uri = NULL; + gchar *file_name_escaped; + + error = NULL; + selection_metadata = NULL; + + query = g_string_new ("SELECT " + "nfo:fileName(?file) " + "nie:contentCreated(?content) " + "year(nie:contentCreated(?content)) " + "month(nie:contentCreated(?content)) " + "day(nie:contentCreated(?content)) " + "hours(nie:contentCreated(?content)) " + "minutes(nie:contentCreated(?content)) " + "seconds(nie:contentCreated(?content)) " + "nfo:model(nfo:equipment(?content)) " + "nmm:seasonNumber(?content) " + "nmm:episodeNumber(?content) " + "nmm:trackNumber(?content) " + "nmm:artistName(nmm:performer(?content)) " + "nie:title(?content) " + "nie:title(nmm:musicAlbum(?content)) " + "WHERE { ?file a nfo:FileDataObject. ?file nie:url ?url. ?content nie:isStoredAs ?file. "); + + parent_uri = nautilus_file_get_parent_uri (NAUTILUS_FILE (selection->data)); + + g_string_append_printf (query, + "FILTER(tracker:uri-is-parent(\"%s\", ?url)) ", + parent_uri); + + for (l = selection; l != NULL; l = l->next) + { + file = NAUTILUS_FILE (l->data); + file_name = nautilus_file_get_name (file); + file_name_escaped = tracker_sparql_escape_string (file_name); + + if (l == selection) + { + g_string_append_printf (query, + "FILTER (nfo:fileName(?file) IN (\"%s\", ", + file_name_escaped); + } + else if (l->next == NULL) + { + g_string_append_printf (query, + "\"%s\")) ", + file_name_escaped); + } + else + { + g_string_append_printf (query, + "\"%s\", ", + file_name_escaped); + } + + file_metadata = g_new0 (FileMetadata, 1); + file_metadata->file_name = g_string_new (file_name); + file_metadata->metadata[ORIGINAL_FILE_NAME] = g_string_new (file_name); + + selection_metadata = g_list_prepend (selection_metadata, file_metadata); + + g_free (file_name); + g_free (file_name_escaped); + } + + selection_metadata = g_list_reverse (selection_metadata); + + g_string_append (query, "} ORDER BY ASC(nie:contentCreated(?content))"); + + connection = nautilus_tracker_get_miner_fs_connection (&error); + if (!connection) + { + if (error) + { + g_warning ("Error on batch rename tracker connection: %s", error->message); + g_error_free (error); + } + + return; + } + + query_data = g_new (QueryData, 1); + query_data->date_order_hash_table = g_hash_table_new_full (g_str_hash, + g_str_equal, + (GDestroyNotify) g_free, + NULL); + query_data->dialog = dialog; + query_data->selection_metadata = selection_metadata; + for (i = 0; i < G_N_ELEMENTS (metadata_tags_constants); i++) + { + query_data->has_metadata[i] = TRUE; + } + query_data->cancellable = cancellable; + + /* Make an asynchronous query to the store */ + tracker_sparql_connection_query_async (connection, + query->str, + cancellable, + batch_rename_dialog_query_callback, + query_data); + + g_string_free (query, TRUE); +} + +GList * +batch_rename_files_get_distinct_parents (GList *selection) +{ + GList *result; + GList *l1; + NautilusFile *file; + NautilusDirectory *directory; + NautilusFile *parent; + + result = NULL; + for (l1 = selection; l1 != NULL; l1 = l1->next) + { + file = NAUTILUS_FILE (l1->data); + parent = nautilus_file_get_parent (file); + directory = nautilus_directory_get_for_file (parent); + if (!g_list_find (result, directory)) + { + result = g_list_prepend (result, directory); + } + + nautilus_file_unref (parent); + } + + return result; +} diff --git a/src/nautilus-batch-rename-utilities.h b/src/nautilus-batch-rename-utilities.h new file mode 100644 index 0000000..9b163aa --- /dev/null +++ b/src/nautilus-batch-rename-utilities.h @@ -0,0 +1,70 @@ +/* nautilus-batch-rename-utilities.c + * + * Copyright (C) 2016 Alexandru Pandelea + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 2 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 warranty of + * MERCHANTABILITY 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 . + */ + +#pragma once + +#include +#include +#include + +GList* batch_rename_dialog_get_new_names_list (NautilusBatchRenameDialogMode mode, + GList *selection, + GList *tags_list, + GList *selection_metadata, + gchar *entry_text, + gchar *replace_text); + +GList* file_names_list_has_duplicates (NautilusBatchRenameDialog *dialog, + NautilusDirectory *model, + GList *names, + GList *selection, + GList *parents_list, + GCancellable *cancellable); + +GList* nautilus_batch_rename_dialog_sort (GList *selection, + SortMode mode, + GHashTable *creation_date_table); + +void check_metadata_for_selection (NautilusBatchRenameDialog *dialog, + GList *selection, + GCancellable *cancellable); + +gboolean selection_has_single_parent (GList *selection); + +void string_free (gpointer mem); + +void conflict_data_free (gpointer mem); + +GList* batch_rename_files_get_distinct_parents (GList *selection); + +gboolean file_name_conflicts_with_results (GList *selection, + GList *new_names, + GString *old_name, + gchar *parent_uri); + +GString* batch_rename_replace_label_text (gchar *label, + const gchar *substr); + +gchar* batch_rename_get_tag_text_representation (TagConstants tag_constants); + +void batch_rename_sort_lists_for_rename (GList **selection, + GList **new_names, + GList **old_names, + GList **new_files, + GList **old_files, + gboolean is_undo_redo); \ No newline at end of file diff --git a/src/nautilus-bookmark-list.c b/src/nautilus-bookmark-list.c new file mode 100644 index 0000000..b2d7e66 --- /dev/null +++ b/src/nautilus-bookmark-list.c @@ -0,0 +1,670 @@ +/* + * Nautilus + * + * Copyright (C) 1999, 2000 Eazel, Inc. + * + * Nautilus is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License as + * published by the Free Software Foundation; either version 2 of the + * License, or (at your option) any later version. + * + * Nautilus is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * 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 . + * + * Authors: John Sullivan + */ + +/* nautilus-bookmark-list.c - implementation of centralized list of bookmarks. + */ + +#include +#include "nautilus-bookmark-list.h" + +#include "nautilus-file-utilities.h" +#include "nautilus-file.h" +#include "nautilus-icon-names.h" + +#include +#include +#include + +#define MAX_BOOKMARK_LENGTH 80 +#define LOAD_JOB 1 +#define SAVE_JOB 2 + +struct _NautilusBookmarkList +{ + GObject parent_instance; + + GList *list; + GFileMonitor *monitor; + GQueue *pending_ops; +}; + +enum +{ + CHANGED, + LAST_SIGNAL +}; + +static guint signals[LAST_SIGNAL]; + +/* forward declarations */ +#define NAUTILUS_BOOKMARK_LIST_ERROR (nautilus_bookmark_list_error_quark ()) +static GQuark nautilus_bookmark_list_error_quark (void); + +static void nautilus_bookmark_list_load_file (NautilusBookmarkList *bookmarks); +static void nautilus_bookmark_list_save_file (NautilusBookmarkList *bookmarks); + +G_DEFINE_TYPE (NautilusBookmarkList, nautilus_bookmark_list, G_TYPE_OBJECT) + +static GQuark +nautilus_bookmark_list_error_quark (void) +{ + return g_quark_from_static_string ("nautilus-bookmark-list-error-quark"); +} + +static NautilusBookmark * +new_bookmark_from_uri (const char *uri, + const char *label) +{ + NautilusBookmark *new_bookmark = NULL; + g_autoptr (GFile) location = NULL; + + if (uri) + { + location = g_file_new_for_uri (uri); + new_bookmark = nautilus_bookmark_new (location, label); + } + + return new_bookmark; +} + +static GFile * +nautilus_bookmark_list_get_legacy_file (void) +{ + g_autofree char *filename = NULL; + + filename = g_build_filename (g_get_home_dir (), + ".gtk-bookmarks", + NULL); + + return g_file_new_for_path (filename); +} + +static GFile * +nautilus_bookmark_list_get_file (void) +{ + g_autofree char *filename = NULL; + + filename = g_build_filename (g_get_user_config_dir (), + "gtk-3.0", + "bookmarks", + NULL); + + return g_file_new_for_path (filename); +} + +/* Initialization. */ + +static void +bookmark_in_list_changed_callback (NautilusBookmark *bookmark, + NautilusBookmarkList *bookmarks) +{ + g_assert (NAUTILUS_IS_BOOKMARK (bookmark)); + g_assert (NAUTILUS_IS_BOOKMARK_LIST (bookmarks)); + + /* save changes to the list */ + nautilus_bookmark_list_save_file (bookmarks); +} + +static void +bookmark_in_list_notify (GObject *object, + GParamSpec *pspec, + NautilusBookmarkList *bookmarks) +{ + /* emit the changed signal without saving, as only appearance properties changed */ + g_signal_emit (bookmarks, signals[CHANGED], 0); +} + +static void +stop_monitoring_bookmark (NautilusBookmarkList *bookmarks, + NautilusBookmark *bookmark) +{ + g_signal_handlers_disconnect_by_func (bookmark, + bookmark_in_list_changed_callback, + bookmarks); +} + +static void +stop_monitoring_one (gpointer data, + gpointer user_data) +{ + g_assert (NAUTILUS_IS_BOOKMARK (data)); + g_assert (NAUTILUS_IS_BOOKMARK_LIST (user_data)); + + stop_monitoring_bookmark (NAUTILUS_BOOKMARK_LIST (user_data), + NAUTILUS_BOOKMARK (data)); +} + +static void +clear (NautilusBookmarkList *bookmarks) +{ + g_list_foreach (bookmarks->list, stop_monitoring_one, bookmarks); + g_list_free_full (bookmarks->list, g_object_unref); + bookmarks->list = NULL; +} + +static void +do_finalize (GObject *object) +{ + NautilusBookmarkList *self = NAUTILUS_BOOKMARK_LIST (object); + + if (self->monitor != NULL) + { + g_file_monitor_cancel (self->monitor); + g_clear_object (&self->monitor); + } + + g_queue_free (self->pending_ops); + + clear (self); + + G_OBJECT_CLASS (nautilus_bookmark_list_parent_class)->finalize (object); +} + +static void +nautilus_bookmark_list_class_init (NautilusBookmarkListClass *class) +{ + GObjectClass *object_class = G_OBJECT_CLASS (class); + + object_class->finalize = do_finalize; + + signals[CHANGED] = + g_signal_new ("changed", + G_TYPE_FROM_CLASS (object_class), + G_SIGNAL_RUN_LAST, + 0, + NULL, NULL, + g_cclosure_marshal_VOID__VOID, + G_TYPE_NONE, 0); +} + +static void +bookmark_monitor_changed_cb (GFileMonitor *monitor, + GFile *child, + GFile *other_file, + GFileMonitorEvent eflags, + gpointer user_data) +{ + if (eflags == G_FILE_MONITOR_EVENT_CHANGED || + eflags == G_FILE_MONITOR_EVENT_CREATED) + { + g_return_if_fail (NAUTILUS_IS_BOOKMARK_LIST (NAUTILUS_BOOKMARK_LIST (user_data))); + nautilus_bookmark_list_load_file (NAUTILUS_BOOKMARK_LIST (user_data)); + } +} + +static void +nautilus_bookmark_list_init (NautilusBookmarkList *bookmarks) +{ + g_autoptr (GFile) file = NULL; + + bookmarks->pending_ops = g_queue_new (); + + nautilus_bookmark_list_load_file (bookmarks); + + file = nautilus_bookmark_list_get_file (); + bookmarks->monitor = g_file_monitor_file (file, G_FILE_MONITOR_NONE, NULL, NULL); + g_file_monitor_set_rate_limit (bookmarks->monitor, 1000); + + g_signal_connect (bookmarks->monitor, "changed", + G_CALLBACK (bookmark_monitor_changed_cb), bookmarks); +} + +static void +insert_bookmark_internal (NautilusBookmarkList *bookmarks, + NautilusBookmark *bookmark, + int index) +{ + bookmarks->list = g_list_insert (bookmarks->list, bookmark, index); + + g_signal_connect_object (bookmark, "contents-changed", + G_CALLBACK (bookmark_in_list_changed_callback), bookmarks, 0); + g_signal_connect_object (bookmark, "notify::icon", + G_CALLBACK (bookmark_in_list_notify), bookmarks, 0); + g_signal_connect_object (bookmark, "notify::name", + G_CALLBACK (bookmark_in_list_notify), bookmarks, 0); +} + +/** + * nautilus_bookmark_list_item_with_location: + * + * Get the bookmark with the specified location, if any + * @bookmarks: the list of bookmarks. + * @location: a #GFile + * @index: location where to store bookmark index, or %NULL + * + * Return value: the bookmark with location @location, or %NULL. + **/ +NautilusBookmark * +nautilus_bookmark_list_item_with_location (NautilusBookmarkList *bookmarks, + GFile *location, + guint *index) +{ + GList *node; + NautilusBookmark *bookmark; + guint idx; + + g_return_val_if_fail (NAUTILUS_IS_BOOKMARK_LIST (bookmarks), NULL); + g_return_val_if_fail (G_IS_FILE (location), NULL); + + idx = 0; + + for (node = bookmarks->list; node != NULL; node = node->next) + { + g_autoptr (GFile) bookmark_location = NULL; + + bookmark = node->data; + bookmark_location = nautilus_bookmark_get_location (bookmark); + + if (g_file_equal (location, bookmark_location)) + { + if (index) + { + *index = idx; + } + + return bookmark; + } + + idx++; + } + + return NULL; +} + +/** + * nautilus_bookmark_list_append: + * + * Append a bookmark to a bookmark list. + * @bookmarks: NautilusBookmarkList to append to. + * @bookmark: Bookmark to append a copy of. + **/ +void +nautilus_bookmark_list_append (NautilusBookmarkList *bookmarks, + NautilusBookmark *bookmark) +{ + g_return_if_fail (NAUTILUS_IS_BOOKMARK_LIST (bookmarks)); + g_return_if_fail (NAUTILUS_IS_BOOKMARK (bookmark)); + + if (g_list_find_custom (bookmarks->list, bookmark, + nautilus_bookmark_compare_with) != NULL) + { + return; + } + + insert_bookmark_internal (bookmarks, g_object_ref (bookmark), -1); + nautilus_bookmark_list_save_file (bookmarks); +} + +static void +process_next_op (NautilusBookmarkList *bookmarks); + +static void +op_processed_cb (NautilusBookmarkList *self) +{ + g_queue_pop_tail (self->pending_ops); + + if (!g_queue_is_empty (self->pending_ops)) + { + process_next_op (self); + } +} + +static void +load_callback (GObject *source_object, + GAsyncResult *res, + gpointer user_data) +{ + NautilusBookmarkList *self = NAUTILUS_BOOKMARK_LIST (source_object); + g_autoptr (GError) error = NULL; + g_autofree gchar *contents = NULL; + char **lines; + int i; + + contents = g_task_propagate_pointer (G_TASK (res), &error); + if (error != NULL) + { + g_warning ("Unable to get contents of the bookmarks file: %s", + error->message); + op_processed_cb (self); + return; + } + + lines = g_strsplit (contents, "\n", -1); + for (i = 0; lines[i]; i++) + { + /* Ignore empty or invalid lines that cannot be parsed properly */ + if (lines[i][0] != '\0' && lines[i][0] != ' ') + { + /* gtk 2.7/2.8 might have labels appended to bookmarks which are separated by a space + * we must seperate the bookmark uri and the potential label + */ + char *space; + g_autofree char *label = NULL; + + space = strchr (lines[i], ' '); + if (space) + { + *space = '\0'; + label = g_strdup (space + 1); + } + + insert_bookmark_internal (self, new_bookmark_from_uri (lines[i], label), -1); + } + } + + g_signal_emit (self, signals[CHANGED], 0); + op_processed_cb (self); + + g_strfreev (lines); +} + +static void +load_io_thread (GTask *task, + gpointer source_object, + gpointer task_data, + GCancellable *cancellable) +{ + GFile *file; + gchar *contents; + GError *error = NULL; + + file = nautilus_bookmark_list_get_file (); + if (!g_file_query_exists (file, NULL)) + { + g_object_unref (file); + file = nautilus_bookmark_list_get_legacy_file (); + } + + g_file_load_contents (file, NULL, &contents, NULL, NULL, &error); + g_object_unref (file); + + if (error != NULL) + { + g_task_return_error (task, error); + } + else + { + g_task_return_pointer (task, contents, g_free); + } +} + +static void +load_file_async (NautilusBookmarkList *self) +{ + g_autoptr (GTask) task = NULL; + + /* Wipe out old list. */ + clear (self); + + task = g_task_new (G_OBJECT (self), + NULL, + load_callback, NULL); + g_task_run_in_thread (task, load_io_thread); +} + +static void +save_callback (GObject *source_object, + GAsyncResult *res, + gpointer user_data) +{ + NautilusBookmarkList *self = NAUTILUS_BOOKMARK_LIST (source_object); + g_autoptr (GError) error = NULL; + gboolean success; + g_autoptr (GFile) file = NULL; + + success = g_task_propagate_boolean (G_TASK (res), &error); + + if (error != NULL) + { + g_warning ("Unable to replace contents of the bookmarks file: %s", + error->message); + } + + /* g_file_replace_contents() returned FALSE, but did not set an error. */ + if (!success) + { + g_warning ("Unable to replace contents of the bookmarks file."); + } + + /* re-enable bookmark file monitoring */ + file = nautilus_bookmark_list_get_file (); + self->monitor = g_file_monitor_file (file, 0, NULL, NULL); + + g_file_monitor_set_rate_limit (self->monitor, 1000); + g_signal_connect (self->monitor, "changed", + G_CALLBACK (bookmark_monitor_changed_cb), self); + + op_processed_cb (self); +} + +static void +save_io_thread (GTask *task, + gpointer source_object, + gpointer task_data, + GCancellable *cancellable) +{ + gchar *contents; + g_autofree gchar *path = NULL; + g_autoptr (GFile) parent = NULL; + g_autoptr (GFile) file = NULL; + gboolean success; + GError *error = NULL; + + file = nautilus_bookmark_list_get_file (); + parent = g_file_get_parent (file); + path = g_file_get_path (parent); + + if (g_mkdir_with_parents (path, 0700) == -1) + { + int saved_errno = errno; + + g_set_error (&error, NAUTILUS_BOOKMARK_LIST_ERROR, 0, + "Failed to create bookmarks folder %s: %s", + path, g_strerror (saved_errno)); + g_task_return_error (task, error); + return; + } + + contents = (gchar *) g_task_get_task_data (task); + + success = g_file_replace_contents (file, + contents, strlen (contents), + NULL, FALSE, 0, NULL, + NULL, &error); + + if (error != NULL) + { + g_task_return_error (task, error); + } + else + { + g_task_return_boolean (task, success); + } +} + +static void +save_file_async (NautilusBookmarkList *self) +{ + g_autoptr (GTask) task = NULL; + GString *bookmark_string; + gchar *contents; + GList *l; + + bookmark_string = g_string_new (NULL); + + /* temporarily disable bookmark file monitoring when writing file */ + if (self->monitor != NULL) + { + g_file_monitor_cancel (self->monitor); + g_clear_object (&self->monitor); + } + + for (l = self->list; l; l = l->next) + { + NautilusBookmark *bookmark; + + bookmark = NAUTILUS_BOOKMARK (l->data); + + /* make sure we save label if it has one for compatibility with GTK 2.7 and 2.8 */ + if (nautilus_bookmark_get_has_custom_name (bookmark)) + { + const char *label; + g_autofree char *uri = NULL; + + label = nautilus_bookmark_get_name (bookmark); + uri = nautilus_bookmark_get_uri (bookmark); + + g_string_append_printf (bookmark_string, + "%s %s\n", uri, label); + } + else + { + g_autofree char *uri = NULL; + + uri = nautilus_bookmark_get_uri (bookmark); + + g_string_append_printf (bookmark_string, "%s\n", uri); + } + } + + task = g_task_new (G_OBJECT (self), + NULL, + save_callback, NULL); + contents = g_string_free (bookmark_string, FALSE); + g_task_set_task_data (task, contents, g_free); + + g_task_run_in_thread (task, save_io_thread); +} + +static void +process_next_op (NautilusBookmarkList *bookmarks) +{ + gint op; + + op = GPOINTER_TO_INT (g_queue_peek_tail (bookmarks->pending_ops)); + + if (op == LOAD_JOB) + { + load_file_async (bookmarks); + } + else + { + save_file_async (bookmarks); + } +} + +/** + * nautilus_bookmark_list_load_file: + * + * Reads bookmarks from file, clobbering contents in memory. + * @bookmarks: the list of bookmarks to fill with file contents. + **/ +static void +nautilus_bookmark_list_load_file (NautilusBookmarkList *bookmarks) +{ + g_queue_push_head (bookmarks->pending_ops, GINT_TO_POINTER (LOAD_JOB)); + + if (g_queue_get_length (bookmarks->pending_ops) == 1) + { + process_next_op (bookmarks); + } +} + +/** + * nautilus_bookmark_list_save_file: + * + * Save bookmarks to disk. + * @bookmarks: the list of bookmarks to save. + **/ +static void +nautilus_bookmark_list_save_file (NautilusBookmarkList *bookmarks) +{ + g_signal_emit (bookmarks, signals[CHANGED], 0); + + g_queue_push_head (bookmarks->pending_ops, GINT_TO_POINTER (SAVE_JOB)); + + if (g_queue_get_length (bookmarks->pending_ops) == 1) + { + process_next_op (bookmarks); + } +} + +gboolean +nautilus_bookmark_list_can_bookmark_location (NautilusBookmarkList *list, + GFile *location) +{ + g_autoptr (NautilusBookmark) bookmark = NULL; + + if (nautilus_bookmark_list_item_with_location (list, location, NULL)) + { + /* Already bookmarked */ + return FALSE; + } + + if (nautilus_is_search_directory (location)) + { + return FALSE; + } + + if (nautilus_is_recent_directory (location) || + nautilus_is_starred_directory (location) || + nautilus_is_home_directory (location) || + nautilus_is_trash_directory (location) || + nautilus_is_other_locations_directory (location)) + { + /* Already in the sidebar */ + return FALSE; + } + + bookmark = nautilus_bookmark_new (location, NULL); + return !nautilus_bookmark_get_is_builtin (bookmark); +} + +/** + * nautilus_bookmark_list_new: + * + * Create a new bookmark_list, with contents read from disk. + * + * Return value: A pointer to the new widget. + **/ +NautilusBookmarkList * +nautilus_bookmark_list_new (void) +{ + NautilusBookmarkList *list; + + list = NAUTILUS_BOOKMARK_LIST (g_object_new (NAUTILUS_TYPE_BOOKMARK_LIST, NULL)); + + return list; +} + +/** + * nautilus_bookmark_list_get_all: + * + * Get a GList of all NautilusBookmark. + * @bookmarks: NautilusBookmarkList from where to get the bookmarks. + **/ +GList * +nautilus_bookmark_list_get_all (NautilusBookmarkList *bookmarks) +{ + g_return_val_if_fail (NAUTILUS_IS_BOOKMARK_LIST (bookmarks), NULL); + + return bookmarks->list; +} diff --git a/src/nautilus-bookmark-list.h b/src/nautilus-bookmark-list.h new file mode 100644 index 0000000..4849ea8 --- /dev/null +++ b/src/nautilus-bookmark-list.h @@ -0,0 +1,47 @@ + +/* + * Nautilus + * + * Copyright (C) 1999, 2000 Eazel, Inc. + * + * Nautilus is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License as + * published by the Free Software Foundation; either version 2 of the + * License, or (at your option) any later version. + * + * Nautilus is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * 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 . + * + * Authors: John Sullivan + */ + +/* nautilus-bookmark-list.h - interface for centralized list of bookmarks. + */ + +#pragma once + +#include "nautilus-bookmark.h" +#include + +G_BEGIN_DECLS + +#define NAUTILUS_TYPE_BOOKMARK_LIST (nautilus_bookmark_list_get_type()) + +G_DECLARE_FINAL_TYPE (NautilusBookmarkList, nautilus_bookmark_list, NAUTILUS, BOOKMARK_LIST, GObject) + +NautilusBookmarkList * nautilus_bookmark_list_new (void); +void nautilus_bookmark_list_append (NautilusBookmarkList *bookmarks, + NautilusBookmark *bookmark); +NautilusBookmark * nautilus_bookmark_list_item_with_location (NautilusBookmarkList *bookmarks, + GFile *location, + guint *index); +gboolean nautilus_bookmark_list_can_bookmark_location (NautilusBookmarkList *list, + GFile *location); +GList * nautilus_bookmark_list_get_all (NautilusBookmarkList *bookmarks); + +G_END_DECLS \ No newline at end of file diff --git a/src/nautilus-bookmark.c b/src/nautilus-bookmark.c new file mode 100644 index 0000000..f016eee --- /dev/null +++ b/src/nautilus-bookmark.c @@ -0,0 +1,788 @@ +/* nautilus-bookmark.c - implementation of individual bookmarks. + * + * Copyright (C) 1999, 2000 Eazel, Inc. + * Copyright (C) 2011, Red Hat, Inc. + * + * The Gnome Library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Library General Public License as + * published by the Free Software Foundation; either version 2 of the + * License, or (at your option) any later version. + * + * The Gnome Library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Library General Public License for more details. + * + * You should have received a copy of the GNU Library General Public + * License along with the Gnome Library; see the file COPYING.LIB. If not, + * see . + * + * Authors: John Sullivan + * Cosimo Cecchi + */ + +#include + +#include "nautilus-bookmark.h" + +#include +#include +#include +#include + +#include "nautilus-file.h" +#include "nautilus-file-utilities.h" +#include "nautilus-icon-names.h" + +#define DEBUG_FLAG NAUTILUS_DEBUG_BOOKMARKS +#include "nautilus-debug.h" + +enum +{ + CONTENTS_CHANGED, + LAST_SIGNAL +}; + +enum +{ + PROP_NAME = 1, + PROP_CUSTOM_NAME, + PROP_LOCATION, + PROP_ICON, + PROP_SYMBOLIC_ICON, + NUM_PROPERTIES +}; + +static GParamSpec *properties[NUM_PROPERTIES] = { NULL }; +static guint signals[LAST_SIGNAL]; + +struct _NautilusBookmark +{ + GObject parent_instance; + + char *name; + gboolean has_custom_name; + GFile *location; + GIcon *icon; + GIcon *symbolic_icon; + NautilusFile *file; + + char *scroll_file; + + gboolean exists; + guint exists_id; + GCancellable *cancellable; +}; + +static void nautilus_bookmark_disconnect_file (NautilusBookmark *file); + +G_DEFINE_TYPE (NautilusBookmark, nautilus_bookmark, G_TYPE_OBJECT); + +static void +nautilus_bookmark_set_name_internal (NautilusBookmark *bookmark, + const char *new_name) +{ + if (g_strcmp0 (bookmark->name, new_name) != 0) + { + g_free (bookmark->name); + bookmark->name = g_strdup (new_name); + + g_object_notify_by_pspec (G_OBJECT (bookmark), properties[PROP_NAME]); + } +} + +static void +bookmark_set_name_from_ready_file (NautilusBookmark *self, + NautilusFile *file) +{ + g_autofree gchar *display_name = NULL; + + if (self->has_custom_name) + { + return; + } + + display_name = nautilus_file_get_display_name (self->file); + + if (nautilus_file_is_other_locations (self->file)) + { + nautilus_bookmark_set_name_internal (self, _("Other Locations")); + } + else if (nautilus_file_is_home (self->file)) + { + nautilus_bookmark_set_name_internal (self, _("Home")); + } + else if (g_strcmp0 (self->name, display_name) != 0) + { + nautilus_bookmark_set_name_internal (self, display_name); + DEBUG ("%s: name changed to %s", nautilus_bookmark_get_name (self), display_name); + } +} + +static void +bookmark_file_changed_callback (NautilusFile *file, + NautilusBookmark *bookmark) +{ + g_autoptr (GFile) location = NULL; + + g_assert (file == bookmark->file); + + DEBUG ("%s: file changed", nautilus_bookmark_get_name (bookmark)); + + location = nautilus_file_get_location (file); + + if (!g_file_equal (bookmark->location, location) && + !nautilus_file_is_in_trash (file)) + { + DEBUG ("%s: file got moved", nautilus_bookmark_get_name (bookmark)); + + g_object_unref (bookmark->location); + bookmark->location = g_object_ref (location); + + g_object_notify_by_pspec (G_OBJECT (bookmark), properties[PROP_LOCATION]); + g_signal_emit (bookmark, signals[CONTENTS_CHANGED], 0); + } + + if (nautilus_file_is_gone (file) || + nautilus_file_is_in_trash (file)) + { + /* The file we were monitoring has been trashed, deleted, + * or moved in a way that we didn't notice. We should make + * a spanking new NautilusFile object for this + * location so if a new file appears in this place + * we will notice. However, we can't immediately do so + * because creating a new NautilusFile directly as a result + * of noticing a file goes away may trigger i/o on that file + * again, noticeing it is gone, leading to a loop. + * So, the new NautilusFile is created when the bookmark + * is used again. However, this is not really a problem, as + * we don't want to change the icon or anything about the + * bookmark just because its not there anymore. + */ + DEBUG ("%s: trashed", nautilus_bookmark_get_name (bookmark)); + nautilus_bookmark_disconnect_file (bookmark); + } + else + { + bookmark_set_name_from_ready_file (bookmark, file); + } +} + +gboolean +nautilus_bookmark_get_is_builtin (NautilusBookmark *bookmark) +{ + GUserDirectory xdg_type; + + /* if this is not an XDG dir, it's never builtin */ + if (!nautilus_bookmark_get_xdg_type (bookmark, &xdg_type)) + { + return FALSE; + } + + /* exclude XDG locations which are not in our builtin list */ + return (xdg_type != G_USER_DIRECTORY_DESKTOP) && + (xdg_type != G_USER_DIRECTORY_TEMPLATES) && + (xdg_type != G_USER_DIRECTORY_PUBLIC_SHARE); +} + +gboolean +nautilus_bookmark_get_xdg_type (NautilusBookmark *bookmark, + GUserDirectory *directory) +{ + gboolean match; + GFile *location; + const gchar *path; + GUserDirectory dir; + + match = FALSE; + + for (dir = 0; dir < G_USER_N_DIRECTORIES; dir++) + { + path = g_get_user_special_dir (dir); + if (!path) + { + continue; + } + + location = g_file_new_for_path (path); + match = g_file_equal (location, bookmark->location); + g_object_unref (location); + + if (match) + { + break; + } + } + + if (match && directory != NULL) + { + *directory = dir; + } + + return match; +} + +static GIcon * +get_native_icon (NautilusBookmark *bookmark, + gboolean symbolic) +{ + GUserDirectory xdg_type; + GIcon *icon = NULL; + + if (bookmark->file == NULL) + { + goto out; + } + + if (!nautilus_bookmark_get_xdg_type (bookmark, &xdg_type)) + { + goto out; + } + + if (xdg_type < G_USER_N_DIRECTORIES) + { + if (symbolic) + { + icon = nautilus_special_directory_get_symbolic_icon (xdg_type); + } + else + { + icon = nautilus_special_directory_get_icon (xdg_type); + } + } + +out: + if (icon == NULL) + { + if (symbolic) + { + icon = g_themed_icon_new (NAUTILUS_ICON_FOLDER); + } + else + { + icon = g_themed_icon_new (NAUTILUS_ICON_FULLCOLOR_FOLDER); + } + } + + return icon; +} + +static void +nautilus_bookmark_set_icon_to_default (NautilusBookmark *bookmark) +{ + g_autoptr (GIcon) icon = NULL; + g_autoptr (GIcon) symbolic_icon = NULL; + + if (!bookmark->exists) + { + DEBUG ("%s: file does not exist, set warning icon", nautilus_bookmark_get_name (bookmark)); + symbolic_icon = g_themed_icon_new ("dialog-warning-symbolic"); + icon = g_themed_icon_new ("dialog-warning"); + } + else if (g_file_is_native (bookmark->location)) + { + symbolic_icon = get_native_icon (bookmark, TRUE); + icon = get_native_icon (bookmark, FALSE); + } + else + { + symbolic_icon = g_themed_icon_new (NAUTILUS_ICON_FOLDER_REMOTE); + icon = g_themed_icon_new (NAUTILUS_ICON_FULLCOLOR_FOLDER_REMOTE); + } + + DEBUG ("%s: setting icon to default", nautilus_bookmark_get_name (bookmark)); + + g_object_set (bookmark, + "icon", icon, + "symbolic-icon", symbolic_icon, + NULL); +} + +static void +nautilus_bookmark_disconnect_file (NautilusBookmark *bookmark) +{ + if (bookmark->file != NULL) + { + DEBUG ("%s: disconnecting file", + nautilus_bookmark_get_name (bookmark)); + + g_signal_handlers_disconnect_by_func (bookmark->file, + G_CALLBACK (bookmark_file_changed_callback), + bookmark); + g_clear_object (&bookmark->file); + } + + if (bookmark->cancellable != NULL) + { + g_cancellable_cancel (bookmark->cancellable); + g_clear_object (&bookmark->cancellable); + } + + if (bookmark->exists_id != 0) + { + g_source_remove (bookmark->exists_id); + bookmark->exists_id = 0; + } +} + +static void +nautilus_bookmark_connect_file (NautilusBookmark *bookmark) +{ + if (bookmark->file != NULL) + { + DEBUG ("%s: file already connected, returning", + nautilus_bookmark_get_name (bookmark)); + return; + } + + if (bookmark->exists) + { + DEBUG ("%s: creating file", nautilus_bookmark_get_name (bookmark)); + + bookmark->file = nautilus_file_get (bookmark->location); + g_assert (!nautilus_file_is_gone (bookmark->file)); + + g_signal_connect_object (bookmark->file, "changed", + G_CALLBACK (bookmark_file_changed_callback), bookmark, 0); + } + + if (bookmark->icon == NULL || + bookmark->symbolic_icon == NULL) + { + nautilus_bookmark_set_icon_to_default (bookmark); + } + + if (bookmark->file != NULL && + nautilus_file_check_if_ready (bookmark->file, NAUTILUS_FILE_ATTRIBUTE_INFO)) + { + bookmark_set_name_from_ready_file (bookmark, bookmark->file); + } + + if (bookmark->name == NULL) + { + bookmark->name = nautilus_compute_title_for_location (bookmark->location); + } +} + +static void +nautilus_bookmark_set_exists (NautilusBookmark *bookmark, + gboolean exists) +{ + if (bookmark->exists == exists) + { + return; + } + + bookmark->exists = exists; + DEBUG ("%s: setting bookmark to exist: %d\n", + nautilus_bookmark_get_name (bookmark), exists); + + /* refresh icon */ + nautilus_bookmark_set_icon_to_default (bookmark); +} + +static gboolean +exists_non_native_idle_cb (gpointer user_data) +{ + NautilusBookmark *bookmark = user_data; + bookmark->exists_id = 0; + nautilus_bookmark_set_exists (bookmark, FALSE); + + return FALSE; +} + +static void +exists_query_info_ready_cb (GObject *source, + GAsyncResult *res, + gpointer user_data) +{ + g_autoptr (GFileInfo) info = NULL; + NautilusBookmark *bookmark; + g_autoptr (GError) error = NULL; + gboolean exists = FALSE; + + info = g_file_query_info_finish (G_FILE (source), res, &error); + if (!info && g_error_matches (error, G_IO_ERROR, G_IO_ERROR_CANCELLED)) + { + return; + } + + bookmark = user_data; + + if (info) + { + exists = TRUE; + + g_clear_object (&bookmark->cancellable); + } + + nautilus_bookmark_set_exists (bookmark, exists); +} + +static void +nautilus_bookmark_update_exists (NautilusBookmark *bookmark) +{ + /* Convert to a path, returning FALSE if not local. */ + if (!g_file_is_native (bookmark->location) && + bookmark->exists_id == 0) + { + bookmark->exists_id = + g_idle_add (exists_non_native_idle_cb, bookmark); + return; + } + + if (bookmark->cancellable != NULL) + { + return; + } + + bookmark->cancellable = g_cancellable_new (); + g_file_query_info_async (bookmark->location, + G_FILE_ATTRIBUTE_STANDARD_TYPE, + 0, G_PRIORITY_DEFAULT, + bookmark->cancellable, + exists_query_info_ready_cb, bookmark); +} + +/* GObject methods */ + +static void +nautilus_bookmark_set_property (GObject *object, + guint property_id, + const GValue *value, + GParamSpec *pspec) +{ + NautilusBookmark *self = NAUTILUS_BOOKMARK (object); + GIcon *new_icon; + + switch (property_id) + { + case PROP_ICON: + { + new_icon = g_value_get_object (value); + + if (new_icon != NULL && !g_icon_equal (self->icon, new_icon)) + { + g_clear_object (&self->icon); + self->icon = g_object_ref (new_icon); + } + } + break; + + case PROP_SYMBOLIC_ICON: + { + new_icon = g_value_get_object (value); + + if (new_icon != NULL && !g_icon_equal (self->symbolic_icon, new_icon)) + { + g_clear_object (&self->symbolic_icon); + self->symbolic_icon = g_object_ref (new_icon); + } + } + break; + + case PROP_LOCATION: + { + self->location = g_value_dup_object (value); + } + break; + + case PROP_CUSTOM_NAME: + { + self->has_custom_name = g_value_get_boolean (value); + } + break; + + case PROP_NAME: + { + nautilus_bookmark_set_name_internal (self, g_value_get_string (value)); + } + break; + + default: + { + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec); + } + break; + } +} + +static void +nautilus_bookmark_get_property (GObject *object, + guint property_id, + GValue *value, + GParamSpec *pspec) +{ + NautilusBookmark *self = NAUTILUS_BOOKMARK (object); + + switch (property_id) + { + case PROP_NAME: + { + g_value_set_string (value, self->name); + } + break; + + case PROP_ICON: + { + g_value_set_object (value, self->icon); + } + break; + + case PROP_SYMBOLIC_ICON: + { + g_value_set_object (value, self->symbolic_icon); + } + break; + + case PROP_LOCATION: + { + g_value_set_object (value, self->location); + } + break; + + case PROP_CUSTOM_NAME: + { + g_value_set_boolean (value, self->has_custom_name); + } + break; + + default: + { + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec); + } + break; + } +} + +static void +nautilus_bookmark_finalize (GObject *object) +{ + NautilusBookmark *bookmark; + + g_assert (NAUTILUS_IS_BOOKMARK (object)); + + bookmark = NAUTILUS_BOOKMARK (object); + + nautilus_bookmark_disconnect_file (bookmark); + + g_object_unref (bookmark->location); + g_clear_object (&bookmark->icon); + g_clear_object (&bookmark->symbolic_icon); + + g_free (bookmark->name); + g_free (bookmark->scroll_file); + + G_OBJECT_CLASS (nautilus_bookmark_parent_class)->finalize (object); +} + +static void +nautilus_bookmark_constructed (GObject *obj) +{ + NautilusBookmark *self = NAUTILUS_BOOKMARK (obj); + + nautilus_bookmark_connect_file (self); + nautilus_bookmark_update_exists (self); +} + +static void +nautilus_bookmark_class_init (NautilusBookmarkClass *class) +{ + GObjectClass *oclass = G_OBJECT_CLASS (class); + + oclass->finalize = nautilus_bookmark_finalize; + oclass->get_property = nautilus_bookmark_get_property; + oclass->set_property = nautilus_bookmark_set_property; + oclass->constructed = nautilus_bookmark_constructed; + + signals[CONTENTS_CHANGED] = + g_signal_new ("contents-changed", + G_TYPE_FROM_CLASS (class), + G_SIGNAL_RUN_LAST, + 0, + NULL, NULL, + g_cclosure_marshal_VOID__VOID, + G_TYPE_NONE, 0); + + properties[PROP_NAME] = + g_param_spec_string ("name", + "Bookmark's name", + "The name of this bookmark", + NULL, + G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS | G_PARAM_CONSTRUCT); + + properties[PROP_CUSTOM_NAME] = + g_param_spec_boolean ("custom-name", + "Whether the bookmark has a custom name", + "Whether the bookmark has a custom name", + FALSE, + G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS | G_PARAM_CONSTRUCT); + + properties[PROP_LOCATION] = + g_param_spec_object ("location", + "Bookmark's location", + "The location of this bookmark", + G_TYPE_FILE, + G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS | G_PARAM_CONSTRUCT_ONLY); + + properties[PROP_ICON] = + g_param_spec_object ("icon", + "Bookmark's icon", + "The icon of this bookmark", + G_TYPE_ICON, + G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS); + + properties[PROP_SYMBOLIC_ICON] = + g_param_spec_object ("symbolic-icon", + "Bookmark's symbolic icon", + "The symbolic icon of this bookmark", + G_TYPE_ICON, + G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS); + + g_object_class_install_properties (oclass, NUM_PROPERTIES, properties); +} + +static void +nautilus_bookmark_init (NautilusBookmark *bookmark) +{ + bookmark->exists = TRUE; +} + +const gchar * +nautilus_bookmark_get_name (NautilusBookmark *bookmark) +{ + g_return_val_if_fail (NAUTILUS_IS_BOOKMARK (bookmark), NULL); + + return bookmark->name; +} + +gboolean +nautilus_bookmark_get_has_custom_name (NautilusBookmark *bookmark) +{ + g_return_val_if_fail (NAUTILUS_IS_BOOKMARK (bookmark), FALSE); + + return (bookmark->has_custom_name); +} + +/** + * nautilus_bookmark_compare_with: + * + * Check whether two bookmarks are considered identical. + * @a: first NautilusBookmark*. + * @b: second NautilusBookmark*. + * + * Return value: 0 if @a and @b have same name and uri, 1 otherwise + * (GCompareFunc style) + **/ +int +nautilus_bookmark_compare_with (gconstpointer a, + gconstpointer b) +{ + NautilusBookmark *bookmark_a; + NautilusBookmark *bookmark_b; + + g_return_val_if_fail (NAUTILUS_IS_BOOKMARK ((gpointer) a), 1); + g_return_val_if_fail (NAUTILUS_IS_BOOKMARK ((gpointer) b), 1); + + bookmark_a = NAUTILUS_BOOKMARK ((gpointer) a); + bookmark_b = NAUTILUS_BOOKMARK ((gpointer) b); + + if (!g_file_equal (bookmark_a->location, + bookmark_b->location)) + { + return 1; + } + + if (g_strcmp0 (bookmark_a->name, + bookmark_b->name) != 0) + { + return 1; + } + + return 0; +} + +GIcon * +nautilus_bookmark_get_symbolic_icon (NautilusBookmark *bookmark) +{ + g_return_val_if_fail (NAUTILUS_IS_BOOKMARK (bookmark), NULL); + + /* Try to connect a file in case file exists now but didn't earlier. */ + nautilus_bookmark_connect_file (bookmark); + + if (bookmark->symbolic_icon) + { + return g_object_ref (bookmark->symbolic_icon); + } + return NULL; +} + +GIcon * +nautilus_bookmark_get_icon (NautilusBookmark *bookmark) +{ + g_return_val_if_fail (NAUTILUS_IS_BOOKMARK (bookmark), NULL); + + /* Try to connect a file in case file exists now but didn't earlier. */ + nautilus_bookmark_connect_file (bookmark); + + if (bookmark->icon) + { + return g_object_ref (bookmark->icon); + } + return NULL; +} + +GFile * +nautilus_bookmark_get_location (NautilusBookmark *bookmark) +{ + g_return_val_if_fail (NAUTILUS_IS_BOOKMARK (bookmark), NULL); + + /* Try to connect a file in case file exists now but didn't earlier. + * This allows a bookmark to update its image properly in the case + * where a new file appears with the same URI as a previously-deleted + * file. Calling connect_file here means that attempts to activate the + * bookmark will update its image if possible. + */ + nautilus_bookmark_connect_file (bookmark); + + return g_object_ref (bookmark->location); +} + +char * +nautilus_bookmark_get_uri (NautilusBookmark *bookmark) +{ + g_autoptr (GFile) file = NULL; + + file = nautilus_bookmark_get_location (bookmark); + + return g_file_get_uri (file); +} + +NautilusBookmark * +nautilus_bookmark_new (GFile *location, + const gchar *custom_name) +{ + NautilusBookmark *new_bookmark; + + new_bookmark = NAUTILUS_BOOKMARK (g_object_new (NAUTILUS_TYPE_BOOKMARK, + "location", location, + "name", custom_name, + "custom-name", custom_name != NULL, + NULL)); + + return new_bookmark; +} + +void +nautilus_bookmark_set_scroll_pos (NautilusBookmark *bookmark, + const char *uri) +{ + g_free (bookmark->scroll_file); + bookmark->scroll_file = g_strdup (uri); +} + +char * +nautilus_bookmark_get_scroll_pos (NautilusBookmark *bookmark) +{ + return g_strdup (bookmark->scroll_file); +} diff --git a/src/nautilus-bookmark.h b/src/nautilus-bookmark.h new file mode 100644 index 0000000..6bf3852 --- /dev/null +++ b/src/nautilus-bookmark.h @@ -0,0 +1,54 @@ + +/* nautilus-bookmark.h - implementation of individual bookmarks. + * + * Copyright (C) 1999, 2000 Eazel, Inc. + * Copyright (C) 2011, Red Hat, Inc. + * + * The Gnome Library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Library General Public License as + * published by the Free Software Foundation; either version 2 of the + * License, or (at your option) any later version. + * + * The Gnome Library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Library General Public License for more details. + * + * You should have received a copy of the GNU Library General Public + * License along with the Gnome Library; see the file COPYING.LIB. If not, + * see . + * + * Authors: John Sullivan + * Cosimo Cecchi + */ + +#pragma once + +#include +#include + +G_BEGIN_DECLS + +#define NAUTILUS_TYPE_BOOKMARK nautilus_bookmark_get_type() + +G_DECLARE_FINAL_TYPE (NautilusBookmark, nautilus_bookmark, NAUTILUS, BOOKMARK, GObject) + +NautilusBookmark * nautilus_bookmark_new (GFile *location, + const char *custom_name); +const char * nautilus_bookmark_get_name (NautilusBookmark *bookmark); +GFile * nautilus_bookmark_get_location (NautilusBookmark *bookmark); +char * nautilus_bookmark_get_uri (NautilusBookmark *bookmark); +GIcon * nautilus_bookmark_get_icon (NautilusBookmark *bookmark); +GIcon * nautilus_bookmark_get_symbolic_icon (NautilusBookmark *bookmark); +gboolean nautilus_bookmark_get_xdg_type (NautilusBookmark *bookmark, + GUserDirectory *directory); +gboolean nautilus_bookmark_get_is_builtin (NautilusBookmark *bookmark); +gboolean nautilus_bookmark_get_has_custom_name (NautilusBookmark *bookmark); +int nautilus_bookmark_compare_with (gconstpointer a, + gconstpointer b); + +void nautilus_bookmark_set_scroll_pos (NautilusBookmark *bookmark, + const char *uri); +char * nautilus_bookmark_get_scroll_pos (NautilusBookmark *bookmark); + +G_END_DECLS diff --git a/src/nautilus-clipboard.c b/src/nautilus-clipboard.c new file mode 100644 index 0000000..99dc02d --- /dev/null +++ b/src/nautilus-clipboard.c @@ -0,0 +1,349 @@ +/* nautilus-clipboard.c + * + * Nautilus Clipboard support. For now, routines to support component cut + * and paste. + * + * Copyright (C) 1999, 2000 Free Software Foundaton + * Copyright (C) 2000, 2001 Eazel, Inc. + * Copyright (C) 2016 Carlos Soriano + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU Library General Public License as + * published by the Free Software Foundation; either version 2 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 warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Library General Public License for more details. + * + * You should have received a copy of the GNU Library General Public + * License along with this program; if not, see . + * + * Authors: Rebecca Schulman , + * Darin Adler + */ + +#include +#include "nautilus-clipboard.h" +#include "nautilus-file-utilities.h" +#include "nautilus-file.h" + +#include +#include +#include + +/* The .files member contains elements of type NautilusFile. */ +struct _NautilusClipboard +{ + gboolean cut; + GList *files; +}; + +/* Boxed type used to wrap this struct in a clipboard GValue. */ +G_DEFINE_BOXED_TYPE (NautilusClipboard, nautilus_clipboard, + nautilus_clipboard_copy, nautilus_clipboard_free) + +static char * +nautilus_clipboard_to_string (NautilusClipboard *clip) +{ + GString *uris; + char *uri; + guint i; + GList *l; + + uris = g_string_new (clip->cut ? "cut" : "copy"); + + for (i = 0, l = clip->files; l != NULL; l = l->next, i++) + { + uri = nautilus_file_get_uri (l->data); + + g_string_append_c (uris, '\n'); + g_string_append (uris, uri); + + g_free (uri); + } + + return g_string_free (uris, FALSE); +} + +static NautilusClipboard * +nautilus_clipboard_from_string (char *string, + GError **error) +{ + NautilusClipboard *clip; + g_auto (GStrv) lines = NULL; + g_autolist (NautilusFile) files = NULL; + + if (string == NULL) + { + *error = g_error_new (G_IO_ERROR, G_IO_ERROR_INVALID_DATA, "Clipboard string cannot be NULL."); + return NULL; + } + + lines = g_strsplit (string, "\n", 0); + + if (g_strcmp0 (lines[0], "cut") != 0 && g_strcmp0 (lines[0], "copy") != 0) + { + /* Translators: Do not translate 'cut' and 'copy'. These are literal keywords. */ + *error = g_error_new (G_IO_ERROR, G_IO_ERROR_INVALID_DATA, "Nautilus Clipboard must begin with “cut” or “copy”."); + return NULL; + } + + /* Line 0 is "cut" or "copy", so uris start at line 1. */ + for (int i = 1; lines[i] != NULL; i++) + { + if (g_strcmp0 (lines[i], "") == 0) + { + *error = g_error_new (G_IO_ERROR, G_IO_ERROR_INVALID_DATA, "Nautilus Clipboard must not have empty lines."); + return NULL; + } + else if (!g_uri_is_valid (lines[i], G_URI_FLAGS_NONE, error)) + { + return NULL; + } + files = g_list_prepend (files, nautilus_file_get_by_uri (lines[i])); + } + + clip = g_new0 (NautilusClipboard, 1); + files = g_list_reverse (files); + clip->files = g_steal_pointer (&files); + clip->cut = g_str_equal (lines[0], "cut"); + + return clip; +} + +#if 0 && NAUTILUS_DND_NEEDS_GTK4_REIMPLEMENTATION +void +nautilus_clipboard_clear_if_colliding_uris (GtkWidget *widget, + const GList *item_uris) +{ + GtkSelectionData *data; + GList *clipboard_item_uris, *l; + gboolean collision; + + collision = FALSE; + data = gtk_clipboard_wait_for_contents (gtk_widget_get_clipboard (widget), + copied_files_atom); + if (data == NULL) + { + return; + } + + clipboard_item_uris = nautilus_clipboard_get_uri_list_from_selection_data (data); + + for (l = (GList *) item_uris; l; l = l->next) + { + if (g_list_find_custom ((GList *) clipboard_item_uris, l->data, + (GCompareFunc) g_strcmp0)) + { + collision = TRUE; + break; + } + } + + if (collision) + { + gtk_clipboard_clear (gtk_widget_get_clipboard (widget)); + } + + if (clipboard_item_uris) + { + g_list_free_full (clipboard_item_uris, g_free); + } +} +#endif + +/* + * This asumes the implementation of GTK_TYPE_FILE_LIST is a GSList. + * As of writing this, the API docs don't provide for this assumption. + */ +static GSList * +convert_file_list_to_gdk_file_list (NautilusClipboard *clip) +{ + GSList *file_list = NULL; + for (GList *l = clip->files; l != NULL; l = l->next) + { + file_list = g_slist_prepend (file_list, + nautilus_file_get_location (l->data)); + } + return g_slist_reverse (file_list); +} + +static void +nautilus_clipboard_serialize (GdkContentSerializer *serializer) +{ + NautilusClipboard *clip; + g_autofree gchar *str = NULL; + g_autoptr (GError) error = NULL; + + clip = g_value_get_boxed (gdk_content_serializer_get_value (serializer)); + + str = nautilus_clipboard_to_string (clip); + + if (g_output_stream_printf (gdk_content_serializer_get_output_stream (serializer), + NULL, + gdk_content_serializer_get_cancellable (serializer), + &error, + "%s", str)) + { + gdk_content_serializer_return_success (serializer); + } + else + { + gdk_content_serializer_return_error (serializer, error); + } +} + +static void +nautilus_clipboard_deserialize_finish (GObject *source, + GAsyncResult *result, + gpointer user_data) +{ + GdkContentDeserializer *deserializer = user_data; + GOutputStream *output = G_OUTPUT_STREAM (source); + GError *error = NULL; + g_autofree gchar *string = NULL; + g_autoptr (NautilusClipboard) clip = NULL; + + if (g_output_stream_splice_finish (output, result, &error) < 0) + { + gdk_content_deserializer_return_error (deserializer, error); + return; + } + + /* write terminating NULL */ + if (g_output_stream_write (output, "", 1, NULL, &error) < 0 || + !g_output_stream_close (output, NULL, &error)) + { + gdk_content_deserializer_return_error (deserializer, error); + return; + } + + string = g_memory_output_stream_steal_data (G_MEMORY_OUTPUT_STREAM (output)); + + clip = nautilus_clipboard_from_string (string, &error); + + if (clip == NULL) + { + gdk_content_deserializer_return_error (deserializer, error); + return; + } + + g_value_set_boxed (gdk_content_deserializer_get_value (deserializer), clip); + gdk_content_deserializer_return_success (deserializer); +} + +static void +nautilus_clipboard_deserialize (GdkContentDeserializer *deserializer) +{ + g_autoptr (GOutputStream) output = NULL; + + output = g_memory_output_stream_new_resizable (); + g_output_stream_splice_async (output, + gdk_content_deserializer_get_input_stream (deserializer), + G_OUTPUT_STREAM_SPLICE_CLOSE_SOURCE, + gdk_content_deserializer_get_priority (deserializer), + gdk_content_deserializer_get_cancellable (deserializer), + nautilus_clipboard_deserialize_finish, + deserializer); +} + +/** + * nautilus_clipboard_peek_files: + * @clip: The current local clipboard value. + * + * Returns: (transfer none): The internal GList of GFile objects. + */ +GList * +nautilus_clipboard_peek_files (NautilusClipboard *clip) +{ + return clip->files; +} + +/** + * nautilus_clipboard_get_uri_list: + * @clip: The current local clipboard value. + * + * Returns: (transfer full): A GList of URI strings. + */ +GList * +nautilus_clipboard_get_uri_list (NautilusClipboard *clip) +{ + GList *uris = NULL; + + for (GList *l = clip->files; l != NULL; l = l->next) + { + uris = g_list_prepend (uris, nautilus_file_get_uri (l->data)); + } + + return g_list_reverse (uris); +} + +gboolean +nautilus_clipboard_is_cut (NautilusClipboard *clip) +{ + return clip->cut; +} + +NautilusClipboard * +nautilus_clipboard_copy (NautilusClipboard *clip) +{ + NautilusClipboard *new_clip = g_new0 (NautilusClipboard, 1); + + new_clip->cut = clip->cut; + new_clip->files = nautilus_file_list_copy (clip->files); + + return new_clip; +} + +void +nautilus_clipboard_free (NautilusClipboard *clip) +{ + nautilus_file_list_free (clip->files); + g_free (clip); +} + +void +nautilus_clipboard_prepare_for_files (GdkClipboard *clipboard, + GList *files, + gboolean cut) +{ + g_autoptr (NautilusClipboard) clip = NULL; + g_autoslist (GFile) file_list = NULL; + GdkContentProvider *providers[2]; + g_autoptr (GdkContentProvider) provider = NULL; + + clip = g_new (NautilusClipboard, 1); + clip->cut = cut; + clip->files = nautilus_file_list_copy (files); + + file_list = convert_file_list_to_gdk_file_list (clip); + + providers[0] = gdk_content_provider_new_typed (NAUTILUS_TYPE_CLIPBOARD, clip); + providers[1] = gdk_content_provider_new_typed (GDK_TYPE_FILE_LIST, file_list); + + provider = gdk_content_provider_new_union (providers, 2); + gdk_clipboard_set_content (clipboard, provider); +} + +void +nautilus_clipboard_register (void) +{ + /* + * While it'is not a public API and the format is not documented, some apps + * have come to use this atom/mime type to integrate with our clipboard. + */ + const gchar *nautilus_clipboard_mime_type = "x-special/gnome-copied-files"; + + gdk_content_register_serializer (NAUTILUS_TYPE_CLIPBOARD, + nautilus_clipboard_mime_type, + nautilus_clipboard_serialize, + NULL, + NULL); + gdk_content_register_deserializer (nautilus_clipboard_mime_type, + NAUTILUS_TYPE_CLIPBOARD, + nautilus_clipboard_deserialize, + NULL, + NULL); +} diff --git a/src/nautilus-clipboard.h b/src/nautilus-clipboard.h new file mode 100644 index 0000000..5ac1522 --- /dev/null +++ b/src/nautilus-clipboard.h @@ -0,0 +1,47 @@ + +/* fm-directory-view.h + * + * Copyright (C) 1999, 2000 Free Software Foundaton + * Copyright (C) 2000 Eazel, Inc. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU Library General Public License as + * published by the Free Software Foundation; either version 2 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 warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Library General Public License for more details. + * + * You should have received a copy of the GNU Library General Public + * License along with this program; if not, see . + * + * Author: Rebecca Schulman + */ + +#pragma once + +#include + +typedef struct _NautilusClipboard NautilusClipboard; +#define NAUTILUS_TYPE_CLIPBOARD (nautilus_clipboard_get_type()) +GType nautilus_clipboard_get_type (void); + +#if 0 && NAUTILUS_DND_NEEDS_GTK4_REIMPLEMENTATION +void nautilus_clipboard_clear_if_colliding_uris (GtkWidget *widget, + const GList *item_uris); +#endif +GList *nautilus_clipboard_peek_files (NautilusClipboard *clip); +GList *nautilus_clipboard_get_uri_list (NautilusClipboard *clip); +gboolean nautilus_clipboard_is_cut (NautilusClipboard *clip); + +NautilusClipboard *nautilus_clipboard_copy (NautilusClipboard *clip); +void nautilus_clipboard_free (NautilusClipboard *clip); +G_DEFINE_AUTOPTR_CLEANUP_FUNC (NautilusClipboard, nautilus_clipboard_free) + +void nautilus_clipboard_prepare_for_files (GdkClipboard *clipboard, + GList *files, + gboolean cut); + +void nautilus_clipboard_register (void); diff --git a/src/nautilus-column-chooser.c b/src/nautilus-column-chooser.c new file mode 100644 index 0000000..72e18bc --- /dev/null +++ b/src/nautilus-column-chooser.c @@ -0,0 +1,597 @@ +/* nautilus-column-chooser.h - A column chooser widget + * + * Copyright (C) 2004 Novell, Inc. + * + * The Gnome Library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Library General Public License as + * published by the Free Software Foundation; either version 2 of the + * License, or (at your option) any later version. + * + * The Gnome Library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Library General Public License for more details. + * + * You should have received a copy of the GNU Library General Public + * License along with the Gnome Library; see the column COPYING.LIB. If not, + * see . + * + * Authors: Dave Camp + */ + +#include +#include "nautilus-column-chooser.h" + +#include +#include +#include + +#include + +#include "nautilus-column-utilities.h" + +struct _NautilusColumnChooser +{ + GtkBox parent; + + GtkWidget *view; + GtkListStore *store; + + GtkWidget *move_up_button; + GtkWidget *move_down_button; + GtkWidget *use_default_button; + + NautilusFile *file; +}; + +enum +{ + COLUMN_VISIBLE, + COLUMN_LABEL, + COLUMN_NAME, + COLUMN_SENSITIVE, + NUM_COLUMNS +}; + +enum +{ + PROP_FILE = 1, + NUM_PROPERTIES +}; + +enum +{ + CHANGED, + USE_DEFAULT, + LAST_SIGNAL +}; +static guint signals[LAST_SIGNAL]; + +G_DEFINE_TYPE (NautilusColumnChooser, nautilus_column_chooser, GTK_TYPE_BOX); + +static void +nautilus_column_chooser_set_property (GObject *object, + guint param_id, + const GValue *value, + GParamSpec *pspec) +{ + NautilusColumnChooser *chooser; + + chooser = NAUTILUS_COLUMN_CHOOSER (object); + + switch (param_id) + { + case PROP_FILE: + { + chooser->file = g_value_get_object (value); + } + break; + + default: + { + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, param_id, pspec); + } + break; + } +} + +static void +update_buttons (NautilusColumnChooser *chooser) +{ + GtkTreeSelection *selection; + GtkTreeIter iter; + + selection = gtk_tree_view_get_selection (GTK_TREE_VIEW (chooser->view)); + + if (gtk_tree_selection_get_selected (selection, NULL, &iter)) + { + gboolean visible; + gboolean top; + gboolean bottom; + GtkTreePath *first; + GtkTreePath *path; + + gtk_tree_model_get (GTK_TREE_MODEL (chooser->store), + &iter, + COLUMN_VISIBLE, &visible, + -1); + + path = gtk_tree_model_get_path (GTK_TREE_MODEL (chooser->store), + &iter); + first = gtk_tree_path_new_first (); + + top = (gtk_tree_path_compare (path, first) == 0); + + gtk_tree_path_free (path); + gtk_tree_path_free (first); + + bottom = !gtk_tree_model_iter_next (GTK_TREE_MODEL (chooser->store), + &iter); + + gtk_widget_set_sensitive (chooser->move_up_button, + !top); + gtk_widget_set_sensitive (chooser->move_down_button, + !bottom); + } + else + { + gtk_widget_set_sensitive (chooser->move_up_button, + FALSE); + gtk_widget_set_sensitive (chooser->move_down_button, + FALSE); + } +} + +static void +list_changed (NautilusColumnChooser *chooser) +{ + update_buttons (chooser); + g_signal_emit (chooser, signals[CHANGED], 0); +} + +static void +toggle_path (NautilusColumnChooser *chooser, + GtkTreePath *path) +{ + GtkTreeIter iter; + gboolean visible; + g_autofree gchar *name = NULL; + + gtk_tree_model_get_iter (GTK_TREE_MODEL (chooser->store), + &iter, path); + gtk_tree_model_get (GTK_TREE_MODEL (chooser->store), &iter, + COLUMN_VISIBLE, &visible, + COLUMN_NAME, &name, -1); + + if (g_strcmp0 (name, "name") == 0) + { + /* Don't allow name column to be disabled. */ + return; + } + + gtk_list_store_set (chooser->store, + &iter, COLUMN_VISIBLE, !visible, -1); + list_changed (chooser); +} + + +static void +visible_toggled_callback (GtkCellRendererToggle *cell, + char *path_string, + gpointer user_data) +{ + GtkTreePath *path; + + path = gtk_tree_path_new_from_string (path_string); + toggle_path (NAUTILUS_COLUMN_CHOOSER (user_data), path); + gtk_tree_path_free (path); +} + +static void +view_row_activated_callback (GtkTreeView *tree_view, + GtkTreePath *path, + GtkTreeViewColumn *column, + gpointer user_data) +{ + toggle_path (NAUTILUS_COLUMN_CHOOSER (user_data), path); +} + +static void +selection_changed_callback (GtkTreeSelection *selection, + gpointer user_data) +{ + update_buttons (NAUTILUS_COLUMN_CHOOSER (user_data)); +} + +static void +row_deleted_callback (GtkTreeModel *model, + GtkTreePath *path, + gpointer user_data) +{ + list_changed (NAUTILUS_COLUMN_CHOOSER (user_data)); +} + +static void +move_up_clicked_callback (GtkWidget *button, + gpointer user_data) +{ + NautilusColumnChooser *chooser; + GtkTreeIter iter; + GtkTreeSelection *selection; + + chooser = NAUTILUS_COLUMN_CHOOSER (user_data); + + selection = gtk_tree_view_get_selection (GTK_TREE_VIEW (chooser->view)); + + if (gtk_tree_selection_get_selected (selection, NULL, &iter)) + { + GtkTreePath *path; + GtkTreeIter prev; + + path = gtk_tree_model_get_path (GTK_TREE_MODEL (chooser->store), &iter); + gtk_tree_path_prev (path); + if (gtk_tree_model_get_iter (GTK_TREE_MODEL (chooser->store), &prev, path)) + { + gtk_list_store_move_before (chooser->store, + &iter, + &prev); + } + gtk_tree_path_free (path); + } + + list_changed (chooser); +} + +static void +move_down_clicked_callback (GtkWidget *button, + gpointer user_data) +{ + NautilusColumnChooser *chooser; + GtkTreeIter iter; + GtkTreeSelection *selection; + + chooser = NAUTILUS_COLUMN_CHOOSER (user_data); + + selection = gtk_tree_view_get_selection (GTK_TREE_VIEW (chooser->view)); + + if (gtk_tree_selection_get_selected (selection, NULL, &iter)) + { + GtkTreeIter next; + + next = iter; + + if (gtk_tree_model_iter_next (GTK_TREE_MODEL (chooser->store), &next)) + { + gtk_list_store_move_after (chooser->store, + &iter, + &next); + } + } + + list_changed (chooser); +} + +static void +use_default_clicked_callback (GtkWidget *button, + gpointer user_data) +{ + g_signal_emit (NAUTILUS_COLUMN_CHOOSER (user_data), + signals[USE_DEFAULT], 0); +} + +static void +populate_tree (NautilusColumnChooser *chooser) +{ + GList *columns; + GList *l; + + columns = nautilus_get_columns_for_file (chooser->file); + + for (l = columns; l != NULL; l = l->next) + { + GtkTreeIter iter; + NautilusColumn *column; + char *name; + char *label; + gboolean visible = FALSE; + gboolean sensitive = TRUE; + + column = NAUTILUS_COLUMN (l->data); + + g_object_get (G_OBJECT (column), + "name", &name, "label", &label, + NULL); + + if (strcmp (name, "name") == 0) + { + visible = TRUE; + sensitive = FALSE; + } + if (strcmp (name, "starred") == 0) + { + g_free (name); + g_free (label); + continue; + } + + gtk_list_store_append (chooser->store, &iter); + gtk_list_store_set (chooser->store, &iter, + COLUMN_VISIBLE, visible, + COLUMN_LABEL, label, + COLUMN_NAME, name, + COLUMN_SENSITIVE, sensitive, + -1); + + g_free (name); + g_free (label); + } + + nautilus_column_list_free (columns); +} + +static void +nautilus_column_chooser_constructed (GObject *object) +{ + NautilusColumnChooser *chooser; + + chooser = NAUTILUS_COLUMN_CHOOSER (object); + + populate_tree (chooser); + + g_signal_connect (chooser->store, "row-deleted", + G_CALLBACK (row_deleted_callback), chooser); +} + +static void +set_visible_columns (NautilusColumnChooser *chooser, + char **visible_columns) +{ + GHashTable *visible_columns_hash; + GtkTreeIter iter; + int i; + + visible_columns_hash = g_hash_table_new (g_str_hash, g_str_equal); + /* always show the name column */ + g_hash_table_insert (visible_columns_hash, "name", "name"); + for (i = 0; visible_columns[i] != NULL; ++i) + { + g_hash_table_insert (visible_columns_hash, + visible_columns[i], + visible_columns[i]); + } + + if (gtk_tree_model_get_iter_first (GTK_TREE_MODEL (chooser->store), + &iter)) + { + do + { + char *name; + gboolean visible; + + gtk_tree_model_get (GTK_TREE_MODEL (chooser->store), + &iter, + COLUMN_NAME, &name, + -1); + + visible = (g_hash_table_lookup (visible_columns_hash, name) != NULL); + + gtk_list_store_set (chooser->store, + &iter, + COLUMN_VISIBLE, visible, + -1); + g_free (name); + } + while (gtk_tree_model_iter_next (GTK_TREE_MODEL (chooser->store), &iter)); + } + + g_hash_table_destroy (visible_columns_hash); +} + +static char ** +get_column_names (NautilusColumnChooser *chooser, + gboolean only_visible) +{ + GPtrArray *ret; + GtkTreeIter iter; + + ret = g_ptr_array_new (); + if (gtk_tree_model_get_iter_first (GTK_TREE_MODEL (chooser->store), + &iter)) + { + do + { + char *name; + gboolean visible; + gtk_tree_model_get (GTK_TREE_MODEL (chooser->store), + &iter, + COLUMN_VISIBLE, &visible, + COLUMN_NAME, &name, + -1); + if (!only_visible || visible) + { + /* give ownership to the array */ + g_ptr_array_add (ret, name); + } + else + { + g_free (name); + } + } + while (gtk_tree_model_iter_next (GTK_TREE_MODEL (chooser->store), &iter)); + } + g_ptr_array_add (ret, NULL); + + return (char **) g_ptr_array_free (ret, FALSE); +} + +static gboolean +get_column_iter (NautilusColumnChooser *chooser, + NautilusColumn *column, + GtkTreeIter *iter) +{ + char *column_name; + + g_object_get (NAUTILUS_COLUMN (column), "name", &column_name, NULL); + + if (gtk_tree_model_get_iter_first (GTK_TREE_MODEL (chooser->store), + iter)) + { + do + { + char *name; + + gtk_tree_model_get (GTK_TREE_MODEL (chooser->store), + iter, + COLUMN_NAME, &name, + -1); + if (!strcmp (name, column_name)) + { + g_free (column_name); + g_free (name); + return TRUE; + } + + g_free (name); + } + while (gtk_tree_model_iter_next (GTK_TREE_MODEL (chooser->store), iter)); + } + g_free (column_name); + return FALSE; +} + +static void +set_column_order (NautilusColumnChooser *chooser, + char **column_order) +{ + GList *columns; + GList *l; + GtkTreePath *path; + + columns = nautilus_get_columns_for_file (chooser->file); + columns = nautilus_sort_columns (columns, column_order); + + g_signal_handlers_block_by_func (chooser->store, + G_CALLBACK (row_deleted_callback), + chooser); + + path = gtk_tree_path_new_first (); + for (l = columns; l != NULL; l = l->next) + { + GtkTreeIter iter; + + if (get_column_iter (chooser, NAUTILUS_COLUMN (l->data), &iter)) + { + GtkTreeIter before; + if (path) + { + gtk_tree_model_get_iter (GTK_TREE_MODEL (chooser->store), + &before, path); + gtk_list_store_move_after (chooser->store, + &iter, &before); + gtk_tree_path_next (path); + } + else + { + gtk_list_store_move_after (chooser->store, + &iter, NULL); + } + } + } + gtk_tree_path_free (path); + g_signal_handlers_unblock_by_func (chooser->store, + G_CALLBACK (row_deleted_callback), + chooser); + + nautilus_column_list_free (columns); +} + +void +nautilus_column_chooser_set_settings (NautilusColumnChooser *chooser, + char **visible_columns, + char **column_order) +{ + g_return_if_fail (NAUTILUS_IS_COLUMN_CHOOSER (chooser)); + g_return_if_fail (visible_columns != NULL); + g_return_if_fail (column_order != NULL); + + set_visible_columns (chooser, visible_columns); + set_column_order (chooser, column_order); + + list_changed (chooser); +} + +void +nautilus_column_chooser_get_settings (NautilusColumnChooser *chooser, + char ***visible_columns, + char ***column_order) +{ + g_return_if_fail (NAUTILUS_IS_COLUMN_CHOOSER (chooser)); + g_return_if_fail (visible_columns != NULL); + g_return_if_fail (column_order != NULL); + + *visible_columns = get_column_names (chooser, TRUE); + *column_order = get_column_names (chooser, FALSE); +} + +static void +nautilus_column_chooser_class_init (NautilusColumnChooserClass *chooser_class) +{ + GtkWidgetClass *widget_class; + GObjectClass *oclass; + + widget_class = GTK_WIDGET_CLASS (chooser_class); + oclass = G_OBJECT_CLASS (chooser_class); + + oclass->set_property = nautilus_column_chooser_set_property; + oclass->constructed = nautilus_column_chooser_constructed; + + gtk_widget_class_set_template_from_resource (widget_class, "/org/gnome/nautilus/ui/nautilus-column-chooser.ui"); + gtk_widget_class_bind_template_child (widget_class, NautilusColumnChooser, view); + gtk_widget_class_bind_template_child (widget_class, NautilusColumnChooser, store); + gtk_widget_class_bind_template_child (widget_class, NautilusColumnChooser, move_up_button); + gtk_widget_class_bind_template_child (widget_class, NautilusColumnChooser, move_down_button); + gtk_widget_class_bind_template_child (widget_class, NautilusColumnChooser, use_default_button); + gtk_widget_class_bind_template_callback (widget_class, view_row_activated_callback); + gtk_widget_class_bind_template_callback (widget_class, selection_changed_callback); + gtk_widget_class_bind_template_callback (widget_class, visible_toggled_callback); + gtk_widget_class_bind_template_callback (widget_class, move_up_clicked_callback); + gtk_widget_class_bind_template_callback (widget_class, move_down_clicked_callback); + gtk_widget_class_bind_template_callback (widget_class, use_default_clicked_callback); + + signals[CHANGED] = g_signal_new + ("changed", + G_TYPE_FROM_CLASS (chooser_class), + G_SIGNAL_RUN_LAST, + 0, NULL, NULL, + g_cclosure_marshal_VOID__VOID, + G_TYPE_NONE, 0); + + signals[USE_DEFAULT] = g_signal_new + ("use-default", + G_TYPE_FROM_CLASS (chooser_class), + G_SIGNAL_RUN_LAST, + 0, NULL, NULL, + g_cclosure_marshal_VOID__VOID, + G_TYPE_NONE, 0); + + g_object_class_install_property (oclass, + PROP_FILE, + g_param_spec_object ("file", + "File", + "The file this column chooser is for", + NAUTILUS_TYPE_FILE, + G_PARAM_CONSTRUCT_ONLY | + G_PARAM_WRITABLE)); +} + +static void +nautilus_column_chooser_init (NautilusColumnChooser *chooser) +{ + gtk_widget_init_template (GTK_WIDGET (chooser)); +} + +GtkWidget * +nautilus_column_chooser_new (NautilusFile *file) +{ + return g_object_new (NAUTILUS_TYPE_COLUMN_CHOOSER, "file", file, NULL); +} diff --git a/src/nautilus-column-chooser.h b/src/nautilus-column-chooser.h new file mode 100644 index 0000000..c52efe4 --- /dev/null +++ b/src/nautilus-column-chooser.h @@ -0,0 +1,38 @@ + +/* nautilus-column-choose.h - A column chooser widget + + Copyright (C) 2004 Novell, Inc. + + The Gnome Library is free software; you can redistribute it and/or + modify it under the terms of the GNU Library General Public License as + published by the Free Software Foundation; either version 2 of the + License, or (at your option) any later version. + + The Gnome Library is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Library General Public License for more details. + + You should have received a copy of the GNU Library General Public + License along with the Gnome Library; see the column COPYING.LIB. If not, + see . + + Authors: Dave Camp +*/ + +#pragma once + +#include +#include "nautilus-file.h" + +#define NAUTILUS_TYPE_COLUMN_CHOOSER nautilus_column_chooser_get_type() + +G_DECLARE_FINAL_TYPE (NautilusColumnChooser, nautilus_column_chooser, NAUTILUS, COLUMN_CHOOSER, GtkBox); + +GtkWidget *nautilus_column_chooser_new (NautilusFile *file); +void nautilus_column_chooser_set_settings (NautilusColumnChooser *chooser, + char **visible_columns, + char **column_order); +void nautilus_column_chooser_get_settings (NautilusColumnChooser *chooser, + char ***visible_columns, + char ***column_order); diff --git a/src/nautilus-column-utilities.c b/src/nautilus-column-utilities.c new file mode 100644 index 0000000..1132705 --- /dev/null +++ b/src/nautilus-column-utilities.c @@ -0,0 +1,416 @@ +/* nautilus-column-utilities.h - Utilities related to column specifications + * + * Copyright (C) 2004 Novell, Inc. + * + * The Gnome Library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Library General Public License as + * published by the Free Software Foundation; either version 2 of the + * License, or (at your option) any later version. + * + * The Gnome Library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Library General Public License for more details. + * + * You should have received a copy of the GNU Library General Public + * License along with the Gnome Library; see the column COPYING.LIB. If not, + * see . + * + * Authors: Dave Camp + */ + +#include +#include "nautilus-column-utilities.h" + +#include +#include +#include +#include +#include "nautilus-module.h" + +static const char *default_column_order[] = +{ + "name", + "size", + "type", + "owner", + "group", + "permissions", + "detailed_type", + "where", + "date_modified_with_time", + "date_modified", + "date_accessed", + "date_created", + "recency", + "starred", + NULL +}; + +static GList * +get_builtin_columns (void) +{ + GList *columns; + + columns = g_list_append (NULL, + g_object_new (NAUTILUS_TYPE_COLUMN, + "name", "name", + "attribute", "name", + "label", _("Name"), + "description", _("The name and icon of the file."), + NULL)); + columns = g_list_append (columns, + g_object_new (NAUTILUS_TYPE_COLUMN, + "name", "size", + "attribute", "size", + "label", _("Size"), + "description", _("The size of the file."), + NULL)); + columns = g_list_append (columns, + g_object_new (NAUTILUS_TYPE_COLUMN, + "name", "type", + "attribute", "type", + "label", _("Type"), + "description", _("The type of the file."), + NULL)); + columns = g_list_append (columns, + g_object_new (NAUTILUS_TYPE_COLUMN, + "name", "date_modified", + "attribute", "date_modified", + "label", _("Modified"), + "description", _("The date the file was modified."), + "default-sort-order", GTK_SORT_DESCENDING, + "xalign", 1.0, + NULL)); + columns = g_list_append (columns, + g_object_new (NAUTILUS_TYPE_COLUMN, + "name", "detailed_type", + "attribute", "detailed_type", + "label", _("Detailed Type"), + "description", _("The detailed type of the file."), + NULL)); + columns = g_list_append (columns, + g_object_new (NAUTILUS_TYPE_COLUMN, + "name", "date_accessed", + "attribute", "date_accessed", + "label", _("Accessed"), + "description", _("The date the file was accessed."), + "default-sort-order", GTK_SORT_DESCENDING, + "xalign", 1.0, + NULL)); + columns = g_list_append (columns, + g_object_new (NAUTILUS_TYPE_COLUMN, + "name", "date_created", + "attribute", "date_created", + "label", _("Created"), + "description", _("The date the file was created."), + "default-sort-order", GTK_SORT_DESCENDING, + "xalign", 1.0, + NULL)); + + columns = g_list_append (columns, + g_object_new (NAUTILUS_TYPE_COLUMN, + "name", "owner", + "attribute", "owner", + "label", _("Owner"), + "description", _("The owner of the file."), + NULL)); + + columns = g_list_append (columns, + g_object_new (NAUTILUS_TYPE_COLUMN, + "name", "group", + "attribute", "group", + "label", _("Group"), + "description", _("The group of the file."), + NULL)); + + columns = g_list_append (columns, + g_object_new (NAUTILUS_TYPE_COLUMN, + "name", "permissions", + "attribute", "permissions", + "label", _("Permissions"), + "description", _("The permissions of the file."), + NULL)); + + columns = g_list_append (columns, + g_object_new (NAUTILUS_TYPE_COLUMN, + "name", "where", + "attribute", "where", + "label", _("Location"), + "description", _("The location of the file."), + NULL)); + + columns = g_list_append (columns, + g_object_new (NAUTILUS_TYPE_COLUMN, + "name", "date_modified_with_time", + "attribute", "date_modified_with_time", + "label", _("Modified — Time"), + "description", _("The date the file was modified."), + "xalign", 1.0, + NULL)); + + columns = g_list_append (columns, + g_object_new (NAUTILUS_TYPE_COLUMN, + "name", "recency", + "attribute", "recency", + "label", _("Recency"), + "description", _("The date the file was accessed by the user."), + "default-sort-order", GTK_SORT_DESCENDING, + "xalign", 1.0, + NULL)); + + columns = g_list_append (columns, + g_object_new (NAUTILUS_TYPE_COLUMN, + "name", "starred", + "attribute", "starred", + "label", _("Star"), + "description", _("Shows if file is starred."), + "default-sort-order", GTK_SORT_DESCENDING, + "xalign", 0.5, + NULL)); + + return columns; +} + +static GList * +get_extension_columns (void) +{ + GList *columns; + GList *providers; + GList *l; + + providers = nautilus_module_get_extensions_for_type (NAUTILUS_TYPE_COLUMN_PROVIDER); + + columns = NULL; + + for (l = providers; l != NULL; l = l->next) + { + NautilusColumnProvider *provider; + GList *provider_columns; + + provider = NAUTILUS_COLUMN_PROVIDER (l->data); + provider_columns = nautilus_column_provider_get_columns (provider); + columns = g_list_concat (columns, provider_columns); + } + + nautilus_module_extension_list_free (providers); + + return columns; +} + +static GList * +get_trash_columns (void) +{ + static GList *columns = NULL; + + if (columns == NULL) + { + columns = g_list_append (columns, + g_object_new (NAUTILUS_TYPE_COLUMN, + "name", "trashed_on", + "attribute", "trashed_on", + "label", _("Trashed On"), + "description", _("Date when file was moved to the Trash"), + "xalign", 1.0, + NULL)); + columns = g_list_append (columns, + g_object_new (NAUTILUS_TYPE_COLUMN, + "name", "trash_orig_path", + "attribute", "trash_orig_path", + "label", _("Original Location"), + "description", _("Original location of file before moved to the Trash"), + NULL)); + } + + return nautilus_column_list_copy (columns); +} + +static GList * +get_search_columns (void) +{ + static GList *columns = NULL; + + if (columns == NULL) + { + columns = g_list_append (columns, + g_object_new (NAUTILUS_TYPE_COLUMN, + "name", "search_relevance", + "attribute", "search_relevance", + "label", _("Relevance"), + "description", _("Relevance rank for search"), + NULL)); + } + + return nautilus_column_list_copy (columns); +} + +GList * +nautilus_get_common_columns (void) +{ + static GList *columns = NULL; + + if (!columns) + { + columns = g_list_concat (get_builtin_columns (), + get_extension_columns ()); + } + + return nautilus_column_list_copy (columns); +} + +GList * +nautilus_get_all_columns (void) +{ + GList *columns = NULL; + + columns = g_list_concat (nautilus_get_common_columns (), + get_trash_columns ()); + columns = g_list_concat (columns, + get_search_columns ()); + + return columns; +} + +GList * +nautilus_get_columns_for_file (NautilusFile *file) +{ + GList *columns; + + columns = nautilus_get_common_columns (); + + if (file != NULL && nautilus_file_is_in_trash (file)) + { + columns = g_list_concat (columns, + get_trash_columns ()); + } + + return columns; +} + +GList * +nautilus_column_list_copy (GList *columns) +{ + GList *ret; + GList *l; + + ret = g_list_copy (columns); + + for (l = ret; l != NULL; l = l->next) + { + g_object_ref (l->data); + } + + return ret; +} + +void +nautilus_column_list_free (GList *columns) +{ + GList *l; + + for (l = columns; l != NULL; l = l->next) + { + g_object_unref (l->data); + } + + g_list_free (columns); +} + +static int +strv_index (char **strv, + const char *str) +{ + int i; + + for (i = 0; strv[i] != NULL; ++i) + { + if (strcmp (strv[i], str) == 0) + { + return i; + } + } + + return -1; +} + +static int +column_compare (NautilusColumn *a, + NautilusColumn *b, + char **column_order) +{ + int index_a; + int index_b; + char *name_a; + char *name_b; + int ret; + + g_object_get (G_OBJECT (a), "name", &name_a, NULL); + index_a = strv_index (column_order, name_a); + + g_object_get (G_OBJECT (b), "name", &name_b, NULL); + index_b = strv_index (column_order, name_b); + + if (index_a == index_b) + { + int pos_a; + int pos_b; + + pos_a = strv_index ((char **) default_column_order, name_a); + pos_b = strv_index ((char **) default_column_order, name_b); + + if (pos_a == pos_b) + { + char *label_a; + char *label_b; + + g_object_get (G_OBJECT (a), "label", &label_a, NULL); + g_object_get (G_OBJECT (b), "label", &label_b, NULL); + ret = strcmp (label_a, label_b); + g_free (label_a); + g_free (label_b); + } + else if (pos_a == -1) + { + ret = 1; + } + else if (pos_b == -1) + { + ret = -1; + } + else + { + ret = index_a - index_b; + } + } + else if (index_a == -1) + { + ret = 1; + } + else if (index_b == -1) + { + ret = -1; + } + else + { + ret = index_a - index_b; + } + + g_free (name_a); + g_free (name_b); + + return ret; +} + +GList * +nautilus_sort_columns (GList *columns, + char **column_order) +{ + if (column_order == NULL) + { + return columns; + } + + return g_list_sort_with_data (columns, + (GCompareDataFunc) column_compare, + column_order); +} diff --git a/src/nautilus-column-utilities.h b/src/nautilus-column-utilities.h new file mode 100644 index 0000000..56a363f --- /dev/null +++ b/src/nautilus-column-utilities.h @@ -0,0 +1,34 @@ + +/* nautilus-column-utilities.h - Utilities related to column specifications + + Copyright (C) 2004 Novell, Inc. + + The Gnome Library is free software; you can redistribute it and/or + modify it under the terms of the GNU Library General Public License as + published by the Free Software Foundation; either version 2 of the + License, or (at your option) any later version. + + The Gnome Library is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Library General Public License for more details. + + You should have received a copy of the GNU Library General Public + License along with the Gnome Library; see the column COPYING.LIB. If not, + see . + + Authors: Dave Camp +*/ + +#pragma once + +#include "nautilus-file.h" + +GList *nautilus_get_all_columns (void); +GList *nautilus_get_common_columns (void); +GList *nautilus_get_columns_for_file (NautilusFile *file); +GList *nautilus_column_list_copy (GList *columns); +void nautilus_column_list_free (GList *columns); + +GList *nautilus_sort_columns (GList *columns, + char **column_order); diff --git a/src/nautilus-compress-dialog-controller.c b/src/nautilus-compress-dialog-controller.c new file mode 100644 index 0000000..9937beb --- /dev/null +++ b/src/nautilus-compress-dialog-controller.c @@ -0,0 +1,612 @@ +/* nautilus-compress-dialog-controller.h + * + * Copyright (C) 2016 the Nautilus developers + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License as + * published by the Free Software Foundation; either version 2 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 warranty of + * MERCHANTABILITY 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 . + * + */ + +#include +#include +#include + +#include + +#include "nautilus-compress-dialog-controller.h" + +#include "nautilus-global-preferences.h" + +struct _NautilusCompressDialogController +{ + NautilusFileNameWidgetController parent_instance; + + GtkWidget *compress_dialog; + GtkWidget *activate_button; + GtkWidget *error_label; + GtkWidget *name_entry; + GtkWidget *extension_dropdown; + GtkSizeGroup *extension_sizegroup; + GtkWidget *passphrase_label; + GtkWidget *passphrase_entry; + + const char *extension; + gchar *passphrase; + + gulong response_handler_id; +}; + +G_DEFINE_TYPE (NautilusCompressDialogController, nautilus_compress_dialog_controller, NAUTILUS_TYPE_FILE_NAME_WIDGET_CONTROLLER); + +#define NAUTILUS_TYPE_COMPRESS_ITEM (nautilus_compress_item_get_type ()) +G_DECLARE_FINAL_TYPE (NautilusCompressItem, nautilus_compress_item, NAUTILUS, COMPRESS_ITEM, GObject) + +struct _NautilusCompressItem +{ + GObject parent_instance; + NautilusCompressionFormat format; + char *extension; + char *description; +}; + +G_DEFINE_TYPE (NautilusCompressItem, nautilus_compress_item, G_TYPE_OBJECT); + +static void +nautilus_compress_item_init (NautilusCompressItem *item) +{ +} + +static void +nautilus_compress_item_finalize (GObject *object) +{ + NautilusCompressItem *item = NAUTILUS_COMPRESS_ITEM (object); + + g_free (item->extension); + g_free (item->description); + + G_OBJECT_CLASS (nautilus_compress_item_parent_class)->finalize (object); +} + +static void +nautilus_compress_item_class_init (NautilusCompressItemClass *class) +{ + GObjectClass *object_class = G_OBJECT_CLASS (class); + + object_class->finalize = nautilus_compress_item_finalize; +} + +static NautilusCompressItem * +nautilus_compress_item_new (NautilusCompressionFormat format, + const char *extension, + const char *description) +{ + NautilusCompressItem *item = g_object_new (NAUTILUS_TYPE_COMPRESS_ITEM, NULL); + + item->format = format; + item->extension = g_strdup (extension); + item->description = g_strdup (description); + + return item; +} + +static gboolean +nautilus_compress_dialog_controller_name_is_valid (NautilusFileNameWidgetController *self, + gchar *name, + gchar **error_message) +{ + gboolean is_valid; + + is_valid = TRUE; + if (strlen (name) == 0) + { + is_valid = FALSE; + } + else if (strstr (name, "/") != NULL) + { + is_valid = FALSE; + *error_message = _("Archive names cannot contain “/”."); + } + else if (strcmp (name, ".") == 0) + { + is_valid = FALSE; + *error_message = _("An archive cannot be called “.”."); + } + else if (strcmp (name, "..") == 0) + { + is_valid = FALSE; + *error_message = _("An archive cannot be called “..”."); + } + else if (nautilus_file_name_widget_controller_is_name_too_long (self, name)) + { + is_valid = FALSE; + *error_message = _("Archive name is too long."); + } + + if (is_valid && g_str_has_prefix (name, ".")) + { + /* We must warn about the side effect */ + *error_message = _("Archives with “.” at the beginning of their name are hidden."); + } + + return is_valid; +} + +static gchar * +nautilus_compress_dialog_controller_get_new_name (NautilusFileNameWidgetController *controller) +{ + NautilusCompressDialogController *self; + g_autofree gchar *basename = NULL; + gchar *error_message = NULL; + gboolean valid_name; + + self = NAUTILUS_COMPRESS_DIALOG_CONTROLLER (controller); + + basename = NAUTILUS_FILE_NAME_WIDGET_CONTROLLER_CLASS (nautilus_compress_dialog_controller_parent_class)->get_new_name (controller); + /* Do not check or add the extension if the name is invalid */ + valid_name = nautilus_compress_dialog_controller_name_is_valid (controller, + basename, + &error_message); + + if (!valid_name) + { + return g_strdup (basename); + } + + if (g_str_has_suffix (basename, self->extension)) + { + return g_strdup (basename); + } + + return g_strconcat (basename, self->extension, NULL); +} + +static void +compress_dialog_controller_on_response (GtkDialog *dialog, + gint response_id, + gpointer user_data) +{ + NautilusCompressDialogController *controller; + + controller = NAUTILUS_COMPRESS_DIALOG_CONTROLLER (user_data); + + if (response_id != GTK_RESPONSE_OK) + { + g_signal_emit_by_name (controller, "cancelled"); + } +} + +static void +update_selected_format (NautilusCompressDialogController *self) +{ + gboolean show_passphrase = FALSE; + guint selected; + GListModel *model; + NautilusCompressItem *item; + + selected = gtk_drop_down_get_selected (GTK_DROP_DOWN (self->extension_dropdown)); + if (selected == GTK_INVALID_LIST_POSITION) + { + return; + } + + model = gtk_drop_down_get_model (GTK_DROP_DOWN (self->extension_dropdown)); + item = g_list_model_get_item (model, selected); + if (item == NULL) + { + return; + } + + if (item->format == NAUTILUS_COMPRESSION_ENCRYPTED_ZIP) + { + show_passphrase = TRUE; + } + + self->extension = item->extension; + + gtk_widget_set_visible (self->passphrase_label, show_passphrase); + gtk_widget_set_visible (self->passphrase_entry, show_passphrase); + if (!show_passphrase) + { + gtk_editable_set_text (GTK_EDITABLE (self->passphrase_entry), ""); + gtk_entry_set_visibility (GTK_ENTRY (self->passphrase_entry), FALSE); + gtk_entry_set_icon_from_icon_name (GTK_ENTRY (self->passphrase_entry), + GTK_ENTRY_ICON_SECONDARY, + "view-conceal"); + } + + g_settings_set_enum (nautilus_compression_preferences, + NAUTILUS_PREFERENCES_DEFAULT_COMPRESSION_FORMAT, + item->format); + + /* Since the extension changes when the button is toggled, force a + * verification of the new file name by simulating an entry change + */ + gtk_widget_set_sensitive (self->activate_button, FALSE); + g_signal_emit_by_name (self->name_entry, "changed"); +} + +static void +extension_dropdown_setup_item (GtkSignalListItemFactory *factory, + GtkListItem *item, + gpointer user_data) +{ + GtkWidget *title; + + title = gtk_label_new (""); + gtk_label_set_xalign (GTK_LABEL (title), 0.0); + + g_object_set_data (G_OBJECT (item), "title", title); + gtk_list_item_set_child (item, title); +} + + +static void +extension_dropdown_setup_item_full (GtkSignalListItemFactory *factory, + GtkListItem *item, + gpointer user_data) +{ + GtkWidget *hbox, *vbox, *title, *subtitle, *checkmark; + + title = gtk_label_new (""); + gtk_label_set_xalign (GTK_LABEL (title), 0.0); + gtk_widget_set_halign (title, GTK_ALIGN_START); + + subtitle = gtk_label_new (""); + gtk_label_set_xalign (GTK_LABEL (subtitle), 0.0); + gtk_widget_add_css_class (subtitle, "dim-label"); + gtk_widget_add_css_class (subtitle, "caption"); + + checkmark = gtk_image_new_from_icon_name ("object-select-symbolic"); + + hbox = gtk_box_new (GTK_ORIENTATION_HORIZONTAL, 6); + vbox = gtk_box_new (GTK_ORIENTATION_VERTICAL, 3); + gtk_widget_set_hexpand (vbox, TRUE); + + gtk_box_append (GTK_BOX (hbox), vbox); + gtk_box_append (GTK_BOX (vbox), title); + gtk_box_append (GTK_BOX (vbox), subtitle); + gtk_box_append (GTK_BOX (hbox), checkmark); + + g_object_set_data (G_OBJECT (item), "title", title); + g_object_set_data (G_OBJECT (item), "subtitle", subtitle); + g_object_set_data (G_OBJECT (item), "checkmark", checkmark); + + gtk_list_item_set_child (item, hbox); +} + +static void +extension_dropdown_on_selected_item_notify (GtkDropDown *dropdown, + GParamSpec *pspec, + GtkListItem *item) +{ + GtkWidget *checkmark; + + checkmark = g_object_get_data (G_OBJECT (item), "checkmark"); + + if (gtk_drop_down_get_selected_item (dropdown) == gtk_list_item_get_item (item)) + { + gtk_widget_set_opacity (checkmark, 1.0); + } + else + { + gtk_widget_set_opacity (checkmark, 0.0); + } +} + +static void +extension_dropdown_bind (GtkSignalListItemFactory *factory, + GtkListItem *list_item, + gpointer user_data) +{ + NautilusCompressDialogController *self; + GtkWidget *title, *subtitle, *checkmark; + NautilusCompressItem *item; + + self = NAUTILUS_COMPRESS_DIALOG_CONTROLLER (user_data); + item = gtk_list_item_get_item (list_item); + + title = g_object_get_data (G_OBJECT (list_item), "title"); + subtitle = g_object_get_data (G_OBJECT (list_item), "subtitle"); + checkmark = g_object_get_data (G_OBJECT (list_item), "checkmark"); + + gtk_label_set_label (GTK_LABEL (title), item->extension); + gtk_size_group_add_widget (self->extension_sizegroup, title); + + if (item->format == NAUTILUS_COMPRESSION_ENCRYPTED_ZIP) + { + gtk_widget_add_css_class (title, "encrypted_zip"); + } + + if (subtitle) + { + gtk_label_set_label (GTK_LABEL (subtitle), item->description); + } + + if (checkmark) + { + g_signal_connect (self->extension_dropdown, + "notify::selected-item", + G_CALLBACK (extension_dropdown_on_selected_item_notify), + list_item); + extension_dropdown_on_selected_item_notify (GTK_DROP_DOWN (self->extension_dropdown), + NULL, + list_item); + } +} + +static void +extension_dropdown_unbind (GtkSignalListItemFactory *factory, + GtkListItem *item, + gpointer user_data) +{ + NautilusCompressDialogController *self; + GtkWidget *title; + + self = NAUTILUS_COMPRESS_DIALOG_CONTROLLER (user_data); + g_signal_handlers_disconnect_by_func (self->extension_dropdown, + extension_dropdown_on_selected_item_notify, + item); + + title = g_object_get_data (G_OBJECT (item), "title"); + if (title) + { + gtk_widget_remove_css_class (title, "encrypted_zip"); + } +} + +static void +passphrase_entry_on_changed (GtkEditable *editable, + gpointer user_data) +{ + NautilusCompressDialogController *self; + const gchar *error_message; + + self = NAUTILUS_COMPRESS_DIALOG_CONTROLLER (user_data); + + g_free (self->passphrase); + self->passphrase = g_strdup (gtk_editable_get_text (GTK_EDITABLE (self->passphrase_entry))); + + /* Simulate a change of the name_entry to ensure the correct sensitivity of + * the activate_button, but only if the name_entry is valid in order to + * avoid changes of the error_revealer. + */ + error_message = gtk_label_get_text (GTK_LABEL (self->error_label)); + if (error_message[0] == '\0') + { + gtk_widget_set_sensitive (self->activate_button, FALSE); + g_signal_emit_by_name (self->name_entry, "changed"); + } +} + +static void +passphrase_entry_on_icon_press (GtkEntry *entry, + GtkEntryIconPosition icon_pos, + gpointer user_data) +{ + NautilusCompressDialogController *self; + gboolean visibility; + + self = NAUTILUS_COMPRESS_DIALOG_CONTROLLER (user_data); + visibility = gtk_entry_get_visibility (GTK_ENTRY (self->passphrase_entry)); + + gtk_entry_set_icon_from_icon_name (GTK_ENTRY (self->passphrase_entry), + GTK_ENTRY_ICON_SECONDARY, + visibility ? "view-conceal" : "view-reveal"); + gtk_entry_set_visibility (GTK_ENTRY (self->passphrase_entry), !visibility); +} + +static void +activate_button_on_sensitive_notify (GObject *gobject, + GParamSpec *pspec, + gpointer user_data) +{ + NautilusCompressDialogController *self; + NautilusCompressionFormat format; + + self = NAUTILUS_COMPRESS_DIALOG_CONTROLLER (user_data); + format = g_settings_get_enum (nautilus_compression_preferences, + NAUTILUS_PREFERENCES_DEFAULT_COMPRESSION_FORMAT); + if (format == NAUTILUS_COMPRESSION_ENCRYPTED_ZIP && + (self->passphrase == NULL || self->passphrase[0] == '\0')) + { + /* Reset sensitivity of the activate_button if password is not set. */ + gtk_widget_set_sensitive (self->activate_button, FALSE); + } +} + +static void +extension_dropdown_setup (NautilusCompressDialogController *self) +{ + GtkListItemFactory *factory, *list_factory; + GListStore *store; + NautilusCompressItem *item; + NautilusCompressionFormat format; + gint i; + + store = g_list_store_new (NAUTILUS_TYPE_COMPRESS_ITEM); + item = nautilus_compress_item_new (NAUTILUS_COMPRESSION_ZIP, + ".zip", + _("Compatible with all operating systems.")); + g_list_store_append (store, item); + g_object_unref (item); + item = nautilus_compress_item_new (NAUTILUS_COMPRESSION_ENCRYPTED_ZIP, + ".zip", + _("Password protected .zip, must be installed on Windows and Mac.")); + g_list_store_append (store, item); + g_object_unref (item); + item = nautilus_compress_item_new (NAUTILUS_COMPRESSION_TAR_XZ, + ".tar.xz", + _("Smaller archives but Linux and Mac only.")); + g_list_store_append (store, item); + g_object_unref (item); + item = nautilus_compress_item_new (NAUTILUS_COMPRESSION_7ZIP, + ".7z", + _("Smaller archives but must be installed on Windows and Mac.")); + g_list_store_append (store, item); + g_object_unref (item); + + factory = gtk_signal_list_item_factory_new (); + g_signal_connect_object (factory, "setup", + G_CALLBACK (extension_dropdown_setup_item), self, 0); + g_signal_connect_object (factory, "bind", + G_CALLBACK (extension_dropdown_bind), self, 0); + g_signal_connect_object (factory, "unbind", + G_CALLBACK (extension_dropdown_unbind), self, 0); + + list_factory = gtk_signal_list_item_factory_new (); + g_signal_connect_object (list_factory, "setup", + G_CALLBACK (extension_dropdown_setup_item_full), self, 0); + g_signal_connect_object (list_factory, "bind", + G_CALLBACK (extension_dropdown_bind), self, 0); + g_signal_connect_object (list_factory, "unbind", + G_CALLBACK (extension_dropdown_unbind), self, 0); + + gtk_drop_down_set_factory (GTK_DROP_DOWN (self->extension_dropdown), factory); + gtk_drop_down_set_list_factory (GTK_DROP_DOWN (self->extension_dropdown), list_factory); + gtk_drop_down_set_model (GTK_DROP_DOWN (self->extension_dropdown), G_LIST_MODEL (store)); + + format = g_settings_get_enum (nautilus_compression_preferences, + NAUTILUS_PREFERENCES_DEFAULT_COMPRESSION_FORMAT); + for (i = 0; i < g_list_model_get_n_items (G_LIST_MODEL (store)); i++) + { + item = g_list_model_get_item (G_LIST_MODEL (store), i); + if (item->format == format) + { + gtk_drop_down_set_selected (GTK_DROP_DOWN (self->extension_dropdown), i); + update_selected_format (self); + g_object_unref (item); + break; + } + + g_object_unref (item); + } + + g_object_unref (store); + g_object_unref (factory); + g_object_unref (list_factory); +} + +NautilusCompressDialogController * +nautilus_compress_dialog_controller_new (GtkWindow *parent_window, + NautilusDirectory *destination_directory, + gchar *initial_name) +{ + NautilusCompressDialogController *self; + g_autoptr (GtkBuilder) builder = NULL; + GtkWidget *compress_dialog; + GtkWidget *error_revealer; + GtkWidget *error_label; + GtkWidget *name_entry; + GtkWidget *activate_button; + GtkWidget *extension_dropdown; + GtkSizeGroup *extension_sizegroup; + GtkWidget *passphrase_label; + GtkWidget *passphrase_entry; + + builder = gtk_builder_new_from_resource ("/org/gnome/nautilus/ui/nautilus-compress-dialog.ui"); + compress_dialog = GTK_WIDGET (gtk_builder_get_object (builder, "compress_dialog")); + error_revealer = GTK_WIDGET (gtk_builder_get_object (builder, "error_revealer")); + error_label = GTK_WIDGET (gtk_builder_get_object (builder, "error_label")); + name_entry = GTK_WIDGET (gtk_builder_get_object (builder, "name_entry")); + activate_button = GTK_WIDGET (gtk_builder_get_object (builder, "activate_button")); + extension_dropdown = GTK_WIDGET (gtk_builder_get_object (builder, "extension_dropdown")); + extension_sizegroup = GTK_SIZE_GROUP (gtk_builder_get_object (builder, "extension_sizegroup")); + passphrase_label = GTK_WIDGET (gtk_builder_get_object (builder, "passphrase_label")); + passphrase_entry = GTK_WIDGET (gtk_builder_get_object (builder, "passphrase_entry")); + + gtk_window_set_transient_for (GTK_WINDOW (compress_dialog), + parent_window); + + self = g_object_new (NAUTILUS_TYPE_COMPRESS_DIALOG_CONTROLLER, + "error-revealer", error_revealer, + "error-label", error_label, + "name-entry", name_entry, + "activate-button", activate_button, + "containing-directory", destination_directory, NULL); + + self->compress_dialog = compress_dialog; + self->activate_button = activate_button; + self->error_label = error_label; + self->name_entry = name_entry; + self->extension_dropdown = extension_dropdown; + self->extension_sizegroup = extension_sizegroup; + self->passphrase_label = passphrase_label; + self->passphrase_entry = passphrase_entry; + + extension_dropdown_setup (self); + + self->response_handler_id = g_signal_connect (compress_dialog, + "response", + (GCallback) compress_dialog_controller_on_response, + self); + + g_signal_connect (self->passphrase_entry, "changed", + G_CALLBACK (passphrase_entry_on_changed), self); + g_signal_connect (self->passphrase_entry, "icon-press", + G_CALLBACK (passphrase_entry_on_icon_press), self); + g_signal_connect (self->activate_button, "notify::sensitive", + G_CALLBACK (activate_button_on_sensitive_notify), self); + g_signal_connect_swapped (self->extension_dropdown, "notify::selected-item", + G_CALLBACK (update_selected_format), self); + + if (initial_name != NULL) + { + gtk_editable_set_text (GTK_EDITABLE (name_entry), initial_name); + } + + gtk_widget_show (compress_dialog); + + return self; +} + +static void +nautilus_compress_dialog_controller_init (NautilusCompressDialogController *self) +{ +} + +static void +nautilus_compress_dialog_controller_finalize (GObject *object) +{ + NautilusCompressDialogController *self; + + self = NAUTILUS_COMPRESS_DIALOG_CONTROLLER (object); + + if (self->compress_dialog != NULL) + { + g_clear_signal_handler (&self->response_handler_id, self->compress_dialog); + gtk_window_destroy (GTK_WINDOW (self->compress_dialog)); + self->compress_dialog = NULL; + } + + g_free (self->passphrase); + + G_OBJECT_CLASS (nautilus_compress_dialog_controller_parent_class)->finalize (object); +} + +static void +nautilus_compress_dialog_controller_class_init (NautilusCompressDialogControllerClass *klass) +{ + GObjectClass *object_class = G_OBJECT_CLASS (klass); + NautilusFileNameWidgetControllerClass *parent_class = NAUTILUS_FILE_NAME_WIDGET_CONTROLLER_CLASS (klass); + + object_class->finalize = nautilus_compress_dialog_controller_finalize; + + parent_class->get_new_name = nautilus_compress_dialog_controller_get_new_name; + parent_class->name_is_valid = nautilus_compress_dialog_controller_name_is_valid; +} + +const gchar * +nautilus_compress_dialog_controller_get_passphrase (NautilusCompressDialogController *self) +{ + return self->passphrase; +} diff --git a/src/nautilus-compress-dialog-controller.h b/src/nautilus-compress-dialog-controller.h new file mode 100644 index 0000000..6c96d68 --- /dev/null +++ b/src/nautilus-compress-dialog-controller.h @@ -0,0 +1,34 @@ +/* nautilus-compress-dialog-controller.h + * + * Copyright (C) 2016 the Nautilus developers + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License as + * published by the Free Software Foundation; either version 2 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 warranty of + * MERCHANTABILITY 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 . + * + */ + +#pragma once + +#include +#include + +#include "nautilus-file-name-widget-controller.h" +#include "nautilus-directory.h" + +#define NAUTILUS_TYPE_COMPRESS_DIALOG_CONTROLLER nautilus_compress_dialog_controller_get_type () +G_DECLARE_FINAL_TYPE (NautilusCompressDialogController, nautilus_compress_dialog_controller, NAUTILUS, COMPRESS_DIALOG_CONTROLLER, NautilusFileNameWidgetController) + +NautilusCompressDialogController * nautilus_compress_dialog_controller_new (GtkWindow *parent_window, + NautilusDirectory *destination_directory, + gchar *initial_name); +const gchar * nautilus_compress_dialog_controller_get_passphrase (NautilusCompressDialogController *controller); diff --git a/src/nautilus-dbus-launcher.c b/src/nautilus-dbus-launcher.c new file mode 100644 index 0000000..491f967 --- /dev/null +++ b/src/nautilus-dbus-launcher.c @@ -0,0 +1,231 @@ +/* + * Copyright (C) 2022 Corey Berla + * + * SPDX-License-Identifier: GPL-3.0-or-later + */ + +#include "nautilus-dbus-launcher.h" + +#include + +#include "nautilus-file.h" +#include "nautilus-ui-utilities.h" + + +typedef struct +{ + GDBusProxy *proxy; + gchar *error; + GCancellable *cancellable; + gboolean ping_on_creation; +} NautilusDBusLauncherData; + +struct _NautilusDBusLauncher +{ + GObject parent; + + NautilusDBusLauncherApp last_app_initialized; + NautilusDBusLauncherData *data[NAUTILUS_DBUS_LAUNCHER_N_APPS]; +}; + +G_DEFINE_TYPE (NautilusDBusLauncher, nautilus_dbus_launcher, G_TYPE_OBJECT) + +static NautilusDBusLauncher *launcher = NULL; + +static void +on_nautilus_dbus_launcher_call_finished (GObject *source_object, + GAsyncResult *res, + gpointer user_data) +{ + GtkWindow *window = user_data; + g_autoptr (GError) error = NULL; + g_autofree char *message = NULL; + + g_dbus_proxy_call_finish (G_DBUS_PROXY (source_object), res, &error); + if (error != NULL) + { + g_warning ("Error calling proxy %s", error->message); + message = g_strdup_printf (_("Details: %s"), error->message); + show_dialog (_("There was an error launching the application."), + message, + window, + GTK_MESSAGE_ERROR); + } +} + +static void +on_nautilus_dbus_launcher_ping_finished (GObject *source_object, + GAsyncResult *res, + gpointer user_data) +{ + NautilusDBusLauncherData *data = user_data; + g_autoptr (GError) error = NULL; + + g_dbus_proxy_call_finish (G_DBUS_PROXY (source_object), res, &error); + + if (error != NULL) + { + data->error = g_strdup (error->message); + } + + g_clear_object (&data->cancellable); +} + +void +nautilus_dbus_launcher_call (NautilusDBusLauncher *self, + NautilusDBusLauncherApp app, + const gchar *method_name, + GVariant *parameters, + GtkWindow *window) +{ + if (self->data[app]->proxy != NULL) + { + g_dbus_proxy_call (self->data[app]->proxy, + method_name, + parameters, + G_DBUS_CALL_FLAGS_NONE, + G_MAXINT, + NULL, + on_nautilus_dbus_launcher_call_finished, + window); + } + else if (window != NULL) + { + show_dialog (_("There was an error launching the application."), + _("Details: The proxy has not been created."), + window, + GTK_MESSAGE_ERROR); + } +} + +static void +on_nautilus_dbus_proxy_ready (GObject *source_object, + GAsyncResult *res, + gpointer user_data) +{ + NautilusDBusLauncherData *data = user_data; + g_autoptr (GError) error = NULL; + + data->proxy = g_dbus_proxy_new_for_bus_finish (res, &error); + + if (error != NULL) + { + g_warning ("Error creating proxy %s", error->message); + data->error = g_strdup (error->message); + g_clear_object (&data->cancellable); + } + else if (data->ping_on_creation) + { + g_dbus_proxy_call (data->proxy, + "org.freedesktop.DBus.Peer.Ping", NULL, + G_DBUS_CALL_FLAGS_NONE, G_MAXINT, data->cancellable, + on_nautilus_dbus_launcher_ping_finished, data); + } +} + +static void +nautilus_dbus_launcher_create_proxy (NautilusDBusLauncherData *data, + const gchar *name, + const gchar *object_path, + const gchar *interface) +{ + g_dbus_proxy_new_for_bus (G_BUS_TYPE_SESSION, + G_DBUS_PROXY_FLAGS_DO_NOT_AUTO_START_AT_CONSTRUCTION, + NULL, + name, + object_path, + interface, + data->cancellable, + on_nautilus_dbus_proxy_ready, + data); +} + +gboolean nautilus_dbus_launcher_is_available (NautilusDBusLauncher *self, + NautilusDBusLauncherApp app) +{ + return self->data[app]->error == NULL && self->data[app]->proxy != NULL; +} + +NautilusDBusLauncher * +nautilus_dbus_launcher_get (void) +{ + return launcher; +} + +NautilusDBusLauncher * +nautilus_dbus_launcher_new (void) +{ + if (launcher != NULL) + { + return g_object_ref (launcher); + } + launcher = g_object_new (NAUTILUS_TYPE_DBUS_LAUNCHER, NULL); + g_object_add_weak_pointer (G_OBJECT (launcher), (gpointer) & launcher); + + return launcher; +} + +static void +nautilus_dbus_launcher_finalize (GObject *object) +{ + NautilusDBusLauncher *self = NAUTILUS_DBUS_LAUNCHER (object); + + for (gint i = 1; i <= self->last_app_initialized; i++) + { + g_clear_object (&self->data[i]->proxy); + g_free (self->data[i]->error); + g_cancellable_cancel (self->data[i]->cancellable); + g_clear_object (&self->data[i]->cancellable); + g_free (self->data[i]); + } + + G_OBJECT_CLASS (nautilus_dbus_launcher_parent_class)->finalize (object); +} + +static void +nautilus_dbus_launcher_class_init (NautilusDBusLauncherClass *klass) +{ + GObjectClass *object_class; + + object_class = G_OBJECT_CLASS (klass); + + object_class->finalize = nautilus_dbus_launcher_finalize; +} + +static void +nautilus_dbus_launcher_data_init (NautilusDBusLauncher *self, + NautilusDBusLauncherApp app, + gboolean ping_on_creation) +{ + NautilusDBusLauncherData *data; + g_assert_true (app == self->last_app_initialized + 1); + + data = g_new0 (NautilusDBusLauncherData, 1); + data->proxy = NULL; + data->error = NULL; + data->ping_on_creation = ping_on_creation; + data->cancellable = g_cancellable_new (); + + self->data[app] = data; + self->last_app_initialized = app; +} + +static void +nautilus_dbus_launcher_init (NautilusDBusLauncher *self) +{ + nautilus_dbus_launcher_data_init (self, NAUTILUS_DBUS_LAUNCHER_SETTINGS, FALSE); + nautilus_dbus_launcher_data_init (self, NAUTILUS_DBUS_LAUNCHER_DISKS, TRUE); + nautilus_dbus_launcher_data_init (self, NAUTILUS_DBUS_LAUNCHER_CONSOLE, TRUE); + + nautilus_dbus_launcher_create_proxy (self->data[NAUTILUS_DBUS_LAUNCHER_SETTINGS], + "org.gnome.Settings", "/org/gnome/Settings", + "org.gtk.Actions"); + + nautilus_dbus_launcher_create_proxy (self->data[NAUTILUS_DBUS_LAUNCHER_DISKS], + "org.gnome.DiskUtility", "/org/gnome/DiskUtility", + "org.gtk.Application"); + + nautilus_dbus_launcher_create_proxy (self->data[NAUTILUS_DBUS_LAUNCHER_CONSOLE], + "org.gnome.Console", "/org/gnome/Console", + "org.freedesktop.Application"); +} diff --git a/src/nautilus-dbus-launcher.h b/src/nautilus-dbus-launcher.h new file mode 100644 index 0000000..0e55337 --- /dev/null +++ b/src/nautilus-dbus-launcher.h @@ -0,0 +1,39 @@ +/* + * Copyright (C) 2022 Corey Berla + * + * SPDX-License-Identifier: GPL-3.0-or-later + */ + +#pragma once + +#include +#include + +#include "nautilus-file.h" + +#define NAUTILUS_TYPE_DBUS_LAUNCHER (nautilus_dbus_launcher_get_type()) + +G_DECLARE_FINAL_TYPE (NautilusDBusLauncher, nautilus_dbus_launcher, NAUTILUS, DBUS_LAUNCHER, GObject) + + +typedef enum { + NAUTILUS_DBUS_APP_0, + NAUTILUS_DBUS_LAUNCHER_SETTINGS, + NAUTILUS_DBUS_LAUNCHER_DISKS, + NAUTILUS_DBUS_LAUNCHER_CONSOLE, + NAUTILUS_DBUS_LAUNCHER_N_APPS +} NautilusDBusLauncherApp; + +NautilusDBusLauncher * nautilus_dbus_launcher_new (void); //to be called on `NautilusApplication::startup` only + +NautilusDBusLauncher * nautilus_dbus_launcher_get (void); //to be called by consumers; doesn't change reference count. + +gboolean nautilus_dbus_launcher_is_available (NautilusDBusLauncher *self, + NautilusDBusLauncherApp app); + +void nautilus_dbus_launcher_call (NautilusDBusLauncher *self, + NautilusDBusLauncherApp app, + const gchar *method_name, + GVariant *parameters, + GtkWindow *window); + diff --git a/src/nautilus-dbus-manager.c b/src/nautilus-dbus-manager.c new file mode 100644 index 0000000..b4c13f1 --- /dev/null +++ b/src/nautilus-dbus-manager.c @@ -0,0 +1,660 @@ +/* + * nautilus-dbus-manager: nautilus DBus interface + * + * Copyright (C) 2010, Red Hat, Inc. + * + * Nautilus is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License as + * published by the Free Software Foundation; either version 2 of the + * License, or (at your option) any later version. + * + * Nautilus is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * 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 . + * + * Author: Cosimo Cecchi + * + */ + +#include + +#include "nautilus-dbus-manager.h" +#include "nautilus-generated.h" +#include "nautilus-generated2.h" + +#include "nautilus-file-operations.h" +#include "nautilus-file-undo-manager.h" +#include "nautilus-file.h" + +#define DEBUG_FLAG NAUTILUS_DEBUG_DBUS +#include "nautilus-debug.h" + +struct _NautilusDBusManager +{ + GObject parent; + + NautilusDBusFileOperations *file_operations; + NautilusDBusFileOperations2 *file_operations2; +}; + +G_DEFINE_TYPE (NautilusDBusManager, nautilus_dbus_manager, G_TYPE_OBJECT); + +static void +nautilus_dbus_manager_dispose (GObject *object) +{ + NautilusDBusManager *self = (NautilusDBusManager *) object; + + if (self->file_operations) + { + g_object_unref (self->file_operations); + self->file_operations = NULL; + } + + if (self->file_operations2) + { + g_object_unref (self->file_operations2); + self->file_operations2 = NULL; + } + + G_OBJECT_CLASS (nautilus_dbus_manager_parent_class)->dispose (object); +} + +static void +undo_redo_on_finished (gpointer user_data) +{ + NautilusFileUndoManager *undo_manager = NULL; + int *handler_id = (int *) user_data; + + undo_manager = nautilus_file_undo_manager_get (); + g_signal_handler_disconnect (undo_manager, *handler_id); + g_application_release (g_application_get_default ()); + g_free (handler_id); +} + +static void +handle_redo_internal (NautilusFileOperationsDBusData *dbus_data) +{ + NautilusFileUndoManager *undo_manager = NULL; + gint *handler_id = g_new0 (int, 1); + + g_application_hold (g_application_get_default ()); + + undo_manager = nautilus_file_undo_manager_get (); + *handler_id = g_signal_connect_swapped (undo_manager, "undo-changed", + G_CALLBACK (undo_redo_on_finished), + handler_id); + nautilus_file_undo_manager_redo (NULL, dbus_data); +} + +static gboolean +handle_redo (NautilusDBusFileOperations *object, + GDBusMethodInvocation *invocation) +{ + handle_redo_internal (NULL); + + nautilus_dbus_file_operations_complete_redo (object, invocation); + return TRUE; /* invocation was handled */ +} + +static gboolean +handle_redo2 (NautilusDBusFileOperations2 *object, + GDBusMethodInvocation *invocation, + GVariant *platform_data) +{ + g_autoptr (NautilusFileOperationsDBusData) dbus_data = NULL; + + dbus_data = nautilus_file_operations_dbus_data_new (platform_data); + + handle_redo_internal (dbus_data); + + nautilus_dbus_file_operations2_complete_redo (object, invocation); + return TRUE; /* invocation was handled */ +} + +static void +handle_undo_internal (NautilusFileOperationsDBusData *dbus_data) +{ + NautilusFileUndoManager *undo_manager = NULL; + gint *handler_id = g_new0 (int, 1); + + g_application_hold (g_application_get_default ()); + + undo_manager = nautilus_file_undo_manager_get (); + *handler_id = g_signal_connect_swapped (undo_manager, "undo-changed", + G_CALLBACK (undo_redo_on_finished), + handler_id); + nautilus_file_undo_manager_undo (NULL, dbus_data); +} + +static gboolean +handle_undo (NautilusDBusFileOperations *object, + GDBusMethodInvocation *invocation) +{ + handle_undo_internal (NULL); + + nautilus_dbus_file_operations_complete_undo (object, invocation); + return TRUE; /* invocation was handled */ +} + +static gboolean +handle_undo2 (NautilusDBusFileOperations2 *object, + GDBusMethodInvocation *invocation, + GVariant *platform_data) +{ + g_autoptr (NautilusFileOperationsDBusData) dbus_data = NULL; + + dbus_data = nautilus_file_operations_dbus_data_new (platform_data); + + handle_undo_internal (dbus_data); + + nautilus_dbus_file_operations2_complete_undo (object, invocation); + return TRUE; /* invocation was handled */ +} + +static void +create_folder_on_finished (GFile *new_file, + gboolean success, + gpointer callback_data) +{ + g_application_release (g_application_get_default ()); +} + +static void +handle_create_folder_internal (const gchar *parent_uri, + const gchar *new_folder_name, + NautilusFileOperationsDBusData *dbus_data) +{ + g_application_hold (g_application_get_default ()); + nautilus_file_operations_new_folder (NULL, dbus_data, + parent_uri, new_folder_name, + create_folder_on_finished, NULL); +} + +static gboolean +handle_create_folder (NautilusDBusFileOperations *object, + GDBusMethodInvocation *invocation, + const gchar *uri) +{ + g_autoptr (GFile) file = NULL; + g_autoptr (GFile) parent_file = NULL; + g_autofree gchar *basename = NULL; + g_autofree gchar *parent_file_uri = NULL; + + file = g_file_new_for_uri (uri); + basename = g_file_get_basename (file); + parent_file = g_file_get_parent (file); + if (parent_file == NULL || basename == NULL) + { + g_dbus_method_invocation_return_error (invocation, G_IO_ERROR, G_IO_ERROR_INVALID_ARGUMENT, "Invalid uri: %s", uri); + return TRUE; + } + parent_file_uri = g_file_get_uri (parent_file); + + handle_create_folder_internal (parent_file_uri, basename, NULL); + + nautilus_dbus_file_operations_complete_create_folder (object, invocation); + return TRUE; /* invocation was handled */ +} + +static gboolean +handle_create_folder2 (NautilusDBusFileOperations2 *object, + GDBusMethodInvocation *invocation, + const gchar *parent_uri, + const gchar *new_folder_name, + GVariant *platform_data) +{ + g_autoptr (NautilusFileOperationsDBusData) dbus_data = NULL; + + dbus_data = nautilus_file_operations_dbus_data_new (platform_data); + + handle_create_folder_internal (parent_uri, new_folder_name, dbus_data); + + nautilus_dbus_file_operations2_complete_create_folder (object, invocation); + return TRUE; /* invocation was handled */ +} + +static void +copy_move_on_finished (GHashTable *debutting_uris, + gboolean success, + gpointer callback_data) +{ + g_application_release (g_application_get_default ()); +} + +static void +handle_copy_uris_internal (const char **sources, + const char *destination, + NautilusFileOperationsDBusData *dbus_data) +{ + GList *source_files = NULL; + gint idx; + + for (idx = 0; sources[idx] != NULL; idx++) + { + source_files = g_list_prepend (source_files, g_strdup (sources[idx])); + } + + g_application_hold (g_application_get_default ()); + nautilus_file_operations_copy_move (source_files, destination, + GDK_ACTION_COPY, NULL, dbus_data, + copy_move_on_finished, NULL); + + g_list_free_full (source_files, g_free); +} + +static gboolean +handle_copy_uris (NautilusDBusFileOperations *object, + GDBusMethodInvocation *invocation, + const gchar **sources, + const gchar *destination) +{ + handle_copy_uris_internal (sources, destination, NULL); + + nautilus_dbus_file_operations_complete_copy_uris (object, invocation); + return TRUE; /* invocation was handled */ +} + +static gboolean +handle_copy_uris2 (NautilusDBusFileOperations2 *object, + GDBusMethodInvocation *invocation, + const gchar **sources, + const gchar *destination, + GVariant *platform_data) +{ + g_autoptr (NautilusFileOperationsDBusData) dbus_data = NULL; + + dbus_data = nautilus_file_operations_dbus_data_new (platform_data); + + handle_copy_uris_internal (sources, destination, dbus_data); + + nautilus_dbus_file_operations2_complete_copy_uris (object, invocation); + return TRUE; /* invocation was handled */ +} + +static void +handle_move_uris_internal (const char **sources, + const char *destination, + NautilusFileOperationsDBusData *dbus_data) +{ + GList *source_files = NULL; + gint idx; + + for (idx = 0; sources[idx] != NULL; idx++) + { + source_files = g_list_prepend (source_files, g_strdup (sources[idx])); + } + + g_application_hold (g_application_get_default ()); + nautilus_file_operations_copy_move (source_files, destination, + GDK_ACTION_MOVE, NULL, dbus_data, + copy_move_on_finished, NULL); + + g_list_free_full (source_files, g_free); +} + +static gboolean +handle_move_uris (NautilusDBusFileOperations *object, + GDBusMethodInvocation *invocation, + const gchar **sources, + const gchar *destination) +{ + handle_move_uris_internal (sources, destination, NULL); + + nautilus_dbus_file_operations_complete_copy_uris (object, invocation); + return TRUE; /* invocation was handled */ +} + +static gboolean +handle_move_uris2 (NautilusDBusFileOperations2 *object, + GDBusMethodInvocation *invocation, + const gchar **sources, + const gchar *destination, + GVariant *platform_data) +{ + g_autoptr (NautilusFileOperationsDBusData) dbus_data = NULL; + + dbus_data = nautilus_file_operations_dbus_data_new (platform_data); + + handle_move_uris_internal (sources, destination, dbus_data); + + nautilus_dbus_file_operations2_complete_copy_uris (object, invocation); + return TRUE; /* invocation was handled */ +} + +/* FIXME: Needs a callback for maintaining alive the application */ +static void +handle_empty_trash_internal (gboolean ask_confirmation, + NautilusFileOperationsDBusData *dbus_data) +{ + nautilus_file_operations_empty_trash (NULL, ask_confirmation, dbus_data); +} + +static gboolean +handle_empty_trash (NautilusDBusFileOperations *object, + GDBusMethodInvocation *invocation) +{ + handle_empty_trash_internal (TRUE, NULL); + + nautilus_dbus_file_operations_complete_empty_trash (object, invocation); + return TRUE; /* invocation was handled */ +} + +static gboolean +handle_empty_trash2 (NautilusDBusFileOperations2 *object, + GDBusMethodInvocation *invocation, + gboolean ask_confirmation, + GVariant *platform_data) +{ + g_autoptr (NautilusFileOperationsDBusData) dbus_data = NULL; + + dbus_data = nautilus_file_operations_dbus_data_new (platform_data); + + handle_empty_trash_internal (ask_confirmation, dbus_data); + + nautilus_dbus_file_operations2_complete_empty_trash (object, invocation); + return TRUE; /* invocation was handled */ +} + +static void +trash_on_finished (GHashTable *debutting_uris, + gboolean user_cancel, + gpointer callback_data) +{ + g_application_release (g_application_get_default ()); +} + +static void +handle_trash_uris_internal (const char **uris, + NautilusFileOperationsDBusData *dbus_data) +{ + g_autolist (GFile) source_files = NULL; + gint idx; + + for (idx = 0; uris[idx] != NULL; idx++) + { + source_files = g_list_prepend (source_files, + g_file_new_for_uri (uris[idx])); + } + + g_application_hold (g_application_get_default ()); + nautilus_file_operations_trash_or_delete_async (source_files, NULL, + dbus_data, + trash_on_finished, NULL); +} + +static gboolean +handle_trash_files (NautilusDBusFileOperations *object, + GDBusMethodInvocation *invocation, + const gchar **sources) +{ + handle_trash_uris_internal (sources, NULL); + + nautilus_dbus_file_operations_complete_trash_files (object, invocation); + return TRUE; /* invocation was handled */ +} + +static gboolean +handle_trash_uris2 (NautilusDBusFileOperations2 *object, + GDBusMethodInvocation *invocation, + const gchar **uris, + GVariant *platform_data) +{ + g_autoptr (NautilusFileOperationsDBusData) dbus_data = NULL; + + dbus_data = nautilus_file_operations_dbus_data_new (platform_data); + + handle_trash_uris_internal (uris, dbus_data); + + nautilus_dbus_file_operations2_complete_trash_uris (object, invocation); + return TRUE; /* invocation was handled */ +} + +static void +delete_on_finished (GHashTable *debutting_uris, + gboolean user_cancel, + gpointer callback_data) +{ + g_application_release (g_application_get_default ()); +} + +static void +handle_delete_uris_internal (const char **uris, + NautilusFileOperationsDBusData *dbus_data) +{ + g_autolist (GFile) source_files = NULL; + gint idx; + + for (idx = 0; uris[idx] != NULL; idx++) + { + source_files = g_list_prepend (source_files, + g_file_new_for_uri (uris[idx])); + } + + g_application_hold (g_application_get_default ()); + nautilus_file_operations_delete_async (source_files, NULL, + dbus_data, + delete_on_finished, NULL); +} + +static gboolean +handle_delete_uris2 (NautilusDBusFileOperations2 *object, + GDBusMethodInvocation *invocation, + const gchar **uris, + GVariant *platform_data) +{ + g_autoptr (NautilusFileOperationsDBusData) dbus_data = NULL; + + dbus_data = nautilus_file_operations_dbus_data_new (platform_data); + + handle_delete_uris_internal (uris, dbus_data); + + nautilus_dbus_file_operations2_complete_delete_uris (object, invocation); + return TRUE; /* invocation was handled */ +} + +static void +rename_file_on_finished (NautilusFile *file, + GFile *result_location, + GError *error, + gpointer callback_data) +{ + g_application_release (g_application_get_default ()); +} + +static void +handle_rename_uri_internal (const gchar *uri, + const gchar *new_name, + NautilusFileOperationsDBusData *dbus_data) +{ + NautilusFile *file = NULL; + + file = nautilus_file_get_by_uri (uri); + + g_application_hold (g_application_get_default ()); + nautilus_file_rename (file, new_name, + rename_file_on_finished, NULL); +} + +static gboolean +handle_rename_file (NautilusDBusFileOperations *object, + GDBusMethodInvocation *invocation, + const gchar *uri, + const gchar *new_name) +{ + handle_rename_uri_internal (uri, new_name, NULL); + + nautilus_dbus_file_operations_complete_rename_file (object, invocation); + + return TRUE; /* invocation was handled */ +} + +static gboolean +handle_rename_uri2 (NautilusDBusFileOperations2 *object, + GDBusMethodInvocation *invocation, + const gchar *uri, + const gchar *new_name, + GVariant *platform_data) +{ + g_autoptr (NautilusFileOperationsDBusData) dbus_data = NULL; + + dbus_data = nautilus_file_operations_dbus_data_new (platform_data); + + handle_rename_uri_internal (uri, new_name, dbus_data); + + nautilus_dbus_file_operations2_complete_rename_uri (object, invocation); + + return TRUE; /* invocation was handled */ +} + +static void +undo_manager_changed (NautilusDBusManager *self) +{ + NautilusFileUndoManagerState undo_state; + + undo_state = nautilus_file_undo_manager_get_state (); + nautilus_dbus_file_operations_set_undo_status (self->file_operations, + undo_state); + nautilus_dbus_file_operations2_set_undo_status (self->file_operations2, + undo_state); +} + +static void +nautilus_dbus_manager_init (NautilusDBusManager *self) +{ + G_GNUC_BEGIN_IGNORE_DEPRECATIONS + self->file_operations = nautilus_dbus_file_operations_skeleton_new (); + G_GNUC_END_IGNORE_DEPRECATIONS + + self->file_operations2 = nautilus_dbus_file_operations2_skeleton_new (); + + g_signal_connect (self->file_operations, + "handle-copy-uris", + G_CALLBACK (handle_copy_uris), + self); + g_signal_connect (self->file_operations2, + "handle-copy-uris", + G_CALLBACK (handle_copy_uris2), + self); + g_signal_connect (self->file_operations, + "handle-move-uris", + G_CALLBACK (handle_move_uris), + self); + g_signal_connect (self->file_operations2, + "handle-move-uris", + G_CALLBACK (handle_move_uris2), + self); + g_signal_connect (self->file_operations, + "handle-empty-trash", + G_CALLBACK (handle_empty_trash), + self); + g_signal_connect (self->file_operations2, + "handle-empty-trash", + G_CALLBACK (handle_empty_trash2), + self); + g_signal_connect (self->file_operations, + "handle-trash-files", + G_CALLBACK (handle_trash_files), + self); + g_signal_connect (self->file_operations2, + "handle-trash-uris", + G_CALLBACK (handle_trash_uris2), + self); + g_signal_connect (self->file_operations2, + "handle-delete-uris", + G_CALLBACK (handle_delete_uris2), + self); + g_signal_connect (self->file_operations, + "handle-create-folder", + G_CALLBACK (handle_create_folder), + self); + g_signal_connect (self->file_operations2, + "handle-create-folder", + G_CALLBACK (handle_create_folder2), + self); + g_signal_connect (self->file_operations, + "handle-rename-file", + G_CALLBACK (handle_rename_file), + self); + g_signal_connect (self->file_operations2, + "handle-rename-uri", + G_CALLBACK (handle_rename_uri2), + self); + g_signal_connect (self->file_operations, + "handle-undo", + G_CALLBACK (handle_undo), + self); + g_signal_connect (self->file_operations2, + "handle-undo", + G_CALLBACK (handle_undo2), + self); + g_signal_connect (self->file_operations, + "handle-redo", + G_CALLBACK (handle_redo), + self); + g_signal_connect (self->file_operations2, + "handle-redo", + G_CALLBACK (handle_redo2), + self); +} + +static void +nautilus_dbus_manager_class_init (NautilusDBusManagerClass *klass) +{ + GObjectClass *oclass = G_OBJECT_CLASS (klass); + + oclass->dispose = nautilus_dbus_manager_dispose; +} + +NautilusDBusManager * +nautilus_dbus_manager_new (void) +{ + return g_object_new (NAUTILUS_TYPE_DBUS_MANAGER, NULL); +} + +gboolean +nautilus_dbus_manager_register (NautilusDBusManager *self, + GDBusConnection *connection, + GError **error) +{ + gboolean success1; + gboolean success2; + gboolean succes; + + success1 = g_dbus_interface_skeleton_export (G_DBUS_INTERFACE_SKELETON (self->file_operations), + connection, + "/org/gnome/Nautilus" PROFILE, + error); + + success2 = g_dbus_interface_skeleton_export (G_DBUS_INTERFACE_SKELETON (self->file_operations2), + connection, + "/org/gnome/Nautilus" PROFILE "/FileOperations2", + error); + + succes = success1 && success2; + + if (succes) + { + g_signal_connect_object (nautilus_file_undo_manager_get (), + "undo-changed", + G_CALLBACK (undo_manager_changed), + self, + G_CONNECT_SWAPPED); + + undo_manager_changed (self); + } + + return succes; +} + +void +nautilus_dbus_manager_unregister (NautilusDBusManager *self) +{ + g_dbus_interface_skeleton_unexport (G_DBUS_INTERFACE_SKELETON (self->file_operations)); + g_dbus_interface_skeleton_unexport (G_DBUS_INTERFACE_SKELETON (self->file_operations2)); + + g_signal_handlers_disconnect_by_data (nautilus_file_undo_manager_get (), self); +} diff --git a/src/nautilus-dbus-manager.h b/src/nautilus-dbus-manager.h new file mode 100644 index 0000000..7ff4f8e --- /dev/null +++ b/src/nautilus-dbus-manager.h @@ -0,0 +1,36 @@ +/* + * nautilus-dbus-manager: nautilus DBus interface + * + * Copyright (C) 2010, Red Hat, Inc. + * + * Nautilus is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License as + * published by the Free Software Foundation; either version 2 of the + * License, or (at your option) any later version. + * + * Nautilus is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * 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 . + * + * Author: Cosimo Cecchi + * + */ + +#pragma once + +#include +#include + +#define NAUTILUS_TYPE_DBUS_MANAGER (nautilus_dbus_manager_get_type()) +G_DECLARE_FINAL_TYPE (NautilusDBusManager, nautilus_dbus_manager, NAUTILUS, DBUS_MANAGER, GObject) + +NautilusDBusManager * nautilus_dbus_manager_new (void); + +gboolean nautilus_dbus_manager_register (NautilusDBusManager *self, + GDBusConnection *connection, + GError **error); +void nautilus_dbus_manager_unregister (NautilusDBusManager *self); diff --git a/src/nautilus-debug.c b/src/nautilus-debug.c new file mode 100644 index 0000000..bbf7656 --- /dev/null +++ b/src/nautilus-debug.c @@ -0,0 +1,180 @@ +/* + * nautilus-debug: debug loggers for nautilus + * + * Copyright (C) 2007 Collabora Ltd. + * Copyright (C) 2007 Nokia Corporation + * Copyright (C) 2010 Red Hat, Inc. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, see . + * + * Based on Empathy's empathy-debug. + */ + +#include "config.h" + +#include +#include + +#include "nautilus-debug.h" + +#include "nautilus-file.h" + +static DebugFlags flags = 0; +static gboolean initialized = FALSE; + +static GDebugKey keys[] = +{ + { "Application", NAUTILUS_DEBUG_APPLICATION }, + { "AsyncJobs", NAUTILUS_DEBUG_ASYNC_JOBS }, + { "Bookmarks", NAUTILUS_DEBUG_BOOKMARKS }, + { "DBus", NAUTILUS_DEBUG_DBUS }, + { "DirectoryView", NAUTILUS_DEBUG_DIRECTORY_VIEW }, + { "File", NAUTILUS_DEBUG_FILE }, + { "IconView", NAUTILUS_DEBUG_GRID_VIEW }, + { "ListView", NAUTILUS_DEBUG_LIST_VIEW }, + { "Mime", NAUTILUS_DEBUG_MIME }, + { "Places", NAUTILUS_DEBUG_PLACES }, + { "Previewer", NAUTILUS_DEBUG_PREVIEWER }, + { "Search", NAUTILUS_DEBUG_SEARCH }, + { "SearchHit", NAUTILUS_DEBUG_SEARCH_HIT }, + { "Smclient", NAUTILUS_DEBUG_SMCLIENT }, + { "Window", NAUTILUS_DEBUG_WINDOW }, + { "Undo", NAUTILUS_DEBUG_UNDO }, + { "Thumbnails", NAUTILUS_DEBUG_THUMBNAILS }, + { "TagManager", NAUTILUS_DEBUG_TAG_MANAGER }, + { 0, } +}; + +static void +nautilus_debug_set_flags_from_env (void) +{ + guint nkeys; + const gchar *flags_string; + + for (nkeys = 0; keys[nkeys].value; nkeys++) + { + } + + flags_string = g_getenv ("NAUTILUS_DEBUG"); + + if (flags_string) + { + nautilus_debug_set_flags (g_parse_debug_string (flags_string, keys, nkeys)); + } + + initialized = TRUE; +} + +void +nautilus_debug_set_flags (DebugFlags new_flags) +{ + flags |= new_flags; + initialized = TRUE; +} + +gboolean +nautilus_debug_flag_is_set (DebugFlags flag) +{ + return flag & flags; +} + +void +nautilus_debug (DebugFlags flag, + const gchar *format, + ...) +{ + va_list args; + va_start (args, format); + nautilus_debug_valist (flag, format, args); + va_end (args); +} + +__attribute__((__format__ (__printf__, 2, 0))) +void +nautilus_debug_valist (DebugFlags flag, + const gchar *format, + va_list args) +{ + if (G_UNLIKELY (!initialized)) + { + nautilus_debug_set_flags_from_env (); + } + + if (flag & flags) + { + g_logv (G_LOG_DOMAIN, G_LOG_LEVEL_DEBUG, format, args); + } +} + +__attribute__((__format__ (__printf__, 3, 0))) +static void +nautilus_debug_files_valist (DebugFlags flag, + GList *files, + const gchar *format, + va_list args) +{ + NautilusFile *file; + GList *l; + gchar *uri, *msg; + + if (G_UNLIKELY (!initialized)) + { + nautilus_debug_set_flags_from_env (); + } + + if (!(flag & flags)) + { + return; + } + + msg = g_strdup_vprintf (format, args); + + g_log (G_LOG_DOMAIN, G_LOG_LEVEL_DEBUG, "%s:", msg); + + for (l = files; l != NULL; l = l->next) + { + file = l->data; + uri = nautilus_file_get_uri (file); + + if (nautilus_file_is_gone (file)) + { + gchar *new_uri; + + /* Hack: this will create an invalid URI, but it's for + * display purposes only. + */ + new_uri = g_strconcat (uri ? uri : "", " (gone)", NULL); + g_free (uri); + uri = new_uri; + } + + g_log (G_LOG_DOMAIN, G_LOG_LEVEL_DEBUG, " %s", uri); + g_free (uri); + } + + g_free (msg); +} + +void +nautilus_debug_files (DebugFlags flag, + GList *files, + const gchar *format, + ...) +{ + va_list args; + + va_start (args, format); + nautilus_debug_files_valist (flag, files, format, args); + va_end (args); +} diff --git a/src/nautilus-debug.h b/src/nautilus-debug.h new file mode 100644 index 0000000..9322a8d --- /dev/null +++ b/src/nautilus-debug.h @@ -0,0 +1,78 @@ +/* + * nautilus-debug: debug loggers for nautilus + * + * Copyright (C) 2007 Collabora Ltd. + * Copyright (C) 2007 Nokia Corporation + * Copyright (C) 2010 Red Hat, Inc. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, see . + * + * Based on Empathy's empathy-debug. + */ + +#pragma once + +#include +#include + +G_BEGIN_DECLS + +typedef enum { + NAUTILUS_DEBUG_APPLICATION = 1 << 1, + NAUTILUS_DEBUG_ASYNC_JOBS = 1 << 2, + NAUTILUS_DEBUG_BOOKMARKS = 1 << 3, + NAUTILUS_DEBUG_DBUS = 1 << 4, + NAUTILUS_DEBUG_DIRECTORY_VIEW = 1 << 5, + NAUTILUS_DEBUG_FILE = 1 << 6, + NAUTILUS_DEBUG_GRID_VIEW = 1 << 7, + NAUTILUS_DEBUG_LIST_VIEW = 1 << 8, + NAUTILUS_DEBUG_MIME = 1 << 9, + NAUTILUS_DEBUG_PLACES = 1 << 10, + NAUTILUS_DEBUG_PREVIEWER = 1 << 11, + NAUTILUS_DEBUG_SMCLIENT = 1 << 12, + NAUTILUS_DEBUG_WINDOW = 1 << 13, + NAUTILUS_DEBUG_UNDO = 1 << 14, + NAUTILUS_DEBUG_SEARCH = 1 << 15, + NAUTILUS_DEBUG_SEARCH_HIT = 1 << 16, + NAUTILUS_DEBUG_THUMBNAILS = 1 << 17, + NAUTILUS_DEBUG_TAG_MANAGER = 1 << 18, +} DebugFlags; + +void nautilus_debug_set_flags (DebugFlags flags); +gboolean nautilus_debug_flag_is_set (DebugFlags flag); + +void nautilus_debug_valist (DebugFlags flag, + const gchar *format, va_list args); + +void nautilus_debug (DebugFlags flag, const gchar *format, ...) + G_GNUC_PRINTF (2, 3); + +void nautilus_debug_files (DebugFlags flag, GList *files, + const gchar *format, ...) G_GNUC_PRINTF (3, 4); + +#ifdef DEBUG_FLAG + +#define DEBUG(format, ...) \ + nautilus_debug (DEBUG_FLAG, "%s: %s: " format, G_STRFUNC, G_STRLOC, \ + ##__VA_ARGS__) + +#define DEBUG_FILES(files, format, ...) \ + nautilus_debug_files (DEBUG_FLAG, files, "%s:" format, G_STRFUNC, \ + ##__VA_ARGS__) + +#define DEBUGGING nautilus_debug_flag_is_set(DEBUG_FLAG) + +#endif /* DEBUG_FLAG */ + +G_END_DECLS diff --git a/src/nautilus-directory-async.c b/src/nautilus-directory-async.c new file mode 100644 index 0000000..3e7f421 --- /dev/null +++ b/src/nautilus-directory-async.c @@ -0,0 +1,4755 @@ +/* + * nautilus-directory-async.c: Nautilus directory model state machine. + * + * Copyright (C) 1999, 2000, 2001 Eazel, Inc. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License as + * published by the Free Software Foundation; either version 2 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 warranty of + * MERCHANTABILITY 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 . + * + * Author: Darin Adler + */ + +#include +#include +#include + +#define DEBUG_FLAG NAUTILUS_DEBUG_ASYNC_JOBS + +#include "nautilus-debug.h" +#include "nautilus-directory-notify.h" +#include "nautilus-directory-private.h" +#include "nautilus-enums.h" +#include "nautilus-file-private.h" +#include "nautilus-file-queue.h" +#include "nautilus-global-preferences.h" +#include "nautilus-metadata.h" +#include "nautilus-profile.h" +#include "nautilus-signaller.h" + +/* turn this on to check if async. job calls are balanced */ +#if 0 +#define DEBUG_ASYNC_JOBS +#endif + +#define DIRECTORY_LOAD_ITEMS_PER_CALLBACK 100 + +/* Keep async. jobs down to this number for all directories. */ +#define MAX_ASYNC_JOBS 10 + +struct ThumbnailState +{ + NautilusDirectory *directory; + GCancellable *cancellable; + NautilusFile *file; +}; + +struct MountState +{ + NautilusDirectory *directory; + GCancellable *cancellable; + NautilusFile *file; +}; + +struct FilesystemInfoState +{ + NautilusDirectory *directory; + GCancellable *cancellable; + NautilusFile *file; +}; + +struct DirectoryLoadState +{ + NautilusDirectory *directory; + GCancellable *cancellable; + GFileEnumerator *enumerator; + GHashTable *load_mime_list_hash; + NautilusFile *load_directory_file; + int load_file_count; +}; + +struct MimeListState +{ + NautilusDirectory *directory; + NautilusFile *mime_list_file; + GCancellable *cancellable; + GFileEnumerator *enumerator; + GHashTable *mime_list_hash; +}; + +struct GetInfoState +{ + NautilusDirectory *directory; + GCancellable *cancellable; +}; + +struct NewFilesState +{ + NautilusDirectory *directory; + GCancellable *cancellable; + int count; +}; + +struct DirectoryCountState +{ + NautilusDirectory *directory; + NautilusFile *count_file; + GCancellable *cancellable; + GFileEnumerator *enumerator; + int file_count; +}; + +struct DeepCountState +{ + NautilusDirectory *directory; + GCancellable *cancellable; + GFileEnumerator *enumerator; + GFile *deep_count_location; + GList *deep_count_subdirectories; + GArray *seen_deep_count_inodes; + char *fs_id; +}; + + + +typedef struct +{ + NautilusFile *file; /* Which file, NULL means all. */ + union + { + NautilusDirectoryCallback directory; + NautilusFileCallback file; + } callback; + gpointer callback_data; + Request request; + gboolean active; /* Set to FALSE when the callback is triggered and + * scheduled to be called at idle, its still kept + * in the list so we can kill it when the file + * goes away. + */ +} ReadyCallback; + +typedef struct +{ + NautilusFile *file; /* Which file, NULL means all. */ + gboolean monitor_hidden_files; /* defines whether "all" includes hidden files */ + gconstpointer client; + Request request; +} Monitor; + +typedef struct +{ + NautilusDirectory *directory; + NautilusInfoProvider *provider; + NautilusOperationHandle *handle; + NautilusOperationResult result; +} InfoProviderResponse; + +typedef gboolean (*RequestCheck) (Request); +typedef gboolean (*FileCheck) (NautilusFile *); + +/* Current number of async. jobs. */ +static int async_job_count; +static GHashTable *waiting_directories; +#ifdef DEBUG_ASYNC_JOBS +static GHashTable *async_jobs; +#endif + +/* Forward declarations for functions that need them. */ +static void deep_count_load (DeepCountState *state, + GFile *location); +static gboolean request_is_satisfied (NautilusDirectory *directory, + NautilusFile *file, + Request request); +static void cancel_loading_attributes (NautilusDirectory *directory, + NautilusFileAttributes file_attributes); +static void add_all_files_to_work_queue (NautilusDirectory *directory); +static void move_file_to_low_priority_queue (NautilusDirectory *directory, + NautilusFile *file); +static void move_file_to_extension_queue (NautilusDirectory *directory, + NautilusFile *file); +static void nautilus_directory_invalidate_file_attributes (NautilusDirectory *directory, + NautilusFileAttributes file_attributes); + +/* Some helpers for case-insensitive strings. + * Move to nautilus-glib-extensions? + */ + +static gboolean +istr_equal (gconstpointer v, + gconstpointer v2) +{ + return g_ascii_strcasecmp (v, v2) == 0; +} + +static guint +istr_hash (gconstpointer key) +{ + const char *p; + guint h; + + h = 0; + for (p = key; *p != '\0'; p++) + { + h = (h << 5) - h + g_ascii_tolower (*p); + } + + return h; +} + +static GHashTable * +istr_set_new (void) +{ + return g_hash_table_new_full (istr_hash, istr_equal, g_free, NULL); +} + +static void +istr_set_insert (GHashTable *table, + const char *istr) +{ + char *key; + + key = g_strdup (istr); + g_hash_table_replace (table, key, key); +} + +static void +add_istr_to_list (gpointer key, + gpointer value, + gpointer callback_data) +{ + GList **list; + + list = callback_data; + *list = g_list_prepend (*list, g_strdup (key)); +} + +static GList * +istr_set_get_as_list (GHashTable *table) +{ + GList *list; + + list = NULL; + g_hash_table_foreach (table, add_istr_to_list, &list); + return list; +} + +static void +istr_set_destroy (GHashTable *table) +{ + g_hash_table_destroy (table); +} + +static void +request_counter_add_request (RequestCounter counter, + Request request) +{ + guint i; + + for (i = 0; i < REQUEST_TYPE_LAST; i++) + { + if (REQUEST_WANTS_TYPE (request, i)) + { + counter[i]++; + } + } +} + +static void +request_counter_remove_request (RequestCounter counter, + Request request) +{ + guint i; + + for (i = 0; i < REQUEST_TYPE_LAST; i++) + { + if (REQUEST_WANTS_TYPE (request, i)) + { + counter[i]--; + } + } +} + +#if 0 +static void +nautilus_directory_verify_request_counts (NautilusDirectory *directory) +{ + GList *l; + RequestCounter counters; + int i; + gboolean fail; + GHashTableIter monitor_iter; + gpointer value; + + fail = FALSE; + for (i = 0; i < REQUEST_TYPE_LAST; i++) + { + counters[i] = 0; + } + g_hash_table_iter_init (&monitor_iter, directory->details->monitor_table); + while (g_hash_table_iter_next (&monitor_iter, NULL, &value)) + { + for (l = value; l; l = l->next) + { + Monitor *monitor = l->data; + request_counter_add_request (counters, monitor->request); + } + } + for (i = 0; i < REQUEST_TYPE_LAST; i++) + { + if (counters[i] != directory->details->monitor_counters[i]) + { + g_warning ("monitor counter for %i is wrong, expecting %d but found %d", + i, counters[i], directory->details->monitor_counters[i]); + fail = TRUE; + } + } + for (i = 0; i < REQUEST_TYPE_LAST; i++) + { + counters[i] = 0; + } + for (l = directory->details->call_when_ready_list; l != NULL; l = l->next) + { + ReadyCallback *callback = l->data; + request_counter_add_request (counters, callback->request); + } + for (i = 0; i < REQUEST_TYPE_LAST; i++) + { + if (counters[i] != directory->details->call_when_ready_counters[i]) + { + g_warning ("call when ready counter for %i is wrong, expecting %d but found %d", + i, counters[i], directory->details->call_when_ready_counters[i]); + fail = TRUE; + } + } + g_assert (!fail); +} +#endif + +/* Start a job. This is really just a way of limiting the number of + * async. requests that we issue at any given time. Without this, the + * number of requests is unbounded. + */ +static gboolean +async_job_start (NautilusDirectory *directory, + const char *job) +{ +#ifdef DEBUG_ASYNC_JOBS + char *key; +#endif + + DEBUG ("starting %s in %p", job, directory->details->location); + + g_assert (async_job_count >= 0); + g_assert (async_job_count <= MAX_ASYNC_JOBS); + + if (async_job_count >= MAX_ASYNC_JOBS) + { + if (waiting_directories == NULL) + { + waiting_directories = g_hash_table_new (NULL, NULL); + } + + g_hash_table_insert (waiting_directories, + directory, + directory); + + return FALSE; + } + +#ifdef DEBUG_ASYNC_JOBS + { + char *uri; + if (async_jobs == NULL) + { + async_jobs = g_hash_table_new (g_str_hash, g_str_equal); + } + uri = nautilus_directory_get_uri (directory); + key = g_strconcat (uri, ": ", job, NULL); + if (g_hash_table_lookup (async_jobs, key) != NULL) + { + g_warning ("same job twice: %s in %s", + job, uri); + } + g_free (uri); + g_hash_table_insert (async_jobs, key, directory); + } +#endif + + async_job_count += 1; + return TRUE; +} + +/* End a job. */ +static void +async_job_end (NautilusDirectory *directory, + const char *job) +{ +#ifdef DEBUG_ASYNC_JOBS + char *key; + gpointer table_key, value; +#endif + + DEBUG ("stopping %s in %p", job, directory->details->location); + + g_assert (async_job_count > 0); + +#ifdef DEBUG_ASYNC_JOBS + { + char *uri; + uri = nautilus_directory_get_uri (directory); + g_assert (async_jobs != NULL); + key = g_strconcat (uri, ": ", job, NULL); + if (!g_hash_table_lookup_extended (async_jobs, key, &table_key, &value)) + { + g_warning ("ending job we didn't start: %s in %s", + job, uri); + } + else + { + g_hash_table_remove (async_jobs, key); + g_free (table_key); + } + g_free (uri); + g_free (key); + } +#endif + + async_job_count -= 1; +} + +/* Helper to get one value from a hash table. */ +static void +get_one_value_callback (gpointer key, + gpointer value, + gpointer callback_data) +{ + gpointer *returned_value; + + returned_value = callback_data; + *returned_value = value; +} + +/* return a single value from a hash table. */ +static gpointer +get_one_value (GHashTable *table) +{ + gpointer value; + + value = NULL; + if (table != NULL) + { + g_hash_table_foreach (table, get_one_value_callback, &value); + } + return value; +} + +/* Wake up directories that are "blocked" as long as there are job + * slots available. + */ +static void +async_job_wake_up (void) +{ + static gboolean already_waking_up = FALSE; + gpointer value; + + g_assert (async_job_count >= 0); + g_assert (async_job_count <= MAX_ASYNC_JOBS); + + if (already_waking_up) + { + return; + } + + already_waking_up = TRUE; + while (async_job_count < MAX_ASYNC_JOBS) + { + value = get_one_value (waiting_directories); + if (value == NULL) + { + break; + } + g_hash_table_remove (waiting_directories, value); + nautilus_directory_async_state_changed + (NAUTILUS_DIRECTORY (value)); + } + already_waking_up = FALSE; +} + +static void +directory_count_cancel (NautilusDirectory *directory) +{ + if (directory->details->count_in_progress != NULL) + { + g_cancellable_cancel (directory->details->count_in_progress->cancellable); + directory->details->count_in_progress = NULL; + } +} + +static void +deep_count_cancel (NautilusDirectory *directory) +{ + if (directory->details->deep_count_in_progress != NULL) + { + g_assert (NAUTILUS_IS_FILE (directory->details->deep_count_file)); + + g_cancellable_cancel (directory->details->deep_count_in_progress->cancellable); + + directory->details->deep_count_file->details->deep_counts_status = NAUTILUS_REQUEST_NOT_STARTED; + + directory->details->deep_count_in_progress->directory = NULL; + directory->details->deep_count_in_progress = NULL; + directory->details->deep_count_file = NULL; + + async_job_end (directory, "deep count"); + } +} + +static void +mime_list_cancel (NautilusDirectory *directory) +{ + if (directory->details->mime_list_in_progress != NULL) + { + g_cancellable_cancel (directory->details->mime_list_in_progress->cancellable); + } +} + +static void +thumbnail_cancel (NautilusDirectory *directory) +{ + if (directory->details->thumbnail_state != NULL) + { + g_cancellable_cancel (directory->details->thumbnail_state->cancellable); + directory->details->thumbnail_state->directory = NULL; + directory->details->thumbnail_state = NULL; + async_job_end (directory, "thumbnail"); + } +} + +static void +mount_cancel (NautilusDirectory *directory) +{ + if (directory->details->mount_state != NULL) + { + g_cancellable_cancel (directory->details->mount_state->cancellable); + directory->details->mount_state->directory = NULL; + directory->details->mount_state = NULL; + async_job_end (directory, "mount"); + } +} + +static void +file_info_cancel (NautilusDirectory *directory) +{ + if (directory->details->get_info_in_progress != NULL) + { + g_cancellable_cancel (directory->details->get_info_in_progress->cancellable); + directory->details->get_info_in_progress->directory = NULL; + directory->details->get_info_in_progress = NULL; + directory->details->get_info_file = NULL; + + async_job_end (directory, "file info"); + } +} + +static void +new_files_cancel (NautilusDirectory *directory) +{ + GList *l; + NewFilesState *state; + + if (directory->details->new_files_in_progress != NULL) + { + for (l = directory->details->new_files_in_progress; l != NULL; l = l->next) + { + state = l->data; + g_cancellable_cancel (state->cancellable); + state->directory = NULL; + } + g_list_free (directory->details->new_files_in_progress); + directory->details->new_files_in_progress = NULL; + } +} + +static int +monitor_key_compare (gconstpointer a, + gconstpointer data) +{ + const Monitor *monitor; + const Monitor *compare_monitor; + + monitor = a; + compare_monitor = data; + + if (monitor->client < compare_monitor->client) + { + return -1; + } + if (monitor->client > compare_monitor->client) + { + return +1; + } + + if (monitor->file < compare_monitor->file) + { + return -1; + } + if (monitor->file > compare_monitor->file) + { + return +1; + } + + return 0; +} + +static Monitor * +find_monitor (NautilusDirectory *directory, + NautilusFile *file, + gconstpointer client) +{ + GList *l; + + l = g_hash_table_lookup (directory->details->monitor_table, file); + + if (l) + { + Monitor key = {}; + key.client = client; + key.file = file; + + l = g_list_find_custom (l, &key, monitor_key_compare); + return l ? l->data : NULL; + } + + return NULL; +} + +static gboolean +insert_new_monitor (NautilusDirectory *directory, + Monitor *monitor) +{ + GList *list; + + if (find_monitor (directory, monitor->file, monitor->client) != NULL) + { + return FALSE; + } + + list = g_hash_table_lookup (directory->details->monitor_table, monitor->file); + if (list == NULL) + { + list = g_list_append (list, monitor); + g_hash_table_insert (directory->details->monitor_table, + monitor->file, + list); + } + else + { + list = g_list_append (list, monitor); + } + + request_counter_add_request (directory->details->monitor_counters, + monitor->request); + return TRUE; +} + +static Monitor * +remove_monitor_from_table (NautilusDirectory *directory, + NautilusFile *file, + gconstpointer client) +{ + GList *list, *l, *new_list; + Monitor *monitor = NULL; + + list = g_hash_table_lookup (directory->details->monitor_table, file); + if (list) + { + Monitor key = {}; + key.client = client; + key.file = file; + + l = g_list_find_custom (list, &key, monitor_key_compare); + monitor = l ? l->data : NULL; + } + + if (monitor != NULL) + { + new_list = g_list_delete_link (list, l); + if (new_list == NULL) + { + g_hash_table_remove (directory->details->monitor_table, file); + } + else + { + g_hash_table_replace (directory->details->monitor_table, file, new_list); + } + } + + return monitor; +} + +static void +remove_monitor (NautilusDirectory *directory, + NautilusFile *file, + gconstpointer client) +{ + Monitor *monitor; + + monitor = remove_monitor_from_table (directory, file, client); + + if (monitor != NULL) + { + request_counter_remove_request (directory->details->monitor_counters, + monitor->request); + g_free (monitor); + } +} + +Request +nautilus_directory_set_up_request (NautilusFileAttributes file_attributes) +{ + Request request; + + request = 0; + + if ((file_attributes & NAUTILUS_FILE_ATTRIBUTE_DIRECTORY_ITEM_COUNT) != 0) + { + REQUEST_SET_TYPE (request, REQUEST_DIRECTORY_COUNT); + } + + if ((file_attributes & NAUTILUS_FILE_ATTRIBUTE_DEEP_COUNTS) != 0) + { + REQUEST_SET_TYPE (request, REQUEST_DEEP_COUNT); + } + + if ((file_attributes & NAUTILUS_FILE_ATTRIBUTE_DIRECTORY_ITEM_MIME_TYPES) != 0) + { + REQUEST_SET_TYPE (request, REQUEST_MIME_LIST); + } + if ((file_attributes & NAUTILUS_FILE_ATTRIBUTE_INFO) != 0) + { + REQUEST_SET_TYPE (request, REQUEST_FILE_INFO); + } + + if ((file_attributes & NAUTILUS_FILE_ATTRIBUTE_EXTENSION_INFO) != 0) + { + REQUEST_SET_TYPE (request, REQUEST_EXTENSION_INFO); + } + + if (file_attributes & NAUTILUS_FILE_ATTRIBUTE_THUMBNAIL) + { + REQUEST_SET_TYPE (request, REQUEST_THUMBNAIL); + REQUEST_SET_TYPE (request, REQUEST_FILE_INFO); + } + + if (file_attributes & NAUTILUS_FILE_ATTRIBUTE_MOUNT) + { + REQUEST_SET_TYPE (request, REQUEST_MOUNT); + REQUEST_SET_TYPE (request, REQUEST_FILE_INFO); + } + + if (file_attributes & NAUTILUS_FILE_ATTRIBUTE_FILESYSTEM_INFO) + { + REQUEST_SET_TYPE (request, REQUEST_FILESYSTEM_INFO); + } + + return request; +} + +static void +mime_db_changed_callback (GObject *ignore, + NautilusDirectory *dir) +{ + NautilusFileAttributes attrs; + + g_assert (dir != NULL); + g_assert (dir->details != NULL); + + attrs = NAUTILUS_FILE_ATTRIBUTE_INFO | + NAUTILUS_FILE_ATTRIBUTE_DIRECTORY_ITEM_MIME_TYPES; + + nautilus_directory_force_reload_internal (dir, attrs); +} + +void +nautilus_directory_monitor_add_internal (NautilusDirectory *directory, + NautilusFile *file, + gconstpointer client, + gboolean monitor_hidden_files, + NautilusFileAttributes file_attributes, + NautilusDirectoryCallback callback, + gpointer callback_data) +{ + Monitor *monitor; + GList *file_list; + char *file_uri = NULL; + char *dir_uri = NULL; + + g_assert (NAUTILUS_IS_DIRECTORY (directory)); + + if (file != NULL) + { + file_uri = nautilus_file_get_uri (file); + } + if (directory != NULL) + { + dir_uri = nautilus_directory_get_uri (directory); + } + nautilus_profile_start ("uri %s file-uri %s client %p", dir_uri, file_uri, client); + g_free (dir_uri); + g_free (file_uri); + + /* Replace any current monitor for this client/file pair. */ + remove_monitor (directory, file, client); + + /* Add the new monitor. */ + monitor = g_new (Monitor, 1); + monitor->file = file; + monitor->monitor_hidden_files = monitor_hidden_files; + monitor->client = client; + monitor->request = nautilus_directory_set_up_request (file_attributes); + + if (file == NULL) + { + REQUEST_SET_TYPE (monitor->request, REQUEST_FILE_LIST); + } + + insert_new_monitor (directory, monitor); + + if (callback != NULL) + { + file_list = nautilus_directory_get_file_list (directory); + (*callback)(directory, file_list, callback_data); + nautilus_file_list_free (file_list); + } + + /* Start the "real" monitoring (FAM or whatever). */ + /* We always monitor the whole directory since in practice + * nautilus almost always shows the whole directory anyway, and + * it allows us to avoid one file monitor per file in a directory. + */ + if (directory->details->monitor == NULL) + { + directory->details->monitor = nautilus_monitor_directory (directory->details->location); + } + + + if (REQUEST_WANTS_TYPE (monitor->request, REQUEST_FILE_INFO) && + directory->details->mime_db_monitor == 0) + { + directory->details->mime_db_monitor = + g_signal_connect_object (nautilus_signaller_get_current (), + "mime-data-changed", + G_CALLBACK (mime_db_changed_callback), directory, 0); + } + + /* Put the monitor file or all the files on the work queue. */ + if (file != NULL) + { + nautilus_directory_add_file_to_work_queue (directory, file); + } + else + { + add_all_files_to_work_queue (directory); + } + + /* Kick off I/O. */ + nautilus_directory_async_state_changed (directory); + nautilus_profile_end (NULL); +} + +static void +set_file_unconfirmed (NautilusFile *file, + gboolean unconfirmed) +{ + NautilusDirectory *directory; + + g_assert (NAUTILUS_IS_FILE (file)); + g_assert (unconfirmed == FALSE || unconfirmed == TRUE); + + if (file->details->unconfirmed == unconfirmed) + { + return; + } + file->details->unconfirmed = unconfirmed; + + directory = file->details->directory; + if (unconfirmed) + { + directory->details->confirmed_file_count--; + } + else + { + directory->details->confirmed_file_count++; + } +} + +static gboolean show_hidden_files = TRUE; + +static void +show_hidden_files_changed_callback (gpointer callback_data) +{ + show_hidden_files = g_settings_get_boolean (gtk_filechooser_preferences, NAUTILUS_PREFERENCES_SHOW_HIDDEN_FILES); +} + +static gboolean +should_skip_file (NautilusDirectory *directory, + GFileInfo *info) +{ + static gboolean show_hidden_files_changed_callback_installed = FALSE; + + /* Add the callback once for the life of our process */ + if (!show_hidden_files_changed_callback_installed) + { + g_signal_connect_swapped (gtk_filechooser_preferences, + "changed::" NAUTILUS_PREFERENCES_SHOW_HIDDEN_FILES, + G_CALLBACK (show_hidden_files_changed_callback), + NULL); + + show_hidden_files_changed_callback_installed = TRUE; + + /* Peek for the first time */ + show_hidden_files_changed_callback (NULL); + } + + if (!show_hidden_files && + (g_file_info_get_is_hidden (info) || + g_file_info_get_is_backup (info))) + { + return TRUE; + } + + return FALSE; +} + +static void +notify_files_changed_while_being_added (NautilusDirectory *directory) +{ + if (directory->details->files_changed_while_adding == NULL) + { + return; + } + + directory->details->files_changed_while_adding = + g_list_reverse (directory->details->files_changed_while_adding); + + nautilus_directory_notify_files_changed (directory->details->files_changed_while_adding); + + g_clear_list (&directory->details->files_changed_while_adding, g_object_unref); +} + +static gboolean +dequeue_pending_idle_callback (gpointer callback_data) +{ + NautilusDirectory *directory; + GList *pending_file_info; + GList *node, *next; + NautilusFile *file; + GList *changed_files, *added_files; + GFileInfo *file_info; + const char *mimetype, *name; + DirectoryLoadState *dir_load_state; + + directory = NAUTILUS_DIRECTORY (callback_data); + + nautilus_directory_ref (directory); + + nautilus_profile_start ("nitems %d", g_list_length (directory->details->pending_file_info)); + + directory->details->dequeue_pending_idle_id = 0; + + /* Handle the files in the order we saw them. */ + pending_file_info = g_list_reverse (directory->details->pending_file_info); + directory->details->pending_file_info = NULL; + + /* If we are no longer monitoring, then throw away these. */ + if (!nautilus_directory_is_file_list_monitored (directory)) + { + nautilus_directory_async_state_changed (directory); + goto drain; + } + + added_files = NULL; + changed_files = NULL; + + dir_load_state = directory->details->directory_load_in_progress; + + /* Build a list of NautilusFile objects. */ + for (node = pending_file_info; node != NULL; node = node->next) + { + file_info = node->data; + + name = g_file_info_get_name (file_info); + + /* Update the file count. */ + /* FIXME bugzilla.gnome.org 45063: This could count a + * file twice if we get it from both load_directory + * and from new_files_callback. Not too hard to fix by + * moving this into the actual callback instead of + * waiting for the idle function. + */ + if (dir_load_state && + !should_skip_file (directory, file_info)) + { + dir_load_state->load_file_count += 1; + + /* Add the MIME type to the set. */ + mimetype = g_file_info_get_content_type (file_info); + if (mimetype != NULL) + { + istr_set_insert (dir_load_state->load_mime_list_hash, + mimetype); + } + } + + /* check if the file already exists */ + file = nautilus_directory_find_file_by_name (directory, name); + if (file != NULL) + { + /* file already exists in dir, check if we still need to + * emit file_added or if it changed */ + set_file_unconfirmed (file, FALSE); + if (!file->details->is_added) + { + /* We consider this newly added even if its in the list. + * This can happen if someone called nautilus_file_get_by_uri() + * on a file in the folder before the add signal was + * emitted */ + nautilus_file_ref (file); + file->details->is_added = TRUE; + added_files = g_list_prepend (added_files, file); + } + else if (nautilus_file_update_info (file, file_info)) + { + /* File changed, notify about the change. */ + nautilus_file_ref (file); + changed_files = g_list_prepend (changed_files, file); + } + } + else + { + /* new file, create a nautilus file object and add it to the list */ + file = nautilus_file_new_from_info (directory, file_info); + nautilus_directory_add_file (directory, file); + file->details->is_added = TRUE; + added_files = g_list_prepend (added_files, file); + } + } + + /* If we are done loading, then we assume that any unconfirmed + * files are gone. + */ + if (directory->details->directory_loaded) + { + for (node = directory->details->file_list; + node != NULL; node = next) + { + file = NAUTILUS_FILE (node->data); + next = node->next; + + if (file->details->unconfirmed) + { + nautilus_file_ref (file); + changed_files = g_list_prepend (changed_files, file); + + nautilus_file_mark_gone (file); + } + } + } + + /* Send the changed and added signals. */ + nautilus_directory_emit_change_signals (directory, changed_files); + nautilus_file_list_free (changed_files); + nautilus_directory_emit_files_added (directory, added_files); + nautilus_file_list_free (added_files); + + if (directory->details->directory_loaded && + !directory->details->directory_loaded_sent_notification) + { + /* Send the done_loading signal. */ + nautilus_directory_emit_done_loading (directory); + + if (dir_load_state) + { + file = dir_load_state->load_directory_file; + + file->details->directory_count = dir_load_state->load_file_count; + file->details->directory_count_is_up_to_date = TRUE; + file->details->got_directory_count = TRUE; + + file->details->got_mime_list = TRUE; + file->details->mime_list_is_up_to_date = TRUE; + g_list_free_full (file->details->mime_list, g_free); + file->details->mime_list = istr_set_get_as_list + (dir_load_state->load_mime_list_hash); + + nautilus_file_changed (file); + } + + nautilus_directory_async_state_changed (directory); + + directory->details->directory_loaded_sent_notification = TRUE; + } + + /* Process changes received for files while they were still being added. + * See Bug 703179 and issue #1576 for a situation this happens. */ + notify_files_changed_while_being_added (directory); + +drain: + g_list_free_full (pending_file_info, g_object_unref); + + /* Get the state machine running again. */ + nautilus_directory_async_state_changed (directory); + + nautilus_profile_end (NULL); + + nautilus_directory_unref (directory); + return FALSE; +} + +void +nautilus_directory_schedule_dequeue_pending (NautilusDirectory *directory) +{ + if (directory->details->dequeue_pending_idle_id == 0) + { + directory->details->dequeue_pending_idle_id + = g_idle_add (dequeue_pending_idle_callback, directory); + } +} + +static void +directory_load_one (NautilusDirectory *directory, + GFileInfo *info) +{ + if (info == NULL) + { + return; + } + + if (g_file_info_get_name (info) == NULL) + { + char *uri; + + uri = nautilus_directory_get_uri (directory); + g_warning ("Got GFileInfo with NULL name in %s, ignoring. This shouldn't happen unless the gvfs backend is broken.\n", uri); + g_free (uri); + + return; + } + + /* Arrange for the "loading" part of the work. */ + g_object_ref (info); + directory->details->pending_file_info + = g_list_prepend (directory->details->pending_file_info, info); + nautilus_directory_schedule_dequeue_pending (directory); +} + +static void +directory_load_cancel (NautilusDirectory *directory) +{ + NautilusFile *file; + DirectoryLoadState *state; + + state = directory->details->directory_load_in_progress; + if (state != NULL) + { + file = state->load_directory_file; + file->details->loading_directory = FALSE; + if (file->details->directory != directory) + { + nautilus_directory_async_state_changed (file->details->directory); + } + + g_cancellable_cancel (state->cancellable); + state->directory = NULL; + directory->details->directory_load_in_progress = NULL; + async_job_end (directory, "file list"); + } +} + +static void +file_list_cancel (NautilusDirectory *directory) +{ + directory_load_cancel (directory); + + if (directory->details->dequeue_pending_idle_id != 0) + { + g_source_remove (directory->details->dequeue_pending_idle_id); + directory->details->dequeue_pending_idle_id = 0; + } + + if (directory->details->pending_file_info != NULL) + { + g_list_free_full (directory->details->pending_file_info, g_object_unref); + directory->details->pending_file_info = NULL; + } +} + +static void +directory_load_done (NautilusDirectory *directory, + GError *error) +{ + GList *node; + + nautilus_profile_start (NULL); + g_object_ref (directory); + + directory->details->directory_loaded = TRUE; + directory->details->directory_loaded_sent_notification = FALSE; + + if (error != NULL) + { + /* The load did not complete successfully. This means + * we don't know the status of the files in this directory. + * We clear the unconfirmed bit on each file here so that + * they won't be marked "gone" later -- we don't know enough + * about them to know whether they are really gone. + */ + for (node = directory->details->file_list; + node != NULL; node = node->next) + { + set_file_unconfirmed (NAUTILUS_FILE (node->data), FALSE); + } + + nautilus_directory_emit_load_error (directory, error); + } + + /* Call the idle function right away. */ + if (directory->details->dequeue_pending_idle_id != 0) + { + g_source_remove (directory->details->dequeue_pending_idle_id); + } + dequeue_pending_idle_callback (directory); + + directory_load_cancel (directory); + + g_object_unref (directory); + nautilus_profile_end (NULL); +} + +void +nautilus_directory_monitor_remove_internal (NautilusDirectory *directory, + NautilusFile *file, + gconstpointer client) +{ + g_assert (NAUTILUS_IS_DIRECTORY (directory)); + g_assert (file == NULL || NAUTILUS_IS_FILE (file)); + g_assert (client != NULL); + + remove_monitor (directory, file, client); + + if (directory->details->monitor != NULL + && g_hash_table_size (directory->details->monitor_table) == 0) + { + nautilus_monitor_cancel (directory->details->monitor); + directory->details->monitor = NULL; + } + + /* XXX - do we need to remove anything from the work queue? */ + + nautilus_directory_async_state_changed (directory); +} + +FileMonitors * +nautilus_directory_remove_file_monitors (NautilusDirectory *directory, + NautilusFile *file) +{ + GList *result, *node; + Monitor *monitor; + + g_assert (NAUTILUS_IS_DIRECTORY (directory)); + g_assert (NAUTILUS_IS_FILE (file)); + g_assert (file->details->directory == directory); + + result = g_hash_table_lookup (directory->details->monitor_table, file); + + if (result != NULL) + { + g_hash_table_remove (directory->details->monitor_table, file); + + for (node = result; node; node = node->next) + { + monitor = node->data; + request_counter_remove_request (directory->details->monitor_counters, + monitor->request); + } + result = g_list_reverse (result); + } + + /* XXX - do we need to remove anything from the work queue? */ + + nautilus_directory_async_state_changed (directory); + + return (FileMonitors *) result; +} + +void +nautilus_directory_add_file_monitors (NautilusDirectory *directory, + NautilusFile *file, + FileMonitors *monitors) +{ + GList *l; + Monitor *monitor; + + g_assert (NAUTILUS_IS_DIRECTORY (directory)); + g_assert (NAUTILUS_IS_FILE (file)); + g_assert (file->details->directory == directory); + + if (monitors == NULL) + { + return; + } + + for (l = (GList *) monitors; l != NULL; l = l->next) + { + monitor = l->data; + + remove_monitor (directory, monitor->file, monitor->client); + insert_new_monitor (directory, monitor); + } + + g_list_free ((GList *) monitors); + + nautilus_directory_add_file_to_work_queue (directory, file); + + nautilus_directory_async_state_changed (directory); +} + +static int +ready_callback_key_compare (gconstpointer a, + gconstpointer b) +{ + const ReadyCallback *callback_a, *callback_b; + + callback_a = a; + callback_b = b; + + if (callback_a->file < callback_b->file) + { + return -1; + } + if (callback_a->file > callback_b->file) + { + return 1; + } + if (callback_a->file == NULL) + { + /* ANSI C doesn't allow ordered compares of function pointers, so we cast them to + * normal pointers to make some overly pedantic compilers (*cough* HP-UX *cough*) + * compile this. Of course, on any compiler where ordered function pointers actually + * break this probably won't work, but at least it will compile on platforms where it + * works, but stupid compilers won't let you use it. + */ + if ((void *) callback_a->callback.directory < (void *) callback_b->callback.directory) + { + return -1; + } + if ((void *) callback_a->callback.directory > (void *) callback_b->callback.directory) + { + return 1; + } + } + else + { + if ((void *) callback_a->callback.file < (void *) callback_b->callback.file) + { + return -1; + } + if ((void *) callback_a->callback.file > (void *) callback_b->callback.file) + { + return 1; + } + } + if (callback_a->callback_data < callback_b->callback_data) + { + return -1; + } + if (callback_a->callback_data > callback_b->callback_data) + { + return 1; + } + return 0; +} + +static int +ready_callback_key_compare_only_active (gconstpointer a, + gconstpointer b) +{ + const ReadyCallback *callback_a; + + callback_a = a; + + /* Non active callbacks never match */ + if (!callback_a->active) + { + return -1; + } + + return ready_callback_key_compare (a, b); +} + +static void +ready_callback_call (NautilusDirectory *directory, + const ReadyCallback *callback) +{ + GList *file_list; + + /* Call the callback. */ + if (callback->file != NULL) + { + if (callback->callback.file) + { + (*callback->callback.file)(callback->file, + callback->callback_data); + } + } + else if (callback->callback.directory != NULL) + { + if (directory == NULL || + !REQUEST_WANTS_TYPE (callback->request, REQUEST_FILE_LIST)) + { + file_list = NULL; + } + else + { + file_list = nautilus_directory_get_file_list (directory); + } + + /* Pass back the file list if the user was waiting for it. */ + (*callback->callback.directory)(directory, + file_list, + callback->callback_data); + + nautilus_file_list_free (file_list); + } +} + +void +nautilus_directory_call_when_ready_internal (NautilusDirectory *directory, + NautilusFile *file, + NautilusFileAttributes file_attributes, + gboolean wait_for_file_list, + NautilusDirectoryCallback directory_callback, + NautilusFileCallback file_callback, + gpointer callback_data) +{ + ReadyCallback callback; + + g_assert (directory == NULL || NAUTILUS_IS_DIRECTORY (directory)); + g_assert (file == NULL || NAUTILUS_IS_FILE (file)); + g_assert (file != NULL || directory_callback != NULL); + + /* Construct a callback object. */ + callback.active = TRUE; + callback.file = file; + if (file == NULL) + { + callback.callback.directory = directory_callback; + } + else + { + callback.callback.file = file_callback; + } + callback.callback_data = callback_data; + callback.request = nautilus_directory_set_up_request (file_attributes); + if (wait_for_file_list) + { + REQUEST_SET_TYPE (callback.request, REQUEST_FILE_LIST); + } + + /* Handle the NULL case. */ + if (directory == NULL) + { + ready_callback_call (NULL, &callback); + return; + } + + /* Check if the callback is already there. */ + if (g_list_find_custom (directory->details->call_when_ready_list, + &callback, + ready_callback_key_compare_only_active) != NULL) + { + if (file_callback != NULL && directory_callback != NULL) + { + g_warning ("tried to add a new callback while an old one was pending"); + } + /* NULL callback means, just read it. Conflicts are ok. */ + return; + } + + /* Add the new callback to the list. */ + directory->details->call_when_ready_list = g_list_prepend + (directory->details->call_when_ready_list, + g_memdup (&callback, sizeof (callback))); + request_counter_add_request (directory->details->call_when_ready_counters, + callback.request); + + /* Put the callback file or all the files on the work queue. */ + if (file != NULL) + { + nautilus_directory_add_file_to_work_queue (directory, file); + } + else + { + add_all_files_to_work_queue (directory); + } + + nautilus_directory_async_state_changed (directory); +} + +gboolean +nautilus_directory_check_if_ready_internal (NautilusDirectory *directory, + NautilusFile *file, + NautilusFileAttributes file_attributes) +{ + Request request; + + g_assert (NAUTILUS_IS_DIRECTORY (directory)); + + request = nautilus_directory_set_up_request (file_attributes); + return request_is_satisfied (directory, file, request); +} + +static void +remove_callback_link_keep_data (NautilusDirectory *directory, + GList *link) +{ + ReadyCallback *callback; + + callback = link->data; + + directory->details->call_when_ready_list = g_list_remove_link + (directory->details->call_when_ready_list, link); + + request_counter_remove_request (directory->details->call_when_ready_counters, + callback->request); + g_list_free_1 (link); +} + +static void +remove_callback_link (NautilusDirectory *directory, + GList *link) +{ + ReadyCallback *callback; + + callback = link->data; + remove_callback_link_keep_data (directory, link); + g_free (callback); +} + +void +nautilus_directory_cancel_callback_internal (NautilusDirectory *directory, + NautilusFile *file, + NautilusDirectoryCallback directory_callback, + NautilusFileCallback file_callback, + gpointer callback_data) +{ + ReadyCallback callback; + GList *node; + + if (directory == NULL) + { + return; + } + + g_assert (NAUTILUS_IS_DIRECTORY (directory)); + g_assert (file == NULL || NAUTILUS_IS_FILE (file)); + g_assert (file != NULL || directory_callback != NULL); + g_assert (file == NULL || file_callback != NULL); + + /* Construct a callback object. */ + callback.file = file; + if (file == NULL) + { + callback.callback.directory = directory_callback; + } + else + { + callback.callback.file = file_callback; + } + callback.callback_data = callback_data; + + /* Remove all queued callback from the list (including non-active). */ + do + { + node = g_list_find_custom (directory->details->call_when_ready_list, + &callback, + ready_callback_key_compare); + if (node != NULL) + { + remove_callback_link (directory, node); + + nautilus_directory_async_state_changed (directory); + } + } + while (node != NULL); +} + +static void +new_files_state_unref (NewFilesState *state) +{ + state->count--; + + if (state->count == 0) + { + if (state->directory) + { + state->directory->details->new_files_in_progress = + g_list_remove (state->directory->details->new_files_in_progress, + state); + } + + g_object_unref (state->cancellable); + g_free (state); + } +} + +static void +new_files_callback (GObject *source_object, + GAsyncResult *res, + gpointer user_data) +{ + NautilusDirectory *directory; + GFileInfo *info; + NewFilesState *state; + + state = user_data; + + if (state->directory == NULL) + { + /* Operation was cancelled. Bail out */ + new_files_state_unref (state); + return; + } + + directory = nautilus_directory_ref (state->directory); + + /* Queue up the new file. */ + info = g_file_query_info_finish (G_FILE (source_object), res, NULL); + if (info != NULL) + { + directory_load_one (directory, info); + g_object_unref (info); + } + + new_files_state_unref (state); + + nautilus_directory_unref (directory); +} + +void +nautilus_directory_get_info_for_new_files (NautilusDirectory *directory, + GList *location_list) +{ + NewFilesState *state; + GFile *location; + GList *l; + + if (location_list == NULL) + { + return; + } + + state = g_new (NewFilesState, 1); + state->directory = directory; + state->cancellable = g_cancellable_new (); + state->count = 0; + + for (l = location_list; l != NULL; l = l->next) + { + location = l->data; + + state->count++; + + g_file_query_info_async (location, + NAUTILUS_FILE_DEFAULT_ATTRIBUTES, + 0, + G_PRIORITY_DEFAULT, + state->cancellable, + new_files_callback, state); + } + + directory->details->new_files_in_progress + = g_list_prepend (directory->details->new_files_in_progress, + state); +} + +void +nautilus_async_destroying_file (NautilusFile *file) +{ + NautilusDirectory *directory; + gboolean changed; + GList *node, *next; + ReadyCallback *callback; + Monitor *monitor; + + directory = file->details->directory; + changed = FALSE; + + /* Check for callbacks. */ + for (node = directory->details->call_when_ready_list; node != NULL; node = next) + { + next = node->next; + callback = node->data; + + if (callback->file == file) + { + /* Client should have cancelled callback. */ + if (callback->active) + { + g_warning ("destroyed file has call_when_ready pending"); + } + remove_callback_link (directory, node); + changed = TRUE; + } + } + + /* Check for monitors. */ + node = g_hash_table_lookup (directory->details->monitor_table, file); + if (node != NULL) + { + /* Client should have removed monitor earlier. */ + g_warning ("destroyed file still being monitored"); + for (; node; node = next) + { + next = node->next; + monitor = node->data; + + remove_monitor (directory, monitor->file, monitor->client); + } + changed = TRUE; + } + + /* Check if it's a file that's currently being worked on. + * If so, make that NULL so it gets canceled right away. + */ + if (directory->details->count_in_progress != NULL && + directory->details->count_in_progress->count_file == file) + { + directory->details->count_in_progress->count_file = NULL; + changed = TRUE; + } + if (directory->details->deep_count_file == file) + { + directory->details->deep_count_file = NULL; + changed = TRUE; + } + if (directory->details->mime_list_in_progress != NULL && + directory->details->mime_list_in_progress->mime_list_file == file) + { + directory->details->mime_list_in_progress->mime_list_file = NULL; + changed = TRUE; + } + if (directory->details->get_info_file == file) + { + directory->details->get_info_file = NULL; + changed = TRUE; + } + if (directory->details->extension_info_file == file) + { + directory->details->extension_info_file = NULL; + changed = TRUE; + } + + if (directory->details->thumbnail_state != NULL && + directory->details->thumbnail_state->file == file) + { + directory->details->thumbnail_state->file = NULL; + changed = TRUE; + } + + if (directory->details->mount_state != NULL && + directory->details->mount_state->file == file) + { + directory->details->mount_state->file = NULL; + changed = TRUE; + } + + if (directory->details->filesystem_info_state != NULL && + directory->details->filesystem_info_state->file == file) + { + directory->details->filesystem_info_state->file = NULL; + changed = TRUE; + } + + /* Let the directory take care of the rest. */ + if (changed) + { + nautilus_directory_async_state_changed (directory); + } +} + +static gboolean +lacks_directory_count (NautilusFile *file) +{ + return !file->details->directory_count_is_up_to_date + && nautilus_file_should_show_directory_item_count (file); +} + +static gboolean +should_get_directory_count_now (NautilusFile *file) +{ + return lacks_directory_count (file) + && !file->details->loading_directory; +} + +static gboolean +lacks_info (NautilusFile *file) +{ + return !file->details->file_info_is_up_to_date + && !file->details->is_gone; +} + +static gboolean +lacks_filesystem_info (NautilusFile *file) +{ + return !file->details->filesystem_info_is_up_to_date; +} + +static gboolean +lacks_deep_count (NautilusFile *file) +{ + return file->details->deep_counts_status != NAUTILUS_REQUEST_DONE; +} + +static gboolean +lacks_mime_list (NautilusFile *file) +{ + return !file->details->mime_list_is_up_to_date; +} + +static gboolean +should_get_mime_list (NautilusFile *file) +{ + return lacks_mime_list (file) + && !file->details->loading_directory; +} + +static gboolean +lacks_extension_info (NautilusFile *file) +{ + return file->details->pending_info_providers != NULL; +} + +static gboolean +lacks_thumbnail (NautilusFile *file) +{ + return nautilus_file_should_show_thumbnail (file) && + file->details->thumbnail_path != NULL && + !file->details->thumbnail_is_up_to_date; +} + +static gboolean +lacks_mount (NautilusFile *file) +{ + return (!file->details->mount_is_up_to_date && + ( + /* Unix mountpoint, could be a GMount */ + file->details->is_mountpoint || + + /* The toplevel directory of something */ + (file->details->type == G_FILE_TYPE_DIRECTORY && + nautilus_file_is_self_owned (file)) || + + /* Mountable, could be a mountpoint */ + (file->details->type == G_FILE_TYPE_MOUNTABLE) + + ) + ); +} + +static gboolean +has_problem (NautilusDirectory *directory, + NautilusFile *file, + FileCheck problem) +{ + GList *node; + + if (file != NULL) + { + return (*problem)(file); + } + + for (node = directory->details->file_list; node != NULL; node = node->next) + { + if ((*problem)(node->data)) + { + return TRUE; + } + } + + return FALSE; +} + +static gboolean +request_is_satisfied (NautilusDirectory *directory, + NautilusFile *file, + Request request) +{ + if (REQUEST_WANTS_TYPE (request, REQUEST_FILE_LIST) && + !(directory->details->directory_loaded && + directory->details->directory_loaded_sent_notification)) + { + return FALSE; + } + + if (REQUEST_WANTS_TYPE (request, REQUEST_DIRECTORY_COUNT)) + { + if (has_problem (directory, file, lacks_directory_count)) + { + return FALSE; + } + } + + if (REQUEST_WANTS_TYPE (request, REQUEST_FILE_INFO)) + { + if (has_problem (directory, file, lacks_info)) + { + return FALSE; + } + } + + if (REQUEST_WANTS_TYPE (request, REQUEST_FILESYSTEM_INFO)) + { + if (has_problem (directory, file, lacks_filesystem_info)) + { + return FALSE; + } + } + + if (REQUEST_WANTS_TYPE (request, REQUEST_DEEP_COUNT)) + { + if (has_problem (directory, file, lacks_deep_count)) + { + return FALSE; + } + } + + if (REQUEST_WANTS_TYPE (request, REQUEST_THUMBNAIL)) + { + if (has_problem (directory, file, lacks_thumbnail)) + { + return FALSE; + } + } + + if (REQUEST_WANTS_TYPE (request, REQUEST_MOUNT)) + { + if (has_problem (directory, file, lacks_mount)) + { + return FALSE; + } + } + + if (REQUEST_WANTS_TYPE (request, REQUEST_MIME_LIST)) + { + if (has_problem (directory, file, lacks_mime_list)) + { + return FALSE; + } + } + + return TRUE; +} + +static gboolean +call_ready_callbacks_at_idle (gpointer callback_data) +{ + NautilusDirectory *directory; + GList *node, *next; + ReadyCallback *callback; + + directory = NAUTILUS_DIRECTORY (callback_data); + directory->details->call_ready_idle_id = 0; + + nautilus_directory_ref (directory); + + callback = NULL; + while (1) + { + /* Check if any callbacks are non-active and call them if they are. */ + for (node = directory->details->call_when_ready_list; + node != NULL; node = next) + { + next = node->next; + callback = node->data; + if (!callback->active) + { + /* Non-active, remove and call */ + break; + } + } + if (node == NULL) + { + break; + } + + /* Callbacks are one-shots, so remove it now. */ + remove_callback_link_keep_data (directory, node); + + /* Call the callback. */ + ready_callback_call (directory, callback); + g_free (callback); + } + + nautilus_directory_async_state_changed (directory); + + nautilus_directory_unref (directory); + + return FALSE; +} + +static void +schedule_call_ready_callbacks (NautilusDirectory *directory) +{ + if (directory->details->call_ready_idle_id == 0) + { + directory->details->call_ready_idle_id + = g_idle_add (call_ready_callbacks_at_idle, directory); + } +} + +/* Marks all callbacks that are ready as non-active and + * calls them at idle time, unless they are removed + * before then */ +static gboolean +call_ready_callbacks (NautilusDirectory *directory) +{ + gboolean found_any; + GList *node, *next; + ReadyCallback *callback; + + found_any = FALSE; + + /* Check if any callbacks are satisifed and mark them for call them if they are. */ + for (node = directory->details->call_when_ready_list; + node != NULL; node = next) + { + next = node->next; + callback = node->data; + if (callback->active && + request_is_satisfied (directory, callback->file, callback->request)) + { + callback->active = FALSE; + found_any = TRUE; + } + } + + if (found_any) + { + schedule_call_ready_callbacks (directory); + } + + return found_any; +} + +static GList * +lookup_monitors (GHashTable *monitor_table, + NautilusFile *file) +{ + /* To find monitors monitoring all files, use lookup_all_files_monitors. */ + g_return_val_if_fail (file, NULL); + + return g_hash_table_lookup (monitor_table, file); +} + +static GList * +lookup_all_files_monitors (GHashTable *monitor_table) +{ + /* monitor->file == NULL means monitor all files. */ + return g_hash_table_lookup (monitor_table, NULL); +} + +gboolean +nautilus_directory_has_active_request_for_file (NautilusDirectory *directory, + NautilusFile *file) +{ + GList *node; + ReadyCallback *callback; + + for (node = directory->details->call_when_ready_list; + node != NULL; node = node->next) + { + callback = node->data; + if (callback->file == file || + callback->file == NULL) + { + return TRUE; + } + } + + if (lookup_monitors (directory->details->monitor_table, file) != NULL) + { + return TRUE; + } + if (lookup_all_files_monitors (directory->details->monitor_table) != NULL) + { + return TRUE; + } + + return FALSE; +} + + +/* This checks if there's a request for monitoring the file list. */ +gboolean +nautilus_directory_is_anyone_monitoring_file_list (NautilusDirectory *directory) +{ + if (directory->details->call_when_ready_counters[REQUEST_FILE_LIST] > 0) + { + return TRUE; + } + + if (directory->details->monitor_counters[REQUEST_FILE_LIST] > 0) + { + return TRUE; + } + + return FALSE; +} + +/* This checks if the file list being monitored. */ +gboolean +nautilus_directory_is_file_list_monitored (NautilusDirectory *directory) +{ + return directory->details->file_list_monitored; +} + +static void +mark_all_files_unconfirmed (NautilusDirectory *directory) +{ + GList *node; + NautilusFile *file; + + for (node = directory->details->file_list; node != NULL; node = node->next) + { + file = node->data; + set_file_unconfirmed (file, TRUE); + } +} + +static void +directory_load_state_free (DirectoryLoadState *state) +{ + if (state->enumerator) + { + if (!g_file_enumerator_is_closed (state->enumerator)) + { + g_file_enumerator_close_async (state->enumerator, + 0, NULL, NULL, NULL); + } + g_object_unref (state->enumerator); + } + + if (state->load_mime_list_hash != NULL) + { + istr_set_destroy (state->load_mime_list_hash); + } + nautilus_file_unref (state->load_directory_file); + g_object_unref (state->cancellable); + g_free (state); +} + +static void +more_files_callback (GObject *source_object, + GAsyncResult *res, + gpointer user_data) +{ + DirectoryLoadState *state; + NautilusDirectory *directory; + GError *error; + GList *files, *l; + GFileInfo *info; + + state = user_data; + + if (state->directory == NULL) + { + /* Operation was cancelled. Bail out */ + directory_load_state_free (state); + return; + } + + directory = nautilus_directory_ref (state->directory); + + g_assert (directory->details->directory_load_in_progress != NULL); + g_assert (directory->details->directory_load_in_progress == state); + + error = NULL; + files = g_file_enumerator_next_files_finish (state->enumerator, + res, &error); + + for (l = files; l != NULL; l = l->next) + { + info = l->data; + directory_load_one (directory, info); + g_object_unref (info); + } + + if (files == NULL) + { + directory_load_done (directory, error); + directory_load_state_free (state); + } + else + { + g_file_enumerator_next_files_async (state->enumerator, + DIRECTORY_LOAD_ITEMS_PER_CALLBACK, + G_PRIORITY_DEFAULT, + state->cancellable, + more_files_callback, + state); + } + + nautilus_directory_unref (directory); + + if (error) + { + g_error_free (error); + } + + g_list_free (files); +} + +static void +enumerate_children_callback (GObject *source_object, + GAsyncResult *res, + gpointer user_data) +{ + DirectoryLoadState *state; + GFileEnumerator *enumerator; + GError *error; + + state = user_data; + + if (state->directory == NULL) + { + /* Operation was cancelled. Bail out */ + directory_load_state_free (state); + return; + } + + error = NULL; + enumerator = g_file_enumerate_children_finish (G_FILE (source_object), + res, &error); + + if (enumerator == NULL) + { + directory_load_done (state->directory, error); + g_error_free (error); + directory_load_state_free (state); + return; + } + else + { + state->enumerator = enumerator; + g_file_enumerator_next_files_async (state->enumerator, + DIRECTORY_LOAD_ITEMS_PER_CALLBACK, + G_PRIORITY_DEFAULT, + state->cancellable, + more_files_callback, + state); + } +} + + +/* Start monitoring the file list if it isn't already. */ +static void +start_monitoring_file_list (NautilusDirectory *directory) +{ + DirectoryLoadState *state; + + if (!directory->details->file_list_monitored) + { + g_assert (!directory->details->directory_load_in_progress); + directory->details->file_list_monitored = TRUE; + nautilus_file_list_ref (directory->details->file_list); + } + + if (directory->details->directory_loaded || + directory->details->directory_load_in_progress != NULL) + { + return; + } + + if (!async_job_start (directory, "file list")) + { + return; + } + + mark_all_files_unconfirmed (directory); + + state = g_new0 (DirectoryLoadState, 1); + state->directory = directory; + state->cancellable = g_cancellable_new (); + state->load_mime_list_hash = istr_set_new (); + state->load_file_count = 0; + + g_assert (directory->details->location != NULL); + state->load_directory_file = + nautilus_directory_get_corresponding_file (directory); + state->load_directory_file->details->loading_directory = TRUE; + + + DEBUG ("load_directory called to monitor file list of %p", directory->details->location); + + directory->details->directory_load_in_progress = state; + + g_file_enumerate_children_async (directory->details->location, + NAUTILUS_FILE_DEFAULT_ATTRIBUTES, + 0, /* flags */ + G_PRIORITY_DEFAULT, /* prio */ + state->cancellable, + enumerate_children_callback, + state); +} + +/* Stop monitoring the file list if it is being monitored. */ +void +nautilus_directory_stop_monitoring_file_list (NautilusDirectory *directory) +{ + if (!directory->details->file_list_monitored) + { + g_assert (directory->details->directory_load_in_progress == NULL); + return; + } + + directory->details->file_list_monitored = FALSE; + file_list_cancel (directory); + nautilus_file_list_unref (directory->details->file_list); + directory->details->directory_loaded = FALSE; +} + +static void +file_list_start_or_stop (NautilusDirectory *directory) +{ + if (nautilus_directory_is_anyone_monitoring_file_list (directory)) + { + start_monitoring_file_list (directory); + } + else + { + nautilus_directory_stop_monitoring_file_list (directory); + } +} + +void +nautilus_file_invalidate_count_and_mime_list (NautilusFile *file) +{ + NautilusFileAttributes attributes; + + attributes = NAUTILUS_FILE_ATTRIBUTE_DIRECTORY_ITEM_COUNT | + NAUTILUS_FILE_ATTRIBUTE_DIRECTORY_ITEM_MIME_TYPES; + + nautilus_file_invalidate_attributes (file, attributes); +} + + +/* Reset count and mime list. Invalidating deep counts is handled by + * itself elsewhere because it's a relatively heavyweight and + * special-purpose operation (see bug 5863). Also, the shallow count + * needs to be refreshed when filtering changes, but the deep count + * deliberately does not take filtering into account. + */ +void +nautilus_directory_invalidate_count_and_mime_list (NautilusDirectory *directory) +{ + NautilusFile *file; + + file = nautilus_directory_get_existing_corresponding_file (directory); + if (file != NULL) + { + nautilus_file_invalidate_count_and_mime_list (file); + } + + nautilus_file_unref (file); +} + +static void +nautilus_directory_invalidate_file_attributes (NautilusDirectory *directory, + NautilusFileAttributes file_attributes) +{ + GList *node; + + cancel_loading_attributes (directory, file_attributes); + + for (node = directory->details->file_list; node != NULL; node = node->next) + { + nautilus_file_invalidate_attributes_internal (NAUTILUS_FILE (node->data), + file_attributes); + } + + if (directory->details->as_file != NULL) + { + nautilus_file_invalidate_attributes_internal (directory->details->as_file, + file_attributes); + } +} + +void +nautilus_directory_force_reload_internal (NautilusDirectory *directory, + NautilusFileAttributes file_attributes) +{ + nautilus_profile_start (NULL); + + /* invalidate attributes that are getting reloaded for all files */ + nautilus_directory_invalidate_file_attributes (directory, file_attributes); + + /* Start a new directory load. */ + file_list_cancel (directory); + directory->details->directory_loaded = FALSE; + + /* Start a new directory count. */ + nautilus_directory_invalidate_count_and_mime_list (directory); + + add_all_files_to_work_queue (directory); + nautilus_directory_async_state_changed (directory); + + nautilus_profile_end (NULL); +} + +static gboolean +monitor_includes_file (const Monitor *monitor, + NautilusFile *file) +{ + if (monitor->file == file) + { + return TRUE; + } + /* monitor->file == NULL means monitor all files. */ + if (monitor->file != NULL) + { + return FALSE; + } + if (file == file->details->directory->details->as_file) + { + return FALSE; + } + return nautilus_file_should_show (file, + monitor->monitor_hidden_files); +} + +static gboolean +is_wanted_by_monitor (NautilusFile *file, + GList *monitors, + RequestType request_type_wanted) +{ + GList *node; + + for (node = monitors; node; node = node->next) + { + Monitor *monitor = node->data; + if (REQUEST_WANTS_TYPE (monitor->request, request_type_wanted)) + { + if (monitor_includes_file (monitor, file)) + { + return TRUE; + } + } + } + + return FALSE; +} + +static gboolean +is_needy (NautilusFile *file, + FileCheck check_missing, + RequestType request_type_wanted) +{ + NautilusDirectory *directory; + GList *node; + ReadyCallback *callback; + + if (!(*check_missing)(file)) + { + return FALSE; + } + + directory = file->details->directory; + if (directory->details->call_when_ready_counters[request_type_wanted] > 0) + { + for (node = directory->details->call_when_ready_list; + node != NULL; node = node->next) + { + callback = node->data; + if (callback->active && + REQUEST_WANTS_TYPE (callback->request, request_type_wanted)) + { + if (callback->file == file) + { + return TRUE; + } + if (callback->file == NULL + && file != directory->details->as_file) + { + return TRUE; + } + } + } + } + + if (directory->details->monitor_counters[request_type_wanted] > 0) + { + GList *monitors; + + monitors = lookup_monitors (directory->details->monitor_table, file); + if (is_wanted_by_monitor (file, monitors, request_type_wanted)) + { + return TRUE; + } + + monitors = lookup_all_files_monitors (directory->details->monitor_table); + if (is_wanted_by_monitor (file, monitors, request_type_wanted)) + { + return TRUE; + } + } + + return FALSE; +} + +static void +directory_count_stop (NautilusDirectory *directory) +{ + NautilusFile *file; + + if (directory->details->count_in_progress != NULL) + { + file = directory->details->count_in_progress->count_file; + if (file != NULL) + { + g_assert (NAUTILUS_IS_FILE (file)); + g_assert (file->details->directory == directory); + if (is_needy (file, + should_get_directory_count_now, + REQUEST_DIRECTORY_COUNT)) + { + return; + } + } + + /* The count is not wanted, so stop it. */ + directory_count_cancel (directory); + } +} + +static guint +count_non_skipped_files (GList *list) +{ + guint count; + GList *node; + GFileInfo *info; + + count = 0; + for (node = list; node != NULL; node = node->next) + { + info = node->data; + if (!should_skip_file (NULL, info)) + { + count += 1; + } + } + return count; +} + +static void +count_children_done (NautilusDirectory *directory, + NautilusFile *count_file, + gboolean succeeded, + int count) +{ + g_assert (NAUTILUS_IS_FILE (count_file)); + + count_file->details->directory_count_is_up_to_date = TRUE; + + /* Record either a failure or success. */ + if (!succeeded) + { + count_file->details->directory_count_failed = TRUE; + count_file->details->got_directory_count = FALSE; + count_file->details->directory_count = 0; + } + else + { + count_file->details->directory_count_failed = FALSE; + count_file->details->got_directory_count = TRUE; + count_file->details->directory_count = count; + } + directory->details->count_in_progress = NULL; + + /* Send file-changed even if count failed, so interested parties can + * distinguish between unknowable and not-yet-known cases. + */ + nautilus_file_changed (count_file); + + /* Start up the next one. */ + async_job_end (directory, "directory count"); + nautilus_directory_async_state_changed (directory); +} + +static void +directory_count_state_free (DirectoryCountState *state) +{ + if (state->enumerator) + { + if (!g_file_enumerator_is_closed (state->enumerator)) + { + g_file_enumerator_close_async (state->enumerator, + 0, NULL, NULL, NULL); + } + g_object_unref (state->enumerator); + } + g_object_unref (state->cancellable); + nautilus_directory_unref (state->directory); + g_free (state); +} + +static void +count_more_files_callback (GObject *source_object, + GAsyncResult *res, + gpointer user_data) +{ + DirectoryCountState *state; + NautilusDirectory *directory; + GError *error; + GList *files; + + state = user_data; + directory = state->directory; + + if (g_cancellable_is_cancelled (state->cancellable)) + { + /* Operation was cancelled. Bail out */ + + async_job_end (directory, "directory count"); + nautilus_directory_async_state_changed (directory); + + directory_count_state_free (state); + + return; + } + + g_assert (directory->details->count_in_progress != NULL); + g_assert (directory->details->count_in_progress == state); + + error = NULL; + files = g_file_enumerator_next_files_finish (state->enumerator, + res, &error); + + state->file_count += count_non_skipped_files (files); + + if (files == NULL) + { + count_children_done (directory, state->count_file, + TRUE, state->file_count); + directory_count_state_free (state); + } + else + { + g_file_enumerator_next_files_async (state->enumerator, + DIRECTORY_LOAD_ITEMS_PER_CALLBACK, + G_PRIORITY_DEFAULT, + state->cancellable, + count_more_files_callback, + state); + } + + g_list_free_full (files, g_object_unref); + + if (error) + { + g_error_free (error); + } +} + +static void +count_children_callback (GObject *source_object, + GAsyncResult *res, + gpointer user_data) +{ + DirectoryCountState *state; + GFileEnumerator *enumerator; + NautilusDirectory *directory; + GError *error; + + state = user_data; + + if (g_cancellable_is_cancelled (state->cancellable)) + { + /* Operation was cancelled. Bail out */ + directory = state->directory; + + async_job_end (directory, "directory count"); + nautilus_directory_async_state_changed (directory); + + directory_count_state_free (state); + + return; + } + + error = NULL; + enumerator = g_file_enumerate_children_finish (G_FILE (source_object), + res, &error); + + if (enumerator == NULL) + { + count_children_done (state->directory, + state->count_file, + FALSE, 0); + g_error_free (error); + directory_count_state_free (state); + return; + } + else + { + state->enumerator = enumerator; + g_file_enumerator_next_files_async (state->enumerator, + DIRECTORY_LOAD_ITEMS_PER_CALLBACK, + G_PRIORITY_DEFAULT, + state->cancellable, + count_more_files_callback, + state); + } +} + +static void +directory_count_start (NautilusDirectory *directory, + NautilusFile *file, + gboolean *doing_io) +{ + DirectoryCountState *state; + GFile *location; + + if (directory->details->count_in_progress != NULL) + { + *doing_io = TRUE; + return; + } + + if (!is_needy (file, + should_get_directory_count_now, + REQUEST_DIRECTORY_COUNT)) + { + return; + } + *doing_io = TRUE; + + if (!nautilus_file_is_directory (file)) + { + file->details->directory_count_is_up_to_date = TRUE; + file->details->directory_count_failed = FALSE; + file->details->got_directory_count = FALSE; + + nautilus_directory_async_state_changed (directory); + return; + } + + if (!async_job_start (directory, "directory count")) + { + return; + } + + /* Start counting. */ + state = g_new0 (DirectoryCountState, 1); + state->count_file = file; + state->directory = nautilus_directory_ref (directory); + state->cancellable = g_cancellable_new (); + + directory->details->count_in_progress = state; + + location = nautilus_file_get_location (file); + + { + g_autofree char *uri = NULL; + uri = g_file_get_uri (location); + DEBUG ("load_directory called to get shallow file count for %s", uri); + } + + g_file_enumerate_children_async (location, + G_FILE_ATTRIBUTE_STANDARD_NAME "," + G_FILE_ATTRIBUTE_STANDARD_IS_HIDDEN "," + G_FILE_ATTRIBUTE_STANDARD_IS_BACKUP, + G_FILE_QUERY_INFO_NOFOLLOW_SYMLINKS, /* flags */ + G_PRIORITY_DEFAULT, /* prio */ + state->cancellable, + count_children_callback, + state); + g_object_unref (location); +} + +static inline gboolean +seen_inode (DeepCountState *state, + GFileInfo *info) +{ + guint64 inode, inode2; + guint i; + + inode = g_file_info_get_attribute_uint64 (info, G_FILE_ATTRIBUTE_UNIX_INODE); + + if (inode != 0) + { + for (i = 0; i < state->seen_deep_count_inodes->len; i++) + { + inode2 = g_array_index (state->seen_deep_count_inodes, guint64, i); + if (inode == inode2) + { + return TRUE; + } + } + } + + return FALSE; +} + +static inline void +mark_inode_as_seen (DeepCountState *state, + GFileInfo *info) +{ + guint64 inode; + + inode = g_file_info_get_attribute_uint64 (info, G_FILE_ATTRIBUTE_UNIX_INODE); + if (inode != 0) + { + g_array_append_val (state->seen_deep_count_inodes, inode); + } +} + +static void +deep_count_one (DeepCountState *state, + GFileInfo *info) +{ + NautilusFile *file; + GFile *subdir; + gboolean is_seen_inode; + const char *fs_id; + + if (should_skip_file (NULL, info)) + { + return; + } + + is_seen_inode = seen_inode (state, info); + if (!is_seen_inode) + { + mark_inode_as_seen (state, info); + } + + file = state->directory->details->deep_count_file; + + if (g_file_info_get_file_type (info) == G_FILE_TYPE_DIRECTORY) + { + /* Count the directory. */ + file->details->deep_directory_count += 1; + + /* Record the fact that we have to descend into this directory. */ + fs_id = g_file_info_get_attribute_string (info, G_FILE_ATTRIBUTE_ID_FILESYSTEM); + if (g_strcmp0 (fs_id, state->fs_id) == 0) + { + /* only if it is on the same filesystem */ + subdir = g_file_get_child (state->deep_count_location, g_file_info_get_name (info)); + state->deep_count_subdirectories = g_list_prepend + (state->deep_count_subdirectories, subdir); + } + } + else + { + /* Even non-regular files count as files. */ + file->details->deep_file_count += 1; + } + + /* Count the size. */ + if (!is_seen_inode && g_file_info_has_attribute (info, G_FILE_ATTRIBUTE_STANDARD_SIZE)) + { + file->details->deep_size += g_file_info_get_size (info); + } +} + +static void +deep_count_state_free (DeepCountState *state) +{ + if (state->enumerator) + { + if (!g_file_enumerator_is_closed (state->enumerator)) + { + g_file_enumerator_close_async (state->enumerator, + 0, NULL, NULL, NULL); + } + g_object_unref (state->enumerator); + } + g_object_unref (state->cancellable); + if (state->deep_count_location) + { + g_object_unref (state->deep_count_location); + } + g_list_free_full (state->deep_count_subdirectories, g_object_unref); + g_array_free (state->seen_deep_count_inodes, TRUE); + g_free (state->fs_id); + g_free (state); +} + +static void +deep_count_next_dir (DeepCountState *state) +{ + GFile *location; + NautilusFile *file; + NautilusDirectory *directory; + gboolean done; + + directory = state->directory; + + g_object_unref (state->deep_count_location); + state->deep_count_location = NULL; + + done = FALSE; + file = directory->details->deep_count_file; + + if (state->deep_count_subdirectories != NULL) + { + /* Work on a new directory. */ + location = state->deep_count_subdirectories->data; + state->deep_count_subdirectories = g_list_remove + (state->deep_count_subdirectories, location); + deep_count_load (state, location); + g_object_unref (location); + } + else + { + file->details->deep_counts_status = NAUTILUS_REQUEST_DONE; + directory->details->deep_count_file = NULL; + directory->details->deep_count_in_progress = NULL; + deep_count_state_free (state); + done = TRUE; + } + + nautilus_file_updated_deep_count_in_progress (file); + + if (done) + { + nautilus_file_changed (file); + async_job_end (directory, "deep count"); + nautilus_directory_async_state_changed (directory); + } +} + +static void +deep_count_more_files_callback (GObject *source_object, + GAsyncResult *res, + gpointer user_data) +{ + DeepCountState *state; + NautilusDirectory *directory; + GList *files, *l; + GFileInfo *info; + + state = user_data; + + if (state->directory == NULL) + { + /* Operation was cancelled. Bail out */ + deep_count_state_free (state); + return; + } + + directory = nautilus_directory_ref (state->directory); + + g_assert (directory->details->deep_count_in_progress != NULL); + g_assert (directory->details->deep_count_in_progress == state); + + files = g_file_enumerator_next_files_finish (state->enumerator, + res, NULL); + + for (l = files; l != NULL; l = l->next) + { + info = l->data; + deep_count_one (state, info); + g_object_unref (info); + } + + if (files == NULL) + { + g_file_enumerator_close_async (state->enumerator, 0, NULL, NULL, NULL); + g_object_unref (state->enumerator); + state->enumerator = NULL; + + deep_count_next_dir (state); + } + else + { + g_file_enumerator_next_files_async (state->enumerator, + DIRECTORY_LOAD_ITEMS_PER_CALLBACK, + G_PRIORITY_LOW, + state->cancellable, + deep_count_more_files_callback, + state); + } + + g_list_free (files); + + nautilus_directory_unref (directory); +} + +static void +deep_count_callback (GObject *source_object, + GAsyncResult *res, + gpointer user_data) +{ + DeepCountState *state; + GFileEnumerator *enumerator; + NautilusFile *file; + + state = user_data; + + if (state->directory == NULL) + { + /* Operation was cancelled. Bail out */ + deep_count_state_free (state); + return; + } + + file = state->directory->details->deep_count_file; + + enumerator = g_file_enumerate_children_finish (G_FILE (source_object), res, NULL); + + if (enumerator == NULL) + { + file->details->deep_unreadable_count += 1; + + deep_count_next_dir (state); + } + else + { + state->enumerator = enumerator; + g_file_enumerator_next_files_async (state->enumerator, + DIRECTORY_LOAD_ITEMS_PER_CALLBACK, + G_PRIORITY_LOW, + state->cancellable, + deep_count_more_files_callback, + state); + } +} + + +static void +deep_count_load (DeepCountState *state, + GFile *location) +{ + state->deep_count_location = g_object_ref (location); + + DEBUG ("load_directory called to get deep file count for %p", location); + g_file_enumerate_children_async (state->deep_count_location, + G_FILE_ATTRIBUTE_STANDARD_NAME "," + G_FILE_ATTRIBUTE_STANDARD_TYPE "," + G_FILE_ATTRIBUTE_STANDARD_SIZE "," + G_FILE_ATTRIBUTE_STANDARD_IS_HIDDEN "," + G_FILE_ATTRIBUTE_STANDARD_IS_BACKUP "," + G_FILE_ATTRIBUTE_ID_FILESYSTEM "," + G_FILE_ATTRIBUTE_UNIX_INODE, + G_FILE_QUERY_INFO_NOFOLLOW_SYMLINKS, /* flags */ + G_PRIORITY_LOW, /* prio */ + state->cancellable, + deep_count_callback, + state); +} + +static void +deep_count_stop (NautilusDirectory *directory) +{ + NautilusFile *file; + + if (directory->details->deep_count_in_progress != NULL) + { + file = directory->details->deep_count_file; + if (file != NULL) + { + g_assert (NAUTILUS_IS_FILE (file)); + g_assert (file->details->directory == directory); + if (is_needy (file, + lacks_deep_count, + REQUEST_DEEP_COUNT)) + { + return; + } + } + + /* The count is not wanted, so stop it. */ + deep_count_cancel (directory); + } +} + +static void +deep_count_got_info (GObject *source_object, + GAsyncResult *res, + gpointer user_data) +{ + GFileInfo *info; + const char *id; + GFile *file = (GFile *) source_object; + DeepCountState *state = (DeepCountState *) user_data; + + info = g_file_query_info_finish (file, res, NULL); + if (info != NULL) + { + id = g_file_info_get_attribute_string (info, G_FILE_ATTRIBUTE_ID_FILESYSTEM); + state->fs_id = g_strdup (id); + g_object_unref (info); + } + deep_count_load (state, file); +} + +static void +deep_count_start (NautilusDirectory *directory, + NautilusFile *file, + gboolean *doing_io) +{ + GFile *location; + DeepCountState *state; + + if (directory->details->deep_count_in_progress != NULL) + { + *doing_io = TRUE; + return; + } + + if (!is_needy (file, + lacks_deep_count, + REQUEST_DEEP_COUNT)) + { + return; + } + *doing_io = TRUE; + + if (!nautilus_file_is_directory (file)) + { + file->details->deep_counts_status = NAUTILUS_REQUEST_DONE; + + nautilus_directory_async_state_changed (directory); + return; + } + + if (!async_job_start (directory, "deep count")) + { + return; + } + + /* Start counting. */ + file->details->deep_counts_status = NAUTILUS_REQUEST_IN_PROGRESS; + file->details->deep_directory_count = 0; + file->details->deep_file_count = 0; + file->details->deep_unreadable_count = 0; + file->details->deep_size = 0; + directory->details->deep_count_file = file; + + state = g_new0 (DeepCountState, 1); + state->directory = directory; + state->cancellable = g_cancellable_new (); + state->seen_deep_count_inodes = g_array_new (FALSE, TRUE, sizeof (guint64)); + state->fs_id = NULL; + + directory->details->deep_count_in_progress = state; + + location = nautilus_file_get_location (file); + g_file_query_info_async (location, + G_FILE_ATTRIBUTE_ID_FILESYSTEM, + G_FILE_QUERY_INFO_NOFOLLOW_SYMLINKS, + G_PRIORITY_DEFAULT, + NULL, + deep_count_got_info, + state); + g_object_unref (location); +} + +static void +mime_list_stop (NautilusDirectory *directory) +{ + NautilusFile *file; + + if (directory->details->mime_list_in_progress != NULL) + { + file = directory->details->mime_list_in_progress->mime_list_file; + if (file != NULL) + { + g_assert (NAUTILUS_IS_FILE (file)); + g_assert (file->details->directory == directory); + if (is_needy (file, + should_get_mime_list, + REQUEST_MIME_LIST)) + { + return; + } + } + + /* The count is not wanted, so stop it. */ + mime_list_cancel (directory); + } +} + +static void +mime_list_state_free (MimeListState *state) +{ + if (state->enumerator) + { + if (!g_file_enumerator_is_closed (state->enumerator)) + { + g_file_enumerator_close_async (state->enumerator, + 0, NULL, NULL, NULL); + } + g_object_unref (state->enumerator); + } + g_object_unref (state->cancellable); + istr_set_destroy (state->mime_list_hash); + nautilus_directory_unref (state->directory); + g_free (state); +} + + +static void +mime_list_done (MimeListState *state, + gboolean success) +{ + NautilusFile *file; + NautilusDirectory *directory; + + directory = state->directory; + g_assert (directory != NULL); + + file = state->mime_list_file; + + file->details->mime_list_is_up_to_date = TRUE; + g_list_free_full (file->details->mime_list, g_free); + if (success) + { + file->details->mime_list_failed = TRUE; + file->details->mime_list = NULL; + } + else + { + file->details->got_mime_list = TRUE; + file->details->mime_list = istr_set_get_as_list (state->mime_list_hash); + } + directory->details->mime_list_in_progress = NULL; + + /* Send file-changed even if getting the item type list + * failed, so interested parties can distinguish between + * unknowable and not-yet-known cases. + */ + nautilus_file_changed (file); + + /* Start up the next one. */ + async_job_end (directory, "MIME list"); + nautilus_directory_async_state_changed (directory); +} + +static void +mime_list_one (MimeListState *state, + GFileInfo *info) +{ + const char *mime_type; + + if (should_skip_file (NULL, info)) + { + g_object_unref (info); + return; + } + + mime_type = g_file_info_get_content_type (info); + if (mime_type != NULL) + { + istr_set_insert (state->mime_list_hash, mime_type); + } +} + +static void +mime_list_callback (GObject *source_object, + GAsyncResult *res, + gpointer user_data) +{ + MimeListState *state; + NautilusDirectory *directory; + GError *error; + GList *files, *l; + GFileInfo *info; + + state = user_data; + directory = state->directory; + + if (g_cancellable_is_cancelled (state->cancellable)) + { + /* Operation was cancelled. Bail out */ + directory->details->mime_list_in_progress = NULL; + + async_job_end (directory, "MIME list"); + nautilus_directory_async_state_changed (directory); + + mime_list_state_free (state); + + return; + } + + g_assert (directory->details->mime_list_in_progress != NULL); + g_assert (directory->details->mime_list_in_progress == state); + + error = NULL; + files = g_file_enumerator_next_files_finish (state->enumerator, + res, &error); + + for (l = files; l != NULL; l = l->next) + { + info = l->data; + mime_list_one (state, info); + g_object_unref (info); + } + + if (files == NULL) + { + mime_list_done (state, error != NULL); + mime_list_state_free (state); + } + else + { + g_file_enumerator_next_files_async (state->enumerator, + DIRECTORY_LOAD_ITEMS_PER_CALLBACK, + G_PRIORITY_DEFAULT, + state->cancellable, + mime_list_callback, + state); + } + + g_list_free (files); + + if (error) + { + g_error_free (error); + } +} + +static void +list_mime_enum_callback (GObject *source_object, + GAsyncResult *res, + gpointer user_data) +{ + MimeListState *state; + GFileEnumerator *enumerator; + NautilusDirectory *directory; + GError *error; + + state = user_data; + + if (g_cancellable_is_cancelled (state->cancellable)) + { + /* Operation was cancelled. Bail out */ + directory = state->directory; + directory->details->mime_list_in_progress = NULL; + + async_job_end (directory, "MIME list"); + nautilus_directory_async_state_changed (directory); + + mime_list_state_free (state); + + return; + } + + error = NULL; + enumerator = g_file_enumerate_children_finish (G_FILE (source_object), + res, &error); + + if (enumerator == NULL) + { + mime_list_done (state, FALSE); + g_error_free (error); + mime_list_state_free (state); + return; + } + else + { + state->enumerator = enumerator; + g_file_enumerator_next_files_async (state->enumerator, + DIRECTORY_LOAD_ITEMS_PER_CALLBACK, + G_PRIORITY_DEFAULT, + state->cancellable, + mime_list_callback, + state); + } +} + +static void +mime_list_start (NautilusDirectory *directory, + NautilusFile *file, + gboolean *doing_io) +{ + MimeListState *state; + GFile *location; + + mime_list_stop (directory); + + if (directory->details->mime_list_in_progress != NULL) + { + *doing_io = TRUE; + return; + } + + /* Figure out which file to get a mime list for. */ + if (!is_needy (file, + should_get_mime_list, + REQUEST_MIME_LIST)) + { + return; + } + *doing_io = TRUE; + + if (!nautilus_file_is_directory (file)) + { + g_list_free (file->details->mime_list); + file->details->mime_list_failed = FALSE; + file->details->got_mime_list = FALSE; + file->details->mime_list_is_up_to_date = TRUE; + + nautilus_directory_async_state_changed (directory); + return; + } + + if (!async_job_start (directory, "MIME list")) + { + return; + } + + + state = g_new0 (MimeListState, 1); + state->mime_list_file = file; + state->directory = nautilus_directory_ref (directory); + state->cancellable = g_cancellable_new (); + state->mime_list_hash = istr_set_new (); + + directory->details->mime_list_in_progress = state; + + location = nautilus_file_get_location (file); + + { + g_autofree char *uri = NULL; + uri = g_file_get_uri (location); + DEBUG ("load_directory called to get MIME list of %s", uri); + } + + g_file_enumerate_children_async (location, + G_FILE_ATTRIBUTE_STANDARD_CONTENT_TYPE, + 0, /* flags */ + G_PRIORITY_LOW, /* prio */ + state->cancellable, + list_mime_enum_callback, + state); + g_object_unref (location); +} + +static void +get_info_state_free (GetInfoState *state) +{ + g_object_unref (state->cancellable); + g_free (state); +} + +static void +query_info_callback (GObject *source_object, + GAsyncResult *res, + gpointer user_data) +{ + NautilusDirectory *directory; + NautilusFile *get_info_file; + GFileInfo *info; + GetInfoState *state; + GError *error; + + state = user_data; + + if (state->directory == NULL) + { + /* Operation was cancelled. Bail out */ + get_info_state_free (state); + return; + } + + directory = nautilus_directory_ref (state->directory); + + get_info_file = directory->details->get_info_file; + g_assert (NAUTILUS_IS_FILE (get_info_file)); + + directory->details->get_info_file = NULL; + directory->details->get_info_in_progress = NULL; + + /* ref here because we might be removing the last ref when we + * mark the file gone below, but we need to keep a ref at + * least long enough to send the change notification. + */ + nautilus_file_ref (get_info_file); + + error = NULL; + info = g_file_query_info_finish (G_FILE (source_object), res, &error); + + if (info == NULL) + { + if (error->domain == G_IO_ERROR && error->code == G_IO_ERROR_NOT_FOUND) + { + /* mark file as gone */ + nautilus_file_mark_gone (get_info_file); + } + get_info_file->details->file_info_is_up_to_date = TRUE; + nautilus_file_clear_info (get_info_file); + get_info_file->details->get_info_failed = TRUE; + get_info_file->details->get_info_error = error; + } + else + { + nautilus_file_update_info (get_info_file, info); + g_object_unref (info); + } + + nautilus_file_changed (get_info_file); + nautilus_file_unref (get_info_file); + + async_job_end (directory, "file info"); + nautilus_directory_async_state_changed (directory); + + nautilus_directory_unref (directory); + + get_info_state_free (state); +} + +static void +file_info_stop (NautilusDirectory *directory) +{ + NautilusFile *file; + + if (directory->details->get_info_in_progress != NULL) + { + file = directory->details->get_info_file; + if (file != NULL) + { + g_assert (NAUTILUS_IS_FILE (file)); + g_assert (file->details->directory == directory); + if (is_needy (file, lacks_info, REQUEST_FILE_INFO)) + { + return; + } + } + + /* The info is not wanted, so stop it. */ + file_info_cancel (directory); + } +} + +static void +file_info_start (NautilusDirectory *directory, + NautilusFile *file, + gboolean *doing_io) +{ + GFile *location; + GetInfoState *state; + + file_info_stop (directory); + + if (directory->details->get_info_in_progress != NULL) + { + *doing_io = TRUE; + return; + } + + if (!is_needy (file, lacks_info, REQUEST_FILE_INFO)) + { + return; + } + *doing_io = TRUE; + + if (!async_job_start (directory, "file info")) + { + return; + } + + directory->details->get_info_file = file; + file->details->get_info_failed = FALSE; + if (file->details->get_info_error) + { + g_error_free (file->details->get_info_error); + file->details->get_info_error = NULL; + } + + state = g_new (GetInfoState, 1); + state->directory = directory; + state->cancellable = g_cancellable_new (); + + directory->details->get_info_in_progress = state; + + location = nautilus_file_get_location (file); + g_file_query_info_async (location, + NAUTILUS_FILE_DEFAULT_ATTRIBUTES, + 0, + G_PRIORITY_DEFAULT, + state->cancellable, query_info_callback, state); + g_object_unref (location); +} + +static void +thumbnail_done (NautilusDirectory *directory, + NautilusFile *file, + GdkPixbuf *pixbuf) +{ + const char *thumb_mtime_str; + time_t thumb_mtime = 0; + + file->details->thumbnail_is_up_to_date = TRUE; + if (file->details->thumbnail) + { + g_object_unref (file->details->thumbnail); + file->details->thumbnail = NULL; + } + + if (pixbuf) + { + thumb_mtime_str = gdk_pixbuf_get_option (pixbuf, "tEXt::Thumb::MTime"); + if (thumb_mtime_str) + { + thumb_mtime = atol (thumb_mtime_str); + } + + if (thumb_mtime == 0 || + thumb_mtime == file->details->mtime) + { + file->details->thumbnail = g_object_ref (pixbuf); + file->details->thumbnail_mtime = thumb_mtime; + } + else + { + g_free (file->details->thumbnail_path); + file->details->thumbnail_path = NULL; + } + } + + nautilus_directory_async_state_changed (directory); +} + +static void +thumbnail_stop (NautilusDirectory *directory) +{ + NautilusFile *file; + + if (directory->details->thumbnail_state != NULL) + { + file = directory->details->thumbnail_state->file; + + if (file != NULL) + { + g_assert (NAUTILUS_IS_FILE (file)); + g_assert (file->details->directory == directory); + if (is_needy (file, + lacks_thumbnail, + REQUEST_THUMBNAIL)) + { + return; + } + } + + /* The link info is not wanted, so stop it. */ + thumbnail_cancel (directory); + } +} + +static void +thumbnail_got_pixbuf (NautilusDirectory *directory, + NautilusFile *file, + GdkPixbuf *pixbuf) +{ + nautilus_directory_ref (directory); + + nautilus_file_ref (file); + thumbnail_done (directory, file, pixbuf); + nautilus_file_changed (file); + nautilus_file_unref (file); + + if (pixbuf) + { + g_object_unref (pixbuf); + } + + nautilus_directory_unref (directory); +} + +static void +thumbnail_state_free (ThumbnailState *state) +{ + g_object_unref (state->cancellable); + g_free (state); +} + +/* scale very large images down to the max. size we need */ +static void +thumbnail_loader_size_prepared (GdkPixbufLoader *loader, + int width, + int height, + gpointer user_data) +{ + int max_thumbnail_size; + double aspect_ratio; + + aspect_ratio = ((double) width) / height; + + /* cf. nautilus_file_get_icon() */ + max_thumbnail_size = NAUTILUS_GRID_ICON_SIZE_EXTRA_LARGE * NAUTILUS_GRID_ICON_SIZE_MEDIUM / NAUTILUS_GRID_ICON_SIZE_SMALL; + if (MAX (width, height) > max_thumbnail_size) + { + if (width > height) + { + width = max_thumbnail_size; + height = width / aspect_ratio; + } + else + { + height = max_thumbnail_size; + width = height * aspect_ratio; + } + + gdk_pixbuf_loader_set_size (loader, width, height); + } +} + +static GdkPixbuf * +get_pixbuf_for_content (goffset file_len, + char *file_contents) +{ + gboolean res; + GdkPixbuf *pixbuf, *pixbuf2; + GdkPixbufLoader *loader; + gsize chunk_len; + pixbuf = NULL; + + loader = gdk_pixbuf_loader_new (); + g_signal_connect (loader, "size-prepared", + G_CALLBACK (thumbnail_loader_size_prepared), + NULL); + + /* For some reason we have to write in chunks, or gdk-pixbuf fails */ + res = TRUE; + while (res && file_len > 0) + { + chunk_len = file_len; + res = gdk_pixbuf_loader_write (loader, (guchar *) file_contents, chunk_len, NULL); + file_contents += chunk_len; + file_len -= chunk_len; + } + if (res) + { + res = gdk_pixbuf_loader_close (loader, NULL); + } + if (res) + { + pixbuf = g_object_ref (gdk_pixbuf_loader_get_pixbuf (loader)); + } + g_object_unref (G_OBJECT (loader)); + + if (pixbuf) + { + pixbuf2 = gdk_pixbuf_apply_embedded_orientation (pixbuf); + g_object_unref (pixbuf); + pixbuf = pixbuf2; + } + return pixbuf; +} + + +static void +thumbnail_read_callback (GObject *source_object, + GAsyncResult *res, + gpointer user_data) +{ + ThumbnailState *state; + gsize file_size; + char *file_contents; + gboolean result; + NautilusDirectory *directory; + GdkPixbuf *pixbuf; + + state = user_data; + + if (state->directory == NULL) + { + /* Operation was cancelled. Bail out */ + thumbnail_state_free (state); + return; + } + + directory = nautilus_directory_ref (state->directory); + + result = g_file_load_contents_finish (G_FILE (source_object), + res, + &file_contents, &file_size, + NULL, NULL); + + pixbuf = NULL; + if (result) + { + pixbuf = get_pixbuf_for_content (file_size, file_contents); + g_free (file_contents); + } + + state->directory->details->thumbnail_state = NULL; + async_job_end (state->directory, "thumbnail"); + + thumbnail_got_pixbuf (state->directory, state->file, pixbuf); + + thumbnail_state_free (state); + + nautilus_directory_unref (directory); +} + +static void +thumbnail_start (NautilusDirectory *directory, + NautilusFile *file, + gboolean *doing_io) +{ + GFile *location; + ThumbnailState *state; + + if (directory->details->thumbnail_state != NULL) + { + *doing_io = TRUE; + return; + } + + if (!is_needy (file, + lacks_thumbnail, + REQUEST_THUMBNAIL)) + { + return; + } + *doing_io = TRUE; + + if (!async_job_start (directory, "thumbnail")) + { + return; + } + + state = g_new0 (ThumbnailState, 1); + state->directory = directory; + state->file = file; + state->cancellable = g_cancellable_new (); + + location = g_file_new_for_path (file->details->thumbnail_path); + + directory->details->thumbnail_state = state; + + g_file_load_contents_async (location, + state->cancellable, + thumbnail_read_callback, + state); + g_object_unref (location); +} + +static void +mount_stop (NautilusDirectory *directory) +{ + NautilusFile *file; + + if (directory->details->mount_state != NULL) + { + file = directory->details->mount_state->file; + + if (file != NULL) + { + g_assert (NAUTILUS_IS_FILE (file)); + g_assert (file->details->directory == directory); + if (is_needy (file, + lacks_mount, + REQUEST_MOUNT)) + { + return; + } + } + + /* The link info is not wanted, so stop it. */ + mount_cancel (directory); + } +} + +static void +mount_state_free (MountState *state) +{ + g_object_unref (state->cancellable); + g_free (state); +} + +static void +got_mount (MountState *state, + GMount *mount) +{ + NautilusDirectory *directory; + NautilusFile *file; + + directory = nautilus_directory_ref (state->directory); + + state->directory->details->mount_state = NULL; + async_job_end (state->directory, "mount"); + + file = nautilus_file_ref (state->file); + + file->details->mount_is_up_to_date = TRUE; + nautilus_file_set_mount (file, mount); + + nautilus_directory_async_state_changed (directory); + nautilus_file_changed (file); + + nautilus_file_unref (file); + + nautilus_directory_unref (directory); + + mount_state_free (state); +} + +static void +find_enclosing_mount_callback (GObject *source_object, + GAsyncResult *res, + gpointer user_data) +{ + GMount *mount; + MountState *state; + GFile *location, *root; + + state = user_data; + if (state->directory == NULL) + { + /* Operation was cancelled. Bail out */ + mount_state_free (state); + return; + } + + mount = g_file_find_enclosing_mount_finish (G_FILE (source_object), + res, NULL); + + if (mount) + { + root = g_mount_get_root (mount); + location = nautilus_file_get_location (state->file); + if (!g_file_equal (location, root)) + { + g_object_unref (mount); + mount = NULL; + } + g_object_unref (root); + g_object_unref (location); + } + + got_mount (state, mount); + + if (mount) + { + g_object_unref (mount); + } +} + +static GMount * +get_mount_at (GFile *target) +{ + GVolumeMonitor *monitor; + GFile *root; + GList *mounts, *l; + GMount *found; + + monitor = g_volume_monitor_get (); + mounts = g_volume_monitor_get_mounts (monitor); + + found = NULL; + for (l = mounts; l != NULL; l = l->next) + { + GMount *mount = G_MOUNT (l->data); + + if (g_mount_is_shadowed (mount)) + { + continue; + } + + root = g_mount_get_root (mount); + + if (g_file_equal (target, root)) + { + found = g_object_ref (mount); + break; + } + + g_object_unref (root); + } + + g_list_free_full (mounts, g_object_unref); + + g_object_unref (monitor); + + return found; +} + +static void +mount_start (NautilusDirectory *directory, + NautilusFile *file, + gboolean *doing_io) +{ + GFile *location; + MountState *state; + + if (directory->details->mount_state != NULL) + { + *doing_io = TRUE; + return; + } + + if (!is_needy (file, + lacks_mount, + REQUEST_MOUNT)) + { + return; + } + *doing_io = TRUE; + + if (!async_job_start (directory, "mount")) + { + return; + } + + state = g_new0 (MountState, 1); + state->directory = directory; + state->file = file; + state->cancellable = g_cancellable_new (); + + location = nautilus_file_get_location (file); + + directory->details->mount_state = state; + + if (file->details->type == G_FILE_TYPE_MOUNTABLE) + { + GFile *target; + GMount *mount; + + mount = NULL; + target = nautilus_file_get_activation_location (file); + if (target != NULL) + { + mount = get_mount_at (target); + g_object_unref (target); + } + + got_mount (state, mount); + + if (mount) + { + g_object_unref (mount); + } + } + else + { + g_file_find_enclosing_mount_async (location, + G_PRIORITY_DEFAULT, + state->cancellable, + find_enclosing_mount_callback, + state); + } + g_object_unref (location); +} + +static void +filesystem_info_cancel (NautilusDirectory *directory) +{ + if (directory->details->filesystem_info_state != NULL) + { + g_cancellable_cancel (directory->details->filesystem_info_state->cancellable); + directory->details->filesystem_info_state->directory = NULL; + directory->details->filesystem_info_state = NULL; + async_job_end (directory, "filesystem info"); + } +} + +static void +filesystem_info_stop (NautilusDirectory *directory) +{ + NautilusFile *file; + + if (directory->details->filesystem_info_state != NULL) + { + file = directory->details->filesystem_info_state->file; + + if (file != NULL) + { + g_assert (NAUTILUS_IS_FILE (file)); + g_assert (file->details->directory == directory); + if (is_needy (file, + lacks_filesystem_info, + REQUEST_FILESYSTEM_INFO)) + { + return; + } + } + + /* The filesystem info is not wanted, so stop it. */ + filesystem_info_cancel (directory); + } +} + +static void +filesystem_info_state_free (FilesystemInfoState *state) +{ + g_object_unref (state->cancellable); + g_free (state); +} + +static void +got_filesystem_info (FilesystemInfoState *state, + GFileInfo *info) +{ + NautilusDirectory *directory; + NautilusFile *file; + const char *filesystem_type; + + /* careful here, info may be NULL */ + + directory = nautilus_directory_ref (state->directory); + + state->directory->details->filesystem_info_state = NULL; + async_job_end (state->directory, "filesystem info"); + + file = nautilus_file_ref (state->file); + + file->details->filesystem_info_is_up_to_date = TRUE; + if (info != NULL) + { + file->details->filesystem_use_preview = + g_file_info_get_attribute_uint32 (info, G_FILE_ATTRIBUTE_FILESYSTEM_USE_PREVIEW); + file->details->filesystem_readonly = + g_file_info_get_attribute_boolean (info, G_FILE_ATTRIBUTE_FILESYSTEM_READONLY); + filesystem_type = g_file_info_get_attribute_string (info, G_FILE_ATTRIBUTE_FILESYSTEM_TYPE); + file->details->filesystem_remote = g_file_info_get_attribute_boolean (info, G_FILE_ATTRIBUTE_FILESYSTEM_REMOTE); + if (g_strcmp0 (file->details->filesystem_type, filesystem_type) != 0) + { + g_clear_pointer (&file->details->filesystem_type, g_ref_string_release); + file->details->filesystem_type = g_ref_string_new_intern (filesystem_type); + } + } + + nautilus_directory_async_state_changed (directory); + nautilus_file_changed (file); + + nautilus_file_unref (file); + + nautilus_directory_unref (directory); + + filesystem_info_state_free (state); +} + +static void +query_filesystem_info_callback (GObject *source_object, + GAsyncResult *res, + gpointer user_data) +{ + GFileInfo *info; + FilesystemInfoState *state; + + state = user_data; + if (state->directory == NULL) + { + /* Operation was cancelled. Bail out */ + filesystem_info_state_free (state); + return; + } + + info = g_file_query_filesystem_info_finish (G_FILE (source_object), res, NULL); + + got_filesystem_info (state, info); + + if (info != NULL) + { + g_object_unref (info); + } +} + +static void +filesystem_info_start (NautilusDirectory *directory, + NautilusFile *file, + gboolean *doing_io) +{ + GFile *location; + FilesystemInfoState *state; + + if (directory->details->filesystem_info_state != NULL) + { + *doing_io = TRUE; + return; + } + + if (!is_needy (file, + lacks_filesystem_info, + REQUEST_FILESYSTEM_INFO)) + { + return; + } + *doing_io = TRUE; + + if (!async_job_start (directory, "filesystem info")) + { + return; + } + + state = g_new0 (FilesystemInfoState, 1); + state->directory = directory; + state->file = file; + state->cancellable = g_cancellable_new (); + + location = nautilus_file_get_location (file); + + directory->details->filesystem_info_state = state; + + g_file_query_filesystem_info_async (location, + G_FILE_ATTRIBUTE_FILESYSTEM_READONLY "," + G_FILE_ATTRIBUTE_FILESYSTEM_USE_PREVIEW "," + G_FILE_ATTRIBUTE_FILESYSTEM_TYPE "," + G_FILE_ATTRIBUTE_FILESYSTEM_REMOTE, + G_PRIORITY_DEFAULT, + state->cancellable, + query_filesystem_info_callback, + state); + g_object_unref (location); +} + +static void +extension_info_cancel (NautilusDirectory *directory) +{ + if (directory->details->extension_info_in_progress != NULL) + { + if (directory->details->extension_info_idle) + { + g_source_remove (directory->details->extension_info_idle); + } + else + { + nautilus_info_provider_cancel_update + (directory->details->extension_info_provider, + directory->details->extension_info_in_progress); + } + + directory->details->extension_info_in_progress = NULL; + directory->details->extension_info_file = NULL; + directory->details->extension_info_provider = NULL; + directory->details->extension_info_idle = 0; + + async_job_end (directory, "extension info"); + } +} + +static void +extension_info_stop (NautilusDirectory *directory) +{ + if (directory->details->extension_info_in_progress != NULL) + { + NautilusFile *file; + + file = directory->details->extension_info_file; + if (file != NULL) + { + g_assert (NAUTILUS_IS_FILE (file)); + g_assert (file->details->directory == directory); + if (is_needy (file, lacks_extension_info, REQUEST_EXTENSION_INFO)) + { + return; + } + } + + /* The info is not wanted, so stop it. */ + extension_info_cancel (directory); + } +} + +static void +finish_info_provider (NautilusDirectory *directory, + NautilusFile *file, + NautilusInfoProvider *provider) +{ + file->details->pending_info_providers = + g_list_remove (file->details->pending_info_providers, + provider); + g_object_unref (provider); + + nautilus_directory_async_state_changed (directory); + + if (file->details->pending_info_providers == NULL) + { + nautilus_file_info_providers_done (file); + } +} + + +static gboolean +info_provider_idle_callback (gpointer user_data) +{ + InfoProviderResponse *response; + NautilusDirectory *directory; + + response = user_data; + directory = response->directory; + + if (response->handle != directory->details->extension_info_in_progress + || response->provider != directory->details->extension_info_provider) + { + g_warning ("Unexpected plugin response. This probably indicates a bug in a Nautilus extension: handle=%p", response->handle); + } + else + { + NautilusFile *file; + async_job_end (directory, "extension info"); + + file = directory->details->extension_info_file; + + directory->details->extension_info_file = NULL; + directory->details->extension_info_provider = NULL; + directory->details->extension_info_in_progress = NULL; + directory->details->extension_info_idle = 0; + + finish_info_provider (directory, file, response->provider); + } + + return FALSE; +} + +static void +info_provider_callback (NautilusInfoProvider *provider, + NautilusOperationHandle *handle, + NautilusOperationResult result, + gpointer user_data) +{ + InfoProviderResponse *response; + + response = g_new0 (InfoProviderResponse, 1); + response->provider = provider; + response->handle = handle; + response->result = result; + response->directory = NAUTILUS_DIRECTORY (user_data); + + response->directory->details->extension_info_idle = + g_idle_add_full (G_PRIORITY_DEFAULT_IDLE, + info_provider_idle_callback, response, + g_free); +} + +static void +extension_info_start (NautilusDirectory *directory, + NautilusFile *file, + gboolean *doing_io) +{ + NautilusInfoProvider *provider; + NautilusOperationResult result; + NautilusOperationHandle *handle; + GClosure *update_complete; + + if (directory->details->extension_info_in_progress != NULL) + { + *doing_io = TRUE; + return; + } + + if (!is_needy (file, lacks_extension_info, REQUEST_EXTENSION_INFO)) + { + return; + } + *doing_io = TRUE; + + if (!async_job_start (directory, "extension info")) + { + return; + } + + provider = file->details->pending_info_providers->data; + + update_complete = g_cclosure_new (G_CALLBACK (info_provider_callback), + directory, + NULL); + g_closure_set_marshal (update_complete, + g_cclosure_marshal_generic); + + result = nautilus_info_provider_update_file_info + (provider, + NAUTILUS_FILE_INFO (file), + update_complete, + &handle); + + g_closure_unref (update_complete); + + if (result == NAUTILUS_OPERATION_COMPLETE || + result == NAUTILUS_OPERATION_FAILED) + { + finish_info_provider (directory, file, provider); + async_job_end (directory, "extension info"); + } + else + { + directory->details->extension_info_in_progress = handle; + directory->details->extension_info_provider = provider; + directory->details->extension_info_file = file; + } +} + +static void +start_or_stop_io (NautilusDirectory *directory) +{ + NautilusFile *file; + gboolean doing_io; + + /* Start or stop reading files. */ + file_list_start_or_stop (directory); + + /* Stop any no longer wanted attribute fetches. */ + file_info_stop (directory); + directory_count_stop (directory); + deep_count_stop (directory); + mime_list_stop (directory); + extension_info_stop (directory); + mount_stop (directory); + thumbnail_stop (directory); + filesystem_info_stop (directory); + + doing_io = FALSE; + /* Take files that are all done off the queue. */ + while (!nautilus_file_queue_is_empty (directory->details->high_priority_queue)) + { + file = nautilus_file_queue_head (directory->details->high_priority_queue); + + /* Start getting attributes if possible */ + file_info_start (directory, file, &doing_io); + + if (doing_io) + { + return; + } + + move_file_to_low_priority_queue (directory, file); + } + + /* High priority queue must be empty */ + while (!nautilus_file_queue_is_empty (directory->details->low_priority_queue)) + { + file = nautilus_file_queue_head (directory->details->low_priority_queue); + + /* Start getting attributes if possible */ + mount_start (directory, file, &doing_io); + directory_count_start (directory, file, &doing_io); + deep_count_start (directory, file, &doing_io); + mime_list_start (directory, file, &doing_io); + thumbnail_start (directory, file, &doing_io); + filesystem_info_start (directory, file, &doing_io); + + if (doing_io) + { + return; + } + + move_file_to_extension_queue (directory, file); + } + + /* Low priority queue must be empty */ + while (!nautilus_file_queue_is_empty (directory->details->extension_queue)) + { + file = nautilus_file_queue_head (directory->details->extension_queue); + + /* Start getting attributes if possible */ + extension_info_start (directory, file, &doing_io); + if (doing_io) + { + return; + } + + nautilus_directory_remove_file_from_work_queue (directory, file); + } +} + +/* Call this when the monitor or call when ready list changes, + * or when some I/O is completed. + */ +void +nautilus_directory_async_state_changed (NautilusDirectory *directory) +{ + /* Check if any callbacks are satisfied and call them if they + * are. Do this last so that any changes done in start or stop + * I/O functions immediately (not in callbacks) are taken into + * consideration. If any callbacks are called, consider the + * I/O state again so that we can release or cancel I/O that + * is not longer needed once the callbacks are satisfied. + */ + + if (directory->details->in_async_service_loop) + { + directory->details->state_changed = TRUE; + return; + } + directory->details->in_async_service_loop = TRUE; + nautilus_directory_ref (directory); + do + { + directory->details->state_changed = FALSE; + start_or_stop_io (directory); + if (call_ready_callbacks (directory)) + { + directory->details->state_changed = TRUE; + } + } + while (directory->details->state_changed); + directory->details->in_async_service_loop = FALSE; + nautilus_directory_unref (directory); + + /* Check if any directories should wake up. */ + async_job_wake_up (); +} + +void +nautilus_directory_cancel (NautilusDirectory *directory) +{ + /* Arbitrary order (kept alphabetical). */ + deep_count_cancel (directory); + directory_count_cancel (directory); + file_info_cancel (directory); + file_list_cancel (directory); + mime_list_cancel (directory); + new_files_cancel (directory); + extension_info_cancel (directory); + thumbnail_cancel (directory); + mount_cancel (directory); + filesystem_info_cancel (directory); + + /* We aren't waiting for anything any more. */ + if (waiting_directories != NULL) + { + g_hash_table_remove (waiting_directories, directory); + } + + /* Check if any directories should wake up. */ + async_job_wake_up (); +} + +static void +cancel_directory_count_for_file (NautilusDirectory *directory, + NautilusFile *file) +{ + if (directory->details->count_in_progress != NULL && + directory->details->count_in_progress->count_file == file) + { + directory_count_cancel (directory); + } +} + +static void +cancel_deep_counts_for_file (NautilusDirectory *directory, + NautilusFile *file) +{ + if (directory->details->deep_count_file == file) + { + deep_count_cancel (directory); + } +} + +static void +cancel_mime_list_for_file (NautilusDirectory *directory, + NautilusFile *file) +{ + if (directory->details->mime_list_in_progress != NULL && + directory->details->mime_list_in_progress->mime_list_file == file) + { + mime_list_cancel (directory); + } +} + +static void +cancel_file_info_for_file (NautilusDirectory *directory, + NautilusFile *file) +{ + if (directory->details->get_info_file == file) + { + file_info_cancel (directory); + } +} + +static void +cancel_thumbnail_for_file (NautilusDirectory *directory, + NautilusFile *file) +{ + if (directory->details->thumbnail_state != NULL && + directory->details->thumbnail_state->file == file) + { + thumbnail_cancel (directory); + } +} + +static void +cancel_mount_for_file (NautilusDirectory *directory, + NautilusFile *file) +{ + if (directory->details->mount_state != NULL && + directory->details->mount_state->file == file) + { + mount_cancel (directory); + } +} + +static void +cancel_filesystem_info_for_file (NautilusDirectory *directory, + NautilusFile *file) +{ + if (directory->details->filesystem_info_state != NULL && + directory->details->filesystem_info_state->file == file) + { + filesystem_info_cancel (directory); + } +} + +static void +cancel_loading_attributes (NautilusDirectory *directory, + NautilusFileAttributes file_attributes) +{ + Request request; + + request = nautilus_directory_set_up_request (file_attributes); + + if (REQUEST_WANTS_TYPE (request, REQUEST_DIRECTORY_COUNT)) + { + directory_count_cancel (directory); + } + if (REQUEST_WANTS_TYPE (request, REQUEST_DEEP_COUNT)) + { + deep_count_cancel (directory); + } + if (REQUEST_WANTS_TYPE (request, REQUEST_MIME_LIST)) + { + mime_list_cancel (directory); + } + if (REQUEST_WANTS_TYPE (request, REQUEST_FILE_INFO)) + { + file_info_cancel (directory); + } + if (REQUEST_WANTS_TYPE (request, REQUEST_FILESYSTEM_INFO)) + { + filesystem_info_cancel (directory); + } + if (REQUEST_WANTS_TYPE (request, REQUEST_EXTENSION_INFO)) + { + extension_info_cancel (directory); + } + + if (REQUEST_WANTS_TYPE (request, REQUEST_THUMBNAIL)) + { + thumbnail_cancel (directory); + } + + if (REQUEST_WANTS_TYPE (request, REQUEST_MOUNT)) + { + mount_cancel (directory); + } + + nautilus_directory_async_state_changed (directory); +} + +void +nautilus_directory_cancel_loading_file_attributes (NautilusDirectory *directory, + NautilusFile *file, + NautilusFileAttributes file_attributes) +{ + Request request; + + nautilus_directory_remove_file_from_work_queue (directory, file); + + request = nautilus_directory_set_up_request (file_attributes); + + if (REQUEST_WANTS_TYPE (request, REQUEST_DIRECTORY_COUNT)) + { + cancel_directory_count_for_file (directory, file); + } + if (REQUEST_WANTS_TYPE (request, REQUEST_DEEP_COUNT)) + { + cancel_deep_counts_for_file (directory, file); + } + if (REQUEST_WANTS_TYPE (request, REQUEST_MIME_LIST)) + { + cancel_mime_list_for_file (directory, file); + } + if (REQUEST_WANTS_TYPE (request, REQUEST_FILE_INFO)) + { + cancel_file_info_for_file (directory, file); + } + if (REQUEST_WANTS_TYPE (request, REQUEST_FILESYSTEM_INFO)) + { + cancel_filesystem_info_for_file (directory, file); + } + if (REQUEST_WANTS_TYPE (request, REQUEST_THUMBNAIL)) + { + cancel_thumbnail_for_file (directory, file); + } + if (REQUEST_WANTS_TYPE (request, REQUEST_MOUNT)) + { + cancel_mount_for_file (directory, file); + } + + nautilus_directory_async_state_changed (directory); +} + +void +nautilus_directory_add_file_to_work_queue (NautilusDirectory *directory, + NautilusFile *file) +{ + g_return_if_fail (file->details->directory == directory); + + nautilus_file_queue_enqueue (directory->details->high_priority_queue, + file); +} + + +static void +add_all_files_to_work_queue (NautilusDirectory *directory) +{ + GList *node; + NautilusFile *file; + + for (node = directory->details->file_list; node != NULL; node = node->next) + { + file = NAUTILUS_FILE (node->data); + + nautilus_directory_add_file_to_work_queue (directory, file); + } +} + +void +nautilus_directory_remove_file_from_work_queue (NautilusDirectory *directory, + NautilusFile *file) +{ + nautilus_file_queue_remove (directory->details->high_priority_queue, + file); + nautilus_file_queue_remove (directory->details->low_priority_queue, + file); + nautilus_file_queue_remove (directory->details->extension_queue, + file); +} + + +static void +move_file_to_low_priority_queue (NautilusDirectory *directory, + NautilusFile *file) +{ + /* Must add before removing to avoid ref underflow */ + nautilus_file_queue_enqueue (directory->details->low_priority_queue, + file); + nautilus_file_queue_remove (directory->details->high_priority_queue, + file); +} + +static void +move_file_to_extension_queue (NautilusDirectory *directory, + NautilusFile *file) +{ + /* Must add before removing to avoid ref underflow */ + nautilus_file_queue_enqueue (directory->details->extension_queue, + file); + nautilus_file_queue_remove (directory->details->low_priority_queue, + file); +} diff --git a/src/nautilus-directory-notify.h b/src/nautilus-directory-notify.h new file mode 100644 index 0000000..4b5030d --- /dev/null +++ b/src/nautilus-directory-notify.h @@ -0,0 +1,57 @@ +/* + nautilus-directory-notify.h: Nautilus directory notify calls. + + Copyright (C) 2000, 2001 Eazel, Inc. + + This program is free software; you can redistribute it and/or + modify it under the terms of the GNU General Public License as + published by the Free Software Foundation; either version 2 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 warranty of + MERCHANTABILITY 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 . + + Author: Darin Adler +*/ + +#pragma once + +#include + +#include "nautilus-types.h" + +typedef struct { + char *from_uri; + char *to_uri; +} URIPair; + +typedef struct { + GFile *from; + GFile *to; +} GFilePair; + +/* Almost-public change notification calls */ +void nautilus_directory_notify_files_added (GList *files); +void nautilus_directory_notify_files_moved (GList *file_pairs); +void nautilus_directory_notify_files_changed (GList *files); +void nautilus_directory_notify_files_removed (GList *files); + +void nautilus_directory_schedule_metadata_copy (GList *file_pairs); +void nautilus_directory_schedule_metadata_move (GList *file_pairs); +void nautilus_directory_schedule_metadata_remove (GList *files); + +void nautilus_directory_schedule_metadata_copy_by_uri (GList *uri_pairs); +void nautilus_directory_schedule_metadata_move_by_uri (GList *uri_pairs); +void nautilus_directory_schedule_metadata_remove_by_uri (GList *uris); + +/* Change notification hack. + * This is called when code modifies the file and it needs to trigger + * a notification. Eventually this should become private, but for now + * it needs to be used for code like the thumbnail generation. + */ +void nautilus_file_changed (NautilusFile *file); diff --git a/src/nautilus-directory-private.h b/src/nautilus-directory-private.h new file mode 100644 index 0000000..0f8fb1b --- /dev/null +++ b/src/nautilus-directory-private.h @@ -0,0 +1,230 @@ +/* + nautilus-directory-private.h: Nautilus directory model. + + Copyright (C) 1999, 2000, 2001 Eazel, Inc. + + This program is free software; you can redistribute it and/or + modify it under the terms of the GNU General Public License as + published by the Free Software Foundation; either version 2 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 warranty of + MERCHANTABILITY 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 . + + Author: Darin Adler +*/ + +#pragma once + +#include +#include +#include + +#include "nautilus-directory.h" +#include "nautilus-file.h" + +typedef struct FileMonitors FileMonitors; +typedef struct DirectoryLoadState DirectoryLoadState; +typedef struct DirectoryCountState DirectoryCountState; +typedef struct DeepCountState DeepCountState; +typedef struct GetInfoState GetInfoState; +typedef struct NewFilesState NewFilesState; +typedef struct MimeListState MimeListState; +typedef struct ThumbnailState ThumbnailState; +typedef struct MountState MountState; +typedef struct FilesystemInfoState FilesystemInfoState; + +typedef enum { + REQUEST_DEEP_COUNT, + REQUEST_DIRECTORY_COUNT, + REQUEST_FILE_INFO, + REQUEST_FILE_LIST, /* always FALSE if file != NULL */ + REQUEST_MIME_LIST, + REQUEST_EXTENSION_INFO, + REQUEST_THUMBNAIL, + REQUEST_MOUNT, + REQUEST_FILESYSTEM_INFO, + REQUEST_TYPE_LAST +} RequestType; + +/* A request for information about one or more files. */ +typedef guint32 Request; +typedef gint32 RequestCounter[REQUEST_TYPE_LAST]; + +#define REQUEST_WANTS_TYPE(request, type) ((request) & (1<<(type))) +#define REQUEST_SET_TYPE(request, type) (request) |= (1<<(type)) + +struct NautilusDirectoryDetails +{ + /* The location. */ + GFile *location; + + /* The file objects. */ + NautilusFile *as_file; + GList *file_list; + GHashTable *file_hash; + + /* Queues of files needing some I/O done. */ + NautilusFileQueue *high_priority_queue; + NautilusFileQueue *low_priority_queue; + NautilusFileQueue *extension_queue; + + /* These lists are going to be pretty short. If we think they + * are going to get big, we can use hash tables instead. + */ + GList *call_when_ready_list; + RequestCounter call_when_ready_counters; + GHashTable *monitor_table; + RequestCounter monitor_counters; + guint call_ready_idle_id; + + NautilusMonitor *monitor; + gulong mime_db_monitor; + + gboolean in_async_service_loop; + gboolean state_changed; + + gboolean file_list_monitored; + gboolean directory_loaded; + gboolean directory_loaded_sent_notification; + DirectoryLoadState *directory_load_in_progress; + + GList *pending_file_info; /* list of GnomeVFSFileInfo's that are pending */ + int confirmed_file_count; + guint dequeue_pending_idle_id; + + GList *new_files_in_progress; /* list of NewFilesState * */ + + /* List of GFile's that received CHANGE events while new files were being added in + * that same folder. We will process this CHANGE events after new_files_in_progress + * list is finished. See Bug 703179 and issue #1576 for a case when this happens. + */ + GList *files_changed_while_adding; + + DirectoryCountState *count_in_progress; + + NautilusFile *deep_count_file; + DeepCountState *deep_count_in_progress; + + MimeListState *mime_list_in_progress; + + NautilusFile *get_info_file; + GetInfoState *get_info_in_progress; + + NautilusFile *extension_info_file; + NautilusInfoProvider *extension_info_provider; + NautilusOperationHandle *extension_info_in_progress; + guint extension_info_idle; + + ThumbnailState *thumbnail_state; + + MountState *mount_state; + + FilesystemInfoState *filesystem_info_state; + + GList *file_operations_in_progress; /* list of FileOperation * */ +}; + +NautilusDirectory *nautilus_directory_get_existing (GFile *location); + +/* async. interface */ +void nautilus_directory_async_state_changed (NautilusDirectory *directory); +void nautilus_directory_call_when_ready_internal (NautilusDirectory *directory, + NautilusFile *file, + NautilusFileAttributes file_attributes, + gboolean wait_for_file_list, + NautilusDirectoryCallback directory_callback, + NautilusFileCallback file_callback, + gpointer callback_data); +gboolean nautilus_directory_check_if_ready_internal (NautilusDirectory *directory, + NautilusFile *file, + NautilusFileAttributes file_attributes); +void nautilus_directory_cancel_callback_internal (NautilusDirectory *directory, + NautilusFile *file, + NautilusDirectoryCallback directory_callback, + NautilusFileCallback file_callback, + gpointer callback_data); +void nautilus_directory_monitor_add_internal (NautilusDirectory *directory, + NautilusFile *file, + gconstpointer client, + gboolean monitor_hidden_files, + NautilusFileAttributes attributes, + NautilusDirectoryCallback callback, + gpointer callback_data); +void nautilus_directory_monitor_remove_internal (NautilusDirectory *directory, + NautilusFile *file, + gconstpointer client); +void nautilus_directory_get_info_for_new_files (NautilusDirectory *directory, + GList *vfs_uris); +NautilusFile * nautilus_directory_get_existing_corresponding_file (NautilusDirectory *directory); +void nautilus_directory_invalidate_count_and_mime_list (NautilusDirectory *directory); +gboolean nautilus_directory_is_file_list_monitored (NautilusDirectory *directory); +gboolean nautilus_directory_is_anyone_monitoring_file_list (NautilusDirectory *directory); +gboolean nautilus_directory_has_active_request_for_file (NautilusDirectory *directory, + NautilusFile *file); +void nautilus_directory_remove_file_monitor_link (NautilusDirectory *directory, + GList *link); +void nautilus_directory_schedule_dequeue_pending (NautilusDirectory *directory); +void nautilus_directory_stop_monitoring_file_list (NautilusDirectory *directory); +void nautilus_directory_cancel (NautilusDirectory *directory); +void nautilus_async_destroying_file (NautilusFile *file); +void nautilus_directory_force_reload_internal (NautilusDirectory *directory, + NautilusFileAttributes file_attributes); +void nautilus_directory_cancel_loading_file_attributes (NautilusDirectory *directory, + NautilusFile *file, + NautilusFileAttributes file_attributes); + +/* Calls shared between directory, file, and async. code. */ +void nautilus_directory_emit_files_added (NautilusDirectory *directory, + GList *added_files); +void nautilus_directory_emit_files_changed (NautilusDirectory *directory, + GList *changed_files); +void nautilus_directory_emit_change_signals (NautilusDirectory *directory, + GList *changed_files); +void emit_change_signals_for_all_files (NautilusDirectory *directory); +void emit_change_signals_for_all_files_in_all_directories (void); +void nautilus_directory_emit_done_loading (NautilusDirectory *directory); +void nautilus_directory_emit_load_error (NautilusDirectory *directory, + GError *error); +NautilusDirectory *nautilus_directory_get_internal (GFile *location, + gboolean create); +char * nautilus_directory_get_name_for_self_as_new_file (NautilusDirectory *directory); +Request nautilus_directory_set_up_request (NautilusFileAttributes file_attributes); + +/* Interface to the file list. */ +NautilusFile * nautilus_directory_find_file_by_name (NautilusDirectory *directory, + const char *filename); + +void nautilus_directory_add_file (NautilusDirectory *directory, + NautilusFile *file); +void nautilus_directory_remove_file (NautilusDirectory *directory, + NautilusFile *file); +FileMonitors * nautilus_directory_remove_file_monitors (NautilusDirectory *directory, + NautilusFile *file); +void nautilus_directory_add_file_monitors (NautilusDirectory *directory, + NautilusFile *file, + FileMonitors *monitors); +void nautilus_directory_add_file (NautilusDirectory *directory, + NautilusFile *file); +GList * nautilus_directory_begin_file_name_change (NautilusDirectory *directory, + NautilusFile *file); +void nautilus_directory_end_file_name_change (NautilusDirectory *directory, + NautilusFile *file, + GList *node); +void nautilus_directory_moved (const char *from_uri, + const char *to_uri); +/* Interface to the work queue. */ + +void nautilus_directory_add_file_to_work_queue (NautilusDirectory *directory, + NautilusFile *file); +void nautilus_directory_remove_file_from_work_queue (NautilusDirectory *directory, + NautilusFile *file); + + +/* debugging functions */ +int nautilus_directory_number_outstanding (void); diff --git a/src/nautilus-directory.c b/src/nautilus-directory.c new file mode 100644 index 0000000..1a9947f --- /dev/null +++ b/src/nautilus-directory.c @@ -0,0 +1,2085 @@ +/* + * nautilus-directory.c: Nautilus directory model. + * + * Copyright (C) 1999, 2000, 2001 Eazel, Inc. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License as + * published by the Free Software Foundation; either version 2 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 warranty of + * MERCHANTABILITY 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 . + * + * Author: Darin Adler + */ + +#include "nautilus-directory-private.h" + +#include +#include +#include +#include + +#include "nautilus-directory-notify.h" +#include "nautilus-directory-private.h" +#include "nautilus-enums.h" +#include "nautilus-file-private.h" +#include "nautilus-file-queue.h" +#include "nautilus-file-utilities.h" +#include "nautilus-global-preferences.h" +#include "nautilus-lib-self-check-functions.h" +#include "nautilus-metadata.h" +#include "nautilus-profile.h" +#include "nautilus-search-directory-file.h" +#include "nautilus-search-directory.h" +#include "nautilus-starred-directory.h" +#include "nautilus-vfs-directory.h" +#include "nautilus-vfs-file.h" + +enum +{ + FILES_ADDED, + FILES_CHANGED, + DONE_LOADING, + LOAD_ERROR, + LAST_SIGNAL +}; + +enum +{ + PROP_LOCATION = 1, + NUM_PROPERTIES +}; + +static guint signals[LAST_SIGNAL]; +static GParamSpec *properties[NUM_PROPERTIES] = { NULL, }; + +static GHashTable *directories; + +static NautilusDirectory *nautilus_directory_new (GFile *location); +static void set_directory_location (NautilusDirectory *directory, + GFile *location); + +G_DEFINE_TYPE (NautilusDirectory, nautilus_directory, G_TYPE_OBJECT); + +static gboolean +real_contains_file (NautilusDirectory *self, + NautilusFile *file) +{ + NautilusDirectory *directory; + + directory = nautilus_file_get_directory (file); + + return directory == self; +} + +static gboolean +real_are_all_files_seen (NautilusDirectory *directory) +{ + return directory->details->directory_loaded; +} + +static gboolean +real_is_not_empty (NautilusDirectory *directory) +{ + return directory->details->file_list != NULL; +} + +static gboolean +is_tentative (NautilusFile *file, + gpointer callback_data) +{ + g_assert (callback_data == NULL); + + /* Avoid returning files with !is_added, because these + * will later be sent with the files_added signal, and a + * user doing get_file_list + files_added monitoring will + * then see the file twice */ + return !file->details->got_file_info || !file->details->is_added; +} + +static GList * +real_get_file_list (NautilusDirectory *directory) +{ + GList *tentative_files, *non_tentative_files; + + tentative_files = nautilus_file_list_filter (directory->details->file_list, + &non_tentative_files, is_tentative, NULL); + nautilus_file_list_free (tentative_files); + + return non_tentative_files; +} + +static gboolean +real_is_editable (NautilusDirectory *directory) +{ + return TRUE; +} + +static NautilusFile * +real_new_file_from_filename (NautilusDirectory *directory, + const char *filename, + gboolean self_owned) +{ + NautilusFile *file; + + g_assert (NAUTILUS_IS_DIRECTORY (directory)); + g_assert (filename != NULL); + g_assert (filename[0] != '\0'); + + if (NAUTILUS_IS_SEARCH_DIRECTORY (directory)) + { + if (self_owned) + { + file = NAUTILUS_FILE (g_object_new (NAUTILUS_TYPE_SEARCH_DIRECTORY_FILE, NULL)); + } + else + { + /* This doesn't normally happen, unless the user somehow types in a uri + * that references a file like this. (See #349840) */ + file = NAUTILUS_FILE (g_object_new (NAUTILUS_TYPE_VFS_FILE, NULL)); + } + } + else + { + file = NAUTILUS_FILE (g_object_new (NAUTILUS_TYPE_VFS_FILE, NULL)); + } + nautilus_file_set_directory (file, directory); + + return file; +} + +static gboolean +real_handles_location (GFile *location) +{ + /* This class is the fallback on handling any location */ + return TRUE; +} + +static void +nautilus_directory_finalize (GObject *object) +{ + NautilusDirectory *directory; + + directory = NAUTILUS_DIRECTORY (object); + + g_hash_table_remove (directories, directory->details->location); + + nautilus_directory_cancel (directory); + g_assert (directory->details->count_in_progress == NULL); + + if (g_hash_table_size (directory->details->monitor_table) != 0) + { + GHashTableIter iter; + gpointer value; + + g_warning ("destroying a NautilusDirectory while it's being monitored"); + + g_hash_table_iter_init (&iter, directory->details->monitor_table); + while (g_hash_table_iter_next (&iter, NULL, &value)) + { + GList *list = value; + g_list_free_full (list, g_free); + } + g_hash_table_remove_all (directory->details->monitor_table); + } + g_hash_table_destroy (directory->details->monitor_table); + + if (directory->details->monitor != NULL) + { + nautilus_monitor_cancel (directory->details->monitor); + } + + if (directory->details->dequeue_pending_idle_id != 0) + { + g_source_remove (directory->details->dequeue_pending_idle_id); + } + + if (directory->details->call_ready_idle_id != 0) + { + g_source_remove (directory->details->call_ready_idle_id); + } + + if (directory->details->location) + { + g_object_unref (directory->details->location); + } + + g_assert (directory->details->file_list == NULL); + g_hash_table_destroy (directory->details->file_hash); + + nautilus_file_queue_destroy (directory->details->high_priority_queue); + nautilus_file_queue_destroy (directory->details->low_priority_queue); + nautilus_file_queue_destroy (directory->details->extension_queue); + g_clear_list (&directory->details->files_changed_while_adding, g_object_unref); + g_assert (directory->details->directory_load_in_progress == NULL); + g_assert (directory->details->count_in_progress == NULL); + g_assert (directory->details->dequeue_pending_idle_id == 0); + g_list_free_full (directory->details->pending_file_info, g_object_unref); + + G_OBJECT_CLASS (nautilus_directory_parent_class)->finalize (object); +} + +static void +nautilus_directory_set_property (GObject *object, + guint property_id, + const GValue *value, + GParamSpec *pspec) +{ + NautilusDirectory *directory = NAUTILUS_DIRECTORY (object); + + switch (property_id) + { + case PROP_LOCATION: + { + set_directory_location (directory, g_value_get_object (value)); + } + break; + + default: + { + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec); + } + break; + } +} + +static void +nautilus_directory_get_property (GObject *object, + guint property_id, + GValue *value, + GParamSpec *pspec) +{ + NautilusDirectory *directory = NAUTILUS_DIRECTORY (object); + + switch (property_id) + { + case PROP_LOCATION: + { + g_value_set_object (value, directory->details->location); + } + break; + + default: + { + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec); + } + break; + } +} + +static void +nautilus_directory_class_init (NautilusDirectoryClass *klass) +{ + GObjectClass *object_class; + + object_class = G_OBJECT_CLASS (klass); + + klass->contains_file = real_contains_file; + klass->are_all_files_seen = real_are_all_files_seen; + klass->is_not_empty = real_is_not_empty; + klass->get_file_list = real_get_file_list; + klass->is_editable = real_is_editable; + klass->new_file_from_filename = real_new_file_from_filename; + klass->handles_location = real_handles_location; + + object_class->finalize = nautilus_directory_finalize; + object_class->set_property = nautilus_directory_set_property; + object_class->get_property = nautilus_directory_get_property; + + signals[FILES_ADDED] = + g_signal_new ("files-added", + G_TYPE_FROM_CLASS (object_class), + G_SIGNAL_RUN_LAST, + G_STRUCT_OFFSET (NautilusDirectoryClass, files_added), + NULL, NULL, + g_cclosure_marshal_VOID__POINTER, + G_TYPE_NONE, 1, G_TYPE_POINTER); + signals[FILES_CHANGED] = + g_signal_new ("files-changed", + G_TYPE_FROM_CLASS (object_class), + G_SIGNAL_RUN_LAST, + G_STRUCT_OFFSET (NautilusDirectoryClass, files_changed), + NULL, NULL, + g_cclosure_marshal_VOID__POINTER, + G_TYPE_NONE, 1, G_TYPE_POINTER); + signals[DONE_LOADING] = + g_signal_new ("done-loading", + G_TYPE_FROM_CLASS (object_class), + G_SIGNAL_RUN_LAST, + G_STRUCT_OFFSET (NautilusDirectoryClass, done_loading), + NULL, NULL, + g_cclosure_marshal_VOID__VOID, + G_TYPE_NONE, 0); + signals[LOAD_ERROR] = + g_signal_new ("load-error", + G_TYPE_FROM_CLASS (object_class), + G_SIGNAL_RUN_LAST, + G_STRUCT_OFFSET (NautilusDirectoryClass, load_error), + NULL, NULL, + g_cclosure_marshal_VOID__POINTER, + G_TYPE_NONE, 1, G_TYPE_POINTER); + + properties[PROP_LOCATION] = + g_param_spec_object ("location", + "The location", + "The location of this directory", + G_TYPE_FILE, + G_PARAM_CONSTRUCT_ONLY | G_PARAM_READWRITE); + + g_type_class_add_private (klass, sizeof (NautilusDirectoryDetails)); + g_object_class_install_properties (object_class, NUM_PROPERTIES, properties); +} + +static void +nautilus_directory_init (NautilusDirectory *directory) +{ + directory->details = G_TYPE_INSTANCE_GET_PRIVATE ((directory), NAUTILUS_TYPE_DIRECTORY, NautilusDirectoryDetails); + directory->details->file_hash = g_hash_table_new_full (g_str_hash, g_str_equal, + g_free, NULL); + directory->details->high_priority_queue = nautilus_file_queue_new (); + directory->details->low_priority_queue = nautilus_file_queue_new (); + directory->details->extension_queue = nautilus_file_queue_new (); + directory->details->monitor_table = g_hash_table_new (NULL, NULL); +} + +NautilusDirectory * +nautilus_directory_ref (NautilusDirectory *directory) +{ + if (directory == NULL) + { + return directory; + } + + g_return_val_if_fail (NAUTILUS_IS_DIRECTORY (directory), NULL); + + g_object_ref (directory); + return directory; +} + +void +nautilus_directory_unref (NautilusDirectory *directory) +{ + if (directory == NULL) + { + return; + } + + g_return_if_fail (NAUTILUS_IS_DIRECTORY (directory)); + + g_object_unref (directory); +} + +static void +collect_all_directories (gpointer key, + gpointer value, + gpointer callback_data) +{ + NautilusDirectory *directory; + GList **dirs; + + directory = NAUTILUS_DIRECTORY (value); + dirs = callback_data; + + *dirs = g_list_prepend (*dirs, nautilus_directory_ref (directory)); +} + +static void +filtering_changed_callback (gpointer callback_data) +{ + g_autolist (NautilusDirectory) dirs = NULL; + + g_assert (callback_data == NULL); + + dirs = NULL; + g_hash_table_foreach (directories, collect_all_directories, &dirs); + + /* Preference about which items to show has changed, so we + * can't trust any of our precomputed directory counts. + */ + for (GList *l = dirs; l != NULL; l = l->next) + { + NautilusDirectory *directory; + + directory = NAUTILUS_DIRECTORY (l->data); + + nautilus_directory_invalidate_count_and_mime_list (directory); + } +} + +void +emit_change_signals_for_all_files (NautilusDirectory *directory) +{ + GList *files; + + files = g_list_copy (directory->details->file_list); + if (directory->details->as_file != NULL) + { + files = g_list_prepend (files, directory->details->as_file); + } + + nautilus_file_list_ref (files); + nautilus_directory_emit_change_signals (directory, files); + + nautilus_file_list_free (files); +} + +void +emit_change_signals_for_all_files_in_all_directories (void) +{ + GList *dirs, *l; + NautilusDirectory *directory; + + dirs = NULL; + g_hash_table_foreach (directories, + collect_all_directories, + &dirs); + + for (l = dirs; l != NULL; l = l->next) + { + directory = NAUTILUS_DIRECTORY (l->data); + emit_change_signals_for_all_files (directory); + nautilus_directory_unref (directory); + } + + g_list_free (dirs); +} + +static void +async_state_changed_one (gpointer key, + gpointer value, + gpointer user_data) +{ + NautilusDirectory *directory; + + g_assert (key != NULL); + g_assert (NAUTILUS_IS_DIRECTORY (value)); + g_assert (user_data == NULL); + + directory = NAUTILUS_DIRECTORY (value); + + nautilus_directory_async_state_changed (directory); + emit_change_signals_for_all_files (directory); +} + +static void +async_data_preference_changed_callback (gpointer callback_data) +{ + g_assert (callback_data == NULL); + + /* Preference involving fetched async data has changed, so + * we have to kick off refetching all async data, and tell + * each file that it (might have) changed. + */ + g_hash_table_foreach (directories, async_state_changed_one, NULL); +} + +static void +add_preferences_callbacks (void) +{ + nautilus_global_preferences_init (); + + g_signal_connect_swapped (gtk_filechooser_preferences, + "changed::" NAUTILUS_PREFERENCES_SHOW_HIDDEN_FILES, + G_CALLBACK (filtering_changed_callback), + NULL); + g_signal_connect_swapped (nautilus_preferences, + "changed::" NAUTILUS_PREFERENCES_SHOW_DIRECTORY_ITEM_COUNTS, + G_CALLBACK (async_data_preference_changed_callback), + NULL); +} + +/** + * nautilus_directory_get_by_uri: + * @uri: URI of directory to get. + * + * Get a directory given a uri. + * Creates the appropriate subclass given the uri mappings. + * Returns a referenced object, not a floating one. Unref when finished. + * If two windows are viewing the same uri, the directory object is shared. + */ +NautilusDirectory * +nautilus_directory_get_internal (GFile *location, + gboolean create) +{ + NautilusDirectory *directory; + + /* Create the hash table first time through. */ + if (directories == NULL) + { + directories = g_hash_table_new (g_file_hash, (GCompareFunc) g_file_equal); + add_preferences_callbacks (); + } + + /* If the object is already in the hash table, look it up. */ + + directory = g_hash_table_lookup (directories, + location); + if (directory != NULL) + { + nautilus_directory_ref (directory); + } + else if (create) + { + /* Create a new directory object instead. */ + directory = nautilus_directory_new (location); + if (directory == NULL) + { + return NULL; + } + + /* Put it in the hash table. */ + g_hash_table_insert (directories, + directory->details->location, + directory); + } + + return directory; +} + +NautilusDirectory * +nautilus_directory_get (GFile *location) +{ + if (location == NULL) + { + return NULL; + } + + return nautilus_directory_get_internal (location, TRUE); +} + +NautilusDirectory * +nautilus_directory_get_existing (GFile *location) +{ + if (location == NULL) + { + return NULL; + } + + return nautilus_directory_get_internal (location, FALSE); +} + + +NautilusDirectory * +nautilus_directory_get_by_uri (const char *uri) +{ + NautilusDirectory *directory; + GFile *location; + + if (uri == NULL) + { + return NULL; + } + + location = g_file_new_for_uri (uri); + + directory = nautilus_directory_get_internal (location, TRUE); + g_object_unref (location); + return directory; +} + +NautilusDirectory * +nautilus_directory_get_for_file (NautilusFile *file) +{ + char *uri; + NautilusDirectory *directory; + + g_return_val_if_fail (NAUTILUS_IS_FILE (file), NULL); + + uri = nautilus_file_get_uri (file); + directory = nautilus_directory_get_by_uri (uri); + g_free (uri); + return directory; +} + +/* Returns a reffed NautilusFile object for this directory. + */ +NautilusFile * +nautilus_directory_get_corresponding_file (NautilusDirectory *directory) +{ + NautilusFile *file; + char *uri; + + file = nautilus_directory_get_existing_corresponding_file (directory); + if (file == NULL) + { + uri = nautilus_directory_get_uri (directory); + file = nautilus_file_get_by_uri (uri); + g_free (uri); + } + + return file; +} + +/* Returns a reffed NautilusFile object for this directory, but only if the + * NautilusFile object has already been created. + */ +NautilusFile * +nautilus_directory_get_existing_corresponding_file (NautilusDirectory *directory) +{ + NautilusFile *file; + char *uri; + + file = directory->details->as_file; + if (file != NULL) + { + nautilus_file_ref (file); + return file; + } + + uri = nautilus_directory_get_uri (directory); + file = nautilus_file_get_existing_by_uri (uri); + g_free (uri); + return file; +} + +/* nautilus_directory_get_name_for_self_as_new_file: + * + * Get a name to display for the file representing this + * directory. This is called only when there's no VFS + * directory for this NautilusDirectory. + */ +char * +nautilus_directory_get_name_for_self_as_new_file (NautilusDirectory *directory) +{ + GFile *file; + char *directory_uri; + char *scheme; + char *name; + char *hostname = NULL; + + directory_uri = nautilus_directory_get_uri (directory); + file = g_file_new_for_uri (directory_uri); + scheme = g_file_get_uri_scheme (file); + g_object_unref (file); + + nautilus_uri_parse (directory_uri, &hostname, NULL, NULL); + if (hostname == NULL || (strlen (hostname) == 0)) + { + name = g_strdup (directory_uri); + } + else if (scheme == NULL) + { + name = g_strdup (hostname); + } + else + { + /* Translators: this is of the format "hostname (uri-scheme)" */ + name = g_strdup_printf (_("%s (%s)"), hostname, scheme); + } + + g_free (directory_uri); + g_free (scheme); + g_free (hostname); + + return name; +} + +char * +nautilus_directory_get_uri (NautilusDirectory *directory) +{ + g_return_val_if_fail (NAUTILUS_IS_DIRECTORY (directory), NULL); + + return g_file_get_uri (directory->details->location); +} + +GFile * +nautilus_directory_get_location (NautilusDirectory *directory) +{ + g_return_val_if_fail (NAUTILUS_IS_DIRECTORY (directory), NULL); + + return g_object_ref (directory->details->location); +} + +NautilusFile * +nautilus_directory_new_file_from_filename (NautilusDirectory *directory, + const char *filename, + gboolean self_owned) +{ + return NAUTILUS_DIRECTORY_CLASS (G_OBJECT_GET_CLASS (directory))->new_file_from_filename (directory, + filename, + self_owned); +} + +static GList * +nautilus_directory_provider_get_all (void) +{ + GIOExtensionPoint *extension_point; + GList *extensions; + + extension_point = g_io_extension_point_lookup (NAUTILUS_DIRECTORY_PROVIDER_EXTENSION_POINT_NAME); + if (extension_point == NULL) + { + g_warning ("Directory provider extension point not registered. Did you call nautilus_ensure_extension_points()?"); + } + extensions = g_io_extension_point_get_extensions (extension_point); + + return extensions; +} + +static NautilusDirectory * +nautilus_directory_new (GFile *location) +{ + GList *extensions; + GList *l; + GIOExtension *gio_extension; + GType handling_provider_type; + gboolean handled = FALSE; + NautilusDirectoryClass *current_provider_class; + NautilusDirectory *handling_instance; + + extensions = nautilus_directory_provider_get_all (); + + for (l = extensions; l != NULL; l = l->next) + { + gio_extension = l->data; + current_provider_class = NAUTILUS_DIRECTORY_CLASS (g_io_extension_ref_class (gio_extension)); + if (current_provider_class->handles_location (location)) + { + handling_provider_type = g_io_extension_get_type (gio_extension); + handled = TRUE; + break; + } + } + + if (!handled) + { + /* This class is the fallback for any location */ + handling_provider_type = NAUTILUS_TYPE_VFS_DIRECTORY; + } + + handling_instance = g_object_new (handling_provider_type, + "location", location, + NULL); + + + return handling_instance; +} + +/** + * nautilus_directory_is_local_or_fuse: + * + * @directory: a #NautilusDirectory + * + * Checks whether this directory contains files with local paths. Usually, this + * means the local path can be obtained by calling g_file_get_path(). As an + * exception, the local URI for files in recent:// can only be obtained from the + * G_FILE_ATTRIBUTE_STANDARD_TARGET_URI attribute. + * + * Returns: %TRUE if a local path is known to be obtainable for all files in + * this directory. Otherwise, %FALSE. + */ +gboolean +nautilus_directory_is_local_or_fuse (NautilusDirectory *directory) +{ + g_return_val_if_fail (NAUTILUS_IS_DIRECTORY (directory), FALSE); + g_return_val_if_fail (directory->details->location, FALSE); + + + if (nautilus_directory_is_in_recent (directory) + || g_file_is_native (directory->details->location)) + { + /* Native files have a local path by definition. The files in recent:/ + * have a local URI stored in the standard::target-uri attribute. */ + return TRUE; + } + else + { + g_autofree char *path = NULL; + + /* Non-native files may have local paths in FUSE mounts. The only way to + * know if that's the case is to test if GIO reports a path. + */ + path = g_file_get_path (directory->details->location); + + return (path != NULL); + } +} + +gboolean +nautilus_directory_is_in_trash (NautilusDirectory *directory) +{ + g_assert (NAUTILUS_IS_DIRECTORY (directory)); + + if (directory->details->location == NULL) + { + return FALSE; + } + + return g_file_has_uri_scheme (directory->details->location, "trash"); +} + +gboolean +nautilus_directory_is_in_recent (NautilusDirectory *directory) +{ + g_assert (NAUTILUS_IS_DIRECTORY (directory)); + + if (directory->details->location == NULL) + { + return FALSE; + } + + return g_file_has_uri_scheme (directory->details->location, "recent"); +} + +gboolean +nautilus_directory_is_in_starred (NautilusDirectory *directory) +{ + g_assert (NAUTILUS_IS_DIRECTORY (directory)); + + if (directory->details->location == NULL) + { + return FALSE; + } + + return g_file_has_uri_scheme (directory->details->location, "starred"); +} + +gboolean +nautilus_directory_is_in_admin (NautilusDirectory *directory) +{ + g_assert (NAUTILUS_IS_DIRECTORY (directory)); + + if (directory->details->location == NULL) + { + return FALSE; + } + + return g_file_has_uri_scheme (directory->details->location, "admin"); +} + +gboolean +nautilus_directory_are_all_files_seen (NautilusDirectory *directory) +{ + g_return_val_if_fail (NAUTILUS_IS_DIRECTORY (directory), FALSE); + + return NAUTILUS_DIRECTORY_CLASS (G_OBJECT_GET_CLASS (directory))->are_all_files_seen (directory); +} + +static void +add_to_hash_table (NautilusDirectory *directory, + NautilusFile *file, + GList *node) +{ + gchar *name; + + name = nautilus_file_get_name (file); + + g_assert (name != NULL); + g_assert (node != NULL); + g_assert (g_hash_table_lookup (directory->details->file_hash, + name) == NULL); + g_hash_table_insert (directory->details->file_hash, name, node); +} + +static GList * +extract_from_hash_table (NautilusDirectory *directory, + NautilusFile *file) +{ + g_autofree gchar *name = NULL; + GList *node; + + name = nautilus_file_get_name (file); + if (name == NULL) + { + return NULL; + } + + /* Find the list node in the hash table. */ + node = g_hash_table_lookup (directory->details->file_hash, name); + g_hash_table_remove (directory->details->file_hash, name); + + return node; +} + +void +nautilus_directory_add_file (NautilusDirectory *directory, + NautilusFile *file) +{ + GList *node; + gboolean add_to_work_queue; + + g_assert (NAUTILUS_IS_DIRECTORY (directory)); + g_assert (NAUTILUS_IS_FILE (file)); + + /* Add to list. */ + node = g_list_prepend (directory->details->file_list, file); + directory->details->file_list = node; + + /* Add to hash table. */ + add_to_hash_table (directory, file, node); + + directory->details->confirmed_file_count++; + + add_to_work_queue = FALSE; + if (nautilus_directory_is_file_list_monitored (directory)) + { + /* Ref if we are monitoring, since monitoring owns the file list. */ + nautilus_file_ref (file); + add_to_work_queue = TRUE; + } + else if (nautilus_directory_has_active_request_for_file (directory, file)) + { + /* We're waiting for the file in a call_when_ready. Make sure + * we add the file to the work queue so that said waiter won't + * wait forever for e.g. all files in the directory to be done */ + add_to_work_queue = TRUE; + } + + if (add_to_work_queue) + { + nautilus_directory_add_file_to_work_queue (directory, file); + } +} + +void +nautilus_directory_remove_file (NautilusDirectory *directory, + NautilusFile *file) +{ + GList *node; + + g_assert (NAUTILUS_IS_DIRECTORY (directory)); + g_assert (NAUTILUS_IS_FILE (file)); + + /* Find the list node in the hash table. */ + node = extract_from_hash_table (directory, file); + g_assert (node != NULL); + g_assert (node->data == file); + + /* Remove the item from the list. */ + directory->details->file_list = g_list_remove_link + (directory->details->file_list, node); + g_list_free_1 (node); + + nautilus_directory_remove_file_from_work_queue (directory, file); + + if (!file->details->unconfirmed) + { + directory->details->confirmed_file_count--; + } + + /* Unref if we are monitoring. */ + if (nautilus_directory_is_file_list_monitored (directory)) + { + nautilus_file_unref (file); + } +} + +GList * +nautilus_directory_begin_file_name_change (NautilusDirectory *directory, + NautilusFile *file) +{ + /* Find the list node in the hash table. */ + return extract_from_hash_table (directory, file); +} + +void +nautilus_directory_end_file_name_change (NautilusDirectory *directory, + NautilusFile *file, + GList *node) +{ + /* Add the list node to the hash table. */ + if (node != NULL) + { + add_to_hash_table (directory, file, node); + } +} + +NautilusFile * +nautilus_directory_find_file_by_name (NautilusDirectory *directory, + const char *name) +{ + GList *node; + + g_return_val_if_fail (NAUTILUS_IS_DIRECTORY (directory), NULL); + g_return_val_if_fail (name != NULL, NULL); + + node = g_hash_table_lookup (directory->details->file_hash, + name); + return node == NULL ? NULL : NAUTILUS_FILE (node->data); +} + +void +nautilus_directory_emit_files_added (NautilusDirectory *directory, + GList *added_files) +{ + nautilus_profile_start (NULL); + if (added_files != NULL) + { + g_signal_emit (directory, + signals[FILES_ADDED], 0, + added_files); + } + nautilus_profile_end (NULL); +} + +void +nautilus_directory_emit_files_changed (NautilusDirectory *directory, + GList *changed_files) +{ + nautilus_profile_start (NULL); + if (changed_files != NULL) + { + g_signal_emit (directory, + signals[FILES_CHANGED], 0, + changed_files); + } + nautilus_profile_end (NULL); +} + +void +nautilus_directory_emit_change_signals (NautilusDirectory *directory, + GList *changed_files) +{ + GList *p; + + nautilus_profile_start (NULL); + for (p = changed_files; p != NULL; p = p->next) + { + nautilus_file_emit_changed (p->data); + } + nautilus_directory_emit_files_changed (directory, changed_files); + nautilus_profile_end (NULL); +} + +void +nautilus_directory_emit_done_loading (NautilusDirectory *directory) +{ + g_signal_emit (directory, + signals[DONE_LOADING], 0); +} + +void +nautilus_directory_emit_load_error (NautilusDirectory *directory, + GError *error) +{ + g_signal_emit (directory, + signals[LOAD_ERROR], 0, + error); +} + +/* Return a directory object for this one's parent. */ +static NautilusDirectory * +get_parent_directory (GFile *location) +{ + NautilusDirectory *directory; + GFile *parent; + + parent = g_file_get_parent (location); + if (parent) + { + directory = nautilus_directory_get_internal (parent, TRUE); + g_object_unref (parent); + return directory; + } + return NULL; +} + +/* If a directory object exists for this one's parent, then + * return it, otherwise return NULL. + */ +static NautilusDirectory * +get_parent_directory_if_exists (GFile *location) +{ + NautilusDirectory *directory; + GFile *parent; + + parent = g_file_get_parent (location); + if (parent) + { + directory = nautilus_directory_get_internal (parent, FALSE); + g_object_unref (parent); + return directory; + } + return NULL; +} + +static void +hash_table_list_prepend (GHashTable *table, + gconstpointer key, + gpointer data) +{ + GList *list; + + list = g_hash_table_lookup (table, key); + list = g_list_prepend (list, data); + g_hash_table_insert (table, (gpointer) key, list); +} + +static void +call_files_added_free_list (gpointer key, + gpointer value, + gpointer user_data) +{ + g_assert (NAUTILUS_IS_DIRECTORY (key)); + g_assert (value != NULL); + g_assert (user_data == NULL); + + g_signal_emit (key, + signals[FILES_ADDED], 0, + value); + g_list_free (value); +} + +static void +call_files_changed_common (NautilusDirectory *self, + GList *file_list) +{ + GList *node; + NautilusFile *file; + + for (node = file_list; node != NULL; node = node->next) + { + NautilusDirectory *directory; + + file = node->data; + directory = nautilus_file_get_directory (file); + + if (directory == self) + { + nautilus_directory_add_file_to_work_queue (self, file); + } + } + nautilus_directory_async_state_changed (self); + nautilus_directory_emit_change_signals (self, file_list); +} + +static void +call_files_changed_free_list (gpointer key, + gpointer value, + gpointer user_data) +{ + g_assert (value != NULL); + g_assert (user_data == NULL); + + call_files_changed_common (NAUTILUS_DIRECTORY (key), value); + g_list_free (value); +} + +static void +call_files_changed_unref_free_list (gpointer key, + gpointer value, + gpointer user_data) +{ + g_assert (value != NULL); + g_assert (user_data == NULL); + + call_files_changed_common (NAUTILUS_DIRECTORY (key), value); + nautilus_file_list_free (value); +} + +static void +call_get_file_info_free_list (gpointer key, + gpointer value, + gpointer user_data) +{ + NautilusDirectory *directory; + GList *files; + + g_assert (NAUTILUS_IS_DIRECTORY (key)); + g_assert (value != NULL); + g_assert (user_data == NULL); + + directory = key; + files = value; + + nautilus_directory_get_info_for_new_files (directory, files); + g_list_foreach (files, (GFunc) g_object_unref, NULL); + g_list_free (files); +} + +static void +invalidate_count_and_unref (gpointer key, + gpointer value, + gpointer user_data) +{ + g_assert (NAUTILUS_IS_DIRECTORY (key)); + g_assert (value == key); + g_assert (user_data == NULL); + + nautilus_directory_invalidate_count_and_mime_list (key); + nautilus_directory_unref (key); +} + +static void +collect_parent_directories (GHashTable *hash_table, + NautilusDirectory *directory) +{ + g_assert (hash_table != NULL); + g_assert (NAUTILUS_IS_DIRECTORY (directory)); + + if (g_hash_table_lookup (hash_table, directory) == NULL) + { + nautilus_directory_ref (directory); + g_hash_table_insert (hash_table, directory, directory); + } +} + +void +nautilus_directory_notify_files_added (GList *files) +{ + GHashTable *added_lists; + GList *p; + NautilusDirectory *directory; + GHashTable *parent_directories; + NautilusFile *file; + GFile *location, *parent; + + nautilus_profile_start (NULL); + + /* Make a list of added files in each directory. */ + added_lists = g_hash_table_new (NULL, NULL); + + /* Make a list of parent directories that will need their counts updated. */ + parent_directories = g_hash_table_new (NULL, NULL); + + for (p = files; p != NULL; p = p->next) + { + location = p->data; + + /* See if the directory is already known. */ + directory = get_parent_directory_if_exists (location); + if (directory == NULL) + { + /* In case the directory is not being + * monitored, but the corresponding file is, + * we must invalidate it's item count. + */ + + + file = NULL; + parent = g_file_get_parent (location); + if (parent) + { + file = nautilus_file_get_existing (parent); + g_object_unref (parent); + } + + if (file != NULL) + { + nautilus_file_invalidate_count_and_mime_list (file); + nautilus_file_unref (file); + } + + continue; + } + + collect_parent_directories (parent_directories, directory); + + /* If no one is monitoring files in the directory, nothing to do. */ + if (!nautilus_directory_is_file_list_monitored (directory)) + { + nautilus_directory_unref (directory); + continue; + } + + file = nautilus_file_get_existing (location); + /* We check is_added here, because the file could have been added + * to the directory by a nautilus_file_get() but not gotten + * files_added emitted + */ + if (file && file->details->is_added) + { + /* A file already exists, it was probably renamed. + * If it was renamed this could be ignored, but + * queue a change just in case */ + nautilus_file_changed (file); + } + else + { + hash_table_list_prepend (added_lists, + directory, + g_object_ref (location)); + } + nautilus_file_unref (file); + nautilus_directory_unref (directory); + } + + /* Now get file info for the new files. This creates NautilusFile + * objects for the new files, and sends out a files_added signal. + */ + g_hash_table_foreach (added_lists, call_get_file_info_free_list, NULL); + g_hash_table_destroy (added_lists); + + /* Invalidate count for each parent directory. */ + g_hash_table_foreach (parent_directories, invalidate_count_and_unref, NULL); + g_hash_table_destroy (parent_directories); + + nautilus_profile_end (NULL); +} + +void +nautilus_directory_notify_files_changed (GList *files) +{ + GHashTable *changed_lists; + GList *node; + GFile *location; + g_autoptr (GFile) parent = NULL; + g_autoptr (NautilusDirectory) dir = NULL; + NautilusFile *file; + + /* Make a list of changed files in each directory. */ + changed_lists = g_hash_table_new (NULL, NULL); + + /* Go through all the notifications. */ + for (node = files; node != NULL; node = node->next) + { + location = node->data; + + /* Find the file. */ + file = nautilus_file_get_existing (location); + if (file != NULL) + { + NautilusDirectory *directory; + + directory = nautilus_file_get_directory (file); + + /* Tell it to re-get info now, and later emit + * a changed signal. + */ + file->details->file_info_is_up_to_date = FALSE; + nautilus_file_invalidate_extension_info_internal (file); + + hash_table_list_prepend (changed_lists, directory, file); + } + else + { + parent = g_file_get_parent (location); + dir = nautilus_directory_get_existing (parent); + if (dir != NULL && dir->details->new_files_in_progress != NULL && + files != dir->details->files_changed_while_adding) + { + dir->details->files_changed_while_adding = + g_list_prepend (dir->details->files_changed_while_adding, + g_object_ref (location)); + } + } + } + + /* Now send out the changed signals. */ + g_hash_table_foreach (changed_lists, call_files_changed_unref_free_list, NULL); + g_hash_table_destroy (changed_lists); +} + +void +nautilus_directory_notify_files_removed (GList *files) +{ + GHashTable *changed_lists; + GList *p; + GHashTable *parent_directories; + NautilusFile *file; + GFile *location; + + /* Make a list of changed files in each directory. */ + changed_lists = g_hash_table_new (NULL, NULL); + + /* Make a list of parent directories that will need their counts updated. */ + parent_directories = g_hash_table_new (NULL, NULL); + + /* Go through all the notifications. */ + for (p = files; p != NULL; p = p->next) + { + NautilusDirectory *directory; + + location = p->data; + + /* Update file count for parent directory if anyone might care. */ + directory = get_parent_directory_if_exists (location); + if (directory != NULL) + { + collect_parent_directories (parent_directories, directory); + nautilus_directory_unref (directory); + } + + /* Find the file. */ + file = nautilus_file_get_existing (location); + if (file != NULL && !nautilus_file_rename_in_progress (file)) + { + directory = nautilus_file_get_directory (file); + + /* Mark it gone and prepare to send the changed signal. */ + nautilus_file_mark_gone (file); + hash_table_list_prepend (changed_lists, + directory, nautilus_file_ref (file)); + } + nautilus_file_unref (file); + } + + /* Now send out the changed signals. */ + g_hash_table_foreach (changed_lists, call_files_changed_unref_free_list, NULL); + g_hash_table_destroy (changed_lists); + + /* Invalidate count for each parent directory. */ + g_hash_table_foreach (parent_directories, invalidate_count_and_unref, NULL); + g_hash_table_destroy (parent_directories); +} + +static void +set_directory_location (NautilusDirectory *directory, + GFile *location) +{ + if (directory->details->location) + { + g_object_unref (directory->details->location); + } + directory->details->location = g_object_ref (location); + + g_object_notify_by_pspec (G_OBJECT (directory), properties[PROP_LOCATION]); +} + +static void +change_directory_location (NautilusDirectory *directory, + GFile *new_location) +{ + /* I believe it's impossible for a self-owned file/directory + * to be moved. But if that did somehow happen, this function + * wouldn't do enough to handle it. + */ + g_assert (directory->details->as_file == NULL); + + g_hash_table_remove (directories, + directory->details->location); + + set_directory_location (directory, new_location); + + g_hash_table_insert (directories, + directory->details->location, + directory); +} + +typedef struct +{ + GFile *container; + GList *directories; +} CollectData; + +static void +collect_directories_by_container (gpointer key, + gpointer value, + gpointer callback_data) +{ + NautilusDirectory *directory; + CollectData *collect_data; + GFile *location; + + location = (GFile *) key; + directory = NAUTILUS_DIRECTORY (value); + collect_data = (CollectData *) callback_data; + + if (g_file_has_prefix (location, collect_data->container) || + g_file_equal (collect_data->container, location)) + { + nautilus_directory_ref (directory); + collect_data->directories = + g_list_prepend (collect_data->directories, + directory); + } +} + +static GList * +nautilus_directory_moved_internal (GFile *old_location, + GFile *new_location) +{ + CollectData collection; + NautilusDirectory *directory; + GList *node, *affected_files; + GFile *new_directory_location; + char *relative_path; + + collection.container = old_location; + collection.directories = NULL; + + g_hash_table_foreach (directories, + collect_directories_by_container, + &collection); + + affected_files = NULL; + + for (node = collection.directories; node != NULL; node = node->next) + { + directory = NAUTILUS_DIRECTORY (node->data); + new_directory_location = NULL; + + if (g_file_equal (directory->details->location, old_location)) + { + new_directory_location = g_object_ref (new_location); + } + else + { + relative_path = g_file_get_relative_path (old_location, + directory->details->location); + if (relative_path != NULL) + { + new_directory_location = g_file_resolve_relative_path (new_location, relative_path); + g_free (relative_path); + } + } + + if (new_directory_location) + { + change_directory_location (directory, new_directory_location); + g_object_unref (new_directory_location); + + /* Collect affected files. */ + if (directory->details->as_file != NULL) + { + affected_files = g_list_prepend + (affected_files, + nautilus_file_ref (directory->details->as_file)); + } + affected_files = g_list_concat + (affected_files, + nautilus_file_list_copy (directory->details->file_list)); + } + + nautilus_directory_unref (directory); + } + + g_list_free (collection.directories); + + return affected_files; +} + +void +nautilus_directory_moved (const char *old_uri, + const char *new_uri) +{ + GList *list, *node; + GHashTable *hash; + NautilusFile *file; + GFile *old_location; + GFile *new_location; + + hash = g_hash_table_new (NULL, NULL); + + old_location = g_file_new_for_uri (old_uri); + new_location = g_file_new_for_uri (new_uri); + + list = nautilus_directory_moved_internal (old_location, new_location); + for (node = list; node != NULL; node = node->next) + { + NautilusDirectory *directory; + + file = NAUTILUS_FILE (node->data); + directory = nautilus_file_get_directory (file); + + hash_table_list_prepend (hash, directory, nautilus_file_ref (file)); + } + nautilus_file_list_free (list); + + g_object_unref (old_location); + g_object_unref (new_location); + + g_hash_table_foreach (hash, call_files_changed_unref_free_list, NULL); + g_hash_table_destroy (hash); +} + +void +nautilus_directory_notify_files_moved (GList *file_pairs) +{ + GList *p, *affected_files, *node; + GFilePair *pair; + NautilusFile *file; + NautilusDirectory *old_directory, *new_directory; + GHashTable *parent_directories; + GList *new_files_list, *unref_list; + GHashTable *added_lists, *changed_lists; + char *name; + NautilusFileAttributes cancel_attributes; + GFile *to_location, *from_location; + + /* Make a list of added and changed files in each directory. */ + new_files_list = NULL; + added_lists = g_hash_table_new (NULL, NULL); + changed_lists = g_hash_table_new (NULL, NULL); + unref_list = NULL; + + /* Make a list of parent directories that will need their counts updated. */ + parent_directories = g_hash_table_new (NULL, NULL); + + cancel_attributes = nautilus_file_get_all_attributes (); + + for (p = file_pairs; p != NULL; p = p->next) + { + pair = p->data; + from_location = pair->from; + to_location = pair->to; + + /* Handle overwriting a file. */ + file = nautilus_file_get_existing (to_location); + if (file != NULL) + { + NautilusDirectory *directory; + + directory = nautilus_file_get_directory (file); + + /* Mark it gone and prepare to send the changed signal. */ + nautilus_file_mark_gone (file); + hash_table_list_prepend (changed_lists, directory, file); + collect_parent_directories (parent_directories, directory); + } + + /* Update any directory objects that are affected. */ + affected_files = nautilus_directory_moved_internal (from_location, + to_location); + for (node = affected_files; node != NULL; node = node->next) + { + NautilusDirectory *directory; + + file = NAUTILUS_FILE (node->data); + directory = nautilus_file_get_directory (file); + hash_table_list_prepend (changed_lists, directory, file); + } + unref_list = g_list_concat (unref_list, affected_files); + + /* Move an existing file. */ + file = nautilus_file_get_existing (from_location); + if (file == NULL) + { + /* Handle this as if it was a new file. */ + new_files_list = g_list_prepend (new_files_list, + to_location); + } + else + { + NautilusDirectory *directory; + + directory = nautilus_file_get_directory (file); + + /* Handle notification in the old directory. */ + old_directory = directory; + collect_parent_directories (parent_directories, old_directory); + + /* Cancel loading of attributes in the old directory */ + nautilus_directory_cancel_loading_file_attributes + (old_directory, file, cancel_attributes); + + /* Locate the new directory. */ + new_directory = get_parent_directory (to_location); + collect_parent_directories (parent_directories, new_directory); + /* We can unref now -- new_directory is in the + * parent directories list so it will be + * around until the end of this function + * anyway. + */ + nautilus_directory_unref (new_directory); + + /* Update the file's name and directory. */ + name = g_file_get_basename (to_location); + nautilus_file_update_name_and_directory + (file, name, new_directory); + g_free (name); + + /* Update file attributes */ + nautilus_file_invalidate_attributes (file, NAUTILUS_FILE_ATTRIBUTE_INFO); + + hash_table_list_prepend (changed_lists, + old_directory, + file); + if (old_directory != new_directory) + { + hash_table_list_prepend (added_lists, + new_directory, + file); + } + + /* Unref each file once to balance out nautilus_file_get_by_uri. */ + unref_list = g_list_prepend (unref_list, file); + } + } + + /* Now send out the changed and added signals for existing file objects. */ + g_hash_table_foreach (changed_lists, call_files_changed_free_list, NULL); + g_hash_table_destroy (changed_lists); + g_hash_table_foreach (added_lists, call_files_added_free_list, NULL); + g_hash_table_destroy (added_lists); + + /* Let the file objects go. */ + nautilus_file_list_free (unref_list); + + /* Invalidate count for each parent directory. */ + g_hash_table_foreach (parent_directories, invalidate_count_and_unref, NULL); + g_hash_table_destroy (parent_directories); + + /* Separate handling for brand new file objects. */ + nautilus_directory_notify_files_added (new_files_list); + g_list_free (new_files_list); +} + +gboolean +nautilus_directory_contains_file (NautilusDirectory *directory, + NautilusFile *file) +{ + g_return_val_if_fail (NAUTILUS_IS_DIRECTORY (directory), FALSE); + g_return_val_if_fail (NAUTILUS_IS_FILE (file), FALSE); + + if (nautilus_file_is_gone (file)) + { + return FALSE; + } + + return NAUTILUS_DIRECTORY_CLASS (G_OBJECT_GET_CLASS (directory))->contains_file (directory, file); +} + +NautilusFile * +nautilus_directory_get_file_by_name (NautilusDirectory *directory, + const gchar *name) +{ + GList *files; + GList *l; + NautilusFile *result = NULL; + + files = nautilus_directory_get_file_list (directory); + + for (l = files; l != NULL; l = l->next) + { + if (nautilus_file_compare_display_name (l->data, name) == 0) + { + result = nautilus_file_ref (l->data); + break; + } + } + + nautilus_file_list_free (files); + + return result; +} + +void +nautilus_directory_call_when_ready (NautilusDirectory *directory, + NautilusFileAttributes file_attributes, + gboolean wait_for_all_files, + NautilusDirectoryCallback callback, + gpointer callback_data) +{ + g_return_if_fail (NAUTILUS_IS_DIRECTORY (directory)); + g_return_if_fail (callback != NULL); + + NAUTILUS_DIRECTORY_CLASS (G_OBJECT_GET_CLASS (directory))->call_when_ready + (directory, file_attributes, wait_for_all_files, + callback, callback_data); +} + +void +nautilus_directory_cancel_callback (NautilusDirectory *directory, + NautilusDirectoryCallback callback, + gpointer callback_data) +{ + g_return_if_fail (NAUTILUS_IS_DIRECTORY (directory)); + g_return_if_fail (callback != NULL); + + NAUTILUS_DIRECTORY_CLASS (G_OBJECT_GET_CLASS (directory))->cancel_callback + (directory, callback, callback_data); +} + +void +nautilus_directory_file_monitor_add (NautilusDirectory *directory, + gconstpointer client, + gboolean monitor_hidden_files, + NautilusFileAttributes file_attributes, + NautilusDirectoryCallback callback, + gpointer callback_data) +{ + g_return_if_fail (NAUTILUS_IS_DIRECTORY (directory)); + g_return_if_fail (client != NULL); + + NAUTILUS_DIRECTORY_CLASS (G_OBJECT_GET_CLASS (directory))->file_monitor_add + (directory, client, + monitor_hidden_files, + file_attributes, + callback, callback_data); +} + +void +nautilus_directory_file_monitor_remove (NautilusDirectory *directory, + gconstpointer client) +{ + g_return_if_fail (NAUTILUS_IS_DIRECTORY (directory)); + g_return_if_fail (client != NULL); + + NAUTILUS_DIRECTORY_CLASS (G_OBJECT_GET_CLASS (directory))->file_monitor_remove + (directory, client); +} + +void +nautilus_directory_force_reload (NautilusDirectory *directory) +{ + g_return_if_fail (NAUTILUS_IS_DIRECTORY (directory)); + + NAUTILUS_DIRECTORY_CLASS (G_OBJECT_GET_CLASS (directory))->force_reload (directory); +} + +gboolean +nautilus_directory_is_not_empty (NautilusDirectory *directory) +{ + g_return_val_if_fail (NAUTILUS_IS_DIRECTORY (directory), FALSE); + + return NAUTILUS_DIRECTORY_CLASS (G_OBJECT_GET_CLASS (directory))->is_not_empty (directory); +} + +GList * +nautilus_directory_get_file_list (NautilusDirectory *directory) +{ + return NAUTILUS_DIRECTORY_CLASS (G_OBJECT_GET_CLASS (directory))->get_file_list (directory); +} + +gboolean +nautilus_directory_is_editable (NautilusDirectory *directory) +{ + return NAUTILUS_DIRECTORY_CLASS (G_OBJECT_GET_CLASS (directory))->is_editable (directory); +} + +GList * +nautilus_directory_match_pattern (NautilusDirectory *directory, + const char *pattern) +{ + GList *files, *l, *ret; + GPatternSpec *spec; + + + ret = NULL; + spec = g_pattern_spec_new (pattern); + + files = nautilus_directory_get_file_list (directory); + for (l = files; l; l = l->next) + { + NautilusFile *file; + char *name; + + file = NAUTILUS_FILE (l->data); + name = nautilus_file_get_display_name (file); + + if (g_pattern_match_string (spec, name)) + { + ret = g_list_prepend (ret, nautilus_file_ref (file)); + } + + g_free (name); + } + + g_pattern_spec_free (spec); + nautilus_file_list_free (files); + + return ret; +} + +/** + * nautilus_directory_list_ref + * + * Ref all the directories in a list. + * @list: GList of directories. + **/ +GList * +nautilus_directory_list_ref (GList *list) +{ + g_list_foreach (list, (GFunc) nautilus_directory_ref, NULL); + return list; +} + +/** + * nautilus_directory_list_unref + * + * Unref all the directories in a list. + * @list: GList of directories. + **/ +void +nautilus_directory_list_unref (GList *list) +{ + g_list_foreach (list, (GFunc) nautilus_directory_unref, NULL); +} + +/** + * nautilus_directory_list_free + * + * Free a list of directories after unrefing them. + * @list: GList of directories. + **/ +void +nautilus_directory_list_free (GList *list) +{ + nautilus_directory_list_unref (list); + g_list_free (list); +} + +/** + * nautilus_directory_list_copy + * + * Copy the list of directories, making a new ref of each, + * @list: GList of directories. + **/ +GList * +nautilus_directory_list_copy (GList *list) +{ + return g_list_copy (nautilus_directory_list_ref (list)); +} + +static int +compare_by_uri (NautilusDirectory *a, + NautilusDirectory *b) +{ + char *uri_a, *uri_b; + int res; + + uri_a = g_file_get_uri (a->details->location); + uri_b = g_file_get_uri (b->details->location); + + res = strcmp (uri_a, uri_b); + + g_free (uri_a); + g_free (uri_b); + + return res; +} + +static int +compare_by_uri_cover (gconstpointer a, + gconstpointer b) +{ + return compare_by_uri (NAUTILUS_DIRECTORY (a), NAUTILUS_DIRECTORY (b)); +} + +/** + * nautilus_directory_list_sort_by_uri + * + * Sort the list of directories by directory uri. + * @list: GList of directories. + **/ +GList * +nautilus_directory_list_sort_by_uri (GList *list) +{ + return g_list_sort (list, compare_by_uri_cover); +} + +#if !defined (NAUTILUS_OMIT_SELF_CHECK) + +#include + +static int data_dummy; +static gboolean got_files_flag; + +static void +got_files_callback (NautilusDirectory *directory, + GList *files, + gpointer callback_data) +{ + g_assert (NAUTILUS_IS_DIRECTORY (directory)); + g_assert (g_list_length (files) > 10); + g_assert (callback_data == &data_dummy); + + got_files_flag = TRUE; +} + +/* Return the number of extant NautilusDirectories */ +int +nautilus_directory_number_outstanding (void) +{ + return directories ? g_hash_table_size (directories) : 0; +} + +void +nautilus_directory_dump (NautilusDirectory *directory) +{ + g_autofree gchar *uri = NULL; + + uri = g_file_get_uri (directory->details->location); + g_print ("uri: %s\n", uri); + g_print ("ref count: %d\n", G_OBJECT (directory)->ref_count); +} + +void +nautilus_self_check_directory (void) +{ + NautilusDirectory *directory; + NautilusFile *file; + + directory = nautilus_directory_get_by_uri ("file:///etc"); + file = nautilus_file_get_by_uri ("file:///etc/passwd"); + + EEL_CHECK_INTEGER_RESULT (g_hash_table_size (directories), 1); + + nautilus_directory_file_monitor_add + (directory, &data_dummy, + TRUE, 0, NULL, NULL); + + /* FIXME: these need to be updated to the new metadata infrastructure + * as make check doesn't pass. + * nautilus_file_set_metadata (file, "test", "default", "value"); + * EEL_CHECK_STRING_RESULT (nautilus_file_get_metadata (file, "test", "default"), "value"); + * + * nautilus_file_set_boolean_metadata (file, "test_boolean", TRUE, TRUE); + * EEL_CHECK_BOOLEAN_RESULT (nautilus_file_get_boolean_metadata (file, "test_boolean", TRUE), TRUE); + * nautilus_file_set_boolean_metadata (file, "test_boolean", TRUE, FALSE); + * EEL_CHECK_BOOLEAN_RESULT (nautilus_file_get_boolean_metadata (file, "test_boolean", TRUE), FALSE); + * EEL_CHECK_BOOLEAN_RESULT (nautilus_file_get_boolean_metadata (NULL, "test_boolean", TRUE), TRUE); + * + * nautilus_file_set_integer_metadata (file, "test_integer", 0, 17); + * EEL_CHECK_INTEGER_RESULT (nautilus_file_get_integer_metadata (file, "test_integer", 0), 17); + * nautilus_file_set_integer_metadata (file, "test_integer", 0, -1); + * EEL_CHECK_INTEGER_RESULT (nautilus_file_get_integer_metadata (file, "test_integer", 0), -1); + * nautilus_file_set_integer_metadata (file, "test_integer", 42, 42); + * EEL_CHECK_INTEGER_RESULT (nautilus_file_get_integer_metadata (file, "test_integer", 42), 42); + * EEL_CHECK_INTEGER_RESULT (nautilus_file_get_integer_metadata (NULL, "test_integer", 42), 42); + * EEL_CHECK_INTEGER_RESULT (nautilus_file_get_integer_metadata (file, "nonexistent_key", 42), 42); + */ + + EEL_CHECK_BOOLEAN_RESULT (nautilus_directory_get_by_uri ("file:///etc") == directory, TRUE); + nautilus_directory_unref (directory); + + EEL_CHECK_BOOLEAN_RESULT (nautilus_directory_get_by_uri ("file:///etc/") == directory, TRUE); + nautilus_directory_unref (directory); + + EEL_CHECK_BOOLEAN_RESULT (nautilus_directory_get_by_uri ("file:///etc////") == directory, TRUE); + nautilus_directory_unref (directory); + + nautilus_file_unref (file); + + nautilus_directory_file_monitor_remove (directory, &data_dummy); + + nautilus_directory_unref (directory); + + for (guint i = 0; g_hash_table_size (directories) != 0 && i < 100000; i++) + { + g_main_context_iteration (NULL, TRUE); + } + + EEL_CHECK_INTEGER_RESULT (g_hash_table_size (directories), 0); + + directory = nautilus_directory_get_by_uri ("file:///etc"); + + got_files_flag = FALSE; + + nautilus_directory_call_when_ready (directory, + NAUTILUS_FILE_ATTRIBUTE_INFO | + NAUTILUS_FILE_ATTRIBUTE_DEEP_COUNTS, + TRUE, + got_files_callback, &data_dummy); + + for (guint i = 0; !got_files_flag && i < 100000; i++) + { + g_main_context_iteration (NULL, TRUE); + } + + EEL_CHECK_BOOLEAN_RESULT (got_files_flag, TRUE); + + EEL_CHECK_BOOLEAN_RESULT (directory->details->file_list == NULL, TRUE); + + EEL_CHECK_INTEGER_RESULT (g_hash_table_size (directories), 1); + + file = nautilus_file_get_by_uri ("file:///etc/passwd"); + + /* EEL_CHECK_STRING_RESULT (nautilus_file_get_metadata (file, "test", "default"), "value"); */ + + nautilus_file_unref (file); + + nautilus_directory_unref (directory); + + EEL_CHECK_INTEGER_RESULT (g_hash_table_size (directories), 0); +} + +#endif /* !NAUTILUS_OMIT_SELF_CHECK */ diff --git a/src/nautilus-directory.h b/src/nautilus-directory.h new file mode 100644 index 0000000..70317a3 --- /dev/null +++ b/src/nautilus-directory.h @@ -0,0 +1,252 @@ +/* + nautilus-directory.h: Nautilus directory model. + + Copyright (C) 1999, 2000, 2001 Eazel, Inc. + + This program is free software; you can redistribute it and/or + modify it under the terms of the GNU General Public License as + published by the Free Software Foundation; either version 2 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 warranty of + MERCHANTABILITY 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 . + + Author: Darin Adler +*/ + +#pragma once + +#include +#include + +#include "nautilus-enums.h" + +/* NautilusDirectory is a class that manages the model for a directory, + real or virtual, for Nautilus, mainly the file-manager component. The directory is + responsible for managing both real data and cached metadata. On top of + the file system independence provided by gio, the directory + object also provides: + + 1) A synchronization framework, which notifies via signals as the + set of known files changes. + 2) An abstract interface for getting attributes and performing + operations on files. +*/ + +#define NAUTILUS_DIRECTORY_PROVIDER_EXTENSION_POINT_NAME "nautilus-directory-provider" + +#define NAUTILUS_TYPE_DIRECTORY nautilus_directory_get_type() +#define NAUTILUS_DIRECTORY(obj) \ + (G_TYPE_CHECK_INSTANCE_CAST ((obj), NAUTILUS_TYPE_DIRECTORY, NautilusDirectory)) +#define NAUTILUS_DIRECTORY_CLASS(klass) \ + (G_TYPE_CHECK_CLASS_CAST ((klass), NAUTILUS_TYPE_DIRECTORY, NautilusDirectoryClass)) +#define NAUTILUS_IS_DIRECTORY(obj) \ + (G_TYPE_CHECK_INSTANCE_TYPE ((obj), NAUTILUS_TYPE_DIRECTORY)) +#define NAUTILUS_IS_DIRECTORY_CLASS(klass) \ + (G_TYPE_CHECK_CLASS_TYPE ((klass), NAUTILUS_TYPE_DIRECTORY)) +#define NAUTILUS_DIRECTORY_GET_CLASS(obj) \ + (G_TYPE_INSTANCE_GET_CLASS ((obj), NAUTILUS_TYPE_DIRECTORY, NautilusDirectoryClass)) + +/* NautilusFile is defined both here and in nautilus-file.h. */ +#ifndef NAUTILUS_FILE_DEFINED +#define NAUTILUS_FILE_DEFINED +typedef struct NautilusFile NautilusFile; +#endif + +typedef struct _NautilusDirectory NautilusDirectory; +typedef struct NautilusDirectoryDetails NautilusDirectoryDetails; + +struct _NautilusDirectory +{ + GObject object; + NautilusDirectoryDetails *details; +}; + +typedef void (*NautilusDirectoryCallback) (NautilusDirectory *directory, + GList *files, + gpointer callback_data); + +typedef struct +{ + GObjectClass parent_class; + + /*** Notification signals for clients to connect to. ***/ + + /* The files_added signal is emitted as the directory model + * discovers new files. + */ + void (* files_added) (NautilusDirectory *directory, + GList *added_files); + + /* The files_changed signal is emitted as changes occur to + * existing files that are noticed by the synchronization framework, + * including when an old file has been deleted. When an old file + * has been deleted, this is the last chance to forget about these + * file objects, which are about to be unref'd. Use a call to + * nautilus_file_is_gone () to test for this case. + */ + void (* files_changed) (NautilusDirectory *directory, + GList *changed_files); + + /* The done_loading signal is emitted when a directory load + * request completes. This is needed because, at least in the + * case where the directory is empty, the caller will receive + * no kind of notification at all when a directory load + * initiated by `nautilus_directory_file_monitor_add' completes. + */ + void (* done_loading) (NautilusDirectory *directory); + + void (* load_error) (NautilusDirectory *directory, + GError *error); + + /*** Virtual functions for subclasses to override. ***/ + gboolean (* contains_file) (NautilusDirectory *directory, + NautilusFile *file); + void (* call_when_ready) (NautilusDirectory *directory, + NautilusFileAttributes file_attributes, + gboolean wait_for_file_list, + NautilusDirectoryCallback callback, + gpointer callback_data); + void (* cancel_callback) (NautilusDirectory *directory, + NautilusDirectoryCallback callback, + gpointer callback_data); + void (* file_monitor_add) (NautilusDirectory *directory, + gconstpointer client, + gboolean monitor_hidden_files, + NautilusFileAttributes monitor_attributes, + NautilusDirectoryCallback initial_files_callback, + gpointer callback_data); + void (* file_monitor_remove) (NautilusDirectory *directory, + gconstpointer client); + void (* force_reload) (NautilusDirectory *directory); + gboolean (* are_all_files_seen) (NautilusDirectory *directory); + gboolean (* is_not_empty) (NautilusDirectory *directory); + + /* get_file_list is a function pointer that subclasses may override to + * customize collecting the list of files in a directory. + * For example, the NautilusDesktopDirectory overrides this so that it can + * merge together the list of files in the $HOME/Desktop directory with + * the list of standard icons (Home, Trash) on the desktop. + */ + GList * (* get_file_list) (NautilusDirectory *directory); + + /* Should return FALSE if the directory is read-only and doesn't + * allow setting of metadata. + * An example of this is the search directory. + */ + gboolean (* is_editable) (NautilusDirectory *directory); + + /* Subclasses can use this to create custom files when asked by the user + * or the nautilus cache. */ + NautilusFile * (* new_file_from_filename) (NautilusDirectory *directory, + const char *filename, + gboolean self_owned); + /* Subclasses can say if they handle the location provided or should the + * nautilus file class handle it. + */ + gboolean (* handles_location) (GFile *location); +} NautilusDirectoryClass; + +/* Basic GObject requirements. */ +GType nautilus_directory_get_type (void); + +/* Get a directory given a uri. + * Creates the appropriate subclass given the uri mappings. + * Returns a referenced object, not a floating one. Unref when finished. + * If two windows are viewing the same uri, the directory object is shared. + */ +NautilusDirectory *nautilus_directory_get (GFile *location); +NautilusDirectory *nautilus_directory_get_by_uri (const char *uri); +NautilusDirectory *nautilus_directory_get_for_file (NautilusFile *file); + +/* Covers for g_object_ref and g_object_unref that provide two conveniences: + * 1) Using these is type safe. + * 2) You are allowed to call these with NULL, + */ +NautilusDirectory *nautilus_directory_ref (NautilusDirectory *directory); +void nautilus_directory_unref (NautilusDirectory *directory); + +G_DEFINE_AUTOPTR_CLEANUP_FUNC (NautilusDirectory, nautilus_directory_unref) + +/* Access to a URI. */ +char * nautilus_directory_get_uri (NautilusDirectory *directory); +GFile * nautilus_directory_get_location (NautilusDirectory *directory); + +/* Is this file still alive and in this directory? */ +gboolean nautilus_directory_contains_file (NautilusDirectory *directory, + NautilusFile *file); + +NautilusFile* nautilus_directory_get_file_by_name (NautilusDirectory *directory, + const gchar *name); +/* Get (and ref) a NautilusFile object for this directory. */ +NautilusFile * nautilus_directory_get_corresponding_file (NautilusDirectory *directory); + +/* Waiting for data that's read asynchronously. + * The file attribute and metadata keys are for files in the directory. + */ +void nautilus_directory_call_when_ready (NautilusDirectory *directory, + NautilusFileAttributes file_attributes, + gboolean wait_for_all_files, + NautilusDirectoryCallback callback, + gpointer callback_data); +void nautilus_directory_cancel_callback (NautilusDirectory *directory, + NautilusDirectoryCallback callback, + gpointer callback_data); + + +/* Monitor the files in a directory. */ +void nautilus_directory_file_monitor_add (NautilusDirectory *directory, + gconstpointer client, + gboolean monitor_hidden_files, + NautilusFileAttributes attributes, + NautilusDirectoryCallback initial_files_callback, + gpointer callback_data); +void nautilus_directory_file_monitor_remove (NautilusDirectory *directory, + gconstpointer client); +void nautilus_directory_force_reload (NautilusDirectory *directory); + +/* Get a list of all files currently known in the directory. */ +GList * nautilus_directory_get_file_list (NautilusDirectory *directory); + +GList * nautilus_directory_match_pattern (NautilusDirectory *directory, + const char *glob); + + +/* Return true if the directory has information about all the files. + * This will be false until the directory has been read at least once. + */ +gboolean nautilus_directory_are_all_files_seen (NautilusDirectory *directory); + +gboolean nautilus_directory_is_local_or_fuse (NautilusDirectory *directory); + +gboolean nautilus_directory_is_in_trash (NautilusDirectory *directory); +gboolean nautilus_directory_is_in_recent (NautilusDirectory *directory); +gboolean nautilus_directory_is_in_starred (NautilusDirectory *directory); +gboolean nautilus_directory_is_in_admin (NautilusDirectory *directory); + +/* Return false if directory contains anything besides a Nautilus metafile. + * Only valid if directory is monitored. Used by the Trash monitor. + */ +gboolean nautilus_directory_is_not_empty (NautilusDirectory *directory); + +/* Convenience functions for dealing with a list of NautilusDirectory objects that each have a ref. + * These are just convenient names for functions that work on lists of GtkObject *. + */ +GList * nautilus_directory_list_ref (GList *directory_list); +void nautilus_directory_list_unref (GList *directory_list); +void nautilus_directory_list_free (GList *directory_list); +GList * nautilus_directory_list_copy (GList *directory_list); +GList * nautilus_directory_list_sort_by_uri (GList *directory_list); + +gboolean nautilus_directory_is_editable (NautilusDirectory *directory); + +void nautilus_directory_dump (NautilusDirectory *directory); + +NautilusFile * nautilus_directory_new_file_from_filename (NautilusDirectory *directory, + const char *filename, + gboolean self_owned); diff --git a/src/nautilus-dnd.c b/src/nautilus-dnd.c new file mode 100644 index 0000000..9663e01 --- /dev/null +++ b/src/nautilus-dnd.c @@ -0,0 +1,317 @@ +/* nautilus-dnd.h - Common Drag & drop handling code + * + * Authors: Pavel Cisler , + * Ettore Perazzoli + * Copyright (C) 2000, 2001 Eazel, Inc. + * Copyright (C) 2022 The GNOME project contributors + * + * SPDX-License-Identifier: GPL-3.0-or-later + */ + +#include "nautilus-directory.h" +#include "nautilus-dnd.h" +#include "nautilus-file-utilities.h" +#include "nautilus-tag-manager.h" + +static gboolean +check_same_fs (NautilusFile *file1, + NautilusFile *file2) +{ + char *id1, *id2; + gboolean result; + + result = FALSE; + + if (file1 != NULL && file2 != NULL) + { + id1 = nautilus_file_get_filesystem_id (file1); + id2 = nautilus_file_get_filesystem_id (file2); + + if (id1 != NULL && id2 != NULL) + { + result = (strcmp (id1, id2) == 0); + } + + g_free (id1); + g_free (id2); + } + + return result; +} + +static gboolean +source_is_deletable (GFile *file) +{ + NautilusFile *naut_file; + gboolean ret; + + /* if there's no a cached NautilusFile, it returns NULL */ + naut_file = nautilus_file_get (file); + if (naut_file == NULL) + { + return FALSE; + } + + ret = nautilus_file_can_delete (naut_file); + nautilus_file_unref (naut_file); + + return ret; +} + +#if 0 && NAUTILUS_DND_NEEDS_GTK4_REIMPLEMENTATION +static void +append_drop_action_menu_item (GtkWidget *menu, + const char *text, + GdkDragAction action, + gboolean sensitive, + DropActionMenuData *damd) +{ + GtkWidget *menu_item; + + menu_item = gtk_button_new_with_mnemonic (text); + gtk_widget_set_sensitive (menu_item, sensitive); + gtk_box_append (GTK_BOX (menu), menu_item); + + gtk_style_context_add_class (gtk_widget_get_style_context (menu_item), "flat"); + + g_object_set_data (G_OBJECT (menu_item), + "action", + GINT_TO_POINTER (action)); + + g_signal_connect (menu_item, "clicked", + G_CALLBACK (drop_action_activated_callback), + damd); + + gtk_widget_show (menu_item); +} +#endif +/* Pops up a menu of actions to perform on dropped files */ +GdkDragAction +nautilus_drag_drop_action_ask (GtkWidget *widget, + GdkDragAction actions) +{ +#if 0 + GtkWidget *popover; + GtkWidget *menu; + GtkWidget *menu_item; + DropActionMenuData damd; + + /* Create the menu and set the sensitivity of the items based on the + * allowed actions. + */ + popover = gtk_popover_new (widget); + gtk_popover_set_position (GTK_POPOVER (popover), GTK_POS_TOP); + + menu = gtk_box_new (GTK_ORIENTATION_VERTICAL, 0); + gtk_widget_set_margin_top (menu, 6); + gtk_widget_set_margin_bottom (menu, 6); + gtk_widget_set_margin_start (menu, 6); + gtk_widget_set_margin_end (menu, 6); + gtk_popover_set_child (GTK_POPOVER (popover), menu); + gtk_widget_show (menu); + + append_drop_action_menu_item (menu, _("_Move Here"), + GDK_ACTION_MOVE, + (actions & GDK_ACTION_MOVE) != 0, + &damd); + + append_drop_action_menu_item (menu, _("_Copy Here"), + GDK_ACTION_COPY, + (actions & GDK_ACTION_COPY) != 0, + &damd); + + append_drop_action_menu_item (menu, _("_Link Here"), + GDK_ACTION_LINK, + (actions & GDK_ACTION_LINK) != 0, + &damd); + + menu_item = gtk_separator_new (GTK_ORIENTATION_VERTICAL); + gtk_box_append (GTK_BOX (menu), menu_item); + gtk_widget_show (menu_item); + + append_drop_action_menu_item (menu, _("Cancel"), 0, TRUE, &damd); + + damd.chosen = 0; + damd.loop = g_main_loop_new (NULL, FALSE); + + g_signal_connect (popover, "closed", + G_CALLBACK (menu_deactivate_callback), + &damd); + + gtk_grab_add (popover); + + /* We don't have pointer coords here. Just pick the center of the widget. */ + gtk_popover_set_pointing_to (GTK_POPOVER (popover), + &(GdkRectangle){ .x = 0.5 * gtk_widget_get_allocated_width (widget), + .y = 0.5 * gtk_widget_get_allocated_height (widget), + .width = 0, .height = 0 }); + + gtk_popover_popup (GTK_POPOVER (popover)); + + g_main_loop_run (damd.loop); + + gtk_grab_remove (popover); + + g_main_loop_unref (damd.loop); + + g_object_ref_sink (popover); + g_object_unref (popover); + + return damd.chosen; +#endif + return 0; +} + +GdkDragAction +nautilus_dnd_get_preferred_action (NautilusFile *target_file, + GFile *dropped) +{ + g_autoptr (NautilusDirectory) directory = NULL; + g_autoptr (GFile) target_location = NULL; + g_autoptr (NautilusFile) dropped_file = NULL; + gboolean same_fs; + gboolean source_deletable; + + g_return_val_if_fail (NAUTILUS_IS_FILE (target_file), 0); + g_return_val_if_fail (dropped == NULL || G_IS_FILE (dropped), 0); + + target_location = nautilus_file_get_location (target_file); + if (g_file_equal (target_location, dropped)) + { + return 0; + } + + /* First check target imperatives */ + directory = nautilus_directory_get_for_file (target_file); + + if (nautilus_is_file_roller_installed () && + nautilus_file_is_archive (target_file)) + { + return GDK_ACTION_COPY; + } + else if (nautilus_file_is_starred_location (target_file)) + { + if (nautilus_tag_manager_can_star_contents (nautilus_tag_manager_get (), dropped)) + { + return GDK_ACTION_COPY; + } + else + { + return 0; + } + } + else if (!nautilus_file_is_directory (target_file) || + !nautilus_file_can_write (target_file) || + !nautilus_directory_is_editable (directory)) + { + /* No other file type other than archives and directories currently + * accepts drops */ + return 0; + } + else if (nautilus_file_is_in_trash (target_file)) + { + return GDK_ACTION_MOVE; + } + + if (g_file_has_uri_scheme (dropped, "trash")) + { + return GDK_ACTION_MOVE; + } + + dropped_file = nautilus_file_get (dropped); + same_fs = check_same_fs (target_file, dropped_file); + source_deletable = source_is_deletable (dropped); + if (same_fs && source_deletable) + { + return GDK_ACTION_MOVE; + } + + return GDK_ACTION_COPY; +} + +#define MAX_DRAWN_DRAG_ICONS 10 + +GdkPaintable * +get_paintable_for_drag_selection (GList *selection, + int scale) +{ + g_autoqueue (GdkPaintable) icons = g_queue_new (); + g_autoptr (GtkSnapshot) snapshot = gtk_snapshot_new (); + NautilusFileIconFlags flags; + GdkPaintable *icon; + guint n_icons; + guint icon_size = NAUTILUS_DRAG_SURFACE_ICON_SIZE; + float dx; + float dy; + /* A wide shadow for the pile of icons gives a sense of floating. */ + GskShadow stack_shadow = {.color = {0, 0, 0, .alpha = 0.15}, .dx = 0, .dy = 2, .radius = 10 }; + /* A slight shadow swhich makes each icon in the stack look separate. */ + GskShadow icon_shadow = {.color = {0, 0, 0, .alpha = 0.30}, .dx = 0, .dy = 1, .radius = 1 }; + + g_return_val_if_fail (NAUTILUS_IS_FILE (selection->data), NULL); + + /* The selection list is reversed compared to what the user sees. Get the + * first items by starting from the end of the list. */ + flags = NAUTILUS_FILE_ICON_FLAGS_USE_THUMBNAILS; + for (GList *l = g_list_last (selection); + l != NULL && g_queue_get_length (icons) <= MAX_DRAWN_DRAG_ICONS; + l = l->prev) + { + icon = nautilus_file_get_icon_paintable (l->data, icon_size, scale, flags); + g_queue_push_tail (icons, icon); + } + + /* When there are 2 or 3 identical icons, we need to space them more, + * otherwise it would be hard to tell there is more than one icon at all. + * The more icons we have, the easier it is to notice multiple icons are + * stacked, and the more compact we want to be. + * + * 1 icon 2 icons 3 icons 4+ icons + * .--------. .--------. .--------. .--------. + * | | | | | | | | + * | | | | | | | | + * | | | | | | | | + * | | | | | | | | + * '--------' |--------| |--------| |--------| + * | | | | |--------| + * | | |--------| |--------| + * '--------' | | |--------| + * '--------' '--------' + */ + n_icons = g_queue_get_length (icons); + dx = (n_icons % 2 == 1) ? 6 : -6; + dy = (n_icons == 2) ? 10 : (n_icons == 3) ? 6 : (n_icons >= 4) ? 4 : 0; + + /* We want the first icon on top of every other. So we need to start drawing + * the stack from the bottom, that is, from the last icon. This requires us + * to jump to the last position and then move upwards one step at a time. + * Also, add 10px horizontal offset, for shadow, to workaround this GTK bug: + * https://gitlab.gnome.org/GNOME/gtk/-/issues/2341 + */ + gtk_snapshot_translate (snapshot, &GRAPHENE_POINT_INIT (10 + (dx / 2), dy * n_icons)); + gtk_snapshot_push_shadow (snapshot, &stack_shadow, 1); + for (GList *l = g_queue_peek_tail_link (icons); l != NULL; l = l->prev) + { + double w = gdk_paintable_get_intrinsic_width (l->data); + double h = gdk_paintable_get_intrinsic_height (l->data); + /* Offsets needed to center thumbnails. Floored to keep images sharp. */ + float x = floor ((icon_size - w) / 2); + float y = floor ((icon_size - h) / 2); + + gtk_snapshot_translate (snapshot, &GRAPHENE_POINT_INIT (-dx, -dy)); + + /* Alternate horizontal offset direction to give a rough pile look. */ + dx = -dx; + + gtk_snapshot_translate (snapshot, &GRAPHENE_POINT_INIT (x, y)); + gtk_snapshot_push_shadow (snapshot, &icon_shadow, 1); + + gdk_paintable_snapshot (l->data, snapshot, w, h); + + gtk_snapshot_pop (snapshot); /* End of icon shadow */ + gtk_snapshot_translate (snapshot, &GRAPHENE_POINT_INIT (-x, -y)); + } + gtk_snapshot_pop (snapshot); /* End of stack shadow */ + + return gtk_snapshot_to_paintable (snapshot, NULL); +} diff --git a/src/nautilus-dnd.h b/src/nautilus-dnd.h new file mode 100644 index 0000000..1df5725 --- /dev/null +++ b/src/nautilus-dnd.h @@ -0,0 +1,26 @@ +/* nautilus-dnd.h - Common Drag & drop handling code + * + * Authors: Pavel Cisler , + * Ettore Perazzoli + * Copyright (C) 2000 Eazel, Inc. + * Copyright (C) 2022 The GNOME project contributors + * + * SPDX-License-Identifier: GPL-3.0-or-later + */ + +#pragma once + +#include +#include "nautilus-file.h" + +#define HOVER_TIMEOUT 500 + +#define NAUTILUS_DRAG_SURFACE_ICON_SIZE 64 + +GdkDragAction nautilus_drag_drop_action_ask (GtkWidget *widget, + GdkDragAction possible_actions); + +GdkDragAction nautilus_dnd_get_preferred_action (NautilusFile *target_file, + GFile *dropped); +GdkPaintable * get_paintable_for_drag_selection (GList *selection, + int scale); diff --git a/src/nautilus-enum-types.c.template b/src/nautilus-enum-types.c.template new file mode 100644 index 0000000..9d8ac83 --- /dev/null +++ b/src/nautilus-enum-types.c.template @@ -0,0 +1,37 @@ +/*** BEGIN file-header ***/ +#include "nautilus-enum-types.h" + +/*** END file-header ***/ + +/*** BEGIN file-production ***/ +/* Enumerations from "@filename@" */ +#include "@filename@" + +/*** END file-production ***/ + +/*** BEGIN value-header ***/ +GType +@enum_name@_get_type (void) +{ + static GType type_once = 0; + + if (g_once_init_enter (&type_once)) + { + static const G@Type@Value values[] = { +/*** END value-header ***/ +/*** BEGIN value-production ***/ + { @VALUENAME@, "@VALUENAME@", "@valuenick@" }, +/*** END value-production ***/ +/*** BEGIN value-tail ***/ + { 0, NULL, NULL } + }; + + GType type = g_@type@_register_static (g_intern_static_string ("@EnumName@"), values); + + g_once_init_leave (&type_once, type); + } + + return type_once; +} + +/*** END value-tail ***/ diff --git a/src/nautilus-enum-types.h.template b/src/nautilus-enum-types.h.template new file mode 100644 index 0000000..399bbca --- /dev/null +++ b/src/nautilus-enum-types.h.template @@ -0,0 +1,25 @@ +/*** BEGIN file-header ***/ +#ifndef NAUTILUS_ENUM_TYPES_H +#define NAUTILUS_ENUM_TYPES_H + +#include + +G_BEGIN_DECLS +/*** END file-header ***/ + +/*** BEGIN file-production ***/ +/* Enumerations from "@filename@" */ + +/*** END file-production ***/ + +/*** BEGIN enumeration-production ***/ +#define @ENUMPREFIX@_TYPE_@ENUMSHORT@ (@enum_name@_get_type ()) +GType @enum_name@_get_type (void); + +/*** END enumeration-production ***/ + +/*** BEGIN file-tail ***/ +G_END_DECLS + +#endif /* NAUTILUS_ENUM_TYPES_H */ +/*** END file-tail ***/ diff --git a/src/nautilus-enums.h b/src/nautilus-enums.h new file mode 100644 index 0000000..022e622 --- /dev/null +++ b/src/nautilus-enums.h @@ -0,0 +1,81 @@ +/* Copyright (C) 2018 Ernestas Kulik + * + * This file is part of Nautilus. + * + * Nautilus is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Nautilus is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Nautilus. If not, see . + */ + +/* This is the little brother of nautilus-types.h and only contains enumerations. + * + * Now that you’ve familiarized yourself with it, the reason for its existence + * is similar, and the split is purely for convenience reasons. Include this + * when you only need a certain enumeration and not the whole header that might + * have had it originally. Otherwise, include both! + */ + +#pragma once + +/* Keep sorted alphabetically. */ + +typedef enum +{ + NAUTILUS_GRID_ICON_SIZE_SMALL = 48, + NAUTILUS_GRID_ICON_SIZE_SMALL_PLUS = 64, + NAUTILUS_GRID_ICON_SIZE_MEDIUM = 96, + NAUTILUS_GRID_ICON_SIZE_LARGE = 168, + NAUTILUS_GRID_ICON_SIZE_EXTRA_LARGE = 256, +} NautilusGridIconSize; + +typedef enum +{ + NAUTILUS_GRID_ZOOM_LEVEL_SMALL, + NAUTILUS_GRID_ZOOM_LEVEL_SMALL_PLUS, + NAUTILUS_GRID_ZOOM_LEVEL_MEDIUM, + NAUTILUS_GRID_ZOOM_LEVEL_LARGE, + NAUTILUS_GRID_ZOOM_LEVEL_EXTRA_LARGE, +} NautilusGridZoomLevel; + +typedef enum +{ + NAUTILUS_LIST_ICON_SIZE_SMALL = 16, + NAUTILUS_LIST_ICON_SIZE_MEDIUM = 32, + NAUTILUS_LIST_ICON_SIZE_LARGE = 64, +} NautilusListIconSize; + +typedef enum +{ + NAUTILUS_LIST_ZOOM_LEVEL_SMALL, + NAUTILUS_LIST_ZOOM_LEVEL_MEDIUM, + NAUTILUS_LIST_ZOOM_LEVEL_LARGE, +} NautilusListZoomLevel; + +typedef enum +{ + NAUTILUS_FILE_ATTRIBUTE_INFO = 1 << 0, /* All standard info */ + NAUTILUS_FILE_ATTRIBUTE_DEEP_COUNTS = 1 << 1, + NAUTILUS_FILE_ATTRIBUTE_DIRECTORY_ITEM_COUNT = 1 << 2, + NAUTILUS_FILE_ATTRIBUTE_DIRECTORY_ITEM_MIME_TYPES = 1 << 3, + NAUTILUS_FILE_ATTRIBUTE_EXTENSION_INFO = 1 << 4, + NAUTILUS_FILE_ATTRIBUTE_THUMBNAIL = 1 << 5, + NAUTILUS_FILE_ATTRIBUTE_MOUNT = 1 << 6, + NAUTILUS_FILE_ATTRIBUTE_FILESYSTEM_INFO = 1 << 7, +} NautilusFileAttributes; + +typedef enum +{ + NAUTILUS_OPEN_FLAG_NORMAL = 1 << 0, + NAUTILUS_OPEN_FLAG_NEW_WINDOW = 1 << 1, + NAUTILUS_OPEN_FLAG_NEW_TAB = 1 << 2, + NAUTILUS_OPEN_FLAG_DONT_MAKE_ACTIVE = 1 << 3, +} NautilusOpenFlags; diff --git a/src/nautilus-error-reporting.c b/src/nautilus-error-reporting.c new file mode 100644 index 0000000..655730a --- /dev/null +++ b/src/nautilus-error-reporting.c @@ -0,0 +1,446 @@ +/* nautilus-error-reporting.h - implementation of file manager functions that report + * errors to the user. + * + * Copyright (C) 2000 Eazel, Inc. + * + * The Gnome Library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Library General Public License as + * published by the Free Software Foundation; either version 2 of the + * License, or (at your option) any later version. + * + * The Gnome Library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Library General Public License for more details. + * + * You should have received a copy of the GNU Library General Public + * License along with the Gnome Library; see the file COPYING.LIB. If not, + * see . + * + * Authors: John Sullivan + */ + +#include + +#include "nautilus-error-reporting.h" + +#include +#include +#include "nautilus-file.h" +#include +#include + +#include "nautilus-ui-utilities.h" + +#define DEBUG_FLAG NAUTILUS_DEBUG_DIRECTORY_VIEW +#include "nautilus-debug.h" + +#define NEW_NAME_TAG "Nautilus: new name" + +static void finish_rename (NautilusFile *file, + gboolean stop_timer, + GError *error); + +static char * +get_truncated_name_for_file (NautilusFile *file) +{ + g_autofree char *file_name = NULL; + + g_assert (NAUTILUS_IS_FILE (file)); + + file_name = nautilus_file_get_display_name (file); + + return eel_str_middle_truncate (file_name, MAXIMUM_DISPLAYED_FILE_NAME_LENGTH); +} + +void +nautilus_report_error_loading_directory (NautilusFile *file, + GError *error, + GtkWindow *parent_window) +{ + g_autofree char *truncated_name = NULL; + g_autofree char *message = NULL; + + if (error == NULL || + error->message == NULL) + { + return; + } + + if (error->domain == G_IO_ERROR && + error->code == G_IO_ERROR_NOT_MOUNTED) + { + /* This case is retried automatically */ + return; + } + + truncated_name = get_truncated_name_for_file (file); + + if (error->domain == G_IO_ERROR) + { + switch (error->code) + { + case G_IO_ERROR_PERMISSION_DENIED: + { + message = g_strdup_printf (_("You do not have the permissions necessary to view the contents of “%s”."), + truncated_name); + } + break; + + case G_IO_ERROR_NOT_FOUND: + { + message = g_strdup_printf (_("“%s” could not be found. Perhaps it has recently been deleted."), + truncated_name); + } + break; + + default: + { + g_autofree char *truncated_error_message = NULL; + + truncated_error_message = eel_str_middle_truncate (error->message, + MAXIMUM_DISPLAYED_ERROR_MESSAGE_LENGTH); + + message = g_strdup_printf (_("Sorry, could not display all the contents of “%s”: %s"), truncated_name, + truncated_error_message); + } + break; + } + } + else + { + message = g_strdup (error->message); + } + + show_dialog (_("This location could not be displayed."), + message, + parent_window, + GTK_MESSAGE_ERROR); +} + +void +nautilus_report_error_setting_group (NautilusFile *file, + GError *error, + GtkWindow *parent_window) +{ + g_autofree char *truncated_name = NULL; + g_autofree char *message = NULL; + + if (error == NULL) + { + return; + } + + truncated_name = get_truncated_name_for_file (file); + + if (error->domain == G_IO_ERROR) + { + switch (error->code) + { + case G_IO_ERROR_PERMISSION_DENIED: + { + message = g_strdup_printf (_("You do not have the permissions necessary to change the group of “%s”."), + truncated_name); + } + break; + + default: + { + } + break; + } + } + + if (message == NULL) + { + g_autofree char *truncated_error_message = NULL; + + truncated_error_message = eel_str_middle_truncate (error->message, + MAXIMUM_DISPLAYED_ERROR_MESSAGE_LENGTH); + + /* We should invent decent error messages for every case we actually experience. */ + g_warning ("Hit unhandled case %s:%d in nautilus_report_error_setting_group", + g_quark_to_string (error->domain), error->code); + /* fall through */ + message = g_strdup_printf (_("Sorry, could not change the group of “%s”: %s"), truncated_name, + truncated_error_message); + } + + + show_dialog (_("The group could not be changed."), message, parent_window, GTK_MESSAGE_ERROR); +} + +void +nautilus_report_error_setting_owner (NautilusFile *file, + GError *error, + GtkWindow *parent_window) +{ + g_autofree char *truncated_name = NULL; + g_autofree char *truncated_error_message = NULL; + g_autofree char *message = NULL; + + if (error == NULL) + { + return; + } + + truncated_name = get_truncated_name_for_file (file); + + truncated_error_message = eel_str_middle_truncate (error->message, + MAXIMUM_DISPLAYED_ERROR_MESSAGE_LENGTH); + message = g_strdup_printf (_("Sorry, could not change the owner of “%s”: %s"), + truncated_name, truncated_error_message); + + show_dialog (_("The owner could not be changed."), message, parent_window, GTK_MESSAGE_ERROR); +} + +void +nautilus_report_error_setting_permissions (NautilusFile *file, + GError *error, + GtkWindow *parent_window) +{ + g_autofree char *truncated_name = NULL; + g_autofree char *truncated_error_message = NULL; + g_autofree char *message = NULL; + + if (error == NULL) + { + return; + } + + truncated_name = get_truncated_name_for_file (file); + + truncated_error_message = eel_str_middle_truncate (error->message, + MAXIMUM_DISPLAYED_ERROR_MESSAGE_LENGTH); + message = g_strdup_printf (_("Sorry, could not change the permissions of “%s”: %s"), + truncated_name, truncated_error_message); + + show_dialog (_("The permissions could not be changed."), + message, + parent_window, + GTK_MESSAGE_ERROR); +} + +typedef struct _NautilusRenameData +{ + char *name; + NautilusFileOperationCallback callback; + gpointer callback_data; +} NautilusRenameData; + +void +nautilus_report_error_renaming_file (NautilusFile *file, + const char *new_name, + GError *error, + GtkWindow *parent_window) +{ + g_autofree char *truncated_old_name = NULL; + g_autofree char *truncated_new_name = NULL; + g_autofree char *message = NULL; + + /* Truncate names for display since very long file names with no spaces + * in them won't get wrapped, and can create insanely wide dialog boxes. + */ + truncated_old_name = get_truncated_name_for_file (file); + truncated_new_name = eel_str_middle_truncate (new_name, MAXIMUM_DISPLAYED_FILE_NAME_LENGTH); + + if (error->domain == G_IO_ERROR) + { + switch (error->code) + { + case G_IO_ERROR_EXISTS: + { + message = g_strdup_printf (_("The name “%s” is already used in this location. " + "Please use a different name."), + truncated_new_name); + } + break; + + case G_IO_ERROR_NOT_FOUND: + { + message = g_strdup_printf (_("There is no “%s” in this location. " + "Perhaps it was just moved or deleted?"), + truncated_old_name); + } + break; + + case G_IO_ERROR_PERMISSION_DENIED: + { + message = g_strdup_printf (_("You do not have the permissions necessary to rename “%s”."), + truncated_old_name); + } + break; + + case G_IO_ERROR_INVALID_FILENAME: + { + if (strchr (new_name, '/') != NULL) + { + message = g_strdup_printf (_("The name “%s” is not valid because it contains the character “/”. " + "Please use a different name."), + truncated_new_name); + } + else + { + message = g_strdup_printf (_("The name “%s” is not valid. " + "Please use a different name."), + truncated_new_name); + } + } + break; + + case G_IO_ERROR_FILENAME_TOO_LONG: + { + message = g_strdup_printf (_("The name “%s” is too long. " + "Please use a different name."), + truncated_new_name); + } + break; + + default: + { + } + break; + } + } + + if (message == NULL) + { + g_autofree char *truncated_error_message = NULL; + + truncated_error_message = eel_str_middle_truncate (error->message, + MAXIMUM_DISPLAYED_ERROR_MESSAGE_LENGTH); + + /* We should invent decent error messages for every case we actually experience. */ + g_warning ("Hit unhandled case %s:%d in nautilus_report_error_renaming_file", + g_quark_to_string (error->domain), error->code); + /* fall through */ + message = g_strdup_printf (_("Sorry, could not rename “%s” to “%s”: %s"), + truncated_old_name, truncated_new_name, + truncated_error_message); + } + + show_dialog (_("The item could not be renamed."), message, parent_window, GTK_MESSAGE_ERROR); +} + +static void +nautilus_rename_data_free (NautilusRenameData *data) +{ + g_free (data->name); + g_free (data); +} + +static void +rename_callback (NautilusFile *file, + GFile *result_location, + GError *error, + gpointer callback_data) +{ + NautilusRenameData *data; + gboolean cancelled = FALSE; + + g_assert (NAUTILUS_IS_FILE (file)); + g_assert (callback_data == NULL); + + data = g_object_get_data (G_OBJECT (file), NEW_NAME_TAG); + g_assert (data != NULL); + + if (error) + { + if (!(error->domain == G_IO_ERROR && error->code == G_IO_ERROR_CANCELLED)) + { + GtkApplication *app = GTK_APPLICATION (g_application_get_default ()); + GtkWindow *window = gtk_application_get_active_window (app); + + /* If rename failed, notify the user. */ + nautilus_report_error_renaming_file (file, data->name, error, window); + } + else + { + cancelled = TRUE; + } + } + + finish_rename (file, !cancelled, error); +} + +static void +cancel_rename_callback (gpointer callback_data) +{ + nautilus_file_cancel (NAUTILUS_FILE (callback_data), rename_callback, NULL); +} + +static void +finish_rename (NautilusFile *file, + gboolean stop_timer, + GError *error) +{ + NautilusRenameData *data; + + data = g_object_get_data (G_OBJECT (file), NEW_NAME_TAG); + if (data == NULL) + { + return; + } + + /* Cancel both the rename and the timed wait. */ + nautilus_file_cancel (file, rename_callback, NULL); + if (stop_timer) + { + eel_timed_wait_stop (cancel_rename_callback, file); + } + + if (data->callback != NULL) + { + data->callback (file, NULL, error, data->callback_data); + } + + /* Let go of file name. */ + g_object_set_data (G_OBJECT (file), NEW_NAME_TAG, NULL); +} + +void +nautilus_rename_file (NautilusFile *file, + const char *new_name, + NautilusFileOperationCallback callback, + gpointer callback_data) +{ + g_autoptr (GError) error = NULL; + NautilusRenameData *data; + g_autofree char *truncated_old_name = NULL; + g_autofree char *truncated_new_name = NULL; + g_autofree char *wait_message = NULL; + g_autofree char *uri = NULL; + + g_return_if_fail (NAUTILUS_IS_FILE (file)); + g_return_if_fail (new_name != NULL); + + /* Stop any earlier rename that's already in progress. */ + error = g_error_new (G_IO_ERROR, G_IO_ERROR_CANCELLED, "Cancelled"); + finish_rename (file, TRUE, error); + + data = g_new0 (NautilusRenameData, 1); + data->name = g_strdup (new_name); + data->callback = callback; + data->callback_data = callback_data; + + /* Attach the new name to the file. */ + g_object_set_data_full (G_OBJECT (file), + NEW_NAME_TAG, + data, (GDestroyNotify) nautilus_rename_data_free); + + /* Start the timed wait to cancel the rename. */ + truncated_old_name = get_truncated_name_for_file (file); + truncated_new_name = eel_str_middle_truncate (new_name, MAXIMUM_DISPLAYED_FILE_NAME_LENGTH); + wait_message = g_strdup_printf (_("Renaming “%s” to “%s”."), + truncated_old_name, + truncated_new_name); + eel_timed_wait_start (cancel_rename_callback, file, wait_message, + NULL); /* FIXME bugzilla.gnome.org 42395: Parent this? */ + + uri = nautilus_file_get_uri (file); + DEBUG ("Renaming file %s to %s", uri, new_name); + + /* Start the rename. */ + nautilus_file_rename (file, new_name, + rename_callback, NULL); +} diff --git a/src/nautilus-error-reporting.h b/src/nautilus-error-reporting.h new file mode 100644 index 0000000..1d9a229 --- /dev/null +++ b/src/nautilus-error-reporting.h @@ -0,0 +1,53 @@ + +/* fm-error-reporting.h - interface for file manager functions that report + errors to the user. + + Copyright (C) 2000 Eazel, Inc. + + The Gnome Library is free software; you can redistribute it and/or + modify it under the terms of the GNU Library General Public License as + published by the Free Software Foundation; either version 2 of the + License, or (at your option) any later version. + + The Gnome Library is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Library General Public License for more details. + + You should have received a copy of the GNU Library General Public + License along with the Gnome Library; see the file COPYING.LIB. If not, + see . + + Authors: John Sullivan +*/ + +#pragma once + +#include +#include "nautilus-file.h" + +#define MAXIMUM_DISPLAYED_FILE_NAME_LENGTH 50 +#define MAXIMUM_DISPLAYED_ERROR_MESSAGE_LENGTH 350 + +void nautilus_report_error_loading_directory (NautilusFile *file, + GError *error, + GtkWindow *parent_window); +void nautilus_report_error_renaming_file (NautilusFile *file, + const char *new_name, + GError *error, + GtkWindow *parent_window); +void nautilus_report_error_setting_permissions (NautilusFile *file, + GError *error, + GtkWindow *parent_window); +void nautilus_report_error_setting_owner (NautilusFile *file, + GError *error, + GtkWindow *parent_window); +void nautilus_report_error_setting_group (NautilusFile *file, + GError *error, + GtkWindow *parent_window); + +/* FIXME bugzilla.gnome.org 42394: Should this file be renamed or should this function be moved? */ +void nautilus_rename_file (NautilusFile *file, + const char *new_name, + NautilusFileOperationCallback callback, + gpointer callback_data); \ No newline at end of file diff --git a/src/nautilus-file-changes-queue.c b/src/nautilus-file-changes-queue.c new file mode 100644 index 0000000..5473271 --- /dev/null +++ b/src/nautilus-file-changes-queue.c @@ -0,0 +1,344 @@ +/* + * Copyright (C) 1999, 2000, 2001 Eazel, Inc. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License as + * published by the Free Software Foundation; either version 2 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 warranty of + * MERCHANTABILITY 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 . + * + * Author: Pavel Cisler + */ + +#include +#include "nautilus-file-changes-queue.h" + +#include "nautilus-directory-notify.h" +#include "nautilus-tag-manager.h" + +typedef enum +{ + CHANGE_FILE_INITIAL, + CHANGE_FILE_ADDED, + CHANGE_FILE_CHANGED, + CHANGE_FILE_REMOVED, + CHANGE_FILE_MOVED, +} NautilusFileChangeKind; + +typedef struct +{ + NautilusFileChangeKind kind; + GFile *from; + GFile *to; + int screen; +} NautilusFileChange; + +typedef struct +{ + GList *head; + GList *tail; + GMutex mutex; +} NautilusFileChangesQueue; + +static NautilusFileChangesQueue * +nautilus_file_changes_queue_new (void) +{ + NautilusFileChangesQueue *result; + + result = g_new0 (NautilusFileChangesQueue, 1); + g_mutex_init (&result->mutex); + + return result; +} + +static NautilusFileChangesQueue * +nautilus_file_changes_queue_get (void) +{ + static NautilusFileChangesQueue *file_changes_queue; + + if (file_changes_queue == NULL) + { + file_changes_queue = nautilus_file_changes_queue_new (); + } + + return file_changes_queue; +} + +static void +nautilus_file_changes_queue_add_common (NautilusFileChangesQueue *queue, + NautilusFileChange *new_item) +{ + /* enqueue the new queue item while locking down the list */ + g_mutex_lock (&queue->mutex); + + queue->head = g_list_prepend (queue->head, new_item); + if (queue->tail == NULL) + { + queue->tail = queue->head; + } + + g_mutex_unlock (&queue->mutex); +} + +void +nautilus_file_changes_queue_file_added (GFile *location) +{ + NautilusFileChange *new_item; + NautilusFileChangesQueue *queue; + + queue = nautilus_file_changes_queue_get (); + + new_item = g_new0 (NautilusFileChange, 1); + new_item->kind = CHANGE_FILE_ADDED; + new_item->from = g_object_ref (location); + nautilus_file_changes_queue_add_common (queue, new_item); +} + +void +nautilus_file_changes_queue_file_changed (GFile *location) +{ + NautilusFileChange *new_item; + NautilusFileChangesQueue *queue; + + queue = nautilus_file_changes_queue_get (); + + new_item = g_new0 (NautilusFileChange, 1); + new_item->kind = CHANGE_FILE_CHANGED; + new_item->from = g_object_ref (location); + nautilus_file_changes_queue_add_common (queue, new_item); +} + +void +nautilus_file_changes_queue_file_removed (GFile *location) +{ + NautilusFileChange *new_item; + NautilusFileChangesQueue *queue; + + queue = nautilus_file_changes_queue_get (); + + new_item = g_new0 (NautilusFileChange, 1); + new_item->kind = CHANGE_FILE_REMOVED; + new_item->from = g_object_ref (location); + nautilus_file_changes_queue_add_common (queue, new_item); +} + +void +nautilus_file_changes_queue_file_moved (GFile *from, + GFile *to) +{ + NautilusFileChange *new_item; + NautilusFileChangesQueue *queue; + + queue = nautilus_file_changes_queue_get (); + + new_item = g_new (NautilusFileChange, 1); + new_item->kind = CHANGE_FILE_MOVED; + new_item->from = g_object_ref (from); + new_item->to = g_object_ref (to); + nautilus_file_changes_queue_add_common (queue, new_item); +} + +static NautilusFileChange * +nautilus_file_changes_queue_get_change (NautilusFileChangesQueue *queue) +{ + GList *new_tail; + NautilusFileChange *result; + + g_assert (queue != NULL); + + /* dequeue the tail item while locking down the list */ + g_mutex_lock (&queue->mutex); + + if (queue->tail == NULL) + { + result = NULL; + } + else + { + new_tail = queue->tail->prev; + result = queue->tail->data; + queue->head = g_list_remove_link (queue->head, + queue->tail); + g_list_free_1 (queue->tail); + queue->tail = new_tail; + } + + g_mutex_unlock (&queue->mutex); + + return result; +} + +enum +{ + CONSUME_CHANGES_MAX_CHUNK = 20 +}; + +static void +pairs_list_free (GList *pairs) +{ + GList *p; + GFilePair *pair; + + /* deep delete the list of pairs */ + + for (p = pairs; p != NULL; p = p->next) + { + /* delete the strings in each pair */ + pair = p->data; + g_object_unref (pair->from); + g_object_unref (pair->to); + } + + /* delete the list and the now empty pair structs */ + g_list_free_full (pairs, g_free); +} + +/* go through changes in the change queue, send ones with the same kind + * in a list to the different nautilus_directory_notify calls + */ +void +nautilus_file_changes_consume_changes (gboolean consume_all) +{ + NautilusFileChange *change; + GList *additions, *changes, *deletions, *moves; + GFilePair *pair; + guint chunk_count; + NautilusFileChangesQueue *queue; + gboolean flush_needed; + + + additions = NULL; + changes = NULL; + deletions = NULL; + moves = NULL; + + queue = nautilus_file_changes_queue_get (); + + /* Consume changes from the queue, stuffing them into one of three lists, + * keep doing it while the changes are of the same kind, then send them off. + * This is to ensure that the changes get sent off in the same order that they + * arrived. + */ + for (chunk_count = 0;; chunk_count++) + { + change = nautilus_file_changes_queue_get_change (queue); + + /* figure out if we need to flush the pending changes that we collected sofar */ + + if (change == NULL) + { + flush_needed = TRUE; + /* no changes left, flush everything */ + } + else + { + flush_needed = additions != NULL + && change->kind != CHANGE_FILE_ADDED; + + flush_needed |= changes != NULL + && change->kind != CHANGE_FILE_CHANGED; + + flush_needed |= moves != NULL + && change->kind != CHANGE_FILE_MOVED; + + flush_needed |= deletions != NULL + && change->kind != CHANGE_FILE_REMOVED; + + flush_needed |= !consume_all && chunk_count >= CONSUME_CHANGES_MAX_CHUNK; + /* we have reached the chunk maximum */ + } + + if (flush_needed) + { + /* Send changes we collected off. + * At one time we may only have one of the lists + * contain changes. + */ + + if (deletions != NULL) + { + deletions = g_list_reverse (deletions); + nautilus_directory_notify_files_removed (deletions); + g_list_free_full (deletions, g_object_unref); + deletions = NULL; + } + if (moves != NULL) + { + moves = g_list_reverse (moves); + nautilus_directory_notify_files_moved (moves); + pairs_list_free (moves); + moves = NULL; + } + if (additions != NULL) + { + additions = g_list_reverse (additions); + nautilus_directory_notify_files_added (additions); + g_list_free_full (additions, g_object_unref); + additions = NULL; + } + if (changes != NULL) + { + changes = g_list_reverse (changes); + nautilus_directory_notify_files_changed (changes); + g_list_free_full (changes, g_object_unref); + changes = NULL; + } + } + + if (change == NULL) + { + /* we are done */ + return; + } + + /* add the new change to the list */ + switch (change->kind) + { + case CHANGE_FILE_ADDED: + { + additions = g_list_prepend (additions, change->from); + } + break; + + case CHANGE_FILE_CHANGED: + { + changes = g_list_prepend (changes, change->from); + } + break; + + case CHANGE_FILE_REMOVED: + { + deletions = g_list_prepend (deletions, change->from); + } + break; + + case CHANGE_FILE_MOVED: + { + nautilus_tag_manager_update_moved_uris (nautilus_tag_manager_get (), + change->from, + change->to); + + pair = g_new (GFilePair, 1); + pair->from = change->from; + pair->to = change->to; + moves = g_list_prepend (moves, pair); + } + break; + + default: + { + g_assert_not_reached (); + } + break; + } + + g_free (change); + } +} diff --git a/src/nautilus-file-changes-queue.h b/src/nautilus-file-changes-queue.h new file mode 100644 index 0000000..8f49bc9 --- /dev/null +++ b/src/nautilus-file-changes-queue.h @@ -0,0 +1,31 @@ +/* + Copyright (C) 1999, 2000, 2001 Eazel, Inc. + + This program is free software; you can redistribute it and/or + modify it under the terms of the GNU General Public License as + published by the Free Software Foundation; either version 2 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 warranty of + MERCHANTABILITY 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 . + + Author: Pavel Cisler +*/ + +#pragma once + +#include +#include + +void nautilus_file_changes_queue_file_added (GFile *location); +void nautilus_file_changes_queue_file_changed (GFile *location); +void nautilus_file_changes_queue_file_removed (GFile *location); +void nautilus_file_changes_queue_file_moved (GFile *from, + GFile *to); + +void nautilus_file_changes_consume_changes (gboolean consume_all); diff --git a/src/nautilus-file-conflict-dialog.c b/src/nautilus-file-conflict-dialog.c new file mode 100644 index 0000000..f7c3cc7 --- /dev/null +++ b/src/nautilus-file-conflict-dialog.c @@ -0,0 +1,307 @@ +/* nautilus-file-conflict-dialog: dialog that handles file conflicts + * during transfer operations. + * + * Copyright (C) 2008-2010 Cosimo Cecchi + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License as + * published by the Free Software Foundation; either version 2 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 warranty of + * MERCHANTABILITY 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 . + * + * Authors: Cosimo Cecchi + */ + +#include +#include "nautilus-file-conflict-dialog.h" + +#include +#include +#include +#include +#include +#include + +#include "nautilus-file.h" +#include "nautilus-icon-info.h" +#include "nautilus-operations-ui-manager.h" + +struct _NautilusFileConflictDialog +{ + GtkDialog parent_instance; + + gchar *conflict_name; + gchar *suggested_name; + + /* UI objects */ + GtkWidget *primary_label; + GtkWidget *secondary_label; + GtkWidget *dest_label; + GtkWidget *src_label; + GtkWidget *expander; + GtkWidget *entry; + GtkWidget *checkbox; + GtkWidget *cancel_button; + GtkWidget *skip_button; + GtkWidget *rename_button; + GtkWidget *replace_button; + GtkWidget *dest_icon; + GtkWidget *src_icon; +}; + +G_DEFINE_TYPE (NautilusFileConflictDialog, nautilus_file_conflict_dialog, GTK_TYPE_DIALOG); + +void +nautilus_file_conflict_dialog_set_text (NautilusFileConflictDialog *fcd, + gchar *primary_text, + gchar *secondary_text) +{ + gtk_label_set_text (GTK_LABEL (fcd->primary_label), primary_text); + gtk_label_set_text (GTK_LABEL (fcd->secondary_label), secondary_text); +} + +void +nautilus_file_conflict_dialog_set_images (NautilusFileConflictDialog *fcd, + GdkPaintable *destination_paintable, + GdkPaintable *source_paintable) +{ + gtk_picture_set_paintable (GTK_PICTURE (fcd->dest_icon), destination_paintable); + gtk_picture_set_paintable (GTK_PICTURE (fcd->src_icon), source_paintable); +} + +void +nautilus_file_conflict_dialog_set_file_labels (NautilusFileConflictDialog *fcd, + gchar *destination_label, + gchar *source_label) +{ + gtk_label_set_markup (GTK_LABEL (fcd->dest_label), destination_label); + gtk_label_set_markup (GTK_LABEL (fcd->src_label), source_label); +} + +void +nautilus_file_conflict_dialog_set_conflict_name (NautilusFileConflictDialog *fcd, + gchar *conflict_name) +{ + fcd->conflict_name = g_strdup (conflict_name); +} + +void +nautilus_file_conflict_dialog_set_suggested_name (NautilusFileConflictDialog *fcd, + gchar *suggested_name) +{ + fcd->suggested_name = g_strdup (suggested_name); + gtk_editable_set_text (GTK_EDITABLE (fcd->entry), suggested_name); +} + +void +nautilus_file_conflict_dialog_set_replace_button_label (NautilusFileConflictDialog *fcd, + gchar *label) +{ + gtk_button_set_label (GTK_BUTTON (fcd->replace_button), label); +} + +void +nautilus_file_conflict_dialog_disable_skip (NautilusFileConflictDialog *fcd) +{ + gtk_widget_hide (fcd->skip_button); +} + +void +nautilus_file_conflict_dialog_disable_replace (NautilusFileConflictDialog *fcd) +{ + gtk_widget_set_sensitive (fcd->replace_button, FALSE); +} + +void +nautilus_file_conflict_dialog_disable_apply_to_all (NautilusFileConflictDialog *fcd) +{ + gtk_widget_hide (fcd->checkbox); +} + +static void +entry_text_changed_cb (GtkEditable *entry, + NautilusFileConflictDialog *dialog) +{ + if (g_strcmp0 (gtk_editable_get_text (GTK_EDITABLE (entry)), "") != 0 && + g_strcmp0 (gtk_editable_get_text (GTK_EDITABLE (entry)), dialog->conflict_name) != 0) + { + gtk_widget_set_sensitive (dialog->rename_button, TRUE); + } + else + { + gtk_widget_set_sensitive (dialog->rename_button, FALSE); + } +} + +static int +get_character_position_after_basename (const gchar *filename) +{ + const gchar *extension; + + extension = eel_filename_get_extension_offset (filename); + + if (extension == NULL) + { + /* If the filename has got no extension, we want the position of the + * the terminating null. */ + return (int) g_utf8_strlen (filename, -1); + } + + return g_utf8_pointer_to_offset (filename, extension); +} + +static void +on_expanded_notify (GtkExpander *w, + GParamSpec *pspec, + NautilusFileConflictDialog *dialog) +{ + if (gtk_expander_get_expanded (w)) + { + gtk_widget_hide (dialog->replace_button); + gtk_widget_show (dialog->rename_button); + gtk_dialog_set_default_response (GTK_DIALOG (dialog), CONFLICT_RESPONSE_RENAME); + + gtk_widget_set_sensitive (dialog->checkbox, FALSE); + + gtk_widget_grab_focus (dialog->entry); + if (g_strcmp0 (gtk_editable_get_text (GTK_EDITABLE (dialog->entry)), dialog->suggested_name) == 0) + { + /* The suggested name is in the form "original (1).txt", if the + * the conflicting name was "original.txt". The user may want to + * replace the "(1)" bits with with something more meaningful, so + * select this region for convenience. */ + + int start_pos; + int end_pos; + + start_pos = get_character_position_after_basename (dialog->conflict_name); + end_pos = get_character_position_after_basename (dialog->suggested_name); + + gtk_editable_select_region (GTK_EDITABLE (dialog->entry), start_pos, end_pos); + } + } + else + { + gtk_widget_hide (dialog->rename_button); + gtk_widget_show (dialog->replace_button); + gtk_dialog_set_default_response (GTK_DIALOG (dialog), CONFLICT_RESPONSE_REPLACE); + + gtk_widget_set_sensitive (dialog->checkbox, TRUE); + } +} + +static void +checkbox_toggled_cb (GtkCheckButton *t, + NautilusFileConflictDialog *dialog) +{ + gtk_widget_set_sensitive (dialog->expander, !gtk_check_button_get_active (t)); +} + +static void +reset_button_clicked_cb (GtkButton *w, + NautilusFileConflictDialog *dialog) +{ + int start_pos, end_pos; + + gtk_editable_set_text (GTK_EDITABLE (dialog->entry), dialog->conflict_name); + gtk_widget_grab_focus (dialog->entry); + eel_filename_get_rename_region (dialog->conflict_name, &start_pos, &end_pos); + gtk_editable_select_region (GTK_EDITABLE (dialog->entry), start_pos, end_pos); +} + +static void +nautilus_file_conflict_dialog_init (NautilusFileConflictDialog *fcd) +{ + gtk_widget_init_template (GTK_WIDGET (fcd)); +} + +static void +do_finalize (GObject *self) +{ + NautilusFileConflictDialog *dialog = NAUTILUS_FILE_CONFLICT_DIALOG (self); + + g_free (dialog->conflict_name); + g_free (dialog->suggested_name); + + G_OBJECT_CLASS (nautilus_file_conflict_dialog_parent_class)->finalize (self); +} + +static void +nautilus_file_conflict_dialog_class_init (NautilusFileConflictDialogClass *klass) +{ + GtkWidgetClass *widget_class = GTK_WIDGET_CLASS (klass); + + gtk_widget_class_set_template_from_resource (widget_class, "/org/gnome/nautilus/ui/nautilus-file-conflict-dialog.ui"); + gtk_widget_class_bind_template_child (widget_class, NautilusFileConflictDialog, primary_label); + gtk_widget_class_bind_template_child (widget_class, NautilusFileConflictDialog, secondary_label); + gtk_widget_class_bind_template_child (widget_class, NautilusFileConflictDialog, dest_label); + gtk_widget_class_bind_template_child (widget_class, NautilusFileConflictDialog, src_label); + gtk_widget_class_bind_template_child (widget_class, NautilusFileConflictDialog, expander); + gtk_widget_class_bind_template_child (widget_class, NautilusFileConflictDialog, entry); + gtk_widget_class_bind_template_child (widget_class, NautilusFileConflictDialog, checkbox); + gtk_widget_class_bind_template_child (widget_class, NautilusFileConflictDialog, cancel_button); + gtk_widget_class_bind_template_child (widget_class, NautilusFileConflictDialog, rename_button); + gtk_widget_class_bind_template_child (widget_class, NautilusFileConflictDialog, replace_button); + gtk_widget_class_bind_template_child (widget_class, NautilusFileConflictDialog, skip_button); + gtk_widget_class_bind_template_child (widget_class, NautilusFileConflictDialog, dest_icon); + gtk_widget_class_bind_template_child (widget_class, NautilusFileConflictDialog, src_icon); + gtk_widget_class_bind_template_callback (widget_class, entry_text_changed_cb); + gtk_widget_class_bind_template_callback (widget_class, on_expanded_notify); + gtk_widget_class_bind_template_callback (widget_class, checkbox_toggled_cb); + gtk_widget_class_bind_template_callback (widget_class, reset_button_clicked_cb); + + G_OBJECT_CLASS (klass)->finalize = do_finalize; +} + +static gboolean +activate_buttons (NautilusFileConflictDialog *fcd) +{ + gtk_widget_set_sensitive (GTK_WIDGET (fcd->cancel_button), TRUE); + gtk_widget_set_sensitive (GTK_WIDGET (fcd->skip_button), TRUE); + gtk_widget_set_sensitive (GTK_WIDGET (fcd->rename_button), TRUE); + gtk_widget_set_sensitive (GTK_WIDGET (fcd->replace_button), TRUE); + gtk_widget_set_sensitive (GTK_WIDGET (fcd->expander), TRUE); + return G_SOURCE_REMOVE; +} + +void +nautilus_file_conflict_dialog_delay_buttons_activation (NautilusFileConflictDialog *fcd) +{ + gtk_widget_set_sensitive (GTK_WIDGET (fcd->cancel_button), FALSE); + gtk_widget_set_sensitive (GTK_WIDGET (fcd->skip_button), FALSE); + gtk_widget_set_sensitive (GTK_WIDGET (fcd->rename_button), FALSE); + gtk_widget_set_sensitive (GTK_WIDGET (fcd->replace_button), FALSE); + gtk_widget_set_sensitive (GTK_WIDGET (fcd->expander), FALSE); + + g_timeout_add_seconds (BUTTON_ACTIVATION_DELAY_IN_SECONDS, + G_SOURCE_FUNC (activate_buttons), + fcd); +} + +char * +nautilus_file_conflict_dialog_get_new_name (NautilusFileConflictDialog *dialog) +{ + return g_strdup (gtk_editable_get_text (GTK_EDITABLE (dialog->entry))); +} + +gboolean +nautilus_file_conflict_dialog_get_apply_to_all (NautilusFileConflictDialog *dialog) +{ + return gtk_check_button_get_active (GTK_CHECK_BUTTON (dialog->checkbox)); +} + +NautilusFileConflictDialog * +nautilus_file_conflict_dialog_new (GtkWindow *parent) +{ + return NAUTILUS_FILE_CONFLICT_DIALOG (g_object_new (NAUTILUS_TYPE_FILE_CONFLICT_DIALOG, + "transient-for", parent, + "use-header-bar", TRUE, + NULL)); +} diff --git a/src/nautilus-file-conflict-dialog.h b/src/nautilus-file-conflict-dialog.h new file mode 100644 index 0000000..70f81d0 --- /dev/null +++ b/src/nautilus-file-conflict-dialog.h @@ -0,0 +1,62 @@ + +/* nautilus-file-conflict-dialog: dialog that handles file conflicts + during transfer operations. + + Copyright (C) 2008, Cosimo Cecchi + + This program is free software; you can redistribute it and/or + modify it under the terms of the GNU General Public License as + published by the Free Software Foundation; either version 2 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 warranty of + MERCHANTABILITY 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 . + + Authors: Cosimo Cecchi +*/ + +#pragma once + +#include +#include +#include + +G_BEGIN_DECLS + +#define NAUTILUS_TYPE_FILE_CONFLICT_DIALOG (nautilus_file_conflict_dialog_get_type ()) + +G_DECLARE_FINAL_TYPE (NautilusFileConflictDialog, nautilus_file_conflict_dialog, NAUTILUS, FILE_CONFLICT_DIALOG, GtkDialog) + +NautilusFileConflictDialog* nautilus_file_conflict_dialog_new (GtkWindow *parent); + +void nautilus_file_conflict_dialog_set_text (NautilusFileConflictDialog *fcd, + gchar *primary_text, + gchar *secondary_text); +void nautilus_file_conflict_dialog_set_images (NautilusFileConflictDialog *fcd, + GdkPaintable *source_paintable, + GdkPaintable *destination_paintable); +void nautilus_file_conflict_dialog_set_file_labels (NautilusFileConflictDialog *fcd, + gchar *destination_label, + gchar *source_label); +void nautilus_file_conflict_dialog_set_conflict_name (NautilusFileConflictDialog *fcd, + gchar *conflict_name); +void nautilus_file_conflict_dialog_set_suggested_name (NautilusFileConflictDialog *fcd, + gchar *suggested_name); +void nautilus_file_conflict_dialog_set_replace_button_label (NautilusFileConflictDialog *fcd, + gchar *label); + +void nautilus_file_conflict_dialog_disable_skip (NautilusFileConflictDialog *fcd); +void nautilus_file_conflict_dialog_disable_replace (NautilusFileConflictDialog *fcd); +void nautilus_file_conflict_dialog_disable_apply_to_all (NautilusFileConflictDialog *fcd); + +void nautilus_file_conflict_dialog_delay_buttons_activation (NautilusFileConflictDialog *fdc); + +char* nautilus_file_conflict_dialog_get_new_name (NautilusFileConflictDialog *dialog); +gboolean nautilus_file_conflict_dialog_get_apply_to_all (NautilusFileConflictDialog *dialog); + +G_END_DECLS diff --git a/src/nautilus-file-name-widget-controller.c b/src/nautilus-file-name-widget-controller.c new file mode 100644 index 0000000..db048b7 --- /dev/null +++ b/src/nautilus-file-name-widget-controller.c @@ -0,0 +1,522 @@ +/* nautilus-file-name-widget-controller.c + * + * Copyright (C) 2016 the Nautilus developers + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License as + * published by the Free Software Foundation; either version 2 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 warranty of + * MERCHANTABILITY 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 . + * + */ + +#include + +#include "nautilus-file-name-widget-controller.h" +#include "nautilus-file-utilities.h" + +#define FILE_NAME_DUPLICATED_LABEL_TIMEOUT 500 + +typedef struct +{ + GtkWidget *error_revealer; + GtkWidget *error_label; + GtkWidget *name_entry; + GtkWidget *activate_button; + NautilusDirectory *containing_directory; + + gboolean duplicated_is_folder; + gint duplicated_label_timeout_id; +} NautilusFileNameWidgetControllerPrivate; + +enum +{ + NAME_ACCEPTED, + CANCELLED, + LAST_SIGNAL +}; + +enum +{ + PROP_ERROR_REVEALER = 1, + PROP_ERROR_LABEL, + PROP_NAME_ENTRY, + PROP_ACTION_BUTTON, + PROP_CONTAINING_DIRECTORY, + NUM_PROPERTIES +}; + +static guint signals[LAST_SIGNAL]; + +G_DEFINE_ABSTRACT_TYPE_WITH_PRIVATE (NautilusFileNameWidgetController, nautilus_file_name_widget_controller, G_TYPE_OBJECT) + +gchar * +nautilus_file_name_widget_controller_get_new_name (NautilusFileNameWidgetController *self) +{ + return NAUTILUS_FILE_NAME_WIDGET_CONTROLLER_GET_CLASS (self)->get_new_name (self); +} + +void +nautilus_file_name_widget_controller_set_containing_directory (NautilusFileNameWidgetController *self, + NautilusDirectory *directory) +{ + g_assert (NAUTILUS_IS_DIRECTORY (directory)); + + g_object_set (self, "containing-directory", directory, NULL); +} + +gboolean +nautilus_file_name_widget_controller_is_name_too_long (NautilusFileNameWidgetController *self, + gchar *name) +{ + NautilusFileNameWidgetControllerPrivate *priv; + size_t name_length; + g_autoptr (GFile) location = NULL; + glong max_name_length; + + priv = nautilus_file_name_widget_controller_get_instance_private (self); + name_length = strlen (name); + location = nautilus_directory_get_location (priv->containing_directory); + max_name_length = nautilus_get_max_child_name_length_for_location (location); + + if (max_name_length == -1) + { + /* We don't know, so let's give it a chance */ + return FALSE; + } + else + { + return name_length > max_name_length + 1; + } +} + +static gboolean +nautilus_file_name_widget_controller_name_is_valid (NautilusFileNameWidgetController *self, + gchar *name, + gchar **error_message) +{ + return NAUTILUS_FILE_NAME_WIDGET_CONTROLLER_GET_CLASS (self)->name_is_valid (self, + name, + error_message); +} + +static gboolean +nautilus_file_name_widget_controller_ignore_existing_file (NautilusFileNameWidgetController *self, + NautilusFile *existing_file) +{ + return NAUTILUS_FILE_NAME_WIDGET_CONTROLLER_GET_CLASS (self)->ignore_existing_file (self, + existing_file); +} + +static gchar * +real_get_new_name (NautilusFileNameWidgetController *self) +{ + NautilusFileNameWidgetControllerPrivate *priv; + + priv = nautilus_file_name_widget_controller_get_instance_private (self); + + return g_strstrip (g_strdup (gtk_editable_get_text (GTK_EDITABLE (priv->name_entry)))); +} + +static gboolean +real_name_is_valid (NautilusFileNameWidgetController *self, + gchar *name, + gchar **error_message) +{ + gboolean is_valid; + + is_valid = TRUE; + if (strlen (name) == 0) + { + is_valid = FALSE; + } + else if (strstr (name, "/") != NULL) + { + is_valid = FALSE; + *error_message = _("File names cannot contain “/”."); + } + else if (strcmp (name, ".") == 0) + { + is_valid = FALSE; + *error_message = _("A file cannot be called “.”."); + } + else if (strcmp (name, "..") == 0) + { + is_valid = FALSE; + *error_message = _("A file cannot be called “..”."); + } + else if (nautilus_file_name_widget_controller_is_name_too_long (self, name)) + { + is_valid = FALSE; + *error_message = _("File name is too long."); + } + + if (is_valid && g_str_has_prefix (name, ".")) + { + /* We must warn about the side effect */ + *error_message = _("Files with “.” at the beginning of their name are hidden."); + } + + return is_valid; +} + +static gboolean +real_ignore_existing_file (NautilusFileNameWidgetController *self, + NautilusFile *existing_file) +{ + return FALSE; +} + +static gboolean +duplicated_file_label_show (NautilusFileNameWidgetController *self) +{ + NautilusFileNameWidgetControllerPrivate *priv; + + priv = nautilus_file_name_widget_controller_get_instance_private (self); + if (priv->duplicated_is_folder) + { + gtk_label_set_label (GTK_LABEL (priv->error_label), + _("A folder with that name already exists.")); + } + else + { + gtk_label_set_label (GTK_LABEL (priv->error_label), + _("A file with that name already exists.")); + } + + gtk_revealer_set_reveal_child (GTK_REVEALER (priv->error_revealer), + TRUE); + + priv->duplicated_label_timeout_id = 0; + + return G_SOURCE_REMOVE; +} + +static void +file_name_widget_controller_process_new_name (NautilusFileNameWidgetController *controller, + gboolean *duplicated_name, + gboolean *valid_name) +{ + NautilusFileNameWidgetControllerPrivate *priv; + g_autofree gchar *name = NULL; + gchar *error_message = NULL; + NautilusFile *existing_file; + priv = nautilus_file_name_widget_controller_get_instance_private (controller); + + g_return_if_fail (NAUTILUS_IS_DIRECTORY (priv->containing_directory)); + + name = nautilus_file_name_widget_controller_get_new_name (controller); + *valid_name = nautilus_file_name_widget_controller_name_is_valid (controller, + name, + &error_message); + + gtk_label_set_label (GTK_LABEL (priv->error_label), error_message); + gtk_revealer_set_reveal_child (GTK_REVEALER (priv->error_revealer), + error_message != NULL); + + existing_file = nautilus_directory_get_file_by_name (priv->containing_directory, name); + *duplicated_name = existing_file != NULL && + !nautilus_file_name_widget_controller_ignore_existing_file (controller, + existing_file); + + gtk_widget_set_sensitive (priv->activate_button, *valid_name && !*duplicated_name); + + if (priv->duplicated_label_timeout_id != 0) + { + g_source_remove (priv->duplicated_label_timeout_id); + priv->duplicated_label_timeout_id = 0; + } + + if (*duplicated_name) + { + priv->duplicated_is_folder = nautilus_file_is_directory (existing_file); + } + + if (existing_file != NULL) + { + nautilus_file_unref (existing_file); + } +} + +static void +file_name_widget_controller_on_changed_directory_info_ready (NautilusDirectory *directory, + GList *files, + gpointer user_data) +{ + NautilusFileNameWidgetController *controller; + NautilusFileNameWidgetControllerPrivate *priv; + gboolean duplicated_name; + gboolean valid_name; + + controller = NAUTILUS_FILE_NAME_WIDGET_CONTROLLER (user_data); + priv = nautilus_file_name_widget_controller_get_instance_private (controller); + + file_name_widget_controller_process_new_name (controller, + &duplicated_name, + &valid_name); + + /* Report duplicated file only if not other message shown (for instance, + * folders like "." or ".." will always exists, but we consider it as an + * error, not as a duplicated file or if the name is the same as the file + * we are renaming also don't report as a duplicated */ + if (duplicated_name && valid_name) + { + priv->duplicated_label_timeout_id = g_timeout_add (FILE_NAME_DUPLICATED_LABEL_TIMEOUT, + (GSourceFunc) duplicated_file_label_show, + controller); + } +} + +static void +file_name_widget_controller_on_changed (gpointer user_data) +{ + NautilusFileNameWidgetController *controller; + NautilusFileNameWidgetControllerPrivate *priv; + + controller = NAUTILUS_FILE_NAME_WIDGET_CONTROLLER (user_data); + priv = nautilus_file_name_widget_controller_get_instance_private (controller); + + nautilus_directory_call_when_ready (priv->containing_directory, + NAUTILUS_FILE_ATTRIBUTE_INFO, + TRUE, + file_name_widget_controller_on_changed_directory_info_ready, + controller); +} + +static void +file_name_widget_controller_on_activate_directory_info_ready (NautilusDirectory *directory, + GList *files, + gpointer user_data) +{ + NautilusFileNameWidgetController *controller; + gboolean duplicated_name; + gboolean valid_name; + + controller = NAUTILUS_FILE_NAME_WIDGET_CONTROLLER (user_data); + + file_name_widget_controller_process_new_name (controller, + &duplicated_name, + &valid_name); + + if (valid_name && !duplicated_name) + { + g_signal_emit (controller, signals[NAME_ACCEPTED], 0); + } + else + { + /* Report duplicated file only if not other message shown (for instance, + * folders like "." or ".." will always exists, but we consider it as an + * error, not as a duplicated file) */ + if (duplicated_name && valid_name) + { + /* Show it inmediatily since the user tried to trigger the action */ + duplicated_file_label_show (controller); + } + } +} + +static void +file_name_widget_controller_on_activate (gpointer user_data) +{ + NautilusFileNameWidgetController *controller; + NautilusFileNameWidgetControllerPrivate *priv; + + controller = NAUTILUS_FILE_NAME_WIDGET_CONTROLLER (user_data); + priv = nautilus_file_name_widget_controller_get_instance_private (controller); + + nautilus_directory_call_when_ready (priv->containing_directory, + NAUTILUS_FILE_ATTRIBUTE_INFO, + TRUE, + file_name_widget_controller_on_activate_directory_info_ready, + controller); +} + +static void +nautilus_file_name_widget_controller_init (NautilusFileNameWidgetController *self) +{ + NautilusFileNameWidgetControllerPrivate *priv; + + priv = nautilus_file_name_widget_controller_get_instance_private (self); + + priv->containing_directory = NULL; +} + +static void +nautilus_file_name_widget_controller_set_property (GObject *object, + guint prop_id, + const GValue *value, + GParamSpec *pspec) +{ + NautilusFileNameWidgetController *controller; + NautilusFileNameWidgetControllerPrivate *priv; + + controller = NAUTILUS_FILE_NAME_WIDGET_CONTROLLER (object); + priv = nautilus_file_name_widget_controller_get_instance_private (controller); + + switch (prop_id) + { + case PROP_ERROR_REVEALER: + { + priv->error_revealer = GTK_WIDGET (g_value_get_object (value)); + } + break; + + case PROP_ERROR_LABEL: + { + priv->error_label = GTK_WIDGET (g_value_get_object (value)); + } + break; + + case PROP_NAME_ENTRY: + { + priv->name_entry = GTK_WIDGET (g_value_get_object (value)); + + g_signal_connect_swapped (G_OBJECT (priv->name_entry), + "activate", + (GCallback) file_name_widget_controller_on_activate, + controller); + g_signal_connect_swapped (G_OBJECT (priv->name_entry), + "changed", + (GCallback) file_name_widget_controller_on_changed, + controller); + } + break; + + case PROP_ACTION_BUTTON: + { + priv->activate_button = GTK_WIDGET (g_value_get_object (value)); + + g_signal_connect_swapped (G_OBJECT (priv->activate_button), + "clicked", + (GCallback) file_name_widget_controller_on_activate, + controller); + } + break; + + case PROP_CONTAINING_DIRECTORY: + { + g_clear_object (&priv->containing_directory); + + priv->containing_directory = NAUTILUS_DIRECTORY (g_value_dup_object (value)); + } + break; + + default: + { + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); + } + break; + } +} + +static void +nautilus_file_name_widget_controller_finalize (GObject *object) +{ + NautilusFileNameWidgetController *self; + NautilusFileNameWidgetControllerPrivate *priv; + + self = NAUTILUS_FILE_NAME_WIDGET_CONTROLLER (object); + priv = nautilus_file_name_widget_controller_get_instance_private (self); + + if (priv->containing_directory != NULL) + { + nautilus_directory_cancel_callback (priv->containing_directory, + file_name_widget_controller_on_changed_directory_info_ready, + self); + nautilus_directory_cancel_callback (priv->containing_directory, + file_name_widget_controller_on_activate_directory_info_ready, + self); + g_clear_object (&priv->containing_directory); + } + + if (priv->duplicated_label_timeout_id > 0) + { + g_source_remove (priv->duplicated_label_timeout_id); + priv->duplicated_label_timeout_id = 0; + } + + G_OBJECT_CLASS (nautilus_file_name_widget_controller_parent_class)->finalize (object); +} + +static void +nautilus_file_name_widget_controller_class_init (NautilusFileNameWidgetControllerClass *klass) +{ + GObjectClass *object_class = G_OBJECT_CLASS (klass); + + object_class->set_property = nautilus_file_name_widget_controller_set_property; + object_class->finalize = nautilus_file_name_widget_controller_finalize; + + klass->get_new_name = real_get_new_name; + klass->name_is_valid = real_name_is_valid; + klass->ignore_existing_file = real_ignore_existing_file; + + signals[NAME_ACCEPTED] = + g_signal_new ("name-accepted", + G_TYPE_FROM_CLASS (klass), + G_SIGNAL_RUN_FIRST, + G_STRUCT_OFFSET (NautilusFileNameWidgetControllerClass, name_accepted), + NULL, NULL, + g_cclosure_marshal_generic, + G_TYPE_NONE, 0); + signals[CANCELLED] = + g_signal_new ("cancelled", + G_TYPE_FROM_CLASS (klass), + G_SIGNAL_RUN_LAST, + 0, + NULL, NULL, + g_cclosure_marshal_generic, + G_TYPE_NONE, 0); + + g_object_class_install_property ( + object_class, + PROP_ERROR_REVEALER, + g_param_spec_object ("error-revealer", + "Error Revealer", + "The error label revealer", + GTK_TYPE_WIDGET, + G_PARAM_WRITABLE | + G_PARAM_CONSTRUCT_ONLY)); + + g_object_class_install_property ( + object_class, + PROP_ERROR_LABEL, + g_param_spec_object ("error-label", + "Error Label", + "The label used for displaying errors", + GTK_TYPE_WIDGET, + G_PARAM_WRITABLE | + G_PARAM_CONSTRUCT_ONLY)); + g_object_class_install_property ( + object_class, + PROP_NAME_ENTRY, + g_param_spec_object ("name-entry", + "Name Entry", + "The entry for the file name", + GTK_TYPE_WIDGET, + G_PARAM_WRITABLE | + G_PARAM_CONSTRUCT_ONLY)); + g_object_class_install_property ( + object_class, + PROP_ACTION_BUTTON, + g_param_spec_object ("activate-button", + "Activate Button", + "The activate button of the widget", + GTK_TYPE_WIDGET, + G_PARAM_WRITABLE | + G_PARAM_CONSTRUCT_ONLY)); + g_object_class_install_property ( + object_class, + PROP_CONTAINING_DIRECTORY, + g_param_spec_object ("containing-directory", + "Containing Directory", + "The directory used to check for duplicate names", + NAUTILUS_TYPE_DIRECTORY, + G_PARAM_WRITABLE)); +} diff --git a/src/nautilus-file-name-widget-controller.h b/src/nautilus-file-name-widget-controller.h new file mode 100644 index 0000000..a492e2c --- /dev/null +++ b/src/nautilus-file-name-widget-controller.h @@ -0,0 +1,52 @@ +/* nautilus-file-name-widget-controller.h + * + * Copyright (C) 2016 the Nautilus developers + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License as + * published by the Free Software Foundation; either version 2 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 warranty of + * MERCHANTABILITY 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 . + * + */ + +#pragma once + +#include +#include + +#include "nautilus-file.h" +#include "nautilus-directory.h" + +#define NAUTILUS_TYPE_FILE_NAME_WIDGET_CONTROLLER nautilus_file_name_widget_controller_get_type () +G_DECLARE_DERIVABLE_TYPE (NautilusFileNameWidgetController, nautilus_file_name_widget_controller, NAUTILUS, FILE_NAME_WIDGET_CONTROLLER, GObject) + +struct _NautilusFileNameWidgetControllerClass +{ + GObjectClass parent_class; + + gchar * (*get_new_name) (NautilusFileNameWidgetController *controller); + + gboolean (*name_is_valid) (NautilusFileNameWidgetController *controller, + gchar *name, + gchar **error_message); + + gboolean (*ignore_existing_file) (NautilusFileNameWidgetController *controller, + NautilusFile *existing_file); + + void (*name_accepted) (NautilusFileNameWidgetController *controller); +}; + +gchar * nautilus_file_name_widget_controller_get_new_name (NautilusFileNameWidgetController *controller); + +void nautilus_file_name_widget_controller_set_containing_directory (NautilusFileNameWidgetController *controller, + NautilusDirectory *directory); +gboolean nautilus_file_name_widget_controller_is_name_too_long (NautilusFileNameWidgetController *self, + gchar *name); diff --git a/src/nautilus-file-operations-dbus-data.c b/src/nautilus-file-operations-dbus-data.c new file mode 100644 index 0000000..666253f --- /dev/null +++ b/src/nautilus-file-operations-dbus-data.c @@ -0,0 +1,75 @@ +/* + * Copyright (C) 2020 Alberts Muktupāvels + * + * Nautilus is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License as + * published by the Free Software Foundation; either version 2 of the + * License, or (at your option) any later version. + * + * Nautilus is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * 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 . + */ + +#include "config.h" +#include "nautilus-file-operations-dbus-data.h" + +struct _NautilusFileOperationsDBusData +{ + gatomicrefcount ref_count; + + char *parent_handle; + + guint32 timestamp; +}; + +NautilusFileOperationsDBusData * +nautilus_file_operations_dbus_data_new (GVariant *platform_data) +{ + NautilusFileOperationsDBusData *self; + GVariantDict dict; + + self = g_new0 (NautilusFileOperationsDBusData, 1); + g_atomic_ref_count_init (&self->ref_count); + + g_variant_dict_init (&dict, platform_data); + + g_variant_dict_lookup (&dict, "parent-handle", "s", &self->parent_handle); + g_variant_dict_lookup (&dict, "timestamp", "u", &self->timestamp); + + return self; +} + +NautilusFileOperationsDBusData * +nautilus_file_operations_dbus_data_ref (NautilusFileOperationsDBusData *self) +{ + g_atomic_ref_count_inc (&self->ref_count); + + return self; +} + +void +nautilus_file_operations_dbus_data_unref (NautilusFileOperationsDBusData *self) +{ + if (g_atomic_ref_count_dec (&self->ref_count)) + { + g_free (self->parent_handle); + g_free (self); + } +} + +const char * +nautilus_file_operations_dbus_data_get_parent_handle (NautilusFileOperationsDBusData *self) +{ + return self->parent_handle; +} + +guint32 +nautilus_file_operations_dbus_data_get_timestamp (NautilusFileOperationsDBusData *self) +{ + return self->timestamp; +} diff --git a/src/nautilus-file-operations-dbus-data.h b/src/nautilus-file-operations-dbus-data.h new file mode 100644 index 0000000..ca719fc --- /dev/null +++ b/src/nautilus-file-operations-dbus-data.h @@ -0,0 +1,34 @@ +/* + * Copyright (C) 2020 Alberts Muktupāvels + * + * Nautilus is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License as + * published by the Free Software Foundation; either version 2 of the + * License, or (at your option) any later version. + * + * Nautilus is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * 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 . + */ + +#pragma once + +#include + +typedef struct _NautilusFileOperationsDBusData NautilusFileOperationsDBusData; + +NautilusFileOperationsDBusData *nautilus_file_operations_dbus_data_new (GVariant *platform_data); + +NautilusFileOperationsDBusData *nautilus_file_operations_dbus_data_ref (NautilusFileOperationsDBusData *self); + +void nautilus_file_operations_dbus_data_unref (NautilusFileOperationsDBusData *self); + +const char *nautilus_file_operations_dbus_data_get_parent_handle (NautilusFileOperationsDBusData *self); + +guint32 nautilus_file_operations_dbus_data_get_timestamp (NautilusFileOperationsDBusData *self); + +G_DEFINE_AUTOPTR_CLEANUP_FUNC (NautilusFileOperationsDBusData, nautilus_file_operations_dbus_data_unref) diff --git a/src/nautilus-file-operations.c b/src/nautilus-file-operations.c new file mode 100644 index 0000000..9a8829e --- /dev/null +++ b/src/nautilus-file-operations.c @@ -0,0 +1,9183 @@ +/* nautilus-file-operations.c - Nautilus file operations. + * + * Copyright (C) 1999, 2000 Free Software Foundation + * Copyright (C) 2000, 2001 Eazel, Inc. + * Copyright (C) 2007 Red Hat, Inc. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License as + * published by the Free Software Foundation; either version 2 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 warranty of + * MERCHANTABILITY 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 . + * + * Authors: Alexander Larsson + * Ettore Perazzoli + * Pavel Cisler + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "nautilus-file-operations.h" + +#include "nautilus-file-changes-queue.h" +#include "nautilus-lib-self-check-functions.h" + +#include "nautilus-progress-info.h" + +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include + +#include "nautilus-error-reporting.h" +#include "nautilus-operations-ui-manager.h" +#include "nautilus-file-changes-queue.h" +#include "nautilus-file-private.h" +#include "nautilus-tag-manager.h" +#include "nautilus-trash-monitor.h" +#include "nautilus-file-utilities.h" +#include "nautilus-file-undo-operations.h" +#include "nautilus-file-undo-manager.h" +#include "nautilus-ui-utilities.h" + +#ifdef GDK_WINDOWING_X11 +#include +#endif + +#ifdef GDK_WINDOWING_WAYLAND +#include +#endif + +typedef struct +{ + GTimer *time; + GtkWindow *parent_window; + NautilusFileOperationsDBusData *dbus_data; + guint inhibit_cookie; + NautilusProgressInfo *progress; + GCancellable *cancellable; + GHashTable *skip_files; + GHashTable *skip_readdir_error; + NautilusFileUndoInfo *undo_info; + gboolean skip_all_error; + gboolean skip_all_conflict; + gboolean merge_all; + gboolean replace_all; + gboolean delete_all; +} CommonJob; + +typedef struct +{ + CommonJob common; + gboolean is_move; + GList *files; + GFile *destination; + GFile *fake_display_source; + GHashTable *debuting_files; + gchar *target_name; + NautilusCopyCallback done_callback; + gpointer done_callback_data; +} CopyMoveJob; + +typedef struct +{ + CommonJob common; + GList *files; + gboolean try_trash; + gboolean user_cancel; + NautilusDeleteCallback done_callback; + gpointer done_callback_data; +} DeleteJob; + +typedef struct +{ + CommonJob common; + GFile *dest_dir; + char *filename; + gboolean make_dir; + GFile *src; + char *src_data; + int length; + GFile *created_file; + NautilusCreateCallback done_callback; + gpointer done_callback_data; +} CreateJob; + + +typedef struct +{ + CommonJob common; + GList *trash_dirs; + gboolean should_confirm; + NautilusOpCallback done_callback; + gpointer done_callback_data; +} EmptyTrashJob; + +typedef struct +{ + CommonJob common; + GFile *file; + gboolean interactive; + NautilusOpCallback done_callback; + gpointer done_callback_data; +} MarkTrustedJob; + +typedef struct +{ + CommonJob common; + GFile *file; + NautilusOpCallback done_callback; + gpointer done_callback_data; + guint32 file_permissions; + guint32 file_mask; + guint32 dir_permissions; + guint32 dir_mask; +} SetPermissionsJob; + +typedef enum +{ + OP_KIND_COPY, + OP_KIND_MOVE, + OP_KIND_DELETE, + OP_KIND_TRASH, + OP_KIND_COMPRESS +} OpKind; + +typedef struct +{ + int num_files_children; + goffset num_bytes_children; +} SourceDirInfo; + +typedef struct +{ + int num_files; + goffset num_bytes; + int num_files_since_progress; + OpKind op; + GHashTable *scanned_dirs_info; +} SourceInfo; + +typedef struct +{ + int num_files; + goffset num_bytes; + OpKind op; + guint64 last_report_time; + int last_reported_files_left; + + /* + * This is used when reporting progress for copy/move operations to not show + * the remaining time. This is needed because some GVfs backends doesn't + * report progress from those operations. Consequently it looks like that it + * is hanged when the remaining time is not updated regularly. See: + * https://gitlab.gnome.org/GNOME/nautilus/-/merge_requests/605 + */ + gboolean partial_progress; +} TransferInfo; + +typedef struct +{ + CommonJob common; + GList *source_files; + GFile *destination_directory; + GList *output_files; + gboolean destination_decided; + gboolean extraction_failed; + + gdouble base_progress; + + guint64 archive_compressed_size; + guint64 total_compressed_size; + gint total_files; + + NautilusExtractCallback done_callback; + gpointer done_callback_data; +} ExtractJob; + +typedef struct +{ + CommonJob common; + GList *source_files; + GFile *output_file; + + AutoarFormat format; + AutoarFilter filter; + gchar *passphrase; + + guint64 total_size; + guint total_files; + + gboolean success; + + NautilusCreateCallback done_callback; + gpointer done_callback_data; +} CompressJob; + +static void +source_info_clear (SourceInfo *source_info) +{ + if (source_info->scanned_dirs_info != NULL) + { + g_hash_table_unref (source_info->scanned_dirs_info); + } +} + +G_DEFINE_AUTO_CLEANUP_CLEAR_FUNC (SourceInfo, source_info_clear) + +#define SOURCE_INFO_INIT { 0 } +#define SECONDS_NEEDED_FOR_RELIABLE_TRANSFER_RATE 8 +#define NSEC_PER_MICROSEC 1000 +#define PROGRESS_NOTIFY_INTERVAL 100 * NSEC_PER_MICROSEC +#define LONG_JOB_THRESHOLD_IN_SECONDS 2 + +#define MAXIMUM_DISPLAYED_FILE_NAME_LENGTH 50 + +#define IS_IO_ERROR(__error, KIND) (((__error)->domain == G_IO_ERROR && (__error)->code == G_IO_ERROR_ ## KIND)) + +#define CANCEL _("_Cancel") +#define SKIP _("_Skip") +#define SKIP_ALL _("S_kip All") +#define RETRY _("_Retry") +#define DELETE _("_Delete") +#define DELETE_ALL _("Delete _All") +#define REPLACE _("_Replace") +#define REPLACE_ALL _("Replace _All") +#define MERGE _("_Merge") +#define MERGE_ALL _("Merge _All") +#define COPY_FORCE _("Copy _Anyway") +#define EMPTY_TRASH _("Empty _Trash") + +static gboolean +is_all_button_text (const char *button_text) +{ + g_assert (button_text != NULL); + + return !strcmp (button_text, SKIP_ALL) || + !strcmp (button_text, REPLACE_ALL) || + !strcmp (button_text, DELETE_ALL) || + !strcmp (button_text, MERGE_ALL); +} + +static void scan_sources (GList *files, + SourceInfo *source_info, + CommonJob *job, + OpKind kind); + + +static void empty_trash_thread_func (GTask *task, + gpointer source_object, + gpointer task_data, + GCancellable *cancellable); + +static void empty_trash_task_done (GObject *source_object, + GAsyncResult *res, + gpointer user_data); + +static char *query_fs_type (GFile *file, + GCancellable *cancellable); + +static void nautilus_file_operations_copy (GTask *task, + gpointer source_object, + gpointer task_data, + GCancellable *cancellable); + +static void nautilus_file_operations_move (GTask *task, + gpointer source_object, + gpointer task_data, + GCancellable *cancellable); + +/* keep in time with get_formatted_time () + * + * This counts and outputs the number of “time units” + * formatted and displayed by get_formatted_time (). + * For instance, if get_formatted_time outputs “3 hours, 4 minutes” + * it yields 7. + */ +static int +seconds_count_format_time_units (int seconds) +{ + int minutes; + int hours; + + if (seconds < 0) + { + /* Just to make sure... */ + seconds = 0; + } + + if (seconds < 60) + { + /* seconds */ + return seconds; + } + + if (seconds < 60 * 60) + { + /* minutes */ + minutes = seconds / 60; + return minutes; + } + + hours = seconds / (60 * 60); + + if (seconds < 60 * 60 * 4) + { + /* minutes + hours */ + minutes = (seconds - hours * 60 * 60) / 60; + return minutes + hours; + } + + return hours; +} + +static gchar * +get_formatted_time (int seconds) +{ + int minutes; + int hours; + gchar *res; + + if (seconds < 0) + { + /* Just to make sure... */ + seconds = 0; + } + + if (seconds < 60) + { + return g_strdup_printf (ngettext ("%'d second", "%'d seconds", (int) seconds), (int) seconds); + } + + if (seconds < 60 * 60) + { + minutes = seconds / 60; + return g_strdup_printf (ngettext ("%'d minute", "%'d minutes", minutes), minutes); + } + + hours = seconds / (60 * 60); + + if (seconds < 60 * 60 * 4) + { + gchar *h, *m; + + minutes = (seconds - hours * 60 * 60) / 60; + + h = g_strdup_printf (ngettext ("%'d hour", "%'d hours", hours), hours); + m = g_strdup_printf (ngettext ("%'d minute", "%'d minutes", minutes), minutes); + res = g_strconcat (h, ", ", m, NULL); + g_free (h); + g_free (m); + return res; + } + + return g_strdup_printf (ngettext ("%'d hour", + "%'d hours", + hours), hours); +} + +static char * +shorten_utf8_string (const char *base, + int reduce_by_num_bytes) +{ + int len; + char *ret; + const char *p; + + len = strlen (base); + len -= reduce_by_num_bytes; + + if (len <= 0) + { + return NULL; + } + + ret = g_new (char, len + 1); + + p = base; + while (len) + { + char *next; + next = g_utf8_next_char (p); + if (next - p > len || *next == '\0') + { + break; + } + + len -= next - p; + p = next; + } + + if (p - base == 0) + { + g_free (ret); + return NULL; + } + else + { + memcpy (ret, base, p - base); + ret[p - base] = '\0'; + return ret; + } +} + +/* Note that we have these two separate functions with separate format + * strings for ease of localization. + */ + +static char * +get_link_name (const char *name, + int count, + int max_length) +{ + const char *format; + char *result; + int unshortened_length; + gboolean use_count; + + g_assert (name != NULL); + + if (count < 0) + { + g_warning ("bad count in get_link_name"); + count = 0; + } + + if (count <= 2) + { + /* Handle special cases for low numbers. + * Perhaps for some locales we will need to add more. + */ + switch (count) + { + default: + { + g_assert_not_reached (); + /* fall through */ + } + + case 0: + { + /* duplicate original file name */ + format = "%s"; + } + break; + + case 1: + { + /* appended to new link file */ + format = _("Link to %s"); + } + break; + + case 2: + { + /* appended to new link file */ + format = _("Another link to %s"); + } + break; + } + + use_count = FALSE; + } + else + { + /* Handle special cases for the first few numbers of each ten. + * For locales where getting this exactly right is difficult, + * these can just be made all the same as the general case below. + */ + switch (count % 10) + { + case 1: + { + /* Localizers: Feel free to leave out the "st" suffix + * if there's no way to do that nicely for a + * particular language. + */ + format = _("%'dst link to %s"); + } + break; + + case 2: + { + /* appended to new link file */ + format = _("%'dnd link to %s"); + } + break; + + case 3: + { + /* appended to new link file */ + format = _("%'drd link to %s"); + } + break; + + default: + { + /* appended to new link file */ + format = _("%'dth link to %s"); + } + break; + } + + use_count = TRUE; + } + +#pragma GCC diagnostic push +#pragma GCC diagnostic ignored "-Wformat-nonliteral" + if (use_count) + { + result = g_strdup_printf (format, count, name); + } + else + { + result = g_strdup_printf (format, name); + } + + if (max_length > 0 && (unshortened_length = strlen (result)) > max_length) + { + char *new_name; + + new_name = shorten_utf8_string (name, unshortened_length - max_length); + if (new_name) + { + g_free (result); + + if (use_count) + { + result = g_strdup_printf (format, count, new_name); + } + else + { + result = g_strdup_printf (format, new_name); + } + + g_assert (strlen (result) <= max_length); + g_free (new_name); + } + } +#pragma GCC diagnostic pop + return result; +} + + +/* Localizers: + * Feel free to leave out the st, nd, rd and th suffix or + * make some or all of them match. + */ + +/* localizers: tag used to detect the first copy of a file */ +static const char untranslated_copy_duplicate_tag[] = N_(" (copy)"); +/* localizers: tag used to detect the second copy of a file */ +static const char untranslated_another_copy_duplicate_tag[] = N_(" (another copy)"); + +/* localizers: tag used to detect the x11th copy of a file */ +static const char untranslated_x11th_copy_duplicate_tag[] = N_("th copy)"); +/* localizers: tag used to detect the x12th copy of a file */ +static const char untranslated_x12th_copy_duplicate_tag[] = N_("th copy)"); +/* localizers: tag used to detect the x13th copy of a file */ +static const char untranslated_x13th_copy_duplicate_tag[] = N_("th copy)"); + +/* localizers: tag used to detect the x1st copy of a file */ +static const char untranslated_st_copy_duplicate_tag[] = N_("st copy)"); +/* localizers: tag used to detect the x2nd copy of a file */ +static const char untranslated_nd_copy_duplicate_tag[] = N_("nd copy)"); +/* localizers: tag used to detect the x3rd copy of a file */ +static const char untranslated_rd_copy_duplicate_tag[] = N_("rd copy)"); + +/* localizers: tag used to detect the xxth copy of a file */ +static const char untranslated_th_copy_duplicate_tag[] = N_("th copy)"); + +#define COPY_DUPLICATE_TAG _(untranslated_copy_duplicate_tag) +#define ANOTHER_COPY_DUPLICATE_TAG _(untranslated_another_copy_duplicate_tag) +#define X11TH_COPY_DUPLICATE_TAG _(untranslated_x11th_copy_duplicate_tag) +#define X12TH_COPY_DUPLICATE_TAG _(untranslated_x12th_copy_duplicate_tag) +#define X13TH_COPY_DUPLICATE_TAG _(untranslated_x13th_copy_duplicate_tag) + +#define ST_COPY_DUPLICATE_TAG _(untranslated_st_copy_duplicate_tag) +#define ND_COPY_DUPLICATE_TAG _(untranslated_nd_copy_duplicate_tag) +#define RD_COPY_DUPLICATE_TAG _(untranslated_rd_copy_duplicate_tag) +#define TH_COPY_DUPLICATE_TAG _(untranslated_th_copy_duplicate_tag) + +/* localizers: appended to first file copy */ +static const char untranslated_first_copy_duplicate_format[] = N_("%s (copy)%s"); +/* localizers: appended to second file copy */ +static const char untranslated_second_copy_duplicate_format[] = N_("%s (another copy)%s"); + +/* localizers: appended to x11th file copy */ +static const char untranslated_x11th_copy_duplicate_format[] = N_("%s (%'dth copy)%s"); +/* localizers: appended to x12th file copy */ +static const char untranslated_x12th_copy_duplicate_format[] = N_("%s (%'dth copy)%s"); +/* localizers: appended to x13th file copy */ +static const char untranslated_x13th_copy_duplicate_format[] = N_("%s (%'dth copy)%s"); + +/* localizers: if in your language there's no difference between 1st, 2nd, 3rd and nth + * plurals, you can leave the st, nd, rd suffixes out and just make all the translated + * strings look like "%s (copy %'d)%s". + */ + +/* localizers: appended to x1st file copy */ +static const char untranslated_st_copy_duplicate_format[] = N_("%s (%'dst copy)%s"); +/* localizers: appended to x2nd file copy */ +static const char untranslated_nd_copy_duplicate_format[] = N_("%s (%'dnd copy)%s"); +/* localizers: appended to x3rd file copy */ +static const char untranslated_rd_copy_duplicate_format[] = N_("%s (%'drd copy)%s"); +/* localizers: appended to xxth file copy */ +static const char untranslated_th_copy_duplicate_format[] = N_("%s (%'dth copy)%s"); + +#define FIRST_COPY_DUPLICATE_FORMAT _(untranslated_first_copy_duplicate_format) +#define SECOND_COPY_DUPLICATE_FORMAT _(untranslated_second_copy_duplicate_format) +#define X11TH_COPY_DUPLICATE_FORMAT _(untranslated_x11th_copy_duplicate_format) +#define X12TH_COPY_DUPLICATE_FORMAT _(untranslated_x12th_copy_duplicate_format) +#define X13TH_COPY_DUPLICATE_FORMAT _(untranslated_x13th_copy_duplicate_format) + +#define ST_COPY_DUPLICATE_FORMAT _(untranslated_st_copy_duplicate_format) +#define ND_COPY_DUPLICATE_FORMAT _(untranslated_nd_copy_duplicate_format) +#define RD_COPY_DUPLICATE_FORMAT _(untranslated_rd_copy_duplicate_format) +#define TH_COPY_DUPLICATE_FORMAT _(untranslated_th_copy_duplicate_format) + +static char * +extract_string_until (const char *original, + const char *until_substring) +{ + char *result; + + g_assert ((int) strlen (original) >= until_substring - original); + g_assert (until_substring - original >= 0); + + result = g_malloc (until_substring - original + 1); + strncpy (result, original, until_substring - original); + result[until_substring - original] = '\0'; + + return result; +} + +/* Dismantle a file name, separating the base name, the file suffix and removing any + * (xxxcopy), etc. string. Figure out the count that corresponds to the given + * (xxxcopy) substring. + */ +static void +parse_previous_duplicate_name (const char *name, + char **name_base, + const char **suffix, + int *count, + gboolean ignore_extension) +{ + const char *tag; + + g_assert (name[0] != '\0'); + + *suffix = eel_filename_get_extension_offset (name); + + if (*suffix == NULL || (*suffix)[1] == '\0') + { + /* no suffix */ + *suffix = ""; + } + + tag = strstr (name, COPY_DUPLICATE_TAG); + if (tag != NULL) + { + if (tag > *suffix) + { + /* handle case "foo. (copy)" */ + *suffix = ""; + } + *name_base = extract_string_until (name, tag); + *count = 1; + return; + } + + + tag = strstr (name, ANOTHER_COPY_DUPLICATE_TAG); + if (tag != NULL) + { + if (tag > *suffix) + { + /* handle case "foo. (another copy)" */ + *suffix = ""; + } + *name_base = extract_string_until (name, tag); + *count = 2; + return; + } + + + /* Check to see if we got one of st, nd, rd, th. */ + tag = strstr (name, X11TH_COPY_DUPLICATE_TAG); + + if (tag == NULL) + { + tag = strstr (name, X12TH_COPY_DUPLICATE_TAG); + } + if (tag == NULL) + { + tag = strstr (name, X13TH_COPY_DUPLICATE_TAG); + } + + if (tag == NULL) + { + tag = strstr (name, ST_COPY_DUPLICATE_TAG); + } + if (tag == NULL) + { + tag = strstr (name, ND_COPY_DUPLICATE_TAG); + } + if (tag == NULL) + { + tag = strstr (name, RD_COPY_DUPLICATE_TAG); + } + if (tag == NULL) + { + tag = strstr (name, TH_COPY_DUPLICATE_TAG); + } + + /* If we got one of st, nd, rd, th, fish out the duplicate number. */ + if (tag != NULL) + { + /* localizers: opening parentheses to match the "th copy)" string */ + tag = strstr (name, _(" (")); + if (tag != NULL) + { + if (tag > *suffix) + { + /* handle case "foo. (22nd copy)" */ + *suffix = ""; + } + *name_base = extract_string_until (name, tag); + /* localizers: opening parentheses of the "th copy)" string */ + if (sscanf (tag, _(" (%'d"), count) == 1) + { + if (*count < 1 || *count > 1000000) + { + /* keep the count within a reasonable range */ + *count = 0; + } + return; + } + *count = 0; + return; + } + } + + + *count = 0; + /* ignore_extension was not used before to let above code handle case "dir (copy).dir" for directories */ + if (**suffix != '\0' && !ignore_extension) + { + *name_base = extract_string_until (name, *suffix); + } + else + { + /* making sure extension is ignored in directories */ + *suffix = ""; + *name_base = g_strdup (name); + } +} + +static char * +make_next_duplicate_name (const char *base, + const char *suffix, + int count, + int max_length) +{ + const char *format; + char *result; + int unshortened_length; + gboolean use_count; + + if (count < 1) + { + g_warning ("bad count %d in get_duplicate_name", count); + count = 1; + } + + if (count <= 2) + { + /* Handle special cases for low numbers. + * Perhaps for some locales we will need to add more. + */ + switch (count) + { + default: + { + g_assert_not_reached (); + /* fall through */ + } + + case 1: + { + format = FIRST_COPY_DUPLICATE_FORMAT; + } + break; + + case 2: + { + format = SECOND_COPY_DUPLICATE_FORMAT; + } + break; + } + + use_count = FALSE; + } + else + { + /* Handle special cases for the first few numbers of each ten. + * For locales where getting this exactly right is difficult, + * these can just be made all the same as the general case below. + */ + + /* Handle special cases for x11th - x20th. + */ + switch (count % 100) + { + case 11: + { + format = X11TH_COPY_DUPLICATE_FORMAT; + } + break; + + case 12: + { + format = X12TH_COPY_DUPLICATE_FORMAT; + } + break; + + case 13: + { + format = X13TH_COPY_DUPLICATE_FORMAT; + } + break; + + default: + { + format = NULL; + } + break; + } + + if (format == NULL) + { + switch (count % 10) + { + case 1: + { + format = ST_COPY_DUPLICATE_FORMAT; + } + break; + + case 2: + { + format = ND_COPY_DUPLICATE_FORMAT; + } + break; + + case 3: + { + format = RD_COPY_DUPLICATE_FORMAT; + } + break; + + default: + { + /* The general case. */ + format = TH_COPY_DUPLICATE_FORMAT; + } + break; + } + } + + use_count = TRUE; + } + +#pragma GCC diagnostic push +#pragma GCC diagnostic ignored "-Wformat-nonliteral" + if (use_count) + { + result = g_strdup_printf (format, base, count, suffix); + } + else + { + result = g_strdup_printf (format, base, suffix); + } + + if (max_length > 0 && (unshortened_length = strlen (result)) > max_length) + { + char *new_base; + + new_base = shorten_utf8_string (base, unshortened_length - max_length); + if (new_base) + { + g_free (result); + + if (use_count) + { + result = g_strdup_printf (format, new_base, count, suffix); + } + else + { + result = g_strdup_printf (format, new_base, suffix); + } + + g_assert (strlen (result) <= max_length); + g_free (new_base); + } + } +#pragma GCC diagnostic pop + + return result; +} + +static char * +get_duplicate_name (const char *name, + int count_increment, + int max_length, + gboolean ignore_extension) +{ + char *result; + char *name_base; + const char *suffix; + int count; + + parse_previous_duplicate_name (name, &name_base, &suffix, &count, ignore_extension); + result = make_next_duplicate_name (name_base, suffix, count + count_increment, max_length); + + g_free (name_base); + + return result; +} + +static gboolean +has_invalid_xml_char (char *str) +{ + gunichar c; + + while (*str != 0) + { + c = g_utf8_get_char (str); + /* characters XML permits */ + if (!(c == 0x9 || + c == 0xA || + c == 0xD || + (c >= 0x20 && c <= 0xD7FF) || + (c >= 0xE000 && c <= 0xFFFD) || + (c >= 0x10000 && c <= 0x10FFFF))) + { + return TRUE; + } + str = g_utf8_next_char (str); + } + return FALSE; +} + +static gchar * +get_basename (GFile *file) +{ + GFileInfo *info; + gchar *name, *basename, *tmp; + GMount *mount; + + if ((mount = nautilus_get_mounted_mount_for_root (file)) != NULL) + { + name = g_mount_get_name (mount); + g_object_unref (mount); + } + else + { + info = g_file_query_info (file, + G_FILE_ATTRIBUTE_STANDARD_DISPLAY_NAME, + 0, + g_cancellable_get_current (), + NULL); + name = NULL; + if (info) + { + name = g_strdup (g_file_info_get_display_name (info)); + g_object_unref (info); + } + } + + if (name == NULL) + { + basename = g_file_get_basename (file); + if (basename == NULL) + { + return g_strdup (_("unknown")); + } + + if (g_utf8_validate (basename, -1, NULL)) + { + name = basename; + } + else + { + name = g_uri_escape_string (basename, G_URI_RESERVED_CHARS_ALLOWED_IN_PATH, TRUE); + g_free (basename); + } + } + + /* Some chars can't be put in the markup we use for the dialogs... */ + if (has_invalid_xml_char (name)) + { + tmp = name; + name = g_uri_escape_string (name, G_URI_RESERVED_CHARS_ALLOWED_IN_PATH, TRUE); + g_free (tmp); + } + + /* Finally, if the string is too long, truncate it. */ + if (name != NULL) + { + tmp = name; + name = eel_str_middle_truncate (tmp, MAXIMUM_DISPLAYED_FILE_NAME_LENGTH); + g_free (tmp); + } + + return name; +} + +static gchar * +get_truncated_parse_name (GFile *file) +{ + g_autofree gchar *parse_name = NULL; + + g_assert (G_IS_FILE (file)); + + parse_name = g_file_get_parse_name (file); + + return eel_str_middle_truncate (parse_name, MAXIMUM_DISPLAYED_FILE_NAME_LENGTH); +} + +#define op_job_new(__type, parent_window, dbus_data) ((__type *) (init_common (sizeof (__type), parent_window, dbus_data))) + +static gpointer +init_common (gsize job_size, + GtkWindow *parent_window, + NautilusFileOperationsDBusData *dbus_data) +{ + CommonJob *common; + + common = g_malloc0 (job_size); + + if (parent_window) + { + common->parent_window = parent_window; + g_object_add_weak_pointer (G_OBJECT (common->parent_window), + (gpointer *) &common->parent_window); + } + + if (dbus_data) + { + common->dbus_data = nautilus_file_operations_dbus_data_ref (dbus_data); + } + + common->progress = nautilus_progress_info_new (); + common->cancellable = nautilus_progress_info_get_cancellable (common->progress); + common->time = g_timer_new (); + common->inhibit_cookie = 0; + + return common; +} + +static void +finalize_common (CommonJob *common) +{ + nautilus_progress_info_finish (common->progress); + + if (common->inhibit_cookie != 0) + { + gtk_application_uninhibit (GTK_APPLICATION (g_application_get_default ()), + common->inhibit_cookie); + } + + common->inhibit_cookie = 0; + g_timer_destroy (common->time); + + if (common->parent_window) + { + g_object_remove_weak_pointer (G_OBJECT (common->parent_window), + (gpointer *) &common->parent_window); + } + + if (common->dbus_data) + { + nautilus_file_operations_dbus_data_unref (common->dbus_data); + } + + if (common->skip_files) + { + g_hash_table_destroy (common->skip_files); + } + if (common->skip_readdir_error) + { + g_hash_table_destroy (common->skip_readdir_error); + } + + if (common->undo_info != NULL) + { + nautilus_file_undo_manager_set_action (common->undo_info); + g_object_unref (common->undo_info); + } + + g_object_unref (common->progress); + g_object_unref (common->cancellable); + g_free (common); +} + +static void +skip_file (CommonJob *common, + GFile *file) +{ + if (common->skip_files == NULL) + { + common->skip_files = + g_hash_table_new_full (g_file_hash, (GEqualFunc) g_file_equal, g_object_unref, NULL); + } + + g_hash_table_insert (common->skip_files, g_object_ref (file), file); +} + +static void +skip_readdir_error (CommonJob *common, + GFile *dir) +{ + if (common->skip_readdir_error == NULL) + { + common->skip_readdir_error = + g_hash_table_new_full (g_file_hash, (GEqualFunc) g_file_equal, g_object_unref, NULL); + } + + g_hash_table_insert (common->skip_readdir_error, g_object_ref (dir), dir); +} + +static gboolean +should_skip_file (CommonJob *common, + GFile *file) +{ + if (common->skip_files != NULL) + { + return g_hash_table_lookup (common->skip_files, file) != NULL; + } + return FALSE; +} + +static gboolean +should_skip_readdir_error (CommonJob *common, + GFile *dir) +{ + if (common->skip_readdir_error != NULL) + { + return g_hash_table_lookup (common->skip_readdir_error, dir) != NULL; + } + return FALSE; +} + +static gboolean +can_delete_without_confirm (GFile *file) +{ + /* In the case of testing, we want to be able to delete + * without asking for confirmation from the user. + */ + if (g_file_has_uri_scheme (file, "burn") || + g_file_has_uri_scheme (file, "recent") || + !g_strcmp0 (g_getenv ("RUNNING_TESTS"), "TRUE")) + { + return TRUE; + } + + return FALSE; +} + +static gboolean +can_delete_files_without_confirm (GList *files) +{ + g_assert (files != NULL); + + while (files != NULL) + { + if (!can_delete_without_confirm (files->data)) + { + return FALSE; + } + + files = files->next; + } + + return TRUE; +} + +typedef struct +{ + GtkWindow **parent_window; + NautilusFileOperationsDBusData *dbus_data; + gboolean ignore_close_box; + GtkMessageType message_type; + const char *primary_text; + const char *secondary_text; + const char *details_text; + const char **button_titles; + gboolean show_all; + gboolean should_start_inactive; + int result; + /* Dialogs are ran from operation threads, which need to be blocked until + * the user gives a valid response + */ + gboolean completed; + GMutex mutex; + GCond cond; +} RunSimpleDialogData; + +static void +set_transient_for (GdkSurface *child_surface, + const char *parent_handle) +{ + GdkDisplay *display; + const char *prefix; + + display = gdk_surface_get_display (child_surface); + +#ifdef GDK_WINDOWING_X11 + if (GDK_IS_X11_DISPLAY (display)) + { + prefix = "x11:"; + + if (g_str_has_prefix (parent_handle, prefix)) + { + const char *handle; + GdkSurface *surface; + + handle = parent_handle + strlen (prefix); + surface = gdk_x11_surface_lookup_for_display (display, strtol (handle, NULL, 16)); + + if (surface != NULL) + { + gdk_toplevel_set_transient_for (GDK_TOPLEVEL (child_surface), surface); + } + } + } +#endif + +#ifdef GDK_WINDOWING_WAYLAND + if (GDK_IS_WAYLAND_DISPLAY (display)) + { + prefix = "wayland:"; + + if (g_str_has_prefix (parent_handle, prefix)) + { + const char *handle; + + handle = parent_handle + strlen (prefix); + + gdk_wayland_toplevel_set_transient_for_exported (GDK_TOPLEVEL (child_surface), (char *) handle); + } + } +#endif +} + +static void +dialog_realize_cb (GtkWidget *widget, + gpointer user_data) +{ + NautilusFileOperationsDBusData *dbus_data = user_data; + const char *parent_handle; + + parent_handle = nautilus_file_operations_dbus_data_get_parent_handle (dbus_data); + set_transient_for (gtk_native_get_surface (gtk_widget_get_native (widget)), parent_handle); +} + +static gboolean +is_long_job (CommonJob *job) +{ + double elapsed = nautilus_progress_info_get_total_elapsed_time (job->progress); + return elapsed > LONG_JOB_THRESHOLD_IN_SECONDS ? TRUE : FALSE; +} + +static gboolean +simple_dialog_button_activate (GtkWidget *button) +{ + gtk_widget_set_sensitive (button, TRUE); + return G_SOURCE_REMOVE; +} + +static void +simple_dialog_cb (GtkDialog *dialog, + gint response_id, + gpointer user_data) +{ + RunSimpleDialogData *data = user_data; + + gtk_window_destroy (GTK_WINDOW (dialog)); + + data->result = response_id; + data->completed = TRUE; + + g_cond_signal (&data->cond); + g_mutex_unlock (&data->mutex); +} + +static gboolean +do_run_simple_dialog (gpointer _data) +{ + RunSimpleDialogData *data = _data; + const char *button_title; + GtkWidget *dialog; + GtkWidget *button; + int response_id; + + g_mutex_lock (&data->mutex); + + /* Create the dialog. */ + dialog = gtk_message_dialog_new (*data->parent_window, + GTK_DIALOG_MODAL, + data->message_type, + GTK_BUTTONS_NONE, + NULL); + + g_object_set (dialog, + "text", data->primary_text, + "secondary-text", data->secondary_text, + NULL); + + for (response_id = 0; + data->button_titles[response_id] != NULL; + response_id++) + { + button_title = data->button_titles[response_id]; + if (!data->show_all && is_all_button_text (button_title)) + { + continue; + } + + button = gtk_dialog_add_button (GTK_DIALOG (dialog), button_title, response_id); + gtk_dialog_set_default_response (GTK_DIALOG (dialog), response_id); + + if (g_strcmp0 (button_title, DELETE) == 0 || + g_strcmp0 (button_title, EMPTY_TRASH) == 0 || + g_strcmp0 (button_title, DELETE_ALL) == 0) + { + gtk_style_context_add_class (gtk_widget_get_style_context (button), + "destructive-action"); + } + + if (data->should_start_inactive) + { + gtk_widget_set_sensitive (button, FALSE); + g_timeout_add_seconds (BUTTON_ACTIVATION_DELAY_IN_SECONDS, + G_SOURCE_FUNC (simple_dialog_button_activate), + button); + } + } + + if (data->details_text) + { + GtkWidget *content_area, *label; + content_area = gtk_message_dialog_get_message_area (GTK_MESSAGE_DIALOG (dialog)); + + label = gtk_label_new (data->details_text); + gtk_label_set_wrap (GTK_LABEL (label), TRUE); + gtk_label_set_selectable (GTK_LABEL (label), TRUE); + gtk_label_set_xalign (GTK_LABEL (label), 0); + /* Ideally, we shouldn’t do this. + * + * Refer to https://gitlab.gnome.org/GNOME/nautilus/merge_requests/94 + * and https://gitlab.gnome.org/GNOME/nautilus/issues/270. + */ + gtk_label_set_ellipsize (GTK_LABEL (label), PANGO_ELLIPSIZE_MIDDLE); + gtk_label_set_max_width_chars (GTK_LABEL (label), + MAXIMUM_DISPLAYED_ERROR_MESSAGE_LENGTH); + + gtk_box_append (GTK_BOX (content_area), label); + + gtk_widget_show (label); + } + + if (data->dbus_data != NULL) + { + guint32 timestamp; + + timestamp = nautilus_file_operations_dbus_data_get_timestamp (data->dbus_data); + + if (nautilus_file_operations_dbus_data_get_parent_handle (data->dbus_data) != NULL) + { + g_signal_connect (dialog, "realize", G_CALLBACK (dialog_realize_cb), data->dbus_data); + } + + if (timestamp != 0) + { + gtk_window_present_with_time (GTK_WINDOW (dialog), timestamp); + } + } + + /* Run it. */ + g_signal_connect (dialog, "response", G_CALLBACK (simple_dialog_cb), data); + gtk_widget_show (dialog); + + return FALSE; +} + +/* NOTE: This frees the primary / secondary strings, in order to + * avoid doing that everywhere. So, make sure they are strduped */ + +static int +run_simple_dialog_va (CommonJob *job, + gboolean ignore_close_box, + GtkMessageType message_type, + char *primary_text, + char *secondary_text, + const char *details_text, + gboolean show_all, + va_list varargs) +{ + RunSimpleDialogData *data; + int res; + const char *button_title; + GPtrArray *ptr_array; + + g_timer_stop (job->time); + + data = g_new0 (RunSimpleDialogData, 1); + data->parent_window = &job->parent_window; + data->dbus_data = job->dbus_data; + data->ignore_close_box = ignore_close_box; + data->message_type = message_type; + data->primary_text = primary_text; + data->secondary_text = secondary_text; + data->details_text = details_text; + data->show_all = show_all; + data->completed = FALSE; + g_mutex_init (&data->mutex); + g_cond_init (&data->cond); + + ptr_array = g_ptr_array_new (); + while ((button_title = va_arg (varargs, const char *)) != NULL) + { + g_ptr_array_add (ptr_array, (char *) button_title); + } + g_ptr_array_add (ptr_array, NULL); + data->button_titles = (const char **) g_ptr_array_free (ptr_array, FALSE); + + nautilus_progress_info_pause (job->progress); + + data->should_start_inactive = is_long_job (job); + + g_mutex_lock (&data->mutex); + + g_main_context_invoke (NULL, + do_run_simple_dialog, + data); + + while (!data->completed) + { + g_cond_wait (&data->cond, &data->mutex); + } + + nautilus_progress_info_resume (job->progress); + res = data->result; + + g_mutex_unlock (&data->mutex); + g_mutex_clear (&data->mutex); + g_cond_clear (&data->cond); + + g_free (data->button_titles); + g_free (data); + + g_timer_continue (job->time); + + g_free (primary_text); + g_free (secondary_text); + + return res; +} + +#if 0 /* Not used at the moment */ +static int +run_simple_dialog (CommonJob *job, + gboolean ignore_close_box, + GtkMessageType message_type, + char *primary_text, + char *secondary_text, + const char *details_text, + ...) +{ + va_list varargs; + int res; + + va_start (varargs, details_text); + res = run_simple_dialog_va (job, + ignore_close_box, + message_type, + primary_text, + secondary_text, + details_text, + varargs); + va_end (varargs); + return res; +} +#endif + +static int +run_error (CommonJob *job, + char *primary_text, + char *secondary_text, + const char *details_text, + gboolean show_all, + ...) +{ + va_list varargs; + int res; + + va_start (varargs, show_all); + res = run_simple_dialog_va (job, + FALSE, + GTK_MESSAGE_ERROR, + primary_text, + secondary_text, + details_text, + show_all, + varargs); + va_end (varargs); + return res; +} + +static int +run_warning (CommonJob *job, + char *primary_text, + char *secondary_text, + const char *details_text, + gboolean show_all, + ...) +{ + va_list varargs; + int res; + + va_start (varargs, show_all); + res = run_simple_dialog_va (job, + FALSE, + GTK_MESSAGE_WARNING, + primary_text, + secondary_text, + details_text, + show_all, + varargs); + va_end (varargs); + return res; +} + +static int +run_question (CommonJob *job, + char *primary_text, + char *secondary_text, + const char *details_text, + gboolean show_all, + ...) +{ + va_list varargs; + int res; + + va_start (varargs, show_all); + res = run_simple_dialog_va (job, + FALSE, + GTK_MESSAGE_QUESTION, + primary_text, + secondary_text, + details_text, + show_all, + varargs); + va_end (varargs); + return res; +} + +static int +run_cancel_or_skip_warning (CommonJob *job, + char *primary_text, + char *secondary_text, + const char *details_text, + int total_operations, + int operations_remaining) +{ + int response; + + if (total_operations == 1) + { + response = run_warning (job, + primary_text, + secondary_text, + details_text, + FALSE, + CANCEL, + NULL); + } + else + { + response = run_warning (job, + primary_text, + secondary_text, + details_text, + operations_remaining > 1, + CANCEL, SKIP_ALL, SKIP, + NULL); + } + + return response; +} + +static void +inhibit_power_manager (CommonJob *job, + const char *message) +{ + job->inhibit_cookie = gtk_application_inhibit (GTK_APPLICATION (g_application_get_default ()), + GTK_WINDOW (job->parent_window), + GTK_APPLICATION_INHIBIT_LOGOUT | + GTK_APPLICATION_INHIBIT_SUSPEND, + message); +} + +static void +abort_job (CommonJob *job) +{ + /* destroy the undo action data too */ + g_clear_object (&job->undo_info); + + g_cancellable_cancel (job->cancellable); +} + +static gboolean +job_aborted (CommonJob *job) +{ + return g_cancellable_is_cancelled (job->cancellable); +} + +static gboolean +confirm_delete_from_trash (CommonJob *job, + GList *files) +{ + char *prompt; + int file_count; + int response; + + file_count = g_list_length (files); + g_assert (file_count > 0); + + if (file_count == 1) + { + g_autofree gchar *basename = NULL; + + basename = get_basename (files->data); + prompt = g_strdup_printf (_("Are you sure you want to permanently delete “%s” " + "from the trash?"), basename); + } + else + { + prompt = g_strdup_printf (ngettext ("Are you sure you want to permanently delete " + "the %'d selected item from the trash?", + "Are you sure you want to permanently delete " + "the %'d selected items from the trash?", + file_count), + file_count); + } + + response = run_warning (job, + prompt, + g_strdup (_("If you delete an item, it will be permanently lost.")), + NULL, + FALSE, + CANCEL, DELETE, + NULL); + + return (response == 1); +} + +static gboolean +confirm_empty_trash (CommonJob *job) +{ + char *prompt; + int response; + + prompt = g_strdup (_("Empty all items from Trash?")); + + response = run_warning (job, + prompt, + g_strdup (_("All items in the Trash will be permanently deleted.")), + NULL, + FALSE, + CANCEL, EMPTY_TRASH, + NULL); + + return (response == 1); +} + +static gboolean +confirm_delete_directly (CommonJob *job, + GList *files) +{ + char *prompt; + int file_count; + int response; + + file_count = g_list_length (files); + g_assert (file_count > 0); + + if (can_delete_files_without_confirm (files)) + { + return TRUE; + } + + if (file_count == 1) + { + g_autofree gchar *basename = NULL; + + basename = get_basename (files->data); + prompt = g_strdup_printf (_("Are you sure you want to permanently delete “%s”?"), + basename); + } + else + { + prompt = g_strdup_printf (ngettext ("Are you sure you want to permanently delete " + "the %'d selected item?", + "Are you sure you want to permanently delete " + "the %'d selected items?", file_count), + file_count); + } + + response = run_warning (job, + prompt, + g_strdup (_("If you delete an item, it will be permanently lost.")), + NULL, + FALSE, + CANCEL, DELETE, + NULL); + + return response == 1; +} + +#pragma GCC diagnostic push +#pragma GCC diagnostic ignored "-Wformat-nonliteral" +static void +report_delete_progress (CommonJob *job, + SourceInfo *source_info, + TransferInfo *transfer_info) +{ + int files_left; + double elapsed, transfer_rate; + int remaining_time; + gint64 now; + char *details; + char *status; + DeleteJob *delete_job; + + delete_job = (DeleteJob *) job; + now = g_get_monotonic_time (); + files_left = source_info->num_files - transfer_info->num_files; + + /* Races and whatnot could cause this to be negative... */ + if (files_left < 0) + { + files_left = 0; + } + + /* If the number of files left is 0, we want to update the status without + * considering this time, since we want to change the status to completed + * and probably we won't get more calls to this function */ + if (transfer_info->last_report_time != 0 && + ABS ((gint64) (transfer_info->last_report_time - now)) < 100 * NSEC_PER_MICROSEC && + files_left > 0) + { + return; + } + + transfer_info->last_report_time = now; + + if (source_info->num_files == 1) + { + g_autofree gchar *basename = NULL; + + if (files_left == 0) + { + status = _("Deleted “%s”"); + } + else + { + status = _("Deleting “%s”"); + } + + basename = get_basename (G_FILE (delete_job->files->data)); + nautilus_progress_info_take_status (job->progress, + g_strdup_printf (status, basename)); + } + else + { + if (files_left == 0) + { + status = ngettext ("Deleted %'d file", + "Deleted %'d files", + source_info->num_files); + } + else + { + status = ngettext ("Deleting %'d file", + "Deleting %'d files", + source_info->num_files); + } + nautilus_progress_info_take_status (job->progress, + g_strdup_printf (status, + source_info->num_files)); + } + + elapsed = g_timer_elapsed (job->time, NULL); + transfer_rate = 0; + remaining_time = INT_MAX; + if (elapsed > 0) + { + transfer_rate = transfer_info->num_files / elapsed; + if (transfer_rate > 0) + { + remaining_time = (source_info->num_files - transfer_info->num_files) / transfer_rate; + } + } + + if (elapsed < SECONDS_NEEDED_FOR_RELIABLE_TRANSFER_RATE || + transfer_rate == 0) + { + if (files_left > 0) + { + /* To translators: %'d is the number of files completed for the operation, + * so it will be something like 2/14. */ + details = g_strdup_printf (_("%'d / %'d"), + transfer_info->num_files + 1, + source_info->num_files); + } + else + { + /* To translators: %'d is the number of files completed for the operation, + * so it will be something like 2/14. */ + details = g_strdup_printf (_("%'d / %'d"), + transfer_info->num_files, + source_info->num_files); + } + } + else + { + if (files_left > 0) + { + gchar *time_left_message; + gchar *files_per_second_message; + gchar *concat_detail; + g_autofree gchar *formatted_time = NULL; + + /* To translators: %s will expand to a time duration like "2 minutes". + * So the whole thing will be something like "1 / 5 -- 2 hours left (4 files/sec)" + * + * The singular/plural form will be used depending on the remaining time (i.e. the %s argument). + */ + time_left_message = ngettext ("%'d / %'d \xE2\x80\x94 %s left", + "%'d / %'d \xE2\x80\x94 %s left", + seconds_count_format_time_units (remaining_time)); + transfer_rate += 0.5; + files_per_second_message = ngettext ("(%d file/sec)", + "(%d files/sec)", + (int) transfer_rate); + concat_detail = g_strconcat (time_left_message, " ", files_per_second_message, NULL); + + formatted_time = get_formatted_time (remaining_time); + details = g_strdup_printf (concat_detail, + transfer_info->num_files + 1, source_info->num_files, + formatted_time, + (int) transfer_rate); + + g_free (concat_detail); + } + else + { + /* To translators: %'d is the number of files completed for the operation, + * so it will be something like 2/14. */ + details = g_strdup_printf (_("%'d / %'d"), + transfer_info->num_files, + source_info->num_files); + } + } + nautilus_progress_info_take_details (job->progress, details); + + if (elapsed > SECONDS_NEEDED_FOR_APROXIMATE_TRANSFER_RATE) + { + nautilus_progress_info_set_remaining_time (job->progress, + remaining_time); + nautilus_progress_info_set_elapsed_time (job->progress, + elapsed); + } + + if (source_info->num_files != 0) + { + nautilus_progress_info_set_progress (job->progress, transfer_info->num_files, source_info->num_files); + } +} +#pragma GCC diagnostic pop + +typedef void (*DeleteCallback) (GFile *file, + GError *error, + gpointer callback_data); + +static gboolean +delete_file_recursively (GFile *file, + GCancellable *cancellable, + DeleteCallback callback, + gpointer callback_data) +{ + gboolean success; + g_autoptr (GError) error = NULL; + + do + { + g_autoptr (GFileEnumerator) enumerator = NULL; + + success = g_file_delete (file, cancellable, &error); + if (success || + !g_error_matches (error, G_IO_ERROR, G_IO_ERROR_NOT_EMPTY)) + { + break; + } + + g_clear_error (&error); + + enumerator = g_file_enumerate_children (file, + G_FILE_ATTRIBUTE_STANDARD_NAME, + G_FILE_QUERY_INFO_NONE, + cancellable, &error); + + if (enumerator) + { + GFileInfo *info; + + success = TRUE; + + info = g_file_enumerator_next_file (enumerator, + cancellable, + &error); + + while (info != NULL) + { + g_autoptr (GFile) child = NULL; + + child = g_file_enumerator_get_child (enumerator, info); + + success = success && delete_file_recursively (child, + cancellable, + callback, + callback_data); + + g_object_unref (info); + + info = g_file_enumerator_next_file (enumerator, + cancellable, + &error); + } + } + + if (error != NULL) + { + success = FALSE; + } + } + while (success); + + if (callback) + { + callback (file, error, callback_data); + } + + return success; +} + +typedef struct +{ + CommonJob *job; + SourceInfo *source_info; + TransferInfo *transfer_info; +} DeleteData; + +static void +file_deleted_callback (GFile *file, + GError *error, + gpointer callback_data) +{ + DeleteData *data = callback_data; + CommonJob *job; + SourceInfo *source_info; + TransferInfo *transfer_info; + GFileType file_type; + char *primary; + char *secondary; + char *details = NULL; + int response; + g_autofree gchar *basename = NULL; + + job = data->job; + source_info = data->source_info; + transfer_info = data->transfer_info; + + data->transfer_info->num_files++; + + if (error == NULL) + { + nautilus_file_changes_queue_file_removed (file); + report_delete_progress (data->job, data->source_info, data->transfer_info); + + return; + } + + if (job_aborted (job) || + job->skip_all_error || + should_skip_file (job, file) || + should_skip_readdir_error (job, file)) + { + return; + } + + primary = g_strdup (_("Error while deleting.")); + + file_type = g_file_query_file_type (file, + G_FILE_QUERY_INFO_NOFOLLOW_SYMLINKS, + job->cancellable); + + basename = get_basename (file); + + if (file_type == G_FILE_TYPE_DIRECTORY) + { + secondary = IS_IO_ERROR (error, PERMISSION_DENIED) ? + g_strdup_printf (_("There was an error deleting the " + "folder “%s”."), + basename) : + g_strdup_printf (_("You do not have sufficient permissions " + "to delete the folder “%s”."), + basename); + } + else + { + secondary = IS_IO_ERROR (error, PERMISSION_DENIED) ? + g_strdup_printf (_("There was an error deleting the " + "file “%s”."), + basename) : + g_strdup_printf (_("You do not have sufficient permissions " + "to delete the file “%s”."), + basename); + } + + details = error->message; + + response = run_cancel_or_skip_warning (job, + primary, + secondary, + details, + source_info->num_files, + source_info->num_files - transfer_info->num_files); + + if (response == 0 || response == GTK_RESPONSE_DELETE_EVENT) + { + abort_job (job); + } + else if (response == 1) + { + /* skip all */ + job->skip_all_error = TRUE; + } +} + +static void +delete_files (CommonJob *job, + GList *files, + int *files_skipped) +{ + GList *l; + GFile *file; + g_auto (SourceInfo) source_info = SOURCE_INFO_INIT; + TransferInfo transfer_info; + DeleteData data; + + if (job_aborted (job)) + { + return; + } + + scan_sources (files, + &source_info, + job, + OP_KIND_DELETE); + if (job_aborted (job)) + { + return; + } + + g_timer_start (job->time); + + memset (&transfer_info, 0, sizeof (transfer_info)); + report_delete_progress (job, &source_info, &transfer_info); + + data.job = job; + data.source_info = &source_info; + data.transfer_info = &transfer_info; + + for (l = files; + l != NULL && !job_aborted (job); + l = l->next) + { + gboolean success; + + file = l->data; + + if (should_skip_file (job, file)) + { + (*files_skipped)++; + continue; + } + + success = delete_file_recursively (file, job->cancellable, + file_deleted_callback, + &data); + + if (!success) + { + (*files_skipped)++; + } + } +} + +#pragma GCC diagnostic push +#pragma GCC diagnostic ignored "-Wformat-nonliteral" +static void +report_trash_progress (CommonJob *job, + SourceInfo *source_info, + TransferInfo *transfer_info) +{ + int files_left; + double elapsed, transfer_rate; + int remaining_time; + gint64 now; + char *details; + char *status; + DeleteJob *delete_job; + + delete_job = (DeleteJob *) job; + now = g_get_monotonic_time (); + files_left = source_info->num_files - transfer_info->num_files; + + /* Races and whatnot could cause this to be negative... */ + if (files_left < 0) + { + files_left = 0; + } + + /* If the number of files left is 0, we want to update the status without + * considering this time, since we want to change the status to completed + * and probably we won't get more calls to this function */ + if (transfer_info->last_report_time != 0 && + ABS ((gint64) (transfer_info->last_report_time - now)) < 100 * NSEC_PER_MICROSEC && + files_left > 0) + { + return; + } + + transfer_info->last_report_time = now; + + if (source_info->num_files == 1) + { + g_autofree gchar *basename = NULL; + + if (files_left > 0) + { + status = _("Trashing “%s”"); + } + else + { + status = _("Trashed “%s”"); + } + + basename = get_basename (G_FILE (delete_job->files->data)); + nautilus_progress_info_take_status (job->progress, + g_strdup_printf (status, basename)); + } + else + { + if (files_left > 0) + { + status = ngettext ("Trashing %'d file", + "Trashing %'d files", + source_info->num_files); + } + else + { + status = ngettext ("Trashed %'d file", + "Trashed %'d files", + source_info->num_files); + } + nautilus_progress_info_take_status (job->progress, + g_strdup_printf (status, + source_info->num_files)); + } + + + elapsed = g_timer_elapsed (job->time, NULL); + transfer_rate = 0; + remaining_time = INT_MAX; + if (elapsed > 0) + { + transfer_rate = transfer_info->num_files / elapsed; + if (transfer_rate > 0) + { + remaining_time = (source_info->num_files - transfer_info->num_files) / transfer_rate; + } + } + + if (elapsed < SECONDS_NEEDED_FOR_RELIABLE_TRANSFER_RATE || + transfer_rate == 0) + { + if (files_left > 0) + { + /* To translators: %'d is the number of files completed for the operation, + * so it will be something like 2/14. */ + details = g_strdup_printf (_("%'d / %'d"), + transfer_info->num_files + 1, + source_info->num_files); + } + else + { + /* To translators: %'d is the number of files completed for the operation, + * so it will be something like 2/14. */ + details = g_strdup_printf (_("%'d / %'d"), + transfer_info->num_files, + source_info->num_files); + } + } + else + { + if (files_left > 0) + { + gchar *time_left_message; + gchar *files_per_second_message; + gchar *concat_detail; + g_autofree gchar *formatted_time = NULL; + + /* To translators: %s will expand to a time duration like "2 minutes". + * So the whole thing will be something like "1 / 5 -- 2 hours left (4 files/sec)" + * + * The singular/plural form will be used depending on the remaining time (i.e. the %s argument). + */ + time_left_message = ngettext ("%'d / %'d \xE2\x80\x94 %s left", + "%'d / %'d \xE2\x80\x94 %s left", + seconds_count_format_time_units (remaining_time)); + files_per_second_message = ngettext ("(%d file/sec)", + "(%d files/sec)", + (int) (transfer_rate + 0.5)); + concat_detail = g_strconcat (time_left_message, " ", files_per_second_message, NULL); + + formatted_time = get_formatted_time (remaining_time); + details = g_strdup_printf (concat_detail, + transfer_info->num_files + 1, + source_info->num_files, + formatted_time, + (int) transfer_rate + 0.5); + + g_free (concat_detail); + } + else + { + /* To translators: %'d is the number of files completed for the operation, + * so it will be something like 2/14. */ + details = g_strdup_printf (_("%'d / %'d"), + transfer_info->num_files, + source_info->num_files); + } + } + nautilus_progress_info_set_details (job->progress, details); + + if (elapsed > SECONDS_NEEDED_FOR_APROXIMATE_TRANSFER_RATE) + { + nautilus_progress_info_set_remaining_time (job->progress, + remaining_time); + nautilus_progress_info_set_elapsed_time (job->progress, + elapsed); + } + + if (source_info->num_files != 0) + { + nautilus_progress_info_set_progress (job->progress, transfer_info->num_files, source_info->num_files); + } +} +#pragma GCC diagnostic pop + +static void +trash_file (CommonJob *job, + GFile *file, + gboolean *skipped_file, + SourceInfo *source_info, + TransferInfo *transfer_info, + gboolean toplevel, + GList **to_delete) +{ + GError *error; + char *primary, *secondary, *details; + int response; + g_autofree gchar *basename = NULL; + + if (should_skip_file (job, file)) + { + *skipped_file = TRUE; + return; + } + + error = NULL; + + if (g_file_trash (file, job->cancellable, &error)) + { + transfer_info->num_files++; + nautilus_file_changes_queue_file_removed (file); + + if (job->undo_info != NULL) + { + nautilus_file_undo_info_trash_add_file (NAUTILUS_FILE_UNDO_INFO_TRASH (job->undo_info), file); + } + + report_trash_progress (job, source_info, transfer_info); + return; + } + + if (job->skip_all_error) + { + *skipped_file = TRUE; + goto skip; + } + + if (job->delete_all) + { + *to_delete = g_list_prepend (*to_delete, file); + goto skip; + } + + basename = get_basename (file); + /* Translators: %s is a file name */ + primary = g_strdup_printf (_("“%s” can’t be put in the trash. Do you want " + "to delete it immediately?"), + basename); + + details = NULL; + secondary = NULL; + if (!IS_IO_ERROR (error, NOT_SUPPORTED)) + { + details = error->message; + } + else if (!g_file_is_native (file)) + { + secondary = g_strdup (_("This remote location does not support sending items to the trash.")); + } + + response = run_question (job, + primary, + secondary, + details, + (source_info->num_files - transfer_info->num_files) > 1, + CANCEL, SKIP_ALL, SKIP, DELETE_ALL, DELETE, + NULL); + + if (response == 0 || response == GTK_RESPONSE_DELETE_EVENT) + { + ((DeleteJob *) job)->user_cancel = TRUE; + abort_job (job); + } + else if (response == 1) /* skip all */ + { + *skipped_file = TRUE; + job->skip_all_error = TRUE; + } + else if (response == 2) /* skip */ + { + *skipped_file = TRUE; + job->skip_all_error = TRUE; + } + else if (response == 3) /* delete all */ + { + *to_delete = g_list_prepend (*to_delete, file); + job->delete_all = TRUE; + } + else if (response == 4) /* delete */ + { + *to_delete = g_list_prepend (*to_delete, file); + } + +skip: + g_error_free (error); +} + +static void +source_info_remove_descendent_files_from_count (GFile *dir, + SourceDirInfo *dir_info, + SourceInfo *source_info) +{ + GFile *other_dir; + SourceDirInfo *other_dir_info; + GHashTableIter dir_info_iter; + + source_info->num_files -= dir_info->num_files_children; + source_info->num_bytes -= dir_info->num_bytes_children; + + g_hash_table_iter_init (&dir_info_iter, source_info->scanned_dirs_info); + while (g_hash_table_iter_next (&dir_info_iter, (gpointer *) &other_dir, (gpointer *) &other_dir_info)) + { + g_assert (other_dir != NULL); + g_assert (other_dir_info != NULL); + + if (other_dir_info != dir_info && + g_file_has_parent (other_dir, dir)) + { + source_info_remove_descendent_files_from_count (other_dir, + other_dir_info, + source_info); + } + } +} + +static void +source_info_remove_file_from_count (GFile *file, + CommonJob *job, + SourceInfo *source_info) +{ + g_autoptr (GFileInfo) file_info = NULL; + SourceDirInfo *dir_info; + + if (g_cancellable_is_cancelled (job->cancellable)) + { + return; + } + + file_info = g_file_query_info (file, + G_FILE_ATTRIBUTE_STANDARD_SIZE, + G_FILE_QUERY_INFO_NOFOLLOW_SYMLINKS, + job->cancellable, + NULL); + + source_info->num_files--; + if (file_info != NULL) + { + source_info->num_bytes -= g_file_info_get_size (file_info); + } + + dir_info = g_hash_table_lookup (source_info->scanned_dirs_info, file); + + if (dir_info != NULL) + { + source_info_remove_descendent_files_from_count (file, + dir_info, + source_info); + } +} + +static void +trash_files (CommonJob *job, + GList *files, + int *files_skipped) +{ + GList *l; + GFile *file; + GList *to_delete; + g_auto (SourceInfo) source_info = SOURCE_INFO_INIT; + TransferInfo transfer_info; + gboolean skipped_file; + + if (job_aborted (job)) + { + return; + } + + scan_sources (files, + &source_info, + job, + OP_KIND_TRASH); + if (job_aborted (job)) + { + return; + } + + g_timer_start (job->time); + + memset (&transfer_info, 0, sizeof (transfer_info)); + report_trash_progress (job, &source_info, &transfer_info); + + to_delete = NULL; + for (l = files; + l != NULL && !job_aborted (job); + l = l->next) + { + file = l->data; + + skipped_file = FALSE; + trash_file (job, file, + &skipped_file, + &source_info, &transfer_info, + TRUE, &to_delete); + if (skipped_file) + { + (*files_skipped)++; + source_info_remove_file_from_count (file, job, &source_info); + report_trash_progress (job, &source_info, &transfer_info); + } + } + + if (to_delete) + { + to_delete = g_list_reverse (to_delete); + delete_files (job, to_delete, files_skipped); + g_list_free (to_delete); + } +} + +static void +delete_task_done (GObject *source_object, + GAsyncResult *res, + gpointer user_data) +{ + DeleteJob *job; + GHashTable *debuting_uris; + + job = user_data; + + g_list_free_full (job->files, g_object_unref); + + if (job->done_callback) + { + debuting_uris = g_hash_table_new_full (g_file_hash, (GEqualFunc) g_file_equal, g_object_unref, NULL); + job->done_callback (debuting_uris, job->user_cancel, job->done_callback_data); + g_hash_table_unref (debuting_uris); + } + + finalize_common ((CommonJob *) job); + + nautilus_file_changes_consume_changes (TRUE); +} + +static void +trash_or_delete_internal (GTask *task, + gpointer source_object, + gpointer task_data, + GCancellable *cancellable) +{ + DeleteJob *job = task_data; + g_autoptr (GList) to_trash_files = NULL; + g_autoptr (GList) to_delete_files = NULL; + GList *l; + GFile *file; + gboolean confirmed; + CommonJob *common; + gboolean must_confirm_delete_in_trash; + gboolean must_confirm_delete; + int files_skipped; + + common = (CommonJob *) job; + + nautilus_progress_info_start (job->common.progress); + + must_confirm_delete_in_trash = FALSE; + must_confirm_delete = FALSE; + files_skipped = 0; + + for (l = job->files; l != NULL; l = l->next) + { + file = l->data; + + if (job->try_trash && + g_file_has_uri_scheme (file, "trash")) + { + must_confirm_delete_in_trash = TRUE; + to_delete_files = g_list_prepend (to_delete_files, file); + } + else if (can_delete_without_confirm (file)) + { + to_delete_files = g_list_prepend (to_delete_files, file); + } + else + { + if (job->try_trash) + { + to_trash_files = g_list_prepend (to_trash_files, file); + } + else + { + must_confirm_delete = TRUE; + to_delete_files = g_list_prepend (to_delete_files, file); + } + } + } + + if (to_delete_files != NULL) + { + to_delete_files = g_list_reverse (to_delete_files); + confirmed = TRUE; + if (must_confirm_delete_in_trash) + { + confirmed = confirm_delete_from_trash (common, to_delete_files); + } + else if (must_confirm_delete) + { + confirmed = confirm_delete_directly (common, to_delete_files); + } + if (confirmed) + { + delete_files (common, to_delete_files, &files_skipped); + } + else + { + job->user_cancel = TRUE; + } + } + + if (to_trash_files != NULL) + { + to_trash_files = g_list_reverse (to_trash_files); + + trash_files (common, to_trash_files, &files_skipped); + } + + if (files_skipped == g_list_length (job->files)) + { + /* User has skipped all files, report user cancel */ + job->user_cancel = TRUE; + } +} + +static DeleteJob * +setup_delete_job (GList *files, + GtkWindow *parent_window, + NautilusFileOperationsDBusData *dbus_data, + gboolean try_trash, + NautilusDeleteCallback done_callback, + gpointer done_callback_data) +{ + DeleteJob *job; + + /* TODO: special case desktop icon link files ... */ + job = op_job_new (DeleteJob, parent_window, dbus_data); + job->files = g_list_copy_deep (files, (GCopyFunc) g_object_ref, NULL); + job->try_trash = try_trash; + job->user_cancel = FALSE; + job->done_callback = done_callback; + job->done_callback_data = done_callback_data; + + if (g_strcmp0 (g_getenv ("RUNNING_TESTS"), "TRUE")) + { + if (try_trash) + { + inhibit_power_manager ((CommonJob *) job, _("Trashing Files")); + } + else + { + inhibit_power_manager ((CommonJob *) job, _("Deleting Files")); + } + } + + if (!nautilus_file_undo_manager_is_operating () && try_trash) + { + job->common.undo_info = nautilus_file_undo_info_trash_new (g_list_length (files)); + } + + return job; +} + +static void +trash_or_delete_internal_sync (GList *files, + GtkWindow *parent_window, + NautilusFileOperationsDBusData *dbus_data, + gboolean try_trash) +{ + GTask *task; + DeleteJob *job; + + job = setup_delete_job (files, + parent_window, + dbus_data, + try_trash, + NULL, + NULL); + + task = g_task_new (NULL, NULL, NULL, job); + g_task_set_task_data (task, job, NULL); + g_task_run_in_thread_sync (task, trash_or_delete_internal); + g_object_unref (task); + /* Since g_task_run_in_thread_sync doesn't work with callbacks (in this case not reaching + * delete_task_done) we need to set up the undo information ourselves. + */ + delete_task_done (NULL, NULL, job); +} + +static void +trash_or_delete_internal_async (GList *files, + GtkWindow *parent_window, + NautilusFileOperationsDBusData *dbus_data, + gboolean try_trash, + NautilusDeleteCallback done_callback, + gpointer done_callback_data) +{ + GTask *task; + DeleteJob *job; + + job = setup_delete_job (files, + parent_window, + dbus_data, + try_trash, + done_callback, + done_callback_data); + + task = g_task_new (NULL, NULL, delete_task_done, job); + g_task_set_task_data (task, job, NULL); + g_task_run_in_thread (task, trash_or_delete_internal); + g_object_unref (task); +} + +void +nautilus_file_operations_trash_or_delete_sync (GList *files) +{ + trash_or_delete_internal_sync (files, NULL, NULL, TRUE); +} + +void +nautilus_file_operations_delete_sync (GList *files) +{ + trash_or_delete_internal_sync (files, NULL, NULL, FALSE); +} + +void +nautilus_file_operations_trash_or_delete_async (GList *files, + GtkWindow *parent_window, + NautilusFileOperationsDBusData *dbus_data, + NautilusDeleteCallback done_callback, + gpointer done_callback_data) +{ + trash_or_delete_internal_async (files, parent_window, + dbus_data, + TRUE, + done_callback, done_callback_data); +} + +void +nautilus_file_operations_delete_async (GList *files, + GtkWindow *parent_window, + NautilusFileOperationsDBusData *dbus_data, + NautilusDeleteCallback done_callback, + gpointer done_callback_data) +{ + trash_or_delete_internal_async (files, parent_window, + dbus_data, + FALSE, + done_callback, done_callback_data); +} + + + +typedef struct +{ + gboolean eject; + GMount *mount; + GMountOperation *mount_operation; + GtkWindow *parent_window; + NautilusUnmountCallback callback; + gpointer callback_data; +} UnmountData; + +static void +unmount_data_free (UnmountData *data) +{ + if (data->parent_window) + { + g_object_remove_weak_pointer (G_OBJECT (data->parent_window), + (gpointer *) &data->parent_window); + } + + g_clear_object (&data->mount_operation); + g_object_unref (data->mount); + g_free (data); +} + +static void +unmount_mount_callback (GObject *source_object, + GAsyncResult *res, + gpointer user_data) +{ + UnmountData *data = user_data; + GError *error; + char *primary; + gboolean unmounted; + + error = NULL; + if (data->eject) + { + unmounted = g_mount_eject_with_operation_finish (G_MOUNT (source_object), + res, &error); + } + else + { + unmounted = g_mount_unmount_with_operation_finish (G_MOUNT (source_object), + res, &error); + } + + if (!unmounted) + { + if (error->code != G_IO_ERROR_FAILED_HANDLED) + { + g_autofree gchar *mount_name = NULL; + + mount_name = g_mount_get_name (G_MOUNT (source_object)); + if (data->eject) + { + primary = g_strdup_printf (_("Unable to eject %s"), + mount_name); + } + else + { + primary = g_strdup_printf (_("Unable to unmount %s"), + mount_name); + } + show_dialog (primary, + error->message, + data->parent_window, + GTK_MESSAGE_ERROR); + g_free (primary); + } + } + + if (data->callback) + { + data->callback (data->callback_data); + } + + if (error != NULL) + { + g_error_free (error); + } + + unmount_data_free (data); +} + +static void +do_unmount (UnmountData *data) +{ + GMountOperation *mount_op; + + if (data->mount_operation) + { + mount_op = g_object_ref (data->mount_operation); + } + else + { + mount_op = gtk_mount_operation_new (data->parent_window); + } + + g_signal_connect (mount_op, "show-unmount-progress", + G_CALLBACK (show_unmount_progress_cb), NULL); + g_signal_connect (mount_op, "aborted", + G_CALLBACK (show_unmount_progress_aborted_cb), NULL); + + if (data->eject) + { + g_mount_eject_with_operation (data->mount, + 0, + mount_op, + NULL, + unmount_mount_callback, + data); + } + else + { + g_mount_unmount_with_operation (data->mount, + 0, + mount_op, + NULL, + unmount_mount_callback, + data); + } + g_object_unref (mount_op); +} + +static gboolean +dir_has_files (GFile *dir) +{ + GFileEnumerator *enumerator; + gboolean res; + GFileInfo *file_info; + + res = FALSE; + + enumerator = g_file_enumerate_children (dir, + G_FILE_ATTRIBUTE_STANDARD_NAME, + 0, + NULL, NULL); + if (enumerator) + { + file_info = g_file_enumerator_next_file (enumerator, NULL, NULL); + if (file_info != NULL) + { + res = TRUE; + g_object_unref (file_info); + } + + g_file_enumerator_close (enumerator, NULL, NULL); + g_object_unref (enumerator); + } + + + return res; +} + +static GList * +get_trash_dirs_for_mount (GMount *mount) +{ + GFile *root; + GFile *trash; + char *relpath; + GList *list; + + root = g_mount_get_root (mount); + if (root == NULL) + { + return NULL; + } + + list = NULL; + + if (g_file_is_native (root)) + { + relpath = g_strdup_printf (".Trash/%d", getuid ()); + trash = g_file_resolve_relative_path (root, relpath); + g_free (relpath); + + list = g_list_prepend (list, g_file_get_child (trash, "files")); + list = g_list_prepend (list, g_file_get_child (trash, "info")); + + g_object_unref (trash); + + relpath = g_strdup_printf (".Trash-%d", getuid ()); + trash = g_file_get_child (root, relpath); + g_free (relpath); + + list = g_list_prepend (list, g_file_get_child (trash, "files")); + list = g_list_prepend (list, g_file_get_child (trash, "info")); + + g_object_unref (trash); + } + + g_object_unref (root); + + return list; +} + +static gboolean +has_trash_files (GMount *mount) +{ + GList *dirs, *l; + GFile *dir; + gboolean res; + + dirs = get_trash_dirs_for_mount (mount); + + res = FALSE; + + for (l = dirs; l != NULL; l = l->next) + { + dir = l->data; + + if (dir_has_files (dir)) + { + res = TRUE; + break; + } + } + + g_list_free_full (dirs, g_object_unref); + + return res; +} + +static GtkWidget * +create_empty_trash_prompt (GtkWindow *parent_window) +{ + GtkWidget *dialog; + + dialog = adw_message_dialog_new (parent_window, + _("Do you want to empty the trash before you unmount?"), + _("In order to regain the free space on this volume " + "the trash must be emptied. All trashed items on the volume " + "will be permanently lost.")); + adw_message_dialog_add_responses (ADW_MESSAGE_DIALOG (dialog), + "do-not-empty", _("Do _not Empty Trash"), + "cancel", _("Cancel"), + "empty-trash", _("Empty _Trash"), + NULL); + adw_message_dialog_set_default_response (ADW_MESSAGE_DIALOG (dialog), "empty-trash"); + adw_message_dialog_set_close_response (ADW_MESSAGE_DIALOG (dialog), "cancel"); + adw_message_dialog_set_response_appearance (ADW_MESSAGE_DIALOG (dialog), + "empty-trash", ADW_RESPONSE_DESTRUCTIVE); + + return dialog; +} + +static void +empty_trash_for_unmount_done (gboolean success, + gpointer user_data) +{ + UnmountData *data = user_data; + do_unmount (data); +} + +static void +empty_trash_prompt_cb (GtkDialog *dialog, + char *response, + gpointer user_data) +{ + UnmountData *data = user_data; + + if (g_strcmp0 (response, "empty-trash") == 0) + { + GTask *task; + EmptyTrashJob *job; + + job = op_job_new (EmptyTrashJob, data->parent_window, NULL); + job->should_confirm = FALSE; + job->trash_dirs = get_trash_dirs_for_mount (data->mount); + job->done_callback = empty_trash_for_unmount_done; + job->done_callback_data = data; + + task = g_task_new (NULL, NULL, empty_trash_task_done, job); + g_task_set_task_data (task, job, NULL); + g_task_run_in_thread (task, empty_trash_thread_func); + g_object_unref (task); + } + else if (g_strcmp0 (response, "cancel") == 0) + { + if (data->callback) + { + data->callback (data->callback_data); + } + + unmount_data_free (data); + } + else if (g_strcmp0 (response, "do-not-empty") == 0) + { + do_unmount (data); + } +} + +void +nautilus_file_operations_unmount_mount_full (GtkWindow *parent_window, + GMount *mount, + GMountOperation *mount_operation, + gboolean eject, + gboolean check_trash, + NautilusUnmountCallback callback, + gpointer callback_data) +{ + UnmountData *data; + + data = g_new0 (UnmountData, 1); + data->callback = callback; + data->callback_data = callback_data; + if (parent_window) + { + data->parent_window = parent_window; + g_object_add_weak_pointer (G_OBJECT (data->parent_window), + (gpointer *) &data->parent_window); + } + if (mount_operation) + { + data->mount_operation = g_object_ref (mount_operation); + } + data->eject = eject; + data->mount = g_object_ref (mount); + + if (check_trash && has_trash_files (mount)) + { + GtkWidget *dialog; + dialog = create_empty_trash_prompt (parent_window); + + g_signal_connect (dialog, "response", G_CALLBACK (empty_trash_prompt_cb), data); + gtk_widget_show (dialog); + return; + } + + do_unmount (data); +} + +void +nautilus_file_operations_unmount_mount (GtkWindow *parent_window, + GMount *mount, + gboolean eject, + gboolean check_trash) +{ + nautilus_file_operations_unmount_mount_full (parent_window, mount, NULL, eject, + check_trash, NULL, NULL); +} + +static void +mount_callback_data_notify (gpointer data, + GObject *object) +{ + GMountOperation *mount_op; + + mount_op = G_MOUNT_OPERATION (data); + g_object_set_data (G_OBJECT (mount_op), "mount-callback", NULL); + g_object_set_data (G_OBJECT (mount_op), "mount-callback-data", NULL); +} + +static void +volume_mount_cb (GObject *source_object, + GAsyncResult *res, + gpointer user_data) +{ + NautilusMountCallback mount_callback; + GObject *mount_callback_data_object; + GMountOperation *mount_op = user_data; + GError *error; + char *primary; + char *name; + gboolean success; + + success = TRUE; + error = NULL; + if (!g_volume_mount_finish (G_VOLUME (source_object), res, &error)) + { + if (error->code != G_IO_ERROR_FAILED_HANDLED && + error->code != G_IO_ERROR_ALREADY_MOUNTED) + { + GtkWindow *parent; + + parent = gtk_mount_operation_get_parent (GTK_MOUNT_OPERATION (mount_op)); + name = g_volume_get_name (G_VOLUME (source_object)); + primary = g_strdup_printf (_("Unable to access “%s”"), name); + g_free (name); + success = FALSE; + show_dialog (primary, + error->message, + parent, + GTK_MESSAGE_ERROR); + g_free (primary); + } + g_error_free (error); + } + + mount_callback = (NautilusMountCallback) + g_object_get_data (G_OBJECT (mount_op), "mount-callback"); + mount_callback_data_object = + g_object_get_data (G_OBJECT (mount_op), "mount-callback-data"); + + if (mount_callback != NULL) + { + (*mount_callback)(G_VOLUME (source_object), + success, + mount_callback_data_object); + + if (mount_callback_data_object != NULL) + { + g_object_weak_unref (mount_callback_data_object, + mount_callback_data_notify, + mount_op); + } + } + + g_object_unref (mount_op); +} + + +void +nautilus_file_operations_mount_volume (GtkWindow *parent_window, + GVolume *volume) +{ + nautilus_file_operations_mount_volume_full (parent_window, volume, + NULL, NULL); +} + +void +nautilus_file_operations_mount_volume_full (GtkWindow *parent_window, + GVolume *volume, + NautilusMountCallback mount_callback, + GObject *mount_callback_data_object) +{ + GMountOperation *mount_op; + + mount_op = gtk_mount_operation_new (parent_window); + g_mount_operation_set_password_save (mount_op, G_PASSWORD_SAVE_FOR_SESSION); + g_object_set_data (G_OBJECT (mount_op), + "mount-callback", + mount_callback); + + if (mount_callback != NULL && + mount_callback_data_object != NULL) + { + g_object_weak_ref (mount_callback_data_object, + mount_callback_data_notify, + mount_op); + } + g_object_set_data (G_OBJECT (mount_op), + "mount-callback-data", + mount_callback_data_object); + + g_volume_mount (volume, 0, mount_op, NULL, volume_mount_cb, mount_op); +} + +static void +report_preparing_count_progress (CommonJob *job, + SourceInfo *source_info) +{ + char *s; + + switch (source_info->op) + { + default: + case OP_KIND_COPY: + { + g_autofree gchar *formatted_size = NULL; + + formatted_size = g_format_size (source_info->num_bytes); + s = g_strdup_printf (ngettext ("Preparing to copy %'d file (%s)", + "Preparing to copy %'d files (%s)", + source_info->num_files), + source_info->num_files, + formatted_size); + } + break; + + case OP_KIND_MOVE: + { + g_autofree gchar *formatted_size = NULL; + + formatted_size = g_format_size (source_info->num_bytes); + s = g_strdup_printf (ngettext ("Preparing to move %'d file (%s)", + "Preparing to move %'d files (%s)", + source_info->num_files), + source_info->num_files, + formatted_size); + } + break; + + case OP_KIND_DELETE: + { + g_autofree gchar *formatted_size = NULL; + + formatted_size = g_format_size (source_info->num_bytes); + s = g_strdup_printf (ngettext ("Preparing to delete %'d file (%s)", + "Preparing to delete %'d files (%s)", + source_info->num_files), + source_info->num_files, + formatted_size); + } + break; + + case OP_KIND_TRASH: + { + s = g_strdup_printf (ngettext ("Preparing to trash %'d file", + "Preparing to trash %'d files", + source_info->num_files), + source_info->num_files); + } + break; + + case OP_KIND_COMPRESS: + { + s = g_strdup_printf (ngettext ("Preparing to compress %'d file", + "Preparing to compress %'d files", + source_info->num_files), + source_info->num_files); + } + } + + nautilus_progress_info_take_details (job->progress, s); + nautilus_progress_info_pulse_progress (job->progress); +} + +static void +count_file (GFileInfo *info, + CommonJob *job, + SourceInfo *source_info, + SourceDirInfo *dir_info) +{ + goffset num_bytes = g_file_info_get_size (info); + + source_info->num_files += 1; + source_info->num_bytes += num_bytes; + + if (dir_info != NULL) + { + dir_info->num_files_children += 1; + dir_info->num_bytes_children += num_bytes; + } + + if (source_info->num_files_since_progress++ > 100) + { + report_preparing_count_progress (job, source_info); + source_info->num_files_since_progress = 0; + } +} + +static char * +get_scan_primary (OpKind kind) +{ + switch (kind) + { + default: + case OP_KIND_COPY: + { + return g_strdup (_("Error while copying.")); + } + + case OP_KIND_MOVE: + { + return g_strdup (_("Error while moving.")); + } + + case OP_KIND_DELETE: + { + return g_strdup (_("Error while deleting.")); + } + + case OP_KIND_TRASH: + { + return g_strdup (_("Error while moving files to trash.")); + } + + case OP_KIND_COMPRESS: + { + return g_strdup (_("Error while compressing files.")); + } + } +} + +static void +scan_dir (GFile *dir, + SourceInfo *source_info, + CommonJob *job, + GQueue *dirs) +{ + GFileInfo *info; + GError *error; + GFile *subdir; + GFileEnumerator *enumerator; + char *primary, *secondary, *details; + int response; + SourceInfo saved_info; + g_autolist (GFile) subdirs = NULL; + SourceDirInfo *dir_info = NULL; + gboolean skip_subdirs = FALSE; + + /* It is possible for this function to be called multiple times for + * the same directory. + * We pass a NULL SourceDirInfo into count_file() if this directory has + * already been scanned once so that its children are not counted more + * than once in the SourceDirInfo corresponding to this directory. + */ + + if (!g_hash_table_contains (source_info->scanned_dirs_info, dir)) + { + dir_info = g_new0 (SourceDirInfo, 1); + + g_hash_table_insert (source_info->scanned_dirs_info, + g_object_ref (dir), + dir_info); + } + + /* Stash a copy of the struct to restore state before goto retry. Note that + * this assumes the code below does not access any pointer member */ + saved_info = *source_info; + +retry: + + if (dir_info != NULL) + { + dir_info->num_files_children = 0; + dir_info->num_bytes_children = 0; + } + + error = NULL; + enumerator = g_file_enumerate_children (dir, + G_FILE_ATTRIBUTE_STANDARD_NAME "," + G_FILE_ATTRIBUTE_STANDARD_TYPE "," + G_FILE_ATTRIBUTE_STANDARD_SIZE, + G_FILE_QUERY_INFO_NOFOLLOW_SYMLINKS, + job->cancellable, + &error); + if (enumerator) + { + error = NULL; + while ((info = g_file_enumerator_next_file (enumerator, job->cancellable, &error)) != NULL) + { + count_file (info, job, source_info, dir_info); + + if (g_file_info_get_file_type (info) == G_FILE_TYPE_DIRECTORY) + { + subdir = g_file_get_child (dir, + g_file_info_get_name (info)); + + subdirs = g_list_prepend (subdirs, subdir); + } + + g_object_unref (info); + } + g_file_enumerator_close (enumerator, job->cancellable, NULL); + g_object_unref (enumerator); + + if (error && IS_IO_ERROR (error, CANCELLED)) + { + g_error_free (error); + } + else if (error) + { + g_autofree gchar *basename = NULL; + + primary = get_scan_primary (source_info->op); + details = NULL; + basename = get_basename (dir); + + if (IS_IO_ERROR (error, PERMISSION_DENIED)) + { + secondary = g_strdup_printf (_("Files in the folder “%s” cannot be handled " + "because you do not have permissions to see them."), + basename); + } + else + { + secondary = g_strdup_printf (_("There was an error getting information about the " + "files in the folder “%s”."), basename); + details = error->message; + } + + response = run_warning (job, + primary, + secondary, + details, + FALSE, + CANCEL, RETRY, SKIP, + NULL); + + g_error_free (error); + + if (response == 0 || response == GTK_RESPONSE_DELETE_EVENT) + { + abort_job (job); + skip_subdirs = TRUE; + } + else if (response == 1) + { + g_clear_list (&subdirs, g_object_unref); + *source_info = saved_info; + goto retry; + } + else if (response == 2) + { + skip_readdir_error (job, dir); + } + else + { + g_assert_not_reached (); + } + } + } + else if (job->skip_all_error) + { + g_error_free (error); + skip_file (job, dir); + skip_subdirs = TRUE; + } + else if (IS_IO_ERROR (error, CANCELLED)) + { + g_error_free (error); + } + else + { + g_autofree gchar *basename = NULL; + + primary = get_scan_primary (source_info->op); + details = NULL; + basename = get_basename (dir); + if (IS_IO_ERROR (error, PERMISSION_DENIED)) + { + secondary = g_strdup_printf (_("The folder “%s” cannot be handled because you " + "do not have permissions to read it."), + basename); + } + else + { + secondary = g_strdup_printf (_("There was an error reading the folder “%s”."), + basename); + details = error->message; + } + /* set show_all to TRUE here, as we don't know how many + * files we'll end up processing yet. + */ + response = run_warning (job, + primary, + secondary, + details, + TRUE, + CANCEL, SKIP_ALL, SKIP, RETRY, + NULL); + + g_error_free (error); + + if (response == 0 || response == GTK_RESPONSE_DELETE_EVENT) + { + abort_job (job); + skip_subdirs = TRUE; + } + else if (response == 1 || response == 2) + { + if (response == 1) + { + job->skip_all_error = TRUE; + } + skip_file (job, dir); + skip_subdirs = TRUE; + } + else if (response == 3) + { + goto retry; + } + else + { + g_assert_not_reached (); + } + } + + if (!skip_subdirs) + { + while (subdirs != NULL) + { + GList *l = subdirs; + subdirs = g_list_remove_link (subdirs, l); + + /* Push to head, since we want depth-first */ + g_queue_push_head_link (dirs, l); + } + } +} + +static void +scan_file (GFile *file, + SourceInfo *source_info, + CommonJob *job) +{ + GFileInfo *info; + GError *error; + GQueue *dirs; + GFile *dir; + char *primary; + char *secondary; + char *details; + int response; + + dirs = g_queue_new (); + +retry: + error = NULL; + info = g_file_query_info (file, + G_FILE_ATTRIBUTE_STANDARD_TYPE "," + G_FILE_ATTRIBUTE_STANDARD_SIZE, + G_FILE_QUERY_INFO_NOFOLLOW_SYMLINKS, + job->cancellable, + &error); + + if (info) + { + count_file (info, job, source_info, NULL); + + /* trashing operation doesn't recurse */ + if (g_file_info_get_file_type (info) == G_FILE_TYPE_DIRECTORY && + source_info->op != OP_KIND_TRASH) + { + g_queue_push_head (dirs, g_object_ref (file)); + } + g_object_unref (info); + } + else if (job->skip_all_error) + { + g_error_free (error); + skip_file (job, file); + } + else if (IS_IO_ERROR (error, CANCELLED)) + { + g_error_free (error); + } + else + { + g_autofree gchar *basename = NULL; + + primary = get_scan_primary (source_info->op); + details = NULL; + basename = get_basename (file); + + if (IS_IO_ERROR (error, PERMISSION_DENIED)) + { + secondary = g_strdup_printf (_("The file “%s” cannot be handled because you do not have " + "permissions to read it."), basename); + } + else + { + secondary = g_strdup_printf (_("There was an error getting information about “%s”."), + basename); + details = error->message; + } + /* set show_all to TRUE here, as we don't know how many + * files we'll end up processing yet. + */ + response = run_warning (job, + primary, + secondary, + details, + TRUE, + CANCEL, SKIP_ALL, SKIP, RETRY, + NULL); + + g_error_free (error); + + if (response == 0 || response == GTK_RESPONSE_DELETE_EVENT) + { + abort_job (job); + } + else if (response == 1 || response == 2) + { + if (response == 1) + { + job->skip_all_error = TRUE; + } + skip_file (job, file); + } + else if (response == 3) + { + goto retry; + } + else + { + g_assert_not_reached (); + } + } + + while (!job_aborted (job) && + (dir = g_queue_pop_head (dirs)) != NULL) + { + scan_dir (dir, source_info, job, dirs); + g_object_unref (dir); + } + + /* Free all from queue if we exited early */ + g_queue_foreach (dirs, (GFunc) g_object_unref, NULL); + g_queue_free (dirs); +} + +static void +scan_sources (GList *files, + SourceInfo *source_info, + CommonJob *job, + OpKind kind) +{ + GList *l; + GFile *file; + + source_info->op = kind; + source_info->scanned_dirs_info = g_hash_table_new_full (g_file_hash, + (GEqualFunc) g_file_equal, + (GDestroyNotify) g_object_unref, + (GDestroyNotify) g_free); + + report_preparing_count_progress (job, source_info); + + for (l = files; l != NULL && !job_aborted (job); l = l->next) + { + file = l->data; + + scan_file (file, + source_info, + job); + } + + /* Make sure we report the final count */ + report_preparing_count_progress (job, source_info); +} + +static void +verify_destination (CommonJob *job, + GFile *dest, + char **dest_fs_id, + goffset required_size) +{ + GFileInfo *info, *fsinfo; + GError *error; + const char *fs_type; + guint64 free_size; + guint64 size_difference; + char *primary, *secondary, *details; + int response; + GFileType file_type; + gboolean dest_is_symlink = FALSE; + + if (dest_fs_id) + { + *dest_fs_id = NULL; + } + +retry: + + error = NULL; + info = g_file_query_info (dest, + G_FILE_ATTRIBUTE_STANDARD_TYPE "," + G_FILE_ATTRIBUTE_ID_FILESYSTEM, + dest_is_symlink ? G_FILE_QUERY_INFO_NONE : G_FILE_QUERY_INFO_NOFOLLOW_SYMLINKS, + job->cancellable, + &error); + + if (info == NULL) + { + g_autofree gchar *basename = NULL; + + if (IS_IO_ERROR (error, CANCELLED)) + { + g_error_free (error); + return; + } + + basename = get_basename (dest); + primary = g_strdup_printf (_("Error while copying to “%s”."), basename); + details = NULL; + + if (IS_IO_ERROR (error, PERMISSION_DENIED)) + { + secondary = g_strdup (_("You do not have permissions to access the destination folder.")); + } + else + { + secondary = g_strdup (_("There was an error getting information about the destination.")); + details = error->message; + } + + response = run_error (job, + primary, + secondary, + details, + FALSE, + CANCEL, RETRY, + NULL); + + g_error_free (error); + + if (response == 0 || response == GTK_RESPONSE_DELETE_EVENT) + { + abort_job (job); + } + else if (response == 1) + { + goto retry; + } + else + { + g_assert_not_reached (); + } + + return; + } + + file_type = g_file_info_get_file_type (info); + if (!dest_is_symlink && file_type == G_FILE_TYPE_SYMBOLIC_LINK) + { + /* Record that destination is a symlink and do real stat() once again */ + dest_is_symlink = TRUE; + g_object_unref (info); + goto retry; + } + + if (dest_fs_id) + { + *dest_fs_id = + g_strdup (g_file_info_get_attribute_string (info, + G_FILE_ATTRIBUTE_ID_FILESYSTEM)); + } + + g_object_unref (info); + + if (file_type != G_FILE_TYPE_DIRECTORY) + { + g_autofree gchar *basename = NULL; + + basename = get_basename (dest); + primary = g_strdup_printf (_("Error while copying to “%s”."), basename); + secondary = g_strdup (_("The destination is not a folder.")); + + run_error (job, + primary, + secondary, + NULL, + FALSE, + CANCEL, + NULL); + + abort_job (job); + return; + } + + if (dest_is_symlink) + { + /* We can't reliably statfs() destination if it's a symlink, thus not doing any further checks. */ + return; + } + + fsinfo = g_file_query_filesystem_info (dest, + G_FILE_ATTRIBUTE_FILESYSTEM_FREE "," + G_FILE_ATTRIBUTE_FILESYSTEM_READONLY "," + G_FILE_ATTRIBUTE_FILESYSTEM_TYPE, + job->cancellable, + NULL); + + if (fsinfo == NULL) + { + /* All sorts of things can go wrong getting the fs info (like not supported) + * only check these things if the fs returns them + */ + return; + } + + /* ramfs reports a free size, but that size is always 0. If we're copying to ramfs, + * skip the free size check. */ + fs_type = g_file_info_get_attribute_string (fsinfo, + G_FILE_ATTRIBUTE_FILESYSTEM_TYPE); + + if (required_size > 0 && + g_strcmp0 (fs_type, "ramfs") != 0 && + g_file_info_has_attribute (fsinfo, G_FILE_ATTRIBUTE_FILESYSTEM_FREE)) + { + free_size = g_file_info_get_attribute_uint64 (fsinfo, + G_FILE_ATTRIBUTE_FILESYSTEM_FREE); + + if (free_size < required_size) + { + g_autofree gchar *basename = NULL; + g_autofree gchar *formatted_size = NULL; + + basename = get_basename (dest); + size_difference = required_size - free_size; + primary = g_strdup_printf (_("Error while copying to “%s”."), basename); + secondary = g_strdup (_("There is not enough space on the destination." + " Try to remove files to make space.")); + + formatted_size = g_format_size (size_difference); + details = g_strdup_printf (_("%s more space is required to copy to the destination."), + formatted_size); + + response = run_warning (job, + primary, + secondary, + details, + FALSE, + CANCEL, + COPY_FORCE, + RETRY, + NULL); + + if (response == 0 || response == GTK_RESPONSE_DELETE_EVENT) + { + abort_job (job); + } + else if (response == 2) + { + goto retry; + } + else if (response == 1) + { + /* We are forced to copy - just fall through ... */ + } + else + { + g_assert_not_reached (); + } + } + } + + if (!job_aborted (job) && + g_file_info_get_attribute_boolean (fsinfo, + G_FILE_ATTRIBUTE_FILESYSTEM_READONLY)) + { + g_autofree gchar *basename = NULL; + + basename = get_basename (dest); + primary = g_strdup_printf (_("Error while copying to “%s”."), basename); + secondary = g_strdup (_("The destination is read-only.")); + + run_error (job, + primary, + secondary, + NULL, + FALSE, + CANCEL, + NULL); + + g_error_free (error); + + abort_job (job); + } + + g_object_unref (fsinfo); +} + +#pragma GCC diagnostic push +#pragma GCC diagnostic ignored "-Wformat-nonliteral" +static void +report_copy_progress (CopyMoveJob *copy_job, + SourceInfo *source_info, + TransferInfo *transfer_info) +{ + int files_left; + goffset total_size; + double elapsed, transfer_rate; + int remaining_time; + guint64 now; + CommonJob *job; + gboolean is_move; + gchar *status; + char *details; + gchar *tmp; + + job = (CommonJob *) copy_job; + + is_move = copy_job->is_move; + + now = g_get_monotonic_time (); + + files_left = source_info->num_files - transfer_info->num_files; + + /* Races and whatnot could cause this to be negative... */ + if (files_left < 0) + { + files_left = 0; + } + + /* If the number of files left is 0, we want to update the status without + * considering this time, since we want to change the status to completed + * and probably we won't get more calls to this function */ + if (transfer_info->last_report_time != 0 && + ABS ((gint64) (transfer_info->last_report_time - now)) < 100 * NSEC_PER_MICROSEC && + files_left > 0) + { + return; + } + transfer_info->last_report_time = now; + + if (files_left != transfer_info->last_reported_files_left || + transfer_info->last_reported_files_left == 0) + { + /* Avoid changing this unless files_left changed since last time */ + transfer_info->last_reported_files_left = files_left; + + if (source_info->num_files == 1) + { + g_autofree gchar *basename_dest = NULL; + + if (copy_job->destination != NULL) + { + if (is_move) + { + if (files_left > 0) + { + status = _("Moving “%s” to “%s”"); + } + else + { + status = _("Moved “%s” to “%s”"); + } + } + else + { + if (files_left > 0) + { + status = _("Copying “%s” to “%s”"); + } + else + { + status = _("Copied “%s” to “%s”"); + } + } + + basename_dest = get_basename (G_FILE (copy_job->destination)); + + if (copy_job->fake_display_source != NULL) + { + g_autofree gchar *basename_fake_display_source = NULL; + + basename_fake_display_source = get_basename (copy_job->fake_display_source); + tmp = g_strdup_printf (status, + basename_fake_display_source, + basename_dest); + } + else + { + g_autofree gchar *basename_data = NULL; + + basename_data = get_basename (G_FILE (copy_job->files->data)); + tmp = g_strdup_printf (status, + basename_data, + basename_dest); + } + + nautilus_progress_info_take_status (job->progress, + tmp); + } + else + { + g_autofree gchar *basename = NULL; + + if (files_left > 0) + { + status = _("Duplicating “%s”"); + } + else + { + status = _("Duplicated “%s”"); + } + + basename = get_basename (G_FILE (copy_job->files->data)); + nautilus_progress_info_take_status (job->progress, + g_strdup_printf (status, + basename)); + } + } + else if (copy_job->files != NULL) + { + if (copy_job->destination != NULL) + { + if (files_left > 0) + { + g_autofree gchar *basename = NULL; + + if (is_move) + { + status = ngettext ("Moving %'d file to “%s”", + "Moving %'d files to “%s”", + source_info->num_files); + } + else + { + status = ngettext ("Copying %'d file to “%s”", + "Copying %'d files to “%s”", + source_info->num_files); + } + + basename = get_basename (G_FILE (copy_job->destination)); + tmp = g_strdup_printf (status, + source_info->num_files, + basename); + + nautilus_progress_info_take_status (job->progress, + tmp); + } + else + { + g_autofree gchar *basename = NULL; + + if (is_move) + { + status = ngettext ("Moved %'d file to “%s”", + "Moved %'d files to “%s”", + source_info->num_files); + } + else + { + status = ngettext ("Copied %'d file to “%s”", + "Copied %'d files to “%s”", + source_info->num_files); + } + + basename = get_basename (G_FILE (copy_job->destination)); + tmp = g_strdup_printf (status, + source_info->num_files, + basename); + + nautilus_progress_info_take_status (job->progress, + tmp); + } + } + else + { + GFile *parent; + g_autofree gchar *basename = NULL; + + parent = g_file_get_parent (copy_job->files->data); + basename = get_basename (parent); + if (files_left > 0) + { + status = ngettext ("Duplicating %'d file in “%s”", + "Duplicating %'d files in “%s”", + source_info->num_files); + nautilus_progress_info_take_status (job->progress, + g_strdup_printf (status, + source_info->num_files, + basename)); + } + else + { + status = ngettext ("Duplicated %'d file in “%s”", + "Duplicated %'d files in “%s”", + source_info->num_files); + nautilus_progress_info_take_status (job->progress, + g_strdup_printf (status, + source_info->num_files, + basename)); + } + g_object_unref (parent); + } + } + } + + total_size = MAX (source_info->num_bytes, transfer_info->num_bytes); + + elapsed = g_timer_elapsed (job->time, NULL); + transfer_rate = 0; + remaining_time = INT_MAX; + if (elapsed > 0) + { + transfer_rate = transfer_info->num_bytes / elapsed; + if (transfer_rate > 0) + { + remaining_time = (total_size - transfer_info->num_bytes) / transfer_rate; + } + } + + if (elapsed < SECONDS_NEEDED_FOR_RELIABLE_TRANSFER_RATE || + transfer_rate == 0 || + !transfer_info->partial_progress) + { + if (source_info->num_files == 1) + { + g_autofree gchar *formatted_size_num_bytes = NULL; + g_autofree gchar *formatted_size_total_size = NULL; + + formatted_size_num_bytes = g_format_size (transfer_info->num_bytes); + formatted_size_total_size = g_format_size (total_size); + /* To translators: %s will expand to a size like "2 bytes" or "3 MB", so something like "4 kb / 4 MB" */ + details = g_strdup_printf (_("%s / %s"), + formatted_size_num_bytes, + formatted_size_total_size); + } + else + { + if (files_left > 0) + { + /* To translators: %'d is the number of files completed for the operation, + * so it will be something like 2/14. */ + details = g_strdup_printf (_("%'d / %'d"), + transfer_info->num_files + 1, + source_info->num_files); + } + else + { + /* To translators: %'d is the number of files completed for the operation, + * so it will be something like 2/14. */ + details = g_strdup_printf (_("%'d / %'d"), + transfer_info->num_files, + source_info->num_files); + } + } + } + else + { + if (source_info->num_files == 1) + { + if (files_left > 0) + { + g_autofree gchar *formatted_time = NULL; + g_autofree gchar *formatted_size_num_bytes = NULL; + g_autofree gchar *formatted_size_total_size = NULL; + g_autofree gchar *formatted_size_transfer_rate = NULL; + + formatted_time = get_formatted_time (remaining_time); + formatted_size_num_bytes = g_format_size (transfer_info->num_bytes); + formatted_size_total_size = g_format_size (total_size); + formatted_size_transfer_rate = g_format_size ((goffset) transfer_rate); + /* To translators: %s will expand to a size like "2 bytes" or "3 MB", %s to a time duration like + * "2 minutes". So the whole thing will be something like "2 kb / 4 MB -- 2 hours left (4kb/sec)" + * + * The singular/plural form will be used depending on the remaining time (i.e. the %s argument). + */ + details = g_strdup_printf (ngettext ("%s / %s \xE2\x80\x94 %s left (%s/sec)", + "%s / %s \xE2\x80\x94 %s left (%s/sec)", + seconds_count_format_time_units (remaining_time)), + formatted_size_num_bytes, + formatted_size_total_size, + formatted_time, + formatted_size_transfer_rate); + } + else + { + g_autofree gchar *formatted_size_num_bytes = NULL; + g_autofree gchar *formatted_size_total_size = NULL; + + formatted_size_num_bytes = g_format_size (transfer_info->num_bytes); + formatted_size_total_size = g_format_size (total_size); + /* To translators: %s will expand to a size like "2 bytes" or "3 MB". */ + details = g_strdup_printf (_("%s / %s"), + formatted_size_num_bytes, + formatted_size_total_size); + } + } + else + { + if (files_left > 0) + { + g_autofree gchar *formatted_time = NULL; + g_autofree gchar *formatted_size = NULL; + formatted_time = get_formatted_time (remaining_time); + formatted_size = g_format_size ((goffset) transfer_rate); + /* To translators: %s will expand to a time duration like "2 minutes". + * So the whole thing will be something like "1 / 5 -- 2 hours left (4kb/sec)" + * + * The singular/plural form will be used depending on the remaining time (i.e. the %s argument). + */ + details = g_strdup_printf (ngettext ("%'d / %'d \xE2\x80\x94 %s left (%s/sec)", + "%'d / %'d \xE2\x80\x94 %s left (%s/sec)", + seconds_count_format_time_units (remaining_time)), + transfer_info->num_files + 1, source_info->num_files, + formatted_time, + formatted_size); + } + else + { + /* To translators: %'d is the number of files completed for the operation, + * so it will be something like 2/14. */ + details = g_strdup_printf (_("%'d / %'d"), + transfer_info->num_files, + source_info->num_files); + } + } + } + nautilus_progress_info_take_details (job->progress, details); + + if (elapsed > SECONDS_NEEDED_FOR_APROXIMATE_TRANSFER_RATE) + { + nautilus_progress_info_set_remaining_time (job->progress, + remaining_time); + nautilus_progress_info_set_elapsed_time (job->progress, + elapsed); + } + + nautilus_progress_info_set_progress (job->progress, transfer_info->num_bytes, total_size); +} +#pragma GCC diagnostic pop + +#define FAT_FORBIDDEN_CHARACTERS "/:*?\"<>\\|" + +static gboolean +fat_str_replace (char *str, + char replacement) +{ + gboolean success; + int i; + + success = FALSE; + for (i = 0; str[i] != '\0'; i++) + { + if (strchr (FAT_FORBIDDEN_CHARACTERS, str[i]) || + str[i] < 32) + { + success = TRUE; + str[i] = replacement; + } + } + + return success; +} + +static gboolean +make_file_name_valid_for_dest_fs (char *filename, + const char *dest_fs_type) +{ + if (dest_fs_type != NULL && filename != NULL) + { + if (/* The fuseblk filesystem type could be of any type + * in theory, but in practice is usually NTFS or exFAT. + * This assumption is a pragmatic way to solve + * https://gitlab.gnome.org/GNOME/nautilus/-/issues/1343 */ + !strcmp (dest_fs_type, "fuse") || + !strcmp (dest_fs_type, "ntfs") || + /* msdos is returned for fat filesystems */ + !strcmp (dest_fs_type, "msdos") || + !strcmp (dest_fs_type, "exfat")) + { + gboolean ret; + int i, old_len; + + ret = fat_str_replace (filename, '_'); + + old_len = strlen (filename); + for (i = 0; i < old_len; i++) + { + if (filename[i] != ' ') + { + g_strchomp (filename); + ret |= (old_len != strlen (filename)); + break; + } + } + + return ret; + } + } + + return FALSE; +} + +static GFile * +get_unique_target_file (GFile *src, + GFile *dest_dir, + gboolean same_fs, + const char *dest_fs_type, + int count) +{ + const char *editname, *end; + char *basename, *new_name; + GFileInfo *info; + GFile *dest; + int max_length; + NautilusFile *file; + gboolean ignore_extension; + + max_length = nautilus_get_max_child_name_length_for_location (dest_dir); + + file = nautilus_file_get (src); + ignore_extension = nautilus_file_is_directory (file); + nautilus_file_unref (file); + + dest = NULL; + info = g_file_query_info (src, + G_FILE_ATTRIBUTE_STANDARD_EDIT_NAME, + 0, NULL, NULL); + if (info != NULL) + { + editname = g_file_info_get_attribute_string (info, G_FILE_ATTRIBUTE_STANDARD_EDIT_NAME); + + if (editname != NULL) + { + new_name = get_duplicate_name (editname, count, max_length, ignore_extension); + make_file_name_valid_for_dest_fs (new_name, dest_fs_type); + dest = g_file_get_child_for_display_name (dest_dir, new_name, NULL); + g_free (new_name); + } + + g_object_unref (info); + } + + if (dest == NULL) + { + basename = g_file_get_basename (src); + g_assert (basename == NULL); + + if (g_utf8_validate (basename, -1, NULL)) + { + new_name = get_duplicate_name (basename, count, max_length, ignore_extension); + make_file_name_valid_for_dest_fs (new_name, dest_fs_type); + dest = g_file_get_child_for_display_name (dest_dir, new_name, NULL); + g_free (new_name); + } + + if (dest == NULL) + { + end = strrchr (basename, '.'); + if (end != NULL) + { + count += atoi (end + 1); + } + new_name = g_strdup_printf ("%s.%d", basename, count); + make_file_name_valid_for_dest_fs (new_name, dest_fs_type); + dest = g_file_get_child (dest_dir, new_name); + g_free (new_name); + } + + g_free (basename); + } + + return dest; +} + +static GFile * +get_target_file_for_link (GFile *src, + GFile *dest_dir, + const char *dest_fs_type, + int count) +{ + const char *editname; + char *basename, *new_name; + GFileInfo *info; + GFile *dest; + int max_length; + + max_length = nautilus_get_max_child_name_length_for_location (dest_dir); + + dest = NULL; + info = g_file_query_info (src, + G_FILE_ATTRIBUTE_STANDARD_EDIT_NAME, + 0, NULL, NULL); + if (info != NULL) + { + editname = g_file_info_get_attribute_string (info, G_FILE_ATTRIBUTE_STANDARD_EDIT_NAME); + + if (editname != NULL) + { + new_name = get_link_name (editname, count, max_length); + make_file_name_valid_for_dest_fs (new_name, dest_fs_type); + dest = g_file_get_child_for_display_name (dest_dir, new_name, NULL); + g_free (new_name); + } + + g_object_unref (info); + } + + if (dest == NULL) + { + basename = g_file_get_basename (src); + make_file_name_valid_for_dest_fs (basename, dest_fs_type); + + if (g_utf8_validate (basename, -1, NULL)) + { + new_name = get_link_name (basename, count, max_length); + make_file_name_valid_for_dest_fs (new_name, dest_fs_type); + dest = g_file_get_child_for_display_name (dest_dir, new_name, NULL); + g_free (new_name); + } + + if (dest == NULL) + { + if (count == 1) + { + new_name = g_strdup_printf ("%s.lnk", basename); + } + else + { + new_name = g_strdup_printf ("%s.lnk%d", basename, count); + } + make_file_name_valid_for_dest_fs (new_name, dest_fs_type); + dest = g_file_get_child (dest_dir, new_name); + g_free (new_name); + } + + g_free (basename); + } + + return dest; +} + +static GFile * +get_target_file_with_custom_name (GFile *src, + GFile *dest_dir, + const char *dest_fs_type, + gboolean same_fs, + const gchar *custom_name) +{ + char *basename; + GFile *dest; + GFileInfo *info; + char *copyname; + + dest = NULL; + + if (custom_name != NULL) + { + copyname = g_strdup (custom_name); + make_file_name_valid_for_dest_fs (copyname, dest_fs_type); + dest = g_file_get_child_for_display_name (dest_dir, copyname, NULL); + + g_free (copyname); + } + + if (dest == NULL && !same_fs) + { + info = g_file_query_info (src, + G_FILE_ATTRIBUTE_STANDARD_COPY_NAME "," + G_FILE_ATTRIBUTE_TRASH_ORIG_PATH, + 0, NULL, NULL); + + if (info) + { + copyname = NULL; + + /* if file is being restored from trash make sure it uses its original name */ + if (g_file_has_uri_scheme (src, "trash")) + { + copyname = g_path_get_basename (g_file_info_get_attribute_byte_string (info, G_FILE_ATTRIBUTE_TRASH_ORIG_PATH)); + } + + if (copyname == NULL) + { + copyname = g_strdup (g_file_info_get_attribute_string (info, G_FILE_ATTRIBUTE_STANDARD_COPY_NAME)); + } + + if (copyname) + { + make_file_name_valid_for_dest_fs (copyname, dest_fs_type); + dest = g_file_get_child_for_display_name (dest_dir, copyname, NULL); + g_free (copyname); + } + + g_object_unref (info); + } + } + + if (dest == NULL) + { + basename = g_file_get_basename (src); + make_file_name_valid_for_dest_fs (basename, dest_fs_type); + dest = g_file_get_child (dest_dir, basename); + g_free (basename); + } + + return dest; +} + +static GFile * +get_target_file (GFile *src, + GFile *dest_dir, + const char *dest_fs_type, + gboolean same_fs) +{ + return get_target_file_with_custom_name (src, dest_dir, dest_fs_type, same_fs, NULL); +} + +static gboolean +has_fs_id (GFile *file, + const char *fs_id) +{ + const char *id; + GFileInfo *info; + gboolean res; + + res = FALSE; + info = g_file_query_info (file, + G_FILE_ATTRIBUTE_ID_FILESYSTEM, + G_FILE_QUERY_INFO_NOFOLLOW_SYMLINKS, + NULL, NULL); + + if (info) + { + id = g_file_info_get_attribute_string (info, G_FILE_ATTRIBUTE_ID_FILESYSTEM); + + if (id && strcmp (id, fs_id) == 0) + { + res = TRUE; + } + + g_object_unref (info); + } + + return res; +} + +static gboolean +is_dir (GFile *file) +{ + GFileType file_type; + + file_type = g_file_query_file_type (file, + G_FILE_QUERY_INFO_NOFOLLOW_SYMLINKS, + NULL); + + return file_type == G_FILE_TYPE_DIRECTORY; +} + +static GFile * +map_possibly_volatile_file_to_real (GFile *volatile_file, + GCancellable *cancellable, + GError **error) +{ + GFile *real_file = NULL; + GFileInfo *info = NULL; + + info = g_file_query_info (volatile_file, + G_FILE_ATTRIBUTE_STANDARD_IS_SYMLINK "," + G_FILE_ATTRIBUTE_STANDARD_IS_VOLATILE "," + G_FILE_ATTRIBUTE_STANDARD_SYMLINK_TARGET, + G_FILE_QUERY_INFO_NOFOLLOW_SYMLINKS, + cancellable, + error); + if (info == NULL) + { + return NULL; + } + else + { + gboolean is_volatile; + + is_volatile = g_file_info_get_attribute_boolean (info, + G_FILE_ATTRIBUTE_STANDARD_IS_VOLATILE); + if (is_volatile) + { + const gchar *target; + + target = g_file_info_get_symlink_target (info); + real_file = g_file_resolve_relative_path (volatile_file, target); + } + } + + g_object_unref (info); + + if (real_file == NULL) + { + real_file = g_object_ref (volatile_file); + } + + return real_file; +} + +static GFile * +map_possibly_volatile_file_to_real_on_write (GFile *volatile_file, + GFileOutputStream *stream, + GCancellable *cancellable, + GError **error) +{ + GFile *real_file = NULL; + GFileInfo *info = NULL; + + info = g_file_output_stream_query_info (stream, + G_FILE_ATTRIBUTE_STANDARD_IS_SYMLINK "," + G_FILE_ATTRIBUTE_STANDARD_IS_VOLATILE "," + G_FILE_ATTRIBUTE_STANDARD_SYMLINK_TARGET, + cancellable, + error); + if (info == NULL) + { + return NULL; + } + else + { + gboolean is_volatile; + + is_volatile = g_file_info_get_attribute_boolean (info, + G_FILE_ATTRIBUTE_STANDARD_IS_VOLATILE); + if (is_volatile) + { + const gchar *target; + + target = g_file_info_get_symlink_target (info); + real_file = g_file_resolve_relative_path (volatile_file, target); + } + } + + g_object_unref (info); + + if (real_file == NULL) + { + real_file = g_object_ref (volatile_file); + } + + return real_file; +} + +static void copy_move_file (CopyMoveJob *job, + GFile *src, + GFile *dest_dir, + gboolean same_fs, + gboolean unique_names, + char **dest_fs_type, + SourceInfo *source_info, + TransferInfo *transfer_info, + GHashTable *debuting_files, + gboolean overwrite, + gboolean *skipped_file, + gboolean readonly_source_fs); + +typedef enum +{ + CREATE_DEST_DIR_RETRY, + CREATE_DEST_DIR_FAILED, + CREATE_DEST_DIR_SUCCESS +} CreateDestDirResult; + +static CreateDestDirResult +create_dest_dir (CommonJob *job, + GFile *src, + GFile **dest, + gboolean same_fs, + char **dest_fs_type) +{ + GError *error; + GFile *new_dest, *dest_dir; + char *primary, *secondary, *details; + int response; + gboolean handled_invalid_filename; + gboolean res; + + handled_invalid_filename = *dest_fs_type != NULL; + +retry: + /* First create the directory, then copy stuff to it before + * copying the attributes, because we need to be sure we can write to it */ + + error = NULL; + res = g_file_make_directory (*dest, job->cancellable, &error); + + if (res) + { + GFile *real; + + real = map_possibly_volatile_file_to_real (*dest, job->cancellable, &error); + if (real == NULL) + { + res = FALSE; + } + else + { + g_object_unref (*dest); + *dest = real; + } + } + + if (!res) + { + g_autofree gchar *basename = NULL; + + if (IS_IO_ERROR (error, CANCELLED)) + { + g_error_free (error); + return CREATE_DEST_DIR_FAILED; + } + else if (IS_IO_ERROR (error, INVALID_FILENAME) && + !handled_invalid_filename) + { + handled_invalid_filename = TRUE; + + g_assert (*dest_fs_type == NULL); + + dest_dir = g_file_get_parent (*dest); + + if (dest_dir != NULL) + { + *dest_fs_type = query_fs_type (dest_dir, job->cancellable); + + new_dest = get_target_file (src, dest_dir, *dest_fs_type, same_fs); + g_object_unref (dest_dir); + + if (!g_file_equal (*dest, new_dest)) + { + g_object_unref (*dest); + *dest = new_dest; + g_error_free (error); + return CREATE_DEST_DIR_RETRY; + } + else + { + g_object_unref (new_dest); + } + } + } + + primary = g_strdup (_("Error while copying.")); + details = NULL; + basename = get_basename (src); + + if (IS_IO_ERROR (error, PERMISSION_DENIED)) + { + secondary = g_strdup_printf (_("The folder “%s” cannot be copied because you do not " + "have permissions to create it in the destination."), + basename); + } + else + { + secondary = g_strdup_printf (_("There was an error creating the folder “%s”."), + basename); + details = error->message; + } + + response = run_warning (job, + primary, + secondary, + details, + FALSE, + CANCEL, SKIP, RETRY, + NULL); + + g_error_free (error); + + if (response == 0 || response == GTK_RESPONSE_DELETE_EVENT) + { + abort_job (job); + } + else if (response == 1) + { + /* Skip: Do Nothing */ + } + else if (response == 2) + { + goto retry; + } + else + { + g_assert_not_reached (); + } + return CREATE_DEST_DIR_FAILED; + } + nautilus_file_changes_queue_file_added (*dest); + + if (job->undo_info != NULL) + { + nautilus_file_undo_info_ext_add_origin_target_pair (NAUTILUS_FILE_UNDO_INFO_EXT (job->undo_info), + src, *dest); + } + + return CREATE_DEST_DIR_SUCCESS; +} + +/* a return value of FALSE means retry, i.e. + * the destination has changed and the source + * is expected to re-try the preceding + * g_file_move() or g_file_copy() call with + * the new destination. + */ +static gboolean +copy_move_directory (CopyMoveJob *copy_job, + GFile *src, + GFile **dest, + gboolean same_fs, + gboolean create_dest, + char **parent_dest_fs_type, + SourceInfo *source_info, + TransferInfo *transfer_info, + GHashTable *debuting_files, + gboolean *skipped_file, + gboolean readonly_source_fs) +{ + g_autoptr (GFileInfo) src_info = NULL; + GFileInfo *info; + GError *error; + GFile *src_file; + GFileEnumerator *enumerator; + char *primary, *secondary, *details; + char *dest_fs_type; + int response; + gboolean skip_error; + gboolean local_skipped_file; + CommonJob *job; + GFileCopyFlags flags; + + job = (CommonJob *) copy_job; + *skipped_file = FALSE; + + if (create_dest) + { + g_autofree char *attrs_to_read = NULL; + + switch (create_dest_dir (job, src, dest, same_fs, parent_dest_fs_type)) + { + case CREATE_DEST_DIR_RETRY: + { + /* next time copy_move_directory() is called, + * create_dest will be FALSE if a directory already + * exists under the new name (i.e. WOULD_RECURSE) + */ + return FALSE; + } + + case CREATE_DEST_DIR_FAILED: + { + *skipped_file = TRUE; + return TRUE; + } + + case CREATE_DEST_DIR_SUCCESS: + default: + { + } + break; + } + + if (debuting_files) + { + g_hash_table_replace (debuting_files, g_object_ref (*dest), GINT_TO_POINTER (TRUE)); + } + + flags = G_FILE_COPY_NOFOLLOW_SYMLINKS; + if (readonly_source_fs) + { + flags |= G_FILE_COPY_TARGET_DEFAULT_PERMS; + } + if (copy_job->is_move) + { + flags |= G_FILE_COPY_ALL_METADATA; + } + + /* Ignore errors here. Failure to copy metadata is not a hard error */ + attrs_to_read = g_file_build_attribute_list_for_copy (*dest, flags, job->cancellable, NULL); + if (attrs_to_read != NULL) + { + src_info = g_file_query_info (src, attrs_to_read, G_FILE_QUERY_INFO_NOFOLLOW_SYMLINKS, job->cancellable, NULL); + } + } + + local_skipped_file = FALSE; + dest_fs_type = NULL; + + skip_error = should_skip_readdir_error (job, src); +retry: + error = NULL; + enumerator = g_file_enumerate_children (src, + G_FILE_ATTRIBUTE_STANDARD_NAME, + G_FILE_QUERY_INFO_NOFOLLOW_SYMLINKS, + job->cancellable, + &error); + if (enumerator) + { + error = NULL; + + while (!job_aborted (job) && + (info = g_file_enumerator_next_file (enumerator, job->cancellable, skip_error ? NULL : &error)) != NULL) + { + src_file = g_file_get_child (src, + g_file_info_get_name (info)); + copy_move_file (copy_job, src_file, *dest, same_fs, FALSE, &dest_fs_type, + source_info, transfer_info, NULL, FALSE, &local_skipped_file, + readonly_source_fs); + + if (local_skipped_file) + { + source_info_remove_file_from_count (src_file, job, source_info); + report_copy_progress (copy_job, source_info, transfer_info); + } + + g_object_unref (src_file); + g_object_unref (info); + } + g_file_enumerator_close (enumerator, job->cancellable, NULL); + g_object_unref (enumerator); + + if (error && IS_IO_ERROR (error, CANCELLED)) + { + g_error_free (error); + } + else if (error) + { + g_autofree gchar *basename = NULL; + + if (copy_job->is_move) + { + primary = g_strdup (_("Error while moving.")); + } + else + { + primary = g_strdup (_("Error while copying.")); + } + details = NULL; + basename = get_basename (src); + + if (IS_IO_ERROR (error, PERMISSION_DENIED)) + { + secondary = g_strdup_printf (_("Files in the folder “%s” cannot be copied because you do " + "not have permissions to see them."), basename); + } + else + { + secondary = g_strdup_printf (_("There was an error getting information about " + "the files in the folder “%s”."), + basename); + details = error->message; + } + + response = run_warning (job, + primary, + secondary, + details, + FALSE, + CANCEL, _("_Skip files"), + NULL); + + g_error_free (error); + + if (response == 0 || response == GTK_RESPONSE_DELETE_EVENT) + { + abort_job (job); + } + else if (response == 1) + { + /* Skip: Do Nothing */ + local_skipped_file = TRUE; + } + else + { + g_assert_not_reached (); + } + } + + /* Count the copied directory as a file */ + transfer_info->num_files++; + + info = g_file_query_info (src, + G_FILE_ATTRIBUTE_STANDARD_SIZE, + G_FILE_QUERY_INFO_NOFOLLOW_SYMLINKS, + job->cancellable, + NULL); + + g_warn_if_fail (info != NULL); + + if (info != NULL) + { + transfer_info->num_bytes += g_file_info_get_size (info); + + g_object_unref (info); + } + + report_copy_progress (copy_job, source_info, transfer_info); + + if (debuting_files) + { + g_hash_table_replace (debuting_files, g_object_ref (*dest), GINT_TO_POINTER (create_dest)); + } + } + else if (IS_IO_ERROR (error, CANCELLED)) + { + g_error_free (error); + } + else + { + g_autofree gchar *basename = NULL; + + if (copy_job->is_move) + { + primary = g_strdup (_("Error while moving.")); + } + else + { + primary = g_strdup (_("Error while copying.")); + } + details = NULL; + basename = get_basename (src); + + if (IS_IO_ERROR (error, PERMISSION_DENIED)) + { + secondary = g_strdup_printf (_("The folder “%s” cannot be copied because you do not have " + "permissions to read it."), basename); + } + else + { + secondary = g_strdup_printf (_("There was an error reading the folder “%s”."), + basename); + + details = error->message; + } + + response = run_warning (job, + primary, + secondary, + details, + FALSE, + CANCEL, SKIP, RETRY, + NULL); + + g_error_free (error); + + if (response == 0 || response == GTK_RESPONSE_DELETE_EVENT) + { + abort_job (job); + } + else if (response == 1) + { + /* Skip: Do Nothing */ + *skipped_file = TRUE; + } + else if (response == 2) + { + goto retry; + } + else + { + g_assert_not_reached (); + } + } + + if (src_info != NULL) + { + /* Ignore errors here. Failure to copy metadata is not a hard error */ + g_file_set_attributes_from_info (*dest, + src_info, + G_FILE_QUERY_INFO_NOFOLLOW_SYMLINKS, + job->cancellable, + NULL); + } + + if (!job_aborted (job) && copy_job->is_move && + /* Don't delete source if there was a skipped file */ + !*skipped_file && + !local_skipped_file) + { + if (!g_file_delete (src, job->cancellable, &error)) + { + g_autofree gchar *basename = NULL; + + if (job->skip_all_error) + { + *skipped_file = TRUE; + goto skip; + } + basename = get_basename (src); + primary = g_strdup_printf (_("Error while moving “%s”."), basename); + secondary = g_strdup (_("Could not remove the source folder.")); + details = error->message; + + response = run_cancel_or_skip_warning (job, + primary, + secondary, + details, + source_info->num_files, + source_info->num_files - transfer_info->num_files); + + if (response == 0 || response == GTK_RESPONSE_DELETE_EVENT) + { + abort_job (job); + } + else if (response == 1) /* skip all */ + { + job->skip_all_error = TRUE; + *skipped_file = TRUE; + } + else if (response == 2) /* skip */ + { + *skipped_file = TRUE; + } + else + { + g_assert_not_reached (); + } + +skip: + g_error_free (error); + } + } + + g_free (dest_fs_type); + return TRUE; +} + + +typedef struct +{ + CommonJob *job; + GFile *source; +} DeleteExistingFileData; + +typedef struct +{ + CopyMoveJob *job; + goffset last_size; + SourceInfo *source_info; + TransferInfo *transfer_info; +} ProgressData; + +static void +copy_file_progress_callback (goffset current_num_bytes, + goffset total_num_bytes, + gpointer user_data) +{ + ProgressData *pdata; + goffset new_size; + + pdata = user_data; + + if (current_num_bytes != 0 && + current_num_bytes != total_num_bytes) + { + pdata->transfer_info->partial_progress = TRUE; + } + + new_size = current_num_bytes - pdata->last_size; + + if (new_size > 0) + { + pdata->transfer_info->num_bytes += new_size; + pdata->last_size = current_num_bytes; + report_copy_progress (pdata->job, + pdata->source_info, + pdata->transfer_info); + } +} + +static gboolean +test_dir_is_parent (GFile *child, + GFile *root) +{ + GFile *f, *tmp; + + f = g_file_dup (child); + while (f) + { + if (g_file_equal (f, root)) + { + g_object_unref (f); + return TRUE; + } + tmp = f; + f = g_file_get_parent (f); + g_object_unref (tmp); + } + if (f) + { + g_object_unref (f); + } + return FALSE; +} + +static char * +query_fs_type (GFile *file, + GCancellable *cancellable) +{ + GFileInfo *fsinfo; + char *ret; + + ret = NULL; + + fsinfo = g_file_query_filesystem_info (file, + G_FILE_ATTRIBUTE_FILESYSTEM_TYPE, + cancellable, + NULL); + if (fsinfo != NULL) + { + ret = g_strdup (g_file_info_get_attribute_string (fsinfo, G_FILE_ATTRIBUTE_FILESYSTEM_TYPE)); + g_object_unref (fsinfo); + } + + if (ret == NULL) + { + /* ensure that we don't attempt to query + * the FS type for each file in a given + * directory, if it can't be queried. */ + ret = g_strdup (""); + } + + return ret; +} + +static FileConflictResponse * +handle_copy_move_conflict (CommonJob *job, + GFile *src, + GFile *dest, + GFile *dest_dir) +{ + FileConflictResponse *response; + g_autofree gchar *basename = NULL; + g_autoptr (GFile) suggested_file = NULL; + g_autofree gchar *suggestion = NULL; + gboolean should_start_inactive; + + g_timer_stop (job->time); + nautilus_progress_info_pause (job->progress); + + should_start_inactive = is_long_job (job); + + basename = g_file_get_basename (dest); + suggested_file = nautilus_generate_unique_file_in_directory (dest_dir, basename); + suggestion = g_file_get_basename (suggested_file); + + response = copy_move_conflict_ask_user_action (job->parent_window, + should_start_inactive, + src, + dest, + dest_dir, + suggestion); + + nautilus_progress_info_resume (job->progress); + g_timer_continue (job->time); + + return response; +} + +static GFile * +get_target_file_for_display_name (GFile *dir, + const gchar *name) +{ + GFile *dest; + + dest = NULL; + dest = g_file_get_child_for_display_name (dir, name, NULL); + + if (dest == NULL) + { + dest = g_file_get_child (dir, name); + } + + return dest; +} + +/* This is a workaround to resolve broken conflict dialog for google-drive + * locations. This is needed to provide POSIX-like behavior for google-drive + * filesystem, where each file has an unique identificator that is not tied to + * its display_name. See the following MR for more details: + * https://gitlab.gnome.org/GNOME/nautilus/merge_requests/514. + */ +static GFile * +get_target_file_from_source_display_name (CopyMoveJob *copy_job, + GFile *src, + GFile *dir) +{ + CommonJob *job; + g_autoptr (GError) error = NULL; + g_autoptr (GFileInfo) info = NULL; + gchar *primary, *secondary; + GFile *dest = NULL; + + job = (CommonJob *) copy_job; + + info = g_file_query_info (src, G_FILE_ATTRIBUTE_STANDARD_DISPLAY_NAME, 0, NULL, &error); + if (info == NULL) + { + if (copy_job->is_move) + { + primary = g_strdup (_("Error while moving.")); + } + else + { + primary = g_strdup (_("Error while copying.")); + } + secondary = g_strdup (_("There was an error getting information about the source.")); + + run_error (job, + primary, + secondary, + error->message, + FALSE, + CANCEL, + NULL); + + abort_job (job); + } + else + { + dest = get_target_file_for_display_name (dir, g_file_info_get_display_name (info)); + } + + return dest; +} + + +/* Debuting files is non-NULL only for toplevel items */ +static void +copy_move_file (CopyMoveJob *copy_job, + GFile *src, + GFile *dest_dir, + gboolean same_fs, + gboolean unique_names, + char **dest_fs_type, + SourceInfo *source_info, + TransferInfo *transfer_info, + GHashTable *debuting_files, + gboolean overwrite, + gboolean *skipped_file, + gboolean readonly_source_fs) +{ + GFile *dest, *new_dest; + g_autofree gchar *dest_uri = NULL; + GError *error; + GFileCopyFlags flags; + char *primary, *secondary, *details; + ProgressData pdata; + gboolean would_recurse; + CommonJob *job; + gboolean res; + int unique_name_nr; + gboolean handled_invalid_filename; + + job = (CommonJob *) copy_job; + + *skipped_file = FALSE; + + if (should_skip_file (job, src)) + { + *skipped_file = TRUE; + return; + } + + unique_name_nr = 1; + + /* another file in the same directory might have handled the invalid + * filename condition for us + */ + handled_invalid_filename = *dest_fs_type != NULL; + + if (unique_names) + { + dest = get_unique_target_file (src, dest_dir, same_fs, *dest_fs_type, unique_name_nr++); + } + else if (copy_job->target_name != NULL) + { + dest = get_target_file_with_custom_name (src, dest_dir, *dest_fs_type, same_fs, + copy_job->target_name); + } + else if (g_file_has_uri_scheme (src, "google-drive") && + g_file_has_uri_scheme (dest_dir, "google-drive")) + { + dest = get_target_file_from_source_display_name (copy_job, src, dest_dir); + if (dest == NULL) + { + *skipped_file = TRUE; + return; + } + } + else + { + dest = get_target_file (src, dest_dir, *dest_fs_type, same_fs); + } + + /* Don't allow recursive move/copy into itself. + * (We would get a file system error if we proceeded but it is nicer to + * detect and report it at this level) */ + if (test_dir_is_parent (dest_dir, src)) + { + int response; + + if (job->skip_all_error) + { + goto out; + } + + /* the run_warning() frees all strings passed in automatically */ + primary = copy_job->is_move ? g_strdup (_("You cannot move a folder into itself.")) + : g_strdup (_("You cannot copy a folder into itself.")); + secondary = g_strdup (_("The destination folder is inside the source folder.")); + + response = run_cancel_or_skip_warning (job, + primary, + secondary, + NULL, + source_info->num_files, + source_info->num_files - transfer_info->num_files); + + if (response == 0 || response == GTK_RESPONSE_DELETE_EVENT) + { + abort_job (job); + } + else if (response == 1) /* skip all */ + { + job->skip_all_error = TRUE; + } + else if (response == 2) /* skip */ + { /* do nothing */ + } + else + { + g_assert_not_reached (); + } + + goto out; + } + + /* Don't allow copying over the source or one of the parents of the source. + */ + if (test_dir_is_parent (src, dest)) + { + int response; + + if (job->skip_all_error) + { + goto out; + } + + /* the run_warning() frees all strings passed in automatically */ + primary = copy_job->is_move ? g_strdup (_("You cannot move a file over itself.")) + : g_strdup (_("You cannot copy a file over itself.")); + secondary = g_strdup (_("The source file would be overwritten by the destination.")); + + response = run_cancel_or_skip_warning (job, + primary, + secondary, + NULL, + source_info->num_files, + source_info->num_files - transfer_info->num_files); + + if (response == 0 || response == GTK_RESPONSE_DELETE_EVENT) + { + abort_job (job); + } + else if (response == 1) /* skip all */ + { + job->skip_all_error = TRUE; + } + else if (response == 2) /* skip */ + { /* do nothing */ + } + else + { + g_assert_not_reached (); + } + + goto out; + } + + +retry: + + error = NULL; + flags = G_FILE_COPY_NOFOLLOW_SYMLINKS; + if (overwrite) + { + flags |= G_FILE_COPY_OVERWRITE; + } + if (readonly_source_fs) + { + flags |= G_FILE_COPY_TARGET_DEFAULT_PERMS; + } + + pdata.job = copy_job; + pdata.last_size = 0; + pdata.source_info = source_info; + pdata.transfer_info = transfer_info; + + if (copy_job->is_move) + { + res = g_file_move (src, dest, + flags, + job->cancellable, + copy_file_progress_callback, + &pdata, + &error); + } + else + { + res = g_file_copy (src, dest, + flags, + job->cancellable, + copy_file_progress_callback, + &pdata, + &error); + } + + if (res) + { + GFile *real; + + real = map_possibly_volatile_file_to_real (dest, job->cancellable, &error); + if (real == NULL) + { + res = FALSE; + } + else + { + g_object_unref (dest); + dest = real; + } + } + + if (res) + { + transfer_info->num_files++; + report_copy_progress (copy_job, source_info, transfer_info); + + if (debuting_files) + { + dest_uri = g_file_get_uri (dest); + + g_hash_table_replace (debuting_files, g_object_ref (dest), GINT_TO_POINTER (!overwrite)); + } + if (copy_job->is_move) + { + nautilus_file_changes_queue_file_moved (src, dest); + } + else + { + nautilus_file_changes_queue_file_added (dest); + } + + if (job->undo_info != NULL) + { + nautilus_file_undo_info_ext_add_origin_target_pair (NAUTILUS_FILE_UNDO_INFO_EXT (job->undo_info), + src, dest); + } + + g_object_unref (dest); + return; + } + + if (!handled_invalid_filename && + IS_IO_ERROR (error, INVALID_FILENAME)) + { + handled_invalid_filename = TRUE; + + g_assert (*dest_fs_type == NULL); + *dest_fs_type = query_fs_type (dest_dir, job->cancellable); + + if (unique_names) + { + new_dest = get_unique_target_file (src, dest_dir, same_fs, *dest_fs_type, unique_name_nr); + } + else + { + new_dest = get_target_file (src, dest_dir, *dest_fs_type, same_fs); + } + + if (!g_file_equal (dest, new_dest)) + { + g_object_unref (dest); + dest = new_dest; + + g_error_free (error); + goto retry; + } + else + { + g_object_unref (new_dest); + } + } + + /* Conflict */ + if (!overwrite && + IS_IO_ERROR (error, EXISTS)) + { + gboolean source_is_directory; + gboolean destination_is_directory; + gboolean is_merge; + FileConflictResponse *response; + + source_is_directory = is_dir (src); + destination_is_directory = is_dir (dest); + + g_error_free (error); + + if (unique_names) + { + g_object_unref (dest); + dest = get_unique_target_file (src, dest_dir, same_fs, *dest_fs_type, unique_name_nr++); + goto retry; + } + + is_merge = FALSE; + + if (source_is_directory && destination_is_directory) + { + is_merge = TRUE; + } + else if (!source_is_directory && destination_is_directory) + { + /* Any sane backend will fail with G_IO_ERROR_IS_DIRECTORY. */ + overwrite = TRUE; + goto retry; + } + + if ((is_merge && job->merge_all) || + (!is_merge && job->replace_all)) + { + overwrite = TRUE; + goto retry; + } + + if (job->skip_all_conflict) + { + goto out; + } + + response = handle_copy_move_conflict (job, src, dest, dest_dir); + + if (response->id == GTK_RESPONSE_CANCEL || + response->id == GTK_RESPONSE_DELETE_EVENT) + { + file_conflict_response_free (response); + abort_job (job); + } + else if (response->id == CONFLICT_RESPONSE_SKIP) + { + if (response->apply_to_all) + { + job->skip_all_conflict = TRUE; + } + file_conflict_response_free (response); + } + else if (response->id == CONFLICT_RESPONSE_REPLACE) /* merge/replace */ + { + if (response->apply_to_all) + { + if (is_merge) + { + job->merge_all = TRUE; + } + else + { + job->replace_all = TRUE; + } + } + overwrite = TRUE; + file_conflict_response_free (response); + goto retry; + } + else if (response->id == CONFLICT_RESPONSE_RENAME) + { + g_object_unref (dest); + dest = get_target_file_for_display_name (dest_dir, + response->new_name); + file_conflict_response_free (response); + goto retry; + } + else + { + g_assert_not_reached (); + } + } + /* Needs to recurse */ + else if (IS_IO_ERROR (error, WOULD_RECURSE) || + IS_IO_ERROR (error, WOULD_MERGE)) + { + gboolean is_merge; + + is_merge = error->code == G_IO_ERROR_WOULD_MERGE; + would_recurse = error->code == G_IO_ERROR_WOULD_RECURSE; + g_error_free (error); + + if (overwrite && would_recurse) + { + error = NULL; + + /* Copying a dir onto file, first remove the file */ + if (!g_file_delete (dest, job->cancellable, &error) && + !IS_IO_ERROR (error, NOT_FOUND)) + { + g_autofree gchar *basename = NULL; + g_autofree gchar *filename = NULL; + int response; + + if (job->skip_all_error) + { + g_error_free (error); + goto out; + } + + basename = get_basename (src); + if (copy_job->is_move) + { + primary = g_strdup_printf (_("Error while moving “%s”."), basename); + } + else + { + primary = g_strdup_printf (_("Error while copying “%s”."), basename); + } + filename = get_truncated_parse_name (dest_dir); + secondary = g_strdup_printf (_("Could not remove the already existing file " + "with the same name in %s."), + filename); + details = error->message; + + /* setting TRUE on show_all here, as we could have + * another error on the same file later. + */ + response = run_warning (job, + primary, + secondary, + details, + TRUE, + CANCEL, SKIP_ALL, SKIP, + NULL); + + g_error_free (error); + + if (response == 0 || response == GTK_RESPONSE_DELETE_EVENT) + { + abort_job (job); + } + else if (response == 1) /* skip all */ + { + job->skip_all_error = TRUE; + } + else if (response == 2) /* skip */ + { /* do nothing */ + } + else + { + g_assert_not_reached (); + } + goto out; + } + if (error) + { + g_error_free (error); + error = NULL; + } + nautilus_file_changes_queue_file_removed (dest); + } + + if (is_merge) + { + /* On merge we now write in the target directory, which may not + * be in the same directory as the source, even if the parent is + * (if the merged directory is a mountpoint). This could cause + * problems as we then don't transcode filenames. + * We just set same_fs to FALSE which is safe but a bit slower. */ + same_fs = FALSE; + } + + if (!copy_move_directory (copy_job, src, &dest, same_fs, + would_recurse, dest_fs_type, + source_info, transfer_info, + debuting_files, skipped_file, + readonly_source_fs)) + { + /* destination changed, since it was an invalid file name */ + g_assert (*dest_fs_type != NULL); + handled_invalid_filename = TRUE; + goto retry; + } + + g_object_unref (dest); + return; + } + else if (IS_IO_ERROR (error, CANCELLED)) + { + g_error_free (error); + } + /* Other error */ + else + { + g_autofree gchar *basename = NULL; + g_autofree gchar *filename = NULL; + int response; + + if (job->skip_all_error) + { + g_error_free (error); + goto out; + } + basename = get_basename (src); + primary = g_strdup_printf (_("Error while copying “%s”."), basename); + filename = get_truncated_parse_name (dest_dir); + secondary = g_strdup_printf (_("There was an error copying the file into %s."), + filename); + details = error->message; + + response = run_cancel_or_skip_warning (job, + primary, + secondary, + details, + source_info->num_files, + source_info->num_files - transfer_info->num_files); + + g_error_free (error); + + if (response == 0 || response == GTK_RESPONSE_DELETE_EVENT) + { + abort_job (job); + } + else if (response == 1) /* skip all */ + { + job->skip_all_error = TRUE; + } + else if (response == 2) /* skip */ + { /* do nothing */ + } + else + { + g_assert_not_reached (); + } + } +out: + *skipped_file = TRUE; /* Or aborted, but same-same */ + g_object_unref (dest); +} + +static void +copy_files (CopyMoveJob *job, + const char *dest_fs_id, + SourceInfo *source_info, + TransferInfo *transfer_info) +{ + CommonJob *common; + GList *l; + GFile *src; + gboolean same_fs; + int i; + gboolean skipped_file; + gboolean unique_names; + GFile *dest; + GFile *source_dir; + char *dest_fs_type; + GFileInfo *inf; + gboolean readonly_source_fs; + + dest_fs_type = NULL; + readonly_source_fs = FALSE; + + common = &job->common; + + report_copy_progress (job, source_info, transfer_info); + + /* Query the source dir, not the file because if it's a symlink we'll follow it */ + source_dir = g_file_get_parent ((GFile *) job->files->data); + if (source_dir) + { + inf = g_file_query_filesystem_info (source_dir, "filesystem::readonly", NULL, NULL); + if (inf != NULL) + { + readonly_source_fs = g_file_info_get_attribute_boolean (inf, "filesystem::readonly"); + g_object_unref (inf); + } + g_object_unref (source_dir); + } + + unique_names = (job->destination == NULL); + i = 0; + for (l = job->files; + l != NULL && !job_aborted (common); + l = l->next) + { + src = l->data; + + same_fs = FALSE; + if (dest_fs_id) + { + same_fs = has_fs_id (src, dest_fs_id); + } + + if (job->destination) + { + dest = g_object_ref (job->destination); + } + else + { + dest = g_file_get_parent (src); + } + if (dest) + { + skipped_file = FALSE; + copy_move_file (job, src, dest, + same_fs, unique_names, + &dest_fs_type, + source_info, transfer_info, + job->debuting_files, + FALSE, &skipped_file, + readonly_source_fs); + g_object_unref (dest); + + if (skipped_file) + { + source_info_remove_file_from_count (src, common, source_info); + report_copy_progress (job, source_info, transfer_info); + } + } + i++; + } + + g_free (dest_fs_type); +} + +static void +copy_task_done (GObject *source_object, + GAsyncResult *res, + gpointer user_data) +{ + CopyMoveJob *job; + + job = user_data; + if (job->done_callback) + { + job->done_callback (job->debuting_files, + !job_aborted ((CommonJob *) job), + job->done_callback_data); + } + + g_list_free_full (job->files, g_object_unref); + if (job->destination) + { + g_object_unref (job->destination); + } + g_hash_table_unref (job->debuting_files); + g_free (job->target_name); + + g_clear_object (&job->fake_display_source); + + finalize_common ((CommonJob *) job); + + nautilus_file_changes_consume_changes (TRUE); +} + +static CopyMoveJob * +copy_job_setup (GList *files, + GFile *target_dir, + GtkWindow *parent_window, + NautilusFileOperationsDBusData *dbus_data, + NautilusCopyCallback done_callback, + gpointer done_callback_data) +{ + CopyMoveJob *job; + + job = op_job_new (CopyMoveJob, parent_window, dbus_data); + job->done_callback = done_callback; + job->done_callback_data = done_callback_data; + job->files = g_list_copy_deep (files, (GCopyFunc) g_object_ref, NULL); + job->destination = g_object_ref (target_dir); + /* Need to indicate the destination for the operation notification open + * button. */ + nautilus_progress_info_set_destination (((CommonJob *) job)->progress, target_dir); + job->debuting_files = g_hash_table_new_full (g_file_hash, (GEqualFunc) g_file_equal, g_object_unref, NULL); + + return job; +} + +static void +nautilus_file_operations_copy (GTask *task, + gpointer source_object, + gpointer task_data, + GCancellable *cancellable) +{ + CopyMoveJob *job; + CommonJob *common; + g_auto (SourceInfo) source_info = SOURCE_INFO_INIT; + TransferInfo transfer_info; + g_autofree char *dest_fs_id = NULL; + GFile *dest; + + job = task_data; + common = &job->common; + + if (g_strcmp0 (g_getenv ("RUNNING_TESTS"), "TRUE")) + { + inhibit_power_manager ((CommonJob *) job, _("Copying Files")); + } + + if (!nautilus_file_undo_manager_is_operating ()) + { + g_autoptr (GFile) src_dir = NULL; + + src_dir = g_file_get_parent (job->files->data); + /* In the case of duplicate, the undo_info is already set, so we don't want to + * overwrite it wrongfully. + */ + if (job->common.undo_info == NULL) + { + job->common.undo_info = nautilus_file_undo_info_ext_new (NAUTILUS_FILE_UNDO_OP_COPY, + g_list_length (job->files), + src_dir, job->destination); + } + } + + nautilus_progress_info_start (job->common.progress); + + scan_sources (job->files, + &source_info, + common, + OP_KIND_COPY); + if (job_aborted (common)) + { + return; + } + + if (job->destination) + { + dest = g_object_ref (job->destination); + } + else + { + /* Duplication, no dest, + * use source for free size, etc + */ + dest = g_file_get_parent (job->files->data); + } + + verify_destination (&job->common, + dest, + &dest_fs_id, + source_info.num_bytes); + g_object_unref (dest); + if (job_aborted (common)) + { + return; + } + + g_timer_start (job->common.time); + + memset (&transfer_info, 0, sizeof (transfer_info)); + copy_files (job, + dest_fs_id, + &source_info, &transfer_info); +} + +void +nautilus_file_operations_copy_sync (GList *files, + GFile *target_dir) +{ + GTask *task; + CopyMoveJob *job; + + job = copy_job_setup (files, + target_dir, + NULL, + NULL, + NULL, + NULL); + + task = g_task_new (NULL, job->common.cancellable, NULL, job); + g_task_set_task_data (task, job, NULL); + g_task_run_in_thread_sync (task, nautilus_file_operations_copy); + g_object_unref (task); + /* Since g_task_run_in_thread_sync doesn't work with callbacks (in this case not reaching + * copy_task_done) we need to set up the undo information ourselves. + */ + copy_task_done (NULL, NULL, job); +} + +void +nautilus_file_operations_copy_async (GList *files, + GFile *target_dir, + GtkWindow *parent_window, + NautilusFileOperationsDBusData *dbus_data, + NautilusCopyCallback done_callback, + gpointer done_callback_data) +{ + GTask *task; + CopyMoveJob *job; + + job = copy_job_setup (files, + target_dir, + parent_window, + dbus_data, + done_callback, + done_callback_data); + + task = g_task_new (NULL, job->common.cancellable, copy_task_done, job); + g_task_set_task_data (task, job, NULL); + g_task_run_in_thread (task, nautilus_file_operations_copy); + g_object_unref (task); +} + +static void +report_preparing_move_progress (CopyMoveJob *move_job, + int total, + int left) +{ + CommonJob *job; + g_autofree gchar *basename = NULL; + + job = (CommonJob *) move_job; + basename = get_basename (move_job->destination); + + nautilus_progress_info_take_status (job->progress, + g_strdup_printf (_("Preparing to move to “%s”"), + basename)); + + nautilus_progress_info_take_details (job->progress, + g_strdup_printf (ngettext ("Preparing to move %'d file", + "Preparing to move %'d files", + left), + left)); + + nautilus_progress_info_pulse_progress (job->progress); +} + +typedef struct +{ + GFile *file; + gboolean overwrite; +} MoveFileCopyFallback; + +static MoveFileCopyFallback * +move_copy_file_callback_new (GFile *file, + gboolean overwrite) +{ + MoveFileCopyFallback *fallback; + + fallback = g_new (MoveFileCopyFallback, 1); + fallback->file = file; + fallback->overwrite = overwrite; + + return fallback; +} + +static GList * +get_files_from_fallbacks (GList *fallbacks) +{ + MoveFileCopyFallback *fallback; + GList *res, *l; + + res = NULL; + for (l = fallbacks; l != NULL; l = l->next) + { + fallback = l->data; + res = g_list_prepend (res, fallback->file); + } + return g_list_reverse (res); +} + +static void +move_file_prepare (CopyMoveJob *move_job, + GFile *src, + GFile *dest_dir, + gboolean same_fs, + char **dest_fs_type, + GHashTable *debuting_files, + GList **fallback_files, + int files_left) +{ + GFile *dest, *new_dest; + g_autofree gchar *dest_uri = NULL; + GError *error; + CommonJob *job; + gboolean overwrite; + char *primary, *secondary, *details; + GFileCopyFlags flags; + MoveFileCopyFallback *fallback; + gboolean handled_invalid_filename; + + overwrite = FALSE; + handled_invalid_filename = *dest_fs_type != NULL; + + job = (CommonJob *) move_job; + + if (g_file_has_uri_scheme (src, "google-drive") && + g_file_has_uri_scheme (dest_dir, "google-drive")) + { + dest = get_target_file_from_source_display_name (move_job, src, dest_dir); + if (dest == NULL) + { + return; + } + } + else + { + dest = get_target_file (src, dest_dir, *dest_fs_type, same_fs); + } + + + /* Don't allow recursive move/copy into itself. + * (We would get a file system error if we proceeded but it is nicer to + * detect and report it at this level) */ + if (test_dir_is_parent (dest_dir, src)) + { + int response; + + if (job->skip_all_error) + { + goto out; + } + + /* the run_warning() frees all strings passed in automatically */ + primary = move_job->is_move ? g_strdup (_("You cannot move a folder into itself.")) + : g_strdup (_("You cannot copy a folder into itself.")); + secondary = g_strdup (_("The destination folder is inside the source folder.")); + + response = run_warning (job, + primary, + secondary, + NULL, + files_left > 1, + CANCEL, SKIP_ALL, SKIP, + NULL); + + if (response == 0 || response == GTK_RESPONSE_DELETE_EVENT) + { + abort_job (job); + } + else if (response == 1) /* skip all */ + { + job->skip_all_error = TRUE; + } + else if (response == 2) /* skip */ + { /* do nothing */ + } + else + { + g_assert_not_reached (); + } + + goto out; + } + + /* Don't allow moving over the source or one of the parents of the source. + */ + if (test_dir_is_parent (src, dest)) + { + int response; + + if (job->skip_all_error) + { + goto out; + } + + /* the run_warning() frees all strings passed in automatically */ + primary = move_job->is_move ? g_strdup (_("You cannot move a file over itself.")) + : g_strdup (_("You cannot copy a file over itself.")); + secondary = g_strdup (_("The source file would be overwritten by the destination.")); + + response = run_warning (job, + primary, + secondary, + NULL, + files_left > 1, + CANCEL, SKIP_ALL, SKIP, + NULL); + + if (response == 0 || response == GTK_RESPONSE_DELETE_EVENT) + { + abort_job (job); + } + else if (response == 1) /* skip all */ + { + job->skip_all_error = TRUE; + } + else if (response == 2) /* skip */ + { + /* do nothing */ + } + else + { + g_assert_not_reached (); + } + + goto out; + } + + +retry: + + flags = G_FILE_COPY_NOFOLLOW_SYMLINKS | G_FILE_COPY_NO_FALLBACK_FOR_MOVE; + if (overwrite) + { + flags |= G_FILE_COPY_OVERWRITE; + } + + error = NULL; + if (g_file_move (src, dest, + flags, + job->cancellable, + NULL, + NULL, + &error)) + { + if (debuting_files) + { + g_hash_table_replace (debuting_files, g_object_ref (dest), GINT_TO_POINTER (TRUE)); + } + + nautilus_file_changes_queue_file_moved (src, dest); + + dest_uri = g_file_get_uri (dest); + + if (job->undo_info != NULL) + { + nautilus_file_undo_info_ext_add_origin_target_pair (NAUTILUS_FILE_UNDO_INFO_EXT (job->undo_info), + src, dest); + } + + return; + } + + if (IS_IO_ERROR (error, INVALID_FILENAME) && + !handled_invalid_filename) + { + g_error_free (error); + + handled_invalid_filename = TRUE; + + g_assert (*dest_fs_type == NULL); + *dest_fs_type = query_fs_type (dest_dir, job->cancellable); + + new_dest = get_target_file (src, dest_dir, *dest_fs_type, same_fs); + if (!g_file_equal (dest, new_dest)) + { + g_object_unref (dest); + dest = new_dest; + goto retry; + } + else + { + g_object_unref (new_dest); + } + } + /* Conflict */ + else if (!overwrite && + IS_IO_ERROR (error, EXISTS)) + { + gboolean source_is_directory; + gboolean destination_is_directory; + gboolean is_merge; + FileConflictResponse *response; + + source_is_directory = is_dir (src); + destination_is_directory = is_dir (dest); + + g_error_free (error); + + is_merge = FALSE; + if (source_is_directory && destination_is_directory) + { + is_merge = TRUE; + } + else if (!source_is_directory && destination_is_directory) + { + /* Any sane backend will fail with G_IO_ERROR_IS_DIRECTORY. */ + overwrite = TRUE; + goto retry; + } + + if ((is_merge && job->merge_all) || + (!is_merge && job->replace_all)) + { + overwrite = TRUE; + goto retry; + } + + if (job->skip_all_conflict) + { + goto out; + } + + response = handle_copy_move_conflict (job, src, dest, dest_dir); + + if (response->id == GTK_RESPONSE_CANCEL || + response->id == GTK_RESPONSE_DELETE_EVENT) + { + file_conflict_response_free (response); + abort_job (job); + } + else if (response->id == CONFLICT_RESPONSE_SKIP) + { + if (response->apply_to_all) + { + job->skip_all_conflict = TRUE; + } + file_conflict_response_free (response); + } + else if (response->id == CONFLICT_RESPONSE_REPLACE) /* merge/replace */ + { + if (response->apply_to_all) + { + if (is_merge) + { + job->merge_all = TRUE; + } + else + { + job->replace_all = TRUE; + } + } + overwrite = TRUE; + file_conflict_response_free (response); + goto retry; + } + else if (response->id == CONFLICT_RESPONSE_RENAME) + { + g_object_unref (dest); + dest = get_target_file_for_display_name (dest_dir, + response->new_name); + file_conflict_response_free (response); + goto retry; + } + else + { + g_assert_not_reached (); + } + } + else if (IS_IO_ERROR (error, WOULD_RECURSE) || + IS_IO_ERROR (error, WOULD_MERGE) || + IS_IO_ERROR (error, NOT_SUPPORTED)) + { + g_error_free (error); + + fallback = move_copy_file_callback_new (src, + overwrite); + *fallback_files = g_list_prepend (*fallback_files, fallback); + } + else if (IS_IO_ERROR (error, CANCELLED)) + { + g_error_free (error); + } + /* Other error */ + else + { + g_autofree gchar *basename = NULL; + g_autofree gchar *filename = NULL; + int response; + + if (job->skip_all_error) + { + g_error_free (error); + goto out; + } + basename = get_basename (src); + primary = g_strdup_printf (_("Error while moving “%s”."), basename); + filename = get_truncated_parse_name (dest_dir); + secondary = g_strdup_printf (_("There was an error moving the file into %s."), + filename); + + details = error->message; + + response = run_warning (job, + primary, + secondary, + details, + files_left > 1, + CANCEL, SKIP_ALL, SKIP, + NULL); + + g_error_free (error); + + if (response == 0 || response == GTK_RESPONSE_DELETE_EVENT) + { + abort_job (job); + } + else if (response == 1) /* skip all */ + { + job->skip_all_error = TRUE; + } + else if (response == 2) /* skip */ + { /* do nothing */ + } + else + { + g_assert_not_reached (); + } + } + +out: + g_object_unref (dest); +} + +static void +move_files_prepare (CopyMoveJob *job, + const char *dest_fs_id, + char **dest_fs_type, + GList **fallbacks) +{ + CommonJob *common; + GList *l; + GFile *src; + gboolean same_fs; + int i; + int total, left; + + common = &job->common; + + total = left = g_list_length (job->files); + + report_preparing_move_progress (job, total, left); + + i = 0; + for (l = job->files; + l != NULL && !job_aborted (common); + l = l->next) + { + src = l->data; + + same_fs = FALSE; + if (dest_fs_id) + { + same_fs = has_fs_id (src, dest_fs_id); + } + + move_file_prepare (job, src, job->destination, + same_fs, dest_fs_type, + job->debuting_files, + fallbacks, + left); + report_preparing_move_progress (job, total, --left); + i++; + } + + *fallbacks = g_list_reverse (*fallbacks); +} + +static void +move_files (CopyMoveJob *job, + GList *fallbacks, + const char *dest_fs_id, + char **dest_fs_type, + SourceInfo *source_info, + TransferInfo *transfer_info) +{ + CommonJob *common; + GList *l; + GFile *src; + gboolean same_fs; + int i; + gboolean skipped_file; + MoveFileCopyFallback *fallback; + common = &job->common; + + report_copy_progress (job, source_info, transfer_info); + + i = 0; + for (l = fallbacks; + l != NULL && !job_aborted (common); + l = l->next) + { + fallback = l->data; + src = fallback->file; + + same_fs = FALSE; + if (dest_fs_id) + { + same_fs = has_fs_id (src, dest_fs_id); + } + + /* Set overwrite to true, as the user has + * selected overwrite on all toplevel items */ + skipped_file = FALSE; + copy_move_file (job, src, job->destination, + same_fs, FALSE, dest_fs_type, + source_info, transfer_info, + job->debuting_files, + fallback->overwrite, &skipped_file, FALSE); + i++; + + if (skipped_file) + { + source_info_remove_file_from_count (src, common, source_info); + report_copy_progress (job, source_info, transfer_info); + } + } +} + + +static void +move_task_done (GObject *source_object, + GAsyncResult *res, + gpointer user_data) +{ + CopyMoveJob *job; + + job = user_data; + if (job->done_callback) + { + job->done_callback (job->debuting_files, + !job_aborted ((CommonJob *) job), + job->done_callback_data); + } + + g_list_free_full (job->files, g_object_unref); + g_object_unref (job->destination); + g_hash_table_unref (job->debuting_files); + + finalize_common ((CommonJob *) job); + + nautilus_file_changes_consume_changes (TRUE); +} + +static CopyMoveJob * +move_job_setup (GList *files, + GFile *target_dir, + GtkWindow *parent_window, + NautilusFileOperationsDBusData *dbus_data, + NautilusCopyCallback done_callback, + gpointer done_callback_data) +{ + CopyMoveJob *job; + + job = op_job_new (CopyMoveJob, parent_window, dbus_data); + job->is_move = TRUE; + job->done_callback = done_callback; + job->done_callback_data = done_callback_data; + job->files = g_list_copy_deep (files, (GCopyFunc) g_object_ref, NULL); + job->destination = g_object_ref (target_dir); + + /* Need to indicate the destination for the operation notification open + * button. */ + nautilus_progress_info_set_destination (((CommonJob *) job)->progress, job->destination); + job->debuting_files = g_hash_table_new_full (g_file_hash, (GEqualFunc) g_file_equal, g_object_unref, NULL); + + return job; +} + +void +nautilus_file_operations_move_sync (GList *files, + GFile *target_dir) +{ + GTask *task; + CopyMoveJob *job; + + job = move_job_setup (files, target_dir, NULL, NULL, NULL, NULL); + task = g_task_new (NULL, job->common.cancellable, NULL, job); + g_task_set_task_data (task, job, NULL); + g_task_run_in_thread_sync (task, nautilus_file_operations_move); + g_object_unref (task); + /* Since g_task_run_in_thread_sync doesn't work with callbacks (in this case not reaching + * move_task_done) we need to set up the undo information ourselves. + */ + move_task_done (NULL, NULL, job); +} + +void +nautilus_file_operations_move_async (GList *files, + GFile *target_dir, + GtkWindow *parent_window, + NautilusFileOperationsDBusData *dbus_data, + NautilusCopyCallback done_callback, + gpointer done_callback_data) +{ + GTask *task; + CopyMoveJob *job; + + job = move_job_setup (files, + target_dir, + parent_window, + dbus_data, + done_callback, + done_callback_data); + + task = g_task_new (NULL, job->common.cancellable, move_task_done, job); + g_task_set_task_data (task, job, NULL); + g_task_run_in_thread (task, nautilus_file_operations_move); + g_object_unref (task); +} + +static void +nautilus_file_operations_move (GTask *task, + gpointer source_object, + gpointer task_data, + GCancellable *cancellable) +{ + CopyMoveJob *job; + CommonJob *common; + GList *fallbacks; + g_auto (SourceInfo) source_info = SOURCE_INFO_INIT; + TransferInfo transfer_info; + g_autofree char *dest_fs_id = NULL; + g_autofree char *dest_fs_type = NULL; + GList *fallback_files; + + job = task_data; + + /* Since we never initiate the app (in the case of + * testing), we can't inhibit its power manager. + * This would terminate the testing. So we avoid + * doing this by checking if the RUNNING_TESTS + * environment variable is set to "TRUE". + */ + if (g_strcmp0 (g_getenv ("RUNNING_TESTS"), "TRUE")) + { + inhibit_power_manager ((CommonJob *) job, _("Moving Files")); + } + + if (!nautilus_file_undo_manager_is_operating ()) + { + g_autoptr (GFile) src_dir = NULL; + + src_dir = g_file_get_parent ((job->files)->data); + + if (g_file_has_uri_scheme (g_list_first (job->files)->data, "trash")) + { + job->common.undo_info = nautilus_file_undo_info_ext_new (NAUTILUS_FILE_UNDO_OP_RESTORE_FROM_TRASH, + g_list_length (job->files), + src_dir, job->destination); + } + else + { + job->common.undo_info = nautilus_file_undo_info_ext_new (NAUTILUS_FILE_UNDO_OP_MOVE, + g_list_length (job->files), + src_dir, job->destination); + } + } + + common = &job->common; + + nautilus_progress_info_start (job->common.progress); + + fallbacks = NULL; + + verify_destination (&job->common, + job->destination, + &dest_fs_id, + -1); + if (job_aborted (common)) + { + goto aborted; + } + + /* This moves all files that we can do without copy + delete */ + move_files_prepare (job, dest_fs_id, &dest_fs_type, &fallbacks); + if (job_aborted (common)) + { + goto aborted; + } + + if (fallbacks == NULL) + { + gint total; + + total = g_list_length (job->files); + + memset (&source_info, 0, sizeof (source_info)); + source_info.num_files = total; + memset (&transfer_info, 0, sizeof (transfer_info)); + transfer_info.num_files = total; + report_copy_progress (job, &source_info, &transfer_info); + + return; + } + + /* The rest we need to do deep copy + delete behind on, + * so scan for size */ + + fallback_files = get_files_from_fallbacks (fallbacks); + scan_sources (fallback_files, + &source_info, + common, + OP_KIND_MOVE); + + g_list_free (fallback_files); + + if (job_aborted (common)) + { + goto aborted; + } + + verify_destination (&job->common, + job->destination, + NULL, + source_info.num_bytes); + if (job_aborted (common)) + { + goto aborted; + } + + memset (&transfer_info, 0, sizeof (transfer_info)); + move_files (job, + fallbacks, + dest_fs_id, &dest_fs_type, + &source_info, &transfer_info); + +aborted: + g_list_free_full (fallbacks, g_free); +} + +static void +report_preparing_link_progress (CopyMoveJob *link_job, + int total, + int left) +{ + CommonJob *job; + g_autofree gchar *basename = NULL; + + job = (CommonJob *) link_job; + basename = get_basename (link_job->destination); + nautilus_progress_info_take_status (job->progress, + g_strdup_printf (_("Creating links in “%s”"), + basename)); + + nautilus_progress_info_take_details (job->progress, + g_strdup_printf (ngettext ("Making link to %'d file", + "Making links to %'d files", + left), + left)); + + nautilus_progress_info_set_progress (job->progress, left, total); +} + +static char * +get_abs_path_for_symlink (GFile *file, + GFile *destination) +{ + GFile *root, *parent; + char *relative, *abs; + + if (g_file_is_native (file) || g_file_is_native (destination)) + { + return g_file_get_path (file); + } + + root = g_object_ref (file); + while ((parent = g_file_get_parent (root)) != NULL) + { + g_object_unref (root); + root = parent; + } + + relative = g_file_get_relative_path (root, file); + g_object_unref (root); + abs = g_strconcat ("/", relative, NULL); + g_free (relative); + return abs; +} + + +static void +link_file (CopyMoveJob *job, + GFile *src, + GFile *dest_dir, + char **dest_fs_type, + GHashTable *debuting_files, + int files_left) +{ + GFile *src_dir; + GFile *new_dest; + g_autoptr (GFile) dest = NULL; + g_autofree gchar *dest_uri = NULL; + int count; + char *path; + gboolean not_local; + GError *error; + CommonJob *common; + char *primary, *secondary, *details; + int response; + gboolean handled_invalid_filename; + + common = (CommonJob *) job; + + count = 0; + + src_dir = g_file_get_parent (src); + if (g_file_equal (src_dir, dest_dir)) + { + count = 1; + } + g_object_unref (src_dir); + + handled_invalid_filename = *dest_fs_type != NULL; + + dest = get_target_file_for_link (src, dest_dir, *dest_fs_type, count); + +retry: + error = NULL; + not_local = FALSE; + + path = get_abs_path_for_symlink (src, dest); + if (path == NULL) + { + not_local = TRUE; + } + else if (g_file_make_symbolic_link (dest, + path, + common->cancellable, + &error)) + { + if (common->undo_info != NULL) + { + nautilus_file_undo_info_ext_add_origin_target_pair (NAUTILUS_FILE_UNDO_INFO_EXT (common->undo_info), + src, dest); + } + + g_free (path); + if (debuting_files) + { + g_hash_table_replace (debuting_files, g_object_ref (dest), GINT_TO_POINTER (TRUE)); + } + + nautilus_file_changes_queue_file_added (dest); + dest_uri = g_file_get_uri (dest); + + return; + } + g_free (path); + + if (error != NULL && + IS_IO_ERROR (error, INVALID_FILENAME) && + !handled_invalid_filename) + { + handled_invalid_filename = TRUE; + + g_assert (*dest_fs_type == NULL); + *dest_fs_type = query_fs_type (dest_dir, common->cancellable); + + new_dest = get_target_file_for_link (src, dest_dir, *dest_fs_type, count); + + if (!g_file_equal (dest, new_dest)) + { + g_object_unref (dest); + dest = new_dest; + g_error_free (error); + + goto retry; + } + else + { + g_object_unref (new_dest); + } + } + /* Conflict */ + if (error != NULL && IS_IO_ERROR (error, EXISTS)) + { + g_object_unref (dest); + dest = get_target_file_for_link (src, dest_dir, *dest_fs_type, count++); + g_error_free (error); + goto retry; + } + else if (error != NULL && IS_IO_ERROR (error, CANCELLED)) + { + g_error_free (error); + } + /* Other error */ + else if (error != NULL) + { + g_autofree gchar *basename = NULL; + + if (common->skip_all_error) + { + return; + } + basename = get_basename (src); + primary = g_strdup_printf (_("Error while creating link to %s."), + basename); + if (not_local) + { + secondary = g_strdup (_("Symbolic links only supported for local files")); + details = NULL; + } + else if (IS_IO_ERROR (error, NOT_SUPPORTED)) + { + secondary = g_strdup (_("The target doesn’t support symbolic links.")); + details = NULL; + } + else + { + g_autofree gchar *filename = NULL; + + filename = get_truncated_parse_name (dest_dir); + secondary = g_strdup_printf (_("There was an error creating the symlink in %s."), + filename); + details = error->message; + } + + response = run_warning (common, + primary, + secondary, + details, + files_left > 1, + CANCEL, SKIP_ALL, SKIP, + NULL); + + if (error) + { + g_error_free (error); + } + + if (response == 0 || response == GTK_RESPONSE_DELETE_EVENT) + { + abort_job (common); + } + else if (response == 1) /* skip all */ + { + common->skip_all_error = TRUE; + } + else if (response == 2) /* skip */ + { /* do nothing */ + } + else + { + g_assert_not_reached (); + } + } +} + +static void +link_task_done (GObject *source_object, + GAsyncResult *res, + gpointer user_data) +{ + CopyMoveJob *job; + + job = user_data; + if (job->done_callback) + { + job->done_callback (job->debuting_files, + !job_aborted ((CommonJob *) job), + job->done_callback_data); + } + + g_list_free_full (job->files, g_object_unref); + g_object_unref (job->destination); + g_hash_table_unref (job->debuting_files); + + finalize_common ((CommonJob *) job); + + nautilus_file_changes_consume_changes (TRUE); +} + +static void +link_task_thread_func (GTask *task, + gpointer source_object, + gpointer task_data, + GCancellable *cancellable) +{ + CopyMoveJob *job; + CommonJob *common; + GFile *src; + g_autofree char *dest_fs_type = NULL; + int total, left; + int i; + GList *l; + + job = task_data; + common = &job->common; + + nautilus_progress_info_start (job->common.progress); + + verify_destination (&job->common, + job->destination, + NULL, + -1); + if (job_aborted (common)) + { + return; + } + + total = left = g_list_length (job->files); + + report_preparing_link_progress (job, total, left); + + i = 0; + for (l = job->files; + l != NULL && !job_aborted (common); + l = l->next) + { + src = l->data; + + link_file (job, src, job->destination, + &dest_fs_type, job->debuting_files, + left); + report_preparing_link_progress (job, total, --left); + i++; + } +} + +void +nautilus_file_operations_link (GList *files, + GFile *target_dir, + GtkWindow *parent_window, + NautilusFileOperationsDBusData *dbus_data, + NautilusCopyCallback done_callback, + gpointer done_callback_data) +{ + g_autoptr (GTask) task = NULL; + CopyMoveJob *job; + + job = op_job_new (CopyMoveJob, parent_window, dbus_data); + job->done_callback = done_callback; + job->done_callback_data = done_callback_data; + job->files = g_list_copy_deep (files, (GCopyFunc) g_object_ref, NULL); + job->destination = g_object_ref (target_dir); + /* Need to indicate the destination for the operation notification open + * button. */ + nautilus_progress_info_set_destination (((CommonJob *) job)->progress, target_dir); + job->debuting_files = g_hash_table_new_full (g_file_hash, (GEqualFunc) g_file_equal, g_object_unref, NULL); + + if (!nautilus_file_undo_manager_is_operating ()) + { + g_autoptr (GFile) src_dir = NULL; + + src_dir = g_file_get_parent (files->data); + job->common.undo_info = nautilus_file_undo_info_ext_new (NAUTILUS_FILE_UNDO_OP_CREATE_LINK, + g_list_length (files), + src_dir, target_dir); + } + + task = g_task_new (NULL, job->common.cancellable, link_task_done, job); + g_task_set_task_data (task, job, NULL); + g_task_run_in_thread (task, link_task_thread_func); +} + + +void +nautilus_file_operations_duplicate (GList *files, + GtkWindow *parent_window, + NautilusFileOperationsDBusData *dbus_data, + NautilusCopyCallback done_callback, + gpointer done_callback_data) +{ + g_autoptr (GTask) task = NULL; + CopyMoveJob *job; + g_autoptr (GFile) parent = NULL; + + job = op_job_new (CopyMoveJob, parent_window, dbus_data); + job->done_callback = done_callback; + job->done_callback_data = done_callback_data; + job->files = g_list_copy_deep (files, (GCopyFunc) g_object_ref, NULL); + job->destination = NULL; + /* Duplicate files doesn't have a destination, since is the same as source. + * For that set as destination the source parent folder */ + parent = g_file_get_parent (files->data); + /* Need to indicate the destination for the operation notification open + * button. */ + nautilus_progress_info_set_destination (((CommonJob *) job)->progress, parent); + job->debuting_files = g_hash_table_new_full (g_file_hash, (GEqualFunc) g_file_equal, g_object_unref, NULL); + + if (!nautilus_file_undo_manager_is_operating ()) + { + g_autoptr (GFile) src_dir = NULL; + + src_dir = g_file_get_parent (files->data); + job->common.undo_info = + nautilus_file_undo_info_ext_new (NAUTILUS_FILE_UNDO_OP_DUPLICATE, + g_list_length (files), + src_dir, src_dir); + } + + task = g_task_new (NULL, job->common.cancellable, copy_task_done, job); + g_task_set_task_data (task, job, NULL); + g_task_run_in_thread (task, nautilus_file_operations_copy); +} + +static void +set_permissions_task_done (GObject *source_object, + GAsyncResult *res, + gpointer user_data) +{ + SetPermissionsJob *job; + + job = user_data; + + g_object_unref (job->file); + + if (job->done_callback) + { + job->done_callback (!job_aborted ((CommonJob *) job), + job->done_callback_data); + } + + finalize_common ((CommonJob *) job); +} + +static void +set_permissions_file (SetPermissionsJob *job, + GFile *file, + GFileInfo *info); + +static void +set_permissions_contained_files (SetPermissionsJob *job, + GFile *file) +{ + CommonJob *common; + GFileEnumerator *enumerator; + + common = (CommonJob *) job; + + enumerator = g_file_enumerate_children (file, + G_FILE_ATTRIBUTE_STANDARD_NAME "," + G_FILE_ATTRIBUTE_STANDARD_TYPE "," + G_FILE_ATTRIBUTE_UNIX_MODE, + G_FILE_QUERY_INFO_NOFOLLOW_SYMLINKS, + common->cancellable, + NULL); + if (enumerator) + { + GFileInfo *child_info; + + while (!job_aborted (common) && + (child_info = g_file_enumerator_next_file (enumerator, common->cancellable, NULL)) != NULL) + { + GFile *child; + + child = g_file_get_child (file, + g_file_info_get_name (child_info)); + set_permissions_file (job, child, child_info); + g_object_unref (child); + g_object_unref (child_info); + } + g_file_enumerator_close (enumerator, common->cancellable, NULL); + g_object_unref (enumerator); + } +} + +static void +set_permissions_file (SetPermissionsJob *job, + GFile *file, + GFileInfo *info) +{ + CommonJob *common; + gboolean free_info; + guint32 current; + guint32 value; + guint32 mask; + + common = (CommonJob *) job; + + nautilus_progress_info_pulse_progress (common->progress); + + free_info = FALSE; + if (info == NULL) + { + free_info = TRUE; + info = g_file_query_info (file, + G_FILE_ATTRIBUTE_STANDARD_TYPE "," + G_FILE_ATTRIBUTE_UNIX_MODE, + G_FILE_QUERY_INFO_NOFOLLOW_SYMLINKS, + common->cancellable, + NULL); + /* Ignore errors */ + if (info == NULL) + { + return; + } + } + + if (g_file_info_get_file_type (info) == G_FILE_TYPE_DIRECTORY) + { + value = job->dir_permissions; + mask = job->dir_mask; + } + else + { + value = job->file_permissions; + mask = job->file_mask; + } + + + if (!job_aborted (common) && + g_file_info_has_attribute (info, G_FILE_ATTRIBUTE_UNIX_MODE)) + { + current = g_file_info_get_attribute_uint32 (info, G_FILE_ATTRIBUTE_UNIX_MODE); + + if (common->undo_info != NULL) + { + nautilus_file_undo_info_rec_permissions_add_file (NAUTILUS_FILE_UNDO_INFO_REC_PERMISSIONS (common->undo_info), + file, current); + } + + current = (current & ~mask) | value; + + g_file_set_attribute_uint32 (file, G_FILE_ATTRIBUTE_UNIX_MODE, + current, G_FILE_QUERY_INFO_NOFOLLOW_SYMLINKS, + common->cancellable, NULL); + } + + if (!job_aborted (common) && + g_file_info_get_file_type (info) == G_FILE_TYPE_DIRECTORY) + { + set_permissions_contained_files (job, file); + } + if (free_info) + { + g_object_unref (info); + } +} + +static void +set_permissions_thread_func (GTask *task, + gpointer source_object, + gpointer task_data, + GCancellable *cancellable) +{ + SetPermissionsJob *job = task_data; + CommonJob *common; + + common = (CommonJob *) job; + + nautilus_progress_info_set_status (common->progress, + _("Setting permissions")); + + nautilus_progress_info_start (job->common.progress); + set_permissions_contained_files (job, job->file); +} + +void +nautilus_file_set_permissions_recursive (const char *directory, + guint32 file_permissions, + guint32 file_mask, + guint32 dir_permissions, + guint32 dir_mask, + NautilusOpCallback callback, + gpointer callback_data) +{ + g_autoptr (GTask) task = NULL; + SetPermissionsJob *job; + + job = op_job_new (SetPermissionsJob, NULL, NULL); + job->file = g_file_new_for_uri (directory); + job->file_permissions = file_permissions; + job->file_mask = file_mask; + job->dir_permissions = dir_permissions; + job->dir_mask = dir_mask; + job->done_callback = callback; + job->done_callback_data = callback_data; + + if (!nautilus_file_undo_manager_is_operating ()) + { + job->common.undo_info = + nautilus_file_undo_info_rec_permissions_new (job->file, + file_permissions, file_mask, + dir_permissions, dir_mask); + } + + task = g_task_new (NULL, NULL, set_permissions_task_done, job); + g_task_set_task_data (task, job, NULL); + g_task_run_in_thread (task, set_permissions_thread_func); +} + +static GList * +location_list_from_uri_list (const GList *uris) +{ + const GList *l; + GList *files; + GFile *f; + + files = NULL; + for (l = uris; l != NULL; l = l->next) + { + f = g_file_new_for_uri (l->data); + files = g_list_prepend (files, f); + } + + return g_list_reverse (files); +} + +typedef struct +{ + NautilusCopyCallback real_callback; + gpointer real_data; +} MoveTrashCBData; + +static void +callback_for_move_to_trash (GHashTable *debuting_uris, + gboolean user_cancelled, + MoveTrashCBData *data) +{ + if (data->real_callback) + { + data->real_callback (debuting_uris, !user_cancelled, data->real_data); + } + g_slice_free (MoveTrashCBData, data); +} + +void +nautilus_file_operations_copy_move (const GList *item_uris, + const char *target_dir, + GdkDragAction copy_action, + GtkWidget *parent_view, + NautilusFileOperationsDBusData *dbus_data, + NautilusCopyCallback done_callback, + gpointer done_callback_data) +{ + GList *locations; + GList *p; + GFile *dest, *src_dir; + GtkWindow *parent_window; + gboolean target_is_mapping; + gboolean have_nonmapping_source; + + dest = NULL; + target_is_mapping = FALSE; + have_nonmapping_source = FALSE; + + if (target_dir) + { + dest = g_file_new_for_uri (target_dir); + if (g_file_has_uri_scheme (dest, "burn")) + { + target_is_mapping = TRUE; + } + } + + locations = location_list_from_uri_list (item_uris); + + for (p = locations; p != NULL; p = p->next) + { + if (!g_file_has_uri_scheme ((GFile * ) p->data, "burn")) + { + have_nonmapping_source = TRUE; + } + } + + if (target_is_mapping && have_nonmapping_source && copy_action == GDK_ACTION_MOVE) + { + /* never move to "burn:///", but fall back to copy. + * This is a workaround, because otherwise the source files would be removed. + */ + copy_action = GDK_ACTION_COPY; + } + + parent_window = NULL; + if (parent_view) + { + parent_window = (GtkWindow *) gtk_widget_get_ancestor (parent_view, GTK_TYPE_WINDOW); + } + + if (g_file_has_uri_scheme (dest, "starred")) + { + g_autolist (NautilusFile) source_file_list = NULL; + + for (GList *l = locations; l != NULL; l = l->next) + { + source_file_list = g_list_prepend (source_file_list, nautilus_file_get (l->data)); + } + + source_file_list = g_list_reverse (source_file_list); + nautilus_tag_manager_star_files (nautilus_tag_manager_get (), + G_OBJECT (parent_view), + source_file_list, NULL, NULL); + } + else if (copy_action == GDK_ACTION_COPY) + { + src_dir = g_file_get_parent (locations->data); + if (target_dir == NULL || + (src_dir != NULL && + g_file_equal (src_dir, dest))) + { + nautilus_file_operations_duplicate (locations, + parent_window, + dbus_data, + done_callback, done_callback_data); + } + else + { + nautilus_file_operations_copy_async (locations, + dest, + parent_window, + dbus_data, + done_callback, done_callback_data); + } + if (src_dir) + { + g_object_unref (src_dir); + } + } + else if (copy_action == GDK_ACTION_MOVE) + { + if (g_file_has_uri_scheme (dest, "trash")) + { + MoveTrashCBData *cb_data; + + cb_data = g_slice_new0 (MoveTrashCBData); + cb_data->real_callback = done_callback; + cb_data->real_data = done_callback_data; + + nautilus_file_operations_trash_or_delete_async (locations, + parent_window, + dbus_data, + (NautilusDeleteCallback) callback_for_move_to_trash, + cb_data); + } + else + { + nautilus_file_operations_move_async (locations, + dest, + parent_window, + dbus_data, + done_callback, done_callback_data); + } + } + else + { + nautilus_file_operations_link (locations, + dest, + parent_window, + dbus_data, + done_callback, done_callback_data); + } + + g_list_free_full (locations, g_object_unref); + if (dest) + { + g_object_unref (dest); + } +} + +static void +create_task_done (GObject *source_object, + GAsyncResult *res, + gpointer user_data) +{ + CreateJob *job; + + job = user_data; + if (job->done_callback) + { + job->done_callback (job->created_file, + !job_aborted ((CommonJob *) job), + job->done_callback_data); + } + + g_object_unref (job->dest_dir); + if (job->src) + { + g_object_unref (job->src); + } + g_free (job->src_data); + g_free (job->filename); + if (job->created_file) + { + g_object_unref (job->created_file); + } + + finalize_common ((CommonJob *) job); + + nautilus_file_changes_consume_changes (TRUE); +} + +static void +create_task_thread_func (GTask *task, + gpointer source_object, + gpointer task_data, + GCancellable *cancellable) +{ + CreateJob *job; + CommonJob *common; + int count; + g_autoptr (GFile) dest = NULL; + g_autofree gchar *dest_uri = NULL; + g_autofree char *filename = NULL; + char *filename_base; + g_autofree char *dest_fs_type = NULL; + GError *error; + gboolean res; + gboolean filename_is_utf8; + char *primary, *secondary, *details; + int response; + char *data; + int length; + GFileOutputStream *out; + gboolean handled_invalid_filename; + int max_length, offset; + + job = task_data; + common = &job->common; + + nautilus_progress_info_start (job->common.progress); + + handled_invalid_filename = FALSE; + + max_length = nautilus_get_max_child_name_length_for_location (job->dest_dir); + + verify_destination (common, + job->dest_dir, + NULL, -1); + if (job_aborted (common)) + { + return; + } + + filename = g_strdup (job->filename); + filename_is_utf8 = FALSE; + if (filename) + { + filename_is_utf8 = g_utf8_validate (filename, -1, NULL); + } + if (filename == NULL) + { + if (job->make_dir) + { + /* localizers: the initial name of a new folder */ + filename = g_strdup (_("Untitled Folder")); + filename_is_utf8 = TRUE; /* Pass in utf8 */ + } + else + { + if (job->src != NULL) + { + g_autofree char *basename = NULL; + + basename = g_file_get_basename (job->src); + filename = g_strdup_printf ("%s", basename); + } + if (filename == NULL) + { + /* localizers: the initial name of a new empty document */ + filename = g_strdup (_("Untitled Document")); + filename_is_utf8 = TRUE; /* Pass in utf8 */ + } + } + } + + make_file_name_valid_for_dest_fs (filename, dest_fs_type); + if (filename_is_utf8) + { + dest = g_file_get_child_for_display_name (job->dest_dir, filename, NULL); + } + if (dest == NULL) + { + dest = g_file_get_child (job->dest_dir, filename); + } + count = 1; + +retry: + + error = NULL; + if (job->make_dir) + { + res = g_file_make_directory (dest, + common->cancellable, + &error); + + if (res) + { + GFile *real; + + real = map_possibly_volatile_file_to_real (dest, common->cancellable, &error); + if (real == NULL) + { + res = FALSE; + } + else + { + g_object_unref (dest); + dest = real; + } + } + + if (res && common->undo_info != NULL) + { + nautilus_file_undo_info_create_set_data (NAUTILUS_FILE_UNDO_INFO_CREATE (common->undo_info), + dest, NULL, 0); + } + } + else + { + if (job->src) + { + res = g_file_copy (job->src, + dest, + G_FILE_COPY_TARGET_DEFAULT_PERMS, + common->cancellable, + NULL, NULL, + &error); + + if (res) + { + GFile *real; + + real = map_possibly_volatile_file_to_real (dest, common->cancellable, &error); + if (real == NULL) + { + res = FALSE; + } + else + { + g_object_unref (dest); + dest = real; + } + } + + if (res && common->undo_info != NULL) + { + g_autofree gchar *uri = NULL; + + uri = g_file_get_uri (job->src); + nautilus_file_undo_info_create_set_data (NAUTILUS_FILE_UNDO_INFO_CREATE (common->undo_info), + dest, uri, 0); + } + } + else + { + data = ""; + length = 0; + if (job->src_data) + { + data = job->src_data; + length = job->length; + } + + out = g_file_create (dest, + G_FILE_CREATE_NONE, + common->cancellable, + &error); + if (out) + { + GFile *real; + + real = map_possibly_volatile_file_to_real_on_write (dest, + out, + common->cancellable, + &error); + if (real == NULL) + { + res = FALSE; + g_object_unref (out); + } + else + { + g_object_unref (dest); + dest = real; + + res = g_output_stream_write_all (G_OUTPUT_STREAM (out), + data, length, + NULL, + common->cancellable, + &error); + if (res) + { + res = g_output_stream_close (G_OUTPUT_STREAM (out), + common->cancellable, + &error); + + if (res && common->undo_info != NULL) + { + nautilus_file_undo_info_create_set_data (NAUTILUS_FILE_UNDO_INFO_CREATE (common->undo_info), + dest, data, length); + } + } + + /* This will close if the write failed and we didn't close */ + g_object_unref (out); + } + } + else + { + res = FALSE; + } + } + } + + if (res) + { + job->created_file = g_object_ref (dest); + nautilus_file_changes_queue_file_added (dest); + dest_uri = g_file_get_uri (dest); + gtk_recent_manager_add_item (gtk_recent_manager_get_default (), dest_uri); + } + else + { + g_assert (error != NULL); + + if (IS_IO_ERROR (error, INVALID_FILENAME) && + !handled_invalid_filename) + { + g_autofree gchar *new_filename = NULL; + + handled_invalid_filename = TRUE; + + g_assert (dest_fs_type == NULL); + dest_fs_type = query_fs_type (job->dest_dir, common->cancellable); + + if (count == 1) + { + new_filename = g_strdup (filename); + } + else + { + g_autofree char *filename2 = NULL; + g_autofree char *suffix = NULL; + + filename_base = filename; + if (job->src != NULL) + { + g_autoptr (NautilusFile) file = NULL; + file = nautilus_file_get (job->src); + if (!nautilus_file_is_directory (file)) + { + filename_base = eel_filename_strip_extension (filename); + } + } + + offset = strlen (filename_base); + suffix = g_strdup (filename + offset); + + filename2 = g_strdup_printf ("%s %d%s", filename_base, count, suffix); + + new_filename = NULL; + if (max_length > 0 && strlen (filename2) > max_length) + { + new_filename = shorten_utf8_string (filename2, strlen (filename2) - max_length); + } + + if (new_filename == NULL) + { + new_filename = g_strdup (filename2); + } + } + + if (make_file_name_valid_for_dest_fs (new_filename, dest_fs_type)) + { + g_object_unref (dest); + + if (filename_is_utf8) + { + dest = g_file_get_child_for_display_name (job->dest_dir, new_filename, NULL); + } + if (dest == NULL) + { + dest = g_file_get_child (job->dest_dir, new_filename); + } + + g_error_free (error); + goto retry; + } + } + + if (IS_IO_ERROR (error, EXISTS)) + { + g_autofree char *suffix = NULL; + g_autofree gchar *filename2 = NULL; + + g_clear_object (&dest); + + filename_base = filename; + if (job->src != NULL) + { + g_autoptr (NautilusFile) file = NULL; + + file = nautilus_file_get (job->src); + if (!nautilus_file_is_directory (file)) + { + filename_base = eel_filename_strip_extension (filename); + } + } + + + offset = strlen (filename_base); + suffix = g_strdup (filename + offset); + + filename2 = g_strdup_printf ("%s %d%s", filename_base, ++count, suffix); + + if (max_length > 0 && strlen (filename2) > max_length) + { + g_autofree char *new_filename = NULL; + + new_filename = shorten_utf8_string (filename2, strlen (filename2) - max_length); + if (new_filename != NULL) + { + g_free (filename2); + filename2 = new_filename; + } + } + + make_file_name_valid_for_dest_fs (filename2, dest_fs_type); + if (filename_is_utf8) + { + dest = g_file_get_child_for_display_name (job->dest_dir, filename2, NULL); + } + if (dest == NULL) + { + dest = g_file_get_child (job->dest_dir, filename2); + } + g_error_free (error); + goto retry; + } + else if (IS_IO_ERROR (error, CANCELLED)) + { + g_error_free (error); + } + /* Other error */ + else + { + g_autofree gchar *basename = NULL; + g_autofree gchar *parse_name = NULL; + + basename = get_basename (dest); + if (job->make_dir) + { + primary = g_strdup_printf (_("Error while creating directory %s."), + basename); + } + else + { + primary = g_strdup_printf (_("Error while creating file %s."), + basename); + } + parse_name = get_truncated_parse_name (job->dest_dir); + secondary = g_strdup_printf (_("There was an error creating the directory in %s."), + parse_name); + + details = error->message; + + response = run_warning (common, + primary, + secondary, + details, + FALSE, + CANCEL, SKIP, + NULL); + + g_error_free (error); + + if (response == 0 || response == GTK_RESPONSE_DELETE_EVENT) + { + abort_job (common); + } + else if (response == 1) /* skip */ + { /* do nothing */ + } + else + { + g_assert_not_reached (); + } + } + } +} + +void +nautilus_file_operations_new_folder (GtkWidget *parent_view, + NautilusFileOperationsDBusData *dbus_data, + const char *parent_dir, + const char *folder_name, + NautilusCreateCallback done_callback, + gpointer done_callback_data) +{ + g_autoptr (GTask) task = NULL; + CreateJob *job; + GtkWindow *parent_window; + + parent_window = NULL; + if (parent_view) + { + parent_window = (GtkWindow *) gtk_widget_get_ancestor (parent_view, GTK_TYPE_WINDOW); + } + + job = op_job_new (CreateJob, parent_window, dbus_data); + job->done_callback = done_callback; + job->done_callback_data = done_callback_data; + job->dest_dir = g_file_new_for_uri (parent_dir); + job->filename = g_strdup (folder_name); + job->make_dir = TRUE; + + if (!nautilus_file_undo_manager_is_operating ()) + { + job->common.undo_info = nautilus_file_undo_info_create_new (NAUTILUS_FILE_UNDO_OP_CREATE_FOLDER); + } + + task = g_task_new (NULL, job->common.cancellable, create_task_done, job); + g_task_set_task_data (task, job, NULL); + g_task_run_in_thread (task, create_task_thread_func); +} + +void +nautilus_file_operations_new_file_from_template (GtkWidget *parent_view, + const char *parent_dir, + const char *target_filename, + const char *template_uri, + NautilusCreateCallback done_callback, + gpointer done_callback_data) +{ + g_autoptr (GTask) task = NULL; + CreateJob *job; + GtkWindow *parent_window; + + parent_window = NULL; + if (parent_view) + { + parent_window = (GtkWindow *) gtk_widget_get_ancestor (parent_view, GTK_TYPE_WINDOW); + } + + job = op_job_new (CreateJob, parent_window, NULL); + job->done_callback = done_callback; + job->done_callback_data = done_callback_data; + job->dest_dir = g_file_new_for_uri (parent_dir); + job->filename = g_strdup (target_filename); + + if (template_uri) + { + job->src = g_file_new_for_uri (template_uri); + } + + if (!nautilus_file_undo_manager_is_operating ()) + { + job->common.undo_info = nautilus_file_undo_info_create_new (NAUTILUS_FILE_UNDO_OP_CREATE_FILE_FROM_TEMPLATE); + } + + task = g_task_new (NULL, job->common.cancellable, create_task_done, job); + g_task_set_task_data (task, job, NULL); + g_task_run_in_thread (task, create_task_thread_func); +} + +void +nautilus_file_operations_new_file (GtkWidget *parent_view, + const char *parent_dir, + const char *target_filename, + const char *initial_contents, + int length, + NautilusCreateCallback done_callback, + gpointer done_callback_data) +{ + g_autoptr (GTask) task = NULL; + CreateJob *job; + GtkWindow *parent_window; + + parent_window = NULL; + if (parent_view) + { + parent_window = (GtkWindow *) gtk_widget_get_ancestor (parent_view, GTK_TYPE_WINDOW); + } + + job = op_job_new (CreateJob, parent_window, NULL); + job->done_callback = done_callback; + job->done_callback_data = done_callback_data; + job->dest_dir = g_file_new_for_uri (parent_dir); + job->src_data = g_memdup (initial_contents, length); + job->length = length; + job->filename = g_strdup (target_filename); + + if (!nautilus_file_undo_manager_is_operating ()) + { + job->common.undo_info = nautilus_file_undo_info_create_new (NAUTILUS_FILE_UNDO_OP_CREATE_EMPTY_FILE); + } + + task = g_task_new (NULL, job->common.cancellable, create_task_done, job); + g_task_set_task_data (task, job, NULL); + g_task_run_in_thread (task, create_task_thread_func); +} + + + +static void +delete_trash_file (CommonJob *job, + GFile *file, + gboolean del_file, + gboolean del_children) +{ + GFileInfo *info; + GFile *child; + GFileEnumerator *enumerator; + + if (job_aborted (job)) + { + return; + } + + if (del_children) + { + gboolean should_recurse; + + /* The g_file_delete operation works differently for locations provided + * by the trash backend as it prevents modifications of trashed items + * For that reason, it is enough to call g_file_delete on top-level + * items only. + */ + should_recurse = !g_file_has_uri_scheme (file, "trash"); + + enumerator = g_file_enumerate_children (file, + G_FILE_ATTRIBUTE_STANDARD_NAME "," + G_FILE_ATTRIBUTE_STANDARD_TYPE, + G_FILE_QUERY_INFO_NOFOLLOW_SYMLINKS, + job->cancellable, + NULL); + if (enumerator) + { + while (!job_aborted (job) && + (info = g_file_enumerator_next_file (enumerator, job->cancellable, NULL)) != NULL) + { + gboolean is_dir; + + child = g_file_get_child (file, + g_file_info_get_name (info)); + is_dir = (g_file_info_get_file_type (info) == G_FILE_TYPE_DIRECTORY); + + delete_trash_file (job, child, TRUE, should_recurse && is_dir); + g_object_unref (child); + g_object_unref (info); + } + g_file_enumerator_close (enumerator, job->cancellable, NULL); + g_object_unref (enumerator); + } + } + + if (!job_aborted (job) && del_file) + { + g_file_delete (file, job->cancellable, NULL); + } +} + +static void +empty_trash_task_done (GObject *source_object, + GAsyncResult *res, + gpointer user_data) +{ + EmptyTrashJob *job; + + job = user_data; + + g_list_free_full (job->trash_dirs, g_object_unref); + + if (job->done_callback) + { + job->done_callback (!job_aborted ((CommonJob *) job), + job->done_callback_data); + } + + finalize_common ((CommonJob *) job); +} + +static void +empty_trash_thread_func (GTask *task, + gpointer source_object, + gpointer task_data, + GCancellable *cancellable) +{ + EmptyTrashJob *job = task_data; + CommonJob *common; + GList *l; + gboolean confirmed; + + common = (CommonJob *) job; + + nautilus_progress_info_start (job->common.progress); + + if (job->should_confirm) + { + confirmed = confirm_empty_trash (common); + } + else + { + confirmed = TRUE; + } + if (confirmed) + { + for (l = job->trash_dirs; + l != NULL && !job_aborted (common); + l = l->next) + { + delete_trash_file (common, l->data, FALSE, TRUE); + } + } +} + +void +nautilus_file_operations_empty_trash (GtkWidget *parent_view, + gboolean ask_confirmation, + NautilusFileOperationsDBusData *dbus_data) +{ + g_autoptr (GTask) task = NULL; + EmptyTrashJob *job; + GtkWindow *parent_window; + + parent_window = NULL; + if (parent_view) + { + parent_window = (GtkWindow *) gtk_widget_get_ancestor (parent_view, GTK_TYPE_WINDOW); + } + + job = op_job_new (EmptyTrashJob, parent_window, dbus_data); + job->trash_dirs = g_list_prepend (job->trash_dirs, + g_file_new_for_uri ("trash:")); + job->should_confirm = ask_confirmation; + + inhibit_power_manager ((CommonJob *) job, _("Emptying Trash")); + + task = g_task_new (NULL, NULL, empty_trash_task_done, job); + g_task_set_task_data (task, job, NULL); + g_task_run_in_thread (task, empty_trash_thread_func); +} + +static void +extract_task_done (GObject *source_object, + GAsyncResult *res, + gpointer user_data) +{ + ExtractJob *extract_job; + + extract_job = user_data; + + if (extract_job->done_callback) + { + extract_job->done_callback (extract_job->output_files, + extract_job->done_callback_data); + } + + g_list_free_full (extract_job->source_files, g_object_unref); + g_list_free_full (extract_job->output_files, g_object_unref); + g_object_unref (extract_job->destination_directory); + + finalize_common ((CommonJob *) extract_job); + + nautilus_file_changes_consume_changes (TRUE); +} + +static GFile * +extract_job_on_decide_destination (AutoarExtractor *extractor, + GFile *destination, + GList *files, + gpointer user_data) +{ + ExtractJob *extract_job = user_data; + GFile *decided_destination; + g_autofree char *basename = NULL; + + nautilus_progress_info_set_details (extract_job->common.progress, + _("Verifying destination")); + + basename = g_file_get_basename (destination); + decided_destination = nautilus_generate_unique_file_in_directory (extract_job->destination_directory, + basename); + + if (job_aborted ((CommonJob *) extract_job)) + { + g_object_unref (decided_destination); + return NULL; + } + + /* The extract_job->destination_decided variable signalizes whether the + * extract_job->output_files list already contains the final location as + * its first link. There is no way to get this over the AutoarExtractor + * API currently. + */ + extract_job->output_files = g_list_prepend (extract_job->output_files, + decided_destination); + extract_job->destination_decided = TRUE; + + return g_object_ref (decided_destination); +} + +static void +extract_job_on_progress (AutoarExtractor *extractor, + guint64 archive_current_decompressed_size, + guint archive_current_decompressed_files, + gpointer user_data) +{ + ExtractJob *extract_job = user_data; + CommonJob *common = user_data; + GFile *source_file; + char *details; + double elapsed; + double transfer_rate; + int remaining_time; + guint64 archive_total_decompressed_size; + gdouble archive_weight; + gdouble archive_decompress_progress; + guint64 job_completed_size; + gdouble job_progress; + g_autofree gchar *basename = NULL; + g_autofree gchar *formatted_size_job_completed_size = NULL; + g_autofree gchar *formatted_size_total_compressed_size = NULL; + + source_file = autoar_extractor_get_source_file (extractor); + + basename = get_basename (source_file); + nautilus_progress_info_take_status (common->progress, + g_strdup_printf (_("Extracting “%s”"), + basename)); + + archive_total_decompressed_size = autoar_extractor_get_total_size (extractor); + + archive_decompress_progress = (gdouble) archive_current_decompressed_size / + (gdouble) archive_total_decompressed_size; + + archive_weight = 0; + if (extract_job->total_compressed_size) + { + archive_weight = (gdouble) extract_job->archive_compressed_size / + (gdouble) extract_job->total_compressed_size; + } + + job_progress = archive_decompress_progress * archive_weight + extract_job->base_progress; + + elapsed = g_timer_elapsed (common->time, NULL); + + transfer_rate = 0; + remaining_time = -1; + + job_completed_size = job_progress * extract_job->total_compressed_size; + + if (elapsed > 0) + { + transfer_rate = job_completed_size / elapsed; + } + if (transfer_rate > 0) + { + remaining_time = (extract_job->total_compressed_size - job_completed_size) / + transfer_rate; + } + + formatted_size_job_completed_size = g_format_size (job_completed_size); + formatted_size_total_compressed_size = g_format_size (extract_job->total_compressed_size); + if (elapsed < SECONDS_NEEDED_FOR_RELIABLE_TRANSFER_RATE || + transfer_rate == 0) + { + /* To translators: %s will expand to a size like "2 bytes" or + * "3 MB", so something like "4 kb / 4 MB" + */ + details = g_strdup_printf (_("%s / %s"), formatted_size_job_completed_size, + formatted_size_total_compressed_size); + } + else + { + g_autofree gchar *formatted_time = NULL; + g_autofree gchar *formatted_size_transfer_rate = NULL; + + formatted_time = get_formatted_time (remaining_time); + formatted_size_transfer_rate = g_format_size ((goffset) transfer_rate); + /* To translators: %s will expand to a size like "2 bytes" or + * "3 MB", %s to a time duration like "2 minutes". So the whole + * thing will be something like + * "2 kb / 4 MB -- 2 hours left (4kb/sec)" + * + * The singular/plural form will be used depending on the + * remaining time (i.e. the %s argument). + */ + details = g_strdup_printf (ngettext ("%s / %s \xE2\x80\x94 %s left (%s/sec)", + "%s / %s \xE2\x80\x94 %s left (%s/sec)", + seconds_count_format_time_units (remaining_time)), + formatted_size_job_completed_size, + formatted_size_total_compressed_size, + formatted_time, + formatted_size_transfer_rate); + } + + nautilus_progress_info_take_details (common->progress, details); + + if (elapsed > SECONDS_NEEDED_FOR_APROXIMATE_TRANSFER_RATE) + { + nautilus_progress_info_set_remaining_time (common->progress, + remaining_time); + nautilus_progress_info_set_elapsed_time (common->progress, + elapsed); + } + + nautilus_progress_info_set_progress (common->progress, job_progress, 1); +} + +static void +extract_job_on_error (AutoarExtractor *extractor, + GError *error, + gpointer user_data) +{ + ExtractJob *extract_job = user_data; + GFile *source_file; + GFile *destination; + gint response_id; + gint remaining_files; + g_autofree gchar *basename = NULL; + + source_file = autoar_extractor_get_source_file (extractor); + + if (IS_IO_ERROR (error, NOT_SUPPORTED)) + { + handle_unsupported_compressed_file (extract_job->common.parent_window, + source_file); + + return; + } + + extract_job->extraction_failed = TRUE; + + /* It is safe to use extract_job->output_files->data only when the + * extract_job->destination_decided variable was set, see comment in the + * extract_job_on_decide_destination function. + */ + if (extract_job->destination_decided) + { + destination = extract_job->output_files->data; + delete_file_recursively (destination, NULL, NULL, NULL); + extract_job->output_files = g_list_delete_link (extract_job->output_files, + extract_job->output_files); + g_object_unref (destination); + } + + if (extract_job->common.skip_all_error) + { + return; + } + + basename = get_basename (source_file); + nautilus_progress_info_take_status (extract_job->common.progress, + g_strdup_printf (_("Error extracting “%s”"), + basename)); + + remaining_files = g_list_length (g_list_find_custom (extract_job->source_files, + source_file, + (GCompareFunc) g_file_equal)) - 1; + response_id = run_cancel_or_skip_warning ((CommonJob *) extract_job, + g_strdup_printf (_("There was an error while extracting “%s”."), + basename), + g_strdup (error->message), + NULL, + extract_job->total_files, + remaining_files); + + if (response_id == 0 || response_id == GTK_RESPONSE_DELETE_EVENT) + { + abort_job ((CommonJob *) extract_job); + } + else if (response_id == 1) + { + extract_job->common.skip_all_error = TRUE; + } +} + +static void +extract_job_on_completed (AutoarExtractor *extractor, + gpointer user_data) +{ + ExtractJob *extract_job = user_data; + GFile *output_file; + + output_file = G_FILE (extract_job->output_files->data); + + nautilus_file_changes_queue_file_added (output_file); +} + +static gchar * +extract_job_on_request_passphrase (AutoarExtractor *extractor, + gpointer user_data) +{ + ExtractJob *extract_job = user_data; + GtkWindow *parent_window; + GFile *source_file; + g_autofree gchar *basename = NULL; + gchar *passphrase; + + parent_window = extract_job->common.parent_window; + source_file = autoar_extractor_get_source_file (extractor); + basename = get_basename (source_file); + + passphrase = extract_ask_passphrase (parent_window, basename); + if (passphrase == NULL) + { + abort_job ((CommonJob *) extract_job); + } + + return passphrase; +} + +static void +extract_job_on_scanned (AutoarExtractor *extractor, + guint total_files, + gpointer user_data) +{ + guint64 total_size; + ExtractJob *extract_job; + GFile *source_file; + g_autofree gchar *basename = NULL; + GFileInfo *fsinfo; + guint64 free_size; + + extract_job = user_data; + total_size = autoar_extractor_get_total_size (extractor); + source_file = autoar_extractor_get_source_file (extractor); + basename = get_basename (source_file); + + fsinfo = g_file_query_filesystem_info (source_file, + G_FILE_ATTRIBUTE_FILESYSTEM_FREE "," + G_FILE_ATTRIBUTE_FILESYSTEM_READONLY, + extract_job->common.cancellable, + NULL); + free_size = g_file_info_get_attribute_uint64 (fsinfo, + G_FILE_ATTRIBUTE_FILESYSTEM_FREE); + + /* FIXME: G_MAXUINT64 is the value used by autoar when the file size cannot + * be determined. Ideally an API should be used instead. + */ + if (total_size != G_MAXUINT64 && total_size > free_size) + { + nautilus_progress_info_take_status (extract_job->common.progress, + g_strdup_printf (_("Error extracting “%s”"), + basename)); + run_error (&extract_job->common, + g_strdup_printf (_("Not enough free space to extract %s"), basename), + NULL, + NULL, + FALSE, + CANCEL, + NULL); + + abort_job ((CommonJob *) extract_job); + } +} + +static void +report_extract_final_progress (ExtractJob *extract_job) +{ + char *status; + g_autofree gchar *basename_dest = NULL; + g_autofree gchar *formatted_size = NULL; + + nautilus_progress_info_set_destination (extract_job->common.progress, + extract_job->destination_directory); + basename_dest = get_basename (extract_job->destination_directory); + + /* The g_list_length function is used intentionally here instead of the + * extract_job->total_files variable to avoid printing wrong basename in + * the case of skipped files. + */ + if (g_list_length (extract_job->source_files) == 1) + { + GFile *source_file; + g_autofree gchar *basename = NULL; + + source_file = G_FILE (extract_job->source_files->data); + basename = get_basename (source_file); + status = g_strdup_printf (_("Extracted “%s” to “%s”"), + basename, + basename_dest); + } + else + { + status = g_strdup_printf (ngettext ("Extracted %'d file to “%s”", + "Extracted %'d files to “%s”", + extract_job->total_files), + extract_job->total_files, + basename_dest); + } + + nautilus_progress_info_take_status (extract_job->common.progress, + status); + formatted_size = g_format_size (extract_job->total_compressed_size); + nautilus_progress_info_take_details (extract_job->common.progress, + g_strdup_printf (_("%s / %s"), + formatted_size, + formatted_size)); + + nautilus_progress_info_set_progress (extract_job->common.progress, 1, 1); +} + +static void +extract_task_thread_func (GTask *task, + gpointer source_object, + gpointer task_data, + GCancellable *cancellable) +{ + ExtractJob *extract_job = task_data; + GList *l; + g_autofree guint64 *archive_compressed_sizes = NULL; + gint i; + + g_timer_start (extract_job->common.time); + + nautilus_progress_info_start (extract_job->common.progress); + + nautilus_progress_info_set_details (extract_job->common.progress, + _("Preparing to extract")); + + extract_job->total_files = g_list_length (extract_job->source_files); + + archive_compressed_sizes = g_malloc0_n (extract_job->total_files, + sizeof (guint64)); + extract_job->total_compressed_size = 0; + + for (l = extract_job->source_files, i = 0; + l != NULL && !job_aborted ((CommonJob *) extract_job); + l = l->next, i++) + { + GFile *source_file; + g_autoptr (GFileInfo) info = NULL; + + source_file = G_FILE (l->data); + info = g_file_query_info (source_file, + G_FILE_ATTRIBUTE_STANDARD_SIZE, + G_FILE_QUERY_INFO_NOFOLLOW_SYMLINKS, + extract_job->common.cancellable, + NULL); + + if (info) + { + archive_compressed_sizes[i] = g_file_info_get_size (info); + extract_job->total_compressed_size += archive_compressed_sizes[i]; + } + } + + extract_job->base_progress = 0; + + for (l = extract_job->source_files, i = 0; + l != NULL && !job_aborted ((CommonJob *) extract_job); + l = l->next, i++) + { + g_autoptr (AutoarExtractor) extractor = NULL; + + extractor = autoar_extractor_new (G_FILE (l->data), + extract_job->destination_directory); + + autoar_extractor_set_notify_interval (extractor, + PROGRESS_NOTIFY_INTERVAL); + g_signal_connect (extractor, "scanned", + G_CALLBACK (extract_job_on_scanned), + extract_job); + g_signal_connect (extractor, "error", + G_CALLBACK (extract_job_on_error), + extract_job); + g_signal_connect (extractor, "decide-destination", + G_CALLBACK (extract_job_on_decide_destination), + extract_job); + g_signal_connect (extractor, "progress", + G_CALLBACK (extract_job_on_progress), + extract_job); + g_signal_connect (extractor, "completed", + G_CALLBACK (extract_job_on_completed), + extract_job); + g_signal_connect (extractor, "request-passphrase", + G_CALLBACK (extract_job_on_request_passphrase), + extract_job); + + extract_job->archive_compressed_size = archive_compressed_sizes[i]; + extract_job->destination_decided = FALSE; + extract_job->extraction_failed = FALSE; + + autoar_extractor_start (extractor, + extract_job->common.cancellable); + + g_signal_handlers_disconnect_by_data (extractor, + extract_job); + + if (!extract_job->extraction_failed) + { + extract_job->base_progress += (gdouble) extract_job->archive_compressed_size / + (gdouble) extract_job->total_compressed_size; + } + else + { + extract_job->total_files--; + extract_job->base_progress *= extract_job->total_compressed_size; + extract_job->total_compressed_size -= extract_job->archive_compressed_size; + extract_job->base_progress /= extract_job->total_compressed_size; + } + } + + if (!job_aborted ((CommonJob *) extract_job)) + { + report_extract_final_progress (extract_job); + } + + if (extract_job->common.undo_info) + { + if (extract_job->output_files) + { + NautilusFileUndoInfoExtract *undo_info; + + undo_info = NAUTILUS_FILE_UNDO_INFO_EXTRACT (extract_job->common.undo_info); + + nautilus_file_undo_info_extract_set_outputs (undo_info, + extract_job->output_files); + } + else + { + /* There is nothing to undo if there is no output */ + g_clear_object (&extract_job->common.undo_info); + } + } +} + +void +nautilus_file_operations_extract_files (GList *files, + GFile *destination_directory, + GtkWindow *parent_window, + NautilusFileOperationsDBusData *dbus_data, + NautilusExtractCallback done_callback, + gpointer done_callback_data) +{ + ExtractJob *extract_job; + g_autoptr (GTask) task = NULL; + + extract_job = op_job_new (ExtractJob, parent_window, dbus_data); + extract_job->source_files = g_list_copy_deep (files, + (GCopyFunc) g_object_ref, + NULL); + extract_job->destination_directory = g_object_ref (destination_directory); + extract_job->done_callback = done_callback; + extract_job->done_callback_data = done_callback_data; + + inhibit_power_manager ((CommonJob *) extract_job, _("Extracting Files")); + + if (!nautilus_file_undo_manager_is_operating ()) + { + extract_job->common.undo_info = nautilus_file_undo_info_extract_new (files, + destination_directory); + } + + task = g_task_new (NULL, extract_job->common.cancellable, + extract_task_done, extract_job); + g_task_set_task_data (task, extract_job, NULL); + g_task_run_in_thread (task, extract_task_thread_func); +} + +static void +compress_task_done (GObject *source_object, + GAsyncResult *res, + gpointer user_data) +{ + CompressJob *compress_job = user_data; + + if (compress_job->done_callback) + { + compress_job->done_callback (compress_job->output_file, + compress_job->success, + compress_job->done_callback_data); + } + + g_object_unref (compress_job->output_file); + g_list_free_full (compress_job->source_files, g_object_unref); + g_free (compress_job->passphrase); + + finalize_common ((CommonJob *) compress_job); + + nautilus_file_changes_consume_changes (TRUE); +} + +static void +compress_job_on_progress (AutoarCompressor *compressor, + guint64 completed_size, + guint completed_files, + gpointer user_data) +{ + CompressJob *compress_job = user_data; + CommonJob *common = user_data; + char *status; + char *details; + int files_left; + double elapsed; + double transfer_rate; + int remaining_time; + g_autofree gchar *basename_output_file = NULL; + + files_left = compress_job->total_files - completed_files; + basename_output_file = get_basename (compress_job->output_file); + if (compress_job->total_files == 1) + { + g_autofree gchar *basename_data = NULL; + + basename_data = get_basename (G_FILE (compress_job->source_files->data)); + status = g_strdup_printf (_("Compressing “%s” into “%s”"), + basename_data, + basename_output_file); + } + else + { + status = g_strdup_printf (ngettext ("Compressing %'d file into “%s”", + "Compressing %'d files into “%s”", + compress_job->total_files), + compress_job->total_files, + basename_output_file); + } + nautilus_progress_info_take_status (common->progress, status); + + elapsed = g_timer_elapsed (common->time, NULL); + + transfer_rate = 0; + remaining_time = -1; + + if (elapsed > 0) + { + if (completed_size > 0) + { + transfer_rate = completed_size / elapsed; + remaining_time = (compress_job->total_size - completed_size) / transfer_rate; + } + else if (completed_files > 0) + { + transfer_rate = completed_files / elapsed; + remaining_time = (compress_job->total_files - completed_files) / transfer_rate; + } + } + + if (elapsed < SECONDS_NEEDED_FOR_RELIABLE_TRANSFER_RATE || + transfer_rate == 0) + { + if (compress_job->total_files == 1) + { + g_autofree gchar *formatted_size_completed_size = NULL; + g_autofree gchar *formatted_size_total_size = NULL; + + formatted_size_completed_size = g_format_size (completed_size); + formatted_size_total_size = g_format_size (compress_job->total_size); + /* To translators: %s will expand to a size like "2 bytes" or "3 MB", so something like "4 kb / 4 MB" */ + details = g_strdup_printf (_("%s / %s"), formatted_size_completed_size, + formatted_size_total_size); + } + else + { + details = g_strdup_printf (_("%'d / %'d"), + files_left > 0 ? completed_files + 1 : completed_files, + compress_job->total_files); + } + } + else + { + if (compress_job->total_files == 1) + { + g_autofree gchar *formatted_size_completed_size = NULL; + g_autofree gchar *formatted_size_total_size = NULL; + + formatted_size_completed_size = g_format_size (completed_size); + formatted_size_total_size = g_format_size (compress_job->total_size); + + if (files_left > 0) + { + g_autofree gchar *formatted_time = NULL; + g_autofree gchar *formatted_size_transfer_rate = NULL; + + formatted_time = get_formatted_time (remaining_time); + formatted_size_transfer_rate = g_format_size ((goffset) transfer_rate); + /* To translators: %s will expand to a size like "2 bytes" or "3 MB", %s to a time duration like + * "2 minutes". So the whole thing will be something like "2 kb / 4 MB -- 2 hours left (4kb/sec)" + * + * The singular/plural form will be used depending on the remaining time (i.e. the %s argument). + */ + details = g_strdup_printf (ngettext ("%s / %s \xE2\x80\x94 %s left (%s/sec)", + "%s / %s \xE2\x80\x94 %s left (%s/sec)", + seconds_count_format_time_units (remaining_time)), + formatted_size_completed_size, + formatted_size_total_size, + formatted_time, + formatted_size_transfer_rate); + } + else + { + /* To translators: %s will expand to a size like "2 bytes" or "3 MB". */ + details = g_strdup_printf (_("%s / %s"), + formatted_size_completed_size, + formatted_size_total_size); + } + } + else + { + if (files_left > 0) + { + g_autofree gchar *formatted_time = NULL; + g_autofree gchar *formatted_size = NULL; + + formatted_time = get_formatted_time (remaining_time); + formatted_size = g_format_size ((goffset) transfer_rate); + /* To translators: %s will expand to a time duration like "2 minutes". + * So the whole thing will be something like "1 / 5 -- 2 hours left (4kb/sec)" + * + * The singular/plural form will be used depending on the remaining time (i.e. the %s argument). + */ + details = g_strdup_printf (ngettext ("%'d / %'d \xE2\x80\x94 %s left (%s/sec)", + "%'d / %'d \xE2\x80\x94 %s left (%s/sec)", + seconds_count_format_time_units (remaining_time)), + completed_files + 1, compress_job->total_files, + formatted_time, + formatted_size); + } + else + { + /* To translators: %'d is the number of files completed for the operation, + * so it will be something like 2/14. */ + details = g_strdup_printf (_("%'d / %'d"), + completed_files, + compress_job->total_files); + } + } + } + + nautilus_progress_info_take_details (common->progress, details); + + if (elapsed > SECONDS_NEEDED_FOR_APROXIMATE_TRANSFER_RATE) + { + nautilus_progress_info_set_remaining_time (common->progress, + remaining_time); + nautilus_progress_info_set_elapsed_time (common->progress, + elapsed); + } + + nautilus_progress_info_set_progress (common->progress, + completed_size, + compress_job->total_size); +} + +static void +compress_job_on_error (AutoarCompressor *compressor, + GError *error, + gpointer user_data) +{ + CompressJob *compress_job = user_data; + char *status; + g_autofree gchar *basename_output_file = NULL; + + basename_output_file = get_basename (compress_job->output_file); + if (compress_job->total_files == 1) + { + g_autofree gchar *basename_data = NULL; + + basename_data = get_basename (G_FILE (compress_job->source_files->data)); + status = g_strdup_printf (_("Error compressing “%s” into “%s”"), + basename_data, + basename_output_file); + } + else + { + status = g_strdup_printf (ngettext ("Error compressing %'d file into “%s”", + "Error compressing %'d files into “%s”", + compress_job->total_files), + compress_job->total_files, + basename_output_file); + } + nautilus_progress_info_take_status (compress_job->common.progress, + status); + + run_error ((CommonJob *) compress_job, + g_strdup (_("There was an error while compressing files.")), + g_strdup (error->message), + NULL, + FALSE, + CANCEL, + NULL); + + abort_job ((CommonJob *) compress_job); +} + +static void +compress_job_on_completed (AutoarCompressor *compressor, + gpointer user_data) +{ + CompressJob *compress_job = user_data; + g_autoptr (GFile) destination_directory = NULL; + char *status; + g_autofree gchar *basename_output_file = NULL; + + basename_output_file = get_basename (compress_job->output_file); + if (compress_job->total_files == 1) + { + g_autofree gchar *basename_data = NULL; + + basename_data = get_basename (G_FILE (compress_job->source_files->data)); + status = g_strdup_printf (_("Compressed “%s” into “%s”"), + basename_data, + basename_output_file); + } + else + { + status = g_strdup_printf (ngettext ("Compressed %'d file into “%s”", + "Compressed %'d files into “%s”", + compress_job->total_files), + compress_job->total_files, + basename_output_file); + } + + nautilus_progress_info_take_status (compress_job->common.progress, + status); + + nautilus_file_changes_queue_file_added (compress_job->output_file); + + destination_directory = g_file_get_parent (compress_job->output_file); + nautilus_progress_info_set_destination (compress_job->common.progress, + destination_directory); +} + +static void +compress_task_thread_func (GTask *task, + gpointer source_object, + gpointer task_data, + GCancellable *cancellable) +{ + CompressJob *compress_job = task_data; + g_auto (SourceInfo) source_info = SOURCE_INFO_INIT; + g_autoptr (AutoarCompressor) compressor = NULL; + + g_timer_start (compress_job->common.time); + + nautilus_progress_info_start (compress_job->common.progress); + + scan_sources (compress_job->source_files, + &source_info, + (CommonJob *) compress_job, + OP_KIND_COMPRESS); + + compress_job->total_files = source_info.num_files; + compress_job->total_size = source_info.num_bytes; + + compressor = autoar_compressor_new (compress_job->source_files, + compress_job->output_file, + compress_job->format, + compress_job->filter, + FALSE); + if (compress_job->passphrase && compress_job->passphrase[0] != '\0') + { + autoar_compressor_set_passphrase (compressor, compress_job->passphrase); + } + + autoar_compressor_set_output_is_dest (compressor, TRUE); + + autoar_compressor_set_notify_interval (compressor, + PROGRESS_NOTIFY_INTERVAL); + + g_signal_connect (compressor, "progress", + G_CALLBACK (compress_job_on_progress), compress_job); + g_signal_connect (compressor, "error", + G_CALLBACK (compress_job_on_error), compress_job); + g_signal_connect (compressor, "completed", + G_CALLBACK (compress_job_on_completed), compress_job); + autoar_compressor_start (compressor, + compress_job->common.cancellable); + + compress_job->success = g_file_query_exists (compress_job->output_file, + NULL); + + /* There is nothing to undo if the output was not created */ + if (compress_job->common.undo_info != NULL && !compress_job->success) + { + g_clear_object (&compress_job->common.undo_info); + } +} + +void +nautilus_file_operations_compress (GList *files, + GFile *output, + AutoarFormat format, + AutoarFilter filter, + const gchar *passphrase, + GtkWindow *parent_window, + NautilusFileOperationsDBusData *dbus_data, + NautilusCreateCallback done_callback, + gpointer done_callback_data) +{ + g_autoptr (GTask) task = NULL; + CompressJob *compress_job; + + compress_job = op_job_new (CompressJob, parent_window, dbus_data); + compress_job->source_files = g_list_copy_deep (files, + (GCopyFunc) g_object_ref, + NULL); + compress_job->output_file = g_object_ref (output); + compress_job->format = format; + compress_job->filter = filter; + compress_job->passphrase = g_strdup (passphrase); + compress_job->done_callback = done_callback; + compress_job->done_callback_data = done_callback_data; + + inhibit_power_manager ((CommonJob *) compress_job, _("Compressing Files")); + + if (!nautilus_file_undo_manager_is_operating ()) + { + compress_job->common.undo_info = nautilus_file_undo_info_compress_new (files, + output, + format, + filter, + passphrase); + } + + task = g_task_new (NULL, compress_job->common.cancellable, + compress_task_done, compress_job); + g_task_set_task_data (task, compress_job, NULL); + g_task_run_in_thread (task, compress_task_thread_func); +} + +#if !defined (NAUTILUS_OMIT_SELF_CHECK) + +void +nautilus_self_check_file_operations (void) +{ + setlocale (LC_MESSAGES, "C"); + + + /* test the next duplicate name generator */ + EEL_CHECK_STRING_RESULT (get_duplicate_name (" (copy)", 1, -1, FALSE), " (another copy)"); + EEL_CHECK_STRING_RESULT (get_duplicate_name ("foo", 1, -1, FALSE), "foo (copy)"); + EEL_CHECK_STRING_RESULT (get_duplicate_name (".bashrc", 1, -1, FALSE), ".bashrc (copy)"); + EEL_CHECK_STRING_RESULT (get_duplicate_name (".foo.txt", 1, -1, FALSE), ".foo (copy).txt"); + EEL_CHECK_STRING_RESULT (get_duplicate_name ("foo foo", 1, -1, FALSE), "foo foo (copy)"); + EEL_CHECK_STRING_RESULT (get_duplicate_name ("foo.txt", 1, -1, FALSE), "foo (copy).txt"); + EEL_CHECK_STRING_RESULT (get_duplicate_name ("foo foo.txt", 1, -1, FALSE), "foo foo (copy).txt"); + EEL_CHECK_STRING_RESULT (get_duplicate_name ("foo foo.txt txt", 1, -1, FALSE), "foo foo (copy).txt txt"); + EEL_CHECK_STRING_RESULT (get_duplicate_name ("foo...txt", 1, -1, FALSE), "foo.. (copy).txt"); + EEL_CHECK_STRING_RESULT (get_duplicate_name ("foo...", 1, -1, FALSE), "foo... (copy)"); + EEL_CHECK_STRING_RESULT (get_duplicate_name ("foo. (copy)", 1, -1, FALSE), "foo. (another copy)"); + EEL_CHECK_STRING_RESULT (get_duplicate_name ("foo (copy)", 1, -1, FALSE), "foo (another copy)"); + EEL_CHECK_STRING_RESULT (get_duplicate_name ("foo (copy).txt", 1, -1, FALSE), "foo (another copy).txt"); + EEL_CHECK_STRING_RESULT (get_duplicate_name ("foo (another copy)", 1, -1, FALSE), "foo (3rd copy)"); + EEL_CHECK_STRING_RESULT (get_duplicate_name ("foo (another copy).txt", 1, -1, FALSE), "foo (3rd copy).txt"); + EEL_CHECK_STRING_RESULT (get_duplicate_name ("foo foo (another copy).txt", 1, -1, FALSE), "foo foo (3rd copy).txt"); + EEL_CHECK_STRING_RESULT (get_duplicate_name ("foo (13th copy)", 1, -1, FALSE), "foo (14th copy)"); + EEL_CHECK_STRING_RESULT (get_duplicate_name ("foo (13th copy).txt", 1, -1, FALSE), "foo (14th copy).txt"); + EEL_CHECK_STRING_RESULT (get_duplicate_name ("foo (21st copy)", 1, -1, FALSE), "foo (22nd copy)"); + EEL_CHECK_STRING_RESULT (get_duplicate_name ("foo (21st copy).txt", 1, -1, FALSE), "foo (22nd copy).txt"); + EEL_CHECK_STRING_RESULT (get_duplicate_name ("foo (22nd copy)", 1, -1, FALSE), "foo (23rd copy)"); + EEL_CHECK_STRING_RESULT (get_duplicate_name ("foo (22nd copy).txt", 1, -1, FALSE), "foo (23rd copy).txt"); + EEL_CHECK_STRING_RESULT (get_duplicate_name ("foo (23rd copy)", 1, -1, FALSE), "foo (24th copy)"); + EEL_CHECK_STRING_RESULT (get_duplicate_name ("foo (23rd copy).txt", 1, -1, FALSE), "foo (24th copy).txt"); + EEL_CHECK_STRING_RESULT (get_duplicate_name ("foo (24th copy)", 1, -1, FALSE), "foo (25th copy)"); + EEL_CHECK_STRING_RESULT (get_duplicate_name ("foo (24th copy).txt", 1, -1, FALSE), "foo (25th copy).txt"); + EEL_CHECK_STRING_RESULT (get_duplicate_name ("foo foo (24th copy)", 1, -1, FALSE), "foo foo (25th copy)"); + EEL_CHECK_STRING_RESULT (get_duplicate_name ("foo foo (24th copy).txt", 1, -1, FALSE), "foo foo (25th copy).txt"); + EEL_CHECK_STRING_RESULT (get_duplicate_name ("foo foo (100000000000000th copy).txt", 1, -1, FALSE), "foo foo (copy).txt"); + EEL_CHECK_STRING_RESULT (get_duplicate_name ("foo (10th copy)", 1, -1, FALSE), "foo (11th copy)"); + EEL_CHECK_STRING_RESULT (get_duplicate_name ("foo (10th copy).txt", 1, -1, FALSE), "foo (11th copy).txt"); + EEL_CHECK_STRING_RESULT (get_duplicate_name ("foo (11th copy)", 1, -1, FALSE), "foo (12th copy)"); + EEL_CHECK_STRING_RESULT (get_duplicate_name ("foo (11th copy).txt", 1, -1, FALSE), "foo (12th copy).txt"); + EEL_CHECK_STRING_RESULT (get_duplicate_name ("foo (12th copy)", 1, -1, FALSE), "foo (13th copy)"); + EEL_CHECK_STRING_RESULT (get_duplicate_name ("foo (12th copy).txt", 1, -1, FALSE), "foo (13th copy).txt"); + EEL_CHECK_STRING_RESULT (get_duplicate_name ("foo (110th copy)", 1, -1, FALSE), "foo (111th copy)"); + EEL_CHECK_STRING_RESULT (get_duplicate_name ("foo (110th copy).txt", 1, -1, FALSE), "foo (111th copy).txt"); + EEL_CHECK_STRING_RESULT (get_duplicate_name ("foo (122nd copy)", 1, -1, FALSE), "foo (123rd copy)"); + EEL_CHECK_STRING_RESULT (get_duplicate_name ("foo (122nd copy).txt", 1, -1, FALSE), "foo (123rd copy).txt"); + EEL_CHECK_STRING_RESULT (get_duplicate_name ("foo (123rd copy)", 1, -1, FALSE), "foo (124th copy)"); + EEL_CHECK_STRING_RESULT (get_duplicate_name ("foo (123rd copy).txt", 1, -1, FALSE), "foo (124th copy).txt"); + EEL_CHECK_STRING_RESULT (get_duplicate_name ("dir.with.dots", 1, -1, TRUE), "dir.with.dots (copy)"); + EEL_CHECK_STRING_RESULT (get_duplicate_name ("dir (copy).dir", 1, -1, TRUE), "dir (another copy).dir"); + + setlocale (LC_MESSAGES, ""); +} + +#endif diff --git a/src/nautilus-file-operations.h b/src/nautilus-file-operations.h new file mode 100644 index 0000000..14d664f --- /dev/null +++ b/src/nautilus-file-operations.h @@ -0,0 +1,166 @@ + +/* nautilus-file-operations: execute file operations. + + Copyright (C) 1999, 2000 Free Software Foundation + Copyright (C) 2000, 2001 Eazel, Inc. + + This program is free software; you can redistribute it and/or + modify it under the terms of the GNU General Public License as + published by the Free Software Foundation; either version 2 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 warranty of + MERCHANTABILITY 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 . + + Authors: Ettore Perazzoli , + Pavel Cisler +*/ + +#pragma once + +#include +#include +#include + +#include "nautilus-file-operations-dbus-data.h" + +#define SECONDS_NEEDED_FOR_APROXIMATE_TRANSFER_RATE 1 + +typedef void (* NautilusCopyCallback) (GHashTable *debuting_uris, + gboolean success, + gpointer callback_data); +typedef void (* NautilusCreateCallback) (GFile *new_file, + gboolean success, + gpointer callback_data); +typedef void (* NautilusOpCallback) (gboolean success, + gpointer callback_data); +typedef void (* NautilusDeleteCallback) (GHashTable *debuting_uris, + gboolean user_cancel, + gpointer callback_data); +typedef void (* NautilusMountCallback) (GVolume *volume, + gboolean success, + GObject *callback_data_object); +typedef void (* NautilusUnmountCallback) (gpointer callback_data); +typedef void (* NautilusExtractCallback) (GList *outputs, + gpointer callback_data); + +/* FIXME: int copy_action should be an enum */ + +void nautilus_file_operations_copy_move (const GList *item_uris, + const char *target_dir_uri, + GdkDragAction copy_action, + GtkWidget *parent_view, + NautilusFileOperationsDBusData *dbus_data, + NautilusCopyCallback done_callback, + gpointer done_callback_data); +void nautilus_file_operations_empty_trash (GtkWidget *parent_view, + gboolean ask_confirmation, + NautilusFileOperationsDBusData *dbus_data); +void nautilus_file_operations_new_folder (GtkWidget *parent_view, + NautilusFileOperationsDBusData *dbus_data, + const char *parent_dir_uri, + const char *folder_name, + NautilusCreateCallback done_callback, + gpointer done_callback_data); +void nautilus_file_operations_new_file (GtkWidget *parent_view, + const char *parent_dir, + const char *target_filename, + const char *initial_contents, + int length, + NautilusCreateCallback done_callback, + gpointer data); +void nautilus_file_operations_new_file_from_template (GtkWidget *parent_view, + const char *parent_dir, + const char *target_filename, + const char *template_uri, + NautilusCreateCallback done_callback, + gpointer data); + +void nautilus_file_operations_trash_or_delete_sync (GList *files); +void nautilus_file_operations_delete_sync (GList *files); +void nautilus_file_operations_trash_or_delete_async (GList *files, + GtkWindow *parent_window, + NautilusFileOperationsDBusData *dbus_data, + NautilusDeleteCallback done_callback, + gpointer done_callback_data); +void nautilus_file_operations_delete_async (GList *files, + GtkWindow *parent_window, + NautilusFileOperationsDBusData *dbus_data, + NautilusDeleteCallback done_callback, + gpointer done_callback_data); + +void nautilus_file_set_permissions_recursive (const char *directory, + guint32 file_permissions, + guint32 file_mask, + guint32 folder_permissions, + guint32 folder_mask, + NautilusOpCallback callback, + gpointer callback_data); + +void nautilus_file_operations_unmount_mount (GtkWindow *parent_window, + GMount *mount, + gboolean eject, + gboolean check_trash); +void nautilus_file_operations_unmount_mount_full (GtkWindow *parent_window, + GMount *mount, + GMountOperation *mount_operation, + gboolean eject, + gboolean check_trash, + NautilusUnmountCallback callback, + gpointer callback_data); +void nautilus_file_operations_mount_volume (GtkWindow *parent_window, + GVolume *volume); +void nautilus_file_operations_mount_volume_full (GtkWindow *parent_window, + GVolume *volume, + NautilusMountCallback mount_callback, + GObject *mount_callback_data_object); + +void nautilus_file_operations_copy_async (GList *files, + GFile *target_dir, + GtkWindow *parent_window, + NautilusFileOperationsDBusData *dbus_data, + NautilusCopyCallback done_callback, + gpointer done_callback_data); +void nautilus_file_operations_copy_sync (GList *files, + GFile *target_dir); + +void nautilus_file_operations_move_async (GList *files, + GFile *target_dir, + GtkWindow *parent_window, + NautilusFileOperationsDBusData *dbus_data, + NautilusCopyCallback done_callback, + gpointer done_callback_data); +void nautilus_file_operations_move_sync (GList *files, + GFile *target_dir); + +void nautilus_file_operations_duplicate (GList *files, + GtkWindow *parent_window, + NautilusFileOperationsDBusData *dbus_data, + NautilusCopyCallback done_callback, + gpointer done_callback_data); +void nautilus_file_operations_link (GList *files, + GFile *target_dir, + GtkWindow *parent_window, + NautilusFileOperationsDBusData *dbus_data, + NautilusCopyCallback done_callback, + gpointer done_callback_data); +void nautilus_file_operations_extract_files (GList *files, + GFile *destination_directory, + GtkWindow *parent_window, + NautilusFileOperationsDBusData *dbus_data, + NautilusExtractCallback done_callback, + gpointer done_callback_data); +void nautilus_file_operations_compress (GList *files, + GFile *output, + AutoarFormat format, + AutoarFilter filter, + const gchar *passphrase, + GtkWindow *parent_window, + NautilusFileOperationsDBusData *dbus_data, + NautilusCreateCallback done_callback, + gpointer done_callback_data); diff --git a/src/nautilus-file-private.h b/src/nautilus-file-private.h new file mode 100644 index 0000000..e575edb --- /dev/null +++ b/src/nautilus-file-private.h @@ -0,0 +1,283 @@ +/* + nautilus-file-private.h: + + Copyright (C) 1999, 2000, 2001 Eazel, Inc. + + This program is free software; you can redistribute it and/or + modify it under the terms of the GNU General Public License as + published by the Free Software Foundation; either version 2 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 warranty of + MERCHANTABILITY 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 . + + Author: Darin Adler +*/ + +#pragma once + +#include "nautilus-directory.h" +#include "nautilus-file.h" +#include "nautilus-monitor.h" +#include "nautilus-file-undo-operations.h" +#include + +#define NAUTILUS_FILE_DEFAULT_ATTRIBUTES \ + "standard::*,access::*,mountable::*,time::*,unix::*,owner::*,selinux::*,thumbnail::*,id::filesystem,trash::orig-path,trash::deletion-date,metadata::*,recent::*" + +/* These are in the typical sort order. Known things come first, then + * things where we can't know, finally things where we don't yet know. + */ +typedef enum { + KNOWN, + UNKNOWABLE, + UNKNOWN +} Knowledge; + +struct NautilusFileDetails +{ + NautilusDirectory *directory; + + GRefString *name; + + /* File info: */ + GFileType type; + + GRefString *display_name; + char *display_name_collation_key; + char *directory_name_collation_key; + GRefString *edit_name; + + goffset size; /* -1 is unknown */ + + int sort_order; + + guint32 permissions; + int uid; /* -1 is none */ + int gid; /* -1 is none */ + + GRefString *owner; + GRefString *owner_real; + GRefString *group; + + time_t atime; /* 0 is unknown */ + time_t mtime; /* 0 is unknown */ + time_t btime; /* 0 is unknown */ + + char *symlink_name; + + GRefString *mime_type; + + char *selinux_context; + char *description; + + GError *get_info_error; + + guint directory_count; + + guint deep_directory_count; + guint deep_file_count; + guint deep_unreadable_count; + goffset deep_size; + + GIcon *icon; + + char *thumbnail_path; + GdkPixbuf *thumbnail; + time_t thumbnail_mtime; + + GList *mime_list; /* If this is a directory, the list of MIME types in it. */ + + /* Info you might get from a link (.desktop, .directory or nautilus link) */ + GIcon *custom_icon; + char *activation_uri; + + /* used during DND, for checking whether source and destination are on + * the same file system. + */ + GRefString *filesystem_id; + + char *trash_orig_path; + + /* The following is for file operations in progress. Since + * there are normally only a few of these, we can move them to + * a separate hash table or something if required to keep the + * file objects small. + */ + GList *operations_in_progress; + + /* NautilusInfoProviders that need to be run for this file */ + GList *pending_info_providers; + + /* Emblems provided by extensions */ + GList *extension_emblems; + GList *pending_extension_emblems; + + /* Attributes provided by extensions */ + GHashTable *extension_attributes; + GHashTable *pending_extension_attributes; + + GHashTable *metadata; + + /* Mount for mountpoint or the references GMount for a "mountable" */ + GMount *mount; + + /* boolean fields: bitfield to save space, since there can be + many NautilusFile objects. */ + + eel_boolean_bit unconfirmed : 1; + eel_boolean_bit is_gone : 1; + /* Set when emitting files_added on the directory to make sure we + add a file, and only once */ + eel_boolean_bit is_added : 1; + /* Set by the NautilusDirectory while it's loading the file + * list so the file knows not to do redundant I/O. + */ + eel_boolean_bit loading_directory : 1; + eel_boolean_bit got_file_info : 1; + eel_boolean_bit get_info_failed : 1; + eel_boolean_bit file_info_is_up_to_date : 1; + + eel_boolean_bit got_directory_count : 1; + eel_boolean_bit directory_count_failed : 1; + eel_boolean_bit directory_count_is_up_to_date : 1; + + eel_boolean_bit deep_counts_status : 2; /* NautilusRequestStatus */ + /* no deep_counts_are_up_to_date field; since we expose + intermediate values for this attribute, we do actually + forget it rather than invalidating. */ + + eel_boolean_bit got_mime_list : 1; + eel_boolean_bit mime_list_failed : 1; + eel_boolean_bit mime_list_is_up_to_date : 1; + + eel_boolean_bit mount_is_up_to_date : 1; + + eel_boolean_bit got_custom_display_name : 1; + eel_boolean_bit got_custom_activation_uri : 1; + + eel_boolean_bit thumbnail_is_up_to_date : 1; + eel_boolean_bit thumbnailing_failed : 1; + + eel_boolean_bit is_thumbnailing : 1; + + eel_boolean_bit is_symlink : 1; + eel_boolean_bit is_mountpoint : 1; + eel_boolean_bit is_hidden : 1; + + eel_boolean_bit has_permissions : 1; + + eel_boolean_bit can_read : 1; + eel_boolean_bit can_write : 1; + eel_boolean_bit can_execute : 1; + eel_boolean_bit can_delete : 1; + eel_boolean_bit can_trash : 1; + eel_boolean_bit can_rename : 1; + eel_boolean_bit can_mount : 1; + eel_boolean_bit can_unmount : 1; + eel_boolean_bit can_eject : 1; + eel_boolean_bit can_start : 1; + eel_boolean_bit can_start_degraded : 1; + eel_boolean_bit can_stop : 1; + eel_boolean_bit start_stop_type : 3; /* GDriveStartStopType */ + eel_boolean_bit can_poll_for_media : 1; + eel_boolean_bit is_media_check_automatic : 1; + + eel_boolean_bit filesystem_readonly : 1; + eel_boolean_bit filesystem_use_preview : 2; /* GFilesystemPreviewType */ + eel_boolean_bit filesystem_info_is_up_to_date : 1; + eel_boolean_bit filesystem_remote : 1; + GRefString *filesystem_type; + + time_t trash_time; /* 0 is unknown */ + time_t recency; /* 0 is unknown */ + + gdouble search_relevance; + gchar *fts_snippet; + + guint64 free_space; /* (guint)-1 for unknown */ + time_t free_space_read; /* The time free_space was updated, or 0 for never */ +}; + +typedef struct { + NautilusFile *file; + GList *files; + gint renamed_files; + gint skipped_files; + GCancellable *cancellable; + NautilusFileOperationCallback callback; + gpointer callback_data; + gboolean is_rename; + + gpointer data; + GDestroyNotify free_data; + NautilusFileUndoInfo *undo_info; +} NautilusFileOperation; + +NautilusFile *nautilus_file_new_from_info (NautilusDirectory *directory, + GFileInfo *info); +void nautilus_file_emit_changed (NautilusFile *file); +void nautilus_file_mark_gone (NautilusFile *file); + +gboolean nautilus_file_get_date (NautilusFile *file, + NautilusDateType date_type, + time_t *date); +void nautilus_file_updated_deep_count_in_progress (NautilusFile *file); + + +void nautilus_file_clear_info (NautilusFile *file); +/* Compare file's state with a fresh file info struct, return FALSE if + * no change, update file and return TRUE if the file info contains + * new state. */ +gboolean nautilus_file_update_info (NautilusFile *file, + GFileInfo *info); +gboolean nautilus_file_update_name (NautilusFile *file, + const char *name); +gboolean nautilus_file_update_metadata_from_info (NautilusFile *file, + GFileInfo *info); + +gboolean nautilus_file_update_name_and_directory (NautilusFile *file, + const char *name, + NautilusDirectory *directory); + +gboolean nautilus_file_set_display_name (NautilusFile *file, + const char *display_name, + const char *edit_name, + gboolean custom); +NautilusDirectory * + nautilus_file_get_directory (NautilusFile *file); +void nautilus_file_set_directory (NautilusFile *file, + NautilusDirectory *directory); +void nautilus_file_set_mount (NautilusFile *file, + GMount *mount); + +/* Mark specified attributes for this file out of date without canceling current + * I/O or kicking off new I/O. + */ +void nautilus_file_invalidate_attributes_internal (NautilusFile *file, + NautilusFileAttributes file_attributes); +NautilusFileAttributes nautilus_file_get_all_attributes (void); +gboolean nautilus_file_is_self_owned (NautilusFile *file); +void nautilus_file_invalidate_count_and_mime_list (NautilusFile *file); +gboolean nautilus_file_rename_in_progress (NautilusFile *file); +void nautilus_file_invalidate_extension_info_internal (NautilusFile *file); +void nautilus_file_info_providers_done (NautilusFile *file); + + +/* Thumbnailing: */ +void nautilus_file_set_is_thumbnailing (NautilusFile *file, + gboolean is_thumbnailing); + +NautilusFileOperation *nautilus_file_operation_new (NautilusFile *file, + NautilusFileOperationCallback callback, + gpointer callback_data); +void nautilus_file_operation_free (NautilusFileOperation *op); +void nautilus_file_operation_complete (NautilusFileOperation *op, + GFile *result_location, + GError *error); +void nautilus_file_operation_cancel (NautilusFileOperation *op); diff --git a/src/nautilus-file-queue.c b/src/nautilus-file-queue.c new file mode 100644 index 0000000..026ded1 --- /dev/null +++ b/src/nautilus-file-queue.c @@ -0,0 +1,130 @@ +/* + * Copyright (C) 2001 Maciej Stachowiak + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License as + * published by the Free Software Foundation; either version 2 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 warranty of + * MERCHANTABILITY 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 . + * + * Author: Maciej Stachowiak + */ + +#include +#include "nautilus-file-queue.h" + +#include + +struct NautilusFileQueue +{ + GList *head; + GList *tail; + GHashTable *item_to_link_map; +}; + +NautilusFileQueue * +nautilus_file_queue_new (void) +{ + NautilusFileQueue *queue; + + queue = g_new0 (NautilusFileQueue, 1); + queue->item_to_link_map = g_hash_table_new (g_direct_hash, g_direct_equal); + + return queue; +} + +void +nautilus_file_queue_destroy (NautilusFileQueue *queue) +{ + g_hash_table_destroy (queue->item_to_link_map); + nautilus_file_list_free (queue->head); + g_free (queue); +} + +void +nautilus_file_queue_enqueue (NautilusFileQueue *queue, + NautilusFile *file) +{ + if (g_hash_table_lookup (queue->item_to_link_map, file) != NULL) + { + /* It's already on the queue. */ + return; + } + + if (queue->tail == NULL) + { + queue->head = g_list_append (NULL, file); + queue->tail = queue->head; + } + else + { + queue->tail = g_list_append (queue->tail, file); + queue->tail = queue->tail->next; + } + + nautilus_file_ref (file); + g_hash_table_insert (queue->item_to_link_map, file, queue->tail); +} + +NautilusFile * +nautilus_file_queue_dequeue (NautilusFileQueue *queue) +{ + NautilusFile *file; + + file = nautilus_file_queue_head (queue); + nautilus_file_queue_remove (queue, file); + + return file; +} + + +void +nautilus_file_queue_remove (NautilusFileQueue *queue, + NautilusFile *file) +{ + GList *link; + + link = g_hash_table_lookup (queue->item_to_link_map, file); + + if (link == NULL) + { + /* It's not on the queue */ + return; + } + + if (link == queue->tail) + { + /* Need to special-case removing the tail. */ + queue->tail = queue->tail->prev; + } + + queue->head = g_list_remove_link (queue->head, link); + g_list_free (link); + g_hash_table_remove (queue->item_to_link_map, file); + + nautilus_file_unref (file); +} + +NautilusFile * +nautilus_file_queue_head (NautilusFileQueue *queue) +{ + if (queue->head == NULL) + { + return NULL; + } + + return NAUTILUS_FILE (queue->head->data); +} + +gboolean +nautilus_file_queue_is_empty (NautilusFileQueue *queue) +{ + return (queue->head == NULL); +} diff --git a/src/nautilus-file-queue.h b/src/nautilus-file-queue.h new file mode 100644 index 0000000..28c7b17 --- /dev/null +++ b/src/nautilus-file-queue.h @@ -0,0 +1,46 @@ +/* + Copyright (C) 2001 Maciej Stachowiak + + This program is free software; you can redistribute it and/or + modify it under the terms of the GNU General Public License as + published by the Free Software Foundation; either version 2 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 warranty of + MERCHANTABILITY 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 . + + Author: Maciej Stachowiak +*/ + +#pragma once + +#include "nautilus-file.h" + +typedef struct NautilusFileQueue NautilusFileQueue; + +NautilusFileQueue *nautilus_file_queue_new (void); +void nautilus_file_queue_destroy (NautilusFileQueue *queue); + +/* Add a file to the tail of the queue, unless it's already in the queue */ +void nautilus_file_queue_enqueue (NautilusFileQueue *queue, + NautilusFile *file); + +/* Return the file at the head of the queue after removing it from the + * queue. This is dangerous unless you have another ref to the file, + * since it will unref it. + */ +NautilusFile * nautilus_file_queue_dequeue (NautilusFileQueue *queue); + +/* Remove a file from an arbitrary point in the queue in constant time. */ +void nautilus_file_queue_remove (NautilusFileQueue *queue, + NautilusFile *file); + +/* Get the file at the head of the queue without removing or unrefing it. */ +NautilusFile * nautilus_file_queue_head (NautilusFileQueue *queue); + +gboolean nautilus_file_queue_is_empty (NautilusFileQueue *queue); diff --git a/src/nautilus-file-undo-manager.c b/src/nautilus-file-undo-manager.c new file mode 100644 index 0000000..f2d1eeb --- /dev/null +++ b/src/nautilus-file-undo-manager.c @@ -0,0 +1,270 @@ +/* nautilus-file-undo-manager.c - Manages the undo/redo stack + * + * Copyright (C) 2007-2011 Amos Brocco + * Copyright (C) 2010, 2012 Red Hat, Inc. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public + * License as published by the Free Software Foundation; either + * version 2 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public + * License along with this library; if not, see . + * + * Authors: Amos Brocco + * Cosimo Cecchi + * + */ + +#include + +#include "nautilus-file-undo-manager.h" + +#include "nautilus-file-operations.h" +#include "nautilus-file.h" +#include "nautilus-trash-monitor.h" + +#include + +#define DEBUG_FLAG NAUTILUS_DEBUG_UNDO +#include "nautilus-debug.h" + +enum +{ + SIGNAL_UNDO_CHANGED, + NUM_SIGNALS, +}; + +static guint signals[NUM_SIGNALS] = { 0, }; + +struct _NautilusFileUndoManager +{ + GObject parent_instance; + NautilusFileUndoInfo *info; + NautilusFileUndoManagerState state; + NautilusFileUndoManagerState last_state; + + guint is_operating : 1; + + gulong trash_signal_id; +}; + +G_DEFINE_TYPE (NautilusFileUndoManager, nautilus_file_undo_manager, G_TYPE_OBJECT) + +static NautilusFileUndoManager *undo_singleton = NULL; + +NautilusFileUndoManager * +nautilus_file_undo_manager_new (void) +{ + if (undo_singleton != NULL) + { + return g_object_ref (undo_singleton); + } + + undo_singleton = g_object_new (NAUTILUS_TYPE_FILE_UNDO_MANAGER, NULL); + g_object_add_weak_pointer (G_OBJECT (undo_singleton), (gpointer) & undo_singleton); + + return undo_singleton; +} + +static void +file_undo_manager_clear (NautilusFileUndoManager *self) +{ + g_clear_object (&self->info); + self->state = NAUTILUS_FILE_UNDO_MANAGER_STATE_NONE; +} + +static void +trash_state_changed_cb (NautilusTrashMonitor *monitor, + gboolean is_empty, + gpointer user_data) +{ + NautilusFileUndoManager *self = user_data; + + /* A trash operation cannot be undone if the trash is empty */ + if (is_empty && + self->state == NAUTILUS_FILE_UNDO_MANAGER_STATE_UNDO && + NAUTILUS_IS_FILE_UNDO_INFO_TRASH (self->info)) + { + file_undo_manager_clear (self); + g_signal_emit (self, signals[SIGNAL_UNDO_CHANGED], 0); + } +} + +static void +nautilus_file_undo_manager_init (NautilusFileUndoManager *self) +{ + self->trash_signal_id = g_signal_connect (nautilus_trash_monitor_get (), + "trash-state-changed", + G_CALLBACK (trash_state_changed_cb), self); +} + +static void +nautilus_file_undo_manager_finalize (GObject *object) +{ + NautilusFileUndoManager *self = NAUTILUS_FILE_UNDO_MANAGER (object); + + g_clear_signal_handler (&self->trash_signal_id, nautilus_trash_monitor_get ()); + + file_undo_manager_clear (self); + + G_OBJECT_CLASS (nautilus_file_undo_manager_parent_class)->finalize (object); +} + +static void +nautilus_file_undo_manager_class_init (NautilusFileUndoManagerClass *klass) +{ + GObjectClass *oclass; + + oclass = G_OBJECT_CLASS (klass); + + oclass->finalize = nautilus_file_undo_manager_finalize; + + signals[SIGNAL_UNDO_CHANGED] = + g_signal_new ("undo-changed", + G_TYPE_FROM_CLASS (klass), + G_SIGNAL_RUN_LAST, + 0, NULL, NULL, + g_cclosure_marshal_VOID__VOID, + G_TYPE_NONE, 0); +} + +static void +undo_info_apply_ready (GObject *source, + GAsyncResult *res, + gpointer user_data) +{ + NautilusFileUndoManager *self = user_data; + NautilusFileUndoInfo *info = NAUTILUS_FILE_UNDO_INFO (source); + gboolean success, user_cancel; + + success = nautilus_file_undo_info_apply_finish (info, res, &user_cancel, NULL); + + self->is_operating = FALSE; + + /* just return in case we got another another operation set */ + if ((self->info != NULL) && + (self->info != info)) + { + return; + } + + if (success) + { + if (self->last_state == NAUTILUS_FILE_UNDO_MANAGER_STATE_UNDO) + { + self->state = NAUTILUS_FILE_UNDO_MANAGER_STATE_REDO; + } + else if (self->last_state == NAUTILUS_FILE_UNDO_MANAGER_STATE_REDO) + { + self->state = NAUTILUS_FILE_UNDO_MANAGER_STATE_UNDO; + } + + self->info = g_object_ref (info); + } + else if (user_cancel) + { + self->state = self->last_state; + self->info = g_object_ref (info); + } + else + { + file_undo_manager_clear (self); + } + + g_signal_emit (self, signals[SIGNAL_UNDO_CHANGED], 0); +} + +static void +do_undo_redo (NautilusFileUndoManager *self, + GtkWindow *parent_window, + NautilusFileOperationsDBusData *dbus_data) +{ + gboolean undo = self->state == NAUTILUS_FILE_UNDO_MANAGER_STATE_UNDO; + + self->last_state = self->state; + + self->is_operating = TRUE; + nautilus_file_undo_info_apply_async (self->info, undo, parent_window, + dbus_data, + undo_info_apply_ready, self); + + /* clear actions while undoing */ + file_undo_manager_clear (self); + g_signal_emit (self, signals[SIGNAL_UNDO_CHANGED], 0); +} + +void +nautilus_file_undo_manager_redo (GtkWindow *parent_window, + NautilusFileOperationsDBusData *dbus_data) +{ + if (undo_singleton->state != NAUTILUS_FILE_UNDO_MANAGER_STATE_REDO) + { + g_warning ("Called redo, but state is %s!", undo_singleton->state == 0 ? + "none" : "undo"); + return; + } + + do_undo_redo (undo_singleton, parent_window, dbus_data); +} + +void +nautilus_file_undo_manager_undo (GtkWindow *parent_window, + NautilusFileOperationsDBusData *dbus_data) +{ + if (undo_singleton->state != NAUTILUS_FILE_UNDO_MANAGER_STATE_UNDO) + { + g_warning ("Called undo, but state is %s!", undo_singleton->state == 0 ? + "none" : "redo"); + return; + } + + do_undo_redo (undo_singleton, parent_window, dbus_data); +} + +void +nautilus_file_undo_manager_set_action (NautilusFileUndoInfo *info) +{ + DEBUG ("Setting undo information %p", info); + + file_undo_manager_clear (undo_singleton); + + if (info != NULL) + { + undo_singleton->info = g_object_ref (info); + undo_singleton->state = NAUTILUS_FILE_UNDO_MANAGER_STATE_UNDO; + undo_singleton->last_state = NAUTILUS_FILE_UNDO_MANAGER_STATE_NONE; + } + + g_signal_emit (undo_singleton, signals[SIGNAL_UNDO_CHANGED], 0); +} + +NautilusFileUndoInfo * +nautilus_file_undo_manager_get_action (void) +{ + return undo_singleton->info; +} + +NautilusFileUndoManagerState +nautilus_file_undo_manager_get_state (void) +{ + return undo_singleton->state; +} + + +gboolean +nautilus_file_undo_manager_is_operating (void) +{ + return undo_singleton->is_operating; +} + +NautilusFileUndoManager * +nautilus_file_undo_manager_get (void) +{ + return undo_singleton; +} diff --git a/src/nautilus-file-undo-manager.h b/src/nautilus-file-undo-manager.h new file mode 100644 index 0000000..7bf0309 --- /dev/null +++ b/src/nautilus-file-undo-manager.h @@ -0,0 +1,55 @@ + +/* nautilus-file-undo-manager.h - Manages the undo/redo stack + * + * Copyright (C) 2007-2011 Amos Brocco + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public + * License as published by the Free Software Foundation; either + * version 2 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public + * License along with this library; if not, see . + * + * Author: Amos Brocco + */ + +#pragma once + +#include +#include +#include +#include + +#include "nautilus-file-undo-operations.h" + +#define NAUTILUS_TYPE_FILE_UNDO_MANAGER\ + (nautilus_file_undo_manager_get_type()) + +G_DECLARE_FINAL_TYPE (NautilusFileUndoManager, nautilus_file_undo_manager, NAUTILUS, FILE_UNDO_MANAGER, GObject) + +typedef enum { + NAUTILUS_FILE_UNDO_MANAGER_STATE_NONE, + NAUTILUS_FILE_UNDO_MANAGER_STATE_UNDO, + NAUTILUS_FILE_UNDO_MANAGER_STATE_REDO +} NautilusFileUndoManagerState; + +NautilusFileUndoManager *nautilus_file_undo_manager_new (void); +NautilusFileUndoManager * nautilus_file_undo_manager_get (void); + +void nautilus_file_undo_manager_set_action (NautilusFileUndoInfo *info); +NautilusFileUndoInfo *nautilus_file_undo_manager_get_action (void); + +NautilusFileUndoManagerState nautilus_file_undo_manager_get_state (void); + +void nautilus_file_undo_manager_undo (GtkWindow *parent_window, + NautilusFileOperationsDBusData *dbus_data); +void nautilus_file_undo_manager_redo (GtkWindow *parent_window, + NautilusFileOperationsDBusData *dbus_data); + +gboolean nautilus_file_undo_manager_is_operating (void); diff --git a/src/nautilus-file-undo-operations.c b/src/nautilus-file-undo-operations.c new file mode 100644 index 0000000..962f7f4 --- /dev/null +++ b/src/nautilus-file-undo-operations.c @@ -0,0 +1,2639 @@ +/* nautilus-file-undo-operations.c - Manages undo/redo of file operations + * + * Copyright (C) 2007-2011 Amos Brocco + * Copyright (C) 2010, 2012 Red Hat, Inc. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public + * License as published by the Free Software Foundation; either + * version 2 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public + * License along with this library; if not, see . + * + * Authors: Amos Brocco + * Cosimo Cecchi + * + */ + +#include + +#include "nautilus-file-undo-operations.h" + +#include + +#include "nautilus-file-operations.h" +#include "nautilus-file.h" +#include "nautilus-file-undo-manager.h" +#include "nautilus-batch-rename-dialog.h" +#include "nautilus-batch-rename-utilities.h" +#include "nautilus-tag-manager.h" + + +/* Since we use g_get_current_time for setting "orig_trash_time" in the undo + * info, there are situations where the difference between this value and the + * real deletion time can differ enough to make the rounding a difference of 1 + * second, failing the equality check. To make sure we avoid this, and to be + * preventive, use 2 seconds epsilon. + */ +#define TRASH_TIME_EPSILON 2 + +typedef struct +{ + NautilusFileUndoOp op_type; + guint count; /* Number of items */ + + GTask *apply_async_task; + + gchar *undo_label; + gchar *redo_label; + gchar *undo_description; + gchar *redo_description; +} NautilusFileUndoInfoPrivate; + +G_DEFINE_TYPE_WITH_PRIVATE (NautilusFileUndoInfo, nautilus_file_undo_info, G_TYPE_OBJECT) + +enum +{ + PROP_OP_TYPE = 1, + PROP_ITEM_COUNT, + N_PROPERTIES +}; + +static GParamSpec *properties[N_PROPERTIES] = { NULL, }; + +/* description helpers */ +static void +nautilus_file_undo_info_init (NautilusFileUndoInfo *self) +{ + NautilusFileUndoInfoPrivate *priv; + + priv = nautilus_file_undo_info_get_instance_private (self); + + priv->apply_async_task = NULL; +} + +static void +nautilus_file_undo_info_get_property (GObject *object, + guint property_id, + GValue *value, + GParamSpec *pspec) +{ + NautilusFileUndoInfo *self; + NautilusFileUndoInfoPrivate *priv; + + self = NAUTILUS_FILE_UNDO_INFO (object); + priv = nautilus_file_undo_info_get_instance_private (self); + + switch (property_id) + { + case PROP_OP_TYPE: + { + g_value_set_int (value, priv->op_type); + } + break; + + case PROP_ITEM_COUNT: + { + g_value_set_int (value, priv->count); + } + break; + + default: + { + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec); + } + break; + } +} + +static void +nautilus_file_undo_info_set_property (GObject *object, + guint property_id, + const GValue *value, + GParamSpec *pspec) +{ + NautilusFileUndoInfo *self; + NautilusFileUndoInfoPrivate *priv; + + self = NAUTILUS_FILE_UNDO_INFO (object); + priv = nautilus_file_undo_info_get_instance_private (self); + + switch (property_id) + { + case PROP_OP_TYPE: + { + priv->op_type = g_value_get_int (value); + } + break; + + case PROP_ITEM_COUNT: + { + priv->count = g_value_get_int (value); + } + break; + + default: + { + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec); + } + break; + } +} + +static void +nautilus_file_redo_info_warn_redo (NautilusFileUndoInfo *self, + GtkWindow *parent_window, + NautilusFileOperationsDBusData *dbus_data) +{ + g_critical ("Object %p of type %s does not implement redo_func!!", + self, G_OBJECT_TYPE_NAME (self)); +} + +static void +nautilus_file_undo_info_warn_undo (NautilusFileUndoInfo *self, + GtkWindow *parent_window, + NautilusFileOperationsDBusData *dbus_data) +{ + g_critical ("Object %p of type %s does not implement undo_func!!", + self, G_OBJECT_TYPE_NAME (self)); +} + +static void +nautilus_file_undo_info_strings_func (NautilusFileUndoInfo *self, + gchar **undo_label, + gchar **undo_description, + gchar **redo_label, + gchar **redo_description) +{ + if (undo_label != NULL) + { + *undo_label = g_strdup (_("Undo")); + } + if (undo_description != NULL) + { + *undo_description = g_strdup (_("Undo last action")); + } + + if (redo_label != NULL) + { + *redo_label = g_strdup (_("Redo")); + } + if (redo_description != NULL) + { + *redo_description = g_strdup (_("Redo last undone action")); + } +} + +static void +nautilus_file_undo_info_finalize (GObject *object) +{ + NautilusFileUndoInfo *self; + NautilusFileUndoInfoPrivate *priv; + + self = NAUTILUS_FILE_UNDO_INFO (object); + priv = nautilus_file_undo_info_get_instance_private (self); + + g_clear_object (&priv->apply_async_task); + + G_OBJECT_CLASS (nautilus_file_undo_info_parent_class)->finalize (object); +} + +static void +nautilus_file_undo_info_class_init (NautilusFileUndoInfoClass *klass) +{ + GObjectClass *oclass = G_OBJECT_CLASS (klass); + + oclass->finalize = nautilus_file_undo_info_finalize; + oclass->get_property = nautilus_file_undo_info_get_property; + oclass->set_property = nautilus_file_undo_info_set_property; + + klass->undo_func = nautilus_file_undo_info_warn_undo; + klass->redo_func = nautilus_file_redo_info_warn_redo; + klass->strings_func = nautilus_file_undo_info_strings_func; + + properties[PROP_OP_TYPE] = + g_param_spec_int ("op-type", + "Undo info op type", + "Type of undo operation", + 0, NAUTILUS_FILE_UNDO_OP_NUM_TYPES - 1, 0, + G_PARAM_READWRITE | + G_PARAM_CONSTRUCT_ONLY); + properties[PROP_ITEM_COUNT] = + g_param_spec_int ("item-count", + "Number of items", + "Number of items", + 0, G_MAXINT, 0, + G_PARAM_READWRITE | + G_PARAM_CONSTRUCT_ONLY); + + g_object_class_install_properties (oclass, N_PROPERTIES, properties); +} + +NautilusFileUndoOp +nautilus_file_undo_info_get_op_type (NautilusFileUndoInfo *self) +{ + NautilusFileUndoInfoPrivate *priv; + + g_return_val_if_fail (NAUTILUS_IS_FILE_UNDO_INFO (self), NAUTILUS_FILE_UNDO_OP_INVALID); + + priv = nautilus_file_undo_info_get_instance_private (self); + + return priv->op_type; +} + +static gint +nautilus_file_undo_info_get_item_count (NautilusFileUndoInfo *self) +{ + NautilusFileUndoInfoPrivate *priv; + + priv = nautilus_file_undo_info_get_instance_private (self); + + return priv->count; +} + +void +nautilus_file_undo_info_apply_async (NautilusFileUndoInfo *self, + gboolean undo, + GtkWindow *parent_window, + NautilusFileOperationsDBusData *dbus_data, + GAsyncReadyCallback callback, + gpointer user_data) +{ + NautilusFileUndoInfoPrivate *priv; + + g_return_if_fail (NAUTILUS_IS_FILE_UNDO_INFO (self)); + + priv = nautilus_file_undo_info_get_instance_private (self); + + g_assert (priv->apply_async_task == NULL); + + priv->apply_async_task = g_task_new (G_OBJECT (self), + NULL, + callback, + user_data); + + if (undo) + { + NAUTILUS_FILE_UNDO_INFO_CLASS (G_OBJECT_GET_CLASS (self))->undo_func (self, + parent_window, + dbus_data); + } + else + { + NAUTILUS_FILE_UNDO_INFO_CLASS (G_OBJECT_GET_CLASS (self))->redo_func (self, + parent_window, + dbus_data); + } +} + +typedef struct +{ + gboolean success; + gboolean user_cancel; +} FileUndoInfoOpRes; + +static void +file_undo_info_op_res_free (gpointer data) +{ + g_slice_free (FileUndoInfoOpRes, data); +} + +gboolean +nautilus_file_undo_info_apply_finish (NautilusFileUndoInfo *self, + GAsyncResult *res, + gboolean *user_cancel, + GError **error) +{ + FileUndoInfoOpRes *op_res; + gboolean success = FALSE; + + op_res = g_task_propagate_pointer (G_TASK (res), error); + + if (op_res != NULL) + { + *user_cancel = op_res->user_cancel; + success = op_res->success; + + file_undo_info_op_res_free (op_res); + } + + return success; +} + +void +nautilus_file_undo_info_get_strings (NautilusFileUndoInfo *self, + gchar **undo_label, + gchar **undo_description, + gchar **redo_label, + gchar **redo_description) +{ + NAUTILUS_FILE_UNDO_INFO_CLASS (G_OBJECT_GET_CLASS (self))->strings_func (self, + undo_label, undo_description, + redo_label, redo_description); +} + +static void +file_undo_info_complete_apply (NautilusFileUndoInfo *self, + gboolean success, + gboolean user_cancel) +{ + NautilusFileUndoInfoPrivate *priv; + FileUndoInfoOpRes *op_res; + + priv = nautilus_file_undo_info_get_instance_private (self); + op_res = g_slice_new0 (FileUndoInfoOpRes); + + op_res->user_cancel = user_cancel; + op_res->success = success; + + g_task_return_pointer (priv->apply_async_task, op_res, + file_undo_info_op_res_free); + + g_clear_object (&priv->apply_async_task); +} + +static void +file_undo_info_transfer_callback (GHashTable *debuting_uris, + gboolean success, + gpointer user_data) +{ + NautilusFileUndoInfo *self = user_data; + + /* TODO: we need to forward the cancelled state from + * the file operation to the file undo info object. + */ + file_undo_info_complete_apply (self, success, FALSE); +} + +static void +file_undo_info_operation_callback (NautilusFile *file, + GFile *result_location, + GError *error, + gpointer user_data) +{ + NautilusFileUndoInfo *self = user_data; + + file_undo_info_complete_apply (self, (error == NULL), + g_error_matches (error, G_IO_ERROR, G_IO_ERROR_CANCELLED)); +} + +static void +file_undo_info_delete_callback (GHashTable *debuting_uris, + gboolean user_cancel, + gpointer user_data) +{ + NautilusFileUndoInfo *self = user_data; + + file_undo_info_complete_apply (self, + !user_cancel, + user_cancel); +} + +/* copy/move/duplicate/link/restore from trash */ +struct _NautilusFileUndoInfoExt +{ + NautilusFileUndoInfo parent_instance; + + GFile *src_dir; + GFile *dest_dir; + GQueue *sources; /* Relative to src_dir */ + GQueue *destinations; /* Relative to dest_dir */ +}; + +G_DEFINE_TYPE (NautilusFileUndoInfoExt, nautilus_file_undo_info_ext, NAUTILUS_TYPE_FILE_UNDO_INFO) + +static char * +ext_get_first_target_short_name (NautilusFileUndoInfoExt *self) +{ + GList *targets_first; + char *file_name = NULL; + + targets_first = g_queue_peek_head_link (self->destinations); + + if (targets_first != NULL && + targets_first->data != NULL) + { + file_name = g_file_get_basename (targets_first->data); + } + + return file_name; +} + +static void +ext_strings_func (NautilusFileUndoInfo *info, + gchar **undo_label, + gchar **undo_description, + gchar **redo_label, + gchar **redo_description) +{ + NautilusFileUndoInfoExt *self = NAUTILUS_FILE_UNDO_INFO_EXT (info); + NautilusFileUndoOp op_type = nautilus_file_undo_info_get_op_type (info); + gint count = nautilus_file_undo_info_get_item_count (info); + gchar *name = NULL, *source, *destination; + + source = g_file_get_path (self->src_dir); + destination = g_file_get_path (self->dest_dir); + + if (count <= 1) + { + name = ext_get_first_target_short_name (self); + } + + if (op_type == NAUTILUS_FILE_UNDO_OP_MOVE) + { + if (count > 1) + { + *undo_description = g_strdup_printf (ngettext ("Move %d item back to “%s”", + "Move %d items back to “%s”", count), + count, source); + *redo_description = g_strdup_printf (ngettext ("Move %d item to “%s”", + "Move %d items to “%s”", count), + count, destination); + + *undo_label = g_strdup_printf (ngettext ("_Undo Move %d item", + "_Undo Move %d items", count), + count); + *redo_label = g_strdup_printf (ngettext ("_Redo Move %d item", + "_Redo Move %d items", count), + count); + } + else + { + *undo_description = g_strdup_printf (_("Move “%s” back to “%s”"), name, source); + *redo_description = g_strdup_printf (_("Move “%s” to “%s”"), name, destination); + + *undo_label = g_strdup (_("_Undo Move")); + *redo_label = g_strdup (_("_Redo Move")); + } + } + else if (op_type == NAUTILUS_FILE_UNDO_OP_RESTORE_FROM_TRASH) + { + *undo_label = g_strdup (_("_Undo Restore from Trash")); + *redo_label = g_strdup (_("_Redo Restore from Trash")); + + if (count > 1) + { + *undo_description = g_strdup_printf (ngettext ("Move %d item back to trash", + "Move %d items back to trash", count), + count); + *redo_description = g_strdup_printf (ngettext ("Restore %d item from trash", + "Restore %d items from trash", count), + count); + } + else + { + *undo_description = g_strdup_printf (_("Move “%s” back to trash"), name); + *redo_description = g_strdup_printf (_("Restore “%s” from trash"), name); + } + } + else if (op_type == NAUTILUS_FILE_UNDO_OP_COPY) + { + if (count > 1) + { + *undo_description = g_strdup_printf (ngettext ("Delete %d copied item", + "Delete %d copied items", count), + count); + *redo_description = g_strdup_printf (ngettext ("Copy %d item to “%s”", + "Copy %d items to “%s”", count), + count, destination); + + *undo_label = g_strdup_printf (ngettext ("_Undo Copy %d item", + "_Undo Copy %d items", count), + count); + *redo_label = g_strdup_printf (ngettext ("_Redo Copy %d item", + "_Redo Copy %d items", count), + count); + } + else + { + *undo_description = g_strdup_printf (_("Delete “%s”"), name); + *redo_description = g_strdup_printf (_("Copy “%s” to “%s”"), name, destination); + + *undo_label = g_strdup (_("_Undo Copy")); + *redo_label = g_strdup (_("_Redo Copy")); + } + } + else if (op_type == NAUTILUS_FILE_UNDO_OP_DUPLICATE) + { + if (count > 1) + { + *undo_description = g_strdup_printf (ngettext ("Delete %d duplicated item", + "Delete %d duplicated items", count), + count); + *redo_description = g_strdup_printf (ngettext ("Duplicate %d item in “%s”", + "Duplicate %d items in “%s”", count), + count, destination); + + *undo_label = g_strdup_printf (ngettext ("_Undo Duplicate %d item", + "_Undo Duplicate %d items", count), + count); + *redo_label = g_strdup_printf (ngettext ("_Redo Duplicate %d item", + "_Redo Duplicate %d items", count), + count); + } + else + { + *undo_description = g_strdup_printf (_("Delete “%s”"), name); + *redo_description = g_strdup_printf (_("Duplicate “%s” in “%s”"), + name, destination); + + *undo_label = g_strdup (_("_Undo Duplicate")); + *redo_label = g_strdup (_("_Redo Duplicate")); + } + } + else if (op_type == NAUTILUS_FILE_UNDO_OP_CREATE_LINK) + { + if (count > 1) + { + *undo_description = g_strdup_printf (ngettext ("Delete links to %d item", + "Delete links to %d items", count), + count); + *redo_description = g_strdup_printf (ngettext ("Create links to %d item", + "Create links to %d items", count), + count); + } + else + { + *undo_description = g_strdup_printf (_("Delete link to “%s”"), name); + *redo_description = g_strdup_printf (_("Create link to “%s”"), name); + + *undo_label = g_strdup (_("_Undo Create Link")); + *redo_label = g_strdup (_("_Redo Create Link")); + } + } + else + { + g_assert_not_reached (); + } + + g_free (name); + g_free (source); + g_free (destination); +} + +static void +ext_create_link_redo_func (NautilusFileUndoInfoExt *self, + GtkWindow *parent_window, + NautilusFileOperationsDBusData *dbus_data) +{ + nautilus_file_operations_link (g_queue_peek_head_link (self->sources), + self->dest_dir, + parent_window, + dbus_data, + file_undo_info_transfer_callback, + self); +} + +static void +ext_duplicate_redo_func (NautilusFileUndoInfoExt *self, + GtkWindow *parent_window, + NautilusFileOperationsDBusData *dbus_data) +{ + nautilus_file_operations_duplicate (g_queue_peek_head_link (self->sources), + parent_window, + dbus_data, + file_undo_info_transfer_callback, + self); +} + +static void +ext_copy_redo_func (NautilusFileUndoInfoExt *self, + GtkWindow *parent_window, + NautilusFileOperationsDBusData *dbus_data) +{ + nautilus_file_operations_copy_async (g_queue_peek_head_link (self->sources), + self->dest_dir, + parent_window, + dbus_data, + file_undo_info_transfer_callback, + self); +} + +static void +ext_move_restore_redo_func (NautilusFileUndoInfoExt *self, + GtkWindow *parent_window, + NautilusFileOperationsDBusData *dbus_data) +{ + nautilus_file_operations_move_async (g_queue_peek_head_link (self->sources), + self->dest_dir, + parent_window, + dbus_data, + file_undo_info_transfer_callback, + self); +} + +static void +ext_redo_func (NautilusFileUndoInfo *info, + GtkWindow *parent_window, + NautilusFileOperationsDBusData *dbus_data) +{ + NautilusFileUndoInfoExt *self = NAUTILUS_FILE_UNDO_INFO_EXT (info); + NautilusFileUndoOp op_type = nautilus_file_undo_info_get_op_type (info); + + if (op_type == NAUTILUS_FILE_UNDO_OP_MOVE || + op_type == NAUTILUS_FILE_UNDO_OP_RESTORE_FROM_TRASH) + { + ext_move_restore_redo_func (self, parent_window, dbus_data); + } + else if (op_type == NAUTILUS_FILE_UNDO_OP_COPY) + { + ext_copy_redo_func (self, parent_window, dbus_data); + } + else if (op_type == NAUTILUS_FILE_UNDO_OP_DUPLICATE) + { + ext_duplicate_redo_func (self, parent_window, dbus_data); + } + else if (op_type == NAUTILUS_FILE_UNDO_OP_CREATE_LINK) + { + ext_create_link_redo_func (self, parent_window, dbus_data); + } + else + { + g_assert_not_reached (); + } +} + +static void +ext_restore_undo_func (NautilusFileUndoInfoExt *self, + GtkWindow *parent_window, + NautilusFileOperationsDBusData *dbus_data) +{ + nautilus_file_operations_trash_or_delete_async (g_queue_peek_head_link (self->destinations), + parent_window, + dbus_data, + file_undo_info_delete_callback, + self); +} + + +static void +ext_move_undo_func (NautilusFileUndoInfoExt *self, + GtkWindow *parent_window, + NautilusFileOperationsDBusData *dbus_data) +{ + nautilus_file_operations_move_async (g_queue_peek_head_link (self->destinations), + self->src_dir, + parent_window, + dbus_data, + file_undo_info_transfer_callback, + self); +} + +static void +ext_copy_duplicate_undo_func (NautilusFileUndoInfoExt *self, + GtkWindow *parent_window, + NautilusFileOperationsDBusData *dbus_data) +{ + GList *files; + + files = g_list_copy (g_queue_peek_head_link (self->destinations)); + files = g_list_reverse (files); /* Deleting must be done in reverse */ + + nautilus_file_operations_delete_async (files, parent_window, + dbus_data, + file_undo_info_delete_callback, self); + + g_list_free (files); +} + +static void +ext_undo_func (NautilusFileUndoInfo *info, + GtkWindow *parent_window, + NautilusFileOperationsDBusData *dbus_data) +{ + NautilusFileUndoInfoExt *self = NAUTILUS_FILE_UNDO_INFO_EXT (info); + NautilusFileUndoOp op_type = nautilus_file_undo_info_get_op_type (info); + + if (op_type == NAUTILUS_FILE_UNDO_OP_COPY || + op_type == NAUTILUS_FILE_UNDO_OP_DUPLICATE || + op_type == NAUTILUS_FILE_UNDO_OP_CREATE_LINK) + { + ext_copy_duplicate_undo_func (self, parent_window, dbus_data); + } + else if (op_type == NAUTILUS_FILE_UNDO_OP_MOVE) + { + ext_move_undo_func (self, parent_window, dbus_data); + } + else if (op_type == NAUTILUS_FILE_UNDO_OP_RESTORE_FROM_TRASH) + { + ext_restore_undo_func (self, parent_window, dbus_data); + } + else + { + g_assert_not_reached (); + } +} + +static void +nautilus_file_undo_info_ext_init (NautilusFileUndoInfoExt *self) +{ +} + +static void +nautilus_file_undo_info_ext_finalize (GObject *obj) +{ + NautilusFileUndoInfoExt *self = NAUTILUS_FILE_UNDO_INFO_EXT (obj); + + if (self->sources) + { + g_queue_free_full (self->sources, g_object_unref); + } + + if (self->destinations) + { + g_queue_free_full (self->destinations, g_object_unref); + } + + g_clear_object (&self->src_dir); + g_clear_object (&self->dest_dir); + + G_OBJECT_CLASS (nautilus_file_undo_info_ext_parent_class)->finalize (obj); +} + +static void +nautilus_file_undo_info_ext_class_init (NautilusFileUndoInfoExtClass *klass) +{ + GObjectClass *oclass = G_OBJECT_CLASS (klass); + NautilusFileUndoInfoClass *iclass = NAUTILUS_FILE_UNDO_INFO_CLASS (klass); + + oclass->finalize = nautilus_file_undo_info_ext_finalize; + + iclass->undo_func = ext_undo_func; + iclass->redo_func = ext_redo_func; + iclass->strings_func = ext_strings_func; +} + +NautilusFileUndoInfo * +nautilus_file_undo_info_ext_new (NautilusFileUndoOp op_type, + gint item_count, + GFile *src_dir, + GFile *target_dir) +{ + NautilusFileUndoInfoExt *self; + + self = g_object_new (NAUTILUS_TYPE_FILE_UNDO_INFO_EXT, + "op-type", op_type, + "item-count", item_count, + NULL); + + self->src_dir = g_object_ref (src_dir); + self->dest_dir = g_object_ref (target_dir); + self->sources = g_queue_new (); + self->destinations = g_queue_new (); + + return NAUTILUS_FILE_UNDO_INFO (self); +} + +void +nautilus_file_undo_info_ext_add_origin_target_pair (NautilusFileUndoInfoExt *self, + GFile *origin, + GFile *target) +{ + g_queue_push_tail (self->sources, g_object_ref (origin)); + g_queue_push_tail (self->destinations, g_object_ref (target)); +} + +/* create new file/folder */ +struct _NautilusFileUndoInfoCreate +{ + NautilusFileUndoInfo parent_instance; + + char *template; + GFile *target_file; + gint length; +}; + +G_DEFINE_TYPE (NautilusFileUndoInfoCreate, nautilus_file_undo_info_create, NAUTILUS_TYPE_FILE_UNDO_INFO) + +static void +create_strings_func (NautilusFileUndoInfo *info, + gchar **undo_label, + gchar **undo_description, + gchar **redo_label, + gchar **redo_description) +{ + NautilusFileUndoInfoCreate *self = NAUTILUS_FILE_UNDO_INFO_CREATE (info); + NautilusFileUndoOp op_type = nautilus_file_undo_info_get_op_type (info); + char *name; + + name = g_file_get_parse_name (self->target_file); + *undo_description = g_strdup_printf (_("Delete “%s”"), name); + + if (op_type == NAUTILUS_FILE_UNDO_OP_CREATE_EMPTY_FILE) + { + *redo_description = g_strdup_printf (_("Create an empty file “%s”"), name); + + *undo_label = g_strdup (_("_Undo Create Empty File")); + *redo_label = g_strdup (_("_Redo Create Empty File")); + } + else if (op_type == NAUTILUS_FILE_UNDO_OP_CREATE_FOLDER) + { + *redo_description = g_strdup_printf (_("Create a new folder “%s”"), name); + + *undo_label = g_strdup (_("_Undo Create Folder")); + *redo_label = g_strdup (_("_Redo Create Folder")); + } + else if (op_type == NAUTILUS_FILE_UNDO_OP_CREATE_FILE_FROM_TEMPLATE) + { + *redo_description = g_strdup_printf (_("Create new file “%s” from template "), name); + + *undo_label = g_strdup (_("_Undo Create from Template")); + *redo_label = g_strdup (_("_Redo Create from Template")); + } + else + { + g_assert_not_reached (); + } + + g_free (name); +} + +static void +create_callback (GFile *new_file, + gboolean success, + gpointer callback_data) +{ + file_undo_info_transfer_callback (NULL, success, callback_data); +} + +static void +create_from_template_redo_func (NautilusFileUndoInfoCreate *self, + GtkWindow *parent_window, + NautilusFileOperationsDBusData *dbus_data) +{ + GFile *parent; + gchar *parent_uri, *new_name; + + parent = g_file_get_parent (self->target_file); + parent_uri = g_file_get_uri (parent); + new_name = g_file_get_basename (self->target_file); + nautilus_file_operations_new_file_from_template (NULL, + parent_uri, new_name, + self->template, + create_callback, self); + + g_free (parent_uri); + g_free (new_name); + g_object_unref (parent); +} + +static void +create_folder_redo_func (NautilusFileUndoInfoCreate *self, + GtkWindow *parent_window, + NautilusFileOperationsDBusData *dbus_data) +{ + GFile *parent; + gchar *parent_uri; + gchar *name; + + name = g_file_get_basename (self->target_file); + parent = g_file_get_parent (self->target_file); + parent_uri = g_file_get_uri (parent); + nautilus_file_operations_new_folder (NULL, + dbus_data, + parent_uri, name, + create_callback, self); + + g_free (name); + g_free (parent_uri); + g_object_unref (parent); +} + +static void +create_empty_redo_func (NautilusFileUndoInfoCreate *self, + GtkWindow *parent_window, + NautilusFileOperationsDBusData *dbus_data) +{ + GFile *parent; + gchar *parent_uri; + gchar *new_name; + + parent = g_file_get_parent (self->target_file); + parent_uri = g_file_get_uri (parent); + new_name = g_file_get_parse_name (self->target_file); + nautilus_file_operations_new_file (NULL, parent_uri, + new_name, + self->template, + self->length, + create_callback, self); + + g_free (parent_uri); + g_free (new_name); + g_object_unref (parent); +} + +static void +create_redo_func (NautilusFileUndoInfo *info, + GtkWindow *parent_window, + NautilusFileOperationsDBusData *dbus_data) +{ + NautilusFileUndoInfoCreate *self = NAUTILUS_FILE_UNDO_INFO_CREATE (info); + NautilusFileUndoOp op_type = nautilus_file_undo_info_get_op_type (info); + + if (op_type == NAUTILUS_FILE_UNDO_OP_CREATE_EMPTY_FILE) + { + create_empty_redo_func (self, parent_window, dbus_data); + } + else if (op_type == NAUTILUS_FILE_UNDO_OP_CREATE_FOLDER) + { + create_folder_redo_func (self, parent_window, dbus_data); + } + else if (op_type == NAUTILUS_FILE_UNDO_OP_CREATE_FILE_FROM_TEMPLATE) + { + create_from_template_redo_func (self, parent_window, dbus_data); + } + else + { + g_assert_not_reached (); + } +} + +static void +create_undo_func (NautilusFileUndoInfo *info, + GtkWindow *parent_window, + NautilusFileOperationsDBusData *dbus_data) +{ + NautilusFileUndoInfoCreate *self = NAUTILUS_FILE_UNDO_INFO_CREATE (info); + GList *files = NULL; + + files = g_list_append (files, g_object_ref (self->target_file)); + nautilus_file_operations_delete_async (files, parent_window, + dbus_data, + file_undo_info_delete_callback, self); + + g_list_free_full (files, g_object_unref); +} + +static void +nautilus_file_undo_info_create_init (NautilusFileUndoInfoCreate *self) +{ +} + +static void +nautilus_file_undo_info_create_finalize (GObject *obj) +{ + NautilusFileUndoInfoCreate *self = NAUTILUS_FILE_UNDO_INFO_CREATE (obj); + g_clear_object (&self->target_file); + g_free (self->template); + + G_OBJECT_CLASS (nautilus_file_undo_info_create_parent_class)->finalize (obj); +} + +static void +nautilus_file_undo_info_create_class_init (NautilusFileUndoInfoCreateClass *klass) +{ + GObjectClass *oclass = G_OBJECT_CLASS (klass); + NautilusFileUndoInfoClass *iclass = NAUTILUS_FILE_UNDO_INFO_CLASS (klass); + + oclass->finalize = nautilus_file_undo_info_create_finalize; + + iclass->undo_func = create_undo_func; + iclass->redo_func = create_redo_func; + iclass->strings_func = create_strings_func; +} + +NautilusFileUndoInfo * +nautilus_file_undo_info_create_new (NautilusFileUndoOp op_type) +{ + return g_object_new (NAUTILUS_TYPE_FILE_UNDO_INFO_CREATE, + "op-type", op_type, + "item-count", 1, + NULL); +} + +void +nautilus_file_undo_info_create_set_data (NautilusFileUndoInfoCreate *self, + GFile *file, + const char *template, + gint length) +{ + self->target_file = g_object_ref (file); + self->template = g_strdup (template); + self->length = length; +} + +/* rename */ +struct _NautilusFileUndoInfoRename +{ + NautilusFileUndoInfo parent_instance; + + GFile *old_file; + GFile *new_file; + gchar *old_display_name; + gchar *new_display_name; +}; + +G_DEFINE_TYPE (NautilusFileUndoInfoRename, nautilus_file_undo_info_rename, NAUTILUS_TYPE_FILE_UNDO_INFO) + +static void +rename_strings_func (NautilusFileUndoInfo *info, + gchar **undo_label, + gchar **undo_description, + gchar **redo_label, + gchar **redo_description) +{ + NautilusFileUndoInfoRename *self = NAUTILUS_FILE_UNDO_INFO_RENAME (info); + gchar *new_name, *old_name; + + new_name = g_file_get_parse_name (self->new_file); + old_name = g_file_get_parse_name (self->old_file); + + *undo_description = g_strdup_printf (_("Rename “%s” as “%s”"), new_name, old_name); + *redo_description = g_strdup_printf (_("Rename “%s” as “%s”"), old_name, new_name); + + *undo_label = g_strdup (_("_Undo Rename")); + *redo_label = g_strdup (_("_Redo Rename")); + + g_free (old_name); + g_free (new_name); +} + +static void +rename_redo_func (NautilusFileUndoInfo *info, + GtkWindow *parent_window, + NautilusFileOperationsDBusData *dbus_data) +{ + NautilusFileUndoInfoRename *self = NAUTILUS_FILE_UNDO_INFO_RENAME (info); + NautilusFile *file; + + file = nautilus_file_get (self->old_file); + nautilus_file_rename (file, self->new_display_name, + file_undo_info_operation_callback, self); + + nautilus_file_unref (file); +} + +static void +rename_undo_func (NautilusFileUndoInfo *info, + GtkWindow *parent_window, + NautilusFileOperationsDBusData *dbus_data) +{ + NautilusFileUndoInfoRename *self = NAUTILUS_FILE_UNDO_INFO_RENAME (info); + NautilusFile *file; + + file = nautilus_file_get (self->new_file); + nautilus_file_rename (file, self->old_display_name, + file_undo_info_operation_callback, self); + + nautilus_file_unref (file); +} + +static void +nautilus_file_undo_info_rename_init (NautilusFileUndoInfoRename *self) +{ +} + +static void +nautilus_file_undo_info_rename_finalize (GObject *obj) +{ + NautilusFileUndoInfoRename *self = NAUTILUS_FILE_UNDO_INFO_RENAME (obj); + g_clear_object (&self->old_file); + g_clear_object (&self->new_file); + g_free (self->old_display_name); + g_free (self->new_display_name); + + G_OBJECT_CLASS (nautilus_file_undo_info_rename_parent_class)->finalize (obj); +} + +static void +nautilus_file_undo_info_rename_class_init (NautilusFileUndoInfoRenameClass *klass) +{ + GObjectClass *oclass = G_OBJECT_CLASS (klass); + NautilusFileUndoInfoClass *iclass = NAUTILUS_FILE_UNDO_INFO_CLASS (klass); + + oclass->finalize = nautilus_file_undo_info_rename_finalize; + + iclass->undo_func = rename_undo_func; + iclass->redo_func = rename_redo_func; + iclass->strings_func = rename_strings_func; +} + +NautilusFileUndoInfo * +nautilus_file_undo_info_rename_new (void) +{ + return g_object_new (NAUTILUS_TYPE_FILE_UNDO_INFO_RENAME, + "op-type", NAUTILUS_FILE_UNDO_OP_RENAME, + "item-count", 1, + NULL); +} + +void +nautilus_file_undo_info_rename_set_data_pre (NautilusFileUndoInfoRename *self, + GFile *old_file, + gchar *old_display_name, + gchar *new_display_name) +{ + self->old_file = g_object_ref (old_file); + self->old_display_name = g_strdup (old_display_name); + self->new_display_name = g_strdup (new_display_name); +} + +void +nautilus_file_undo_info_rename_set_data_post (NautilusFileUndoInfoRename *self, + GFile *new_file) +{ + self->new_file = g_object_ref (new_file); +} + +/* batch rename */ +struct _NautilusFileUndoInfoBatchRename +{ + NautilusFileUndoInfo parent_instance; + + GList *old_files; + GList *new_files; + GList *old_display_names; + GList *new_display_names; +}; + +G_DEFINE_TYPE (NautilusFileUndoInfoBatchRename, nautilus_file_undo_info_batch_rename, NAUTILUS_TYPE_FILE_UNDO_INFO); + +static void +batch_rename_strings_func (NautilusFileUndoInfo *info, + gchar **undo_label, + gchar **undo_description, + gchar **redo_label, + gchar **redo_description) +{ + NautilusFileUndoInfoBatchRename *self = NAUTILUS_FILE_UNDO_INFO_BATCH_RENAME (info); + + *undo_description = g_strdup_printf (ngettext ("Batch rename %d file", + "Batch rename %d files", + g_list_length (self->new_files)), + g_list_length (self->new_files)); + *redo_description = g_strdup_printf (ngettext ("Batch rename %d file", + "Batch rename %d files", + g_list_length (self->new_files)), + g_list_length (self->new_files)); + + *undo_label = g_strdup (_("_Undo Batch Rename")); + *redo_label = g_strdup (_("_Redo Batch Rename")); +} + +static void +batch_rename_redo_func (NautilusFileUndoInfo *info, + GtkWindow *parent_window, + NautilusFileOperationsDBusData *dbus_data) +{ + NautilusFileUndoInfoBatchRename *self = NAUTILUS_FILE_UNDO_INFO_BATCH_RENAME (info); + + GList *l, *files; + NautilusFile *file; + GFile *old_file; + + files = NULL; + + for (l = self->old_files; l != NULL; l = l->next) + { + old_file = l->data; + + file = nautilus_file_get (old_file); + files = g_list_prepend (files, file); + } + + files = g_list_reverse (files); + + batch_rename_sort_lists_for_rename (&files, + &self->new_display_names, + &self->old_display_names, + &self->new_files, + &self->old_files, + TRUE); + + nautilus_file_batch_rename (files, self->new_display_names, file_undo_info_operation_callback, self); +} + +static void +batch_rename_undo_func (NautilusFileUndoInfo *info, + GtkWindow *parent_window, + NautilusFileOperationsDBusData *dbus_data) +{ + NautilusFileUndoInfoBatchRename *self = NAUTILUS_FILE_UNDO_INFO_BATCH_RENAME (info); + + GList *l, *files; + NautilusFile *file; + GFile *new_file; + + files = NULL; + + for (l = self->new_files; l != NULL; l = l->next) + { + new_file = l->data; + + file = nautilus_file_get (new_file); + files = g_list_prepend (files, file); + } + + files = g_list_reverse (files); + + batch_rename_sort_lists_for_rename (&files, + &self->old_display_names, + &self->new_display_names, + &self->old_files, + &self->new_files, + TRUE); + + nautilus_file_batch_rename (files, self->old_display_names, file_undo_info_operation_callback, self); +} + +static void +nautilus_file_undo_info_batch_rename_init (NautilusFileUndoInfoBatchRename *self) +{ +} + +static void +nautilus_file_undo_info_batch_rename_finalize (GObject *obj) +{ + GList *l; + GFile *file; + GString *string; + NautilusFileUndoInfoBatchRename *self = NAUTILUS_FILE_UNDO_INFO_BATCH_RENAME (obj); + + for (l = self->new_files; l != NULL; l = l->next) + { + file = l->data; + + g_clear_object (&file); + } + + for (l = self->old_files; l != NULL; l = l->next) + { + file = l->data; + + g_clear_object (&file); + } + + for (l = self->new_display_names; l != NULL; l = l->next) + { + string = l->data; + + g_string_free (string, TRUE); + } + + for (l = self->old_display_names; l != NULL; l = l->next) + { + string = l->data; + + g_string_free (string, TRUE); + } + + g_list_free (self->new_files); + g_list_free (self->old_files); + g_list_free (self->new_display_names); + g_list_free (self->old_display_names); + + G_OBJECT_CLASS (nautilus_file_undo_info_batch_rename_parent_class)->finalize (obj); +} + +static void +nautilus_file_undo_info_batch_rename_class_init (NautilusFileUndoInfoBatchRenameClass *klass) +{ + GObjectClass *oclass = G_OBJECT_CLASS (klass); + NautilusFileUndoInfoClass *iclass = NAUTILUS_FILE_UNDO_INFO_CLASS (klass); + + oclass->finalize = nautilus_file_undo_info_batch_rename_finalize; + + iclass->undo_func = batch_rename_undo_func; + iclass->redo_func = batch_rename_redo_func; + iclass->strings_func = batch_rename_strings_func; +} + +NautilusFileUndoInfo * +nautilus_file_undo_info_batch_rename_new (gint item_count) +{ + return g_object_new (NAUTILUS_TYPE_FILE_UNDO_INFO_BATCH_RENAME, + "op-type", NAUTILUS_FILE_UNDO_OP_BATCH_RENAME, + "item-count", item_count, + NULL); +} + +void +nautilus_file_undo_info_batch_rename_set_data_pre (NautilusFileUndoInfoBatchRename *self, + GList *old_files) +{ + GList *l; + GString *old_name; + GFile *file; + + self->old_files = old_files; + self->old_display_names = NULL; + + for (l = old_files; l != NULL; l = l->next) + { + file = l->data; + + old_name = g_string_new (g_file_get_basename (file)); + + self->old_display_names = g_list_prepend (self->old_display_names, old_name); + } + + self->old_display_names = g_list_reverse (self->old_display_names); +} + +void +nautilus_file_undo_info_batch_rename_set_data_post (NautilusFileUndoInfoBatchRename *self, + GList *new_files) +{ + GList *l; + GString *new_name; + GFile *file; + + self->new_files = new_files; + self->new_display_names = NULL; + + for (l = new_files; l != NULL; l = l->next) + { + file = l->data; + + new_name = g_string_new (g_file_get_basename (file)); + + self->new_display_names = g_list_prepend (self->new_display_names, new_name); + } + + self->new_display_names = g_list_reverse (self->new_display_names); +} + +/* starred files */ +struct _NautilusFileUndoInfoStarred +{ + NautilusFileUndoInfo parent_instance; + + GList *files; + /* Whether the action was starring or unstarring */ + gboolean starred; +}; + +G_DEFINE_TYPE (NautilusFileUndoInfoStarred, nautilus_file_undo_info_starred, NAUTILUS_TYPE_FILE_UNDO_INFO); + +enum +{ + PROP_FILES = 1, + PROP_STARRED, + NUM_PROPERTIES +}; + +static void +starred_strings_func (NautilusFileUndoInfo *info, + gchar **undo_label, + gchar **undo_description, + gchar **redo_label, + gchar **redo_description) +{ + NautilusFileUndoInfoStarred *self = NAUTILUS_FILE_UNDO_INFO_STARRED (info); + + if (self->starred) + { + *undo_description = g_strdup_printf (ngettext ("Unstar %d file", + "Unstar %d files", + g_list_length (self->files)), + g_list_length (self->files)); + *redo_description = g_strdup_printf (ngettext ("Star %d file", + "Star %d files", + g_list_length (self->files)), + g_list_length (self->files)); + *undo_label = g_strdup (_("_Undo Starring")); + *redo_label = g_strdup (_("_Redo Starring")); + } + else + { + *undo_description = g_strdup_printf (ngettext ("Star %d file", + "Star %d files", + g_list_length (self->files)), + g_list_length (self->files)); + *redo_description = g_strdup_printf (ngettext ("Unstar %d file", + "Unstar %d files", + g_list_length (self->files)), + g_list_length (self->files)); + *undo_label = g_strdup (_("_Undo Unstarring")); + *redo_label = g_strdup (_("_Redo Unstarring")); + } +} + +static void +on_undo_starred_tags_updated (GObject *object, + GAsyncResult *res, + gpointer user_data) +{ + GTask *task; + NautilusFileUndoInfo *undo_info; + + undo_info = NAUTILUS_FILE_UNDO_INFO (object); + + task = user_data; + g_clear_object (&task); + + file_undo_info_operation_callback (NULL, NULL, NULL, undo_info); +} + +static void +starred_redo_func (NautilusFileUndoInfo *info, + GtkWindow *parent_window, + NautilusFileOperationsDBusData *dbus_data) +{ + NautilusFileUndoInfoStarred *self = NAUTILUS_FILE_UNDO_INFO_STARRED (info); + + if (self->starred) + { + nautilus_tag_manager_star_files (nautilus_tag_manager_get (), + G_OBJECT (info), + self->files, + on_undo_starred_tags_updated, + NULL); + } + else + { + nautilus_tag_manager_unstar_files (nautilus_tag_manager_get (), + G_OBJECT (info), + self->files, + on_undo_starred_tags_updated, + NULL); + } +} + +static void +starred_undo_func (NautilusFileUndoInfo *info, + GtkWindow *parent_window, + NautilusFileOperationsDBusData *dbus_data) +{ + NautilusFileUndoInfoStarred *self = NAUTILUS_FILE_UNDO_INFO_STARRED (info); + + if (self->starred) + { + nautilus_tag_manager_unstar_files (nautilus_tag_manager_get (), + G_OBJECT (info), + self->files, + on_undo_starred_tags_updated, + NULL); + } + else + { + nautilus_tag_manager_star_files (nautilus_tag_manager_get (), + G_OBJECT (info), + self->files, + on_undo_starred_tags_updated, + NULL); + } +} + +static void +nautilus_file_undo_info_starred_set_property (GObject *object, + guint prop_id, + const GValue *value, + GParamSpec *pspec) +{ + NautilusFileUndoInfoStarred *self = NAUTILUS_FILE_UNDO_INFO_STARRED (object); + + switch (prop_id) + { + case PROP_FILES: + { + self->files = nautilus_file_list_copy (g_value_get_pointer (value)); + } + break; + + case PROP_STARRED: + { + self->starred = g_value_get_boolean (value); + } + break; + + default: + { + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); + } + } +} + +static void +nautilus_file_undo_info_starred_init (NautilusFileUndoInfoStarred *self) +{ +} + +static void +nautilus_file_undo_info_starred_finalize (GObject *obj) +{ + NautilusFileUndoInfoStarred *self = NAUTILUS_FILE_UNDO_INFO_STARRED (obj); + + nautilus_file_list_free (self->files); + + G_OBJECT_CLASS (nautilus_file_undo_info_starred_parent_class)->finalize (obj); +} + +static void +nautilus_file_undo_info_starred_class_init (NautilusFileUndoInfoStarredClass *klass) +{ + GObjectClass *oclass = G_OBJECT_CLASS (klass); + NautilusFileUndoInfoClass *iclass = NAUTILUS_FILE_UNDO_INFO_CLASS (klass); + + oclass->finalize = nautilus_file_undo_info_starred_finalize; + oclass->set_property = nautilus_file_undo_info_starred_set_property; + + iclass->undo_func = starred_undo_func; + iclass->redo_func = starred_redo_func; + iclass->strings_func = starred_strings_func; + + g_object_class_install_property (oclass, + PROP_FILES, + g_param_spec_pointer ("files", + "files", + "The files for which to undo star/unstar", + G_PARAM_WRITABLE | + G_PARAM_CONSTRUCT_ONLY)); + g_object_class_install_property (oclass, + PROP_STARRED, + g_param_spec_boolean ("starred", + "starred", + "Whether the files were starred or unstarred", + FALSE, + G_PARAM_WRITABLE | + G_PARAM_CONSTRUCT_ONLY)); +} + +GList * +nautilus_file_undo_info_starred_get_files (NautilusFileUndoInfoStarred *self) +{ + return self->files; +} + +gboolean +nautilus_file_undo_info_starred_is_starred (NautilusFileUndoInfoStarred *self) +{ + return self->starred; +} + +NautilusFileUndoInfo * +nautilus_file_undo_info_starred_new (GList *files, + gboolean starred) +{ + NautilusFileUndoInfoStarred *self; + + self = g_object_new (NAUTILUS_TYPE_FILE_UNDO_INFO_STARRED, + "op-type", NAUTILUS_FILE_UNDO_OP_STARRED, + "item-count", g_list_length (files), + "files", files, + "starred", starred, + NULL); + + return NAUTILUS_FILE_UNDO_INFO (self); +} + +/* trash */ +struct _NautilusFileUndoInfoTrash +{ + NautilusFileUndoInfo parent_instance; + + GHashTable *trashed; +}; + +G_DEFINE_TYPE (NautilusFileUndoInfoTrash, nautilus_file_undo_info_trash, NAUTILUS_TYPE_FILE_UNDO_INFO) + +static void +trash_strings_func (NautilusFileUndoInfo *info, + gchar **undo_label, + gchar **undo_description, + gchar **redo_label, + gchar **redo_description) +{ + NautilusFileUndoInfoTrash *self = NAUTILUS_FILE_UNDO_INFO_TRASH (info); + gint count = g_hash_table_size (self->trashed); + + if (count != 1) + { + *undo_description = g_strdup_printf (ngettext ("Restore %d item from trash", + "Restore %d items from trash", count), + count); + *redo_description = g_strdup_printf (ngettext ("Move %d item to trash", + "Move %d items to trash", count), + count); + } + else + { + GList *keys; + char *name, *orig_path; + GFile *file; + + keys = g_hash_table_get_keys (self->trashed); + file = keys->data; + name = g_file_get_basename (file); + orig_path = g_file_get_path (file); + *undo_description = g_strdup_printf (_("Restore “%s” to “%s”"), name, orig_path); + + g_free (name); + g_free (orig_path); + g_list_free (keys); + + name = g_file_get_parse_name (file); + *redo_description = g_strdup_printf (_("Move “%s” to trash"), name); + + g_free (name); + } + + *undo_label = g_strdup (_("_Undo Trash")); + *redo_label = g_strdup (_("_Redo Trash")); +} + +static void +trash_redo_func_callback (GHashTable *debuting_uris, + gboolean user_cancel, + gpointer user_data) +{ + NautilusFileUndoInfoTrash *self = user_data; + GHashTable *new_trashed_files; + GTimeVal current_time; + gsize updated_trash_time; + GFile *file; + GList *keys, *l; + + if (!user_cancel) + { + new_trashed_files = + g_hash_table_new_full (g_file_hash, (GEqualFunc) g_file_equal, + g_object_unref, NULL); + + keys = g_hash_table_get_keys (self->trashed); + + g_get_current_time (¤t_time); + updated_trash_time = current_time.tv_sec; + + for (l = keys; l != NULL; l = l->next) + { + file = l->data; + g_hash_table_insert (new_trashed_files, + g_object_ref (file), GSIZE_TO_POINTER (updated_trash_time)); + } + + g_list_free (keys); + g_hash_table_destroy (self->trashed); + + self->trashed = new_trashed_files; + } + + file_undo_info_delete_callback (debuting_uris, user_cancel, user_data); +} + +static void +trash_redo_func (NautilusFileUndoInfo *info, + GtkWindow *parent_window, + NautilusFileOperationsDBusData *dbus_data) +{ + NautilusFileUndoInfoTrash *self = NAUTILUS_FILE_UNDO_INFO_TRASH (info); + + if (g_hash_table_size (self->trashed) > 0) + { + GList *locations; + + locations = g_hash_table_get_keys (self->trashed); + nautilus_file_operations_trash_or_delete_async (locations, parent_window, + dbus_data, + trash_redo_func_callback, self); + + g_list_free (locations); + } +} + +static void +trash_retrieve_files_to_restore_thread (GTask *task, + gpointer source_object, + gpointer task_data, + GCancellable *cancellable) +{ + NautilusFileUndoInfoTrash *self = NAUTILUS_FILE_UNDO_INFO_TRASH (source_object); + GFileEnumerator *enumerator; + GHashTable *to_restore; + GFile *trash; + GError *error = NULL; + + to_restore = g_hash_table_new_full (g_file_hash, (GEqualFunc) g_file_equal, + g_object_unref, g_object_unref); + + trash = g_file_new_for_uri ("trash:///"); + + enumerator = g_file_enumerate_children (trash, + G_FILE_ATTRIBUTE_STANDARD_NAME "," + G_FILE_ATTRIBUTE_TRASH_DELETION_DATE "," + G_FILE_ATTRIBUTE_TRASH_ORIG_PATH, + G_FILE_QUERY_INFO_NOFOLLOW_SYMLINKS, + NULL, &error); + + if (enumerator) + { + GFileInfo *info; + gpointer lookupvalue; + GFile *item; + glong trash_time, orig_trash_time; + const char *origpath; + GFile *origfile; + + while ((info = g_file_enumerator_next_file (enumerator, NULL, &error)) != NULL) + { + /* Retrieve the original file uri */ + origpath = g_file_info_get_attribute_byte_string (info, G_FILE_ATTRIBUTE_TRASH_ORIG_PATH); + origfile = g_file_new_for_path (origpath); + + lookupvalue = g_hash_table_lookup (self->trashed, origfile); + + if (lookupvalue) + { + GDateTime *date; + + orig_trash_time = GPOINTER_TO_SIZE (lookupvalue); + trash_time = 0; + date = g_file_info_get_deletion_date (info); + if (date) + { + trash_time = g_date_time_to_unix (date); + g_date_time_unref (date); + } + + if (ABS (orig_trash_time - trash_time) <= TRASH_TIME_EPSILON) + { + /* File in the trash */ + item = g_file_get_child (trash, g_file_info_get_name (info)); + g_hash_table_insert (to_restore, item, g_object_ref (origfile)); + } + } + + g_object_unref (origfile); + } + g_file_enumerator_close (enumerator, FALSE, NULL); + g_object_unref (enumerator); + } + g_object_unref (trash); + + if (error != NULL) + { + g_task_return_error (task, error); + g_hash_table_destroy (to_restore); + } + else + { + g_task_return_pointer (task, to_restore, NULL); + } +} + +static void +trash_retrieve_files_to_restore_async (NautilusFileUndoInfoTrash *self, + GAsyncReadyCallback callback, + gpointer user_data) +{ + GTask *task; + + task = g_task_new (G_OBJECT (self), NULL, callback, user_data); + + g_task_run_in_thread (task, trash_retrieve_files_to_restore_thread); + + g_object_unref (task); +} + +static void +trash_retrieve_files_ready (GObject *source, + GAsyncResult *res, + gpointer user_data) +{ + NautilusFileUndoInfoTrash *self = NAUTILUS_FILE_UNDO_INFO_TRASH (source); + GHashTable *files_to_restore; + GError *error = NULL; + + files_to_restore = g_task_propagate_pointer (G_TASK (res), &error); + + if (error == NULL && g_hash_table_size (files_to_restore) > 0) + { + GList *gfiles_in_trash, *l; + GFile *item; + GFile *dest; + + gfiles_in_trash = g_hash_table_get_keys (files_to_restore); + + for (l = gfiles_in_trash; l != NULL; l = l->next) + { + item = l->data; + dest = g_hash_table_lookup (files_to_restore, item); + + g_file_move (item, dest, G_FILE_COPY_NOFOLLOW_SYMLINKS, NULL, NULL, NULL, NULL); + } + + g_list_free (gfiles_in_trash); + + /* Here we must do what's necessary for the callback */ + file_undo_info_transfer_callback (NULL, (error == NULL), self); + } + else + { + file_undo_info_transfer_callback (NULL, FALSE, self); + } + + if (files_to_restore != NULL) + { + g_hash_table_destroy (files_to_restore); + } + + g_clear_error (&error); +} + +static void +trash_undo_func (NautilusFileUndoInfo *info, + GtkWindow *parent_window, + NautilusFileOperationsDBusData *dbus_data) +{ + NautilusFileUndoInfoTrash *self = NAUTILUS_FILE_UNDO_INFO_TRASH (info); + + trash_retrieve_files_to_restore_async (self, trash_retrieve_files_ready, NULL); +} + +static void +nautilus_file_undo_info_trash_init (NautilusFileUndoInfoTrash *self) +{ + self->trashed = g_hash_table_new_full (g_file_hash, (GEqualFunc) g_file_equal, + g_object_unref, NULL); +} + +static void +nautilus_file_undo_info_trash_finalize (GObject *obj) +{ + NautilusFileUndoInfoTrash *self = NAUTILUS_FILE_UNDO_INFO_TRASH (obj); + g_hash_table_destroy (self->trashed); + + G_OBJECT_CLASS (nautilus_file_undo_info_trash_parent_class)->finalize (obj); +} + +static void +nautilus_file_undo_info_trash_class_init (NautilusFileUndoInfoTrashClass *klass) +{ + GObjectClass *oclass = G_OBJECT_CLASS (klass); + NautilusFileUndoInfoClass *iclass = NAUTILUS_FILE_UNDO_INFO_CLASS (klass); + + oclass->finalize = nautilus_file_undo_info_trash_finalize; + + iclass->undo_func = trash_undo_func; + iclass->redo_func = trash_redo_func; + iclass->strings_func = trash_strings_func; +} + +NautilusFileUndoInfo * +nautilus_file_undo_info_trash_new (gint item_count) +{ + return g_object_new (NAUTILUS_TYPE_FILE_UNDO_INFO_TRASH, + "op-type", NAUTILUS_FILE_UNDO_OP_MOVE_TO_TRASH, + "item-count", item_count, + NULL); +} + +void +nautilus_file_undo_info_trash_add_file (NautilusFileUndoInfoTrash *self, + GFile *file) +{ + GTimeVal current_time; + gsize orig_trash_time; + + g_get_current_time (¤t_time); + orig_trash_time = current_time.tv_sec; + + g_hash_table_insert (self->trashed, g_object_ref (file), GSIZE_TO_POINTER (orig_trash_time)); +} + +GList * +nautilus_file_undo_info_trash_get_files (NautilusFileUndoInfoTrash *self) +{ + return g_hash_table_get_keys (self->trashed); +} + +/* recursive permissions */ +struct _NautilusFileUndoInfoRecPermissions +{ + NautilusFileUndoInfo parent_instance; + + GFile *dest_dir; + GHashTable *original_permissions; + guint32 dir_mask; + guint32 dir_permissions; + guint32 file_mask; + guint32 file_permissions; +}; + +G_DEFINE_TYPE (NautilusFileUndoInfoRecPermissions, nautilus_file_undo_info_rec_permissions, NAUTILUS_TYPE_FILE_UNDO_INFO) + +static void +rec_permissions_strings_func (NautilusFileUndoInfo *info, + gchar **undo_label, + gchar **undo_description, + gchar **redo_label, + gchar **redo_description) +{ + NautilusFileUndoInfoRecPermissions *self = NAUTILUS_FILE_UNDO_INFO_REC_PERMISSIONS (info); + char *name; + + name = g_file_get_path (self->dest_dir); + + *undo_description = g_strdup_printf (_("Restore original permissions of items enclosed in “%s”"), name); + *redo_description = g_strdup_printf (_("Set permissions of items enclosed in “%s”"), name); + + *undo_label = g_strdup (_("_Undo Change Permissions")); + *redo_label = g_strdup (_("_Redo Change Permissions")); + + g_free (name); +} + +static void +rec_permissions_callback (gboolean success, + gpointer callback_data) +{ + file_undo_info_transfer_callback (NULL, success, callback_data); +} + +static void +rec_permissions_redo_func (NautilusFileUndoInfo *info, + GtkWindow *parent_window, + NautilusFileOperationsDBusData *dbus_data) +{ + NautilusFileUndoInfoRecPermissions *self = NAUTILUS_FILE_UNDO_INFO_REC_PERMISSIONS (info); + gchar *parent_uri; + + parent_uri = g_file_get_uri (self->dest_dir); + nautilus_file_set_permissions_recursive (parent_uri, + self->file_permissions, + self->file_mask, + self->dir_permissions, + self->dir_mask, + rec_permissions_callback, self); + g_free (parent_uri); +} + +static void +rec_permissions_undo_func (NautilusFileUndoInfo *info, + GtkWindow *parent_window, + NautilusFileOperationsDBusData *dbus_data) +{ + NautilusFileUndoInfoRecPermissions *self = NAUTILUS_FILE_UNDO_INFO_REC_PERMISSIONS (info); + + if (g_hash_table_size (self->original_permissions) > 0) + { + GList *gfiles_list; + guint32 perm; + GList *l; + GFile *dest; + char *item; + + gfiles_list = g_hash_table_get_keys (self->original_permissions); + for (l = gfiles_list; l != NULL; l = l->next) + { + item = l->data; + perm = GPOINTER_TO_UINT (g_hash_table_lookup (self->original_permissions, item)); + dest = g_file_new_for_uri (item); + g_file_set_attribute_uint32 (dest, + G_FILE_ATTRIBUTE_UNIX_MODE, + perm, G_FILE_QUERY_INFO_NOFOLLOW_SYMLINKS, NULL, NULL); + g_object_unref (dest); + } + + g_list_free (gfiles_list); + /* Here we must do what's necessary for the callback */ + file_undo_info_transfer_callback (NULL, TRUE, self); + } +} + +static void +nautilus_file_undo_info_rec_permissions_init (NautilusFileUndoInfoRecPermissions *self) +{ + self->original_permissions = g_hash_table_new_full (g_str_hash, g_str_equal, + g_free, NULL); +} + +static void +nautilus_file_undo_info_rec_permissions_finalize (GObject *obj) +{ + NautilusFileUndoInfoRecPermissions *self = NAUTILUS_FILE_UNDO_INFO_REC_PERMISSIONS (obj); + + g_hash_table_destroy (self->original_permissions); + g_clear_object (&self->dest_dir); + + G_OBJECT_CLASS (nautilus_file_undo_info_rec_permissions_parent_class)->finalize (obj); +} + +static void +nautilus_file_undo_info_rec_permissions_class_init (NautilusFileUndoInfoRecPermissionsClass *klass) +{ + GObjectClass *oclass = G_OBJECT_CLASS (klass); + NautilusFileUndoInfoClass *iclass = NAUTILUS_FILE_UNDO_INFO_CLASS (klass); + + oclass->finalize = nautilus_file_undo_info_rec_permissions_finalize; + + iclass->undo_func = rec_permissions_undo_func; + iclass->redo_func = rec_permissions_redo_func; + iclass->strings_func = rec_permissions_strings_func; +} + +NautilusFileUndoInfo * +nautilus_file_undo_info_rec_permissions_new (GFile *dest, + guint32 file_permissions, + guint32 file_mask, + guint32 dir_permissions, + guint32 dir_mask) +{ + NautilusFileUndoInfoRecPermissions *self; + + self = g_object_new (NAUTILUS_TYPE_FILE_UNDO_INFO_REC_PERMISSIONS, + "op-type", NAUTILUS_FILE_UNDO_OP_RECURSIVE_SET_PERMISSIONS, + "item-count", 1, + NULL); + + self->dest_dir = g_object_ref (dest); + self->file_permissions = file_permissions; + self->file_mask = file_mask; + self->dir_permissions = dir_permissions; + self->dir_mask = dir_mask; + + return NAUTILUS_FILE_UNDO_INFO (self); +} + +void +nautilus_file_undo_info_rec_permissions_add_file (NautilusFileUndoInfoRecPermissions *self, + GFile *file, + guint32 permission) +{ + gchar *original_uri = g_file_get_uri (file); + g_hash_table_insert (self->original_permissions, original_uri, GUINT_TO_POINTER (permission)); +} + +/* single file change permissions */ +struct _NautilusFileUndoInfoPermissions +{ + NautilusFileUndoInfo parent_instance; + + GFile *target_file; + guint32 current_permissions; + guint32 new_permissions; +}; + +G_DEFINE_TYPE (NautilusFileUndoInfoPermissions, nautilus_file_undo_info_permissions, NAUTILUS_TYPE_FILE_UNDO_INFO) + +static void +permissions_strings_func (NautilusFileUndoInfo *info, + gchar **undo_label, + gchar **undo_description, + gchar **redo_label, + gchar **redo_description) +{ + NautilusFileUndoInfoPermissions *self = NAUTILUS_FILE_UNDO_INFO_PERMISSIONS (info); + gchar *name; + + name = g_file_get_parse_name (self->target_file); + *undo_description = g_strdup_printf (_("Restore original permissions of “%s”"), name); + *redo_description = g_strdup_printf (_("Set permissions of “%s”"), name); + + *undo_label = g_strdup (_("_Undo Change Permissions")); + *redo_label = g_strdup (_("_Redo Change Permissions")); + + g_free (name); +} + +static void +permissions_real_func (NautilusFileUndoInfoPermissions *self, + guint32 permissions) +{ + NautilusFile *file; + + file = nautilus_file_get (self->target_file); + nautilus_file_set_permissions (file, permissions, + file_undo_info_operation_callback, self); + + nautilus_file_unref (file); +} + +static void +permissions_redo_func (NautilusFileUndoInfo *info, + GtkWindow *parent_window, + NautilusFileOperationsDBusData *dbus_data) +{ + NautilusFileUndoInfoPermissions *self = NAUTILUS_FILE_UNDO_INFO_PERMISSIONS (info); + permissions_real_func (self, self->new_permissions); +} + +static void +permissions_undo_func (NautilusFileUndoInfo *info, + GtkWindow *parent_window, + NautilusFileOperationsDBusData *dbus_data) +{ + NautilusFileUndoInfoPermissions *self = NAUTILUS_FILE_UNDO_INFO_PERMISSIONS (info); + permissions_real_func (self, self->current_permissions); +} + +static void +nautilus_file_undo_info_permissions_init (NautilusFileUndoInfoPermissions *self) +{ +} + +static void +nautilus_file_undo_info_permissions_finalize (GObject *obj) +{ + NautilusFileUndoInfoPermissions *self = NAUTILUS_FILE_UNDO_INFO_PERMISSIONS (obj); + g_clear_object (&self->target_file); + + G_OBJECT_CLASS (nautilus_file_undo_info_permissions_parent_class)->finalize (obj); +} + +static void +nautilus_file_undo_info_permissions_class_init (NautilusFileUndoInfoPermissionsClass *klass) +{ + GObjectClass *oclass = G_OBJECT_CLASS (klass); + NautilusFileUndoInfoClass *iclass = NAUTILUS_FILE_UNDO_INFO_CLASS (klass); + + oclass->finalize = nautilus_file_undo_info_permissions_finalize; + + iclass->undo_func = permissions_undo_func; + iclass->redo_func = permissions_redo_func; + iclass->strings_func = permissions_strings_func; +} + +NautilusFileUndoInfo * +nautilus_file_undo_info_permissions_new (GFile *file, + guint32 current_permissions, + guint32 new_permissions) +{ + NautilusFileUndoInfoPermissions *self; + + self = g_object_new (NAUTILUS_TYPE_FILE_UNDO_INFO_PERMISSIONS, + "op-type", NAUTILUS_FILE_UNDO_OP_SET_PERMISSIONS, + "item-count", 1, + NULL); + + self->target_file = g_object_ref (file); + self->current_permissions = current_permissions; + self->new_permissions = new_permissions; + + return NAUTILUS_FILE_UNDO_INFO (self); +} + +/* group and owner change */ +struct _NautilusFileUndoInfoOwnership +{ + NautilusFileUndoInfo parent_instance; + + GFile *target_file; + char *original_ownership; + char *new_ownership; +}; + +G_DEFINE_TYPE (NautilusFileUndoInfoOwnership, nautilus_file_undo_info_ownership, NAUTILUS_TYPE_FILE_UNDO_INFO) + +static void +ownership_strings_func (NautilusFileUndoInfo *info, + gchar **undo_label, + gchar **undo_description, + gchar **redo_label, + gchar **redo_description) +{ + NautilusFileUndoInfoOwnership *self = NAUTILUS_FILE_UNDO_INFO_OWNERSHIP (info); + NautilusFileUndoOp op_type = nautilus_file_undo_info_get_op_type (info); + gchar *name; + + name = g_file_get_parse_name (self->target_file); + + if (op_type == NAUTILUS_FILE_UNDO_OP_CHANGE_OWNER) + { + *undo_description = g_strdup_printf (_("Restore group of “%s” to “%s”"), + name, self->original_ownership); + *redo_description = g_strdup_printf (_("Set group of “%s” to “%s”"), + name, self->new_ownership); + + *undo_label = g_strdup (_("_Undo Change Group")); + *redo_label = g_strdup (_("_Redo Change Group")); + } + else if (op_type == NAUTILUS_FILE_UNDO_OP_CHANGE_GROUP) + { + *undo_description = g_strdup_printf (_("Restore owner of “%s” to “%s”"), + name, self->original_ownership); + *redo_description = g_strdup_printf (_("Set owner of “%s” to “%s”"), + name, self->new_ownership); + + *undo_label = g_strdup (_("_Undo Change Owner")); + *redo_label = g_strdup (_("_Redo Change Owner")); + } + + g_free (name); +} + +static void +ownership_real_func (NautilusFileUndoInfoOwnership *self, + const gchar *ownership) +{ + NautilusFileUndoOp op_type = nautilus_file_undo_info_get_op_type (NAUTILUS_FILE_UNDO_INFO (self)); + NautilusFile *file; + + file = nautilus_file_get (self->target_file); + + if (op_type == NAUTILUS_FILE_UNDO_OP_CHANGE_OWNER) + { + nautilus_file_set_owner (file, + ownership, + file_undo_info_operation_callback, self); + } + else if (op_type == NAUTILUS_FILE_UNDO_OP_CHANGE_GROUP) + { + nautilus_file_set_group (file, + ownership, + file_undo_info_operation_callback, self); + } + + nautilus_file_unref (file); +} + +static void +ownership_redo_func (NautilusFileUndoInfo *info, + GtkWindow *parent_window, + NautilusFileOperationsDBusData *dbus_data) +{ + NautilusFileUndoInfoOwnership *self = NAUTILUS_FILE_UNDO_INFO_OWNERSHIP (info); + ownership_real_func (self, self->new_ownership); +} + +static void +ownership_undo_func (NautilusFileUndoInfo *info, + GtkWindow *parent_window, + NautilusFileOperationsDBusData *dbus_data) +{ + NautilusFileUndoInfoOwnership *self = NAUTILUS_FILE_UNDO_INFO_OWNERSHIP (info); + ownership_real_func (self, self->original_ownership); +} + +static void +nautilus_file_undo_info_ownership_init (NautilusFileUndoInfoOwnership *self) +{ +} + +static void +nautilus_file_undo_info_ownership_finalize (GObject *obj) +{ + NautilusFileUndoInfoOwnership *self = NAUTILUS_FILE_UNDO_INFO_OWNERSHIP (obj); + + g_clear_object (&self->target_file); + g_free (self->original_ownership); + g_free (self->new_ownership); + + G_OBJECT_CLASS (nautilus_file_undo_info_ownership_parent_class)->finalize (obj); +} + +static void +nautilus_file_undo_info_ownership_class_init (NautilusFileUndoInfoOwnershipClass *klass) +{ + GObjectClass *oclass = G_OBJECT_CLASS (klass); + NautilusFileUndoInfoClass *iclass = NAUTILUS_FILE_UNDO_INFO_CLASS (klass); + + oclass->finalize = nautilus_file_undo_info_ownership_finalize; + + iclass->undo_func = ownership_undo_func; + iclass->redo_func = ownership_redo_func; + iclass->strings_func = ownership_strings_func; +} + +NautilusFileUndoInfo * +nautilus_file_undo_info_ownership_new (NautilusFileUndoOp op_type, + GFile *file, + const char *current_data, + const char *new_data) +{ + NautilusFileUndoInfoOwnership *self; + + self = g_object_new (NAUTILUS_TYPE_FILE_UNDO_INFO_OWNERSHIP, + "item-count", 1, + "op-type", op_type, + NULL); + + self->target_file = g_object_ref (file); + self->original_ownership = g_strdup (current_data); + self->new_ownership = g_strdup (new_data); + + return NAUTILUS_FILE_UNDO_INFO (self); +} + +/* extract */ +struct _NautilusFileUndoInfoExtract +{ + NautilusFileUndoInfo parent_instance; + + GList *sources; + GFile *destination_directory; + GList *outputs; +}; + +G_DEFINE_TYPE (NautilusFileUndoInfoExtract, nautilus_file_undo_info_extract, NAUTILUS_TYPE_FILE_UNDO_INFO) + +static void +extract_callback (GList *outputs, + gpointer callback_data) +{ + NautilusFileUndoInfoExtract *self = NAUTILUS_FILE_UNDO_INFO_EXTRACT (callback_data); + gboolean success; + + nautilus_file_undo_info_extract_set_outputs (self, outputs); + + success = self->outputs != NULL; + + file_undo_info_transfer_callback (NULL, success, self); +} + +static void +extract_strings_func (NautilusFileUndoInfo *info, + gchar **undo_label, + gchar **undo_description, + gchar **redo_label, + gchar **redo_description) +{ + NautilusFileUndoInfoExtract *self = NAUTILUS_FILE_UNDO_INFO_EXTRACT (info); + gint total_sources; + gint total_outputs; + + *undo_label = g_strdup (_("_Undo Extract")); + *redo_label = g_strdup (_("_Redo Extract")); + + total_sources = g_list_length (self->sources); + total_outputs = g_list_length (self->outputs); + + if (total_outputs == 1) + { + GFile *output; + g_autofree gchar *name = NULL; + + output = self->outputs->data; + name = g_file_get_parse_name (output); + + *undo_description = g_strdup_printf (_("Delete “%s”"), name); + } + else + { + *undo_description = g_strdup_printf (ngettext ("Delete %d extracted file", + "Delete %d extracted files", + total_outputs), + total_outputs); + } + + if (total_sources == 1) + { + GFile *source; + g_autofree gchar *name = NULL; + + source = self->sources->data; + name = g_file_get_parse_name (source); + + *redo_description = g_strdup_printf (_("Extract “%s”"), name); + } + else + { + *redo_description = g_strdup_printf (ngettext ("Extract %d file", + "Extract %d files", + total_sources), + total_sources); + } +} + +static void +extract_redo_func (NautilusFileUndoInfo *info, + GtkWindow *parent_window, + NautilusFileOperationsDBusData *dbus_data) +{ + NautilusFileUndoInfoExtract *self = NAUTILUS_FILE_UNDO_INFO_EXTRACT (info); + + nautilus_file_operations_extract_files (self->sources, + self->destination_directory, + parent_window, + dbus_data, + extract_callback, + self); +} + +static void +extract_undo_func (NautilusFileUndoInfo *info, + GtkWindow *parent_window, + NautilusFileOperationsDBusData *dbus_data) +{ + NautilusFileUndoInfoExtract *self = NAUTILUS_FILE_UNDO_INFO_EXTRACT (info); + + nautilus_file_operations_delete_async (self->outputs, parent_window, + dbus_data, + file_undo_info_delete_callback, self); +} + +static void +nautilus_file_undo_info_extract_init (NautilusFileUndoInfoExtract *self) +{ +} + +static void +nautilus_file_undo_info_extract_finalize (GObject *obj) +{ + NautilusFileUndoInfoExtract *self = NAUTILUS_FILE_UNDO_INFO_EXTRACT (obj); + + g_object_unref (self->destination_directory); + g_list_free_full (self->sources, g_object_unref); + if (self->outputs) + { + g_list_free_full (self->outputs, g_object_unref); + } + + G_OBJECT_CLASS (nautilus_file_undo_info_extract_parent_class)->finalize (obj); +} + +static void +nautilus_file_undo_info_extract_class_init (NautilusFileUndoInfoExtractClass *klass) +{ + GObjectClass *oclass = G_OBJECT_CLASS (klass); + NautilusFileUndoInfoClass *iclass = NAUTILUS_FILE_UNDO_INFO_CLASS (klass); + + oclass->finalize = nautilus_file_undo_info_extract_finalize; + + iclass->undo_func = extract_undo_func; + iclass->redo_func = extract_redo_func; + iclass->strings_func = extract_strings_func; +} + +void +nautilus_file_undo_info_extract_set_outputs (NautilusFileUndoInfoExtract *self, + GList *outputs) +{ + if (self->outputs) + { + g_list_free_full (self->outputs, g_object_unref); + } + self->outputs = g_list_copy_deep (outputs, + (GCopyFunc) g_object_ref, + NULL); +} + +NautilusFileUndoInfo * +nautilus_file_undo_info_extract_new (GList *sources, + GFile *destination_directory) +{ + NautilusFileUndoInfoExtract *self; + + self = g_object_new (NAUTILUS_TYPE_FILE_UNDO_INFO_EXTRACT, + "item-count", 1, + "op-type", NAUTILUS_FILE_UNDO_OP_EXTRACT, + NULL); + + self->sources = g_list_copy_deep (sources, + (GCopyFunc) g_object_ref, + NULL); + self->destination_directory = g_object_ref (destination_directory); + + return NAUTILUS_FILE_UNDO_INFO (self); +} + + +/* compress */ +struct _NautilusFileUndoInfoCompress +{ + NautilusFileUndoInfo parent_instance; + + GList *sources; + GFile *output; + AutoarFormat format; + AutoarFilter filter; + gchar *passphrase; +}; + +G_DEFINE_TYPE (NautilusFileUndoInfoCompress, nautilus_file_undo_info_compress, NAUTILUS_TYPE_FILE_UNDO_INFO) + +static void +compress_callback (GFile *new_file, + gboolean success, + gpointer callback_data) +{ + NautilusFileUndoInfoCompress *self = NAUTILUS_FILE_UNDO_INFO_COMPRESS (callback_data); + + if (success) + { + g_set_object (&self->output, new_file); + } + + file_undo_info_transfer_callback (NULL, success, self); +} + +static void +compress_strings_func (NautilusFileUndoInfo *info, + gchar **undo_label, + gchar **undo_description, + gchar **redo_label, + gchar **redo_description) +{ + NautilusFileUndoInfoCompress *self = NAUTILUS_FILE_UNDO_INFO_COMPRESS (info); + g_autofree gchar *output_name = NULL; + gint sources_count; + + output_name = g_file_get_parse_name (self->output); + *undo_description = g_strdup_printf (_("Delete “%s”"), output_name); + + sources_count = g_list_length (self->sources); + if (sources_count == 1) + { + GFile *source; + g_autofree gchar *source_name = NULL; + + source = self->sources->data; + source_name = g_file_get_parse_name (source); + + *redo_description = g_strdup_printf (_("Compress “%s”"), source_name); + } + else + { + *redo_description = g_strdup_printf (ngettext ("Compress %d file", + "Compress %d files", + sources_count), + sources_count); + } + + *undo_label = g_strdup (_("_Undo Compress")); + *redo_label = g_strdup (_("_Redo Compress")); +} + +static void +compress_redo_func (NautilusFileUndoInfo *info, + GtkWindow *parent_window, + NautilusFileOperationsDBusData *dbus_data) +{ + NautilusFileUndoInfoCompress *self = NAUTILUS_FILE_UNDO_INFO_COMPRESS (info); + + nautilus_file_operations_compress (self->sources, + self->output, + self->format, + self->filter, + self->passphrase, + parent_window, + dbus_data, + compress_callback, + self); +} + +static void +compress_undo_func (NautilusFileUndoInfo *info, + GtkWindow *parent_window, + NautilusFileOperationsDBusData *dbus_data) +{ + NautilusFileUndoInfoCompress *self = NAUTILUS_FILE_UNDO_INFO_COMPRESS (info); + GList *files = NULL; + + files = g_list_prepend (files, self->output); + + nautilus_file_operations_delete_async (files, parent_window, + dbus_data, + file_undo_info_delete_callback, self); + + g_list_free (files); +} + +static void +nautilus_file_undo_info_compress_init (NautilusFileUndoInfoCompress *self) +{ +} + +static void +nautilus_file_undo_info_compress_finalize (GObject *obj) +{ + NautilusFileUndoInfoCompress *self = NAUTILUS_FILE_UNDO_INFO_COMPRESS (obj); + + g_list_free_full (self->sources, g_object_unref); + g_clear_object (&self->output); + g_free (self->passphrase); + + G_OBJECT_CLASS (nautilus_file_undo_info_compress_parent_class)->finalize (obj); +} + +static void +nautilus_file_undo_info_compress_class_init (NautilusFileUndoInfoCompressClass *klass) +{ + GObjectClass *oclass = G_OBJECT_CLASS (klass); + NautilusFileUndoInfoClass *iclass = NAUTILUS_FILE_UNDO_INFO_CLASS (klass); + + oclass->finalize = nautilus_file_undo_info_compress_finalize; + + iclass->undo_func = compress_undo_func; + iclass->redo_func = compress_redo_func; + iclass->strings_func = compress_strings_func; +} + +NautilusFileUndoInfo * +nautilus_file_undo_info_compress_new (GList *sources, + GFile *output, + AutoarFormat format, + AutoarFilter filter, + const gchar *passphrase) +{ + NautilusFileUndoInfoCompress *self; + + self = g_object_new (NAUTILUS_TYPE_FILE_UNDO_INFO_COMPRESS, + "item-count", 1, + "op-type", NAUTILUS_FILE_UNDO_OP_COMPRESS, + NULL); + + self->sources = g_list_copy_deep (sources, (GCopyFunc) g_object_ref, NULL); + self->output = g_object_ref (output); + self->format = format; + self->filter = filter; + self->passphrase = g_strdup (passphrase); + + return NAUTILUS_FILE_UNDO_INFO (self); +} diff --git a/src/nautilus-file-undo-operations.h b/src/nautilus-file-undo-operations.h new file mode 100644 index 0000000..09ae17c --- /dev/null +++ b/src/nautilus-file-undo-operations.h @@ -0,0 +1,230 @@ + +/* nautilus-file-undo-operations.h - Manages undo/redo of file operations + * + * Copyright (C) 2007-2011 Amos Brocco + * Copyright (C) 2010 Red Hat, Inc. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public + * License as published by the Free Software Foundation; either + * version 2 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public + * License along with this library; if not, see . + * + * Authors: Amos Brocco + * Cosimo Cecchi + * + */ + +#pragma once + +#include +#include +#include + +#include "nautilus-file-operations-dbus-data.h" + +typedef enum +{ + NAUTILUS_FILE_UNDO_OP_INVALID, + NAUTILUS_FILE_UNDO_OP_COPY, + NAUTILUS_FILE_UNDO_OP_DUPLICATE, + NAUTILUS_FILE_UNDO_OP_MOVE, + NAUTILUS_FILE_UNDO_OP_RENAME, + NAUTILUS_FILE_UNDO_OP_BATCH_RENAME, + NAUTILUS_FILE_UNDO_OP_STARRED, + NAUTILUS_FILE_UNDO_OP_CREATE_EMPTY_FILE, + NAUTILUS_FILE_UNDO_OP_CREATE_FILE_FROM_TEMPLATE, + NAUTILUS_FILE_UNDO_OP_CREATE_FOLDER, + NAUTILUS_FILE_UNDO_OP_EXTRACT, + NAUTILUS_FILE_UNDO_OP_COMPRESS, + NAUTILUS_FILE_UNDO_OP_MOVE_TO_TRASH, + NAUTILUS_FILE_UNDO_OP_RESTORE_FROM_TRASH, + NAUTILUS_FILE_UNDO_OP_CREATE_LINK, + NAUTILUS_FILE_UNDO_OP_RECURSIVE_SET_PERMISSIONS, + NAUTILUS_FILE_UNDO_OP_SET_PERMISSIONS, + NAUTILUS_FILE_UNDO_OP_CHANGE_GROUP, + NAUTILUS_FILE_UNDO_OP_CHANGE_OWNER, + NAUTILUS_FILE_UNDO_OP_NUM_TYPES, +} NautilusFileUndoOp; + +#define NAUTILUS_TYPE_FILE_UNDO_INFO nautilus_file_undo_info_get_type () +G_DECLARE_DERIVABLE_TYPE (NautilusFileUndoInfo, nautilus_file_undo_info, + NAUTILUS, FILE_UNDO_INFO, + GObject) + +struct _NautilusFileUndoInfoClass +{ + GObjectClass parent_class; + + void (* undo_func) (NautilusFileUndoInfo *self, + GtkWindow *parent_window, + NautilusFileOperationsDBusData *dbus_data); + void (* redo_func) (NautilusFileUndoInfo *self, + GtkWindow *parent_window, + NautilusFileOperationsDBusData *dbus_data); + + void (* strings_func) (NautilusFileUndoInfo *self, + gchar **undo_label, + gchar **undo_description, + gchar **redo_label, + gchar **redo_description); +}; + +void nautilus_file_undo_info_apply_async (NautilusFileUndoInfo *self, + gboolean undo, + GtkWindow *parent_window, + NautilusFileOperationsDBusData *dbus_data, + GAsyncReadyCallback callback, + gpointer user_data); +gboolean nautilus_file_undo_info_apply_finish (NautilusFileUndoInfo *self, + GAsyncResult *res, + gboolean *user_cancel, + GError **error); + +void nautilus_file_undo_info_get_strings (NautilusFileUndoInfo *self, + gchar **undo_label, + gchar **undo_description, + gchar **redo_label, + gchar **redo_description); + +NautilusFileUndoOp nautilus_file_undo_info_get_op_type (NautilusFileUndoInfo *self); + +/* copy/move/duplicate/link/restore from trash */ +#define NAUTILUS_TYPE_FILE_UNDO_INFO_EXT nautilus_file_undo_info_ext_get_type () +G_DECLARE_FINAL_TYPE (NautilusFileUndoInfoExt, nautilus_file_undo_info_ext, + NAUTILUS, FILE_UNDO_INFO_EXT, + NautilusFileUndoInfo) + +NautilusFileUndoInfo *nautilus_file_undo_info_ext_new (NautilusFileUndoOp op_type, + gint item_count, + GFile *src_dir, + GFile *target_dir); +void nautilus_file_undo_info_ext_add_origin_target_pair (NautilusFileUndoInfoExt *self, + GFile *origin, + GFile *target); + +/* create new file/folder */ +#define NAUTILUS_TYPE_FILE_UNDO_INFO_CREATE nautilus_file_undo_info_create_get_type () +G_DECLARE_FINAL_TYPE (NautilusFileUndoInfoCreate, nautilus_file_undo_info_create, + NAUTILUS, FILE_UNDO_INFO_CREATE, + NautilusFileUndoInfo) + +NautilusFileUndoInfo *nautilus_file_undo_info_create_new (NautilusFileUndoOp op_type); +void nautilus_file_undo_info_create_set_data (NautilusFileUndoInfoCreate *self, + GFile *file, + const char *template, + gint length); + +/* rename */ +#define NAUTILUS_TYPE_FILE_UNDO_INFO_RENAME nautilus_file_undo_info_rename_get_type () +G_DECLARE_FINAL_TYPE (NautilusFileUndoInfoRename, nautilus_file_undo_info_rename, + NAUTILUS, FILE_UNDO_INFO_RENAME, + NautilusFileUndoInfo) + +NautilusFileUndoInfo *nautilus_file_undo_info_rename_new (void); +void nautilus_file_undo_info_rename_set_data_pre (NautilusFileUndoInfoRename *self, + GFile *old_file, + gchar *old_display_name, + gchar *new_display_name); +void nautilus_file_undo_info_rename_set_data_post (NautilusFileUndoInfoRename *self, + GFile *new_file); + +/* batch rename */ +#define NAUTILUS_TYPE_FILE_UNDO_INFO_BATCH_RENAME nautilus_file_undo_info_batch_rename_get_type () +G_DECLARE_FINAL_TYPE (NautilusFileUndoInfoBatchRename, nautilus_file_undo_info_batch_rename, + NAUTILUS, FILE_UNDO_INFO_BATCH_RENAME, + NautilusFileUndoInfo) + +NautilusFileUndoInfo *nautilus_file_undo_info_batch_rename_new (gint item_count); +void nautilus_file_undo_info_batch_rename_set_data_pre (NautilusFileUndoInfoBatchRename *self, + GList *old_files); +void nautilus_file_undo_info_batch_rename_set_data_post (NautilusFileUndoInfoBatchRename *self, + GList *new_files); + +/* starred files */ +#define NAUTILUS_TYPE_FILE_UNDO_INFO_STARRED (nautilus_file_undo_info_starred_get_type ()) +G_DECLARE_FINAL_TYPE (NautilusFileUndoInfoStarred, nautilus_file_undo_info_starred, + NAUTILUS, FILE_UNDO_INFO_STARRED, + NautilusFileUndoInfo) + +NautilusFileUndoInfo *nautilus_file_undo_info_starred_new (GList *files, + gboolean starred); +GList *nautilus_file_undo_info_starred_get_files (NautilusFileUndoInfoStarred *self); +gboolean nautilus_file_undo_info_starred_is_starred (NautilusFileUndoInfoStarred *self); + +/* trash */ +#define NAUTILUS_TYPE_FILE_UNDO_INFO_TRASH (nautilus_file_undo_info_trash_get_type ()) +G_DECLARE_FINAL_TYPE (NautilusFileUndoInfoTrash, nautilus_file_undo_info_trash, + NAUTILUS, FILE_UNDO_INFO_TRASH, + NautilusFileUndoInfo) + +NautilusFileUndoInfo *nautilus_file_undo_info_trash_new (gint item_count); +void nautilus_file_undo_info_trash_add_file (NautilusFileUndoInfoTrash *self, + GFile *file); +GList *nautilus_file_undo_info_trash_get_files (NautilusFileUndoInfoTrash *self); + +/* recursive permissions */ +#define NAUTILUS_TYPE_FILE_UNDO_INFO_REC_PERMISSIONS nautilus_file_undo_info_rec_permissions_get_type () +G_DECLARE_FINAL_TYPE (NautilusFileUndoInfoRecPermissions, nautilus_file_undo_info_rec_permissions, + NAUTILUS, FILE_UNDO_INFO_REC_PERMISSIONS, + NautilusFileUndoInfo) + +NautilusFileUndoInfo *nautilus_file_undo_info_rec_permissions_new (GFile *dest, + guint32 file_permissions, + guint32 file_mask, + guint32 dir_permissions, + guint32 dir_mask); +void nautilus_file_undo_info_rec_permissions_add_file (NautilusFileUndoInfoRecPermissions *self, + GFile *file, + guint32 permission); + +/* single file change permissions */ +#define NAUTILUS_TYPE_FILE_UNDO_INFO_PERMISSIONS nautilus_file_undo_info_permissions_get_type () +G_DECLARE_FINAL_TYPE (NautilusFileUndoInfoPermissions, nautilus_file_undo_info_permissions, + NAUTILUS, FILE_UNDO_INFO_PERMISSIONS, + NautilusFileUndoInfo) + +NautilusFileUndoInfo *nautilus_file_undo_info_permissions_new (GFile *file, + guint32 current_permissions, + guint32 new_permissions); + +/* group and owner change */ +#define NAUTILUS_TYPE_FILE_UNDO_INFO_OWNERSHIP nautilus_file_undo_info_ownership_get_type () +G_DECLARE_FINAL_TYPE (NautilusFileUndoInfoOwnership, nautilus_file_undo_info_ownership, + NAUTILUS, FILE_UNDO_INFO_OWNERSHIP, + NautilusFileUndoInfo) + +NautilusFileUndoInfo *nautilus_file_undo_info_ownership_new (NautilusFileUndoOp op_type, + GFile *file, + const char *current_data, + const char *new_data); + +/* extract */ +#define NAUTILUS_TYPE_FILE_UNDO_INFO_EXTRACT nautilus_file_undo_info_extract_get_type () +G_DECLARE_FINAL_TYPE (NautilusFileUndoInfoExtract, nautilus_file_undo_info_extract, + NAUTILUS, FILE_UNDO_INFO_EXTRACT, + NautilusFileUndoInfo) + +NautilusFileUndoInfo * nautilus_file_undo_info_extract_new (GList *sources, + GFile *destination_directory); +void nautilus_file_undo_info_extract_set_outputs (NautilusFileUndoInfoExtract *self, + GList *outputs); + +/* compress */ +#define NAUTILUS_TYPE_FILE_UNDO_INFO_COMPRESS nautilus_file_undo_info_compress_get_type () +G_DECLARE_FINAL_TYPE (NautilusFileUndoInfoCompress, nautilus_file_undo_info_compress, + NAUTILUS, FILE_UNDO_INFO_COMPRESS, + NautilusFileUndoInfo) + +NautilusFileUndoInfo * nautilus_file_undo_info_compress_new (GList *sources, + GFile *output, + AutoarFormat format, + AutoarFilter filter, + const gchar *passphrase); diff --git a/src/nautilus-file-utilities.c b/src/nautilus-file-utilities.c new file mode 100644 index 0000000..6043a11 --- /dev/null +++ b/src/nautilus-file-utilities.c @@ -0,0 +1,1515 @@ +/* nautilus-file-utilities.c - implementation of file manipulation routines. + * + * Copyright (C) 1999, 2000, 2001 Eazel, Inc. + * + * The Gnome Library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Library General Public License as + * published by the Free Software Foundation; either version 2 of the + * License, or (at your option) any later version. + * + * The Gnome Library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Library General Public License for more details. + * + * You should have received a copy of the GNU Library General Public + * License along with the Gnome Library; see the file COPYING.LIB. If not, + * see . + * + * Authors: John Sullivan + */ + +#include +#include "nautilus-file-utilities.h" + +#include "nautilus-application.h" +#include "nautilus-global-preferences.h" +#include "nautilus-icon-names.h" +#include "nautilus-lib-self-check-functions.h" +#include "nautilus-metadata.h" +#include "nautilus-file.h" +#include "nautilus-file-operations.h" +#include "nautilus-search-directory.h" +#include "nautilus-starred-directory.h" +#include "nautilus-ui-utilities.h" +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#define NAUTILUS_USER_DIRECTORY_NAME "nautilus" +#define DEFAULT_NAUTILUS_DIRECTORY_MODE (0755) + +/* Allowed characters outside alphanumeric for unreserved. */ +#define G_URI_OTHER_UNRESERVED "-._~" + +/* This or something equivalent will eventually go into glib/guri.h */ +gboolean +nautilus_uri_parse (const char *uri, + char **host, + guint16 *port, + char **userinfo) +{ + g_autofree char *tmp_str = NULL; + const char *start, *p; + char c; + + g_return_val_if_fail (uri != NULL, FALSE); + + if (host) + { + *host = NULL; + } + + if (port) + { + *port = 0; + } + + if (userinfo) + { + *userinfo = NULL; + } + + /* From RFC 3986 Decodes: + * URI = scheme ":" hier-part [ "?" query ] [ "#" fragment ] + * hier-part = "//" authority path-abempty + * path-abempty = *( "/" segment ) + * authority = [ userinfo "@" ] host [ ":" port ] + */ + + /* Check we have a valid scheme */ + tmp_str = g_uri_parse_scheme (uri); + + if (tmp_str == NULL) + { + return FALSE; + } + + /* Decode hier-part: + * hier-part = "//" authority path-abempty + */ + p = uri; + start = strstr (p, "//"); + + if (start == NULL) + { + return FALSE; + } + + start += 2; + + if (strchr (start, '@') != NULL) + { + /* Decode userinfo: + * userinfo = *( unreserved / pct-encoded / sub-delims / ":" ) + * unreserved = ALPHA / DIGIT / "-" / "." / "_" / "~" + * pct-encoded = "%" HEXDIG HEXDIG + */ + p = start; + while (1) + { + c = *p++; + + if (c == '@') + { + break; + } + + /* pct-encoded */ + if (c == '%') + { + if (!(g_ascii_isxdigit (p[0]) || + g_ascii_isxdigit (p[1]))) + { + return FALSE; + } + + p++; + + continue; + } + + /* unreserved / sub-delims / : */ + if (!(g_ascii_isalnum (c) || + strchr (G_URI_OTHER_UNRESERVED, c) || + strchr (G_URI_RESERVED_CHARS_SUBCOMPONENT_DELIMITERS, c) || + c == ':')) + { + return FALSE; + } + } + + if (userinfo) + { + *userinfo = g_strndup (start, p - start - 1); + } + + start = p; + } + else + { + p = start; + } + + + /* decode host: + * host = IP-literal / IPv4address / reg-name + * reg-name = *( unreserved / pct-encoded / sub-delims ) + */ + + /* If IPv6 or IPvFuture */ + if (*p == '[') + { + start++; + p++; + while (1) + { + c = *p++; + + if (c == ']') + { + break; + } + + /* unreserved / sub-delims */ + if (!(g_ascii_isalnum (c) || + strchr (G_URI_OTHER_UNRESERVED, c) || + strchr (G_URI_RESERVED_CHARS_SUBCOMPONENT_DELIMITERS, c) || + c == ':' || + c == '.')) + { + goto error; + } + } + } + else + { + while (1) + { + c = *p++; + + if (c == ':' || + c == '/' || + c == '?' || + c == '#' || + c == '\0') + { + break; + } + + /* pct-encoded */ + if (c == '%') + { + if (!(g_ascii_isxdigit (p[0]) || + g_ascii_isxdigit (p[1]))) + { + goto error; + } + + p++; + + continue; + } + + /* unreserved / sub-delims */ + if (!(g_ascii_isalnum (c) || + strchr (G_URI_OTHER_UNRESERVED, c) || + strchr (G_URI_RESERVED_CHARS_SUBCOMPONENT_DELIMITERS, c))) + { + goto error; + } + } + } + + if (host) + { + *host = g_uri_unescape_segment (start, p - 1, NULL); + } + + if (c == ':') + { + /* Decode pot: + * port = *DIGIT + */ + guint tmp = 0; + + while (1) + { + c = *p++; + + if (c == '/' || + c == '?' || + c == '#' || + c == '\0') + { + break; + } + + if (!g_ascii_isdigit (c)) + { + goto error; + } + + tmp = (tmp * 10) + (c - '0'); + + if (tmp > 65535) + { + goto error; + } + } + if (port) + { + *port = (guint16) tmp; + } + } + + return TRUE; + +error: + if (host && *host) + { + g_free (*host); + *host = NULL; + } + + if (userinfo && *userinfo) + { + g_free (*userinfo); + *userinfo = NULL; + } + + return FALSE; +} + +char * +nautilus_compute_title_for_location (GFile *location) +{ + NautilusFile *file; + GMount *mount; + char *title; + + /* TODO-gio: This doesn't really work all that great if the + * info about the file isn't known atm... */ + + if (nautilus_is_home_directory (location)) + { + return g_strdup (_("Home")); + } + + if ((mount = nautilus_get_mounted_mount_for_root (location)) != NULL) + { + title = g_mount_get_name (mount); + + g_object_unref (mount); + + return title; + } + + title = NULL; + if (location) + { + file = nautilus_file_get (location); + + if (nautilus_file_is_other_locations (file)) + { + title = g_strdup (_("Other Locations")); + } + else if (nautilus_file_is_starred_location (file)) + { + title = g_strdup (_("Starred")); + } + else + { + title = nautilus_file_get_description (file); + + if (title == NULL) + { + title = nautilus_file_get_display_name (file); + } + } + nautilus_file_unref (file); + } + + if (title == NULL) + { + title = g_file_get_basename (location); + } + + return title; +} + + +/** + * nautilus_get_user_directory: + * + * Get the path for the directory containing nautilus settings. + * + * Return value: the directory path. + **/ +char * +nautilus_get_user_directory (void) +{ + char *user_directory = NULL; + + user_directory = g_build_filename (g_get_user_config_dir (), + NAUTILUS_USER_DIRECTORY_NAME, + NULL); + + if (!g_file_test (user_directory, G_FILE_TEST_EXISTS)) + { + g_mkdir_with_parents (user_directory, DEFAULT_NAUTILUS_DIRECTORY_MODE); + /* FIXME bugzilla.gnome.org 41286: + * How should we handle the case where this mkdir fails? + * Note that nautilus_application_startup will refuse to launch if this + * directory doesn't get created, so that case is OK. But the directory + * could be deleted after Nautilus was launched, and perhaps + * there is some bad side-effect of not handling that case. + */ + } + + return user_directory; +} + +/** + * nautilus_get_scripts_directory_path: + * + * Get the path for the directory containing nautilus scripts. + * + * Return value: the directory path containing nautilus scripts + **/ +char * +nautilus_get_scripts_directory_path (void) +{ + return g_build_filename (g_get_user_data_dir (), "nautilus", "scripts", NULL); +} + +char * +nautilus_get_home_directory_uri (void) +{ + return g_filename_to_uri (g_get_home_dir (), NULL, NULL); +} + + +gboolean +nautilus_should_use_templates_directory (void) +{ + const char *dir; + gboolean res; + + dir = g_get_user_special_dir (G_USER_DIRECTORY_TEMPLATES); + res = dir && (g_strcmp0 (dir, g_get_home_dir ()) != 0); + return res; +} + +char * +nautilus_get_templates_directory (void) +{ + return g_strdup (g_get_user_special_dir (G_USER_DIRECTORY_TEMPLATES)); +} + +char * +nautilus_get_templates_directory_uri (void) +{ + char *directory, *uri; + + directory = nautilus_get_templates_directory (); + uri = g_filename_to_uri (directory, NULL, NULL); + g_free (directory); + return uri; +} + +gboolean +nautilus_is_home_directory_file (GFile *dir, + const char *filename) +{ + char *dirname; + static GFile *home_dir_dir = NULL; + static char *home_dir_filename = NULL; + + if (home_dir_dir == NULL) + { + dirname = g_path_get_dirname (g_get_home_dir ()); + home_dir_dir = g_file_new_for_path (dirname); + g_free (dirname); + home_dir_filename = g_path_get_basename (g_get_home_dir ()); + } + + return (g_file_equal (dir, home_dir_dir) && + strcmp (filename, home_dir_filename) == 0); +} + +gboolean +nautilus_is_home_directory (GFile *dir) +{ + static GFile *home_dir = NULL; + + if (home_dir == NULL) + { + home_dir = g_file_new_for_path (g_get_home_dir ()); + } + + return g_file_equal (dir, home_dir); +} + +gboolean +nautilus_is_root_directory (GFile *dir) +{ + static GFile *root_dir = NULL; + + if (root_dir == NULL) + { + root_dir = g_file_new_for_path ("/"); + } + + return g_file_equal (dir, root_dir); +} + +gboolean +nautilus_is_search_directory (GFile *dir) +{ + g_autofree gchar *uri = NULL; + + uri = g_file_get_uri (dir); + return eel_uri_is_search (uri); +} + +gboolean +nautilus_is_recent_directory (GFile *dir) +{ + g_autofree gchar *uri = NULL; + + uri = g_file_get_uri (dir); + + return eel_uri_is_recent (uri); +} + +gboolean +nautilus_is_starred_directory (GFile *dir) +{ + g_autofree gchar *uri = NULL; + + uri = g_file_get_uri (dir); + + if (eel_uri_is_starred (uri)) + { + return TRUE; + } + + return FALSE; +} + +gboolean +nautilus_is_trash_directory (GFile *dir) +{ + g_autofree gchar *uri = NULL; + + uri = g_file_get_uri (dir); + return eel_uri_is_trash (uri); +} + +gboolean +nautilus_is_other_locations_directory (GFile *dir) +{ + g_autofree gchar *uri = NULL; + + uri = g_file_get_uri (dir); + return eel_uri_is_other_locations (uri); +} + +GMount * +nautilus_get_mounted_mount_for_root (GFile *location) +{ + g_autoptr (GVolumeMonitor) volume_monitor = NULL; + GList *mounts; + GList *l; + GMount *mount; + GMount *result = NULL; + GFile *root = NULL; + + volume_monitor = g_volume_monitor_get (); + mounts = g_volume_monitor_get_mounts (volume_monitor); + + for (l = mounts; l != NULL; l = l->next) + { + mount = l->data; + + if (g_mount_is_shadowed (mount)) + { + continue; + } + + root = g_mount_get_root (mount); + if (g_file_equal (location, root)) + { + result = g_object_ref (mount); + break; + } + } + + g_clear_object (&root); + g_list_free_full (mounts, g_object_unref); + + return result; +} + +GFile * +nautilus_generate_unique_file_in_directory (GFile *directory, + const char *basename) +{ + g_autofree char *basename_without_extension = NULL; + const char *extension; + GFile *child; + int copy; + + g_return_val_if_fail (directory != NULL, NULL); + g_return_val_if_fail (basename != NULL, NULL); + g_return_val_if_fail (g_file_query_exists (directory, NULL), NULL); + + basename_without_extension = eel_filename_strip_extension (basename); + extension = eel_filename_get_extension_offset (basename); + + child = g_file_get_child (directory, basename); + + copy = 1; + while (g_file_query_exists (child, NULL)) + { + g_autofree char *filename = NULL; + + g_object_unref (child); + + filename = g_strdup_printf ("%s (%d)%s", + basename_without_extension, + copy, + extension ? extension : ""); + child = g_file_get_child (directory, filename); + + copy++; + } + + return child; +} + +GFile * +nautilus_find_existing_uri_in_hierarchy (GFile *location) +{ + GFileInfo *info; + GFile *tmp; + + g_assert (location != NULL); + + location = g_object_ref (location); + while (location != NULL) + { + info = g_file_query_info (location, + G_FILE_ATTRIBUTE_STANDARD_NAME, + 0, NULL, NULL); + g_object_unref (info); + if (info != NULL) + { + return location; + } + tmp = location; + location = g_file_get_parent (location); + g_object_unref (tmp); + } + + return location; +} + +static gboolean +have_program_in_path (const char *name) +{ + gchar *path; + gboolean result; + + path = g_find_program_in_path (name); + result = (path != NULL); + g_free (path); + return result; +} + +static GIcon * +special_directory_get_icon (GUserDirectory directory, + gboolean symbolic) +{ +#define ICON_CASE(x) \ + case G_USER_DIRECTORY_ ## x: \ + return (symbolic) ? g_themed_icon_new (NAUTILUS_ICON_FOLDER_ ## x) : g_themed_icon_new (NAUTILUS_ICON_FULLCOLOR_FOLDER_ ## x); + + switch (directory) + { + ICON_CASE (DOCUMENTS); + ICON_CASE (DOWNLOAD); + ICON_CASE (MUSIC); + ICON_CASE (PICTURES); + ICON_CASE (PUBLIC_SHARE); + ICON_CASE (TEMPLATES); + ICON_CASE (VIDEOS); + + default: + { + return (symbolic) ? g_themed_icon_new (NAUTILUS_ICON_FOLDER) : g_themed_icon_new (NAUTILUS_ICON_FULLCOLOR_FOLDER); + } + } + +#undef ICON_CASE +} + +GIcon * +nautilus_special_directory_get_symbolic_icon (GUserDirectory directory) +{ + return special_directory_get_icon (directory, TRUE); +} + +GIcon * +nautilus_special_directory_get_icon (GUserDirectory directory) +{ + return special_directory_get_icon (directory, FALSE); +} + +gboolean +nautilus_is_file_roller_installed (void) +{ + static int installed = -1; + + if (installed < 0) + { + if (have_program_in_path ("file-roller")) + { + installed = 1; + } + else + { + installed = 0; + } + } + + return installed > 0 ? TRUE : FALSE; +} + +GHashTable * +nautilus_trashed_files_get_original_directories (GList *files, + GList **unhandled_files) +{ + GHashTable *directories; + NautilusFile *file, *original_file, *original_dir; + GList *l, *m; + + directories = NULL; + + if (unhandled_files != NULL) + { + *unhandled_files = NULL; + } + + for (l = files; l != NULL; l = l->next) + { + file = NAUTILUS_FILE (l->data); + original_file = nautilus_file_get_trash_original_file (file); + + original_dir = NULL; + if (original_file != NULL) + { + original_dir = nautilus_file_get_parent (original_file); + } + + if (original_dir != NULL) + { + if (directories == NULL) + { + directories = g_hash_table_new_full (g_direct_hash, g_direct_equal, + (GDestroyNotify) nautilus_file_unref, + (GDestroyNotify) nautilus_file_list_free); + } + nautilus_file_ref (original_dir); + m = g_hash_table_lookup (directories, original_dir); + if (m != NULL) + { + g_hash_table_steal (directories, original_dir); + nautilus_file_unref (original_dir); + } + m = g_list_append (m, nautilus_file_ref (file)); + g_hash_table_insert (directories, original_dir, m); + } + else if (unhandled_files != NULL) + { + *unhandled_files = g_list_append (*unhandled_files, nautilus_file_ref (file)); + } + + nautilus_file_unref (original_file); + nautilus_file_unref (original_dir); + } + + return directories; +} + +GList * +nautilus_file_list_from_uri_list (GList *uris) +{ + GList *l; + GList *result = NULL; + + for (l = uris; l != NULL; l = l->next) + { + g_autoptr (GFile) location = NULL; + + location = g_file_new_for_uri (l->data); + result = g_list_prepend (result, nautilus_file_get (location)); + } + + return g_list_reverse (result); +} + +static GList * +locations_from_file_list (GList *file_list) +{ + NautilusFile *file; + GList *l, *ret; + + ret = NULL; + + for (l = file_list; l != NULL; l = l->next) + { + file = NAUTILUS_FILE (l->data); + ret = g_list_prepend (ret, nautilus_file_get_location (file)); + } + + return g_list_reverse (ret); +} + +typedef struct +{ + GHashTable *original_dirs_hash; + GtkWindow *parent_window; +} RestoreFilesData; + +static void +ensure_dirs_task_ready_cb (GObject *_source, + GAsyncResult *res, + gpointer user_data) +{ + NautilusFile *original_dir; + GFile *original_dir_location; + GList *original_dirs, *files, *locations, *l; + RestoreFilesData *data = user_data; + + original_dirs = g_hash_table_get_keys (data->original_dirs_hash); + for (l = original_dirs; l != NULL; l = l->next) + { + original_dir = NAUTILUS_FILE (l->data); + original_dir_location = nautilus_file_get_location (original_dir); + + files = g_hash_table_lookup (data->original_dirs_hash, original_dir); + locations = locations_from_file_list (files); + + nautilus_file_operations_move_async (locations, + original_dir_location, + data->parent_window, + NULL, NULL, NULL); + + g_list_free_full (locations, g_object_unref); + g_object_unref (original_dir_location); + } + + g_list_free (original_dirs); + + g_hash_table_unref (data->original_dirs_hash); + g_slice_free (RestoreFilesData, data); +} + +static void +ensure_dirs_task_thread_func (GTask *task, + gpointer source, + gpointer task_data, + GCancellable *cancellable) +{ + RestoreFilesData *data = task_data; + NautilusFile *original_dir; + GFile *original_dir_location; + GList *original_dirs, *l; + + original_dirs = g_hash_table_get_keys (data->original_dirs_hash); + for (l = original_dirs; l != NULL; l = l->next) + { + original_dir = NAUTILUS_FILE (l->data); + original_dir_location = nautilus_file_get_location (original_dir); + + g_file_make_directory_with_parents (original_dir_location, cancellable, NULL); + g_object_unref (original_dir_location); + } + + g_task_return_pointer (task, NULL, NULL); +} + +static void +restore_files_ensure_parent_directories (GHashTable *original_dirs_hash, + GtkWindow *parent_window) +{ + RestoreFilesData *data; + GTask *ensure_dirs_task; + + data = g_slice_new0 (RestoreFilesData); + data->parent_window = parent_window; + data->original_dirs_hash = g_hash_table_ref (original_dirs_hash); + + ensure_dirs_task = g_task_new (NULL, NULL, ensure_dirs_task_ready_cb, data); + g_task_set_task_data (ensure_dirs_task, data, NULL); + g_task_run_in_thread (ensure_dirs_task, ensure_dirs_task_thread_func); + g_object_unref (ensure_dirs_task); +} + +void +nautilus_restore_files_from_trash (GList *files, + GtkWindow *parent_window) +{ + NautilusFile *file; + GHashTable *original_dirs_hash; + GList *unhandled_files, *l; + char *message, *file_name; + + original_dirs_hash = nautilus_trashed_files_get_original_directories (files, &unhandled_files); + + for (l = unhandled_files; l != NULL; l = l->next) + { + file = NAUTILUS_FILE (l->data); + file_name = nautilus_file_get_display_name (file); + message = g_strdup_printf (_("Could not determine original location of “%s” "), file_name); + g_free (file_name); + + show_dialog (message, + _("The item cannot be restored from trash"), + parent_window, + GTK_MESSAGE_WARNING); + g_free (message); + } + + if (original_dirs_hash != NULL) + { + restore_files_ensure_parent_directories (original_dirs_hash, parent_window); + g_hash_table_unref (original_dirs_hash); + } + + nautilus_file_list_unref (unhandled_files); +} + +typedef struct +{ + NautilusMountGetContent callback; + gpointer user_data; +} GetContentTypesData; + +static void +get_types_cb (GObject *source_object, + GAsyncResult *res, + gpointer user_data) +{ + GetContentTypesData *data; + char **types; + + data = user_data; + types = g_mount_guess_content_type_finish (G_MOUNT (source_object), res, NULL); + + g_object_set_data_full (source_object, + "nautilus-content-type-cache", + g_strdupv (types), + (GDestroyNotify) g_strfreev); + + if (data->callback) + { + data->callback ((const char **) types, data->user_data); + } + g_strfreev (types); + g_slice_free (GetContentTypesData, data); +} + +void +nautilus_get_x_content_types_for_mount_async (GMount *mount, + NautilusMountGetContent callback, + GCancellable *cancellable, + gpointer user_data) +{ + char **cached; + GetContentTypesData *data; + + if (mount == NULL) + { + if (callback) + { + callback (NULL, user_data); + } + return; + } + + cached = g_object_get_data (G_OBJECT (mount), "nautilus-content-type-cache"); + if (cached != NULL) + { + if (callback) + { + callback ((const char **) cached, user_data); + } + return; + } + + data = g_slice_new0 (GetContentTypesData); + data->callback = callback; + data->user_data = user_data; + + g_mount_guess_content_type (mount, + FALSE, + cancellable, + get_types_cb, + data); +} + +char ** +nautilus_get_cached_x_content_types_for_mount (GMount *mount) +{ + char **cached; + + if (mount == NULL) + { + return NULL; + } + + cached = g_object_get_data (G_OBJECT (mount), "nautilus-content-type-cache"); + if (cached != NULL) + { + return g_strdupv (cached); + } + + return NULL; +} + +char * +get_message_for_content_type (const char *content_type) +{ + char *message; + char *description; + + description = g_content_type_get_description (content_type); + + /* Customize greeting for well-known content types */ + if (strcmp (content_type, "x-content/audio-cdda") == 0) + { + /* translators: these describe the contents of removable media */ + message = g_strdup (_("Audio CD")); + } + else if (strcmp (content_type, "x-content/audio-dvd") == 0) + { + message = g_strdup (_("Audio DVD")); + } + else if (strcmp (content_type, "x-content/video-dvd") == 0) + { + message = g_strdup (_("Video DVD")); + } + else if (strcmp (content_type, "x-content/video-vcd") == 0) + { + message = g_strdup (_("Video CD")); + } + else if (strcmp (content_type, "x-content/video-svcd") == 0) + { + message = g_strdup (_("Super Video CD")); + } + else if (strcmp (content_type, "x-content/image-photocd") == 0) + { + message = g_strdup (_("Photo CD")); + } + else if (strcmp (content_type, "x-content/image-picturecd") == 0) + { + message = g_strdup (_("Picture CD")); + } + else if (strcmp (content_type, "x-content/image-dcf") == 0) + { + message = g_strdup (_("Contains digital photos")); + } + else if (strcmp (content_type, "x-content/audio-player") == 0) + { + message = g_strdup (_("Contains music")); + } + else if (strcmp (content_type, "x-content/unix-software") == 0) + { + message = g_strdup (_("Contains software to run")); + } + else if (strcmp (content_type, "x-content/ostree-repository") == 0) + { + message = g_strdup (_("Contains software to install")); + } + else + { + /* fallback to generic greeting */ + message = g_strdup_printf (_("Detected as “%s”"), description); + } + + g_free (description); + + return message; +} + +char * +get_message_for_two_content_types (const char * const *content_types) +{ + char *message; + + g_assert (content_types[0] != NULL); + g_assert (content_types[1] != NULL); + + /* few combinations make sense */ + if (strcmp (content_types[0], "x-content/image-dcf") == 0 + || strcmp (content_types[1], "x-content/image-dcf") == 0) + { + if (strcmp (content_types[0], "x-content/audio-player") == 0) + { + /* translators: these describe the contents of removable media */ + message = g_strdup (_("Contains music and photos")); + } + else if (strcmp (content_types[1], "x-content/audio-player") == 0) + { + message = g_strdup (_("Contains photos and music")); + } + else + { + message = g_strdup (_("Contains digital photos")); + } + } + else if ((strcmp (content_types[0], "x-content/video-vcd") == 0 + || strcmp (content_types[1], "x-content/video-vcd") == 0) + && (strcmp (content_types[0], "x-content/video-dvd") == 0 + || strcmp (content_types[1], "x-content/video-dvd") == 0)) + { + message = g_strdup_printf ("%s/%s", + get_message_for_content_type (content_types[0]), + get_message_for_content_type (content_types[1])); + } + else + { + message = get_message_for_content_type (content_types[0]); + } + + return message; +} + +gboolean +should_handle_content_type (const char *content_type) +{ + g_autoptr (GAppInfo) default_app = NULL; + + default_app = g_app_info_get_default_for_type (content_type, FALSE); + + return !g_str_has_prefix (content_type, "x-content/blank-") && + !g_content_type_is_a (content_type, "x-content/win32-software") && + default_app != NULL; +} + +gboolean +should_handle_content_types (const char * const *content_types) +{ + int i; + + for (i = 0; content_types[i] != NULL; i++) + { + if (should_handle_content_type (content_types[i])) + { + return TRUE; + } + } + + return FALSE; +} + +gboolean +nautilus_file_selection_equal (GList *selection_a, + GList *selection_b) +{ + GList *al, *bl; + gboolean selection_matches; + + if (selection_a == NULL || selection_b == NULL) + { + return (selection_a == selection_b); + } + + if (g_list_length (selection_a) != g_list_length (selection_b)) + { + return FALSE; + } + + selection_matches = TRUE; + + for (al = selection_a; al; al = al->next) + { + GFile *a_location = nautilus_file_get_location (NAUTILUS_FILE (al->data)); + gboolean found = FALSE; + + for (bl = selection_b; bl; bl = bl->next) + { + GFile *b_location = nautilus_file_get_location (NAUTILUS_FILE (bl->data)); + found = g_file_equal (b_location, a_location); + g_object_unref (b_location); + + if (found) + { + break; + } + } + + selection_matches = found; + g_object_unref (a_location); + + if (!selection_matches) + { + break; + } + } + + return selection_matches; +} + +static char * +trim_whitespace (const gchar *string) +{ + glong space_count; + glong length; + gchar *offset; + + space_count = 0; + length = g_utf8_strlen (string, -1); + offset = g_utf8_offset_to_pointer (string, length); + + while (space_count <= length) + { + gunichar character; + + offset = g_utf8_prev_char (offset); + character = g_utf8_get_char (offset); + + if (!g_unichar_isspace (character)) + { + break; + } + + space_count++; + } + + if (space_count == 0) + { + return g_strdup (string); + } + + return g_utf8_substring (string, 0, length - space_count); +} + +char * +nautilus_get_common_filename_prefix (GList *file_list, + int min_required_len) +{ + GList *file_names = NULL; + GList *directory_names = NULL; + char *result_files; + g_autofree char *result = NULL; + g_autofree char *result_trimmed = NULL; + + if (file_list == NULL) + { + return NULL; + } + + for (GList *l = file_list; l != NULL; l = l->next) + { + char *name; + + g_return_val_if_fail (NAUTILUS_IS_FILE (l->data), NULL); + + name = nautilus_file_get_display_name (l->data); + + /* Since the concept of file extensions does not apply to directories, + * we filter those out. + */ + if (nautilus_file_is_directory (l->data)) + { + directory_names = g_list_prepend (directory_names, name); + } + else + { + file_names = g_list_prepend (file_names, name); + } + } + + result_files = nautilus_get_common_filename_prefix_from_filenames (file_names, min_required_len); + + if (directory_names == NULL) + { + return result_files; + } + + if (result_files != NULL) + { + directory_names = g_list_prepend (directory_names, result_files); + } + + result = eel_str_get_common_prefix (directory_names, min_required_len); + + g_list_free_full (file_names, g_free); + g_list_free_full (directory_names, g_free); + + if (result == NULL) + { + return NULL; + } + + result_trimmed = trim_whitespace (result); + + if (g_utf8_strlen (result_trimmed, -1) < min_required_len) + { + return NULL; + } + + return g_steal_pointer (&result_trimmed); +} + +char * +nautilus_get_common_filename_prefix_from_filenames (GList *filenames, + int min_required_len) +{ + GList *stripped_filenames = NULL; + char *common_prefix; + char *truncated; + int common_prefix_len; + + for (GList *i = filenames; i != NULL; i = i->next) + { + gchar *stripped_filename; + + stripped_filename = eel_filename_strip_extension (i->data); + + stripped_filenames = g_list_prepend (stripped_filenames, stripped_filename); + } + + common_prefix = eel_str_get_common_prefix (stripped_filenames, min_required_len); + if (common_prefix == NULL) + { + return NULL; + } + + g_list_free_full (stripped_filenames, g_free); + + truncated = trim_whitespace (common_prefix); + g_free (common_prefix); + + common_prefix_len = g_utf8_strlen (truncated, -1); + if (common_prefix_len < min_required_len) + { + g_free (truncated); + return NULL; + } + + return truncated; +} + +glong +nautilus_get_max_child_name_length_for_location (GFile *location) +{ + g_autofree gchar *path = NULL; + glong name_max; + glong path_max; + glong max_child_name_length; + + g_return_val_if_fail (G_IS_FILE (location), -1); + + if (!g_file_has_uri_scheme (location, "file")) + { + /* FIXME: Can we query length limits for non-"file://" locations? */ + return -1; + } + + path = g_file_get_path (location); + + g_return_val_if_fail (path != NULL, -1); + + name_max = pathconf (path, _PC_NAME_MAX); + path_max = pathconf (path, _PC_PATH_MAX); + max_child_name_length = -1; + + if (name_max == -1) + { + if (path_max != -1) + { + /* We don't know the name max, but we know the name can't make the + * path longer than this. + * Subtracting 1 because PATH_MAX includes the terminating null + * character, as per limits.h(0P). */ + max_child_name_length = MAX ((path_max - 1) - strlen (path), 0); + } + } + else + { + /* No need to subtract 1, because NAME_MAX excludes the terminating null + * character, as per limits.h(0P) */ + max_child_name_length = name_max; + if (path_max != -1) + { + max_child_name_length = CLAMP ((path_max - 1) - strlen (path), + 0, + max_child_name_length); + } + } + + return max_child_name_length; +} + +#if !defined (NAUTILUS_OMIT_SELF_CHECK) + +void +nautilus_self_check_file_utilities (void) +{ +} + +void +nautilus_ensure_extension_builtins (void) +{ + /* Please add new extension types here, even if you can guarantee + * that they will be registered by the time the extension point + * is iterating over its extensions. + */ + g_type_ensure (NAUTILUS_TYPE_SEARCH_DIRECTORY); + g_type_ensure (NAUTILUS_TYPE_STARRED_DIRECTORY); +} + +void +nautilus_ensure_extension_points (void) +{ + static gsize once_init_value = 0; + + if (g_once_init_enter (&once_init_value)) + { + GIOExtensionPoint *extension_point; + + extension_point = g_io_extension_point_register (NAUTILUS_DIRECTORY_PROVIDER_EXTENSION_POINT_NAME); + g_io_extension_point_set_required_type (extension_point, NAUTILUS_TYPE_DIRECTORY); + + g_once_init_leave (&once_init_value, 1); + } +} + +#endif /* !NAUTILUS_OMIT_SELF_CHECK */ + +gboolean +nautilus_file_can_rename_files (GList *files) +{ + GList *l; + NautilusFile *file; + + for (l = files; l != NULL; l = l->next) + { + file = NAUTILUS_FILE (l->data); + + if (!nautilus_file_can_rename (file)) + { + return FALSE; + } + } + + return TRUE; +} + +/* Try to get a native file:// URI instead of any other GVFS + * scheme, for interoperability with apps only handling file:// URIs. + */ +gchar * +nautilus_uri_to_native_uri (const gchar *uri) +{ + g_autoptr (GFile) file = NULL; + g_autofree gchar *path = NULL; + + file = g_file_new_for_uri (uri); + path = g_file_get_path (file); + + if (path != NULL) + { + return g_filename_to_uri (path, NULL, NULL); + } + + return NULL; +} + +NautilusQueryRecursive +location_settings_search_get_recursive (void) +{ + switch (g_settings_get_enum (nautilus_preferences, "recursive-search")) + { + case NAUTILUS_SPEED_TRADEOFF_ALWAYS: + { + return NAUTILUS_QUERY_RECURSIVE_ALWAYS; + } + break; + + case NAUTILUS_SPEED_TRADEOFF_LOCAL_ONLY: + { + return NAUTILUS_QUERY_RECURSIVE_LOCAL_ONLY; + } + break; + + case NAUTILUS_SPEED_TRADEOFF_NEVER: + { + return NAUTILUS_QUERY_RECURSIVE_NEVER; + } + break; + } + + return NAUTILUS_QUERY_RECURSIVE_ALWAYS; +} + +NautilusQueryRecursive +location_settings_search_get_recursive_for_location (GFile *location) +{ + NautilusQueryRecursive recursive = location_settings_search_get_recursive (); + + g_return_val_if_fail (location, recursive); + + if (recursive == NAUTILUS_QUERY_RECURSIVE_LOCAL_ONLY) + { + g_autoptr (NautilusFile) file = nautilus_file_get_existing (location); + + g_return_val_if_fail (file != NULL, recursive); + + if (nautilus_file_is_remote (file)) + { + recursive = NAUTILUS_QUERY_RECURSIVE_NEVER; + } + } + + return recursive; +} + +/* check_schema_available() was copied from GNOME Settings */ +gboolean +check_schema_available (const gchar *schema_id) +{ + GSettingsSchemaSource *source; + g_autoptr (GSettingsSchema) schema = NULL; + + if (nautilus_application_is_sandboxed ()) + { + return TRUE; + } + + source = g_settings_schema_source_get_default (); + if (!source) + { + return FALSE; + } + + schema = g_settings_schema_source_lookup (source, schema_id, TRUE); + if (!schema) + { + return FALSE; + } + + return TRUE; +} diff --git a/src/nautilus-file-utilities.h b/src/nautilus-file-utilities.h new file mode 100644 index 0000000..e87c024 --- /dev/null +++ b/src/nautilus-file-utilities.h @@ -0,0 +1,145 @@ + +/* nautilus-file-utilities.h - interface for file manipulation routines. + + Copyright (C) 1999, 2000, 2001 Eazel, Inc. + + The Gnome Library is free software; you can redistribute it and/or + modify it under the terms of the GNU Library General Public License as + published by the Free Software Foundation; either version 2 of the + License, or (at your option) any later version. + + The Gnome Library is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Library General Public License for more details. + + You should have received a copy of the GNU Library General Public + License along with the Gnome Library; see the file COPYING.LIB. If not, + see . + + Authors: John Sullivan +*/ + +#pragma once + +#include +#include + +#include + +#include "nautilus-query.h" + +#define NAUTILUS_DESKTOP_ID APPLICATION_ID ".desktop" + +/* These functions all return something something that needs to be + * freed with g_free, is not NULL, and is guaranteed to exist. + */ +char * nautilus_get_user_directory (void); +char * nautilus_get_home_directory_uri (void); +gboolean nautilus_is_root_directory (GFile *dir); +gboolean nautilus_is_home_directory (GFile *dir); +gboolean nautilus_is_home_directory_file (GFile *dir, + const char *filename); +gboolean nautilus_is_search_directory (GFile *dir); +gboolean nautilus_is_recent_directory (GFile *dir); +gboolean nautilus_is_starred_directory (GFile *dir); +gboolean nautilus_is_trash_directory (GFile *dir); +gboolean nautilus_is_other_locations_directory (GFile *dir); +GMount * nautilus_get_mounted_mount_for_root (GFile *location); + +gboolean nautilus_should_use_templates_directory (void); +char * nautilus_get_templates_directory (void); +char * nautilus_get_templates_directory_uri (void); + +char * nautilus_compute_title_for_location (GFile *file); + +gboolean nautilus_is_file_roller_installed (void); + +GIcon * nautilus_special_directory_get_icon (GUserDirectory directory); +GIcon * nautilus_special_directory_get_symbolic_icon (GUserDirectory directory); + +gboolean nautilus_uri_parse (const char *uri, + char **host, + guint16 *port, + char **userinfo); + +/* Return an allocated file location that is guranteed to be unique, but + * tries to make the location name readable to users. + * This isn't race-free, so don't use for security-related things + */ +GFile * nautilus_generate_unique_file_in_directory (GFile *directory, + const char *basename); + +GFile * nautilus_find_existing_uri_in_hierarchy (GFile *location); + +char * nautilus_get_scripts_directory_path (void); + +GHashTable * nautilus_trashed_files_get_original_directories (GList *files, + GList **unhandled_files); +void nautilus_restore_files_from_trash (GList *files, + GtkWindow *parent_window); + +typedef void (*NautilusMountGetContent) (const char **content, gpointer user_data); + +char ** nautilus_get_cached_x_content_types_for_mount (GMount *mount); +void nautilus_get_x_content_types_for_mount_async (GMount *mount, + NautilusMountGetContent callback, + GCancellable *cancellable, + gpointer user_data); +char * get_message_for_content_type (const char *content_type); +char * get_message_for_two_content_types (const char * const *content_types); +gboolean should_handle_content_type (const char *content_type); +gboolean should_handle_content_types (const char * const *content_type); + +gboolean nautilus_file_selection_equal (GList *selection_a, GList *selection_b); + +/** + * nautilus_get_common_filename_prefix: + * @file_list: set of files (NautilusFile *) + * @min_required_len: the minimum number of characters required in the prefix + * + * Returns: the common filename prefix for a set of files, or NULL if + * there isn't a common prefix of length min_required_len + */ +char * nautilus_get_common_filename_prefix (GList *file_list, + int min_required_len); + +/** + * nautilus_get_common_filename_prefix_from_filenames: + * @filename_list: set of file names (char *) + * @min_required_len: the minimum number of characters required in the prefix + * + * Returns: the common filename prefix for a set of filenames, or NULL if + * there isn't a common prefix of length min_required_len + */ +char * nautilus_get_common_filename_prefix_from_filenames (GList *filename_list, + int min_required_len); + +/** + * nautilus_get_max_child_name_for_location: + * @location: a #GFile representing a directory + * + * Gets the maximum file name length for files inside @location. + * + * This call does blocking I/O. + * + * Returns: The maximum file name length in bytes (not including the + * terminating null of a filename string), -1 if the maximum length + * could not be determined or 0 if @location path is too long. + */ + +glong nautilus_get_max_child_name_length_for_location (GFile *location); + +void nautilus_ensure_extension_points (void); +void nautilus_ensure_extension_builtins (void); + +gboolean nautilus_file_can_rename_files (GList *files); + +GList * nautilus_file_list_from_uri_list (GList *uris); + +gchar * nautilus_uri_to_native_uri (const gchar *uri); + +NautilusQueryRecursive location_settings_search_get_recursive (void); +NautilusQueryRecursive location_settings_search_get_recursive_for_location (GFile *location); + +gboolean check_schema_available (const gchar *schema_id); diff --git a/src/nautilus-file.c b/src/nautilus-file.c new file mode 100644 index 0000000..b340cfc --- /dev/null +++ b/src/nautilus-file.c @@ -0,0 +1,9586 @@ +/* + * nautilus-file.c: Nautilus file model. + * + * Copyright (C) 1999, 2000, 2001 Eazel, Inc. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License as + * published by the Free Software Foundation; either version 2 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 warranty of + * MERCHANTABILITY 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 . + * + * Author: Darin Adler + */ + +#include "nautilus-file.h" + +#ifndef NAUTILUS_COMPILATION +#define NAUTILUS_COMPILATION +#endif +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#define DEBUG_FLAG NAUTILUS_DEBUG_FILE +#include "nautilus-debug.h" + +#include "nautilus-directory-notify.h" +#include "nautilus-directory-private.h" +#include "nautilus-enums.h" +#include "nautilus-file-operations.h" +#include "nautilus-file-private.h" +#include "nautilus-file-undo-manager.h" +#include "nautilus-file-undo-operations.h" +#include "nautilus-file-utilities.h" +#include "nautilus-global-preferences.h" +#include "nautilus-icon-info.h" +#include "nautilus-lib-self-check-functions.h" +#include "nautilus-metadata.h" +#include "nautilus-module.h" +#include "nautilus-signaller.h" +#include "nautilus-tag-manager.h" +#include "nautilus-thumbnails.h" +#include "nautilus-ui-utilities.h" +#include "nautilus-vfs-file.h" +#include "nautilus-video-mime-types.h" + +#ifdef HAVE_SELINUX +#include +#endif + +/* Time in seconds to cache getpwuid results */ +#define GETPWUID_CACHE_TIME (5 * 60) + +#define ICON_NAME_THUMBNAIL_LOADING "image-loading" + +#undef NAUTILUS_FILE_DEBUG_REF +#undef NAUTILUS_FILE_DEBUG_REF_VALGRIND + +#ifdef NAUTILUS_FILE_DEBUG_REF_VALGRIND +#include +#define DEBUG_REF_PRINTF VALGRIND_PRINTF_BACKTRACE +#else +#define DEBUG_REF_PRINTF printf +#endif + +#define MEGA_TO_BASE_RATE 1000000 + +/* Files that start with these characters sort after files that don't. */ +#define SORT_LAST_CHAR1 '.' +#define SORT_LAST_CHAR2 '#' + +/* Name of Nautilus trash directories */ +#define TRASH_DIRECTORY_NAME ".Trash" + +#define METADATA_ID_IS_LIST_MASK (1U << 31) + +typedef enum +{ + SHOW_HIDDEN = 1 << 0, +} FilterOptions; + +typedef enum +{ + NAUTILUS_DATE_FORMAT_REGULAR = 0, + NAUTILUS_DATE_FORMAT_REGULAR_WITH_TIME = 1, + NAUTILUS_DATE_FORMAT_FULL = 2, +} NautilusDateFormat; + +typedef void (*ModifyListFunction) (GList **list, + NautilusFile *file); + +enum +{ + CHANGED, + UPDATED_DEEP_COUNT_IN_PROGRESS, + LAST_SIGNAL +}; + +static guint signals[LAST_SIGNAL]; + +static GHashTable *symbolic_links; + +static guint64 cached_thumbnail_limit; +static NautilusSpeedTradeoffValue show_file_thumbs; + +static NautilusSpeedTradeoffValue show_directory_item_count; + +static GQuark attribute_name_q, + attribute_size_q, + attribute_type_q, + attribute_detailed_type_q, + attribute_modification_date_q, + attribute_date_modified_q, + attribute_date_modified_full_q, + attribute_date_modified_with_time_q, + attribute_accessed_date_q, + attribute_date_accessed_q, + attribute_date_accessed_full_q, + attribute_date_created_q, + attribute_date_created_full_q, + attribute_mime_type_q, + attribute_size_detail_q, + attribute_deep_size_q, + attribute_deep_file_count_q, + attribute_deep_directory_count_q, + attribute_deep_total_count_q, + attribute_search_relevance_q, + attribute_trashed_on_q, + attribute_trashed_on_full_q, + attribute_trash_orig_path_q, + attribute_recency_q, + attribute_permissions_q, + attribute_selinux_context_q, + attribute_octal_permissions_q, + attribute_owner_q, + attribute_group_q, + attribute_uri_q, + attribute_where_q, + attribute_link_target_q, + attribute_volume_q, + attribute_free_space_q, + attribute_starred_q; + +static void nautilus_file_info_iface_init (NautilusFileInfoInterface *iface); +static char *nautilus_file_get_owner_as_string (NautilusFile *file, + gboolean include_real_name); +static char *nautilus_file_get_type_as_string (NautilusFile *file); +static char *nautilus_file_get_type_as_string_no_extra_text (NautilusFile *file); +static char *nautilus_file_get_detailed_type_as_string (NautilusFile *file); +static gboolean update_info_and_name (NautilusFile *file, + GFileInfo *info); +static const char *nautilus_file_peek_display_name (NautilusFile *file); +static const char *nautilus_file_peek_display_name_collation_key (NautilusFile *file); +static void file_mount_unmounted (GMount *mount, + gpointer data); +static void metadata_hash_free (GHashTable *hash); + +G_DEFINE_TYPE_WITH_CODE (NautilusFile, nautilus_file, G_TYPE_OBJECT, + G_IMPLEMENT_INTERFACE (NAUTILUS_TYPE_FILE_INFO, + nautilus_file_info_iface_init)); + +static void +nautilus_file_init (NautilusFile *file) +{ + file->details = G_TYPE_INSTANCE_GET_PRIVATE ((file), NAUTILUS_TYPE_FILE, NautilusFileDetails); + + nautilus_file_clear_info (file); + nautilus_file_invalidate_extension_info_internal (file); + + file->details->free_space = -1; +} + +static GObject * +nautilus_file_constructor (GType type, + guint n_construct_properties, + GObjectConstructParam *construct_params) +{ + GObject *object; + NautilusFile *file; + + object = (*G_OBJECT_CLASS (nautilus_file_parent_class)->constructor)(type, + n_construct_properties, + construct_params); + + file = NAUTILUS_FILE (object); + + /* Set to default type after full construction */ + if (NAUTILUS_FILE_GET_CLASS (file)->default_file_type != G_FILE_TYPE_UNKNOWN) + { + file->details->type = NAUTILUS_FILE_GET_CLASS (file)->default_file_type; + } + + return object; +} + +gboolean +nautilus_file_set_display_name (NautilusFile *file, + const char *display_name, + const char *edit_name, + gboolean custom) +{ + gboolean changed; + + if (custom && display_name == NULL) + { + /* We're re-setting a custom display name, invalidate it if + * we already set it so that the old one is re-read */ + if (file->details->got_custom_display_name) + { + file->details->got_custom_display_name = FALSE; + nautilus_file_invalidate_attributes (file, + NAUTILUS_FILE_ATTRIBUTE_INFO); + } + return FALSE; + } + + if (display_name == NULL || *display_name == 0) + { + return FALSE; + } + + if (!custom && file->details->got_custom_display_name) + { + return FALSE; + } + + if (edit_name == NULL) + { + edit_name = display_name; + } + + changed = FALSE; + + if (g_strcmp0 (file->details->display_name, display_name) != 0) + { + changed = TRUE; + + g_clear_pointer (&file->details->display_name, g_ref_string_release); + + if (g_strcmp0 (file->details->name, display_name) == 0) + { + file->details->display_name = g_ref_string_acquire (file->details->name); + } + else + { + file->details->display_name = g_ref_string_new (display_name); + } + + g_free (file->details->display_name_collation_key); + file->details->display_name_collation_key = g_utf8_collate_key_for_filename (display_name, -1); + } + + if (g_strcmp0 (file->details->edit_name, edit_name) != 0) + { + changed = TRUE; + + g_clear_pointer (&file->details->edit_name, g_ref_string_release); + if (g_strcmp0 (file->details->display_name, edit_name) == 0) + { + file->details->edit_name = g_ref_string_acquire (file->details->display_name); + } + else + { + file->details->edit_name = g_ref_string_new (edit_name); + } + } + + file->details->got_custom_display_name = custom; + return changed; +} + +static void +nautilus_file_clear_display_name (NautilusFile *file) +{ + g_clear_pointer (&file->details->display_name, g_ref_string_release); + g_free (file->details->display_name_collation_key); + file->details->display_name_collation_key = NULL; + g_clear_pointer (&file->details->edit_name, g_ref_string_release); +} + +static gboolean +foreach_metadata_free (gpointer key, + gpointer value, + gpointer user_data) +{ + guint id; + + id = GPOINTER_TO_UINT (key); + + if (id & METADATA_ID_IS_LIST_MASK) + { + g_strfreev ((char **) value); + } + else + { + g_free ((char *) value); + } + return TRUE; +} + + +static void +metadata_hash_free (GHashTable *hash) +{ + g_hash_table_foreach_remove (hash, + foreach_metadata_free, + NULL); + g_hash_table_destroy (hash); +} + +static gboolean +_g_strv_equal (GStrv a, + GStrv b) +{ + if (g_strv_length (a) != g_strv_length (b)) + { + return FALSE; + } + + for (int i = 0; a[i] != NULL; i++) + { + if (strcmp (a[i], b[i]) != 0) + { + return FALSE; + } + } + + return TRUE; +} + +static gboolean +metadata_hash_equal (GHashTable *hash1, + GHashTable *hash2) +{ + GHashTableIter iter; + gpointer key1, value1, value2; + guint id; + + if (hash1 == NULL && hash2 == NULL) + { + return TRUE; + } + + if (hash1 == NULL || hash2 == NULL) + { + return FALSE; + } + + if (g_hash_table_size (hash1) != + g_hash_table_size (hash2)) + { + return FALSE; + } + + g_hash_table_iter_init (&iter, hash1); + while (g_hash_table_iter_next (&iter, &key1, &value1)) + { + value2 = g_hash_table_lookup (hash2, key1); + if (value2 == NULL) + { + return FALSE; + } + id = GPOINTER_TO_UINT (key1); + if (id & METADATA_ID_IS_LIST_MASK) + { + if (!_g_strv_equal ((char **) value1, (char **) value2)) + { + return FALSE; + } + } + else + { + if (strcmp ((char *) value1, (char *) value2) != 0) + { + return FALSE; + } + } + } + + return TRUE; +} + +static void +clear_metadata (NautilusFile *file) +{ + if (file->details->metadata) + { + metadata_hash_free (file->details->metadata); + file->details->metadata = NULL; + } +} + +static GHashTable * +get_metadata_from_info (GFileInfo *info) +{ + GHashTable *metadata; + char **attrs; + guint id; + int i; + GFileAttributeType type; + gpointer value; + + attrs = g_file_info_list_attributes (info, "metadata"); + + metadata = g_hash_table_new (NULL, NULL); + + for (i = 0; attrs[i] != NULL; i++) + { + id = nautilus_metadata_get_id (attrs[i] + strlen ("metadata::")); + if (id == 0) + { + continue; + } + + if (!g_file_info_get_attribute_data (info, attrs[i], + &type, &value, NULL)) + { + continue; + } + + if (type == G_FILE_ATTRIBUTE_TYPE_STRING) + { + g_hash_table_insert (metadata, GUINT_TO_POINTER (id), + g_strdup ((char *) value)); + } + else if (type == G_FILE_ATTRIBUTE_TYPE_STRINGV) + { + id |= METADATA_ID_IS_LIST_MASK; + g_hash_table_insert (metadata, GUINT_TO_POINTER (id), + g_strdupv ((char **) value)); + } + } + + g_strfreev (attrs); + + return metadata; +} + +gboolean +nautilus_file_update_metadata_from_info (NautilusFile *file, + GFileInfo *info) +{ + gboolean changed = FALSE; + + if (g_file_info_has_namespace (info, "metadata")) + { + GHashTable *metadata; + + metadata = get_metadata_from_info (info); + if (!metadata_hash_equal (metadata, + file->details->metadata)) + { + changed = TRUE; + clear_metadata (file); + file->details->metadata = metadata; + } + else + { + metadata_hash_free (metadata); + } + } + else if (file->details->metadata) + { + changed = TRUE; + clear_metadata (file); + } + return changed; +} + +void +nautilus_file_clear_info (NautilusFile *file) +{ + file->details->got_file_info = FALSE; + if (file->details->get_info_error) + { + g_error_free (file->details->get_info_error); + file->details->get_info_error = NULL; + } + /* Reset to default type, which might be other than unknown for + * special kinds of files like the desktop or a search directory */ + file->details->type = NAUTILUS_FILE_GET_CLASS (file)->default_file_type; + + if (!file->details->got_custom_display_name) + { + nautilus_file_clear_display_name (file); + } + + if (!file->details->got_custom_activation_uri && + file->details->activation_uri != NULL) + { + g_free (file->details->activation_uri); + file->details->activation_uri = NULL; + } + + if (file->details->icon != NULL) + { + g_object_unref (file->details->icon); + file->details->icon = NULL; + } + + g_free (file->details->thumbnail_path); + file->details->thumbnail_path = NULL; + file->details->thumbnailing_failed = FALSE; + + file->details->is_symlink = FALSE; + file->details->is_hidden = FALSE; + file->details->is_mountpoint = FALSE; + file->details->uid = -1; + file->details->gid = -1; + file->details->can_read = TRUE; + file->details->can_write = TRUE; + file->details->can_execute = TRUE; + file->details->can_delete = TRUE; + file->details->can_trash = TRUE; + file->details->can_rename = TRUE; + file->details->can_mount = FALSE; + file->details->can_unmount = FALSE; + file->details->can_eject = FALSE; + file->details->can_start = FALSE; + file->details->can_start_degraded = FALSE; + file->details->can_stop = FALSE; + file->details->start_stop_type = G_DRIVE_START_STOP_TYPE_UNKNOWN; + file->details->can_poll_for_media = FALSE; + file->details->is_media_check_automatic = FALSE; + file->details->has_permissions = FALSE; + file->details->permissions = 0; + file->details->size = -1; + file->details->sort_order = 0; + file->details->mtime = 0; + file->details->atime = 0; + file->details->btime = 0; + file->details->trash_time = 0; + file->details->recency = 0; + g_free (file->details->symlink_name); + file->details->symlink_name = NULL; + g_clear_pointer (&file->details->mime_type, g_ref_string_release); + g_free (file->details->selinux_context); + file->details->selinux_context = NULL; + g_free (file->details->description); + file->details->description = NULL; + g_clear_pointer (&file->details->owner, g_ref_string_release); + g_clear_pointer (&file->details->owner_real, g_ref_string_release); + g_clear_pointer (&file->details->group, g_ref_string_release); + + g_clear_pointer (&file->details->filesystem_id, g_ref_string_release); + + clear_metadata (file); +} + +NautilusDirectory * +nautilus_file_get_directory (NautilusFile *file) +{ + g_return_val_if_fail (NAUTILUS_IS_FILE (file), NULL); + + return file->details->directory; +} + +void +nautilus_file_set_directory (NautilusFile *file, + NautilusDirectory *directory) +{ + char *parent_uri; + + g_clear_object (&file->details->directory); + g_free (file->details->directory_name_collation_key); + + file->details->directory = nautilus_directory_ref (directory); + + parent_uri = nautilus_file_get_parent_uri (file); + file->details->directory_name_collation_key = g_utf8_collate_key_for_filename (parent_uri, -1); + g_free (parent_uri); +} + +static NautilusFile * +nautilus_file_new_from_filename (NautilusDirectory *directory, + const char *filename, + gboolean self_owned) +{ + NautilusFile *file; + + g_assert (NAUTILUS_IS_DIRECTORY (directory)); + g_assert (filename != NULL); + g_assert (filename[0] != '\0'); + + file = nautilus_directory_new_file_from_filename (directory, filename, self_owned); + file->details->name = g_ref_string_new (filename); + +#ifdef NAUTILUS_FILE_DEBUG_REF + DEBUG_REF_PRINTF ("%10p ref'd", file); +#endif + + return file; +} + +static void +modify_link_hash_table (NautilusFile *file, + ModifyListFunction modify_function) +{ + char *target_uri; + gboolean found; + gpointer original_key; + GList **list_ptr; + + /* Check if there is a symlink name. If none, we are OK. */ + if (file->details->symlink_name == NULL || !nautilus_file_is_symbolic_link (file)) + { + return; + } + + /* Create the hash table first time through. */ + if (symbolic_links == NULL) + { + symbolic_links = g_hash_table_new (g_str_hash, g_str_equal); + } + + target_uri = nautilus_file_get_symbolic_link_target_uri (file); + + /* Find the old contents of the hash table. */ + found = g_hash_table_lookup_extended + (symbolic_links, target_uri, + &original_key, (gpointer *) &list_ptr); + if (!found) + { + list_ptr = g_new0 (GList *, 1); + original_key = g_strdup (target_uri); + g_hash_table_insert (symbolic_links, original_key, list_ptr); + } + (*modify_function)(list_ptr, file); + if (*list_ptr == NULL) + { + g_hash_table_remove (symbolic_links, target_uri); + g_free (list_ptr); + g_free (original_key); + } + g_free (target_uri); +} + +static void +symbolic_link_weak_notify (gpointer data, + GObject *where_the_object_was) +{ + GList **list = data; + /* This really shouldn't happen, but we're seeing some strange things in + * bug #358172 where the symlink hashtable isn't correctly updated. */ + *list = g_list_remove (*list, where_the_object_was); +} + +static void +add_to_link_hash_table_list (GList **list, + NautilusFile *file) +{ + if (g_list_find (*list, file) != NULL) + { + g_warning ("Adding file to symlink_table multiple times. " + "Please add feedback of what you were doing at http://bugzilla.gnome.org/show_bug.cgi?id=358172\n"); + return; + } + g_object_weak_ref (G_OBJECT (file), symbolic_link_weak_notify, list); + *list = g_list_prepend (*list, file); +} + +static void +add_to_link_hash_table (NautilusFile *file) +{ + modify_link_hash_table (file, add_to_link_hash_table_list); +} + +static void +remove_from_link_hash_table_list (GList **list, + NautilusFile *file) +{ + if (g_list_find (*list, file) != NULL) + { + g_object_weak_unref (G_OBJECT (file), symbolic_link_weak_notify, list); + *list = g_list_remove (*list, file); + } +} + +static void +remove_from_link_hash_table (NautilusFile *file) +{ + modify_link_hash_table (file, remove_from_link_hash_table_list); +} + +NautilusFile * +nautilus_file_new_from_info (NautilusDirectory *directory, + GFileInfo *info) +{ + NautilusFile *file; + + g_return_val_if_fail (NAUTILUS_IS_DIRECTORY (directory), NULL); + g_return_val_if_fail (info != NULL, NULL); + + file = NAUTILUS_FILE (g_object_new (NAUTILUS_TYPE_VFS_FILE, NULL)); + nautilus_file_set_directory (file, directory); + + update_info_and_name (file, info); + +#ifdef NAUTILUS_FILE_DEBUG_REF + DEBUG_REF_PRINTF ("%10p ref'd", file); +#endif + + return file; +} + +static NautilusFileInfo * +nautilus_file_get_internal (GFile *location, + gboolean create) +{ + gboolean self_owned; + NautilusDirectory *directory; + NautilusFile *file; + GFile *parent; + char *basename; + + g_assert (location != NULL); + + parent = g_file_get_parent (location); + + self_owned = FALSE; + if (parent == NULL) + { + self_owned = TRUE; + parent = g_object_ref (location); + } + + /* Get object that represents the directory. */ + directory = nautilus_directory_get_internal (parent, create); + + g_object_unref (parent); + + /* Get the name for the file. */ + if (self_owned && directory != NULL) + { + basename = nautilus_directory_get_name_for_self_as_new_file (directory); + } + else + { + basename = g_file_get_basename (location); + } + /* Check to see if it's a file that's already known. */ + if (directory == NULL) + { + file = NULL; + } + else if (self_owned) + { + file = directory->details->as_file; + } + else + { + file = nautilus_directory_find_file_by_name (directory, basename); + } + + /* Ref or create the file. */ + if (file != NULL) + { + nautilus_file_ref (file); + } + else if (create) + { + file = nautilus_file_new_from_filename (directory, basename, self_owned); + if (self_owned) + { + g_assert (directory->details->as_file == NULL); + directory->details->as_file = file; + } + else + { + nautilus_directory_add_file (directory, file); + } + } + + g_free (basename); + nautilus_directory_unref (directory); + + return NAUTILUS_FILE_INFO (file); +} + +NautilusFile * +nautilus_file_get (GFile *location) +{ + g_return_val_if_fail (G_IS_FILE (location), NULL); + + return NAUTILUS_FILE (nautilus_file_get_internal (location, TRUE)); +} + +NautilusFile * +nautilus_file_get_existing (GFile *location) +{ + g_return_val_if_fail (G_IS_FILE (location), NULL); + + return NAUTILUS_FILE (nautilus_file_get_internal (location, FALSE)); +} + +NautilusFile * +nautilus_file_get_existing_by_uri (const char *uri) +{ + g_autoptr (GFile) location = NULL; + + location = g_file_new_for_uri (uri); + + return nautilus_file_get_existing (location); +} + +NautilusFile * +nautilus_file_get_by_uri (const char *uri) +{ + g_autoptr (GFile) location = NULL; + + location = g_file_new_for_uri (uri); + + return nautilus_file_get (location); +} + +gboolean +nautilus_file_is_self_owned (NautilusFile *file) +{ + return file->details->directory->details->as_file == file; +} + +static void +finalize (GObject *object) +{ + NautilusDirectory *directory; + NautilusFile *file; + char *uri; + + file = NAUTILUS_FILE (object); + + g_assert (file->details->operations_in_progress == NULL); + + if (file->details->is_thumbnailing) + { + uri = nautilus_file_get_uri (file); + nautilus_thumbnail_remove_from_queue (uri); + g_free (uri); + } + + nautilus_async_destroying_file (file); + + remove_from_link_hash_table (file); + + directory = file->details->directory; + + if (nautilus_file_is_self_owned (file)) + { + directory->details->as_file = NULL; + } + else + { + if (!file->details->is_gone) + { + nautilus_directory_remove_file (directory, file); + } + } + + if (file->details->get_info_error) + { + g_error_free (file->details->get_info_error); + } + + nautilus_directory_unref (directory); + g_clear_pointer (&file->details->name, g_ref_string_release); + g_clear_pointer (&file->details->display_name, g_ref_string_release); + g_free (file->details->display_name_collation_key); + g_free (file->details->directory_name_collation_key); + g_clear_pointer (&file->details->edit_name, g_ref_string_release); + if (file->details->icon) + { + g_object_unref (file->details->icon); + } + g_free (file->details->thumbnail_path); + g_free (file->details->symlink_name); + g_clear_pointer (&file->details->mime_type, g_ref_string_release); + g_clear_pointer (&file->details->owner, g_ref_string_release); + g_clear_pointer (&file->details->owner_real, g_ref_string_release); + g_clear_pointer (&file->details->group, g_ref_string_release); + g_free (file->details->selinux_context); + g_free (file->details->description); + g_free (file->details->activation_uri); + g_clear_object (&file->details->custom_icon); + + if (file->details->thumbnail) + { + g_object_unref (file->details->thumbnail); + } + + if (file->details->mount) + { + g_signal_handlers_disconnect_by_func (file->details->mount, file_mount_unmounted, file); + g_object_unref (file->details->mount); + } + + g_clear_pointer (&file->details->filesystem_id, g_ref_string_release); + g_clear_pointer (&file->details->filesystem_type, g_ref_string_release); + g_free (file->details->trash_orig_path); + + g_list_free_full (file->details->mime_list, g_free); + g_list_free_full (file->details->pending_extension_emblems, g_free); + g_list_free_full (file->details->extension_emblems, g_free); + g_list_free_full (file->details->pending_info_providers, g_object_unref); + + if (file->details->pending_extension_attributes) + { + g_hash_table_destroy (file->details->pending_extension_attributes); + } + + if (file->details->extension_attributes) + { + g_hash_table_destroy (file->details->extension_attributes); + } + + if (file->details->metadata) + { + metadata_hash_free (file->details->metadata); + } + + g_free (file->details->fts_snippet); + + G_OBJECT_CLASS (nautilus_file_parent_class)->finalize (object); +} + +NautilusFile * +nautilus_file_ref (NautilusFile *file) +{ + if (file == NULL) + { + return NULL; + } + g_return_val_if_fail (NAUTILUS_IS_FILE (file), NULL); + +#ifdef NAUTILUS_FILE_DEBUG_REF + DEBUG_REF_PRINTF ("%10p ref'd", file); +#endif + + return g_object_ref (file); +} + +void +nautilus_file_unref (NautilusFile *file) +{ + if (file == NULL) + { + return; + } + + g_return_if_fail (NAUTILUS_IS_FILE (file)); + +#ifdef NAUTILUS_FILE_DEBUG_REF + DEBUG_REF_PRINTF ("%10p unref'd", file); +#endif + + g_object_unref (file); +} + +/** + * nautilus_file_get_parent_uri_for_display: + * + * Get the uri for the parent directory. + * + * @file: The file in question. + * + * Return value: A string representing the parent's location, + * formatted for user display (including stripping "file://" + * and adding trailing slash). + * If the parent is NULL, returns the empty string. + */ +char * +nautilus_file_get_parent_uri_for_display (NautilusFile *file) +{ + g_autoptr (GFile) parent = NULL; + char *result; + + g_assert (NAUTILUS_IS_FILE (file)); + + parent = nautilus_file_get_parent_location (file); + if (parent) + { + g_autofree gchar *parse_name = g_file_get_parse_name (parent); + + /* Ensure a trailing slash to emphasize it is a directory */ + if (g_str_has_suffix (parse_name, G_DIR_SEPARATOR_S)) + { + result = g_steal_pointer (&parse_name); + } + else + { + result = g_strconcat (parse_name, G_DIR_SEPARATOR_S, NULL); + } + } + else + { + result = g_strdup (""); + } + + return result; +} + +/** + * nautilus_file_get_parent_uri: + * + * Get the uri for the parent directory. + * + * @file: The file in question. + * + * Return value: A string for the parent's location, in "raw URI" form. + * Use nautilus_file_get_parent_uri_for_display instead if the + * result is to be displayed on-screen. + * If the parent is NULL, returns the empty string. + */ +char * +nautilus_file_get_parent_uri (NautilusFile *file) +{ + g_return_val_if_fail (NAUTILUS_IS_FILE (file), NULL); + + return nautilus_file_info_get_parent_uri (NAUTILUS_FILE_INFO (file)); +} + +GFile * +nautilus_file_get_parent_location (NautilusFile *file) +{ + g_return_val_if_fail (NAUTILUS_IS_FILE (file), NULL); + + return nautilus_file_info_get_parent_location (NAUTILUS_FILE_INFO (file)); +} + +NautilusFile * +nautilus_file_get_parent (NautilusFile *file) +{ + NautilusFileInfo *file_info; + + g_return_val_if_fail (NAUTILUS_IS_FILE (file), NULL); + + file_info = NAUTILUS_FILE_INFO (file); + + return NAUTILUS_FILE (nautilus_file_info_get_parent_info (file_info)); +} + +/** + * nautilus_file_can_read: + * + * Check whether the user is allowed to read the contents of this file. + * + * @file: The file to check. + * + * Return value: FALSE if the user is definitely not allowed to read + * the contents of the file. If the user has read permission, or + * the code can't tell whether the user has read permission, + * returns TRUE (so failures must always be handled). + */ +gboolean +nautilus_file_can_read (NautilusFile *file) +{ + g_return_val_if_fail (NAUTILUS_IS_FILE (file), FALSE); + + return file->details->can_read; +} + +/** + * nautilus_file_can_write: + * + * Check whether the user is allowed to write to this file. + * + * @file: The file to check. + * + * Return value: FALSE if the user is definitely not allowed to write + * to the file. If the user has write permission, or + * the code can't tell whether the user has write permission, + * returns TRUE (so failures must always be handled). + */ +gboolean +nautilus_file_can_write (NautilusFile *file) +{ + g_return_val_if_fail (NAUTILUS_IS_FILE (file), FALSE); + + return nautilus_file_info_can_write (NAUTILUS_FILE_INFO (file)); +} + +/** + * nautilus_file_can_execute: + * + * Check whether the user is allowed to execute this file. + * + * @file: The file to check. + * + * Return value: FALSE if the user is definitely not allowed to execute + * the file. If the user has execute permission, or + * the code can't tell whether the user has execute permission, + * returns TRUE (so failures must always be handled). + */ +gboolean +nautilus_file_can_execute (NautilusFile *file) +{ + g_return_val_if_fail (NAUTILUS_IS_FILE (file), FALSE); + + return file->details->can_execute; +} + +gboolean +nautilus_file_can_mount (NautilusFile *file) +{ + g_return_val_if_fail (NAUTILUS_IS_FILE (file), FALSE); + + return file->details->can_mount; +} + +gboolean +nautilus_file_can_unmount (NautilusFile *file) +{ + g_return_val_if_fail (NAUTILUS_IS_FILE (file), FALSE); + + return file->details->can_unmount || + (file->details->mount != NULL && + g_mount_can_unmount (file->details->mount)); +} + +gboolean +nautilus_file_can_eject (NautilusFile *file) +{ + g_return_val_if_fail (NAUTILUS_IS_FILE (file), FALSE); + + return file->details->can_eject || + (file->details->mount != NULL && + g_mount_can_eject (file->details->mount)); +} + +gboolean +nautilus_file_can_start (NautilusFile *file) +{ + gboolean ret; + GDrive *drive; + + g_return_val_if_fail (NAUTILUS_IS_FILE (file), FALSE); + + ret = FALSE; + + if (file->details->can_start) + { + ret = TRUE; + goto out; + } + + if (file->details->mount != NULL) + { + drive = g_mount_get_drive (file->details->mount); + if (drive != NULL) + { + ret = g_drive_can_start (drive); + g_object_unref (drive); + } + } + +out: + return ret; +} + +gboolean +nautilus_file_can_start_degraded (NautilusFile *file) +{ + gboolean ret; + GDrive *drive; + + g_return_val_if_fail (NAUTILUS_IS_FILE (file), FALSE); + + ret = FALSE; + + if (file->details->can_start_degraded) + { + ret = TRUE; + goto out; + } + + if (file->details->mount != NULL) + { + drive = g_mount_get_drive (file->details->mount); + if (drive != NULL) + { + ret = g_drive_can_start_degraded (drive); + g_object_unref (drive); + } + } + +out: + return ret; +} + +gboolean +nautilus_file_can_poll_for_media (NautilusFile *file) +{ + gboolean ret; + GDrive *drive; + + g_return_val_if_fail (NAUTILUS_IS_FILE (file), FALSE); + + ret = FALSE; + + if (file->details->can_poll_for_media) + { + ret = TRUE; + goto out; + } + + if (file->details->mount != NULL) + { + drive = g_mount_get_drive (file->details->mount); + if (drive != NULL) + { + ret = g_drive_can_poll_for_media (drive); + g_object_unref (drive); + } + } + +out: + return ret; +} + +gboolean +nautilus_file_is_media_check_automatic (NautilusFile *file) +{ + gboolean ret; + GDrive *drive; + + g_return_val_if_fail (NAUTILUS_IS_FILE (file), FALSE); + + ret = FALSE; + + if (file->details->is_media_check_automatic) + { + ret = TRUE; + goto out; + } + + if (file->details->mount != NULL) + { + drive = g_mount_get_drive (file->details->mount); + if (drive != NULL) + { + ret = g_drive_is_media_check_automatic (drive); + g_object_unref (drive); + } + } + +out: + return ret; +} + + +gboolean +nautilus_file_can_stop (NautilusFile *file) +{ + gboolean ret; + GDrive *drive; + + g_return_val_if_fail (NAUTILUS_IS_FILE (file), FALSE); + + ret = FALSE; + + if (file->details->can_stop) + { + ret = TRUE; + goto out; + } + + if (file->details->mount != NULL) + { + drive = g_mount_get_drive (file->details->mount); + if (drive != NULL) + { + ret = g_drive_can_stop (drive); + g_object_unref (drive); + } + } + +out: + return ret; +} + +GDriveStartStopType +nautilus_file_get_start_stop_type (NautilusFile *file) +{ + GDriveStartStopType ret; + GDrive *drive; + + g_return_val_if_fail (NAUTILUS_IS_FILE (file), FALSE); + + ret = file->details->start_stop_type; + if (ret != G_DRIVE_START_STOP_TYPE_UNKNOWN) + { + goto out; + } + + if (file->details->mount != NULL) + { + drive = g_mount_get_drive (file->details->mount); + if (drive != NULL) + { + ret = g_drive_get_start_stop_type (drive); + g_object_unref (drive); + } + } + +out: + return ret; +} + +void +nautilus_file_mount (NautilusFile *file, + GMountOperation *mount_op, + GCancellable *cancellable, + NautilusFileOperationCallback callback, + gpointer callback_data) +{ + GError *error; + + if (NAUTILUS_FILE_GET_CLASS (file)->mount == NULL) + { + if (callback) + { + error = NULL; + g_set_error_literal (&error, G_IO_ERROR, G_IO_ERROR_NOT_SUPPORTED, + _("This file cannot be mounted")); + callback (file, NULL, error, callback_data); + g_error_free (error); + } + } + else + { + NAUTILUS_FILE_GET_CLASS (file)->mount (file, mount_op, cancellable, callback, callback_data); + } +} + +typedef struct +{ + NautilusFile *file; + NautilusFileOperationCallback callback; + gpointer callback_data; +} UnmountData; + +static void +unmount_done (void *callback_data) +{ + UnmountData *data; + + data = (UnmountData *) callback_data; + if (data->callback) + { + data->callback (data->file, NULL, NULL, data->callback_data); + } + nautilus_file_unref (data->file); + g_free (data); +} + +void +nautilus_file_unmount (NautilusFile *file, + GMountOperation *mount_op, + GCancellable *cancellable, + NautilusFileOperationCallback callback, + gpointer callback_data) +{ + GError *error; + UnmountData *data; + + if (file->details->can_unmount) + { + if (NAUTILUS_FILE_GET_CLASS (file)->unmount != NULL) + { + NAUTILUS_FILE_GET_CLASS (file)->unmount (file, mount_op, cancellable, callback, callback_data); + } + else + { + if (callback) + { + error = NULL; + g_set_error_literal (&error, G_IO_ERROR, G_IO_ERROR_NOT_SUPPORTED, + _("This file cannot be unmounted")); + callback (file, NULL, error, callback_data); + g_error_free (error); + } + } + } + else if (file->details->mount != NULL && + g_mount_can_unmount (file->details->mount)) + { + GtkWindow *parent; + + parent = gtk_mount_operation_get_parent (GTK_MOUNT_OPERATION (mount_op)); + + data = g_new0 (UnmountData, 1); + data->file = nautilus_file_ref (file); + data->callback = callback; + data->callback_data = callback_data; + nautilus_file_operations_unmount_mount_full (parent, file->details->mount, mount_op, FALSE, TRUE, unmount_done, data); + } + else if (callback) + { + callback (file, NULL, NULL, callback_data); + } +} + +void +nautilus_file_eject (NautilusFile *file, + GMountOperation *mount_op, + GCancellable *cancellable, + NautilusFileOperationCallback callback, + gpointer callback_data) +{ + GError *error; + UnmountData *data; + + if (file->details->can_eject) + { + if (NAUTILUS_FILE_GET_CLASS (file)->eject != NULL) + { + NAUTILUS_FILE_GET_CLASS (file)->eject (file, mount_op, cancellable, callback, callback_data); + } + else + { + if (callback) + { + error = NULL; + g_set_error_literal (&error, G_IO_ERROR, G_IO_ERROR_NOT_SUPPORTED, + _("This file cannot be ejected")); + callback (file, NULL, error, callback_data); + g_error_free (error); + } + } + } + else if (file->details->mount != NULL && + g_mount_can_eject (file->details->mount)) + { + GtkWindow *parent; + + parent = gtk_mount_operation_get_parent (GTK_MOUNT_OPERATION (mount_op)); + + data = g_new0 (UnmountData, 1); + data->file = nautilus_file_ref (file); + data->callback = callback; + data->callback_data = callback_data; + nautilus_file_operations_unmount_mount_full (parent, file->details->mount, mount_op, TRUE, TRUE, unmount_done, data); + } + else if (callback) + { + callback (file, NULL, NULL, callback_data); + } +} + +void +nautilus_file_start (NautilusFile *file, + GMountOperation *start_op, + GCancellable *cancellable, + NautilusFileOperationCallback callback, + gpointer callback_data) +{ + GError *error; + + if ((file->details->can_start || file->details->can_start_degraded) && + NAUTILUS_FILE_GET_CLASS (file)->start != NULL) + { + NAUTILUS_FILE_GET_CLASS (file)->start (file, start_op, cancellable, callback, callback_data); + } + else + { + if (callback) + { + error = NULL; + g_set_error_literal (&error, G_IO_ERROR, G_IO_ERROR_NOT_SUPPORTED, + _("This file cannot be started")); + callback (file, NULL, error, callback_data); + g_error_free (error); + } + } +} + +static void +file_stop_callback (GObject *source_object, + GAsyncResult *res, + gpointer callback_data) +{ + NautilusFileOperation *op; + gboolean stopped; + GError *error; + + op = callback_data; + + error = NULL; + stopped = g_drive_stop_finish (G_DRIVE (source_object), + res, &error); + + if (!stopped && + error->domain == G_IO_ERROR && + (error->code == G_IO_ERROR_FAILED_HANDLED || + error->code == G_IO_ERROR_CANCELLED)) + { + g_error_free (error); + error = NULL; + } + + nautilus_file_operation_complete (op, NULL, error); + if (error) + { + g_error_free (error); + } +} + +void +nautilus_file_stop (NautilusFile *file, + GMountOperation *mount_op, + GCancellable *cancellable, + NautilusFileOperationCallback callback, + gpointer callback_data) +{ + GError *error; + + if (NAUTILUS_FILE_GET_CLASS (file)->stop != NULL) + { + if (file->details->can_stop) + { + NAUTILUS_FILE_GET_CLASS (file)->stop (file, mount_op, cancellable, callback, callback_data); + } + else + { + if (callback) + { + error = NULL; + g_set_error_literal (&error, G_IO_ERROR, G_IO_ERROR_NOT_SUPPORTED, + _("This file cannot be stopped")); + callback (file, NULL, error, callback_data); + g_error_free (error); + } + } + } + else + { + GDrive *drive; + + drive = NULL; + if (file->details->mount != NULL) + { + drive = g_mount_get_drive (file->details->mount); + } + + if (drive != NULL && g_drive_can_stop (drive)) + { + NautilusFileOperation *op; + + op = nautilus_file_operation_new (file, callback, callback_data); + if (cancellable) + { + g_object_unref (op->cancellable); + op->cancellable = g_object_ref (cancellable); + } + + g_drive_stop (drive, + G_MOUNT_UNMOUNT_NONE, + mount_op, + op->cancellable, + file_stop_callback, + op); + } + else + { + if (callback) + { + error = NULL; + g_set_error_literal (&error, G_IO_ERROR, G_IO_ERROR_NOT_SUPPORTED, + _("This file cannot be stopped")); + callback (file, NULL, error, callback_data); + g_error_free (error); + } + } + + if (drive != NULL) + { + g_object_unref (drive); + } + } +} + +void +nautilus_file_poll_for_media (NautilusFile *file) +{ + if (file->details->can_poll_for_media) + { + if (NAUTILUS_FILE_GET_CLASS (file)->stop != NULL) + { + NAUTILUS_FILE_GET_CLASS (file)->poll_for_media (file); + } + } + else if (file->details->mount != NULL) + { + GDrive *drive; + drive = g_mount_get_drive (file->details->mount); + if (drive != NULL) + { + g_drive_poll_for_media (drive, + NULL, /* cancellable */ + NULL, /* GAsyncReadyCallback */ + NULL); /* user_data */ + g_object_unref (drive); + } + } +} + +/** + * nautilus_file_can_rename: + * + * Check whether the user is allowed to change the name of the file. + * + * @file: The file to check. + * + * Return value: FALSE if the user is definitely not allowed to change + * the name of the file. If the user is allowed to change the name, or + * the code can't tell whether the user is allowed to change the name, + * returns TRUE (so rename failures must always be handled). + */ +gboolean +nautilus_file_can_rename (NautilusFile *file) +{ + gboolean can_rename; + + g_return_val_if_fail (NAUTILUS_IS_FILE (file), FALSE); + + /* Nonexistent files can't be renamed. */ + if (nautilus_file_is_gone (file)) + { + return FALSE; + } + + /* Self-owned files can't be renamed */ + if (nautilus_file_is_self_owned (file)) + { + return FALSE; + } + + if (nautilus_file_is_home (file)) + { + return FALSE; + } + + can_rename = TRUE; + + if (!can_rename) + { + return FALSE; + } + + return file->details->can_rename; +} + +gboolean +nautilus_file_can_delete (NautilusFile *file) +{ + g_return_val_if_fail (NAUTILUS_IS_FILE (file), FALSE); + + /* Nonexistent files can't be deleted. */ + if (nautilus_file_is_gone (file)) + { + return FALSE; + } + + /* Self-owned files can't be deleted */ + if (nautilus_file_is_self_owned (file)) + { + return FALSE; + } + + return file->details->can_delete; +} + +gboolean +nautilus_file_can_trash (NautilusFile *file) +{ + g_return_val_if_fail (NAUTILUS_IS_FILE (file), FALSE); + + /* Nonexistent files can't be deleted. */ + if (nautilus_file_is_gone (file)) + { + return FALSE; + } + + /* Self-owned files can't be deleted */ + if (nautilus_file_is_self_owned (file)) + { + return FALSE; + } + + return file->details->can_trash; +} + +GFile * +nautilus_file_get_location (NautilusFile *file) +{ + g_return_val_if_fail (NAUTILUS_IS_FILE (file), NULL); + + return nautilus_file_info_get_location (NAUTILUS_FILE_INFO (file)); +} + +/* Return the actual uri associated with the passed-in file. */ +char * +nautilus_file_get_uri (NautilusFile *file) +{ + g_return_val_if_fail (NAUTILUS_IS_FILE (file), NULL); + + return nautilus_file_info_get_uri (NAUTILUS_FILE_INFO (file)); +} + +char * +nautilus_file_get_uri_scheme (NautilusFile *file) +{ + g_return_val_if_fail (NAUTILUS_IS_FILE (file), NULL); + + return nautilus_file_info_get_uri_scheme (NAUTILUS_FILE_INFO (file)); +} + + +gboolean +nautilus_file_opens_in_view (NautilusFile *file) +{ + return nautilus_file_is_directory (file); +} + +NautilusFileOperation * +nautilus_file_operation_new (NautilusFile *file, + NautilusFileOperationCallback callback, + gpointer callback_data) +{ + NautilusFileOperation *op; + + op = g_new0 (NautilusFileOperation, 1); + op->file = nautilus_file_ref (file); + op->callback = callback; + op->callback_data = callback_data; + op->cancellable = g_cancellable_new (); + + op->file->details->operations_in_progress = g_list_prepend + (op->file->details->operations_in_progress, op); + + return op; +} + +static void +nautilus_file_operation_remove (NautilusFileOperation *op) +{ + GList *l; + NautilusFile *file; + + op->file->details->operations_in_progress = g_list_remove + (op->file->details->operations_in_progress, op); + + + for (l = op->files; l != NULL; l = l->next) + { + file = NAUTILUS_FILE (l->data); + file->details->operations_in_progress = g_list_remove + (file->details->operations_in_progress, op); + } +} + +void +nautilus_file_operation_free (NautilusFileOperation *op) +{ + nautilus_file_operation_remove (op); + + if (op->files == NULL) + { + nautilus_file_unref (op->file); + } + else + { + nautilus_file_list_free (op->files); + } + + g_object_unref (op->cancellable); + if (op->free_data) + { + op->free_data (op->data); + } + + if (op->undo_info != NULL) + { + nautilus_file_undo_manager_set_action (op->undo_info); + g_object_unref (op->undo_info); + } + + g_free (op); +} + +void +nautilus_file_operation_complete (NautilusFileOperation *op, + GFile *result_file, + GError *error) +{ + /* Claim that something changed even if the operation failed. + * This makes it easier for some clients who see the "reverting" + * as "changing back". + */ + nautilus_file_operation_remove (op); + + if (op->files == NULL) + { + nautilus_file_changed (op->file); + } + + if (op->callback) + { + (*op->callback)(op->file, result_file, error, op->callback_data); + } + + if (error != NULL) + { + g_clear_object (&op->undo_info); + } + + nautilus_file_operation_free (op); +} + +void +nautilus_file_operation_cancel (NautilusFileOperation *op) +{ + /* Cancel the operation if it's still in progress. */ + g_cancellable_cancel (op->cancellable); +} + +static void +rename_get_info_callback (GObject *source_object, + GAsyncResult *res, + gpointer callback_data) +{ + NautilusFileOperation *op; + NautilusDirectory *directory; + NautilusFile *existing_file; + char *old_uri; + char *new_uri; + const char *new_name; + GFileInfo *new_info; + GError *error; + + op = callback_data; + + error = NULL; + new_info = g_file_query_info_finish (G_FILE (source_object), res, &error); + if (new_info != NULL) + { + g_autoptr (GFile) old_location = NULL; + g_autoptr (GFile) new_location = NULL; + + directory = op->file->details->directory; + + new_name = g_file_info_get_name (new_info); + + /* If there was another file by the same name in this + * directory and it is not the same file that we are + * renaming, mark it gone. + */ + existing_file = nautilus_directory_find_file_by_name (directory, new_name); + if (existing_file != NULL && existing_file != op->file) + { + nautilus_file_mark_gone (existing_file); + nautilus_file_changed (existing_file); + } + + old_location = nautilus_file_get_location (op->file); + old_uri = g_file_get_uri (old_location); + + update_info_and_name (op->file, new_info); + + new_location = nautilus_file_get_location (op->file); + new_uri = g_file_get_uri (new_location); + + nautilus_directory_moved (old_uri, new_uri); + nautilus_tag_manager_update_moved_uris (nautilus_tag_manager_get (), + old_location, + new_location); + + g_free (new_uri); + g_free (old_uri); + + g_object_unref (new_info); + } + nautilus_file_operation_complete (op, NULL, error); + if (error) + { + g_error_free (error); + } +} + +static void +rename_callback (GObject *source_object, + GAsyncResult *res, + gpointer callback_data) +{ + NautilusFileOperation *op; + GFile *new_file; + GError *error; + + op = callback_data; + + error = NULL; + new_file = g_file_set_display_name_finish (G_FILE (source_object), + res, &error); + + if (new_file != NULL) + { + if (op->undo_info != NULL) + { + nautilus_file_undo_info_rename_set_data_post (NAUTILUS_FILE_UNDO_INFO_RENAME (op->undo_info), + new_file); + } + g_file_query_info_async (new_file, + NAUTILUS_FILE_DEFAULT_ATTRIBUTES, + 0, + G_PRIORITY_DEFAULT, + op->cancellable, + rename_get_info_callback, op); + } + else + { + nautilus_file_operation_complete (op, NULL, error); + g_error_free (error); + } +} + +static gboolean +name_is (NautilusFile *file, + const char *new_name) +{ + const char *old_name; + old_name = file->details->name; + return strcmp (new_name, old_name) == 0; +} + +static gchar * +nautilus_file_can_rename_file (NautilusFile *file, + const char *new_name, + NautilusFileOperationCallback callback, + gpointer callback_data) +{ + GError *error; + gchar *new_file_name; + + /* Return an error for incoming names containing path separators. + * But not for .desktop files as '/' are allowed for them */ + if (strstr (new_name, "/") != NULL) + { + error = g_error_new (G_IO_ERROR, G_IO_ERROR_INVALID_ARGUMENT, + _("Slashes are not allowed in filenames")); + if (callback != NULL) + { + (*callback)(file, NULL, error, callback_data); + } + g_error_free (error); + return NULL; + } + + /* Can't rename a file that's already gone. + * We need to check this here because there may be a new + * file with the same name. + */ + if (nautilus_file_rename_handle_file_gone (file, callback, callback_data)) + { + return NULL; + } + + /* Test the name-hasn't-changed case explicitly, for two reasons. + * (1) rename returns an error if new & old are same. + * (2) We don't want to send file-changed signal if nothing changed. + */ + if (name_is (file, new_name)) + { + if (callback != NULL) + { + (*callback)(file, NULL, NULL, callback_data); + } + return NULL; + } + + /* Self-owned files can't be renamed. Test the name-not-actually-changing + * case before this case. + */ + if (nautilus_file_is_self_owned (file)) + { + /* Claim that something changed even if the rename + * failed. This makes it easier for some clients who + * see the "reverting" to the old name as "changing + * back". + */ + nautilus_file_changed (file); + error = g_error_new (G_IO_ERROR, G_IO_ERROR_NOT_SUPPORTED, + _("Toplevel files cannot be renamed")); + + if (callback != NULL) + { + (*callback)(file, NULL, error, callback_data); + } + g_error_free (error); + + return NULL; + } + + new_file_name = g_strdup (new_name); + + return new_file_name; +} + +void +nautilus_file_rename (NautilusFile *file, + const char *new_name, + NautilusFileOperationCallback callback, + gpointer callback_data) +{ + NautilusFileOperation *op; + char *old_name; + char *new_file_name; + GFile *location; + + g_return_if_fail (NAUTILUS_IS_FILE (file)); + g_return_if_fail (new_name != NULL); + g_return_if_fail (callback != NULL); + + new_file_name = nautilus_file_can_rename_file (file, + new_name, + callback, + callback_data); + + if (new_file_name == NULL) + { + return; + } + + /* Set up a renaming operation. */ + op = nautilus_file_operation_new (file, callback, callback_data); + op->is_rename = TRUE; + location = nautilus_file_get_location (file); + + /* Tell the undo manager a rename is taking place */ + if (!nautilus_file_undo_manager_is_operating ()) + { + op->undo_info = nautilus_file_undo_info_rename_new (); + + old_name = nautilus_file_get_display_name (file); + nautilus_file_undo_info_rename_set_data_pre (NAUTILUS_FILE_UNDO_INFO_RENAME (op->undo_info), + location, old_name, new_file_name); + g_free (old_name); + } + + /* Do the renaming. */ + g_file_set_display_name_async (location, + new_file_name, + G_PRIORITY_DEFAULT, + op->cancellable, + rename_callback, + op); + g_free (new_file_name); + g_object_unref (location); +} + +gboolean +nautilus_file_rename_handle_file_gone (NautilusFile *file, + NautilusFileOperationCallback callback, + gpointer callback_data) +{ + GError *error; + + if (nautilus_file_is_gone (file)) + { + /* Claim that something changed even if the rename + * failed. This makes it easier for some clients who + * see the "reverting" to the old name as "changing + * back". + */ + nautilus_file_changed (file); + error = g_error_new (G_IO_ERROR, G_IO_ERROR_NOT_FOUND, + _("File not found")); + if (callback) + { + (*callback)(file, NULL, error, callback_data); + } + g_error_free (error); + return TRUE; + } + + return FALSE; +} + +typedef struct +{ + NautilusFileOperation *op; + NautilusFile *file; +} BatchRenameData; + +static void +batch_rename_get_info_callback (GObject *source_object, + GAsyncResult *res, + gpointer callback_data) +{ + NautilusFileOperation *op; + NautilusDirectory *directory; + NautilusFile *existing_file; + char *old_uri; + char *new_uri; + const char *new_name; + GFileInfo *new_info; + GError *error; + BatchRenameData *data; + + data = callback_data; + + op = data->op; + op->file = data->file; + + error = NULL; + new_info = g_file_query_info_finish (G_FILE (source_object), res, &error); + if (new_info != NULL) + { + old_uri = nautilus_file_get_uri (op->file); + + new_name = g_file_info_get_name (new_info); + + directory = op->file->details->directory; + + /* If there was another file by the same name in this + * directory and it is not the same file that we are + * renaming, mark it gone. + */ + existing_file = nautilus_directory_find_file_by_name (directory, new_name); + if (existing_file != NULL && existing_file != op->file) + { + nautilus_file_mark_gone (existing_file); + nautilus_file_changed (existing_file); + } + + update_info_and_name (op->file, new_info); + + new_uri = nautilus_file_get_uri (op->file); + nautilus_directory_moved (old_uri, new_uri); + g_free (new_uri); + g_free (old_uri); + g_object_unref (new_info); + } + + op->renamed_files++; + + if (op->files == NULL || + op->renamed_files + op->skipped_files == g_list_length (op->files)) + { + nautilus_file_operation_complete (op, NULL, error); + } + + g_free (data); + + if (error) + { + g_error_free (error); + } +} + +static void +real_batch_rename (GList *files, + GList *new_names, + NautilusFileOperationCallback callback, + gpointer callback_data) +{ + GList *l1, *l2, *old_files, *new_files; + NautilusFileOperation *op; + GFile *location; + GString *new_name; + NautilusFile *file; + GError *error; + GFile *new_file; + BatchRenameData *data; + + error = NULL; + old_files = NULL; + new_files = NULL; + + /* Set up a batch renaming operation. */ + op = nautilus_file_operation_new (files->data, callback, callback_data); + op->files = nautilus_file_list_copy (files); + op->renamed_files = 0; + op->skipped_files = 0; + + for (l1 = files->next; l1 != NULL; l1 = l1->next) + { + file = NAUTILUS_FILE (l1->data); + + file->details->operations_in_progress = g_list_prepend (file->details->operations_in_progress, + op); + } + + for (l1 = files, l2 = new_names; l1 != NULL && l2 != NULL; l1 = l1->next, l2 = l2->next) + { + g_autofree gchar *new_file_name = NULL; + file = NAUTILUS_FILE (l1->data); + new_name = l2->data; + + location = nautilus_file_get_location (file); + + new_file_name = nautilus_file_can_rename_file (file, + new_name->str, + callback, + callback_data); + + if (new_file_name == NULL) + { + op->skipped_files++; + + continue; + } + + g_assert (G_IS_FILE (location)); + + /* Do the renaming. */ + new_file = g_file_set_display_name (location, + new_file_name, + op->cancellable, + &error); + + data = g_new0 (BatchRenameData, 1); + data->op = op; + data->file = file; + + g_file_query_info_async (new_file, + NAUTILUS_FILE_DEFAULT_ATTRIBUTES, + 0, + G_PRIORITY_DEFAULT, + op->cancellable, + batch_rename_get_info_callback, + data); + + if (error != NULL) + { + g_warning ("Batch rename for file \"%s\" failed", nautilus_file_get_name (file)); + g_error_free (error); + error = NULL; + + op->skipped_files++; + } + else + { + old_files = g_list_append (old_files, location); + new_files = g_list_append (new_files, new_file); + } + } + + /* Tell the undo manager a batch rename is taking place if at least + * a file has been renamed*/ + if (!nautilus_file_undo_manager_is_operating () && op->skipped_files != g_list_length (files)) + { + op->undo_info = nautilus_file_undo_info_batch_rename_new (g_list_length (new_files)); + + nautilus_file_undo_info_batch_rename_set_data_pre (NAUTILUS_FILE_UNDO_INFO_BATCH_RENAME (op->undo_info), + old_files); + + nautilus_file_undo_info_batch_rename_set_data_post (NAUTILUS_FILE_UNDO_INFO_BATCH_RENAME (op->undo_info), + new_files); + + nautilus_file_undo_manager_set_action (op->undo_info); + } + + if (op->skipped_files == g_list_length (files)) + { + nautilus_file_operation_complete (op, NULL, error); + } +} + +void +nautilus_file_batch_rename (GList *files, + GList *new_names, + NautilusFileOperationCallback callback, + gpointer callback_data) +{ + real_batch_rename (files, + new_names, + callback, + callback_data); +} + +gboolean +nautilus_file_rename_in_progress (NautilusFile *file) +{ + GList *node; + NautilusFileOperation *op; + + for (node = file->details->operations_in_progress; node != NULL; node = node->next) + { + op = node->data; + if (op->is_rename) + { + return TRUE; + } + } + return FALSE; +} + +void +nautilus_file_cancel (NautilusFile *file, + NautilusFileOperationCallback callback, + gpointer callback_data) +{ + GList *node, *next; + NautilusFileOperation *op; + + for (node = file->details->operations_in_progress; node != NULL; node = next) + { + next = node->next; + op = node->data; + + g_assert (op->file == file); + if (op->callback == callback && op->callback_data == callback_data) + { + nautilus_file_operation_cancel (op); + } + } +} + +gboolean +nautilus_file_matches_uri (NautilusFile *file, + const char *match_uri) +{ + GFile *match_file, *location; + gboolean result; + + g_return_val_if_fail (NAUTILUS_IS_FILE (file), FALSE); + g_return_val_if_fail (match_uri != NULL, FALSE); + + location = nautilus_file_get_location (file); + match_file = g_file_new_for_uri (match_uri); + result = g_file_equal (location, match_file); + g_object_unref (location); + g_object_unref (match_file); + + return result; +} + +int +nautilus_file_compare_location (NautilusFile *file_1, + NautilusFile *file_2) +{ + GFile *loc_a, *loc_b; + gboolean res; + + loc_a = nautilus_file_get_location (file_1); + loc_b = nautilus_file_get_location (file_2); + + res = !g_file_equal (loc_a, loc_b); + + g_object_unref (loc_a); + g_object_unref (loc_b); + + return (gint) res; +} + +/** + * nautilus_file_has_local_path: + * + * @file: a #NautilusFile + * + * Checks whether this file has an obtainable local paths. Usually, this means + * the local path can be obtained by calling g_file_get_path(); this includes + * native and FUSE files. As an exception, the local URI for files in recent:// + * can only be obtained from the G_FILE_ATTRIBUTE_STANDARD_TARGET_URI attribute. + * + * Returns: %TRUE if a local path is known to be obtainable for this file. + */ +gboolean +nautilus_file_has_local_path (NautilusFile *file) +{ + g_return_val_if_fail (NAUTILUS_IS_FILE (file), FALSE); + + return nautilus_directory_is_local_or_fuse (file->details->directory); +} + +static void +update_link (NautilusFile *link_file, + NautilusFile *target_file) +{ + g_assert (NAUTILUS_IS_FILE (link_file)); + g_assert (NAUTILUS_IS_FILE (target_file)); + + /* FIXME bugzilla.gnome.org 42044: If we don't put any code + * here then the hash table is a waste of time. + */ +} + +static GList * +get_link_files (NautilusFile *target_file) +{ + char *uri; + GList **link_files; + + if (symbolic_links == NULL) + { + link_files = NULL; + } + else + { + uri = nautilus_file_get_uri (target_file); + link_files = g_hash_table_lookup (symbolic_links, uri); + g_free (uri); + } + if (link_files) + { + return nautilus_file_list_copy (*link_files); + } + return NULL; +} + +static void +update_links_if_target (NautilusFile *target_file) +{ + GList *link_files, *p; + + link_files = get_link_files (target_file); + for (p = link_files; p != NULL; p = p->next) + { + update_link (NAUTILUS_FILE (p->data), target_file); + } + nautilus_file_list_free (link_files); +} + +static gboolean +update_info_internal (NautilusFile *file, + GFileInfo *info, + gboolean update_name) +{ + GList *node; + gboolean changed; + gboolean is_symlink, is_hidden, is_mountpoint; + gboolean has_permissions; + guint32 permissions; + gboolean can_read, can_write, can_execute, can_delete, can_trash, can_rename, can_mount, can_unmount, can_eject; + gboolean can_start, can_start_degraded, can_stop, can_poll_for_media, is_media_check_automatic; + GDriveStartStopType start_stop_type; + gboolean thumbnailing_failed; + int uid, gid; + goffset size; + int sort_order; + time_t atime, mtime, btime; + time_t trash_time; + time_t recency; + GTimeVal g_trash_time; + const char *time_string; + const char *symlink_name, *mime_type, *selinux_context, *name, *thumbnail_path; + GFileType file_type; + GIcon *icon; + char *old_activation_uri; + const char *activation_uri; + const char *description; + const char *filesystem_id; + const char *trash_orig_path; + const char *group, *owner, *owner_real; + gboolean free_owner, free_group; + + if (file->details->is_gone) + { + return FALSE; + } + + if (info == NULL) + { + nautilus_file_mark_gone (file); + return TRUE; + } + + file->details->file_info_is_up_to_date = TRUE; + + /* FIXME bugzilla.gnome.org 42044: Need to let links that + * point to the old name know that the file has been renamed. + */ + + remove_from_link_hash_table (file); + + changed = FALSE; + + if (!file->details->got_file_info) + { + changed = TRUE; + } + file->details->got_file_info = TRUE; + + changed |= nautilus_file_set_display_name (file, + g_file_info_get_display_name (info), + g_file_info_get_edit_name (info), + FALSE); + + file_type = g_file_info_get_file_type (info); + if (file->details->type != file_type) + { + changed = TRUE; + } + file->details->type = file_type; + + if (!file->details->got_custom_activation_uri && + (g_file_info_get_attribute_boolean (info, G_FILE_ATTRIBUTE_STANDARD_IS_VIRTUAL) || + file_type == G_FILE_TYPE_SHORTCUT || + nautilus_file_is_in_recent (file))) + { + activation_uri = g_file_info_get_attribute_string (info, G_FILE_ATTRIBUTE_STANDARD_TARGET_URI); + if (activation_uri == NULL) + { + if (file->details->activation_uri) + { + g_free (file->details->activation_uri); + file->details->activation_uri = NULL; + changed = TRUE; + } + } + else + { + old_activation_uri = file->details->activation_uri; + file->details->activation_uri = g_strdup (activation_uri); + + if (old_activation_uri) + { + if (strcmp (old_activation_uri, + file->details->activation_uri) != 0) + { + changed = TRUE; + } + g_free (old_activation_uri); + } + else + { + changed = TRUE; + } + } + } + + is_symlink = g_file_info_get_is_symlink (info); + if (file->details->is_symlink != is_symlink) + { + changed = TRUE; + } + file->details->is_symlink = is_symlink; + + is_hidden = g_file_info_get_is_hidden (info) || g_file_info_get_is_backup (info); + if (file->details->is_hidden != is_hidden) + { + changed = TRUE; + } + file->details->is_hidden = is_hidden; + + is_mountpoint = g_file_info_get_attribute_boolean (info, G_FILE_ATTRIBUTE_UNIX_IS_MOUNTPOINT); + if (file->details->is_mountpoint != is_mountpoint) + { + changed = TRUE; + } + file->details->is_mountpoint = is_mountpoint; + + has_permissions = g_file_info_has_attribute (info, G_FILE_ATTRIBUTE_UNIX_MODE); + permissions = g_file_info_get_attribute_uint32 (info, G_FILE_ATTRIBUTE_UNIX_MODE); + if (file->details->has_permissions != has_permissions || + file->details->permissions != permissions) + { + changed = TRUE; + } + file->details->has_permissions = has_permissions; + file->details->permissions = permissions; + + /* We default to TRUE for this if we can't know */ + can_read = TRUE; + can_write = TRUE; + can_execute = TRUE; + can_delete = TRUE; + can_rename = TRUE; + can_trash = FALSE; + can_mount = FALSE; + can_unmount = FALSE; + can_eject = FALSE; + can_start = FALSE; + can_start_degraded = FALSE; + can_stop = FALSE; + can_poll_for_media = FALSE; + is_media_check_automatic = FALSE; + start_stop_type = G_DRIVE_START_STOP_TYPE_UNKNOWN; + if (g_file_info_has_attribute (info, G_FILE_ATTRIBUTE_ACCESS_CAN_READ)) + { + can_read = g_file_info_get_attribute_boolean (info, + G_FILE_ATTRIBUTE_ACCESS_CAN_READ); + } + if (g_file_info_has_attribute (info, G_FILE_ATTRIBUTE_ACCESS_CAN_WRITE)) + { + can_write = g_file_info_get_attribute_boolean (info, + G_FILE_ATTRIBUTE_ACCESS_CAN_WRITE); + } + if (g_file_info_has_attribute (info, G_FILE_ATTRIBUTE_ACCESS_CAN_EXECUTE)) + { + can_execute = g_file_info_get_attribute_boolean (info, + G_FILE_ATTRIBUTE_ACCESS_CAN_EXECUTE); + } + if (g_file_info_has_attribute (info, G_FILE_ATTRIBUTE_ACCESS_CAN_DELETE)) + { + can_delete = g_file_info_get_attribute_boolean (info, + G_FILE_ATTRIBUTE_ACCESS_CAN_DELETE); + } + if (g_file_info_has_attribute (info, G_FILE_ATTRIBUTE_ACCESS_CAN_TRASH)) + { + can_trash = g_file_info_get_attribute_boolean (info, + G_FILE_ATTRIBUTE_ACCESS_CAN_TRASH); + } + if (g_file_info_has_attribute (info, G_FILE_ATTRIBUTE_ACCESS_CAN_RENAME)) + { + can_rename = g_file_info_get_attribute_boolean (info, + G_FILE_ATTRIBUTE_ACCESS_CAN_RENAME); + } + if (g_file_info_has_attribute (info, G_FILE_ATTRIBUTE_MOUNTABLE_CAN_MOUNT)) + { + can_mount = g_file_info_get_attribute_boolean (info, + G_FILE_ATTRIBUTE_MOUNTABLE_CAN_MOUNT); + } + if (g_file_info_has_attribute (info, G_FILE_ATTRIBUTE_MOUNTABLE_CAN_UNMOUNT)) + { + can_unmount = g_file_info_get_attribute_boolean (info, + G_FILE_ATTRIBUTE_MOUNTABLE_CAN_UNMOUNT); + } + if (g_file_info_has_attribute (info, G_FILE_ATTRIBUTE_MOUNTABLE_CAN_EJECT)) + { + can_eject = g_file_info_get_attribute_boolean (info, + G_FILE_ATTRIBUTE_MOUNTABLE_CAN_EJECT); + } + if (g_file_info_has_attribute (info, G_FILE_ATTRIBUTE_MOUNTABLE_CAN_START)) + { + can_start = g_file_info_get_attribute_boolean (info, + G_FILE_ATTRIBUTE_MOUNTABLE_CAN_START); + } + if (g_file_info_has_attribute (info, G_FILE_ATTRIBUTE_MOUNTABLE_CAN_START_DEGRADED)) + { + can_start_degraded = g_file_info_get_attribute_boolean (info, + G_FILE_ATTRIBUTE_MOUNTABLE_CAN_START_DEGRADED); + } + if (g_file_info_has_attribute (info, G_FILE_ATTRIBUTE_MOUNTABLE_CAN_STOP)) + { + can_stop = g_file_info_get_attribute_boolean (info, + G_FILE_ATTRIBUTE_MOUNTABLE_CAN_STOP); + } + if (g_file_info_has_attribute (info, G_FILE_ATTRIBUTE_MOUNTABLE_START_STOP_TYPE)) + { + start_stop_type = g_file_info_get_attribute_uint32 (info, + G_FILE_ATTRIBUTE_MOUNTABLE_START_STOP_TYPE); + } + if (g_file_info_has_attribute (info, G_FILE_ATTRIBUTE_MOUNTABLE_CAN_POLL)) + { + can_poll_for_media = g_file_info_get_attribute_boolean (info, + G_FILE_ATTRIBUTE_MOUNTABLE_CAN_POLL); + } + if (g_file_info_has_attribute (info, G_FILE_ATTRIBUTE_MOUNTABLE_IS_MEDIA_CHECK_AUTOMATIC)) + { + is_media_check_automatic = g_file_info_get_attribute_boolean (info, + G_FILE_ATTRIBUTE_MOUNTABLE_IS_MEDIA_CHECK_AUTOMATIC); + } + if (file->details->can_read != can_read || + file->details->can_write != can_write || + file->details->can_execute != can_execute || + file->details->can_delete != can_delete || + file->details->can_trash != can_trash || + file->details->can_rename != can_rename || + file->details->can_mount != can_mount || + file->details->can_unmount != can_unmount || + file->details->can_eject != can_eject || + file->details->can_start != can_start || + file->details->can_start_degraded != can_start_degraded || + file->details->can_stop != can_stop || + file->details->start_stop_type != start_stop_type || + file->details->can_poll_for_media != can_poll_for_media || + file->details->is_media_check_automatic != is_media_check_automatic) + { + changed = TRUE; + } + + file->details->can_read = can_read; + file->details->can_write = can_write; + file->details->can_execute = can_execute; + file->details->can_delete = can_delete; + file->details->can_trash = can_trash; + file->details->can_rename = can_rename; + file->details->can_mount = can_mount; + file->details->can_unmount = can_unmount; + file->details->can_eject = can_eject; + file->details->can_start = can_start; + file->details->can_start_degraded = can_start_degraded; + file->details->can_stop = can_stop; + file->details->start_stop_type = start_stop_type; + file->details->can_poll_for_media = can_poll_for_media; + file->details->is_media_check_automatic = is_media_check_automatic; + + free_owner = FALSE; + owner = g_file_info_get_attribute_string (info, G_FILE_ATTRIBUTE_OWNER_USER); + owner_real = g_file_info_get_attribute_string (info, G_FILE_ATTRIBUTE_OWNER_USER_REAL); + free_group = FALSE; + group = g_file_info_get_attribute_string (info, G_FILE_ATTRIBUTE_OWNER_GROUP); + + uid = -1; + gid = -1; + if (g_file_info_has_attribute (info, G_FILE_ATTRIBUTE_UNIX_UID)) + { + uid = g_file_info_get_attribute_uint32 (info, G_FILE_ATTRIBUTE_UNIX_UID); + if (owner == NULL) + { + free_owner = TRUE; + owner = g_strdup_printf ("%d", uid); + } + } + if (g_file_info_has_attribute (info, G_FILE_ATTRIBUTE_UNIX_GID)) + { + gid = g_file_info_get_attribute_uint32 (info, G_FILE_ATTRIBUTE_UNIX_GID); + if (group == NULL) + { + free_group = TRUE; + group = g_strdup_printf ("%d", gid); + } + } + if (file->details->uid != uid || + file->details->gid != gid) + { + changed = TRUE; + } + file->details->uid = uid; + file->details->gid = gid; + + if (g_strcmp0 (file->details->owner, owner) != 0) + { + changed = TRUE; + g_clear_pointer (&file->details->owner, g_ref_string_release); + file->details->owner = g_ref_string_new_intern (owner); + } + + if (g_strcmp0 (file->details->owner_real, owner_real) != 0) + { + changed = TRUE; + g_clear_pointer (&file->details->owner_real, g_ref_string_release); + file->details->owner_real = g_ref_string_new_intern (owner_real); + } + + if (g_strcmp0 (file->details->group, group) != 0) + { + changed = TRUE; + g_clear_pointer (&file->details->group, g_ref_string_release); + file->details->group = g_ref_string_new_intern (group); + } + + if (free_owner) + { + g_free ((char *) owner); + } + if (free_group) + { + g_free ((char *) group); + } + + size = -1; + if (g_file_info_has_attribute (info, G_FILE_ATTRIBUTE_STANDARD_SIZE)) + { + size = g_file_info_get_size (info); + } + if (file->details->size != size) + { + changed = TRUE; + } + file->details->size = size; + + sort_order = g_file_info_get_sort_order (info); + if (file->details->sort_order != sort_order) + { + changed = TRUE; + } + file->details->sort_order = sort_order; + + atime = g_file_info_get_attribute_uint64 (info, G_FILE_ATTRIBUTE_TIME_ACCESS); + mtime = g_file_info_get_attribute_uint64 (info, G_FILE_ATTRIBUTE_TIME_MODIFIED); + btime = g_file_info_get_attribute_uint64 (info, G_FILE_ATTRIBUTE_TIME_CREATED); + if (file->details->atime != atime || + file->details->mtime != mtime) + { + if (file->details->thumbnail == NULL) + { + file->details->thumbnail_is_up_to_date = FALSE; + } + + changed = TRUE; + } + file->details->atime = atime; + file->details->mtime = mtime; + file->details->btime = btime; + + if (file->details->thumbnail != NULL && + file->details->thumbnail_mtime != 0 && + file->details->thumbnail_mtime != mtime) + { + file->details->thumbnail_is_up_to_date = FALSE; + changed = TRUE; + } + + icon = g_file_info_get_icon (info); + if (!g_icon_equal (icon, file->details->icon)) + { + changed = TRUE; + + if (file->details->icon) + { + g_object_unref (file->details->icon); + } + file->details->icon = g_object_ref (icon); + } + + thumbnail_path = g_file_info_get_attribute_byte_string (info, G_FILE_ATTRIBUTE_THUMBNAIL_PATH); + if (g_strcmp0 (file->details->thumbnail_path, thumbnail_path) != 0) + { + changed = TRUE; + g_free (file->details->thumbnail_path); + file->details->thumbnail_path = g_strdup (thumbnail_path); + } + + thumbnailing_failed = g_file_info_get_attribute_boolean (info, G_FILE_ATTRIBUTE_THUMBNAILING_FAILED); + if (file->details->thumbnailing_failed != thumbnailing_failed) + { + changed = TRUE; + file->details->thumbnailing_failed = thumbnailing_failed; + } + + symlink_name = g_file_info_get_symlink_target (info); + if (g_strcmp0 (file->details->symlink_name, symlink_name) != 0) + { + changed = TRUE; + g_free (file->details->symlink_name); + file->details->symlink_name = g_strdup (symlink_name); + } + + mime_type = g_file_info_get_content_type (info); + if (mime_type == NULL) + { + mime_type = g_file_info_get_attribute_string (info, G_FILE_ATTRIBUTE_STANDARD_FAST_CONTENT_TYPE); + } + if (g_strcmp0 (file->details->mime_type, mime_type) != 0) + { + changed = TRUE; + g_clear_pointer (&file->details->mime_type, g_ref_string_release); + file->details->mime_type = g_ref_string_new_intern (mime_type); + } + + selinux_context = g_file_info_get_attribute_string (info, G_FILE_ATTRIBUTE_SELINUX_CONTEXT); + if (g_strcmp0 (file->details->selinux_context, selinux_context) != 0) + { + changed = TRUE; + g_free (file->details->selinux_context); + file->details->selinux_context = g_strdup (selinux_context); + } + + description = g_file_info_get_attribute_string (info, G_FILE_ATTRIBUTE_STANDARD_DESCRIPTION); + if (g_strcmp0 (file->details->description, description) != 0) + { + changed = TRUE; + g_free (file->details->description); + file->details->description = g_strdup (description); + } + + filesystem_id = g_file_info_get_attribute_string (info, G_FILE_ATTRIBUTE_ID_FILESYSTEM); + if (g_strcmp0 (file->details->filesystem_id, filesystem_id) != 0) + { + changed = TRUE; + g_clear_pointer (&file->details->filesystem_id, g_ref_string_release); + file->details->filesystem_id = g_ref_string_new_intern (filesystem_id); + } + + trash_time = 0; + time_string = g_file_info_get_attribute_string (info, "trash::deletion-date"); + if (time_string != NULL) + { + g_time_val_from_iso8601 (time_string, &g_trash_time); + trash_time = g_trash_time.tv_sec; + } + if (file->details->trash_time != trash_time) + { + changed = TRUE; + file->details->trash_time = trash_time; + } + + recency = g_file_info_get_attribute_int64 (info, G_FILE_ATTRIBUTE_RECENT_MODIFIED); + if (file->details->recency != recency) + { + changed = TRUE; + file->details->recency = recency; + } + + trash_orig_path = g_file_info_get_attribute_byte_string (info, "trash::orig-path"); + if (g_strcmp0 (file->details->trash_orig_path, trash_orig_path) != 0) + { + changed = TRUE; + g_free (file->details->trash_orig_path); + file->details->trash_orig_path = g_strdup (trash_orig_path); + } + + changed |= + nautilus_file_update_metadata_from_info (file, info); + + if (update_name) + { + name = g_file_info_get_name (info); + if (file->details->name == NULL || + strcmp (file->details->name, name) != 0) + { + changed = TRUE; + + node = nautilus_directory_begin_file_name_change + (file->details->directory, file); + + g_clear_pointer (&file->details->name, g_ref_string_release); + if (g_strcmp0 (file->details->display_name, name) == 0) + { + file->details->name = g_ref_string_acquire (file->details->display_name); + } + else + { + file->details->name = g_ref_string_new (name); + } + + if (!file->details->got_custom_display_name && + g_file_info_get_display_name (info) == NULL) + { + /* If the file info's display name is NULL, + * nautilus_file_set_display_name() did + * not unset the display name. + */ + nautilus_file_clear_display_name (file); + } + + nautilus_directory_end_file_name_change + (file->details->directory, file, node); + } + } + + if (changed) + { + add_to_link_hash_table (file); + + update_links_if_target (file); + } + + return changed; +} + +static gboolean +update_info_and_name (NautilusFile *file, + GFileInfo *info) +{ + return update_info_internal (file, info, TRUE); +} + +gboolean +nautilus_file_update_info (NautilusFile *file, + GFileInfo *info) +{ + return update_info_internal (file, info, FALSE); +} + +static gboolean +update_name_internal (NautilusFile *file, + const char *name, + gboolean in_directory) +{ + GList *node; + + g_assert (name != NULL); + + if (file->details->is_gone) + { + return FALSE; + } + + if (name_is (file, name)) + { + return FALSE; + } + + node = NULL; + if (in_directory) + { + node = nautilus_directory_begin_file_name_change + (file->details->directory, file); + } + + g_clear_pointer (&file->details->name, g_ref_string_release); + file->details->name = g_ref_string_new (name); + + if (!file->details->got_custom_display_name) + { + nautilus_file_clear_display_name (file); + } + + if (in_directory) + { + nautilus_directory_end_file_name_change + (file->details->directory, file, node); + } + + return TRUE; +} + +gboolean +nautilus_file_update_name (NautilusFile *file, + const char *name) +{ + gboolean ret; + + ret = update_name_internal (file, name, TRUE); + + if (ret) + { + update_links_if_target (file); + } + + return ret; +} + +gboolean +nautilus_file_update_name_and_directory (NautilusFile *file, + const char *name, + NautilusDirectory *new_directory) +{ + NautilusDirectory *old_directory; + FileMonitors *monitors; + + g_return_val_if_fail (NAUTILUS_IS_FILE (file), FALSE); + g_return_val_if_fail (NAUTILUS_IS_DIRECTORY (file->details->directory), FALSE); + g_return_val_if_fail (!file->details->is_gone, FALSE); + g_return_val_if_fail (!nautilus_file_is_self_owned (file), FALSE); + g_return_val_if_fail (NAUTILUS_IS_DIRECTORY (new_directory), FALSE); + + old_directory = file->details->directory; + if (old_directory == new_directory) + { + if (name) + { + return update_name_internal (file, name, TRUE); + } + else + { + return FALSE; + } + } + + nautilus_file_ref (file); + + /* FIXME bugzilla.gnome.org 42044: Need to let links that + * point to the old name know that the file has been moved. + */ + + remove_from_link_hash_table (file); + + monitors = nautilus_directory_remove_file_monitors (old_directory, file); + nautilus_directory_remove_file (old_directory, file); + + nautilus_file_set_directory (file, new_directory); + + if (name) + { + update_name_internal (file, name, FALSE); + } + + nautilus_directory_add_file (new_directory, file); + nautilus_directory_add_file_monitors (new_directory, file, monitors); + + add_to_link_hash_table (file); + + update_links_if_target (file); + + nautilus_file_unref (file); + + return TRUE; +} + +static Knowledge +get_item_count (NautilusFile *file, + guint *count) +{ + gboolean known, unreadable; + + known = nautilus_file_get_directory_item_count + (file, count, &unreadable); + if (!known) + { + return UNKNOWN; + } + if (unreadable) + { + return UNKNOWABLE; + } + return KNOWN; +} + +static Knowledge +get_size (NautilusFile *file, + goffset *size) +{ + /* If we tried and failed, then treat it like there is no size + * to know. + */ + if (file->details->get_info_failed) + { + return UNKNOWABLE; + } + + /* If the info is NULL that means we haven't even tried yet, + * so it's just unknown, not unknowable. + */ + if (!file->details->got_file_info) + { + return UNKNOWN; + } + + /* If we got info with no size in it, it means there is no + * such thing as a size as far as gnome-vfs is concerned, + * so "unknowable". + */ + if (file->details->size == -1) + { + return UNKNOWABLE; + } + + /* We have a size! */ + *size = file->details->size; + return KNOWN; +} + +static Knowledge +get_time (NautilusFile *file, + time_t *time_out, + NautilusDateType type) +{ + time_t time; + + /* If we tried and failed, then treat it like there is no size + * to know. + */ + if (file->details->get_info_failed) + { + return UNKNOWABLE; + } + + /* If the info is NULL that means we haven't even tried yet, + * so it's just unknown, not unknowable. + */ + if (!file->details->got_file_info) + { + return UNKNOWN; + } + + switch (type) + { + case NAUTILUS_DATE_TYPE_MODIFIED: + { + time = file->details->mtime; + } + break; + + case NAUTILUS_DATE_TYPE_ACCESSED: + { + time = file->details->atime; + } + break; + + case NAUTILUS_DATE_TYPE_CREATED: + { + time = file->details->btime; + } + break; + + case NAUTILUS_DATE_TYPE_TRASHED: + { + time = file->details->trash_time; + } + break; + + case NAUTILUS_DATE_TYPE_RECENCY: + { + time = file->details->recency; + } + break; + + default: + { + g_assert_not_reached (); + } + break; + } + + *time_out = time; + + /* If we got info with no modification time in it, it means + * there is no such thing as a modification time as far as + * gnome-vfs is concerned, so "unknowable". + */ + if (time == 0) + { + return UNKNOWABLE; + } + return KNOWN; +} + +static int +compare_directories_by_count (NautilusFile *file_1, + NautilusFile *file_2) +{ + /* Sort order: + * Directories with unknown # of items + * Directories with "unknowable" # of items + * Directories with 0 items + * Directories with n items + */ + + Knowledge count_known_1, count_known_2; + guint count_1, count_2; + + count_known_1 = get_item_count (file_1, &count_1); + count_known_2 = get_item_count (file_2, &count_2); + + if (count_known_1 > count_known_2) + { + return -1; + } + if (count_known_1 < count_known_2) + { + return +1; + } + + /* count_known_1 and count_known_2 are equal now. Check if count + * details are UNKNOWABLE or UNKNOWN. + */ + if (count_known_1 == UNKNOWABLE || count_known_1 == UNKNOWN) + { + return 0; + } + + if (count_1 < count_2) + { + return -1; + } + if (count_1 > count_2) + { + return +1; + } + + return 0; +} + +static int +compare_files_by_size (NautilusFile *file_1, + NautilusFile *file_2) +{ + /* Sort order: + * Files with unknown size. + * Files with "unknowable" size. + * Files with smaller sizes. + * Files with large sizes. + */ + + Knowledge size_known_1, size_known_2; + goffset size_1 = 0, size_2 = 0; + + size_known_1 = get_size (file_1, &size_1); + size_known_2 = get_size (file_2, &size_2); + + if (size_known_1 > size_known_2) + { + return -1; + } + if (size_known_1 < size_known_2) + { + return +1; + } + + /* size_known_1 and size_known_2 are equal now. Check if size + * details are UNKNOWABLE or UNKNOWN + */ + if (size_known_1 == UNKNOWABLE || size_known_1 == UNKNOWN) + { + return 0; + } + + if (size_1 < size_2) + { + return -1; + } + if (size_1 > size_2) + { + return +1; + } + + return 0; +} + +static int +compare_by_size (NautilusFile *file_1, + NautilusFile *file_2) +{ + /* Sort order: + * Directories with n items + * Directories with 0 items + * Directories with "unknowable" # of items + * Directories with unknown # of items + * Files with large sizes. + * Files with smaller sizes. + * Files with "unknowable" size. + * Files with unknown size. + */ + + gboolean is_directory_1, is_directory_2; + + is_directory_1 = nautilus_file_is_directory (file_1); + is_directory_2 = nautilus_file_is_directory (file_2); + + if (is_directory_1 && !is_directory_2) + { + return -1; + } + if (is_directory_2 && !is_directory_1) + { + return +1; + } + + if (is_directory_1) + { + return compare_directories_by_count (file_1, file_2); + } + else + { + return compare_files_by_size (file_1, file_2); + } +} + +static int +compare_by_display_name (NautilusFile *file_1, + NautilusFile *file_2) +{ + const char *name_1, *name_2; + const char *key_1, *key_2; + gboolean sort_last_1, sort_last_2; + int compare; + + name_1 = nautilus_file_peek_display_name (file_1); + name_2 = nautilus_file_peek_display_name (file_2); + + sort_last_1 = name_1[0] == SORT_LAST_CHAR1 || name_1[0] == SORT_LAST_CHAR2; + sort_last_2 = name_2[0] == SORT_LAST_CHAR1 || name_2[0] == SORT_LAST_CHAR2; + + if (sort_last_1 && !sort_last_2) + { + compare = +1; + } + else if (!sort_last_1 && sort_last_2) + { + compare = -1; + } + else + { + key_1 = nautilus_file_peek_display_name_collation_key (file_1); + key_2 = nautilus_file_peek_display_name_collation_key (file_2); + compare = strcmp (key_1, key_2); + } + + return compare; +} + +static int +compare_by_directory_name (NautilusFile *file_1, + NautilusFile *file_2) +{ + return strcmp (file_1->details->directory_name_collation_key, + file_2->details->directory_name_collation_key); +} + +static GList * +prepend_automatic_keywords (NautilusFile *file, + GList *names) +{ + /* Prepend in reverse order. */ + NautilusFile *parent; + + parent = nautilus_file_get_parent (file); + + /* Trash files are assumed to be read-only, + * so we want to ignore them here. */ + if (!nautilus_file_can_write (file) && + !nautilus_file_is_in_trash (file) && + (parent == NULL || nautilus_file_can_write (parent))) + { + names = g_list_prepend + (names, g_strdup (NAUTILUS_FILE_EMBLEM_NAME_CANT_WRITE)); + } + if (!nautilus_file_can_read (file)) + { + names = g_list_prepend + (names, g_strdup (NAUTILUS_FILE_EMBLEM_NAME_CANT_READ)); + } + if (nautilus_file_is_symbolic_link (file)) + { + names = g_list_prepend + (names, g_strdup (NAUTILUS_FILE_EMBLEM_NAME_SYMBOLIC_LINK)); + } + + if (parent) + { + nautilus_file_unref (parent); + } + + + return names; +} + +static int +compare_by_type (NautilusFile *file_1, + NautilusFile *file_2) +{ + gboolean is_directory_1; + gboolean is_directory_2; + char *type_string_1; + char *type_string_2; + int result; + + /* Directories go first. Then, if mime types are identical, + * don't bother getting strings (for speed). This assumes + * that the string is dependent entirely on the mime type, + * which is true now but might not be later. + */ + is_directory_1 = nautilus_file_is_directory (file_1); + is_directory_2 = nautilus_file_is_directory (file_2); + + if (is_directory_1 && is_directory_2) + { + return 0; + } + + if (is_directory_1) + { + return -1; + } + + if (is_directory_2) + { + return +1; + } + + if (file_1->details->mime_type != NULL && + file_2->details->mime_type != NULL && + strcmp (file_1->details->mime_type, + file_2->details->mime_type) == 0) + { + return 0; + } + + type_string_1 = nautilus_file_get_type_as_string_no_extra_text (file_1); + type_string_2 = nautilus_file_get_type_as_string_no_extra_text (file_2); + + if (type_string_1 == NULL || type_string_2 == NULL) + { + if (type_string_1 != NULL) + { + return -1; + } + + if (type_string_2 != NULL) + { + return 1; + } + + return 0; + } + + result = g_utf8_collate (type_string_1, type_string_2); + if (result == 0) + { + /* Among files of the same (generic) type, sort them by mime type. */ + result = g_utf8_collate (file_1->details->mime_type, file_2->details->mime_type); + } + + g_free (type_string_1); + g_free (type_string_2); + + return result; +} + +static int +compare_by_starred (NautilusFile *file_1, + NautilusFile *file_2) +{ + NautilusTagManager *tag_manager = nautilus_tag_manager_get (); + g_autofree gchar *uri_1 = NULL; + g_autofree gchar *uri_2 = NULL; + gboolean file_1_is_starred; + gboolean file_2_is_starred; + + uri_1 = nautilus_file_get_uri (file_1); + uri_2 = nautilus_file_get_uri (file_2); + + file_1_is_starred = nautilus_tag_manager_file_is_starred (tag_manager, + uri_1); + file_2_is_starred = nautilus_tag_manager_file_is_starred (tag_manager, + uri_2); + if (!!file_1_is_starred == !!file_2_is_starred) + { + return 0; + } + else if (file_1_is_starred && !file_2_is_starred) + { + return -1; + } + else + { + return 1; + } +} + +static Knowledge +get_search_relevance (NautilusFile *file, + gdouble *relevance_out) +{ + /* we're only called in search directories, and in that + * case, the relevance is always known (or zero). + */ + *relevance_out = file->details->search_relevance; + return KNOWN; +} + +static int +compare_by_search_relevance (NautilusFile *file_1, + NautilusFile *file_2) +{ + gdouble r_1, r_2; + + get_search_relevance (file_1, &r_1); + get_search_relevance (file_2, &r_2); + + if (r_1 < r_2) + { + return -1; + } + if (r_1 > r_2) + { + return +1; + } + + return 0; +} + +static int +compare_by_time (NautilusFile *file_1, + NautilusFile *file_2, + NautilusDateType type) +{ + /* Sort order: + * Files with unknown times. + * Files with "unknowable" times. + * Files with older times. + * Files with newer times. + */ + + Knowledge time_known_1, time_known_2; + time_t time_1, time_2; + + time_1 = 0; + time_2 = 0; + + time_known_1 = get_time (file_1, &time_1, type); + time_known_2 = get_time (file_2, &time_2, type); + + if (time_known_1 > time_known_2) + { + return -1; + } + if (time_known_1 < time_known_2) + { + return +1; + } + + /* Now time_known_1 is equal to time_known_2. Check whether + * we failed to get modification times for files + */ + if (time_known_1 == UNKNOWABLE || time_known_1 == UNKNOWN) + { + return 0; + } + + if (time_1 < time_2) + { + return -1; + } + if (time_1 > time_2) + { + return +1; + } + + return 0; +} + +static int +compare_by_full_path (NautilusFile *file_1, + NautilusFile *file_2) +{ + int compare; + + compare = compare_by_directory_name (file_1, file_2); + if (compare != 0) + { + return compare; + } + return compare_by_display_name (file_1, file_2); +} + +static int +nautilus_file_compare_for_sort_internal (NautilusFile *file_1, + NautilusFile *file_2, + gboolean directories_first, + gboolean reversed) +{ + gboolean is_directory_1, is_directory_2; + + if (directories_first) + { + is_directory_1 = nautilus_file_is_directory (file_1); + is_directory_2 = nautilus_file_is_directory (file_2); + + if (is_directory_1 && !is_directory_2) + { + return -1; + } + + if (is_directory_2 && !is_directory_1) + { + return +1; + } + } + + if (file_1->details->sort_order < file_2->details->sort_order) + { + return reversed ? 1 : -1; + } + else if (file_1->details->sort_order > file_2->details->sort_order) + { + return reversed ? -1 : 1; + } + + return 0; +} + +/** + * nautilus_file_compare_for_sort: + * @file_1: A file object + * @file_2: Another file object + * @sort_type: Sort criterion + * @directories_first: Put all directories before any non-directories + * @reversed: Reverse the order of the items, except that + * the directories_first flag is still respected. + * + * Return value: int < 0 if @file_1 should come before file_2 in a + * sorted list; int > 0 if @file_2 should come before file_1 in a + * sorted list; 0 if @file_1 and @file_2 are equal for this sort criterion. Note + * that each named sort type may actually break ties several ways, with the name + * of the sort criterion being the primary but not only differentiator. + **/ +int +nautilus_file_compare_for_sort (NautilusFile *file_1, + NautilusFile *file_2, + NautilusFileSortType sort_type, + gboolean directories_first, + gboolean reversed) +{ + int result; + + if (file_1 == file_2) + { + return 0; + } + + result = nautilus_file_compare_for_sort_internal (file_1, file_2, directories_first, reversed); + + if (result == 0) + { + switch (sort_type) + { + case NAUTILUS_FILE_SORT_BY_DISPLAY_NAME: + { + result = compare_by_display_name (file_1, file_2); + if (result == 0) + { + result = compare_by_directory_name (file_1, file_2); + } + } + break; + + case NAUTILUS_FILE_SORT_BY_SIZE: + { + /* Compare directory sizes ourselves, then if necessary + * use GnomeVFS to compare file sizes. + */ + result = compare_by_size (file_1, file_2); + if (result == 0) + { + result = compare_by_full_path (file_1, file_2); + } + } + break; + + case NAUTILUS_FILE_SORT_BY_TYPE: + { + /* GnomeVFS doesn't know about our special text for certain + * mime types, so we handle the mime-type sorting ourselves. + */ + result = compare_by_type (file_1, file_2); + if (result == 0) + { + result = compare_by_full_path (file_1, file_2); + } + } + break; + + case NAUTILUS_FILE_SORT_BY_STARRED: + { + result = compare_by_starred (file_1, file_2); + if (result == 0) + { + result = compare_by_full_path (file_1, file_2); + } + } + break; + + case NAUTILUS_FILE_SORT_BY_MTIME: + { + result = compare_by_time (file_1, file_2, NAUTILUS_DATE_TYPE_MODIFIED); + if (result == 0) + { + result = compare_by_full_path (file_1, file_2); + } + } + break; + + case NAUTILUS_FILE_SORT_BY_ATIME: + { + result = compare_by_time (file_1, file_2, NAUTILUS_DATE_TYPE_ACCESSED); + if (result == 0) + { + result = compare_by_full_path (file_1, file_2); + } + } + break; + + case NAUTILUS_FILE_SORT_BY_BTIME: + { + result = compare_by_time (file_1, file_2, NAUTILUS_DATE_TYPE_CREATED); + if (result == 0) + { + result = compare_by_full_path (file_1, file_2); + } + } + break; + + case NAUTILUS_FILE_SORT_BY_TRASHED_TIME: + { + result = compare_by_time (file_1, file_2, NAUTILUS_DATE_TYPE_TRASHED); + if (result == 0) + { + result = compare_by_full_path (file_1, file_2); + } + } + break; + + case NAUTILUS_FILE_SORT_BY_SEARCH_RELEVANCE: + { + result = compare_by_search_relevance (file_1, file_2); + if (result == 0) + { + result = compare_by_full_path (file_1, file_2); + + /* ensure alphabetical order for files of the same relevance */ + reversed = FALSE; + } + } + break; + + case NAUTILUS_FILE_SORT_BY_RECENCY: + { + result = compare_by_time (file_1, file_2, NAUTILUS_DATE_TYPE_RECENCY); + if (result == 0) + { + result = compare_by_full_path (file_1, file_2); + } + } + break; + + default: + { + g_return_val_if_reached (0); + } + } + + if (reversed) + { + result = -result; + } + } + + return result; +} + +int +nautilus_file_compare_for_sort_by_attribute_q (NautilusFile *file_1, + NautilusFile *file_2, + GQuark attribute, + gboolean directories_first, + gboolean reversed) +{ + int result; + + if (file_1 == file_2) + { + return 0; + } + + /* Convert certain attributes into NautilusFileSortTypes and use + * nautilus_file_compare_for_sort() + */ + if (attribute == 0 || attribute == attribute_name_q) + { + return nautilus_file_compare_for_sort (file_1, file_2, + NAUTILUS_FILE_SORT_BY_DISPLAY_NAME, + directories_first, + reversed); + } + else if (attribute == attribute_size_q) + { + return nautilus_file_compare_for_sort (file_1, file_2, + NAUTILUS_FILE_SORT_BY_SIZE, + directories_first, + reversed); + } + else if (attribute == attribute_type_q) + { + return nautilus_file_compare_for_sort (file_1, file_2, + NAUTILUS_FILE_SORT_BY_TYPE, + directories_first, + reversed); + } + else if (attribute == attribute_starred_q) + { + return nautilus_file_compare_for_sort (file_1, file_2, + NAUTILUS_FILE_SORT_BY_STARRED, + directories_first, + reversed); + } + else if (attribute == attribute_modification_date_q || attribute == attribute_date_modified_q || attribute == attribute_date_modified_with_time_q || attribute == attribute_date_modified_full_q) + { + return nautilus_file_compare_for_sort (file_1, file_2, + NAUTILUS_FILE_SORT_BY_MTIME, + directories_first, + reversed); + } + else if (attribute == attribute_accessed_date_q || attribute == attribute_date_accessed_q || attribute == attribute_date_accessed_full_q) + { + return nautilus_file_compare_for_sort (file_1, file_2, + NAUTILUS_FILE_SORT_BY_ATIME, + directories_first, + reversed); + } + else if (attribute == attribute_date_created_q || attribute == attribute_date_created_full_q) + { + return nautilus_file_compare_for_sort (file_1, file_2, + NAUTILUS_FILE_SORT_BY_BTIME, + directories_first, + reversed); + } + else if (attribute == attribute_trashed_on_q || attribute == attribute_trashed_on_full_q) + { + return nautilus_file_compare_for_sort (file_1, file_2, + NAUTILUS_FILE_SORT_BY_TRASHED_TIME, + directories_first, + reversed); + } + else if (attribute == attribute_search_relevance_q) + { + return nautilus_file_compare_for_sort (file_1, file_2, + NAUTILUS_FILE_SORT_BY_SEARCH_RELEVANCE, + directories_first, + reversed); + } + else if (attribute == attribute_recency_q) + { + return nautilus_file_compare_for_sort (file_1, file_2, + NAUTILUS_FILE_SORT_BY_RECENCY, + directories_first, + reversed); + } + + /* it is a normal attribute, compare by strings */ + + result = nautilus_file_compare_for_sort_internal (file_1, file_2, directories_first, reversed); + + if (result == 0) + { + char *value_1; + char *value_2; + + value_1 = nautilus_file_get_string_attribute_q (file_1, + attribute); + value_2 = nautilus_file_get_string_attribute_q (file_2, + attribute); + + if (value_1 != NULL && value_2 != NULL) + { + result = strcmp (value_1, value_2); + } + + g_free (value_1); + g_free (value_2); + + if (reversed) + { + result = -result; + } + } + + return result; +} + +int +nautilus_file_compare_for_sort_by_attribute (NautilusFile *file_1, + NautilusFile *file_2, + const char *attribute, + gboolean directories_first, + gboolean reversed) +{ + return nautilus_file_compare_for_sort_by_attribute_q (file_1, file_2, + g_quark_from_string (attribute), + directories_first, + reversed); +} + + +/** + * nautilus_file_compare_name: + * @file: A file object + * @string: A string we are comparing it with + * + * Return value: result of a comparison of the file name and the given string. + **/ +int +nautilus_file_compare_display_name (NautilusFile *file, + const char *string) +{ + const char *name; + int result; + + g_return_val_if_fail (string != NULL, -1); + + name = nautilus_file_peek_display_name (file); + result = g_strcmp0 (name, string); + return result; +} + + +gboolean +nautilus_file_is_hidden_file (NautilusFile *file) +{ + return file->details->is_hidden; +} + +/** + * nautilus_file_should_show: + * @file: the file to check + * @show_hidden: whether we want to show hidden files or not + * + * Determines if a #NautilusFile should be shown. Note that when browsing + * a trash directory, this function will always return %TRUE. + * + * Returns: %TRUE if the file should be shown, %FALSE if it shouldn't. + */ +gboolean +nautilus_file_should_show (NautilusFile *file, + gboolean show_hidden) +{ + /* Never hide any files in trash. */ + if (nautilus_file_is_in_trash (file)) + { + return TRUE; + } + + if (!show_hidden && nautilus_file_is_hidden_file (file)) + { + return FALSE; + } + + return TRUE; +} + +gboolean +nautilus_file_is_home (NautilusFile *file) +{ + g_autoptr (GFile) location = NULL; + + g_return_val_if_fail (NAUTILUS_IS_FILE (file), FALSE); + + location = nautilus_directory_get_location (file->details->directory); + if (location == NULL) + { + return FALSE; + } + + return nautilus_is_home_directory_file (location, file->details->name); +} + +gboolean +nautilus_file_is_in_search (NautilusFile *file) +{ + char *uri; + gboolean ret; + + uri = nautilus_file_get_uri (file); + ret = eel_uri_is_search (uri); + g_free (uri); + + return ret; +} + +static gboolean +filter_hidden_partition_callback (NautilusFile *file, + gpointer callback_data) +{ + FilterOptions options; + + options = GPOINTER_TO_INT (callback_data); + + return nautilus_file_should_show (file, + options & SHOW_HIDDEN); +} + +GList * +nautilus_file_list_filter_hidden (GList *files, + gboolean show_hidden) +{ + GList *filtered_files; + GList *removed_files; + + /* FIXME bugzilla.gnome.org 40653: + * Eventually this should become a generic filtering thingy. + */ + + filtered_files = nautilus_file_list_filter (files, + &removed_files, + filter_hidden_partition_callback, + GINT_TO_POINTER ((show_hidden ? SHOW_HIDDEN : 0))); + nautilus_file_list_free (removed_files); + + return filtered_files; +} + +/* This functions filters a file list when its items match a certain condition + * in the filter function. This function preserves the ordering. + */ +GList * +nautilus_file_list_filter (GList *files, + GList **failed, + NautilusFileFilterFunc filter_function, + gpointer user_data) +{ + GList *filtered = NULL; + GList *l; + GList *reversed; + + *failed = NULL; + /* Avoid using g_list_append since it's O(n) */ + reversed = g_list_copy (files); + reversed = g_list_reverse (reversed); + for (l = reversed; l != NULL; l = l->next) + { + if (filter_function (l->data, user_data)) + { + filtered = g_list_prepend (filtered, nautilus_file_ref (l->data)); + } + else + { + *failed = g_list_prepend (*failed, nautilus_file_ref (l->data)); + } + } + + g_list_free (reversed); + + return filtered; +} + +gboolean +nautilus_file_list_are_all_folders (const GList *files) +{ + const GList *l; + + for (l = files; l != NULL; l = l->next) + { + if (!nautilus_file_is_directory (NAUTILUS_FILE (l->data))) + { + return FALSE; + } + } + + return TRUE; +} + +char * +nautilus_file_get_metadata (NautilusFile *file, + const char *key, + const char *default_metadata) +{ + guint id; + char *value; + + g_return_val_if_fail (key != NULL, g_strdup (default_metadata)); + g_return_val_if_fail (key[0] != '\0', g_strdup (default_metadata)); + + if (file == NULL || + file->details->metadata == NULL) + { + return g_strdup (default_metadata); + } + + g_return_val_if_fail (NAUTILUS_IS_FILE (file), g_strdup (default_metadata)); + + id = nautilus_metadata_get_id (key); + value = g_hash_table_lookup (file->details->metadata, GUINT_TO_POINTER (id)); + + if (value) + { + return g_strdup (value); + } + return g_strdup (default_metadata); +} + +/** + * nautilus_file_get_metadata_list: + * @file: A #NautilusFile to get metadata from. + * @key: A string representation of the metadata key (use macros when possible). + * + * Get the value of a metadata attribute which holds a list of strings. + * + * Returns: (transfer full): A zero-terminated array of newly allocated strings. + */ +gchar ** +nautilus_file_get_metadata_list (NautilusFile *file, + const char *key) +{ + guint id; + char **value; + + g_return_val_if_fail (key != NULL, NULL); + g_return_val_if_fail (key[0] != '\0', NULL); + + if (file == NULL || + file->details->metadata == NULL) + { + return NULL; + } + + g_return_val_if_fail (NAUTILUS_IS_FILE (file), NULL); + + id = nautilus_metadata_get_id (key); + id |= METADATA_ID_IS_LIST_MASK; + + value = g_hash_table_lookup (file->details->metadata, GUINT_TO_POINTER (id)); + + return g_strdupv (value); +} + +void +nautilus_file_set_metadata (NautilusFile *file, + const char *key, + const char *default_metadata, + const char *metadata) +{ + const char *val; + + g_return_if_fail (NAUTILUS_IS_FILE (file)); + g_return_if_fail (key != NULL); + g_return_if_fail (key[0] != '\0'); + + val = metadata; + if (val == NULL) + { + val = default_metadata; + } + + NAUTILUS_FILE_CLASS (G_OBJECT_GET_CLASS (file))->set_metadata (file, key, val); +} + +/** + * nautilus_file_set_metadata_list: + * @file: A #NautilusFile to set metadata into. + * @key: A string representation of the metadata key (use macros when possible). + * @list: (transfer none): A zero-terminated array of newly allocated strings. + * + * Set the value of a metadata attribute which takes a list of strings. + */ +void +nautilus_file_set_metadata_list (NautilusFile *file, + const char *key, + gchar **list) +{ + g_return_if_fail (NAUTILUS_IS_FILE (file)); + g_return_if_fail (key != NULL); + g_return_if_fail (key[0] != '\0'); + + NAUTILUS_FILE_CLASS (G_OBJECT_GET_CLASS (file))->set_metadata_as_list (file, key, list); +} + +gboolean +nautilus_file_get_boolean_metadata (NautilusFile *file, + const char *key, + gboolean default_metadata) +{ + char *result_as_string; + gboolean result; + + g_return_val_if_fail (key != NULL, default_metadata); + g_return_val_if_fail (key[0] != '\0', default_metadata); + + if (file == NULL) + { + return default_metadata; + } + + g_return_val_if_fail (NAUTILUS_IS_FILE (file), default_metadata); + + result_as_string = nautilus_file_get_metadata + (file, key, default_metadata ? "true" : "false"); + g_assert (result_as_string != NULL); + + if (g_ascii_strcasecmp (result_as_string, "true") == 0) + { + result = TRUE; + } + else if (g_ascii_strcasecmp (result_as_string, "false") == 0) + { + result = FALSE; + } + else + { + g_error ("boolean metadata with value other than true or false"); + result = default_metadata; + } + + g_free (result_as_string); + return result; +} + +int +nautilus_file_get_integer_metadata (NautilusFile *file, + const char *key, + int default_metadata) +{ + char *result_as_string; + char default_as_string[32]; + int result; + char c; + + g_return_val_if_fail (key != NULL, default_metadata); + g_return_val_if_fail (key[0] != '\0', default_metadata); + + if (file == NULL) + { + return default_metadata; + } + g_return_val_if_fail (NAUTILUS_IS_FILE (file), default_metadata); + + g_snprintf (default_as_string, sizeof (default_as_string), "%d", default_metadata); + result_as_string = nautilus_file_get_metadata + (file, key, default_as_string); + + /* Normally we can't get a a NULL, but we check for it here to + * handle the oddball case of a non-existent directory. + */ + if (result_as_string == NULL) + { + result = default_metadata; + } + else + { + if (sscanf (result_as_string, " %d %c", &result, &c) != 1) + { + result = default_metadata; + } + g_free (result_as_string); + } + + return result; +} + +static gboolean +get_time_from_time_string (const char *time_string, + time_t *time) +{ + long scanned_time; + char c; + + g_assert (time != NULL); + + /* Only accept string if it has one integer with nothing + * afterwards. + */ + if (time_string == NULL || + sscanf (time_string, "%ld%c", &scanned_time, &c) != 1) + { + return FALSE; + } + *time = (time_t) scanned_time; + return TRUE; +} + +time_t +nautilus_file_get_time_metadata (NautilusFile *file, + const char *key) +{ + time_t time; + char *time_string; + + time_string = nautilus_file_get_metadata (file, key, NULL); + if (!get_time_from_time_string (time_string, &time)) + { + time = UNDEFINED_TIME; + } + g_free (time_string); + + return time; +} + +void +nautilus_file_set_time_metadata (NautilusFile *file, + const char *key, + time_t time) +{ + char time_str[21]; + char *metadata; + + if (time != UNDEFINED_TIME) + { + /* 2^64 turns out to be 20 characters */ + g_snprintf (time_str, 20, "%ld", (long int) time); + time_str[20] = '\0'; + metadata = time_str; + } + else + { + metadata = NULL; + } + + nautilus_file_set_metadata (file, key, NULL, metadata); +} + + +void +nautilus_file_set_boolean_metadata (NautilusFile *file, + const char *key, + gboolean default_metadata, + gboolean metadata) +{ + g_return_if_fail (NAUTILUS_IS_FILE (file)); + g_return_if_fail (key != NULL); + g_return_if_fail (key[0] != '\0'); + + nautilus_file_set_metadata (file, key, + default_metadata ? "true" : "false", + metadata ? "true" : "false"); +} + +void +nautilus_file_set_integer_metadata (NautilusFile *file, + const char *key, + int default_metadata, + int metadata) +{ + char value_as_string[32]; + char default_as_string[32]; + + g_return_if_fail (NAUTILUS_IS_FILE (file)); + g_return_if_fail (key != NULL); + g_return_if_fail (key[0] != '\0'); + + g_snprintf (value_as_string, sizeof (value_as_string), "%d", metadata); + g_snprintf (default_as_string, sizeof (default_as_string), "%d", default_metadata); + + nautilus_file_set_metadata (file, key, + default_as_string, value_as_string); +} + +static const char * +nautilus_file_peek_display_name_collation_key (NautilusFile *file) +{ + const char *res; + + res = file->details->display_name_collation_key; + if (res == NULL) + { + res = ""; + } + + return res; +} + +static const char * +nautilus_file_peek_display_name (NautilusFile *file) +{ + const char *name; + char *escaped_name; + + /* FIXME: for some reason we can get a NautilusFile instance which is + * no longer valid or could be freed somewhere else in the same time. + * There's race condition somewhere. See bug 602500. + */ + if (file == NULL || nautilus_file_is_gone (file)) + { + return ""; + } + + /* Default to display name based on filename if its not set yet */ + + if (file->details->display_name == NULL) + { + name = file->details->name; + if (g_utf8_validate (name, -1, NULL)) + { + nautilus_file_set_display_name (file, + name, + NULL, + FALSE); + } + else + { + escaped_name = g_uri_escape_string (name, G_URI_RESERVED_CHARS_ALLOWED_IN_PATH, TRUE); + nautilus_file_set_display_name (file, + escaped_name, + NULL, + FALSE); + g_free (escaped_name); + } + } + + return file->details->display_name ? + file->details->display_name : ""; +} + +char * +nautilus_file_get_display_name (NautilusFile *file) +{ + if (nautilus_file_is_other_locations (file)) + { + return g_strdup (_("Other Locations")); + } + if (nautilus_file_is_starred_location (file)) + { + return g_strdup (_("Starred")); + } + + return g_strdup (nautilus_file_peek_display_name (file)); +} + +char * +nautilus_file_get_edit_name (NautilusFile *file) +{ + const char *res; + + res = file->details->edit_name; + if (res == NULL) + { + res = ""; + } + + return g_strdup (res); +} + +char * +nautilus_file_get_name (NautilusFile *file) +{ + g_return_val_if_fail (NAUTILUS_IS_FILE (file), NULL); + + return nautilus_file_info_get_name (NAUTILUS_FILE_INFO (file)); +} + +/** + * nautilus_file_get_description: + * @file: a #NautilusFile. + * + * Gets the standard::description key from @file, if + * it has been cached. + * + * Returns: a string containing the value of the standard::description + * key, or %NULL. + */ +char * +nautilus_file_get_description (NautilusFile *file) +{ + return g_strdup (file->details->description); +} + +void +nautilus_file_monitor_add (NautilusFile *file, + gconstpointer client, + NautilusFileAttributes attributes) +{ + g_return_if_fail (NAUTILUS_IS_FILE (file)); + g_return_if_fail (client != NULL); + + NAUTILUS_FILE_CLASS (G_OBJECT_GET_CLASS (file))->monitor_add (file, client, attributes); +} + +void +nautilus_file_monitor_remove (NautilusFile *file, + gconstpointer client) +{ + g_return_if_fail (NAUTILUS_IS_FILE (file)); + g_return_if_fail (client != NULL); + + NAUTILUS_FILE_CLASS (G_OBJECT_GET_CLASS (file))->monitor_remove (file, client); +} + +gboolean +nautilus_file_has_activation_uri (NautilusFile *file) +{ + return file->details->activation_uri != NULL; +} + + +/* Return the uri associated with the passed-in file, which may not be + * the actual uri if the file is an desktop file or a nautilus + * xml link file. + */ +char * +nautilus_file_get_activation_uri (NautilusFile *file) +{ + g_return_val_if_fail (NAUTILUS_IS_FILE (file), NULL); + + return nautilus_file_info_get_activation_uri (NAUTILUS_FILE_INFO (file)); +} + +GFile * +nautilus_file_get_activation_location (NautilusFile *file) +{ + g_return_val_if_fail (NAUTILUS_IS_FILE (file), NULL); + + if (file->details->activation_uri != NULL) + { + return g_file_new_for_uri (file->details->activation_uri); + } + + return nautilus_file_get_location (file); +} + +static gboolean +is_uri_relative (const char *uri) +{ + char *scheme; + gboolean ret; + + scheme = g_uri_parse_scheme (uri); + ret = (scheme == NULL); + g_free (scheme); + return ret; +} + +static char * +get_custom_icon_metadata_uri (NautilusFile *file) +{ + char *custom_icon_uri; + char *uri; + char *dir_uri; + + uri = nautilus_file_get_metadata (file, NAUTILUS_METADATA_KEY_CUSTOM_ICON, NULL); + if (uri != NULL && + nautilus_file_is_directory (file) && + is_uri_relative (uri)) + { + dir_uri = nautilus_file_get_uri (file); + custom_icon_uri = g_build_filename (dir_uri, uri, NULL); + g_free (dir_uri); + g_free (uri); + } + else + { + custom_icon_uri = uri; + } + return custom_icon_uri; +} + +static char * +get_custom_icon_metadata_name (NautilusFile *file) +{ + char *icon_name; + + icon_name = nautilus_file_get_metadata (file, + NAUTILUS_METADATA_KEY_CUSTOM_ICON_NAME, NULL); + + return icon_name; +} + +static GIcon * +get_mount_icon (NautilusFile *file) +{ + GMount *mount; + GIcon *mount_icon; + + mount = nautilus_file_get_mount (file); + mount_icon = NULL; + + if (mount != NULL) + { + mount_icon = g_mount_get_icon (mount); + g_object_unref (mount); + } + else + { + g_autoptr (GFile) location = nautilus_file_get_location (file); + + /* Root directory doesn't have a GMount, but for UI purposes we want + * it to be treated the same way. */ + if (nautilus_is_root_directory (location)) + { + mount_icon = g_themed_icon_new_with_default_fallbacks ("drive-harddisk"); + } + } + + return mount_icon; +} + +static GIcon * +get_custom_icon (NautilusFile *file) +{ + char *custom_icon_uri, *custom_icon_name; + GFile *icon_file; + GIcon *icon; + + if (file == NULL) + { + return NULL; + } + + icon = NULL; + + /* Metadata takes precedence; first we look at the custom + * icon URI, then at the custom icon name. + */ + custom_icon_uri = get_custom_icon_metadata_uri (file); + + if (custom_icon_uri) + { + icon_file = g_file_new_for_uri (custom_icon_uri); + icon = g_file_icon_new (icon_file); + g_object_unref (icon_file); + g_free (custom_icon_uri); + } + + if (icon == NULL) + { + custom_icon_name = get_custom_icon_metadata_name (file); + + if (custom_icon_name != NULL) + { + icon = g_themed_icon_new_with_default_fallbacks (custom_icon_name); + g_free (custom_icon_name); + } + } + + return icon; +} + +static GIcon * +get_default_file_icon (void) +{ + static GIcon *fallback_icon = NULL; + if (fallback_icon == NULL) + { + fallback_icon = g_themed_icon_new ("text-x-generic"); + } + + return fallback_icon; +} + +GFilesystemPreviewType +nautilus_file_get_filesystem_use_preview (NautilusFile *file) +{ + GFilesystemPreviewType use_preview; + NautilusFile *parent; + + parent = nautilus_file_get_parent (file); + if (parent != NULL) + { + use_preview = parent->details->filesystem_use_preview; + g_object_unref (parent); + } + else + { + use_preview = 0; + } + + return use_preview; +} + +char * +nautilus_file_get_filesystem_type (NautilusFile *file) +{ + char *filesystem_type = NULL; + + g_assert (NAUTILUS_IS_FILE (file)); + + if (nautilus_file_is_directory (file)) + { + filesystem_type = g_strdup (file->details->filesystem_type); + } + else + { + g_autoptr (NautilusFile) parent = NULL; + + parent = nautilus_file_get_parent (file); + if (parent != NULL) + { + filesystem_type = g_strdup (parent->details->filesystem_type); + } + } + + return filesystem_type; +} + +gboolean +nautilus_file_get_filesystem_remote (NautilusFile *file) +{ + g_assert (NAUTILUS_IS_FILE (file)); + + if (nautilus_file_is_directory (file) && file->details->filesystem_info_is_up_to_date) + { + return file->details->filesystem_remote; + } + else + { + g_autoptr (NautilusFile) parent = NULL; + + parent = nautilus_file_get_parent (file); + if (parent != NULL) + { + return parent->details->filesystem_remote; + } + } + + return FALSE; +} + +static gboolean +get_speed_tradeoff_preference_for_file (NautilusFile *file, + NautilusSpeedTradeoffValue value) +{ + GFilesystemPreviewType use_preview; + + g_return_val_if_fail (NAUTILUS_IS_FILE (file), FALSE); + + use_preview = nautilus_file_get_filesystem_use_preview (file); + + if (value == NAUTILUS_SPEED_TRADEOFF_ALWAYS) + { + if (use_preview == G_FILESYSTEM_PREVIEW_TYPE_NEVER) + { + return FALSE; + } + else + { + return TRUE; + } + } + else if (value == NAUTILUS_SPEED_TRADEOFF_NEVER) + { + return FALSE; + } + else if (value == NAUTILUS_SPEED_TRADEOFF_LOCAL_ONLY) + { + if (use_preview == G_FILESYSTEM_PREVIEW_TYPE_NEVER) + { + /* file system says to never preview anything */ + return FALSE; + } + else if (use_preview == G_FILESYSTEM_PREVIEW_TYPE_IF_LOCAL) + { + /* file system says we should treat file as if it's local */ + return TRUE; + } + else + { + /* only local files */ + return !nautilus_file_is_remote (file); + } + } + + return FALSE; +} + +gboolean +nautilus_file_should_show_thumbnail (NautilusFile *file) +{ + const char *mime_type; + + mime_type = file->details->mime_type; + if (mime_type == NULL) + { + mime_type = "application/octet-stream"; + } + + /* If the thumbnail has already been created, don't care about the size + * of the original file. + */ + if (nautilus_thumbnail_is_mimetype_limited_by_size (mime_type) && + file->details->thumbnail_path == NULL && + nautilus_file_get_size (file) > cached_thumbnail_limit) + { + return FALSE; + } + + return get_speed_tradeoff_preference_for_file (file, show_file_thumbs); +} + +static gboolean +nautilus_is_video_file (NautilusFile *file) +{ + const char *mime_type; + guint i; + + mime_type = file->details->mime_type; + if (mime_type == NULL) + { + return FALSE; + } + + for (i = 0; video_mime_types[i] != NULL; i++) + { + if (g_content_type_equals (video_mime_types[i], mime_type)) + { + return TRUE; + } + } + + return FALSE; +} + +static GList * +sort_keyword_list_and_remove_duplicates (GList *keywords) +{ + GList *p; + GList *duplicate_link; + + if (keywords != NULL) + { + keywords = g_list_sort (keywords, (GCompareFunc) g_utf8_collate); + + p = keywords; + while (p->next != NULL) + { + if (strcmp ((const char *) p->data, (const char *) p->next->data) == 0) + { + duplicate_link = p->next; + keywords = g_list_remove_link (keywords, duplicate_link); + g_list_free_full (duplicate_link, g_free); + } + else + { + p = p->next; + } + } + } + + return keywords; +} + +static void +clean_up_metadata_keywords (NautilusFile *file, + GList **metadata_keywords) +{ + NautilusFile *parent_file; + GList *l, *res = NULL; + char *exclude[4]; + char *keyword; + gboolean found; + gint i; + + i = 0; + exclude[i++] = NAUTILUS_FILE_EMBLEM_NAME_TRASH; + exclude[i++] = NAUTILUS_FILE_EMBLEM_NAME_NOTE; + + parent_file = nautilus_file_get_parent (file); + if (parent_file) + { + if (!nautilus_file_can_write (parent_file)) + { + exclude[i++] = NAUTILUS_FILE_EMBLEM_NAME_CANT_WRITE; + } + nautilus_file_unref (parent_file); + } + exclude[i++] = NULL; + + for (l = *metadata_keywords; l != NULL; l = l->next) + { + keyword = l->data; + found = FALSE; + + for (i = 0; exclude[i] != NULL; i++) + { + if (strcmp (exclude[i], keyword) == 0) + { + found = TRUE; + break; + } + } + + if (!found) + { + res = g_list_prepend (res, keyword); + } + } + + g_list_free (*metadata_keywords); + *metadata_keywords = res; +} + +/** + * nautilus_file_get_keywords + * + * Return this file's keywords. + * @file: NautilusFile representing the file in question. + * + * Returns: A list of keywords. + * + **/ +static GList * +nautilus_file_get_keywords (NautilusFile *file) +{ + GList *keywords; + gchar **metadata_strv; + GList *metadata_keywords = NULL; + + if (file == NULL) + { + return NULL; + } + + g_return_val_if_fail (NAUTILUS_IS_FILE (file), NULL); + + keywords = g_list_copy_deep (file->details->extension_emblems, (GCopyFunc) g_strdup, NULL); + keywords = g_list_concat (keywords, g_list_copy_deep (file->details->pending_extension_emblems, (GCopyFunc) g_strdup, NULL)); + + metadata_strv = nautilus_file_get_metadata_list (file, NAUTILUS_METADATA_KEY_EMBLEMS); + /* Convert array to list */ + for (gint i = 0; metadata_strv != NULL && metadata_strv[i] != NULL; i++) + { + metadata_keywords = g_list_prepend (metadata_keywords, metadata_strv[i]); + } + /* Free only the container array. The strings are owned by the list now. */ + g_free (metadata_strv); + + clean_up_metadata_keywords (file, &metadata_keywords); + keywords = g_list_concat (keywords, metadata_keywords); + + return sort_keyword_list_and_remove_duplicates (keywords); +} + +/** + * nautilus_file_get_emblem_icons + * + * Return the list of names of emblems that this file should display, + * in canonical order. + * @file: NautilusFile representing the file in question. + * + * Returns: (transfer full) (element-type GIcon): A list of emblem names. + * + **/ +GList * +nautilus_file_get_emblem_icons (NautilusFile *file) +{ + GList *keywords, *l; + GList *icons; + char *icon_names[2]; + char *keyword; + GIcon *icon; + + if (file == NULL) + { + return NULL; + } + + g_return_val_if_fail (NAUTILUS_IS_FILE (file), NULL); + + keywords = nautilus_file_get_keywords (file); + keywords = prepend_automatic_keywords (file, keywords); + + icons = NULL; + for (l = keywords; l != NULL; l = l->next) + { + keyword = l->data; + + icon_names[0] = g_strconcat ("emblem-", keyword, NULL); + icon_names[1] = keyword; + icon = g_themed_icon_new_from_names (icon_names, 2); + g_free (icon_names[0]); + + icons = g_list_prepend (icons, icon); + } + + icon = get_mount_icon (file); + if (icon != NULL) + { + icons = g_list_prepend (icons, icon); + } + + g_list_free_full (keywords, g_free); + + return icons; +} + +GIcon * +nautilus_file_get_gicon (NautilusFile *file, + NautilusFileIconFlags flags) +{ + GIcon *icon; + + if (file == NULL) + { + return NULL; + } + + icon = get_custom_icon (file); + if (icon != NULL) + { + return icon; + } + + if (flags & NAUTILUS_FILE_ICON_FLAGS_USE_MOUNT_ICON) + { + icon = get_mount_icon (file); + + if (icon != NULL) + { + goto out; + } + } + + if (file->details->icon) + { + icon = g_object_ref (file->details->icon); + } + +out: + if (icon == NULL) + { + icon = g_object_ref (get_default_file_icon ()); + } + + return icon; +} + +char * +nautilus_file_get_thumbnail_path (NautilusFile *file) +{ + return g_strdup (file->details->thumbnail_path); +} + +static NautilusIconInfo * +nautilus_file_get_thumbnail_icon (NautilusFile *file, + int size, + int scale, + NautilusFileIconFlags flags) +{ + g_autoptr (GdkPaintable) paintable = NULL; + NautilusIconInfo *icon; + + icon = NULL; + + if (file->details->thumbnail != NULL) + { + GdkPixbuf *pixbuf = file->details->thumbnail; + double width = gdk_pixbuf_get_width (pixbuf) / scale; + double height = gdk_pixbuf_get_height (pixbuf) / scale; + g_autoptr (GdkTexture) texture = gdk_texture_new_for_pixbuf (pixbuf); + g_autoptr (GtkSnapshot) snapshot = gtk_snapshot_new (); + GskRoundedRect rounded_rect; + + if (MAX (width, height) > size) + { + float scale_down_factor = MAX (width, height) / size; + + width = width / scale_down_factor; + height = height / scale_down_factor; + } + + gsk_rounded_rect_init_from_rect (&rounded_rect, + &GRAPHENE_RECT_INIT (0, 0, width, height), + 2 /* radius*/); + gtk_snapshot_push_rounded_clip (snapshot, &rounded_rect); + + gdk_paintable_snapshot (GDK_PAINTABLE (texture), + GDK_SNAPSHOT (snapshot), + width, height); + + if (size >= NAUTILUS_GRID_ICON_SIZE_SMALL && + nautilus_is_video_file (file)) + { + nautilus_ui_frame_video (snapshot, width, height); + } + + gtk_snapshot_pop (snapshot); /* End rounded clip */ + + DEBUG ("Returning thumbnailed image, at size %d %d", + (int) (width), (int) (height)); + paintable = gtk_snapshot_to_paintable (snapshot, NULL); + } + else if (file->details->thumbnail_path == NULL && + file->details->can_read && + !file->details->is_thumbnailing && + !file->details->thumbnailing_failed && + nautilus_can_thumbnail (file)) + { + nautilus_create_thumbnail (file); + } + + if (paintable != NULL) + { + icon = nautilus_icon_info_new_for_paintable (paintable, scale); + } + else if (file->details->is_thumbnailing) + { + g_autoptr (GIcon) gicon = g_themed_icon_new (ICON_NAME_THUMBNAIL_LOADING); + icon = nautilus_icon_info_lookup (gicon, size, scale); + } + + return icon; +} + +NautilusIconInfo * +nautilus_file_get_icon (NautilusFile *file, + int size, + int scale, + NautilusFileIconFlags flags) +{ + NautilusIconInfo *icon; + GIcon *gicon; + + icon = NULL; + + if (file == NULL) + { + goto out; + } + + gicon = get_custom_icon (file); + if (gicon != NULL) + { + icon = nautilus_icon_info_lookup (gicon, size, scale); + g_object_unref (gicon); + + goto out; + } + + DEBUG ("Called file_get_icon(), at size %d", size); + + if (flags & NAUTILUS_FILE_ICON_FLAGS_USE_THUMBNAILS && + nautilus_file_should_show_thumbnail (file) && + size >= NAUTILUS_THUMBNAIL_MINIMUM_ICON_SIZE) + { + icon = nautilus_file_get_thumbnail_icon (file, size, scale, flags); + } + + if (icon == NULL) + { + gicon = nautilus_file_get_gicon (file, flags); + icon = nautilus_icon_info_lookup (gicon, size, scale); + g_object_unref (gicon); + + if (nautilus_icon_info_is_fallback (icon)) + { + g_object_unref (icon); + icon = nautilus_icon_info_lookup (get_default_file_icon (), size, scale); + } + } + +out: + return icon; +} + +GdkTexture * +nautilus_file_get_icon_texture (NautilusFile *file, + int size, + int scale, + NautilusFileIconFlags flags) +{ + g_autoptr (NautilusIconInfo) info = NULL; + + info = nautilus_file_get_icon (file, size, scale, flags); + + return nautilus_icon_info_get_texture (info); +} + +GdkPaintable * +nautilus_file_get_icon_paintable (NautilusFile *file, + int size, + int scale, + NautilusFileIconFlags flags) +{ + g_autoptr (NautilusIconInfo) info = NULL; + + info = nautilus_file_get_icon (file, size, scale, flags); + + return nautilus_icon_info_get_paintable (info); +} + +gboolean +nautilus_file_get_date (NautilusFile *file, + NautilusDateType date_type, + time_t *date) +{ + if (date != NULL) + { + *date = 0; + } + + g_return_val_if_fail (date_type == NAUTILUS_DATE_TYPE_ACCESSED + || date_type == NAUTILUS_DATE_TYPE_MODIFIED + || date_type == NAUTILUS_DATE_TYPE_CREATED + || date_type == NAUTILUS_DATE_TYPE_TRASHED + || date_type == NAUTILUS_DATE_TYPE_RECENCY, + FALSE); + + if (file == NULL) + { + return FALSE; + } + + g_return_val_if_fail (NAUTILUS_IS_FILE (file), FALSE); + + return NAUTILUS_FILE_CLASS (G_OBJECT_GET_CLASS (file))->get_date (file, date_type, date); +} + +static char * +nautilus_file_get_where_string (NautilusFile *file) +{ + if (file == NULL) + { + return NULL; + } + + g_return_val_if_fail (NAUTILUS_IS_FILE (file), NULL); + + return NAUTILUS_FILE_CLASS (G_OBJECT_GET_CLASS (file))->get_where_string (file); +} + +static char * +nautilus_file_get_trash_original_file_parent_as_string (NautilusFile *file) +{ + NautilusFile *orig_file; + char *filename; + + filename = NULL; + orig_file = nautilus_file_get_trash_original_file (file); + if (orig_file != NULL) + { + filename = nautilus_file_get_parent_uri_for_display (orig_file); + + nautilus_file_unref (orig_file); + } + + return filename; +} + +/** + * nautilus_file_get_date_as_string: + * + * Get a user-displayable string representing a file modification date. + * The caller is responsible for g_free-ing this string. + * @file: NautilusFile representing the file in question. + * + * Returns: Newly allocated string ready to display to the user. + * + **/ +static char * +nautilus_file_get_date_as_string (NautilusFile *file, + NautilusDateType date_type, + NautilusDateFormat date_format) +{ + time_t file_time_raw; + GDateTime *file_date_time, *now; + GDateTime *today_midnight; + gint days_ago; + gboolean use_24; + const gchar *format; + gchar *result; + gchar *result_with_ratio; + + if (!nautilus_file_get_date (file, date_type, &file_time_raw)) + { + return NULL; + } + + file_date_time = g_date_time_new_from_unix_local (file_time_raw); + if (date_format != NAUTILUS_DATE_FORMAT_FULL) + { + GDateTime *file_date; + + now = g_date_time_new_now_local (); + today_midnight = g_date_time_new_local (g_date_time_get_year (now), + g_date_time_get_month (now), + g_date_time_get_day_of_month (now), + 0, 0, 0); + + file_date = g_date_time_new_local (g_date_time_get_year (file_date_time), + g_date_time_get_month (file_date_time), + g_date_time_get_day_of_month (file_date_time), + 0, 0, 0); + + days_ago = g_date_time_difference (today_midnight, file_date) / G_TIME_SPAN_DAY; + + use_24 = g_settings_get_enum (gnome_interface_preferences, "clock-format") == + G_DESKTOP_CLOCK_FORMAT_24H; + + /* Show only the time if date is on today */ + if (days_ago == 0) + { + if (use_24) + { + /* Translators: Time in 24h format */ + format = _("%H:%M"); + } + else + { + /* Translators: Time in 12h format */ + format = _("%l:%M %p"); + } + } + /* Show the word "Yesterday" and time if date is on yesterday */ + else if (days_ago == 1) + { + if (date_format == NAUTILUS_DATE_FORMAT_REGULAR) + { + /* xgettext:no-c-format */ + format = _("Yesterday"); + } + else + { + if (use_24) + { + /* Translators: this is the word Yesterday followed by + * a time in 24h format. i.e. "Yesterday 23:04" */ + /* xgettext:no-c-format */ + format = _("Yesterday %H:%M"); + } + else + { + /* Translators: this is the word Yesterday followed by + * a time in 12h format. i.e. "Yesterday 9:04 PM" */ + /* xgettext:no-c-format */ + format = _("Yesterday %l:%M %p"); + } + } + } + /* Show a week day and time if date is in the last week */ + else if (days_ago > 1 && days_ago < 7) + { + if (date_format == NAUTILUS_DATE_FORMAT_REGULAR) + { + /* xgettext:no-c-format */ + format = _("%a"); + } + else + { + if (use_24) + { + /* Translators: this is the name of the week day followed by + * a time in 24h format. i.e. "Monday 23:04" */ + /* xgettext:no-c-format */ + format = _("%a %H:%M"); + } + else + { + /* Translators: this is the week day name followed by + * a time in 12h format. i.e. "Monday 9:04 PM" */ + /* xgettext:no-c-format */ + format = _("%a %l:%M %p"); + } + } + } + else if (g_date_time_get_year (file_date) == g_date_time_get_year (now)) + { + if (date_format == NAUTILUS_DATE_FORMAT_REGULAR) + { + /* Translators: this is the day of the month followed + * by the abbreviated month name i.e. "3 Feb" */ + /* xgettext:no-c-format */ + format = _("%-e %b"); + } + else + { + if (use_24) + { + /* Translators: this is the day of the month followed + * by the abbreviated month name followed by a time in + * 24h format i.e. "3 Feb 23:04" */ + /* xgettext:no-c-format */ + format = _("%-e %b %H:%M"); + } + else + { + /* Translators: this is the day of the month followed + * by the abbreviated month name followed by a time in + * 12h format i.e. "3 Feb 9:04" */ + /* xgettext:no-c-format */ + format = _("%-e %b %l:%M %p"); + } + } + } + else + { + if (date_format == NAUTILUS_DATE_FORMAT_REGULAR) + { + /* Translators: this is the day of the month followed by the abbreviated + * month name followed by the year i.e. "3 Feb 2015" */ + /* xgettext:no-c-format */ + format = _("%-e %b %Y"); + } + else + { + if (use_24) + { + /* Translators: this is the day number followed + * by the abbreviated month name followed by the year followed + * by a time in 24h format i.e. "3 Feb 2015 23:04" */ + /* xgettext:no-c-format */ + format = _("%-e %b %Y %H:%M"); + } + else + { + /* Translators: this is the day number followed + * by the abbreviated month name followed by the year followed + * by a time in 12h format i.e. "3 Feb 2015 9:04 PM" */ + /* xgettext:no-c-format */ + format = _("%-e %b %Y %l:%M %p"); + } + } + } + + g_date_time_unref (file_date); + g_date_time_unref (now); + g_date_time_unref (today_midnight); + } + else + { + /* xgettext:no-c-format */ + format = _("%c"); + } + + result = g_date_time_format (file_date_time, format); + g_date_time_unref (file_date_time); + + /* Replace ":" with ratio. Replacement is done afterward because g_date_time_format + * may fail with utf8 chars in some locales */ + result_with_ratio = eel_str_replace_substring (result, ":", "∶"); + g_free (result); + + return result_with_ratio; +} + +static void +show_directory_item_count_changed_callback (gpointer callback_data) +{ + show_directory_item_count = g_settings_get_enum (nautilus_preferences, NAUTILUS_PREFERENCES_SHOW_DIRECTORY_ITEM_COUNTS); +} + +gboolean +nautilus_file_should_show_directory_item_count (NautilusFile *file) +{ + static gboolean show_directory_item_count_callback_added = FALSE; + + g_return_val_if_fail (NAUTILUS_IS_FILE (file), FALSE); + + if (file->details->mime_type && + strcmp (file->details->mime_type, "x-directory/smb-share") == 0) + { + return FALSE; + } + + /* Add the callback once for the life of our process */ + if (!show_directory_item_count_callback_added) + { + g_signal_connect_swapped (nautilus_preferences, + "changed::" NAUTILUS_PREFERENCES_SHOW_DIRECTORY_ITEM_COUNTS, + G_CALLBACK (show_directory_item_count_changed_callback), + NULL); + show_directory_item_count_callback_added = TRUE; + + /* Peek for the first time */ + show_directory_item_count_changed_callback (NULL); + } + + return get_speed_tradeoff_preference_for_file (file, show_directory_item_count); +} + +/** + * nautilus_file_get_directory_item_count + * + * Get the number of items in a directory. + * @file: NautilusFile representing a directory. + * @count: Place to put count. + * @count_unreadable: Set to TRUE (if non-NULL) if permissions prevent + * the item count from being read on this directory. Otherwise set to FALSE. + * + * Returns: TRUE if count is available. + * + **/ +gboolean +nautilus_file_get_directory_item_count (NautilusFile *file, + guint *count, + gboolean *count_unreadable) +{ + if (count != NULL) + { + *count = 0; + } + if (count_unreadable != NULL) + { + *count_unreadable = FALSE; + } + + g_return_val_if_fail (NAUTILUS_IS_FILE (file), FALSE); + + if (!nautilus_file_is_directory (file)) + { + return FALSE; + } + + if (!nautilus_file_should_show_directory_item_count (file)) + { + return FALSE; + } + + return NAUTILUS_FILE_CLASS (G_OBJECT_GET_CLASS (file))->get_item_count + (file, count, count_unreadable); +} + +/** + * nautilus_file_get_deep_counts + * + * Get the statistics about items inside a directory. + * @file: NautilusFile representing a directory or file. + * @directory_count: Place to put count of directories inside. + * @files_count: Place to put count of files inside. + * @unreadable_directory_count: Number of directories encountered + * that were unreadable. + * @total_size: Total size of all files and directories visited. + * @force: Whether the deep counts should even be collected if + * nautilus_file_should_show_directory_item_count returns FALSE + * for this file. + * + * Returns: Status to indicate whether sizes are available. + * + **/ +NautilusRequestStatus +nautilus_file_get_deep_counts (NautilusFile *file, + guint *directory_count, + guint *file_count, + guint *unreadable_directory_count, + goffset *total_size, + gboolean force) +{ + if (directory_count != NULL) + { + *directory_count = 0; + } + if (file_count != NULL) + { + *file_count = 0; + } + if (unreadable_directory_count != NULL) + { + *unreadable_directory_count = 0; + } + if (total_size != NULL) + { + *total_size = 0; + } + + g_return_val_if_fail (NAUTILUS_IS_FILE (file), NAUTILUS_REQUEST_DONE); + + if (!force && !nautilus_file_should_show_directory_item_count (file)) + { + /* Set field so an existing value isn't treated as up-to-date + * when preference changes later. + */ + file->details->deep_counts_status = NAUTILUS_REQUEST_NOT_STARTED; + return file->details->deep_counts_status; + } + + return NAUTILUS_FILE_CLASS (G_OBJECT_GET_CLASS (file))->get_deep_counts + (file, directory_count, file_count, + unreadable_directory_count, total_size); +} + +void +nautilus_file_recompute_deep_counts (NautilusFile *file) +{ + if (file->details->deep_counts_status != NAUTILUS_REQUEST_IN_PROGRESS) + { + file->details->deep_counts_status = NAUTILUS_REQUEST_NOT_STARTED; + if (file->details->directory != NULL) + { + nautilus_directory_add_file_to_work_queue (file->details->directory, file); + nautilus_directory_async_state_changed (file->details->directory); + } + } +} + +gboolean +nautilus_file_can_get_size (NautilusFile *file) +{ + return file->details->size == -1; +} + + +/** + * nautilus_file_get_size + * + * Get the file size. + * @file: NautilusFile representing the file in question. + * + * Returns: Size in bytes. + * + **/ +goffset +nautilus_file_get_size (NautilusFile *file) +{ + /* Before we have info on the file, we don't know the size. */ + if (file->details->size == -1) + { + return 0; + } + return file->details->size; +} + +time_t +nautilus_file_get_mtime (NautilusFile *file) +{ + return file->details->mtime; +} + +time_t +nautilus_file_get_atime (NautilusFile *file) +{ + return file->details->atime; +} + +time_t +nautilus_file_get_btime (NautilusFile *file) +{ + return file->details->btime; +} + +time_t +nautilus_file_get_recency (NautilusFile *file) +{ + g_return_val_if_fail (NAUTILUS_IS_FILE (file), 0); + + return file->details->recency; +} + +time_t +nautilus_file_get_trash_time (NautilusFile *file) +{ + g_return_val_if_fail (NAUTILUS_IS_FILE (file), 0); + + return file->details->trash_time; +} + +static void +set_attributes_get_info_callback (GObject *source_object, + GAsyncResult *res, + gpointer callback_data) +{ + NautilusFileOperation *op; + GFileInfo *new_info; + GError *error; + + op = callback_data; + + error = NULL; + new_info = g_file_query_info_finish (G_FILE (source_object), res, &error); + if (new_info != NULL) + { + if (nautilus_file_update_info (op->file, new_info)) + { + nautilus_file_changed (op->file); + } + g_object_unref (new_info); + } + nautilus_file_operation_complete (op, NULL, error); + if (error) + { + g_error_free (error); + } +} + + +static void +set_attributes_callback (GObject *source_object, + GAsyncResult *result, + gpointer callback_data) +{ + NautilusFileOperation *op; + GError *error; + gboolean res; + + op = callback_data; + + error = NULL; + res = g_file_set_attributes_finish (G_FILE (source_object), + result, + NULL, + &error); + + if (res) + { + g_file_query_info_async (G_FILE (source_object), + NAUTILUS_FILE_DEFAULT_ATTRIBUTES, + 0, + G_PRIORITY_DEFAULT, + op->cancellable, + set_attributes_get_info_callback, op); + } + else + { + nautilus_file_operation_complete (op, NULL, error); + g_error_free (error); + } +} + +void +nautilus_file_set_attributes (NautilusFile *file, + GFileInfo *attributes, + NautilusFileOperationCallback callback, + gpointer callback_data) +{ + NautilusFileOperation *op; + GFile *location; + + op = nautilus_file_operation_new (file, callback, callback_data); + + location = nautilus_file_get_location (file); + g_file_set_attributes_async (location, + attributes, + 0, + G_PRIORITY_DEFAULT, + op->cancellable, + set_attributes_callback, + op); + g_object_unref (location); +} + +void +nautilus_file_set_search_relevance (NautilusFile *file, + gdouble relevance) +{ + file->details->search_relevance = relevance; +} + +void +nautilus_file_set_search_fts_snippet (NautilusFile *file, + const gchar *fts_snippet) +{ + file->details->fts_snippet = g_strdup (fts_snippet); +} + +const gchar * +nautilus_file_get_search_fts_snippet (NautilusFile *file) +{ + return file->details->fts_snippet; +} + +/** + * nautilus_file_can_get_permissions: + * + * Check whether the permissions for a file are determinable. + * This might not be the case for files on non-UNIX file systems. + * + * @file: The file in question. + * + * Return value: TRUE if the permissions are valid. + */ +gboolean +nautilus_file_can_get_permissions (NautilusFile *file) +{ + return file->details->has_permissions; +} + +/** + * nautilus_file_can_set_permissions: + * + * Check whether the current user is allowed to change + * the permissions of a file. + * + * @file: The file in question. + * + * Return value: TRUE if the current user can change the + * permissions of @file, FALSE otherwise. It's always possible + * that when you actually try to do it, you will fail. + */ +gboolean +nautilus_file_can_set_permissions (NautilusFile *file) +{ + g_autoptr (GFile) location = NULL; + uid_t user_id; + + location = nautilus_file_get_location (file); + + if (file->details->uid != -1 && + g_file_is_native (location)) + { + /* Check the user. */ + user_id = geteuid (); + + /* Owner is allowed to set permissions. */ + if (user_id == (uid_t) file->details->uid) + { + return TRUE; + } + + /* Root is also allowed to set permissions. */ + if (user_id == 0) + { + return TRUE; + } + + /* Nobody else is allowed. */ + return FALSE; + } + + /* pretend to have full chmod rights when no info is available, relevant when + * the FS can't provide ownership info, for instance for FTP */ + return TRUE; +} + +guint +nautilus_file_get_permissions (NautilusFile *file) +{ + g_return_val_if_fail (nautilus_file_can_get_permissions (file), 0); + + return file->details->permissions; +} + +/** + * nautilus_file_set_permissions: + * + * Change a file's permissions. This should only be called if + * nautilus_file_can_set_permissions returned TRUE. + * + * @file: NautilusFile representing the file in question. + * @new_permissions: New permissions value. This is the whole + * set of permissions, not a delta. + **/ +void +nautilus_file_set_permissions (NautilusFile *file, + guint32 new_permissions, + NautilusFileOperationCallback callback, + gpointer callback_data) +{ + GFileInfo *info; + GError *error; + + if (!nautilus_file_can_set_permissions (file)) + { + /* Claim that something changed even if the permission change failed. + * This makes it easier for some clients who see the "reverting" + * to the old permissions as "changing back". + */ + nautilus_file_changed (file); + error = g_error_new (G_IO_ERROR, G_IO_ERROR_PERMISSION_DENIED, + _("Not allowed to set permissions")); + (*callback)(file, NULL, error, callback_data); + g_error_free (error); + return; + } + + /* Test the permissions-haven't-changed case explicitly + * because we don't want to send the file-changed signal if + * nothing changed. + */ + if (new_permissions == file->details->permissions) + { + (*callback)(file, NULL, NULL, callback_data); + return; + } + + if (!nautilus_file_undo_manager_is_operating ()) + { + NautilusFileUndoInfo *undo_info; + + undo_info = nautilus_file_undo_info_permissions_new (nautilus_file_get_location (file), + file->details->permissions, + new_permissions); + nautilus_file_undo_manager_set_action (undo_info); + } + + info = g_file_info_new (); + g_file_info_set_attribute_uint32 (info, G_FILE_ATTRIBUTE_UNIX_MODE, new_permissions); + nautilus_file_set_attributes (file, info, callback, callback_data); + + g_object_unref (info); +} + +/** + * nautilus_file_can_get_selinux_context: + * + * Check whether the selinux context for a file are determinable. + * This might not be the case for files on non-UNIX file systems, + * files without a context or systems that don't support selinux. + * + * @file: The file in question. + * + * Return value: TRUE if the permissions are valid. + */ +gboolean +nautilus_file_can_get_selinux_context (NautilusFile *file) +{ + return file->details->selinux_context != NULL; +} + + +/** + * nautilus_file_get_selinux_context: + * + * Get a user-displayable string representing a file's selinux + * context + * @file: NautilusFile representing the file in question. + * + * Returns: Newly allocated string ready to display to the user. + * + **/ +char * +nautilus_file_get_selinux_context (NautilusFile *file) +{ + char *translated; + char *raw; + + g_return_val_if_fail (NAUTILUS_IS_FILE (file), NULL); + + if (!nautilus_file_can_get_selinux_context (file)) + { + return NULL; + } + + raw = file->details->selinux_context; + +#ifdef HAVE_SELINUX + if (selinux_raw_to_trans_context (raw, &translated) == 0) + { + char *tmp; + tmp = g_strdup (translated); + freecon (translated); + translated = tmp; + } + else +#endif + { + translated = g_strdup (raw); + } + + return translated; +} + +static char * +get_real_name (const char *name, + const char *gecos) +{ + char *locale_string, *part_before_comma, *capitalized_login_name, *real_name; + + if (gecos == NULL) + { + return NULL; + } + + locale_string = eel_str_strip_substring_and_after (gecos, ","); + if (!g_utf8_validate (locale_string, -1, NULL)) + { + part_before_comma = g_locale_to_utf8 (locale_string, -1, NULL, NULL, NULL); + g_free (locale_string); + } + else + { + part_before_comma = locale_string; + } + + if (!g_utf8_validate (name, -1, NULL)) + { + locale_string = g_locale_to_utf8 (name, -1, NULL, NULL, NULL); + } + else + { + locale_string = g_strdup (name); + } + + capitalized_login_name = eel_str_capitalize (locale_string); + g_free (locale_string); + + if (capitalized_login_name == NULL) + { + real_name = part_before_comma; + } + else + { + real_name = eel_str_replace_substring + (part_before_comma, "&", capitalized_login_name); + g_free (part_before_comma); + } + + + if (g_strcmp0 (real_name, NULL) == 0 + || g_strcmp0 (name, real_name) == 0 + || g_strcmp0 (capitalized_login_name, real_name) == 0) + { + g_free (real_name); + real_name = NULL; + } + + g_free (capitalized_login_name); + + return real_name; +} + +static gboolean +get_group_id_from_group_name (const char *group_name, + uid_t *gid) +{ + struct group *group; + + g_assert (gid != NULL); + + group = getgrnam (group_name); + + if (group == NULL) + { + return FALSE; + } + + *gid = group->gr_gid; + + return TRUE; +} + +static gboolean +get_ids_from_user_name (const char *user_name, + uid_t *uid, + uid_t *gid) +{ + struct passwd *password_info; + + g_assert (uid != NULL || gid != NULL); + + password_info = getpwnam (user_name); + + if (password_info == NULL) + { + return FALSE; + } + + if (uid != NULL) + { + *uid = password_info->pw_uid; + } + + if (gid != NULL) + { + *gid = password_info->pw_gid; + } + + return TRUE; +} + +static gboolean +get_user_id_from_user_name (const char *user_name, + uid_t *id) +{ + return get_ids_from_user_name (user_name, id, NULL); +} + +static gboolean +get_id_from_digit_string (const char *digit_string, + uid_t *id) +{ + long scanned_id; + char c; + + g_assert (id != NULL); + + /* Only accept string if it has one integer with nothing + * afterwards. + */ + if (sscanf (digit_string, "%ld%c", &scanned_id, &c) != 1) + { + return FALSE; + } + *id = scanned_id; + return TRUE; +} + +/** + * nautilus_file_can_get_owner: + * + * Check whether the owner a file is determinable. + * This might not be the case for files on non-UNIX file systems. + * + * @file: The file in question. + * + * Return value: TRUE if the owner is valid. + */ +gboolean +nautilus_file_can_get_owner (NautilusFile *file) +{ + /* Before we have info on a file, the owner is unknown. */ + return file->details->uid != -1; +} + +/** + * nautilus_file_get_uid: + * + * Get the user id of the file's owner. + * + * @file: The file in question. + * + * Return value: (transfer none): the user id. + */ +const uid_t +nautilus_file_get_uid (NautilusFile *file) +{ + return file->details->uid; +} + +/** + * nautilus_file_get_owner_name: + * + * Get the user name of the file's owner. If the owner has no + * name, returns the userid as a string. The caller is responsible + * for g_free-ing this string. + * + * @file: The file in question. + * + * Return value: A newly-allocated string. + */ +char * +nautilus_file_get_owner_name (NautilusFile *file) +{ + return nautilus_file_get_owner_as_string (file, FALSE); +} + +/** + * nautilus_file_can_set_owner: + * + * Check whether the current user is allowed to change + * the owner of a file. + * + * @file: The file in question. + * + * Return value: TRUE if the current user can change the + * owner of @file, FALSE otherwise. It's always possible + * that when you actually try to do it, you will fail. + */ +gboolean +nautilus_file_can_set_owner (NautilusFile *file) +{ + /* Not allowed to set the owner if we can't + * even read it. This can happen on non-UNIX file + * systems. + */ + if (!nautilus_file_can_get_owner (file)) + { + return FALSE; + } + + /* Owner can be changed only in admin backend or by root */ + return nautilus_file_is_in_admin (file) || geteuid () == 0; +} + +/** + * nautilus_file_set_owner: + * + * Set the owner of a file. This will only have any effect if + * nautilus_file_can_set_owner returns TRUE. + * + * @file: The file in question. + * @user_name_or_id: The user name to set the owner to. + * If the string does not match any user name, and the + * string is an integer, the owner will be set to the + * userid represented by that integer. + * @callback: Function called when asynch owner change succeeds or fails. + * @callback_data: Parameter passed back with callback function. + */ +void +nautilus_file_set_owner (NautilusFile *file, + const char *user_name_or_id, + NautilusFileOperationCallback callback, + gpointer callback_data) +{ + GError *error; + GFileInfo *info; + uid_t new_id; + + if (!nautilus_file_can_set_owner (file)) + { + /* Claim that something changed even if the permission + * change failed. This makes it easier for some + * clients who see the "reverting" to the old owner as + * "changing back". + */ + nautilus_file_changed (file); + error = g_error_new (G_IO_ERROR, G_IO_ERROR_PERMISSION_DENIED, + _("Not allowed to set owner")); + (*callback)(file, NULL, error, callback_data); + g_error_free (error); + return; + } + + /* If no match treating user_name_or_id as name, try treating + * it as id. + */ + if (!get_user_id_from_user_name (user_name_or_id, &new_id) + && !get_id_from_digit_string (user_name_or_id, &new_id)) + { + /* Claim that something changed even if the permission + * change failed. This makes it easier for some + * clients who see the "reverting" to the old owner as + * "changing back". + */ + nautilus_file_changed (file); + error = g_error_new (G_IO_ERROR, G_IO_ERROR_INVALID_ARGUMENT, + _("Specified owner “%s” doesn’t exist"), user_name_or_id); + (*callback)(file, NULL, error, callback_data); + g_error_free (error); + return; + } + + /* Test the owner-hasn't-changed case explicitly because we + * don't want to send the file-changed signal if nothing + * changed. + */ + if (new_id == (uid_t) file->details->uid) + { + (*callback)(file, NULL, NULL, callback_data); + return; + } + + if (!nautilus_file_undo_manager_is_operating ()) + { + NautilusFileUndoInfo *undo_info; + char *current_owner; + + current_owner = nautilus_file_get_owner_as_string (file, FALSE); + + undo_info = nautilus_file_undo_info_ownership_new (NAUTILUS_FILE_UNDO_OP_CHANGE_OWNER, + nautilus_file_get_location (file), + current_owner, + user_name_or_id); + nautilus_file_undo_manager_set_action (undo_info); + + g_free (current_owner); + } + + info = g_file_info_new (); + g_file_info_set_attribute_uint32 (info, G_FILE_ATTRIBUTE_UNIX_UID, new_id); + nautilus_file_set_attributes (file, info, callback, callback_data); + g_object_unref (info); +} + +/** + * nautilus_get_user_names: + * + * Get a list of user names. For users with a different associated + * "real name", the real name follows the standard user name, separated + * by a dash surrounded by spaces. The caller is responsible for freeing + * this list and its contents. + */ +GList * +nautilus_get_user_names (void) +{ + GList *list; + char *real_name, *name; + struct passwd *user; + + list = NULL; + + setpwent (); + + while ((user = getpwent ()) != NULL) + { + real_name = get_real_name (user->pw_name, user->pw_gecos); + if (real_name != NULL && !g_str_equal (real_name, "")) + { + name = g_strconcat (user->pw_name, " – ", real_name, NULL); + } + else + { + name = g_strdup (user->pw_name); + } + g_free (real_name); + list = g_list_prepend (list, name); + } + + endpwent (); + + return g_list_sort (list, (GCompareFunc) g_utf8_collate); +} + +/** + * nautilus_file_can_get_group: + * + * Check whether the group a file is determinable. + * This might not be the case for files on non-UNIX file systems. + * + * @file: The file in question. + * + * Return value: TRUE if the group is valid. + */ +gboolean +nautilus_file_can_get_group (NautilusFile *file) +{ + /* Before we have info on a file, the group is unknown. */ + return file->details->gid != -1; +} + +/** + * nautilus_file_get_gid: + * + * Get the group id of the file's group. + * + * @file: The file in question. + * + * Return value: (transfer none): the group id. + */ +const gid_t +nautilus_file_get_gid (NautilusFile *file) +{ + return file->details->gid; +} + +/** + * nautilus_file_get_group_name: + * + * Get the name of the file's group. If the group has no + * name, returns the groupid as a string. The caller is responsible + * for g_free-ing this string. + * + * @file: The file in question. + * + * Return value: A newly-allocated string. + **/ +char * +nautilus_file_get_group_name (NautilusFile *file) +{ + return g_strdup (file->details->group); +} + +/** + * nautilus_file_can_set_group: + * + * Check whether the current user is allowed to change + * the group of a file. + * + * @file: The file in question. + * + * Return value: TRUE if the current user can change the + * group of @file, FALSE otherwise. It's always possible + * that when you actually try to do it, you will fail. + */ +gboolean +nautilus_file_can_set_group (NautilusFile *file) +{ + uid_t user_id; + + /* Not allowed to set the permissions if we can't + * even read them. This can happen on non-UNIX file + * systems. + */ + if (!nautilus_file_can_get_group (file)) + { + return FALSE; + } + + /* Check the user. */ + user_id = geteuid (); + + /* Owner is allowed to set group (with restrictions). */ + if (user_id == (uid_t) file->details->uid) + { + return TRUE; + } + + /* Root is also allowed to set group. */ + if (user_id == 0) + { + return TRUE; + } + + /* Nobody else is allowed. */ + return FALSE; +} + +/* Get a list of group names, filtered to only the ones + * that contain the given username. If the username is + * NULL, returns a list of all group names. + */ +static GList * +nautilus_get_group_names_for_user (void) +{ + GList *list; + struct group *group; + int count, i; + gid_t gid_list[NGROUPS_MAX + 1]; + + + list = NULL; + + count = getgroups (NGROUPS_MAX + 1, gid_list); + for (i = 0; i < count; i++) + { + group = getgrgid (gid_list[i]); + if (group == NULL) + { + break; + } + + list = g_list_prepend (list, g_strdup (group->gr_name)); + } + + return g_list_sort (list, (GCompareFunc) g_utf8_collate); +} + +/** + * nautilus_get_group_names: + * + * Get a list of all group names. + */ +GList * +nautilus_get_all_group_names (void) +{ + GList *list; + struct group *group; + + list = NULL; + + setgrent (); + + while ((group = getgrent ()) != NULL) + { + list = g_list_prepend (list, g_strdup (group->gr_name)); + } + + endgrent (); + + return g_list_sort (list, (GCompareFunc) g_utf8_collate); +} + +/** + * nautilus_file_get_settable_group_names: + * + * Get a list of all group names that the current user + * can set the group of a specific file to. + * + * @file: The NautilusFile in question. + */ +GList * +nautilus_file_get_settable_group_names (NautilusFile *file) +{ + uid_t user_id; + GList *result; + + if (!nautilus_file_can_set_group (file)) + { + return NULL; + } + + /* Check the user. */ + user_id = geteuid (); + + if (user_id == 0) + { + /* Root is allowed to set group to anything. */ + result = nautilus_get_all_group_names (); + } + else if (user_id == (uid_t) file->details->uid) + { + /* Owner is allowed to set group to any that owner is member of. */ + result = nautilus_get_group_names_for_user (); + } + else + { + g_warning ("unhandled case in nautilus_get_settable_group_names"); + result = NULL; + } + + return result; +} + +/** + * nautilus_file_set_group: + * + * Set the group of a file. This will only have any effect if + * nautilus_file_can_set_group returns TRUE. + * + * @file: The file in question. + * @group_name_or_id: The group name to set the owner to. + * If the string does not match any group name, and the + * string is an integer, the group will be set to the + * group id represented by that integer. + * @callback: Function called when asynch group change succeeds or fails. + * @callback_data: Parameter passed back with callback function. + */ +void +nautilus_file_set_group (NautilusFile *file, + const char *group_name_or_id, + NautilusFileOperationCallback callback, + gpointer callback_data) +{ + GError *error; + GFileInfo *info; + uid_t new_id; + + if (!nautilus_file_can_set_group (file)) + { + /* Claim that something changed even if the group + * change failed. This makes it easier for some + * clients who see the "reverting" to the old group as + * "changing back". + */ + nautilus_file_changed (file); + error = g_error_new (G_IO_ERROR, G_IO_ERROR_PERMISSION_DENIED, + _("Not allowed to set group")); + (*callback)(file, NULL, error, callback_data); + g_error_free (error); + return; + } + + /* If no match treating group_name_or_id as name, try treating + * it as id. + */ + if (!get_group_id_from_group_name (group_name_or_id, &new_id) + && !get_id_from_digit_string (group_name_or_id, &new_id)) + { + /* Claim that something changed even if the group + * change failed. This makes it easier for some + * clients who see the "reverting" to the old group as + * "changing back". + */ + nautilus_file_changed (file); + error = g_error_new (G_IO_ERROR, G_IO_ERROR_INVALID_ARGUMENT, + _("Specified group “%s” doesn’t exist"), group_name_or_id); + (*callback)(file, NULL, error, callback_data); + g_error_free (error); + return; + } + + if (new_id == (gid_t) file->details->gid) + { + (*callback)(file, NULL, NULL, callback_data); + return; + } + + if (!nautilus_file_undo_manager_is_operating ()) + { + NautilusFileUndoInfo *undo_info; + char *current_group; + + current_group = nautilus_file_get_group_name (file); + undo_info = nautilus_file_undo_info_ownership_new (NAUTILUS_FILE_UNDO_OP_CHANGE_GROUP, + nautilus_file_get_location (file), + current_group, + group_name_or_id); + nautilus_file_undo_manager_set_action (undo_info); + + g_free (current_group); + } + + info = g_file_info_new (); + g_file_info_set_attribute_uint32 (info, G_FILE_ATTRIBUTE_UNIX_GID, new_id); + nautilus_file_set_attributes (file, info, callback, callback_data); + g_object_unref (info); +} + +/** + * nautilus_file_get_octal_permissions_as_string: + * + * Get a user-displayable string representing a file's permissions + * as an octal number. The caller + * is responsible for g_free-ing this string. + * @file: NautilusFile representing the file in question. + * + * Returns: Newly allocated string ready to display to the user. + * + **/ +static char * +nautilus_file_get_octal_permissions_as_string (NautilusFile *file) +{ + guint32 permissions; + + g_assert (NAUTILUS_IS_FILE (file)); + + if (!nautilus_file_can_get_permissions (file)) + { + return NULL; + } + + permissions = file->details->permissions; + return g_strdup_printf ("%03o", permissions); +} + +/** + * nautilus_file_get_permissions_as_string: + * + * Get a user-displayable string representing a file's permissions. The caller + * is responsible for g_free-ing this string. + * @file: NautilusFile representing the file in question. + * + * Returns: Newly allocated string ready to display to the user. + * + **/ +static char * +nautilus_file_get_permissions_as_string (NautilusFile *file) +{ + guint32 permissions; + gboolean is_directory; + gboolean is_link; + gboolean suid, sgid, sticky; + + if (!nautilus_file_can_get_permissions (file)) + { + return NULL; + } + + g_assert (NAUTILUS_IS_FILE (file)); + + permissions = file->details->permissions; + is_directory = nautilus_file_is_directory (file); + is_link = nautilus_file_is_symbolic_link (file); + + /* We use ls conventions for displaying these three obscure flags */ + suid = permissions & S_ISUID; + sgid = permissions & S_ISGID; + sticky = permissions & S_ISVTX; + + return g_strdup_printf ("%c%c%c%c%c%c%c%c%c%c", + is_link ? 'l' : is_directory ? 'd' : '-', + permissions & S_IRUSR ? 'r' : '-', + permissions & S_IWUSR ? 'w' : '-', + permissions & S_IXUSR + ? (suid ? 's' : 'x') + : (suid ? 'S' : '-'), + permissions & S_IRGRP ? 'r' : '-', + permissions & S_IWGRP ? 'w' : '-', + permissions & S_IXGRP + ? (sgid ? 's' : 'x') + : (sgid ? 'S' : '-'), + permissions & S_IROTH ? 'r' : '-', + permissions & S_IWOTH ? 'w' : '-', + permissions & S_IXOTH + ? (sticky ? 't' : 'x') + : (sticky ? 'T' : '-')); +} + +/** + * nautilus_file_get_owner_as_string: + * + * Get a user-displayable string representing a file's owner. The caller + * is responsible for g_free-ing this string. + * @file: NautilusFile representing the file in question. + * @include_real_name: Whether or not to append the real name (if any) + * for this user after the user name. + * + * Returns: Newly allocated string ready to display to the user. + * + **/ +static char * +nautilus_file_get_owner_as_string (NautilusFile *file, + gboolean include_real_name) +{ + char *user_name; + + /* Before we have info on a file, the owner is unknown. */ + if (file->details->owner == NULL && + file->details->owner_real == NULL) + { + return NULL; + } + + if (include_real_name && + file->details->uid == getuid ()) + { + /* Translators: This is a username followed by "(You)" to indicate the file is owned by the current user */ + user_name = g_strdup_printf (_("%s (You)"), file->details->owner); + } + else if (file->details->owner_real == NULL) + { + user_name = g_strdup (file->details->owner); + } + else if (file->details->owner == NULL) + { + user_name = g_strdup (file->details->owner_real); + } + else if (include_real_name && + strcmp (file->details->owner, file->details->owner_real) != 0) + { + user_name = g_strdup (file->details->owner_real); + } + else + { + user_name = g_strdup (file->details->owner); + } + + return user_name; +} + +static char * +format_item_count_for_display (guint item_count, + gboolean includes_directories, + gboolean includes_files) +{ + g_assert (includes_directories || includes_files); + + return g_strdup_printf (includes_directories + ? (includes_files + ? ngettext ("%'u item", "%'u items", item_count) + : ngettext ("%'u folder", "%'u folders", item_count)) + : ngettext ("%'u file", "%'u files", item_count), item_count); +} + +/** + * nautilus_file_get_size_as_string: + * + * Get a user-displayable string representing a file size. The caller + * is responsible for g_free-ing this string. The string is an item + * count for directories. + * @file: NautilusFile representing the file in question. + * + * Returns: Newly allocated string ready to display to the user. + * + **/ +static char * +nautilus_file_get_size_as_string (NautilusFile *file) +{ + guint item_count; + gboolean count_unreadable; + + if (file == NULL) + { + return NULL; + } + + g_assert (NAUTILUS_IS_FILE (file)); + + if (nautilus_file_is_directory (file)) + { + if (!nautilus_file_get_directory_item_count (file, &item_count, &count_unreadable)) + { + return NULL; + } + return format_item_count_for_display (item_count, TRUE, TRUE); + } + + if (file->details->size == -1) + { + return NULL; + } + return g_format_size (file->details->size); +} + +/** + * nautilus_file_get_size_as_string_with_real_size: + * + * Get a user-displayable string representing a file size. The caller + * is responsible for g_free-ing this string. The string is an item + * count for directories. + * This function adds the real size in the string. + * @file: NautilusFile representing the file in question. + * + * Returns: Newly allocated string ready to display to the user. + * + **/ +static char * +nautilus_file_get_size_as_string_with_real_size (NautilusFile *file) +{ + guint item_count; + gboolean count_unreadable; + g_autofree char *size_str = NULL; + + if (file == NULL) + { + return NULL; + } + + g_assert (NAUTILUS_IS_FILE (file)); + + if (nautilus_file_is_directory (file)) + { + if (!nautilus_file_get_directory_item_count (file, &item_count, &count_unreadable)) + { + return NULL; + } + return format_item_count_for_display (item_count, TRUE, TRUE); + } + + if (file->details->size == -1) + { + return NULL; + } + + size_str = g_strdup_printf ("%'" G_GOFFSET_FORMAT, file->details->size); + return g_strdup_printf (ngettext ("%s byte", + "%s bytes", + file->details->size), + size_str); +} + + +static char * +nautilus_file_get_deep_count_as_string_internal (NautilusFile *file, + gboolean report_size, + gboolean report_directory_count, + gboolean report_file_count) +{ + NautilusRequestStatus status; + guint directory_count; + guint file_count; + guint unreadable_count; + guint total_count; + goffset total_size; + + /* Must ask for size or some kind of count, but not both. */ + g_assert (!report_size || (!report_directory_count && !report_file_count)); + g_assert (report_size || report_directory_count || report_file_count); + + if (file == NULL) + { + return NULL; + } + + g_assert (NAUTILUS_IS_FILE (file)); + g_assert (nautilus_file_is_directory (file)); + + status = nautilus_file_get_deep_counts + (file, &directory_count, &file_count, &unreadable_count, &total_size, FALSE); + + /* Check whether any info is available. */ + if (status == NAUTILUS_REQUEST_NOT_STARTED) + { + return NULL; + } + + total_count = file_count + directory_count; + + if (total_count == 0) + { + switch (status) + { + case NAUTILUS_REQUEST_IN_PROGRESS: + { + /* Don't return confident "zero" until we're finished looking, + * because of next case. + */ + return NULL; + } + + case NAUTILUS_REQUEST_DONE: + { + /* Don't return "zero" if we there were contents but we couldn't read them. */ + if (unreadable_count != 0) + { + return NULL; + } + } + + default: + {} + break; + } + } + + /* Note that we don't distinguish the "everything was readable" case + * from the "some things but not everything was readable" case here. + * Callers can distinguish them using nautilus_file_get_deep_counts + * directly if desired. + */ + if (report_size) + { + return g_format_size (total_size); + } + + return format_item_count_for_display (report_directory_count + ? (report_file_count ? total_count : directory_count) + : file_count, + report_directory_count, report_file_count); +} + +/** + * nautilus_file_get_deep_size_as_string: + * + * Get a user-displayable string representing the size of all contained + * items (only makes sense for directories). The caller + * is responsible for g_free-ing this string. + * @file: NautilusFile representing the file in question. + * + * Returns: Newly allocated string ready to display to the user. + * + **/ +static char * +nautilus_file_get_deep_size_as_string (NautilusFile *file) +{ + return nautilus_file_get_deep_count_as_string_internal (file, TRUE, FALSE, FALSE); +} + +/** + * nautilus_file_get_deep_total_count_as_string: + * + * Get a user-displayable string representing the count of all contained + * items (only makes sense for directories). The caller + * is responsible for g_free-ing this string. + * @file: NautilusFile representing the file in question. + * + * Returns: Newly allocated string ready to display to the user. + * + **/ +static char * +nautilus_file_get_deep_total_count_as_string (NautilusFile *file) +{ + return nautilus_file_get_deep_count_as_string_internal (file, FALSE, TRUE, TRUE); +} + +/** + * nautilus_file_get_deep_file_count_as_string: + * + * Get a user-displayable string representing the count of all contained + * items, not including directories. It only makes sense to call this + * function on a directory. The caller + * is responsible for g_free-ing this string. + * @file: NautilusFile representing the file in question. + * + * Returns: Newly allocated string ready to display to the user. + * + **/ +static char * +nautilus_file_get_deep_file_count_as_string (NautilusFile *file) +{ + return nautilus_file_get_deep_count_as_string_internal (file, FALSE, FALSE, TRUE); +} + +/** + * nautilus_file_get_deep_directory_count_as_string: + * + * Get a user-displayable string representing the count of all contained + * directories. It only makes sense to call this + * function on a directory. The caller + * is responsible for g_free-ing this string. + * @file: NautilusFile representing the file in question. + * + * Returns: Newly allocated string ready to display to the user. + * + **/ +static char * +nautilus_file_get_deep_directory_count_as_string (NautilusFile *file) +{ + return nautilus_file_get_deep_count_as_string_internal (file, FALSE, TRUE, FALSE); +} + +/** + * nautilus_file_get_string_attribute: + * + * Get a user-displayable string from a named attribute. Use g_free to + * free this string. If the value is unknown, returns NULL. You can call + * nautilus_file_get_string_attribute_with_default if you want a non-NULL + * default. + * + * @file: NautilusFile representing the file in question. + * @attribute_name: The name of the desired attribute. The currently supported + * set includes "name", "type", "detailed_type", "mime_type", "size", "deep_size", "deep_directory_count", + * "deep_file_count", "deep_total_count", "date_modified", "date_accessed", "date_created", + * "date_modified_full", "date_accessed_full", "date_created_full", + * "owner", "group", "permissions", "octal_permissions", "uri", "where", + * "link_target", "volume", "free_space", "selinux_context", "trashed_on", "trashed_on_full", "trashed_orig_path", + * "recency" + * + * Returns: Newly allocated string ready to display to the user, or NULL + * if the value is unknown or @attribute_name is not supported. + * + **/ +char * +nautilus_file_get_string_attribute_q (NautilusFile *file, + GQuark attribute_q) +{ + char *extension_attribute; + + if (attribute_q == attribute_name_q) + { + return nautilus_file_get_display_name (file); + } + if (attribute_q == attribute_type_q) + { + return nautilus_file_get_type_as_string (file); + } + if (attribute_q == attribute_detailed_type_q) + { + return nautilus_file_get_detailed_type_as_string (file); + } + if (attribute_q == attribute_mime_type_q) + { + return nautilus_file_get_mime_type (file); + } + if (attribute_q == attribute_size_q) + { + return nautilus_file_get_size_as_string (file); + } + if (attribute_q == attribute_size_detail_q) + { + return nautilus_file_get_size_as_string_with_real_size (file); + } + if (attribute_q == attribute_deep_size_q) + { + return nautilus_file_get_deep_size_as_string (file); + } + if (attribute_q == attribute_deep_file_count_q) + { + return nautilus_file_get_deep_file_count_as_string (file); + } + if (attribute_q == attribute_deep_directory_count_q) + { + return nautilus_file_get_deep_directory_count_as_string (file); + } + if (attribute_q == attribute_deep_total_count_q) + { + return nautilus_file_get_deep_total_count_as_string (file); + } + if (attribute_q == attribute_trash_orig_path_q) + { + return nautilus_file_get_trash_original_file_parent_as_string (file); + } + if (attribute_q == attribute_date_modified_q) + { + return nautilus_file_get_date_as_string (file, + NAUTILUS_DATE_TYPE_MODIFIED, + NAUTILUS_DATE_FORMAT_REGULAR); + } + if (attribute_q == attribute_date_modified_full_q) + { + return nautilus_file_get_date_as_string (file, + NAUTILUS_DATE_TYPE_MODIFIED, + NAUTILUS_DATE_FORMAT_FULL); + } + if (attribute_q == attribute_date_modified_with_time_q) + { + return nautilus_file_get_date_as_string (file, + NAUTILUS_DATE_TYPE_MODIFIED, + NAUTILUS_DATE_FORMAT_REGULAR_WITH_TIME); + } + if (attribute_q == attribute_date_accessed_q) + { + return nautilus_file_get_date_as_string (file, + NAUTILUS_DATE_TYPE_ACCESSED, + NAUTILUS_DATE_FORMAT_REGULAR); + } + if (attribute_q == attribute_date_accessed_full_q) + { + return nautilus_file_get_date_as_string (file, + NAUTILUS_DATE_TYPE_ACCESSED, + NAUTILUS_DATE_FORMAT_FULL); + } + if (attribute_q == attribute_date_created_q) + { + return nautilus_file_get_date_as_string (file, + NAUTILUS_DATE_TYPE_CREATED, + NAUTILUS_DATE_FORMAT_REGULAR); + } + if (attribute_q == attribute_date_created_full_q) + { + return nautilus_file_get_date_as_string (file, + NAUTILUS_DATE_TYPE_CREATED, + NAUTILUS_DATE_FORMAT_FULL); + } + if (attribute_q == attribute_trashed_on_q) + { + return nautilus_file_get_date_as_string (file, + NAUTILUS_DATE_TYPE_TRASHED, + NAUTILUS_DATE_FORMAT_REGULAR); + } + if (attribute_q == attribute_trashed_on_full_q) + { + return nautilus_file_get_date_as_string (file, + NAUTILUS_DATE_TYPE_TRASHED, + NAUTILUS_DATE_FORMAT_FULL); + } + if (attribute_q == attribute_recency_q) + { + return nautilus_file_get_date_as_string (file, + NAUTILUS_DATE_TYPE_RECENCY, + NAUTILUS_DATE_FORMAT_REGULAR); + } + if (attribute_q == attribute_permissions_q) + { + return nautilus_file_get_permissions_as_string (file); + } + if (attribute_q == attribute_selinux_context_q) + { + return nautilus_file_get_selinux_context (file); + } + if (attribute_q == attribute_octal_permissions_q) + { + return nautilus_file_get_octal_permissions_as_string (file); + } + if (attribute_q == attribute_owner_q) + { + return nautilus_file_get_owner_as_string (file, TRUE); + } + if (attribute_q == attribute_group_q) + { + return nautilus_file_get_group_name (file); + } + if (attribute_q == attribute_uri_q) + { + return nautilus_file_get_uri (file); + } + if (attribute_q == attribute_where_q) + { + return nautilus_file_get_where_string (file); + } + if (attribute_q == attribute_link_target_q) + { + return nautilus_file_get_symbolic_link_target_path (file); + } + if (attribute_q == attribute_volume_q) + { + return nautilus_file_get_volume_name (file); + } + if (attribute_q == attribute_free_space_q) + { + return nautilus_file_get_volume_free_space (file); + } + + extension_attribute = NULL; + + if (file->details->pending_extension_attributes) + { + extension_attribute = g_hash_table_lookup (file->details->pending_extension_attributes, + GINT_TO_POINTER (attribute_q)); + } + + if (extension_attribute == NULL && file->details->extension_attributes) + { + extension_attribute = g_hash_table_lookup (file->details->extension_attributes, + GINT_TO_POINTER (attribute_q)); + } + + return g_strdup (extension_attribute); +} + +char * +nautilus_file_get_string_attribute (NautilusFile *file, + const char *attribute_name) +{ + g_return_val_if_fail (NAUTILUS_IS_FILE (file), NULL); + + return nautilus_file_info_get_string_attribute (NAUTILUS_FILE_INFO (file), attribute_name); +} + + +/** + * nautilus_file_get_string_attribute_with_default: + * + * Get a user-displayable string from a named attribute. Use g_free to + * free this string. If the value is unknown, returns a string representing + * the unknown value, which varies with attribute. You can call + * nautilus_file_get_string_attribute if you want NULL instead of a default + * result. + * + * @file: NautilusFile representing the file in question. + * @attribute_name: The name of the desired attribute. See the description of + * nautilus_file_get_string for the set of available attributes. + * + * Returns: Newly allocated string ready to display to the user, or a string + * such as "unknown" if the value is unknown or @attribute_name is not supported. + * + **/ +char * +nautilus_file_get_string_attribute_with_default_q (NautilusFile *file, + GQuark attribute_q) +{ + char *result; + guint item_count; + gboolean count_unreadable; + NautilusRequestStatus status; + + result = nautilus_file_get_string_attribute_q (file, attribute_q); + if (result != NULL) + { + return result; + } + + /* Supply default values for the ones we know about. */ + /* FIXME bugzilla.gnome.org 40646: + * Use hash table and switch statement or function pointers for speed? + */ + if (attribute_q == attribute_size_q) + { + if (!nautilus_file_should_show_directory_item_count (file)) + { + return g_strdup ("—"); + } + count_unreadable = FALSE; + if (nautilus_file_is_directory (file)) + { + nautilus_file_get_directory_item_count (file, &item_count, &count_unreadable); + } + return g_strdup (count_unreadable ? "—" : "…"); + } + if (attribute_q == attribute_deep_size_q) + { + status = nautilus_file_get_deep_counts (file, NULL, NULL, NULL, NULL, FALSE); + if (status == NAUTILUS_REQUEST_DONE) + { + /* This means no contents at all were readable */ + return g_strdup (_("? bytes")); + } + return g_strdup ("…"); + } + if (attribute_q == attribute_deep_file_count_q + || attribute_q == attribute_deep_directory_count_q + || attribute_q == attribute_deep_total_count_q) + { + status = nautilus_file_get_deep_counts (file, NULL, NULL, NULL, NULL, FALSE); + if (status == NAUTILUS_REQUEST_DONE) + { + /* This means no contents at all were readable */ + return g_strdup (_("? items")); + } + return g_strdup ("…"); + } + if (attribute_q == attribute_type_q + || attribute_q == attribute_detailed_type_q + || attribute_q == attribute_mime_type_q) + { + /* Translators: This about a file type. */ + return g_strdup (_("Unknown type")); + } + if (attribute_q == attribute_trashed_on_q) + { + /* If n/a */ + return g_strdup (""); + } + if (attribute_q == attribute_trash_orig_path_q) + { + /* If n/a */ + return g_strdup (""); + } + if (attribute_q == attribute_recency_q) + { + /* If n/a */ + return g_strdup (""); + } + if (attribute_q == attribute_starred_q) + { + /* If n/a */ + return g_strdup (""); + } + if (attribute_q == attribute_date_created_full_q) + { + /* If n/a */ + return g_strdup ("—"); + } + + /* Fallback, use for both unknown attributes and attributes + * for which we have no more appropriate default. + */ + return g_strdup (_("Unknown")); +} + +char * +nautilus_file_get_string_attribute_with_default (NautilusFile *file, + const char *attribute_name) +{ + return nautilus_file_get_string_attribute_with_default_q (file, g_quark_from_string (attribute_name)); +} + +gboolean +nautilus_file_is_date_sort_attribute_q (GQuark attribute_q) +{ + if (attribute_q == attribute_modification_date_q || + attribute_q == attribute_date_modified_q || + attribute_q == attribute_date_modified_full_q || + attribute_q == attribute_date_modified_with_time_q || + attribute_q == attribute_accessed_date_q || + attribute_q == attribute_date_accessed_q || + attribute_q == attribute_date_accessed_full_q || + attribute_q == attribute_date_created_q || + attribute_q == attribute_date_created_full_q || + attribute_q == attribute_trashed_on_q || + attribute_q == attribute_trashed_on_full_q || + attribute_q == attribute_recency_q) + { + return TRUE; + } + + return FALSE; +} + +struct +{ + const char *icon_name; + const char *display_name; +} mime_type_map[] = +{ + { "application-x-executable", N_("Program") }, + { "audio-x-generic", N_("Audio") }, + { "font-x-generic", N_("Font") }, + { "image-x-generic", N_("Image") }, + { "package-x-generic", N_("Archive") }, + { "text-html", N_("Markup") }, + { "text-x-generic", N_("Text") }, + { "text-x-generic-template", N_("Text") }, + { "text-x-script", N_("Program") }, + { "video-x-generic", N_("Video") }, + { "x-office-address-book", N_("Contacts") }, + { "x-office-calendar", N_("Calendar") }, + { "x-office-document", N_("Document") }, + { "x-office-presentation", N_("Presentation") }, + { "x-office-spreadsheet", N_("Spreadsheet") }, +}; + +static char * +get_basic_type_for_mime_type (const char *mime_type) +{ + char *icon_name; + char *basic_type = NULL; + + icon_name = g_content_type_get_generic_icon_name (mime_type); + if (icon_name != NULL) + { + int i; + + for (i = 0; i < G_N_ELEMENTS (mime_type_map); i++) + { + if (strcmp (mime_type_map[i].icon_name, icon_name) == 0) + { + basic_type = g_strdup (gettext (mime_type_map[i].display_name)); + break; + } + } + } + + if (basic_type == NULL) + { + /* Refers to a file type which is known but not one of the basic types */ + basic_type = g_strdup (_("Other")); + } + + g_free (icon_name); + + return basic_type; +} + +static char * +get_description (NautilusFile *file, + gboolean detailed) +{ + const char *mime_type; + + g_assert (NAUTILUS_IS_FILE (file)); + + mime_type = file->details->mime_type; + if (mime_type == NULL) + { + return NULL; + } + + if (g_content_type_is_unknown (mime_type)) + { + if (nautilus_file_is_executable (file)) + { + return g_strdup (_("Program")); + } + return g_strdup (_("Binary")); + } + + if (strcmp (mime_type, "inode/directory") == 0) + { + return g_strdup (_("Folder")); + } + + if (detailed) + { + char *description; + + description = g_content_type_get_description (mime_type); + if (description != NULL) + { + return description; + } + } + else + { + char *category; + + category = get_basic_type_for_mime_type (mime_type); + if (category != NULL) + { + return category; + } + } + + return g_strdup (mime_type); +} + +/* Takes ownership of string */ +static char * +update_description_for_link (NautilusFile *file, + char *string) +{ + char *res; + + if (nautilus_file_is_symbolic_link (file)) + { + g_assert (!nautilus_file_is_broken_symbolic_link (file)); + if (string == NULL) + { + return g_strdup (_("Link")); + } + /* Note to localizers: convert file type string for file + * (e.g. "folder", "plain text") to file type for symbolic link + * to that kind of file (e.g. "link to folder"). + */ + res = g_strdup_printf (_("Link to %s"), string); + g_free (string); + return res; + } + + return string; +} + +static char * +nautilus_file_get_type_as_string (NautilusFile *file) +{ + if (file == NULL) + { + return NULL; + } + + if (nautilus_file_is_broken_symbolic_link (file)) + { + return g_strdup (_("Link (broken)")); + } + + return update_description_for_link (file, get_description (file, FALSE)); +} + +static char * +nautilus_file_get_type_as_string_no_extra_text (NautilusFile *file) +{ + if (file == NULL) + { + return NULL; + } + + if (nautilus_file_is_broken_symbolic_link (file)) + { + return g_strdup (_("Link (broken)")); + } + + return get_description (file, FALSE); +} + +static char * +nautilus_file_get_detailed_type_as_string (NautilusFile *file) +{ + if (file == NULL) + { + return NULL; + } + + if (nautilus_file_is_broken_symbolic_link (file)) + { + return g_strdup (_("Link (broken)")); + } + + return update_description_for_link (file, get_description (file, TRUE)); +} + +/** + * nautilus_file_get_file_type + * + * Return this file's type. + * @file: NautilusFile representing the file in question. + * + * Returns: The type. + * + **/ +GFileType +nautilus_file_get_file_type (NautilusFile *file) +{ + g_return_val_if_fail (NAUTILUS_IS_FILE (file), G_FILE_TYPE_UNKNOWN); + + return nautilus_file_info_get_file_type (NAUTILUS_FILE_INFO (file)); +} + +/** + * nautilus_file_get_mime_type + * + * Return this file's default mime type. + * @file: NautilusFile representing the file in question. + * + * Returns: The mime type. + * + **/ +char * +nautilus_file_get_mime_type (NautilusFile *file) +{ + g_return_val_if_fail (NAUTILUS_IS_FILE (file), NULL); + + return nautilus_file_info_get_mime_type (NAUTILUS_FILE_INFO (file)); +} + +/** + * nautilus_file_is_mime_type + * + * Check whether a file is of a particular MIME type, or inherited + * from it. + * @file: NautilusFile representing the file in question. + * @mime_type: The MIME-type string to test (e.g. "text/plain") + * + * Return value: TRUE if @mime_type exactly matches the + * file's MIME type. + * + **/ +gboolean +nautilus_file_is_mime_type (NautilusFile *file, + const char *mime_type) +{ + g_return_val_if_fail (NAUTILUS_IS_FILE (file), FALSE); + g_return_val_if_fail (mime_type != NULL, FALSE); + + return nautilus_file_info_is_mime_type (NAUTILUS_FILE_INFO (file), mime_type); +} + +char * +nautilus_file_get_extension (NautilusFile *file) +{ + char *name; + char *extension = NULL; + + name = nautilus_file_get_name (file); + if (name != NULL) + { + extension = g_strdup (eel_filename_get_extension_offset (name)); + g_free (name); + } + + return extension; +} + +gboolean +nautilus_file_is_launchable (NautilusFile *file) +{ + gboolean type_can_be_executable; + + type_can_be_executable = FALSE; + if (file->details->mime_type != NULL) + { + type_can_be_executable = + g_content_type_can_be_executable (file->details->mime_type); + } + + return type_can_be_executable && + nautilus_file_can_get_permissions (file) && + nautilus_file_can_execute (file) && + nautilus_file_is_executable (file) && + nautilus_file_is_regular_file (file); +} + +/** + * nautilus_file_is_symbolic_link + * + * Check if this file is a symbolic link. + * @file: NautilusFile representing the file in question. + * + * Returns: True if the file is a symbolic link. + * + **/ +gboolean +nautilus_file_is_symbolic_link (NautilusFile *file) +{ + return file->details->is_symlink; +} + +GMount * +nautilus_file_get_mount (NautilusFile *file) +{ + g_return_val_if_fail (NAUTILUS_IS_FILE (file), NULL); + + return nautilus_file_info_get_mount (NAUTILUS_FILE_INFO (file)); +} + +static void +file_mount_unmounted (GMount *mount, + gpointer data) +{ + NautilusFile *file; + + file = NAUTILUS_FILE (data); + + nautilus_file_invalidate_attributes (file, NAUTILUS_FILE_ATTRIBUTE_MOUNT); +} + +void +nautilus_file_set_mount (NautilusFile *file, + GMount *mount) +{ + if (file->details->mount) + { + g_signal_handlers_disconnect_by_func (file->details->mount, file_mount_unmounted, file); + g_object_unref (file->details->mount); + file->details->mount = NULL; + } + + if (mount) + { + file->details->mount = g_object_ref (mount); + g_signal_connect (mount, "unmounted", + G_CALLBACK (file_mount_unmounted), file); + } +} + +/** + * nautilus_file_is_broken_symbolic_link + * + * Check if this file is a symbolic link with a missing target. + * @file: NautilusFile representing the file in question. + * + * Returns: True if the file is a symbolic link with a missing target. + * + **/ +gboolean +nautilus_file_is_broken_symbolic_link (NautilusFile *file) +{ + if (file == NULL) + { + return FALSE; + } + + g_return_val_if_fail (NAUTILUS_IS_FILE (file), FALSE); + + /* Non-broken symbolic links return the target's type for get_file_type. */ + return nautilus_file_get_file_type (file) == G_FILE_TYPE_SYMBOLIC_LINK; +} + +static void +get_fs_free_cb (GObject *source_object, + GAsyncResult *res, + gpointer user_data) +{ + NautilusFile *file; + guint64 free_space; + GFileInfo *info; + + file = NAUTILUS_FILE (user_data); + + free_space = (guint64) - 1; + info = g_file_query_filesystem_info_finish (G_FILE (source_object), + res, NULL); + if (info) + { + if (g_file_info_has_attribute (info, G_FILE_ATTRIBUTE_FILESYSTEM_FREE)) + { + free_space = g_file_info_get_attribute_uint64 (info, G_FILE_ATTRIBUTE_FILESYSTEM_FREE); + } + g_object_unref (info); + } + + if (file->details->free_space != free_space) + { + file->details->free_space = free_space; + nautilus_file_emit_changed (file); + } + + nautilus_file_unref (file); +} + +/** + * nautilus_file_get_volume_free_space + * Get a nicely formatted char with free space on the file's volume + * @file: NautilusFile representing the file in question. + * + * Returns: newly-allocated copy of file size in a formatted string + */ +char * +nautilus_file_get_volume_free_space (NautilusFile *file) +{ + GFile *location; + char *res; + time_t now; + + now = time (NULL); + /* Update first time and then every 2 seconds */ + if (file->details->free_space_read == 0 || + (now - file->details->free_space_read) > 2) + { + file->details->free_space_read = now; + location = nautilus_file_get_location (file); + g_file_query_filesystem_info_async (location, + G_FILE_ATTRIBUTE_FILESYSTEM_FREE, + 0, NULL, + get_fs_free_cb, + nautilus_file_ref (file)); + g_object_unref (location); + } + + res = NULL; + if (file->details->free_space != (guint64) - 1) + { + g_autofree gchar *size_string = g_format_size (file->details->free_space); + + /* Translators: This refers to available space in a folder; e.g.: 100 MB Free */ + res = g_strdup_printf (_("%s Free"), size_string); + } + + return res; +} + +/** + * nautilus_file_get_volume_name + * Get the path of the volume the file resides on + * @file: NautilusFile representing the file in question. + * + * Returns: newly-allocated copy of the volume name of the target file, + * if the volume name isn't set, it returns the mount path of the volume + */ +char * +nautilus_file_get_volume_name (NautilusFile *file) +{ + GFile *location; + char *res; + GMount *mount; + + res = NULL; + + location = nautilus_file_get_location (file); + mount = g_file_find_enclosing_mount (location, NULL, NULL); + if (mount) + { + res = g_strdup (g_mount_get_name (mount)); + g_object_unref (mount); + } + g_object_unref (location); + + return res; +} + +/** + * nautilus_file_get_symbolic_link_target_path + * + * Get the file path of the target of a symbolic link. It is an error + * to call this function on a file that isn't a symbolic link. + * @file: NautilusFile representing the symbolic link in question. + * + * Returns: newly-allocated copy of the file path of the target of the symbolic link. + */ +char * +nautilus_file_get_symbolic_link_target_path (NautilusFile *file) +{ + if (!nautilus_file_is_symbolic_link (file)) + { + g_warning ("File has symlink target, but is not marked as symlink"); + } + + return g_strdup (file->details->symlink_name); +} + +/** + * nautilus_file_get_symbolic_link_target_uri + * + * Get the uri of the target of a symbolic link. It is an error + * to call this function on a file that isn't a symbolic link. + * @file: NautilusFile representing the symbolic link in question. + * + * Returns: newly-allocated copy of the uri of the target of the symbolic link. + */ +char * +nautilus_file_get_symbolic_link_target_uri (NautilusFile *file) +{ + GFile *location, *parent, *target; + char *target_uri; + + if (!nautilus_file_is_symbolic_link (file)) + { + g_warning ("File has symlink target, but is not marked as symlink"); + } + + if (file->details->symlink_name == NULL) + { + return NULL; + } + else + { + target = NULL; + + location = nautilus_file_get_location (file); + parent = g_file_get_parent (location); + g_object_unref (location); + if (parent) + { + target = g_file_resolve_relative_path (parent, file->details->symlink_name); + g_object_unref (parent); + } + + target_uri = NULL; + if (target) + { + target_uri = g_file_get_uri (target); + g_object_unref (target); + } + return target_uri; + } +} + +/** + * nautilus_file_is_regular_file + * + * Check if this file is a regular file. + * @file: NautilusFile representing the file in question. + * + * Returns: TRUE if @file is a regular file. + * + **/ +gboolean +nautilus_file_is_regular_file (NautilusFile *file) +{ + return nautilus_file_get_file_type (file) == G_FILE_TYPE_REGULAR; +} + +/** + * nautilus_file_is_directory + * + * Check if this file is a directory. + * @file: NautilusFile representing the file in question. + * + * Returns: TRUE if @file is a directory. + * + **/ +gboolean +nautilus_file_is_directory (NautilusFile *file) +{ + g_return_val_if_fail (NAUTILUS_IS_FILE (file), FALSE); + + return nautilus_file_info_is_directory (NAUTILUS_FILE_INFO (file)); +} + +/** + * nautilus_file_is_user_special_directory + * + * Check if this file is a special platform directory. + * @file: NautilusFile representing the file in question. + * @special_directory: GUserDirectory representing the type to test for + * + * Returns: TRUE if @file is a special directory of the given kind. + */ +gboolean +nautilus_file_is_user_special_directory (NautilusFile *file, + GUserDirectory special_directory) +{ + gboolean is_special_dir; + const gchar *special_dir; + + if (nautilus_file_is_home (file)) + { + /* A xdg-user-dir is disabled by setting it to the home directory */ + return FALSE; + } + + special_dir = g_get_user_special_dir (special_directory); + is_special_dir = FALSE; + + if (special_dir) + { + GFile *loc; + GFile *special_gfile; + + loc = nautilus_file_get_location (file); + special_gfile = g_file_new_for_path (special_dir); + is_special_dir = g_file_equal (loc, special_gfile); + g_object_unref (special_gfile); + g_object_unref (loc); + } + + return is_special_dir; +} + +gboolean +nautilus_file_is_archive (NautilusFile *file) +{ + g_autofree char *mime_type = NULL; + + mime_type = nautilus_file_get_mime_type (file); + + return autoar_check_mime_type_supported (mime_type); +} + + +/** + * nautilus_file_is_in_trash + * + * Check if this file is a file in trash. + * @file: NautilusFile representing the file in question. + * + * Returns: TRUE if @file is in a trash. + * + **/ +gboolean +nautilus_file_is_in_trash (NautilusFile *file) +{ + g_assert (NAUTILUS_IS_FILE (file)); + + return nautilus_directory_is_in_trash (file->details->directory); +} + +/** + * nautilus_file_is_in_recent + * + * Check if this file is a file in Recent. + * @file: NautilusFile representing the file in question. + * + * Returns: TRUE if @file is in Recent. + * + **/ +gboolean +nautilus_file_is_in_recent (NautilusFile *file) +{ + g_assert (NAUTILUS_IS_FILE (file)); + + return nautilus_directory_is_in_recent (file->details->directory); +} + +/** + * nautilus_file_is_in_starred + * + * Check if this file is a file in Starred. + * @file: NautilusFile representing the file in question. + * + * Returns: TRUE if @file is in Starred. + * + **/ +gboolean +nautilus_file_is_in_starred (NautilusFile *file) +{ + g_assert (NAUTILUS_IS_FILE (file)); + + return nautilus_directory_is_in_starred (file->details->directory); +} + +/** + * nautilus_file_is_remote + * + * Check if this file is a file in a remote filesystem. + * @file: NautilusFile representing the file in question. + * + * Returns: TRUE if @file is in a remote filesystem. + * + **/ +gboolean +nautilus_file_is_remote (NautilusFile *file) +{ + g_return_val_if_fail (NAUTILUS_IS_FILE (file), FALSE); + + return nautilus_file_get_filesystem_remote (file); +} + +/** + * nautilus_file_is_other_locations + * + * Check if this file is Other Locations. + * @file: NautilusFile representing the file in question. + * + * Returns: TRUE if @file is Other Locations. + * + **/ +gboolean +nautilus_file_is_other_locations (NautilusFile *file) +{ + gboolean is_other_locations; + gchar *uri; + + g_return_val_if_fail (NAUTILUS_IS_FILE (file), FALSE); + + uri = nautilus_file_get_uri (file); + is_other_locations = g_strcmp0 (uri, "other-locations:///") == 0; + + g_free (uri); + + return is_other_locations; +} + +/** + * nautilus_file_is_starred_location + * + * Check if this file is the Favorite location. + * @file: NautilusFile representing the file in question. + * + * Returns: TRUE if @file is the Favorite location. + * + **/ +gboolean +nautilus_file_is_starred_location (NautilusFile *file) +{ + g_autofree gchar *uri = NULL; + + g_return_val_if_fail (NAUTILUS_IS_FILE (file), FALSE); + + uri = nautilus_file_get_uri (file); + + return eel_uri_is_starred (uri); +} + +/** + * nautilus_file_is_in_admin + * + * Check if this file is using admin backend. + * @file: NautilusFile representing the file in question. + * + * Returns: TRUE if @file is using admin backend. + * + **/ +gboolean +nautilus_file_is_in_admin (NautilusFile *file) +{ + g_return_val_if_fail (NAUTILUS_IS_FILE (file), FALSE); + + return nautilus_directory_is_in_admin (file->details->directory); +} + +GError * +nautilus_file_get_file_info_error (NautilusFile *file) +{ + if (!file->details->get_info_failed) + { + return NULL; + } + + return file->details->get_info_error; +} + +/** + * nautilus_file_contains_text + * + * Check if this file contains text. + * This is private and is used to decide whether or not to read the top left text. + * @file: NautilusFile representing the file in question. + * + * Returns: TRUE if @file has a text MIME type. + * + **/ +gboolean +nautilus_file_contains_text (NautilusFile *file) +{ + if (file == NULL) + { + return FALSE; + } + + /* All text files inherit from text/plain */ + return nautilus_file_is_mime_type (file, "text/plain"); +} + +/** + * nautilus_file_is_executable + * + * Check if this file is executable at all. + * @file: NautilusFile representing the file in question. + * + * Returns: TRUE if any of the execute bits are set. FALSE if + * not, or if the permissions are unknown. + * + **/ +gboolean +nautilus_file_is_executable (NautilusFile *file) +{ + if (!file->details->has_permissions) + { + /* File's permissions field is not valid. + * Can't access specific permissions, so return FALSE. + */ + return FALSE; + } + + return file->details->can_execute; +} + +char * +nautilus_file_get_filesystem_id (NautilusFile *file) +{ + return g_strdup (file->details->filesystem_id); +} + +NautilusFile * +nautilus_file_get_trash_original_file (NautilusFile *file) +{ + GFile *location; + NautilusFile *original_file; + + original_file = NULL; + + if (file->details->trash_orig_path != NULL) + { + location = g_file_new_for_path (file->details->trash_orig_path); + original_file = nautilus_file_get (location); + g_object_unref (location); + } + + return original_file; +} + +void +nautilus_file_mark_gone (NautilusFile *file) +{ + NautilusDirectory *directory; + + if (file->details->is_gone) + { + return; + } + + file->details->is_gone = TRUE; + + update_links_if_target (file); + + /* Drop it from the symlink hash ! */ + remove_from_link_hash_table (file); + + /* Removing the file from the directory can result in dropping the last + * reference, and so clearing the info then will result in a crash. + */ + nautilus_file_clear_info (file); + + /* Let the directory know it's gone. */ + directory = file->details->directory; + if (!nautilus_file_is_self_owned (file)) + { + nautilus_directory_remove_file (directory, file); + } + + /* FIXME bugzilla.gnome.org 42429: + * Maybe we can get rid of the name too eventually, but + * for now that would probably require too many if statements + * everywhere anyone deals with the name. Maybe we can give it + * a hard-coded "" name or something. + */ +} + +/** + * nautilus_file_changed + * + * Notify the user that this file has changed. + * @file: NautilusFile representing the file in question. + **/ +void +nautilus_file_changed (NautilusFile *file) +{ + GList fake_list; + + g_return_if_fail (NAUTILUS_IS_FILE (file)); + + if (nautilus_file_is_self_owned (file)) + { + nautilus_file_emit_changed (file); + } + else + { + fake_list.data = file; + fake_list.next = NULL; + fake_list.prev = NULL; + nautilus_directory_emit_change_signals + (file->details->directory, &fake_list); + } +} + +/** + * nautilus_file_updated_deep_count_in_progress + * + * Notify clients that a newer deep count is available for + * the directory in question. + */ +void +nautilus_file_updated_deep_count_in_progress (NautilusFile *file) +{ + GList *link_files, *node; + + g_assert (NAUTILUS_IS_FILE (file)); + g_assert (nautilus_file_is_directory (file)); + + /* Send out a signal. */ + g_signal_emit (file, signals[UPDATED_DEEP_COUNT_IN_PROGRESS], 0, file); + + /* Tell link files pointing to this object about the change. */ + link_files = get_link_files (file); + for (node = link_files; node != NULL; node = node->next) + { + nautilus_file_updated_deep_count_in_progress (NAUTILUS_FILE (node->data)); + } + nautilus_file_list_free (link_files); +} + +/** + * nautilus_file_emit_changed + * + * Emit a file changed signal. + * This can only be called by the directory, since the directory + * also has to emit a files_changed signal. + * + * @file: NautilusFile representing the file in question. + **/ +void +nautilus_file_emit_changed (NautilusFile *file) +{ + GList *link_files, *p; + + g_assert (NAUTILUS_IS_FILE (file)); + + /* Send out a signal. */ + g_signal_emit (file, signals[CHANGED], 0, file); + + /* Tell link files pointing to this object about the change. */ + link_files = get_link_files (file); + for (p = link_files; p != NULL; p = p->next) + { + /* Looking for directly recursive links. */ + g_autolist (NautilusFile) link_targets = NULL; + NautilusDirectory *directory; + + /* Files can be links to themselves. */ + if (p->data == file) + { + continue; + } + + link_targets = get_link_files (p->data); + directory = nautilus_file_get_directory (p->data); + + /* Reiterating (heh) that this will break with more complex cycles. + * Users, stop trying to break things on purpose. + */ + if (g_list_find (link_targets, file) != NULL && + directory == nautilus_file_get_directory (file)) + { + g_signal_emit (p->data, signals[CHANGED], 0, p->data); + continue; + } + + nautilus_file_changed (NAUTILUS_FILE (p->data)); + } + nautilus_file_list_free (link_files); +} + +/** + * nautilus_file_is_gone + * + * Check if a file has already been deleted. + * @file: NautilusFile representing the file in question. + * + * Returns: TRUE if the file is already gone. + **/ +gboolean +nautilus_file_is_gone (NautilusFile *file) +{ + g_return_val_if_fail (NAUTILUS_IS_FILE (file), FALSE); + + return nautilus_file_info_is_gone (NAUTILUS_FILE_INFO (file)); +} + +/** + * nautilus_file_is_not_yet_confirmed + * + * Check if we're in a state where we don't know if a file really + * exists or not, before the initial I/O is complete. + * @file: NautilusFile representing the file in question. + * + * Returns: TRUE if the file is already gone. + **/ +gboolean +nautilus_file_is_not_yet_confirmed (NautilusFile *file) +{ + g_return_val_if_fail (NAUTILUS_IS_FILE (file), FALSE); + + return !file->details->got_file_info; +} + +/** + * nautilus_file_check_if_ready + * + * Check whether the values for a set of file attributes are + * currently available, without doing any additional work. This + * is useful for callers that want to reflect updated information + * when it is ready but don't want to force the work required to + * obtain the information, which might be slow network calls, e.g. + * + * @file: The file being queried. + * @file_attributes: A bit-mask with the desired information. + * + * Return value: TRUE if all of the specified attributes are currently readable. + */ +gboolean +nautilus_file_check_if_ready (NautilusFile *file, + NautilusFileAttributes file_attributes) +{ + /* To be parallel with call_when_ready, return + * TRUE for NULL file. + */ + if (file == NULL) + { + return TRUE; + } + + g_return_val_if_fail (NAUTILUS_IS_FILE (file), FALSE); + + return NAUTILUS_FILE_CLASS (G_OBJECT_GET_CLASS (file))->check_if_ready (file, file_attributes); +} + +void +nautilus_file_call_when_ready (NautilusFile *file, + NautilusFileAttributes file_attributes, + NautilusFileCallback callback, + gpointer callback_data) +{ + if (file == NULL) + { + (*callback)(file, callback_data); + return; + } + + g_return_if_fail (NAUTILUS_IS_FILE (file)); + + NAUTILUS_FILE_CLASS (G_OBJECT_GET_CLASS (file))->call_when_ready + (file, file_attributes, callback, callback_data); +} + +void +nautilus_file_cancel_call_when_ready (NautilusFile *file, + NautilusFileCallback callback, + gpointer callback_data) +{ + g_return_if_fail (callback != NULL); + + if (file == NULL) + { + return; + } + + g_return_if_fail (NAUTILUS_IS_FILE (file)); + + NAUTILUS_FILE_CLASS (G_OBJECT_GET_CLASS (file))->cancel_call_when_ready + (file, callback, callback_data); +} + +static void +invalidate_directory_count (NautilusFile *file) +{ + file->details->directory_count_is_up_to_date = FALSE; +} + +static void +invalidate_deep_counts (NautilusFile *file) +{ + file->details->deep_counts_status = NAUTILUS_REQUEST_NOT_STARTED; +} + +static void +invalidate_mime_list (NautilusFile *file) +{ + file->details->mime_list_is_up_to_date = FALSE; +} + +static void +invalidate_file_info (NautilusFile *file) +{ + file->details->file_info_is_up_to_date = FALSE; +} + +static void +invalidate_thumbnail (NautilusFile *file) +{ + file->details->thumbnail_is_up_to_date = FALSE; +} + +static void +invalidate_mount (NautilusFile *file) +{ + file->details->mount_is_up_to_date = FALSE; +} + +void +nautilus_file_invalidate_extension_info_internal (NautilusFile *file) +{ + if (file->details->pending_info_providers) + { + g_list_free_full (file->details->pending_info_providers, g_object_unref); + } + + file->details->pending_info_providers = + nautilus_module_get_extensions_for_type (NAUTILUS_TYPE_INFO_PROVIDER); +} + +void +nautilus_file_invalidate_attributes_internal (NautilusFile *file, + NautilusFileAttributes file_attributes) +{ + Request request; + + if (file == NULL) + { + return; + } + + request = nautilus_directory_set_up_request (file_attributes); + + if (REQUEST_WANTS_TYPE (request, REQUEST_DIRECTORY_COUNT)) + { + invalidate_directory_count (file); + } + if (REQUEST_WANTS_TYPE (request, REQUEST_DEEP_COUNT)) + { + invalidate_deep_counts (file); + } + if (REQUEST_WANTS_TYPE (request, REQUEST_MIME_LIST)) + { + invalidate_mime_list (file); + } + if (REQUEST_WANTS_TYPE (request, REQUEST_FILE_INFO)) + { + invalidate_file_info (file); + } + if (REQUEST_WANTS_TYPE (request, REQUEST_EXTENSION_INFO)) + { + nautilus_file_invalidate_extension_info_internal (file); + } + if (REQUEST_WANTS_TYPE (request, REQUEST_THUMBNAIL)) + { + invalidate_thumbnail (file); + } + if (REQUEST_WANTS_TYPE (request, REQUEST_MOUNT)) + { + invalidate_mount (file); + } + + /* FIXME bugzilla.gnome.org 45075: implement invalidating metadata */ +} + +gboolean +nautilus_file_is_thumbnailing (NautilusFile *file) +{ + g_return_val_if_fail (NAUTILUS_IS_FILE (file), FALSE); + + return file->details->is_thumbnailing; +} + +void +nautilus_file_set_is_thumbnailing (NautilusFile *file, + gboolean is_thumbnailing) +{ + g_return_if_fail (NAUTILUS_IS_FILE (file)); + + file->details->is_thumbnailing = is_thumbnailing; +} + + +/** + * nautilus_file_invalidate_attributes + * + * Invalidate the specified attributes and force a reload. + * @file: NautilusFile representing the file in question. + * @file_attributes: attributes to froget. + **/ + +void +nautilus_file_invalidate_attributes (NautilusFile *file, + NautilusFileAttributes file_attributes) +{ + /* Cancel possible in-progress loads of any of these attributes */ + nautilus_directory_cancel_loading_file_attributes (file->details->directory, + file, + file_attributes); + + /* Actually invalidate the values */ + nautilus_file_invalidate_attributes_internal (file, file_attributes); + + nautilus_directory_add_file_to_work_queue (file->details->directory, file); + + /* Kick off I/O if necessary */ + nautilus_directory_async_state_changed (file->details->directory); +} + +NautilusFileAttributes +nautilus_file_get_all_attributes (void) +{ + return NAUTILUS_FILE_ATTRIBUTE_INFO | + NAUTILUS_FILE_ATTRIBUTE_DEEP_COUNTS | + NAUTILUS_FILE_ATTRIBUTE_DIRECTORY_ITEM_COUNT | + NAUTILUS_FILE_ATTRIBUTE_DIRECTORY_ITEM_MIME_TYPES | + NAUTILUS_FILE_ATTRIBUTE_EXTENSION_INFO | + NAUTILUS_FILE_ATTRIBUTE_THUMBNAIL | + NAUTILUS_FILE_ATTRIBUTE_MOUNT; +} + +void +nautilus_file_invalidate_all_attributes (NautilusFile *file) +{ + NautilusFileAttributes all_attributes; + + all_attributes = nautilus_file_get_all_attributes (); + nautilus_file_invalidate_attributes (file, all_attributes); +} + + +/** + * nautilus_file_dump + * + * Debugging call, prints out the contents of the file + * fields. + * + * @file: file to dump. + **/ +void +nautilus_file_dump (NautilusFile *file) +{ + long size = file->details->deep_size; + char *uri; + const char *file_kind; + + uri = nautilus_file_get_uri (file); + g_print ("uri: %s \n", uri); + if (!file->details->got_file_info) + { + g_print ("no file info \n"); + } + else if (file->details->get_info_failed) + { + g_print ("failed to get file info \n"); + } + else + { + g_print ("size: %ld \n", size); + switch (file->details->type) + { + case G_FILE_TYPE_REGULAR: + { + file_kind = "regular file"; + } + break; + + case G_FILE_TYPE_DIRECTORY: + { + file_kind = "folder"; + } + break; + + case G_FILE_TYPE_SPECIAL: + { + file_kind = "special"; + } + break; + + case G_FILE_TYPE_SYMBOLIC_LINK: + { + file_kind = "symbolic link"; + } + break; + + case G_FILE_TYPE_UNKNOWN: + default: + { + file_kind = "unknown"; + } + break; + } + g_print ("kind: %s \n", file_kind); + if (file->details->type == G_FILE_TYPE_SYMBOLIC_LINK) + { + g_print ("link to %s \n", file->details->symlink_name); + /* FIXME bugzilla.gnome.org 42430: add following of symlinks here */ + } + /* FIXME bugzilla.gnome.org 42431: add permissions and other useful stuff here */ + } + g_free (uri); +} + +/** + * nautilus_file_list_ref + * + * Ref all the files in a list. + * @list: GList of files. + **/ +GList * +nautilus_file_list_ref (GList *list) +{ + g_list_foreach (list, (GFunc) nautilus_file_ref, NULL); + return list; +} + +/** + * nautilus_file_list_unref + * + * Unref all the files in a list. + * @list: GList of files. + **/ +void +nautilus_file_list_unref (GList *list) +{ + g_list_foreach (list, (GFunc) nautilus_file_unref, NULL); +} + +/** + * nautilus_file_list_free + * + * Free a list of files after unrefing them. + * @list: GList of files. + **/ +void +nautilus_file_list_free (GList *list) +{ + nautilus_file_list_unref (list); + g_list_free (list); +} + +/** + * nautilus_file_list_copy + * + * Copy the list of files, making a new ref of each, + * @list: GList of files. + **/ +GList * +nautilus_file_list_copy (GList *list) +{ + return g_list_copy (nautilus_file_list_ref (list)); +} + +static gboolean +get_attributes_for_default_sort_type (NautilusFile *file, + gboolean *is_recent, + gboolean *is_download, + gboolean *is_trash, + gboolean *is_search) +{ + gboolean is_recent_dir, is_download_dir, is_trash_dir, is_search_dir, retval; + + *is_recent = FALSE; + *is_download = FALSE; + *is_trash = FALSE; + *is_search = FALSE; + retval = FALSE; + + /* special handling for certain directories */ + if (file && nautilus_file_is_directory (file)) + { + is_recent_dir = + nautilus_file_is_in_recent (file); + is_download_dir = + nautilus_file_is_user_special_directory (file, G_USER_DIRECTORY_DOWNLOAD); + is_trash_dir = + nautilus_file_is_in_trash (file); + is_search_dir = + nautilus_file_is_in_search (file); + + if (is_download_dir) + { + *is_download = TRUE; + retval = TRUE; + } + else if (is_trash_dir) + { + *is_trash = TRUE; + retval = TRUE; + } + else if (is_recent_dir) + { + *is_recent = TRUE; + retval = TRUE; + } + else if (is_search_dir) + { + *is_search = TRUE; + retval = TRUE; + } + } + + return retval; +} +/** + * nautilus_file_get_default_sort_type: + * @file: A #NautilusFile representing a location + * @reversed: (out): Location to store whether the order is reversed by default. + * + * Gets which sort order applies by default for the provided locations. + * + * If @file is a location with special needs (e.g. Trash or Recent), the return + * value and @reversed flag are dictated by design. Otherwise, they stem from + * the "default-sort-order" and "default-sort-in-reverse-order" preference keys. + * + * Returns: The default #NautilusFileSortType for this @file. + */ +NautilusFileSortType +nautilus_file_get_default_sort_type (NautilusFile *file, + gboolean *reversed) +{ + NautilusFileSortType retval; + gboolean is_recent; + gboolean is_download; + gboolean is_trash; + gboolean is_search; + gboolean res; + + retval = g_settings_get_enum (nautilus_preferences, + NAUTILUS_PREFERENCES_DEFAULT_SORT_ORDER); + is_recent = FALSE; + is_download = FALSE; + is_trash = FALSE; + is_search = FALSE; + res = get_attributes_for_default_sort_type (file, &is_recent, &is_download, &is_trash, &is_search); + + if (res) + { + if (is_recent) + { + retval = NAUTILUS_FILE_SORT_BY_RECENCY; + } + else if (is_download) + { + retval = NAUTILUS_FILE_SORT_BY_MTIME; + } + else if (is_trash) + { + retval = NAUTILUS_FILE_SORT_BY_TRASHED_TIME; + } + else if (is_search) + { + retval = NAUTILUS_FILE_SORT_BY_SEARCH_RELEVANCE; + } + else + { + g_assert_not_reached (); + } + + if (reversed != NULL) + { + *reversed = res; + } + } + else + { + if (reversed != NULL) + { + *reversed = g_settings_get_boolean (nautilus_preferences, + NAUTILUS_PREFERENCES_DEFAULT_SORT_IN_REVERSE_ORDER); + } + } + + return retval; +} + +static int +compare_by_display_name_cover (gconstpointer a, + gconstpointer b) +{ + return compare_by_display_name (NAUTILUS_FILE (a), NAUTILUS_FILE (b)); +} + +/** + * nautilus_file_list_sort_by_display_name + * + * Sort the list of files by file name. + * @list: GList of files. + **/ +GList * +nautilus_file_list_sort_by_display_name (GList *list) +{ + return g_list_sort (list, compare_by_display_name_cover); +} + +static GList *ready_data_list = NULL; + +typedef struct +{ + GList *file_list; + GList *remaining_files; + NautilusFileListCallback callback; + gpointer callback_data; +} FileListReadyData; + +static void +file_list_ready_data_free (FileListReadyData *data) +{ + GList *l; + + l = g_list_find (ready_data_list, data); + if (l != NULL) + { + ready_data_list = g_list_delete_link (ready_data_list, l); + + nautilus_file_list_free (data->file_list); + g_list_free (data->remaining_files); + g_free (data); + } +} + +static FileListReadyData * +file_list_ready_data_new (GList *file_list, + NautilusFileListCallback callback, + gpointer callback_data) +{ + FileListReadyData *data; + + data = g_new0 (FileListReadyData, 1); + data->file_list = nautilus_file_list_copy (file_list); + data->remaining_files = g_list_copy (file_list); + data->callback = callback; + data->callback_data = callback_data; + + ready_data_list = g_list_prepend (ready_data_list, data); + + return data; +} + +static void +file_list_file_ready_callback (NautilusFile *file, + gpointer user_data) +{ + FileListReadyData *data; + + data = user_data; + data->remaining_files = g_list_remove (data->remaining_files, file); + + if (data->remaining_files == NULL) + { + if (data->callback) + { + (*data->callback)(data->file_list, data->callback_data); + } + + file_list_ready_data_free (data); + } +} + +void +nautilus_file_list_call_when_ready (GList *file_list, + NautilusFileAttributes attributes, + NautilusFileListHandle **handle, + NautilusFileListCallback callback, + gpointer callback_data) +{ + GList *l; + FileListReadyData *data; + NautilusFile *file; + + g_return_if_fail (file_list != NULL); + + data = file_list_ready_data_new + (file_list, callback, callback_data); + + if (handle) + { + *handle = (NautilusFileListHandle *) data; + } + + + l = file_list; + while (l != NULL) + { + file = NAUTILUS_FILE (l->data); + /* Need to do this here, as the list can be modified by this call */ + l = l->next; + nautilus_file_call_when_ready (file, + attributes, + file_list_file_ready_callback, + data); + } +} + +void +nautilus_file_list_cancel_call_when_ready (NautilusFileListHandle *handle) +{ + GList *l; + NautilusFile *file; + FileListReadyData *data; + + g_return_if_fail (handle != NULL); + + data = (FileListReadyData *) handle; + + l = g_list_find (ready_data_list, data); + if (l != NULL) + { + for (l = data->remaining_files; l != NULL; l = l->next) + { + file = NAUTILUS_FILE (l->data); + + NAUTILUS_FILE_CLASS (G_OBJECT_GET_CLASS (file))->cancel_call_when_ready + (file, file_list_file_ready_callback, data); + } + + file_list_ready_data_free (data); + } +} + +static void +thumbnail_limit_changed_callback (gpointer user_data) +{ + cached_thumbnail_limit = g_settings_get_uint64 (nautilus_preferences, + NAUTILUS_PREFERENCES_FILE_THUMBNAIL_LIMIT); + + /*Converts the obtained limit in MB to bytes */ + cached_thumbnail_limit *= MEGA_TO_BASE_RATE; + + /* Tell the world that icons might have changed. We could invent a narrower-scope + * signal to mean only "thumbnails might have changed" if this ends up being slow + * for some reason. + */ + emit_change_signals_for_all_files_in_all_directories (); +} + +static void +show_thumbnails_changed_callback (gpointer user_data) +{ + show_file_thumbs = g_settings_get_enum (nautilus_preferences, NAUTILUS_PREFERENCES_SHOW_FILE_THUMBNAILS); + + /* Tell the world that icons might have changed. We could invent a narrower-scope + * signal to mean only "thumbnails might have changed" if this ends up being slow + * for some reason. + */ + emit_change_signals_for_all_files_in_all_directories (); +} + +static void +mime_type_data_changed_callback (GObject *signaller, + gpointer user_data) +{ + /* Tell the world that icons might have changed. We could invent a narrower-scope + * signal to mean only "thumbnails might have changed" if this ends up being slow + * for some reason. + */ + emit_change_signals_for_all_files_in_all_directories (); +} + +static gboolean +real_get_item_count (NautilusFile *file, + guint *count, + gboolean *count_unreadable) +{ + if (count_unreadable != NULL) + { + *count_unreadable = file->details->directory_count_failed; + } + + if (!file->details->got_directory_count) + { + if (count != NULL) + { + *count = 0; + } + return FALSE; + } + + if (count != NULL) + { + *count = file->details->directory_count; + } + + return TRUE; +} + +static NautilusRequestStatus +real_get_deep_counts (NautilusFile *file, + guint *directory_count, + guint *file_count, + guint *unreadable_directory_count, + goffset *total_size) +{ + GFileType type; + + type = nautilus_file_get_file_type (file); + + if (directory_count != NULL) + { + *directory_count = 0; + } + if (file_count != NULL) + { + *file_count = 0; + } + if (unreadable_directory_count != NULL) + { + *unreadable_directory_count = 0; + } + if (total_size != NULL) + { + *total_size = 0; + } + + if (type != G_FILE_TYPE_DIRECTORY) + { + return NAUTILUS_REQUEST_DONE; + } + + if (file->details->deep_counts_status != NAUTILUS_REQUEST_NOT_STARTED) + { + if (directory_count != NULL) + { + *directory_count = file->details->deep_directory_count; + } + if (file_count != NULL) + { + *file_count = file->details->deep_file_count; + } + if (unreadable_directory_count != NULL) + { + *unreadable_directory_count = file->details->deep_unreadable_count; + } + if (total_size != NULL) + { + *total_size = file->details->deep_size; + } + return file->details->deep_counts_status; + } + + /* For directories, or before we know the type, we haven't started. */ + if (type == G_FILE_TYPE_UNKNOWN || type == G_FILE_TYPE_DIRECTORY) + { + return NAUTILUS_REQUEST_NOT_STARTED; + } + + /* For other types, we are done, and the zeros are permanent. */ + return NAUTILUS_REQUEST_DONE; +} + +static void +real_set_metadata (NautilusFile *file, + const char *key, + const char *value) +{ + /* Dummy default impl */ +} + +static void +real_set_metadata_as_list (NautilusFile *file, + const char *key, + char **value) +{ + /* Dummy default impl */ +} + +static void +nautilus_file_class_init (NautilusFileClass *class) +{ + nautilus_file_info_getter = nautilus_file_get_internal; + + attribute_name_q = g_quark_from_static_string ("name"); + attribute_size_q = g_quark_from_static_string ("size"); + attribute_type_q = g_quark_from_static_string ("type"); + attribute_detailed_type_q = g_quark_from_static_string ("detailed_type"); + attribute_modification_date_q = g_quark_from_static_string ("modification_date"); + attribute_date_modified_q = g_quark_from_static_string ("date_modified"); + attribute_date_modified_full_q = g_quark_from_static_string ("date_modified_full"); + attribute_date_modified_with_time_q = g_quark_from_static_string ("date_modified_with_time"); + attribute_recency_q = g_quark_from_static_string ("recency"); + attribute_accessed_date_q = g_quark_from_static_string ("accessed_date"); + attribute_date_accessed_q = g_quark_from_static_string ("date_accessed"); + attribute_date_accessed_full_q = g_quark_from_static_string ("date_accessed_full"); + attribute_date_created_q = g_quark_from_static_string ("date_created"); + attribute_date_created_full_q = g_quark_from_static_string ("date_created_full"); + attribute_mime_type_q = g_quark_from_static_string ("mime_type"); + attribute_size_detail_q = g_quark_from_static_string ("size_detail"); + attribute_deep_size_q = g_quark_from_static_string ("deep_size"); + attribute_deep_file_count_q = g_quark_from_static_string ("deep_file_count"); + attribute_deep_directory_count_q = g_quark_from_static_string ("deep_directory_count"); + attribute_deep_total_count_q = g_quark_from_static_string ("deep_total_count"); + attribute_search_relevance_q = g_quark_from_static_string ("search_relevance"); + attribute_trashed_on_q = g_quark_from_static_string ("trashed_on"); + attribute_trashed_on_full_q = g_quark_from_static_string ("trashed_on_full"); + attribute_trash_orig_path_q = g_quark_from_static_string ("trash_orig_path"); + attribute_permissions_q = g_quark_from_static_string ("permissions"); + attribute_selinux_context_q = g_quark_from_static_string ("selinux_context"); + attribute_octal_permissions_q = g_quark_from_static_string ("octal_permissions"); + attribute_owner_q = g_quark_from_static_string ("owner"); + attribute_group_q = g_quark_from_static_string ("group"); + attribute_uri_q = g_quark_from_static_string ("uri"); + attribute_where_q = g_quark_from_static_string ("where"); + attribute_link_target_q = g_quark_from_static_string ("link_target"); + attribute_volume_q = g_quark_from_static_string ("volume"); + attribute_free_space_q = g_quark_from_static_string ("free_space"); + attribute_starred_q = g_quark_from_static_string ("starred"); + + G_OBJECT_CLASS (class)->finalize = finalize; + G_OBJECT_CLASS (class)->constructor = nautilus_file_constructor; + + class->get_item_count = real_get_item_count; + class->get_deep_counts = real_get_deep_counts; + class->set_metadata = real_set_metadata; + class->set_metadata_as_list = real_set_metadata_as_list; + + signals[CHANGED] = + g_signal_new ("changed", + G_TYPE_FROM_CLASS (class), + G_SIGNAL_RUN_LAST, + G_STRUCT_OFFSET (NautilusFileClass, changed), + NULL, NULL, + g_cclosure_marshal_VOID__VOID, + G_TYPE_NONE, 0); + + signals[UPDATED_DEEP_COUNT_IN_PROGRESS] = + g_signal_new ("updated-deep-count-in-progress", + G_TYPE_FROM_CLASS (class), + G_SIGNAL_RUN_LAST, + G_STRUCT_OFFSET (NautilusFileClass, updated_deep_count_in_progress), + NULL, NULL, + g_cclosure_marshal_VOID__VOID, + G_TYPE_NONE, 0); + + g_type_class_add_private (class, sizeof (NautilusFileDetails)); + + thumbnail_limit_changed_callback (NULL); + g_signal_connect_swapped (nautilus_preferences, + "changed::" NAUTILUS_PREFERENCES_FILE_THUMBNAIL_LIMIT, + G_CALLBACK (thumbnail_limit_changed_callback), + NULL); + show_thumbnails_changed_callback (NULL); + g_signal_connect_swapped (nautilus_preferences, + "changed::" NAUTILUS_PREFERENCES_SHOW_FILE_THUMBNAILS, + G_CALLBACK (show_thumbnails_changed_callback), + NULL); + + g_signal_connect (nautilus_signaller_get_current (), + "mime-data-changed", + G_CALLBACK (mime_type_data_changed_callback), + NULL); +} + +void +nautilus_file_info_providers_done (NautilusFile *file) +{ + g_list_free_full (file->details->extension_emblems, g_free); + file->details->extension_emblems = file->details->pending_extension_emblems; + file->details->pending_extension_emblems = NULL; + + if (file->details->extension_attributes) + { + g_hash_table_destroy (file->details->extension_attributes); + } + + file->details->extension_attributes = file->details->pending_extension_attributes; + file->details->pending_extension_attributes = NULL; + + nautilus_file_changed (file); +} + +static gboolean +is_gone (NautilusFileInfo *file_info) +{ + NautilusFile *file; + + file = NAUTILUS_FILE (file_info); + + return file->details->is_gone; +} + +static char * +get_name (NautilusFileInfo *file_info) +{ + NautilusFile *file; + + file = NAUTILUS_FILE (file_info); + + return g_strdup (file->details->name); +} + +static char * +get_uri (NautilusFileInfo *file_info) +{ + NautilusFile *file; + g_autoptr (GFile) location = NULL; + + file = NAUTILUS_FILE (file_info); + location = nautilus_file_get_location (file); + + return g_file_get_uri (location); +} + +static char * +get_parent_uri (NautilusFileInfo *file_info) +{ + NautilusFile *file; + + file = NAUTILUS_FILE (file_info); + + if (nautilus_file_is_self_owned (file)) + { + /* Callers expect an empty string, not a NULL. */ + return g_strdup (""); + } + + return nautilus_directory_get_uri (file->details->directory); +} + +static char * +get_uri_scheme (NautilusFileInfo *file_info) +{ + NautilusFile *file; + g_autoptr (GFile) location = NULL; + + file = NAUTILUS_FILE (file_info); + + if (file->details->directory == NULL) + { + return NULL; + } + + location = nautilus_directory_get_location (file->details->directory); + if (location == NULL) + { + return NULL; + } + + return g_file_get_uri_scheme (location); +} + +static char * +get_mime_type (NautilusFileInfo *file_info) +{ + NautilusFile *file; + + file = NAUTILUS_FILE (file_info); + + if (file->details->mime_type != NULL) + { + return g_strdup (file->details->mime_type); + } + + return g_strdup ("application/octet-stream"); +} + +static gboolean +is_mime_type (NautilusFileInfo *file_info, + const char *mime_type) +{ + NautilusFile *file; + + file = NAUTILUS_FILE (file_info); + + if (file->details->mime_type == NULL) + { + return FALSE; + } + + return g_content_type_is_a (file->details->mime_type, mime_type); +} + +static gboolean +is_directory (NautilusFileInfo *file_info) +{ + NautilusFile *file; + + file = NAUTILUS_FILE (file_info); + + return nautilus_file_get_file_type (file) == G_FILE_TYPE_DIRECTORY; +} + +static void +add_emblem (NautilusFileInfo *file_info, + const char *emblem_name) +{ + NautilusFile *file; + + file = NAUTILUS_FILE (file_info); + + if (file->details->pending_info_providers) + { + file->details->pending_extension_emblems = g_list_prepend (file->details->pending_extension_emblems, + g_strdup (emblem_name)); + } + else + { + file->details->extension_emblems = g_list_prepend (file->details->extension_emblems, + g_strdup (emblem_name)); + } + + nautilus_file_changed (file); +} + +static char * +get_string_attribute (NautilusFileInfo *file_info, + const char *attribute_name) +{ + NautilusFile *file; + + file = NAUTILUS_FILE (file_info); + + return nautilus_file_get_string_attribute_q (file, g_quark_from_string (attribute_name)); +} + +static void +add_string_attribute (NautilusFileInfo *file_info, + const char *attribute_name, + const char *value) +{ + NautilusFile *file; + + file = NAUTILUS_FILE (file_info); + + if (file->details->pending_info_providers != NULL) + { + /* Lazily create hashtable */ + if (file->details->pending_extension_attributes == NULL) + { + file->details->pending_extension_attributes = + g_hash_table_new_full (g_direct_hash, g_direct_equal, + NULL, + (GDestroyNotify) g_free); + } + g_hash_table_insert (file->details->pending_extension_attributes, + GINT_TO_POINTER (g_quark_from_string (attribute_name)), + g_strdup (value)); + } + else + { + if (file->details->extension_attributes == NULL) + { + file->details->extension_attributes = + g_hash_table_new_full (g_direct_hash, g_direct_equal, + NULL, + (GDestroyNotify) g_free); + } + g_hash_table_insert (file->details->extension_attributes, + GINT_TO_POINTER (g_quark_from_string (attribute_name)), + g_strdup (value)); + } + + nautilus_file_changed (file); +} + +static void +invalidate_extension_info (NautilusFileInfo *file_info) +{ + NautilusFile *file; + + file = NAUTILUS_FILE (file_info); + + nautilus_file_invalidate_attributes (file, NAUTILUS_FILE_ATTRIBUTE_EXTENSION_INFO); +} + +static char * +get_activation_uri (NautilusFileInfo *file_info) +{ + NautilusFile *file; + + file = NAUTILUS_FILE (file_info); + + if (file->details->activation_uri != NULL) + { + return g_strdup (file->details->activation_uri); + } + + return nautilus_file_get_uri (file); +} + +static GFileType +get_file_type (NautilusFileInfo *file_info) +{ + NautilusFile *file; + + file = NAUTILUS_FILE (file_info); + + return file->details->type; +} + +static GFile * +get_location (NautilusFileInfo *file_info) +{ + NautilusFile *file; + g_autoptr (GFile) location = NULL; + + file = NAUTILUS_FILE (file_info); + location = nautilus_directory_get_location (file->details->directory); + + if (nautilus_file_is_self_owned (file)) + { + return g_object_ref (location); + } + + return g_file_get_child (location, file->details->name); +} + +static GFile * +get_parent_location (NautilusFileInfo *file_info) +{ + NautilusFile *file; + + file = NAUTILUS_FILE (file_info); + + if (nautilus_file_is_self_owned (file)) + { + return NULL; + } + + return nautilus_directory_get_location (file->details->directory); +} + +static NautilusFileInfo * +get_parent_info (NautilusFileInfo *file_info) +{ + NautilusFile *file; + NautilusFile *parent_file; + + file = NAUTILUS_FILE (file_info); + + if (nautilus_file_is_self_owned (file)) + { + return NULL; + } + + parent_file = nautilus_directory_get_corresponding_file (file->details->directory); + + return NAUTILUS_FILE_INFO (parent_file); +} + +static GMount * +get_mount (NautilusFileInfo *file_info) +{ + NautilusFile *file; + + file = NAUTILUS_FILE (file_info); + + if (file->details->mount) + { + return g_object_ref (file->details->mount); + } + + return NULL; +} + +static gboolean +can_write (NautilusFileInfo *file_info) +{ + NautilusFile *file; + + file = NAUTILUS_FILE (file_info); + + return file->details->can_write; +} + +static void +nautilus_file_info_iface_init (NautilusFileInfoInterface *iface) +{ + iface->is_gone = is_gone; + + iface->get_name = get_name; + iface->get_uri = get_uri; + iface->get_parent_uri = get_parent_uri; + iface->get_uri_scheme = get_uri_scheme; + + iface->get_mime_type = get_mime_type; + iface->is_mime_type = is_mime_type; + iface->is_directory = is_directory; + + iface->add_emblem = add_emblem; + iface->get_string_attribute = get_string_attribute; + iface->add_string_attribute = add_string_attribute; + iface->invalidate_extension_info = invalidate_extension_info; + + iface->get_activation_uri = get_activation_uri; + + iface->get_file_type = get_file_type; + iface->get_location = get_location; + iface->get_parent_location = get_parent_location; + iface->get_parent_info = get_parent_info; + iface->get_mount = get_mount; + iface->can_write = can_write; +} + +#if !defined (NAUTILUS_OMIT_SELF_CHECK) + +void +nautilus_self_check_file (void) +{ + NautilusFile *file_1; + NautilusFile *file_2; + GList *list; + + /* refcount checks */ + + EEL_CHECK_INTEGER_RESULT (nautilus_directory_number_outstanding (), 0); + + file_1 = nautilus_file_get_by_uri ("file:///home/"); + + EEL_CHECK_INTEGER_RESULT (G_OBJECT (file_1)->ref_count, 1); + EEL_CHECK_INTEGER_RESULT (G_OBJECT (file_1->details->directory)->ref_count, 1); + EEL_CHECK_INTEGER_RESULT (nautilus_directory_number_outstanding (), 1); + + nautilus_file_unref (file_1); + + EEL_CHECK_INTEGER_RESULT (nautilus_directory_number_outstanding (), 0); + + file_1 = nautilus_file_get_by_uri ("file:///etc"); + file_2 = nautilus_file_get_by_uri ("file:///usr"); + + list = NULL; + list = g_list_prepend (list, file_1); + list = g_list_prepend (list, file_2); + + nautilus_file_list_ref (list); + + EEL_CHECK_INTEGER_RESULT (G_OBJECT (file_1)->ref_count, 2); + EEL_CHECK_INTEGER_RESULT (G_OBJECT (file_2)->ref_count, 2); + + nautilus_file_list_unref (list); + + EEL_CHECK_INTEGER_RESULT (G_OBJECT (file_1)->ref_count, 1); + EEL_CHECK_INTEGER_RESULT (G_OBJECT (file_2)->ref_count, 1); + + nautilus_file_list_free (list); + + EEL_CHECK_INTEGER_RESULT (nautilus_directory_number_outstanding (), 0); + + + /* name checks */ + file_1 = nautilus_file_get_by_uri ("file:///home/"); + + EEL_CHECK_STRING_RESULT (nautilus_file_get_name (file_1), "home"); + + EEL_CHECK_BOOLEAN_RESULT (nautilus_file_get_by_uri ("file:///home/") == file_1, TRUE); + nautilus_file_unref (file_1); + + EEL_CHECK_BOOLEAN_RESULT (nautilus_file_get_by_uri ("file:///home") == file_1, TRUE); + nautilus_file_unref (file_1); + + nautilus_file_unref (file_1); + + file_1 = nautilus_file_get_by_uri ("file:///home"); + EEL_CHECK_STRING_RESULT (nautilus_file_get_name (file_1), "home"); + nautilus_file_unref (file_1); + + /* sorting */ + file_1 = nautilus_file_get_by_uri ("file:///etc"); + file_2 = nautilus_file_get_by_uri ("file:///usr"); + + EEL_CHECK_INTEGER_RESULT (G_OBJECT (file_1)->ref_count, 1); + EEL_CHECK_INTEGER_RESULT (G_OBJECT (file_2)->ref_count, 1); + + EEL_CHECK_BOOLEAN_RESULT (nautilus_file_compare_for_sort (file_1, file_2, NAUTILUS_FILE_SORT_BY_DISPLAY_NAME, FALSE, FALSE) < 0, TRUE); + EEL_CHECK_BOOLEAN_RESULT (nautilus_file_compare_for_sort (file_1, file_2, NAUTILUS_FILE_SORT_BY_DISPLAY_NAME, FALSE, TRUE) > 0, TRUE); + EEL_CHECK_BOOLEAN_RESULT (nautilus_file_compare_for_sort (file_1, file_1, NAUTILUS_FILE_SORT_BY_DISPLAY_NAME, FALSE, FALSE) == 0, TRUE); + EEL_CHECK_BOOLEAN_RESULT (nautilus_file_compare_for_sort (file_1, file_1, NAUTILUS_FILE_SORT_BY_DISPLAY_NAME, TRUE, FALSE) == 0, TRUE); + EEL_CHECK_BOOLEAN_RESULT (nautilus_file_compare_for_sort (file_1, file_1, NAUTILUS_FILE_SORT_BY_DISPLAY_NAME, FALSE, TRUE) == 0, TRUE); + EEL_CHECK_BOOLEAN_RESULT (nautilus_file_compare_for_sort (file_1, file_1, NAUTILUS_FILE_SORT_BY_DISPLAY_NAME, TRUE, TRUE) == 0, TRUE); + + nautilus_file_unref (file_1); + nautilus_file_unref (file_2); +} + +#endif /* !NAUTILUS_OMIT_SELF_CHECK */ diff --git a/src/nautilus-file.h b/src/nautilus-file.h new file mode 100644 index 0000000..57cca17 --- /dev/null +++ b/src/nautilus-file.h @@ -0,0 +1,590 @@ +/* + nautilus-file.h: Nautilus file model. + + Copyright (C) 1999, 2000, 2001 Eazel, Inc. + + This program is free software; you can redistribute it and/or + modify it under the terms of the GNU General Public License as + published by the Free Software Foundation; either version 2 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 warranty of + MERCHANTABILITY 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 . + + Author: Darin Adler +*/ + +#pragma once + +#include +#include + +#include "nautilus-types.h" + +/* NautilusFile is an object used to represent a single element of a + * NautilusDirectory. It's lightweight and relies on NautilusDirectory + * to do most of the work. + */ + +/* NautilusFile is defined both here and in nautilus-directory.h. */ +#ifndef NAUTILUS_FILE_DEFINED +#define NAUTILUS_FILE_DEFINED +typedef struct NautilusFile NautilusFile; +#endif + +#define NAUTILUS_TYPE_FILE nautilus_file_get_type() +#define NAUTILUS_FILE(obj) \ + (G_TYPE_CHECK_INSTANCE_CAST ((obj), NAUTILUS_TYPE_FILE, NautilusFile)) +#define NAUTILUS_FILE_CLASS(klass) \ + (G_TYPE_CHECK_CLASS_CAST ((klass), NAUTILUS_TYPE_FILE, NautilusFileClass)) +#define NAUTILUS_IS_FILE(obj) \ + (G_TYPE_CHECK_INSTANCE_TYPE ((obj), NAUTILUS_TYPE_FILE)) +#define NAUTILUS_IS_FILE_CLASS(klass) \ + (G_TYPE_CHECK_CLASS_TYPE ((klass), NAUTILUS_TYPE_FILE)) +#define NAUTILUS_FILE_GET_CLASS(obj) \ + (G_TYPE_INSTANCE_GET_CLASS ((obj), NAUTILUS_TYPE_FILE, NautilusFileClass)) + +typedef enum { + /* These may be set as default-sort-order. When touching this, make sure to + * keep the values in sync with the "org.gnome.nautilus.SortOrder" enum in the + * `data/org.gnome.nautilus.gschema.xml` schemas file, and the attributes[] + * array in `src/nautilus-list-view.c`. + */ + NAUTILUS_FILE_SORT_BY_DISPLAY_NAME = 0, + NAUTILUS_FILE_SORT_BY_SIZE = 1, + NAUTILUS_FILE_SORT_BY_TYPE = 2, + NAUTILUS_FILE_SORT_BY_MTIME = 3, + NAUTILUS_FILE_SORT_BY_ATIME = 4, + NAUTILUS_FILE_SORT_BY_BTIME = 5, + NAUTILUS_FILE_SORT_BY_STARRED = 6, + + /* The following are specific to special locations and as such are not to be + * included in the "org.gnome.nautilus.SortOrder" enum. + */ + NAUTILUS_FILE_SORT_BY_TRASHED_TIME, + NAUTILUS_FILE_SORT_BY_SEARCH_RELEVANCE, + NAUTILUS_FILE_SORT_BY_RECENCY +} NautilusFileSortType; + +typedef enum { + NAUTILUS_REQUEST_NOT_STARTED, + NAUTILUS_REQUEST_IN_PROGRESS, + NAUTILUS_REQUEST_DONE +} NautilusRequestStatus; + +typedef enum { + NAUTILUS_FILE_ICON_FLAGS_NONE = 0, + NAUTILUS_FILE_ICON_FLAGS_USE_THUMBNAILS = (1<<0), + /* uses the icon of the mount if present */ + NAUTILUS_FILE_ICON_FLAGS_USE_MOUNT_ICON = (1<<1), +} NautilusFileIconFlags; + +#define NAUTILUS_THUMBNAIL_MINIMUM_ICON_SIZE 32 + +/* Emblems sometimes displayed for NautilusFiles. Do not localize. */ +#define NAUTILUS_FILE_EMBLEM_NAME_SYMBOLIC_LINK "symbolic-link" +#define NAUTILUS_FILE_EMBLEM_NAME_CANT_READ "unreadable" +#define NAUTILUS_FILE_EMBLEM_NAME_CANT_WRITE "readonly" +#define NAUTILUS_FILE_EMBLEM_NAME_TRASH "trash" +#define NAUTILUS_FILE_EMBLEM_NAME_NOTE "note" + +typedef void (*NautilusFileCallback) (NautilusFile *file, + gpointer callback_data); +typedef gboolean (*NautilusFileFilterFunc) (NautilusFile *file, + gpointer callback_data); +typedef void (*NautilusFileListCallback) (GList *file_list, + gpointer callback_data); +typedef void (*NautilusFileOperationCallback) (NautilusFile *file, + GFile *result_location, + GError *error, + gpointer callback_data); + + +#define NAUTILUS_FILE_ATTRIBUTES_FOR_ICON (NAUTILUS_FILE_ATTRIBUTE_INFO | NAUTILUS_FILE_ATTRIBUTE_THUMBNAIL) + +typedef void NautilusFileListHandle; + +/* GObject requirements. */ +GType nautilus_file_get_type (void); + +/* Getting at a single file. */ +NautilusFile * nautilus_file_get (GFile *location); +NautilusFile * nautilus_file_get_by_uri (const char *uri); + +/* Get a file only if the nautilus version already exists */ +NautilusFile * nautilus_file_get_existing (GFile *location); +NautilusFile * nautilus_file_get_existing_by_uri (const char *uri); + +/* Covers for g_object_ref and g_object_unref that provide two conveniences: + * 1) Using these is type safe. + * 2) You are allowed to call these with NULL, + */ +NautilusFile * nautilus_file_ref (NautilusFile *file); +void nautilus_file_unref (NautilusFile *file); + +G_DEFINE_AUTOPTR_CLEANUP_FUNC (NautilusFile, nautilus_file_unref) + +/* Monitor the file. */ +void nautilus_file_monitor_add (NautilusFile *file, + gconstpointer client, + NautilusFileAttributes attributes); +void nautilus_file_monitor_remove (NautilusFile *file, + gconstpointer client); + +/* Waiting for data that's read asynchronously. + * This interface currently works only for metadata, but could be expanded + * to other attributes as well. + */ +void nautilus_file_call_when_ready (NautilusFile *file, + NautilusFileAttributes attributes, + NautilusFileCallback callback, + gpointer callback_data); +void nautilus_file_cancel_call_when_ready (NautilusFile *file, + NautilusFileCallback callback, + gpointer callback_data); +gboolean nautilus_file_check_if_ready (NautilusFile *file, + NautilusFileAttributes attributes); +void nautilus_file_invalidate_attributes (NautilusFile *file, + NautilusFileAttributes attributes); +void nautilus_file_invalidate_all_attributes (NautilusFile *file); + +/* Basic attributes for file objects. */ +gboolean nautilus_file_contains_text (NautilusFile *file); +char * nautilus_file_get_display_name (NautilusFile *file); +char * nautilus_file_get_edit_name (NautilusFile *file); +char * nautilus_file_get_name (NautilusFile *file); +GFile * nautilus_file_get_location (NautilusFile *file); +char * nautilus_file_get_description (NautilusFile *file); +char * nautilus_file_get_uri (NautilusFile *file); +char * nautilus_file_get_uri_scheme (NautilusFile *file); +NautilusFile * nautilus_file_get_parent (NautilusFile *file); +GFile * nautilus_file_get_parent_location (NautilusFile *file); +char * nautilus_file_get_parent_uri (NautilusFile *file); +char * nautilus_file_get_parent_uri_for_display (NautilusFile *file); +char * nautilus_file_get_thumbnail_path (NautilusFile *file); +gboolean nautilus_file_can_get_size (NautilusFile *file); +goffset nautilus_file_get_size (NautilusFile *file); +time_t nautilus_file_get_mtime (NautilusFile *file); +time_t nautilus_file_get_atime (NautilusFile *file); +time_t nautilus_file_get_btime (NautilusFile *file); +time_t nautilus_file_get_recency (NautilusFile *file); +time_t nautilus_file_get_trash_time (NautilusFile *file); +GFileType nautilus_file_get_file_type (NautilusFile *file); +char * nautilus_file_get_mime_type (NautilusFile *file); +char * nautilus_file_get_extension (NautilusFile *file); +gboolean nautilus_file_is_mime_type (NautilusFile *file, + const char *mime_type); +gboolean nautilus_file_is_launchable (NautilusFile *file); +gboolean nautilus_file_is_symbolic_link (NautilusFile *file); +GMount * nautilus_file_get_mount (NautilusFile *file); +char * nautilus_file_get_volume_free_space (NautilusFile *file); +char * nautilus_file_get_volume_name (NautilusFile *file); +char * nautilus_file_get_symbolic_link_target_path (NautilusFile *file); +char * nautilus_file_get_symbolic_link_target_uri (NautilusFile *file); +gboolean nautilus_file_is_broken_symbolic_link (NautilusFile *file); +gboolean nautilus_file_is_executable (NautilusFile *file); +gboolean nautilus_file_is_directory (NautilusFile *file); +gboolean nautilus_file_is_regular_file (NautilusFile *file); +gboolean nautilus_file_is_user_special_directory (NautilusFile *file, + GUserDirectory special_directory); +gboolean nautilus_file_is_archive (NautilusFile *file); +gboolean nautilus_file_is_in_search (NautilusFile *file); +gboolean nautilus_file_is_in_trash (NautilusFile *file); +gboolean nautilus_file_is_in_recent (NautilusFile *file); +gboolean nautilus_file_is_in_starred (NautilusFile *file); +gboolean nautilus_file_is_in_admin (NautilusFile *file); +gboolean nautilus_file_is_remote (NautilusFile *file); +gboolean nautilus_file_is_other_locations (NautilusFile *file); +gboolean nautilus_file_is_starred_location (NautilusFile *file); +gboolean nautilus_file_is_home (NautilusFile *file); +GError * nautilus_file_get_file_info_error (NautilusFile *file); +gboolean nautilus_file_get_directory_item_count (NautilusFile *file, + guint *count, + gboolean *count_unreadable); +void nautilus_file_recompute_deep_counts (NautilusFile *file); +NautilusRequestStatus nautilus_file_get_deep_counts (NautilusFile *file, + guint *directory_count, + guint *file_count, + guint *unreadable_directory_count, + goffset *total_size, + gboolean force); +gboolean nautilus_file_should_show_thumbnail (NautilusFile *file); +gboolean nautilus_file_should_show_directory_item_count (NautilusFile *file); + +void nautilus_file_set_search_relevance (NautilusFile *file, + gdouble relevance); +void nautilus_file_set_search_fts_snippet (NautilusFile *file, + const gchar *fts_snippet); +const gchar* nautilus_file_get_search_fts_snippet (NautilusFile *file); + +void nautilus_file_set_attributes (NautilusFile *file, + GFileInfo *attributes, + NautilusFileOperationCallback callback, + gpointer callback_data); +GFilesystemPreviewType nautilus_file_get_filesystem_use_preview (NautilusFile *file); + +char * nautilus_file_get_filesystem_id (NautilusFile *file); + +char * nautilus_file_get_filesystem_type (NautilusFile *file); + +gboolean nautilus_file_get_filesystem_remote (NautilusFile *file); + +NautilusFile * nautilus_file_get_trash_original_file (NautilusFile *file); + +/* Permissions. */ +gboolean nautilus_file_can_get_permissions (NautilusFile *file); +gboolean nautilus_file_can_set_permissions (NautilusFile *file); +guint nautilus_file_get_permissions (NautilusFile *file); +gboolean nautilus_file_can_get_owner (NautilusFile *file); +gboolean nautilus_file_can_set_owner (NautilusFile *file); +gboolean nautilus_file_can_get_group (NautilusFile *file); +gboolean nautilus_file_can_set_group (NautilusFile *file); +const uid_t nautilus_file_get_uid (NautilusFile *file); +char * nautilus_file_get_owner_name (NautilusFile *file); +const gid_t nautilus_file_get_gid (NautilusFile *file); +char * nautilus_file_get_group_name (NautilusFile *file); +GList * nautilus_get_user_names (void); +GList * nautilus_get_all_group_names (void); +GList * nautilus_file_get_settable_group_names (NautilusFile *file); +gboolean nautilus_file_can_get_selinux_context (NautilusFile *file); +char * nautilus_file_get_selinux_context (NautilusFile *file); + +/* "Capabilities". */ +gboolean nautilus_file_can_read (NautilusFile *file); +gboolean nautilus_file_can_write (NautilusFile *file); +gboolean nautilus_file_can_execute (NautilusFile *file); +gboolean nautilus_file_can_rename (NautilusFile *file); +gboolean nautilus_file_can_delete (NautilusFile *file); +gboolean nautilus_file_can_trash (NautilusFile *file); + +gboolean nautilus_file_can_mount (NautilusFile *file); +gboolean nautilus_file_can_unmount (NautilusFile *file); +gboolean nautilus_file_can_eject (NautilusFile *file); +gboolean nautilus_file_can_start (NautilusFile *file); +gboolean nautilus_file_can_start_degraded (NautilusFile *file); +gboolean nautilus_file_can_stop (NautilusFile *file); +GDriveStartStopType nautilus_file_get_start_stop_type (NautilusFile *file); +gboolean nautilus_file_can_poll_for_media (NautilusFile *file); +gboolean nautilus_file_is_media_check_automatic (NautilusFile *file); + +void nautilus_file_mount (NautilusFile *file, + GMountOperation *mount_op, + GCancellable *cancellable, + NautilusFileOperationCallback callback, + gpointer callback_data); +void nautilus_file_unmount (NautilusFile *file, + GMountOperation *mount_op, + GCancellable *cancellable, + NautilusFileOperationCallback callback, + gpointer callback_data); +void nautilus_file_eject (NautilusFile *file, + GMountOperation *mount_op, + GCancellable *cancellable, + NautilusFileOperationCallback callback, + gpointer callback_data); + +void nautilus_file_start (NautilusFile *file, + GMountOperation *start_op, + GCancellable *cancellable, + NautilusFileOperationCallback callback, + gpointer callback_data); +void nautilus_file_stop (NautilusFile *file, + GMountOperation *mount_op, + GCancellable *cancellable, + NautilusFileOperationCallback callback, + gpointer callback_data); +void nautilus_file_poll_for_media (NautilusFile *file); + +/* Basic operations for file objects. */ +void nautilus_file_set_owner (NautilusFile *file, + const char *user_name_or_id, + NautilusFileOperationCallback callback, + gpointer callback_data); +void nautilus_file_set_group (NautilusFile *file, + const char *group_name_or_id, + NautilusFileOperationCallback callback, + gpointer callback_data); +void nautilus_file_set_permissions (NautilusFile *file, + guint32 permissions, + NautilusFileOperationCallback callback, + gpointer callback_data); +void nautilus_file_rename (NautilusFile *file, + const char *new_name, + NautilusFileOperationCallback callback, + gpointer callback_data); +void nautilus_file_batch_rename (GList *files, + GList *new_names, + NautilusFileOperationCallback callback, + gpointer callback_data); +void nautilus_file_cancel (NautilusFile *file, + NautilusFileOperationCallback callback, + gpointer callback_data); + +/* Return true if this file has already been deleted. + * This object will be unref'd after sending the files_removed signal, + * but it could hang around longer if someone ref'd it. + */ +gboolean nautilus_file_is_gone (NautilusFile *file); + +/* Used in subclasses that handles the rename of a file. This handles the case + * when the file is gone. If this returns TRUE, simply do nothing + */ +gboolean nautilus_file_rename_handle_file_gone (NautilusFile *file, + NautilusFileOperationCallback callback, + gpointer callback_data); + +/* Return true if this file is not confirmed to have ever really + * existed. This is true when the NautilusFile object has been created, but no I/O + * has yet confirmed the existence of a file by that name. + */ +gboolean nautilus_file_is_not_yet_confirmed (NautilusFile *file); + +/* Simple getting and setting top-level metadata. */ +char * nautilus_file_get_metadata (NautilusFile *file, + const char *key, + const char *default_metadata); +gchar ** nautilus_file_get_metadata_list (NautilusFile *file, + const char *key); +void nautilus_file_set_metadata (NautilusFile *file, + const char *key, + const char *default_metadata, + const char *metadata); +void nautilus_file_set_metadata_list (NautilusFile *file, + const char *key, + gchar **list); + +/* Covers for common data types. */ +gboolean nautilus_file_get_boolean_metadata (NautilusFile *file, + const char *key, + gboolean default_metadata); +void nautilus_file_set_boolean_metadata (NautilusFile *file, + const char *key, + gboolean default_metadata, + gboolean metadata); +int nautilus_file_get_integer_metadata (NautilusFile *file, + const char *key, + int default_metadata); +void nautilus_file_set_integer_metadata (NautilusFile *file, + const char *key, + int default_metadata, + int metadata); + +#define UNDEFINED_TIME ((time_t) (-1)) + +time_t nautilus_file_get_time_metadata (NautilusFile *file, + const char *key); +void nautilus_file_set_time_metadata (NautilusFile *file, + const char *key, + time_t time); + + +/* Attributes for file objects as user-displayable strings. */ +char * nautilus_file_get_string_attribute (NautilusFile *file, + const char *attribute_name); +char * nautilus_file_get_string_attribute_q (NautilusFile *file, + GQuark attribute_q); +char * nautilus_file_get_string_attribute_with_default (NautilusFile *file, + const char *attribute_name); +char * nautilus_file_get_string_attribute_with_default_q (NautilusFile *file, + GQuark attribute_q); + +/* Matching with another URI. */ +gboolean nautilus_file_matches_uri (NautilusFile *file, + const char *uri); + +gboolean nautilus_file_has_local_path (NautilusFile *file); + +/* Comparing two file objects for sorting */ +NautilusFileSortType nautilus_file_get_default_sort_type (NautilusFile *file, + gboolean *reversed); + +int nautilus_file_compare_for_sort (NautilusFile *file_1, + NautilusFile *file_2, + NautilusFileSortType sort_type, + gboolean directories_first, + gboolean reversed); +int nautilus_file_compare_for_sort_by_attribute (NautilusFile *file_1, + NautilusFile *file_2, + const char *attribute, + gboolean directories_first, + gboolean reversed); +int nautilus_file_compare_for_sort_by_attribute_q (NautilusFile *file_1, + NautilusFile *file_2, + GQuark attribute, + gboolean directories_first, + gboolean reversed); +gboolean nautilus_file_is_date_sort_attribute_q (GQuark attribute); + +int nautilus_file_compare_location (NautilusFile *file_1, + NautilusFile *file_2); + +/* Compare display name of file with string for equality */ +int nautilus_file_compare_display_name (NautilusFile *file, + const char *string); + +/* filtering functions for use by various directory views */ +gboolean nautilus_file_is_hidden_file (NautilusFile *file); +gboolean nautilus_file_should_show (NautilusFile *file, + gboolean show_hidden); +GList *nautilus_file_list_filter_hidden (GList *files, + gboolean show_hidden); + + +/* Get the URI that's used when activating the file. + * Getting this can require reading the contents of the file. + */ +gboolean nautilus_file_has_activation_uri (NautilusFile *file); +char * nautilus_file_get_activation_uri (NautilusFile *file); +GFile * nautilus_file_get_activation_location (NautilusFile *file); +GIcon * nautilus_file_get_gicon (NautilusFile *file, + NautilusFileIconFlags flags); +NautilusIconInfo * nautilus_file_get_icon (NautilusFile *file, + int size, + int scale, + NautilusFileIconFlags flags); +GdkTexture * nautilus_file_get_icon_texture (NautilusFile *file, + int size, + int scale, + NautilusFileIconFlags flags); +GdkPaintable * nautilus_file_get_icon_paintable (NautilusFile *file, + int size, + int scale, + NautilusFileIconFlags flags); + +GList * nautilus_file_get_emblem_icons (NautilusFile *file); + +/* Whether the file should open inside a view */ +gboolean nautilus_file_opens_in_view (NautilusFile *file); +/* Thumbnailing handling */ +gboolean nautilus_file_is_thumbnailing (NautilusFile *file); + +/* Convenience functions for dealing with a list of NautilusFile objects that each have a ref. + * These are just convenient names for functions that work on lists of GtkObject *. + */ +GList * nautilus_file_list_ref (GList *file_list); +void nautilus_file_list_unref (GList *file_list); +void nautilus_file_list_free (GList *file_list); +GList * nautilus_file_list_copy (GList *file_list); +GList * nautilus_file_list_sort_by_display_name (GList *file_list); +void nautilus_file_list_call_when_ready (GList *file_list, + NautilusFileAttributes attributes, + NautilusFileListHandle **handle, + NautilusFileListCallback callback, + gpointer callback_data); +void nautilus_file_list_cancel_call_when_ready (NautilusFileListHandle *handle); + +GList * nautilus_file_list_filter (GList *files, + GList **failed, + NautilusFileFilterFunc filter_function, + gpointer user_data); +gboolean nautilus_file_list_are_all_folders (const GList *files); + +/* Debugging */ +void nautilus_file_dump (NautilusFile *file); + +typedef struct NautilusFileDetails NautilusFileDetails; + +struct NautilusFile { + GObject parent_slot; + NautilusFileDetails *details; +}; + +/* This is actually a "protected" type, but it must be here so we can + * compile the get_date function pointer declaration below. + */ +typedef enum { + NAUTILUS_DATE_TYPE_MODIFIED, + NAUTILUS_DATE_TYPE_ACCESSED, + NAUTILUS_DATE_TYPE_CREATED, + NAUTILUS_DATE_TYPE_TRASHED, + NAUTILUS_DATE_TYPE_RECENCY +} NautilusDateType; + +gboolean nautilus_file_get_date (NautilusFile *file, + NautilusDateType date_type, + time_t *date); + +typedef struct { + GObjectClass parent_slot; + + /* Subclasses can set this to something other than G_FILE_TYPE_UNKNOWN and + it will be used as the default file type. This is useful when creating + a "virtual" NautilusFile subclass that you can't actually get real + information about. For exaple NautilusDesktopDirectoryFile. */ + GFileType default_file_type; + + /* Called when the file notices any change. */ + void (* changed) (NautilusFile *file); + + /* Called periodically while directory deep count is being computed. */ + void (* updated_deep_count_in_progress) (NautilusFile *file); + + /* Virtual functions (mainly used for trash directory). */ + void (* monitor_add) (NautilusFile *file, + gconstpointer client, + NautilusFileAttributes attributes); + void (* monitor_remove) (NautilusFile *file, + gconstpointer client); + void (* call_when_ready) (NautilusFile *file, + NautilusFileAttributes attributes, + NautilusFileCallback callback, + gpointer callback_data); + void (* cancel_call_when_ready) (NautilusFile *file, + NautilusFileCallback callback, + gpointer callback_data); + gboolean (* check_if_ready) (NautilusFile *file, + NautilusFileAttributes attributes); + gboolean (* get_item_count) (NautilusFile *file, + guint *count, + gboolean *count_unreadable); + NautilusRequestStatus (* get_deep_counts) (NautilusFile *file, + guint *directory_count, + guint *file_count, + guint *unreadable_directory_count, + goffset *total_size); + gboolean (* get_date) (NautilusFile *file, + NautilusDateType type, + time_t *date); + char * (* get_where_string) (NautilusFile *file); + + void (* set_metadata) (NautilusFile *file, + const char *key, + const char *value); + void (* set_metadata_as_list) (NautilusFile *file, + const char *key, + char **value); + + void (* mount) (NautilusFile *file, + GMountOperation *mount_op, + GCancellable *cancellable, + NautilusFileOperationCallback callback, + gpointer callback_data); + void (* unmount) (NautilusFile *file, + GMountOperation *mount_op, + GCancellable *cancellable, + NautilusFileOperationCallback callback, + gpointer callback_data); + void (* eject) (NautilusFile *file, + GMountOperation *mount_op, + GCancellable *cancellable, + NautilusFileOperationCallback callback, + gpointer callback_data); + + void (* start) (NautilusFile *file, + GMountOperation *start_op, + GCancellable *cancellable, + NautilusFileOperationCallback callback, + gpointer callback_data); + void (* stop) (NautilusFile *file, + GMountOperation *mount_op, + GCancellable *cancellable, + NautilusFileOperationCallback callback, + gpointer callback_data); + + void (* poll_for_media) (NautilusFile *file); +} NautilusFileClass; diff --git a/src/nautilus-files-view-dnd.c b/src/nautilus-files-view-dnd.c new file mode 100644 index 0000000..96a5c82 --- /dev/null +++ b/src/nautilus-files-view-dnd.c @@ -0,0 +1,362 @@ +/* + * nautilus-view-dnd.c: DnD helpers for NautilusFilesView + * + * Copyright (C) 1999, 2000 Free Software Foundaton + * Copyright (C) 2000, 2001 Eazel, Inc. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License as + * published by the Free Software Foundation; either version 2 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 warranty of + * MERCHANTABILITY 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 . + * + * Authors: Ettore Perazzoli + * Darin Adler + * John Sullivan + * Pavel Cisler + */ + +#include + +#include "nautilus-files-view-dnd.h" + +#include "nautilus-files-view.h" +#include "nautilus-application.h" + +#include +#include + +#include + +#include "nautilus-clipboard.h" +#include "nautilus-dnd.h" +#include "nautilus-global-preferences.h" +#include "nautilus-ui-utilities.h" + +#define GET_ANCESTOR(obj) \ + GTK_WINDOW (gtk_widget_get_ancestor (GTK_WIDGET (obj), GTK_TYPE_WINDOW)) + +void +nautilus_files_view_handle_uri_list_drop (NautilusFilesView *view, + const char *item_uris, + const char *target_uri, + GdkDragAction action) +{ + gchar **uri_list; + GList *real_uri_list = NULL; + char *container_uri; + const char *real_target_uri; + int n_uris, i; + + if (item_uris == NULL) + { + return; + } + + container_uri = NULL; + if (target_uri == NULL) + { + container_uri = nautilus_files_view_get_backing_uri (view); + g_assert (container_uri != NULL); + } + + if (action == GDK_ACTION_ASK) + { +#if 0 && NAUTILUS_DND_NEEDS_GTK4_REIMPLEMENTATION + action = nautilus_drag_drop_action_ask + (GTK_WIDGET (view), + GDK_ACTION_MOVE | GDK_ACTION_COPY | GDK_ACTION_LINK); + if (action == 0) +#endif + { + g_free (container_uri); + return; + } + } + + if ((action != GDK_ACTION_COPY) && + (action != GDK_ACTION_MOVE) && + (action != GDK_ACTION_LINK)) + { + show_dialog (_("Drag and drop is not supported."), + _("An invalid drag type was used."), + GET_ANCESTOR (view), + GTK_MESSAGE_WARNING); + g_free (container_uri); + return; + } + + n_uris = 0; + uri_list = g_uri_list_extract_uris (item_uris); + for (i = 0; uri_list[i] != NULL; i++) + { + real_uri_list = g_list_append (real_uri_list, uri_list[i]); + n_uris++; + } + g_free (uri_list); + + /* do nothing if no real uris are left */ + if (n_uris == 0) + { + g_free (container_uri); + return; + } + + real_target_uri = target_uri != NULL ? target_uri : container_uri; + + nautilus_files_view_move_copy_items (view, real_uri_list, + real_target_uri, + action); + + g_list_free_full (real_uri_list, g_free); + + g_free (container_uri); +} + +#define MAX_LEN_FILENAME 64 +#define MIN_LEN_FILENAME 8 + +static char * +get_drop_filename (const char *text) +{ + char *filename; + char trimmed[MAX_LEN_FILENAME]; + int i; + int last_word = -1; + int end_sentence = -1; + int last_nonspace = -1; + int start_sentence = -1; + int num_attrs; + PangoLogAttr *attrs; + gchar *current_char; + + num_attrs = MIN (g_utf8_strlen (text, -1), MAX_LEN_FILENAME) + 1; + attrs = g_new (PangoLogAttr, num_attrs); + g_utf8_strncpy (trimmed, text, num_attrs - 1); + pango_get_log_attrs (trimmed, -1, -1, pango_language_get_default (), attrs, num_attrs); + + /* since the end of the text will always match a word boundary don't include it */ + for (i = 0; (i < num_attrs - 1); i++) + { + if (attrs[i].is_sentence_start && start_sentence == -1) + { + start_sentence = i; + } + if (!attrs[i].is_white) + { + last_nonspace = i; + } + if (attrs[i].is_word_boundary) + { + last_word = last_nonspace; + } + if (attrs[i].is_sentence_end) + { + end_sentence = last_nonspace; + break; + } + } + g_free (attrs); + + if (end_sentence > 0) + { + i = end_sentence; + } + else + { + i = last_word; + } + + if (i - start_sentence > MIN_LEN_FILENAME) + { + g_autofree char *substring = g_utf8_substring (trimmed, start_sentence, i); + filename = g_strdup_printf ("%s.txt", substring); + } + else + { + /* Translator: This is the filename used for when you dnd text to a directory */ + filename = g_strdup (_("Dropped Text.txt")); + } + + /* Remove any invalid characters */ + for (current_char = filename; + *current_char; + current_char = g_utf8_next_char (current_char)) + { + if (G_IS_DIR_SEPARATOR (g_utf8_get_char (current_char))) + { + *current_char = '-'; + } + } + + return filename; +} + +void +nautilus_files_view_handle_text_drop (NautilusFilesView *view, + const char *text, + const char *target_uri, + GdkDragAction action) +{ + int length; + char *container_uri; + char *filename; + + if (text == NULL) + { + return; + } + + g_return_if_fail (action == GDK_ACTION_COPY); + + container_uri = NULL; + if (target_uri == NULL) + { + container_uri = nautilus_files_view_get_backing_uri (view); + g_assert (container_uri != NULL); + } + + length = strlen (text); + + /* try to get text to use as a filename */ + filename = get_drop_filename (text); + + nautilus_files_view_new_file_with_initial_contents (view, + target_uri != NULL ? target_uri : container_uri, + filename, + text, + length); + g_free (filename); + g_free (container_uri); +} + +void +nautilus_files_view_handle_raw_drop (NautilusFilesView *view, + const char *raw_data, + int length, + const char *target_uri, + const char *direct_save_uri, + GdkDragAction action) +{ + char *container_uri, *filename; + GFile *direct_save_full; + + if (raw_data == NULL) + { + return; + } + + g_return_if_fail (action == GDK_ACTION_COPY); + + container_uri = NULL; + if (target_uri == NULL) + { + container_uri = nautilus_files_view_get_backing_uri (view); + g_assert (container_uri != NULL); + } + + filename = NULL; + if (direct_save_uri != NULL) + { + direct_save_full = g_file_new_for_uri (direct_save_uri); + filename = g_file_get_basename (direct_save_full); + } + if (filename == NULL) + { + /* Translator: This is the filename used for when you dnd raw + * data to a directory, if the source didn't supply a name. + */ + filename = g_strdup (_("dropped data")); + } + + nautilus_files_view_new_file_with_initial_contents ( + view, target_uri != NULL ? target_uri : container_uri, + filename, raw_data, length); + + g_free (container_uri); + g_free (filename); +} + +void +nautilus_files_view_drop_proxy_received_uris (NautilusFilesView *view, + const GList *source_uri_list, + const char *target_uri, + GdkDragAction action) +{ + g_autofree char *container_uri = NULL; + g_autoptr (GFile) source_location = g_file_new_for_uri (source_uri_list->data); + g_autoptr (GFile) target_location = g_file_new_for_uri (target_uri); + + if (target_uri == NULL) + { + container_uri = nautilus_files_view_get_backing_uri (view); + g_assert (container_uri != NULL); + } + if (g_file_has_parent (source_location, target_location) && + action & GDK_ACTION_MOVE) + { + /* By default dragging to the same directory is allowed so that + * users can duplicate a file using the CTRL modifier key. Prevent + * an accidental MOVE, by rejecting what would be an error anyways. */ + return; + } + + if (action == GDK_ACTION_ASK) + { +#if 0 && NAUTILUS_DND_NEEDS_GTK4_REIMPLEMENTATION + action = nautilus_drag_drop_action_ask + (GTK_WIDGET (view), + GDK_ACTION_MOVE | GDK_ACTION_COPY | GDK_ACTION_LINK); + if (action == 0) +#endif + { + return; + } + } + +#if 0 && NAUTILUS_DND_NEEDS_GTK4_REIMPLEMENTATION + nautilus_clipboard_clear_if_colliding_uris (GTK_WIDGET (view), + source_uri_list); +#endif + nautilus_files_view_move_copy_items (view, source_uri_list, + target_uri != NULL ? target_uri : container_uri, + action); +} + +void +nautilus_files_view_handle_hover (NautilusFilesView *view, + const char *target_uri) +{ + NautilusWindowSlot *slot; + GFile *location; + GFile *current_location; + NautilusFile *target_file; + gboolean target_is_dir; + gboolean open_folder_on_hover; + + slot = nautilus_files_view_get_nautilus_window_slot (view); + + location = g_file_new_for_uri (target_uri); + target_file = nautilus_file_get_existing (location); + target_is_dir = nautilus_file_get_file_type (target_file) == G_FILE_TYPE_DIRECTORY; + current_location = nautilus_window_slot_get_location (slot); + open_folder_on_hover = g_settings_get_boolean (nautilus_preferences, + NAUTILUS_PREFERENCES_OPEN_FOLDER_ON_DND_HOVER); + + if (target_is_dir && open_folder_on_hover && + !(current_location != NULL && g_file_equal (location, current_location))) + { + nautilus_application_open_location_full (NAUTILUS_APPLICATION (g_application_get_default ()), + location, NAUTILUS_OPEN_FLAG_DONT_MAKE_ACTIVE, + NULL, NULL, slot); + } + g_object_unref (location); + nautilus_file_unref (target_file); +} diff --git a/src/nautilus-files-view-dnd.h b/src/nautilus-files-view-dnd.h new file mode 100644 index 0000000..9cde154 --- /dev/null +++ b/src/nautilus-files-view-dnd.h @@ -0,0 +1,51 @@ + +/* + * nautilus-view-dnd.h: DnD helpers for NautilusFilesView + * + * Copyright (C) 1999, 2000 Free Software Foundaton + * Copyright (C) 2000, 2001 Eazel, Inc. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License as + * published by the Free Software Foundation; either version 2 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 warranty of + * MERCHANTABILITY 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 . + * + * Authors: Ettore Perazzoli + * Darin Adler + * John Sullivan + * Pavel Cisler + */ + +#pragma once + +#include "nautilus-files-view.h" + +void nautilus_files_view_handle_uri_list_drop (NautilusFilesView *view, + const char *item_uris, + const char *target_uri, + GdkDragAction action); +void nautilus_files_view_handle_text_drop (NautilusFilesView *view, + const char *text, + const char *target_uri, + GdkDragAction action); +void nautilus_files_view_handle_raw_drop (NautilusFilesView *view, + const char *raw_data, + int length, + const char *target_uri, + const char *direct_save_uri, + GdkDragAction action); +void nautilus_files_view_handle_hover (NautilusFilesView *view, + const char *target_uri); + +void nautilus_files_view_drop_proxy_received_uris (NautilusFilesView *view, + const GList *uris, + const char *target_location, + GdkDragAction action); diff --git a/src/nautilus-files-view.c b/src/nautilus-files-view.c new file mode 100644 index 0000000..13f256a --- /dev/null +++ b/src/nautilus-files-view.c @@ -0,0 +1,9880 @@ +/* nautilus-files-view.c + * + * Copyright (C) 1999, 2000 Free Software Foundation + * Copyright (C) 2000, 2001 Eazel, Inc. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License as + * published by the Free Software Foundation; either version 2 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 warranty of + * MERCHANTABILITY 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 . + * + * Authors: Ettore Perazzoli, + * John Sullivan , + * Darin Adler , + * Pavel Cisler , + * David Emory Watson + */ + +#include "nautilus-files-view.h" + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#define DEBUG_FLAG NAUTILUS_DEBUG_DIRECTORY_VIEW +#include "nautilus-debug.h" + +#include "nautilus-application.h" +#include "nautilus-app-chooser.h" +#include "nautilus-batch-rename-dialog.h" +#include "nautilus-batch-rename-utilities.h" +#include "nautilus-clipboard.h" +#include "nautilus-compress-dialog-controller.h" +#include "nautilus-dbus-launcher.h" +#include "nautilus-directory.h" +#include "nautilus-dnd.h" +#include "nautilus-enums.h" +#include "nautilus-error-reporting.h" +#include "nautilus-file-changes-queue.h" +#include "nautilus-file-name-widget-controller.h" +#include "nautilus-file-operations.h" +#include "nautilus-file-private.h" +#include "nautilus-file-undo-manager.h" +#include "nautilus-file-utilities.h" +#include "nautilus-file.h" +#include "nautilus-floating-bar.h" +#include "nautilus-global-preferences.h" +#include "nautilus-icon-info.h" +#include "nautilus-icon-names.h" +#include "nautilus-list-view.h" +#include "nautilus-metadata.h" +#include "nautilus-mime-actions.h" +#include "nautilus-module.h" +#include "nautilus-new-folder-dialog-controller.h" +#include "nautilus-previewer.h" +#include "nautilus-profile.h" +#include "nautilus-program-choosing.h" +#include "nautilus-properties-window.h" +#include "nautilus-rename-file-popover-controller.h" +#include "nautilus-search-directory.h" +#include "nautilus-signaller.h" +#include "nautilus-tag-manager.h" +#include "nautilus-toolbar.h" +#include "nautilus-trash-monitor.h" +#include "nautilus-ui-utilities.h" +#include "nautilus-view.h" +#include "nautilus-grid-view.h" +#include "nautilus-window.h" +#include "nautilus-tracker-utilities.h" + +/* Minimum starting update inverval */ +#define UPDATE_INTERVAL_MIN 100 +/* Maximum update interval */ +#define UPDATE_INTERVAL_MAX 2000 +/* Amount of miliseconds the update interval is increased */ +#define UPDATE_INTERVAL_INC 250 +/* Interval at which the update interval is increased */ +#define UPDATE_INTERVAL_TIMEOUT_INTERVAL 250 +/* Milliseconds that have to pass without a change to reset the update interval */ +#define UPDATE_INTERVAL_RESET 1000 + +#define SILENT_WINDOW_OPEN_LIMIT 5 + +#define DUPLICATE_HORIZONTAL_ICON_OFFSET 70 +#define DUPLICATE_VERTICAL_ICON_OFFSET 30 + +#define MAX_QUEUED_UPDATES 500 + +#define MAX_MENU_LEVELS 5 +#define TEMPLATE_LIMIT 30 + +#define SHORTCUTS_PATH "/nautilus/scripts-accels" + +/* Delay to show the Loading... floating bar */ +#define FLOATING_BAR_LOADING_DELAY 200 /* ms */ + +#define MIN_COMMON_FILENAME_PREFIX_LENGTH 4 + +enum +{ + ADD_FILES, + BEGIN_FILE_CHANGES, + BEGIN_LOADING, + CLEAR, + END_FILE_CHANGES, + END_LOADING, + FILE_CHANGED, + MOVE_COPY_ITEMS, + REMOVE_FILE, + SELECTION_CHANGED, + TRASH, + DELETE, + LAST_SIGNAL +}; + +enum +{ + PROP_WINDOW_SLOT = 1, + PROP_SUPPORTS_ZOOMING, + PROP_ICON, + PROP_SEARCHING, + PROP_LOADING, + PROP_SELECTION, + PROP_LOCATION, + PROP_SEARCH_QUERY, + PROP_EXTENSIONS_BACKGROUND_MENU, + PROP_TEMPLATES_MENU, + NUM_PROPERTIES +}; + +static guint signals[LAST_SIGNAL]; + +static char *scripts_directory_uri = NULL; +static int scripts_directory_uri_length; + +static GHashTable *script_accels = NULL; + +typedef struct +{ + /* Main components */ + GtkWidget *overlay; + + NautilusWindowSlot *slot; + NautilusDirectory *model; + NautilusFile *directory_as_file; + GFile *location; + guint dir_merge_id; + + NautilusQuery *search_query; + + NautilusRenameFilePopoverController *rename_file_controller; + NautilusNewFolderDialogController *new_folder_controller; + NautilusCompressDialogController *compress_controller; + + gboolean supports_zooming; + + GList *scripts_directory_list; + GList *templates_directory_list; + gboolean scripts_menu_updated; + gboolean templates_menu_updated; + + guint display_selection_idle_id; + guint update_context_menus_timeout_id; + guint update_status_idle_id; + guint reveal_selection_idle_id; + + guint display_pending_source_id; + guint changes_timeout_id; + + guint update_interval; + guint64 last_queued; + + gulong files_added_handler_id; + gulong files_changed_handler_id; + gulong load_error_handler_id; + gulong done_loading_handler_id; + gulong file_changed_handler_id; + + /* Containers with FileAndDirectory* elements */ + GList *new_added_files; + GList *new_changed_files; + GHashTable *non_ready_files; + GList *old_added_files; + GList *old_changed_files; + + GList *pending_selection; + GHashTable *pending_reveal; + + /* whether we are in the active slot */ + gboolean active; + + /* loading indicates whether this view has begun loading a directory. + * This flag should need not be set inside subclasses. NautilusFilesView automatically + * sets 'loading' to TRUE before it begins loading a directory's contents and to FALSE + * after it finishes loading the directory and its view. + */ + gboolean loading; + + gboolean in_destruction; + + gboolean sort_directories_first; + + gboolean show_hidden_files; + gboolean ignore_hidden_file_preferences; + + gboolean selection_was_removed; + + gboolean metadata_for_directory_as_file_pending; + gboolean metadata_for_files_in_directory_pending; + + GList *subdirectory_list; + + GMenu *selection_menu_model; + GMenu *background_menu_model; + + GtkWidget *selection_menu; + GtkWidget *background_menu; + + GActionGroup *view_action_group; + + GtkWidget *stack; + + GtkWidget *scrolled_window; + + /* Empty states */ + GtkWidget *empty_view_page; + + /* Floating bar */ + guint floating_bar_set_status_timeout_id; + guint floating_bar_loading_timeout_id; + guint floating_bar_set_passthrough_timeout_id; + GtkWidget *floating_bar; + + /* Toolbar menu */ + NautilusToolbarMenuSections *toolbar_menu_sections; + + /* Exposed menus, for the path bar etc. */ + GMenuModel *extensions_background_menu; + GMenuModel *templates_menu; + + /* Non exported menu, only for caching */ + GMenuModel *scripts_menu; + + GCancellable *clipboard_cancellable; + + GCancellable *starred_cancellable; + + gulong name_accepted_handler_id; + gulong cancelled_handler_id; +} NautilusFilesViewPrivate; + +/** + * FileAndDirectory: + * @file: A #NautilusFile + * @directory: A #NautilusDirectory where @file is present. + * + * The #FileAndDirectory struct is used to relate files to the directories they + * are displayed in. This is necessary because the same file can appear multiple + * times in the same view, by expanding folders as a tree in a list of search + * results. (Adapted from commit 671e4bdaa4d07b039015bedfcb5d42026e5d099e) + */ +typedef struct +{ + NautilusFile *file; + NautilusDirectory *directory; +} FileAndDirectory; + +typedef struct +{ + NautilusFilesView *view; + GList *selection; +} CompressCallbackData; + +/* forward declarations */ + +static gboolean display_selection_info_idle_callback (gpointer data); +static void trash_or_delete_files (GtkWindow *parent_window, + const GList *files, + NautilusFilesView *view); +static void load_directory (NautilusFilesView *view, + NautilusDirectory *directory); +static void on_clipboard_owner_changed (GdkClipboard *clipboard, + gpointer user_data); +static void schedule_update_context_menus (NautilusFilesView *view); +static void remove_update_context_menus_timeout_callback (NautilusFilesView *view); +static void schedule_update_status (NautilusFilesView *view); +static void remove_update_status_idle_callback (NautilusFilesView *view); +static void reset_update_interval (NautilusFilesView *view); +static void schedule_idle_display_of_pending_files (NautilusFilesView *view); +static void unschedule_display_of_pending_files (NautilusFilesView *view); +static void disconnect_model_handlers (NautilusFilesView *view); +static void metadata_for_directory_as_file_ready_callback (NautilusFile *file, + gpointer callback_data); +static void metadata_for_files_in_directory_ready_callback (NautilusDirectory *directory, + GList *files, + gpointer callback_data); +static void nautilus_files_view_trash_state_changed_callback (NautilusTrashMonitor *trash, + gboolean state, + gpointer callback_data); +static void nautilus_files_view_select_file (NautilusFilesView *view, + NautilusFile *file); + +static void update_templates_directory (NautilusFilesView *view); + +static void extract_files (NautilusFilesView *view, + GList *files, + GFile *destination_directory); +static void extract_files_to_chosen_location (NautilusFilesView *view, + GList *files); + +static void nautilus_files_view_check_empty_states (NautilusFilesView *view); + +static gboolean nautilus_files_view_is_searching (NautilusView *view); + +static void nautilus_files_view_iface_init (NautilusViewInterface *view); + +static void set_search_query_internal (NautilusFilesView *files_view, + NautilusQuery *query, + NautilusDirectory *base_model); + +static gboolean nautilus_files_view_is_read_only (NautilusFilesView *view); + +G_DEFINE_TYPE_WITH_CODE (NautilusFilesView, + nautilus_files_view, + ADW_TYPE_BIN, + G_IMPLEMENT_INTERFACE (NAUTILUS_TYPE_VIEW, nautilus_files_view_iface_init) + G_ADD_PRIVATE (NautilusFilesView)); + +/* Clipboard async helpers. */ +static void +clipboard_read_value_callback (GObject *source_object, + GAsyncResult *result, + gpointer user_data) +{ + GdkClipboard *clipboard = GDK_CLIPBOARD (source_object); + g_autoptr (GTask) task = user_data; + g_autoptr (GError) error = NULL; + const GValue *value; + + value = gdk_clipboard_read_value_finish (clipboard, result, &error); + + if (error != NULL) + { + g_task_return_error (task, g_steal_pointer (&error)); + } + else + { + g_warn_if_fail (G_VALUE_HOLDS (value, NAUTILUS_TYPE_CLIPBOARD)); + + g_task_return_pointer (task, g_value_get_boxed (value), NULL); + } +} + +void +nautilus_files_view_get_clipboard_async (NautilusFilesView *self, + GAsyncReadyCallback callback, + gpointer callback_data) +{ + NautilusFilesViewPrivate *priv = nautilus_files_view_get_instance_private (self); + GTask *task; + + if (priv->clipboard_cancellable == NULL) + { + priv->clipboard_cancellable = g_cancellable_new (); + } + + task = g_task_new (self, + priv->clipboard_cancellable, + callback, + callback_data); + gdk_clipboard_read_value_async (gtk_widget_get_clipboard (GTK_WIDGET (self)), + NAUTILUS_TYPE_CLIPBOARD, + G_PRIORITY_DEFAULT, + priv->clipboard_cancellable, + clipboard_read_value_callback, + task); +} + +NautilusClipboard * +nautilus_files_view_get_clipboard_finish (NautilusFilesView *self, + GAsyncResult *result, + GError **error) +{ + return g_task_propagate_pointer (G_TASK (result), error); +} + +/* + * Floating Bar code + */ +static void +remove_loading_floating_bar (NautilusFilesView *view) +{ + NautilusFilesViewPrivate *priv; + + priv = nautilus_files_view_get_instance_private (view); + + if (priv->floating_bar_loading_timeout_id != 0) + { + g_source_remove (priv->floating_bar_loading_timeout_id); + priv->floating_bar_loading_timeout_id = 0; + } + + gtk_widget_hide (priv->floating_bar); + nautilus_floating_bar_set_show_stop (NAUTILUS_FLOATING_BAR (priv->floating_bar), FALSE); +} + +static void +real_setup_loading_floating_bar (NautilusFilesView *view) +{ + NautilusFilesViewPrivate *priv; + + priv = nautilus_files_view_get_instance_private (view); + + nautilus_floating_bar_set_primary_label (NAUTILUS_FLOATING_BAR (priv->floating_bar), + nautilus_view_is_searching (NAUTILUS_VIEW (view)) ? _("Searching…") : _("Loading…")); + nautilus_floating_bar_set_details_label (NAUTILUS_FLOATING_BAR (priv->floating_bar), NULL); + nautilus_floating_bar_set_show_spinner (NAUTILUS_FLOATING_BAR (priv->floating_bar), priv->loading); + nautilus_floating_bar_set_show_stop (NAUTILUS_FLOATING_BAR (priv->floating_bar), priv->loading); + + gtk_widget_set_halign (priv->floating_bar, GTK_ALIGN_END); + gtk_widget_show (priv->floating_bar); +} + +static gboolean +setup_loading_floating_bar_timeout_cb (gpointer user_data) +{ + NautilusFilesView *view = user_data; + NautilusFilesViewPrivate *priv; + + priv = nautilus_files_view_get_instance_private (view); + + priv->floating_bar_loading_timeout_id = 0; + real_setup_loading_floating_bar (view); + + return FALSE; +} + +static void +setup_loading_floating_bar (NautilusFilesView *view) +{ + NautilusFilesViewPrivate *priv; + + priv = nautilus_files_view_get_instance_private (view); + + /* setup loading overlay */ + if (priv->floating_bar_set_status_timeout_id != 0) + { + g_source_remove (priv->floating_bar_set_status_timeout_id); + priv->floating_bar_set_status_timeout_id = 0; + } + + if (priv->floating_bar_loading_timeout_id != 0) + { + g_source_remove (priv->floating_bar_loading_timeout_id); + priv->floating_bar_loading_timeout_id = 0; + } + + priv->floating_bar_loading_timeout_id = + g_timeout_add (FLOATING_BAR_LOADING_DELAY, setup_loading_floating_bar_timeout_cb, view); +} + +static void +floating_bar_stop_cb (NautilusFloatingBar *floating_bar, + NautilusFilesView *view) +{ + NautilusFilesViewPrivate *priv; + + priv = nautilus_files_view_get_instance_private (view); + + remove_loading_floating_bar (view); + nautilus_window_slot_stop_loading (priv->slot); +} + +static void +real_floating_bar_set_short_status (NautilusFilesView *view, + const gchar *primary_status, + const gchar *detail_status) +{ + NautilusFilesViewPrivate *priv; + + priv = nautilus_files_view_get_instance_private (view); + + if (priv->loading) + { + return; + } + + nautilus_floating_bar_set_show_spinner (NAUTILUS_FLOATING_BAR (priv->floating_bar), + FALSE); + nautilus_floating_bar_set_show_stop (NAUTILUS_FLOATING_BAR (priv->floating_bar), + FALSE); + + if (primary_status == NULL && detail_status == NULL) + { + gtk_widget_hide (priv->floating_bar); + nautilus_floating_bar_remove_hover_timeout (NAUTILUS_FLOATING_BAR (priv->floating_bar)); + return; + } + + nautilus_floating_bar_set_labels (NAUTILUS_FLOATING_BAR (priv->floating_bar), + primary_status, + detail_status); + + gtk_widget_show (priv->floating_bar); +} + +typedef struct +{ + gchar *primary_status; + gchar *detail_status; + NautilusFilesView *view; +} FloatingBarSetStatusData; + +static void +floating_bar_set_status_data_free (gpointer data) +{ + FloatingBarSetStatusData *status_data = data; + + g_free (status_data->primary_status); + g_free (status_data->detail_status); + + g_slice_free (FloatingBarSetStatusData, data); +} + +static gboolean +floating_bar_set_status_timeout_cb (gpointer data) +{ + NautilusFilesViewPrivate *priv; + + FloatingBarSetStatusData *status_data = data; + + priv = nautilus_files_view_get_instance_private (status_data->view); + + priv->floating_bar_set_status_timeout_id = 0; + real_floating_bar_set_short_status (status_data->view, + status_data->primary_status, + status_data->detail_status); + + return FALSE; +} + +static gboolean +remove_floating_bar_passthrough (gpointer data) +{ + NautilusFilesViewPrivate *priv; + + priv = nautilus_files_view_get_instance_private (NAUTILUS_FILES_VIEW (data)); + gtk_widget_set_can_target (priv->floating_bar, TRUE); + priv->floating_bar_set_passthrough_timeout_id = 0; + + return G_SOURCE_REMOVE; +} + +static void +set_floating_bar_status (NautilusFilesView *view, + const gchar *primary_status, + const gchar *detail_status) +{ + GtkSettings *settings; + gint double_click_time; + FloatingBarSetStatusData *status_data; + NautilusFilesViewPrivate *priv; + + priv = nautilus_files_view_get_instance_private (view); + + if (priv->floating_bar_set_status_timeout_id != 0) + { + g_source_remove (priv->floating_bar_set_status_timeout_id); + priv->floating_bar_set_status_timeout_id = 0; + } + + settings = gtk_settings_get_for_display (gtk_widget_get_display (GTK_WIDGET (view))); + g_object_get (settings, + "gtk-double-click-time", &double_click_time, + NULL); + + status_data = g_slice_new0 (FloatingBarSetStatusData); + status_data->primary_status = g_strdup (primary_status); + status_data->detail_status = g_strdup (detail_status); + status_data->view = view; + + if (priv->floating_bar_set_passthrough_timeout_id != 0) + { + g_source_remove (priv->floating_bar_set_passthrough_timeout_id); + priv->floating_bar_set_passthrough_timeout_id = 0; + } + /* Activate passthrough on the floating bar just long enough for a + * potential double click to happen, so to not interfere with it */ + gtk_widget_set_can_target (priv->floating_bar, FALSE); + priv->floating_bar_set_passthrough_timeout_id = g_timeout_add ((guint) double_click_time, + remove_floating_bar_passthrough, + view); + + /* waiting for half of the double-click-time before setting + * the status seems to be a good approximation of not setting it + * too often and not delaying the statusbar too much. + */ + priv->floating_bar_set_status_timeout_id = + g_timeout_add_full (G_PRIORITY_DEFAULT, + (guint) (double_click_time / 2), + floating_bar_set_status_timeout_cb, + status_data, + floating_bar_set_status_data_free); +} + +static char * +real_get_backing_uri (NautilusFilesView *view) +{ + NautilusFilesViewPrivate *priv; + + g_return_val_if_fail (NAUTILUS_IS_FILES_VIEW (view), NULL); + + priv = nautilus_files_view_get_instance_private (view); + + if (priv->model == NULL) + { + return NULL; + } + + return nautilus_directory_get_uri (priv->model); +} + +/** + * + * nautilus_files_view_get_backing_uri: + * + * Returns the URI for the target location of new directory, new file, new + * link and paste operations. + */ + +char * +nautilus_files_view_get_backing_uri (NautilusFilesView *view) +{ + g_return_val_if_fail (NAUTILUS_IS_FILES_VIEW (view), NULL); + + return NAUTILUS_FILES_VIEW_CLASS (G_OBJECT_GET_CLASS (view))->get_backing_uri (view); +} + +/** + * nautilus_files_view_select_all: + * + * select all the items in the view + * + **/ +static void +nautilus_files_view_select_all (NautilusFilesView *view) +{ + g_return_if_fail (NAUTILUS_IS_FILES_VIEW (view)); + + NAUTILUS_FILES_VIEW_CLASS (G_OBJECT_GET_CLASS (view))->select_all (view); +} + +static void +nautilus_files_view_select_first (NautilusFilesView *view) +{ + g_return_if_fail (NAUTILUS_IS_FILES_VIEW (view)); + + NAUTILUS_FILES_VIEW_CLASS (G_OBJECT_GET_CLASS (view))->select_first (view); +} + +static void +nautilus_files_view_call_set_selection (NautilusFilesView *view, + GList *selection) +{ + g_return_if_fail (NAUTILUS_IS_FILES_VIEW (view)); + + NAUTILUS_FILES_VIEW_CLASS (G_OBJECT_GET_CLASS (view))->set_selection (view, selection); +} + +static GList * +nautilus_files_view_get_selection_for_file_transfer (NautilusFilesView *view) +{ + g_return_val_if_fail (NAUTILUS_IS_FILES_VIEW (view), NULL); + + return NAUTILUS_FILES_VIEW_CLASS (G_OBJECT_GET_CLASS (view))->get_selection_for_file_transfer (view); +} + +static void +nautilus_files_view_invert_selection (NautilusFilesView *view) +{ + g_return_if_fail (NAUTILUS_IS_FILES_VIEW (view)); + + NAUTILUS_FILES_VIEW_CLASS (G_OBJECT_GET_CLASS (view))->invert_selection (view); +} + +/** + * nautilus_files_view_reveal_selection: + * + * Scroll as necessary to reveal the selected items. + **/ +static void +nautilus_files_view_reveal_selection (NautilusFilesView *view) +{ + g_return_if_fail (NAUTILUS_IS_FILES_VIEW (view)); + + NAUTILUS_FILES_VIEW_CLASS (G_OBJECT_GET_CLASS (view))->reveal_selection (view); +} + +/** + * nautilus_files_view_get_toolbar_menu_sections: + * @view: a #NautilusFilesView + * + * Retrieves the menu sections that should be added to the toolbar menu when + * this view is active + * + * Returns: (transfer none): a #NautilusToolbarMenuSections with the details of + * which menu sections should be added to the menu + */ +static NautilusToolbarMenuSections * +nautilus_files_view_get_toolbar_menu_sections (NautilusView *view) +{ + NautilusFilesViewPrivate *priv; + + g_return_val_if_fail (NAUTILUS_IS_FILES_VIEW (view), NULL); + + priv = nautilus_files_view_get_instance_private (NAUTILUS_FILES_VIEW (view)); + + return priv->toolbar_menu_sections; +} + +static GMenuModel * +nautilus_files_view_get_templates_menu (NautilusView *self) +{ + GMenuModel *menu; + + g_object_get (self, "templates-menu", &menu, NULL); + + return menu; +} + +static GMenuModel * +nautilus_files_view_get_extensions_background_menu (NautilusView *self) +{ + GMenuModel *menu; + + g_object_get (self, "extensions-background-menu", &menu, NULL); + + return menu; +} + +static GMenuModel * +real_get_extensions_background_menu (NautilusView *view) +{ + NautilusFilesViewPrivate *priv; + + g_return_val_if_fail (NAUTILUS_IS_FILES_VIEW (view), NULL); + + priv = nautilus_files_view_get_instance_private (NAUTILUS_FILES_VIEW (view)); + + return priv->extensions_background_menu; +} + +static GMenuModel * +real_get_templates_menu (NautilusView *view) +{ + NautilusFilesViewPrivate *priv; + + g_return_val_if_fail (NAUTILUS_IS_FILES_VIEW (view), NULL); + + priv = nautilus_files_view_get_instance_private (NAUTILUS_FILES_VIEW (view)); + + return priv->templates_menu; +} + +static void +nautilus_files_view_set_templates_menu (NautilusView *self, + GMenuModel *menu) +{ + g_object_set (self, "templates-menu", menu, NULL); +} + +static void +nautilus_files_view_set_extensions_background_menu (NautilusView *self, + GMenuModel *menu) +{ + g_object_set (self, "extensions-background-menu", menu, NULL); +} + +static void +real_set_extensions_background_menu (NautilusView *view, + GMenuModel *menu) +{ + NautilusFilesViewPrivate *priv; + + g_return_if_fail (NAUTILUS_IS_FILES_VIEW (view)); + + priv = nautilus_files_view_get_instance_private (NAUTILUS_FILES_VIEW (view)); + + g_set_object (&priv->extensions_background_menu, menu); +} + +static void +real_set_templates_menu (NautilusView *view, + GMenuModel *menu) +{ + NautilusFilesViewPrivate *priv; + + g_return_if_fail (NAUTILUS_IS_FILES_VIEW (view)); + + priv = nautilus_files_view_get_instance_private (NAUTILUS_FILES_VIEW (view)); + + g_set_object (&priv->templates_menu, menu); +} + +static gboolean +showing_trash_directory (NautilusFilesView *view) +{ + NautilusFile *file; + + file = nautilus_files_view_get_directory_as_file (view); + if (file != NULL) + { + return nautilus_file_is_in_trash (file); + } + return FALSE; +} + +static gboolean +showing_recent_directory (NautilusFilesView *view) +{ + NautilusFile *file; + + file = nautilus_files_view_get_directory_as_file (view); + if (file != NULL) + { + return nautilus_file_is_in_recent (file); + } + return FALSE; +} + +static gboolean +showing_starred_directory (NautilusFilesView *view) +{ + NautilusFile *file; + + file = nautilus_files_view_get_directory_as_file (view); + if (file != NULL) + { + return nautilus_file_is_in_starred (file); + } + return FALSE; +} + +static gboolean +nautilus_files_view_supports_creating_files (NautilusFilesView *view) +{ + g_return_val_if_fail (NAUTILUS_IS_FILES_VIEW (view), FALSE); + + return !nautilus_files_view_is_read_only (view) + && !showing_trash_directory (view) + && !showing_recent_directory (view) + && !showing_starred_directory (view); +} + +static gboolean +nautilus_files_view_supports_extract_here (NautilusFilesView *view) +{ + g_return_val_if_fail (NAUTILUS_IS_FILES_VIEW (view), FALSE); + + return nautilus_files_view_supports_creating_files (view) + && !nautilus_view_is_searching (NAUTILUS_VIEW (view)); +} + +static gboolean +nautilus_files_view_is_empty (NautilusFilesView *view) +{ + g_return_val_if_fail (NAUTILUS_IS_FILES_VIEW (view), FALSE); + + return NAUTILUS_FILES_VIEW_CLASS (G_OBJECT_GET_CLASS (view))->is_empty (view); +} + +/** + * nautilus_files_view_bump_zoom_level: + * + * bump the current zoom level by invoking the relevant subclass through the slot + * + **/ +void +nautilus_files_view_bump_zoom_level (NautilusFilesView *view, + int zoom_increment) +{ + g_return_if_fail (NAUTILUS_IS_FILES_VIEW (view)); + + if (!nautilus_files_view_supports_zooming (view)) + { + return; + } + + NAUTILUS_FILES_VIEW_CLASS (G_OBJECT_GET_CLASS (view))->bump_zoom_level (view, zoom_increment); +} + +/** + * nautilus_files_view_can_zoom_in: + * + * Determine whether the view can be zoomed any closer. + * @view: The zoomable NautilusFilesView. + * + * Return value: TRUE if @view can be zoomed any closer, FALSE otherwise. + * + **/ +gboolean +nautilus_files_view_can_zoom_in (NautilusFilesView *view) +{ + g_return_val_if_fail (NAUTILUS_IS_FILES_VIEW (view), FALSE); + + if (!nautilus_files_view_supports_zooming (view)) + { + return FALSE; + } + + return NAUTILUS_FILES_VIEW_CLASS (G_OBJECT_GET_CLASS (view))->can_zoom_in (view); +} + +/** + * nautilus_files_view_can_zoom_out: + * + * Determine whether the view can be zoomed any further away. + * @view: The zoomable NautilusFilesView. + * + * Return value: TRUE if @view can be zoomed any further away, FALSE otherwise. + * + **/ +gboolean +nautilus_files_view_can_zoom_out (NautilusFilesView *view) +{ + g_return_val_if_fail (NAUTILUS_IS_FILES_VIEW (view), FALSE); + + if (!nautilus_files_view_supports_zooming (view)) + { + return FALSE; + } + + return NAUTILUS_FILES_VIEW_CLASS (G_OBJECT_GET_CLASS (view))->can_zoom_out (view); +} + +gboolean +nautilus_files_view_supports_zooming (NautilusFilesView *view) +{ + NautilusFilesViewPrivate *priv; + + priv = nautilus_files_view_get_instance_private (view); + + g_return_val_if_fail (NAUTILUS_IS_FILES_VIEW (view), FALSE); + + return priv->supports_zooming; +} + +/** + * nautilus_files_view_restore_standard_zoom_level: + * + * Restore the zoom level to 100% + */ +static void +nautilus_files_view_restore_standard_zoom_level (NautilusFilesView *view) +{ + if (!nautilus_files_view_supports_zooming (view)) + { + return; + } + + NAUTILUS_FILES_VIEW_CLASS (G_OBJECT_GET_CLASS (view))->restore_standard_zoom_level (view); +} + +static gboolean +nautilus_files_view_is_zoom_level_default (NautilusFilesView *view) +{ + g_return_val_if_fail (NAUTILUS_IS_FILES_VIEW (view), FALSE); + + return NAUTILUS_FILES_VIEW_CLASS (G_OBJECT_GET_CLASS (view))->is_zoom_level_default (view); +} + +gboolean +nautilus_files_view_is_searching (NautilusView *view) +{ + NautilusFilesView *files_view; + NautilusFilesViewPrivate *priv; + + files_view = NAUTILUS_FILES_VIEW (view); + priv = nautilus_files_view_get_instance_private (files_view); + + if (!priv->model) + { + return FALSE; + } + + return NAUTILUS_IS_SEARCH_DIRECTORY (priv->model); +} + +static guint +nautilus_files_view_get_view_id (NautilusView *view) +{ + return NAUTILUS_FILES_VIEW_CLASS (G_OBJECT_GET_CLASS (view))->get_view_id (NAUTILUS_FILES_VIEW (view)); +} + +char * +nautilus_files_view_get_first_visible_file (NautilusFilesView *view) +{ + return NAUTILUS_FILES_VIEW_CLASS (G_OBJECT_GET_CLASS (view))->get_first_visible_file (view); +} + +void +nautilus_files_view_scroll_to_file (NautilusFilesView *view, + const char *uri) +{ + NAUTILUS_FILES_VIEW_CLASS (G_OBJECT_GET_CLASS (view))->scroll_to_file (view, uri); +} + +/** + * nautilus_files_view_get_selection: + * + * Get a list of NautilusFile pointers that represents the + * currently-selected items in this view. Subclasses must override + * the signal handler for the 'get_selection' signal. Callers are + * responsible for g_free-ing the list (and unrefing its data). + * @view: NautilusFilesView whose selected items are of interest. + * + * Return value: GList of NautilusFile pointers representing the selection. + * + **/ +static GList * +nautilus_files_view_get_selection (NautilusView *view) +{ + g_return_val_if_fail (NAUTILUS_IS_FILES_VIEW (view), NULL); + + return NAUTILUS_FILES_VIEW_CLASS (G_OBJECT_GET_CLASS (view))->get_selection (NAUTILUS_FILES_VIEW (view)); +} + +typedef struct +{ + NautilusFile *file; + NautilusFilesView *directory_view; +} ScriptLaunchParameters; + +typedef struct +{ + NautilusFile *file; + NautilusFilesView *directory_view; +} CreateTemplateParameters; + +static FileAndDirectory * +file_and_directory_new (NautilusFile *file, + NautilusDirectory *directory) +{ + FileAndDirectory *fad; + + fad = g_new0 (FileAndDirectory, 1); + fad->directory = nautilus_directory_ref (directory); + fad->file = nautilus_file_ref (file); + + return fad; +} + +static NautilusFile * +file_and_directory_get_file (FileAndDirectory *fad) +{ + g_return_val_if_fail (fad != NULL, NULL); + + return nautilus_file_ref (fad->file); +} + +static void +file_and_directory_free (gpointer data) +{ + FileAndDirectory *fad = data; + + nautilus_directory_unref (fad->directory); + nautilus_file_unref (fad->file); + g_free (fad); +} + +G_DEFINE_AUTOPTR_CLEANUP_FUNC (FileAndDirectory, file_and_directory_free) + +static gboolean +file_and_directory_equal (gconstpointer v1, + gconstpointer v2) +{ + const FileAndDirectory *fad1, *fad2; + fad1 = v1; + fad2 = v2; + + return (fad1->file == fad2->file && + fad1->directory == fad2->directory); +} + +static guint +file_and_directory_hash (gconstpointer v) +{ + const FileAndDirectory *fad; + + fad = v; + return GPOINTER_TO_UINT (fad->file) ^ GPOINTER_TO_UINT (fad->directory); +} + +static ScriptLaunchParameters * +script_launch_parameters_new (NautilusFile *file, + NautilusFilesView *directory_view) +{ + ScriptLaunchParameters *result; + + result = g_new0 (ScriptLaunchParameters, 1); + result->directory_view = directory_view; + nautilus_file_ref (file); + result->file = file; + + return result; +} + +static void +script_launch_parameters_free (ScriptLaunchParameters *parameters) +{ + nautilus_file_unref (parameters->file); + g_free (parameters); +} + +static CreateTemplateParameters * +create_template_parameters_new (NautilusFile *file, + NautilusFilesView *directory_view) +{ + CreateTemplateParameters *result; + + result = g_new0 (CreateTemplateParameters, 1); + result->directory_view = directory_view; + nautilus_file_ref (file); + result->file = file; + + return result; +} + +static void +create_templates_parameters_free (CreateTemplateParameters *parameters) +{ + nautilus_file_unref (parameters->file); + g_free (parameters); +} + +NautilusWindow * +nautilus_files_view_get_window (NautilusFilesView *view) +{ + NautilusFilesViewPrivate *priv; + + priv = nautilus_files_view_get_instance_private (view); + + return nautilus_window_slot_get_window (priv->slot); +} + +NautilusWindowSlot * +nautilus_files_view_get_nautilus_window_slot (NautilusFilesView *view) +{ + NautilusFilesViewPrivate *priv; + + priv = nautilus_files_view_get_instance_private (view); + + g_assert (priv->slot != NULL); + + return priv->slot; +} + +/* Returns the GtkWindow that this directory view occupies, or NULL + * if at the moment this directory view is not in a GtkWindow or the + * GtkWindow cannot be determined. Primarily used for parenting dialogs. + */ +static GtkWindow * +nautilus_files_view_get_containing_window (NautilusFilesView *view) +{ + GtkWidget *window; + + g_assert (NAUTILUS_IS_FILES_VIEW (view)); + + window = gtk_widget_get_ancestor (GTK_WIDGET (view), GTK_TYPE_WINDOW); + if (window == NULL) + { + return NULL; + } + + return GTK_WINDOW (window); +} + +static char * +get_view_directory (NautilusFilesView *view) +{ + NautilusFilesViewPrivate *priv; + char *uri, *path; + GFile *f; + + priv = nautilus_files_view_get_instance_private (view); + + uri = nautilus_directory_get_uri (priv->model); + f = g_file_new_for_uri (uri); + path = g_file_get_path (f); + g_object_unref (f); + g_free (uri); + + return path; +} + +typedef struct +{ + gchar *uri; + gboolean is_update; +} PreviewExportData; + +static void +preview_export_data_free (gpointer _data) +{ + PreviewExportData *data = _data; + g_free (data->uri); + g_free (data); +} + +G_DEFINE_AUTOPTR_CLEANUP_FUNC (PreviewExportData, preview_export_data_free) + +static void +on_window_handle_export (NautilusWindow *window, + const char *handle, + guint xid, + gpointer user_data) +{ + g_autoptr (PreviewExportData) data = user_data; + nautilus_previewer_call_show_file (data->uri, handle, xid, !data->is_update); +} + +static void +nautilus_files_view_preview (NautilusFilesView *view, + PreviewExportData *data) +{ + if (!nautilus_window_export_handle (nautilus_files_view_get_window (view), + on_window_handle_export, + data)) + { + /* Let's use a fallback, so at least a preview will be displayed */ + nautilus_previewer_call_show_file (data->uri, "x11:0", 0, !data->is_update); + } +} + +static void +nautilus_files_view_preview_update (NautilusFilesView *view) +{ + NautilusFilesViewPrivate *priv = nautilus_files_view_get_instance_private (view); + GtkApplication *app; + GtkWindow *window; + g_autolist (NautilusFile) selection = NULL; + PreviewExportData *data; + + if (!priv->active || + !nautilus_previewer_is_visible ()) + { + return; + } + + app = GTK_APPLICATION (g_application_get_default ()); + window = GTK_WINDOW (nautilus_files_view_get_window (view)); + if (window == NULL || window != gtk_application_get_active_window (app)) + { + return; + } + + selection = nautilus_view_get_selection (NAUTILUS_VIEW (view)); + if (selection == NULL) + { + return; + } + + data = g_new0 (PreviewExportData, 1); + data->uri = nautilus_file_get_uri (selection->data); + data->is_update = TRUE; + + nautilus_files_view_preview (view, data); +} + +void +nautilus_files_view_preview_selection_event (NautilusFilesView *view, + GtkDirectionType direction) +{ + NAUTILUS_FILES_VIEW_CLASS (G_OBJECT_GET_CLASS (view))->preview_selection_event (view, direction); +} + +void +nautilus_files_view_activate_selection (NautilusFilesView *view) +{ + g_autolist (NautilusFile) selection = NULL; + + selection = nautilus_view_get_selection (NAUTILUS_VIEW (view)); + nautilus_files_view_activate_files (view, + selection, + 0, + TRUE); +} + +void +nautilus_files_view_activate_files (NautilusFilesView *view, + GList *files, + NautilusOpenFlags flags, + gboolean confirm_multiple) +{ + NautilusFilesViewPrivate *priv; + GList *files_to_extract; + GList *files_to_activate; + char *path; + + if (files == NULL) + { + return; + } + + priv = nautilus_files_view_get_instance_private (view); + + files_to_extract = nautilus_file_list_filter (files, + &files_to_activate, + (NautilusFileFilterFunc) nautilus_mime_file_extracts, + NULL); + + if (nautilus_files_view_supports_extract_here (view)) + { + g_autoptr (GFile) location = NULL; + g_autoptr (GFile) parent = NULL; + + location = nautilus_file_get_location (NAUTILUS_FILE (g_list_first (files)->data)); + /* Get a parent from a random file. We assume all files has a common parent. + * But don't assume the parent is the view location, since that's not the + * case in list view when expand-folder setting is set + */ + parent = g_file_get_parent (location); + extract_files (view, files_to_extract, parent); + } + else + { + extract_files_to_chosen_location (view, files_to_extract); + } + + path = get_view_directory (view); + nautilus_mime_activate_files (nautilus_files_view_get_containing_window (view), + priv->slot, + files_to_activate, + path, + flags, + confirm_multiple); + + g_free (path); + g_list_free (files_to_extract); + g_list_free (files_to_activate); +} + +void +nautilus_files_view_activate_file (NautilusFilesView *view, + NautilusFile *file, + NautilusOpenFlags flags) +{ + g_autoptr (GList) files = NULL; + + files = g_list_append (files, file); + nautilus_files_view_activate_files (view, files, flags, FALSE); +} + +static void +action_open_with_default_application (GSimpleAction *action, + GVariant *state, + gpointer user_data) +{ + NautilusFilesView *view; + + view = NAUTILUS_FILES_VIEW (user_data); + nautilus_files_view_activate_selection (view); +} + +static void +action_open_item_location (GSimpleAction *action, + GVariant *state, + gpointer user_data) +{ + NautilusFilesView *view; + g_autolist (NautilusFile) selection = NULL; + NautilusFile *item; + GFile *activation_location; + NautilusFile *activation_file; + NautilusFile *parent; + g_autoptr (GFile) parent_location = NULL; + + view = NAUTILUS_FILES_VIEW (user_data); + selection = nautilus_view_get_selection (NAUTILUS_VIEW (view)); + + if (!selection) + { + return; + } + + item = NAUTILUS_FILE (selection->data); + activation_location = nautilus_file_get_activation_location (item); + activation_file = nautilus_file_get (activation_location); + parent = nautilus_file_get_parent (activation_file); + parent_location = nautilus_file_get_location (parent); + + if (nautilus_file_is_in_recent (item)) + { + /* Selection logic will check against a NautilusFile of the + * activation uri, not the recent:// one. Fixes bug 784516 */ + nautilus_file_unref (item); + item = nautilus_file_ref (activation_file); + selection->data = item; + } + + nautilus_application_open_location_full (NAUTILUS_APPLICATION (g_application_get_default ()), + parent_location, 0, selection, NULL, + nautilus_files_view_get_nautilus_window_slot (view)); + + nautilus_file_unref (parent); + nautilus_file_unref (activation_file); + g_object_unref (activation_location); +} + +static void +action_open_item_new_tab (GSimpleAction *action, + GVariant *state, + gpointer user_data) +{ + NautilusFilesView *view; + g_autolist (NautilusFile) selection = NULL; + + view = NAUTILUS_FILES_VIEW (user_data); + selection = nautilus_view_get_selection (NAUTILUS_VIEW (view)); + + nautilus_files_view_activate_files (view, + selection, + NAUTILUS_OPEN_FLAG_NEW_TAB | + NAUTILUS_OPEN_FLAG_DONT_MAKE_ACTIVE, + TRUE); +} + +static void +app_chooser_dialog_response_cb (GtkDialog *dialog, + gint response_id, + gpointer user_data) +{ + GtkWindow *parent_window; + GList *files; + GAppInfo *info; + + parent_window = user_data; + files = g_object_get_data (G_OBJECT (dialog), "directory-view:files"); + + if (response_id != GTK_RESPONSE_OK) + { + goto out; + } + + info = nautilus_app_chooser_get_app_info (NAUTILUS_APP_CHOOSER (dialog)); + + nautilus_launch_application (info, files, parent_window); + + g_object_unref (info); +out: + gtk_window_destroy (GTK_WINDOW (dialog)); +} + +static void +choose_program (NautilusFilesView *view, + GList *files) +{ + GtkWidget *dialog; + GtkWindow *parent_window; + + g_assert (NAUTILUS_IS_FILES_VIEW (view)); + + parent_window = nautilus_files_view_get_containing_window (view); + + dialog = GTK_WIDGET (nautilus_app_chooser_new (files, parent_window)); + g_object_set_data_full (G_OBJECT (dialog), + "directory-view:files", + files, + (GDestroyNotify) nautilus_file_list_free); + gtk_widget_show (dialog); + + g_signal_connect_object (dialog, "response", + G_CALLBACK (app_chooser_dialog_response_cb), + parent_window, 0); +} + +static void +open_with_other_program (NautilusFilesView *view) +{ + g_autolist (NautilusFile) selection = NULL; + + g_assert (NAUTILUS_IS_FILES_VIEW (view)); + + selection = nautilus_view_get_selection (NAUTILUS_VIEW (view)); + choose_program (view, g_steal_pointer (&selection)); +} + +static void +action_open_with_other_application (GSimpleAction *action, + GVariant *state, + gpointer user_data) +{ + g_assert (NAUTILUS_IS_FILES_VIEW (user_data)); + + open_with_other_program (NAUTILUS_FILES_VIEW (user_data)); +} + +static void +action_open_current_directory_with_other_application (GSimpleAction *action, + GVariant *state, + gpointer user_data) +{ + NautilusFilesView *view; + NautilusFilesViewPrivate *priv; + GList *files; + + g_return_if_fail (NAUTILUS_IS_FILES_VIEW (user_data)); + + view = NAUTILUS_FILES_VIEW (user_data); + priv = nautilus_files_view_get_instance_private (view); + + if (priv->directory_as_file != NULL) + { + files = g_list_append (NULL, nautilus_file_ref (priv->directory_as_file)); + choose_program (view, files); + } +} + +static void +trash_or_delete_selected_files (NautilusFilesView *view) +{ + NautilusFilesViewPrivate *priv; + priv = nautilus_files_view_get_instance_private (view); + + /* This might be rapidly called multiple times for the same selection + * when using keybindings. So we remember if the current selection + * was already removed (but the view doesn't know about it yet). + */ + if (!priv->selection_was_removed) + { + g_autolist (NautilusFile) selection = NULL; + selection = nautilus_files_view_get_selection_for_file_transfer (view); + trash_or_delete_files (nautilus_files_view_get_containing_window (view), + selection, + view); + priv->selection_was_removed = TRUE; + } +} + +static void +action_move_to_trash (GSimpleAction *action, + GVariant *state, + gpointer user_data) +{ + trash_or_delete_selected_files (NAUTILUS_FILES_VIEW (user_data)); +} + +static void +action_remove_from_recent (GSimpleAction *action, + GVariant *state, + gpointer user_data) +{ + /* TODO:implement a set of functions for this, is very confusing to + * call trash_or_delete_file to remove from recent, even if it does like + * that not deleting/moving the files to trash */ + trash_or_delete_selected_files (NAUTILUS_FILES_VIEW (user_data)); +} + +static void +delete_selected_files (NautilusFilesView *view) +{ + GList *selection; + GList *node; + GList *locations; + + selection = nautilus_files_view_get_selection_for_file_transfer (view); + if (selection == NULL) + { + return; + } + + locations = NULL; + for (node = selection; node != NULL; node = node->next) + { + locations = g_list_prepend (locations, + nautilus_file_get_location ((NautilusFile *) node->data)); + } + locations = g_list_reverse (locations); + + nautilus_file_operations_delete_async (locations, nautilus_files_view_get_containing_window (view), NULL, NULL, NULL); + + g_list_free_full (locations, g_object_unref); + nautilus_file_list_free (selection); +} + +static void +action_delete (GSimpleAction *action, + GVariant *state, + gpointer user_data) +{ + delete_selected_files (NAUTILUS_FILES_VIEW (user_data)); +} + +static void +action_star (GSimpleAction *action, + GVariant *state, + gpointer user_data) +{ + NautilusFilesView *view; + g_autolist (NautilusFile) selection = NULL; + NautilusFilesViewPrivate *priv; + + view = NAUTILUS_FILES_VIEW (user_data); + priv = nautilus_files_view_get_instance_private (view); + selection = nautilus_view_get_selection (NAUTILUS_VIEW (view)); + + nautilus_tag_manager_star_files (nautilus_tag_manager_get (), + G_OBJECT (view), + selection, + NULL, + priv->starred_cancellable); +} + +static void +action_unstar (GSimpleAction *action, + GVariant *state, + gpointer user_data) +{ + NautilusFilesView *view; + g_autolist (NautilusFile) selection = NULL; + NautilusFilesViewPrivate *priv; + + view = NAUTILUS_FILES_VIEW (user_data); + priv = nautilus_files_view_get_instance_private (view); + selection = nautilus_view_get_selection (NAUTILUS_VIEW (view)); + + nautilus_tag_manager_unstar_files (nautilus_tag_manager_get (), + G_OBJECT (view), + selection, + NULL, + priv->starred_cancellable); +} + +static void +action_restore_from_trash (GSimpleAction *action, + GVariant *state, + gpointer user_data) +{ + NautilusFilesView *view; + GList *selection; + + view = NAUTILUS_FILES_VIEW (user_data); + + selection = nautilus_files_view_get_selection_for_file_transfer (view); + nautilus_restore_files_from_trash (selection, + nautilus_files_view_get_containing_window (view)); + + nautilus_file_list_free (selection); +} + +static void +action_select_all (GSimpleAction *action, + GVariant *state, + gpointer user_data) +{ + NautilusFilesView *view; + + g_assert (NAUTILUS_IS_FILES_VIEW (user_data)); + + view = NAUTILUS_FILES_VIEW (user_data); + + nautilus_files_view_select_all (view); +} + +static void +action_invert_selection (GSimpleAction *action, + GVariant *state, + gpointer user_data) +{ + g_assert (NAUTILUS_IS_FILES_VIEW (user_data)); + + nautilus_files_view_invert_selection (user_data); +} + +static void +action_preview_selection (GSimpleAction *action, + GVariant *state, + gpointer user_data) +{ + NautilusFilesView *view = NAUTILUS_FILES_VIEW (user_data); + g_autolist (NautilusFile) selection = NULL; + PreviewExportData *data = g_new0 (PreviewExportData, 1); + + selection = nautilus_view_get_selection (NAUTILUS_VIEW (view)); + + data->uri = nautilus_file_get_uri (selection->data); + data->is_update = FALSE; + + nautilus_files_view_preview (view, data); +} + +static void +action_popup_menu (GSimpleAction *action, + GVariant *state, + gpointer user_data) +{ + NautilusFilesView *view = NAUTILUS_FILES_VIEW (user_data); + g_autolist (NautilusFile) selection = nautilus_files_view_get_selection (NAUTILUS_VIEW (view)); + + if (selection == NULL) + { + nautilus_files_view_pop_up_background_context_menu (view, 0, 0); + return; + } + + nautilus_files_view_pop_up_selection_context_menu (view, -1, -1); +} + +static void +pattern_select_response_cb (GtkWidget *dialog, + int response, + gpointer user_data) +{ + NautilusFilesView *view; + NautilusDirectory *directory; + GtkWidget *entry; + GList *selection; + + view = NAUTILUS_FILES_VIEW (user_data); + + switch (response) + { + case GTK_RESPONSE_OK: + { + entry = g_object_get_data (G_OBJECT (dialog), "entry"); + directory = nautilus_files_view_get_model (view); + selection = nautilus_directory_match_pattern (directory, + gtk_editable_get_text (GTK_EDITABLE (entry))); + + nautilus_files_view_call_set_selection (view, selection); + nautilus_files_view_reveal_selection (view); + + if (selection) + { + nautilus_file_list_free (selection); + } + /* fall through */ + } + + case GTK_RESPONSE_NONE: + case GTK_RESPONSE_DELETE_EVENT: + case GTK_RESPONSE_CANCEL: + { + gtk_window_destroy (GTK_WINDOW (dialog)); + } + break; + + default: + { + g_assert_not_reached (); + } + } +} + +static void +select_pattern (NautilusFilesView *view) +{ + g_autoptr (GtkBuilder) builder = NULL; + GtkWidget *dialog; + NautilusWindow *window; + GtkWidget *example; + GtkWidget *entry; + char *example_pattern; + + window = nautilus_files_view_get_window (view); + builder = gtk_builder_new_from_resource ("/org/gnome/nautilus/ui/nautilus-files-view-select-items.ui"); + dialog = GTK_WIDGET (gtk_builder_get_object (builder, "select_items_dialog")); + + example = GTK_WIDGET (gtk_builder_get_object (builder, "example")); + example_pattern = g_strdup_printf ("%s%s ", + _("Examples: "), + "*.png, file\?\?.txt, pict*.\?\?\?"); + gtk_label_set_markup (GTK_LABEL (example), example_pattern); + g_free (example_pattern); + gtk_window_set_transient_for (GTK_WINDOW (dialog), GTK_WINDOW (window)); + + entry = GTK_WIDGET (gtk_builder_get_object (builder, "pattern_entry")); + + g_object_set_data (G_OBJECT (dialog), "entry", entry); + g_signal_connect (dialog, "response", + G_CALLBACK (pattern_select_response_cb), + view); + gtk_widget_show (dialog); +} + +static void +action_select_pattern (GSimpleAction *action, + GVariant *state, + gpointer user_data) +{ + g_assert (NAUTILUS_IS_FILES_VIEW (user_data)); + + select_pattern (user_data); +} + +typedef struct +{ + NautilusFilesView *directory_view; + GHashTable *added_locations; + GList *selection; +} NewFolderData; + +typedef struct +{ + NautilusFilesView *directory_view; + GHashTable *to_remove_locations; + NautilusFile *new_folder; +} NewFolderSelectionData; + +static void +track_newly_added_locations (NautilusFilesView *view, + GList *new_files, + gpointer user_data) +{ + GHashTable *added_locations; + + added_locations = user_data; + + while (new_files) + { + NautilusFile *new_file; + + new_file = NAUTILUS_FILE (new_files->data); + + g_hash_table_add (added_locations, + nautilus_file_get_location (new_file)); + + new_files = new_files->next; + } +} + +static void +new_folder_done (GFile *new_folder, + gboolean success, + gpointer user_data) +{ + NautilusFilesView *directory_view; + NautilusFilesViewPrivate *priv; + NautilusFile *file; + NewFolderData *data; + + data = (NewFolderData *) user_data; + + directory_view = data->directory_view; + priv = nautilus_files_view_get_instance_private (directory_view); + + if (directory_view == NULL) + { + goto fail; + } + + g_signal_handlers_disconnect_by_func (directory_view, + G_CALLBACK (track_newly_added_locations), + data->added_locations); + + if (new_folder == NULL) + { + goto fail; + } + + file = nautilus_file_get (new_folder); + + if (data->selection != NULL) + { + GList *uris, *l; + char *target_uri; + + uris = NULL; + for (l = data->selection; l != NULL; l = l->next) + { + uris = g_list_prepend (uris, nautilus_file_get_uri ((NautilusFile *) l->data)); + } + uris = g_list_reverse (uris); + + target_uri = nautilus_file_get_uri (file); + + nautilus_files_view_move_copy_items (directory_view, + uris, + target_uri, + GDK_ACTION_MOVE); + g_list_free_full (uris, g_free); + g_free (target_uri); + } + + if (g_hash_table_contains (data->added_locations, new_folder)) + { + /* The file was already added */ + nautilus_files_view_select_file (directory_view, file); + nautilus_files_view_reveal_selection (directory_view); + } + else + { + g_hash_table_insert (priv->pending_reveal, + file, + GUINT_TO_POINTER (TRUE)); + } + + nautilus_file_unref (file); + +fail: + g_hash_table_destroy (data->added_locations); + + if (data->directory_view != NULL) + { + g_object_remove_weak_pointer (G_OBJECT (data->directory_view), + (gpointer *) &data->directory_view); + } + + nautilus_file_list_free (data->selection); + g_free (data); +} + + +static NewFolderData * +new_folder_data_new (NautilusFilesView *directory_view, + gboolean with_selection) +{ + NewFolderData *data; + + data = g_new (NewFolderData, 1); + data->directory_view = directory_view; + data->added_locations = g_hash_table_new_full (g_file_hash, (GEqualFunc) g_file_equal, + g_object_unref, NULL); + if (with_selection) + { + data->selection = nautilus_files_view_get_selection_for_file_transfer (directory_view); + } + else + { + data->selection = NULL; + } + g_object_add_weak_pointer (G_OBJECT (data->directory_view), + (gpointer *) &data->directory_view); + + return data; +} + +static GdkRectangle * +nautilus_files_view_compute_rename_popover_pointing_to (NautilusFilesView *view) +{ + return NAUTILUS_FILES_VIEW_CLASS (G_OBJECT_GET_CLASS (view))->compute_rename_popover_pointing_to (view); +} + +static void +disconnect_rename_controller_signals (NautilusFilesView *self) +{ + NautilusFilesViewPrivate *priv; + + g_assert (NAUTILUS_IS_FILES_VIEW (self)); + + priv = nautilus_files_view_get_instance_private (self); + + g_clear_signal_handler (&priv->name_accepted_handler_id, priv->rename_file_controller); + g_clear_signal_handler (&priv->cancelled_handler_id, priv->rename_file_controller); +} + +static void +rename_file_popover_controller_on_name_accepted (NautilusFileNameWidgetController *controller, + gpointer user_data) +{ + NautilusFilesView *view; + NautilusFile *target_file; + g_autofree gchar *name = NULL; + NautilusFilesViewPrivate *priv; + + view = NAUTILUS_FILES_VIEW (user_data); + priv = nautilus_files_view_get_instance_private (view); + + name = nautilus_file_name_widget_controller_get_new_name (controller); + + target_file = + nautilus_rename_file_popover_controller_get_target_file (priv->rename_file_controller); + + /* Put it on the queue for reveal after the view acknowledges the change */ + g_hash_table_insert (priv->pending_reveal, + target_file, + GUINT_TO_POINTER (FALSE)); + + nautilus_rename_file (target_file, name, NULL, NULL); + + disconnect_rename_controller_signals (view); +} + +static void +rename_file_popover_controller_on_cancelled (NautilusFileNameWidgetController *controller, + gpointer user_data) +{ + NautilusFilesView *view; + + view = NAUTILUS_FILES_VIEW (user_data); + + disconnect_rename_controller_signals (view); +} + +static void +nautilus_files_view_rename_file_popover_new (NautilusFilesView *view, + NautilusFile *target_file) +{ + GdkRectangle *pointing_to; + NautilusFilesViewPrivate *priv; + + priv = nautilus_files_view_get_instance_private (view); + + /* Make sure the whole item is visible. The selection is a single item, the + * one to rename with the popover, so we can use reveal_selection() for this. + */ + nautilus_files_view_reveal_selection (view); + + pointing_to = nautilus_files_view_compute_rename_popover_pointing_to (view); + + nautilus_rename_file_popover_controller_show_for_file (priv->rename_file_controller, + target_file, + pointing_to); + + priv->name_accepted_handler_id = g_signal_connect (priv->rename_file_controller, + "name-accepted", + G_CALLBACK (rename_file_popover_controller_on_name_accepted), + view); + priv->cancelled_handler_id = g_signal_connect (priv->rename_file_controller, + "cancelled", + G_CALLBACK (rename_file_popover_controller_on_cancelled), + view); +} + +static void +new_folder_dialog_controller_on_name_accepted (NautilusFileNameWidgetController *controller, + gpointer user_data) +{ + NautilusFilesView *view; + NautilusFilesViewPrivate *priv; + NewFolderData *data; + g_autofree gchar *parent_uri = NULL; + g_autofree gchar *name = NULL; + NautilusFile *parent; + gboolean with_selection; + + view = NAUTILUS_FILES_VIEW (user_data); + priv = nautilus_files_view_get_instance_private (view); + + with_selection = + nautilus_new_folder_dialog_controller_get_with_selection (priv->new_folder_controller); + + data = new_folder_data_new (view, with_selection); + + name = nautilus_file_name_widget_controller_get_new_name (controller); + g_signal_connect_data (view, + "add-files", + G_CALLBACK (track_newly_added_locations), + data->added_locations, + (GClosureNotify) NULL, + G_CONNECT_AFTER); + + parent_uri = nautilus_files_view_get_backing_uri (view); + parent = nautilus_file_get_by_uri (parent_uri); + nautilus_file_operations_new_folder (GTK_WIDGET (view), + NULL, + parent_uri, name, + new_folder_done, data); + + g_clear_object (&priv->new_folder_controller); + + /* After the dialog is destroyed the focus, is probably in the menu item + * that created the dialog, but we want the focus to be in the newly created + * folder. + */ + gtk_widget_grab_focus (GTK_WIDGET (view)); + + g_object_unref (parent); +} + +static void +new_folder_dialog_controller_on_cancelled (NautilusNewFolderDialogController *controller, + gpointer user_data) +{ + NautilusFilesView *view; + NautilusFilesViewPrivate *priv; + + view = NAUTILUS_FILES_VIEW (user_data); + priv = nautilus_files_view_get_instance_private (view); + + g_clear_object (&priv->new_folder_controller); +} + +static void +nautilus_files_view_new_folder_dialog_new (NautilusFilesView *view, + gboolean with_selection) +{ + g_autoptr (NautilusDirectory) containing_directory = NULL; + NautilusFilesViewPrivate *priv; + g_autofree char *uri = NULL; + g_autofree char *common_prefix = NULL; + + priv = nautilus_files_view_get_instance_private (view); + + if (priv->new_folder_controller != NULL) + { + return; + } + + uri = nautilus_files_view_get_backing_uri (view); + containing_directory = nautilus_directory_get_by_uri (uri); + + if (with_selection) + { + g_autolist (NautilusFile) selection = NULL; + selection = nautilus_view_get_selection (NAUTILUS_VIEW (view)); + common_prefix = nautilus_get_common_filename_prefix (selection, MIN_COMMON_FILENAME_PREFIX_LENGTH); + } + + priv->new_folder_controller = + nautilus_new_folder_dialog_controller_new (nautilus_files_view_get_containing_window (view), + containing_directory, + with_selection, + common_prefix); + + g_signal_connect (priv->new_folder_controller, + "name-accepted", + (GCallback) new_folder_dialog_controller_on_name_accepted, + view); + g_signal_connect (priv->new_folder_controller, + "cancelled", + (GCallback) new_folder_dialog_controller_on_cancelled, + view); +} + +typedef struct +{ + NautilusFilesView *view; + GHashTable *added_locations; +} CompressData; + +static void +compress_done (GFile *new_file, + gboolean success, + gpointer user_data) +{ + CompressData *data; + NautilusFilesView *view; + NautilusFilesViewPrivate *priv; + NautilusFile *file; + char *uri = NULL; + + data = user_data; + view = data->view; + + if (view == NULL) + { + goto out; + } + + priv = nautilus_files_view_get_instance_private (view); + + g_signal_handlers_disconnect_by_func (view, + G_CALLBACK (track_newly_added_locations), + data->added_locations); + + if (!success) + { + goto out; + } + + file = nautilus_file_get (new_file); + + if (g_hash_table_contains (data->added_locations, new_file)) + { + /* The file was already added */ + nautilus_files_view_select_file (view, file); + nautilus_files_view_reveal_selection (view); + } + else + { + g_hash_table_insert (priv->pending_reveal, + file, + GUINT_TO_POINTER (TRUE)); + } + + uri = nautilus_file_get_uri (file); + gtk_recent_manager_add_item (gtk_recent_manager_get_default (), uri); + + nautilus_file_unref (file); +out: + g_hash_table_destroy (data->added_locations); + + if (data->view != NULL) + { + g_object_remove_weak_pointer (G_OBJECT (data->view), + (gpointer *) &data->view); + } + + g_free (uri); + g_free (data); +} + +static void +compress_dialog_controller_on_name_accepted (NautilusFileNameWidgetController *controller, + gpointer user_data) +{ + CompressCallbackData *callback_data = user_data; + NautilusFilesView *view; + g_autofree gchar *name = NULL; + GList *source_files = NULL; + GList *l; + CompressData *data; + g_autoptr (GFile) output = NULL; + g_autoptr (GFile) parent = NULL; + NautilusCompressionFormat compression_format; + NautilusFilesViewPrivate *priv; + AutoarFormat format; + AutoarFilter filter; + const gchar *passphrase = NULL; + + view = NAUTILUS_FILES_VIEW (callback_data->view); + priv = nautilus_files_view_get_instance_private (view); + + for (l = callback_data->selection; l != NULL; l = l->next) + { + source_files = g_list_prepend (source_files, + nautilus_file_get_location (l->data)); + } + source_files = g_list_reverse (source_files); + + name = nautilus_file_name_widget_controller_get_new_name (controller); + /* Get a parent from a random file. We assume all files has a common parent. + * But don't assume the parent is the view location, since that's not the + * case in list view when expand-folder setting is set + */ + parent = g_file_get_parent (G_FILE (g_list_first (source_files)->data)); + output = g_file_get_child (parent, name); + + data = g_new (CompressData, 1); + data->view = view; + data->added_locations = g_hash_table_new_full (g_file_hash, (GEqualFunc) g_file_equal, + g_object_unref, NULL); + g_object_add_weak_pointer (G_OBJECT (data->view), + (gpointer *) &data->view); + + g_signal_connect_data (view, + "add-files", + G_CALLBACK (track_newly_added_locations), + data->added_locations, + NULL, + G_CONNECT_AFTER); + + compression_format = g_settings_get_enum (nautilus_compression_preferences, + NAUTILUS_PREFERENCES_DEFAULT_COMPRESSION_FORMAT); + + switch (compression_format) + { + case NAUTILUS_COMPRESSION_ZIP: + { + format = AUTOAR_FORMAT_ZIP; + filter = AUTOAR_FILTER_NONE; + } + break; + + case NAUTILUS_COMPRESSION_ENCRYPTED_ZIP: + { + format = AUTOAR_FORMAT_ZIP; + filter = AUTOAR_FILTER_NONE; + passphrase = nautilus_compress_dialog_controller_get_passphrase (priv->compress_controller); + } + break; + + case NAUTILUS_COMPRESSION_TAR_XZ: + { + format = AUTOAR_FORMAT_TAR; + filter = AUTOAR_FILTER_XZ; + } + break; + + case NAUTILUS_COMPRESSION_7ZIP: + { + format = AUTOAR_FORMAT_7ZIP; + filter = AUTOAR_FILTER_NONE; + } + break; + + default: + { + g_assert_not_reached (); + } + } + + nautilus_file_operations_compress (source_files, output, + format, + filter, + passphrase, + nautilus_files_view_get_containing_window (view), + NULL, + compress_done, + data); + + g_list_free_full (source_files, g_object_unref); + g_clear_object (&priv->compress_controller); +} + +static void +compress_dialog_controller_on_cancelled (NautilusNewFolderDialogController *controller, + gpointer user_data) +{ + NautilusFilesView *view; + NautilusFilesViewPrivate *priv; + + view = NAUTILUS_FILES_VIEW (user_data); + priv = nautilus_files_view_get_instance_private (view); + + g_clear_object (&priv->compress_controller); +} + +static void +compress_callback_data_free (CompressCallbackData *data) +{ + nautilus_file_list_free (data->selection); + g_free (data); +} + +static void +nautilus_files_view_compress_dialog_new (NautilusFilesView *view) +{ + NautilusDirectory *containing_directory; + NautilusFilesViewPrivate *priv; + g_autolist (NautilusFile) selection = NULL; + g_autofree char *common_prefix = NULL; + CompressCallbackData *data; + + priv = nautilus_files_view_get_instance_private (view); + + if (priv->compress_controller != NULL) + { + return; + } + + containing_directory = nautilus_directory_get_by_uri (nautilus_files_view_get_backing_uri (view)); + + selection = nautilus_view_get_selection (NAUTILUS_VIEW (view)); + + if (g_list_length (selection) == 1) + { + g_autofree char *display_name = NULL; + + display_name = nautilus_file_get_display_name (selection->data); + + if (nautilus_file_is_directory (selection->data)) + { + common_prefix = g_steal_pointer (&display_name); + } + else + { + common_prefix = eel_filename_strip_extension (display_name); + } + } + else + { + common_prefix = nautilus_get_common_filename_prefix (selection, + MIN_COMMON_FILENAME_PREFIX_LENGTH); + } + + priv->compress_controller = nautilus_compress_dialog_controller_new (nautilus_files_view_get_containing_window (view), + containing_directory, + common_prefix); + + data = g_new0 (CompressCallbackData, 1); + data->view = view; + data->selection = nautilus_files_view_get_selection_for_file_transfer (view); + + g_signal_connect_data (priv->compress_controller, + "name-accepted", + (GCallback) compress_dialog_controller_on_name_accepted, + data, + (GClosureNotify) compress_callback_data_free, + G_CONNECT_AFTER); + + g_signal_connect (priv->compress_controller, + "cancelled", + (GCallback) compress_dialog_controller_on_cancelled, + view); +} + +static void +nautilus_files_view_new_folder (NautilusFilesView *directory_view, + gboolean with_selection) +{ + nautilus_files_view_new_folder_dialog_new (directory_view, with_selection); +} + +static NewFolderData * +setup_new_folder_data (NautilusFilesView *directory_view) +{ + NewFolderData *data; + + data = new_folder_data_new (directory_view, FALSE); + + g_signal_connect_data (directory_view, + "add-files", + G_CALLBACK (track_newly_added_locations), + data->added_locations, + (GClosureNotify) NULL, + G_CONNECT_AFTER); + + return data; +} + +void +nautilus_files_view_new_file_with_initial_contents (NautilusFilesView *view, + const char *parent_uri, + const char *filename, + const char *initial_contents, + int length) +{ + NewFolderData *data; + + g_assert (parent_uri != NULL); + + data = setup_new_folder_data (view); + + nautilus_file_operations_new_file (GTK_WIDGET (view), + parent_uri, filename, + initial_contents, length, + new_folder_done, data); +} + +static void +nautilus_files_view_new_file (NautilusFilesView *directory_view, + const char *parent_uri, + NautilusFile *source) +{ + NewFolderData *data; + char *source_uri; + char *container_uri; + + container_uri = NULL; + if (parent_uri == NULL) + { + container_uri = nautilus_files_view_get_backing_uri (directory_view); + g_assert (container_uri != NULL); + } + + if (source == NULL) + { + nautilus_files_view_new_file_with_initial_contents (directory_view, + parent_uri != NULL ? parent_uri : container_uri, + NULL, + NULL, + 0); + g_free (container_uri); + return; + } + + data = setup_new_folder_data (directory_view); + + source_uri = nautilus_file_get_uri (source); + + nautilus_file_operations_new_file_from_template (GTK_WIDGET (directory_view), + parent_uri != NULL ? parent_uri : container_uri, + NULL, + source_uri, + new_folder_done, data); + + g_free (source_uri); + g_free (container_uri); +} + +static void +action_empty_trash (GSimpleAction *action, + GVariant *state, + gpointer user_data) +{ + NautilusFilesView *view; + GtkRoot *window; + + g_assert (NAUTILUS_IS_FILES_VIEW (user_data)); + + view = NAUTILUS_FILES_VIEW (user_data); + window = gtk_widget_get_root (GTK_WIDGET (view)); + + nautilus_file_operations_empty_trash (GTK_WIDGET (window), TRUE, NULL); +} + +static void +action_new_folder (GSimpleAction *action, + GVariant *state, + gpointer user_data) +{ + g_assert (NAUTILUS_IS_FILES_VIEW (user_data)); + + nautilus_files_view_new_folder (NAUTILUS_FILES_VIEW (user_data), FALSE); +} + +static void +action_new_folder_with_selection (GSimpleAction *action, + GVariant *state, + gpointer user_data) +{ + g_assert (NAUTILUS_IS_FILES_VIEW (user_data)); + + nautilus_files_view_new_folder (NAUTILUS_FILES_VIEW (user_data), TRUE); +} + +static void +real_open_console (NautilusFile *file, + NautilusFilesView *view) +{ + GtkRoot *window = gtk_widget_get_root (GTK_WIDGET (view)); + GVariant *parameters; + g_autofree gchar *uri = NULL; + + uri = nautilus_file_get_uri (file); + parameters = g_variant_new_parsed ("([%s], @a{sv} {})", uri); + nautilus_dbus_launcher_call (nautilus_dbus_launcher_get (), + NAUTILUS_DBUS_LAUNCHER_CONSOLE, + "Open", + parameters, GTK_WINDOW (window)); +} + +static void +action_open_console (GSimpleAction *action, + GVariant *state, + gpointer user_data) +{ + g_autolist (NautilusFile) selection = NULL; + + selection = nautilus_view_get_selection (NAUTILUS_VIEW (user_data)); + g_return_if_fail (selection != NULL && g_list_length (selection) == 1); + + real_open_console (NAUTILUS_FILE (selection->data), NAUTILUS_FILES_VIEW (user_data)); +} + +static void +action_current_dir_open_console (GSimpleAction *action, + GVariant *state, + gpointer user_data) +{ + NautilusFilesView *view; + NautilusFilesViewPrivate *priv; + + g_return_if_fail (NAUTILUS_IS_FILES_VIEW (user_data)); + + view = NAUTILUS_FILES_VIEW (user_data); + priv = nautilus_files_view_get_instance_private (view); + real_open_console (priv->directory_as_file, view); +} + +static void +action_properties (GSimpleAction *action, + GVariant *state, + gpointer user_data) +{ + NautilusFilesView *view; + NautilusFilesViewPrivate *priv; + g_autolist (NautilusFile) selection = NULL; + GList *files; + + g_assert (NAUTILUS_IS_FILES_VIEW (user_data)); + + view = NAUTILUS_FILES_VIEW (user_data); + priv = nautilus_files_view_get_instance_private (view); + selection = nautilus_view_get_selection (NAUTILUS_VIEW (view)); + if (g_list_length (selection) == 0) + { + if (priv->directory_as_file != NULL) + { + files = g_list_append (NULL, nautilus_file_ref (priv->directory_as_file)); + + nautilus_properties_window_present (files, GTK_WIDGET (view), NULL, + NULL, NULL); + + nautilus_file_list_free (files); + } + } + else + { + nautilus_properties_window_present (selection, GTK_WIDGET (view), NULL, + NULL, NULL); + } +} + +static void +action_current_dir_properties (GSimpleAction *action, + GVariant *state, + gpointer user_data) +{ + NautilusFilesView *view; + NautilusFilesViewPrivate *priv; + GList *files; + + g_return_if_fail (NAUTILUS_IS_FILES_VIEW (user_data)); + + view = NAUTILUS_FILES_VIEW (user_data); + priv = nautilus_files_view_get_instance_private (view); + + if (priv->directory_as_file != NULL) + { + files = g_list_append (NULL, nautilus_file_ref (priv->directory_as_file)); + + nautilus_properties_window_present (files, GTK_WIDGET (view), NULL, + NULL, NULL); + + nautilus_file_list_free (files); + } +} + +static void +nautilus_files_view_set_show_hidden_files (NautilusFilesView *view, + gboolean show_hidden) +{ + NautilusFilesViewPrivate *priv; + + priv = nautilus_files_view_get_instance_private (view); + + if (priv->ignore_hidden_file_preferences) + { + return; + } + + if (show_hidden != priv->show_hidden_files) + { + priv->show_hidden_files = show_hidden; + + g_settings_set_boolean (gtk_filechooser_preferences, + NAUTILUS_PREFERENCES_SHOW_HIDDEN_FILES, + show_hidden); + + if (priv->model != NULL) + { + load_directory (view, priv->model); + } + } +} + +static void +action_show_hidden_files (GSimpleAction *action, + GVariant *state, + gpointer user_data) +{ + gboolean show_hidden; + NautilusFilesView *view; + + g_assert (NAUTILUS_IS_FILES_VIEW (user_data)); + + view = NAUTILUS_FILES_VIEW (user_data); + show_hidden = g_variant_get_boolean (state); + + nautilus_files_view_set_show_hidden_files (view, show_hidden); + + g_simple_action_set_state (action, state); +} + +static void +action_zoom_in (GSimpleAction *action, + GVariant *state, + gpointer user_data) +{ + NautilusFilesView *view; + + g_assert (NAUTILUS_IS_FILES_VIEW (user_data)); + + view = NAUTILUS_FILES_VIEW (user_data); + + nautilus_files_view_bump_zoom_level (view, 1); +} + +static void +action_zoom_out (GSimpleAction *action, + GVariant *state, + gpointer user_data) +{ + NautilusFilesView *view; + + g_assert (NAUTILUS_IS_FILES_VIEW (user_data)); + + view = NAUTILUS_FILES_VIEW (user_data); + + nautilus_files_view_bump_zoom_level (view, -1); +} + +static void +action_zoom_standard (GSimpleAction *action, + GVariant *state, + gpointer user_data) +{ + nautilus_files_view_restore_standard_zoom_level (user_data); +} + +static void +action_open_item_new_window (GSimpleAction *action, + GVariant *state, + gpointer user_data) +{ + NautilusFilesView *view; + GList *selection; + + view = NAUTILUS_FILES_VIEW (user_data); + selection = nautilus_view_get_selection (NAUTILUS_VIEW (view)); + + nautilus_files_view_activate_files (view, + selection, + NAUTILUS_OPEN_FLAG_NEW_WINDOW, + TRUE); + + nautilus_file_list_free (selection); +} + +static void +handle_clipboard_data (NautilusFilesView *view, + NautilusClipboard *clip, + char *destination_uri, + GdkDragAction action) +{ + GList *item_uris = nautilus_clipboard_get_uri_list (clip); + + if (item_uris != NULL && destination_uri != NULL) + { + nautilus_files_view_move_copy_items (view, item_uris, destination_uri, + action); + + /* If items are cut then remove from clipboard */ + if (action == GDK_ACTION_MOVE) + { + gdk_clipboard_set_content (gtk_widget_get_clipboard (GTK_WIDGET (view)), + NULL); + } + + g_list_free_full (item_uris, g_free); + } +} + +static void +paste_clipboard_data (NautilusFilesView *view, + NautilusClipboard *clip, + char *destination_uri) +{ + GdkDragAction action; + + if (nautilus_clipboard_is_cut (clip)) + { + action = GDK_ACTION_MOVE; + } + else + { + action = GDK_ACTION_COPY; + } + + handle_clipboard_data (view, clip, destination_uri, action); +} + +static void +paste_clipboard_received_callback (GObject *source_object, + GAsyncResult *res, + gpointer user_data) +{ + NautilusFilesView *view = NAUTILUS_FILES_VIEW (source_object); + NautilusClipboard *clip; + g_autofree char *view_uri = NULL; + + clip = nautilus_files_view_get_clipboard_finish (view, res, NULL); + if (clip != NULL) + { + view_uri = nautilus_files_view_get_backing_uri (view); + paste_clipboard_data (view, clip, view_uri); + } +} + +static void +paste_files (NautilusFilesView *view) +{ + nautilus_files_view_get_clipboard_async (view, + paste_clipboard_received_callback, + NULL); +} + +static void +action_paste_files (GSimpleAction *action, + GVariant *state, + gpointer user_data) +{ + NautilusFilesView *view; + + view = NAUTILUS_FILES_VIEW (user_data); + + paste_files (view); +} + +static void +action_paste_files_accel (GSimpleAction *action, + GVariant *state, + gpointer user_data) +{ + NautilusFilesView *view; + + view = NAUTILUS_FILES_VIEW (user_data); + + if (nautilus_files_view_is_read_only (view)) + { + show_dialog (_("Could not paste files"), + _("Permissions do not allow pasting files in this directory"), + nautilus_files_view_get_containing_window (view), + GTK_MESSAGE_ERROR); + } + else + { + paste_files (view); + } +} + +static void +create_links_clipboard_received_callback (GObject *source_object, + GAsyncResult *res, + gpointer user_data) +{ + NautilusFilesView *view = NAUTILUS_FILES_VIEW (source_object); + NautilusClipboard *clip; + g_autofree char *view_uri = NULL; + + clip = nautilus_files_view_get_clipboard_finish (view, res, NULL); + if (clip != NULL) + { + view_uri = nautilus_files_view_get_backing_uri (view); + handle_clipboard_data (view, clip, view_uri, GDK_ACTION_LINK); + } +} + +static void +action_create_links (GSimpleAction *action, + GVariant *state, + gpointer user_data) +{ + g_assert (NAUTILUS_IS_FILES_VIEW (user_data)); + + nautilus_files_view_get_clipboard_async (NAUTILUS_FILES_VIEW (user_data), + create_links_clipboard_received_callback, + NULL); +} + +static void +click_policy_changed_callback (gpointer callback_data) +{ + NautilusFilesView *view; + + view = NAUTILUS_FILES_VIEW (callback_data); + + NAUTILUS_FILES_VIEW_CLASS (G_OBJECT_GET_CLASS (view))->click_policy_changed (view); +} + +gboolean +nautilus_files_view_should_sort_directories_first (NautilusFilesView *view) +{ + NautilusFilesViewPrivate *priv; + gboolean is_search; + + priv = nautilus_files_view_get_instance_private (view); + is_search = nautilus_view_is_searching (NAUTILUS_VIEW (view)); + + return priv->sort_directories_first && !is_search; +} + +static void +sort_directories_first_changed_callback (gpointer callback_data) +{ + NautilusFilesView *view; + NautilusFilesViewPrivate *priv; + gboolean preference_value; + + view = NAUTILUS_FILES_VIEW (callback_data); + priv = nautilus_files_view_get_instance_private (view); + + preference_value = + g_settings_get_boolean (gtk_filechooser_preferences, NAUTILUS_PREFERENCES_SORT_DIRECTORIES_FIRST); + + if (preference_value != priv->sort_directories_first) + { + priv->sort_directories_first = preference_value; + NAUTILUS_FILES_VIEW_CLASS (G_OBJECT_GET_CLASS (view))->sort_directories_first_changed (view); + } +} + +static void +show_hidden_files_changed_callback (gpointer callback_data) +{ + NautilusFilesView *view; + NautilusFilesViewPrivate *priv; + gboolean preference_value; + + view = NAUTILUS_FILES_VIEW (callback_data); + priv = nautilus_files_view_get_instance_private (view); + + preference_value = + g_settings_get_boolean (gtk_filechooser_preferences, NAUTILUS_PREFERENCES_SHOW_HIDDEN_FILES); + + nautilus_files_view_set_show_hidden_files (view, preference_value); + + if (priv->active) + { + schedule_update_context_menus (view); + } +} + +static gboolean +set_up_scripts_directory_global (void) +{ + g_autofree gchar *old_scripts_directory_path = NULL; + g_autoptr (GFile) old_scripts_directory = NULL; + g_autofree gchar *scripts_directory_path = NULL; + g_autoptr (GFile) scripts_directory = NULL; + const char *override; + GFileType file_type; + g_autoptr (GError) error = NULL; + + if (scripts_directory_uri != NULL) + { + return TRUE; + } + + scripts_directory_path = nautilus_get_scripts_directory_path (); + + override = g_getenv ("GNOME22_USER_DIR"); + + if (override) + { + old_scripts_directory_path = g_build_filename (override, + "nautilus-scripts", + NULL); + } + else + { + old_scripts_directory_path = g_build_filename (g_get_home_dir (), + ".gnome2", + "nautilus-scripts", + NULL); + } + + old_scripts_directory = g_file_new_for_path (old_scripts_directory_path); + scripts_directory = g_file_new_for_path (scripts_directory_path); + + file_type = g_file_query_file_type (old_scripts_directory, + G_FILE_QUERY_INFO_NONE, + NULL); + + if (file_type == G_FILE_TYPE_DIRECTORY && + !g_file_query_exists (scripts_directory, NULL)) + { + g_autoptr (GFile) updated = NULL; + const char *message; + + /* test if we already attempted to migrate first */ + updated = g_file_get_child (old_scripts_directory, "DEPRECATED-DIRECTORY"); + message = _("Nautilus 3.6 deprecated this directory and tried migrating " + "this configuration to ~/.local/share/nautilus"); + if (!g_file_query_exists (updated, NULL)) + { + g_autoptr (GFile) parent = NULL; + + parent = g_file_get_parent (scripts_directory); + g_file_make_directory_with_parents (parent, NULL, &error); + + if (error == NULL || + g_error_matches (error, G_IO_ERROR, G_IO_ERROR_EXISTS)) + { + g_clear_error (&error); + + g_file_set_attribute_uint32 (parent, + G_FILE_ATTRIBUTE_UNIX_MODE, + S_IRWXU, + G_FILE_QUERY_INFO_NONE, + NULL, NULL); + + g_file_move (old_scripts_directory, + scripts_directory, + G_FILE_COPY_NONE, + NULL, NULL, NULL, + &error); + + if (error == NULL) + { + g_file_replace_contents (updated, + message, strlen (message), + NULL, + FALSE, + G_FILE_CREATE_PRIVATE, + NULL, NULL, NULL); + } + } + + g_clear_error (&error); + } + } + + g_file_make_directory_with_parents (scripts_directory, NULL, &error); + + if (error == NULL || + g_error_matches (error, G_IO_ERROR, G_IO_ERROR_EXISTS)) + { + g_file_set_attribute_uint32 (scripts_directory, + G_FILE_ATTRIBUTE_UNIX_MODE, + S_IRWXU, + G_FILE_QUERY_INFO_NONE, + NULL, NULL); + + scripts_directory_uri = g_file_get_uri (scripts_directory); + scripts_directory_uri_length = strlen (scripts_directory_uri); + } + + return scripts_directory_uri != NULL; +} + +static void +scripts_added_or_changed_callback (NautilusDirectory *directory, + GList *files, + gpointer callback_data) +{ + NautilusFilesView *view; + NautilusFilesViewPrivate *priv; + + view = NAUTILUS_FILES_VIEW (callback_data); + priv = nautilus_files_view_get_instance_private (view); + + priv->scripts_menu_updated = FALSE; + if (priv->active) + { + schedule_update_context_menus (view); + } +} + +static void +templates_added_or_changed_callback (NautilusDirectory *directory, + GList *files, + gpointer callback_data) +{ + NautilusFilesView *view; + NautilusFilesViewPrivate *priv; + + view = NAUTILUS_FILES_VIEW (callback_data); + priv = nautilus_files_view_get_instance_private (view); + + priv->templates_menu_updated = FALSE; + if (priv->active) + { + schedule_update_context_menus (view); + } +} + +static void +add_directory_to_directory_list (NautilusFilesView *view, + NautilusDirectory *directory, + GList **directory_list, + GCallback changed_callback) +{ + NautilusFileAttributes attributes; + + if (g_list_find (*directory_list, directory) == NULL) + { + nautilus_directory_ref (directory); + + attributes = + NAUTILUS_FILE_ATTRIBUTES_FOR_ICON | + NAUTILUS_FILE_ATTRIBUTE_INFO | + NAUTILUS_FILE_ATTRIBUTE_DIRECTORY_ITEM_COUNT; + + nautilus_directory_file_monitor_add (directory, directory_list, + FALSE, attributes, + (NautilusDirectoryCallback) changed_callback, view); + + g_signal_connect_object (directory, "files-added", + G_CALLBACK (changed_callback), view, 0); + g_signal_connect_object (directory, "files-changed", + G_CALLBACK (changed_callback), view, 0); + + *directory_list = g_list_append (*directory_list, directory); + } +} + +static void +remove_directory_from_directory_list (NautilusFilesView *view, + NautilusDirectory *directory, + GList **directory_list, + GCallback changed_callback) +{ + *directory_list = g_list_remove (*directory_list, directory); + + g_signal_handlers_disconnect_by_func (directory, + G_CALLBACK (changed_callback), + view); + + nautilus_directory_file_monitor_remove (directory, directory_list); + + nautilus_directory_unref (directory); +} + + +static void +add_directory_to_scripts_directory_list (NautilusFilesView *view, + NautilusDirectory *directory) +{ + NautilusFilesViewPrivate *priv; + + priv = nautilus_files_view_get_instance_private (view); + + add_directory_to_directory_list (view, directory, + &priv->scripts_directory_list, + G_CALLBACK (scripts_added_or_changed_callback)); +} + +static void +remove_directory_from_scripts_directory_list (NautilusFilesView *view, + NautilusDirectory *directory) +{ + NautilusFilesViewPrivate *priv; + + priv = nautilus_files_view_get_instance_private (view); + + remove_directory_from_directory_list (view, directory, + &priv->scripts_directory_list, + G_CALLBACK (scripts_added_or_changed_callback)); +} + +static void +add_directory_to_templates_directory_list (NautilusFilesView *view, + NautilusDirectory *directory) +{ + NautilusFilesViewPrivate *priv; + + priv = nautilus_files_view_get_instance_private (view); + + add_directory_to_directory_list (view, directory, + &priv->templates_directory_list, + G_CALLBACK (templates_added_or_changed_callback)); +} + +static void +remove_directory_from_templates_directory_list (NautilusFilesView *view, + NautilusDirectory *directory) +{ + NautilusFilesViewPrivate *priv; + + priv = nautilus_files_view_get_instance_private (view); + + remove_directory_from_directory_list (view, directory, + &priv->templates_directory_list, + G_CALLBACK (templates_added_or_changed_callback)); +} + +static void +slot_active_changed (NautilusWindowSlot *slot, + GParamSpec *pspec, + NautilusFilesView *view) +{ + NautilusFilesViewPrivate *priv; + + priv = nautilus_files_view_get_instance_private (view); + + if (priv->active == nautilus_window_slot_get_active (slot)) + { + return; + } + + priv->active = nautilus_window_slot_get_active (slot); + + if (priv->active) + { + /* Avoid updating the toolbar withouth making sure the toolbar + * zoom slider has the correct adjustment that changes when the + * view mode changes + */ + nautilus_files_view_update_context_menus (view); + nautilus_files_view_update_toolbar_menus (view); + + schedule_update_context_menus (view); + + gtk_widget_insert_action_group (GTK_WIDGET (nautilus_files_view_get_window (view)), + "view", + G_ACTION_GROUP (priv->view_action_group)); + } + else + { + remove_update_context_menus_timeout_callback (view); + gtk_widget_insert_action_group (GTK_WIDGET (nautilus_files_view_get_window (view)), + "view", + NULL); + } +} + +static gboolean +nautilus_files_view_focus (GtkWidget *widget, + GtkDirectionType direction) +{ + NautilusFilesView *view; + NautilusFilesViewPrivate *priv; + GtkWidget *focus; + GtkWidget *main_child; + + view = NAUTILUS_FILES_VIEW (widget); + priv = nautilus_files_view_get_instance_private (view); + focus = gtk_window_get_focus (GTK_WINDOW (gtk_widget_get_root (widget))); + + /* In general, we want to forward focus movement to the main child. However, + * we must chain up for default focus handling in case the focus in in any + * other child, e.g. a popover. */ + if (gtk_widget_is_ancestor (focus, widget) && + !gtk_widget_is_ancestor (focus, priv->scrolled_window)) + { + if (GTK_WIDGET_CLASS (nautilus_files_view_parent_class)->focus (widget, direction)) + { + return TRUE; + } + else + { + /* The default handler returns FALSE if a popover has just been + * closed, because it moves the focus forward. But we want to move + * focus back into the view's main child. So, fall through. */ + } + } + + main_child = gtk_scrolled_window_get_child (GTK_SCROLLED_WINDOW (priv->scrolled_window)); + if (main_child != NULL) + { + return gtk_widget_child_focus (main_child, direction); + } + + return FALSE; +} + +static gboolean +nautilus_files_view_grab_focus (GtkWidget *widget) +{ + /* focus the child of the scrolled window if it exists */ + NautilusFilesView *view; + NautilusFilesViewPrivate *priv; + GtkWidget *child; + + view = NAUTILUS_FILES_VIEW (widget); + priv = nautilus_files_view_get_instance_private (view); + child = gtk_scrolled_window_get_child (GTK_SCROLLED_WINDOW (priv->scrolled_window)); + + if (child != NULL) + { + return gtk_widget_grab_focus (GTK_WIDGET (child)); + } + + return GTK_WIDGET_CLASS (nautilus_files_view_parent_class)->grab_focus (widget); +} + +static void +nautilus_files_view_set_selection (NautilusView *nautilus_files_view, + GList *selection) +{ + NautilusFilesView *view; + NautilusFilesViewPrivate *priv; + GList *pending_selection; + + view = NAUTILUS_FILES_VIEW (nautilus_files_view); + priv = nautilus_files_view_get_instance_private (view); + + if (!priv->loading) + { + /* If we aren't still loading, set the selection right now, + * and reveal the new selection. + */ + nautilus_files_view_call_set_selection (view, selection); + nautilus_files_view_reveal_selection (view); + } + else + { + /* If we are still loading, set the list of pending URIs instead. + * done_loading() will eventually select the pending URIs and reveal them. + */ + pending_selection = g_list_copy_deep (selection, + (GCopyFunc) g_object_ref, NULL); + g_list_free_full (priv->pending_selection, g_object_unref); + + priv->pending_selection = pending_selection; + } +} + +static void +nautilus_files_view_dispose (GObject *object) +{ + NautilusFilesView *view; + NautilusFilesViewPrivate *priv; + GdkClipboard *clipboard; + GList *node, *next; + + view = NAUTILUS_FILES_VIEW (object); + priv = nautilus_files_view_get_instance_private (view); + + priv->in_destruction = TRUE; + nautilus_files_view_stop_loading (view); + + g_clear_pointer (&priv->selection_menu, gtk_widget_unparent); + g_clear_pointer (&priv->background_menu, gtk_widget_unparent); + + if (priv->model) + { + nautilus_directory_unref (priv->model); + priv->model = NULL; + } + + for (node = priv->scripts_directory_list; node != NULL; node = next) + { + next = node->next; + remove_directory_from_scripts_directory_list (view, node->data); + } + + for (node = priv->templates_directory_list; node != NULL; node = next) + { + next = node->next; + remove_directory_from_templates_directory_list (view, node->data); + } + + while (priv->subdirectory_list != NULL) + { + nautilus_files_view_remove_subdirectory (view, + priv->subdirectory_list->data); + } + + remove_update_context_menus_timeout_callback (view); + remove_update_status_idle_callback (view); + + if (priv->display_selection_idle_id != 0) + { + g_source_remove (priv->display_selection_idle_id); + priv->display_selection_idle_id = 0; + } + + if (priv->reveal_selection_idle_id != 0) + { + g_source_remove (priv->reveal_selection_idle_id); + priv->reveal_selection_idle_id = 0; + } + + if (priv->floating_bar_set_status_timeout_id != 0) + { + g_source_remove (priv->floating_bar_set_status_timeout_id); + priv->floating_bar_set_status_timeout_id = 0; + } + + if (priv->floating_bar_loading_timeout_id != 0) + { + g_source_remove (priv->floating_bar_loading_timeout_id); + priv->floating_bar_loading_timeout_id = 0; + } + + if (priv->floating_bar_set_passthrough_timeout_id != 0) + { + g_source_remove (priv->floating_bar_set_passthrough_timeout_id); + priv->floating_bar_set_passthrough_timeout_id = 0; + } + + g_signal_handlers_disconnect_by_func (nautilus_preferences, + schedule_update_context_menus, view); + g_signal_handlers_disconnect_by_func (nautilus_preferences, + click_policy_changed_callback, view); + g_signal_handlers_disconnect_by_func (gtk_filechooser_preferences, + sort_directories_first_changed_callback, view); + g_signal_handlers_disconnect_by_func (gtk_filechooser_preferences, + show_hidden_files_changed_callback, view); + g_signal_handlers_disconnect_by_func (nautilus_window_state, + nautilus_files_view_display_selection_info, view); + g_signal_handlers_disconnect_by_func (gnome_lockdown_preferences, + schedule_update_context_menus, view); + g_signal_handlers_disconnect_by_func (nautilus_trash_monitor_get (), + nautilus_files_view_trash_state_changed_callback, view); + + clipboard = gdk_display_get_clipboard (gdk_display_get_default ()); + g_signal_handlers_disconnect_by_func (clipboard, on_clipboard_owner_changed, view); + g_cancellable_cancel (priv->clipboard_cancellable); + + nautilus_file_unref (priv->directory_as_file); + priv->directory_as_file = NULL; + + g_clear_object (&priv->search_query); + g_clear_object (&priv->location); + + G_OBJECT_CLASS (nautilus_files_view_parent_class)->dispose (object); +} + +static void +nautilus_files_view_finalize (GObject *object) +{ + NautilusFilesView *view; + NautilusFilesViewPrivate *priv; + + view = NAUTILUS_FILES_VIEW (object); + priv = nautilus_files_view_get_instance_private (view); + + g_clear_object (&priv->view_action_group); + g_clear_object (&priv->background_menu_model); + g_clear_object (&priv->selection_menu_model); + g_clear_object (&priv->toolbar_menu_sections->sort_section); + g_clear_object (&priv->extensions_background_menu); + g_clear_object (&priv->templates_menu); + g_clear_object (&priv->rename_file_controller); + g_clear_object (&priv->new_folder_controller); + g_clear_object (&priv->compress_controller); + /* We don't own the slot, so no unref */ + priv->slot = NULL; + + g_free (priv->toolbar_menu_sections); + + g_hash_table_destroy (priv->non_ready_files); + g_hash_table_destroy (priv->pending_reveal); + + g_clear_object (&priv->clipboard_cancellable); + + g_cancellable_cancel (priv->starred_cancellable); + g_clear_object (&priv->starred_cancellable); + + G_OBJECT_CLASS (nautilus_files_view_parent_class)->finalize (object); +} + +/** + * nautilus_files_view_display_selection_info: + * + * Display information about the current selection, and notify the view frame of the changed selection. + * @view: NautilusFilesView for which to display selection info. + * + **/ +void +nautilus_files_view_display_selection_info (NautilusFilesView *view) +{ + g_autolist (NautilusFile) selection = NULL; + goffset non_folder_size; + gboolean non_folder_size_known; + guint non_folder_count, folder_count, folder_item_count; + gboolean folder_item_count_known; + guint file_item_count; + GList *p; + char *first_item_name; + char *non_folder_count_str; + char *non_folder_item_count_str; + char *non_folder_counts_str; + char *folder_count_str; + char *folder_item_count_str; + char *folder_counts_str; + char *primary_status; + char *detail_status; + NautilusFile *file; + + g_return_if_fail (NAUTILUS_IS_FILES_VIEW (view)); + + selection = nautilus_view_get_selection (NAUTILUS_VIEW (view)); + + folder_item_count_known = TRUE; + folder_count = 0; + folder_item_count = 0; + non_folder_count = 0; + non_folder_size_known = FALSE; + non_folder_size = 0; + first_item_name = NULL; + folder_count_str = NULL; + folder_item_count_str = NULL; + folder_counts_str = NULL; + non_folder_count_str = NULL; + non_folder_item_count_str = NULL; + non_folder_counts_str = NULL; + + for (p = selection; p != NULL; p = p->next) + { + file = p->data; + if (nautilus_file_is_directory (file)) + { + folder_count++; + if (nautilus_file_get_directory_item_count (file, &file_item_count, NULL)) + { + folder_item_count += file_item_count; + } + else + { + folder_item_count_known = FALSE; + } + } + else + { + non_folder_count++; + if (!nautilus_file_can_get_size (file)) + { + non_folder_size_known = TRUE; + non_folder_size += nautilus_file_get_size (file); + } + } + + if (first_item_name == NULL) + { + first_item_name = nautilus_file_get_display_name (file); + } + } + + /* Break out cases for localization's sake. But note that there are still pieces + * being assembled in a particular order, which may be a problem for some localizers. + */ + + if (folder_count != 0) + { + if (folder_count == 1 && non_folder_count == 0) + { + folder_count_str = g_strdup_printf (_("“%s” selected"), first_item_name); + } + else + { + folder_count_str = g_strdup_printf (ngettext ("%'d folder selected", + "%'d folders selected", + folder_count), + folder_count); + } + + if (folder_count == 1) + { + if (!folder_item_count_known) + { + folder_item_count_str = g_strdup (""); + } + else + { + folder_item_count_str = g_strdup_printf (ngettext ("(containing %'d item)", + "(containing %'d items)", + folder_item_count), + folder_item_count); + } + } + else + { + if (!folder_item_count_known) + { + folder_item_count_str = g_strdup (""); + } + else + { + /* translators: this is preceded with a string of form 'N folders' (N more than 1) */ + folder_item_count_str = g_strdup_printf (ngettext ("(containing a total of %'d item)", + "(containing a total of %'d items)", + folder_item_count), + folder_item_count); + } + } + } + + if (non_folder_count != 0) + { + if (folder_count == 0) + { + if (non_folder_count == 1) + { + non_folder_count_str = g_strdup_printf (_("“%s” selected"), + first_item_name); + } + else + { + non_folder_count_str = g_strdup_printf (ngettext ("%'d item selected", + "%'d items selected", + non_folder_count), + non_folder_count); + } + } + else + { + /* Folders selected also, use "other" terminology */ + non_folder_count_str = g_strdup_printf (ngettext ("%'d other item selected", + "%'d other items selected", + non_folder_count), + non_folder_count); + } + + if (non_folder_size_known) + { + char *size_string; + + size_string = g_format_size (non_folder_size); + /* This is marked for translation in case a localiser + * needs to use something other than parentheses. The + * the message in parentheses is the size of the selected items. + */ + non_folder_item_count_str = g_strdup_printf (_("(%s)"), size_string); + g_free (size_string); + } + else + { + non_folder_item_count_str = g_strdup (""); + } + } + + if (folder_count == 0 && non_folder_count == 0) + { + primary_status = NULL; + detail_status = NULL; + } + else if (folder_count == 0) + { + primary_status = g_strdup (non_folder_count_str); + detail_status = g_strdup (non_folder_item_count_str); + } + else if (non_folder_count == 0) + { + primary_status = g_strdup (folder_count_str); + detail_status = g_strdup (folder_item_count_str); + } + else + { + if (folder_item_count_known) + { + folder_counts_str = g_strconcat (folder_count_str, " ", folder_item_count_str, NULL); + } + else + { + folder_counts_str = g_strdup (folder_count_str); + } + + if (non_folder_size_known) + { + non_folder_counts_str = g_strconcat (non_folder_count_str, " ", non_folder_item_count_str, NULL); + } + else + { + non_folder_counts_str = g_strdup (non_folder_count_str); + } + /* This is marked for translation in case a localizer + * needs to change ", " to something else. The comma + * is between the message about the number of folders + * and the number of items in those folders and the + * message about the number of other items and the + * total size of those items. + */ + primary_status = g_strdup_printf (_("%s, %s"), + folder_counts_str, + non_folder_counts_str); + detail_status = NULL; + } + + g_free (first_item_name); + g_free (folder_count_str); + g_free (folder_item_count_str); + g_free (folder_counts_str); + g_free (non_folder_count_str); + g_free (non_folder_item_count_str); + g_free (non_folder_counts_str); + + set_floating_bar_status (view, primary_status, detail_status); + + g_free (primary_status); + g_free (detail_status); +} + +static void +nautilus_files_view_send_selection_change (NautilusFilesView *view) +{ + g_signal_emit (view, signals[SELECTION_CHANGED], 0); + g_object_notify (G_OBJECT (view), "selection"); +} + +static void +nautilus_files_view_set_location (NautilusView *view, + GFile *location) +{ + NautilusDirectory *directory; + NautilusFilesView *files_view; + + nautilus_profile_start (NULL); + files_view = NAUTILUS_FILES_VIEW (view); + directory = nautilus_directory_get (location); + + nautilus_files_view_stop_loading (files_view); + /* In case we want to load a previous search we need to extract the real + * location and the search location, and load the directory when everything + * is ready. That's why we cannot use the nautilus_view_set_query, because + * to set a query we need a previous location loaded, but to load a search + * location we need to know the real location behind it. */ + if (NAUTILUS_IS_SEARCH_DIRECTORY (directory)) + { + NautilusQuery *previous_query; + NautilusDirectory *base_model; + + base_model = nautilus_search_directory_get_base_model (NAUTILUS_SEARCH_DIRECTORY (directory)); + previous_query = nautilus_search_directory_get_query (NAUTILUS_SEARCH_DIRECTORY (directory)); + set_search_query_internal (files_view, previous_query, base_model); + g_object_unref (previous_query); + } + else + { + load_directory (NAUTILUS_FILES_VIEW (view), directory); + } + nautilus_directory_unref (directory); + nautilus_profile_end (NULL); +} + +static gboolean +reveal_selection_idle_callback (gpointer data) +{ + NautilusFilesView *view; + NautilusFilesViewPrivate *priv; + + view = NAUTILUS_FILES_VIEW (data); + priv = nautilus_files_view_get_instance_private (view); + + priv->reveal_selection_idle_id = 0; + nautilus_files_view_reveal_selection (view); + + return FALSE; +} + +static void +nautilus_files_view_check_empty_states (NautilusFilesView *view) +{ + NAUTILUS_FILES_VIEW_CLASS (G_OBJECT_GET_CLASS (view))->check_empty_states (view); +} + +static void +real_check_empty_states (NautilusFilesView *view) +{ + NautilusFilesViewPrivate *priv = nautilus_files_view_get_instance_private (view); + g_autofree gchar *uri = NULL; + AdwStatusPage *status_page = ADW_STATUS_PAGE (priv->empty_view_page); + + if (!priv->loading && + nautilus_files_view_is_empty (view)) + { + uri = g_file_get_uri (priv->location); + + if (nautilus_view_is_searching (NAUTILUS_VIEW (view))) + { + adw_status_page_set_icon_name (status_page, "edit-find-symbolic"); + adw_status_page_set_title (status_page, _("No Results Found")); + adw_status_page_set_description (status_page, _("Try a different search.")); + } + else if (eel_uri_is_trash_root (uri)) + { + adw_status_page_set_icon_name (status_page, "user-trash-symbolic"); + adw_status_page_set_title (status_page, _("Trash is Empty")); + adw_status_page_set_description (status_page, NULL); + } + else if (eel_uri_is_starred (uri)) + { + adw_status_page_set_icon_name (status_page, "starred-symbolic"); + adw_status_page_set_title (status_page, _("No Starred Files")); + adw_status_page_set_description (status_page, NULL); + } + else if (eel_uri_is_recent (uri)) + { + adw_status_page_set_icon_name (status_page, "document-open-recent-symbolic"); + adw_status_page_set_title (status_page, _("No Recent Files")); + adw_status_page_set_description (status_page, NULL); + } + else + { + adw_status_page_set_icon_name (status_page, "folder-symbolic"); + adw_status_page_set_title (status_page, _("Folder is Empty")); + adw_status_page_set_description (status_page, NULL); + } + + gtk_stack_set_visible_child (GTK_STACK (priv->stack), priv->empty_view_page); + } + else + { + gtk_stack_set_visible_child (GTK_STACK (priv->stack), priv->scrolled_window); + } +} + +static void +done_loading (NautilusFilesView *view, + gboolean all_files_seen) +{ + NautilusFilesViewPrivate *priv; + g_autolist (NautilusFile) selection = NULL; + gboolean do_reveal = FALSE; + + priv = nautilus_files_view_get_instance_private (view); + + if (!priv->loading) + { + return; + } + + nautilus_profile_start (NULL); + + if (!priv->in_destruction) + { + remove_loading_floating_bar (view); + schedule_update_context_menus (view); + schedule_update_status (view); + nautilus_files_view_update_toolbar_menus (view); + reset_update_interval (view); + + selection = nautilus_view_get_selection (NAUTILUS_VIEW (view)); + + if (nautilus_view_is_searching (NAUTILUS_VIEW (view)) && + all_files_seen && selection == NULL && priv->pending_selection == NULL) + { + nautilus_files_view_select_first (view); + do_reveal = TRUE; + } + else if (priv->pending_selection != NULL && all_files_seen) + { + g_autolist (NautilusFile) pending_selection = NULL; + pending_selection = g_steal_pointer (&priv->pending_selection); + + nautilus_files_view_call_set_selection (view, pending_selection); + do_reveal = TRUE; + } + + g_clear_pointer (&priv->pending_selection, nautilus_file_list_free); + + if (do_reveal) + { + if (NAUTILUS_IS_LIST_VIEW (view) || NAUTILUS_IS_GRID_VIEW (view)) + { + /* HACK: We should be able to directly call reveal_selection here, + * but at this point the GtkTreeView hasn't allocated the new nodes + * yet, and it has a bug in the scroll calculation dealing with this + * special case. It would always make the selection the top row, even + * if no scrolling would be neccessary to reveal it. So we let it + * allocate before revealing. + */ + if (priv->reveal_selection_idle_id != 0) + { + g_source_remove (priv->reveal_selection_idle_id); + } + priv->reveal_selection_idle_id = + g_idle_add (reveal_selection_idle_callback, view); + } + else + { + nautilus_files_view_reveal_selection (view); + } + } + nautilus_files_view_display_selection_info (view); + } + + priv->loading = FALSE; + g_signal_emit (view, signals[END_LOADING], 0, all_files_seen); + g_object_notify (G_OBJECT (view), "loading"); + + if (!priv->in_destruction) + { + nautilus_files_view_check_empty_states (view); + } + + nautilus_profile_end (NULL); +} + + +typedef struct +{ + GHashTable *debuting_files; + GList *added_files; +} DebutingFilesData; + +static void +debuting_files_data_free (DebutingFilesData *data) +{ + g_hash_table_unref (data->debuting_files); + nautilus_file_list_free (data->added_files); + g_free (data); +} + +/* This signal handler watch for the arrival of the icons created + * as the result of a file operation. Once the last one is detected + * it selects and reveals them all. + */ +static void +debuting_files_add_files_callback (NautilusFilesView *view, + GList *new_files, + DebutingFilesData *data) +{ + GFile *location; + GList *l; + + nautilus_profile_start (NULL); + + for (l = new_files; l != NULL; l = l->next) + { + location = nautilus_file_get_location (NAUTILUS_FILE (l->data)); + + if (g_hash_table_remove (data->debuting_files, location)) + { + nautilus_file_ref (NAUTILUS_FILE (l->data)); + data->added_files = g_list_prepend (data->added_files, NAUTILUS_FILE (l->data)); + } + g_object_unref (location); + } + + if (g_hash_table_size (data->debuting_files) == 0) + { + nautilus_files_view_call_set_selection (view, data->added_files); + nautilus_files_view_reveal_selection (view); + g_signal_handlers_disconnect_by_func (view, + G_CALLBACK (debuting_files_add_files_callback), + data); + } + + nautilus_profile_end (NULL); +} + +typedef struct +{ + GList *added_files; + NautilusFilesView *directory_view; +} CopyMoveDoneData; + +static void +copy_move_done_data_free (CopyMoveDoneData *data) +{ + g_assert (data != NULL); + + if (data->directory_view != NULL) + { + g_object_remove_weak_pointer (G_OBJECT (data->directory_view), + (gpointer *) &data->directory_view); + } + + nautilus_file_list_free (data->added_files); + g_free (data); +} + +static void +pre_copy_move_add_files_callback (NautilusFilesView *view, + GList *new_files, + CopyMoveDoneData *data) +{ + GList *l; + + for (l = new_files; l != NULL; l = l->next) + { + nautilus_file_ref (NAUTILUS_FILE (l->data)); + data->added_files = g_list_prepend (data->added_files, l->data); + } +} + +/* This needs to be called prior to nautilus_file_operations_copy_move. + * It hooks up a signal handler to catch any icons that get added before + * the copy_done_callback is invoked. The return value should be passed + * as the data for uri_copy_move_done_callback. + */ +static CopyMoveDoneData * +pre_copy_move (NautilusFilesView *directory_view) +{ + CopyMoveDoneData *copy_move_done_data; + + copy_move_done_data = g_new0 (CopyMoveDoneData, 1); + copy_move_done_data->directory_view = directory_view; + + g_object_add_weak_pointer (G_OBJECT (copy_move_done_data->directory_view), + (gpointer *) ©_move_done_data->directory_view); + + /* We need to run after the default handler adds the folder we want to + * operate on. The ADD_FILES signal is registered as G_SIGNAL_RUN_LAST, so we + * must use connect_after. + */ + g_signal_connect_after (directory_view, "add-files", + G_CALLBACK (pre_copy_move_add_files_callback), copy_move_done_data); + + return copy_move_done_data; +} + +/* This function is used to pull out any debuting uris that were added + * and (as a side effect) remove them from the debuting uri hash table. + */ +static gboolean +copy_move_done_partition_func (NautilusFile *file, + gpointer callback_data) +{ + GFile *location; + gboolean result; + + location = nautilus_file_get_location (file); + result = g_hash_table_remove ((GHashTable *) callback_data, location); + g_object_unref (location); + + return result; +} + +static gboolean +remove_not_really_moved_files (gpointer key, + gpointer value, + gpointer callback_data) +{ + GList **added_files; + GFile *loc; + + loc = key; + + if (GPOINTER_TO_INT (value)) + { + return FALSE; + } + + added_files = callback_data; + *added_files = g_list_prepend (*added_files, + nautilus_file_get (loc)); + return TRUE; +} + +/* When this function is invoked, the file operation is over, but all + * the icons may not have been added to the directory view yet, so + * we can't select them yet. + * + * We're passed a hash table of the uri's to look out for, we hook + * up a signal handler to await their arrival. + */ +static void +copy_move_done_callback (GHashTable *debuting_files, + gboolean success, + gpointer data) +{ + NautilusFilesView *directory_view; + CopyMoveDoneData *copy_move_done_data; + DebutingFilesData *debuting_files_data; + GList *failed_files; + + copy_move_done_data = (CopyMoveDoneData *) data; + directory_view = copy_move_done_data->directory_view; + + if (directory_view != NULL) + { + g_assert (NAUTILUS_IS_FILES_VIEW (directory_view)); + + debuting_files_data = g_new (DebutingFilesData, 1); + debuting_files_data->debuting_files = g_hash_table_ref (debuting_files); + debuting_files_data->added_files = nautilus_file_list_filter (copy_move_done_data->added_files, + &failed_files, + copy_move_done_partition_func, + debuting_files); + nautilus_file_list_free (copy_move_done_data->added_files); + copy_move_done_data->added_files = failed_files; + + /* We're passed the same data used by pre_copy_move_add_files_callback, so disconnecting + * it will free data. We've already siphoned off the added_files we need, and stashed the + * directory_view pointer. + */ + g_signal_handlers_disconnect_by_func (directory_view, + G_CALLBACK (pre_copy_move_add_files_callback), + data); + + /* Any items in the debuting_files hash table that have + * "FALSE" as their value aren't really being copied + * or moved, so we can't wait for an add_files signal + * to come in for those. + */ + g_hash_table_foreach_remove (debuting_files, + remove_not_really_moved_files, + &debuting_files_data->added_files); + + if (g_hash_table_size (debuting_files) == 0) + { + /* on the off-chance that all the icons have already been added */ + if (debuting_files_data->added_files != NULL) + { + nautilus_files_view_call_set_selection (directory_view, + debuting_files_data->added_files); + nautilus_files_view_reveal_selection (directory_view); + } + debuting_files_data_free (debuting_files_data); + } + else + { + /* We need to run after the default handler adds the folder we want to + * operate on. The ADD_FILES signal is registered as G_SIGNAL_RUN_LAST, so we + * must use connect_after. + */ + g_signal_connect_data (directory_view, + "add-files", + G_CALLBACK (debuting_files_add_files_callback), + debuting_files_data, + (GClosureNotify) debuting_files_data_free, + G_CONNECT_AFTER); + } + /* Schedule menu update for undo items */ + schedule_update_context_menus (directory_view); + } + + copy_move_done_data_free (copy_move_done_data); +} + +static gboolean +view_file_still_belongs (NautilusFilesView *view, + FileAndDirectory *fad) +{ + NautilusFilesViewPrivate *priv; + + priv = nautilus_files_view_get_instance_private (view); + + if (priv->model != fad->directory && + g_list_find (priv->subdirectory_list, fad->directory) == NULL) + { + return FALSE; + } + + return nautilus_directory_contains_file (fad->directory, fad->file); +} + +static gboolean +still_should_show_file (NautilusFilesView *view, + FileAndDirectory *fad) +{ + return nautilus_files_view_should_show_file (view, fad->file) && + view_file_still_belongs (view, fad); +} + +static gboolean +ready_to_load (NautilusFile *file) +{ + return nautilus_file_check_if_ready (file, + NAUTILUS_FILE_ATTRIBUTES_FOR_ICON); +} + +/* Go through all the new added and changed files. + * Put any that are not ready to load in the non_ready_files hash table. + * Add all the rest to the old_added_files and old_changed_files lists. + * Sort the old_*_files lists if anything was added to them. + */ +static void +process_new_files (NautilusFilesView *view) +{ + NautilusFilesViewPrivate *priv; + g_autolist (FileAndDirectory) new_added_files = NULL; + g_autolist (FileAndDirectory) new_changed_files = NULL; + GList *old_added_files; + GList *old_changed_files; + GHashTable *non_ready_files; + GList *node, *next; + FileAndDirectory *pending; + gboolean in_non_ready; + + priv = nautilus_files_view_get_instance_private (view); + + new_added_files = g_steal_pointer (&priv->new_added_files); + new_changed_files = g_steal_pointer (&priv->new_changed_files); + + non_ready_files = priv->non_ready_files; + + old_added_files = priv->old_added_files; + old_changed_files = priv->old_changed_files; + + /* Newly added files go into the old_added_files list if they're + * ready, and into the hash table if they're not. + */ + for (node = new_added_files; node != NULL; node = next) + { + next = node->next; + pending = (FileAndDirectory *) node->data; + in_non_ready = g_hash_table_contains (non_ready_files, pending); + if (nautilus_files_view_should_show_file (view, pending->file)) + { + if (ready_to_load (pending->file)) + { + if (in_non_ready) + { + g_hash_table_remove (non_ready_files, pending); + } + new_added_files = g_list_delete_link (new_added_files, node); + old_added_files = g_list_prepend (old_added_files, pending); + } + else + { + if (!in_non_ready) + { + new_added_files = g_list_delete_link (new_added_files, node); + g_hash_table_add (non_ready_files, pending); + } + } + } + } + + /* Newly changed files go into the old_added_files list if they're ready + * and were seen non-ready in the past, into the old_changed_files list + * if they are read and were not seen non-ready in the past, and into + * the hash table if they're not ready. + */ + for (node = new_changed_files; node != NULL; node = next) + { + next = node->next; + pending = (FileAndDirectory *) node->data; + if (!still_should_show_file (view, pending) || ready_to_load (pending->file)) + { + if (g_hash_table_contains (non_ready_files, pending)) + { + g_hash_table_remove (non_ready_files, pending); + if (still_should_show_file (view, pending)) + { + new_changed_files = g_list_delete_link (new_changed_files, node); + old_added_files = g_list_prepend (old_added_files, pending); + } + } + else + { + new_changed_files = g_list_delete_link (new_changed_files, node); + old_changed_files = g_list_prepend (old_changed_files, pending); + } + } + } + + if (old_added_files != priv->old_added_files) + { + priv->old_added_files = old_added_files; + } + + if (old_changed_files != priv->old_changed_files) + { + priv->old_changed_files = old_changed_files; + } +} + +static void +on_end_file_changes (NautilusFilesView *view) +{ + NautilusFilesViewPrivate *priv; + + priv = nautilus_files_view_get_instance_private (view); + + /* Addition and removal of files modify the empty state */ + nautilus_files_view_check_empty_states (view); + /* If the view is empty, zoom slider and sort menu are insensitive */ + nautilus_files_view_update_toolbar_menus (view); + + /* Reveal files that were pending to be revealed, only if all of them + * were acknowledged by the view + */ + if (g_hash_table_size (priv->pending_reveal) > 0) + { + GList *keys; + GList *l; + gboolean all_files_acknowledged = TRUE; + + keys = g_hash_table_get_keys (priv->pending_reveal); + for (l = keys; l && all_files_acknowledged; l = l->next) + { + all_files_acknowledged = GPOINTER_TO_UINT (g_hash_table_lookup (priv->pending_reveal, + l->data)); + } + + if (all_files_acknowledged) + { + nautilus_files_view_set_selection (NAUTILUS_VIEW (view), keys); + nautilus_files_view_reveal_selection (view); + g_hash_table_remove_all (priv->pending_reveal); + } + + g_list_free (keys); + } +} + +static int +compare_pointers (gconstpointer pointer_1, + gconstpointer pointer_2) +{ + if (pointer_1 < pointer_2) + { + return -1; + } + else if (pointer_1 > pointer_2) + { + return +1; + } + + return 0; +} + +static gboolean +_g_lists_sort_and_check_for_intersection (GList **list_1, + GList **list_2) +{ + GList *node_1; + GList *node_2; + int compare_result; + + *list_1 = g_list_sort (*list_1, compare_pointers); + *list_2 = g_list_sort (*list_2, compare_pointers); + + node_1 = *list_1; + node_2 = *list_2; + + while (node_1 != NULL && node_2 != NULL) + { + compare_result = compare_pointers (node_1->data, node_2->data); + if (compare_result == 0) + { + return TRUE; + } + if (compare_result <= 0) + { + node_1 = node_1->next; + } + if (compare_result >= 0) + { + node_2 = node_2->next; + } + } + + return FALSE; +} + +static void +process_old_files (NautilusFilesView *view) +{ + NautilusFilesViewPrivate *priv; + g_autolist (FileAndDirectory) files_added = NULL; + g_autolist (FileAndDirectory) files_changed = NULL; + FileAndDirectory *pending; + GList *files; + g_autoptr (GList) pending_additions = NULL; + + priv = nautilus_files_view_get_instance_private (view); + files_added = g_steal_pointer (&priv->old_added_files); + files_changed = g_steal_pointer (&priv->old_changed_files); + + + if (files_added != NULL || files_changed != NULL) + { + gboolean send_selection_change = FALSE; + + g_signal_emit (view, signals[BEGIN_FILE_CHANGES], 0); + + for (GList *node = files_added; node != NULL; node = node->next) + { + pending = node->data; + pending_additions = g_list_prepend (pending_additions, pending->file); + /* Acknowledge the files that were pending to be revealed */ + if (g_hash_table_contains (priv->pending_reveal, pending->file)) + { + g_hash_table_insert (priv->pending_reveal, + pending->file, + GUINT_TO_POINTER (TRUE)); + } + } + pending_additions = g_list_reverse (pending_additions); + + if (files_added != NULL) + { + g_signal_emit (view, + signals[ADD_FILES], 0, pending_additions); + } + + for (GList *node = files_changed; node != NULL; node = node->next) + { + gboolean should_show_file; + pending = node->data; + should_show_file = still_should_show_file (view, pending); + g_signal_emit (view, + signals[should_show_file ? FILE_CHANGED : REMOVE_FILE], 0, + pending->file, pending->directory); + + /* Acknowledge the files that were pending to be revealed */ + if (g_hash_table_contains (priv->pending_reveal, pending->file)) + { + if (should_show_file) + { + g_hash_table_insert (priv->pending_reveal, + pending->file, + GUINT_TO_POINTER (TRUE)); + } + else + { + g_hash_table_remove (priv->pending_reveal, + pending->file); + } + } + } + + if (files_changed != NULL) + { + g_autolist (NautilusFile) selection = NULL; + selection = nautilus_view_get_selection (NAUTILUS_VIEW (view)); + files = g_list_copy_deep (files_changed, (GCopyFunc) file_and_directory_get_file, NULL); + send_selection_change = _g_lists_sort_and_check_for_intersection + (&files, &selection); + nautilus_file_list_free (files); + } + + if (send_selection_change) + { + /* Send a selection change since some file names could + * have changed. + */ + nautilus_files_view_send_selection_change (view); + } + + g_signal_emit (view, signals[END_FILE_CHANGES], 0); + } +} + +static void +display_pending_files (NautilusFilesView *view) +{ + NautilusFilesViewPrivate *priv; + g_autolist (NautilusFile) selection = NULL; + + process_new_files (view); + process_old_files (view); + + priv = nautilus_files_view_get_instance_private (view); + selection = nautilus_files_view_get_selection (NAUTILUS_VIEW (view)); + + if (selection == NULL && + !priv->pending_selection && + nautilus_view_is_searching (NAUTILUS_VIEW (view))) + { + nautilus_files_view_select_first (view); + } + + if (priv->model != NULL + && nautilus_directory_are_all_files_seen (priv->model) + && g_hash_table_size (priv->non_ready_files) == 0) + { + done_loading (view, TRUE); + } +} + +static gboolean +display_selection_info_idle_callback (gpointer data) +{ + NautilusFilesView *view; + NautilusFilesViewPrivate *priv; + + view = NAUTILUS_FILES_VIEW (data); + priv = nautilus_files_view_get_instance_private (view); + + g_object_ref (G_OBJECT (view)); + + priv->display_selection_idle_id = 0; + nautilus_files_view_display_selection_info (view); + nautilus_files_view_send_selection_change (view); + + g_object_unref (G_OBJECT (view)); + + return FALSE; +} + +static void +remove_update_context_menus_timeout_callback (NautilusFilesView *view) +{ + NautilusFilesViewPrivate *priv; + + priv = nautilus_files_view_get_instance_private (view); + + if (priv->update_context_menus_timeout_id != 0) + { + g_source_remove (priv->update_context_menus_timeout_id); + priv->update_context_menus_timeout_id = 0; + } +} + +static void +update_context_menus_if_pending (NautilusFilesView *view) +{ + NautilusFilesViewPrivate *priv = nautilus_files_view_get_instance_private (view); + + if (priv->update_context_menus_timeout_id != 0) + { + remove_update_context_menus_timeout_callback (view); + nautilus_files_view_update_context_menus (view); + } +} + +static gboolean +update_context_menus_timeout_callback (gpointer data) +{ + NautilusFilesView *view; + NautilusFilesViewPrivate *priv; + + view = NAUTILUS_FILES_VIEW (data); + priv = nautilus_files_view_get_instance_private (view); + + g_object_ref (G_OBJECT (view)); + + priv->update_context_menus_timeout_id = 0; + nautilus_files_view_update_context_menus (view); + + g_object_unref (G_OBJECT (view)); + + return FALSE; +} + +static gboolean +display_pending_callback (gpointer data) +{ + NautilusFilesView *view; + NautilusFilesViewPrivate *priv; + + view = NAUTILUS_FILES_VIEW (data); + priv = nautilus_files_view_get_instance_private (view); + + g_object_ref (G_OBJECT (view)); + + priv->display_pending_source_id = 0; + + display_pending_files (view); + + g_object_unref (G_OBJECT (view)); + + return FALSE; +} + +static void +schedule_idle_display_of_pending_files (NautilusFilesView *view) +{ + NautilusFilesViewPrivate *priv; + + priv = nautilus_files_view_get_instance_private (view); + + /* Get rid of a pending source as it might be a timeout */ + unschedule_display_of_pending_files (view); + + /* We want higher priority than the idle that handles the relayout + * to avoid a resort on each add. But we still want to allow repaints + * and other hight prio events while we have pending files to show. */ + priv->display_pending_source_id = + g_idle_add_full (G_PRIORITY_DEFAULT_IDLE - 20, + display_pending_callback, view, NULL); +} + +static void +schedule_timeout_display_of_pending_files (NautilusFilesView *view, + guint interval) +{ + NautilusFilesViewPrivate *priv; + + priv = nautilus_files_view_get_instance_private (view); + + /* No need to schedule an update if there's already one pending. */ + if (priv->display_pending_source_id != 0) + { + return; + } + + priv->display_pending_source_id = + g_timeout_add (interval, display_pending_callback, view); +} + +static void +unschedule_display_of_pending_files (NautilusFilesView *view) +{ + NautilusFilesViewPrivate *priv; + + priv = nautilus_files_view_get_instance_private (view); + + /* Get rid of source if it's active. */ + if (priv->display_pending_source_id != 0) + { + g_source_remove (priv->display_pending_source_id); + priv->display_pending_source_id = 0; + } +} + +static void +queue_pending_files (NautilusFilesView *view, + NautilusDirectory *directory, + GList *files, + GList **pending_list) +{ + NautilusFilesViewPrivate *priv; + GList *fad_list; + + priv = nautilus_files_view_get_instance_private (view); + + if (files == NULL) + { + return; + } + + fad_list = g_list_copy_deep (files, (GCopyFunc) file_and_directory_new, directory); + *pending_list = g_list_concat (fad_list, *pending_list); + /* Generally we don't want to show the files while the directory is loading + * the files themselves, so we avoid jumping and oddities. However, for + * search it can be a long wait, and we actually want to show files as + * they are getting found. So for search is fine if not all files are + * seen */ + if (!priv->loading || + (nautilus_directory_are_all_files_seen (directory) || + nautilus_view_is_searching (NAUTILUS_VIEW (view)))) + { + schedule_timeout_display_of_pending_files (view, priv->update_interval); + } +} + +static void +remove_changes_timeout_callback (NautilusFilesView *view) +{ + NautilusFilesViewPrivate *priv; + + priv = nautilus_files_view_get_instance_private (view); + + if (priv->changes_timeout_id != 0) + { + g_source_remove (priv->changes_timeout_id); + priv->changes_timeout_id = 0; + } +} + +static void +reset_update_interval (NautilusFilesView *view) +{ + NautilusFilesViewPrivate *priv; + + priv = nautilus_files_view_get_instance_private (view); + + priv->update_interval = UPDATE_INTERVAL_MIN; + remove_changes_timeout_callback (view); + /* Reschedule a pending timeout to idle */ + if (priv->display_pending_source_id != 0) + { + schedule_idle_display_of_pending_files (view); + } +} + +static gboolean +changes_timeout_callback (gpointer data) +{ + gint64 now; + gint64 time_delta; + gboolean ret; + NautilusFilesView *view; + NautilusFilesViewPrivate *priv; + + view = NAUTILUS_FILES_VIEW (data); + priv = nautilus_files_view_get_instance_private (view); + + g_object_ref (G_OBJECT (view)); + + now = g_get_monotonic_time (); + time_delta = now - priv->last_queued; + + if (time_delta < UPDATE_INTERVAL_RESET * 1000) + { + if (priv->update_interval < UPDATE_INTERVAL_MAX && + priv->loading) + { + /* Increase */ + priv->update_interval += UPDATE_INTERVAL_INC; + } + ret = TRUE; + } + else + { + /* Reset */ + reset_update_interval (view); + ret = FALSE; + } + + g_object_unref (G_OBJECT (view)); + + return ret; +} + +static void +schedule_changes (NautilusFilesView *view) +{ + NautilusFilesViewPrivate *priv; + + priv = nautilus_files_view_get_instance_private (view); + /* Remember when the change was queued */ + priv->last_queued = g_get_monotonic_time (); + + /* No need to schedule if there are already changes pending or during loading */ + if (priv->changes_timeout_id != 0 || + priv->loading) + { + return; + } + + priv->changes_timeout_id = + g_timeout_add (UPDATE_INTERVAL_TIMEOUT_INTERVAL, changes_timeout_callback, view); +} + +static void +files_added_callback (NautilusDirectory *directory, + GList *files, + gpointer callback_data) +{ + NautilusFilesViewPrivate *priv; + NautilusFilesView *view; + GtkWindow *window; + char *uri; + + view = NAUTILUS_FILES_VIEW (callback_data); + priv = nautilus_files_view_get_instance_private (view); + + nautilus_profile_start (NULL); + + window = nautilus_files_view_get_containing_window (view); + uri = nautilus_files_view_get_uri (view); + DEBUG_FILES (files, "Files added in window %p: %s", + window, uri ? uri : "(no directory)"); + g_free (uri); + + schedule_changes (view); + + queue_pending_files (view, directory, files, &priv->new_added_files); + + /* The number of items could have changed */ + schedule_update_status (view); + + nautilus_profile_end (NULL); +} + +static void +files_changed_callback (NautilusDirectory *directory, + GList *files, + gpointer callback_data) +{ + NautilusFilesViewPrivate *priv; + NautilusFilesView *view; + GtkWindow *window; + char *uri; + + view = NAUTILUS_FILES_VIEW (callback_data); + priv = nautilus_files_view_get_instance_private (view); + + window = nautilus_files_view_get_containing_window (view); + uri = nautilus_files_view_get_uri (view); + DEBUG_FILES (files, "Files changed in window %p: %s", + window, uri ? uri : "(no directory)"); + g_free (uri); + + schedule_changes (view); + + queue_pending_files (view, directory, files, &priv->new_changed_files); + + /* The free space or the number of items could have changed */ + schedule_update_status (view); + + /* A change in MIME type could affect the Open with menu, for + * one thing, so we need to update menus when files change. + */ + schedule_update_context_menus (view); +} + +static void +done_loading_callback (NautilusDirectory *directory, + gpointer callback_data) +{ + NautilusFilesView *view; + NautilusFilesViewPrivate *priv; + + view = NAUTILUS_FILES_VIEW (callback_data); + priv = nautilus_files_view_get_instance_private (view); + + nautilus_profile_start (NULL); + process_new_files (view); + if (g_hash_table_size (priv->non_ready_files) == 0) + { + /* Unschedule a pending update and schedule a new one with the minimal + * update interval. This gives the view a short chance at gathering the + * (cached) deep counts. + */ + unschedule_display_of_pending_files (view); + schedule_timeout_display_of_pending_files (view, UPDATE_INTERVAL_MIN); + + remove_loading_floating_bar (view); + } + nautilus_profile_end (NULL); +} + +static void +load_error_callback (NautilusDirectory *directory, + GError *error, + gpointer callback_data) +{ + NautilusFilesView *view; + + view = NAUTILUS_FILES_VIEW (callback_data); + + /* FIXME: By doing a stop, we discard some pending files. Is + * that OK? + */ + nautilus_files_view_stop_loading (view); + + nautilus_report_error_loading_directory + (nautilus_files_view_get_directory_as_file (view), + error, + nautilus_files_view_get_containing_window (view)); +} + +void +nautilus_files_view_add_subdirectory (NautilusFilesView *view, + NautilusDirectory *directory) +{ + NautilusFileAttributes attributes; + NautilusFilesViewPrivate *priv; + + priv = nautilus_files_view_get_instance_private (view); + + g_return_if_fail (!g_list_find (priv->subdirectory_list, directory)); + + nautilus_directory_ref (directory); + + attributes = + NAUTILUS_FILE_ATTRIBUTES_FOR_ICON | + NAUTILUS_FILE_ATTRIBUTE_DIRECTORY_ITEM_COUNT | + NAUTILUS_FILE_ATTRIBUTE_INFO | + NAUTILUS_FILE_ATTRIBUTE_MOUNT | + NAUTILUS_FILE_ATTRIBUTE_EXTENSION_INFO; + + nautilus_directory_file_monitor_add (directory, + &priv->model, + priv->show_hidden_files, + attributes, + files_added_callback, view); + + g_signal_connect + (directory, "files-added", + G_CALLBACK (files_added_callback), view); + g_signal_connect + (directory, "files-changed", + G_CALLBACK (files_changed_callback), view); + + priv->subdirectory_list = g_list_prepend ( + priv->subdirectory_list, directory); +} + +void +nautilus_files_view_remove_subdirectory (NautilusFilesView *view, + NautilusDirectory *directory) +{ + NautilusFilesViewPrivate *priv; + priv = nautilus_files_view_get_instance_private (view); + + g_return_if_fail (g_list_find (priv->subdirectory_list, directory)); + + priv->subdirectory_list = g_list_remove ( + priv->subdirectory_list, directory); + + g_signal_handlers_disconnect_by_func (directory, + G_CALLBACK (files_added_callback), + view); + g_signal_handlers_disconnect_by_func (directory, + G_CALLBACK (files_changed_callback), + view); + + nautilus_directory_file_monitor_remove (directory, &priv->model); + + nautilus_directory_unref (directory); +} + +/** + * nautilus_files_view_get_loading: + * @view: an #NautilusFilesView. + * + * Return value: #gboolean inicating whether @view is currently loaded. + * + **/ +gboolean +nautilus_files_view_get_loading (NautilusFilesView *view) +{ + NautilusFilesViewPrivate *priv; + + g_return_val_if_fail (NAUTILUS_IS_FILES_VIEW (view), FALSE); + + priv = nautilus_files_view_get_instance_private (view); + + return priv->loading; +} + +/** + * nautilus_files_view_get_model: + * + * Get the model for this NautilusFilesView. + * @view: NautilusFilesView of interest. + * + * Return value: NautilusDirectory for this view. + * + **/ +NautilusDirectory * +nautilus_files_view_get_model (NautilusFilesView *view) +{ + NautilusFilesViewPrivate *priv; + + g_return_val_if_fail (NAUTILUS_IS_FILES_VIEW (view), NULL); + + priv = nautilus_files_view_get_instance_private (view); + + return priv->model; +} + +GtkWidget * +nautilus_files_view_get_content_widget (NautilusFilesView *view) +{ + NautilusFilesViewPrivate *priv; + + g_return_val_if_fail (NAUTILUS_IS_FILES_VIEW (view), NULL); + + priv = nautilus_files_view_get_instance_private (view); + + return priv->scrolled_window; +} + +/* home_dir_in_selection() + * + * Return TRUE if the home directory is in the selection. + */ + +static gboolean +home_dir_in_selection (GList *selection) +{ + for (GList *node = selection; node != NULL; node = node->next) + { + if (nautilus_file_is_home (NAUTILUS_FILE (node->data))) + { + return TRUE; + } + } + + return FALSE; +} + +static void +trash_or_delete_done_cb (GHashTable *debuting_uris, + gboolean user_cancel, + NautilusFilesView *view) +{ + NautilusFilesViewPrivate *priv; + + priv = nautilus_files_view_get_instance_private (view); + if (user_cancel) + { + priv->selection_was_removed = FALSE; + } +} + +static void +trash_or_delete_files (GtkWindow *parent_window, + const GList *files, + NautilusFilesView *view) +{ + GList *locations; + const GList *node; + + locations = NULL; + for (node = files; node != NULL; node = node->next) + { + locations = g_list_prepend (locations, + nautilus_file_get_location ((NautilusFile *) node->data)); + } + + locations = g_list_reverse (locations); + + nautilus_file_operations_trash_or_delete_async (locations, + parent_window, + NULL, + (NautilusDeleteCallback) trash_or_delete_done_cb, + view); + g_list_free_full (locations, g_object_unref); +} + +NautilusFile * +nautilus_files_view_get_directory_as_file (NautilusFilesView *view) +{ + NautilusFilesViewPrivate *priv; + + g_assert (NAUTILUS_IS_FILES_VIEW (view)); + + priv = nautilus_files_view_get_instance_private (view); + + return priv->directory_as_file; +} + +static GdkTexture * +get_menu_icon_for_file (NautilusFile *file, + GtkWidget *widget) +{ + int scale = gtk_widget_get_scale_factor (widget); + + return nautilus_file_get_icon_texture (file, 16, scale, 0); +} + +static GList * +get_extension_selection_menu_items (NautilusFilesView *view) +{ + GList *items; + GList *providers; + GList *l; + g_autolist (NautilusFile) selection = NULL; + + selection = nautilus_view_get_selection (NAUTILUS_VIEW (view)); + providers = nautilus_module_get_extensions_for_type (NAUTILUS_TYPE_MENU_PROVIDER); + items = NULL; + + for (l = providers; l != NULL; l = l->next) + { + NautilusMenuProvider *provider; + GList *file_items; + + provider = NAUTILUS_MENU_PROVIDER (l->data); + file_items = nautilus_menu_provider_get_file_items (provider, + selection); + items = g_list_concat (items, file_items); + } + + nautilus_module_extension_list_free (providers); + + return items; +} + +static GList * +get_extension_background_menu_items (NautilusFilesView *view) +{ + NautilusFilesViewPrivate *priv; + GList *items; + GList *providers; + GList *l; + + priv = nautilus_files_view_get_instance_private (view); + providers = nautilus_module_get_extensions_for_type (NAUTILUS_TYPE_MENU_PROVIDER); + items = NULL; + + for (l = providers; l != NULL; l = l->next) + { + NautilusMenuProvider *provider; + NautilusFileInfo *file_info; + GList *file_items; + + provider = NAUTILUS_MENU_PROVIDER (l->data); + file_info = NAUTILUS_FILE_INFO (priv->directory_as_file); + file_items = nautilus_menu_provider_get_background_items (provider, + file_info); + items = g_list_concat (items, file_items); + } + + nautilus_module_extension_list_free (providers); + + return items; +} + +static void +extension_action_callback (GSimpleAction *action, + GVariant *state, + gpointer user_data) +{ + NautilusMenuItem *item = user_data; + nautilus_menu_item_activate (item); +} + +static void +add_extension_action (NautilusFilesView *view, + NautilusMenuItem *item, + const char *action_name) +{ + NautilusFilesViewPrivate *priv; + gboolean sensitive; + GSimpleAction *action; + + priv = nautilus_files_view_get_instance_private (view); + + g_object_get (item, + "sensitive", &sensitive, + NULL); + + action = g_simple_action_new (action_name, NULL); + g_signal_connect_data (action, "activate", + G_CALLBACK (extension_action_callback), + g_object_ref (item), + (GClosureNotify) g_object_unref, 0); + + g_action_map_add_action (G_ACTION_MAP (priv->view_action_group), + G_ACTION (action)); + g_simple_action_set_enabled (action, sensitive); + + g_object_unref (action); +} + +static GMenuModel * +build_menu_for_extension_menu_items (NautilusFilesView *view, + const gchar *extension_prefix, + GList *menu_items) +{ + GList *l; + GMenu *gmenu; + gint idx = 0; + + gmenu = g_menu_new (); + + for (l = menu_items; l; l = l->next) + { + NautilusMenuItem *item; + NautilusMenu *menu; + GMenuItem *menu_item; + char *name, *label; + g_autofree gchar *escaped_name = NULL; + char *extension_id, *detailed_action_name; + + item = NAUTILUS_MENU_ITEM (l->data); + + g_object_get (item, + "label", &label, + "menu", &menu, + "name", &name, + NULL); + + escaped_name = g_uri_escape_string (name, NULL, TRUE); + extension_id = g_strdup_printf ("extension_%s_%d_%s", + extension_prefix, idx, escaped_name); + add_extension_action (view, item, extension_id); + + detailed_action_name = g_strconcat ("view.", extension_id, NULL); + menu_item = g_menu_item_new (label, detailed_action_name); + + if (menu != NULL) + { + GList *children; + g_autoptr (GMenuModel) children_menu = NULL; + + children = nautilus_menu_get_items (menu); + children_menu = build_menu_for_extension_menu_items (view, extension_id, children); + g_menu_item_set_submenu (menu_item, children_menu); + + nautilus_menu_item_list_free (children); + } + + g_menu_append_item (gmenu, menu_item); + idx++; + + g_free (extension_id); + g_free (detailed_action_name); + g_free (name); + g_free (label); + g_object_unref (menu_item); + } + + return G_MENU_MODEL (gmenu); +} + +static void +update_extensions_menus (NautilusFilesView *view, + GtkBuilder *builder) +{ + GList *selection_items, *background_items; + GObject *object; + g_autoptr (GMenuModel) background_menu = NULL; + g_autoptr (GMenuModel) selection_menu = NULL; + + selection_items = get_extension_selection_menu_items (view); + if (selection_items != NULL) + { + selection_menu = build_menu_for_extension_menu_items (view, "extensions", + selection_items); + + object = gtk_builder_get_object (builder, "selection-extensions-section"); + nautilus_gmenu_set_from_model (G_MENU (object), selection_menu); + + nautilus_menu_item_list_free (selection_items); + } + + background_items = get_extension_background_menu_items (view); + if (background_items != NULL) + { + background_menu = build_menu_for_extension_menu_items (view, "extensions", + background_items); + + object = gtk_builder_get_object (builder, "background-extensions-section"); + nautilus_gmenu_set_from_model (G_MENU (object), background_menu); + + nautilus_menu_item_list_free (background_items); + } + + nautilus_view_set_extensions_background_menu (NAUTILUS_VIEW (view), background_menu); +} + +static char * +change_to_view_directory (NautilusFilesView *view) +{ + char *path; + char *old_path; + + old_path = g_get_current_dir (); + + path = get_view_directory (view); + + /* FIXME: What to do about non-local directories? */ + if (path != NULL) + { + g_chdir (path); + } + + g_free (path); + + return old_path; +} + +static char ** +get_file_names_as_parameter_array (GList *selection, + NautilusDirectory *model) +{ + char **parameters; + g_autoptr (GFile) model_location = NULL; + int i; + + if (model == NULL) + { + return NULL; + } + + parameters = g_new (char *, g_list_length (selection) + 1); + + model_location = nautilus_directory_get_location (model); + + i = 0; + for (GList *node = selection; node != NULL; node = node->next, i++) + { + g_autoptr (GFile) file_location = NULL; + NautilusFile *file = NAUTILUS_FILE (node->data); + + if (!nautilus_file_has_local_path (file)) + { + parameters[i] = NULL; + g_strfreev (parameters); + return NULL; + } + + file_location = nautilus_file_get_location (file); + parameters[i] = g_file_get_relative_path (model_location, file_location); + if (parameters[i] == NULL) + { + parameters[i] = g_file_get_path (file_location); + } + } + + parameters[i] = NULL; + return parameters; +} + +static char * +get_file_paths_or_uris_as_newline_delimited_string (GList *selection, + gboolean get_paths) +{ + GString *expanding_string; + + expanding_string = g_string_new (""); + for (GList *node = selection; node != NULL; node = node->next) + { + NautilusFile *file = NAUTILUS_FILE (node->data); + g_autofree gchar *uri = NULL; + + uri = nautilus_file_get_uri (file); + if (uri == NULL) + { + continue; + } + + if (get_paths) + { + g_autofree gchar *path = NULL; + + if (!nautilus_file_has_local_path (file)) + { + g_string_free (expanding_string, TRUE); + return g_strdup (""); + } + + path = g_filename_from_uri (uri, NULL, NULL); + if (path != NULL) + { + g_string_append (expanding_string, path); + g_string_append (expanding_string, "\n"); + } + } + else + { + g_string_append (expanding_string, uri); + g_string_append (expanding_string, "\n"); + } + } + + return g_string_free (expanding_string, FALSE); +} + +static char * +get_file_paths_as_newline_delimited_string (GList *selection) +{ + return get_file_paths_or_uris_as_newline_delimited_string (selection, TRUE); +} + +static char * +get_file_uris_as_newline_delimited_string (GList *selection) +{ + return get_file_paths_or_uris_as_newline_delimited_string (selection, FALSE); +} + +/* + * Set up some environment variables that scripts can use + * to take advantage of the current Nautilus state. + */ +static void +set_script_environment_variables (NautilusFilesView *view, + GList *selected_files) +{ + g_autofree gchar *file_paths = NULL; + g_autofree gchar *uris = NULL; + g_autofree gchar *uri = NULL; + NautilusFilesViewPrivate *priv; + + priv = nautilus_files_view_get_instance_private (view); + + file_paths = get_file_paths_as_newline_delimited_string (selected_files); + g_setenv ("NAUTILUS_SCRIPT_SELECTED_FILE_PATHS", file_paths, TRUE); + + uris = get_file_uris_as_newline_delimited_string (selected_files); + g_setenv ("NAUTILUS_SCRIPT_SELECTED_URIS", uris, TRUE); + + uri = nautilus_directory_get_uri (priv->model); + g_setenv ("NAUTILUS_SCRIPT_CURRENT_URI", uri, TRUE); +} + +/* Unset all the special script environment variables. */ +static void +unset_script_environment_variables (void) +{ + g_unsetenv ("NAUTILUS_SCRIPT_SELECTED_FILE_PATHS"); + g_unsetenv ("NAUTILUS_SCRIPT_SELECTED_URIS"); + g_unsetenv ("NAUTILUS_SCRIPT_CURRENT_URI"); +} + +static void +run_script (GSimpleAction *action, + GVariant *state, + gpointer user_data) +{ + ScriptLaunchParameters *launch_parameters; + NautilusFilesViewPrivate *priv; + g_autofree gchar *file_uri = NULL; + g_autofree gchar *local_file_path = NULL; + g_autofree gchar *quoted_path = NULL; + g_autofree gchar *old_working_dir = NULL; + g_autolist (NautilusFile) selection = NULL; + g_auto (GStrv) parameters = NULL; + GdkDisplay *display; + + launch_parameters = (ScriptLaunchParameters *) user_data; + priv = nautilus_files_view_get_instance_private (launch_parameters->directory_view); + + file_uri = nautilus_file_get_uri (launch_parameters->file); + local_file_path = g_filename_from_uri (file_uri, NULL, NULL); + g_assert (local_file_path != NULL); + quoted_path = g_shell_quote (local_file_path); + + old_working_dir = change_to_view_directory (launch_parameters->directory_view); + + selection = nautilus_view_get_selection (NAUTILUS_VIEW (launch_parameters->directory_view)); + set_script_environment_variables (launch_parameters->directory_view, selection); + + parameters = get_file_names_as_parameter_array (selection, priv->model); + + display = gtk_widget_get_display (GTK_WIDGET (launch_parameters->directory_view)); + + DEBUG ("run_script, script_path=“%s” (omitting script parameters)", + local_file_path); + + nautilus_launch_application_from_command_array (display, quoted_path, FALSE, + (const char * const *) parameters); + + unset_script_environment_variables (); + g_chdir (old_working_dir); +} + +static void +add_script_to_scripts_menus (NautilusFilesView *view, + NautilusFile *file, + GMenu *menu) +{ + NautilusFilesViewPrivate *priv; + gchar *name; + g_autofree gchar *uri = NULL; + g_autofree gchar *escaped_uri = NULL; + GdkTexture *mimetype_icon; + gchar *action_name, *detailed_action_name; + ScriptLaunchParameters *launch_parameters; + GAction *action; + GMenuItem *menu_item; + const gchar *shortcut; + + priv = nautilus_files_view_get_instance_private (view); + launch_parameters = script_launch_parameters_new (file, view); + + name = nautilus_file_get_display_name (file); + + uri = nautilus_file_get_uri (file); + escaped_uri = g_uri_escape_string (uri, NULL, TRUE); + action_name = g_strconcat ("script_", escaped_uri, NULL); + + action = G_ACTION (g_simple_action_new (action_name, NULL)); + + g_signal_connect_data (action, "activate", + G_CALLBACK (run_script), + launch_parameters, + (GClosureNotify) script_launch_parameters_free, 0); + + g_action_map_add_action (G_ACTION_MAP (priv->view_action_group), action); + + g_object_unref (action); + + detailed_action_name = g_strconcat ("view.", action_name, NULL); + menu_item = g_menu_item_new (name, detailed_action_name); + + mimetype_icon = get_menu_icon_for_file (file, GTK_WIDGET (view)); + if (mimetype_icon != NULL) + { + g_menu_item_set_icon (menu_item, G_ICON (mimetype_icon)); + g_object_unref (mimetype_icon); + } + + g_menu_append_item (menu, menu_item); + + if ((shortcut = g_hash_table_lookup (script_accels, name))) + { + nautilus_application_set_accelerator (g_application_get_default (), + detailed_action_name, shortcut); + } + + g_free (name); + g_free (action_name); + g_free (detailed_action_name); + g_object_unref (menu_item); +} + +static gboolean +directory_belongs_in_scripts_menu (const char *uri) +{ + int num_levels; + int i; + + if (!g_str_has_prefix (uri, scripts_directory_uri)) + { + return FALSE; + } + + num_levels = 0; + for (i = scripts_directory_uri_length; uri[i] != '\0'; i++) + { + if (uri[i] == '/') + { + num_levels++; + } + } + + if (num_levels > MAX_MENU_LEVELS) + { + return FALSE; + } + + return TRUE; +} + +/* Expected format: accel script_name */ +static void +nautilus_load_custom_accel_for_scripts (void) +{ + gchar *path, *contents; + gchar **lines, **result; + GError *error = NULL; + const int max_len = 100; + int i; + + path = g_build_filename (g_get_user_config_dir (), SHORTCUTS_PATH, NULL); + + if (g_file_get_contents (path, &contents, NULL, &error)) + { + lines = g_strsplit (contents, "\n", -1); + for (i = 0; lines[i] && (strstr (lines[i], " ") > 0); i++) + { + result = g_strsplit (lines[i], " ", 2); + g_hash_table_insert (script_accels, + g_strndup (result[1], max_len), + g_strndup (result[0], max_len)); + g_strfreev (result); + } + + g_free (contents); + g_strfreev (lines); + } + else + { + DEBUG ("Unable to open '%s', error message: %s", path, error->message); + g_clear_error (&error); + } + + g_free (path); +} + +static GMenu * +update_directory_in_scripts_menu (NautilusFilesView *view, + NautilusDirectory *directory) +{ + GList *file_list, *filtered, *node; + GMenu *menu, *children_menu; + GMenuItem *menu_item; + gboolean any_scripts; + NautilusFile *file; + NautilusDirectory *dir; + char *uri; + gchar *file_name; + int num; + + g_return_val_if_fail (NAUTILUS_IS_FILES_VIEW (view), NULL); + g_return_val_if_fail (NAUTILUS_IS_DIRECTORY (directory), NULL); + + if (script_accels == NULL) + { + script_accels = g_hash_table_new_full (g_str_hash, g_str_equal, + g_free, g_free); + nautilus_load_custom_accel_for_scripts (); + } + + file_list = nautilus_directory_get_file_list (directory); + filtered = nautilus_file_list_filter_hidden (file_list, FALSE); + nautilus_file_list_free (file_list); + menu = g_menu_new (); + + filtered = nautilus_file_list_sort_by_display_name (filtered); + + num = 0; + any_scripts = FALSE; + for (node = filtered; num < TEMPLATE_LIMIT && node != NULL; node = node->next, num++) + { + file = node->data; + if (nautilus_file_is_directory (file)) + { + uri = nautilus_file_get_uri (file); + if (directory_belongs_in_scripts_menu (uri)) + { + dir = nautilus_directory_get_by_uri (uri); + add_directory_to_scripts_directory_list (view, dir); + + children_menu = update_directory_in_scripts_menu (view, dir); + + if (children_menu != NULL) + { + file_name = nautilus_file_get_display_name (file); + menu_item = g_menu_item_new_submenu (file_name, + G_MENU_MODEL (children_menu)); + g_menu_append_item (menu, menu_item); + any_scripts = TRUE; + g_object_unref (menu_item); + g_object_unref (children_menu); + g_free (file_name); + } + + nautilus_directory_unref (dir); + } + g_free (uri); + } + else if (nautilus_file_is_launchable (file)) + { + add_script_to_scripts_menus (view, file, menu); + any_scripts = TRUE; + } + } + + nautilus_file_list_free (filtered); + + if (!any_scripts) + { + g_object_unref (menu); + menu = NULL; + } + + return menu; +} + + + +static void +update_scripts_menu (NautilusFilesView *view, + GtkBuilder *builder) +{ + NautilusFilesViewPrivate *priv; + g_autolist (NautilusDirectory) sorted_copy = NULL; + g_autoptr (NautilusDirectory) directory = NULL; + g_autoptr (GMenu) submenu = NULL; + + priv = nautilus_files_view_get_instance_private (view); + + sorted_copy = nautilus_directory_list_sort_by_uri + (nautilus_directory_list_copy (priv->scripts_directory_list)); + + for (GList *dir_l = sorted_copy; dir_l != NULL; dir_l = dir_l->next) + { + g_autofree char *uri = nautilus_directory_get_uri (dir_l->data); + if (!directory_belongs_in_scripts_menu (uri)) + { + remove_directory_from_scripts_directory_list (view, dir_l->data); + } + } + + directory = nautilus_directory_get_by_uri (scripts_directory_uri); + submenu = update_directory_in_scripts_menu (view, directory); + g_set_object (&priv->scripts_menu, G_MENU_MODEL (submenu)); +} + +static void +create_template (GSimpleAction *action, + GVariant *state, + gpointer user_data) +{ + CreateTemplateParameters *parameters; + + parameters = user_data; + + nautilus_files_view_new_file (parameters->directory_view, NULL, parameters->file); +} + +static void +add_template_to_templates_menus (NautilusFilesView *view, + NautilusFile *file, + GMenu *menu) +{ + NautilusFilesViewPrivate *priv; + char *uri, *name; + g_autofree gchar *escaped_uri = NULL; + GdkTexture *mimetype_icon; + char *action_name, *detailed_action_name; + CreateTemplateParameters *parameters; + GAction *action; + g_autofree char *label = NULL; + GMenuItem *menu_item; + + priv = nautilus_files_view_get_instance_private (view); + name = nautilus_file_get_display_name (file); + uri = nautilus_file_get_uri (file); + escaped_uri = g_uri_escape_string (uri, NULL, TRUE); + action_name = g_strconcat ("template_", escaped_uri, NULL); + action = G_ACTION (g_simple_action_new (action_name, NULL)); + parameters = create_template_parameters_new (file, view); + + g_signal_connect_data (action, "activate", + G_CALLBACK (create_template), + parameters, + (GClosureNotify) create_templates_parameters_free, 0); + + g_action_map_add_action (G_ACTION_MAP (priv->view_action_group), action); + + detailed_action_name = g_strconcat ("view.", action_name, NULL); + label = eel_str_double_underscores (name); + menu_item = g_menu_item_new (label, detailed_action_name); + + mimetype_icon = get_menu_icon_for_file (file, GTK_WIDGET (view)); + if (mimetype_icon != NULL) + { + g_menu_item_set_icon (menu_item, G_ICON (mimetype_icon)); + g_object_unref (mimetype_icon); + } + + g_menu_append_item (menu, menu_item); + + g_free (name); + g_free (uri); + g_free (action_name); + g_free (detailed_action_name); + g_object_unref (action); + g_object_unref (menu_item); +} + +static void +update_templates_directory (NautilusFilesView *view) +{ + NautilusFilesViewPrivate *priv; + NautilusDirectory *templates_directory; + GList *node, *next; + char *templates_uri; + + priv = nautilus_files_view_get_instance_private (view); + + for (node = priv->templates_directory_list; node != NULL; node = next) + { + next = node->next; + remove_directory_from_templates_directory_list (view, node->data); + } + + if (nautilus_should_use_templates_directory ()) + { + templates_uri = nautilus_get_templates_directory_uri (); + templates_directory = nautilus_directory_get_by_uri (templates_uri); + g_free (templates_uri); + add_directory_to_templates_directory_list (view, templates_directory); + nautilus_directory_unref (templates_directory); + } +} + +static gboolean +directory_belongs_in_templates_menu (const char *templates_directory_uri, + const char *uri) +{ + int num_levels; + int i; + + if (templates_directory_uri == NULL) + { + return FALSE; + } + + if (!g_str_has_prefix (uri, templates_directory_uri)) + { + return FALSE; + } + + num_levels = 0; + for (i = strlen (templates_directory_uri); uri[i] != '\0'; i++) + { + if (uri[i] == '/') + { + num_levels++; + } + } + + if (num_levels > MAX_MENU_LEVELS) + { + return FALSE; + } + + return TRUE; +} + +static gboolean +filter_templates_callback (NautilusFile *file, + gpointer callback_data) +{ + gboolean show_hidden = GPOINTER_TO_INT (callback_data); + + if (nautilus_file_is_hidden_file (file)) + { + if (!show_hidden) + { + return FALSE; + } + + if (nautilus_file_is_directory (file)) + { + return FALSE; + } + } + + return TRUE; +} + +static GList * +filter_templates (GList *files, + gboolean show_hidden) +{ + GList *filtered_files; + GList *removed_files; + + filtered_files = nautilus_file_list_filter (files, + &removed_files, + filter_templates_callback, + GINT_TO_POINTER (show_hidden)); + nautilus_file_list_free (removed_files); + + return filtered_files; +} + +static GMenuModel * +update_directory_in_templates_menu (NautilusFilesView *view, + NautilusDirectory *directory) +{ + NautilusFilesViewPrivate *priv; + GList *file_list, *filtered, *node; + GMenu *menu; + GMenuItem *menu_item; + gboolean any_templates; + NautilusFile *file; + NautilusDirectory *dir; + char *uri; + char *templates_directory_uri; + int num; + + g_return_val_if_fail (NAUTILUS_IS_FILES_VIEW (view), NULL); + g_return_val_if_fail (NAUTILUS_IS_DIRECTORY (directory), NULL); + + priv = nautilus_files_view_get_instance_private (view); + + file_list = nautilus_directory_get_file_list (directory); + + /* + * The nautilus_file_list_filter_hidden() function isn't used here, because + * we want to show hidden files, but not directories. This is a compromise + * to allow creating hidden files but to prevent content from .git directory + * for example. See https://gitlab.gnome.org/GNOME/nautilus/issues/1413. + */ + filtered = filter_templates (file_list, priv->show_hidden_files); + nautilus_file_list_free (file_list); + templates_directory_uri = nautilus_get_templates_directory_uri (); + menu = g_menu_new (); + + filtered = nautilus_file_list_sort_by_display_name (filtered); + + num = 0; + any_templates = FALSE; + for (node = filtered; num < TEMPLATE_LIMIT && node != NULL; node = node->next, num++) + { + file = node->data; + if (nautilus_file_is_directory (file)) + { + uri = nautilus_file_get_uri (file); + if (directory_belongs_in_templates_menu (templates_directory_uri, uri)) + { + g_autoptr (GMenuModel) children_menu = NULL; + + dir = nautilus_directory_get_by_uri (uri); + add_directory_to_templates_directory_list (view, dir); + + children_menu = update_directory_in_templates_menu (view, dir); + + if (children_menu != NULL) + { + g_autofree char *display_name = NULL; + g_autofree char *label = NULL; + + display_name = nautilus_file_get_display_name (file); + label = eel_str_double_underscores (display_name); + menu_item = g_menu_item_new_submenu (label, children_menu); + g_menu_append_item (menu, menu_item); + any_templates = TRUE; + g_object_unref (menu_item); + } + + nautilus_directory_unref (dir); + } + g_free (uri); + } + else if (nautilus_file_can_read (file)) + { + add_template_to_templates_menus (view, file, menu); + any_templates = TRUE; + } + } + + nautilus_file_list_free (filtered); + g_free (templates_directory_uri); + + if (!any_templates) + { + g_object_unref (menu); + menu = NULL; + } + + return G_MENU_MODEL (menu); +} + + + +static void +update_templates_menu (NautilusFilesView *view, + GtkBuilder *builder) +{ + NautilusFilesViewPrivate *priv; + g_autolist (NautilusDirectory) sorted_copy = NULL; + g_autoptr (NautilusDirectory) directory = NULL; + g_autoptr (GMenuModel) submenu = NULL; + g_autofree char *templates_directory_uri = NULL; + + priv = nautilus_files_view_get_instance_private (view); + + if (!nautilus_should_use_templates_directory ()) + { + nautilus_view_set_templates_menu (NAUTILUS_VIEW (view), NULL); + return; + } + + templates_directory_uri = nautilus_get_templates_directory_uri (); + sorted_copy = nautilus_directory_list_sort_by_uri + (nautilus_directory_list_copy (priv->templates_directory_list)); + + for (GList *dir_l = sorted_copy; dir_l != NULL; dir_l = dir_l->next) + { + g_autofree char *uri = nautilus_directory_get_uri (dir_l->data); + if (!directory_belongs_in_templates_menu (templates_directory_uri, uri)) + { + remove_directory_from_templates_directory_list (view, dir_l->data); + } + } + + directory = nautilus_directory_get_by_uri (templates_directory_uri); + submenu = update_directory_in_templates_menu (view, directory); + + nautilus_view_set_templates_menu (NAUTILUS_VIEW (view), submenu); +} + + +static void +action_open_scripts_folder (GSimpleAction *action, + GVariant *state, + gpointer user_data) +{ + static GFile *location = NULL; + + if (location == NULL) + { + location = g_file_new_for_uri (scripts_directory_uri); + } + + nautilus_application_open_location_full (NAUTILUS_APPLICATION (g_application_get_default ()), + location, 0, NULL, NULL, NULL); +} + +typedef struct _CopyCallbackData +{ + NautilusFilesView *view; + GList *selection; + gboolean is_move; +} CopyCallbackData; + +static void +copy_data_free (CopyCallbackData *data) +{ + nautilus_file_list_free (data->selection); + g_free (data); +} + +static void +on_destination_dialog_response (GtkDialog *dialog, + gint response_id, + gpointer user_data) +{ + CopyCallbackData *copy_data = user_data; + + if (response_id == GTK_RESPONSE_OK) + { + g_autoptr (GFile) target_location = NULL; + char *target_uri; + GList *uris, *l; + + target_location = gtk_file_chooser_get_file (GTK_FILE_CHOOSER (dialog)); + target_uri = g_file_get_uri (target_location); + uris = NULL; + for (l = copy_data->selection; l != NULL; l = l->next) + { + uris = g_list_prepend (uris, + nautilus_file_get_uri ((NautilusFile *) l->data)); + } + uris = g_list_reverse (uris); + + nautilus_files_view_move_copy_items (copy_data->view, uris, target_uri, + copy_data->is_move ? GDK_ACTION_MOVE : GDK_ACTION_COPY); + + g_list_free_full (uris, g_free); + g_free (target_uri); + } + + copy_data_free (copy_data); + gtk_window_destroy (GTK_WINDOW (dialog)); +} + +static void +copy_or_move_selection (NautilusFilesView *view, + gboolean is_move) +{ + NautilusFilesViewPrivate *priv; + GtkWidget *dialog; + g_autoptr (GFile) location = NULL; + CopyCallbackData *copy_data; + GList *selection; + const gchar *title; + NautilusDirectory *directory; + + priv = nautilus_files_view_get_instance_private (view); + + if (is_move) + { + title = _("Select Move Destination"); + } + else + { + title = _("Select Copy Destination"); + } + + selection = nautilus_files_view_get_selection_for_file_transfer (view); + + dialog = gtk_file_chooser_dialog_new (title, + GTK_WINDOW (nautilus_files_view_get_window (view)), + GTK_FILE_CHOOSER_ACTION_SELECT_FOLDER, + _("_Cancel"), GTK_RESPONSE_CANCEL, + _("_Select"), GTK_RESPONSE_OK, + NULL); + + gtk_dialog_set_default_response (GTK_DIALOG (dialog), + GTK_RESPONSE_OK); + + gtk_window_set_destroy_with_parent (GTK_WINDOW (dialog), TRUE); + gtk_window_set_modal (GTK_WINDOW (dialog), TRUE); + + copy_data = g_new0 (CopyCallbackData, 1); + copy_data->view = view; + copy_data->selection = selection; + copy_data->is_move = is_move; + + if (nautilus_view_is_searching (NAUTILUS_VIEW (view))) + { + directory = nautilus_search_directory_get_base_model (NAUTILUS_SEARCH_DIRECTORY (priv->model)); + location = nautilus_directory_get_location (directory); + } + else if (showing_starred_directory (view)) + { + location = nautilus_file_get_parent_location (NAUTILUS_FILE (selection->data)); + } + else + { + location = nautilus_directory_get_location (priv->model); + } + + gtk_file_chooser_set_current_folder (GTK_FILE_CHOOSER (dialog), location, NULL); + g_signal_connect (dialog, "response", + G_CALLBACK (on_destination_dialog_response), + copy_data); + + gtk_widget_show (dialog); +} + +static void +action_copy (GSimpleAction *action, + GVariant *state, + gpointer user_data) +{ + NautilusFilesView *view; + GdkClipboard *clipboard; + GList *selection; + + view = NAUTILUS_FILES_VIEW (user_data); + + selection = nautilus_files_view_get_selection_for_file_transfer (view); + clipboard = gtk_widget_get_clipboard (GTK_WIDGET (view)); + nautilus_clipboard_prepare_for_files (clipboard, selection, FALSE); + + nautilus_file_list_free (selection); +} + +static void +action_cut (GSimpleAction *action, + GVariant *state, + gpointer user_data) +{ + NautilusFilesView *view; + GList *selection; + GdkClipboard *clipboard; + + view = NAUTILUS_FILES_VIEW (user_data); + + selection = nautilus_files_view_get_selection_for_file_transfer (view); + clipboard = gtk_widget_get_clipboard (GTK_WIDGET (view)); + nautilus_clipboard_prepare_for_files (clipboard, selection, TRUE); + + nautilus_file_list_free (selection); +} + +static void +action_copy_current_location (GSimpleAction *action, + GVariant *state, + gpointer user_data) +{ + NautilusFilesView *view; + GdkClipboard *clipboard; + GList *files; + NautilusFilesViewPrivate *priv; + + view = NAUTILUS_FILES_VIEW (user_data); + priv = nautilus_files_view_get_instance_private (view); + + if (priv->directory_as_file != NULL) + { + files = g_list_append (NULL, nautilus_file_ref (priv->directory_as_file)); + + clipboard = gtk_widget_get_clipboard (GTK_WIDGET (view)); + nautilus_clipboard_prepare_for_files (clipboard, files, FALSE); + + nautilus_file_list_free (files); + } +} + +static void +action_create_links_in_place (GSimpleAction *action, + GVariant *state, + gpointer user_data) +{ + NautilusFilesView *view; + GList *selection; + GList *item_uris; + GList *l; + char *destination_uri; + + view = NAUTILUS_FILES_VIEW (user_data); + + selection = nautilus_files_view_get_selection_for_file_transfer (view); + + item_uris = NULL; + for (l = selection; l != NULL; l = l->next) + { + item_uris = g_list_prepend (item_uris, nautilus_file_get_uri (l->data)); + } + item_uris = g_list_reverse (item_uris); + + destination_uri = nautilus_files_view_get_backing_uri (view); + + nautilus_files_view_move_copy_items (view, item_uris, destination_uri, + GDK_ACTION_LINK); + + g_list_free_full (item_uris, g_free); + nautilus_file_list_free (selection); +} + +static void +action_copy_to (GSimpleAction *action, + GVariant *state, + gpointer user_data) +{ + NautilusFilesView *view; + + view = NAUTILUS_FILES_VIEW (user_data); + copy_or_move_selection (view, FALSE); +} + +static void +action_move_to (GSimpleAction *action, + GVariant *state, + gpointer user_data) +{ + NautilusFilesView *view; + + view = NAUTILUS_FILES_VIEW (user_data); + copy_or_move_selection (view, TRUE); +} + +static void +paste_into_clipboard_received_callback (GObject *source_object, + GAsyncResult *res, + gpointer user_data) +{ + NautilusFilesView *view = NAUTILUS_FILES_VIEW (source_object); + NautilusClipboard *clip; + g_autofree char *directory_uri = user_data; + + clip = nautilus_files_view_get_clipboard_finish (view, res, NULL); + if (clip != NULL) + { + paste_clipboard_data (view, clip, directory_uri); + } +} + +static void +paste_into (NautilusFilesView *view, + NautilusFile *target) +{ + g_assert (NAUTILUS_IS_FILES_VIEW (view)); + g_assert (NAUTILUS_IS_FILE (target)); + + nautilus_files_view_get_clipboard_async (view, + paste_into_clipboard_received_callback, + nautilus_file_get_activation_uri (target)); +} + +static void +action_paste_files_into (GSimpleAction *action, + GVariant *state, + gpointer user_data) +{ + NautilusFilesView *view; + g_autolist (NautilusFile) selection = NULL; + + view = NAUTILUS_FILES_VIEW (user_data); + selection = nautilus_view_get_selection (NAUTILUS_VIEW (view)); + if (selection != NULL) + { + paste_into (view, NAUTILUS_FILE (selection->data)); + } +} + +static void +real_action_rename (NautilusFilesView *view) +{ + NautilusFile *file; + g_autolist (NautilusFile) selection = NULL; + GtkWidget *dialog; + + g_assert (NAUTILUS_IS_FILES_VIEW (view)); + + selection = nautilus_view_get_selection (NAUTILUS_VIEW (view)); + + if (selection != NULL) + { + /* If there is more than one file selected, invoke a batch renamer */ + if (selection->next != NULL) + { + NautilusWindow *window; + + window = nautilus_files_view_get_window (view); + gtk_widget_set_cursor_from_name (GTK_WIDGET (window), "progress"); + + dialog = nautilus_batch_rename_dialog_new (selection, + nautilus_files_view_get_model (view), + window); + + gtk_widget_show (GTK_WIDGET (dialog)); + } + else + { + file = NAUTILUS_FILE (selection->data); + + nautilus_files_view_rename_file_popover_new (view, file); + } + } +} + +static void +action_rename (GSimpleAction *action, + GVariant *state, + gpointer user_data) +{ + real_action_rename (NAUTILUS_FILES_VIEW (user_data)); +} + +typedef struct +{ + NautilusFilesView *view; + GHashTable *added_locations; +} ExtractData; + +static void +extract_done (GList *outputs, + gpointer user_data) +{ + NautilusFilesViewPrivate *priv; + ExtractData *data; + GList *l; + gboolean all_files_acknowledged; + + data = user_data; + + if (data->view == NULL) + { + goto out; + } + + priv = nautilus_files_view_get_instance_private (data->view); + + g_signal_handlers_disconnect_by_func (data->view, + G_CALLBACK (track_newly_added_locations), + data->added_locations); + + if (outputs == NULL) + { + goto out; + } + + all_files_acknowledged = TRUE; + for (l = outputs; l && all_files_acknowledged; l = l->next) + { + all_files_acknowledged = g_hash_table_contains (data->added_locations, + l->data); + } + + if (all_files_acknowledged) + { + GList *selection = NULL; + + for (l = outputs; l != NULL; l = l->next) + { + selection = g_list_prepend (selection, + nautilus_file_get (l->data)); + } + + nautilus_files_view_set_selection (NAUTILUS_VIEW (data->view), + selection); + nautilus_files_view_reveal_selection (data->view); + + nautilus_file_list_free (selection); + } + else + { + for (l = outputs; l != NULL; l = l->next) + { + gboolean acknowledged; + + acknowledged = g_hash_table_contains (data->added_locations, + l->data); + + g_hash_table_insert (priv->pending_reveal, + nautilus_file_get (l->data), + GUINT_TO_POINTER (acknowledged)); + } + } +out: + g_hash_table_destroy (data->added_locations); + + if (data->view != NULL) + { + g_object_remove_weak_pointer (G_OBJECT (data->view), + (gpointer *) &data->view); + } + + g_free (data); +} + +static void +extract_files (NautilusFilesView *view, + GList *files, + GFile *destination_directory) +{ + GList *locations = NULL; + GList *l; + gboolean extracting_to_current_directory; + + if (files == NULL) + { + return; + } + + for (l = files; l != NULL; l = l->next) + { + locations = g_list_prepend (locations, + nautilus_file_get_location (l->data)); + } + + locations = g_list_reverse (locations); + + extracting_to_current_directory = g_file_equal (destination_directory, + nautilus_view_get_location (NAUTILUS_VIEW (view))); + + if (extracting_to_current_directory) + { + ExtractData *data; + + data = g_new (ExtractData, 1); + data->view = view; + data->added_locations = g_hash_table_new_full (g_file_hash, + (GEqualFunc) g_file_equal, + g_object_unref, NULL); + + + g_object_add_weak_pointer (G_OBJECT (data->view), + (gpointer *) &data->view); + + g_signal_connect_data (view, + "add-files", + G_CALLBACK (track_newly_added_locations), + data->added_locations, + NULL, + G_CONNECT_AFTER); + + nautilus_file_operations_extract_files (locations, + destination_directory, + nautilus_files_view_get_containing_window (view), + NULL, + extract_done, + data); + } + else + { + nautilus_file_operations_extract_files (locations, + destination_directory, + nautilus_files_view_get_containing_window (view), + NULL, + NULL, + NULL); + } + + g_list_free_full (locations, g_object_unref); +} + +typedef struct +{ + NautilusFilesView *view; + GList *files; +} ExtractToData; + +static void +on_extract_destination_dialog_response (GtkDialog *dialog, + gint response_id, + gpointer user_data) +{ + ExtractToData *data; + + data = user_data; + + if (response_id == GTK_RESPONSE_OK) + { + g_autoptr (GFile) destination_directory = NULL; + + destination_directory = gtk_file_chooser_get_file (GTK_FILE_CHOOSER (dialog)); + + extract_files (data->view, data->files, destination_directory); + } + + gtk_window_destroy (GTK_WINDOW (dialog)); + nautilus_file_list_free (data->files); + g_free (data); +} + +static void +extract_files_to_chosen_location (NautilusFilesView *view, + GList *files) +{ + NautilusFilesViewPrivate *priv; + ExtractToData *data; + GtkWidget *dialog; + g_autoptr (GFile) location = NULL; + + priv = nautilus_files_view_get_instance_private (view); + + if (files == NULL) + { + return; + } + + data = g_new (ExtractToData, 1); + + dialog = gtk_file_chooser_dialog_new (_("Select Extract Destination"), + GTK_WINDOW (nautilus_files_view_get_window (view)), + GTK_FILE_CHOOSER_ACTION_SELECT_FOLDER, + _("_Cancel"), GTK_RESPONSE_CANCEL, + _("_Select"), GTK_RESPONSE_OK, + NULL); + + gtk_dialog_set_default_response (GTK_DIALOG (dialog), + GTK_RESPONSE_OK); + + gtk_window_set_destroy_with_parent (GTK_WINDOW (dialog), TRUE); + gtk_window_set_modal (GTK_WINDOW (dialog), TRUE); + + /* The file chooser will not be able to display the search directory, + * so we need to get the base directory of the search if we are, in fact, + * in search. + */ + if (nautilus_view_is_searching (NAUTILUS_VIEW (view))) + { + NautilusSearchDirectory *search_directory; + NautilusDirectory *directory; + + search_directory = NAUTILUS_SEARCH_DIRECTORY (priv->model); + directory = nautilus_search_directory_get_base_model (search_directory); + location = nautilus_directory_get_location (directory); + } + else + { + location = nautilus_directory_get_location (priv->model); + } + + gtk_file_chooser_set_current_folder (GTK_FILE_CHOOSER (dialog), location, NULL); + + data->view = view; + data->files = nautilus_file_list_copy (files); + + g_signal_connect (dialog, "response", + G_CALLBACK (on_extract_destination_dialog_response), + data); + + gtk_widget_show (dialog); +} + +static void +action_extract_here (GSimpleAction *action, + GVariant *state, + gpointer user_data) +{ + NautilusFilesView *view; + g_autolist (NautilusFile) selection = NULL; + g_autoptr (GFile) location = NULL; + g_autoptr (GFile) parent = NULL; + + view = NAUTILUS_FILES_VIEW (user_data); + + selection = nautilus_view_get_selection (NAUTILUS_VIEW (view)); + location = nautilus_file_get_location (NAUTILUS_FILE (g_list_first (selection)->data)); + /* Get a parent from a random file. We assume all files has a common parent. + * But don't assume the parent is the view location, since that's not the + * case in list view when expand-folder setting is set + */ + parent = g_file_get_parent (location); + + extract_files (view, selection, parent); +} + +static void +action_extract_to (GSimpleAction *action, + GVariant *state, + gpointer user_data) +{ + NautilusFilesView *view; + g_autolist (NautilusFile) selection = NULL; + + view = NAUTILUS_FILES_VIEW (user_data); + + selection = nautilus_view_get_selection (NAUTILUS_VIEW (view)); + + extract_files_to_chosen_location (view, selection); +} + +static void +action_compress (GSimpleAction *action, + GVariant *state, + gpointer user_data) +{ + NautilusFilesView *view = user_data; + + nautilus_files_view_compress_dialog_new (view); +} + +static void +send_email_done (GObject *source_object, + GAsyncResult *res, + gpointer user_data) +{ + GtkWindow *window = user_data; + g_autoptr (GError) error = NULL; + + xdp_portal_compose_email_finish (XDP_PORTAL (source_object), res, &error); + if (error != NULL) + { + show_dialog (_("Error sending email."), + error->message, + window, + GTK_MESSAGE_ERROR); + } +} + +static void +real_send_email (GStrv attachments, + NautilusFilesView *view) +{ + /* Although the documentation says that addresses can be NULL, it takes + * no action when addresses is NULL. Since we don't know the address, + * provide an empty list */ + const char * const addresses[] = {NULL}; + g_autoptr (XdpPortal) portal = NULL; + XdpParent *parent; + GtkWidget *toplevel; + + portal = xdp_portal_new (); + toplevel = gtk_widget_get_ancestor (GTK_WIDGET (view), GTK_TYPE_WINDOW); + parent = xdp_parent_new_gtk (GTK_WINDOW (toplevel)); + xdp_portal_compose_email (portal, parent, addresses, + NULL, NULL, NULL, NULL, (const char * const *) attachments, + XDP_EMAIL_FLAG_NONE, NULL, send_email_done, toplevel); +} + +static void +email_archive_ready (GFile *new_file, + gboolean success, + gpointer user_data) +{ + g_autoptr (GStrvBuilder) strv_builder = NULL; + g_auto (GStrv) attachments = NULL; + NautilusFilesView *view = user_data; + + if (success) + { + strv_builder = g_strv_builder_new (); + g_strv_builder_add (strv_builder, g_file_get_path (new_file)); + attachments = g_strv_builder_end (strv_builder); + real_send_email (attachments, view); + } +} + +static void +action_send_email (GSimpleAction *action, + GVariant *state, + gpointer user_data) +{ + NautilusFilesView *view = user_data; + g_autolist (NautilusFile) selection = NULL; + g_auto (GStrv) attachments = NULL; + g_autoptr (GStrvBuilder) strv_builder = NULL; + gboolean has_directory = FALSE; + + strv_builder = g_strv_builder_new (); + selection = nautilus_files_view_get_selection (NAUTILUS_VIEW (view)); + + for (GList *l = selection; l != NULL; l = l->next) + { + if (nautilus_file_has_local_path (l->data)) + { + g_autoptr (GFile) location = nautilus_file_get_location (l->data); + g_strv_builder_add (strv_builder, g_file_get_path (location)); + } + /* If there's a directory in the list, we can't attach a folder, + * so to keep things simple let's archive the whole selection */ + if (nautilus_file_is_directory (l->data)) + { + has_directory = TRUE; + break; + } + } + + if (has_directory) + { + g_autolist (GFile) source_locations = NULL; + g_autofree gchar *archive_directory_name = NULL; + g_autoptr (GFile) archive_directory = NULL; + g_autoptr (GFile) archive_location = NULL; + + for (GList *l = selection; l != NULL; l = l->next) + { + source_locations = g_list_prepend (source_locations, + nautilus_file_get_location (l->data)); + } + source_locations = g_list_reverse (source_locations); + archive_directory_name = g_dir_make_tmp ("nautilus-sendto-XXXXXX", NULL); + archive_directory = g_file_new_for_path (archive_directory_name); + archive_location = g_file_get_child (archive_directory, "archive.zip"); + nautilus_file_operations_compress (source_locations, archive_location, + AUTOAR_FORMAT_ZIP, AUTOAR_FILTER_NONE, + NULL, + nautilus_files_view_get_containing_window (view), + NULL, email_archive_ready, view); + } + else + { + attachments = g_strv_builder_end (strv_builder); + real_send_email (attachments, view); + } +} + +static gboolean +can_run_in_terminal (GList *selection) +{ + NautilusFile *file; + + if (g_list_length (selection) != 1) + { + return FALSE; + } + + file = NAUTILUS_FILE (selection->data); + + if (nautilus_file_is_launchable (file) && + nautilus_file_contains_text (file)) + { + g_autofree gchar *activation_uri = NULL; + g_autofree gchar *executable_path = NULL; + + activation_uri = nautilus_file_get_activation_uri (file); + executable_path = g_filename_from_uri (activation_uri, NULL, NULL); + + if (executable_path != NULL) + { + return TRUE; + } + } + + return FALSE; +} + +static void +action_run_in_terminal (GSimpleAction *action, + GVariant *state, + gpointer user_data) +{ + NautilusFilesView *view; + g_autolist (NautilusFile) selection = NULL; + g_autofree char *old_working_dir = NULL; + g_autofree char *uri = NULL; + g_autofree char *executable_path = NULL; + g_autofree char *quoted_path = NULL; + GtkWindow *parent_window; + GdkDisplay *display; + + g_assert (NAUTILUS_IS_FILES_VIEW (user_data)); + + view = NAUTILUS_FILES_VIEW (user_data); + + selection = nautilus_view_get_selection (NAUTILUS_VIEW (view)); + + if (!can_run_in_terminal (selection)) + { + return; + } + + old_working_dir = change_to_view_directory (view); + + uri = nautilus_file_get_activation_uri (NAUTILUS_FILE (selection->data)); + executable_path = g_filename_from_uri (uri, NULL, NULL); + quoted_path = g_shell_quote (executable_path); + + parent_window = nautilus_files_view_get_containing_window (view); + display = gtk_widget_get_display (GTK_WIDGET (parent_window)); + + DEBUG ("Launching in terminal %s", quoted_path); + + nautilus_launch_application_from_command (display, quoted_path, TRUE, NULL); + + g_chdir (old_working_dir); +} + +static gboolean +can_set_wallpaper (GList *selection) +{ + NautilusFile *file; + + if (g_list_length (selection) != 1) + { + return FALSE; + } + + file = NAUTILUS_FILE (selection->data); + if (!nautilus_file_is_mime_type (file, "image/*")) + { + return FALSE; + } + + /* FIXME: check file size? */ + + return TRUE; +} + +static void +set_wallpaper_with_portal_cb (GObject *source, + GAsyncResult *result, + gpointer user_data) +{ + XdpPortal *portal = XDP_PORTAL (source); + g_autoptr (GError) error = NULL; + + if (!xdp_portal_set_wallpaper_finish (portal, result, &error) + && !g_error_matches (error, G_IO_ERROR, G_IO_ERROR_CANCELLED)) + { + g_warning ("Failed to set wallpaper via portal: %s", error->message); + } +} + +static void +set_wallpaper_with_portal (NautilusFile *file, + gpointer user_data) +{ + g_autoptr (XdpPortal) portal = NULL; + g_autofree gchar *uri = NULL; + XdpParent *parent = NULL; + GtkWidget *toplevel; + + portal = xdp_portal_new (); + toplevel = gtk_widget_get_ancestor (GTK_WIDGET (user_data), GTK_TYPE_WINDOW); + parent = xdp_parent_new_gtk (GTK_WINDOW (toplevel)); + uri = nautilus_file_get_uri (file); + + xdp_portal_set_wallpaper (portal, + parent, + uri, + XDP_WALLPAPER_FLAG_BACKGROUND | XDP_WALLPAPER_FLAG_PREVIEW, + NULL, + set_wallpaper_with_portal_cb, + NULL); + xdp_parent_free (parent); +} + +static void +action_set_as_wallpaper (GSimpleAction *action, + GVariant *state, + gpointer user_data) +{ + g_autolist (NautilusFile) selection = NULL; + + g_assert (NAUTILUS_IS_FILES_VIEW (user_data)); + + selection = nautilus_view_get_selection (user_data); + if (can_set_wallpaper (selection)) + { + NautilusFile *file; + + file = NAUTILUS_FILE (selection->data); + + set_wallpaper_with_portal (file, user_data); + } +} + +static void +file_mount_callback (NautilusFile *file, + GFile *result_location, + GError *error, + gpointer callback_data) +{ + NautilusFilesView *view; + + view = NAUTILUS_FILES_VIEW (callback_data); + + if (error != NULL && + (error->domain != G_IO_ERROR || + (error->code != G_IO_ERROR_CANCELLED && + error->code != G_IO_ERROR_FAILED_HANDLED && + error->code != G_IO_ERROR_ALREADY_MOUNTED))) + { + char *text; + char *name; + name = nautilus_file_get_display_name (file); + /* Translators: %s is a file name formatted for display */ + text = g_strdup_printf (_("Unable to access “%s”"), name); + show_dialog (text, + error->message, + GTK_WINDOW (nautilus_files_view_get_window (view)), + GTK_MESSAGE_ERROR); + g_free (text); + g_free (name); + } +} + +static void +file_unmount_callback (NautilusFile *file, + GFile *result_location, + GError *error, + gpointer callback_data) +{ + NautilusFilesView *view; + + view = NAUTILUS_FILES_VIEW (callback_data); + g_object_unref (view); + + if (error != NULL && + (error->domain != G_IO_ERROR || + (error->code != G_IO_ERROR_CANCELLED && + error->code != G_IO_ERROR_FAILED_HANDLED))) + { + char *text; + char *name; + name = nautilus_file_get_display_name (file); + /* Translators: %s is a file name formatted for display */ + text = g_strdup_printf (_("Unable to remove “%s”"), name); + show_dialog (text, + error->message, + GTK_WINDOW (nautilus_files_view_get_window (view)), + GTK_MESSAGE_ERROR); + g_free (text); + g_free (name); + } +} + +static void +file_eject_callback (NautilusFile *file, + GFile *result_location, + GError *error, + gpointer callback_data) +{ + NautilusFilesView *view; + + view = NAUTILUS_FILES_VIEW (callback_data); + g_object_unref (view); + + if (error != NULL && + (error->domain != G_IO_ERROR || + (error->code != G_IO_ERROR_CANCELLED && + error->code != G_IO_ERROR_FAILED_HANDLED))) + { + char *text; + char *name; + name = nautilus_file_get_display_name (file); + /* Translators: %s is a file name formatted for display */ + text = g_strdup_printf (_("Unable to eject “%s”"), name); + show_dialog (text, + error->message, + GTK_WINDOW (nautilus_files_view_get_window (view)), + GTK_MESSAGE_ERROR); + g_free (text); + g_free (name); + } +} + +static void +file_stop_callback (NautilusFile *file, + GFile *result_location, + GError *error, + gpointer callback_data) +{ + NautilusFilesView *view; + + view = NAUTILUS_FILES_VIEW (callback_data); + + if (error != NULL && + (error->domain != G_IO_ERROR || + (error->code != G_IO_ERROR_CANCELLED && + error->code != G_IO_ERROR_FAILED_HANDLED))) + { + show_dialog (_("Unable to stop drive"), + error->message, + GTK_WINDOW (nautilus_files_view_get_window (view)), + GTK_MESSAGE_ERROR); + } +} + +static void +action_mount_volume (GSimpleAction *action, + GVariant *state, + gpointer user_data) +{ + NautilusFile *file; + GList *selection, *l; + NautilusFilesView *view; + GMountOperation *mount_op; + + view = NAUTILUS_FILES_VIEW (user_data); + + selection = nautilus_view_get_selection (NAUTILUS_VIEW (view)); + for (l = selection; l != NULL; l = l->next) + { + file = NAUTILUS_FILE (l->data); + + if (nautilus_file_can_mount (file)) + { + mount_op = gtk_mount_operation_new (nautilus_files_view_get_containing_window (view)); + g_mount_operation_set_password_save (mount_op, G_PASSWORD_SAVE_FOR_SESSION); + nautilus_file_mount (file, mount_op, NULL, + file_mount_callback, + view); + g_object_unref (mount_op); + } + } + nautilus_file_list_free (selection); +} + +static void +action_unmount_volume (GSimpleAction *action, + GVariant *state, + gpointer user_data) +{ + NautilusFile *file; + g_autolist (NautilusFile) selection = NULL; + GList *l; + NautilusFilesView *view; + + view = NAUTILUS_FILES_VIEW (user_data); + + selection = nautilus_view_get_selection (NAUTILUS_VIEW (view)); + + for (l = selection; l != NULL; l = l->next) + { + file = NAUTILUS_FILE (l->data); + if (nautilus_file_can_unmount (file)) + { + GMountOperation *mount_op; + mount_op = gtk_mount_operation_new (nautilus_files_view_get_containing_window (view)); + nautilus_file_unmount (file, mount_op, NULL, + file_unmount_callback, g_object_ref (view)); + g_object_unref (mount_op); + } + } +} + +static void +action_eject_volume (GSimpleAction *action, + GVariant *state, + gpointer user_data) +{ + NautilusFile *file; + g_autolist (NautilusFile) selection = NULL; + GList *l; + NautilusFilesView *view; + + view = NAUTILUS_FILES_VIEW (user_data); + + selection = nautilus_view_get_selection (NAUTILUS_VIEW (view)); + for (l = selection; l != NULL; l = l->next) + { + file = NAUTILUS_FILE (l->data); + + if (nautilus_file_can_eject (file)) + { + GMountOperation *mount_op; + mount_op = gtk_mount_operation_new (nautilus_files_view_get_containing_window (view)); + nautilus_file_eject (file, mount_op, NULL, + file_eject_callback, g_object_ref (view)); + g_object_unref (mount_op); + } + } +} + +static void +file_start_callback (NautilusFile *file, + GFile *result_location, + GError *error, + gpointer callback_data) +{ + NautilusFilesView *view; + + view = NAUTILUS_FILES_VIEW (callback_data); + + if (error != NULL && + (error->domain != G_IO_ERROR || + (error->code != G_IO_ERROR_CANCELLED && + error->code != G_IO_ERROR_FAILED_HANDLED && + error->code != G_IO_ERROR_ALREADY_MOUNTED))) + { + char *text; + char *name; + name = nautilus_file_get_display_name (file); + /* Translators: %s is a file name formatted for display */ + text = g_strdup_printf (_("Unable to start “%s”"), name); + show_dialog (text, + error->message, + GTK_WINDOW (nautilus_files_view_get_window (view)), + GTK_MESSAGE_ERROR); + g_free (text); + g_free (name); + } +} + +static void +action_start_volume (GSimpleAction *action, + GVariant *state, + gpointer user_data) +{ + NautilusFile *file; + g_autolist (NautilusFile) selection = NULL; + GList *l; + NautilusFilesView *view; + GMountOperation *mount_op; + + view = NAUTILUS_FILES_VIEW (user_data); + + selection = nautilus_view_get_selection (NAUTILUS_VIEW (view)); + for (l = selection; l != NULL; l = l->next) + { + file = NAUTILUS_FILE (l->data); + + if (nautilus_file_can_start (file) || nautilus_file_can_start_degraded (file)) + { + mount_op = gtk_mount_operation_new (nautilus_files_view_get_containing_window (view)); + nautilus_file_start (file, mount_op, NULL, + file_start_callback, view); + g_object_unref (mount_op); + } + } +} + +static void +action_stop_volume (GSimpleAction *action, + GVariant *state, + gpointer user_data) +{ + NautilusFile *file; + g_autolist (NautilusFile) selection = NULL; + GList *l; + NautilusFilesView *view; + + view = NAUTILUS_FILES_VIEW (user_data); + + selection = nautilus_view_get_selection (NAUTILUS_VIEW (view)); + for (l = selection; l != NULL; l = l->next) + { + file = NAUTILUS_FILE (l->data); + + if (nautilus_file_can_stop (file)) + { + GMountOperation *mount_op; + mount_op = gtk_mount_operation_new (nautilus_files_view_get_containing_window (view)); + nautilus_file_stop (file, mount_op, NULL, + file_stop_callback, view); + g_object_unref (mount_op); + } + } +} + +static void +action_detect_media (GSimpleAction *action, + GVariant *state, + gpointer user_data) +{ + NautilusFile *file; + g_autolist (NautilusFile) selection = NULL; + GList *l; + NautilusView *view; + + view = NAUTILUS_VIEW (user_data); + + selection = nautilus_view_get_selection (view); + for (l = selection; l != NULL; l = l->next) + { + file = NAUTILUS_FILE (l->data); + + if (nautilus_file_can_poll_for_media (file) && !nautilus_file_is_media_check_automatic (file)) + { + nautilus_file_poll_for_media (file); + } + } +} + +const GActionEntry view_entries[] = +{ + /* Toolbar menu */ + { "zoom-in", action_zoom_in }, + { "zoom-out", action_zoom_out }, + { "zoom-standard", action_zoom_standard }, + { "show-hidden-files", NULL, NULL, "true", action_show_hidden_files }, + /* Background menu */ + { "empty-trash", action_empty_trash }, + { "new-folder", action_new_folder }, + { "select-all", action_select_all }, + { "paste", action_paste_files }, + { "copy-current-location", action_copy_current_location }, + { "paste_accel", action_paste_files_accel }, + { "create-link", action_create_links }, + { "create-link-shortcut", action_create_links }, + /* Selection menu */ + { "new-folder-with-selection", action_new_folder_with_selection }, + { "open-scripts-folder", action_open_scripts_folder }, + { "open-item-location", action_open_item_location }, + { "open-with-default-application", action_open_with_default_application }, + { "open-with-other-application", action_open_with_other_application }, + { "open-current-directory-with-other-application", action_open_current_directory_with_other_application }, + { "open-item-new-window", action_open_item_new_window }, + { "open-item-new-tab", action_open_item_new_tab }, + { "cut", action_cut}, + { "copy", action_copy}, + { "create-link-in-place", action_create_links_in_place }, + { "create-link-in-place-shortcut", action_create_links_in_place }, + { "move-to", action_move_to}, + { "copy-to", action_copy_to}, + { "move-to-trash", action_move_to_trash}, + { "delete-from-trash", action_delete }, + { "star", action_star}, + { "unstar", action_unstar}, + /* We separate the shortcut and the menu item since we want the shortcut + * to always be available, but we don't want the menu item shown if not + * completely necesary. Since the visibility of the menu item is based on + * the action enability, we need to split the actions for the menu and the + * shortcut. */ + { "delete-permanently-shortcut", action_delete }, + { "delete-permanently-menu-item", action_delete }, + /* This is only shown when the setting to show always delete permanently + * is set and when the common use cases for delete permanently which uses + * Delete as a shortcut are not needed. For instance this will be only + * present when the setting is true and when it can trash files */ + { "permanent-delete-permanently-menu-item", action_delete }, + { "remove-from-recent", action_remove_from_recent }, + { "restore-from-trash", action_restore_from_trash}, + { "paste-into", action_paste_files_into }, + { "rename", action_rename}, + { "extract-here", action_extract_here }, + { "extract-to", action_extract_to }, + { "compress", action_compress }, + { "send-email", action_send_email }, + { "console", action_open_console }, + { "current-directory-console", action_current_dir_open_console }, + { "properties", action_properties}, + { "current-directory-properties", action_current_dir_properties}, + { "run-in-terminal", action_run_in_terminal }, + { "set-as-wallpaper", action_set_as_wallpaper }, + { "mount-volume", action_mount_volume }, + { "unmount-volume", action_unmount_volume }, + { "eject-volume", action_eject_volume }, + { "start-volume", action_start_volume }, + { "stop-volume", action_stop_volume }, + { "detect-media", action_detect_media }, + /* Only accesible by shorcuts */ + { "select-pattern", action_select_pattern }, + { "invert-selection", action_invert_selection }, + { "preview-selection", action_preview_selection }, + { "popup-menu", action_popup_menu }, +}; + +static gboolean +can_paste_into_file (NautilusFile *file) +{ + if (nautilus_file_is_directory (file) && + nautilus_file_can_write (file)) + { + return TRUE; + } + if (nautilus_file_has_activation_uri (file)) + { + GFile *location; + NautilusFile *activation_file; + gboolean res; + + location = nautilus_file_get_activation_location (file); + activation_file = nautilus_file_get (location); + g_object_unref (location); + + /* The target location might not have data for it read yet, + * and we can't want to do sync I/O, so treat the unknown + * case as can-write */ + res = (nautilus_file_get_file_type (activation_file) == G_FILE_TYPE_UNKNOWN) || + (nautilus_file_get_file_type (activation_file) == G_FILE_TYPE_DIRECTORY && + nautilus_file_can_write (activation_file)); + + nautilus_file_unref (activation_file); + + return res; + } + + return FALSE; +} + +static void +update_actions_clipboard_contents_received (GObject *source_object, + GAsyncResult *res, + gpointer user_data) +{ + NautilusFilesView *view = NAUTILUS_FILES_VIEW (source_object); + NautilusFilesViewPrivate *priv = nautilus_files_view_get_instance_private (view); + NautilusClipboard *clip; + gboolean can_link_from_copied_files; + gboolean settings_show_create_link; + gboolean is_read_only; + gboolean selection_contains_recent; + gboolean selection_contains_starred; + GAction *action; + + clip = nautilus_files_view_get_clipboard_finish (view, res, NULL); + + if (priv->in_destruction || + !priv->active) + { + /* We've been destroyed or became inactive since call */ + return; + } + + settings_show_create_link = g_settings_get_boolean (nautilus_preferences, + NAUTILUS_PREFERENCES_SHOW_CREATE_LINK); + is_read_only = nautilus_files_view_is_read_only (view); + selection_contains_recent = showing_recent_directory (view); + selection_contains_starred = showing_starred_directory (view); + can_link_from_copied_files = clip != NULL && !nautilus_clipboard_is_cut (clip) && + !selection_contains_recent && !selection_contains_starred && + !is_read_only; + + action = g_action_map_lookup_action (G_ACTION_MAP (priv->view_action_group), + "create-link"); + g_simple_action_set_enabled (G_SIMPLE_ACTION (action), + can_link_from_copied_files && + settings_show_create_link); + action = g_action_map_lookup_action (G_ACTION_MAP (priv->view_action_group), + "create-link-shortcut"); + g_simple_action_set_enabled (G_SIMPLE_ACTION (action), + can_link_from_copied_files && + !settings_show_create_link); +} + +static void +update_actions_state_for_clipboard_targets (NautilusFilesView *view) +{ + NautilusFilesViewPrivate *priv; + GdkClipboard *clipboard; + GdkContentFormats *formats; + gboolean is_data_copied; + GAction *action; + + priv = nautilus_files_view_get_instance_private (view); + clipboard = gtk_widget_get_clipboard (GTK_WIDGET (view)); + formats = gdk_clipboard_get_formats (clipboard); + is_data_copied = gdk_content_formats_contain_gtype (formats, NAUTILUS_TYPE_CLIPBOARD); + + action = g_action_map_lookup_action (G_ACTION_MAP (priv->view_action_group), + "paste"); + /* Take into account if the action was previously disabled for other reasons, + * like the directory not being writabble */ + g_simple_action_set_enabled (G_SIMPLE_ACTION (action), + is_data_copied && g_action_get_enabled (action)); + + action = g_action_map_lookup_action (G_ACTION_MAP (priv->view_action_group), + "paste-into"); + + g_simple_action_set_enabled (G_SIMPLE_ACTION (action), + is_data_copied && g_action_get_enabled (action)); + + action = g_action_map_lookup_action (G_ACTION_MAP (priv->view_action_group), + "create-link"); + + g_simple_action_set_enabled (G_SIMPLE_ACTION (action), + is_data_copied && g_action_get_enabled (action)); +} + +static void +file_should_show_foreach (NautilusFile *file, + gboolean *show_mount, + gboolean *show_unmount, + gboolean *show_eject, + gboolean *show_start, + gboolean *show_stop, + gboolean *show_poll, + GDriveStartStopType *start_stop_type) +{ + *show_mount = FALSE; + *show_unmount = FALSE; + *show_eject = FALSE; + *show_start = FALSE; + *show_stop = FALSE; + *show_poll = FALSE; + + if (nautilus_file_can_eject (file)) + { + *show_eject = TRUE; + } + + if (nautilus_file_can_mount (file)) + { + *show_mount = TRUE; + } + + if (nautilus_file_can_start (file) || nautilus_file_can_start_degraded (file)) + { + *show_start = TRUE; + } + + if (nautilus_file_can_stop (file)) + { + *show_stop = TRUE; + } + + /* Dot not show both Unmount and Eject/Safe Removal; too confusing to + * have too many menu entries */ + if (nautilus_file_can_unmount (file) && !*show_eject && !*show_stop) + { + *show_unmount = TRUE; + } + + if (nautilus_file_can_poll_for_media (file) && !nautilus_file_is_media_check_automatic (file)) + { + *show_poll = TRUE; + } + + *start_stop_type = nautilus_file_get_start_stop_type (file); +} + +static gboolean +can_restore_from_trash (GList *files) +{ + NautilusFile *original_file; + NautilusFile *original_dir; + GHashTable *original_dirs_hash; + GList *original_dirs; + gboolean can_restore; + + original_file = NULL; + original_dir = NULL; + original_dirs = NULL; + original_dirs_hash = NULL; + + if (files != NULL) + { + if (g_list_length (files) == 1) + { + original_file = nautilus_file_get_trash_original_file (files->data); + } + else + { + original_dirs_hash = nautilus_trashed_files_get_original_directories (files, NULL); + if (original_dirs_hash != NULL) + { + original_dirs = g_hash_table_get_keys (original_dirs_hash); + if (g_list_length (original_dirs) == 1) + { + original_dir = nautilus_file_ref (NAUTILUS_FILE (original_dirs->data)); + } + } + } + } + + can_restore = original_file != NULL || original_dirs != NULL; + + nautilus_file_unref (original_file); + nautilus_file_unref (original_dir); + g_list_free (original_dirs); + + if (original_dirs_hash != NULL) + { + g_hash_table_destroy (original_dirs_hash); + } + return can_restore; +} + +static void +on_clipboard_owner_changed (GdkClipboard *clipboard, + gpointer user_data) +{ + NautilusFilesView *self = NAUTILUS_FILES_VIEW (user_data); + + /* We need to update paste and paste-like actions */ + nautilus_files_view_update_actions_state (self); +} + +static gboolean +can_delete_all (GList *files) +{ + NautilusFile *file; + GList *l; + + for (l = files; l != NULL; l = l->next) + { + file = l->data; + if (!nautilus_file_can_delete (file)) + { + return FALSE; + } + } + return TRUE; +} + +static gboolean +can_trash_all (GList *files) +{ + NautilusFile *file; + GList *l; + + for (l = files; l != NULL; l = l->next) + { + file = l->data; + if (!nautilus_file_can_trash (file)) + { + return FALSE; + } + } + return TRUE; +} + +static gboolean +all_in_trash (GList *files) +{ + NautilusFile *file; + GList *l; + + for (l = files; l != NULL; l = l->next) + { + file = l->data; + if (!nautilus_file_is_in_trash (file)) + { + return FALSE; + } + } + return TRUE; +} + +static gboolean +can_extract_all (GList *files) +{ + NautilusFile *file; + GList *l; + + for (l = files; l != NULL; l = l->next) + { + file = l->data; + if (!nautilus_file_is_archive (file)) + { + return FALSE; + } + } + return TRUE; +} + +static gboolean +nautilus_handles_all_files_to_extract (GList *files) +{ + NautilusFile *file; + GList *l; + + for (l = files; l != NULL; l = l->next) + { + file = l->data; + if (!nautilus_mime_file_extracts (file)) + { + return FALSE; + } + } + return TRUE; +} + +GActionGroup * +nautilus_files_view_get_action_group (NautilusFilesView *view) +{ + NautilusFilesViewPrivate *priv; + + g_assert (NAUTILUS_IS_FILES_VIEW (view)); + + priv = nautilus_files_view_get_instance_private (view); + + return priv->view_action_group; +} + +static void +real_update_actions_state (NautilusFilesView *view) +{ + NautilusFilesViewPrivate *priv; + g_autolist (NautilusFile) selection = NULL; + GList *l; + gint selection_count; + gboolean zoom_level_is_default; + gboolean selection_contains_home_dir; + gboolean selection_contains_recent; + gboolean selection_contains_search; + gboolean selection_contains_starred; + gboolean selection_all_in_trash; + gboolean selection_is_read_only; + gboolean can_create_files; + gboolean can_delete_files; + gboolean can_move_files; + gboolean can_trash_files; + gboolean can_copy_files; + gboolean can_paste_files_into; + gboolean can_extract_files; + gboolean handles_all_files_to_extract; + gboolean can_extract_here; + gboolean item_opens_in_view; + gboolean is_read_only; + gboolean is_in_trash; + GAction *action; + GActionGroup *view_action_group; + gboolean show_mount; + gboolean show_unmount; + gboolean show_eject; + gboolean show_start; + gboolean show_stop; + gboolean show_detect_media; + gboolean settings_show_delete_permanently; + gboolean settings_show_create_link; + GDriveStartStopType start_stop_type; + g_autoptr (GFile) current_location = NULL; + g_autofree gchar *current_uri = NULL; + gboolean can_star_current_directory; + gboolean show_star; + gboolean show_unstar; + gchar *uri; + g_autoptr (GAppInfo) app_info_mailto = NULL; + + priv = nautilus_files_view_get_instance_private (view); + + view_action_group = priv->view_action_group; + + selection = nautilus_view_get_selection (NAUTILUS_VIEW (view)); + selection_count = g_list_length (selection); + selection_contains_home_dir = home_dir_in_selection (selection); + selection_contains_recent = showing_recent_directory (view); + selection_contains_starred = showing_starred_directory (view); + selection_contains_search = nautilus_view_is_searching (NAUTILUS_VIEW (view)); + selection_is_read_only = selection_count == 1 && + (!nautilus_file_can_write (NAUTILUS_FILE (selection->data)) && + !nautilus_file_has_activation_uri (NAUTILUS_FILE (selection->data))); + selection_all_in_trash = all_in_trash (selection); + zoom_level_is_default = nautilus_files_view_is_zoom_level_default (view); + + is_read_only = nautilus_files_view_is_read_only (view); + is_in_trash = showing_trash_directory (view); + can_create_files = nautilus_files_view_supports_creating_files (view); + can_delete_files = + can_delete_all (selection) && + selection_count != 0 && + !selection_contains_home_dir; + can_trash_files = + can_trash_all (selection) && + selection_count != 0 && + !selection_contains_home_dir; + can_copy_files = selection_count != 0; + can_move_files = can_delete_files && !selection_contains_recent && + !selection_contains_starred; + can_paste_files_into = (selection_count == 1 && + can_paste_into_file (NAUTILUS_FILE (selection->data))); + can_extract_files = selection_count != 0 && + can_extract_all (selection); + can_extract_here = nautilus_files_view_supports_extract_here (view); + handles_all_files_to_extract = nautilus_handles_all_files_to_extract (selection); + settings_show_delete_permanently = g_settings_get_boolean (nautilus_preferences, + NAUTILUS_PREFERENCES_SHOW_DELETE_PERMANENTLY); + settings_show_create_link = g_settings_get_boolean (nautilus_preferences, + NAUTILUS_PREFERENCES_SHOW_CREATE_LINK); + + app_info_mailto = g_app_info_get_default_for_uri_scheme ("mailto"); + + /* Right click actions + * Selection menu actions + */ + action = g_action_map_lookup_action (G_ACTION_MAP (view_action_group), + "new-folder-with-selection"); + g_simple_action_set_enabled (G_SIMPLE_ACTION (action), + can_create_files && can_delete_files && (selection_count > 1) && !selection_contains_recent + && !selection_contains_starred); + + action = g_action_map_lookup_action (G_ACTION_MAP (view_action_group), + "rename"); + if (selection_count > 1) + { + g_simple_action_set_enabled (G_SIMPLE_ACTION (action), + nautilus_file_can_rename_files (selection)); + } + else + { + g_simple_action_set_enabled (G_SIMPLE_ACTION (action), + selection_count == 1 && + nautilus_file_can_rename (selection->data)); + } + + action = g_action_map_lookup_action (G_ACTION_MAP (view_action_group), + "extract-here"); + g_simple_action_set_enabled (G_SIMPLE_ACTION (action), + can_extract_files && + !handles_all_files_to_extract && + can_extract_here); + + action = g_action_map_lookup_action (G_ACTION_MAP (view_action_group), + "extract-to"); + g_simple_action_set_enabled (G_SIMPLE_ACTION (action), + can_extract_files && + (!handles_all_files_to_extract || + can_extract_here)); + + action = g_action_map_lookup_action (G_ACTION_MAP (view_action_group), + "compress"); + g_simple_action_set_enabled (G_SIMPLE_ACTION (action), + can_create_files && can_copy_files); + + action = g_action_map_lookup_action (G_ACTION_MAP (view_action_group), + "open-item-location"); + + g_simple_action_set_enabled (G_SIMPLE_ACTION (action), + selection_count == 1 && + (selection_contains_recent || selection_contains_search || + selection_contains_starred)); + + item_opens_in_view = selection_count != 0; + + for (l = selection; l != NULL; l = l->next) + { + NautilusFile *file; + + file = NAUTILUS_FILE (selection->data); + + if (!nautilus_file_opens_in_view (file)) + { + item_opens_in_view = FALSE; + } + + if (!item_opens_in_view) + { + break; + } + } + + action = g_action_map_lookup_action (G_ACTION_MAP (view_action_group), + "open-with-default-application"); + g_simple_action_set_enabled (G_SIMPLE_ACTION (action), selection_count != 0); + + /* Allow to select a different application to open the item */ + action = g_action_map_lookup_action (G_ACTION_MAP (view_action_group), + "open-with-other-application"); + g_simple_action_set_enabled (G_SIMPLE_ACTION (action), + selection_count > 0); + + action = g_action_map_lookup_action (G_ACTION_MAP (view_action_group), + "open-item-new-tab"); + g_simple_action_set_enabled (G_SIMPLE_ACTION (action), item_opens_in_view); + action = g_action_map_lookup_action (G_ACTION_MAP (view_action_group), + "open-item-new-window"); + g_simple_action_set_enabled (G_SIMPLE_ACTION (action), item_opens_in_view); + action = g_action_map_lookup_action (G_ACTION_MAP (view_action_group), + "run-in-terminal"); + g_simple_action_set_enabled (G_SIMPLE_ACTION (action), can_run_in_terminal (selection)); + action = g_action_map_lookup_action (G_ACTION_MAP (view_action_group), + "set-as-wallpaper"); + g_simple_action_set_enabled (G_SIMPLE_ACTION (action), can_set_wallpaper (selection)); + action = g_action_map_lookup_action (G_ACTION_MAP (view_action_group), + "restore-from-trash"); + g_simple_action_set_enabled (G_SIMPLE_ACTION (action), can_restore_from_trash (selection)); + + action = g_action_map_lookup_action (G_ACTION_MAP (view_action_group), + "move-to-trash"); + g_simple_action_set_enabled (G_SIMPLE_ACTION (action), can_trash_files); + + action = g_action_map_lookup_action (G_ACTION_MAP (view_action_group), + "delete-from-trash"); + g_simple_action_set_enabled (G_SIMPLE_ACTION (action), + can_delete_files && selection_all_in_trash); + + action = g_action_map_lookup_action (G_ACTION_MAP (view_action_group), + "delete-permanently-shortcut"); + g_simple_action_set_enabled (G_SIMPLE_ACTION (action), + can_delete_files); + + action = g_action_map_lookup_action (G_ACTION_MAP (view_action_group), + "delete-permanently-menu-item"); + g_simple_action_set_enabled (G_SIMPLE_ACTION (action), + can_delete_files && !can_trash_files && + !selection_all_in_trash && !selection_contains_recent && + !selection_contains_starred); + + action = g_action_map_lookup_action (G_ACTION_MAP (view_action_group), + "permanent-delete-permanently-menu-item"); + g_simple_action_set_enabled (G_SIMPLE_ACTION (action), + can_delete_files && can_trash_files && + settings_show_delete_permanently && + !selection_all_in_trash && !selection_contains_recent && + !selection_contains_starred); + + action = g_action_map_lookup_action (G_ACTION_MAP (view_action_group), + "remove-from-recent"); + g_simple_action_set_enabled (G_SIMPLE_ACTION (action), + selection_contains_recent && selection_count > 0); + + action = g_action_map_lookup_action (G_ACTION_MAP (view_action_group), + "cut"); + g_simple_action_set_enabled (G_SIMPLE_ACTION (action), + can_move_files && !selection_contains_recent && + !selection_contains_starred); + action = g_action_map_lookup_action (G_ACTION_MAP (view_action_group), + "copy"); + g_simple_action_set_enabled (G_SIMPLE_ACTION (action), + can_copy_files); + action = g_action_map_lookup_action (G_ACTION_MAP (view_action_group), + "create-link-in-place"); + g_simple_action_set_enabled (G_SIMPLE_ACTION (action), + can_copy_files && + can_create_files && + settings_show_create_link); + action = g_action_map_lookup_action (G_ACTION_MAP (view_action_group), + "create-link-in-place-shortcut"); + g_simple_action_set_enabled (G_SIMPLE_ACTION (action), + can_copy_files && + can_create_files && + !settings_show_create_link); + action = g_action_map_lookup_action (G_ACTION_MAP (view_action_group), + "send-email"); + g_simple_action_set_enabled (G_SIMPLE_ACTION (action), + app_info_mailto != NULL); + action = g_action_map_lookup_action (G_ACTION_MAP (view_action_group), + "copy-to"); + g_simple_action_set_enabled (G_SIMPLE_ACTION (action), + can_copy_files); + action = g_action_map_lookup_action (G_ACTION_MAP (view_action_group), + "move-to"); + g_simple_action_set_enabled (G_SIMPLE_ACTION (action), + can_move_files && !selection_contains_recent && + !selection_contains_starred); + action = g_action_map_lookup_action (G_ACTION_MAP (view_action_group), + "preview-selection"); + g_simple_action_set_enabled (G_SIMPLE_ACTION (action), selection_count != 0); + action = g_action_map_lookup_action (G_ACTION_MAP (view_action_group), + "copy-current-location"); + g_simple_action_set_enabled (G_SIMPLE_ACTION (action), + !selection_contains_recent && + !selection_contains_search && + !selection_contains_starred); + + /* Drive menu */ + show_mount = (selection != NULL); + show_unmount = (selection != NULL); + show_eject = (selection != NULL); + show_start = (selection != NULL && selection_count == 1); + show_stop = (selection != NULL && selection_count == 1); + show_detect_media = (selection != NULL && selection_count == 1); + for (l = selection; l != NULL && (show_mount || show_unmount + || show_eject + || show_start || show_stop + || show_detect_media); + l = l->next) + { + NautilusFile *file; + gboolean show_mount_one; + gboolean show_unmount_one; + gboolean show_eject_one; + gboolean show_start_one; + gboolean show_stop_one; + gboolean show_detect_media_one; + + file = NAUTILUS_FILE (l->data); + file_should_show_foreach (file, + &show_mount_one, + &show_unmount_one, + &show_eject_one, + &show_start_one, + &show_stop_one, + &show_detect_media_one, + &start_stop_type); + + show_mount &= show_mount_one; + show_unmount &= show_unmount_one; + show_eject &= show_eject_one; + show_start &= show_start_one; + show_stop &= show_stop_one; + show_detect_media &= show_detect_media_one; + } + + action = g_action_map_lookup_action (G_ACTION_MAP (view_action_group), + "mount-volume"); + g_simple_action_set_enabled (G_SIMPLE_ACTION (action), + show_mount); + action = g_action_map_lookup_action (G_ACTION_MAP (view_action_group), + "unmount-volume"); + g_simple_action_set_enabled (G_SIMPLE_ACTION (action), + show_unmount); + action = g_action_map_lookup_action (G_ACTION_MAP (view_action_group), + "eject-volume"); + g_simple_action_set_enabled (G_SIMPLE_ACTION (action), + show_eject); + action = g_action_map_lookup_action (G_ACTION_MAP (view_action_group), + "start-volume"); + g_simple_action_set_enabled (G_SIMPLE_ACTION (action), + show_start); + action = g_action_map_lookup_action (G_ACTION_MAP (view_action_group), + "stop-volume"); + g_simple_action_set_enabled (G_SIMPLE_ACTION (action), + show_stop); + action = g_action_map_lookup_action (G_ACTION_MAP (view_action_group), + "detect-media"); + g_simple_action_set_enabled (G_SIMPLE_ACTION (action), + show_detect_media); + + /* Background menu actions */ + action = g_action_map_lookup_action (G_ACTION_MAP (view_action_group), + "open-current-directory-with-other-application"); + g_simple_action_set_enabled (G_SIMPLE_ACTION (action), + !selection_contains_recent && + !selection_contains_search && + !selection_contains_starred); + action = g_action_map_lookup_action (G_ACTION_MAP (view_action_group), + "new-folder"); + g_simple_action_set_enabled (G_SIMPLE_ACTION (action), can_create_files); + + action = g_action_map_lookup_action (G_ACTION_MAP (view_action_group), + "empty-trash"); + + g_simple_action_set_enabled (G_SIMPLE_ACTION (action), + !nautilus_trash_monitor_is_empty () && + is_in_trash); + + action = g_action_map_lookup_action (G_ACTION_MAP (view_action_group), + "paste"); + g_simple_action_set_enabled (G_SIMPLE_ACTION (action), + !is_read_only && !selection_contains_recent && + !selection_contains_starred); + + action = g_action_map_lookup_action (G_ACTION_MAP (view_action_group), + "paste-into"); + g_simple_action_set_enabled (G_SIMPLE_ACTION (action), + can_paste_files_into); + + action = g_action_map_lookup_action (G_ACTION_MAP (view_action_group), + "console"); + g_simple_action_set_enabled (G_SIMPLE_ACTION (action), + selection_count == 1 && nautilus_file_is_directory (selection->data) && + nautilus_dbus_launcher_is_available (nautilus_dbus_launcher_get (), + NAUTILUS_DBUS_LAUNCHER_CONSOLE)); + action = g_action_map_lookup_action (G_ACTION_MAP (view_action_group), + "current-directory-console"); + g_simple_action_set_enabled (G_SIMPLE_ACTION (action), + nautilus_dbus_launcher_is_available (nautilus_dbus_launcher_get (), + NAUTILUS_DBUS_LAUNCHER_CONSOLE)); + action = g_action_map_lookup_action (G_ACTION_MAP (view_action_group), + "properties"); + g_simple_action_set_enabled (G_SIMPLE_ACTION (action), + TRUE); + action = g_action_map_lookup_action (G_ACTION_MAP (view_action_group), + "current-directory-properties"); + g_simple_action_set_enabled (G_SIMPLE_ACTION (action), + !selection_contains_recent && + !selection_contains_search && + !selection_contains_starred); + + /* Actions that are related to the clipboard need request, request the data + * and update them once we have the data */ + update_actions_state_for_clipboard_targets (view); + nautilus_files_view_get_clipboard_async (view, + update_actions_clipboard_contents_received, + NULL); + + action = g_action_map_lookup_action (G_ACTION_MAP (view_action_group), + "select-all"); + g_simple_action_set_enabled (G_SIMPLE_ACTION (action), + !nautilus_files_view_is_empty (view) && + !priv->loading); + + /* Toolbar menu actions */ + g_action_group_change_action_state (view_action_group, + "show-hidden-files", + g_variant_new_boolean (priv->show_hidden_files)); + + /* Zoom */ + action = g_action_map_lookup_action (G_ACTION_MAP (view_action_group), + "zoom-in"); + g_simple_action_set_enabled (G_SIMPLE_ACTION (action), + nautilus_files_view_can_zoom_in (view)); + action = g_action_map_lookup_action (G_ACTION_MAP (view_action_group), + "zoom-out"); + g_simple_action_set_enabled (G_SIMPLE_ACTION (action), + nautilus_files_view_can_zoom_out (view)); + action = g_action_map_lookup_action (G_ACTION_MAP (view_action_group), + "zoom-standard"); + g_simple_action_set_enabled (G_SIMPLE_ACTION (action), + nautilus_files_view_supports_zooming (view) && !zoom_level_is_default); + action = g_action_map_lookup_action (G_ACTION_MAP (view_action_group), + "zoom-to-level"); + g_simple_action_set_enabled (G_SIMPLE_ACTION (action), + !nautilus_files_view_is_empty (view)); + + current_location = nautilus_file_get_location (nautilus_files_view_get_directory_as_file (view)); + current_uri = g_file_get_uri (current_location); + can_star_current_directory = nautilus_tag_manager_can_star_contents (nautilus_tag_manager_get (), current_location); + + show_star = (selection != NULL) && + (can_star_current_directory || selection_contains_starred); + show_unstar = (selection != NULL) && + (can_star_current_directory || selection_contains_starred); + for (l = selection; l != NULL; l = l->next) + { + NautilusFile *file; + + file = NAUTILUS_FILE (l->data); + uri = nautilus_file_get_uri (file); + + if (!show_star && !show_unstar) + { + break; + } + + if (nautilus_tag_manager_file_is_starred (nautilus_tag_manager_get (), uri)) + { + show_star = FALSE; + } + else + { + show_unstar = FALSE; + } + + g_free (uri); + } + + action = g_action_map_lookup_action (G_ACTION_MAP (view_action_group), + "star"); + g_simple_action_set_enabled (G_SIMPLE_ACTION (action), show_star); + + action = g_action_map_lookup_action (G_ACTION_MAP (view_action_group), + "unstar"); + g_simple_action_set_enabled (G_SIMPLE_ACTION (action), show_unstar && selection_contains_starred); +} + +/* Convenience function to be called when updating menus, + * so children can subclass it and it will be called when + * they chain up to the parent in update_context_menus + * or update_toolbar_menus + */ +void +nautilus_files_view_update_actions_state (NautilusFilesView *view) +{ + g_assert (NAUTILUS_IS_FILES_VIEW (view)); + + NAUTILUS_FILES_VIEW_CLASS (G_OBJECT_GET_CLASS (view))->update_actions_state (view); +} + +static void +update_selection_menu (NautilusFilesView *view, + GtkBuilder *builder) +{ + NautilusFilesViewPrivate *priv = nautilus_files_view_get_instance_private (view); + g_autolist (NautilusFile) selection = NULL; + GList *l; + gint selection_count; + gboolean show_app; + gboolean show_run; + gboolean show_extract; + gboolean item_opens_in_view; + gchar *item_label; + GAppInfo *app; + GIcon *app_icon; + GMenuItem *menu_item; + GObject *object; + gboolean show_mount; + gboolean show_unmount; + gboolean show_eject; + gboolean show_start; + gboolean show_stop; + gboolean show_detect_media; + gboolean show_scripts = FALSE; + gint i; + GDriveStartStopType start_stop_type; + + selection = nautilus_view_get_selection (NAUTILUS_VIEW (view)); + selection_count = g_list_length (selection); + + show_mount = (selection != NULL); + show_unmount = (selection != NULL); + show_eject = (selection != NULL); + show_start = (selection != NULL && selection_count == 1); + show_stop = (selection != NULL && selection_count == 1); + show_detect_media = (selection != NULL && selection_count == 1); + start_stop_type = G_DRIVE_START_STOP_TYPE_UNKNOWN; + item_label = g_strdup_printf (ngettext ("New Folder with Selection (%'d Item)", + "New Folder with Selection (%'d Items)", + selection_count), + selection_count); + menu_item = g_menu_item_new (item_label, "view.new-folder-with-selection"); + g_menu_item_set_attribute (menu_item, "hidden-when", "s", "action-disabled"); + object = gtk_builder_get_object (builder, "new-folder-with-selection-section"); + g_menu_append_item (G_MENU (object), menu_item); + g_object_unref (menu_item); + g_free (item_label); + + /* Open With menu item */ + show_extract = show_app = show_run = item_opens_in_view = selection_count != 0; + for (l = selection; l != NULL; l = l->next) + { + NautilusFile *file; + + file = NAUTILUS_FILE (l->data); + + if (show_extract && !nautilus_mime_file_extracts (file)) + { + show_extract = FALSE; + } + + if (show_app && !nautilus_mime_file_opens_in_external_app (file)) + { + show_app = FALSE; + } + + if (show_run && !nautilus_mime_file_launches (file)) + { + show_run = FALSE; + } + + if (item_opens_in_view && !nautilus_file_opens_in_view (file)) + { + item_opens_in_view = FALSE; + } + + if (!show_extract && !show_app && !show_run && !item_opens_in_view) + { + break; + } + } + + item_label = NULL; + app = NULL; + app_icon = NULL; + if (show_app) + { + app = nautilus_mime_get_default_application_for_files (selection); + } + + if (app != NULL) + { + char *escaped_app; + + escaped_app = eel_str_double_underscores (g_app_info_get_name (app)); + item_label = g_strdup_printf (_("Open With %s"), escaped_app); + + app_icon = g_app_info_get_icon (app); + if (app_icon != NULL) + { + g_object_ref (app_icon); + } + g_free (escaped_app); + g_object_unref (app); + } + else if (show_run) + { + item_label = g_strdup (_("Run")); + } + else if (show_extract) + { + item_label = nautilus_files_view_supports_extract_here (view) ? + g_strdup (_("Extract")) : + g_strdup (_("Extract to…")); + } + else + { + item_label = g_strdup (_("Open")); + } + + /* The action already exists in the submenu if item opens in view */ + if (!item_opens_in_view) + { + menu_item = g_menu_item_new (item_label, "view.open-with-default-application"); + if (app_icon != NULL) + { + g_menu_item_set_icon (menu_item, app_icon); + } + + object = gtk_builder_get_object (builder, "open-with-application-section"); + g_menu_prepend_item (G_MENU (object), menu_item); + + g_object_unref (menu_item); + } + else + { + object = gtk_builder_get_object (builder, "open-with-application-section"); + i = nautilus_g_menu_model_find_by_string (G_MENU_MODEL (object), + "nautilus-menu-item", + "open_with_in_main_menu"); + g_menu_remove (G_MENU (object), i); + } + + /* The "Open" submenu should be hidden if the item doesn't open in the view. */ + object = gtk_builder_get_object (builder, "open-with-application-section"); + i = nautilus_g_menu_model_find_by_string (G_MENU_MODEL (object), + "nautilus-menu-item", + "open_in_view_submenu"); + nautilus_g_menu_replace_string_in_item (G_MENU (object), i, + "hidden-when", + (!item_opens_in_view) ? "action-missing" : NULL); + + g_free (item_label); + + /* Drives */ + for (l = selection; l != NULL && (show_mount || show_unmount + || show_eject + || show_start || show_stop + || show_detect_media); + l = l->next) + { + NautilusFile *file; + gboolean show_mount_one; + gboolean show_unmount_one; + gboolean show_eject_one; + gboolean show_start_one; + gboolean show_stop_one; + gboolean show_detect_media_one; + + file = NAUTILUS_FILE (l->data); + file_should_show_foreach (file, + &show_mount_one, + &show_unmount_one, + &show_eject_one, + &show_start_one, + &show_stop_one, + &show_detect_media_one, + &start_stop_type); + + show_mount &= show_mount_one; + show_unmount &= show_unmount_one; + show_eject &= show_eject_one; + show_start &= show_start_one; + show_stop &= show_stop_one; + show_detect_media &= show_detect_media_one; + } + + if (show_start) + { + switch (start_stop_type) + { + default: + case G_DRIVE_START_STOP_TYPE_UNKNOWN: + case G_DRIVE_START_STOP_TYPE_SHUTDOWN: + { + item_label = _("_Start"); + } + break; + + case G_DRIVE_START_STOP_TYPE_NETWORK: + { + item_label = _("_Connect"); + } + break; + + case G_DRIVE_START_STOP_TYPE_MULTIDISK: + { + item_label = _("_Start Multi-disk Drive"); + } + break; + + case G_DRIVE_START_STOP_TYPE_PASSWORD: + { + item_label = _("U_nlock Drive"); + } + break; + } + + menu_item = g_menu_item_new (item_label, "view.start-volume"); + object = gtk_builder_get_object (builder, "drive-section"); + g_menu_append_item (G_MENU (object), menu_item); + g_object_unref (menu_item); + } + + if (show_stop) + { + switch (start_stop_type) + { + default: + case G_DRIVE_START_STOP_TYPE_UNKNOWN: + { + item_label = _("Stop Drive"); + } + break; + + case G_DRIVE_START_STOP_TYPE_SHUTDOWN: + { + item_label = _("_Safely Remove Drive"); + } + break; + + case G_DRIVE_START_STOP_TYPE_NETWORK: + { + item_label = _("_Disconnect"); + } + break; + + case G_DRIVE_START_STOP_TYPE_MULTIDISK: + { + item_label = _("_Stop Multi-disk Drive"); + } + break; + + case G_DRIVE_START_STOP_TYPE_PASSWORD: + { + item_label = _("_Lock Drive"); + } + break; + } + + menu_item = g_menu_item_new (item_label, "view.stop-volume"); + object = gtk_builder_get_object (builder, "drive-section"); + g_menu_append_item (G_MENU (object), menu_item); + g_object_unref (menu_item); + } + + if (!priv->scripts_menu_updated) + { + update_scripts_menu (view, builder); + priv->scripts_menu_updated = TRUE; + } + + if (priv->scripts_menu != NULL) + { + show_scripts = TRUE; + object = gtk_builder_get_object (builder, "scripts-submenu-section"); + nautilus_gmenu_set_from_model (G_MENU (object), priv->scripts_menu); + } + + object = gtk_builder_get_object (builder, "open-with-application-section"); + i = nautilus_g_menu_model_find_by_string (G_MENU_MODEL (object), + "nautilus-menu-item", + "scripts-submenu"); + nautilus_g_menu_replace_string_in_item (G_MENU (object), i, + "hidden-when", + (!show_scripts) ? "action-missing" : NULL); +} + +static void +update_background_menu (NautilusFilesView *view, + GtkBuilder *builder) +{ + NautilusFilesViewPrivate *priv = nautilus_files_view_get_instance_private (view); + GObject *object; + gboolean remove_submenu = TRUE; + gint i; + + if (nautilus_files_view_supports_creating_files (view) && + !showing_recent_directory (view) && + !showing_starred_directory (view)) + { + if (!priv->templates_menu_updated) + { + update_templates_menu (view, builder); + priv->templates_menu_updated = TRUE; + } + + object = gtk_builder_get_object (builder, "templates-submenu"); + nautilus_gmenu_set_from_model (G_MENU (object), priv->templates_menu); + + if (priv->templates_menu != NULL) + { + remove_submenu = FALSE; + } + } + else + { + /* This is necessary because the pathbar menu relies on it being NULL + * to hide the submenu. */ + nautilus_view_set_templates_menu (NAUTILUS_VIEW (view), NULL); + + /* And this is necessary to regenerate the templates menu when we go + * back to a normal folder. */ + priv->templates_menu_updated = FALSE; + } + + i = nautilus_g_menu_model_find_by_string (G_MENU_MODEL (priv->background_menu_model), + "nautilus-menu-item", + "templates-submenu"); + nautilus_g_menu_replace_string_in_item (priv->background_menu_model, i, + "hidden-when", + remove_submenu ? "action-missing" : NULL); +} + +static void +real_update_context_menus (NautilusFilesView *view) +{ + NautilusFilesViewPrivate *priv; + g_autoptr (GtkBuilder) builder = NULL; + GObject *object; + + priv = nautilus_files_view_get_instance_private (view); + builder = gtk_builder_new_from_resource ("/org/gnome/nautilus/ui/nautilus-files-view-context-menus.ui"); + + g_clear_object (&priv->background_menu_model); + g_clear_object (&priv->selection_menu_model); + + object = gtk_builder_get_object (builder, "background-menu"); + priv->background_menu_model = g_object_ref (G_MENU (object)); + + object = gtk_builder_get_object (builder, "selection-menu"); + priv->selection_menu_model = g_object_ref (G_MENU (object)); + + update_selection_menu (view, builder); + update_background_menu (view, builder); + update_extensions_menus (view, builder); + + nautilus_files_view_update_actions_state (view); +} + +/* Convenience function to reset the context menus owned by the view and update + * them with the current state. + * Children can subclass it and add items on the menu after chaining up to the + * parent, so menus are already reseted. + * It will also update the actions state, which will also update children + * actions state if the children subclass nautilus_files_view_update_actions_state + */ +void +nautilus_files_view_update_context_menus (NautilusFilesView *view) +{ + g_assert (NAUTILUS_IS_FILES_VIEW (view)); + + NAUTILUS_FILES_VIEW_CLASS (G_OBJECT_GET_CLASS (view))->update_context_menus (view); +} + +static void +nautilus_files_view_reset_view_menu (NautilusFilesView *view) +{ + NautilusFilesViewPrivate *priv = nautilus_files_view_get_instance_private (view); + NautilusFile *file; + GMenuModel *sort_section = priv->toolbar_menu_sections->sort_section; + const gchar *action; + gint i; + + file = nautilus_files_view_get_directory_as_file (NAUTILUS_FILES_VIEW (view)); + + /* When not in the special location, set an inexistant action to hide the + * menu item. This works under the assumptiont that the menu item has its + * "hidden-when" attribute set to "action-disabled", and that an inexistant + * action is treated as a disabled action. */ + action = nautilus_file_is_in_trash (file) ? "view.sort" : "doesnt-exist"; + i = nautilus_g_menu_model_find_by_string (sort_section, "nautilus-menu-item", "last_trashed"); + nautilus_g_menu_replace_string_in_item (G_MENU (sort_section), i, "action", action); + + action = nautilus_file_is_in_recent (file) ? "view.sort" : "doesnt-exist"; + i = nautilus_g_menu_model_find_by_string (sort_section, "nautilus-menu-item", "recency"); + nautilus_g_menu_replace_string_in_item (G_MENU (sort_section), i, "action", action); + + action = nautilus_file_is_in_search (file) ? "view.sort" : "doesnt-exist"; + i = nautilus_g_menu_model_find_by_string (sort_section, "nautilus-menu-item", "relevance"); + nautilus_g_menu_replace_string_in_item (G_MENU (sort_section), i, "action", action); +} + +/* Convenience function to reset the menus owned by the view but managed on + * the toolbar, and update them with the current state. + * It will also update the actions state, which will also update children + * actions state if the children subclass nautilus_files_view_update_actions_state + */ +void +nautilus_files_view_update_toolbar_menus (NautilusFilesView *view) +{ + NautilusWindow *window; + NautilusFilesViewPrivate *priv; + + g_assert (NAUTILUS_IS_FILES_VIEW (view)); + + priv = nautilus_files_view_get_instance_private (view); + + /* Don't update after destroy (#349551), + * or if we are not active. + */ + if (priv->in_destruction || + !priv->active) + { + return; + } + window = nautilus_files_view_get_window (view); + nautilus_window_reset_menus (window); + + nautilus_files_view_update_actions_state (view); + nautilus_files_view_reset_view_menu (view); +} + +static GdkRectangle * +nautilus_files_view_reveal_for_selection_context_menu (NautilusFilesView *view) +{ + return NAUTILUS_FILES_VIEW_CLASS (G_OBJECT_GET_CLASS (view))->reveal_for_selection_context_menu (view); +} + +/** + * nautilus_files_view_pop_up_selection_context_menu + * + * Pop up a context menu appropriate to the selected items. + * @view: NautilusFilesView of interest. + * @event: The event that triggered this context menu. + * + **/ +void +nautilus_files_view_pop_up_selection_context_menu (NautilusFilesView *view, + gdouble x, + gdouble y) +{ + NautilusFilesViewPrivate *priv; + + g_assert (NAUTILUS_IS_FILES_VIEW (view)); + + priv = nautilus_files_view_get_instance_private (view); + + /* Make the context menu items not flash as they update to proper disabled, + * etc. states by forcing menus to update now. + */ + update_context_menus_if_pending (view); + + /* Destroy old popover and create a new one, to avoid duplicate submenu bugs + * and showing old model temporarily. We don't do this when popover is + * closed because it wouldn't activate the actions then. */ + g_clear_pointer (&priv->selection_menu, gtk_widget_unparent); + priv->selection_menu = gtk_popover_menu_new_from_model (NULL); + + /* There's something related to NautilusFilesView that isn't grabbing the + * focus back when the popover is closed. Let's force it as a workaround. */ + g_signal_connect_object (priv->selection_menu, "closed", + G_CALLBACK (gtk_widget_grab_focus), view, + G_CONNECT_SWAPPED); + gtk_widget_set_parent (priv->selection_menu, GTK_WIDGET (view)); + gtk_popover_set_has_arrow (GTK_POPOVER (priv->selection_menu), FALSE); + gtk_widget_set_halign (priv->selection_menu, GTK_ALIGN_START); + + gtk_popover_menu_set_menu_model (GTK_POPOVER_MENU (priv->selection_menu), + G_MENU_MODEL (priv->selection_menu_model)); + if (x == -1 && y == -1) + { + /* If triggered from the keyboard, popup at selection, not pointer */ + g_autofree GdkRectangle *rectangle = NULL; + + rectangle = nautilus_files_view_reveal_for_selection_context_menu (view); + g_return_if_fail (rectangle != NULL); + gtk_popover_set_pointing_to (GTK_POPOVER (priv->selection_menu), + rectangle); + } + else + { + gtk_popover_set_pointing_to (GTK_POPOVER (priv->selection_menu), + &(GdkRectangle){x, y, 0, 0}); + } + gtk_popover_popup (GTK_POPOVER (priv->selection_menu)); +} + +/** + * nautilus_files_view_pop_up_background_context_menu + * + * Pop up a context menu appropriate to the location in view. + * @view: NautilusFilesView of interest. + * + **/ +void +nautilus_files_view_pop_up_background_context_menu (NautilusFilesView *view, + gdouble x, + gdouble y) +{ + NautilusFilesViewPrivate *priv; + + g_assert (NAUTILUS_IS_FILES_VIEW (view)); + + priv = nautilus_files_view_get_instance_private (view); + + /* Make the context menu items not flash as they update to proper disabled, + * etc. states by forcing menus to update now. + */ + update_context_menus_if_pending (view); + + /* Destroy old popover and create a new one, to avoid duplicate submenu bugs + * and showing old model temporarily. We don't do this when popover is + * closed because it wouldn't activate the actions then. */ + g_clear_pointer (&priv->background_menu, gtk_widget_unparent); + priv->background_menu = gtk_popover_menu_new_from_model (NULL); + + /* There's something related to NautilusFilesView that isn't grabbing the + * focus back when the popover is closed. Let's force it as a workaround. */ + g_signal_connect_object (priv->background_menu, "closed", + G_CALLBACK (gtk_widget_grab_focus), view, + G_CONNECT_SWAPPED); + gtk_widget_set_parent (priv->background_menu, GTK_WIDGET (view)); + gtk_popover_set_has_arrow (GTK_POPOVER (priv->background_menu), FALSE); + gtk_widget_set_halign (priv->background_menu, GTK_ALIGN_START); + + gtk_popover_menu_set_menu_model (GTK_POPOVER_MENU (priv->background_menu), + G_MENU_MODEL (priv->background_menu_model)); + + gtk_popover_set_pointing_to (GTK_POPOVER (priv->background_menu), + &(GdkRectangle){x, y, 0, 0}); + gtk_popover_popup (GTK_POPOVER (priv->background_menu)); +} + +static void +schedule_update_context_menus (NautilusFilesView *view) +{ + NautilusFilesViewPrivate *priv; + + g_assert (NAUTILUS_IS_FILES_VIEW (view)); + + priv = nautilus_files_view_get_instance_private (view); + + /* Don't schedule updates after destroy (#349551), + * or if we are not active. + */ + if (priv->in_destruction || + !priv->active) + { + return; + } + + /* Schedule a menu update with the current update interval */ + if (priv->update_context_menus_timeout_id == 0) + { + priv->update_context_menus_timeout_id + = g_timeout_add (priv->update_interval, update_context_menus_timeout_callback, view); + } +} + +static void +remove_update_status_idle_callback (NautilusFilesView *view) +{ + NautilusFilesViewPrivate *priv; + + priv = nautilus_files_view_get_instance_private (view); + + if (priv->update_status_idle_id != 0) + { + g_source_remove (priv->update_status_idle_id); + priv->update_status_idle_id = 0; + } +} + +static gboolean +update_status_idle_callback (gpointer data) +{ + NautilusFilesViewPrivate *priv; + NautilusFilesView *view; + + view = NAUTILUS_FILES_VIEW (data); + priv = nautilus_files_view_get_instance_private (view); + nautilus_files_view_display_selection_info (view); + priv->update_status_idle_id = 0; + return FALSE; +} + +static void +schedule_update_status (NautilusFilesView *view) +{ + NautilusFilesViewPrivate *priv; + + g_assert (NAUTILUS_IS_FILES_VIEW (view)); + + priv = nautilus_files_view_get_instance_private (view); + + /* Make sure we haven't already destroyed it */ + if (priv->in_destruction) + { + return; + } + + if (priv->loading) + { + /* Don't update status bar while loading the dir */ + return; + } + + if (priv->update_status_idle_id == 0) + { + priv->update_status_idle_id = + g_idle_add_full (G_PRIORITY_DEFAULT_IDLE - 20, + update_status_idle_callback, view, NULL); + } +} + +/** + * nautilus_files_view_notify_selection_changed: + * + * Notify this view that the selection has changed. This is normally + * called only by subclasses. + * @view: NautilusFilesView whose selection has changed. + * + **/ +void +nautilus_files_view_notify_selection_changed (NautilusFilesView *view) +{ + NautilusFilesViewPrivate *priv; + GtkWindow *window; + g_autolist (NautilusFile) selection = NULL; + + g_return_if_fail (NAUTILUS_IS_FILES_VIEW (view)); + + priv = nautilus_files_view_get_instance_private (view); + + selection = nautilus_view_get_selection (NAUTILUS_VIEW (view)); + window = nautilus_files_view_get_containing_window (view); + DEBUG_FILES (selection, "Selection changed in window %p", window); + + priv->selection_was_removed = FALSE; + + /* Schedule a display of the new selection. */ + if (priv->display_selection_idle_id == 0) + { + priv->display_selection_idle_id + = g_idle_add (display_selection_info_idle_callback, + view); + } + + schedule_update_context_menus (view); +} + +static void +file_changed_callback (NautilusFile *file, + gpointer callback_data) +{ + NautilusFilesView *view = NAUTILUS_FILES_VIEW (callback_data); + + schedule_changes (view); + + schedule_update_context_menus (view); + schedule_update_status (view); +} + +/** + * load_directory: + * + * Switch the displayed location to a new uri. If the uri is not valid, + * the location will not be switched; user feedback will be provided instead. + * @view: NautilusFilesView whose location will be changed. + * @uri: A string representing the uri to switch to. + * + **/ +static void +load_directory (NautilusFilesView *view, + NautilusDirectory *directory) +{ + NautilusFileAttributes attributes; + NautilusFilesViewPrivate *priv; + + g_assert (NAUTILUS_IS_FILES_VIEW (view)); + g_assert (NAUTILUS_IS_DIRECTORY (directory)); + + priv = nautilus_files_view_get_instance_private (view); + + nautilus_profile_start (NULL); + + nautilus_files_view_stop_loading (view); + g_signal_emit (view, signals[CLEAR], 0); + + priv->loading = TRUE; + + setup_loading_floating_bar (view); + + /* HACK: Fix for https://gitlab.gnome.org/GNOME/nautilus/-/issues/1452 */ + { + GtkScrolledWindow *content = GTK_SCROLLED_WINDOW (priv->scrolled_window); + + /* If we load a new location while the view is still scrolling due to + * kinetic deceleration, we get a sudden jump to the same scrolling + * position as the previous location, as well as residual scrolling + * movement in the new location. + * + * This is both undesirable and unexpected from a user POV, so we want + * to abort deceleration when switching locations. + * + * However, gtk_scrolled_window_cancel_deceleration() is private. So, + * we make use of an undocumented behavior of ::set_kinetic_scrolling(), + * which calls ::cancel_deceleration() when set to FALSE. + */ + gtk_scrolled_window_set_kinetic_scrolling (content, FALSE); + gtk_scrolled_window_set_kinetic_scrolling (content, TRUE); + } + + /* Update menus when directory is empty, before going to new + * location, so they won't have any false lingering knowledge + * of old selection. + */ + schedule_update_context_menus (view); + + while (priv->subdirectory_list != NULL) + { + nautilus_files_view_remove_subdirectory (view, + priv->subdirectory_list->data); + } + + /* Avoid freeing it and won't be able to ref it */ + if (priv->model != directory) + { + nautilus_directory_unref (priv->model); + priv->model = nautilus_directory_ref (directory); + } + + nautilus_file_unref (priv->directory_as_file); + priv->directory_as_file = nautilus_directory_get_corresponding_file (directory); + + g_clear_object (&priv->location); + priv->location = nautilus_directory_get_location (directory); + + g_object_notify (G_OBJECT (view), "location"); + g_object_notify (G_OBJECT (view), "loading"); + g_object_notify (G_OBJECT (view), "searching"); + + /* FIXME bugzilla.gnome.org 45062: In theory, we also need to monitor metadata here (as + * well as doing a call when ready), in case external forces + * change the directory's file metadata. + */ + attributes = + NAUTILUS_FILE_ATTRIBUTE_INFO | + NAUTILUS_FILE_ATTRIBUTE_MOUNT | + NAUTILUS_FILE_ATTRIBUTE_FILESYSTEM_INFO; + priv->metadata_for_directory_as_file_pending = TRUE; + priv->metadata_for_files_in_directory_pending = TRUE; + nautilus_file_call_when_ready + (priv->directory_as_file, + attributes, + metadata_for_directory_as_file_ready_callback, view); + nautilus_directory_call_when_ready + (priv->model, + attributes, + FALSE, + metadata_for_files_in_directory_ready_callback, view); + + /* If capabilities change, then we need to update the menus + * because of New Folder, and relative emblems. + */ + attributes = + NAUTILUS_FILE_ATTRIBUTE_INFO | + NAUTILUS_FILE_ATTRIBUTE_FILESYSTEM_INFO; + nautilus_file_monitor_add (priv->directory_as_file, + &priv->directory_as_file, + attributes); + + priv->file_changed_handler_id = g_signal_connect + (priv->directory_as_file, "changed", + G_CALLBACK (file_changed_callback), view); + + nautilus_profile_end (NULL); +} + +static void +finish_loading (NautilusFilesView *view) +{ + NautilusFileAttributes attributes; + NautilusFilesViewPrivate *priv; + + priv = nautilus_files_view_get_instance_private (view); + + nautilus_profile_start (NULL); + + /* Tell interested parties that we've begun loading this directory now. + * Subclasses use this to know that the new metadata is now available. + */ + nautilus_profile_start ("BEGIN_LOADING"); + g_signal_emit (view, signals[BEGIN_LOADING], 0); + nautilus_profile_end ("BEGIN_LOADING"); + + nautilus_files_view_check_empty_states (view); + + if (nautilus_directory_are_all_files_seen (priv->model)) + { + /* Unschedule a pending update and schedule a new one with the minimal + * update interval. This gives the view a short chance at gathering the + * (cached) deep counts. + */ + unschedule_display_of_pending_files (view); + schedule_timeout_display_of_pending_files (view, UPDATE_INTERVAL_MIN); + } + + /* Start loading. */ + + /* Connect handlers to learn about loading progress. */ + priv->done_loading_handler_id = g_signal_connect (priv->model, "done-loading", + G_CALLBACK (done_loading_callback), view); + priv->load_error_handler_id = g_signal_connect (priv->model, "load-error", + G_CALLBACK (load_error_callback), view); + + /* Monitor the things needed to get the right icon. Also + * monitor a directory's item count because the "size" + * attribute is based on that, and the file's metadata + * and possible custom name. + */ + attributes = + NAUTILUS_FILE_ATTRIBUTES_FOR_ICON | + NAUTILUS_FILE_ATTRIBUTE_DIRECTORY_ITEM_COUNT | + NAUTILUS_FILE_ATTRIBUTE_INFO | + NAUTILUS_FILE_ATTRIBUTE_MOUNT | + NAUTILUS_FILE_ATTRIBUTE_EXTENSION_INFO; + + priv->files_added_handler_id = g_signal_connect + (priv->model, "files-added", + G_CALLBACK (files_added_callback), view); + priv->files_changed_handler_id = g_signal_connect + (priv->model, "files-changed", + G_CALLBACK (files_changed_callback), view); + + nautilus_directory_file_monitor_add (priv->model, + &priv->model, + priv->show_hidden_files, + attributes, + files_added_callback, view); + + nautilus_profile_end (NULL); +} + +static void +finish_loading_if_all_metadata_loaded (NautilusFilesView *view) +{ + NautilusFilesViewPrivate *priv; + + priv = nautilus_files_view_get_instance_private (view); + + if (!priv->metadata_for_directory_as_file_pending && + !priv->metadata_for_files_in_directory_pending) + { + finish_loading (view); + } +} + +static void +metadata_for_directory_as_file_ready_callback (NautilusFile *file, + gpointer callback_data) +{ + NautilusFilesView *view; + NautilusFilesViewPrivate *priv; + + view = callback_data; + + g_assert (NAUTILUS_IS_FILES_VIEW (view)); + priv = nautilus_files_view_get_instance_private (view); + g_assert (priv->directory_as_file == file); + g_assert (priv->metadata_for_directory_as_file_pending); + + nautilus_profile_start (NULL); + + priv->metadata_for_directory_as_file_pending = FALSE; + + finish_loading_if_all_metadata_loaded (view); + nautilus_profile_end (NULL); +} + +static void +metadata_for_files_in_directory_ready_callback (NautilusDirectory *directory, + GList *files, + gpointer callback_data) +{ + NautilusFilesView *view; + NautilusFilesViewPrivate *priv; + + view = callback_data; + + g_assert (NAUTILUS_IS_FILES_VIEW (view)); + priv = nautilus_files_view_get_instance_private (view); + g_assert (priv->model == directory); + g_assert (priv->metadata_for_files_in_directory_pending); + + nautilus_profile_start (NULL); + + priv->metadata_for_files_in_directory_pending = FALSE; + + finish_loading_if_all_metadata_loaded (view); + nautilus_profile_end (NULL); +} + +static void +disconnect_model_handlers (NautilusFilesView *view) +{ + NautilusFilesViewPrivate *priv; + + priv = nautilus_files_view_get_instance_private (view); + + if (priv->model == NULL) + { + return; + } + g_clear_signal_handler (&priv->files_added_handler_id, priv->model); + g_clear_signal_handler (&priv->files_changed_handler_id, priv->model); + g_clear_signal_handler (&priv->done_loading_handler_id, priv->model); + g_clear_signal_handler (&priv->load_error_handler_id, priv->model); + g_clear_signal_handler (&priv->file_changed_handler_id, priv->directory_as_file); + nautilus_file_cancel_call_when_ready (priv->directory_as_file, + metadata_for_directory_as_file_ready_callback, + view); + nautilus_directory_cancel_callback (priv->model, + metadata_for_files_in_directory_ready_callback, + view); + nautilus_directory_file_monitor_remove (priv->model, + &priv->model); + nautilus_file_monitor_remove (priv->directory_as_file, + &priv->directory_as_file); +} + +static void +nautilus_files_view_select_file (NautilusFilesView *view, + NautilusFile *file) +{ + GList file_list; + + file_list.data = file; + file_list.next = NULL; + file_list.prev = NULL; + nautilus_files_view_call_set_selection (view, &file_list); +} + +/** + * nautilus_files_view_stop_loading: + * + * Stop the current ongoing process, such as switching to a new uri. + * @view: NautilusFilesView in question. + * + **/ +void +nautilus_files_view_stop_loading (NautilusFilesView *view) +{ + NautilusFilesViewPrivate *priv; + + g_return_if_fail (NAUTILUS_IS_FILES_VIEW (view)); + + priv = nautilus_files_view_get_instance_private (view); + + unschedule_display_of_pending_files (view); + reset_update_interval (view); + + /* Free extra undisplayed files */ + g_list_free_full (priv->new_added_files, file_and_directory_free); + priv->new_added_files = NULL; + + g_list_free_full (priv->new_changed_files, file_and_directory_free); + priv->new_changed_files = NULL; + + g_hash_table_remove_all (priv->non_ready_files); + + g_list_free_full (priv->old_added_files, file_and_directory_free); + priv->old_added_files = NULL; + + g_list_free_full (priv->old_changed_files, file_and_directory_free); + priv->old_changed_files = NULL; + + g_list_free_full (priv->pending_selection, g_object_unref); + priv->pending_selection = NULL; + + done_loading (view, FALSE); + + disconnect_model_handlers (view); +} + +gboolean +nautilus_files_view_is_editable (NautilusFilesView *view) +{ + NautilusDirectory *directory; + + directory = nautilus_files_view_get_model (view); + + if (directory != NULL) + { + return nautilus_directory_is_editable (directory); + } + + return TRUE; +} + +static gboolean +nautilus_files_view_is_read_only (NautilusFilesView *view) +{ + NautilusFile *file; + + if (!nautilus_files_view_is_editable (view)) + { + return TRUE; + } + + file = nautilus_files_view_get_directory_as_file (view); + if (file != NULL) + { + return !nautilus_file_can_write (file); + } + return FALSE; +} + +/** + * nautilus_files_view_should_show_file + * + * Returns whether or not this file should be displayed based on + * current filtering options. + */ +gboolean +nautilus_files_view_should_show_file (NautilusFilesView *view, + NautilusFile *file) +{ + NautilusFilesViewPrivate *priv; + + priv = nautilus_files_view_get_instance_private (view); + + return nautilus_file_should_show (file, + priv->show_hidden_files); +} + +void +nautilus_files_view_ignore_hidden_file_preferences (NautilusFilesView *view) +{ + NautilusFilesViewPrivate *priv; + + priv = nautilus_files_view_get_instance_private (view); + + g_return_if_fail (priv->model == NULL); + + if (priv->ignore_hidden_file_preferences) + { + return; + } + + priv->show_hidden_files = FALSE; + priv->ignore_hidden_file_preferences = TRUE; +} + +char * +nautilus_files_view_get_uri (NautilusFilesView *view) +{ + NautilusFilesViewPrivate *priv; + + g_return_val_if_fail (NAUTILUS_IS_FILES_VIEW (view), NULL); + + priv = nautilus_files_view_get_instance_private (view); + + if (priv->model == NULL) + { + return NULL; + } + return nautilus_directory_get_uri (priv->model); +} + +void +nautilus_files_view_move_copy_items (NautilusFilesView *view, + const GList *item_uris, + const char *target_uri, + int copy_action) +{ + NautilusFile *target_file; + + target_file = nautilus_file_get_existing_by_uri (target_uri); + if (copy_action == GDK_ACTION_COPY && + nautilus_is_file_roller_installed () && + target_file != NULL && + nautilus_file_is_archive (target_file)) + { + char *command, *quoted_uri, *tmp; + const GList *l; + GdkDisplay *display; + + /* Handle dropping onto a file-roller archiver file, instead of starting a move/copy */ + + nautilus_file_unref (target_file); + + quoted_uri = g_shell_quote (target_uri); + command = g_strconcat ("file-roller -a ", quoted_uri, NULL); + g_free (quoted_uri); + + for (l = item_uris; l != NULL; l = l->next) + { + quoted_uri = g_shell_quote ((char *) l->data); + + tmp = g_strconcat (command, " ", quoted_uri, NULL); + g_free (command); + command = tmp; + + g_free (quoted_uri); + } + + display = gtk_widget_get_display (GTK_WIDGET (view)); + if (display == NULL) + { + display = gdk_display_get_default (); + } + + nautilus_launch_application_from_command (display, command, FALSE, NULL); + g_free (command); + + return; + } + nautilus_file_unref (target_file); + + nautilus_file_operations_copy_move + (item_uris, + target_uri, copy_action, GTK_WIDGET (view), + NULL, + copy_move_done_callback, pre_copy_move (view)); +} + +static void +nautilus_files_view_trash_state_changed_callback (NautilusTrashMonitor *trash_monitor, + gboolean state, + gpointer callback_data) +{ + NautilusFilesView *view; + + view = (NautilusFilesView *) callback_data; + g_assert (NAUTILUS_IS_FILES_VIEW (view)); + + schedule_update_context_menus (view); +} + +static void +nautilus_files_view_get_property (GObject *object, + guint prop_id, + GValue *value, + GParamSpec *pspec) +{ + NautilusFilesView *view = NAUTILUS_FILES_VIEW (object); + NautilusFilesViewPrivate *priv = nautilus_files_view_get_instance_private (view); + + switch (prop_id) + { + case PROP_LOADING: + { + g_value_set_boolean (value, nautilus_view_is_loading (NAUTILUS_VIEW (view))); + } + break; + + case PROP_SEARCHING: + { + g_value_set_boolean (value, nautilus_view_is_searching (NAUTILUS_VIEW (view))); + } + break; + + case PROP_LOCATION: + { + g_value_set_object (value, nautilus_view_get_location (NAUTILUS_VIEW (view))); + } + break; + + case PROP_SELECTION: + { + g_value_set_pointer (value, nautilus_view_get_selection (NAUTILUS_VIEW (view))); + } + break; + + case PROP_SEARCH_QUERY: + { + g_value_set_object (value, priv->search_query); + } + break; + + case PROP_EXTENSIONS_BACKGROUND_MENU: + { + g_value_set_object (value, + real_get_extensions_background_menu (NAUTILUS_VIEW (view))); + } + break; + + case PROP_TEMPLATES_MENU: + { + g_value_set_object (value, + real_get_templates_menu (NAUTILUS_VIEW (view))); + } + break; + + default: + { + g_assert_not_reached (); + } + break; + } +} + +static void +nautilus_files_view_set_property (GObject *object, + guint prop_id, + const GValue *value, + GParamSpec *pspec) +{ + NautilusFilesView *directory_view; + NautilusFilesViewPrivate *priv; + NautilusWindowSlot *slot; + + directory_view = NAUTILUS_FILES_VIEW (object); + priv = nautilus_files_view_get_instance_private (directory_view); + + switch (prop_id) + { + case PROP_WINDOW_SLOT: + { + g_assert (priv->slot == NULL); + + slot = NAUTILUS_WINDOW_SLOT (g_value_get_object (value)); + priv->slot = slot; + + g_signal_connect_object (priv->slot, + "notify::active", G_CALLBACK (slot_active_changed), + directory_view, 0); + } + break; + + case PROP_SUPPORTS_ZOOMING: + { + priv->supports_zooming = g_value_get_boolean (value); + } + break; + + case PROP_LOCATION: + { + nautilus_view_set_location (NAUTILUS_VIEW (directory_view), g_value_get_object (value)); + } + break; + + case PROP_SEARCH_QUERY: + { + nautilus_view_set_search_query (NAUTILUS_VIEW (directory_view), g_value_get_object (value)); + } + break; + + case PROP_SELECTION: + { + nautilus_view_set_selection (NAUTILUS_VIEW (directory_view), g_value_get_pointer (value)); + } + break; + + case PROP_EXTENSIONS_BACKGROUND_MENU: + { + real_set_extensions_background_menu (NAUTILUS_VIEW (directory_view), + g_value_get_object (value)); + } + break; + + case PROP_TEMPLATES_MENU: + { + real_set_templates_menu (NAUTILUS_VIEW (directory_view), + g_value_get_object (value)); + } + break; + + default: + { + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); + } + break; + } +} + +/* handle Ctrl+Scroll, which will cause a zoom-in/out */ +static gboolean +on_scroll (GtkEventControllerScroll *scroll, + gdouble dx, + gdouble dy, + gpointer user_data) +{ + NautilusFilesView *directory_view; + GdkModifierType state; + + directory_view = NAUTILUS_FILES_VIEW (user_data); + + state = gtk_event_controller_get_current_event_state (GTK_EVENT_CONTROLLER (scroll)); + if (state & GDK_CONTROL_MASK) + { + if (dy <= -1) + { + /* Zoom In */ + nautilus_files_view_bump_zoom_level (directory_view, 1); + return GDK_EVENT_STOP; + } + else if (dy >= 1) + { + /* Zoom Out */ + nautilus_files_view_bump_zoom_level (directory_view, -1); + return GDK_EVENT_STOP; + } + } + + return GDK_EVENT_PROPAGATE; +} + +static void +on_scroll_begin (GtkEventControllerScroll *scroll, + gpointer user_data) +{ + GdkModifierType state; + + state = gtk_event_controller_get_current_event_state (GTK_EVENT_CONTROLLER (scroll)); + if (state & GDK_CONTROL_MASK) + { + gtk_event_controller_scroll_set_flags (scroll, + GTK_EVENT_CONTROLLER_SCROLL_VERTICAL | + GTK_EVENT_CONTROLLER_SCROLL_DISCRETE); + } +} + +static void +on_scroll_end (GtkEventControllerScroll *scroll, + gpointer user_data) +{ + gtk_event_controller_scroll_set_flags (scroll, GTK_EVENT_CONTROLLER_SCROLL_VERTICAL); +} + +static void +on_parent_changed (GObject *object, + GParamSpec *pspec, + gpointer user_data) +{ + GtkWidget *widget; + NautilusWindow *window; + NautilusFilesView *view; + NautilusFilesViewPrivate *priv; + GtkWidget *parent; + + widget = GTK_WIDGET (object); + view = NAUTILUS_FILES_VIEW (object); + priv = nautilus_files_view_get_instance_private (view); + + parent = gtk_widget_get_parent (widget); + window = nautilus_files_view_get_window (view); + + if (parent != NULL) + { + if (priv->slot == nautilus_window_get_active_slot (window)) + { + priv->active = TRUE; + gtk_widget_insert_action_group (GTK_WIDGET (nautilus_files_view_get_window (view)), + "view", + G_ACTION_GROUP (priv->view_action_group)); + } + } + else + { + remove_update_context_menus_timeout_callback (view); + /* Only remove the action group if this is still the active view. + * Otherwise we might be removing an action group set by a different + * view i.e. if slot_active_changed() is called before this one. + */ + if (priv->active) + { + gtk_widget_insert_action_group (GTK_WIDGET (nautilus_files_view_get_window (view)), + "view", + NULL); + } + } +} + +static NautilusQuery * +nautilus_files_view_get_search_query (NautilusView *view) +{ + NautilusFilesViewPrivate *priv; + + priv = nautilus_files_view_get_instance_private (NAUTILUS_FILES_VIEW (view)); + + return priv->search_query; +} + +static void +set_search_query_internal (NautilusFilesView *files_view, + NautilusQuery *query, + NautilusDirectory *base_model) +{ + GFile *location; + NautilusFilesViewPrivate *priv; + + location = NULL; + priv = nautilus_files_view_get_instance_private (files_view); + + g_set_object (&priv->search_query, query); + g_object_notify (G_OBJECT (files_view), "search-query"); + + if (!nautilus_query_is_empty (query)) + { + if (nautilus_view_is_searching (NAUTILUS_VIEW (files_view))) + { + /* + * Reuse the search directory and reload it. + */ + nautilus_search_directory_set_query (NAUTILUS_SEARCH_DIRECTORY (priv->model), query); + /* It's important to use load_directory instead of set_location, + * since the location is already correct, however we need + * to reload the directory with the new query set. But + * set_location has a check for wheter the location is a + * search directory, so setting the location to a search + * directory when is already serching will enter a loop. + */ + load_directory (files_view, priv->model); + } + else + { + NautilusDirectory *directory; + gchar *uri; + + uri = nautilus_search_directory_generate_new_uri (); + location = g_file_new_for_uri (uri); + + directory = nautilus_directory_get (location); + g_assert (NAUTILUS_IS_SEARCH_DIRECTORY (directory)); + nautilus_search_directory_set_base_model (NAUTILUS_SEARCH_DIRECTORY (directory), base_model); + nautilus_search_directory_set_query (NAUTILUS_SEARCH_DIRECTORY (directory), query); + + load_directory (files_view, directory); + + g_object_notify (G_OBJECT (files_view), "searching"); + + nautilus_directory_unref (directory); + g_free (uri); + } + } + else + { + if (nautilus_view_is_searching (NAUTILUS_VIEW (files_view))) + { + location = nautilus_directory_get_location (base_model); + + nautilus_view_set_location (NAUTILUS_VIEW (files_view), location); + } + } + g_clear_object (&location); +} + +static void +nautilus_files_view_set_search_query (NautilusView *view, + NautilusQuery *query) +{ + NautilusDirectory *base_model; + NautilusFilesView *files_view; + NautilusFilesViewPrivate *priv; + + files_view = NAUTILUS_FILES_VIEW (view); + priv = nautilus_files_view_get_instance_private (files_view); + + if (nautilus_view_is_searching (view)) + { + base_model = nautilus_search_directory_get_base_model (NAUTILUS_SEARCH_DIRECTORY (priv->model)); + } + else + { + base_model = priv->model; + } + + set_search_query_internal (NAUTILUS_FILES_VIEW (view), query, base_model); +} + +static GFile * +nautilus_files_view_get_location (NautilusView *view) +{ + NautilusFilesViewPrivate *priv; + NautilusFilesView *files_view; + + files_view = NAUTILUS_FILES_VIEW (view); + priv = nautilus_files_view_get_instance_private (files_view); + + return priv->location; +} + +static gboolean +nautilus_files_view_is_loading (NautilusView *view) +{ + NautilusFilesViewPrivate *priv; + NautilusFilesView *files_view; + + files_view = NAUTILUS_FILES_VIEW (view); + priv = nautilus_files_view_get_instance_private (files_view); + + return priv->loading; +} + +static void +nautilus_files_view_iface_init (NautilusViewInterface *iface) +{ + iface->get_location = nautilus_files_view_get_location; + iface->set_location = nautilus_files_view_set_location; + iface->get_selection = nautilus_files_view_get_selection; + iface->set_selection = nautilus_files_view_set_selection; + iface->get_search_query = nautilus_files_view_get_search_query; + iface->set_search_query = nautilus_files_view_set_search_query; + iface->get_toolbar_menu_sections = nautilus_files_view_get_toolbar_menu_sections; + iface->is_searching = nautilus_files_view_is_searching; + iface->is_loading = nautilus_files_view_is_loading; + iface->get_view_id = nautilus_files_view_get_view_id; + iface->get_templates_menu = nautilus_files_view_get_templates_menu; + iface->set_templates_menu = nautilus_files_view_set_templates_menu; + iface->get_extensions_background_menu = nautilus_files_view_get_extensions_background_menu; + iface->set_extensions_background_menu = nautilus_files_view_set_extensions_background_menu; +} + +static void +nautilus_files_view_class_init (NautilusFilesViewClass *klass) +{ + GObjectClass *oclass; + GtkWidgetClass *widget_class; + + widget_class = GTK_WIDGET_CLASS (klass); + oclass = G_OBJECT_CLASS (klass); + + oclass->dispose = nautilus_files_view_dispose; + oclass->finalize = nautilus_files_view_finalize; + oclass->get_property = nautilus_files_view_get_property; + oclass->set_property = nautilus_files_view_set_property; + + widget_class->focus = nautilus_files_view_focus; + widget_class->grab_focus = nautilus_files_view_grab_focus; + + + signals[ADD_FILES] = + g_signal_new ("add-files", + G_TYPE_FROM_CLASS (klass), + G_SIGNAL_RUN_LAST, + G_STRUCT_OFFSET (NautilusFilesViewClass, add_files), + NULL, NULL, + g_cclosure_marshal_generic, + G_TYPE_NONE, 1, G_TYPE_POINTER); + signals[BEGIN_FILE_CHANGES] = + g_signal_new ("begin-file-changes", + G_TYPE_FROM_CLASS (klass), + G_SIGNAL_RUN_LAST, + G_STRUCT_OFFSET (NautilusFilesViewClass, begin_file_changes), + NULL, NULL, + g_cclosure_marshal_VOID__VOID, + G_TYPE_NONE, 0); + signals[BEGIN_LOADING] = + g_signal_new ("begin-loading", + G_TYPE_FROM_CLASS (klass), + G_SIGNAL_RUN_LAST, + G_STRUCT_OFFSET (NautilusFilesViewClass, begin_loading), + NULL, NULL, + g_cclosure_marshal_VOID__VOID, + G_TYPE_NONE, 0); + signals[CLEAR] = + g_signal_new ("clear", + G_TYPE_FROM_CLASS (klass), + G_SIGNAL_RUN_LAST, + G_STRUCT_OFFSET (NautilusFilesViewClass, clear), + NULL, NULL, + g_cclosure_marshal_VOID__VOID, + G_TYPE_NONE, 0); + signals[END_FILE_CHANGES] = + g_signal_new ("end-file-changes", + G_TYPE_FROM_CLASS (klass), + G_SIGNAL_RUN_LAST, + G_STRUCT_OFFSET (NautilusFilesViewClass, end_file_changes), + NULL, NULL, + g_cclosure_marshal_VOID__VOID, + G_TYPE_NONE, 0); + signals[END_LOADING] = + g_signal_new ("end-loading", + G_TYPE_FROM_CLASS (klass), + G_SIGNAL_RUN_LAST, + G_STRUCT_OFFSET (NautilusFilesViewClass, end_loading), + NULL, NULL, + g_cclosure_marshal_VOID__BOOLEAN, + G_TYPE_NONE, 1, G_TYPE_BOOLEAN); + signals[FILE_CHANGED] = + g_signal_new ("file-changed", + G_TYPE_FROM_CLASS (klass), + G_SIGNAL_RUN_LAST, + G_STRUCT_OFFSET (NautilusFilesViewClass, file_changed), + NULL, NULL, + g_cclosure_marshal_generic, + G_TYPE_NONE, 2, NAUTILUS_TYPE_FILE, NAUTILUS_TYPE_DIRECTORY); + signals[REMOVE_FILE] = + g_signal_new ("remove-file", + G_TYPE_FROM_CLASS (klass), + G_SIGNAL_RUN_LAST, + G_STRUCT_OFFSET (NautilusFilesViewClass, remove_file), + NULL, NULL, + g_cclosure_marshal_generic, + G_TYPE_NONE, 2, NAUTILUS_TYPE_FILE, NAUTILUS_TYPE_DIRECTORY); + signals[SELECTION_CHANGED] = + g_signal_new ("selection-changed", + G_TYPE_FROM_CLASS (klass), + G_SIGNAL_RUN_LAST, + 0, + NULL, NULL, + g_cclosure_marshal_VOID__VOID, + G_TYPE_NONE, 0); + + klass->get_backing_uri = real_get_backing_uri; + klass->get_window = nautilus_files_view_get_window; + klass->update_context_menus = real_update_context_menus; + klass->update_actions_state = real_update_actions_state; + klass->check_empty_states = real_check_empty_states; + + g_object_class_install_property ( + oclass, + PROP_WINDOW_SLOT, + g_param_spec_object ("window-slot", + "Window Slot", + "The parent window slot reference", + NAUTILUS_TYPE_WINDOW_SLOT, + G_PARAM_WRITABLE | + G_PARAM_CONSTRUCT_ONLY)); + g_object_class_install_property ( + oclass, + PROP_SUPPORTS_ZOOMING, + g_param_spec_boolean ("supports-zooming", + "Supports zooming", + "Whether the view supports zooming", + TRUE, + G_PARAM_WRITABLE | G_PARAM_CONSTRUCT_ONLY | + G_PARAM_STATIC_STRINGS)); + + g_object_class_override_property (oclass, PROP_LOADING, "loading"); + g_object_class_override_property (oclass, PROP_SEARCHING, "searching"); + g_object_class_override_property (oclass, PROP_LOCATION, "location"); + g_object_class_override_property (oclass, PROP_SELECTION, "selection"); + g_object_class_override_property (oclass, PROP_SEARCH_QUERY, "search-query"); + g_object_class_override_property (oclass, PROP_EXTENSIONS_BACKGROUND_MENU, "extensions-background-menu"); + g_object_class_override_property (oclass, PROP_TEMPLATES_MENU, "templates-menu"); + + gtk_widget_class_set_template_from_resource (widget_class, + "/org/gnome/nautilus/ui/nautilus-files-view.ui"); + + gtk_widget_class_bind_template_child_private (widget_class, NautilusFilesView, overlay); + gtk_widget_class_bind_template_child_private (widget_class, NautilusFilesView, stack); + gtk_widget_class_bind_template_child_private (widget_class, NautilusFilesView, empty_view_page); + gtk_widget_class_bind_template_child_private (widget_class, NautilusFilesView, scrolled_window); + gtk_widget_class_bind_template_child_private (widget_class, NautilusFilesView, floating_bar); + + /* See also the global accelerators in init() in addition to all the local + * ones defined below. + */ + + /* Only one delete action is enabled at a time, so we can just activate several + * delete or trash actions with the same shortcut without worrying: only the + * enabled one will be activated. + */ + gtk_widget_class_add_binding_action (widget_class, GDK_KEY_KP_Delete, GDK_SHIFT_MASK, "view.delete-permanently-shortcut", NULL); + gtk_widget_class_add_binding_action (widget_class, GDK_KEY_Delete, GDK_SHIFT_MASK, "view.delete-permanently-shortcut", NULL); + gtk_widget_class_add_binding_action (widget_class, GDK_KEY_KP_Delete, GDK_SHIFT_MASK, "view.permanent-delete-permanently-menu-item", NULL); + gtk_widget_class_add_binding_action (widget_class, GDK_KEY_Delete, GDK_SHIFT_MASK, "view.permanent-delete-permanently-menu-item", NULL); + gtk_widget_class_add_binding_action (widget_class, GDK_KEY_KP_Delete, 0, "view.move-to-trash", NULL); + gtk_widget_class_add_binding_action (widget_class, GDK_KEY_Delete, 0, "view.move-to-trash", NULL); + gtk_widget_class_add_binding_action (widget_class, GDK_KEY_KP_Delete, 0, "view.delete-from-trash", NULL); + gtk_widget_class_add_binding_action (widget_class, GDK_KEY_Delete, 0, "view.delete-from-trash", NULL); + /* When trash is not available, allow the "Delete" keys to delete permanently, that is, when + * the menu item is available, since we never make both the trash and delete-permanently-menu-item + * actions active. + */ + gtk_widget_class_add_binding_action (widget_class, GDK_KEY_KP_Delete, 0, "view.delete-permanently-menu-item", NULL); + gtk_widget_class_add_binding_action (widget_class, GDK_KEY_Delete, 0, "view.delete-permanently-menu-item", NULL); + + gtk_widget_class_add_binding_action (widget_class, GDK_KEY_F2, 0, "view.rename", NULL); + gtk_widget_class_add_binding_action (widget_class, GDK_KEY_Menu, 0, "view.popup-menu", NULL); + gtk_widget_class_add_binding_action (widget_class, GDK_KEY_F10, GDK_SHIFT_MASK, "view.popup-menu", NULL); + gtk_widget_class_add_binding_action (widget_class, GDK_KEY_o, GDK_CONTROL_MASK, "view.open-with-default-application", NULL); + gtk_widget_class_add_binding_action (widget_class, GDK_KEY_Down, GDK_ALT_MASK, "view.open-with-default-application", NULL); + /* This is not necessary per-se, because it's the default activation + * keybinding. But in order for it to appear in the context menu as a + * keyboard shortcut, we need to bind it to the menu item action here. */ + gtk_widget_class_add_binding_action (widget_class, GDK_KEY_Return, 0, "view.open-with-default-application", NULL); + gtk_widget_class_add_binding_action (widget_class, GDK_KEY_i, GDK_CONTROL_MASK, "view.properties", NULL); + gtk_widget_class_add_binding_action (widget_class, GDK_KEY_Return, GDK_ALT_MASK, "view.properties", NULL); + gtk_widget_class_add_binding_action (widget_class, GDK_KEY_a, GDK_CONTROL_MASK, "view.select-all", NULL); + gtk_widget_class_add_binding_action (widget_class, GDK_KEY_i, GDK_CONTROL_MASK | GDK_SHIFT_MASK, "view.invert-selection", NULL); + gtk_widget_class_add_binding_action (widget_class, GDK_KEY_m, GDK_CONTROL_MASK, "view.create-link", NULL); + gtk_widget_class_add_binding_action (widget_class, GDK_KEY_m, GDK_CONTROL_MASK, "view.create-link-shortcut", NULL); + gtk_widget_class_add_binding_action (widget_class, GDK_KEY_m, GDK_CONTROL_MASK | GDK_SHIFT_MASK, "view.create-link-in-place", NULL); + gtk_widget_class_add_binding_action (widget_class, GDK_KEY_m, GDK_CONTROL_MASK | GDK_SHIFT_MASK, "view.create-link-in-place-shortcut", NULL); + gtk_widget_class_add_binding_action (widget_class, GDK_KEY_Return, GDK_CONTROL_MASK, "view.open-item-new-tab", NULL); + gtk_widget_class_add_binding_action (widget_class, GDK_KEY_Return, GDK_SHIFT_MASK, "view.open-item-new-window", NULL); + gtk_widget_class_add_binding_action (widget_class, GDK_KEY_o, GDK_CONTROL_MASK | GDK_ALT_MASK, "view.open-item-location", NULL); + gtk_widget_class_add_binding_action (widget_class, GDK_KEY_c, GDK_CONTROL_MASK, "view.copy", NULL); + gtk_widget_class_add_binding_action (widget_class, GDK_KEY_x, GDK_CONTROL_MASK, "view.cut", NULL); +} + +static void +nautilus_files_view_init (NautilusFilesView *view) +{ + NautilusFilesViewPrivate *priv; + GtkBuilder *builder; + NautilusDirectory *scripts_directory; + NautilusDirectory *templates_directory; + GtkEventController *controller; + GtkShortcut *shortcut; + gchar *templates_uri; + GdkClipboard *clipboard; + GApplication *app; + const gchar *zoom_in_accels[] = + { + "equal", + "plus", + "KP_Add", + "ZoomIn", + NULL + }; + const gchar *zoom_out_accels[] = + { + "minus", + "KP_Subtract", + "ZoomOut", + NULL + }; + const gchar *zoom_standard_accels[] = + { + "0", + "KP_0", + NULL + }; + + nautilus_profile_start (NULL); + + priv = nautilus_files_view_get_instance_private (view); + + /* Toolbar menu */ + builder = gtk_builder_new_from_resource ("/org/gnome/nautilus/ui/nautilus-toolbar-view-menu.ui"); + priv->toolbar_menu_sections = g_new0 (NautilusToolbarMenuSections, 1); + priv->toolbar_menu_sections->sort_section = G_MENU_MODEL (g_object_ref (gtk_builder_get_object (builder, "sort_section"))); + + g_signal_connect (view, + "end-file-changes", + G_CALLBACK (on_end_file_changes), + view); + g_signal_connect (view, + "notify::selection", + G_CALLBACK (nautilus_files_view_preview_update), + view); + g_signal_connect (view, + "notify::parent", + G_CALLBACK (on_parent_changed), + NULL); + + g_object_unref (builder); + + g_type_ensure (NAUTILUS_TYPE_FLOATING_BAR); + gtk_widget_init_template (GTK_WIDGET (view)); + + controller = gtk_event_controller_scroll_new (GTK_EVENT_CONTROLLER_SCROLL_VERTICAL); + gtk_widget_add_controller (priv->scrolled_window, controller); + gtk_event_controller_set_propagation_phase (controller, GTK_PHASE_CAPTURE); + g_signal_connect (controller, "scroll", G_CALLBACK (on_scroll), view); + g_signal_connect (controller, "scroll-begin", G_CALLBACK (on_scroll_begin), view); + g_signal_connect (controller, "scroll-end", G_CALLBACK (on_scroll_end), view); + + g_signal_connect (priv->floating_bar, + "stop", + G_CALLBACK (floating_bar_stop_cb), + view); + + priv->non_ready_files = + g_hash_table_new_full (file_and_directory_hash, + file_and_directory_equal, + file_and_directory_free, + NULL); + + priv->pending_reveal = g_hash_table_new (NULL, NULL); + + if (set_up_scripts_directory_global ()) + { + scripts_directory = nautilus_directory_get_by_uri (scripts_directory_uri); + add_directory_to_scripts_directory_list (view, scripts_directory); + nautilus_directory_unref (scripts_directory); + } + else + { + g_warning ("Ignoring scripts directory, it may be a broken link\n"); + } + + if (nautilus_should_use_templates_directory ()) + { + templates_uri = nautilus_get_templates_directory_uri (); + templates_directory = nautilus_directory_get_by_uri (templates_uri); + g_free (templates_uri); + add_directory_to_templates_directory_list (view, templates_directory); + nautilus_directory_unref (templates_directory); + } + update_templates_directory (view); + + priv->sort_directories_first = + g_settings_get_boolean (gtk_filechooser_preferences, NAUTILUS_PREFERENCES_SORT_DIRECTORIES_FIRST); + priv->show_hidden_files = + g_settings_get_boolean (gtk_filechooser_preferences, NAUTILUS_PREFERENCES_SHOW_HIDDEN_FILES); + + g_signal_connect_object (nautilus_trash_monitor_get (), "trash-state-changed", + G_CALLBACK (nautilus_files_view_trash_state_changed_callback), view, 0); + + /* React to clipboard changes */ + clipboard = gdk_display_get_clipboard (gdk_display_get_default ()); + g_signal_connect (clipboard, "changed", + G_CALLBACK (on_clipboard_owner_changed), view); + + /* Register to menu provider extension signal managing menu updates */ + g_signal_connect_object (nautilus_signaller_get_current (), "popup-menu-changed", + G_CALLBACK (schedule_update_context_menus), view, G_CONNECT_SWAPPED); + + gtk_widget_show (GTK_WIDGET (view)); + + g_signal_connect_swapped (nautilus_preferences, + "changed::" NAUTILUS_PREFERENCES_CLICK_POLICY, + G_CALLBACK (click_policy_changed_callback), + view); + g_signal_connect_swapped (gtk_filechooser_preferences, + "changed::" NAUTILUS_PREFERENCES_SORT_DIRECTORIES_FIRST, + G_CALLBACK (sort_directories_first_changed_callback), view); + g_signal_connect_swapped (gtk_filechooser_preferences, + "changed::" NAUTILUS_PREFERENCES_SHOW_HIDDEN_FILES, + G_CALLBACK (show_hidden_files_changed_callback), view); + g_signal_connect_swapped (gnome_lockdown_preferences, + "changed::" NAUTILUS_PREFERENCES_LOCKDOWN_COMMAND_LINE, + G_CALLBACK (schedule_update_context_menus), view); + + priv->in_destruction = FALSE; + + priv->view_action_group = G_ACTION_GROUP (g_simple_action_group_new ()); + g_action_map_add_action_entries (G_ACTION_MAP (priv->view_action_group), + view_entries, + G_N_ELEMENTS (view_entries), + view); + gtk_widget_insert_action_group (GTK_WIDGET (view), + "view", + G_ACTION_GROUP (priv->view_action_group)); + app = g_application_get_default (); + + /* NOTE: Please do not add any key here that could interfere with + * the rest of the app's use of those keys. Some example of keys set here + * that broke keynav include Enter/Return, Menu, F2 and Delete keys. + * The accelerators below are set on the whole app level for the sole purpose + * of making it more convenient when you don't have the focus exactly on the + * files view, but some keys are used in a contextual way, and those should + * should be added in nautilus_files_view_class_init() above instead of a + * global accelerator, unless it really makes sense to have them globally + * (e.g. Zoom in/out shortcuts). + */ + nautilus_application_set_accelerators (app, "view.zoom-in", zoom_in_accels); + nautilus_application_set_accelerators (app, "view.zoom-out", zoom_out_accels); + nautilus_application_set_accelerator (app, "view.show-hidden-files", "h"); + /* Despite putting copy/cut at the widget scope instead of the global one, + * we're putting paste globally so that it's easy to switch between apps + * with e.g. Alt+Tab and paste directly the copied file without having to + * make sure the focus is on the files view. + */ + nautilus_application_set_accelerator (app, "view.paste_accel", "v"); + nautilus_application_set_accelerator (app, "view.new-folder", "n"); + nautilus_application_set_accelerator (app, "view.select-pattern", "s"); + nautilus_application_set_accelerators (app, "view.zoom-standard", zoom_standard_accels); + + /* This one should have been a keybinding, because it should trigger only + * when the view is focused. Unfortunately, children can override bindings, + * and such is the case of GtkListItemWidget which binds the spacebar to its + * `|listitem.select` action. + * + * So, we make it a local shortcut (like keybindings are), but using the + * capture phase instead, to trigger it first (keybindings use bubble phase). + */ + shortcut = gtk_shortcut_new (gtk_keyval_trigger_new (GDK_KEY_space, 0), + gtk_named_action_new ("view.preview-selection")); + + controller = gtk_shortcut_controller_new (); + gtk_widget_add_controller (GTK_WIDGET (view), controller); + /* By default, :scope is GTK_SHORTCUT_SCOPE_LOCAL, so no need to set it. */ + gtk_event_controller_set_propagation_phase (controller, GTK_PHASE_CAPTURE); + gtk_shortcut_controller_add_shortcut (GTK_SHORTCUT_CONTROLLER (controller), shortcut); + + priv->starred_cancellable = g_cancellable_new (); + + priv->rename_file_controller = nautilus_rename_file_popover_controller_new (GTK_WIDGET (view)); + + nautilus_profile_end (NULL); +} + +NautilusFilesView * +nautilus_files_view_new (guint id, + NautilusWindowSlot *slot) +{ + NautilusFilesView *view = NULL; + + switch (id) + { + case NAUTILUS_VIEW_GRID_ID: + { + view = NAUTILUS_FILES_VIEW (nautilus_grid_view_new (slot)); + } + break; + + case NAUTILUS_VIEW_LIST_ID: + { + view = NAUTILUS_FILES_VIEW (nautilus_list_view_new (slot)); + } + break; + + default: + { + g_critical ("Unknown view type ID: %d. Falling back to list.", id); + view = NAUTILUS_FILES_VIEW (nautilus_list_view_new (slot)); + } + } + + if (view == NULL) + { + g_assert_not_reached (); + } + else if (g_object_is_floating (view)) + { + g_object_ref_sink (view); + } + + return view; +} diff --git a/src/nautilus-files-view.h b/src/nautilus-files-view.h new file mode 100644 index 0000000..6806a2b --- /dev/null +++ b/src/nautilus-files-view.h @@ -0,0 +1,315 @@ +/* nautilus-view.h + * + * Copyright (C) 1999, 2000 Free Software Foundaton + * Copyright (C) 2000, 2001 Eazel, Inc. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License as + * published by the Free Software Foundation; either version 2 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 warranty of + * MERCHANTABILITY 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 . + * + * Authors: Ettore Perazzoli + * Darin Adler + * John Sullivan + * Pavel Cisler + */ + +#pragma once + +#include +#include + +#include "nautilus-directory.h" +#include "nautilus-file.h" + +#include "nautilus-window.h" +#include "nautilus-view.h" +#include "nautilus-window-slot.h" + +G_BEGIN_DECLS + +#define NAUTILUS_TYPE_FILES_VIEW nautilus_files_view_get_type() + +G_DECLARE_DERIVABLE_TYPE (NautilusFilesView, nautilus_files_view, NAUTILUS, FILES_VIEW, AdwBin) + +struct _NautilusFilesViewClass { + AdwBinClass parent_class; + + /* The 'clear' signal is emitted to empty the view of its contents. + * It must be replaced by each subclass. + */ + void (* clear) (NautilusFilesView *view); + + /* The 'begin_file_changes' signal is emitted before a set of files + * are added to the view. It can be replaced by a subclass to do any + * necessary preparation for a set of new files. The default + * implementation does nothing. + */ + void (* begin_file_changes) (NautilusFilesView *view); + + /* The 'add_files' signal is emitted to add a set of files to the view. + * It must be replaced by each subclass. + */ + void (* add_files) (NautilusFilesView *view, + GList *files); + void (* remove_file) (NautilusFilesView *view, + NautilusFile *file, + NautilusDirectory *directory); + + /* The 'file_changed' signal is emitted to signal a change in a file, + * including the file being removed. + * It must be replaced by each subclass. + */ + void (* file_changed) (NautilusFilesView *view, + NautilusFile *file, + NautilusDirectory *directory); + + /* The 'end_file_changes' signal is emitted after a set of files + * are added to the view. It can be replaced by a subclass to do any + * necessary cleanup (typically, cleanup for code in begin_file_changes). + * The default implementation does nothing. + */ + void (* end_file_changes) (NautilusFilesView *view); + + /* The 'begin_loading' signal is emitted before any of the contents + * of a directory are added to the view. It can be replaced by a + * subclass to do any necessary preparation to start dealing with a + * new directory. The default implementation does nothing. + */ + void (* begin_loading) (NautilusFilesView *view); + + /* The 'end_loading' signal is emitted after all of the contents + * of a directory are added to the view. It can be replaced by a + * subclass to do any necessary clean-up. The default implementation + * does nothing. + * + * If all_files_seen is true, the handler may assume that + * no load error ocurred, and all files of the underlying + * directory were loaded. + * + * Otherwise, end_loading was emitted due to cancellation, + * which usually means that not all files are available. + */ + void (* end_loading) (NautilusFilesView *view, + gboolean all_files_seen); + + /* Function pointers that don't have corresponding signals */ + + /* get_backing uri is a function pointer for subclasses to + * override. Subclasses may replace it with a function that + * returns the URI for the location where to create new folders, + * files, links and paste the clipboard to. + */ + + char * (* get_backing_uri) (NautilusFilesView *view); + + /* get_selection is not a signal; it is just a function pointer for + * subclasses to replace (override). Subclasses must replace it + * with a function that returns a newly-allocated GList of + * NautilusFile pointers. + */ + GList * (* get_selection) (NautilusFilesView *view); + + /* get_selection_for_file_transfer is a function pointer for + * subclasses to replace (override). Subclasses must replace it + * with a function that returns a newly-allocated GList of + * NautilusFile pointers. The difference from get_selection is + * that any files in the selection that also has a parent folder + * in the selection is not included. + */ + GList * (* get_selection_for_file_transfer)(NautilusFilesView *view); + + /* select_all is a function pointer that subclasses must override to + * select all of the items in the view */ + void (* select_all) (NautilusFilesView *view); + + /* select_first is a function pointer that subclasses must override to + * select the first item in the view */ + void (* select_first) (NautilusFilesView *view); + + /* set_selection is a function pointer that subclasses must + * override to select the specified items (and unselect all + * others). The argument is a list of NautilusFiles. */ + + void (* set_selection) (NautilusFilesView *view, + GList *selection); + + /* invert_selection is a function pointer that subclasses must + * override to invert selection. */ + + void (* invert_selection) (NautilusFilesView *view); + + /* bump_zoom_level is a function pointer that subclasses must override + * to change the zoom level of an object. */ + void (* bump_zoom_level) (NautilusFilesView *view, + int zoom_increment); + + /* + * restore_default_zoom_level: restores the zoom level to 100% (or to + * whatever is considered the 'standard' zoom level for the view). */ + void (* restore_standard_zoom_level) (NautilusFilesView *view); + + /* can_zoom_in is a function pointer that subclasses must override to + * return whether the view is at maximum size (furthest-in zoom level) */ + gboolean (* can_zoom_in) (NautilusFilesView *view); + + /* can_zoom_out is a function pointer that subclasses must override to + * return whether the view is at minimum size (furthest-out zoom level) */ + gboolean (* can_zoom_out) (NautilusFilesView *view); + + gboolean (*is_zoom_level_default) (NautilusFilesView *view); + + /* reveal_selection is a function pointer that subclasses may + * override to make sure the selected items are sufficiently + * apparent to the user (e.g., scrolled into view). By default, + * this does nothing. + */ + void (* reveal_selection) (NautilusFilesView *view); + + /* update_menus is a function pointer that subclasses can override to + * update the sensitivity or wording of menu items in the menu bar. + * It is called (at least) whenever the selection changes. If overridden, + * subclasses must call parent class's function. + */ + void (* update_context_menus) (NautilusFilesView *view); + + void (* update_actions_state) (NautilusFilesView *view); + + /* is_empty is a function pointer that subclasses must + * override to report whether the view contains any items. + */ + gboolean (* is_empty) (NautilusFilesView *view); + + /* Preference change callbacks, overridden by icon and list views. + * Icon and list views respond by synchronizing to the new preference + * values and forcing an update if appropriate. + */ + void (* click_policy_changed) (NautilusFilesView *view); + void (* sort_directories_first_changed) (NautilusFilesView *view); + + /* Get the id for this view. Its a guint*/ + guint (* get_view_id) (NautilusFilesView *view); + + /* Return the uri of the first visible file */ + char * (* get_first_visible_file) (NautilusFilesView *view); + /* Scroll the view so that the file specified by the uri is at the top + of the view */ + void (* scroll_to_file) (NautilusFilesView *view, + const char *uri); + + NautilusWindow * (*get_window) (NautilusFilesView *view); + + GdkRectangle * (* compute_rename_popover_pointing_to) (NautilusFilesView *view); + + GdkRectangle * (* reveal_for_selection_context_menu) (NautilusFilesView *view); + + /* Use this to show an optional visual feedback when the directory is empty. + * By default it shows a widget overlay on top of the view */ + void (* check_empty_states) (NautilusFilesView *view); + + void (* preview_selection_event) (NautilusFilesView *view, + GtkDirectionType direction); +}; + +NautilusFilesView * nautilus_files_view_new (guint id, + NautilusWindowSlot *slot); + +/* Functions callable from the user interface and elsewhere. */ +NautilusWindowSlot *nautilus_files_view_get_nautilus_window_slot (NautilusFilesView *view); +char * nautilus_files_view_get_uri (NautilusFilesView *view); + +void nautilus_files_view_display_selection_info (NautilusFilesView *view); + +/* Wrappers for signal emitters. These are normally called + * only by NautilusFilesView itself. They have corresponding signals + * that observers might want to connect with. + */ +gboolean nautilus_files_view_get_loading (NautilusFilesView *view); + +/* Hooks for subclasses to call. These are normally called only by + * NautilusFilesView and its subclasses + */ +void nautilus_files_view_activate_files (NautilusFilesView *view, + GList *files, + NautilusOpenFlags flags, + gboolean confirm_multiple); +void nautilus_files_view_activate_file (NautilusFilesView *view, + NautilusFile *file, + NautilusOpenFlags flags); +void nautilus_files_view_notify_selection_changed (NautilusFilesView *view); +NautilusDirectory *nautilus_files_view_get_model (NautilusFilesView *view); +NautilusFile *nautilus_files_view_get_directory_as_file (NautilusFilesView *view); +void nautilus_files_view_pop_up_background_context_menu (NautilusFilesView *view, + gdouble x, + gdouble y); +void nautilus_files_view_pop_up_selection_context_menu (NautilusFilesView *view, + gdouble x, + gdouble y); +gboolean nautilus_files_view_should_show_file (NautilusFilesView *view, + NautilusFile *file); +gboolean nautilus_files_view_should_sort_directories_first (NautilusFilesView *view); +void nautilus_files_view_ignore_hidden_file_preferences (NautilusFilesView *view); + +void nautilus_files_view_add_subdirectory (NautilusFilesView *view, + NautilusDirectory *directory); +void nautilus_files_view_remove_subdirectory (NautilusFilesView *view, + NautilusDirectory *directory); + +gboolean nautilus_files_view_is_editable (NautilusFilesView *view); +NautilusWindow * nautilus_files_view_get_window (NautilusFilesView *view); + +/* file operations */ +char * nautilus_files_view_get_backing_uri (NautilusFilesView *view); +void nautilus_files_view_move_copy_items (NautilusFilesView *view, + const GList *item_uris, + const char *target_uri, + int copy_action); +void nautilus_files_view_new_file_with_initial_contents (NautilusFilesView *view, + const char *parent_uri, + const char *filename, + const char *initial_contents, + int length); + +/* clipboard reading */ +void nautilus_files_view_get_clipboard_async (NautilusFilesView *self, + GAsyncReadyCallback callback, + gpointer callback_data); +NautilusClipboard *nautilus_files_view_get_clipboard_finish (NautilusFilesView *self, + GAsyncResult *result, + GError **error); + +/* selection handling */ +void nautilus_files_view_activate_selection (NautilusFilesView *view); +void nautilus_files_view_preview_selection_event (NautilusFilesView *view, + GtkDirectionType direction); +void nautilus_files_view_stop_loading (NautilusFilesView *view); + +char * nautilus_files_view_get_first_visible_file (NautilusFilesView *view); +void nautilus_files_view_scroll_to_file (NautilusFilesView *view, + const char *uri); +char * nautilus_files_view_get_title (NautilusFilesView *view); +gboolean nautilus_files_view_supports_zooming (NautilusFilesView *view); +void nautilus_files_view_bump_zoom_level (NautilusFilesView *view, + int zoom_increment); +gboolean nautilus_files_view_can_zoom_in (NautilusFilesView *view); +gboolean nautilus_files_view_can_zoom_out (NautilusFilesView *view); + +void nautilus_files_view_update_context_menus (NautilusFilesView *view); +void nautilus_files_view_update_toolbar_menus (NautilusFilesView *view); +void nautilus_files_view_update_actions_state (NautilusFilesView *view); + +void nautilus_files_view_action_show_hidden_files (NautilusFilesView *view, + gboolean show_hidden); + +GActionGroup * nautilus_files_view_get_action_group (NautilusFilesView *view); +GtkWidget* nautilus_files_view_get_content_widget (NautilusFilesView *view); + +G_END_DECLS diff --git a/src/nautilus-floating-bar.c b/src/nautilus-floating-bar.c new file mode 100644 index 0000000..547651f --- /dev/null +++ b/src/nautilus-floating-bar.c @@ -0,0 +1,549 @@ +/* Nautilus - Floating status bar. + * + * Copyright (C) 2011 Red Hat Inc. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Library General Public + * License as published by the Free Software Foundation; either + * version 2 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Library General Public License for more details. + * + * You should have received a copy of the GNU Library General Public + * License along with this library; if not, see . + * + * Authors: Cosimo Cecchi + * + */ + +#include + +#include + +#include "nautilus-floating-bar.h" + +#define HOVER_HIDE_TIMEOUT_INTERVAL 100 + +struct _NautilusFloatingBar +{ + GtkBox parent; + + gchar *primary_label; + gchar *details_label; + + GtkWidget *primary_label_widget; + GtkWidget *details_label_widget; + GtkWidget *spinner; + gboolean show_spinner; + GtkWidget *stop_button; + gboolean show_stop; + gboolean is_interactive; + guint hover_timeout_id; + + GtkEventController *motion_controller; + double pointer_x_in_parent_coordinates; + double pointer_y_in_parent_coordinates; +}; + +enum +{ + PROP_PRIMARY_LABEL = 1, + PROP_DETAILS_LABEL, + PROP_SHOW_SPINNER, + PROP_SHOW_STOP, + NUM_PROPERTIES +}; + +enum +{ + STOP, + NUM_SIGNALS +}; + +static GParamSpec *properties[NUM_PROPERTIES] = { NULL, }; +static guint signals[NUM_SIGNALS] = { 0, }; + +G_DEFINE_TYPE (NautilusFloatingBar, nautilus_floating_bar, + GTK_TYPE_BOX); + +static void +stop_button_clicked_cb (GtkButton *button, + NautilusFloatingBar *self) +{ + g_signal_emit (self, signals[STOP], 0); +} + +static void +nautilus_floating_bar_finalize (GObject *obj) +{ + NautilusFloatingBar *self = NAUTILUS_FLOATING_BAR (obj); + + nautilus_floating_bar_remove_hover_timeout (self); + g_free (self->primary_label); + g_free (self->details_label); + g_clear_object (&self->motion_controller); + + G_OBJECT_CLASS (nautilus_floating_bar_parent_class)->finalize (obj); +} + +static void +nautilus_floating_bar_get_property (GObject *object, + guint property_id, + GValue *value, + GParamSpec *pspec) +{ + NautilusFloatingBar *self = NAUTILUS_FLOATING_BAR (object); + + switch (property_id) + { + case PROP_PRIMARY_LABEL: + { + g_value_set_string (value, self->primary_label); + } + break; + + case PROP_DETAILS_LABEL: + { + g_value_set_string (value, self->details_label); + } + break; + + case PROP_SHOW_SPINNER: + { + g_value_set_boolean (value, self->show_spinner); + } + break; + + case PROP_SHOW_STOP: + { + g_value_set_boolean (value, self->show_stop); + } + break; + + default: + { + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec); + } + break; + } +} + +static void +nautilus_floating_bar_set_property (GObject *object, + guint property_id, + const GValue *value, + GParamSpec *pspec) +{ + NautilusFloatingBar *self = NAUTILUS_FLOATING_BAR (object); + + switch (property_id) + { + case PROP_PRIMARY_LABEL: + { + nautilus_floating_bar_set_primary_label (self, g_value_get_string (value)); + } + break; + + case PROP_DETAILS_LABEL: + { + nautilus_floating_bar_set_details_label (self, g_value_get_string (value)); + } + break; + + case PROP_SHOW_SPINNER: + { + nautilus_floating_bar_set_show_spinner (self, g_value_get_boolean (value)); + } + break; + + case PROP_SHOW_STOP: + { + nautilus_floating_bar_set_show_stop (self, g_value_get_boolean (value)); + } + break; + + default: + { + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec); + } + break; + } +} + +static void +update_labels (NautilusFloatingBar *self) +{ + gboolean primary_visible, details_visible; + + primary_visible = (self->primary_label != NULL) && + (strlen (self->primary_label) > 0); + details_visible = (self->details_label != NULL) && + (strlen (self->details_label) > 0); + + gtk_label_set_text (GTK_LABEL (self->primary_label_widget), + self->primary_label); + gtk_widget_set_visible (self->primary_label_widget, primary_visible); + + gtk_label_set_text (GTK_LABEL (self->details_label_widget), + self->details_label); + gtk_widget_set_visible (self->details_label_widget, details_visible); +} + +void +nautilus_floating_bar_remove_hover_timeout (NautilusFloatingBar *self) +{ + if (self->hover_timeout_id != 0) + { + g_source_remove (self->hover_timeout_id); + self->hover_timeout_id = 0; + } +} + +typedef struct +{ + NautilusFloatingBar *floating_bar; + gint x_down_limit; + gint x_upper_limit; + gint y_down_limit; + gint y_upper_limit; +} CheckPointerData; + +static void +check_pointer_data_free (gpointer data) +{ + g_slice_free (CheckPointerData, data); +} + +static gboolean +check_pointer_timeout (gpointer user_data) +{ + CheckPointerData *data = user_data; + NautilusFloatingBar *self = data->floating_bar; + double pointer_x = self->pointer_x_in_parent_coordinates; + double pointer_y = self->pointer_y_in_parent_coordinates; + + if (pointer_x == -1 || + pointer_y == -1 || + pointer_x < data->x_down_limit || + pointer_x > data->x_upper_limit || + pointer_y < data->y_down_limit || + pointer_y > data->y_upper_limit) + { + gtk_widget_show (GTK_WIDGET (self)); + self->hover_timeout_id = 0; + + return G_SOURCE_REMOVE; + } + else + { + gtk_widget_hide (GTK_WIDGET (self)); + } + + return G_SOURCE_CONTINUE; +} + +static void +on_event_controller_motion_motion (GtkEventControllerMotion *controller, + double x, + double y, + gpointer user_data) +{ + NautilusFloatingBar *self = NAUTILUS_FLOATING_BAR (user_data); + GtkWidget *parent; + CheckPointerData *data; + gdouble x_pos; + gdouble y_pos; + + self->pointer_x_in_parent_coordinates = x; + self->pointer_y_in_parent_coordinates = y; + + if (self->is_interactive || !gtk_widget_is_visible (GTK_WIDGET (self))) + { + return; + } + + parent = gtk_event_controller_get_widget (GTK_EVENT_CONTROLLER (controller)); + gtk_widget_translate_coordinates (GTK_WIDGET (self), parent, 0, 0, &x_pos, &y_pos); + + if (x < x_pos || y < y_pos) + { + return; + } + + if (self->hover_timeout_id != 0) + { + return; + } + + data = g_slice_new (CheckPointerData); + data->floating_bar = self; + data->x_down_limit = x_pos; + data->x_upper_limit = x_pos + gtk_widget_get_allocated_width (GTK_WIDGET (self)); + data->y_down_limit = y_pos; + data->y_upper_limit = y_pos + gtk_widget_get_allocated_height (GTK_WIDGET (self)); + + self->hover_timeout_id = g_timeout_add_full (G_PRIORITY_DEFAULT, HOVER_HIDE_TIMEOUT_INTERVAL, + check_pointer_timeout, data, + check_pointer_data_free); + + g_source_set_name_by_id (self->hover_timeout_id, "[nautilus-floating-bar] on_event_controller_motion_motion"); +} + +static void +on_event_controller_motion_leave (GtkEventControllerMotion *controller, + gpointer user_data) +{ + NautilusFloatingBar *self = NAUTILUS_FLOATING_BAR (user_data); + + self->pointer_x_in_parent_coordinates = -1; + self->pointer_y_in_parent_coordinates = -1; +} + +static void +on_parent_changed (GObject *object, + GParamSpec *pspec, + gpointer user_data) +{ + NautilusFloatingBar *self = NAUTILUS_FLOATING_BAR (object); + GtkWidget *parent; + + parent = gtk_widget_get_parent (GTK_WIDGET (object)); + + if (self->motion_controller != NULL) + { + GtkWidget *old_parent; + + old_parent = gtk_event_controller_get_widget (self->motion_controller); + g_warn_if_fail (old_parent != NULL); + if (old_parent != NULL) + { + gtk_widget_remove_controller (old_parent, self->motion_controller); + } + + g_object_unref (self->motion_controller); + self->motion_controller = NULL; + } + + if (parent != NULL) + { + self->motion_controller = g_object_ref (gtk_event_controller_motion_new ()); + gtk_widget_add_controller (parent, self->motion_controller); + + gtk_event_controller_set_propagation_phase (self->motion_controller, + GTK_PHASE_CAPTURE); + g_signal_connect (self->motion_controller, "leave", + G_CALLBACK (on_event_controller_motion_leave), self); + g_signal_connect (self->motion_controller, "motion", + G_CALLBACK (on_event_controller_motion_motion), self); + } +} + +static void +nautilus_floating_bar_constructed (GObject *obj) +{ + NautilusFloatingBar *self = NAUTILUS_FLOATING_BAR (obj); + GtkWidget *w, *box, *labels_box; + GtkStyleContext *context; + + G_OBJECT_CLASS (nautilus_floating_bar_parent_class)->constructed (obj); + + box = GTK_WIDGET (obj); + + w = gtk_spinner_new (); + gtk_box_append (GTK_BOX (box), w); + gtk_widget_set_visible (w, self->show_spinner); + /* As a workaround for https://gitlab.gnome.org/GNOME/gtk/-/issues/1025, + * ensure the spinner animates if and only if it's visible, to reduce CPU + * usage. */ + g_object_bind_property (obj, "show-spinner", + w, "spinning", + G_BINDING_SYNC_CREATE); + self->spinner = w; + + gtk_widget_set_size_request (w, 16, 16); + gtk_widget_set_margin_start (w, 8); + + labels_box = gtk_box_new (GTK_ORIENTATION_HORIZONTAL, 6); + gtk_box_append (GTK_BOX (box), labels_box); + g_object_set (labels_box, + "hexpand", TRUE, + "margin-top", 2, + "margin-bottom", 2, + "margin-start", 12, + "margin-end", 12, + NULL); + gtk_widget_show (labels_box); + + w = gtk_label_new (NULL); + gtk_label_set_ellipsize (GTK_LABEL (w), PANGO_ELLIPSIZE_MIDDLE); + gtk_label_set_single_line_mode (GTK_LABEL (w), TRUE); + gtk_box_append (GTK_BOX (labels_box), w); + self->primary_label_widget = w; + gtk_widget_show (w); + + w = gtk_label_new (NULL); + gtk_label_set_single_line_mode (GTK_LABEL (w), TRUE); + gtk_box_append (GTK_BOX (labels_box), w); + self->details_label_widget = w; + gtk_widget_show (w); + + w = gtk_button_new_from_icon_name ("process-stop-symbolic"); + context = gtk_widget_get_style_context (w); + gtk_style_context_add_class (context, "circular"); + gtk_style_context_add_class (context, "flat"); + gtk_widget_set_valign (w, GTK_ALIGN_CENTER); + gtk_box_append (GTK_BOX (self), w); + self->stop_button = w; + gtk_widget_set_visible (w, FALSE); + + g_signal_connect (self->stop_button, "clicked", + G_CALLBACK (stop_button_clicked_cb), self); +} + +static void +nautilus_floating_bar_init (NautilusFloatingBar *self) +{ + GtkStyleContext *context; + + context = gtk_widget_get_style_context (GTK_WIDGET (self)); + gtk_style_context_add_class (context, "floating-bar"); + + self->motion_controller = NULL; + self->pointer_x_in_parent_coordinates = -1; + self->pointer_y_in_parent_coordinates = -1; + + g_signal_connect (self, + "notify::parent", + G_CALLBACK (on_parent_changed), + NULL); +} + +static void +nautilus_floating_bar_class_init (NautilusFloatingBarClass *klass) +{ + GObjectClass *oclass = G_OBJECT_CLASS (klass); + + oclass->constructed = nautilus_floating_bar_constructed; + oclass->set_property = nautilus_floating_bar_set_property; + oclass->get_property = nautilus_floating_bar_get_property; + oclass->finalize = nautilus_floating_bar_finalize; + + properties[PROP_PRIMARY_LABEL] = + g_param_spec_string ("primary-label", + "Bar's primary label", + "Primary label displayed by the bar", + NULL, + G_PARAM_WRITABLE | G_PARAM_STATIC_STRINGS); + properties[PROP_DETAILS_LABEL] = + g_param_spec_string ("details-label", + "Bar's details label", + "Details label displayed by the bar", + NULL, + G_PARAM_WRITABLE | G_PARAM_STATIC_STRINGS); + properties[PROP_SHOW_SPINNER] = + g_param_spec_boolean ("show-spinner", + "Show spinner", + "Whether a spinner should be shown in the floating bar", + FALSE, + G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS); + properties[PROP_SHOW_STOP] = + g_param_spec_boolean ("show-stop", + "Show stop button", + "Whether a stop button should be shown in the floating bar", + FALSE, + G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS); + + signals[STOP] = g_signal_new ("stop", + G_TYPE_FROM_CLASS (klass), + G_SIGNAL_RUN_LAST, + 0, NULL, NULL, + g_cclosure_marshal_VOID__VOID, + G_TYPE_NONE, 0); + + g_object_class_install_properties (oclass, NUM_PROPERTIES, properties); +} + +void +nautilus_floating_bar_set_primary_label (NautilusFloatingBar *self, + const gchar *label) +{ + if (g_strcmp0 (self->primary_label, label) != 0) + { + g_free (self->primary_label); + self->primary_label = g_strdup (label); + + g_object_notify_by_pspec (G_OBJECT (self), properties[PROP_PRIMARY_LABEL]); + + update_labels (self); + } +} + +void +nautilus_floating_bar_set_details_label (NautilusFloatingBar *self, + const gchar *label) +{ + if (g_strcmp0 (self->details_label, label) != 0) + { + g_free (self->details_label); + self->details_label = g_strdup (label); + + g_object_notify_by_pspec (G_OBJECT (self), properties[PROP_DETAILS_LABEL]); + + update_labels (self); + } +} + +void +nautilus_floating_bar_set_labels (NautilusFloatingBar *self, + const gchar *primary_label, + const gchar *details_label) +{ + nautilus_floating_bar_set_primary_label (self, primary_label); + nautilus_floating_bar_set_details_label (self, details_label); +} + +void +nautilus_floating_bar_set_show_spinner (NautilusFloatingBar *self, + gboolean show_spinner) +{ + if (self->show_spinner != show_spinner) + { + self->show_spinner = show_spinner; + gtk_widget_set_visible (self->spinner, + show_spinner); + + g_object_notify_by_pspec (G_OBJECT (self), properties[PROP_SHOW_SPINNER]); + } +} + +void +nautilus_floating_bar_set_show_stop (NautilusFloatingBar *self, + gboolean show_stop) +{ + if (self->show_stop != show_stop) + { + self->show_stop = show_stop; + gtk_widget_set_visible (self->stop_button, + show_stop); + self->is_interactive = show_stop; + + g_object_notify_by_pspec (G_OBJECT (self), properties[PROP_SHOW_STOP]); + } +} + +GtkWidget * +nautilus_floating_bar_new (const gchar *primary_label, + const gchar *details_label, + gboolean show_spinner) +{ + return g_object_new (NAUTILUS_TYPE_FLOATING_BAR, + "primary-label", primary_label, + "details-label", details_label, + "show-spinner", show_spinner, + "orientation", GTK_ORIENTATION_HORIZONTAL, + "spacing", 8, + NULL); +} diff --git a/src/nautilus-floating-bar.h b/src/nautilus-floating-bar.h new file mode 100644 index 0000000..61256da --- /dev/null +++ b/src/nautilus-floating-bar.h @@ -0,0 +1,48 @@ + +/* Nautilus - Floating status bar. + * + * Copyright (C) 2011 Red Hat Inc. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Library General Public + * License as published by the Free Software Foundation; either + * version 2 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Library General Public License for more details. + * + * You should have received a copy of the GNU Library General Public + * License along with this library; if not, see . + * + * Authors: Cosimo Cecchi + * + */ + +#pragma once + +#include + +#define NAUTILUS_FLOATING_BAR_ACTION_ID_STOP 1 + +#define NAUTILUS_TYPE_FLOATING_BAR nautilus_floating_bar_get_type() +G_DECLARE_FINAL_TYPE (NautilusFloatingBar, nautilus_floating_bar, NAUTILUS, FLOATING_BAR, GtkBox) + +GtkWidget * nautilus_floating_bar_new (const gchar *primary_label, + const gchar *details_label, + gboolean show_spinner); + +void nautilus_floating_bar_set_primary_label (NautilusFloatingBar *self, + const gchar *label); +void nautilus_floating_bar_set_details_label (NautilusFloatingBar *self, + const gchar *label); +void nautilus_floating_bar_set_labels (NautilusFloatingBar *self, + const gchar *primary, + const gchar *detail); +void nautilus_floating_bar_set_show_spinner (NautilusFloatingBar *self, + gboolean show_spinner); +void nautilus_floating_bar_set_show_stop (NautilusFloatingBar *self, + gboolean show_spinner); + +void nautilus_floating_bar_remove_hover_timeout (NautilusFloatingBar *self); diff --git a/src/nautilus-freedesktop-dbus.c b/src/nautilus-freedesktop-dbus.c new file mode 100644 index 0000000..2eaebcc --- /dev/null +++ b/src/nautilus-freedesktop-dbus.c @@ -0,0 +1,329 @@ +/* + * nautilus-freedesktop-dbus: Implementation for the org.freedesktop DBus file-management interfaces + * + * Nautilus is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License as + * published by the Free Software Foundation; either version 2 of the + * License, or (at your option) any later version. + * + * Nautilus is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * 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 . + * + * Authors: Akshay Gupta + * Federico Mena Quintero + */ + +#include "nautilus-freedesktop-dbus.h" + +/* We share the same debug domain as nautilus-dbus-manager */ +#define DEBUG_FLAG NAUTILUS_DEBUG_DBUS +#include "nautilus-debug.h" + +#include "nautilus-application.h" +#include "nautilus-file.h" +#include "nautilus-freedesktop-generated.h" +#include "nautilus-properties-window.h" + +#include + +struct _NautilusFreedesktopDBus +{ + GObject parent; + + /* Id from g_dbus_own_name() */ + guint owner_id; + + /* Our DBus implementation skeleton */ + NautilusFreedesktopFileManager1 *skeleton; + + GStrv pending_open_locations; + GVariant *pending_open_windows_with_locations; + + gboolean name_lost; +}; + +G_DEFINE_TYPE (NautilusFreedesktopDBus, nautilus_freedesktop_dbus, G_TYPE_OBJECT); + +static gboolean +skeleton_handle_show_items_cb (NautilusFreedesktopFileManager1 *object, + GDBusMethodInvocation *invocation, + const gchar * const *uris, + const gchar *startup_id, + gpointer data) +{ + NautilusApplication *application; + int i; + + application = NAUTILUS_APPLICATION (g_application_get_default ()); + + for (i = 0; uris[i] != NULL; i++) + { + g_autoptr (GFile) file = NULL; + g_autoptr (GFile) parent = NULL; + + file = g_file_new_for_uri (uris[i]); + parent = g_file_get_parent (file); + + if (parent != NULL) + { + nautilus_application_open_location (application, parent, file, startup_id); + } + else + { + nautilus_application_open_location (application, file, NULL, startup_id); + } + } + + nautilus_freedesktop_file_manager1_complete_show_items (object, invocation); + return TRUE; +} + +static gboolean +skeleton_handle_show_folders_cb (NautilusFreedesktopFileManager1 *object, + GDBusMethodInvocation *invocation, + const gchar * const *uris, + const gchar *startup_id, + gpointer data) +{ + NautilusApplication *application; + int i; + + application = NAUTILUS_APPLICATION (g_application_get_default ()); + + for (i = 0; uris[i] != NULL; i++) + { + g_autoptr (GFile) file = NULL; + + file = g_file_new_for_uri (uris[i]); + + nautilus_application_open_location (application, file, NULL, startup_id); + } + + nautilus_freedesktop_file_manager1_complete_show_folders (object, invocation); + return TRUE; +} + +static void +properties_window_on_finished (gpointer user_data) +{ + g_application_release (g_application_get_default ()); +} + +static gboolean +skeleton_handle_show_item_properties_cb (NautilusFreedesktopFileManager1 *object, + GDBusMethodInvocation *invocation, + const gchar * const *uris, + const gchar *startup_id, + gpointer data) +{ + GList *files; + int i; + + files = NULL; + + for (i = 0; uris[i] != NULL; i++) + { + files = g_list_prepend (files, nautilus_file_get_by_uri (uris[i])); + } + + files = g_list_reverse (files); + + g_application_hold (g_application_get_default ()); + nautilus_properties_window_present (files, NULL, startup_id, + properties_window_on_finished, NULL); + + nautilus_file_list_free (files); + + nautilus_freedesktop_file_manager1_complete_show_item_properties (object, invocation); + return TRUE; +} + +static void +bus_acquired_cb (GDBusConnection *conn, + const gchar *name, + gpointer user_data) +{ + NautilusFreedesktopDBus *fdb = user_data; + + DEBUG ("Bus acquired at %s", name); + + fdb->skeleton = nautilus_freedesktop_file_manager1_skeleton_new (); + + g_signal_connect (fdb->skeleton, "handle-show-items", + G_CALLBACK (skeleton_handle_show_items_cb), fdb); + g_signal_connect (fdb->skeleton, "handle-show-folders", + G_CALLBACK (skeleton_handle_show_folders_cb), fdb); + g_signal_connect (fdb->skeleton, "handle-show-item-properties", + G_CALLBACK (skeleton_handle_show_item_properties_cb), fdb); + + g_dbus_interface_skeleton_export (G_DBUS_INTERFACE_SKELETON (fdb->skeleton), conn, NAUTILUS_FDO_DBUS_PATH, NULL); + + if (G_UNLIKELY (fdb->pending_open_locations != NULL)) + { + g_auto (GStrv) locations = NULL; + + locations = g_steal_pointer (&fdb->pending_open_locations); + + nautilus_freedesktop_dbus_set_open_locations (fdb, (const gchar **) locations); + } + + if (G_UNLIKELY (fdb->pending_open_windows_with_locations != NULL)) + { + g_autoptr (GVariant) locations = NULL; + + locations = g_steal_pointer (&fdb->pending_open_windows_with_locations); + + nautilus_freedesktop_dbus_set_open_windows_with_locations (fdb, locations); + } +} + +static void +name_acquired_cb (GDBusConnection *connection, + const gchar *name, + gpointer user_data) +{ + DEBUG ("Acquired the name %s on the session message bus\n", name); +} + +static void +name_lost_cb (GDBusConnection *connection, + const gchar *name, + gpointer user_data) +{ + NautilusFreedesktopDBus *fdb; + + DEBUG ("Lost (or failed to acquire) the name %s on the session message bus\n", name); + + fdb = NAUTILUS_FREEDESKTOP_DBUS (user_data); + + fdb->name_lost = TRUE; +} + +static void +nautilus_freedesktop_dbus_dispose (GObject *object) +{ + NautilusFreedesktopDBus *fdb = (NautilusFreedesktopDBus *) object; + + if (fdb->owner_id != 0) + { + g_bus_unown_name (fdb->owner_id); + fdb->owner_id = 0; + } + + if (fdb->skeleton != NULL) + { + g_dbus_interface_skeleton_unexport (G_DBUS_INTERFACE_SKELETON (fdb->skeleton)); + g_object_unref (fdb->skeleton); + fdb->skeleton = NULL; + } + + G_OBJECT_CLASS (nautilus_freedesktop_dbus_parent_class)->dispose (object); +} + +static void +nautilus_freedesktop_dbus_finalize (GObject *object) +{ + NautilusFreedesktopDBus *fdb; + + fdb = NAUTILUS_FREEDESKTOP_DBUS (object); + + g_clear_pointer (&fdb->pending_open_locations, g_strfreev); + g_clear_pointer (&fdb->pending_open_windows_with_locations, g_variant_unref); + + G_OBJECT_CLASS (nautilus_freedesktop_dbus_parent_class)->finalize (object); +} + +static void +nautilus_freedesktop_dbus_class_init (NautilusFreedesktopDBusClass *klass) +{ + GObjectClass *object_class = G_OBJECT_CLASS (klass); + + object_class->dispose = nautilus_freedesktop_dbus_dispose; + object_class->finalize = nautilus_freedesktop_dbus_finalize; +} + +static void +nautilus_freedesktop_dbus_init (NautilusFreedesktopDBus *fdb) +{ + fdb->owner_id = g_bus_own_name (G_BUS_TYPE_SESSION, + NAUTILUS_FDO_DBUS_NAME, + G_BUS_NAME_OWNER_FLAGS_NONE, + bus_acquired_cb, + name_acquired_cb, + name_lost_cb, + fdb, + NULL); + fdb->skeleton = NULL; + fdb->pending_open_locations = NULL; + fdb->pending_open_windows_with_locations = NULL; + fdb->name_lost = FALSE; +} + +void +nautilus_freedesktop_dbus_set_open_locations (NautilusFreedesktopDBus *fdb, + const gchar **locations) +{ + g_return_if_fail (NAUTILUS_IS_FREEDESKTOP_DBUS (fdb)); + + if (G_UNLIKELY (fdb->skeleton == NULL)) + { + if (G_LIKELY (fdb->name_lost)) + { + return; + } + + g_clear_pointer (&fdb->pending_open_locations, g_strfreev); + + fdb->pending_open_locations = g_strdupv ((gchar **) locations); + } + else + { + nautilus_freedesktop_file_manager1_set_open_locations (fdb->skeleton, locations); + } +} + +/** + * nautilus_freedesktop_dbus_set_open_windows_with_locations: + * fdb: The skeleton for the dbus interface + * locations: Mapping of windows to locations open in each window + * + * This allows the application to publish the locations that are open in each window. + * It is used by shell extensions like dash-to-dock/ubuntu-dock to match special dock + * icons to the windows where the icon's location is open. For example, the Trash or + * a removable device. + */ +void +nautilus_freedesktop_dbus_set_open_windows_with_locations (NautilusFreedesktopDBus *fdb, + GVariant *locations) +{ + g_return_if_fail (NAUTILUS_IS_FREEDESKTOP_DBUS (fdb)); + + if (G_UNLIKELY (fdb->skeleton == NULL)) + { + if (G_LIKELY (fdb->name_lost)) + { + return; + } + + g_clear_pointer (&fdb->pending_open_windows_with_locations, g_variant_unref); + + fdb->pending_open_windows_with_locations = g_variant_ref (locations); + } + else + { + nautilus_freedesktop_file_manager1_set_open_windows_with_locations (fdb->skeleton, + locations); + } +} + +/* Tries to own the org.freedesktop.FileManager1 service name */ +NautilusFreedesktopDBus * +nautilus_freedesktop_dbus_new (void) +{ + return g_object_new (NAUTILUS_TYPE_FREEDESKTOP_DBUS, NULL); +} diff --git a/src/nautilus-freedesktop-dbus.h b/src/nautilus-freedesktop-dbus.h new file mode 100644 index 0000000..416900e --- /dev/null +++ b/src/nautilus-freedesktop-dbus.h @@ -0,0 +1,37 @@ +/* + * nautilus-freedesktop-dbus: Implementation for the org.freedesktop DBus file-management interfaces + * + * Nautilus is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License as + * published by the Free Software Foundation; either version 2 of the + * License, or (at your option) any later version. + * + * Nautilus is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * 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 . + * + * Authors: Akshay Gupta + * Federico Mena Quintero + */ + +#pragma once + +#include + +#define NAUTILUS_FDO_DBUS_IFACE "org.freedesktop.FileManager1" +#define NAUTILUS_FDO_DBUS_NAME "org.freedesktop.FileManager1" +#define NAUTILUS_FDO_DBUS_PATH "/org/freedesktop/FileManager1" + +#define NAUTILUS_TYPE_FREEDESKTOP_DBUS nautilus_freedesktop_dbus_get_type() + +G_DECLARE_FINAL_TYPE (NautilusFreedesktopDBus, nautilus_freedesktop_dbus, NAUTILUS, FREEDESKTOP_DBUS, GObject); + +NautilusFreedesktopDBus * nautilus_freedesktop_dbus_new (void); + +void nautilus_freedesktop_dbus_set_open_locations (NautilusFreedesktopDBus *fdb, const gchar **locations); + +void nautilus_freedesktop_dbus_set_open_windows_with_locations (NautilusFreedesktopDBus *fdb, GVariant *locations); diff --git a/src/nautilus-global-preferences.c b/src/nautilus-global-preferences.c new file mode 100644 index 0000000..6bc9919 --- /dev/null +++ b/src/nautilus-global-preferences.c @@ -0,0 +1,67 @@ +/* nautilus-global-preferences.c - Nautilus specific preference keys and + * functions. + * + * Copyright (C) 1999, 2000, 2001 Eazel, Inc. + * + * The Gnome Library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Library General Public License as + * published by the Free Software Foundation; either version 2 of the + * License, or (at your option) any later version. + * + * The Gnome Library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Library General Public License for more details. + * + * You should have received a copy of the GNU Library General Public + * License along with the Gnome Library; see the file COPYING.LIB. If not, + * see . + * + * Authors: Ramiro Estrugo + */ + +#include +#include "nautilus-global-preferences.h" + +#include "nautilus-file-utilities.h" +#include "nautilus-file.h" +#include "src/nautilus-files-view.h" +#include +#include +#include +#include + +GSettings *nautilus_preferences; +GSettings *nautilus_compression_preferences; +GSettings *nautilus_icon_view_preferences; +GSettings *nautilus_list_view_preferences; +GSettings *nautilus_window_state; +GSettings *gtk_filechooser_preferences; +GSettings *gnome_lockdown_preferences; +GSettings *gnome_interface_preferences; +GSettings *gnome_privacy_preferences; + +void +nautilus_global_preferences_init (void) +{ + static gboolean initialized = FALSE; + + if (initialized) + { + return; + } + + initialized = TRUE; + + nautilus_preferences = g_settings_new ("org.gnome.nautilus.preferences"); + nautilus_compression_preferences = g_settings_new ("org.gnome.nautilus.compression"); + nautilus_window_state = g_settings_new ("org.gnome.nautilus.window-state"); + nautilus_icon_view_preferences = g_settings_new ("org.gnome.nautilus.icon-view"); + nautilus_list_view_preferences = g_settings_new ("org.gnome.nautilus.list-view"); + /* Some settings such as show hidden files are shared between Nautilus and GTK file chooser */ + gtk_filechooser_preferences = g_settings_new_with_path ("org.gtk.gtk4.Settings.FileChooser", + "/org/gtk/gtk4/settings/file-chooser/"); + gnome_lockdown_preferences = g_settings_new ("org.gnome.desktop.lockdown"); + gnome_interface_preferences = g_settings_new ("org.gnome.desktop.interface"); + gnome_privacy_preferences = g_settings_new ("org.gnome.desktop.privacy"); +} diff --git a/src/nautilus-global-preferences.h b/src/nautilus-global-preferences.h new file mode 100644 index 0000000..5a73717 --- /dev/null +++ b/src/nautilus-global-preferences.h @@ -0,0 +1,145 @@ + +/* nautilus-global-preferences.h - Nautilus specific preference keys and + functions. + + Copyright (C) 1999, 2000, 2001 Eazel, Inc. + + This program is free software; you can redistribute it and/or + modify it under the terms of the GNU Library General Public License as + published by the Free Software Foundation; either version 2 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 warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Library General Public License for more details. + + You should have received a copy of the GNU Library General Public + License along with this program; see the file COPYING.LIB. If not, + see . + + Authors: Ramiro Estrugo +*/ + +#pragma once + +#include "nautilus-global-preferences.h" +#include + +G_BEGIN_DECLS + +/* Display */ +#define NAUTILUS_PREFERENCES_SHOW_HIDDEN_FILES "show-hidden" + +/* Mouse */ +#define NAUTILUS_PREFERENCES_MOUSE_USE_EXTRA_BUTTONS "mouse-use-extra-buttons" +#define NAUTILUS_PREFERENCES_MOUSE_FORWARD_BUTTON "mouse-forward-button" +#define NAUTILUS_PREFERENCES_MOUSE_BACK_BUTTON "mouse-back-button" + +/* Single/Double click preference */ +#define NAUTILUS_PREFERENCES_CLICK_POLICY "click-policy" + +/* Drag and drop preferences */ +#define NAUTILUS_PREFERENCES_OPEN_FOLDER_ON_DND_HOVER "open-folder-on-dnd-hover" + +/* Installing new packages when unknown mime type activated */ +#define NAUTILUS_PREFERENCES_INSTALL_MIME_ACTIVATION "install-mime-activation" + +/* Spatial or browser mode */ +#define NAUTILUS_PREFERENCES_NEW_TAB_POSITION "tabs-open-position" + +#define NAUTILUS_PREFERENCES_ALWAYS_USE_LOCATION_ENTRY "always-use-location-entry" + +/* Which views should be displayed for new windows */ +#define NAUTILUS_WINDOW_STATE_INITIAL_SIZE "initial-size" +#define NAUTILUS_WINDOW_STATE_MAXIMIZED "maximized" + +/* Sorting order */ +#define NAUTILUS_PREFERENCES_SORT_DIRECTORIES_FIRST "sort-directories-first" +#define NAUTILUS_PREFERENCES_DEFAULT_SORT_ORDER "default-sort-order" +#define NAUTILUS_PREFERENCES_DEFAULT_SORT_IN_REVERSE_ORDER "default-sort-in-reverse-order" + +/* The default folder viewer - one of the two enums below */ +#define NAUTILUS_PREFERENCES_DEFAULT_FOLDER_VIEWER "default-folder-viewer" + +/* Compression */ +#define NAUTILUS_PREFERENCES_DEFAULT_COMPRESSION_FORMAT "default-compression-format" + +typedef enum +{ + NAUTILUS_COMPRESSION_ZIP = 0, + NAUTILUS_COMPRESSION_TAR_XZ, + NAUTILUS_COMPRESSION_7ZIP, + NAUTILUS_COMPRESSION_ENCRYPTED_ZIP +} NautilusCompressionFormat; + +/* Icon View */ +#define NAUTILUS_PREFERENCES_ICON_VIEW_DEFAULT_ZOOM_LEVEL "default-zoom-level" + +/* Which text attributes appear beneath icon names */ +#define NAUTILUS_PREFERENCES_ICON_VIEW_CAPTIONS "captions" + +/* List View */ +#define NAUTILUS_PREFERENCES_LIST_VIEW_DEFAULT_ZOOM_LEVEL "default-zoom-level" +#define NAUTILUS_PREFERENCES_LIST_VIEW_DEFAULT_VISIBLE_COLUMNS "default-visible-columns" +#define NAUTILUS_PREFERENCES_LIST_VIEW_DEFAULT_COLUMN_ORDER "default-column-order" +#define NAUTILUS_PREFERENCES_LIST_VIEW_USE_TREE "use-tree-view" + +enum +{ + NAUTILUS_CLICK_POLICY_SINGLE, + NAUTILUS_CLICK_POLICY_DOUBLE +}; + +typedef enum +{ + NAUTILUS_SPEED_TRADEOFF_ALWAYS, + NAUTILUS_SPEED_TRADEOFF_LOCAL_ONLY, + NAUTILUS_SPEED_TRADEOFF_NEVER +} NautilusSpeedTradeoffValue; + +#define NAUTILUS_PREFERENCES_SHOW_DIRECTORY_ITEM_COUNTS "show-directory-item-counts" +#define NAUTILUS_PREFERENCES_SHOW_FILE_THUMBNAILS "show-image-thumbnails" +#define NAUTILUS_PREFERENCES_FILE_THUMBNAIL_LIMIT "thumbnail-limit" + +typedef enum +{ + NAUTILUS_COMPLEX_SEARCH_BAR, + NAUTILUS_SIMPLE_SEARCH_BAR +} NautilusSearchBarMode; + +/* Lockdown */ +#define NAUTILUS_PREFERENCES_LOCKDOWN_COMMAND_LINE "disable-command-line" + +/* Recent files */ +#define NAUTILUS_PREFERENCES_RECENT_FILES_ENABLED "remember-recent-files" + +/* Default view when searching */ +#define NAUTILUS_PREFERENCES_SEARCH_VIEW "search-view" + +/* Search behaviour */ +#define NAUTILUS_PREFERENCES_RECURSIVE_SEARCH "recursive-search" + +/* Context menu options */ +#define NAUTILUS_PREFERENCES_SHOW_DELETE_PERMANENTLY "show-delete-permanently" +#define NAUTILUS_PREFERENCES_SHOW_CREATE_LINK "show-create-link" + +/* Full Text Search enabled */ +#define NAUTILUS_PREFERENCES_FTS_ENABLED "fts-enabled" + +/* Gtk settings migration happened */ +#define NAUTILUS_PREFERENCES_MIGRATED_GTK_SETTINGS "migrated-gtk-settings" + +void nautilus_global_preferences_init (void); + +extern GSettings *nautilus_preferences; +extern GSettings *nautilus_compression_preferences; +extern GSettings *nautilus_icon_view_preferences; +extern GSettings *nautilus_list_view_preferences; +extern GSettings *nautilus_window_state; +extern GSettings *gtk_filechooser_preferences; +extern GSettings *gnome_lockdown_preferences; +extern GSettings *gnome_interface_preferences; +extern GSettings *gnome_privacy_preferences; + +G_END_DECLS diff --git a/src/nautilus-grid-cell.c b/src/nautilus-grid-cell.c new file mode 100644 index 0000000..9da7b78 --- /dev/null +++ b/src/nautilus-grid-cell.c @@ -0,0 +1,282 @@ +/* + * Copyright (C) 2022 The GNOME project contributors + * + * SPDX-License-Identifier: GPL-3.0-or-later + */ + +#include "nautilus-grid-cell.h" + +struct _NautilusGridCell +{ + NautilusViewCell parent_instance; + + GSignalGroup *item_signal_group; + + GQuark *caption_attributes; + + GtkWidget *fixed_height_box; + GtkWidget *icon; + GtkWidget *emblems_box; + GtkWidget *label; + GtkWidget *first_caption; + GtkWidget *second_caption; + GtkWidget *third_caption; +}; + +G_DEFINE_TYPE (NautilusGridCell, nautilus_grid_cell, NAUTILUS_TYPE_VIEW_CELL) + +static void +update_icon (NautilusGridCell *self) +{ + g_autoptr (NautilusViewItem) item = NULL; + NautilusFileIconFlags flags; + g_autoptr (GdkPaintable) icon_paintable = NULL; + GtkStyleContext *style_context; + NautilusFile *file; + guint icon_size; + gint scale_factor; + g_autofree gchar *thumbnail_path = NULL; + + item = nautilus_view_cell_get_item (NAUTILUS_VIEW_CELL (self)); + g_return_if_fail (item != NULL); + file = nautilus_view_item_get_file (item); + icon_size = nautilus_view_item_get_icon_size (item); + scale_factor = gtk_widget_get_scale_factor (GTK_WIDGET (self)); + flags = NAUTILUS_FILE_ICON_FLAGS_USE_THUMBNAILS; + + icon_paintable = nautilus_file_get_icon_paintable (file, icon_size, scale_factor, flags); + gtk_picture_set_paintable (GTK_PICTURE (self->icon), icon_paintable); + + /* Set the same height and width for all icons regardless of aspect ratio. + */ + gtk_widget_set_size_request (self->fixed_height_box, icon_size, icon_size); + style_context = gtk_widget_get_style_context (self->icon); + thumbnail_path = nautilus_file_get_thumbnail_path (file); + if (thumbnail_path != NULL && + nautilus_file_should_show_thumbnail (file)) + { + gtk_style_context_add_class (style_context, "thumbnail"); + } + else + { + gtk_style_context_remove_class (style_context, "thumbnail"); + } +} + +static void +update_captions (NautilusGridCell *self) +{ + g_autoptr (NautilusViewItem) item = NULL; + NautilusFile *file; + GtkWidget * const caption_labels[] = + { + self->first_caption, + self->second_caption, + self->third_caption + }; + G_STATIC_ASSERT (G_N_ELEMENTS (caption_labels) == NAUTILUS_GRID_CELL_N_CAPTIONS); + + item = nautilus_view_cell_get_item (NAUTILUS_VIEW_CELL (self)); + g_return_if_fail (item != NULL); + file = nautilus_view_item_get_file (item); + for (guint i = 0; i < NAUTILUS_GRID_CELL_N_CAPTIONS; i++) + { + GQuark attribute_q = self->caption_attributes[i]; + gboolean show_caption; + + show_caption = (attribute_q != 0); + gtk_widget_set_visible (caption_labels[i], show_caption); + if (show_caption) + { + g_autofree gchar *string = NULL; + string = nautilus_file_get_string_attribute_q (file, attribute_q); + gtk_label_set_text (GTK_LABEL (caption_labels[i]), string); + } + } +} + +static void +update_emblems (NautilusGridCell *self) +{ + g_autoptr (NautilusViewItem) item = NULL; + NautilusFile *file; + GtkWidget *child; + GtkIconTheme *theme; + g_autolist (GIcon) emblems = NULL; + + item = nautilus_view_cell_get_item (NAUTILUS_VIEW_CELL (self)); + g_return_if_fail (item != NULL); + file = nautilus_view_item_get_file (item); + + /* Remove old emblems. */ + while ((child = gtk_widget_get_first_child (self->emblems_box)) != NULL) + { + gtk_box_remove (GTK_BOX (self->emblems_box), child); + } + + theme = gtk_icon_theme_get_for_display (gdk_display_get_default ()); + emblems = nautilus_file_get_emblem_icons (file); + for (GList *l = emblems; l != NULL; l = l->next) + { + if (!gtk_icon_theme_has_gicon (theme, l->data)) + { + g_autofree gchar *icon_string = g_icon_to_string (l->data); + g_warning ("Failed to add emblem. “%s” not found in the icon theme", + icon_string); + continue; + } + + gtk_box_append (GTK_BOX (self->emblems_box), + gtk_image_new_from_gicon (l->data)); + } +} + + +static void +on_file_changed (NautilusGridCell *self) +{ + g_autoptr (NautilusViewItem) item = NULL; + NautilusFile *file; + g_autofree gchar *name = NULL; + + item = nautilus_view_cell_get_item (NAUTILUS_VIEW_CELL (self)); + g_return_if_fail (item != NULL); + file = nautilus_view_item_get_file (item); + + update_icon (self); + update_emblems (self); + + name = nautilus_file_get_display_name (file); + + gtk_label_set_text (GTK_LABEL (self->label), name); + update_captions (self); +} + +static void +on_item_size_changed (NautilusGridCell *self) +{ + update_icon (self); + update_captions (self); +} + +static void +on_item_is_cut_changed (NautilusGridCell *self) +{ + gboolean is_cut; + g_autoptr (NautilusViewItem) item = NULL; + + item = nautilus_view_cell_get_item (NAUTILUS_VIEW_CELL (self)); + g_object_get (item, + "is-cut", &is_cut, + NULL); + if (is_cut) + { + gtk_widget_add_css_class (self->icon, "cut"); + } + else + { + gtk_widget_remove_css_class (self->icon, "cut"); + } +} + +static gboolean +on_label_query_tooltip (GtkWidget *widget, + int x, + int y, + gboolean keyboard_tip, + GtkTooltip *tooltip, + gpointer user_data) +{ + GtkLabel *label = GTK_LABEL (widget); + + if (pango_layout_is_ellipsized (gtk_label_get_layout (label))) + { + gtk_tooltip_set_markup (tooltip, gtk_label_get_label (label)); + return TRUE; + } + + return FALSE; +} + +static void +finalize (GObject *object) +{ + NautilusGridCell *self = (NautilusGridCell *) object; + + g_object_unref (self->item_signal_group); + G_OBJECT_CLASS (nautilus_grid_cell_parent_class)->finalize (object); +} + +static void +nautilus_grid_cell_class_init (NautilusGridCellClass *klass) +{ + GObjectClass *object_class = G_OBJECT_CLASS (klass); + GtkWidgetClass *widget_class = GTK_WIDGET_CLASS (klass); + + object_class->finalize = finalize; + + gtk_widget_class_set_template_from_resource (widget_class, "/org/gnome/nautilus/ui/nautilus-grid-cell.ui"); + + gtk_widget_class_bind_template_child (widget_class, NautilusGridCell, fixed_height_box); + gtk_widget_class_bind_template_child (widget_class, NautilusGridCell, icon); + gtk_widget_class_bind_template_child (widget_class, NautilusGridCell, emblems_box); + gtk_widget_class_bind_template_child (widget_class, NautilusGridCell, label); + gtk_widget_class_bind_template_child (widget_class, NautilusGridCell, first_caption); + gtk_widget_class_bind_template_child (widget_class, NautilusGridCell, second_caption); + gtk_widget_class_bind_template_child (widget_class, NautilusGridCell, third_caption); + + gtk_widget_class_set_accessible_role (widget_class, GTK_ACCESSIBLE_ROLE_GRID_CELL); +} + +static void +nautilus_grid_cell_init (NautilusGridCell *self) +{ + gtk_widget_init_template (GTK_WIDGET (self)); + + g_signal_connect (self->label, "query-tooltip", + G_CALLBACK (on_label_query_tooltip), NULL); + + /* Connect automatically to an item. */ + self->item_signal_group = g_signal_group_new (NAUTILUS_TYPE_VIEW_ITEM); + g_signal_group_connect_swapped (self->item_signal_group, "notify::icon-size", + (GCallback) on_item_size_changed, self); + g_signal_group_connect_swapped (self->item_signal_group, "notify::is-cut", + (GCallback) on_item_is_cut_changed, self); + g_signal_group_connect_swapped (self->item_signal_group, "file-changed", + (GCallback) on_file_changed, self); + g_signal_connect_object (self->item_signal_group, "bind", + (GCallback) on_file_changed, self, + G_CONNECT_SWAPPED); + + g_object_bind_property (self, "item", + self->item_signal_group, "target", + G_BINDING_SYNC_CREATE); + +#if PANGO_VERSION_CHECK (1, 44, 4) + { + PangoAttrList *attr_list; + + /* GTK4 TODO: This attribute is set in the UI file but GTK 3 ignores it. + * Remove this block after the switch to GTK 4. */ + attr_list = pango_attr_list_new (); + pango_attr_list_insert (attr_list, pango_attr_insert_hyphens_new (FALSE)); + gtk_label_set_attributes (GTK_LABEL (self->label), attr_list); + pango_attr_list_unref (attr_list); + } +#endif +} + +NautilusGridCell * +nautilus_grid_cell_new (NautilusListBase *view) +{ + return g_object_new (NAUTILUS_TYPE_GRID_CELL, + "view", view, + NULL); +} + +void +nautilus_grid_cell_set_caption_attributes (NautilusGridCell *self, + GQuark *attrs) +{ + self->caption_attributes = attrs; +} diff --git a/src/nautilus-grid-cell.h b/src/nautilus-grid-cell.h new file mode 100644 index 0000000..52da1f8 --- /dev/null +++ b/src/nautilus-grid-cell.h @@ -0,0 +1,29 @@ +/* + * Copyright (C) 2022 The GNOME project contributors + * + * SPDX-License-Identifier: GPL-3.0-or-later + */ + +#pragma once + +#include "nautilus-view-cell.h" + +G_BEGIN_DECLS + +enum +{ + NAUTILUS_GRID_CELL_FIRST_CAPTION, + NAUTILUS_GRID_CELL_SECOND_CAPTION, + NAUTILUS_GRID_CELL_THIRD_CAPTION, + NAUTILUS_GRID_CELL_N_CAPTIONS +}; + +#define NAUTILUS_TYPE_GRID_CELL (nautilus_grid_cell_get_type()) + +G_DECLARE_FINAL_TYPE (NautilusGridCell, nautilus_grid_cell, NAUTILUS, GRID_CELL, NautilusViewCell) + +NautilusGridCell * nautilus_grid_cell_new (NautilusListBase *view); +void nautilus_grid_cell_set_caption_attributes (NautilusGridCell *self, + GQuark *attrs); + +G_END_DECLS diff --git a/src/nautilus-grid-view.c b/src/nautilus-grid-view.c new file mode 100644 index 0000000..8e38d7c --- /dev/null +++ b/src/nautilus-grid-view.c @@ -0,0 +1,604 @@ +/* + * Copyright (C) 2022 The GNOME project contributors + * + * SPDX-License-Identifier: GPL-3.0-or-later + */ + +#include "nautilus-list-base-private.h" +#include "nautilus-grid-view.h" + +#include "nautilus-grid-cell.h" +#include "nautilus-global-preferences.h" + +struct _NautilusGridView +{ + NautilusListBase parent_instance; + + GtkGridView *view_ui; + + GActionGroup *action_group; + gint zoom_level; + + gboolean directories_first; + + GQuark caption_attributes[NAUTILUS_GRID_CELL_N_CAPTIONS]; + + NautilusFileSortType sort_type; + gboolean reversed; +}; + +G_DEFINE_TYPE (NautilusGridView, nautilus_grid_view, NAUTILUS_TYPE_LIST_BASE) + +static guint get_icon_size_for_zoom_level (NautilusGridZoomLevel zoom_level); + +static gint +nautilus_grid_view_sort (gconstpointer a, + gconstpointer b, + gpointer user_data) +{ + NautilusGridView *self = user_data; + NautilusFile *file_a; + NautilusFile *file_b; + + file_a = nautilus_view_item_get_file (NAUTILUS_VIEW_ITEM ((gpointer) a)); + file_b = nautilus_view_item_get_file (NAUTILUS_VIEW_ITEM ((gpointer) b)); + + return nautilus_file_compare_for_sort (file_a, file_b, + self->sort_type, + self->directories_first, + self->reversed); +} + +static void +real_bump_zoom_level (NautilusFilesView *files_view, + int zoom_increment) +{ + NautilusGridView *self = NAUTILUS_GRID_VIEW (files_view); + NautilusGridZoomLevel new_level; + + new_level = self->zoom_level + zoom_increment; + + if (new_level >= NAUTILUS_GRID_ZOOM_LEVEL_SMALL && + new_level <= NAUTILUS_GRID_ZOOM_LEVEL_EXTRA_LARGE) + { + g_action_group_change_action_state (self->action_group, + "zoom-to-level", + g_variant_new_int32 (new_level)); + } +} + +static guint +get_icon_size_for_zoom_level (NautilusGridZoomLevel zoom_level) +{ + switch (zoom_level) + { + case NAUTILUS_GRID_ZOOM_LEVEL_SMALL: + { + return NAUTILUS_GRID_ICON_SIZE_SMALL; + } + break; + + case NAUTILUS_GRID_ZOOM_LEVEL_SMALL_PLUS: + { + return NAUTILUS_GRID_ICON_SIZE_SMALL_PLUS; + } + break; + + case NAUTILUS_GRID_ZOOM_LEVEL_MEDIUM: + { + return NAUTILUS_GRID_ICON_SIZE_MEDIUM; + } + break; + + case NAUTILUS_GRID_ZOOM_LEVEL_LARGE: + { + return NAUTILUS_GRID_ICON_SIZE_LARGE; + } + break; + + case NAUTILUS_GRID_ZOOM_LEVEL_EXTRA_LARGE: + { + return NAUTILUS_GRID_ICON_SIZE_EXTRA_LARGE; + } + break; + } + g_return_val_if_reached (NAUTILUS_GRID_ICON_SIZE_MEDIUM); +} + +static gint +get_default_zoom_level (void) +{ + NautilusGridZoomLevel default_zoom_level; + + default_zoom_level = g_settings_get_enum (nautilus_icon_view_preferences, + NAUTILUS_PREFERENCES_ICON_VIEW_DEFAULT_ZOOM_LEVEL); + + /* Sanitize preference value */ + return CLAMP (default_zoom_level, + NAUTILUS_GRID_ZOOM_LEVEL_SMALL, + NAUTILUS_GRID_ZOOM_LEVEL_EXTRA_LARGE); +} + +static void +set_captions_from_preferences (NautilusGridView *self) +{ + g_auto (GStrv) value = NULL; + gint n_captions_for_zoom_level; + + value = g_settings_get_strv (nautilus_icon_view_preferences, + NAUTILUS_PREFERENCES_ICON_VIEW_CAPTIONS); + + /* Set a celling on the number of captions depending on the zoom level. */ + n_captions_for_zoom_level = MIN (1 + self->zoom_level, + G_N_ELEMENTS (self->caption_attributes)); + + /* Reset array to zeros beforehand, as we may not refill all elements. */ + memset (&self->caption_attributes, 0, sizeof (self->caption_attributes)); + for (gint i = 0, quark_i = 0; + value[i] != NULL && quark_i < n_captions_for_zoom_level; + i++) + { + if (g_strcmp0 (value[i], "none") == 0) + { + continue; + } + + /* Convert to quarks in advance, otherwise each NautilusFile attribute + * getter would call g_quark_from_string() once for each file. */ + self->caption_attributes[quark_i] = g_quark_from_string (value[i]); + quark_i++; + } +} + +static void +set_zoom_level (NautilusGridView *self, + guint new_level) +{ + self->zoom_level = new_level; + + /* The zoom level may change how many captions are allowed. Update it before + * setting the icon size, under the assumption that NautilusGridCell + * updates captions whenever the icon size is set*/ + set_captions_from_preferences (self); + + nautilus_list_base_set_icon_size (NAUTILUS_LIST_BASE (self), + get_icon_size_for_zoom_level (new_level)); + + nautilus_files_view_update_toolbar_menus (NAUTILUS_FILES_VIEW (self)); +} + +static void +real_restore_standard_zoom_level (NautilusFilesView *files_view) +{ + NautilusGridView *self; + + self = NAUTILUS_GRID_VIEW (files_view); + g_action_group_change_action_state (self->action_group, + "zoom-to-level", + g_variant_new_int32 (NAUTILUS_GRID_ZOOM_LEVEL_MEDIUM)); +} + +static gboolean +real_is_zoom_level_default (NautilusFilesView *files_view) +{ + NautilusGridView *self; + guint icon_size; + + self = NAUTILUS_GRID_VIEW (files_view); + icon_size = get_icon_size_for_zoom_level (self->zoom_level); + + return icon_size == NAUTILUS_GRID_ICON_SIZE_MEDIUM; +} + +static gboolean +real_can_zoom_in (NautilusFilesView *files_view) +{ + NautilusGridView *self = NAUTILUS_GRID_VIEW (files_view); + + return self->zoom_level < NAUTILUS_GRID_ZOOM_LEVEL_EXTRA_LARGE; +} + +static gboolean +real_can_zoom_out (NautilusFilesView *files_view) +{ + NautilusGridView *self = NAUTILUS_GRID_VIEW (files_view); + + return self->zoom_level > NAUTILUS_GRID_ZOOM_LEVEL_SMALL; +} + +/* The generic implementation in src/nautilus-list-base.c doesn't allow the + * 2-dimensional movements expected from a grid. Let's hack GTK here. */ +static void +real_preview_selection_event (NautilusFilesView *files_view, + GtkDirectionType direction) +{ + NautilusGridView *self = NAUTILUS_GRID_VIEW (files_view); + guint direction_keyval; + g_autoptr (GtkShortcutTrigger) direction_trigger = NULL; + g_autoptr (GListModel) controllers = NULL; + gboolean success = FALSE; + + /* We want the same behavior as when the user presses the arrow keys while + * the focus is in the view. So, let's get the matching arrow key. */ + switch (direction) + { + case GTK_DIR_UP: + { + direction_keyval = GDK_KEY_Up; + } + break; + + case GTK_DIR_DOWN: + { + direction_keyval = GDK_KEY_Down; + } + break; + + case GTK_DIR_LEFT: + { + direction_keyval = GDK_KEY_Left; + } + break; + + case GTK_DIR_RIGHT: + { + direction_keyval = GDK_KEY_Right; + } + break; + + default: + { + g_return_if_reached (); + } + } + + /* We cannot simulate a click, but we can find the shortcut it triggers and + * activate its action programatically. + * + * First, we create out would-be trigger.*/ + direction_trigger = gtk_keyval_trigger_new (direction_keyval, 0); + + /* Then we iterate over the shortcut installed in GtkGridView until we find + * a matching trigger. There may be multiple shortcut controllers, and each + * shortcut controller may hold multiple shortcuts each. Let's loop. */ + controllers = gtk_widget_observe_controllers (GTK_WIDGET (self->view_ui)); + for (guint i = 0; i < g_list_model_get_n_items (controllers); i++) + { + g_autoptr (GtkEventController) controller = g_list_model_get_item (controllers, i); + + if (!GTK_IS_SHORTCUT_CONTROLLER (controller)) + { + continue; + } + + for (guint j = 0; j < g_list_model_get_n_items (G_LIST_MODEL (controller)); j++) + { + g_autoptr (GtkShortcut) shortcut = g_list_model_get_item (G_LIST_MODEL (controller), j); + GtkShortcutTrigger *trigger = gtk_shortcut_get_trigger (shortcut); + + if (gtk_shortcut_trigger_equal (trigger, direction_trigger)) + { + /* Match found. Activate the action to move cursor. */ + success = gtk_shortcut_action_activate (gtk_shortcut_get_action (shortcut), + 0, + GTK_WIDGET (self->view_ui), + gtk_shortcut_get_arguments (shortcut)); + break; + } + } + } + + /* If the hack fails (GTK may change it's internal behavior), fallback. */ + if (!success) + { + NAUTILUS_FILES_VIEW_CLASS (nautilus_grid_view_parent_class)->preview_selection_event (files_view, direction); + } +} + +/* We only care about the keyboard activation part that GtkGridView provides, + * but we don't need any special filtering here. Indeed, we ask GtkGridView + * to not activate on single click, and we get to handle double clicks before + * GtkGridView does (as one of widget subclassing's goal is to modify the parent + * class's behavior), while claiming the click gestures, so it means GtkGridView + * will never react to a click event to emit this signal. So we should be pretty + * safe here with regards to our custom item click handling. + */ +static void +on_grid_view_item_activated (GtkGridView *grid_view, + guint position, + gpointer user_data) +{ + NautilusGridView *self = NAUTILUS_GRID_VIEW (user_data); + + nautilus_files_view_activate_selection (NAUTILUS_FILES_VIEW (self)); +} + +static guint +real_get_icon_size (NautilusListBase *list_base_view) +{ + NautilusGridView *self = NAUTILUS_GRID_VIEW (list_base_view); + + return get_icon_size_for_zoom_level (self->zoom_level); +} + +static GtkWidget * +real_get_view_ui (NautilusListBase *list_base_view) +{ + NautilusGridView *self = NAUTILUS_GRID_VIEW (list_base_view); + + return GTK_WIDGET (self->view_ui); +} + +static void +real_scroll_to_item (NautilusListBase *list_base_view, + guint position) +{ + NautilusGridView *self = NAUTILUS_GRID_VIEW (list_base_view); + + gtk_widget_activate_action (GTK_WIDGET (self->view_ui), + "list.scroll-to-item", + "u", + position); +} + +static void +real_sort_directories_first_changed (NautilusFilesView *files_view) +{ + NautilusGridView *self; + NautilusViewModel *model; + g_autoptr (GtkCustomSorter) sorter = NULL; + + self = NAUTILUS_GRID_VIEW (files_view); + self->directories_first = nautilus_files_view_should_sort_directories_first (NAUTILUS_FILES_VIEW (self)); + + model = nautilus_list_base_get_model (NAUTILUS_LIST_BASE (self)); + sorter = gtk_custom_sorter_new (nautilus_grid_view_sort, self, NULL); + nautilus_view_model_set_sorter (model, GTK_SORTER (sorter)); +} + +static void +action_sort_order_changed (GSimpleAction *action, + GVariant *value, + gpointer user_data) +{ + const gchar *target_name; + NautilusGridView *self = NAUTILUS_GRID_VIEW (user_data); + NautilusViewModel *model; + g_autoptr (GtkCustomSorter) sorter = NULL; + + /* Don't resort if the action is in the same state as before */ + if (g_variant_equal (value, g_action_get_state (G_ACTION (action)))) + { + return; + } + + g_variant_get (value, "(&sb)", &target_name, &self->reversed); + self->sort_type = get_sorts_type_from_metadata_text (target_name); + + sorter = gtk_custom_sorter_new (nautilus_grid_view_sort, self, NULL); + model = nautilus_list_base_get_model (NAUTILUS_LIST_BASE (self)); + nautilus_view_model_set_sorter (model, GTK_SORTER (sorter)); + set_directory_sort_metadata (nautilus_files_view_get_directory_as_file (NAUTILUS_FILES_VIEW (self)), + target_name, + self->reversed); + + g_simple_action_set_state (action, value); +} + +static guint +real_get_view_id (NautilusFilesView *files_view) +{ + return NAUTILUS_VIEW_GRID_ID; +} + +static void +action_zoom_to_level (GSimpleAction *action, + GVariant *state, + gpointer user_data) +{ + NautilusGridView *self = NAUTILUS_GRID_VIEW (user_data); + int zoom_level; + + zoom_level = g_variant_get_int32 (state); + set_zoom_level (self, zoom_level); + g_simple_action_set_state (G_SIMPLE_ACTION (action), state); + + if (g_settings_get_enum (nautilus_icon_view_preferences, + NAUTILUS_PREFERENCES_ICON_VIEW_DEFAULT_ZOOM_LEVEL) != zoom_level) + { + g_settings_set_enum (nautilus_icon_view_preferences, + NAUTILUS_PREFERENCES_ICON_VIEW_DEFAULT_ZOOM_LEVEL, + zoom_level); + } +} + +static void +on_captions_preferences_changed (NautilusGridView *self) +{ + set_captions_from_preferences (self); + + /* Hack: this relies on the assumption that NautilusGridCell updates + * captions whenever the icon size is set (even if it's the same value). */ + nautilus_list_base_set_icon_size (NAUTILUS_LIST_BASE (self), + get_icon_size_for_zoom_level (self->zoom_level)); +} + +static void +dispose (GObject *object) +{ + G_OBJECT_CLASS (nautilus_grid_view_parent_class)->dispose (object); +} + +static void +finalize (GObject *object) +{ + G_OBJECT_CLASS (nautilus_grid_view_parent_class)->finalize (object); +} + +static void +bind_cell (GtkSignalListItemFactory *factory, + GtkListItem *listitem, + gpointer user_data) +{ + GtkWidget *cell; + NautilusViewItem *item; + + cell = gtk_list_item_get_child (listitem); + item = NAUTILUS_VIEW_ITEM (gtk_list_item_get_item (listitem)); + + nautilus_view_item_set_item_ui (item, cell); + + if (nautilus_view_cell_once (NAUTILUS_VIEW_CELL (cell))) + { + GtkWidget *parent; + + /* At the time of ::setup emission, the item ui has got no parent yet, + * that's why we need to complete the widget setup process here, on the + * first time ::bind is emitted. */ + parent = gtk_widget_get_parent (cell); + gtk_widget_set_halign (parent, GTK_ALIGN_CENTER); + gtk_widget_set_valign (parent, GTK_ALIGN_START); + gtk_widget_set_margin_top (parent, 3); + gtk_widget_set_margin_bottom (parent, 3); + gtk_widget_set_margin_start (parent, 3); + gtk_widget_set_margin_end (parent, 3); + + gtk_accessible_update_relation (GTK_ACCESSIBLE (parent), + GTK_ACCESSIBLE_RELATION_LABELLED_BY, cell, NULL, + -1); + } +} + +static void +unbind_cell (GtkSignalListItemFactory *factory, + GtkListItem *listitem, + gpointer user_data) +{ + NautilusViewItem *item; + + item = NAUTILUS_VIEW_ITEM (gtk_list_item_get_item (listitem)); + + nautilus_view_item_set_item_ui (item, NULL); +} + +static void +setup_cell (GtkSignalListItemFactory *factory, + GtkListItem *listitem, + gpointer user_data) +{ + NautilusGridView *self = NAUTILUS_GRID_VIEW (user_data); + NautilusGridCell *cell; + + cell = nautilus_grid_cell_new (NAUTILUS_LIST_BASE (self)); + setup_cell_common (listitem, NAUTILUS_VIEW_CELL (cell)); + + nautilus_grid_cell_set_caption_attributes (cell, self->caption_attributes); +} + +static GtkGridView * +create_view_ui (NautilusGridView *self) +{ + NautilusViewModel *model; + GtkListItemFactory *factory; + GtkWidget *widget; + + model = nautilus_list_base_get_model (NAUTILUS_LIST_BASE (self)); + + factory = gtk_signal_list_item_factory_new (); + g_signal_connect (factory, "setup", G_CALLBACK (setup_cell), self); + g_signal_connect (factory, "bind", G_CALLBACK (bind_cell), self); + g_signal_connect (factory, "unbind", G_CALLBACK (unbind_cell), self); + + widget = gtk_grid_view_new (GTK_SELECTION_MODEL (model), factory); + + /* We don't use the built-in child activation feature for clicks because it + * doesn't fill all our needs nor does it match our expected behavior. + * Instead, we roll our own event handling and double/single click mode. + * However, GtkGridView:single-click-activate has other effects besides + * activation, as it affects the selection behavior as well (e.g. selects on + * hover). Setting it to FALSE gives us the expected behavior. */ + gtk_grid_view_set_single_click_activate (GTK_GRID_VIEW (widget), FALSE); + gtk_grid_view_set_max_columns (GTK_GRID_VIEW (widget), 20); + gtk_grid_view_set_enable_rubberband (GTK_GRID_VIEW (widget), TRUE); + + /* While we don't want to use GTK's click activation, we'll let it handle + * the key activation part (with Enter). + */ + g_signal_connect (widget, "activate", G_CALLBACK (on_grid_view_item_activated), self); + + return GTK_GRID_VIEW (widget); +} + +const GActionEntry view_icon_actions[] = +{ + { "sort", NULL, "(sb)", "('invalid',false)", action_sort_order_changed }, + { "zoom-to-level", NULL, NULL, "100", action_zoom_to_level } +}; + +static void +nautilus_grid_view_class_init (NautilusGridViewClass *klass) +{ + GObjectClass *object_class = G_OBJECT_CLASS (klass); + NautilusFilesViewClass *files_view_class = NAUTILUS_FILES_VIEW_CLASS (klass); + NautilusListBaseClass *list_base_view_class = NAUTILUS_LIST_BASE_CLASS (klass); + + object_class->dispose = dispose; + object_class->finalize = finalize; + + files_view_class->bump_zoom_level = real_bump_zoom_level; + files_view_class->can_zoom_in = real_can_zoom_in; + files_view_class->can_zoom_out = real_can_zoom_out; + files_view_class->sort_directories_first_changed = real_sort_directories_first_changed; + files_view_class->get_view_id = real_get_view_id; + files_view_class->restore_standard_zoom_level = real_restore_standard_zoom_level; + files_view_class->is_zoom_level_default = real_is_zoom_level_default; + files_view_class->preview_selection_event = real_preview_selection_event; + + list_base_view_class->get_icon_size = real_get_icon_size; + list_base_view_class->get_view_ui = real_get_view_ui; + list_base_view_class->scroll_to_item = real_scroll_to_item; +} + +static void +nautilus_grid_view_init (NautilusGridView *self) +{ + GtkWidget *content_widget; + + gtk_widget_add_css_class (GTK_WIDGET (self), "nautilus-grid-view"); + + set_captions_from_preferences (self); + g_signal_connect_object (nautilus_icon_view_preferences, + "changed::" NAUTILUS_PREFERENCES_ICON_VIEW_CAPTIONS, + G_CALLBACK (on_captions_preferences_changed), + self, + G_CONNECT_SWAPPED); + + content_widget = nautilus_files_view_get_content_widget (NAUTILUS_FILES_VIEW (self)); + + self->view_ui = create_view_ui (self); + nautilus_list_base_setup_gestures (NAUTILUS_LIST_BASE (self)); + + gtk_scrolled_window_set_child (GTK_SCROLLED_WINDOW (content_widget), + GTK_WIDGET (self->view_ui)); + + self->action_group = nautilus_files_view_get_action_group (NAUTILUS_FILES_VIEW (self)); + g_action_map_add_action_entries (G_ACTION_MAP (self->action_group), + view_icon_actions, + G_N_ELEMENTS (view_icon_actions), + self); + + self->directories_first = nautilus_files_view_should_sort_directories_first (NAUTILUS_FILES_VIEW (self)); + + self->zoom_level = get_default_zoom_level (); + /* Keep the action synced with the actual value, so the toolbar can poll it */ + g_action_group_change_action_state (nautilus_files_view_get_action_group (NAUTILUS_FILES_VIEW (self)), + "zoom-to-level", g_variant_new_int32 (self->zoom_level)); +} + +NautilusGridView * +nautilus_grid_view_new (NautilusWindowSlot *slot) +{ + return g_object_new (NAUTILUS_TYPE_GRID_VIEW, + "window-slot", slot, + NULL); +} diff --git a/src/nautilus-grid-view.h b/src/nautilus-grid-view.h new file mode 100644 index 0000000..4c8835f --- /dev/null +++ b/src/nautilus-grid-view.h @@ -0,0 +1,20 @@ +/* + * Copyright (C) 2022 The GNOME project contributors + * + * SPDX-License-Identifier: GPL-3.0-or-later + */ + +#pragma once + +#include "nautilus-list-base.h" +#include "nautilus-window-slot.h" + +G_BEGIN_DECLS + +#define NAUTILUS_TYPE_GRID_VIEW (nautilus_grid_view_get_type()) + +G_DECLARE_FINAL_TYPE (NautilusGridView, nautilus_grid_view, NAUTILUS, GRID_VIEW, NautilusListBase) + +NautilusGridView *nautilus_grid_view_new (NautilusWindowSlot *slot); + +G_END_DECLS diff --git a/src/nautilus-history-controls.c b/src/nautilus-history-controls.c new file mode 100644 index 0000000..8b14cd6 --- /dev/null +++ b/src/nautilus-history-controls.c @@ -0,0 +1,313 @@ +/* + * Copyright (C) 2022 The GNOME project contributors + * + * SPDX-License-Identifier: GPL-2.0-or-later + */ + +#include "nautilus-history-controls.h" + +#include "nautilus-bookmark.h" +#include "nautilus-window.h" + +struct _NautilusHistoryControls +{ + AdwBin parent_instance; + + GtkWidget *back_button; + GtkWidget *back_menu; + + GtkWidget *forward_button; + GtkWidget *forward_menu; + + NautilusWindowSlot *window_slot; +}; + +G_DEFINE_FINAL_TYPE (NautilusHistoryControls, nautilus_history_controls, ADW_TYPE_BIN) + +enum +{ + PROP_0, + PROP_WINDOW_SLOT, + N_PROPS +}; + +static GParamSpec *properties[N_PROPS]; + +static void +fill_menu (NautilusHistoryControls *self, + GMenu *menu, + gboolean back) +{ + guint index; + GList *list; + const gchar *name; + + list = back ? nautilus_window_slot_get_back_history (self->window_slot) : + nautilus_window_slot_get_forward_history (self->window_slot); + + index = 0; + while (list != NULL) + { + g_autoptr (GMenuItem) item = NULL; + + name = nautilus_bookmark_get_name (NAUTILUS_BOOKMARK (list->data)); + item = g_menu_item_new (name, NULL); + g_menu_item_set_action_and_target (item, + back ? "win.back-n" : "win.forward-n", + "u", index); + g_menu_append_item (menu, item); + + list = g_list_next (list); + ++index; + } +} + +static void +show_menu (NautilusHistoryControls *self, + GtkWidget *widget) +{ + g_autoptr (GMenu) menu = NULL; + NautilusNavigationDirection direction; + GtkPopoverMenu *popover; + + menu = g_menu_new (); + + direction = GPOINTER_TO_UINT (g_object_get_data (G_OBJECT (widget), + "nav-direction")); + + switch (direction) + { + case NAUTILUS_NAVIGATION_DIRECTION_FORWARD: + { + fill_menu (self, menu, FALSE); + popover = GTK_POPOVER_MENU (self->forward_menu); + } + break; + + case NAUTILUS_NAVIGATION_DIRECTION_BACK: + { + fill_menu (self, menu, TRUE); + popover = GTK_POPOVER_MENU (self->back_menu); + } + break; + + default: + { + g_assert_not_reached (); + } + break; + } + + gtk_popover_menu_set_menu_model (popover, G_MENU_MODEL (menu)); + gtk_popover_popup (GTK_POPOVER (popover)); +} + +static void +navigation_button_press_cb (GtkGestureClick *gesture, + gint n_press, + gdouble x, + gdouble y, + gpointer user_data) +{ + NautilusHistoryControls *self; + NautilusWindow *window; + GtkWidget *widget; + guint button; + + self = NAUTILUS_HISTORY_CONTROLS (user_data); + button = gtk_gesture_single_get_current_button (GTK_GESTURE_SINGLE (gesture)); + widget = gtk_event_controller_get_widget (GTK_EVENT_CONTROLLER (gesture)); + window = NAUTILUS_WINDOW (gtk_widget_get_root (GTK_WIDGET (self))); + + if (button == GDK_BUTTON_PRIMARY) + { + /* Don't do anything, primary click is handled through activate */ + gtk_gesture_set_state (GTK_GESTURE (gesture), GTK_EVENT_SEQUENCE_DENIED); + return; + } + else if (button == GDK_BUTTON_MIDDLE) + { + NautilusNavigationDirection direction; + + direction = GPOINTER_TO_UINT (g_object_get_data (G_OBJECT (widget), + "nav-direction")); + + nautilus_window_back_or_forward_in_new_tab (window, direction); + } + else if (button == GDK_BUTTON_SECONDARY) + { + show_menu (self, widget); + } +} + +static void +back_button_longpress_cb (GtkGestureLongPress *gesture, + double x, + double y, + gpointer user_data) +{ + NautilusHistoryControls *self = user_data; + + show_menu (self, self->back_button); +} + +static void +forward_button_longpress_cb (GtkGestureLongPress *gesture, + double x, + double y, + gpointer user_data) +{ + NautilusHistoryControls *self = user_data; + + show_menu (self, self->forward_button); +} + + +static void +nautilus_history_controls_contructed (GObject *object) +{ + NautilusHistoryControls *self; + GtkEventController *controller; + + self = NAUTILUS_HISTORY_CONTROLS (object); + + controller = GTK_EVENT_CONTROLLER (gtk_gesture_long_press_new ()); + gtk_widget_add_controller (self->back_button, controller); + g_signal_connect (controller, "pressed", + G_CALLBACK (back_button_longpress_cb), self); + + controller = GTK_EVENT_CONTROLLER (gtk_gesture_long_press_new ()); + gtk_widget_add_controller (self->forward_button, controller); + g_signal_connect (controller, "pressed", + G_CALLBACK (forward_button_longpress_cb), self); + + g_object_set_data (G_OBJECT (self->back_button), "nav-direction", + GUINT_TO_POINTER (NAUTILUS_NAVIGATION_DIRECTION_BACK)); + g_object_set_data (G_OBJECT (self->forward_button), "nav-direction", + GUINT_TO_POINTER (NAUTILUS_NAVIGATION_DIRECTION_FORWARD)); + + controller = GTK_EVENT_CONTROLLER (gtk_gesture_click_new ()); + gtk_widget_add_controller (self->back_button, controller); + gtk_gesture_single_set_button (GTK_GESTURE_SINGLE (controller), 0); + g_signal_connect (controller, "pressed", + G_CALLBACK (navigation_button_press_cb), self); + + controller = GTK_EVENT_CONTROLLER (gtk_gesture_click_new ()); + gtk_widget_add_controller (self->forward_button, controller); + gtk_gesture_single_set_button (GTK_GESTURE_SINGLE (controller), 0); + g_signal_connect (controller, "pressed", + G_CALLBACK (navigation_button_press_cb), self); +} + +static void +nautilus_history_controls_dispose (GObject *object) +{ + NautilusHistoryControls *self; + + self = NAUTILUS_HISTORY_CONTROLS (object); + + g_clear_pointer (&self->back_menu, gtk_widget_unparent); + g_clear_pointer (&self->forward_menu, gtk_widget_unparent); + + G_OBJECT_CLASS (nautilus_history_controls_parent_class)->dispose (object); +} + +static void +nautilus_history_controls_set_window_slot (NautilusHistoryControls *self, + NautilusWindowSlot *window_slot) +{ + g_return_if_fail (NAUTILUS_IS_HISTORY_CONTROLS (self)); + g_return_if_fail (window_slot == NULL || NAUTILUS_IS_WINDOW_SLOT (window_slot)); + + if (self->window_slot == window_slot) + { + return; + } + + self->window_slot = window_slot; + + g_object_notify_by_pspec (G_OBJECT (self), properties[PROP_WINDOW_SLOT]); +} + +static void +nautilus_history_controls_get_property (GObject *object, + guint prop_id, + GValue *value, + GParamSpec *pspec) +{ + NautilusHistoryControls *self = NAUTILUS_HISTORY_CONTROLS (object); + + switch (prop_id) + { + case PROP_WINDOW_SLOT: + { + g_value_set_object (value, G_OBJECT (self->window_slot)); + break; + } + + default: + { + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); + } + } +} + +static void +nautilus_history_controls_set_property (GObject *object, + guint prop_id, + const GValue *value, + GParamSpec *pspec) +{ + NautilusHistoryControls *self = NAUTILUS_HISTORY_CONTROLS (object); + + switch (prop_id) + { + case PROP_WINDOW_SLOT: + { + nautilus_history_controls_set_window_slot (self, NAUTILUS_WINDOW_SLOT (g_value_get_object (value))); + break; + } + + default: + { + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); + } + } +} + +static void +nautilus_history_controls_class_init (NautilusHistoryControlsClass *klass) +{ + GObjectClass *object_class = G_OBJECT_CLASS (klass); + GtkWidgetClass *widget_class = GTK_WIDGET_CLASS (klass); + + object_class->constructed = nautilus_history_controls_contructed; + object_class->dispose = nautilus_history_controls_dispose; + object_class->set_property = nautilus_history_controls_set_property; + object_class->get_property = nautilus_history_controls_get_property; + + properties[PROP_WINDOW_SLOT] = g_param_spec_object ("window-slot", + NULL, NULL, + NAUTILUS_TYPE_WINDOW_SLOT, + G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS | G_PARAM_EXPLICIT_NOTIFY); + + g_object_class_install_properties (object_class, N_PROPS, properties); + + gtk_widget_class_set_template_from_resource (widget_class, + "/org/gnome/nautilus/ui/nautilus-history-controls.ui"); + gtk_widget_class_bind_template_child (widget_class, NautilusHistoryControls, back_button); + gtk_widget_class_bind_template_child (widget_class, NautilusHistoryControls, back_menu); + gtk_widget_class_bind_template_child (widget_class, NautilusHistoryControls, forward_button); + gtk_widget_class_bind_template_child (widget_class, NautilusHistoryControls, forward_menu); +} + +static void +nautilus_history_controls_init (NautilusHistoryControls *self) +{ + gtk_widget_init_template (GTK_WIDGET (self)); + + gtk_widget_set_parent (self->back_menu, self->back_button); + g_signal_connect (self->back_menu, "destroy", G_CALLBACK (gtk_widget_unparent), NULL); + gtk_widget_set_parent (self->forward_menu, self->forward_button); + g_signal_connect (self->forward_menu, "destroy", G_CALLBACK (gtk_widget_unparent), NULL); +} diff --git a/src/nautilus-history-controls.h b/src/nautilus-history-controls.h new file mode 100644 index 0000000..26d484d --- /dev/null +++ b/src/nautilus-history-controls.h @@ -0,0 +1,20 @@ +/* + * Copyright (C) 2022 The GNOME project contributors + * + * SPDX-License-Identifier: GPL-2.0-or-later + */ + +#pragma once + +#include +#include + +#include "nautilus-window-slot.h" + +G_BEGIN_DECLS + +#define NAUTILUS_TYPE_HISTORY_CONTROLS nautilus_history_controls_get_type() + +G_DECLARE_FINAL_TYPE (NautilusHistoryControls, nautilus_history_controls, NAUTILUS, HISTORY_CONTROLS, AdwBin) + +G_END_DECLS diff --git a/src/nautilus-icon-info.c b/src/nautilus-icon-info.c new file mode 100644 index 0000000..c6b1e9a --- /dev/null +++ b/src/nautilus-icon-info.c @@ -0,0 +1,505 @@ +/* nautilus-icon-info.c + * Copyright (C) 2007 Red Hat, Inc., Alexander Larsson + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Library General Public + * License as published by the Free Software Foundation; either + * version 2 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Library General Public License for more details. + * + * You should have received a copy of the GNU Library General Public + * License along with this library; if not, see . + */ + +#include "nautilus-icon-info.h" + +#include "nautilus-enums.h" + +struct _NautilusIconInfo +{ + GObject parent; + + gboolean sole_owner; + gint64 last_use_time; + GdkPaintable *paintable; + + char *icon_name; +}; + +static void schedule_reap_cache (void); + +G_DEFINE_TYPE (NautilusIconInfo, + nautilus_icon_info, + G_TYPE_OBJECT); + +static void +nautilus_icon_info_init (NautilusIconInfo *icon) +{ + icon->last_use_time = g_get_monotonic_time (); + icon->sole_owner = TRUE; +} + +gboolean +nautilus_icon_info_is_fallback (NautilusIconInfo *icon) +{ + return icon->paintable == NULL; +} + +static void +paintable_toggle_notify (gpointer info, + GObject *object, + gboolean is_last_ref) +{ + NautilusIconInfo *icon = info; + + if (is_last_ref) + { + icon->sole_owner = TRUE; + g_object_remove_toggle_ref (object, + paintable_toggle_notify, + info); + icon->last_use_time = g_get_monotonic_time (); + schedule_reap_cache (); + } +} + +static void +nautilus_icon_info_finalize (GObject *object) +{ + NautilusIconInfo *icon; + + icon = NAUTILUS_ICON_INFO (object); + + if (!icon->sole_owner && icon->paintable) + { + g_object_remove_toggle_ref (G_OBJECT (icon->paintable), + paintable_toggle_notify, + icon); + } + + if (icon->paintable) + { + g_object_unref (icon->paintable); + } + g_free (icon->icon_name); + + G_OBJECT_CLASS (nautilus_icon_info_parent_class)->finalize (object); +} + +static void +nautilus_icon_info_class_init (NautilusIconInfoClass *icon_info_class) +{ + GObjectClass *gobject_class; + + gobject_class = (GObjectClass *) icon_info_class; + + gobject_class->finalize = nautilus_icon_info_finalize; +} + +NautilusIconInfo * +nautilus_icon_info_new_for_paintable (GdkPaintable *paintable, + gint scale) +{ + NautilusIconInfo *icon; + + icon = g_object_new (NAUTILUS_TYPE_ICON_INFO, NULL); + + if (paintable != NULL) + { + icon->paintable = g_object_ref (paintable); + } + + return icon; +} + +static NautilusIconInfo * +nautilus_icon_info_new_for_icon_paintable (GtkIconPaintable *icon_paintable, + gint scale) +{ + NautilusIconInfo *icon; + g_autoptr (GFile) file = NULL; + char *basename, *p; + + icon = nautilus_icon_info_new_for_paintable (GDK_PAINTABLE (icon_paintable), scale); + + file = gtk_icon_paintable_get_file (icon_paintable); + if (file != NULL) + { + basename = g_file_get_basename (file); + p = strrchr (basename, '.'); + if (p) + { + *p = 0; + } + icon->icon_name = basename; + } + else + { + icon->icon_name = g_strdup (gtk_icon_paintable_get_icon_name (icon_paintable)); + } + + return icon; +} + + +typedef struct +{ + GIcon *icon; + int scale; + int size; +} LoadableIconKey; + +typedef struct +{ + char *icon_name; + int scale; + int size; +} ThemedIconKey; + +static GHashTable *loadable_icon_cache = NULL; +static GHashTable *themed_icon_cache = NULL; +static guint reap_cache_timeout = 0; + +#define MICROSEC_PER_SEC ((guint64) 1000000L) + +static guint time_now; + +static gboolean +reap_old_icon (gpointer key, + gpointer value, + gpointer user_info) +{ + NautilusIconInfo *icon = value; + gboolean *reapable_icons_left = user_info; + + if (icon->sole_owner) + { + if (time_now - icon->last_use_time > 30 * MICROSEC_PER_SEC) + { + /* This went unused 30 secs ago. reap */ + return TRUE; + } + else + { + /* We can reap this soon */ + *reapable_icons_left = TRUE; + } + } + + return FALSE; +} + +static gboolean +reap_cache (gpointer data) +{ + gboolean reapable_icons_left; + + reapable_icons_left = TRUE; + + time_now = g_get_monotonic_time (); + + if (loadable_icon_cache) + { + g_hash_table_foreach_remove (loadable_icon_cache, + reap_old_icon, + &reapable_icons_left); + } + + if (themed_icon_cache) + { + g_hash_table_foreach_remove (themed_icon_cache, + reap_old_icon, + &reapable_icons_left); + } + + if (reapable_icons_left) + { + return TRUE; + } + else + { + reap_cache_timeout = 0; + return FALSE; + } +} + +static void +schedule_reap_cache (void) +{ + if (reap_cache_timeout == 0) + { + reap_cache_timeout = g_timeout_add_seconds_full (0, 5, + reap_cache, + NULL, NULL); + } +} + +void +nautilus_icon_info_clear_caches (void) +{ + if (loadable_icon_cache) + { + g_hash_table_remove_all (loadable_icon_cache); + } + + if (themed_icon_cache) + { + g_hash_table_remove_all (themed_icon_cache); + } +} + +static guint +loadable_icon_key_hash (LoadableIconKey *key) +{ + return g_icon_hash (key->icon) ^ key->scale ^ key->size; +} + +static gboolean +loadable_icon_key_equal (const LoadableIconKey *a, + const LoadableIconKey *b) +{ + return a->size == b->size && + a->scale == b->scale && + g_icon_equal (a->icon, b->icon); +} + +static LoadableIconKey * +loadable_icon_key_new (GIcon *icon, + int scale, + int size) +{ + LoadableIconKey *key; + + key = g_slice_new (LoadableIconKey); + key->icon = g_object_ref (icon); + key->scale = scale; + key->size = size; + + return key; +} + +static void +loadable_icon_key_free (LoadableIconKey *key) +{ + g_object_unref (key->icon); + g_slice_free (LoadableIconKey, key); +} + +static guint +themed_icon_key_hash (ThemedIconKey *key) +{ + return g_str_hash (key->icon_name) ^ key->size; +} + +static gboolean +themed_icon_key_equal (const ThemedIconKey *a, + const ThemedIconKey *b) +{ + return a->size == b->size && + a->scale == b->scale && + g_str_equal (a->icon_name, b->icon_name); +} + +static ThemedIconKey * +themed_icon_key_new (const char *icon_name, + int scale, + int size) +{ + ThemedIconKey *key; + + key = g_slice_new (ThemedIconKey); + key->icon_name = g_strdup (icon_name); + key->scale = scale; + key->size = size; + + return key; +} + +static void +themed_icon_key_free (ThemedIconKey *key) +{ + g_free (key->icon_name); + g_slice_free (ThemedIconKey, key); +} + +NautilusIconInfo * +nautilus_icon_info_lookup (GIcon *icon, + int size, + int scale) +{ + NautilusIconInfo *icon_info; + g_autoptr (GtkIconPaintable) icon_paintable = NULL; + + if (G_IS_LOADABLE_ICON (icon)) + { + g_autoptr (GdkPixbuf) pixbuf = NULL; + g_autoptr (GdkPaintable) paintable = NULL; + LoadableIconKey lookup_key; + LoadableIconKey *key; + GInputStream *stream; + + if (loadable_icon_cache == NULL) + { + loadable_icon_cache = + g_hash_table_new_full ((GHashFunc) loadable_icon_key_hash, + (GEqualFunc) loadable_icon_key_equal, + (GDestroyNotify) loadable_icon_key_free, + (GDestroyNotify) g_object_unref); + } + + lookup_key.icon = icon; + lookup_key.scale = scale; + lookup_key.size = size * scale; + + icon_info = g_hash_table_lookup (loadable_icon_cache, &lookup_key); + if (icon_info) + { + return g_object_ref (icon_info); + } + + stream = g_loadable_icon_load (G_LOADABLE_ICON (icon), + size * scale, + NULL, NULL, NULL); + if (stream) + { + pixbuf = gdk_pixbuf_new_from_stream_at_scale (stream, + size * scale, size * scale, + TRUE, + NULL, NULL); + g_input_stream_close (stream, NULL, NULL); + g_object_unref (stream); + } + + if (pixbuf != NULL) + { + double width = gdk_pixbuf_get_width (pixbuf) / scale; + double height = gdk_pixbuf_get_height (pixbuf) / scale; + g_autoptr (GdkTexture) texture = gdk_texture_new_for_pixbuf (pixbuf); + g_autoptr (GtkSnapshot) snapshot = gtk_snapshot_new (); + + gdk_paintable_snapshot (GDK_PAINTABLE (texture), + GDK_SNAPSHOT (snapshot), + width, height); + paintable = gtk_snapshot_to_paintable (snapshot, NULL); + } + + icon_info = nautilus_icon_info_new_for_paintable (paintable, scale); + + key = loadable_icon_key_new (icon, scale, size); + g_hash_table_insert (loadable_icon_cache, key, icon_info); + + return g_object_ref (icon_info); + } + + icon_paintable = gtk_icon_theme_lookup_by_gicon (gtk_icon_theme_get_for_display (gdk_display_get_default ()), + icon, size, scale, GTK_TEXT_DIR_NONE, 0); + if (icon_paintable == NULL) + { + return nautilus_icon_info_new_for_paintable (NULL, scale); + } + + if (G_IS_THEMED_ICON (icon)) + { + ThemedIconKey lookup_key; + ThemedIconKey *key; + const char *icon_name; + + if (themed_icon_cache == NULL) + { + themed_icon_cache = + g_hash_table_new_full ((GHashFunc) themed_icon_key_hash, + (GEqualFunc) themed_icon_key_equal, + (GDestroyNotify) themed_icon_key_free, + (GDestroyNotify) g_object_unref); + } + + icon_name = gtk_icon_paintable_get_icon_name (icon_paintable); + + lookup_key.icon_name = (char *) icon_name; + lookup_key.scale = scale; + lookup_key.size = size; + + icon_info = g_hash_table_lookup (themed_icon_cache, &lookup_key); + if (!icon_info) + { + icon_info = nautilus_icon_info_new_for_icon_paintable (icon_paintable, scale); + + key = themed_icon_key_new (icon_name, scale, size); + g_hash_table_insert (themed_icon_cache, key, icon_info); + } + + return g_object_ref (icon_info); + } + else + { + return nautilus_icon_info_new_for_icon_paintable (icon_paintable, scale); + } +} + +static GdkPaintable * +nautilus_icon_info_get_paintable_nodefault (NautilusIconInfo *icon) +{ + GdkPaintable *res; + + if (icon->paintable == NULL) + { + res = NULL; + } + else + { + res = g_object_ref (icon->paintable); + + if (icon->sole_owner) + { + icon->sole_owner = FALSE; + g_object_add_toggle_ref (G_OBJECT (res), + paintable_toggle_notify, + icon); + } + } + + return res; +} + +GdkPaintable * +nautilus_icon_info_get_paintable (NautilusIconInfo *icon) +{ + GdkPaintable *res; + + res = nautilus_icon_info_get_paintable_nodefault (icon); + if (res == NULL) + { + res = GDK_PAINTABLE (gdk_texture_new_from_resource ("/org/gnome/nautilus/text-x-preview.png")); + } + + return res; +} + +GdkTexture * +nautilus_icon_info_get_texture (NautilusIconInfo *icon) +{ + g_autoptr (GdkPaintable) paintable = NULL; + GdkTexture *res; + + paintable = nautilus_icon_info_get_paintable_nodefault (icon); + if (GDK_IS_TEXTURE (paintable)) + { + res = GDK_TEXTURE (g_steal_pointer (&paintable)); + } + else + { + res = gdk_texture_new_from_resource ("/org/gnome/nautilus/text-x-preview.png"); + } + + return res; +} + +const char * +nautilus_icon_info_get_used_name (NautilusIconInfo *icon) +{ + return icon->icon_name; +} diff --git a/src/nautilus-icon-info.h b/src/nautilus-icon-info.h new file mode 100644 index 0000000..727d3b2 --- /dev/null +++ b/src/nautilus-icon-info.h @@ -0,0 +1,29 @@ +#pragma once + +#include +#include +#include +#include +#include + +G_BEGIN_DECLS + +/* Maximum size of an icon that the icon factory will ever produce */ +#define NAUTILUS_ICON_MAXIMUM_SIZE 320 + +#define NAUTILUS_TYPE_ICON_INFO (nautilus_icon_info_get_type ()) +G_DECLARE_FINAL_TYPE (NautilusIconInfo, nautilus_icon_info, NAUTILUS, ICON_INFO, GObject) + +NautilusIconInfo * nautilus_icon_info_new_for_paintable (GdkPaintable *paintable, + int scale); +NautilusIconInfo * nautilus_icon_info_lookup (GIcon *icon, + int size, + int scale); +gboolean nautilus_icon_info_is_fallback (NautilusIconInfo *icon); +GdkPaintable * nautilus_icon_info_get_paintable (NautilusIconInfo *icon); +GdkTexture * nautilus_icon_info_get_texture (NautilusIconInfo *icon); +const char * nautilus_icon_info_get_used_name (NautilusIconInfo *icon); + +void nautilus_icon_info_clear_caches (void); + +G_END_DECLS diff --git a/src/nautilus-icon-names.h b/src/nautilus-icon-names.h new file mode 100644 index 0000000..4e2624c --- /dev/null +++ b/src/nautilus-icon-names.h @@ -0,0 +1,26 @@ +#pragma once + +/* Icons for places */ +#define NAUTILUS_ICON_FILESYSTEM "drive-harddisk-symbolic" +#define NAUTILUS_ICON_FOLDER "folder-symbolic" +#define NAUTILUS_ICON_FOLDER_REMOTE "folder-remote-symbolic" +#define NAUTILUS_ICON_HOME "user-home-symbolic" + +#define NAUTILUS_ICON_FOLDER_DOCUMENTS "folder-documents-symbolic" +#define NAUTILUS_ICON_FOLDER_DOWNLOAD "folder-download-symbolic" +#define NAUTILUS_ICON_FOLDER_MUSIC "folder-music-symbolic" +#define NAUTILUS_ICON_FOLDER_PICTURES "folder-pictures-symbolic" +#define NAUTILUS_ICON_FOLDER_PUBLIC_SHARE "folder-publicshare-symbolic" +#define NAUTILUS_ICON_FOLDER_TEMPLATES "folder-templates-symbolic" +#define NAUTILUS_ICON_FOLDER_VIDEOS "folder-videos-symbolic" + +/* Fullcolor icons */ +#define NAUTILUS_ICON_FULLCOLOR_FOLDER "folder" +#define NAUTILUS_ICON_FULLCOLOR_FOLDER_REMOTE "folder-remote" +#define NAUTILUS_ICON_FULLCOLOR_FOLDER_DOCUMENTS "folder-documents" +#define NAUTILUS_ICON_FULLCOLOR_FOLDER_DOWNLOAD "folder-download" +#define NAUTILUS_ICON_FULLCOLOR_FOLDER_MUSIC "folder-music" +#define NAUTILUS_ICON_FULLCOLOR_FOLDER_PICTURES "folder-pictures" +#define NAUTILUS_ICON_FULLCOLOR_FOLDER_PUBLIC_SHARE "folder-publicshare" +#define NAUTILUS_ICON_FULLCOLOR_FOLDER_TEMPLATES "folder-templates" +#define NAUTILUS_ICON_FULLCOLOR_FOLDER_VIDEOS "folder-videos" \ No newline at end of file diff --git a/src/nautilus-keyfile-metadata.c b/src/nautilus-keyfile-metadata.c new file mode 100644 index 0000000..76b4fae --- /dev/null +++ b/src/nautilus-keyfile-metadata.c @@ -0,0 +1,343 @@ +/* + * Nautilus + * + * Copyright (C) 2011 Red Hat, Inc. + * + * Nautilus is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License as + * published by the Free Software Foundation; either version 2 of the + * License, or (at your option) any later version. + * + * Nautilus is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public + * License along with this program; see the file COPYING. If not, + * see . + * + * Authors: Cosimo Cecchi + */ + +#include + +#include "nautilus-keyfile-metadata.h" + +#include "nautilus-directory-notify.h" +#include "nautilus-file-private.h" +#include "nautilus-file-utilities.h" + +#include + +#include +#include +#include + +typedef struct +{ + GKeyFile *keyfile; + guint save_in_idle_id; +} KeyfileMetadataData; + +static GHashTable *data_hash = NULL; + +static KeyfileMetadataData * +keyfile_metadata_data_new (const char *keyfile_filename) +{ + KeyfileMetadataData *data; + GKeyFile *retval; + GError *error = NULL; + + retval = g_key_file_new (); + + g_key_file_load_from_file (retval, + keyfile_filename, + G_KEY_FILE_NONE, + &error); + + if (error != NULL) + { + if (!g_error_matches (error, + G_FILE_ERROR, + G_FILE_ERROR_NOENT)) + { + g_print ("Unable to open the desktop metadata keyfile: %s\n", + error->message); + } + + g_error_free (error); + } + + data = g_slice_new0 (KeyfileMetadataData); + data->keyfile = retval; + + return data; +} + +static void +keyfile_metadata_data_free (KeyfileMetadataData *data) +{ + g_key_file_unref (data->keyfile); + + if (data->save_in_idle_id != 0) + { + g_source_remove (data->save_in_idle_id); + } + + g_slice_free (KeyfileMetadataData, data); +} + +static GKeyFile * +get_keyfile (const char *keyfile_filename) +{ + KeyfileMetadataData *data; + + if (data_hash == NULL) + { + data_hash = g_hash_table_new_full (g_str_hash, + g_str_equal, + g_free, + (GDestroyNotify) keyfile_metadata_data_free); + } + + data = g_hash_table_lookup (data_hash, keyfile_filename); + + if (data == NULL) + { + data = keyfile_metadata_data_new (keyfile_filename); + + g_hash_table_insert (data_hash, + g_strdup (keyfile_filename), + data); + } + + return data->keyfile; +} + +static gboolean +save_in_idle_cb (const gchar *keyfile_filename) +{ + KeyfileMetadataData *data; + gchar *contents; + gsize length; + GError *error = NULL; + + data = g_hash_table_lookup (data_hash, keyfile_filename); + data->save_in_idle_id = 0; + + contents = g_key_file_to_data (data->keyfile, &length, NULL); + + if (contents != NULL) + { + g_file_set_contents (keyfile_filename, + contents, length, + &error); + g_free (contents); + } + + if (error != NULL) + { + g_warning ("Couldn't save the desktop metadata keyfile to disk: %s", + error->message); + g_error_free (error); + } + + return FALSE; +} + +static void +save_in_idle (const char *keyfile_filename) +{ + KeyfileMetadataData *data; + + g_return_if_fail (data_hash != NULL); + + data = g_hash_table_lookup (data_hash, keyfile_filename); + g_return_if_fail (data != NULL); + + if (data->save_in_idle_id != 0) + { + return; + } + + data->save_in_idle_id = g_idle_add_full (G_PRIORITY_DEFAULT_IDLE, + (GSourceFunc) save_in_idle_cb, + g_strdup (keyfile_filename), + g_free); +} + +void +nautilus_keyfile_metadata_set_string (NautilusFile *file, + const char *keyfile_filename, + const gchar *name, + const gchar *key, + const gchar *string) +{ + GKeyFile *keyfile; + + keyfile = get_keyfile (keyfile_filename); + + g_key_file_set_string (keyfile, + name, + key, + string); + + save_in_idle (keyfile_filename); + + if (nautilus_keyfile_metadata_update_from_keyfile (file, keyfile_filename, name)) + { + nautilus_file_changed (file); + } +} + +#define STRV_TERMINATOR "@x-nautilus-desktop-metadata-term@" + +void +nautilus_keyfile_metadata_set_stringv (NautilusFile *file, + const char *keyfile_filename, + const char *name, + const char *key, + const char * const *stringv) +{ + GKeyFile *keyfile; + guint length; + gchar **actual_stringv = NULL; + gboolean free_strv = FALSE; + + keyfile = get_keyfile (keyfile_filename); + + /* if we would be setting a single-length strv, append a fake + * terminator to the array, to be able to differentiate it later from + * the single string case + */ + length = g_strv_length ((gchar **) stringv); + + if (length == 1) + { + actual_stringv = g_malloc0 (3 * sizeof (gchar *)); + actual_stringv[0] = (gchar *) stringv[0]; + actual_stringv[1] = STRV_TERMINATOR; + actual_stringv[2] = NULL; + + length = 2; + free_strv = TRUE; + } + else + { + actual_stringv = (gchar **) stringv; + } + + g_key_file_set_string_list (keyfile, + name, + key, + (const gchar **) actual_stringv, + length); + + save_in_idle (keyfile_filename); + + if (nautilus_keyfile_metadata_update_from_keyfile (file, keyfile_filename, name)) + { + nautilus_file_changed (file); + } + + if (free_strv) + { + g_free (actual_stringv); + } +} + +gboolean +nautilus_keyfile_metadata_update_from_keyfile (NautilusFile *file, + const char *keyfile_filename, + const gchar *name) +{ + gchar **keys, **values; + const gchar *actual_values[2]; + const gchar *key, *value; + gchar *gio_key; + gsize length, values_length; + GKeyFile *keyfile; + GFileInfo *info; + gint idx; + gboolean res; + + keyfile = get_keyfile (keyfile_filename); + + keys = g_key_file_get_keys (keyfile, + name, + &length, + NULL); + + if (keys == NULL) + { + return FALSE; + } + + info = g_file_info_new (); + + for (idx = 0; idx < length; idx++) + { + key = keys[idx]; + values = g_key_file_get_string_list (keyfile, + name, + key, + &values_length, + NULL); + + gio_key = g_strconcat ("metadata::", key, NULL); + + if (values_length < 1) + { + continue; + } + else if (values_length == 1) + { + g_file_info_set_attribute_string (info, + gio_key, + values[0]); + } + else if (values_length == 2) + { + /* deal with the fact that single-length strv are stored + * with an additional terminator in the keyfile string, to differentiate + * them from the regular string case. + */ + value = values[1]; + + if (g_strcmp0 (value, STRV_TERMINATOR) == 0) + { + /* if the 2nd value is the terminator, remove it */ + actual_values[0] = values[0]; + actual_values[1] = NULL; + + g_file_info_set_attribute_stringv (info, + gio_key, + (gchar **) actual_values); + } + else + { + /* otherwise, set it as a regular strv */ + g_file_info_set_attribute_stringv (info, + gio_key, + values); + } + } + else + { + g_file_info_set_attribute_stringv (info, + gio_key, + values); + } + + g_free (gio_key); + g_strfreev (values); + } + + res = nautilus_file_update_metadata_from_info (file, info); + + g_strfreev (keys); + g_object_unref (info); + + return res; +} diff --git a/src/nautilus-keyfile-metadata.h b/src/nautilus-keyfile-metadata.h new file mode 100644 index 0000000..9f471ca --- /dev/null +++ b/src/nautilus-keyfile-metadata.h @@ -0,0 +1,43 @@ +/* + * Nautilus + * + * Copyright (C) 2011 Red Hat, Inc. + * + * Nautilus is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License as + * published by the Free Software Foundation; either version 2 of the + * License, or (at your option) any later version. + * + * Nautilus is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public + * License along with this program; see the file COPYING. If not, + * see . + * + * Authors: Cosimo Cecchi + */ + +#pragma once + +#include + +#include "nautilus-types.h" + +void nautilus_keyfile_metadata_set_string (NautilusFile *file, + const char *keyfile_filename, + const gchar *name, + const gchar *key, + const gchar *string); + +void nautilus_keyfile_metadata_set_stringv (NautilusFile *file, + const char *keyfile_filename, + const char *name, + const char *key, + const char * const *stringv); + +gboolean nautilus_keyfile_metadata_update_from_keyfile (NautilusFile *file, + const char *keyfile_filename, + const gchar *name); diff --git a/src/nautilus-label-cell.c b/src/nautilus-label-cell.c new file mode 100644 index 0000000..e8b62df --- /dev/null +++ b/src/nautilus-label-cell.c @@ -0,0 +1,158 @@ +/* + * Copyright (C) 2022 António Fernandes + * + * SPDX-License-Identifier: GPL-3.0-or-later + */ + +/* Needed for NautilusColumn (full GType). */ +#include + +#include "nautilus-label-cell.h" + +struct _NautilusLabelCell +{ + NautilusViewCell parent_instance; + + GSignalGroup *item_signal_group; + + NautilusColumn *column; + GQuark attribute_q; + + GtkLabel *label; + + gboolean show_snippet; +}; + +G_DEFINE_TYPE (NautilusLabelCell, nautilus_label_cell, NAUTILUS_TYPE_VIEW_CELL) + +enum +{ + PROP_0, + PROP_COLUMN, + N_PROPS +}; + +static GParamSpec *properties[N_PROPS] = { NULL, }; + +static void +on_file_changed (NautilusLabelCell *self) +{ + g_autoptr (NautilusViewItem) item = NULL; + NautilusFile *file; + g_autofree gchar *string = NULL; + + item = nautilus_view_cell_get_item (NAUTILUS_VIEW_CELL (self)); + g_return_if_fail (item != NULL); + file = nautilus_view_item_get_file (item); + + string = nautilus_file_get_string_attribute_q (file, self->attribute_q); + gtk_label_set_text (self->label, string); +} + +static void +nautilus_label_cell_set_property (GObject *object, + guint prop_id, + const GValue *value, + GParamSpec *pspec) +{ + NautilusLabelCell *self = NAUTILUS_LABEL_CELL (object); + + switch (prop_id) + { + case PROP_COLUMN: + { + self->column = g_value_get_object (value); + } + break; + + default: + { + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); + } + } +} + +static void +nautilus_label_cell_init (NautilusLabelCell *self) +{ + GtkWidget *child; + + child = gtk_label_new (NULL); + adw_bin_set_child (ADW_BIN (self), child); + gtk_widget_set_valign (child, GTK_ALIGN_CENTER); + gtk_widget_add_css_class (child, "dim-label"); + self->label = GTK_LABEL (child); + + /* Connect automatically to an item. */ + self->item_signal_group = g_signal_group_new (NAUTILUS_TYPE_VIEW_ITEM); + g_signal_group_connect_swapped (self->item_signal_group, "file-changed", + (GCallback) on_file_changed, self); + g_signal_connect_object (self->item_signal_group, "bind", + (GCallback) on_file_changed, self, + G_CONNECT_SWAPPED); + g_object_bind_property (self, "item", + self->item_signal_group, "target", + G_BINDING_SYNC_CREATE); +} + +static void +nautilus_label_cell_constructed (GObject *object) +{ + NautilusLabelCell *self = NAUTILUS_LABEL_CELL (object); + g_autofree gchar *column_name = NULL; + gfloat xalign; + + G_OBJECT_CLASS (nautilus_label_cell_parent_class)->constructed (object); + + g_object_get (self->column, + "attribute_q", &self->attribute_q, + "name", &column_name, + "xalign", &xalign, + NULL); + gtk_label_set_xalign (self->label, xalign); + + if (g_strcmp0 (column_name, "permissions") == 0) + { + gtk_widget_add_css_class (GTK_WIDGET (self->label), "monospace"); + } + else + { + gtk_widget_add_css_class (GTK_WIDGET (self->label), "numeric"); + } +} + +static void +nautilus_label_cell_finalize (GObject *object) +{ + NautilusLabelCell *self = (NautilusLabelCell *) object; + + g_object_unref (self->item_signal_group); + G_OBJECT_CLASS (nautilus_label_cell_parent_class)->finalize (object); +} + +static void +nautilus_label_cell_class_init (NautilusLabelCellClass *klass) +{ + GObjectClass *object_class = G_OBJECT_CLASS (klass); + + object_class->set_property = nautilus_label_cell_set_property; + object_class->constructed = nautilus_label_cell_constructed; + object_class->finalize = nautilus_label_cell_finalize; + + properties[PROP_COLUMN] = g_param_spec_object ("column", + "", "", + NAUTILUS_TYPE_COLUMN, + G_PARAM_WRITABLE | G_PARAM_CONSTRUCT_ONLY | G_PARAM_STATIC_STRINGS); + + g_object_class_install_properties (object_class, N_PROPS, properties); +} + +NautilusViewCell * +nautilus_label_cell_new (NautilusListBase *view, + NautilusColumn *column) +{ + return NAUTILUS_VIEW_CELL (g_object_new (NAUTILUS_TYPE_LABEL_CELL, + "view", view, + "column", column, + NULL)); +} diff --git a/src/nautilus-label-cell.h b/src/nautilus-label-cell.h new file mode 100644 index 0000000..b476ef9 --- /dev/null +++ b/src/nautilus-label-cell.h @@ -0,0 +1,23 @@ +/* + * Copyright (C) 2022 António Fernandes + * + * SPDX-License-Identifier: GPL-3.0-or-later + */ + +#pragma once + +/* Needed for NautilusColumn (typedef only). */ +#include "nautilus-types.h" + +#include "nautilus-view-cell.h" + +G_BEGIN_DECLS + +#define NAUTILUS_TYPE_LABEL_CELL (nautilus_label_cell_get_type()) + +G_DECLARE_FINAL_TYPE (NautilusLabelCell, nautilus_label_cell, NAUTILUS, LABEL_CELL, NautilusViewCell) + +NautilusViewCell * nautilus_label_cell_new (NautilusListBase *view, + NautilusColumn *column); + +G_END_DECLS diff --git a/src/nautilus-lib-self-check-functions.c b/src/nautilus-lib-self-check-functions.c new file mode 100644 index 0000000..11f9288 --- /dev/null +++ b/src/nautilus-lib-self-check-functions.c @@ -0,0 +1,35 @@ +/* + * nautilus-lib-self-check-functions.c: Wrapper for all self check functions + * in Nautilus proper. + * + * Copyright (C) 2000 Eazel, Inc. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License as + * published by the Free Software Foundation; either version 2 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 warranty of + * MERCHANTABILITY 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 . + * + * Author: Darin Adler + */ + +#include + +#if !defined (NAUTILUS_OMIT_SELF_CHECK) + +#include "nautilus-lib-self-check-functions.h" + +void +nautilus_run_lib_self_checks (void) +{ + NAUTILUS_LIB_FOR_EACH_SELF_CHECK_FUNCTION (EEL_CALL_SELF_CHECK_FUNCTION) +} + +#endif /* ! NAUTILUS_OMIT_SELF_CHECK */ diff --git a/src/nautilus-lib-self-check-functions.h b/src/nautilus-lib-self-check-functions.h new file mode 100644 index 0000000..93850c9 --- /dev/null +++ b/src/nautilus-lib-self-check-functions.h @@ -0,0 +1,48 @@ +/* + nautilus-lib-self-check-functions.h: Wrapper and prototypes for all + self-check functions in libnautilus. + + Copyright (C) 2000 Eazel, Inc. + + This program is free software; you can redistribute it and/or + modify it under the terms of the GNU General Public License as + published by the Free Software Foundation; either version 2 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 warranty of + MERCHANTABILITY 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 . + + Author: Darin Adler +*/ + +#pragma once + +#include + +void nautilus_run_lib_self_checks (void); + +/* Putting the prototypes for these self-check functions in each + header file for the files they are defined in would make compiling + the self-check framework take way too long (since one file would + have to include everything). + + So we put the list of functions here instead. + + Instead of just putting prototypes here, we put this macro that + can be used to do operations on the whole list of functions. +*/ + +#define NAUTILUS_LIB_FOR_EACH_SELF_CHECK_FUNCTION(macro) \ + macro (nautilus_self_check_file_utilities) \ + macro (nautilus_self_check_file_operations) \ + macro (nautilus_self_check_directory) \ + macro (nautilus_self_check_file) \ +/* Add new self-check functions to the list above this line. */ + +/* Generate prototypes for all the functions. */ +NAUTILUS_LIB_FOR_EACH_SELF_CHECK_FUNCTION (EEL_SELF_CHECK_FUNCTION_PROTOTYPE) diff --git a/src/nautilus-list-base-private.h b/src/nautilus-list-base-private.h new file mode 100644 index 0000000..96944d5 --- /dev/null +++ b/src/nautilus-list-base-private.h @@ -0,0 +1,33 @@ +/* + * Copyright (C) 2022 The GNOME project contributors + * + * SPDX-License-Identifier: GPL-3.0-or-later + */ + +#pragma once + +#include "nautilus-list-base.h" +#include "nautilus-view-model.h" +#include "nautilus-view-cell.h" + +/* + * Private header to be included only by subclasses. + */ + +G_BEGIN_DECLS + +/* Methods */ +NautilusViewModel *nautilus_list_base_get_model (NautilusListBase *self); +void nautilus_list_base_set_icon_size (NautilusListBase *self, + gint icon_size); +void nautilus_list_base_setup_gestures (NautilusListBase *self); + +/* Shareable helpers */ +void set_directory_sort_metadata (NautilusFile *file, + const gchar *metadata_name, + gboolean reversed); +const NautilusFileSortType get_sorts_type_from_metadata_text (const char *metadata_name); +void setup_cell_common (GtkListItem *listitem, + NautilusViewCell *cell); + +G_END_DECLS diff --git a/src/nautilus-list-base.c b/src/nautilus-list-base.c new file mode 100644 index 0000000..6b8d2f6 --- /dev/null +++ b/src/nautilus-list-base.c @@ -0,0 +1,1892 @@ +/* + * Copyright (C) 2022 The GNOME project contributors + * + * SPDX-License-Identifier: GPL-3.0-or-later + */ + +#include "nautilus-list-base-private.h" + +#include "nautilus-clipboard.h" +#include "nautilus-dnd.h" +#include "nautilus-view-cell.h" +#include "nautilus-view-item.h" +#include "nautilus-view-model.h" +#include "nautilus-files-view.h" +#include "nautilus-files-view-dnd.h" +#include "nautilus-file.h" +#include "nautilus-file-operations.h" +#include "nautilus-metadata.h" +#include "nautilus-global-preferences.h" +#include "nautilus-thumbnails.h" + +#ifdef GDK_WINDOWING_X11 +#include +#endif + +/** + * NautilusListBase: + * + * Abstract class containing shared code for #NautilusFilesView implementations + * using a #GtkListBase-derived widget (e.g. GtkGridView, GtkColumnView) which + * takes a #NautilusViewModel instance as its model and and a #NautilusViewCell + * instance as #GtkListItem:child. + * + * It has been has been created to avoid code duplication in implementations, + * while keeping #NautilusFilesView implementation-agnostic (should the need for + * non-#GtkListBase views arise). + */ + +typedef struct _NautilusListBasePrivate NautilusListBasePrivate; +struct _NautilusListBasePrivate +{ + NautilusViewModel *model; + + GList *cut_files; + + guint scroll_to_file_handle_id; + guint prioritize_thumbnailing_handle_id; + GtkAdjustment *vadjustment; + + gboolean single_click_mode; + gboolean activate_on_release; + gboolean deny_background_click; + + GdkDragAction drag_item_action; + GdkDragAction drag_view_action; + graphene_point_t hover_start_point; + guint hover_timer_id; + GtkDropTarget *view_drop_target; +}; + +G_DEFINE_ABSTRACT_TYPE_WITH_PRIVATE (NautilusListBase, nautilus_list_base, NAUTILUS_TYPE_FILES_VIEW) + +typedef struct +{ + const NautilusFileSortType sort_type; + const gchar *metadata_name; +} SortConstants; + +static const SortConstants sorts_constants[] = +{ + { + NAUTILUS_FILE_SORT_BY_DISPLAY_NAME, + "name", + }, + { + NAUTILUS_FILE_SORT_BY_SIZE, + "size", + }, + { + NAUTILUS_FILE_SORT_BY_TYPE, + "type", + }, + { + NAUTILUS_FILE_SORT_BY_MTIME, + "date_modified", + }, + { + NAUTILUS_FILE_SORT_BY_ATIME, + "date_accessed", + }, + { + NAUTILUS_FILE_SORT_BY_BTIME, + "date_created", + }, + { + NAUTILUS_FILE_SORT_BY_TRASHED_TIME, + "trashed_on", + }, + { + NAUTILUS_FILE_SORT_BY_SEARCH_RELEVANCE, + "search_relevance", + }, + { + NAUTILUS_FILE_SORT_BY_RECENCY, + "recency", + }, +}; + +static inline NautilusViewItem * +get_view_item (GListModel *model, + guint position) +{ + return NAUTILUS_VIEW_ITEM (g_list_model_get_item (model, position)); +} + +static const SortConstants * +get_sorts_constants_from_sort_type (NautilusFileSortType sort_type) +{ + guint i; + + for (i = 0; i < G_N_ELEMENTS (sorts_constants); i++) + { + if (sort_type == sorts_constants[i].sort_type) + { + return &sorts_constants[i]; + } + } + + return &sorts_constants[0]; +} + +static const SortConstants * +get_sorts_constants_from_metadata_text (const char *metadata_name) +{ + guint i; + + for (i = 0; i < G_N_ELEMENTS (sorts_constants); i++) + { + if (g_strcmp0 (sorts_constants[i].metadata_name, metadata_name) == 0) + { + return &sorts_constants[i]; + } + } + + return &sorts_constants[0]; +} + +const NautilusFileSortType +get_sorts_type_from_metadata_text (const char *metadata_name) +{ + return get_sorts_constants_from_metadata_text (metadata_name)->sort_type; +} + +static const SortConstants * +get_default_sort_order (NautilusFile *file, + gboolean *reversed) +{ + NautilusFileSortType sort_type; + + sort_type = nautilus_file_get_default_sort_type (file, reversed); + + return get_sorts_constants_from_sort_type (sort_type); +} + +static const SortConstants * +get_directory_sort_by (NautilusFile *file, + gboolean *reversed) +{ + const SortConstants *default_sort; + g_autofree char *sort_by = NULL; + + default_sort = get_default_sort_order (file, reversed); + g_return_val_if_fail (default_sort != NULL, NULL); + + if (default_sort->sort_type == NAUTILUS_FILE_SORT_BY_RECENCY || + default_sort->sort_type == NAUTILUS_FILE_SORT_BY_TRASHED_TIME || + default_sort->sort_type == NAUTILUS_FILE_SORT_BY_SEARCH_RELEVANCE) + { + /* These defaults are important. Ignore metadata. */ + return default_sort; + } + + sort_by = nautilus_file_get_metadata (file, + NAUTILUS_METADATA_KEY_ICON_VIEW_SORT_BY, + default_sort->metadata_name); + + *reversed = nautilus_file_get_boolean_metadata (file, + NAUTILUS_METADATA_KEY_ICON_VIEW_SORT_REVERSED, + *reversed); + + return get_sorts_constants_from_metadata_text (sort_by); +} + +void +set_directory_sort_metadata (NautilusFile *file, + const gchar *metadata_name, + gboolean reversed) +{ + const SortConstants *default_sort; + gboolean default_reversed; + + default_sort = get_default_sort_order (file, &default_reversed); + + nautilus_file_set_metadata (file, + NAUTILUS_METADATA_KEY_ICON_VIEW_SORT_BY, + default_sort->metadata_name, + metadata_name); + nautilus_file_set_boolean_metadata (file, + NAUTILUS_METADATA_KEY_ICON_VIEW_SORT_REVERSED, + default_reversed, + reversed); +} + +static void +update_sort_order_from_metadata_and_preferences (NautilusListBase *self) +{ + const SortConstants *default_directory_sort; + GActionGroup *view_action_group; + gboolean reversed; + + default_directory_sort = get_directory_sort_by (nautilus_files_view_get_directory_as_file (NAUTILUS_FILES_VIEW (self)), + &reversed); + view_action_group = nautilus_files_view_get_action_group (NAUTILUS_FILES_VIEW (self)); + g_action_group_change_action_state (view_action_group, + "sort", + g_variant_new ("(sb)", + default_directory_sort->metadata_name, + reversed)); +} + +void +nautilus_list_base_set_icon_size (NautilusListBase *self, + gint icon_size) +{ + GListModel *model; + guint n_items; + + model = G_LIST_MODEL (nautilus_list_base_get_model (self)); + + n_items = g_list_model_get_n_items (model); + for (guint i = 0; i < n_items; i++) + { + g_autoptr (NautilusViewItem) current_item = NULL; + + current_item = get_view_item (model, i); + nautilus_view_item_set_icon_size (current_item, icon_size); + } +} + +static void +set_focus_item (NautilusListBase *self, + NautilusViewItem *item) +{ + NautilusListBasePrivate *priv = nautilus_list_base_get_instance_private (self); + GtkWidget *item_widget = nautilus_view_item_get_item_ui (item); + GtkWidget *parent; + + if (item_widget == NULL) + { + /* We can't set the focus if the item isn't created yet. Return early to prevent a crash */ + return; + } + + parent = gtk_widget_get_parent (item_widget); + + if (!gtk_widget_grab_focus (parent)) + { + /* In GtkColumnView, the parent is a cell; its parent is the row. */ + gtk_widget_grab_focus (gtk_widget_get_parent (parent)); + } + + /* HACK: Grabbing focus is not enough for the listbase item tracker to + * acknowledge it. So, poke the internal actions to fix the bug reported + * in https://gitlab.gnome.org/GNOME/nautilus/-/issues/2294 */ + gtk_widget_activate_action (item_widget, + "list.select-item", + "(ubb)", + nautilus_view_model_get_index (priv->model, item), + FALSE, FALSE); +} + +static guint +nautilus_list_base_get_icon_size (NautilusListBase *self) +{ + return NAUTILUS_LIST_BASE_CLASS (G_OBJECT_GET_CLASS (self))->get_icon_size (self); +} + +/* GtkListBase changes selection only with the primary button, and only after + * release. But we need to anticipate selection earlier if we are to activate it + * or open its context menu. This helper should be used in these situations if + * it's desirable to act on a multi-item selection, because it preserves it. */ +static void +select_single_item_if_not_selected (NautilusListBase *self, + NautilusViewItem *item) +{ + NautilusViewModel *model; + guint position; + + model = nautilus_list_base_get_model (self); + position = nautilus_view_model_get_index (model, item); + if (!gtk_selection_model_is_selected (GTK_SELECTION_MODEL (model), position)) + { + gtk_selection_model_select_item (GTK_SELECTION_MODEL (model), position, TRUE); + set_focus_item (self, item); + } +} + +static void +activate_selection_on_click (NautilusListBase *self, + gboolean open_in_new_tab) +{ + g_autolist (NautilusFile) selection = NULL; + NautilusOpenFlags flags = 0; + NautilusFilesView *files_view = NAUTILUS_FILES_VIEW (self); + + selection = nautilus_view_get_selection (NAUTILUS_VIEW (self)); + if (open_in_new_tab) + { + flags |= NAUTILUS_OPEN_FLAG_NEW_TAB; + flags |= NAUTILUS_OPEN_FLAG_DONT_MAKE_ACTIVE; + } + nautilus_files_view_activate_files (files_view, selection, flags, TRUE); +} + +static void +open_context_menu_on_press (NautilusListBase *self, + NautilusViewCell *cell, + gdouble x, + gdouble y) +{ + g_autoptr (NautilusViewItem) item = NULL; + gdouble view_x, view_y; + + item = nautilus_view_cell_get_item (cell); + g_return_if_fail (item != NULL); + + /* Antecipate selection, if necessary. */ + select_single_item_if_not_selected (self, item); + + gtk_widget_translate_coordinates (GTK_WIDGET (cell), GTK_WIDGET (self), + x, y, + &view_x, &view_y); + nautilus_files_view_pop_up_selection_context_menu (NAUTILUS_FILES_VIEW (self), + view_x, view_y); +} + +static void +rubberband_set_state (NautilusListBase *self, + gboolean enabled) +{ + /* This is a temporary workaround to deal with the rubberbanding issues + * during a drag and drop. Disable rubberband on item press and enable + * rubberbnad on item release/stop. The permanent solution for this is + * https://gitlab.gnome.org/GNOME/gtk/-/merge_requests/4831 */ + + GtkWidget *view; + + view = NAUTILUS_LIST_BASE_CLASS (G_OBJECT_GET_CLASS (self))->get_view_ui (self); + if (GTK_IS_GRID_VIEW (view)) + { + gtk_grid_view_set_enable_rubberband (GTK_GRID_VIEW (view), enabled); + } + else if (GTK_IS_COLUMN_VIEW (view)) + { + gtk_column_view_set_enable_rubberband (GTK_COLUMN_VIEW (view), enabled); + } +} + +static void +on_item_click_pressed (GtkGestureClick *gesture, + gint n_press, + gdouble x, + gdouble y, + gpointer user_data) +{ + NautilusViewCell *cell = user_data; + g_autoptr (NautilusListBase) self = nautilus_view_cell_get_view (cell); + NautilusListBasePrivate *priv = nautilus_list_base_get_instance_private (self); + guint button; + GdkModifierType modifiers; + gboolean selection_mode; + + button = gtk_gesture_single_get_current_button (GTK_GESTURE_SINGLE (gesture)); + modifiers = gtk_event_controller_get_current_event_state (GTK_EVENT_CONTROLLER (gesture)); + selection_mode = (modifiers & (GDK_CONTROL_MASK | GDK_SHIFT_MASK)); + + /* Before anything else, store event state to be read by other handlers. */ + priv->deny_background_click = TRUE; + priv->activate_on_release = (priv->single_click_mode && + button == GDK_BUTTON_PRIMARY && + n_press == 1 && + !selection_mode); + + rubberband_set_state (self, FALSE); + + /* It's safe to claim event sequence on press in the following cases because + * they don't interfere with touch scrolling. */ + if (button == GDK_BUTTON_PRIMARY && n_press == 2 && !priv->single_click_mode) + { + /* If Ctrl + Shift are held, we don't want to activate selection. But + * we still need to claim the event, otherwise GtkListBase's default + * gesture is going to trigger activation. */ + if (!selection_mode) + { + activate_selection_on_click (self, FALSE); + } + gtk_gesture_set_state (GTK_GESTURE (gesture), GTK_EVENT_SEQUENCE_CLAIMED); + } + else if (button == GDK_BUTTON_MIDDLE && n_press == 1) + { + g_autoptr (NautilusViewItem) item = nautilus_view_cell_get_item (cell); + g_return_if_fail (item != NULL); + + /* Anticipate selection, if necessary, to activate it. */ + select_single_item_if_not_selected (self, item); + activate_selection_on_click (self, TRUE); + gtk_gesture_set_state (GTK_GESTURE (gesture), GTK_EVENT_SEQUENCE_CLAIMED); + } + else if (button == GDK_BUTTON_SECONDARY && n_press == 1) + { + open_context_menu_on_press (self, cell, x, y); + gtk_gesture_set_state (GTK_GESTURE (gesture), GTK_EVENT_SEQUENCE_CLAIMED); + } +} + +static void +on_item_click_released (GtkGestureClick *gesture, + gint n_press, + gdouble x, + gdouble y, + gpointer user_data) +{ + NautilusViewCell *cell = user_data; + g_autoptr (NautilusListBase) self = nautilus_view_cell_get_view (cell); + NautilusListBasePrivate *priv = nautilus_list_base_get_instance_private (self); + + if (priv->activate_on_release) + { + NautilusViewModel *model; + g_autoptr (NautilusViewItem) item = NULL; + guint i; + + model = nautilus_list_base_get_model (self); + item = nautilus_view_cell_get_item (cell); + g_return_if_fail (item != NULL); + i = nautilus_view_model_get_index (model, item); + + /* Anticipate selection, enforcing single selection of target item. */ + gtk_selection_model_select_item (GTK_SELECTION_MODEL (model), i, TRUE); + + activate_selection_on_click (self, FALSE); + } + + rubberband_set_state (self, TRUE); + priv->activate_on_release = FALSE; + priv->deny_background_click = FALSE; +} + +static void +on_item_click_stopped (GtkGestureClick *gesture, + gpointer user_data) +{ + NautilusViewCell *cell = user_data; + g_autoptr (NautilusListBase) self = nautilus_view_cell_get_view (cell); + NautilusListBasePrivate *priv = nautilus_list_base_get_instance_private (self); + + rubberband_set_state (self, TRUE); + priv->activate_on_release = FALSE; + priv->deny_background_click = FALSE; +} + +static void +on_view_click_pressed (GtkGestureClick *gesture, + gint n_press, + gdouble x, + gdouble y, + gpointer user_data) +{ + NautilusListBase *self = user_data; + NautilusListBasePrivate *priv = nautilus_list_base_get_instance_private (self); + guint button; + GdkModifierType modifiers; + gboolean selection_mode; + + if (priv->deny_background_click) + { + /* Item was clicked. */ + gtk_gesture_set_state (GTK_GESTURE (gesture), GTK_EVENT_SEQUENCE_DENIED); + return; + } + + /* We are overriding many of the gestures for the views so let's make sure to + * grab the focus in order to make rubberbanding and background click work */ + gtk_widget_grab_focus (GTK_WIDGET (self)); + + /* Don't interfere with GtkListBase default selection handling when + * holding Ctrl and Shift. */ + modifiers = gtk_event_controller_get_current_event_state (GTK_EVENT_CONTROLLER (gesture)); + selection_mode = (modifiers & (GDK_CONTROL_MASK | GDK_SHIFT_MASK)); + if (!selection_mode) + { + nautilus_view_set_selection (NAUTILUS_VIEW (self), NULL); + } + + button = gtk_gesture_single_get_current_button (GTK_GESTURE_SINGLE (gesture)); + if (button == GDK_BUTTON_SECONDARY) + { + GtkWidget *event_widget; + gdouble view_x; + gdouble view_y; + + event_widget = gtk_event_controller_get_widget (GTK_EVENT_CONTROLLER (gesture)); + gtk_widget_translate_coordinates (event_widget, GTK_WIDGET (self), + x, y, + &view_x, &view_y); + nautilus_files_view_pop_up_background_context_menu (NAUTILUS_FILES_VIEW (self), + view_x, view_y); + } +} + +static void +on_item_longpress_pressed (GtkGestureLongPress *gesture, + gdouble x, + gdouble y, + gpointer user_data) +{ + NautilusViewCell *cell = user_data; + g_autoptr (NautilusListBase) self = nautilus_view_cell_get_view (cell); + + open_context_menu_on_press (self, cell, x, y); + gtk_gesture_set_state (GTK_GESTURE (gesture), GTK_EVENT_SEQUENCE_CLAIMED); +} + +static void +on_view_longpress_pressed (GtkGestureLongPress *gesture, + gdouble x, + gdouble y, + gpointer user_data) +{ + NautilusListBase *self = NAUTILUS_LIST_BASE (user_data); + GtkWidget *event_widget; + gdouble view_x; + gdouble view_y; + + event_widget = gtk_event_controller_get_widget (GTK_EVENT_CONTROLLER (gesture)); + + gtk_widget_translate_coordinates (event_widget, + GTK_WIDGET (self), + x, y, &view_x, &view_y); + + nautilus_view_set_selection (NAUTILUS_VIEW (self), NULL); + nautilus_files_view_pop_up_background_context_menu (NAUTILUS_FILES_VIEW (self), + view_x, view_y); +} + +static GdkContentProvider * +on_item_drag_prepare (GtkDragSource *source, + double x, + double y, + gpointer user_data) +{ + NautilusViewCell *cell = user_data; + g_autoptr (NautilusListBase) self = nautilus_view_cell_get_view (cell); + GtkWidget *view_ui; + g_autolist (NautilusFile) selection = NULL; + g_autoslist (GFile) file_list = NULL; + g_autoptr (GdkPaintable) paintable = NULL; + g_autoptr (NautilusViewItem) item = nautilus_view_cell_get_item (cell); + GdkDragAction actions; + gint scale_factor; + + /* Anticipate selection, if necessary, for dragging the clicked item. */ + select_single_item_if_not_selected (self, item); + + selection = nautilus_view_get_selection (NAUTILUS_VIEW (self)); + g_return_val_if_fail (selection != NULL, NULL); + + gtk_gesture_set_state (GTK_GESTURE (source), GTK_EVENT_SEQUENCE_CLAIMED); + + actions = GDK_ACTION_ALL | GDK_ACTION_ASK; + + for (GList *l = selection; l != NULL; l = l->next) + { + /* Convert to GTK_TYPE_FILE_LIST, which is assumed to be a GSList. */ + file_list = g_slist_prepend (file_list, nautilus_file_get_activation_location (l->data)); + + if (!nautilus_file_can_delete (l->data)) + { + actions &= ~GDK_ACTION_MOVE; + } + } + file_list = g_slist_reverse (file_list); + + gtk_drag_source_set_actions (source, actions); + + scale_factor = gtk_widget_get_scale_factor (GTK_WIDGET (self)); + paintable = get_paintable_for_drag_selection (selection, scale_factor); + + view_ui = NAUTILUS_LIST_BASE_CLASS (G_OBJECT_GET_CLASS (self))->get_view_ui (self); + if (GTK_IS_GRID_VIEW (view_ui)) + { + x = x * NAUTILUS_DRAG_SURFACE_ICON_SIZE / nautilus_list_base_get_icon_size (self); + y = y * NAUTILUS_DRAG_SURFACE_ICON_SIZE / nautilus_list_base_get_icon_size (self); + } + else + { + x = 0; + y = 0; + } + + gtk_drag_source_set_icon (source, paintable, x, y); + + return gdk_content_provider_new_typed (GDK_TYPE_FILE_LIST, file_list); +} + +static gboolean +hover_timer (gpointer user_data) +{ + NautilusViewCell *cell = user_data; + g_autoptr (NautilusListBase) self = nautilus_view_cell_get_view (cell); + NautilusListBasePrivate *priv = nautilus_list_base_get_instance_private (self); + g_autoptr (NautilusViewItem) item = nautilus_view_cell_get_item (cell); + g_autofree gchar *uri = NULL; + + priv->hover_timer_id = 0; + + if (priv->drag_item_action == 0) + { + /* If we aren't able to dropped don't change the location. This stops + * drops onto themselves, and another unnecessary drops. */ + return G_SOURCE_REMOVE; + } + + uri = nautilus_file_get_uri (nautilus_view_item_get_file (item)); + nautilus_files_view_handle_hover (NAUTILUS_FILES_VIEW (self), uri); + + return G_SOURCE_REMOVE; +} + +static void +on_item_drag_hover_enter (GtkDropControllerMotion *controller, + gdouble x, + gdouble y, + gpointer user_data) +{ + NautilusViewCell *cell = user_data; + g_autoptr (NautilusListBase) self = nautilus_view_cell_get_view (cell); + NautilusListBasePrivate *priv = nautilus_list_base_get_instance_private (self); + + priv->hover_start_point.x = x; + priv->hover_start_point.y = y; +} + +static void +on_item_drag_hover_leave (GtkDropControllerMotion *controller, + gpointer user_data) +{ + NautilusViewCell *cell = user_data; + g_autoptr (NautilusListBase) self = nautilus_view_cell_get_view (cell); + NautilusListBasePrivate *priv = nautilus_list_base_get_instance_private (self); + + g_clear_handle_id (&priv->hover_timer_id, g_source_remove); +} + +static void +on_item_drag_hover_motion (GtkDropControllerMotion *controller, + gdouble x, + gdouble y, + gpointer user_data) +{ + NautilusViewCell *cell = user_data; + g_autoptr (NautilusListBase) self = nautilus_view_cell_get_view (cell); + NautilusListBasePrivate *priv = nautilus_list_base_get_instance_private (self); + graphene_point_t start = priv->hover_start_point; + + /* This condition doubles in two roles: + * - If the timeout hasn't started yet, to ensure the pointer has entered + * deep enough into the cell before starting the timeout to switch; + * - If the timeout has already started, to reset it if the pointer is + * moving a lot. + * Both serve to prevent accidental triggering of switch-on-hover. */ + if (gtk_drag_check_threshold (GTK_WIDGET (cell), start.x, start.y, x, y)) + { + g_clear_handle_id (&priv->hover_timer_id, g_source_remove); + priv->hover_timer_id = g_timeout_add (HOVER_TIMEOUT, hover_timer, cell); + priv->hover_start_point.x = x; + priv->hover_start_point.y = y; + } +} + +static GdkDragAction +get_preferred_action (NautilusFile *target_file, + const GValue *value) +{ + GdkDragAction action = 0; + + if (value == NULL) + { + action = nautilus_dnd_get_preferred_action (target_file, NULL); + } + else if (G_VALUE_HOLDS (value, GDK_TYPE_FILE_LIST)) + { + GSList *source_file_list = g_value_get_boxed (value); + if (source_file_list != NULL) + { + action = nautilus_dnd_get_preferred_action (target_file, source_file_list->data); + } + else + { + action = nautilus_dnd_get_preferred_action (target_file, NULL); + } + } + else if (G_VALUE_HOLDS (value, G_TYPE_STRING)) + { + action = GDK_ACTION_COPY; + } + + return action; +} + +static void +real_perform_drop (NautilusListBase *self, + const GValue *value, + GdkDragAction action, + GFile *target_location) +{ + g_autofree gchar *target_uri = g_file_get_uri (target_location); + + if (!gdk_drag_action_is_unique (action)) + { + /* TODO: Implement */ + } + else if (G_VALUE_HOLDS (value, G_TYPE_STRING)) + { + nautilus_files_view_handle_text_drop (NAUTILUS_FILES_VIEW (self), + g_value_get_string (value), + target_uri, action); + } + else if (G_VALUE_HOLDS (value, GDK_TYPE_FILE_LIST)) + { + GSList *source_file_list = g_value_get_boxed (value); + GList *source_uri_list = NULL; + + for (GSList *l = source_file_list; l != NULL; l = l->next) + { + source_uri_list = g_list_prepend (source_uri_list, g_file_get_uri (l->data)); + } + source_uri_list = g_list_reverse (source_uri_list); + + nautilus_files_view_drop_proxy_received_uris (NAUTILUS_FILES_VIEW (self), + source_uri_list, + target_uri, + action); + g_list_free_full (source_uri_list, g_free); + } +} + +static GdkDragAction +on_item_drag_enter (GtkDropTarget *target, + double x, + double y, + gpointer user_data) +{ + NautilusViewCell *cell = user_data; + g_autoptr (NautilusListBase) self = nautilus_view_cell_get_view (cell); + NautilusListBasePrivate *priv = nautilus_list_base_get_instance_private (self); + g_autoptr (NautilusViewItem) item = NULL; + const GValue *value; + g_autoptr (NautilusFile) dest_file = NULL; + + /* Reset action cache. */ + priv->drag_item_action = 0; + + item = nautilus_view_cell_get_item (cell); + if (item == NULL) + { + gtk_drop_target_reject (target); + return 0; + } + + dest_file = nautilus_file_ref (nautilus_view_item_get_file (item)); + + if (!nautilus_file_is_archive (dest_file) && !nautilus_file_is_directory (dest_file)) + { + gtk_drop_target_reject (target); + return 0; + } + + value = gtk_drop_target_get_value (target); + priv->drag_item_action = get_preferred_action (dest_file, value); + if (priv->drag_item_action == 0) + { + gtk_drop_target_reject (target); + return 0; + } + + nautilus_view_item_set_drag_accept (item, TRUE); + return priv->drag_item_action; +} + +static void +on_item_drag_value_notify (GObject *object, + GParamSpec *pspec, + gpointer user_data) +{ + GtkDropTarget *target = GTK_DROP_TARGET (object); + NautilusViewCell *cell = user_data; + g_autoptr (NautilusListBase) self = nautilus_view_cell_get_view (cell); + NautilusListBasePrivate *priv = nautilus_list_base_get_instance_private (self); + const GValue *value; + g_autoptr (NautilusViewItem) item = NULL; + + value = gtk_drop_target_get_value (target); + if (value == NULL) + { + return; + } + + item = nautilus_view_cell_get_item (cell); + g_return_if_fail (NAUTILUS_IS_VIEW_ITEM (item)); + + priv->drag_item_action = get_preferred_action (nautilus_view_item_get_file (item), value); +} + +static GdkDragAction +on_item_drag_motion (GtkDropTarget *target, + double x, + double y, + gpointer user_data) +{ + NautilusViewCell *cell = user_data; + g_autoptr (NautilusListBase) self = nautilus_view_cell_get_view (cell); + NautilusListBasePrivate *priv = nautilus_list_base_get_instance_private (self); + + /* There's a bug in GtkDropTarget where motion overrides enter + * so until we fix that let's just return the action that we already + * received from enter*/ + + return priv->drag_item_action; +} + +static void +on_item_drag_leave (GtkDropTarget *dest, + gpointer user_data) +{ + NautilusViewCell *cell = user_data; + g_autoptr (NautilusViewItem) item = nautilus_view_cell_get_item (cell); + + nautilus_view_item_set_drag_accept (item, FALSE); +} + +static gboolean +on_item_drop (GtkDropTarget *target, + const GValue *value, + double x, + double y, + gpointer user_data) +{ + NautilusViewCell *cell = user_data; + g_autoptr (NautilusListBase) self = nautilus_view_cell_get_view (cell); + NautilusListBasePrivate *priv = nautilus_list_base_get_instance_private (self); + g_autoptr (NautilusViewItem) item = nautilus_view_cell_get_item (cell); + GdkDragAction actions; + GFile *target_location; + + actions = gdk_drop_get_actions (gtk_drop_target_get_current_drop (target)); + target_location = nautilus_file_get_location (nautilus_view_item_get_file (item)); + + #ifdef GDK_WINDOWING_X11 + if (GDK_IS_X11_DISPLAY (gtk_widget_get_display (GTK_WIDGET (self)))) + { + /* Temporary workaround until the below GTK MR (or equivalend fix) + * is merged. Without this fix, the preferred action isn't set correctly. + * https://gitlab.gnome.org/GNOME/gtk/-/merge_requests/4982 */ + GdkDrag *drag = gdk_drop_get_drag (gtk_drop_target_get_current_drop (target)); + actions = drag != NULL ? gdk_drag_get_selected_action (drag) : GDK_ACTION_COPY; + } + #endif + + /* In x11 the leave signal isn't emitted on a drop so we need to clear the timeout */ + g_clear_handle_id (&priv->hover_timer_id, g_source_remove); + + real_perform_drop (self, value, actions, target_location); + + return TRUE; +} + +static GdkDragAction +on_view_drag_enter (GtkDropTarget *target, + double x, + double y, + gpointer user_data) +{ + NautilusListBase *self = user_data; + NautilusListBasePrivate *priv = nautilus_list_base_get_instance_private (self); + NautilusFile *dest_file; + const GValue *value; + + dest_file = nautilus_files_view_get_directory_as_file (NAUTILUS_FILES_VIEW (self)); + value = gtk_drop_target_get_value (target); + priv->drag_view_action = get_preferred_action (dest_file, value); + if (priv->drag_view_action == 0) + { + /* Don't summarily reject because the view's location might change on + * hover, so a DND action may become available. */ + return 0; + } + + return priv->drag_view_action; +} + +static void +on_view_drag_value_notify (GObject *object, + GParamSpec *pspec, + gpointer user_data) +{ + GtkDropTarget *target = GTK_DROP_TARGET (object); + NautilusListBase *self = user_data; + NautilusListBasePrivate *priv = nautilus_list_base_get_instance_private (self); + const GValue *value; + NautilusFile *dest_file; + + value = gtk_drop_target_get_value (target); + if (value == NULL) + { + return; + } + + dest_file = nautilus_files_view_get_directory_as_file (NAUTILUS_FILES_VIEW (self)); + priv->drag_view_action = get_preferred_action (dest_file, value); +} + +static GdkDragAction +on_view_drag_motion (GtkDropTarget *target, + double x, + double y, + gpointer user_data) +{ + NautilusListBase *self = user_data; + NautilusListBasePrivate *priv = nautilus_list_base_get_instance_private (self); + + return priv->drag_view_action; +} + +static gboolean +on_view_drop (GtkDropTarget *target, + const GValue *value, + double x, + double y, + gpointer user_data) +{ + NautilusListBase *self = user_data; + NautilusListBasePrivate *priv = nautilus_list_base_get_instance_private (self); + GdkDragAction actions; + GFile *target_location; + + if (priv->drag_view_action == 0) + { + /* We didn't reject earlier because the view's location may change and, + * as a result, a drop action might become available. */ + return FALSE; + } + + actions = gdk_drop_get_actions (gtk_drop_target_get_current_drop (target)); + target_location = nautilus_view_get_location (NAUTILUS_VIEW (self)); + + #ifdef GDK_WINDOWING_X11 + if (GDK_IS_X11_DISPLAY (gtk_widget_get_display (GTK_WIDGET (self)))) + { + /* Temporary workaround until the below GTK MR (or equivalend fix) + * is merged. Without this fix, the preferred action isn't set correctly. + * https://gitlab.gnome.org/GNOME/gtk/-/merge_requests/4982 */ + GdkDrag *drag = gdk_drop_get_drag (gtk_drop_target_get_current_drop (target)); + actions = drag != NULL ? gdk_drag_get_selected_action (drag) : GDK_ACTION_COPY; + } + #endif + + real_perform_drop (self, value, actions, target_location); + + return TRUE; +} + +void +setup_cell_common (GtkListItem *listitem, + NautilusViewCell *cell) +{ + GtkEventController *controller; + GtkDropTarget *drop_target; + + g_object_bind_property (listitem, "item", + cell, "item", + G_BINDING_SYNC_CREATE); + gtk_list_item_set_child (listitem, GTK_WIDGET (cell)); + + controller = GTK_EVENT_CONTROLLER (gtk_gesture_click_new ()); + gtk_widget_add_controller (GTK_WIDGET (cell), controller); + gtk_event_controller_set_propagation_phase (controller, GTK_PHASE_BUBBLE); + gtk_gesture_single_set_button (GTK_GESTURE_SINGLE (controller), 0); + g_signal_connect (controller, "pressed", G_CALLBACK (on_item_click_pressed), cell); + g_signal_connect (controller, "stopped", G_CALLBACK (on_item_click_stopped), cell); + g_signal_connect (controller, "released", G_CALLBACK (on_item_click_released), cell); + + controller = GTK_EVENT_CONTROLLER (gtk_gesture_long_press_new ()); + gtk_widget_add_controller (GTK_WIDGET (cell), controller); + gtk_event_controller_set_propagation_phase (controller, GTK_PHASE_BUBBLE); + gtk_gesture_single_set_touch_only (GTK_GESTURE_SINGLE (controller), TRUE); + g_signal_connect (controller, "pressed", G_CALLBACK (on_item_longpress_pressed), cell); + + controller = GTK_EVENT_CONTROLLER (gtk_drag_source_new ()); + gtk_widget_add_controller (GTK_WIDGET (cell), controller); + gtk_event_controller_set_propagation_phase (controller, GTK_PHASE_CAPTURE); + g_signal_connect (controller, "prepare", G_CALLBACK (on_item_drag_prepare), cell); + + /* TODO: Implement GDK_ACTION_ASK */ + drop_target = gtk_drop_target_new (G_TYPE_INVALID, GDK_ACTION_ALL); + gtk_drop_target_set_preload (drop_target, TRUE); + /* TODO: Implement GDK_TYPE_STRING */ + gtk_drop_target_set_gtypes (drop_target, (GType[2]) { GDK_TYPE_FILE_LIST, G_TYPE_STRING }, 2); + g_signal_connect (drop_target, "enter", G_CALLBACK (on_item_drag_enter), cell); + g_signal_connect (drop_target, "notify::value", G_CALLBACK (on_item_drag_value_notify), cell); + g_signal_connect (drop_target, "leave", G_CALLBACK (on_item_drag_leave), cell); + g_signal_connect (drop_target, "motion", G_CALLBACK (on_item_drag_motion), cell); + g_signal_connect (drop_target, "drop", G_CALLBACK (on_item_drop), cell); + gtk_widget_add_controller (GTK_WIDGET (cell), GTK_EVENT_CONTROLLER (drop_target)); + + controller = gtk_drop_controller_motion_new (); + gtk_widget_add_controller (GTK_WIDGET (cell), controller); + g_signal_connect (controller, "enter", G_CALLBACK (on_item_drag_hover_enter), cell); + g_signal_connect (controller, "leave", G_CALLBACK (on_item_drag_hover_leave), cell); + g_signal_connect (controller, "motion", G_CALLBACK (on_item_drag_hover_motion), cell); +} + +static void +nautilus_list_base_scroll_to_item (NautilusListBase *self, + guint position) +{ + NAUTILUS_LIST_BASE_CLASS (G_OBJECT_GET_CLASS (self))->scroll_to_item (self, position); +} + +static GtkWidget * +nautilus_list_base_get_view_ui (NautilusListBase *self) +{ + return NAUTILUS_LIST_BASE_CLASS (G_OBJECT_GET_CLASS (self))->get_view_ui (self); +} + +typedef struct +{ + NautilusListBase *self; + GQuark attribute_q; +} NautilusListBaseSortData; + +static void +real_begin_loading (NautilusFilesView *files_view) +{ + NautilusListBase *self = NAUTILUS_LIST_BASE (files_view); + NautilusListBasePrivate *priv = nautilus_list_base_get_instance_private (self); + + /* Temporary workaround */ + rubberband_set_state (self, TRUE); + + /*TODO move this to the files view class begin_loading and hook up? */ + + + /* TODO: This calls sort once, and update_context_menus calls update_actions + * which calls the action again + */ + update_sort_order_from_metadata_and_preferences (NAUTILUS_LIST_BASE (files_view)); + + /* We could have changed to the trash directory or to searching, and then + * we need to update the menus */ + nautilus_files_view_update_context_menus (files_view); + nautilus_files_view_update_toolbar_menus (files_view); + + /* When double clicking on an item this deny_background_click can persist + * because the new view interrupts the gesture sequence, so lets reset it.*/ + priv->deny_background_click = FALSE; + + /* When DnD is used to navigate between directories, the normal callbacks + * are ignored. Update DnD variables here upon navigating to a directory*/ + if (gtk_drop_target_get_current_drop (priv->view_drop_target) != NULL) + { + priv->drag_view_action = get_preferred_action (nautilus_files_view_get_directory_as_file (files_view), + gtk_drop_target_get_value (priv->view_drop_target)); + priv->drag_item_action = 0; + } +} + +static void +real_clear (NautilusFilesView *files_view) +{ + NautilusListBase *self = NAUTILUS_LIST_BASE (files_view); + NautilusListBasePrivate *priv = nautilus_list_base_get_instance_private (self); + + g_clear_handle_id (&priv->scroll_to_file_handle_id, g_source_remove); + nautilus_view_model_remove_all_items (priv->model); +} + +static void +set_click_mode_from_settings (NautilusListBase *self) +{ + NautilusListBasePrivate *priv = nautilus_list_base_get_instance_private (self); + int click_policy; + + click_policy = g_settings_get_enum (nautilus_preferences, + NAUTILUS_PREFERENCES_CLICK_POLICY); + + priv->single_click_mode = (click_policy == NAUTILUS_CLICK_POLICY_SINGLE); +} + +static void +real_click_policy_changed (NautilusFilesView *files_view) +{ + set_click_mode_from_settings (NAUTILUS_LIST_BASE (files_view)); +} + +static void +real_file_changed (NautilusFilesView *files_view, + NautilusFile *file, + NautilusDirectory *directory) +{ + NautilusListBase *self = NAUTILUS_LIST_BASE (files_view); + NautilusListBasePrivate *priv = nautilus_list_base_get_instance_private (self); + NautilusViewItem *item; + + item = nautilus_view_model_get_item_from_file (priv->model, file); + nautilus_view_item_file_changed (item); +} + +static GList * +real_get_selection (NautilusFilesView *files_view) +{ + NautilusListBase *self = NAUTILUS_LIST_BASE (files_view); + NautilusListBasePrivate *priv = nautilus_list_base_get_instance_private (self); + g_autoptr (GtkSelectionFilterModel) selection = NULL; + guint n_selected; + GList *selected_files = NULL; + + selection = gtk_selection_filter_model_new (GTK_SELECTION_MODEL (priv->model)); + n_selected = g_list_model_get_n_items (G_LIST_MODEL (selection)); + for (guint i = 0; i < n_selected; i++) + { + g_autoptr (NautilusViewItem) item = NULL; + + item = get_view_item (G_LIST_MODEL (selection), i); + selected_files = g_list_prepend (selected_files, + g_object_ref (nautilus_view_item_get_file (item))); + } + + selected_files = g_list_reverse (selected_files); + + return selected_files; +} + +static gboolean +real_is_empty (NautilusFilesView *files_view) +{ + NautilusListBase *self = NAUTILUS_LIST_BASE (files_view); + NautilusListBasePrivate *priv = nautilus_list_base_get_instance_private (self); + + return g_list_model_get_n_items (G_LIST_MODEL (priv->model)) == 0; +} + +static void +real_end_file_changes (NautilusFilesView *files_view) +{ +} + +static void +real_remove_file (NautilusFilesView *files_view, + NautilusFile *file, + NautilusDirectory *directory) +{ + NautilusListBase *self = NAUTILUS_LIST_BASE (files_view); + NautilusListBasePrivate *priv = nautilus_list_base_get_instance_private (self); + NautilusViewItem *item; + + item = nautilus_view_model_get_item_from_file (priv->model, file); + if (item != NULL) + { + nautilus_view_model_remove_item (priv->model, item); + nautilus_files_view_notify_selection_changed (files_view); + } +} + +static GQueue * +convert_glist_to_queue (GList *list) +{ + GList *l; + GQueue *queue; + + queue = g_queue_new (); + for (l = list; l != NULL; l = l->next) + { + g_queue_push_tail (queue, l->data); + } + + return queue; +} + +static GQueue * +convert_files_to_items (NautilusListBase *self, + GQueue *files) +{ + GList *l; + GQueue *models; + + models = g_queue_new (); + for (l = g_queue_peek_head_link (files); l != NULL; l = l->next) + { + NautilusViewItem *item; + + item = nautilus_view_item_new (NAUTILUS_FILE (l->data), + nautilus_list_base_get_icon_size (self)); + g_queue_push_tail (models, item); + } + + return models; +} + +static void +real_set_selection (NautilusFilesView *files_view, + GList *selection) +{ + NautilusListBase *self = NAUTILUS_LIST_BASE (files_view); + NautilusListBasePrivate *priv = nautilus_list_base_get_instance_private (self); + g_autoptr (GQueue) selection_files = NULL; + g_autoptr (GQueue) selection_items = NULL; + g_autoptr (GtkBitset) update_set = NULL; + g_autoptr (GtkBitset) new_selection_set = NULL; + g_autoptr (GtkBitset) old_selection_set = NULL; + + old_selection_set = gtk_selection_model_get_selection (GTK_SELECTION_MODEL (priv->model)); + /* We aren't allowed to modify the actual selection bitset */ + update_set = gtk_bitset_copy (old_selection_set); + new_selection_set = gtk_bitset_new_empty (); + + /* Convert file list into set of model indices */ + selection_files = convert_glist_to_queue (selection); + selection_items = nautilus_view_model_get_items_from_files (priv->model, selection_files); + for (GList *l = g_queue_peek_head_link (selection_items); l != NULL; l = l->next) + { + gtk_bitset_add (new_selection_set, + nautilus_view_model_get_index (priv->model, l->data)); + } + + /* Set focus on the first selected row. */ + if (!g_queue_is_empty (selection_items)) + { + NautilusViewItem *item = g_queue_peek_head (selection_items); + set_focus_item (self, item); + } + + gtk_bitset_union (update_set, new_selection_set); + gtk_selection_model_set_selection (GTK_SELECTION_MODEL (priv->model), + new_selection_set, + update_set); +} + +static void +real_select_all (NautilusFilesView *files_view) +{ + NautilusListBase *self = NAUTILUS_LIST_BASE (files_view); + NautilusListBasePrivate *priv = nautilus_list_base_get_instance_private (self); + + gtk_selection_model_select_all (GTK_SELECTION_MODEL (priv->model)); +} + +static void +real_invert_selection (NautilusFilesView *files_view) +{ + NautilusListBase *self = NAUTILUS_LIST_BASE (files_view); + NautilusListBasePrivate *priv = nautilus_list_base_get_instance_private (self); + GtkSelectionModel *selection_model = GTK_SELECTION_MODEL (priv->model); + g_autoptr (GtkBitset) selected = NULL; + g_autoptr (GtkBitset) all = NULL; + g_autoptr (GtkBitset) new_selected = NULL; + + selected = gtk_selection_model_get_selection (selection_model); + + /* We are going to flip the selection state of every item in the model. */ + all = gtk_bitset_new_range (0, g_list_model_get_n_items (G_LIST_MODEL (priv->model))); + + /* The new selection is all items minus the ones currently selected. */ + new_selected = gtk_bitset_copy (all); + gtk_bitset_subtract (new_selected, selected); + + gtk_selection_model_set_selection (selection_model, new_selected, all); +} + +static guint +get_first_selected_item (NautilusListBase *self) +{ + NautilusListBasePrivate *priv = nautilus_list_base_get_instance_private (self); + g_autolist (NautilusFile) selection = NULL; + NautilusFile *file; + NautilusViewItem *item; + + selection = nautilus_view_get_selection (NAUTILUS_VIEW (self)); + if (selection == NULL) + { + return G_MAXUINT; + } + + file = NAUTILUS_FILE (selection->data); + item = nautilus_view_model_get_item_from_file (priv->model, file); + + return nautilus_view_model_get_index (priv->model, item); +} + +static void +real_reveal_selection (NautilusFilesView *files_view) +{ + NautilusListBase *self = NAUTILUS_LIST_BASE (files_view); + + nautilus_list_base_scroll_to_item (self, get_first_selected_item (self)); +} + +static void +on_clipboard_contents_received (GObject *source_object, + GAsyncResult *res, + gpointer user_data) +{ + NautilusFilesView *files_view = NAUTILUS_FILES_VIEW (source_object); + NautilusListBase *self = NAUTILUS_LIST_BASE (files_view); + NautilusListBasePrivate *priv = nautilus_list_base_get_instance_private (self); + NautilusClipboard *clip; + NautilusViewItem *item; + + for (GList *l = priv->cut_files; l != NULL; l = l->next) + { + item = nautilus_view_model_get_item_from_file (priv->model, l->data); + if (item != NULL) + { + nautilus_view_item_set_cut (item, FALSE); + } + } + g_clear_list (&priv->cut_files, g_object_unref); + + clip = nautilus_files_view_get_clipboard_finish (files_view, res, NULL); + if (clip != NULL && nautilus_clipboard_is_cut (clip)) + { + priv->cut_files = g_list_copy_deep (nautilus_clipboard_peek_files (clip), + (GCopyFunc) g_object_ref, + NULL); + } + + for (GList *l = priv->cut_files; l != NULL; l = l->next) + { + item = nautilus_view_model_get_item_from_file (priv->model, l->data); + if (item != NULL) + { + nautilus_view_item_set_cut (item, TRUE); + } + } +} + +static void +update_clipboard_status (NautilusFilesView *view) +{ + nautilus_files_view_get_clipboard_async (view, + on_clipboard_contents_received, + NULL); +} + +static void +on_clipboard_owner_changed (GdkClipboard *clipboard, + gpointer user_data) +{ + update_clipboard_status (NAUTILUS_FILES_VIEW (user_data)); +} + +static void +real_end_loading (NautilusFilesView *files_view, + gboolean all_files_seen) +{ + update_clipboard_status (files_view); +} + +static guint +get_first_visible_item (NautilusListBase *self) +{ + NautilusListBasePrivate *priv = nautilus_list_base_get_instance_private (self); + guint n_items; + GtkWidget *view_ui; + GtkBorder border = {0}; + + n_items = g_list_model_get_n_items (G_LIST_MODEL (priv->model)); + view_ui = nautilus_list_base_get_view_ui (self); + gtk_scrollable_get_border (GTK_SCROLLABLE (view_ui), &border); + + for (guint i = 0; i < n_items; i++) + { + g_autoptr (NautilusViewItem) item = NULL; + GtkWidget *item_ui; + + item = get_view_item (G_LIST_MODEL (priv->model), i); + item_ui = nautilus_view_item_get_item_ui (item); + if (item_ui != NULL && gtk_widget_get_mapped (item_ui)) + { + GtkWidget *list_item_widget = gtk_widget_get_parent (item_ui); + gdouble h = gtk_widget_get_allocated_height (list_item_widget); + gdouble y; + + gtk_widget_translate_coordinates (list_item_widget, GTK_WIDGET (self), + 0, h, NULL, &y); + if (y >= border.top) + { + return i; + } + } + } + + return G_MAXUINT; +} + +static char * +real_get_first_visible_file (NautilusFilesView *files_view) +{ + NautilusListBase *self = NAUTILUS_LIST_BASE (files_view); + NautilusListBasePrivate *priv = nautilus_list_base_get_instance_private (self); + guint i; + g_autoptr (NautilusViewItem) item = NULL; + gchar *uri = NULL; + + i = get_first_visible_item (self); + if (i < G_MAXUINT) + { + item = get_view_item (G_LIST_MODEL (priv->model), i); + uri = nautilus_file_get_uri (nautilus_view_item_get_file (item)); + } + return uri; +} + +typedef struct +{ + NautilusListBase *view; + char *uri; +} ScrollToFileData; + +static void +scroll_to_file_data_free (ScrollToFileData *data) +{ + g_free (data->uri); + g_free (data); +} + +static gboolean +scroll_to_file_on_idle (ScrollToFileData *data) +{ + NautilusListBase *self = data->view; + NautilusListBasePrivate *priv = nautilus_list_base_get_instance_private (self); + g_autoptr (NautilusFile) file = NULL; + NautilusViewItem *item; + guint i; + + priv->scroll_to_file_handle_id = 0; + + file = nautilus_file_get_existing_by_uri (data->uri); + item = nautilus_view_model_get_item_from_file (priv->model, file); + g_return_val_if_fail (item != NULL, G_SOURCE_REMOVE); + + i = nautilus_view_model_get_index (priv->model, item); + nautilus_list_base_scroll_to_item (self, i); + + return G_SOURCE_REMOVE; +} + +static void +real_scroll_to_file (NautilusFilesView *files_view, + const char *uri) +{ + NautilusListBase *self = NAUTILUS_LIST_BASE (files_view); + NautilusListBasePrivate *priv = nautilus_list_base_get_instance_private (self); + ScrollToFileData *data; + guint handle_id; + + data = g_new (ScrollToFileData, 1); + data->view = self; + data->uri = g_strdup (uri); + handle_id = g_idle_add_full (G_PRIORITY_LOW, + (GSourceFunc) scroll_to_file_on_idle, + data, + (GDestroyNotify) scroll_to_file_data_free); + priv->scroll_to_file_handle_id = handle_id; +} + +static void +real_add_files (NautilusFilesView *files_view, + GList *files) +{ + NautilusListBase *self = NAUTILUS_LIST_BASE (files_view); + NautilusListBasePrivate *priv = nautilus_list_base_get_instance_private (self); + g_autoptr (GQueue) files_queue = NULL; + g_autoptr (GQueue) items = NULL; + + files_queue = convert_glist_to_queue (files); + items = convert_files_to_items (self, files_queue); + nautilus_view_model_add_items (priv->model, items); +} + +static void +real_select_first (NautilusFilesView *files_view) +{ + NautilusListBase *self = NAUTILUS_LIST_BASE (files_view); + NautilusListBasePrivate *priv = nautilus_list_base_get_instance_private (self); + + gtk_selection_model_select_item (GTK_SELECTION_MODEL (priv->model), 0, TRUE); +} + +static GdkRectangle * +get_rectangle_for_item_ui (NautilusListBase *self, + GtkWidget *item_ui) +{ + GdkRectangle *rectangle; + GtkWidget *content_widget; + gdouble view_x; + gdouble view_y; + + rectangle = g_new0 (GdkRectangle, 1); + gtk_widget_get_allocation (item_ui, rectangle); + + content_widget = nautilus_files_view_get_content_widget (NAUTILUS_FILES_VIEW (self)); + gtk_widget_translate_coordinates (item_ui, content_widget, + rectangle->x, rectangle->y, + &view_x, &view_y); + rectangle->x = view_x; + rectangle->y = view_y; + + return rectangle; +} + +static GdkRectangle * +real_compute_rename_popover_pointing_to (NautilusFilesView *files_view) +{ + NautilusListBase *self = NAUTILUS_LIST_BASE (files_view); + NautilusListBasePrivate *priv = nautilus_list_base_get_instance_private (self); + g_autoptr (NautilusViewItem) item = NULL; + GtkWidget *item_ui; + + /* We only allow one item to be renamed with a popover */ + item = get_view_item (G_LIST_MODEL (priv->model), get_first_selected_item (self)); + item_ui = nautilus_view_item_get_item_ui (item); + g_return_val_if_fail (item_ui != NULL, NULL); + + return get_rectangle_for_item_ui (self, item_ui); +} + +static GdkRectangle * +real_reveal_for_selection_context_menu (NautilusFilesView *files_view) +{ + NautilusListBase *self = NAUTILUS_LIST_BASE (files_view); + NautilusListBasePrivate *priv = nautilus_list_base_get_instance_private (self); + g_autoptr (GtkSelectionFilterModel) selection = NULL; + guint n_selected; + GtkWidget *focus_child; + guint i; + GtkWidget *item_ui; + + selection = gtk_selection_filter_model_new (GTK_SELECTION_MODEL (priv->model)); + n_selected = g_list_model_get_n_items (G_LIST_MODEL (selection)); + g_return_val_if_fail (n_selected > 0, NULL); + + /* Get the focused item_ui, if selected. + * Otherwise, get the selected item_ui which is sorted the lowest.*/ + focus_child = gtk_widget_get_focus_child (nautilus_list_base_get_view_ui (self)); + for (i = 0; i < n_selected; i++) + { + g_autoptr (NautilusViewItem) item = NULL; + + item = get_view_item (G_LIST_MODEL (selection), i); + item_ui = nautilus_view_item_get_item_ui (item); + if (item_ui != NULL && gtk_widget_get_parent (item_ui) == focus_child) + { + break; + } + } + nautilus_list_base_scroll_to_item (self, i); + + return get_rectangle_for_item_ui (self, item_ui); +} + +static void +real_preview_selection_event (NautilusFilesView *files_view, + GtkDirectionType direction) +{ + NautilusListBase *self = NAUTILUS_LIST_BASE (files_view); + NautilusListBasePrivate *priv = nautilus_list_base_get_instance_private (self); + guint i; + gboolean rtl = (gtk_widget_get_direction (GTK_WIDGET (self)) == GTK_TEXT_DIR_RTL); + + i = get_first_selected_item (self); + if (direction == GTK_DIR_UP || + direction == (rtl ? GTK_DIR_RIGHT : GTK_DIR_LEFT)) + { + if (i == 0) + { + return; + } + + i--; + } + else + { + i++; + + if (i >= g_list_model_get_n_items (G_LIST_MODEL (priv->model))) + { + return; + } + } + + gtk_selection_model_select_item (GTK_SELECTION_MODEL (priv->model), i, TRUE); + set_focus_item (self, g_list_model_get_item (G_LIST_MODEL (priv->model), i)); +} + +static void +default_sort_order_changed_callback (NautilusListBase *self) +{ + update_sort_order_from_metadata_and_preferences (self); +} + +static void +nautilus_list_base_dispose (GObject *object) +{ + NautilusListBase *self = NAUTILUS_LIST_BASE (object); + NautilusListBasePrivate *priv = nautilus_list_base_get_instance_private (self); + + g_clear_handle_id (&priv->scroll_to_file_handle_id, g_source_remove); + g_clear_handle_id (&priv->prioritize_thumbnailing_handle_id, g_source_remove); + g_clear_handle_id (&priv->hover_timer_id, g_source_remove); + + G_OBJECT_CLASS (nautilus_list_base_parent_class)->dispose (object); +} + +static void +nautilus_list_base_finalize (GObject *object) +{ + NautilusListBase *self = NAUTILUS_LIST_BASE (object); + NautilusListBasePrivate *priv = nautilus_list_base_get_instance_private (self); + + g_clear_list (&priv->cut_files, g_object_unref); + + G_OBJECT_CLASS (nautilus_list_base_parent_class)->finalize (object); +} + +static gboolean +prioritize_thumbnailing_on_idle (NautilusListBase *self) +{ + NautilusListBasePrivate *priv = nautilus_list_base_get_instance_private (self); + gdouble page_size; + GtkWidget *first_visible_child; + GtkWidget *next_child; + guint first_index; + guint next_index; + gdouble y; + guint last_index; + g_autoptr (NautilusViewItem) first_item = NULL; + NautilusFile *file; + + priv->prioritize_thumbnailing_handle_id = 0; + + page_size = gtk_adjustment_get_page_size (priv->vadjustment); + first_index = get_first_visible_item (self); + if (first_index == G_MAXUINT) + { + return G_SOURCE_REMOVE; + } + + first_item = get_view_item (G_LIST_MODEL (priv->model), first_index); + + first_visible_child = nautilus_view_item_get_item_ui (first_item); + + for (next_index = first_index + 1; next_index < g_list_model_get_n_items (G_LIST_MODEL (priv->model)); next_index++) + { + g_autoptr (NautilusViewItem) next_item = NULL; + + next_item = get_view_item (G_LIST_MODEL (priv->model), next_index); + next_child = nautilus_view_item_get_item_ui (next_item); + if (next_child == NULL) + { + break; + } + if (gtk_widget_translate_coordinates (next_child, first_visible_child, + 0, 0, NULL, &y)) + { + if (y > page_size) + { + break; + } + } + } + last_index = next_index - 1; + + /* Do the iteration in reverse to give higher priority to the top */ + for (gint i = 0; i <= last_index - first_index; i++) + { + g_autoptr (NautilusViewItem) item = NULL; + + item = get_view_item (G_LIST_MODEL (priv->model), last_index - i); + g_return_val_if_fail (item != NULL, G_SOURCE_REMOVE); + + file = nautilus_view_item_get_file (NAUTILUS_VIEW_ITEM (item)); + if (file != NULL && nautilus_file_is_thumbnailing (file)) + { + g_autofree gchar *uri = nautilus_file_get_uri (file); + nautilus_thumbnail_prioritize (uri); + } + } + + return G_SOURCE_REMOVE; +} + +static void +on_vadjustment_changed (GtkAdjustment *adjustment, + gpointer user_data) +{ + NautilusListBase *self = NAUTILUS_LIST_BASE (user_data); + NautilusListBasePrivate *priv = nautilus_list_base_get_instance_private (self); + guint handle_id; + + /* Schedule on idle to rate limit and to avoid delaying scrolling. */ + if (priv->prioritize_thumbnailing_handle_id == 0) + { + handle_id = g_idle_add ((GSourceFunc) prioritize_thumbnailing_on_idle, self); + priv->prioritize_thumbnailing_handle_id = handle_id; + } +} + +static gboolean +nautilus_list_base_focus (GtkWidget *widget, + GtkDirectionType direction) +{ + NautilusListBase *self = NAUTILUS_LIST_BASE (widget); + g_autolist (NautilusFile) selection = NULL; + gboolean no_selection; + gboolean handled; + + /* If focus is already inside the view, allow to immediately tab out of it, + * instead of having to cycle through every item (potentially many). */ + if (direction == GTK_DIR_TAB_FORWARD || direction == GTK_DIR_TAB_BACKWARD) + { + GtkWidget *focus_widget = gtk_root_get_focus (gtk_widget_get_root (widget)); + if (focus_widget != NULL && gtk_widget_is_ancestor (focus_widget, widget)) + { + return FALSE; + } + } + + selection = nautilus_view_get_selection (NAUTILUS_VIEW (self)); + no_selection = (selection == NULL); + + handled = GTK_WIDGET_CLASS (nautilus_list_base_parent_class)->focus (widget, direction); + + if (handled && no_selection) + { + GtkWidget *focus_widget = gtk_root_get_focus (gtk_widget_get_root (widget)); + + /* Workaround for https://gitlab.gnome.org/GNOME/nautilus/-/issues/2489 + * Also ensures an item gets selected when using to focus the view. + * Ideally to be fixed in GtkListBase instead. */ + if (focus_widget != NULL) + { + gtk_widget_activate_action (focus_widget, + "listitem.select", + "(bb)", + FALSE, FALSE); + } + } + + return handled; +} + +static void +nautilus_list_base_class_init (NautilusListBaseClass *klass) +{ + GObjectClass *object_class = G_OBJECT_CLASS (klass); + GtkWidgetClass *widget_class = GTK_WIDGET_CLASS (klass); + NautilusFilesViewClass *files_view_class = NAUTILUS_FILES_VIEW_CLASS (klass); + + object_class->dispose = nautilus_list_base_dispose; + object_class->finalize = nautilus_list_base_finalize; + + widget_class->focus = nautilus_list_base_focus; + + files_view_class->add_files = real_add_files; + files_view_class->begin_loading = real_begin_loading; + files_view_class->clear = real_clear; + files_view_class->click_policy_changed = real_click_policy_changed; + files_view_class->file_changed = real_file_changed; + files_view_class->get_selection = real_get_selection; + /* TODO: remove this get_selection_for_file_transfer, this doesn't even + * take into account we could us the view for recursive search :/ + * CanvasView has the same issue. */ + files_view_class->get_selection_for_file_transfer = real_get_selection; + files_view_class->is_empty = real_is_empty; + files_view_class->remove_file = real_remove_file; + files_view_class->select_all = real_select_all; + files_view_class->set_selection = real_set_selection; + files_view_class->invert_selection = real_invert_selection; + files_view_class->end_file_changes = real_end_file_changes; + files_view_class->end_loading = real_end_loading; + files_view_class->get_first_visible_file = real_get_first_visible_file; + files_view_class->reveal_selection = real_reveal_selection; + files_view_class->scroll_to_file = real_scroll_to_file; + files_view_class->select_first = real_select_first; + files_view_class->compute_rename_popover_pointing_to = real_compute_rename_popover_pointing_to; + files_view_class->reveal_for_selection_context_menu = real_reveal_for_selection_context_menu; + files_view_class->preview_selection_event = real_preview_selection_event; +} + +static void +nautilus_list_base_init (NautilusListBase *self) +{ + NautilusListBasePrivate *priv = nautilus_list_base_get_instance_private (self); + GtkWidget *content_widget; + GtkAdjustment *vadjustment; + + gtk_widget_add_css_class (GTK_WIDGET (self), "view"); + + g_signal_connect_object (nautilus_preferences, + "changed::" NAUTILUS_PREFERENCES_DEFAULT_SORT_ORDER, + G_CALLBACK (default_sort_order_changed_callback), + self, + G_CONNECT_SWAPPED); + g_signal_connect_object (nautilus_preferences, + "changed::" NAUTILUS_PREFERENCES_DEFAULT_SORT_IN_REVERSE_ORDER, + G_CALLBACK (default_sort_order_changed_callback), + self, + G_CONNECT_SWAPPED); + + /* React to clipboard changes */ + g_signal_connect_object (gdk_display_get_clipboard (gdk_display_get_default ()), + "changed", + G_CALLBACK (on_clipboard_owner_changed), self, 0); + + content_widget = nautilus_files_view_get_content_widget (NAUTILUS_FILES_VIEW (self)); + vadjustment = gtk_scrolled_window_get_vadjustment (GTK_SCROLLED_WINDOW (content_widget)); + + priv->vadjustment = vadjustment; + g_signal_connect (vadjustment, "changed", (GCallback) on_vadjustment_changed, self); + g_signal_connect (vadjustment, "value-changed", (GCallback) on_vadjustment_changed, self); + + priv->model = nautilus_view_model_new (); + + g_signal_connect_object (GTK_SELECTION_MODEL (priv->model), + "selection-changed", + G_CALLBACK (nautilus_files_view_notify_selection_changed), + NAUTILUS_FILES_VIEW (self), + G_CONNECT_SWAPPED); + + set_click_mode_from_settings (self); +} + +NautilusViewModel * +nautilus_list_base_get_model (NautilusListBase *self) +{ + NautilusListBasePrivate *priv = nautilus_list_base_get_instance_private (self); + + return priv->model; +} + +void +nautilus_list_base_setup_gestures (NautilusListBase *self) +{ + NautilusListBasePrivate *priv = nautilus_list_base_get_instance_private (self); + GtkEventController *controller; + GtkDropTarget *drop_target; + + controller = GTK_EVENT_CONTROLLER (gtk_gesture_click_new ()); + gtk_widget_add_controller (GTK_WIDGET (self), controller); + gtk_gesture_single_set_button (GTK_GESTURE_SINGLE (controller), 0); + g_signal_connect (controller, "pressed", + G_CALLBACK (on_view_click_pressed), self); + + controller = GTK_EVENT_CONTROLLER (gtk_gesture_long_press_new ()); + gtk_widget_add_controller (GTK_WIDGET (self), controller); + gtk_gesture_single_set_touch_only (GTK_GESTURE_SINGLE (controller), TRUE); + g_signal_connect (controller, "pressed", + G_CALLBACK (on_view_longpress_pressed), self); + + /* TODO: Implement GDK_ACTION_ASK */ + drop_target = gtk_drop_target_new (G_TYPE_INVALID, GDK_ACTION_ALL); + gtk_drop_target_set_preload (drop_target, TRUE); + /* TODO: Implement GDK_TYPE_STRING */ + gtk_drop_target_set_gtypes (drop_target, (GType[2]) { GDK_TYPE_FILE_LIST, G_TYPE_STRING }, 2); + g_signal_connect (drop_target, "enter", G_CALLBACK (on_view_drag_enter), self); + g_signal_connect (drop_target, "notify::value", G_CALLBACK (on_view_drag_value_notify), self); + g_signal_connect (drop_target, "motion", G_CALLBACK (on_view_drag_motion), self); + g_signal_connect (drop_target, "drop", G_CALLBACK (on_view_drop), self); + gtk_widget_add_controller (GTK_WIDGET (self), GTK_EVENT_CONTROLLER (drop_target)); + priv->view_drop_target = drop_target; +} diff --git a/src/nautilus-list-base.h b/src/nautilus-list-base.h new file mode 100644 index 0000000..007ab07 --- /dev/null +++ b/src/nautilus-list-base.h @@ -0,0 +1,27 @@ +/* + * Copyright (C) 2022 The GNOME project contributors + * + * SPDX-License-Identifier: GPL-3.0-or-later + */ + +#pragma once + +#include "nautilus-files-view.h" + +G_BEGIN_DECLS + +#define NAUTILUS_TYPE_LIST_BASE (nautilus_list_base_get_type()) + +G_DECLARE_DERIVABLE_TYPE (NautilusListBase, nautilus_list_base, NAUTILUS, LIST_BASE, NautilusFilesView) + +struct _NautilusListBaseClass +{ + NautilusFilesViewClass parent_class; + + guint (*get_icon_size) (NautilusListBase *self); + GtkWidget *(*get_view_ui) (NautilusListBase *self); + void (*scroll_to_item) (NautilusListBase *self, + guint position); +}; + +G_END_DECLS diff --git a/src/nautilus-list-view.c b/src/nautilus-list-view.c new file mode 100644 index 0000000..18f860b --- /dev/null +++ b/src/nautilus-list-view.c @@ -0,0 +1,1258 @@ +/* + * Copyright (C) 2000 Eazel, Inc. + * Copyright (C) 2001, 2002 Anders Carlsson + * Copyright (C) 2022 GNOME project contributors + * + * SPDX-License-Identifier: GPL-3.0-or-later + */ + +#include + +/* Needed for NautilusColumn. */ +#include + +#include "nautilus-list-base-private.h" +#include "nautilus-list-view.h" + +#include "nautilus-column-chooser.h" +#include "nautilus-column-utilities.h" +#include "nautilus-directory.h" +#include "nautilus-file.h" +#include "nautilus-file-utilities.h" +#include "nautilus-global-preferences.h" +#include "nautilus-label-cell.h" +#include "nautilus-metadata.h" +#include "nautilus-name-cell.h" +#include "nautilus-search-directory.h" +#include "nautilus-star-cell.h" +#include "nautilus-tag-manager.h" + +struct _NautilusListView +{ + NautilusListBase parent_instance; + + GtkColumnView *view_ui; + + GActionGroup *action_group; + gint zoom_level; + + gboolean directories_first; + + GQuark path_attribute_q; + GFile *file_path_base_location; + + GtkColumnViewColumn *star_column; + GtkWidget *column_editor; + GHashTable *factory_to_column_map; + + GHashTable *all_view_columns_hash; + + /* Column sort hack state */ + gboolean column_header_was_clicked; + GQuark clicked_column_attribute_q; +}; + +G_DEFINE_TYPE (NautilusListView, nautilus_list_view, NAUTILUS_TYPE_LIST_BASE) + + +static void on_sorter_changed (GtkSorter *sorter, + GtkSorterChange change, + gpointer user_data); + +static const char *default_columns_for_recent[] = +{ + "name", "size", "recency", NULL +}; + +static const char *default_columns_for_trash[] = +{ + "name", "size", "trashed_on", NULL +}; + +static guint +get_icon_size_for_zoom_level (NautilusListZoomLevel zoom_level) +{ + switch (zoom_level) + { + case NAUTILUS_LIST_ZOOM_LEVEL_SMALL: + { + return NAUTILUS_LIST_ICON_SIZE_SMALL; + } + break; + + case NAUTILUS_LIST_ZOOM_LEVEL_MEDIUM: + { + return NAUTILUS_LIST_ICON_SIZE_MEDIUM; + } + break; + + case NAUTILUS_LIST_ZOOM_LEVEL_LARGE: + { + return NAUTILUS_LIST_ICON_SIZE_LARGE; + } + break; + } + g_return_val_if_reached (NAUTILUS_LIST_ICON_SIZE_MEDIUM); +} + +static guint +real_get_icon_size (NautilusListBase *list_base_view) +{ + NautilusListView *self = NAUTILUS_LIST_VIEW (list_base_view); + + return get_icon_size_for_zoom_level (self->zoom_level); +} + +static GtkWidget * +real_get_view_ui (NautilusListBase *list_base_view) +{ + NautilusListView *self = NAUTILUS_LIST_VIEW (list_base_view); + + return GTK_WIDGET (self->view_ui); +} + +static void +apply_columns_settings (NautilusListView *self, + char **column_order, + char **visible_columns) +{ + g_autolist (NautilusColumn) all_columns = NULL; + NautilusFile *file; + NautilusDirectory *directory; + g_autoptr (GFile) location = NULL; + g_autoptr (GList) view_columns = NULL; + GListModel *old_view_columns; + g_autoptr (GHashTable) visible_columns_hash = NULL; + int column_i = 0; + + file = nautilus_files_view_get_directory_as_file (NAUTILUS_FILES_VIEW (self)); + directory = nautilus_files_view_get_model (NAUTILUS_FILES_VIEW (self)); + if (NAUTILUS_IS_SEARCH_DIRECTORY (directory)) + { + g_autoptr (NautilusQuery) query = NULL; + + query = nautilus_search_directory_get_query (NAUTILUS_SEARCH_DIRECTORY (directory)); + location = nautilus_query_get_location (query); + } + else + { + location = nautilus_file_get_location (file); + } + + all_columns = nautilus_get_columns_for_file (file); + all_columns = nautilus_sort_columns (all_columns, column_order); + + /* hash table to lookup if a given column should be visible */ + visible_columns_hash = g_hash_table_new_full (g_str_hash, + g_str_equal, + (GDestroyNotify) g_free, + (GDestroyNotify) g_free); + /* always show name column */ + g_hash_table_insert (visible_columns_hash, g_strdup ("name"), g_strdup ("name")); + + /* always show star column if supported */ + if (nautilus_tag_manager_can_star_contents (nautilus_tag_manager_get (), location) || + nautilus_is_starred_directory (location)) + { + g_hash_table_insert (visible_columns_hash, g_strdup ("starred"), g_strdup ("starred")); + } + + if (visible_columns != NULL) + { + for (int i = 0; visible_columns[i] != NULL; ++i) + { + g_hash_table_insert (visible_columns_hash, + g_ascii_strdown (visible_columns[i], -1), + g_ascii_strdown (visible_columns[i], -1)); + } + } + + old_view_columns = gtk_column_view_get_columns (self->view_ui); + for (GList *l = all_columns; l != NULL; l = l->next) + { + g_autofree char *name = NULL; + g_autofree char *lowercase = NULL; + + g_object_get (G_OBJECT (l->data), "name", &name, NULL); + lowercase = g_ascii_strdown (name, -1); + + if (g_hash_table_lookup (visible_columns_hash, lowercase) != NULL) + { + GtkColumnViewColumn *view_column; + + view_column = g_hash_table_lookup (self->all_view_columns_hash, name); + if (view_column != NULL) + { + view_columns = g_list_prepend (view_columns, view_column); + } + } + } + + view_columns = g_list_reverse (view_columns); + + /* hide columns that are not present in the configuration */ + for (guint i = 0; i < g_list_model_get_n_items (old_view_columns); i++) + { + g_autoptr (GtkColumnViewColumn) view_column = NULL; + + view_column = g_list_model_get_item (old_view_columns, i); + if (g_list_find (view_columns, view_column) == NULL) + { + gtk_column_view_remove_column (self->view_ui, view_column); + } + } + + /* place columns in the correct order */ + for (GList *l = view_columns; l != NULL; l = l->next, column_i++) + { + gtk_column_view_insert_column (self->view_ui, column_i, l->data); + } +} + +static void +real_scroll_to_item (NautilusListBase *list_base_view, + guint position) +{ + NautilusListView *self = NAUTILUS_LIST_VIEW (list_base_view); + GtkWidget *child; + + child = gtk_widget_get_last_child (GTK_WIDGET (self->view_ui)); + + while (child != NULL && !GTK_IS_LIST_VIEW (child)) + { + child = gtk_widget_get_prev_sibling (child); + } + + if (child != NULL) + { + gtk_widget_activate_action (child, "list.scroll-to-item", "u", position); + } +} + +typedef struct +{ + GQuark attribute; + NautilusListView *view; +} SortData; + +static gint +nautilus_list_view_sort (gconstpointer a, + gconstpointer b, + gpointer user_data) +{ + SortData *data = user_data; + NautilusListView *self = data->view; + GQuark attribute_q = data->attribute; + NautilusFile *file_a = nautilus_view_item_get_file (NAUTILUS_VIEW_ITEM ((gpointer) a)); + NautilusFile *file_b = nautilus_view_item_get_file (NAUTILUS_VIEW_ITEM ((gpointer) b)); + + /* Hack: We don't know what column is being sorted on when the column + * headers are clicked. So let's just look at what attribute was most + * recently used for sorting. + * https://gitlab.gnome.org/GNOME/gtk/-/issues/4833 */ + if (self->clicked_column_attribute_q == 0 && self->column_header_was_clicked) + { + self->clicked_column_attribute_q = attribute_q; + } + + g_return_val_if_fail (file_a != NULL && file_b != NULL, GTK_ORDERING_EQUAL); + + /* The reversed argument is FALSE because the columnview sorter handles that + * itself and if we don't want to reverse the reverse. The directories_first + * argument is also FALSE for the same reason: we don't want the columnview + * sorter to reverse it (it would display directories last!); instead we + * handle directories_first in a separate sorter. */ + return nautilus_file_compare_for_sort_by_attribute_q (file_a, file_b, + attribute_q, + FALSE /* directories_first */, + FALSE /* reversed */); +} + +static gint +sort_directories_func (gconstpointer a, + gconstpointer b, + gpointer user_data) +{ + gboolean *directories_first = user_data; + + if (*directories_first) + { + NautilusFile *file_a = nautilus_view_item_get_file (NAUTILUS_VIEW_ITEM ((gpointer) a)); + NautilusFile *file_b = nautilus_view_item_get_file (NAUTILUS_VIEW_ITEM ((gpointer) b)); + gboolean a_is_directory = nautilus_file_is_directory (file_a); + gboolean b_is_directory = nautilus_file_is_directory (file_b); + + if (a_is_directory && !b_is_directory) + { + return GTK_ORDERING_SMALLER; + } + if (b_is_directory && !a_is_directory) + { + return GTK_ORDERING_LARGER; + } + } + return GTK_ORDERING_EQUAL; +} + +static char ** +get_default_visible_columns (NautilusListView *self) +{ + NautilusFile *file; + + file = nautilus_files_view_get_directory_as_file (NAUTILUS_FILES_VIEW (self)); + + if (nautilus_file_is_in_trash (file)) + { + return g_strdupv ((gchar **) default_columns_for_trash); + } + + if (nautilus_file_is_in_recent (file)) + { + return g_strdupv ((gchar **) default_columns_for_recent); + } + + return g_settings_get_strv (nautilus_list_view_preferences, + NAUTILUS_PREFERENCES_LIST_VIEW_DEFAULT_VISIBLE_COLUMNS); +} + +static char ** +get_visible_columns (NautilusListView *self) +{ + NautilusFile *file; + g_autofree gchar **visible_columns = NULL; + + file = nautilus_files_view_get_directory_as_file (NAUTILUS_FILES_VIEW (self)); + + visible_columns = nautilus_file_get_metadata_list (file, + NAUTILUS_METADATA_KEY_LIST_VIEW_VISIBLE_COLUMNS); + if (visible_columns == NULL || visible_columns[0] == NULL) + { + return get_default_visible_columns (self); + } + + return g_steal_pointer (&visible_columns); +} + +static char ** +get_default_column_order (NautilusListView *self) +{ + NautilusFile *file; + + file = nautilus_files_view_get_directory_as_file (NAUTILUS_FILES_VIEW (self)); + + if (nautilus_file_is_in_trash (file)) + { + return g_strdupv ((gchar **) default_columns_for_trash); + } + + if (nautilus_file_is_in_recent (file)) + { + return g_strdupv ((gchar **) default_columns_for_recent); + } + + return g_settings_get_strv (nautilus_list_view_preferences, + NAUTILUS_PREFERENCES_LIST_VIEW_DEFAULT_COLUMN_ORDER); +} + +static char ** +get_column_order (NautilusListView *self) +{ + NautilusFile *file; + g_autofree gchar **column_order = NULL; + + file = nautilus_files_view_get_directory_as_file (NAUTILUS_FILES_VIEW (self)); + + column_order = nautilus_file_get_metadata_list (file, + NAUTILUS_METADATA_KEY_LIST_VIEW_COLUMN_ORDER); + + if (column_order != NULL && column_order[0] != NULL) + { + return g_steal_pointer (&column_order); + } + + return get_default_column_order (self); +} +static void +update_columns_settings_from_metadata_and_preferences (NautilusListView *self) +{ + g_auto (GStrv) column_order = get_column_order (self); + g_auto (GStrv) visible_columns = get_visible_columns (self); + + apply_columns_settings (self, column_order, visible_columns); +} + +static GFile * +get_base_location (NautilusListView *self) +{ + NautilusDirectory *directory; + GFile *base_location = NULL; + + directory = nautilus_files_view_get_model (NAUTILUS_FILES_VIEW (self)); + if (NAUTILUS_IS_SEARCH_DIRECTORY (directory)) + { + g_autoptr (NautilusQuery) query = NULL; + g_autoptr (GFile) location = NULL; + + query = nautilus_search_directory_get_query (NAUTILUS_SEARCH_DIRECTORY (directory)); + location = nautilus_query_get_location (query); + + if (!nautilus_is_recent_directory (location) && + !nautilus_is_starred_directory (location) && + !nautilus_is_trash_directory (location)) + { + base_location = g_steal_pointer (&location); + } + } + + return base_location; +} + +static void +on_column_view_item_activated (GtkGridView *grid_view, + guint position, + gpointer user_data) +{ + NautilusListView *self = NAUTILUS_LIST_VIEW (user_data); + + nautilus_files_view_activate_selection (NAUTILUS_FILES_VIEW (self)); +} + +static GtkColumnView * +create_view_ui (NautilusListView *self) +{ + NautilusViewModel *model; + GtkWidget *widget; + + model = nautilus_list_base_get_model (NAUTILUS_LIST_BASE (self)); + widget = gtk_column_view_new (GTK_SELECTION_MODEL (model)); + + gtk_widget_set_hexpand (widget, TRUE); + + + /* We don't use the built-in child activation feature for click because it + * doesn't fill all our needs nor does it match our expected behavior. + * Instead, we roll our own event handling and double/single click mode. + * However, GtkColumnView:single-click-activate has other effects besides + * activation, as it affects the selection behavior as well (e.g. selects on + * hover). Setting it to FALSE gives us the expected behavior. */ + gtk_column_view_set_single_click_activate (GTK_COLUMN_VIEW (widget), FALSE); + gtk_column_view_set_enable_rubberband (GTK_COLUMN_VIEW (widget), TRUE); + + /* While we don't want to use GTK's click activation, we'll let it handle + * the key activation part (with Enter). + */ + g_signal_connect (widget, "activate", G_CALLBACK (on_column_view_item_activated), self); + + return GTK_COLUMN_VIEW (widget); +} + +static void +column_chooser_changed_callback (NautilusColumnChooser *chooser, + NautilusListView *view) +{ + NautilusFile *file; + char **visible_columns; + char **column_order; + + file = nautilus_files_view_get_directory_as_file (NAUTILUS_FILES_VIEW (view)); + + nautilus_column_chooser_get_settings (chooser, + &visible_columns, + &column_order); + + nautilus_file_set_metadata_list (file, + NAUTILUS_METADATA_KEY_LIST_VIEW_VISIBLE_COLUMNS, + visible_columns); + nautilus_file_set_metadata_list (file, + NAUTILUS_METADATA_KEY_LIST_VIEW_COLUMN_ORDER, + column_order); + + apply_columns_settings (view, column_order, visible_columns); + + g_strfreev (visible_columns); + g_strfreev (column_order); +} + +static void +column_chooser_set_from_arrays (NautilusColumnChooser *chooser, + NautilusListView *view, + char **visible_columns, + char **column_order) +{ + g_signal_handlers_block_by_func + (chooser, G_CALLBACK (column_chooser_changed_callback), view); + + nautilus_column_chooser_set_settings (chooser, + visible_columns, + column_order); + + g_signal_handlers_unblock_by_func + (chooser, G_CALLBACK (column_chooser_changed_callback), view); +} + +static void +column_chooser_set_from_settings (NautilusColumnChooser *chooser, + NautilusListView *view) +{ + char **visible_columns; + char **column_order; + + visible_columns = get_visible_columns (view); + column_order = get_column_order (view); + + column_chooser_set_from_arrays (chooser, view, + visible_columns, column_order); + + g_strfreev (visible_columns); + g_strfreev (column_order); +} + +static void +column_chooser_use_default_callback (NautilusColumnChooser *chooser, + NautilusListView *view) +{ + NautilusFile *file; + char **default_columns; + char **default_order; + + file = nautilus_files_view_get_directory_as_file + (NAUTILUS_FILES_VIEW (view)); + + nautilus_file_set_metadata_list (file, NAUTILUS_METADATA_KEY_LIST_VIEW_COLUMN_ORDER, NULL); + nautilus_file_set_metadata_list (file, NAUTILUS_METADATA_KEY_LIST_VIEW_VISIBLE_COLUMNS, NULL); + + /* set view values ourselves, as new metadata could not have been + * updated yet. + */ + default_columns = get_default_visible_columns (view); + default_order = get_default_column_order (view); + + apply_columns_settings (view, default_order, default_columns); + column_chooser_set_from_arrays (chooser, view, + default_columns, default_order); + + g_strfreev (default_columns); + g_strfreev (default_order); +} + +static GtkWidget * +create_column_editor (NautilusListView *view) +{ + g_autoptr (GtkBuilder) builder = NULL; + GtkWidget *window; + AdwWindowTitle *window_title; + GtkWidget *box; + GtkWidget *column_chooser; + NautilusFile *file; + char *name; + + builder = gtk_builder_new_from_resource ("/org/gnome/nautilus/ui/nautilus-list-view-column-editor.ui"); + + window = GTK_WIDGET (gtk_builder_get_object (builder, "window")); + gtk_window_set_transient_for (GTK_WINDOW (window), + GTK_WINDOW (gtk_widget_get_root (GTK_WIDGET (view)))); + + file = nautilus_files_view_get_directory_as_file (NAUTILUS_FILES_VIEW (view)); + name = nautilus_file_get_display_name (file); + window_title = ADW_WINDOW_TITLE (gtk_builder_get_object (builder, "window_title")); + adw_window_title_set_subtitle (window_title, name); + g_free (name); + + box = GTK_WIDGET (gtk_builder_get_object (builder, "box")); + + column_chooser = nautilus_column_chooser_new (file); + gtk_widget_set_vexpand (column_chooser, TRUE); + gtk_box_append (GTK_BOX (box), column_chooser); + + g_signal_connect (column_chooser, "changed", + G_CALLBACK (column_chooser_changed_callback), + view); + g_signal_connect (column_chooser, "use-default", + G_CALLBACK (column_chooser_use_default_callback), + view); + + column_chooser_set_from_settings + (NAUTILUS_COLUMN_CHOOSER (column_chooser), view); + + return window; +} + +static void +action_visible_columns (GSimpleAction *action, + GVariant *state, + gpointer user_data) +{ + NautilusListView *self = NAUTILUS_LIST_VIEW (user_data); + + if (self->column_editor) + { + gtk_widget_show (self->column_editor); + } + else + { + self->column_editor = create_column_editor (self); + g_object_add_weak_pointer (G_OBJECT (self->column_editor), + (gpointer *) &self->column_editor); + + gtk_widget_show (self->column_editor); + } +} + +static void +action_sort_order_changed (GSimpleAction *action, + GVariant *value, + gpointer user_data) +{ + const gchar *target_name; + gboolean reversed; + NautilusFileSortType sort_type; + NautilusListView *self; + GListModel *view_columns; + NautilusViewModel *model; + g_autoptr (GtkColumnViewColumn) sort_column = NULL; + GtkSorter *sorter; + + /* This array makes the #NautilusFileSortType values correspond to the + * respective column attribute. + */ + const char *attributes[] = + { + "name", + "size", + "type", + "date_modified", + "date_accessed", + "date_created", + "starred", + "trashed_on", + "search_relevance", + "recency", + NULL + }; + + /* Don't resort if the action is in the same state as before */ + if (g_variant_equal (value, g_action_get_state (G_ACTION (action)))) + { + return; + } + + self = NAUTILUS_LIST_VIEW (user_data); + g_variant_get (value, "(&sb)", &target_name, &reversed); + + if (g_strcmp0 (target_name, "unknown") == 0) + { + /* Sort order has been changed without using this action. */ + g_simple_action_set_state (action, value); + return; + } + + sort_type = get_sorts_type_from_metadata_text (target_name); + + view_columns = gtk_column_view_get_columns (self->view_ui); + for (guint i = 0; i < g_list_model_get_n_items (view_columns); i++) + { + g_autoptr (GtkColumnViewColumn) view_column = NULL; + GtkListItemFactory *factory; + NautilusColumn *nautilus_column; + gchar *attribute; + + view_column = g_list_model_get_item (view_columns, i); + factory = gtk_column_view_column_get_factory (view_column); + nautilus_column = g_hash_table_lookup (self->factory_to_column_map, factory); + if (nautilus_column == NULL) + { + continue; + } + g_object_get (nautilus_column, "attribute", &attribute, NULL); + if (g_strcmp0 (attributes[sort_type], attribute) == 0) + { + sort_column = g_steal_pointer (&view_column); + break; + } + } + + model = nautilus_list_base_get_model (NAUTILUS_LIST_BASE (self)); + sorter = nautilus_view_model_get_sorter (model); + + /* Ask the column view to sort by column if it hasn't just done so already. */ + if (!self->column_header_was_clicked) + { + g_signal_handlers_block_by_func (sorter, on_sorter_changed, self); + /* FIXME: Set NULL to stop drawing the arrow on previous sort column + * to workaround https://gitlab.gnome.org/GNOME/gtk/-/issues/4696 */ + gtk_column_view_sort_by_column (self->view_ui, NULL, FALSE); + gtk_column_view_sort_by_column (self->view_ui, sort_column, reversed); + g_signal_handlers_unblock_by_func (sorter, on_sorter_changed, self); + } + + self->column_header_was_clicked = FALSE; + + set_directory_sort_metadata (nautilus_files_view_get_directory_as_file (NAUTILUS_FILES_VIEW (self)), + target_name, + reversed); + + g_simple_action_set_state (action, value); +} + +static void +set_zoom_level (NautilusListView *self, + guint new_level) +{ + self->zoom_level = new_level; + + nautilus_list_base_set_icon_size (NAUTILUS_LIST_BASE (self), + get_icon_size_for_zoom_level (new_level)); + + if (self->zoom_level == NAUTILUS_LIST_ZOOM_LEVEL_SMALL) + { + gtk_widget_add_css_class (GTK_WIDGET (self), "compact"); + } + else + { + gtk_widget_remove_css_class (GTK_WIDGET (self), "compact"); + } + + nautilus_files_view_update_toolbar_menus (NAUTILUS_FILES_VIEW (self)); +} + +static void +action_zoom_to_level (GSimpleAction *action, + GVariant *state, + gpointer user_data) +{ + NautilusListView *self = NAUTILUS_LIST_VIEW (user_data); + int zoom_level; + + zoom_level = g_variant_get_int32 (state); + set_zoom_level (self, zoom_level); + g_simple_action_set_state (G_SIMPLE_ACTION (action), state); + + if (g_settings_get_enum (nautilus_list_view_preferences, + NAUTILUS_PREFERENCES_LIST_VIEW_DEFAULT_ZOOM_LEVEL) != zoom_level) + { + g_settings_set_enum (nautilus_list_view_preferences, + NAUTILUS_PREFERENCES_LIST_VIEW_DEFAULT_ZOOM_LEVEL, + zoom_level); + } +} + +const GActionEntry list_view_entries[] = +{ + { "visible-columns", action_visible_columns }, + { "sort", NULL, "(sb)", "('invalid',false)", action_sort_order_changed }, + { "zoom-to-level", NULL, NULL, "1", action_zoom_to_level } +}; + +static void +real_begin_loading (NautilusFilesView *files_view) +{ + NautilusListView *self = NAUTILUS_LIST_VIEW (files_view); + NautilusFile *file; + + /* We need to setup the columns before chaining up */ + update_columns_settings_from_metadata_and_preferences (self); + + NAUTILUS_FILES_VIEW_CLASS (nautilus_list_view_parent_class)->begin_loading (files_view); + + self->clicked_column_attribute_q = 0; + + self->path_attribute_q = 0; + g_clear_object (&self->file_path_base_location); + file = nautilus_files_view_get_directory_as_file (files_view); + if (nautilus_file_is_in_trash (file)) + { + self->path_attribute_q = g_quark_from_string ("trash_orig_path"); + self->file_path_base_location = get_base_location (self); + } + else if (nautilus_file_is_in_search (file) || + nautilus_file_is_in_recent (file) || + nautilus_file_is_in_starred (file)) + { + self->path_attribute_q = g_quark_from_string ("where"); + self->file_path_base_location = get_base_location (self); + } +} + +static void +real_bump_zoom_level (NautilusFilesView *files_view, + int zoom_increment) +{ + NautilusListView *self = NAUTILUS_LIST_VIEW (files_view); + NautilusListZoomLevel new_level; + + new_level = self->zoom_level + zoom_increment; + + if (new_level >= NAUTILUS_LIST_ZOOM_LEVEL_SMALL && + new_level <= NAUTILUS_LIST_ZOOM_LEVEL_LARGE) + { + g_action_group_change_action_state (self->action_group, + "zoom-to-level", + g_variant_new_int32 (new_level)); + } +} + +static gint +get_default_zoom_level (void) +{ + NautilusListZoomLevel default_zoom_level; + + default_zoom_level = g_settings_get_enum (nautilus_list_view_preferences, + NAUTILUS_PREFERENCES_LIST_VIEW_DEFAULT_ZOOM_LEVEL); + + /* Sanitize preference value. */ + return CLAMP (default_zoom_level, + NAUTILUS_LIST_ZOOM_LEVEL_SMALL, + NAUTILUS_LIST_ZOOM_LEVEL_LARGE); +} + +static void +real_restore_standard_zoom_level (NautilusFilesView *files_view) +{ + NautilusListView *self; + + self = NAUTILUS_LIST_VIEW (files_view); + g_action_group_change_action_state (self->action_group, + "zoom-to-level", + g_variant_new_int32 (NAUTILUS_LIST_ZOOM_LEVEL_MEDIUM)); +} + +static gboolean +real_can_zoom_in (NautilusFilesView *files_view) +{ + NautilusListView *self = NAUTILUS_LIST_VIEW (files_view); + + return self->zoom_level < NAUTILUS_LIST_ZOOM_LEVEL_LARGE; +} + +static gboolean +real_can_zoom_out (NautilusFilesView *files_view) +{ + NautilusListView *self = NAUTILUS_LIST_VIEW (files_view); + + return self->zoom_level > NAUTILUS_LIST_ZOOM_LEVEL_SMALL; +} + +static gboolean +real_is_zoom_level_default (NautilusFilesView *files_view) +{ + NautilusListView *self; + guint icon_size; + + self = NAUTILUS_LIST_VIEW (files_view); + icon_size = get_icon_size_for_zoom_level (self->zoom_level); + + return icon_size == NAUTILUS_LIST_ICON_SIZE_MEDIUM; +} + +static void +real_sort_directories_first_changed (NautilusFilesView *files_view) +{ + NautilusListView *self = NAUTILUS_LIST_VIEW (files_view); + NautilusViewModel *model; + + self->directories_first = nautilus_files_view_should_sort_directories_first (NAUTILUS_FILES_VIEW (self)); + + /* Reset the sorter to trigger ressorting */ + model = nautilus_list_base_get_model (NAUTILUS_LIST_BASE (self)); + nautilus_view_model_set_sorter (model, nautilus_view_model_get_sorter (model)); +} + +static void +on_sorter_changed (GtkSorter *sorter, + GtkSorterChange change, + gpointer user_data) +{ + NautilusListView *self = NAUTILUS_LIST_VIEW (user_data); + NautilusViewModel *model = nautilus_list_base_get_model (NAUTILUS_LIST_BASE (self)); + + /* Set the conditions to capture the sort attribute the first time that + * nautilus_list_view_sort() is called. */ + self->column_header_was_clicked = TRUE; + self->clicked_column_attribute_q = 0; + + /* If there is only one file, enforce a comparison against a dummy item, to + * ensure nautilus_list_view_sort() gets called at least once. */ + if (g_list_model_get_n_items (G_LIST_MODEL (model)) == 1) + { + NautilusViewItem *item = g_list_model_get_item (G_LIST_MODEL (model), 0); + g_autoptr (NautilusViewItem) dummy_item = NULL; + + dummy_item = nautilus_view_item_new (nautilus_view_item_get_file (item), + NAUTILUS_LIST_ICON_SIZE_SMALL); + + gtk_sorter_compare (sorter, item, dummy_item); + } +} + +static void +on_after_sorter_changed (GtkSorter *sorter, + GtkSorterChange change, + gpointer user_data) +{ + NautilusListView *self = NAUTILUS_LIST_VIEW (user_data); + GActionGroup *action_group = nautilus_files_view_get_action_group (NAUTILUS_FILES_VIEW (self)); + g_autoptr (GVariant) state = NULL; + const gchar *new_sort_text; + gboolean reversed; + const gchar *current_sort_text; + + if (!self->column_header_was_clicked || self->clicked_column_attribute_q == 0) + { + return; + } + + state = g_action_group_get_action_state (action_group, "sort"); + g_variant_get (state, "(&sb)", ¤t_sort_text, &reversed); + + new_sort_text = g_quark_to_string (self->clicked_column_attribute_q); + + if (g_strcmp0 (new_sort_text, current_sort_text) == 0) + { + reversed = !reversed; + } + else + { + reversed = FALSE; + } + + g_action_group_change_action_state (action_group, "sort", + g_variant_new ("(sb)", new_sort_text, reversed)); +} + +static guint +real_get_view_id (NautilusFilesView *files_view) +{ + return NAUTILUS_VIEW_LIST_ID; +} + +static void +on_item_click_released_workaround (GtkGestureClick *gesture, + gint n_press, + gdouble x, + gdouble y, + gpointer user_data) +{ + NautilusViewCell *cell = user_data; + NautilusListView *self = NAUTILUS_LIST_VIEW (nautilus_view_cell_get_view (cell)); + GdkModifierType modifiers; + + modifiers = gtk_event_controller_get_current_event_state (GTK_EVENT_CONTROLLER (gesture)); + if (n_press == 1 && + modifiers & (GDK_CONTROL_MASK | GDK_SHIFT_MASK)) + { + NautilusViewModel *model; + g_autoptr (NautilusViewItem) item = NULL; + guint i; + + model = nautilus_list_base_get_model (NAUTILUS_LIST_BASE (self)); + item = nautilus_view_cell_get_item (cell); + g_return_if_fail (item != NULL); + i = nautilus_view_model_get_index (model, item); + + gtk_widget_activate_action (GTK_WIDGET (cell), + "list.select-item", + "(ubb)", + i, + modifiers & GDK_CONTROL_MASK, + modifiers & GDK_SHIFT_MASK); + } +} + +/* This whole event handler is a workaround to a GtkColumnView bug: it + * activates the list|select-item action twice, which may cause the + * second activation to reverse the effects of the first: + * https://gitlab.gnome.org/GNOME/gtk/-/issues/4819 + * + * As a workaround, we are going to activate the action a 3rd time. + * The third time is the charm, as the saying goes. */ +static void +setup_selection_click_workaround (NautilusViewCell *cell) +{ + GtkEventController *controller; + + controller = GTK_EVENT_CONTROLLER (gtk_gesture_click_new ()); + gtk_widget_add_controller (GTK_WIDGET (cell), controller); + gtk_event_controller_set_propagation_phase (controller, GTK_PHASE_BUBBLE); + gtk_gesture_single_set_button (GTK_GESTURE_SINGLE (controller), GDK_BUTTON_PRIMARY); + g_signal_connect (controller, "released", G_CALLBACK (on_item_click_released_workaround), cell); +} + +static void +setup_name_cell (GtkSignalListItemFactory *factory, + GtkListItem *listitem, + gpointer user_data) +{ + NautilusListView *self = NAUTILUS_LIST_VIEW (user_data); + NautilusViewCell *cell; + + cell = nautilus_name_cell_new (NAUTILUS_LIST_BASE (self)); + setup_cell_common (listitem, cell); + + nautilus_name_cell_set_path (NAUTILUS_NAME_CELL (cell), + self->path_attribute_q, + self->file_path_base_location); + if (NAUTILUS_IS_SEARCH_DIRECTORY (nautilus_files_view_get_model (NAUTILUS_FILES_VIEW (self)))) + { + nautilus_name_cell_show_snippet (NAUTILUS_NAME_CELL (cell)); + } + + setup_selection_click_workaround (cell); +} + +static void +bind_name_cell (GtkSignalListItemFactory *factory, + GtkListItem *listitem, + gpointer user_data) +{ + GtkWidget *cell; + NautilusViewItem *item; + + cell = gtk_list_item_get_child (listitem); + item = NAUTILUS_VIEW_ITEM (gtk_list_item_get_item (listitem)); + + nautilus_view_item_set_item_ui (item, gtk_list_item_get_child (listitem)); + + if (nautilus_view_cell_once (NAUTILUS_VIEW_CELL (cell))) + { + GtkWidget *row_widget; + + /* At the time of ::setup emission, the item ui has got no parent yet, + * that's why we need to complete the widget setup process here, on the + * first time ::bind is emitted. */ + row_widget = gtk_widget_get_parent (gtk_widget_get_parent (cell)); + + gtk_accessible_update_relation (GTK_ACCESSIBLE (row_widget), + GTK_ACCESSIBLE_RELATION_LABELLED_BY, cell, NULL, + -1); + } +} + +static void +unbind_name_cell (GtkSignalListItemFactory *factory, + GtkListItem *listitem, + gpointer user_data) +{ + NautilusViewItem *item; + + item = NAUTILUS_VIEW_ITEM (gtk_list_item_get_item (listitem)); + g_return_if_fail (NAUTILUS_IS_VIEW_ITEM (item)); + + nautilus_view_item_set_item_ui (item, NULL); +} + +static void +setup_star_cell (GtkSignalListItemFactory *factory, + GtkListItem *listitem, + gpointer user_data) +{ + NautilusViewCell *cell; + + cell = nautilus_star_cell_new (NAUTILUS_LIST_BASE (user_data)); + setup_cell_common (listitem, cell); + setup_selection_click_workaround (cell); +} + +static void +setup_label_cell (GtkSignalListItemFactory *factory, + GtkListItem *listitem, + gpointer user_data) +{ + NautilusListView *self = user_data; + NautilusColumn *nautilus_column; + NautilusViewCell *cell; + + nautilus_column = g_hash_table_lookup (self->factory_to_column_map, factory); + + cell = nautilus_label_cell_new (NAUTILUS_LIST_BASE (user_data), nautilus_column); + setup_cell_common (listitem, cell); + setup_selection_click_workaround (cell); +} + +static void +setup_view_columns (NautilusListView *self) +{ + GtkListItemFactory *factory; + g_autolist (NautilusColumn) nautilus_columns = NULL; + + nautilus_columns = nautilus_get_all_columns (); + + self->factory_to_column_map = g_hash_table_new_full (g_direct_hash, + g_direct_equal, + NULL, + g_object_unref); + self->all_view_columns_hash = g_hash_table_new_full (g_str_hash, + g_str_equal, + (GDestroyNotify) g_free, + g_object_unref); + + for (GList *l = nautilus_columns; l != NULL; l = l->next) + { + NautilusColumn *nautilus_column = NAUTILUS_COLUMN (l->data); + SortData *data; + g_autofree gchar *name = NULL; + g_autofree gchar *label = NULL; + GQuark attribute_q = 0; + GtkSortType sort_order; + g_autoptr (GtkCustomSorter) sorter = NULL; + g_autoptr (GtkColumnViewColumn) view_column = NULL; + + g_object_get (nautilus_column, + "name", &name, + "label", &label, + "attribute_q", &attribute_q, + "default-sort-order", &sort_order, + NULL); + + data = g_new0 (SortData, 1); + data->attribute = attribute_q; + data->view = self; + + sorter = gtk_custom_sorter_new (nautilus_list_view_sort, + data, + g_free); + + factory = gtk_signal_list_item_factory_new (); + view_column = gtk_column_view_column_new (NULL, factory); + gtk_column_view_column_set_expand (view_column, FALSE); + gtk_column_view_column_set_resizable (view_column, TRUE); + gtk_column_view_column_set_title (view_column, label); + gtk_column_view_column_set_sorter (view_column, GTK_SORTER (sorter)); + + if (!strcmp (name, "name")) + { + g_signal_connect (factory, "setup", G_CALLBACK (setup_name_cell), self); + g_signal_connect (factory, "bind", G_CALLBACK (bind_name_cell), self); + g_signal_connect (factory, "unbind", G_CALLBACK (unbind_name_cell), self); + + gtk_column_view_column_set_expand (view_column, TRUE); + } + else if (g_strcmp0 (name, "starred") == 0) + { + g_signal_connect (factory, "setup", G_CALLBACK (setup_star_cell), self); + + gtk_column_view_column_set_title (view_column, ""); + gtk_column_view_column_set_resizable (view_column, FALSE); + + self->star_column = view_column; + } + else + { + g_signal_connect (factory, "setup", G_CALLBACK (setup_label_cell), self); + } + + g_hash_table_insert (self->factory_to_column_map, + factory, + g_object_ref (nautilus_column)); + g_hash_table_insert (self->all_view_columns_hash, + g_steal_pointer (&name), + g_steal_pointer (&view_column)); + } +} + +static void +nautilus_list_view_init (NautilusListView *self) +{ + NautilusViewModel *model; + GtkWidget *content_widget; + g_autoptr (GtkCustomSorter) directories_sorter = NULL; + g_autoptr (GtkMultiSorter) sorter = NULL; + + gtk_widget_add_css_class (GTK_WIDGET (self), "nautilus-list-view"); + + g_signal_connect_object (nautilus_list_view_preferences, + "changed::" NAUTILUS_PREFERENCES_LIST_VIEW_DEFAULT_VISIBLE_COLUMNS, + G_CALLBACK (update_columns_settings_from_metadata_and_preferences), + self, + G_CONNECT_SWAPPED); + g_signal_connect_object (nautilus_list_view_preferences, + "changed::" NAUTILUS_PREFERENCES_LIST_VIEW_DEFAULT_COLUMN_ORDER, + G_CALLBACK (update_columns_settings_from_metadata_and_preferences), + self, + G_CONNECT_SWAPPED); + + content_widget = nautilus_files_view_get_content_widget (NAUTILUS_FILES_VIEW (self)); + + self->view_ui = create_view_ui (self); + nautilus_list_base_setup_gestures (NAUTILUS_LIST_BASE (self)); + + setup_view_columns (self); + + self->directories_first = nautilus_files_view_should_sort_directories_first (NAUTILUS_FILES_VIEW (self)); + directories_sorter = gtk_custom_sorter_new (sort_directories_func, &self->directories_first, NULL); + + sorter = gtk_multi_sorter_new (); + gtk_multi_sorter_append (sorter, g_object_ref (GTK_SORTER (directories_sorter))); + gtk_multi_sorter_append (sorter, g_object_ref (gtk_column_view_get_sorter (self->view_ui))); + g_signal_connect_object (sorter, "changed", G_CALLBACK (on_sorter_changed), self, 0); + g_signal_connect_object (sorter, "changed", G_CALLBACK (on_after_sorter_changed), self, G_CONNECT_AFTER); + + model = nautilus_list_base_get_model (NAUTILUS_LIST_BASE (self)); + nautilus_view_model_set_sorter (model, GTK_SORTER (sorter)); + + gtk_scrolled_window_set_child (GTK_SCROLLED_WINDOW (content_widget), + GTK_WIDGET (self->view_ui)); + + self->action_group = nautilus_files_view_get_action_group (NAUTILUS_FILES_VIEW (self)); + g_action_map_add_action_entries (G_ACTION_MAP (self->action_group), + list_view_entries, + G_N_ELEMENTS (list_view_entries), + self); + + self->zoom_level = get_default_zoom_level (); + g_action_group_change_action_state (nautilus_files_view_get_action_group (NAUTILUS_FILES_VIEW (self)), + "zoom-to-level", g_variant_new_int32 (self->zoom_level)); +} + +static void +nautilus_list_view_dispose (GObject *object) +{ + NautilusListView *self = NAUTILUS_LIST_VIEW (object); + NautilusViewModel *model; + + model = nautilus_list_base_get_model (NAUTILUS_LIST_BASE (self)); + nautilus_view_model_set_sorter (model, NULL); + + g_clear_object (&self->file_path_base_location); + g_clear_pointer (&self->factory_to_column_map, g_hash_table_destroy); + g_clear_pointer (&self->all_view_columns_hash, g_hash_table_destroy); + + G_OBJECT_CLASS (nautilus_list_view_parent_class)->dispose (object); +} + +static void +nautilus_list_view_finalize (GObject *object) +{ + G_OBJECT_CLASS (nautilus_list_view_parent_class)->finalize (object); +} + +static void +nautilus_list_view_class_init (NautilusListViewClass *klass) +{ + GObjectClass *object_class = G_OBJECT_CLASS (klass); + NautilusFilesViewClass *files_view_class = NAUTILUS_FILES_VIEW_CLASS (klass); + NautilusListBaseClass *list_base_view_class = NAUTILUS_LIST_BASE_CLASS (klass); + + object_class->dispose = nautilus_list_view_dispose; + object_class->finalize = nautilus_list_view_finalize; + + files_view_class->begin_loading = real_begin_loading; + files_view_class->bump_zoom_level = real_bump_zoom_level; + files_view_class->can_zoom_in = real_can_zoom_in; + files_view_class->can_zoom_out = real_can_zoom_out; + files_view_class->sort_directories_first_changed = real_sort_directories_first_changed; + files_view_class->get_view_id = real_get_view_id; + files_view_class->restore_standard_zoom_level = real_restore_standard_zoom_level; + files_view_class->is_zoom_level_default = real_is_zoom_level_default; + + list_base_view_class->get_icon_size = real_get_icon_size; + list_base_view_class->get_view_ui = real_get_view_ui; + list_base_view_class->scroll_to_item = real_scroll_to_item; +} + +NautilusListView * +nautilus_list_view_new (NautilusWindowSlot *slot) +{ + return g_object_new (NAUTILUS_TYPE_LIST_VIEW, + "window-slot", slot, + NULL); +} diff --git a/src/nautilus-list-view.h b/src/nautilus-list-view.h new file mode 100644 index 0000000..8c21336 --- /dev/null +++ b/src/nautilus-list-view.h @@ -0,0 +1,22 @@ +/* + * Copyright (C) 2000 Eazel, Inc. + * Copyright (C) 2001, 2002 Anders Carlsson + * Copyright (C) 2022 GNOME project contributors + * + * SPDX-License-Identifier: GPL-3.0-or-later + */ + +#pragma once + +#include "nautilus-list-base.h" +#include "nautilus-window-slot.h" + +G_BEGIN_DECLS + +#define NAUTILUS_TYPE_LIST_VIEW (nautilus_list_view_get_type()) + +G_DECLARE_FINAL_TYPE (NautilusListView, nautilus_list_view, NAUTILUS, LIST_VIEW, NautilusListBase) + +NautilusListView *nautilus_list_view_new (NautilusWindowSlot *slot); + +G_END_DECLS diff --git a/src/nautilus-location-entry.c b/src/nautilus-location-entry.c new file mode 100644 index 0000000..4ae027c --- /dev/null +++ b/src/nautilus-location-entry.c @@ -0,0 +1,845 @@ +/* + * Nautilus + * + * Copyright (C) 2000 Eazel, Inc. + * + * Nautilus is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License as + * published by the Free Software Foundation; either version 2 of the + * License, or (at your option) any later version. + * + * Nautilus is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public + * License along with this program; see the file COPYING. If not, + * see . + * + * Author: Maciej Stachowiak + * Ettore Perazzoli + * Michael Meeks + * Andy Hertzfeld + * + */ + +/* nautilus-location-bar.c - Location bar for Nautilus + */ + +#include +#include "nautilus-location-entry.h" + +#include "nautilus-application.h" +#include "nautilus-window.h" +#include +#include +#include +#include +#include "nautilus-file-utilities.h" +#include "nautilus-clipboard.h" +#include +#include +#include +#include +#include + + +typedef struct _NautilusLocationEntryPrivate +{ + char *current_directory; + GFilenameCompleter *completer; + + guint idle_id; + gboolean idle_insert_completion; + + GFile *last_location; + + gboolean has_special_text; + NautilusLocationEntryAction secondary_action; + + GtkEventController *controller; + + GtkEntryCompletion *completion; + GtkListStore *completions_store; + GtkCellRenderer *completion_cell; +} NautilusLocationEntryPrivate; + +enum +{ + CANCEL, + LOCATION_CHANGED, + LAST_SIGNAL +}; + +static guint signals[LAST_SIGNAL]; + +G_DEFINE_TYPE_WITH_PRIVATE (NautilusLocationEntry, nautilus_location_entry, GTK_TYPE_ENTRY); + +static void on_after_insert_text (GtkEditable *editable, + const gchar *text, + gint length, + gint *position, + gpointer data); + +static void on_after_delete_text (GtkEditable *editable, + gint start_pos, + gint end_pos, + gpointer data); + +static GFile * +nautilus_location_entry_get_location (NautilusLocationEntry *entry) +{ + char *user_location; + GFile *location; + + user_location = gtk_editable_get_chars (GTK_EDITABLE (entry), 0, -1); + location = g_file_parse_name (user_location); + g_free (user_location); + + return location; +} + +static void +nautilus_location_entry_set_text (NautilusLocationEntry *entry, + const char *new_text) +{ + GtkEditable *delegate; + + delegate = gtk_editable_get_delegate (GTK_EDITABLE (entry)); + g_signal_handlers_block_by_func (delegate, G_CALLBACK (on_after_insert_text), entry); + g_signal_handlers_block_by_func (delegate, G_CALLBACK (on_after_delete_text), entry); + + gtk_editable_set_text (GTK_EDITABLE (entry), new_text); + + g_signal_handlers_unblock_by_func (delegate, G_CALLBACK (on_after_insert_text), entry); + g_signal_handlers_unblock_by_func (delegate, G_CALLBACK (on_after_delete_text), entry); +} + +static void +nautilus_location_entry_insert_prefix (NautilusLocationEntry *entry, + GtkEntryCompletion *completion) +{ + GtkEditable *delegate; + + delegate = gtk_editable_get_delegate (GTK_EDITABLE (entry)); + g_signal_handlers_block_by_func (delegate, G_CALLBACK (on_after_insert_text), entry); + + gtk_entry_completion_insert_prefix (completion); + + g_signal_handlers_unblock_by_func (delegate, G_CALLBACK (on_after_insert_text), entry); +} + +static void +emit_location_changed (NautilusLocationEntry *entry) +{ + GFile *location; + + location = nautilus_location_entry_get_location (entry); + g_signal_emit (entry, signals[LOCATION_CHANGED], 0, location); + g_object_unref (location); +} + +static void +nautilus_location_entry_update_action (NautilusLocationEntry *entry) +{ + NautilusLocationEntryPrivate *priv; + const char *current_text; + GFile *location; + + priv = nautilus_location_entry_get_instance_private (entry); + + if (priv->last_location == NULL) + { + nautilus_location_entry_set_secondary_action (entry, + NAUTILUS_LOCATION_ENTRY_ACTION_GOTO); + return; + } + + current_text = gtk_editable_get_text (GTK_EDITABLE (entry)); + location = g_file_parse_name (current_text); + + if (g_file_equal (priv->last_location, location)) + { + nautilus_location_entry_set_secondary_action (entry, + NAUTILUS_LOCATION_ENTRY_ACTION_CLEAR); + } + else + { + nautilus_location_entry_set_secondary_action (entry, + NAUTILUS_LOCATION_ENTRY_ACTION_GOTO); + } + + g_object_unref (location); +} + +static int +get_editable_number_of_chars (GtkEditable *editable) +{ + char *text; + int length; + + text = gtk_editable_get_chars (editable, 0, -1); + length = g_utf8_strlen (text, -1); + g_free (text); + return length; +} + +static void +set_position_and_selection_to_end (GtkEditable *editable) +{ + int end; + + end = get_editable_number_of_chars (editable); + gtk_editable_select_region (editable, end, end); + gtk_editable_set_position (editable, end); +} + +static void +nautilus_location_entry_update_current_uri (NautilusLocationEntry *entry, + const char *uri) +{ + NautilusLocationEntryPrivate *priv; + + priv = nautilus_location_entry_get_instance_private (entry); + + g_free (priv->current_directory); + priv->current_directory = g_strdup (uri); + + nautilus_location_entry_set_text (entry, uri); + set_position_and_selection_to_end (GTK_EDITABLE (entry)); +} + +void +nautilus_location_entry_set_location (NautilusLocationEntry *entry, + GFile *location) +{ + NautilusLocationEntryPrivate *priv; + gchar *uri, *formatted_uri; + + g_assert (location != NULL); + + priv = nautilus_location_entry_get_instance_private (entry); + + /* Note: This is called in reaction to external changes, and + * thus should not emit the LOCATION_CHANGED signal. */ + uri = g_file_get_uri (location); + formatted_uri = g_file_get_parse_name (location); + + if (eel_uri_is_search (uri)) + { + nautilus_location_entry_set_special_text (entry, ""); + } + else + { + nautilus_location_entry_update_current_uri (entry, formatted_uri); + } + + /* remember the original location for later comparison */ + if (!priv->last_location || + !g_file_equal (priv->last_location, location)) + { + g_clear_object (&priv->last_location); + priv->last_location = g_object_ref (location); + } + + nautilus_location_entry_update_action (entry); + + /* invalidate the completions list */ + gtk_list_store_clear (priv->completions_store); + + g_free (uri); + g_free (formatted_uri); +} + +static void +set_prefix_dimming (GtkCellRenderer *completion_cell, + char *user_location) +{ + g_autofree char *location_basename = NULL; + PangoAttrList *attrs; + PangoAttribute *attr; + + /* Dim the prefixes of the completion rows, leaving the basenames + * highlighted. This makes it easier to find what you're looking for. + * + * Perhaps a better solution would be to *only* show the basenames, but + * it would take a reimplementation of GtkEntryCompletion to align the + * popover. */ + + location_basename = g_path_get_basename (user_location); + + attrs = pango_attr_list_new (); + + /* 55% opacity. This is the same as the dim-label style class in Adwaita. */ + attr = pango_attr_foreground_alpha_new (36045); + attr->end_index = strlen (user_location) - strlen (location_basename); + pango_attr_list_insert (attrs, attr); + + g_object_set (completion_cell, "attributes", attrs, NULL); + pango_attr_list_unref (attrs); +} + +static gboolean +position_and_selection_are_at_end (GtkEditable *editable) +{ + int end; + int start_sel, end_sel; + + end = get_editable_number_of_chars (editable); + if (gtk_editable_get_selection_bounds (editable, &start_sel, &end_sel)) + { + if (start_sel != end || end_sel != end) + { + return FALSE; + } + } + return gtk_editable_get_position (editable) == end; +} + +/* Update the path completions list based on the current text of the entry. */ +static gboolean +update_completions_store (gpointer callback_data) +{ + NautilusLocationEntry *entry; + NautilusLocationEntryPrivate *priv; + GtkEditable *editable; + g_autofree char *absolute_location = NULL; + g_autofree char *user_location = NULL; + gboolean is_relative = FALSE; + int start_sel; + g_autofree char *uri_scheme = NULL; + g_auto (GStrv) completions = NULL; + char *completion; + int i; + GtkTreeIter iter; + int current_dir_strlen; + + entry = NAUTILUS_LOCATION_ENTRY (callback_data); + priv = nautilus_location_entry_get_instance_private (entry); + editable = GTK_EDITABLE (entry); + + priv->idle_id = 0; + + /* Only do completions when we are typing at the end of the + * text. */ + if (!position_and_selection_are_at_end (editable)) + { + return FALSE; + } + + if (gtk_editable_get_selection_bounds (editable, &start_sel, NULL)) + { + user_location = gtk_editable_get_chars (editable, 0, start_sel); + } + else + { + user_location = gtk_editable_get_chars (editable, 0, -1); + } + + g_strstrip (user_location); + set_prefix_dimming (priv->completion_cell, user_location); + + uri_scheme = g_uri_parse_scheme (user_location); + + if (!g_path_is_absolute (user_location) && uri_scheme == NULL && user_location[0] != '~') + { + is_relative = TRUE; + absolute_location = g_build_filename (priv->current_directory, user_location, NULL); + } + else + { + absolute_location = g_steal_pointer (&user_location); + } + + completions = g_filename_completer_get_completions (priv->completer, absolute_location); + + /* populate the completions model */ + gtk_list_store_clear (priv->completions_store); + + current_dir_strlen = strlen (priv->current_directory); + for (i = 0; completions[i] != NULL; i++) + { + completion = completions[i]; + + if (is_relative && strlen (completion) >= current_dir_strlen) + { + /* For relative paths, we need to strip the current directory + * (and the trailing slash) so the completions will match what's + * in the text entry */ + completion += current_dir_strlen; + if (G_IS_DIR_SEPARATOR (completion[0])) + { + completion++; + } + } + + gtk_list_store_append (priv->completions_store, &iter); + gtk_list_store_set (priv->completions_store, &iter, 0, completion, -1); + } + + /* refilter the completions dropdown */ + gtk_entry_completion_complete (priv->completion); + + if (priv->idle_insert_completion) + { + /* insert the completion */ + nautilus_location_entry_insert_prefix (entry, priv->completion); + } + + return FALSE; +} + +static void +got_completion_data_callback (GFilenameCompleter *completer, + NautilusLocationEntry *entry) +{ + NautilusLocationEntryPrivate *priv; + + priv = nautilus_location_entry_get_instance_private (entry); + + if (priv->idle_id) + { + g_source_remove (priv->idle_id); + priv->idle_id = 0; + } + update_completions_store (entry); +} + +static void +finalize (GObject *object) +{ + NautilusLocationEntry *entry; + NautilusLocationEntryPrivate *priv; + + entry = NAUTILUS_LOCATION_ENTRY (object); + priv = nautilus_location_entry_get_instance_private (entry); + + g_object_unref (priv->completer); + + g_clear_object (&priv->last_location); + g_clear_object (&priv->completion); + g_clear_object (&priv->completions_store); + g_free (priv->current_directory); + + G_OBJECT_CLASS (nautilus_location_entry_parent_class)->finalize (object); +} + +static void +nautilus_location_entry_dispose (GObject *object) +{ + NautilusLocationEntry *entry; + NautilusLocationEntryPrivate *priv; + + entry = NAUTILUS_LOCATION_ENTRY (object); + priv = nautilus_location_entry_get_instance_private (entry); + + /* cancel the pending idle call, if any */ + if (priv->idle_id != 0) + { + g_source_remove (priv->idle_id); + priv->idle_id = 0; + } + + + G_OBJECT_CLASS (nautilus_location_entry_parent_class)->dispose (object); +} + +static void +on_has_focus_changed (GObject *object, + GParamSpec *pspec, + gpointer user_data) +{ + NautilusLocationEntry *entry; + NautilusLocationEntryPrivate *priv; + + if (!gtk_widget_has_focus (GTK_WIDGET (object))) + { + return; + } + + entry = NAUTILUS_LOCATION_ENTRY (object); + priv = nautilus_location_entry_get_instance_private (entry); + + /* The entry has text which is not worth preserving on focus-in. */ + if (priv->has_special_text) + { + nautilus_location_entry_set_text (entry, ""); + } +} + +static void +nautilus_location_entry_text_changed (NautilusLocationEntry *entry, + GParamSpec *pspec) +{ + NautilusLocationEntryPrivate *priv; + + priv = nautilus_location_entry_get_instance_private (entry); + + priv->has_special_text = FALSE; +} + +static void +nautilus_location_entry_icon_release (GtkEntry *gentry, + GtkEntryIconPosition position, + gpointer unused) +{ + NautilusLocationEntry *entry; + NautilusLocationEntryPrivate *priv; + + entry = NAUTILUS_LOCATION_ENTRY (gentry); + priv = nautilus_location_entry_get_instance_private (entry); + + switch (priv->secondary_action) + { + case NAUTILUS_LOCATION_ENTRY_ACTION_GOTO: + { + g_signal_emit_by_name (gentry, "activate", gentry); + } + break; + + case NAUTILUS_LOCATION_ENTRY_ACTION_CLEAR: + { + nautilus_location_entry_set_text (entry, ""); + } + break; + + default: + { + g_assert_not_reached (); + } + } +} + +static gboolean +nautilus_location_entry_key_pressed (GtkEventControllerKey *controller, + unsigned int keyval, + unsigned int keycode, + GdkModifierType state, + gpointer user_data) +{ + GtkWidget *widget; + GtkEditable *editable; + gboolean selected; + + + widget = gtk_event_controller_get_widget (GTK_EVENT_CONTROLLER (controller)); + editable = GTK_EDITABLE (widget); + selected = gtk_editable_get_selection_bounds (editable, NULL, NULL); + + if (!gtk_editable_get_editable (editable)) + { + return GDK_EVENT_PROPAGATE; + } + + /* The location bar entry wants TAB to work kind of + * like it does in the shell for command completion, + * so if we get a tab and there's a selection, we + * should position the insertion point at the end of + * the selection. + */ + if (keyval == GDK_KEY_Tab && !(state & (GDK_CONTROL_MASK | GDK_SHIFT_MASK))) + { + if (selected) + { + int position; + + position = strlen (gtk_editable_get_text (GTK_EDITABLE (editable))); + gtk_editable_select_region (editable, position, position); + } + else + { + gtk_widget_error_bell (widget); + } + + return GDK_EVENT_STOP; + } + + if ((keyval == GDK_KEY_Right || keyval == GDK_KEY_End) && + !(state & (GDK_SHIFT_MASK | GDK_CONTROL_MASK)) && selected) + { + set_position_and_selection_to_end (editable); + } + + return GDK_EVENT_PROPAGATE; +} + +static void +after_text_change (NautilusLocationEntry *self, + gboolean insert) +{ + NautilusLocationEntryPrivate *priv = nautilus_location_entry_get_instance_private (self); + + /* Only insert a completion if a character was typed. Otherwise, + * update the completions store (i.e. in case backspace was pressed) + * but don't insert the completion into the entry. */ + priv->idle_insert_completion = insert; + + /* Do the expand at idle time to avoid slowing down typing when the + * directory is large. */ + if (priv->idle_id == 0) + { + priv->idle_id = g_idle_add (update_completions_store, self); + } +} + +static void +on_after_insert_text (GtkEditable *editable, + const gchar *text, + gint length, + gint *position, + gpointer data) +{ + NautilusLocationEntry *self = NAUTILUS_LOCATION_ENTRY (data); + + after_text_change (self, TRUE); +} + +static void +on_after_delete_text (GtkEditable *editable, + gint start_pos, + gint end_pos, + gpointer data) +{ + NautilusLocationEntry *self = NAUTILUS_LOCATION_ENTRY (data); + + after_text_change (self, FALSE); +} + +static void +nautilus_location_entry_activate (GtkEntry *entry) +{ + NautilusLocationEntry *loc_entry; + NautilusLocationEntryPrivate *priv; + const gchar *entry_text; + gchar *full_path, *uri_scheme = NULL; + g_autofree char *path = NULL; + + loc_entry = NAUTILUS_LOCATION_ENTRY (entry); + priv = nautilus_location_entry_get_instance_private (loc_entry); + entry_text = gtk_editable_get_text (GTK_EDITABLE (entry)); + path = g_strdup (entry_text); + path = g_strchug (path); + path = g_strchomp (path); + + if (path != NULL && *path != '\0') + { + uri_scheme = g_uri_parse_scheme (path); + + if (!g_path_is_absolute (path) && uri_scheme == NULL && path[0] != '~') + { + /* Fix non absolute paths */ + full_path = g_build_filename (priv->current_directory, path, NULL); + nautilus_location_entry_set_text (loc_entry, full_path); + g_free (full_path); + } + + g_free (uri_scheme); + } +} + +static void +nautilus_location_entry_cancel (NautilusLocationEntry *entry) +{ + NautilusLocationEntryPrivate *priv; + + priv = nautilus_location_entry_get_instance_private (entry); + + nautilus_location_entry_set_location (entry, priv->last_location); +} + +static void +nautilus_location_entry_class_init (NautilusLocationEntryClass *class) +{ + GObjectClass *gobject_class; + GtkEntryClass *entry_class; + g_autoptr (GtkShortcut) shortcut = NULL; + + gobject_class = G_OBJECT_CLASS (class); + gobject_class->dispose = nautilus_location_entry_dispose; + gobject_class->finalize = finalize; + + entry_class = GTK_ENTRY_CLASS (class); + entry_class->activate = nautilus_location_entry_activate; + + class->cancel = nautilus_location_entry_cancel; + + signals[CANCEL] = g_signal_new + ("cancel", + G_TYPE_FROM_CLASS (class), + G_SIGNAL_RUN_LAST | G_SIGNAL_ACTION, + G_STRUCT_OFFSET (NautilusLocationEntryClass, + cancel), + NULL, NULL, + g_cclosure_marshal_VOID__VOID, + G_TYPE_NONE, 0); + + signals[LOCATION_CHANGED] = g_signal_new + ("location-changed", + G_TYPE_FROM_CLASS (class), + G_SIGNAL_RUN_LAST, 0, + NULL, NULL, + g_cclosure_marshal_generic, + G_TYPE_NONE, 1, G_TYPE_OBJECT); + + shortcut = gtk_shortcut_new (gtk_keyval_trigger_new (GDK_KEY_Escape, 0), + gtk_signal_action_new ("cancel")); + gtk_widget_class_add_shortcut (GTK_WIDGET_CLASS (class), shortcut); +} + +void +nautilus_location_entry_set_secondary_action (NautilusLocationEntry *entry, + NautilusLocationEntryAction secondary_action) +{ + NautilusLocationEntryPrivate *priv; + + priv = nautilus_location_entry_get_instance_private (entry); + + if (priv->secondary_action == secondary_action) + { + return; + } + + switch (secondary_action) + { + case NAUTILUS_LOCATION_ENTRY_ACTION_CLEAR: + { + gtk_entry_set_icon_from_icon_name (GTK_ENTRY (entry), + GTK_ENTRY_ICON_SECONDARY, + "edit-clear-symbolic"); + } + break; + + case NAUTILUS_LOCATION_ENTRY_ACTION_GOTO: + { + gtk_entry_set_icon_from_icon_name (GTK_ENTRY (entry), + GTK_ENTRY_ICON_SECONDARY, + "go-next-symbolic"); + } + break; + + default: + { + g_assert_not_reached (); + } + } + priv->secondary_action = secondary_action; +} + +static void +editable_activate_callback (GtkEntry *entry, + gpointer user_data) +{ + NautilusLocationEntry *self = user_data; + const char *entry_text; + g_autofree gchar *path = NULL; + + entry_text = gtk_editable_get_text (GTK_EDITABLE (entry)); + path = g_strdup (entry_text); + path = g_strchug (path); + path = g_strchomp (path); + + if (path != NULL && *path != '\0') + { + nautilus_location_entry_set_text (self, path); + emit_location_changed (self); + } +} + +static void +editable_changed_callback (GtkEntry *entry, + gpointer user_data) +{ + nautilus_location_entry_update_action (NAUTILUS_LOCATION_ENTRY (entry)); +} + +static void +nautilus_location_entry_init (NautilusLocationEntry *entry) +{ + NautilusLocationEntryPrivate *priv; + GtkEventController *controller; + + priv = nautilus_location_entry_get_instance_private (entry); + + priv->completer = g_filename_completer_new (); + g_filename_completer_set_dirs_only (priv->completer, TRUE); + + nautilus_location_entry_set_secondary_action (entry, + NAUTILUS_LOCATION_ENTRY_ACTION_CLEAR); + + g_signal_connect (entry, "notify::has-focus", + G_CALLBACK (on_has_focus_changed), NULL); + + g_signal_connect (entry, "notify::text", + G_CALLBACK (nautilus_location_entry_text_changed), NULL); + + g_signal_connect (entry, "icon-release", + G_CALLBACK (nautilus_location_entry_icon_release), NULL); + + g_signal_connect (priv->completer, "got-completion-data", + G_CALLBACK (got_completion_data_callback), entry); + + g_signal_connect_object (entry, "activate", + G_CALLBACK (editable_activate_callback), entry, G_CONNECT_AFTER); + g_signal_connect_object (entry, "changed", + G_CALLBACK (editable_changed_callback), entry, 0); + + controller = gtk_event_controller_key_new (); + gtk_widget_add_controller (GTK_WIDGET (entry), controller); + /* In GTK3, the Tab key binding (for focus change) happens in the bubble + * phase, and we want to stop that from happening. After porting to GTK4 + * we need to check whether this is still correct. */ + gtk_event_controller_set_propagation_phase (controller, GTK_PHASE_BUBBLE); + g_signal_connect (controller, "key-pressed", + G_CALLBACK (nautilus_location_entry_key_pressed), NULL); + + g_signal_connect_after (gtk_editable_get_delegate (GTK_EDITABLE (entry)), + "insert-text", + G_CALLBACK (on_after_insert_text), + entry); + g_signal_connect_after (gtk_editable_get_delegate (GTK_EDITABLE (entry)), + "delete-text", + G_CALLBACK (on_after_delete_text), + entry); + + priv->completion = gtk_entry_completion_new (); + priv->completions_store = gtk_list_store_new (1, G_TYPE_STRING); + gtk_entry_completion_set_model (priv->completion, GTK_TREE_MODEL (priv->completions_store)); + + g_object_set (priv->completion, + "text-column", 0, + "inline-completion", FALSE, + "inline-selection", TRUE, + "popup-single-match", TRUE, + NULL); + + priv->completion_cell = gtk_cell_renderer_text_new (); + g_object_set (priv->completion_cell, "xpad", 6, NULL); + + gtk_cell_layout_pack_start (GTK_CELL_LAYOUT (priv->completion), priv->completion_cell, FALSE); + gtk_cell_layout_add_attribute (GTK_CELL_LAYOUT (priv->completion), priv->completion_cell, "text", 0); + + gtk_entry_set_completion (GTK_ENTRY (entry), priv->completion); +} + +GtkWidget * +nautilus_location_entry_new (void) +{ + GtkWidget *entry; + + entry = GTK_WIDGET (g_object_new (NAUTILUS_TYPE_LOCATION_ENTRY, NULL)); + + return entry; +} + +void +nautilus_location_entry_set_special_text (NautilusLocationEntry *entry, + const char *special_text) +{ + NautilusLocationEntryPrivate *priv; + + priv = nautilus_location_entry_get_instance_private (entry); + + nautilus_location_entry_set_text (entry, special_text); + priv->has_special_text = TRUE; +} diff --git a/src/nautilus-location-entry.h b/src/nautilus-location-entry.h new file mode 100644 index 0000000..bdff538 --- /dev/null +++ b/src/nautilus-location-entry.h @@ -0,0 +1,51 @@ + +/* + * Nautilus + * + * Copyright (C) 2000 Eazel, Inc. + * + * Nautilus is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License as + * published by the Free Software Foundation; either version 2 of the + * License, or (at your option) any later version. + * + * Nautilus is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public + * License along with this program; see the file COPYING. If not, + * see . + * + * Author: Maciej Stachowiak + * Ettore Perazzoli + */ + +#pragma once + +#include + +#define NAUTILUS_TYPE_LOCATION_ENTRY nautilus_location_entry_get_type() +G_DECLARE_DERIVABLE_TYPE (NautilusLocationEntry, nautilus_location_entry, + NAUTILUS, LOCATION_ENTRY, + GtkEntry) + +typedef struct _NautilusLocationEntryClass { + GtkEntryClass parent_class; + /* for GtkBindingSet */ + void (* cancel) (NautilusLocationEntry *entry); +} NautilusLocationEntryClass; + +typedef enum { + NAUTILUS_LOCATION_ENTRY_ACTION_GOTO, + NAUTILUS_LOCATION_ENTRY_ACTION_CLEAR +} NautilusLocationEntryAction; + +GtkWidget* nautilus_location_entry_new (void); +void nautilus_location_entry_set_special_text (NautilusLocationEntry *entry, + const char *special_text); +void nautilus_location_entry_set_secondary_action (NautilusLocationEntry *entry, + NautilusLocationEntryAction secondary_action); +void nautilus_location_entry_set_location (NautilusLocationEntry *entry, + GFile *location); \ No newline at end of file diff --git a/src/nautilus-main.c b/src/nautilus-main.c new file mode 100644 index 0000000..4c8bb93 --- /dev/null +++ b/src/nautilus-main.c @@ -0,0 +1,89 @@ +/* + * Nautilus + * + * Copyright (C) 1999, 2000 Red Hat, Inc. + * Copyright (C) 1999, 2000 Eazel, Inc. + * + * Nautilus is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License as + * published by the Free Software Foundation; either version 2 of the + * License, or (at your option) any later version. + * + * Nautilus is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * 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 . + * + * Authors: Elliot Lee , + * Darin Adler , + * John Sullivan + * + */ + +/* nautilus-main.c: Implementation of the routines that drive program lifecycle and main window creation/destruction. */ + +#include + +#include "nautilus-application.h" +#include "nautilus-resources.h" + +#include "nautilus-debug.h" +#include + +#include +#include +#include + +#include + +#ifdef HAVE_LOCALE_H +#include +#endif +#ifdef HAVE_MALLOC_H +#include +#endif +#include +#include +#include + +int +main (int argc, + char *argv[]) +{ + gint retval; + NautilusApplication *application; + + if (g_getenv ("NAUTILUS_DEBUG") != NULL) + { + eel_make_warnings_and_criticals_stop_in_debugger (); + } + + /* Initialize gettext support */ + bindtextdomain (GETTEXT_PACKAGE, LOCALEDIR); + bind_textdomain_codeset (GETTEXT_PACKAGE, "UTF-8"); + textdomain (GETTEXT_PACKAGE); + + g_set_prgname (APPLICATION_ID); + + nautilus_register_resource (); + /* Run the nautilus application. */ + application = nautilus_application_new (); + + /* hold indefinitely if we're asked to persist */ + if (g_getenv ("NAUTILUS_PERSIST") != NULL) + { + g_application_hold (G_APPLICATION (application)); + } + + retval = g_application_run (G_APPLICATION (application), + argc, argv); + + g_object_unref (application); + + eel_debug_shut_down (); + + return retval; +} diff --git a/src/nautilus-metadata.c b/src/nautilus-metadata.c new file mode 100644 index 0000000..e462298 --- /dev/null +++ b/src/nautilus-metadata.c @@ -0,0 +1,55 @@ +/* nautilus-metadata.c - metadata utils + * + * Copyright (C) 2009 Red Hatl, Inc. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Library General Public + * License as published by the Free Software Foundation; either + * version 2 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Library General Public License for more details. + * + * You should have received a copy of the GNU Library General Public + * License along with this library; if not, see . + */ + +#include +#include "nautilus-metadata.h" +#include + +static char *used_metadata_names[] = +{ + NAUTILUS_METADATA_KEY_ICON_VIEW_SORT_BY, + NAUTILUS_METADATA_KEY_ICON_VIEW_SORT_REVERSED, + NAUTILUS_METADATA_KEY_LIST_VIEW_SORT_COLUMN, + NAUTILUS_METADATA_KEY_LIST_VIEW_SORT_REVERSED, + NAUTILUS_METADATA_KEY_LIST_VIEW_VISIBLE_COLUMNS, + NAUTILUS_METADATA_KEY_LIST_VIEW_COLUMN_ORDER, + NAUTILUS_METADATA_KEY_CUSTOM_ICON, + NAUTILUS_METADATA_KEY_CUSTOM_ICON_NAME, + NAUTILUS_METADATA_KEY_EMBLEMS, + NULL +}; + +guint +nautilus_metadata_get_id (const char *metadata) +{ + static GHashTable *hash; + int i; + + if (hash == NULL) + { + hash = g_hash_table_new (g_str_hash, g_str_equal); + for (i = 0; used_metadata_names[i] != NULL; i++) + { + g_hash_table_insert (hash, + used_metadata_names[i], + GINT_TO_POINTER (i + 1)); + } + } + + return GPOINTER_TO_INT (g_hash_table_lookup (hash, metadata)); +} diff --git a/src/nautilus-metadata.h b/src/nautilus-metadata.h new file mode 100644 index 0000000..299e3a8 --- /dev/null +++ b/src/nautilus-metadata.h @@ -0,0 +1,45 @@ +/* + nautilus-metadata.h: #defines and other metadata-related info + + Copyright (C) 2000 Eazel, Inc. + + This program is free software; you can redistribute it and/or + modify it under the terms of the GNU General Public License as + published by the Free Software Foundation; either version 2 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 warranty of + MERCHANTABILITY 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 . + + Author: John Sullivan +*/ + +#pragma once + +/* Keys for getting/setting Nautilus metadata. All metadata used in Nautilus + * should define its key here, so we can keep track of the whole set easily. + * Any updates here needs to be added in nautilus-metadata.c too. + */ + +#include + +/* Per-file */ + +#define NAUTILUS_METADATA_KEY_ICON_VIEW_SORT_BY "nautilus-icon-view-sort-by" +#define NAUTILUS_METADATA_KEY_ICON_VIEW_SORT_REVERSED "nautilus-icon-view-sort-reversed" + +#define NAUTILUS_METADATA_KEY_LIST_VIEW_SORT_COLUMN "nautilus-list-view-sort-column" +#define NAUTILUS_METADATA_KEY_LIST_VIEW_SORT_REVERSED "nautilus-list-view-sort-reversed" +#define NAUTILUS_METADATA_KEY_LIST_VIEW_VISIBLE_COLUMNS "nautilus-list-view-visible-columns" +#define NAUTILUS_METADATA_KEY_LIST_VIEW_COLUMN_ORDER "nautilus-list-view-column-order" + +#define NAUTILUS_METADATA_KEY_CUSTOM_ICON "custom-icon" +#define NAUTILUS_METADATA_KEY_CUSTOM_ICON_NAME "custom-icon-name" +#define NAUTILUS_METADATA_KEY_EMBLEMS "emblems" + +guint nautilus_metadata_get_id (const char *metadata); diff --git a/src/nautilus-mime-actions.c b/src/nautilus-mime-actions.c new file mode 100644 index 0000000..d4dfd16 --- /dev/null +++ b/src/nautilus-mime-actions.c @@ -0,0 +1,2325 @@ +/* nautilus-mime-actions.c - uri-specific versions of mime action functions + * + * Copyright (C) 2000, 2001 Eazel, Inc. + * + * The Gnome Library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Library General Public License as + * published by the Free Software Foundation; either version 2 of the + * License, or (at your option) any later version. + * + * The Gnome Library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Library General Public License for more details. + * + * You should have received a copy of the GNU Library General Public + * License along with the Gnome Library; see the file COPYING.LIB. If not, + * see . + * + * Authors: Maciej Stachowiak + */ + +#include "nautilus-mime-actions.h" + +#include +#include +#include +#include +#include +#include + +#define DEBUG_FLAG NAUTILUS_DEBUG_MIME +#include "nautilus-debug.h" + +#include "nautilus-application.h" +#include "nautilus-enums.h" +#include "nautilus-file.h" +#include "nautilus-file-utilities.h" +#include "nautilus-file-operations.h" +#include "nautilus-global-preferences.h" +#include "nautilus-metadata.h" +#include "nautilus-program-choosing.h" +#include "nautilus-signaller.h" +#include "nautilus-ui-utilities.h" +#include "nautilus-window.h" +#include "nautilus-window-slot.h" + +typedef enum +{ + ACTIVATION_ACTION_LAUNCH, + ACTIVATION_ACTION_LAUNCH_IN_TERMINAL, + ACTIVATION_ACTION_OPEN_IN_VIEW, + ACTIVATION_ACTION_OPEN_IN_APPLICATION, + ACTIVATION_ACTION_EXTRACT, + ACTIVATION_ACTION_DO_NOTHING, +} ActivationAction; + +typedef struct +{ + NautilusFile *file; + char *uri; +} LaunchLocation; + +typedef struct +{ + GAppInfo *application; + GList *uris; +} ApplicationLaunchParameters; + +typedef struct +{ + NautilusWindowSlot *slot; + gpointer window; + GtkWindow *parent_window; + GCancellable *cancellable; + GList *locations; + GList *mountables; + GList *start_mountables; + GList *not_mounted; + NautilusOpenFlags flags; + char *timed_wait_prompt; + gboolean timed_wait_active; + NautilusFileListHandle *files_handle; + gboolean tried_mounting; + char *activation_directory; + gboolean user_confirmation; + GQueue *open_in_view_files; + GQueue *open_in_app_uris; + GQueue *launch_files; + GQueue *launch_in_terminal_files; + GList *open_in_app_parameters; + GList *unhandled_open_in_app_uris; +} ActivateParameters; + +typedef struct +{ + ActivateParameters *activation_params; + GQueue *uris; +} ApplicationLaunchAsyncParameters; + +/* Microsoft mime types at https://blogs.msdn.microsoft.com/vsofficedeveloper/2008/05/08/office-2007-file-format-mime-types-for-http-content-streaming-2/ */ +struct +{ + char *name; + char *mimetypes[20]; +} mimetype_groups[] = +{ + { + N_("Anything"), + { NULL } + }, + { + N_("Files"), + { "application/octet-stream", + "text/plain", + NULL} + }, + { + N_("Folders"), + { "inode/directory", + NULL} + }, + { N_("Documents"), + { "application/rtf", + "application/msword", + "application/vnd.sun.xml.writer", + "application/vnd.sun.xml.writer.global", + "application/vnd.sun.xml.writer.template", + "application/vnd.oasis.opendocument.text", + "application/vnd.oasis.opendocument.text-template", + "application/x-abiword", + "application/x-applix-word", + "application/x-mswrite", + "application/docbook+xml", + "application/x-kword", + "application/x-kword-crypt", + "application/x-lyx", + "application/vnd.openxmlformats-officedocument.wordprocessingml.document", + NULL}}, + { N_("Illustration"), + { "application/illustrator", + "application/vnd.corel-draw", + "application/vnd.stardivision.draw", + "application/vnd.oasis.opendocument.graphics", + "application/x-dia-diagram", + "application/x-karbon", + "application/x-killustrator", + "application/x-kivio", + "application/x-kontour", + "application/x-wpg", + NULL}}, + { N_("Music"), + { "application/ogg", + "audio/x-vorbis+ogg", + "audio/ac3", + "audio/basic", + "audio/midi", + "audio/x-flac", + "audio/mp4", + "audio/mpeg", + "audio/x-mpeg", + "audio/x-ms-asx", + "audio/x-pn-realaudio", + NULL}}, + { N_("PDF / PostScript"), + { "application/pdf", + "application/postscript", + "application/x-dvi", + "image/x-eps", + "image/vnd.djvu+multipage", + NULL}}, + { N_("Picture"), + { "application/vnd.oasis.opendocument.image", + "application/x-krita", + "image/bmp", + "image/cgm", + "image/gif", + "image/jpeg", + "image/jpeg2000", + "image/png", + "image/svg+xml", + "image/tiff", + "image/x-compressed-xcf", + "image/x-pcx", + "image/x-photo-cd", + "image/x-psd", + "image/x-tga", + "image/x-xcf", + NULL}}, + { N_("Presentation"), + { "application/vnd.ms-powerpoint", + "application/vnd.sun.xml.impress", + "application/vnd.oasis.opendocument.presentation", + "application/x-magicpoint", + "application/x-kpresenter", + "application/vnd.openxmlformats-officedocument.presentationml.presentation", + NULL}}, + { N_("Spreadsheet"), + { "application/vnd.lotus-1-2-3", + "application/vnd.ms-excel", + "application/vnd.stardivision.calc", + "application/vnd.sun.xml.calc", + "application/vnd.oasis.opendocument.spreadsheet", + "application/x-applix-spreadsheet", + "application/x-gnumeric", + "application/x-kspread", + "application/x-kspread-crypt", + "application/x-quattropro", + "application/x-sc", + "application/x-siag", + "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet", + NULL}}, + { N_("Text File"), + { "text/plain", + NULL}}, + { N_("Video"), + { "video/mp4", + "video/3gpp", + "video/mpeg", + "video/quicktime", + "video/vivo", + "video/x-avi", + "video/x-mng", + "video/x-ms-asf", + "video/x-ms-wmv", + "video/x-msvideo", + "video/x-nsv", + "video/x-real-video", + NULL}} +}; + +/* Number of seconds until cancel dialog shows up */ +#define DELAY_UNTIL_CANCEL_MSECS 5000 + +#define SILENT_WINDOW_OPEN_LIMIT 5 +#define SILENT_OPEN_LIMIT 5 + +/* This number controls a maximum character count for a URL that is + * displayed as part of a dialog. It's fairly arbitrary -- big enough + * to allow most "normal" URIs to display in full, but small enough to + * prevent the dialog from getting insanely wide. + */ +#define MAX_URI_IN_DIALOG_LENGTH 60 + +static void activate_files_internal (ActivateParameters *parameters); +static void cancel_activate_callback (gpointer callback_data); +static void activate_activation_uris_ready_callback (GList *files, + gpointer callback_data); +static void activation_mount_mountables (ActivateParameters *parameters); +static void activation_start_mountables (ActivateParameters *parameters); +static void activate_callback (GList *files, + gpointer callback_data); +static void activation_mount_not_mounted (ActivateParameters *parameters); + +static void +launch_location_free (LaunchLocation *location) +{ + nautilus_file_unref (location->file); + g_free (location->uri); + g_free (location); +} + +static void +launch_location_list_free (GList *list) +{ + g_list_foreach (list, (GFunc) launch_location_free, NULL); + g_list_free (list); +} + +static GList * +get_file_list_for_launch_locations (GList *locations) +{ + GList *files, *l; + LaunchLocation *location; + + files = NULL; + for (l = locations; l != NULL; l = l->next) + { + location = l->data; + + files = g_list_prepend (files, + nautilus_file_ref (location->file)); + } + return g_list_reverse (files); +} + + +static LaunchLocation * +launch_location_from_file (NautilusFile *file) +{ + LaunchLocation *location; + location = g_new (LaunchLocation, 1); + location->file = nautilus_file_ref (file); + location->uri = nautilus_file_get_uri (file); + + return location; +} + +static void +launch_location_update_from_file (LaunchLocation *location, + NautilusFile *file) +{ + nautilus_file_unref (location->file); + g_free (location->uri); + location->file = nautilus_file_ref (file); + location->uri = nautilus_file_get_uri (file); +} + +static void +launch_location_update_from_uri (LaunchLocation *location, + const char *uri) +{ + nautilus_file_unref (location->file); + g_free (location->uri); + location->file = nautilus_file_get_by_uri (uri); + location->uri = g_strdup (uri); +} + +static LaunchLocation * +find_launch_location_for_file (GList *list, + NautilusFile *file) +{ + LaunchLocation *location; + GList *l; + + for (l = list; l != NULL; l = l->next) + { + location = l->data; + + if (location->file == file) + { + return location; + } + } + return NULL; +} + +static GList * +launch_locations_from_file_list (GList *list) +{ + GList *new; + + new = NULL; + while (list) + { + new = g_list_prepend (new, + launch_location_from_file (list->data)); + list = list->next; + } + new = g_list_reverse (new); + return new; +} + +static ApplicationLaunchParameters * +application_launch_parameters_new (GAppInfo *application, + GList *uris) +{ + ApplicationLaunchParameters *result; + + result = g_new0 (ApplicationLaunchParameters, 1); + result->application = g_object_ref (application); + result->uris = g_list_copy_deep (uris, (GCopyFunc) g_strdup, NULL); + + return result; +} + +static void +application_launch_parameters_free (ApplicationLaunchParameters *parameters) +{ + g_object_unref (parameters->application); + g_list_free_full (parameters->uris, g_free); + + g_free (parameters); +} + +static gboolean +nautilus_mime_actions_check_if_required_attributes_ready (NautilusFile *file) +{ + NautilusFileAttributes attributes; + gboolean ready; + + attributes = nautilus_mime_actions_get_required_file_attributes (); + ready = nautilus_file_check_if_ready (file, attributes); + + return ready; +} + +NautilusFileAttributes +nautilus_mime_actions_get_required_file_attributes (void) +{ + return NAUTILUS_FILE_ATTRIBUTE_INFO; +} + +GAppInfo * +nautilus_mime_get_default_application_for_file (NautilusFile *file) +{ + GAppInfo *app; + char *mime_type; + char *uri_scheme; + + if (!nautilus_mime_actions_check_if_required_attributes_ready (file)) + { + return NULL; + } + + mime_type = nautilus_file_get_mime_type (file); + app = g_app_info_get_default_for_type (mime_type, + !nautilus_file_has_local_path (file)); + g_free (mime_type); + + if (app == NULL) + { + uri_scheme = nautilus_file_get_uri_scheme (file); + if (uri_scheme != NULL) + { + app = g_app_info_get_default_for_uri_scheme (uri_scheme); + g_free (uri_scheme); + } + } + + return app; +} + +static int +file_compare_by_mime_type (NautilusFile *file_a, + NautilusFile *file_b) +{ + char *mime_type_a, *mime_type_b; + int ret; + + mime_type_a = nautilus_file_get_mime_type (file_a); + mime_type_b = nautilus_file_get_mime_type (file_b); + + ret = strcmp (mime_type_a, mime_type_b); + + g_free (mime_type_a); + g_free (mime_type_b); + + return ret; +} + +static int +file_compare_by_parent_uri (NautilusFile *file_a, + NautilusFile *file_b) +{ + char *parent_uri_a, *parent_uri_b; + int ret; + + parent_uri_a = nautilus_file_get_parent_uri (file_a); + parent_uri_b = nautilus_file_get_parent_uri (file_b); + + ret = strcmp (parent_uri_a, parent_uri_b); + + g_free (parent_uri_a); + g_free (parent_uri_b); + + return ret; +} + +GAppInfo * +nautilus_mime_get_default_application_for_files (GList *files) +{ + GList *l, *sorted_files; + NautilusFile *file; + GAppInfo *app, *one_app; + + g_assert (files != NULL); + + sorted_files = g_list_sort (g_list_copy (files), (GCompareFunc) file_compare_by_mime_type); + + app = NULL; + for (l = sorted_files; l != NULL; l = l->next) + { + file = l->data; + + if (l->prev && + file_compare_by_mime_type (file, l->prev->data) == 0 && + file_compare_by_parent_uri (file, l->prev->data) == 0) + { + continue; + } + + one_app = nautilus_mime_get_default_application_for_file (file); + if (one_app == NULL || (app != NULL && !g_app_info_equal (app, one_app))) + { + if (app) + { + g_object_unref (app); + } + if (one_app) + { + g_object_unref (one_app); + } + app = NULL; + break; + } + + if (app == NULL) + { + app = one_app; + } + else + { + g_object_unref (one_app); + } + } + + g_list_free (sorted_files); + + return app; +} + +static void +trash_or_delete_files (GtkWindow *parent_window, + const GList *files, + gboolean delete_if_all_already_in_trash) +{ + GList *locations; + const GList *node; + + locations = NULL; + for (node = files; node != NULL; node = node->next) + { + locations = g_list_prepend (locations, + nautilus_file_get_location ((NautilusFile *) node->data)); + } + + locations = g_list_reverse (locations); + + nautilus_file_operations_trash_or_delete_async (locations, + parent_window, + NULL, + NULL, NULL); + g_list_free_full (locations, g_object_unref); +} + +typedef struct +{ + GtkWindow *parent_window; + NautilusFile *file; +} TrashBrokenSymbolicLinkData; + +static void +trash_symbolic_link_cb (GtkDialog *dialog, + char *response, + gpointer user_data) +{ + g_autofree TrashBrokenSymbolicLinkData *data = NULL; + GList file_as_list; + + data = user_data; + + if (g_strcmp0 (response, "move-to-trash") == 0) + { + file_as_list.data = data->file; + file_as_list.next = NULL; + file_as_list.prev = NULL; + trash_or_delete_files (data->parent_window, &file_as_list, TRUE); + } +} + +static void +report_broken_symbolic_link (GtkWindow *parent_window, + NautilusFile *file) +{ + char *target_path; + char *display_name; + char *detail; + GtkWidget *dialog; + TrashBrokenSymbolicLinkData *data; + + gboolean can_trash; + + g_assert (nautilus_file_is_broken_symbolic_link (file)); + + display_name = nautilus_file_get_display_name (file); + can_trash = nautilus_file_can_trash (file) && !nautilus_file_is_in_trash (file); + + target_path = nautilus_file_get_symbolic_link_target_path (file); + if (target_path == NULL) + { + detail = g_strdup (_("This link cannot be used because it has no target.")); + } + else + { + detail = g_strdup_printf (_("This link cannot be used because its target " + "“%s” doesn’t exist."), target_path); + } + + if (can_trash) + { + dialog = adw_message_dialog_new (parent_window, NULL, detail); + adw_message_dialog_format_heading (ADW_MESSAGE_DIALOG (dialog), + _("The link “%s” is broken. Move it to Trash?"), + display_name); + adw_message_dialog_add_responses (ADW_MESSAGE_DIALOG (dialog), + "cancel", _("_Cancel"), + "move-to-trash", _("Mo_ve to Trash"), + NULL); + } + else + { + dialog = adw_message_dialog_new (parent_window, NULL, detail); + adw_message_dialog_format_heading (ADW_MESSAGE_DIALOG (dialog), + _("The link “%s” is broken."), + display_name); + adw_message_dialog_add_response (ADW_MESSAGE_DIALOG (dialog), + "cancel", _("Cancel")); + } + g_free (display_name); + + adw_message_dialog_set_default_response (ADW_MESSAGE_DIALOG (dialog), "cancel"); + + /* Make this modal to avoid problems with reffing the view & file + * to keep them around in case the view changes, which would then + * cause the old view not to be destroyed, which would cause its + * merged Bonobo items not to be un-merged. Maybe we need to unmerge + * explicitly when disconnecting views instead of relying on the + * unmerge in Destroy. But since BonoboUIHandler is probably going + * to change wildly, I don't want to mess with this now. + */ + + data = g_new0 (TrashBrokenSymbolicLinkData, 1); + data->parent_window = parent_window; + data->file = file; + + g_signal_connect (G_OBJECT (dialog), + "response", + G_CALLBACK (trash_symbolic_link_cb), + data); + + g_free (target_path); + g_free (detail); +} + +static ActivationAction +get_default_executable_text_file_action (void) +{ + return ACTIVATION_ACTION_OPEN_IN_APPLICATION; +} + +static ActivationAction +get_activation_action (NautilusFile *file) +{ + ActivationAction action; + char *activation_uri; + gboolean handles_extract = FALSE; + g_autoptr (GAppInfo) app_info = NULL; + const gchar *app_id; + + app_info = nautilus_mime_get_default_application_for_file (file); + if (app_info != NULL) + { + app_id = g_app_info_get_id (app_info); + handles_extract = g_strcmp0 (app_id, NAUTILUS_DESKTOP_ID) == 0; + } + if (handles_extract && nautilus_file_is_archive (file)) + { + return ACTIVATION_ACTION_EXTRACT; + } + + activation_uri = nautilus_file_get_activation_uri (file); + if (activation_uri == NULL) + { + activation_uri = nautilus_file_get_uri (file); + } + + action = ACTIVATION_ACTION_DO_NOTHING; + if (nautilus_file_is_launchable (file)) + { + char *executable_path; + + action = ACTIVATION_ACTION_LAUNCH; + + executable_path = g_filename_from_uri (activation_uri, NULL, NULL); + if (!executable_path) + { + action = ACTIVATION_ACTION_DO_NOTHING; + } + else if (nautilus_file_contains_text (file)) + { + action = get_default_executable_text_file_action (); + } + g_free (executable_path); + } + + if (action == ACTIVATION_ACTION_DO_NOTHING) + { + if (nautilus_file_opens_in_view (file)) + { + action = ACTIVATION_ACTION_OPEN_IN_VIEW; + } + else + { + action = ACTIVATION_ACTION_OPEN_IN_APPLICATION; + } + } + g_free (activation_uri); + + return action; +} + +gboolean +nautilus_mime_file_extracts (NautilusFile *file) +{ + return get_activation_action (file) == ACTIVATION_ACTION_EXTRACT; +} + +gboolean +nautilus_mime_file_launches (NautilusFile *file) +{ + ActivationAction activation_action; + + activation_action = get_activation_action (file); + + return (activation_action == ACTIVATION_ACTION_LAUNCH); +} + +gboolean +nautilus_mime_file_opens_in_external_app (NautilusFile *file) +{ + ActivationAction activation_action; + + activation_action = get_activation_action (file); + + return (activation_action == ACTIVATION_ACTION_OPEN_IN_APPLICATION); +} + + +static unsigned int +mime_application_hash (GAppInfo *app) +{ + const char *id; + + id = g_app_info_get_id (app); + + if (id == NULL) + { + return GPOINTER_TO_UINT (app); + } + + return g_str_hash (id); +} + +static void +list_to_parameters_foreach (GAppInfo *application, + GList *uris, + GList **ret) +{ + ApplicationLaunchParameters *parameters; + + uris = g_list_reverse (uris); + + parameters = application_launch_parameters_new + (application, uris); + *ret = g_list_prepend (*ret, parameters); +} + + +/** + * make_activation_parameters + * + * Construct a list of ApplicationLaunchParameters from a list of NautilusFiles, + * where files that have the same default application are put into the same + * launch parameter, and others are put into the unhandled_files list. + * + * @files: Files to use for construction. + * @unhandled_files: Files without any default application will be put here. + * + * Return value: Newly allocated list of ApplicationLaunchParameters. + **/ +static GList * +make_activation_parameters (GList *uris, + GList **unhandled_uris) +{ + GList *ret, *l, *app_uris; + NautilusFile *file; + GAppInfo *app, *old_app; + GHashTable *app_table; + char *uri; + + ret = NULL; + *unhandled_uris = NULL; + + app_table = g_hash_table_new_full + ((GHashFunc) mime_application_hash, + (GEqualFunc) g_app_info_equal, + (GDestroyNotify) g_object_unref, + (GDestroyNotify) g_list_free); + + for (l = uris; l != NULL; l = l->next) + { + uri = l->data; + file = nautilus_file_get_by_uri (uri); + + app = nautilus_mime_get_default_application_for_file (file); + if (app != NULL) + { + app_uris = NULL; + + if (g_hash_table_lookup_extended (app_table, app, + (gpointer *) &old_app, + (gpointer *) &app_uris)) + { + g_hash_table_steal (app_table, old_app); + + app_uris = g_list_prepend (app_uris, uri); + + g_object_unref (app); + app = old_app; + } + else + { + app_uris = g_list_prepend (NULL, uri); + } + + g_hash_table_insert (app_table, app, app_uris); + } + else + { + *unhandled_uris = g_list_prepend (*unhandled_uris, uri); + } + nautilus_file_unref (file); + } + + g_hash_table_foreach (app_table, + (GHFunc) list_to_parameters_foreach, + &ret); + + g_hash_table_destroy (app_table); + + *unhandled_uris = g_list_reverse (*unhandled_uris); + + return g_list_reverse (ret); +} + +static gboolean +file_was_cancelled (NautilusFile *file) +{ + GError *error; + + error = nautilus_file_get_file_info_error (file); + return + error != NULL && + error->domain == G_IO_ERROR && + error->code == G_IO_ERROR_CANCELLED; +} + +static gboolean +file_was_not_mounted (NautilusFile *file) +{ + GError *error; + + error = nautilus_file_get_file_info_error (file); + return + error != NULL && + error->domain == G_IO_ERROR && + error->code == G_IO_ERROR_NOT_MOUNTED; +} + +static void +activation_parameters_free (ActivateParameters *parameters) +{ + if (parameters->timed_wait_active) + { + eel_timed_wait_stop (cancel_activate_callback, parameters); + } + + if (parameters->slot) + { + g_object_remove_weak_pointer (G_OBJECT (parameters->slot), (gpointer *) ¶meters->slot); + } + if (parameters->parent_window) + { + g_object_remove_weak_pointer (G_OBJECT (parameters->parent_window), (gpointer *) ¶meters->parent_window); + } + g_object_unref (parameters->cancellable); + launch_location_list_free (parameters->locations); + nautilus_file_list_free (parameters->mountables); + nautilus_file_list_free (parameters->start_mountables); + nautilus_file_list_free (parameters->not_mounted); + g_free (parameters->activation_directory); + g_free (parameters->timed_wait_prompt); + g_assert (parameters->files_handle == NULL); + g_clear_pointer (¶meters->open_in_view_files, g_queue_free); + g_clear_pointer (¶meters->open_in_app_uris, g_queue_free); + g_clear_pointer (¶meters->launch_files, g_queue_free); + g_clear_pointer (¶meters->launch_in_terminal_files, g_queue_free); + g_list_free (parameters->open_in_app_parameters); + g_list_free (parameters->unhandled_open_in_app_uris); + g_free (parameters); +} + +static void +application_launch_async_parameters_free (ApplicationLaunchAsyncParameters *parameters) +{ + g_queue_free (parameters->uris); + activation_parameters_free (parameters->activation_params); + + g_free (parameters); +} + +static void +cancel_activate_callback (gpointer callback_data) +{ + ActivateParameters *parameters = callback_data; + + parameters->timed_wait_active = FALSE; + + g_cancellable_cancel (parameters->cancellable); + + if (parameters->files_handle) + { + nautilus_file_list_cancel_call_when_ready (parameters->files_handle); + parameters->files_handle = NULL; + activation_parameters_free (parameters); + } +} + +static void +activation_start_timed_cancel (ActivateParameters *parameters) +{ + parameters->timed_wait_active = TRUE; + eel_timed_wait_start_with_duration + (DELAY_UNTIL_CANCEL_MSECS, + cancel_activate_callback, + parameters, + parameters->timed_wait_prompt, + parameters->parent_window); +} + +static void +pause_activation_timed_cancel (ActivateParameters *parameters) +{ + if (parameters->timed_wait_active) + { + eel_timed_wait_stop (cancel_activate_callback, parameters); + parameters->timed_wait_active = FALSE; + } +} + +static void +unpause_activation_timed_cancel (ActivateParameters *parameters) +{ + if (!parameters->timed_wait_active) + { + activation_start_timed_cancel (parameters); + } +} + + +static void +activate_mount_op_active (GtkMountOperation *operation, + GParamSpec *pspec, + ActivateParameters *parameters) +{ + gboolean is_active; + + g_object_get (operation, "is-showing", &is_active, NULL); + + if (is_active) + { + pause_activation_timed_cancel (parameters); + } + else + { + unpause_activation_timed_cancel (parameters); + } +} + +static void +on_confirm_multiple_windows_response (GtkDialog *dialog, + gchar *response, + ActivateParameters *parameters) +{ + if (g_strcmp0 (response, "open-all") == 0) + { + unpause_activation_timed_cancel (parameters); + activate_files_internal (parameters); + } + else + { + activation_parameters_free (parameters); + } +} + +static void +show_confirm_multiple (ActivateParameters *parameters, + int window_count, + int tab_count) +{ + GtkWindow *parent_window = parameters->parent_window; + GtkWidget *dialog; + char *prompt; + char *detail; + + prompt = _("Are you sure you want to open all files?"); + if (tab_count > 0 && window_count > 0) + { + int count = tab_count + window_count; + detail = g_strdup_printf (ngettext ("This will open %d separate tab and window.", + "This will open %d separate tabs and windows.", count), count); + } + else if (tab_count > 0) + { + detail = g_strdup_printf (ngettext ("This will open %d separate tab.", + "This will open %d separate tabs.", tab_count), tab_count); + } + else + { + detail = g_strdup_printf (ngettext ("This will open %d separate window.", + "This will open %d separate windows.", window_count), window_count); + } + + dialog = adw_message_dialog_new (parent_window, prompt, detail); + adw_message_dialog_add_responses (ADW_MESSAGE_DIALOG (dialog), + "cancel", _("_Cancel"), + "open-all", _("_Open All"), + NULL); + + adw_message_dialog_set_default_response (ADW_MESSAGE_DIALOG (dialog), "open-all"); + + g_signal_connect (dialog, "response", + G_CALLBACK (on_confirm_multiple_windows_response), parameters); + gtk_window_present (GTK_WINDOW (dialog)); + + g_free (detail); +} + +typedef struct +{ + NautilusWindowSlot *slot; + GtkWindow *parent_window; + NautilusFile *file; + GList *files; + NautilusOpenFlags flags; + char *activation_directory; + gboolean user_confirmation; + char *uri; + GDBusProxy *proxy; + GtkWidget *dialog; +} ActivateParametersInstall; + +static void +activate_parameters_install_free (ActivateParametersInstall *parameters_install) +{ + if (parameters_install->slot) + { + g_object_remove_weak_pointer (G_OBJECT (parameters_install->slot), (gpointer *) ¶meters_install->slot); + } + if (parameters_install->parent_window) + { + g_object_remove_weak_pointer (G_OBJECT (parameters_install->parent_window), (gpointer *) ¶meters_install->parent_window); + } + + if (parameters_install->proxy != NULL) + { + g_object_unref (parameters_install->proxy); + } + + nautilus_file_unref (parameters_install->file); + nautilus_file_list_free (parameters_install->files); + g_free (parameters_install->activation_directory); + g_free (parameters_install->uri); + g_free (parameters_install); +} + +static char * +get_application_no_mime_type_handler_message (NautilusFile *file, + char *uri) +{ + char *uri_for_display; + char *name; + char *error_message; + + name = nautilus_file_get_display_name (file); + + /* Truncate the URI so it doesn't get insanely wide. Note that even + * though the dialog uses wrapped text, if the URI doesn't contain + * white space then the text-wrapping code is too stupid to wrap it. + */ + uri_for_display = eel_str_middle_truncate (name, MAX_URI_IN_DIALOG_LENGTH); + error_message = g_strdup_printf (_("Could Not Display “%s”"), uri_for_display); + g_free (uri_for_display); + g_free (name); + + return error_message; +} + +static void +open_with_response_cb (GtkDialog *dialog, + gint response_id, + gpointer user_data) +{ + GtkWindow *parent_window; + NautilusFile *file; + GList files; + GAppInfo *info; + ActivateParametersInstall *parameters = user_data; + + if (response_id != GTK_RESPONSE_OK) + { + gtk_window_destroy (GTK_WINDOW (dialog)); + return; + } + + parent_window = parameters->parent_window; + file = g_object_get_data (G_OBJECT (dialog), "mime-action:file"); + info = gtk_app_chooser_get_app_info (GTK_APP_CHOOSER (dialog)); + + gtk_window_destroy (GTK_WINDOW (dialog)); + + g_signal_emit_by_name (nautilus_signaller_get_current (), "mime-data-changed"); + + files.next = NULL; + files.prev = NULL; + files.data = file; + nautilus_launch_application (info, &files, parent_window); + + g_object_unref (info); + + activate_parameters_install_free (parameters); +} + +static void +choose_program (GtkDialog *message_dialog, + gchar *response, + gpointer callback_data) +{ + GtkWidget *dialog; + NautilusFile *file; + GFile *location; + ActivateParametersInstall *parameters = callback_data; + + if (g_strcmp0 (response, "select-application") != 0) + { + activate_parameters_install_free (parameters); + return; + } + + file = g_object_get_data (G_OBJECT (message_dialog), "mime-action:file"); + + g_assert (NAUTILUS_IS_FILE (file)); + + location = nautilus_file_get_location (file); + nautilus_file_ref (file); + + /* Destroy the message dialog after ref:ing the file */ + gtk_window_destroy (GTK_WINDOW (message_dialog)); + + dialog = gtk_app_chooser_dialog_new (parameters->parent_window, + GTK_DIALOG_MODAL, + location); + g_object_set_data_full (G_OBJECT (dialog), + "mime-action:file", + nautilus_file_ref (file), + (GDestroyNotify) nautilus_file_unref); + + gtk_widget_show (dialog); + + g_signal_connect (dialog, + "response", + G_CALLBACK (open_with_response_cb), + parameters); + + g_object_unref (location); + nautilus_file_unref (file); +} + +static void +show_unhandled_type_error (ActivateParametersInstall *parameters) +{ + GtkWidget *dialog; + g_autofree char *body = NULL; + g_autofree char *content_type_description = NULL; + + char *mime_type = nautilus_file_get_mime_type (parameters->file); + char *error_message = get_application_no_mime_type_handler_message (parameters->file, parameters->uri); + + if (g_content_type_is_unknown (mime_type)) + { + body = g_strdup (_("The file is of an unknown type")); + } + else + { + content_type_description = g_content_type_get_description (mime_type); + body = g_strdup_printf (_("There is no application installed for “%s” files"), content_type_description); + } + + dialog = adw_message_dialog_new (parameters->parent_window, error_message, body); + adw_message_dialog_add_responses (ADW_MESSAGE_DIALOG (dialog), + "select-application", _("_Select Application"), + "ok", _("_OK"), + NULL); + adw_message_dialog_set_default_response (ADW_MESSAGE_DIALOG (dialog), "ok"); + + g_object_set_data_full (G_OBJECT (dialog), + "mime-action:file", + nautilus_file_ref (parameters->file), + (GDestroyNotify) nautilus_file_unref); + + gtk_window_present (GTK_WINDOW (dialog)); + + g_signal_connect (dialog, "response", + G_CALLBACK (choose_program), parameters); + + g_free (error_message); + g_free (mime_type); +} + +static void +search_for_application_dbus_call_notify_cb (GDBusProxy *proxy, + GAsyncResult *result, + gpointer user_data) +{ + ActivateParametersInstall *parameters_install = user_data; + GVariant *variant; + GError *error = NULL; + + variant = g_dbus_proxy_call_finish (proxy, result, &error); + if (variant == NULL) + { + if (!g_dbus_error_is_remote_error (error) || + g_strcmp0 (g_dbus_error_get_remote_error (error), "org.freedesktop.PackageKit.Modify.Failed") == 0) + { + char *message; + + message = g_strdup_printf ("%s\n%s", + _("There was an internal error trying to search for applications:"), + error->message); + show_dialog (_("Unable to search for application"), + message, + parameters_install->parent_window, + GTK_MESSAGE_ERROR); + g_free (message); + } + else + { + g_warning ("Error while trying to search for applications: %s", + error->message); + } + + g_error_free (error); + activate_parameters_install_free (parameters_install); + return; + } + + g_variant_unref (variant); + + activate_parameters_install_free (parameters_install); +} + +static void +search_for_application_mime_type (ActivateParametersInstall *parameters_install, + const gchar *mime_type) +{ + gchar *desktop_startup_id; + + g_assert (parameters_install->proxy != NULL); + + desktop_startup_id = g_strdup_printf ("_TIME%i", (guint32) GDK_CURRENT_TIME); + + g_dbus_proxy_call (parameters_install->proxy, + "InstallMimeTypes", + g_variant_new_parsed ("([%s], %s, %s, [{%s, %v}])", + mime_type, + "hide-confirm-search", + APPLICATION_ID, + "desktop-startup-id", + g_variant_new_take_string (desktop_startup_id)), + G_DBUS_CALL_FLAGS_NONE, + G_MAXINT /* no timeout */, + NULL /* cancellable */, + (GAsyncReadyCallback) search_for_application_dbus_call_notify_cb, + parameters_install); + + DEBUG ("InstallMimeType method invoked for %s", mime_type); +} + +static void +application_unhandled_file_install (GtkDialog *dialog, + gchar *response, + ActivateParametersInstall *parameters_install) +{ + char *mime_type; + + parameters_install->dialog = NULL; + + if (g_strcmp0 (response, "search-in-software") == 0) + { + mime_type = nautilus_file_get_mime_type (parameters_install->file); + search_for_application_mime_type (parameters_install, mime_type); + g_free (mime_type); + } + else + { + /* free as we're not going to get the async dbus callback */ + activate_parameters_install_free (parameters_install); + } +} + +static void +pk_proxy_appeared_cb (GObject *source, + GAsyncResult *res, + gpointer user_data) +{ + ActivateParametersInstall *parameters_install = user_data; + char *mime_type, *name_owner; + char *error_message; + GtkWidget *dialog; + GDBusProxy *proxy; + GError *error = NULL; + g_autofree char *content_type_description = NULL; + + proxy = g_dbus_proxy_new_for_bus_finish (res, &error); + name_owner = g_dbus_proxy_get_name_owner (proxy); + + if (error != NULL || name_owner == NULL) + { + g_warning ("Couldn't call Modify on the PackageKit interface: %s", + error != NULL ? error->message : "no owner for PackageKit"); + g_clear_error (&error); + + /* show an unhelpful dialog */ + show_unhandled_type_error (parameters_install); + + return; + } + + g_free (name_owner); + + mime_type = nautilus_file_get_mime_type (parameters_install->file); + content_type_description = g_content_type_get_description (mime_type); + error_message = get_application_no_mime_type_handler_message (parameters_install->file, + parameters_install->uri); + /* use a custom dialog to prompt the user to install new software */ + dialog = adw_message_dialog_new (parameters_install->parent_window, error_message, NULL); + adw_message_dialog_add_responses (ADW_MESSAGE_DIALOG (dialog), + "cancel", _("_Cancel"), + "search-in-software", _("_Search in Software"), + NULL); + adw_message_dialog_format_body (ADW_MESSAGE_DIALOG (dialog), + _("There is no application installed for “%s” files. " + "Do you want to search for an application to open this file?"), + content_type_description); + + adw_message_dialog_set_default_response (ADW_MESSAGE_DIALOG (dialog), "search-in-software"); + + parameters_install->dialog = dialog; + parameters_install->proxy = proxy; + + g_signal_connect (dialog, "response", + G_CALLBACK (application_unhandled_file_install), + parameters_install); + gtk_window_present (GTK_WINDOW (dialog)); + g_free (mime_type); +} + +static void +application_unhandled_uri (ActivateParameters *parameters, + char *uri) +{ + gboolean show_install_mime; + char *mime_type; + NautilusFile *file; + ActivateParametersInstall *parameters_install; + + file = nautilus_file_get_by_uri (uri); + + mime_type = nautilus_file_get_mime_type (file); + + /* copy the parts of parameters we are interested in as the orignal will be unref'd */ + parameters_install = g_new0 (ActivateParametersInstall, 1); + parameters_install->slot = parameters->slot; + g_object_add_weak_pointer (G_OBJECT (parameters_install->slot), (gpointer *) ¶meters_install->slot); + if (parameters->parent_window) + { + parameters_install->parent_window = parameters->parent_window; + g_object_add_weak_pointer (G_OBJECT (parameters_install->parent_window), (gpointer *) ¶meters_install->parent_window); + } + parameters_install->activation_directory = g_strdup (parameters->activation_directory); + parameters_install->file = file; + parameters_install->files = get_file_list_for_launch_locations (parameters->locations); + parameters_install->flags = parameters->flags; + parameters_install->user_confirmation = parameters->user_confirmation; + parameters_install->uri = g_strdup (uri); + +#ifdef ENABLE_PACKAGEKIT + /* allow an admin to disable the PackageKit search functionality */ + show_install_mime = g_settings_get_boolean (nautilus_preferences, NAUTILUS_PREFERENCES_INSTALL_MIME_ACTIVATION); +#else + /* we have no install functionality */ + show_install_mime = FALSE; +#endif + /* There is no use trying to look for handlers of application/octet-stream */ + if (g_content_type_is_unknown (mime_type)) + { + show_install_mime = FALSE; + } + + g_free (mime_type); + + if (!show_install_mime) + { + goto out; + } + + g_dbus_proxy_new_for_bus (G_BUS_TYPE_SESSION, + G_DBUS_PROXY_FLAGS_NONE, + NULL, + "org.freedesktop.PackageKit", + "/org/freedesktop/PackageKit", + "org.freedesktop.PackageKit.Modify2", + NULL, + pk_proxy_appeared_cb, + parameters_install); + + return; + +out: + /* show an unhelpful dialog */ + show_unhandled_type_error (parameters_install); +} + +static void +launch_default_for_uris_callback (GObject *source_object, + GAsyncResult *res, + gpointer user_data) +{ + ApplicationLaunchAsyncParameters *params; + ActivateParameters *activation_params; + char *uri; + g_autoptr (GError) error = NULL; + + params = user_data; + activation_params = params->activation_params; + uri = g_queue_pop_head (params->uris); + + nautilus_launch_default_for_uri_finish (res, &error); + if (error == NULL) + { + gtk_recent_manager_add_item (gtk_recent_manager_get_default (), uri); + } + + if (!g_queue_is_empty (params->uris)) + { + nautilus_launch_default_for_uri_async (g_queue_peek_head (params->uris), + activation_params->parent_window, + activation_params->cancellable, + launch_default_for_uris_callback, + params); + } + else + { + application_launch_async_parameters_free (params); + } +} + +static void +activate_files (ActivateParameters *parameters) +{ + NautilusFile *file; + int count; + gint num_windows = 0; + gint num_tabs = 0; + GList *l; + ActivationAction action; + + parameters->launch_files = g_queue_new (); + parameters->launch_in_terminal_files = g_queue_new (); + parameters->open_in_view_files = g_queue_new (); + parameters->open_in_app_uris = g_queue_new (); + + for (l = parameters->locations; l != NULL; l = l->next) + { + LaunchLocation *location; + + location = l->data; + file = location->file; + + if (file_was_cancelled (file)) + { + continue; + } + + action = get_activation_action (file); + + switch (action) + { + case ACTIVATION_ACTION_LAUNCH: + { + g_queue_push_tail (parameters->launch_files, file); + } + break; + + case ACTIVATION_ACTION_LAUNCH_IN_TERMINAL: + { + g_queue_push_tail (parameters->launch_in_terminal_files, file); + } + break; + + case ACTIVATION_ACTION_OPEN_IN_VIEW: + { + g_queue_push_tail (parameters->open_in_view_files, file); + } + break; + + case ACTIVATION_ACTION_OPEN_IN_APPLICATION: + { + g_queue_push_tail (parameters->open_in_app_uris, location->uri); + } + break; + + case ACTIVATION_ACTION_DO_NOTHING: + { + } + break; + + case ACTIVATION_ACTION_EXTRACT: + { + /* Extraction of files should be handled in the view */ + g_assert_not_reached (); + } + break; + } + } + + count = g_queue_get_length (parameters->open_in_view_files); + if (count > 1) + { + if ((parameters->flags & NAUTILUS_OPEN_FLAG_NEW_WINDOW) == 0) + { + parameters->flags |= NAUTILUS_OPEN_FLAG_NEW_TAB; + num_tabs += count; + } + else + { + parameters->flags |= NAUTILUS_OPEN_FLAG_NEW_WINDOW; + num_windows += count; + } + } + + if (parameters->open_in_app_uris != NULL) + { + if (nautilus_application_is_sandboxed ()) + { + num_windows += g_queue_get_length (parameters->open_in_app_uris); + } + else + { + parameters->open_in_app_parameters = make_activation_parameters (g_queue_peek_head_link (parameters->open_in_app_uris), + ¶meters->unhandled_open_in_app_uris); + num_windows += g_list_length (parameters->open_in_app_parameters); + num_windows += g_list_length (parameters->unhandled_open_in_app_uris); + } + } + + num_windows += g_queue_get_length (parameters->launch_files); + num_windows += g_queue_get_length (parameters->launch_in_terminal_files); + + if (parameters->user_confirmation && + num_tabs + num_windows > SILENT_OPEN_LIMIT) + { + pause_activation_timed_cancel (parameters); + show_confirm_multiple (parameters, num_windows, num_tabs); + } + else + { + activate_files_internal (parameters); + } +} + +static void +activate_files_internal (ActivateParameters *parameters) +{ + NautilusFile *file; + ApplicationLaunchParameters *one_parameters; + g_autofree char *old_working_dir = NULL; + GdkDisplay *display; + GList *l; + + if (parameters->activation_directory && + (!g_queue_is_empty (parameters->launch_files) || + !g_queue_is_empty (parameters->launch_in_terminal_files))) + { + old_working_dir = g_get_current_dir (); + g_chdir (parameters->activation_directory); + } + + display = gtk_widget_get_display (GTK_WIDGET (parameters->parent_window)); + for (l = g_queue_peek_head_link (parameters->launch_files); l != NULL; l = l->next) + { + g_autofree char *uri = NULL; + g_autofree char *executable_path = NULL; + g_autofree char *quoted_path = NULL; + + file = NAUTILUS_FILE (l->data); + + uri = nautilus_file_get_activation_uri (file); + executable_path = g_filename_from_uri (uri, NULL, NULL); + quoted_path = g_shell_quote (executable_path); + + DEBUG ("Launching file path %s", quoted_path); + + nautilus_launch_application_from_command (display, quoted_path, FALSE, NULL); + } + + for (l = g_queue_peek_head_link (parameters->launch_in_terminal_files); l != NULL; l = l->next) + { + g_autofree char *uri = NULL; + g_autofree char *executable_path = NULL; + g_autofree char *quoted_path = NULL; + + file = NAUTILUS_FILE (l->data); + + uri = nautilus_file_get_activation_uri (file); + executable_path = g_filename_from_uri (uri, NULL, NULL); + quoted_path = g_shell_quote (executable_path); + + DEBUG ("Launching in terminal file quoted path %s", quoted_path); + + nautilus_launch_application_from_command (display, quoted_path, TRUE, NULL); + } + + if (old_working_dir != NULL) + { + g_chdir (old_working_dir); + } + + if (parameters->slot != NULL) + { + if ((parameters->flags & NAUTILUS_OPEN_FLAG_NEW_TAB) != 0) + { + /* When inserting N tabs after the current one, + * we first open tab N, then tab N-1, ..., then tab 0. + * Each of them is appended to the current tab, i.e. + * prepended to the list of tabs to open. + */ + g_queue_reverse (parameters->open_in_view_files); + } + + for (l = g_queue_peek_head_link (parameters->open_in_view_files); l != NULL; l = l->next) + { + g_autofree char *uri = NULL; + g_autoptr (GFile) location = NULL; + g_autoptr (GFile) location_with_permissions = NULL; + /* The ui should ask for navigation or object windows + * depending on what the current one is */ + file = NAUTILUS_FILE (l->data); + uri = nautilus_file_get_activation_uri (file); + location = g_file_new_for_uri (uri); + if (g_file_is_native (location) && + (nautilus_file_is_in_admin (file) || + !nautilus_file_can_read (file) || + !nautilus_file_can_execute (file))) + { + g_autofree gchar *file_path = NULL; + + g_free (uri); + + file_path = g_file_get_path (location); + uri = g_strconcat ("admin://", file_path, NULL); + } + + location_with_permissions = g_file_new_for_uri (uri); + /* FIXME: we need to pass the parent_window, but we only use it for the current active window, + * which nautilus-application should take care of. However is not working and creating regressions + * in some cases. Until we figure out what's going on, continue to use the parameters->slot + * to make splicit the window we want to use for activating the files */ + nautilus_application_open_location_full (NAUTILUS_APPLICATION (g_application_get_default ()), + location_with_permissions, parameters->flags, NULL, NULL, parameters->slot); + } + } + + if (!g_queue_is_empty (parameters->open_in_app_uris) && nautilus_application_is_sandboxed ()) + { + const char *uri; + ApplicationLaunchAsyncParameters *async_params; + + uri = g_queue_peek_head (parameters->open_in_app_uris); + + async_params = g_new0 (ApplicationLaunchAsyncParameters, 1); + async_params->activation_params = parameters; + async_params->uris = g_steal_pointer (¶meters->open_in_app_uris); + + nautilus_launch_default_for_uri_async (uri, + parameters->parent_window, + parameters->cancellable, + launch_default_for_uris_callback, + async_params); + return; + } + + if (!g_queue_is_empty (parameters->open_in_app_uris)) + { + for (l = parameters->open_in_app_parameters; l != NULL; l = l->next) + { + one_parameters = l->data; + + nautilus_launch_application_by_uri (one_parameters->application, + one_parameters->uris, + parameters->parent_window); + application_launch_parameters_free (one_parameters); + } + + for (l = parameters->unhandled_open_in_app_uris; l != NULL; l = l->next) + { + char *uri = l->data; + + /* this does not block */ + application_unhandled_uri (parameters, uri); + } + } + + activation_parameters_free (parameters); +} + +static void +activation_mount_not_mounted_callback (GObject *source_object, + GAsyncResult *res, + gpointer user_data) +{ + ActivateParameters *parameters = user_data; + GError *error; + NautilusFile *file; + LaunchLocation *loc; + + file = parameters->not_mounted->data; + + error = NULL; + if (!g_file_mount_enclosing_volume_finish (G_FILE (source_object), res, &error)) + { + if (error->domain != G_IO_ERROR || + (error->code != G_IO_ERROR_CANCELLED && + error->code != G_IO_ERROR_FAILED_HANDLED && + error->code != G_IO_ERROR_ALREADY_MOUNTED)) + { + show_dialog (_("Unable to access location"), + error->message, + parameters->parent_window, + GTK_MESSAGE_ERROR); + } + + if (error->domain != G_IO_ERROR || + error->code != G_IO_ERROR_ALREADY_MOUNTED) + { + loc = find_launch_location_for_file (parameters->locations, + file); + if (loc) + { + parameters->locations = + g_list_remove (parameters->locations, loc); + launch_location_free (loc); + } + } + + g_error_free (error); + } + + parameters->not_mounted = g_list_delete_link (parameters->not_mounted, + parameters->not_mounted); + nautilus_file_unref (file); + + activation_mount_not_mounted (parameters); +} + +static void +activation_mount_not_mounted (ActivateParameters *parameters) +{ + NautilusFile *file; + GFile *location; + LaunchLocation *loc; + GMountOperation *mount_op; + GList *l, *next, *files; + + if (parameters->not_mounted != NULL) + { + file = parameters->not_mounted->data; + mount_op = gtk_mount_operation_new (parameters->parent_window); + g_mount_operation_set_password_save (mount_op, G_PASSWORD_SAVE_FOR_SESSION); + g_signal_connect (mount_op, "notify::is-showing", + G_CALLBACK (activate_mount_op_active), parameters); + location = nautilus_file_get_location (file); + g_file_mount_enclosing_volume (location, 0, mount_op, parameters->cancellable, + activation_mount_not_mounted_callback, parameters); + g_object_unref (location); + /* unref mount_op here - g_file_mount_enclosing_volume() does ref for itself */ + g_object_unref (mount_op); + return; + } + + parameters->tried_mounting = TRUE; + + if (parameters->locations == NULL) + { + activation_parameters_free (parameters); + return; + } + + /* once the mount is finished, refresh all attributes + * - fixes new windows not appearing after successful mount + */ + for (l = parameters->locations; l != NULL; l = next) + { + loc = l->data; + next = l->next; + nautilus_file_invalidate_all_attributes (loc->file); + } + + files = get_file_list_for_launch_locations (parameters->locations); + nautilus_file_list_call_when_ready + (files, + nautilus_mime_actions_get_required_file_attributes (), + ¶meters->files_handle, + activate_callback, parameters); + nautilus_file_list_free (files); +} + + +static void +activate_callback (GList *files, + gpointer callback_data) +{ + ActivateParameters *parameters = callback_data; + GList *l, *next; + NautilusFile *file; + LaunchLocation *location; + + parameters->files_handle = NULL; + + for (l = parameters->locations; l != NULL; l = next) + { + location = l->data; + file = location->file; + next = l->next; + + if (file_was_cancelled (file)) + { + launch_location_free (location); + parameters->locations = g_list_delete_link (parameters->locations, l); + continue; + } + + if (file_was_not_mounted (file)) + { + if (parameters->tried_mounting) + { + launch_location_free (location); + parameters->locations = g_list_delete_link (parameters->locations, l); + } + else + { + parameters->not_mounted = g_list_prepend (parameters->not_mounted, + nautilus_file_ref (file)); + } + continue; + } + } + + + if (parameters->not_mounted != NULL) + { + activation_mount_not_mounted (parameters); + } + else + { + activate_files (parameters); + } +} + +static void +activate_activation_uris_ready_callback (GList *files_ignore, + gpointer callback_data) +{ + ActivateParameters *parameters = callback_data; + GList *l, *next, *files; + NautilusFile *file; + LaunchLocation *location; + + parameters->files_handle = NULL; + + for (l = parameters->locations; l != NULL; l = next) + { + location = l->data; + file = location->file; + next = l->next; + + if (file_was_cancelled (file)) + { + launch_location_free (location); + parameters->locations = g_list_delete_link (parameters->locations, l); + continue; + } + + if (nautilus_file_is_broken_symbolic_link (file)) + { + launch_location_free (location); + parameters->locations = g_list_delete_link (parameters->locations, l); + pause_activation_timed_cancel (parameters); + report_broken_symbolic_link (parameters->parent_window, file); + unpause_activation_timed_cancel (parameters); + continue; + } + + if (nautilus_file_get_file_type (file) == G_FILE_TYPE_MOUNTABLE && + !nautilus_file_has_activation_uri (file)) + { + /* Don't launch these... There is nothing we + * can do */ + launch_location_free (location); + parameters->locations = g_list_delete_link (parameters->locations, l); + continue; + } + } + + if (parameters->locations == NULL) + { + activation_parameters_free (parameters); + return; + } + + /* Convert the files to the actual activation uri files */ + for (l = parameters->locations; l != NULL; l = l->next) + { + char *uri; + location = l->data; + + /* We want the file for the activation URI since we care + * about the attributes for that, not for the original file. + */ + uri = nautilus_file_get_activation_uri (location->file); + if (uri != NULL) + { + launch_location_update_from_uri (location, uri); + } + g_free (uri); + } + + + /* get the parameters for the actual files */ + files = get_file_list_for_launch_locations (parameters->locations); + nautilus_file_list_call_when_ready + (files, + nautilus_mime_actions_get_required_file_attributes (), + ¶meters->files_handle, + activate_callback, parameters); + nautilus_file_list_free (files); +} + +static void +activate_regular_files (ActivateParameters *parameters) +{ + GList *l, *files; + NautilusFile *file; + LaunchLocation *location; + + /* link target info might be stale, re-read it */ + for (l = parameters->locations; l != NULL; l = l->next) + { + location = l->data; + file = location->file; + + if (file_was_cancelled (file)) + { + launch_location_free (location); + parameters->locations = g_list_delete_link (parameters->locations, l); + continue; + } + } + + if (parameters->locations == NULL) + { + activation_parameters_free (parameters); + return; + } + + files = get_file_list_for_launch_locations (parameters->locations); + nautilus_file_list_call_when_ready + (files, nautilus_mime_actions_get_required_file_attributes (), + ¶meters->files_handle, + activate_activation_uris_ready_callback, parameters); + nautilus_file_list_free (files); +} + +static void +activation_mountable_mounted (NautilusFile *file, + GFile *result_location, + GError *error, + gpointer callback_data) +{ + ActivateParameters *parameters = callback_data; + NautilusFile *target_file; + LaunchLocation *location; + + /* Remove from list of files that have to be mounted */ + parameters->mountables = g_list_remove (parameters->mountables, file); + nautilus_file_unref (file); + + + if (error == NULL) + { + /* Replace file with the result of the mount */ + target_file = nautilus_file_get (result_location); + + location = find_launch_location_for_file (parameters->locations, + file); + if (location) + { + launch_location_update_from_file (location, target_file); + } + nautilus_file_unref (target_file); + } + else + { + /* Remove failed file */ + + if (error->domain != G_IO_ERROR || + (error->code != G_IO_ERROR_FAILED_HANDLED && + error->code != G_IO_ERROR_ALREADY_MOUNTED)) + { + location = find_launch_location_for_file (parameters->locations, + file); + if (location) + { + parameters->locations = + g_list_remove (parameters->locations, + location); + launch_location_free (location); + } + } + + if (error->domain != G_IO_ERROR || + (error->code != G_IO_ERROR_CANCELLED && + error->code != G_IO_ERROR_FAILED_HANDLED && + error->code != G_IO_ERROR_ALREADY_MOUNTED)) + { + show_dialog (_("Unable to access location"), + error->message, + parameters->parent_window, + GTK_MESSAGE_ERROR); + } + + if (error->code == G_IO_ERROR_CANCELLED) + { + activation_parameters_free (parameters); + return; + } + } + + /* Mount more mountables */ + activation_mount_mountables (parameters); +} + + +static void +activation_mount_mountables (ActivateParameters *parameters) +{ + NautilusFile *file; + GMountOperation *mount_op; + + if (parameters->mountables != NULL) + { + file = parameters->mountables->data; + mount_op = gtk_mount_operation_new (parameters->parent_window); + g_mount_operation_set_password_save (mount_op, G_PASSWORD_SAVE_FOR_SESSION); + g_signal_connect (mount_op, "notify::is-showing", + G_CALLBACK (activate_mount_op_active), parameters); + nautilus_file_mount (file, + mount_op, + parameters->cancellable, + activation_mountable_mounted, + parameters); + g_object_unref (mount_op); + return; + } + + if (parameters->mountables == NULL && parameters->start_mountables == NULL) + { + activate_regular_files (parameters); + } +} + + +static void +activation_mountable_started (NautilusFile *file, + GFile *gfile_of_file, + GError *error, + gpointer callback_data) +{ + ActivateParameters *parameters = callback_data; + LaunchLocation *location; + + /* Remove from list of files that have to be mounted */ + parameters->start_mountables = g_list_remove (parameters->start_mountables, file); + nautilus_file_unref (file); + + if (error == NULL) + { + /* Remove file */ + location = find_launch_location_for_file (parameters->locations, file); + if (location != NULL) + { + parameters->locations = g_list_remove (parameters->locations, location); + launch_location_free (location); + } + } + else + { + /* Remove failed file */ + if (error->domain != G_IO_ERROR || + (error->code != G_IO_ERROR_FAILED_HANDLED)) + { + location = find_launch_location_for_file (parameters->locations, + file); + if (location) + { + parameters->locations = + g_list_remove (parameters->locations, + location); + launch_location_free (location); + } + } + + if (error->domain != G_IO_ERROR || + (error->code != G_IO_ERROR_CANCELLED && + error->code != G_IO_ERROR_FAILED_HANDLED)) + { + show_dialog (_("Unable to start location"), + error->message, + parameters->parent_window, + GTK_MESSAGE_ERROR); + } + + if (error->code == G_IO_ERROR_CANCELLED) + { + activation_parameters_free (parameters); + return; + } + } + + /* Start more mountables */ + activation_start_mountables (parameters); +} + +static void +activation_start_mountables (ActivateParameters *parameters) +{ + NautilusFile *file; + GMountOperation *start_op; + + if (parameters->start_mountables != NULL) + { + file = parameters->start_mountables->data; + start_op = gtk_mount_operation_new (parameters->parent_window); + g_signal_connect (start_op, "notify::is-showing", + G_CALLBACK (activate_mount_op_active), parameters); + nautilus_file_start (file, + start_op, + parameters->cancellable, + activation_mountable_started, + parameters); + g_object_unref (start_op); + return; + } + + if (parameters->mountables == NULL && parameters->start_mountables == NULL) + { + activate_regular_files (parameters); + } +} + +/** + * nautilus_mime_activate_files: + * + * Activate a list of files. Each one might launch with an application or + * with a component. This is normally called only by subclasses. + * @view: FMDirectoryView in question. + * @files: A GList of NautilusFiles to activate. + * + **/ +void +nautilus_mime_activate_files (GtkWindow *parent_window, + NautilusWindowSlot *slot, + GList *files, + const char *launch_directory, + NautilusOpenFlags flags, + gboolean user_confirmation) +{ + ActivateParameters *parameters; + char *file_name; + int file_count; + GList *l, *next; + NautilusFile *file; + LaunchLocation *location; + + if (files == NULL) + { + return; + } + + DEBUG_FILES (files, "Calling activate_files() with files:"); + + parameters = g_new0 (ActivateParameters, 1); + parameters->slot = slot; + g_object_add_weak_pointer (G_OBJECT (parameters->slot), (gpointer *) ¶meters->slot); + if (parent_window) + { + parameters->parent_window = parent_window; + g_object_add_weak_pointer (G_OBJECT (parameters->parent_window), (gpointer *) ¶meters->parent_window); + } + parameters->cancellable = g_cancellable_new (); + parameters->activation_directory = g_strdup (launch_directory); + parameters->locations = launch_locations_from_file_list (files); + parameters->flags = flags; + parameters->user_confirmation = user_confirmation; + + file_count = g_list_length (files); + if (file_count == 1) + { + file_name = nautilus_file_get_display_name (files->data); + parameters->timed_wait_prompt = g_strdup_printf (_("Opening “%s”."), file_name); + g_free (file_name); + } + else + { + parameters->timed_wait_prompt = g_strdup_printf (ngettext ("Opening %d item.", + "Opening %d items.", + file_count), + file_count); + } + + + for (l = parameters->locations; l != NULL; l = next) + { + location = l->data; + file = location->file; + next = l->next; + + if (nautilus_file_can_mount (file)) + { + parameters->mountables = g_list_prepend (parameters->mountables, + nautilus_file_ref (file)); + } + + if (nautilus_file_can_start (file)) + { + parameters->start_mountables = g_list_prepend (parameters->start_mountables, + nautilus_file_ref (file)); + } + } + + activation_start_timed_cancel (parameters); + if (parameters->mountables != NULL) + { + activation_mount_mountables (parameters); + } + if (parameters->start_mountables != NULL) + { + activation_start_mountables (parameters); + } + if (parameters->mountables == NULL && parameters->start_mountables == NULL) + { + activate_regular_files (parameters); + } +} + +/** + * nautilus_mime_activate_file: + * + * Activate a file in this view. This might involve switching the displayed + * location for the current window, or launching an application. + * @view: FMDirectoryView in question. + * @file: A NautilusFile representing the file in this view to activate. + * @use_new_window: Should this item be opened in a new window? + * + **/ + +void +nautilus_mime_activate_file (GtkWindow *parent_window, + NautilusWindowSlot *slot, + NautilusFile *file, + const char *launch_directory, + NautilusOpenFlags flags) +{ + GList *files; + + g_return_if_fail (NAUTILUS_IS_FILE (file)); + + files = g_list_prepend (NULL, file); + nautilus_mime_activate_files (parent_window, slot, files, launch_directory, flags, FALSE); + g_list_free (files); +} + +gint +nautilus_mime_types_get_number_of_groups (void) +{ + return G_N_ELEMENTS (mimetype_groups); +} + +const gchar * +nautilus_mime_types_group_get_name (gint group_index) +{ + g_return_val_if_fail (group_index < G_N_ELEMENTS (mimetype_groups), NULL); + + return gettext (mimetype_groups[group_index].name); +} + +GPtrArray * +nautilus_mime_types_group_get_mimetypes (gint group_index) +{ + GStrv group; + GPtrArray *mimetypes; + + g_return_val_if_fail (group_index < G_N_ELEMENTS (mimetype_groups), NULL); + + group = mimetype_groups[group_index].mimetypes; + mimetypes = g_ptr_array_new_full (g_strv_length (group), g_free); + + /* Setup the new mimetypes set */ + for (gint i = 0; group[i] != NULL; i++) + { + g_ptr_array_add (mimetypes, g_strdup (group[i])); + } + + return mimetypes; +} diff --git a/src/nautilus-mime-actions.h b/src/nautilus-mime-actions.h new file mode 100644 index 0000000..24b891b --- /dev/null +++ b/src/nautilus-mime-actions.h @@ -0,0 +1,54 @@ + +/* nautilus-mime-actions.h - uri-specific versions of mime action functions + + Copyright (C) 2000 Eazel, Inc. + + The Gnome Library is free software; you can redistribute it and/or + modify it under the terms of the GNU Library General Public License as + published by the Free Software Foundation; either version 2 of the + License, or (at your option) any later version. + + The Gnome Library is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Library General Public License for more details. + + You should have received a copy of the GNU Library General Public + License along with the Gnome Library; see the file COPYING.LIB. If not, + see . + + Authors: Maciej Stachowiak +*/ + +#pragma once + +#include +#include +#include + +#include "nautilus-types.h" + +NautilusFileAttributes nautilus_mime_actions_get_required_file_attributes (void); + +GAppInfo * nautilus_mime_get_default_application_for_file (NautilusFile *file); +GList * nautilus_mime_get_applications_for_file (NautilusFile *file); + +GAppInfo * nautilus_mime_get_default_application_for_files (GList *files); + +gboolean nautilus_mime_file_extracts (NautilusFile *file); +gboolean nautilus_mime_file_opens_in_external_app (NautilusFile *file); +gboolean nautilus_mime_file_launches (NautilusFile *file); +void nautilus_mime_activate_files (GtkWindow *parent_window, + NautilusWindowSlot *slot, + GList *files, + const char *launch_directory, + NautilusOpenFlags flags, + gboolean user_confirmation); +void nautilus_mime_activate_file (GtkWindow *parent_window, + NautilusWindowSlot *slot_info, + NautilusFile *file, + const char *launch_directory, + NautilusOpenFlags flags); +gint nautilus_mime_types_get_number_of_groups (void); +const gchar* nautilus_mime_types_group_get_name (gint group_index); +GPtrArray* nautilus_mime_types_group_get_mimetypes (gint group_index); diff --git a/src/nautilus-module.c b/src/nautilus-module.c new file mode 100644 index 0000000..ced810e --- /dev/null +++ b/src/nautilus-module.c @@ -0,0 +1,332 @@ +/* + * nautilus-module.h - Interface to nautilus extensions + * + * Copyright (C) 2003 Novell, Inc. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Library General Public + * License as published by the Free Software Foundation; either + * version 2 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Library General Public License for more details. + * + * You should have received a copy of the GNU Library General Public + * License along with this library; if not, see . + * + * Author: Dave Camp + * + */ + +#include +#include "nautilus-module.h" + +#include +#include + +#define NAUTILUS_TYPE_MODULE (nautilus_module_get_type ()) +#define NAUTILUS_MODULE(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), NAUTILUS_TYPE_MODULE, NautilusModule)) +#define NAUTILUS_MODULE_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), NAUTILUS_TYPE_MODULE, NautilusModule)) +#define NAUTILUS_IS_MODULE(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), NAUTILUS_TYPE_MODULE)) +#define NAUTILUS_IS_MODULE_CLASS(klass) (G_TYPE_CLASS_CHECK_CLASS_TYPE ((klass), NAUTILUS_TYPE_MODULE)) + +typedef struct _NautilusModule NautilusModule; +typedef struct _NautilusModuleClass NautilusModuleClass; + +struct _NautilusModule +{ + GTypeModule parent; + + GModule *library; + + char *path; + + void (*initialize) (GTypeModule *module); + void (*shutdown) (void); + + void (*list_types) (const GType **types, + int *num_types); +}; + +struct _NautilusModuleClass +{ + GTypeModuleClass parent; +}; + +static GList *module_objects = NULL; +static GStrv installed_module_names = NULL; + +static GType nautilus_module_get_type (void); + +G_DEFINE_TYPE (NautilusModule, nautilus_module, G_TYPE_TYPE_MODULE); + +static gboolean +module_pulls_in_orbit (GModule *module) +{ + gpointer symbol; + gboolean res; + + res = g_module_symbol (module, "ORBit_realloc_tcval", &symbol); + + return res; +} + +static gboolean +nautilus_module_load (GTypeModule *gmodule) +{ + NautilusModule *module; + + module = NAUTILUS_MODULE (gmodule); + + module->library = g_module_open (module->path, G_MODULE_BIND_LAZY | G_MODULE_BIND_LOCAL); + + if (!module->library) + { + g_warning ("%s", g_module_error ()); + return FALSE; + } + + /* ORBit installs atexit() handlers, which would get unloaded together + * with the module now that the main process doesn't depend on GConf anymore, + * causing nautilus to sefgault at exit. + * If we detect that an extension would pull in ORBit, we make the + * module resident to prevent that. + */ + if (module_pulls_in_orbit (module->library)) + { + g_module_make_resident (module->library); + } + + if (!g_module_symbol (module->library, + "nautilus_module_initialize", + (gpointer *) &module->initialize) || + !g_module_symbol (module->library, + "nautilus_module_shutdown", + (gpointer *) &module->shutdown) || + !g_module_symbol (module->library, + "nautilus_module_list_types", + (gpointer *) &module->list_types)) + { + g_warning ("%s", g_module_error ()); + g_module_close (module->library); + + return FALSE; + } + + module->initialize (gmodule); + + return TRUE; +} + +static void +nautilus_module_unload (GTypeModule *gmodule) +{ + NautilusModule *module; + + module = NAUTILUS_MODULE (gmodule); + + module->shutdown (); + + g_module_close (module->library); + + module->initialize = NULL; + module->shutdown = NULL; + module->list_types = NULL; +} + +static void +nautilus_module_finalize (GObject *object) +{ + NautilusModule *module; + + module = NAUTILUS_MODULE (object); + + g_free (module->path); + + G_OBJECT_CLASS (nautilus_module_parent_class)->finalize (object); +} + +static void +nautilus_module_init (NautilusModule *module) +{ +} + +static void +nautilus_module_class_init (NautilusModuleClass *class) +{ + G_OBJECT_CLASS (class)->finalize = nautilus_module_finalize; + G_TYPE_MODULE_CLASS (class)->load = nautilus_module_load; + G_TYPE_MODULE_CLASS (class)->unload = nautilus_module_unload; +} + +static void +module_object_weak_notify (gpointer user_data, + GObject *object) +{ + module_objects = g_list_remove (module_objects, object); +} + +static void +add_module_objects (NautilusModule *module) +{ + const GType *types; + int num_types; + int i; + + module->list_types (&types, &num_types); + + for (i = 0; i < num_types; i++) + { + if (types[i] == 0) /* Work around broken extensions */ + { + break; + } + nautilus_module_add_type (types[i]); + } +} + +static NautilusModule * +nautilus_module_load_file (const char *filename, + GStrvBuilder *installed_module_name_builder) +{ + NautilusModule *module; + + module = g_object_new (NAUTILUS_TYPE_MODULE, NULL); + module->path = g_strdup (filename); + + if (g_type_module_use (G_TYPE_MODULE (module))) + { + add_module_objects (module); + g_type_module_unuse (G_TYPE_MODULE (module)); + g_strv_builder_add (installed_module_name_builder, filename); + return module; + } + else + { + g_object_unref (module); + return NULL; + } +} + +char * +nautilus_module_get_installed_module_names (void) +{ + return g_strjoinv ("\n", installed_module_names); +} + +static void +load_module_dir (const char *dirname) +{ + GDir *dir; + + g_autoptr (GStrvBuilder) installed_module_name_builder = g_strv_builder_new (); + dir = g_dir_open (dirname, 0, NULL); + + if (dir) + { + const char *name; + + while ((name = g_dir_read_name (dir))) + { + if (g_str_has_suffix (name, "." G_MODULE_SUFFIX)) + { + char *filename; + + filename = g_build_filename (dirname, + name, + NULL); + nautilus_module_load_file (filename, installed_module_name_builder); + g_free (filename); + } + } + + g_dir_close (dir); + } + + installed_module_names = g_strv_builder_end (installed_module_name_builder); +} + +static void +free_module_objects (void) +{ + GList *l, *next; + + for (l = module_objects; l != NULL; l = next) + { + next = l->next; + g_object_unref (l->data); + } + + g_list_free (module_objects); + g_strfreev (installed_module_names); +} + +void +nautilus_module_setup (void) +{ + static gboolean initialized = FALSE; + const gchar *disable_plugins; + + disable_plugins = g_getenv ("NAUTILUS_DISABLE_PLUGINS"); + if (g_strcmp0 (disable_plugins, "TRUE") == 0) + { + /* Troublingshooting envvar is set to disable extensions */ + return; + } + + if (!initialized) + { + initialized = TRUE; + + load_module_dir (NAUTILUS_EXTENSIONDIR); + + eel_debug_call_at_shutdown (free_module_objects); + } +} + +GList * +nautilus_module_get_extensions_for_type (GType type) +{ + GList *l; + GList *ret = NULL; + + for (l = module_objects; l != NULL; l = l->next) + { + if (G_TYPE_CHECK_INSTANCE_TYPE (G_OBJECT (l->data), + type)) + { + g_object_ref (l->data); + ret = g_list_prepend (ret, l->data); + } + } + + return ret; +} + +void +nautilus_module_extension_list_free (GList *extensions) +{ + GList *l, *next; + + for (l = extensions; l != NULL; l = next) + { + next = l->next; + g_object_unref (l->data); + } + g_list_free (extensions); +} + +void +nautilus_module_add_type (GType type) +{ + GObject *object; + + object = g_object_new (type, NULL); + g_object_weak_ref (object, + (GWeakNotify) module_object_weak_notify, + NULL); + + module_objects = g_list_prepend (module_objects, object); +} diff --git a/src/nautilus-module.h b/src/nautilus-module.h new file mode 100644 index 0000000..dc91918 --- /dev/null +++ b/src/nautilus-module.h @@ -0,0 +1,39 @@ +/* + * nautilus-module.h - Interface to nautilus extensions + * + * Copyright (C) 2003 Novell, Inc. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Library General Public + * License as published by the Free Software Foundation; either + * version 2 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Library General Public License for more details. + * + * You should have received a copy of the GNU Library General Public + * License along with this library; if not, see . + * + * Author: Dave Camp + * + */ + +#pragma once + +#include + +G_BEGIN_DECLS + +void nautilus_module_setup (void); +GList *nautilus_module_get_extensions_for_type (GType type); +void nautilus_module_extension_list_free (GList *list); +gchar *nautilus_module_get_installed_module_names (void); + + +/* Add a type to the module interface - allows nautilus to add its own modules + * without putting them in separate shared libraries */ +void nautilus_module_add_type (GType type); + +G_END_DECLS diff --git a/src/nautilus-monitor.c b/src/nautilus-monitor.c new file mode 100644 index 0000000..cf3ef4e --- /dev/null +++ b/src/nautilus-monitor.c @@ -0,0 +1,182 @@ +/* + * nautilus-monitor.c: file and directory change monitoring for nautilus + * + * Copyright (C) 2000, 2001 Eazel, Inc. + * Copyright (C) 2016 Red Hat + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License as + * published by the Free Software Foundation; either version 2 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 warranty of + * MERCHANTABILITY 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 . + * + * Authors: Seth Nickell + * Darin Adler + * Alex Graveley + * Carlos Soriano + */ + +#include +#include "nautilus-monitor.h" +#include "nautilus-file-changes-queue.h" +#include "nautilus-file-utilities.h" + +#include + +struct NautilusMonitor +{ + GFileMonitor *monitor; + GVolumeMonitor *volume_monitor; + GFile *location; +}; + +static gboolean call_consume_changes_idle_id = 0; + +static gboolean +call_consume_changes_idle_cb (gpointer not_used) +{ + nautilus_file_changes_consume_changes (TRUE); + call_consume_changes_idle_id = 0; + return FALSE; +} + +static void +schedule_call_consume_changes (void) +{ + if (call_consume_changes_idle_id == 0) + { + call_consume_changes_idle_id = + g_idle_add (call_consume_changes_idle_cb, NULL); + } +} + +static void +mount_removed (GVolumeMonitor *volume_monitor, + GMount *mount, + gpointer user_data) +{ + NautilusMonitor *monitor = user_data; + GFile *mount_location; + + mount_location = g_mount_get_root (mount); + + if (g_file_has_prefix (monitor->location, mount_location)) + { + nautilus_file_changes_queue_file_removed (monitor->location); + schedule_call_consume_changes (); + } + + g_object_unref (mount_location); +} + +static void +dir_changed (GFileMonitor *monitor, + GFile *child, + GFile *other_file, + GFileMonitorEvent event_type, + gpointer user_data) +{ + char *to_uri; + + to_uri = NULL; + if (other_file) + { + to_uri = g_file_get_uri (other_file); + } + + switch (event_type) + { + default: + case G_FILE_MONITOR_EVENT_CHANGED: + { + /* ignore */ + } + break; + + case G_FILE_MONITOR_EVENT_ATTRIBUTE_CHANGED: + case G_FILE_MONITOR_EVENT_CHANGES_DONE_HINT: + { + nautilus_file_changes_queue_file_changed (child); + } + break; + + case G_FILE_MONITOR_EVENT_UNMOUNTED: + case G_FILE_MONITOR_EVENT_DELETED: + { + nautilus_file_changes_queue_file_removed (child); + } + break; + + case G_FILE_MONITOR_EVENT_CREATED: + { + nautilus_file_changes_queue_file_added (child); + } + break; + } + + g_free (to_uri); + + schedule_call_consume_changes (); +} + +NautilusMonitor * +nautilus_monitor_directory (GFile *location) +{ + GFileMonitor *dir_monitor; + NautilusMonitor *ret; + + ret = g_slice_new0 (NautilusMonitor); + dir_monitor = g_file_monitor_directory (location, G_FILE_MONITOR_WATCH_MOUNTS, NULL, NULL); + + if (dir_monitor != NULL) + { + ret->monitor = dir_monitor; + } + else if (!g_file_is_native (location)) + { + ret->location = g_object_ref (location); + ret->volume_monitor = g_volume_monitor_get (); + } + + if (ret->monitor != NULL) + { + g_signal_connect (ret->monitor, "changed", + G_CALLBACK (dir_changed), ret); + } + + if (ret->volume_monitor != NULL) + { + g_signal_connect (ret->volume_monitor, "mount-removed", + G_CALLBACK (mount_removed), ret); + } + + /* We return a monitor even on failure, so we can avoid later trying again */ + return ret; +} + +void +nautilus_monitor_cancel (NautilusMonitor *monitor) +{ + if (monitor->monitor != NULL) + { + g_signal_handlers_disconnect_by_func (monitor->monitor, dir_changed, monitor); + g_file_monitor_cancel (monitor->monitor); + g_object_unref (monitor->monitor); + } + + if (monitor->volume_monitor != NULL) + { + g_signal_handlers_disconnect_by_func (monitor->volume_monitor, mount_removed, monitor); + g_object_unref (monitor->volume_monitor); + } + + g_clear_object (&monitor->location); + g_slice_free (NautilusMonitor, monitor); +} diff --git a/src/nautilus-monitor.h b/src/nautilus-monitor.h new file mode 100644 index 0000000..ad0386d --- /dev/null +++ b/src/nautilus-monitor.h @@ -0,0 +1,33 @@ +/* + nautilus-monitor.h: file and directory change monitoring for nautilus + + Copyright (C) 2000, 2001 Eazel, Inc. + Copyright (C) 2016 Red Hat, Inc. + + This program is free software; you can redistribute it and/or + modify it under the terms of the GNU General Public License as + published by the Free Software Foundation; either version 2 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 warranty of + MERCHANTABILITY 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 . + + Authors: Seth Nickell + Darin Adler + Carlos Soriano +*/ + +#pragma once + +#include +#include + +typedef struct NautilusMonitor NautilusMonitor; + +NautilusMonitor *nautilus_monitor_directory (GFile *location); +void nautilus_monitor_cancel (NautilusMonitor *monitor); \ No newline at end of file diff --git a/src/nautilus-name-cell.c b/src/nautilus-name-cell.c new file mode 100644 index 0000000..f7e0a27 --- /dev/null +++ b/src/nautilus-name-cell.c @@ -0,0 +1,340 @@ +/* + * Copyright (C) 2022 The GNOME project contributors + * + * SPDX-License-Identifier: GPL-3.0-or-later + */ + +#include "nautilus-name-cell.h" +#include "nautilus-file-utilities.h" + +struct _NautilusNameCell +{ + NautilusViewCell parent_instance; + + GSignalGroup *item_signal_group; + + GQuark path_attribute_q; + GFile *file_path_base_location; + + GtkWidget *fixed_height_box; + GtkWidget *icon; + GtkWidget *label; + GtkWidget *emblems_box; + GtkWidget *snippet_button; + GtkWidget *snippet; + GtkWidget *path; + + gboolean show_snippet; +}; + +G_DEFINE_TYPE (NautilusNameCell, nautilus_name_cell, NAUTILUS_TYPE_VIEW_CELL) + +static gchar * +get_path_text (NautilusFile *file, + GQuark path_attribute_q, + GFile *base_location) +{ + g_autofree gchar *path = NULL; + g_autoptr (GFile) dir_location = NULL; + g_autoptr (GFile) home_location = g_file_new_for_path (g_get_home_dir ()); + g_autoptr (GFile) root_location = g_file_new_for_path ("/"); + GFile *relative_location_base; + + if (path_attribute_q == 0) + { + return NULL; + } + + path = nautilus_file_get_string_attribute_q (file, path_attribute_q); + dir_location = g_file_new_for_commandline_arg (path); + + if (base_location != NULL && g_file_equal (base_location, dir_location)) + { + /* Only occurs when search result is + * a direct child of the base location + */ + return NULL; + } + + if (g_file_equal (dir_location, home_location)) + { + return nautilus_compute_title_for_location (home_location); + } + + relative_location_base = base_location; + if (relative_location_base == NULL) + { + /* Only occurs in Recent, Starred and Trash. */ + relative_location_base = home_location; + } + + if (!g_file_equal (relative_location_base, root_location) && + g_file_has_prefix (dir_location, relative_location_base)) + { + g_autofree gchar *relative_path = NULL; + g_autofree gchar *display_name = NULL; + + relative_path = g_file_get_relative_path (relative_location_base, dir_location); + display_name = g_filename_display_name (relative_path); + + /* Ensure a trailing slash to emphasize it is a directory */ + if (g_str_has_suffix (display_name, G_DIR_SEPARATOR_S)) + { + return g_steal_pointer (&display_name); + } + + return g_strconcat (display_name, G_DIR_SEPARATOR_S, NULL); + } + + return g_steal_pointer (&path); +} + +static void +update_labels (NautilusNameCell *self) +{ + g_autoptr (NautilusViewItem) item = NULL; + NautilusFile *file; + g_autofree gchar *display_name = NULL; + g_autofree gchar *path_text = NULL; + const gchar *fts_snippet = NULL; + + item = nautilus_view_cell_get_item (NAUTILUS_VIEW_CELL (self)); + g_return_if_fail (item != NULL); + file = nautilus_view_item_get_file (item); + + display_name = nautilus_file_get_display_name (file); + path_text = get_path_text (file, + self->path_attribute_q, + self->file_path_base_location); + if (self->show_snippet) + { + fts_snippet = nautilus_file_get_search_fts_snippet (file); + } + + gtk_label_set_text (GTK_LABEL (self->label), display_name); + gtk_label_set_text (GTK_LABEL (self->path), path_text); + gtk_label_set_markup (GTK_LABEL (self->snippet), fts_snippet); + + gtk_widget_set_visible (self->path, (path_text != NULL)); + gtk_widget_set_visible (self->snippet_button, (fts_snippet != NULL)); +} + +static void +update_icon (NautilusNameCell *self) +{ + NautilusFileIconFlags flags; + g_autoptr (GdkPaintable) icon_paintable = NULL; + GtkStyleContext *style_context; + g_autoptr (NautilusViewItem) item = NULL; + NautilusFile *file; + guint icon_size; + gint scale_factor; + int icon_height; + int extra_margin; + g_autofree gchar *thumbnail_path = NULL; + + item = nautilus_view_cell_get_item (NAUTILUS_VIEW_CELL (self)); + g_return_if_fail (item != NULL); + + file = nautilus_view_item_get_file (item); + icon_size = nautilus_view_item_get_icon_size (item); + scale_factor = gtk_widget_get_scale_factor (GTK_WIDGET (self)); + flags = NAUTILUS_FILE_ICON_FLAGS_USE_THUMBNAILS; + + icon_paintable = nautilus_file_get_icon_paintable (file, icon_size, scale_factor, flags); + gtk_picture_set_paintable (GTK_PICTURE (self->icon), icon_paintable); + + /* Set the same width for all icons regardless of aspect ratio. + * Don't set the width here because it would get GtkPicture w4h confused. + */ + gtk_widget_set_size_request (self->fixed_height_box, icon_size, -1); + + /* Give all items the same minimum width. This cannot be done by setting the + * width request directly, as above, because it would get mess up with + * height for width calculations. + * + * Instead we must add margins on both sides of the icon which, summed up + * with the icon's actual width, equal the desired item width. */ + icon_height = gdk_paintable_get_intrinsic_height (icon_paintable); + extra_margin = (icon_size - icon_height) / 2; + gtk_widget_set_margin_top (self->fixed_height_box, extra_margin); + gtk_widget_set_margin_bottom (self->fixed_height_box, extra_margin); + + style_context = gtk_widget_get_style_context (self->icon); + thumbnail_path = nautilus_file_get_thumbnail_path (file); + if (icon_size >= NAUTILUS_THUMBNAIL_MINIMUM_ICON_SIZE && + thumbnail_path != NULL && + nautilus_file_should_show_thumbnail (file)) + { + gtk_style_context_add_class (style_context, "thumbnail"); + } + else + { + gtk_style_context_remove_class (style_context, "thumbnail"); + } +} + +static void +update_emblems (NautilusNameCell *self) +{ + g_autoptr (NautilusViewItem) item = NULL; + NautilusFile *file; + GtkWidget *child; + GtkIconTheme *theme; + g_autolist (GIcon) emblems = NULL; + + item = nautilus_view_cell_get_item (NAUTILUS_VIEW_CELL (self)); + g_return_if_fail (item != NULL); + file = nautilus_view_item_get_file (item); + + /* Remove old emblems. */ + while ((child = gtk_widget_get_first_child (self->emblems_box)) != NULL) + { + gtk_box_remove (GTK_BOX (self->emblems_box), child); + } + + theme = gtk_icon_theme_get_for_display (gdk_display_get_default ()); + emblems = nautilus_file_get_emblem_icons (file); + for (GList *l = emblems; l != NULL; l = l->next) + { + if (!gtk_icon_theme_has_gicon (theme, l->data)) + { + g_autofree gchar *icon_string = g_icon_to_string (l->data); + g_warning ("Failed to add emblem. “%s” not found in the icon theme", + icon_string); + continue; + } + + gtk_box_append (GTK_BOX (self->emblems_box), + gtk_image_new_from_gicon (l->data)); + } +} + +static void +on_file_changed (NautilusNameCell *self) +{ + update_icon (self); + update_labels (self); + update_emblems (self); +} + +static void +on_item_size_changed (NautilusNameCell *self) +{ + update_icon (self); +} + +static void +on_item_drag_accept_changed (NautilusNameCell *self) +{ + gboolean drag_accept; + g_autoptr (NautilusViewItem) item = NULL; + GtkWidget *list_row = gtk_widget_get_parent (gtk_widget_get_parent (GTK_WIDGET (self))); + + item = nautilus_view_cell_get_item (NAUTILUS_VIEW_CELL (self)); + g_object_get (item, "drag-accept", &drag_accept, NULL); + if (drag_accept) + { + gtk_widget_set_state_flags (list_row, GTK_STATE_FLAG_DROP_ACTIVE, FALSE); + } + else + { + gtk_widget_unset_state_flags (list_row, GTK_STATE_FLAG_DROP_ACTIVE); + } +} + +static void +on_item_is_cut_changed (NautilusNameCell *self) +{ + gboolean is_cut; + g_autoptr (NautilusViewItem) item = NULL; + + item = nautilus_view_cell_get_item (NAUTILUS_VIEW_CELL (self)); + g_object_get (item, + "is-cut", &is_cut, + NULL); + if (is_cut) + { + gtk_widget_add_css_class (self->icon, "cut"); + } + else + { + gtk_widget_remove_css_class (self->icon, "cut"); + } +} + +static void +nautilus_name_cell_init (NautilusNameCell *self) +{ + gtk_widget_init_template (GTK_WIDGET (self)); + + /* Connect automatically to an item. */ + self->item_signal_group = g_signal_group_new (NAUTILUS_TYPE_VIEW_ITEM); + g_signal_group_connect_swapped (self->item_signal_group, "notify::icon-size", + (GCallback) on_item_size_changed, self); + g_signal_group_connect_swapped (self->item_signal_group, "notify::drag-accept", + (GCallback) on_item_drag_accept_changed, self); + g_signal_group_connect_swapped (self->item_signal_group, "notify::is-cut", + (GCallback) on_item_is_cut_changed, self); + g_signal_group_connect_swapped (self->item_signal_group, "file-changed", + (GCallback) on_file_changed, self); + g_signal_connect_object (self->item_signal_group, "bind", + (GCallback) on_file_changed, self, + G_CONNECT_SWAPPED); + + g_object_bind_property (self, "item", + self->item_signal_group, "target", + G_BINDING_SYNC_CREATE); +} + +static void +nautilus_name_cell_finalize (GObject *object) +{ + NautilusNameCell *self = (NautilusNameCell *) object; + + g_clear_object (&self->item_signal_group); + g_clear_object (&self->file_path_base_location); + G_OBJECT_CLASS (nautilus_name_cell_parent_class)->finalize (object); +} + +static void +nautilus_name_cell_class_init (NautilusNameCellClass *klass) +{ + GObjectClass *object_class = G_OBJECT_CLASS (klass); + GtkWidgetClass *widget_class = GTK_WIDGET_CLASS (klass); + + object_class->finalize = nautilus_name_cell_finalize; + + gtk_widget_class_set_template_from_resource (widget_class, "/org/gnome/nautilus/ui/nautilus-name-cell.ui"); + + gtk_widget_class_bind_template_child (widget_class, NautilusNameCell, fixed_height_box); + gtk_widget_class_bind_template_child (widget_class, NautilusNameCell, icon); + gtk_widget_class_bind_template_child (widget_class, NautilusNameCell, label); + gtk_widget_class_bind_template_child (widget_class, NautilusNameCell, emblems_box); + gtk_widget_class_bind_template_child (widget_class, NautilusNameCell, snippet_button); + gtk_widget_class_bind_template_child (widget_class, NautilusNameCell, snippet); + gtk_widget_class_bind_template_child (widget_class, NautilusNameCell, path); +} + +NautilusViewCell * +nautilus_name_cell_new (NautilusListBase *view) +{ + return NAUTILUS_VIEW_CELL (g_object_new (NAUTILUS_TYPE_NAME_CELL, + "view", view, + NULL)); +} + +void +nautilus_name_cell_set_path (NautilusNameCell *self, + GQuark path_attribute_q, + GFile *base_location) +{ + self->path_attribute_q = path_attribute_q; + g_set_object (&self->file_path_base_location, base_location); +} + +void +nautilus_name_cell_show_snippet (NautilusNameCell *self) +{ + self->show_snippet = TRUE; +} diff --git a/src/nautilus-name-cell.h b/src/nautilus-name-cell.h new file mode 100644 index 0000000..62862cd --- /dev/null +++ b/src/nautilus-name-cell.h @@ -0,0 +1,23 @@ +/* + * Copyright (C) 2022 António Fernandes + * + * SPDX-License-Identifier: GPL-3.0-or-later + */ + +#pragma once + +#include "nautilus-view-cell.h" + +G_BEGIN_DECLS + +#define NAUTILUS_TYPE_NAME_CELL (nautilus_name_cell_get_type()) + +G_DECLARE_FINAL_TYPE (NautilusNameCell, nautilus_name_cell, NAUTILUS, NAME_CELL, NautilusViewCell) + +NautilusViewCell * nautilus_name_cell_new (NautilusListBase *view); +void nautilus_name_cell_set_path (NautilusNameCell *self, + GQuark path_attribute_q, + GFile *base_location); +void nautilus_name_cell_show_snippet (NautilusNameCell *self); + +G_END_DECLS diff --git a/src/nautilus-new-folder-dialog-controller.c b/src/nautilus-new-folder-dialog-controller.c new file mode 100644 index 0000000..9638c87 --- /dev/null +++ b/src/nautilus-new-folder-dialog-controller.c @@ -0,0 +1,191 @@ +/* nautilus-new-folder-dialog-controller.c + * + * Copyright (C) 2016 the Nautilus developers + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License as + * published by the Free Software Foundation; either version 2 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 warranty of + * MERCHANTABILITY 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 . + * + */ + +#include + +#include + +#include "nautilus-new-folder-dialog-controller.h" + + +struct _NautilusNewFolderDialogController +{ + NautilusFileNameWidgetController parent_instance; + + GtkWidget *new_folder_dialog; + + gboolean with_selection; + + gulong response_handler_id; +}; + +G_DEFINE_TYPE (NautilusNewFolderDialogController, nautilus_new_folder_dialog_controller, NAUTILUS_TYPE_FILE_NAME_WIDGET_CONTROLLER) + +static gboolean +nautilus_new_folder_dialog_controller_name_is_valid (NautilusFileNameWidgetController *self, + gchar *name, + gchar **error_message) +{ + gboolean is_valid; + + is_valid = TRUE; + if (strlen (name) == 0) + { + is_valid = FALSE; + } + else if (strstr (name, "/") != NULL) + { + is_valid = FALSE; + *error_message = _("Folder names cannot contain “/”."); + } + else if (strcmp (name, ".") == 0) + { + is_valid = FALSE; + *error_message = _("A folder cannot be called “.”."); + } + else if (strcmp (name, "..") == 0) + { + is_valid = FALSE; + *error_message = _("A folder cannot be called “..”."); + } + else if (nautilus_file_name_widget_controller_is_name_too_long (self, name)) + { + is_valid = FALSE; + *error_message = _("Folder name is too long."); + } + + if (is_valid && g_str_has_prefix (name, ".")) + { + /* We must warn about the side effect */ + *error_message = _("Folders with “.” at the beginning of their name are hidden."); + return TRUE; + } + + return is_valid; +} + +static void +new_folder_dialog_controller_on_response (GtkDialog *dialog, + gint response_id, + gpointer user_data) +{ + NautilusNewFolderDialogController *controller; + + controller = NAUTILUS_NEW_FOLDER_DIALOG_CONTROLLER (user_data); + + if (response_id != GTK_RESPONSE_OK) + { + g_signal_emit_by_name (controller, "cancelled"); + } +} + +NautilusNewFolderDialogController * +nautilus_new_folder_dialog_controller_new (GtkWindow *parent_window, + NautilusDirectory *destination_directory, + gboolean with_selection, + gchar *initial_name) +{ + NautilusNewFolderDialogController *self; + g_autoptr (GtkBuilder) builder = NULL; + GtkWidget *new_folder_dialog; + GtkWidget *error_revealer; + GtkWidget *error_label; + GtkWidget *name_entry; + GtkWidget *activate_button; + GtkWidget *name_label; + + builder = gtk_builder_new_from_resource ("/org/gnome/nautilus/ui/nautilus-create-folder-dialog.ui"); + new_folder_dialog = GTK_WIDGET (gtk_builder_get_object (builder, "create_folder_dialog")); + error_revealer = GTK_WIDGET (gtk_builder_get_object (builder, "error_revealer")); + error_label = GTK_WIDGET (gtk_builder_get_object (builder, "error_label")); + name_entry = GTK_WIDGET (gtk_builder_get_object (builder, "name_entry")); + activate_button = GTK_WIDGET (gtk_builder_get_object (builder, "ok_button")); + name_label = GTK_WIDGET (gtk_builder_get_object (builder, "name_label")); + + gtk_window_set_transient_for (GTK_WINDOW (new_folder_dialog), + parent_window); + + self = g_object_new (NAUTILUS_TYPE_NEW_FOLDER_DIALOG_CONTROLLER, + "error-revealer", error_revealer, + "error-label", error_label, + "name-entry", name_entry, + "activate-button", activate_button, + "containing-directory", destination_directory, NULL); + + self->with_selection = with_selection; + + self->new_folder_dialog = new_folder_dialog; + + self->response_handler_id = g_signal_connect (new_folder_dialog, + "response", + (GCallback) new_folder_dialog_controller_on_response, + self); + + if (initial_name != NULL) + { + gtk_editable_set_text (GTK_EDITABLE (name_entry), initial_name); + } + + gtk_button_set_label (GTK_BUTTON (activate_button), _("Create")); + gtk_label_set_text (GTK_LABEL (name_label), _("Folder name")); + gtk_window_set_title (GTK_WINDOW (new_folder_dialog), _("New Folder")); + + gtk_widget_show (new_folder_dialog); + + return self; +} + +gboolean +nautilus_new_folder_dialog_controller_get_with_selection (NautilusNewFolderDialogController *self) +{ + return self->with_selection; +} + +static void +nautilus_new_folder_dialog_controller_init (NautilusNewFolderDialogController *self) +{ +} + +static void +nautilus_new_folder_dialog_controller_finalize (GObject *object) +{ + NautilusNewFolderDialogController *self; + + self = NAUTILUS_NEW_FOLDER_DIALOG_CONTROLLER (object); + + if (self->new_folder_dialog != NULL) + { + g_clear_signal_handler (&self->response_handler_id, self->new_folder_dialog); + gtk_window_destroy (GTK_WINDOW (self->new_folder_dialog)); + self->new_folder_dialog = NULL; + } + + G_OBJECT_CLASS (nautilus_new_folder_dialog_controller_parent_class)->finalize (object); +} + +static void +nautilus_new_folder_dialog_controller_class_init (NautilusNewFolderDialogControllerClass *klass) +{ + GObjectClass *object_class = G_OBJECT_CLASS (klass); + NautilusFileNameWidgetControllerClass *parent_class = NAUTILUS_FILE_NAME_WIDGET_CONTROLLER_CLASS (klass); + + object_class->finalize = nautilus_new_folder_dialog_controller_finalize; + + parent_class->name_is_valid = nautilus_new_folder_dialog_controller_name_is_valid; +} diff --git a/src/nautilus-new-folder-dialog-controller.h b/src/nautilus-new-folder-dialog-controller.h new file mode 100644 index 0000000..a4a22a6 --- /dev/null +++ b/src/nautilus-new-folder-dialog-controller.h @@ -0,0 +1,36 @@ +/* nautilus-new-folder-dialog-controller.h + * + * Copyright (C) 2016 the Nautilus developers + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License as + * published by the Free Software Foundation; either version 2 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 warranty of + * MERCHANTABILITY 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 . + * + */ + +#pragma once + +#include +#include + +#include "nautilus-file-name-widget-controller.h" +#include "nautilus-directory.h" + +#define NAUTILUS_TYPE_NEW_FOLDER_DIALOG_CONTROLLER nautilus_new_folder_dialog_controller_get_type () +G_DECLARE_FINAL_TYPE (NautilusNewFolderDialogController, nautilus_new_folder_dialog_controller, NAUTILUS, NEW_FOLDER_DIALOG_CONTROLLER, NautilusFileNameWidgetController) + +NautilusNewFolderDialogController * nautilus_new_folder_dialog_controller_new (GtkWindow *parent_window, + NautilusDirectory *destination_directory, + gboolean with_selection, + gchar *initial_name); + +gboolean nautilus_new_folder_dialog_controller_get_with_selection (NautilusNewFolderDialogController *controller); \ No newline at end of file diff --git a/src/nautilus-operations-ui-manager.c b/src/nautilus-operations-ui-manager.c new file mode 100644 index 0000000..2371339 --- /dev/null +++ b/src/nautilus-operations-ui-manager.c @@ -0,0 +1,688 @@ +#include + +#include "nautilus-operations-ui-manager.h" + +#include "nautilus-file.h" +#include "nautilus-file-operations.h" +#include "nautilus-file-conflict-dialog.h" +#include "nautilus-mime-actions.h" +#include "nautilus-program-choosing.h" + +typedef struct +{ + GSourceFunc source_func; + GMutex mutex; + GCond cond; + gboolean completed; +} ContextInvokeData; + +G_LOCK_DEFINE_STATIC (main_context_sync); + +static gboolean +invoke_main_context_source_func_wrapper (gpointer user_data) +{ + ContextInvokeData *data = (ContextInvokeData *) user_data; + + g_mutex_lock (&data->mutex); + + while (data->source_func (user_data)) + { + } + + return G_SOURCE_REMOVE; +} + +static void +invoke_main_context_completed (gpointer user_data) +{ + ContextInvokeData *data = (ContextInvokeData *) user_data; + + data->completed = TRUE; + + g_cond_signal (&data->cond); + g_mutex_unlock (&data->mutex); +} + +/* This function is used to run UI on the main thread in order to ask the user + * for an action during an operation. Since the operation cannot progress until + * an action is provided by the user, the current thread needs to be blocked. + * For this we wait on a condition on the shared data. We proceed further + * unblocking the thread when invoke_main_context_completed() is called in the + * UI thread. The user_data pointer must reference a struct whose first member + * is of type ContextInvokeData. + */ +static void +invoke_main_context_sync (GMainContext *main_context, + GSourceFunc source_func, + gpointer user_data) +{ + ContextInvokeData *data = (ContextInvokeData *) user_data; + /* Allow only one thread at a time to invoke the main context so we + * don't get race conditions which could lead to multiple dialogs being + * displayed at the same time + */ + G_LOCK (main_context_sync); + + data->source_func = source_func; + + g_mutex_init (&data->mutex); + g_cond_init (&data->cond); + data->completed = FALSE; + + g_mutex_lock (&data->mutex); + + g_main_context_invoke (main_context, + invoke_main_context_source_func_wrapper, + user_data); + + while (!data->completed) + { + g_cond_wait (&data->cond, &data->mutex); + } + + g_mutex_unlock (&data->mutex); + + G_UNLOCK (main_context_sync); + + g_mutex_clear (&data->mutex); + g_cond_clear (&data->cond); +} + +typedef struct +{ + ContextInvokeData parent_type; + + GFile *source_name; + GFile *destination_name; + GFile *destination_directory_name; + + gchar *suggestion; + + GtkWindow *parent; + + gboolean should_start_inactive; + + FileConflictResponse *response; + + NautilusFile *source; + NautilusFile *destination; + NautilusFile *destination_directory_file; + + NautilusFileConflictDialog *dialog; + + NautilusFileListCallback on_file_list_ready; + NautilusFileListHandle *handle; + gulong source_handler_id; + gulong destination_handler_id; +} FileConflictDialogData; + +void +file_conflict_response_free (FileConflictResponse *response) +{ + g_free (response->new_name); + g_slice_free (FileConflictResponse, response); +} + +static void +set_copy_move_dialog_text (FileConflictDialogData *data) +{ + g_autofree gchar *primary_text = NULL; + g_autofree gchar *secondary_text = NULL; + const gchar *message_extra; + time_t source_mtime; + time_t destination_mtime; + g_autofree gchar *message = NULL; + g_autofree gchar *destination_name = NULL; + g_autofree gchar *destination_directory_name = NULL; + gboolean source_is_directory; + gboolean destination_is_directory; + + source_mtime = nautilus_file_get_mtime (data->source); + destination_mtime = nautilus_file_get_mtime (data->destination); + + destination_name = nautilus_file_get_display_name (data->destination); + destination_directory_name = nautilus_file_get_display_name (data->destination_directory_file); + + source_is_directory = nautilus_file_is_directory (data->source); + destination_is_directory = nautilus_file_is_directory (data->destination); + + if (destination_is_directory) + { + if (nautilus_file_is_symbolic_link (data->source) + && !nautilus_file_is_symbolic_link (data->destination)) + { + primary_text = g_strdup_printf (_("You are trying to replace the destination folder “%s” with a symbolic link."), + destination_name); + message = g_strdup_printf (_("This is not allowed in order to avoid the deletion of the destination folder’s contents.")); + message_extra = _("Please rename the symbolic link or press the skip button."); + } + else if (source_is_directory) + { + primary_text = g_strdup_printf (_("Merge folder “%s”?"), + destination_name); + + message_extra = _("Merging will ask for confirmation before replacing any files in " + "the folder that conflict with the files being copied."); + + if (source_mtime > destination_mtime) + { + message = g_strdup_printf (_("An older folder with the same name already exists in “%s”."), + destination_directory_name); + } + else if (source_mtime < destination_mtime) + { + message = g_strdup_printf (_("A newer folder with the same name already exists in “%s”."), + destination_directory_name); + } + else + { + message = g_strdup_printf (_("Another folder with the same name already exists in “%s”."), + destination_directory_name); + } + } + else + { + primary_text = g_strdup_printf (_("Replace folder “%s”?"), + destination_name); + message_extra = _("Replacing it will remove all files in the folder."); + message = g_strdup_printf (_("A folder with the same name already exists in “%s”."), + destination_directory_name); + } + } + else + { + primary_text = g_strdup_printf (_("Replace file “%s”?"), + destination_name); + + message_extra = _("Replacing it will overwrite its content."); + + if (source_mtime > destination_mtime) + { + message = g_strdup_printf (_("An older file with the same name already exists in “%s”."), + destination_directory_name); + } + else if (source_mtime < destination_mtime) + { + message = g_strdup_printf (_("A newer file with the same name already exists in “%s”."), + destination_directory_name); + } + else + { + message = g_strdup_printf (_("Another file with the same name already exists in “%s”."), + destination_directory_name); + } + } + + secondary_text = g_strdup_printf ("%s\n%s", message, message_extra); + + nautilus_file_conflict_dialog_set_text (data->dialog, + primary_text, + secondary_text); +} + +static void +set_images (FileConflictDialogData *data) +{ + GdkPaintable *source_paintable; + GdkPaintable *destination_paintable; + + destination_paintable = nautilus_file_get_icon_paintable (data->destination, + NAUTILUS_GRID_ICON_SIZE_SMALL, + 1, + NAUTILUS_FILE_ICON_FLAGS_USE_THUMBNAILS); + + source_paintable = nautilus_file_get_icon_paintable (data->source, + NAUTILUS_GRID_ICON_SIZE_SMALL, + 1, + NAUTILUS_FILE_ICON_FLAGS_USE_THUMBNAILS); + + nautilus_file_conflict_dialog_set_images (data->dialog, + destination_paintable, + source_paintable); + + g_object_unref (destination_paintable); + g_object_unref (source_paintable); +} + +static void +set_file_labels (FileConflictDialogData *data) +{ + GString *destination_label; + GString *source_label; + gboolean source_is_directory; + gboolean destination_is_directory; + gboolean should_show_type; + g_autofree char *destination_mime_type = NULL; + g_autofree char *destination_date = NULL; + g_autofree char *destination_size = NULL; + g_autofree char *destination_type = NULL; + g_autofree char *source_date = NULL; + g_autofree char *source_size = NULL; + g_autofree char *source_type = NULL; + + source_is_directory = nautilus_file_is_directory (data->source); + destination_is_directory = nautilus_file_is_directory (data->destination); + + destination_mime_type = nautilus_file_get_mime_type (data->destination); + should_show_type = !nautilus_file_is_mime_type (data->source, + destination_mime_type); + + destination_date = nautilus_file_get_string_attribute_with_default (data->destination, + "date_modified"); + destination_size = nautilus_file_get_string_attribute_with_default (data->destination, + "size"); + + if (should_show_type) + { + destination_type = nautilus_file_get_string_attribute_with_default (data->destination, + "type"); + } + + destination_label = g_string_new (NULL); + if (destination_is_directory) + { + g_string_append_printf (destination_label, "%s\n", _("Original folder")); + g_string_append_printf (destination_label, "%s %s\n", _("Contents:"), destination_size); + } + else + { + g_string_append_printf (destination_label, "%s\n", _("Original file")); + g_string_append_printf (destination_label, "%s %s\n", _("Size:"), destination_size); + } + + if (should_show_type) + { + g_string_append_printf (destination_label, "%s %s\n", _("Type:"), destination_type); + } + + g_string_append_printf (destination_label, "%s %s", _("Last modified:"), destination_date); + + source_date = nautilus_file_get_string_attribute_with_default (data->source, + "date_modified"); + source_size = nautilus_file_get_string_attribute_with_default (data->source, + "size"); + + if (should_show_type) + { + source_type = nautilus_file_get_string_attribute_with_default (data->source, + "type"); + } + + source_label = g_string_new (NULL); + if (source_is_directory) + { + g_string_append_printf (source_label, "%s\n", + destination_is_directory ? + _("Merge with") : _("Replace with")); + g_string_append_printf (source_label, "%s %s\n", _("Contents:"), source_size); + } + else + { + g_string_append_printf (source_label, "%s\n", _("Replace with")); + g_string_append_printf (source_label, "%s %s\n", _("Size:"), source_size); + } + + if (should_show_type) + { + g_string_append_printf (source_label, "%s %s\n", _("Type:"), source_type); + } + + g_string_append_printf (source_label, "%s %s", _("Last modified:"), source_date); + + nautilus_file_conflict_dialog_set_file_labels (data->dialog, + destination_label->str, + source_label->str); + + g_string_free (destination_label, TRUE); + g_string_free (source_label, TRUE); +} + +static void +set_conflict_and_suggested_names (FileConflictDialogData *data) +{ + g_autofree gchar *conflict_name = NULL; + + conflict_name = nautilus_file_get_edit_name (data->destination); + + nautilus_file_conflict_dialog_set_conflict_name (data->dialog, + conflict_name); + + nautilus_file_conflict_dialog_set_suggested_name (data->dialog, + data->suggestion); +} + +static void +set_replace_button_label (FileConflictDialogData *data) +{ + gboolean source_is_directory, destination_is_directory; + + source_is_directory = nautilus_file_is_directory (data->source); + destination_is_directory = nautilus_file_is_directory (data->destination); + + if (destination_is_directory) + { + if (nautilus_file_is_symbolic_link (data->source) + && !nautilus_file_is_symbolic_link (data->destination)) + { + nautilus_file_conflict_dialog_disable_replace (data->dialog); + nautilus_file_conflict_dialog_disable_apply_to_all (data->dialog); + } + else if (source_is_directory) + { + nautilus_file_conflict_dialog_set_replace_button_label (data->dialog, + _("Merge")); + } + } +} + +static void +file_icons_changed (NautilusFile *file, + FileConflictDialogData *data) +{ + set_images (data); +} + +static void +copy_move_conflict_on_file_list_ready (GList *files, + gpointer user_data) +{ + FileConflictDialogData *data = user_data; + g_autofree gchar *title = NULL; + + data->handle = NULL; + + if (nautilus_file_is_directory (data->source)) + { + title = g_strdup (nautilus_file_is_directory (data->destination) ? + _("Merge Folder") : + _("File and Folder conflict")); + } + else + { + title = g_strdup (nautilus_file_is_directory (data->destination) ? + _("File and Folder conflict") : + _("File conflict")); + } + + gtk_window_set_title (GTK_WINDOW (data->dialog), title); + + set_copy_move_dialog_text (data); + + set_images (data); + + set_file_labels (data); + + set_conflict_and_suggested_names (data); + + set_replace_button_label (data); + + nautilus_file_monitor_add (data->source, data, NAUTILUS_FILE_ATTRIBUTES_FOR_ICON); + nautilus_file_monitor_add (data->destination, data, NAUTILUS_FILE_ATTRIBUTES_FOR_ICON); + + data->source_handler_id = g_signal_connect (data->source, "changed", + G_CALLBACK (file_icons_changed), data); + data->destination_handler_id = g_signal_connect (data->destination, "changed", + G_CALLBACK (file_icons_changed), data); +} + +static void +on_conflict_dialog_response (GtkDialog *dialog, + gint response_id, + gpointer user_data) +{ + FileConflictDialogData *data = user_data; + + if (data->handle != NULL) + { + nautilus_file_list_cancel_call_when_ready (data->handle); + } + + if (data->source_handler_id) + { + g_signal_handler_disconnect (data->source, data->source_handler_id); + nautilus_file_monitor_remove (data->source, data); + } + + if (data->destination_handler_id) + { + g_signal_handler_disconnect (data->destination, data->destination_handler_id); + nautilus_file_monitor_remove (data->destination, data); + } + + if (response_id == CONFLICT_RESPONSE_RENAME) + { + data->response->new_name = + nautilus_file_conflict_dialog_get_new_name (data->dialog); + } + else if (response_id != GTK_RESPONSE_CANCEL && + response_id != GTK_RESPONSE_NONE) + { + data->response->apply_to_all = + nautilus_file_conflict_dialog_get_apply_to_all (data->dialog); + } + + data->response->id = response_id; + + gtk_window_destroy (GTK_WINDOW (data->dialog)); + + nautilus_file_unref (data->source); + nautilus_file_unref (data->destination); + nautilus_file_unref (data->destination_directory_file); + + invoke_main_context_completed (user_data); +} + +static gboolean +run_file_conflict_dialog (gpointer user_data) +{ + FileConflictDialogData *data = user_data; + GList *files = NULL; + + data->source = nautilus_file_get (data->source_name); + data->destination = nautilus_file_get (data->destination_name); + data->destination_directory_file = nautilus_file_get (data->destination_directory_name); + + data->dialog = nautilus_file_conflict_dialog_new (data->parent); + + if (data->should_start_inactive) + { + nautilus_file_conflict_dialog_delay_buttons_activation (data->dialog); + } + + files = g_list_prepend (files, data->source); + files = g_list_prepend (files, data->destination); + files = g_list_prepend (files, data->destination_directory_file); + + nautilus_file_list_call_when_ready (files, + NAUTILUS_FILE_ATTRIBUTES_FOR_ICON | NAUTILUS_FILE_ATTRIBUTE_DIRECTORY_ITEM_COUNT, + &data->handle, + data->on_file_list_ready, + data); + + g_signal_connect (data->dialog, "response", G_CALLBACK (on_conflict_dialog_response), data); + gtk_widget_show (GTK_WIDGET (data->dialog)); + + g_list_free (files); + + return G_SOURCE_REMOVE; +} + +FileConflictResponse * +copy_move_conflict_ask_user_action (GtkWindow *parent_window, + gboolean should_start_inactive, + GFile *source_name, + GFile *destination_name, + GFile *destination_directory_name, + gchar *suggestion) +{ + FileConflictDialogData *data; + FileConflictResponse *response; + + data = g_slice_new0 (FileConflictDialogData); + data->parent = parent_window; + data->should_start_inactive = should_start_inactive; + data->source_name = source_name; + data->destination_name = destination_name; + data->destination_directory_name = destination_directory_name; + data->suggestion = suggestion; + + data->response = g_slice_new0 (FileConflictResponse); + data->response->new_name = NULL; + + data->on_file_list_ready = copy_move_conflict_on_file_list_ready; + + invoke_main_context_sync (NULL, + run_file_conflict_dialog, + data); + + response = g_steal_pointer (&data->response); + g_slice_free (FileConflictDialogData, data); + + return response; +} + +typedef struct +{ + ContextInvokeData parent_type; + GtkWindow *parent_window; + NautilusFile *file; +} HandleUnsupportedFileData; + +static void +on_app_chooser_response (GtkDialog *dialog, + gint response_id, + gpointer user_data) +{ + HandleUnsupportedFileData *data = user_data; + g_autoptr (GAppInfo) application = NULL; + + if (response_id == GTK_RESPONSE_OK) + { + application = gtk_app_chooser_get_app_info (GTK_APP_CHOOSER (dialog)); + } + + gtk_window_destroy (GTK_WINDOW (dialog)); + + if (application != NULL) + { + GList files = {data->file, NULL, NULL}; + nautilus_launch_application (application, &files, data->parent_window); + } + + invoke_main_context_completed (user_data); +} + +static gboolean +open_file_in_application (gpointer user_data) +{ + HandleUnsupportedFileData *data; + g_autofree gchar *mime_type = NULL; + GtkWidget *dialog; + const char *heading; + + data = user_data; + mime_type = nautilus_file_get_mime_type (data->file); + dialog = gtk_app_chooser_dialog_new_for_content_type (data->parent_window, + GTK_DIALOG_MODAL | + GTK_DIALOG_DESTROY_WITH_PARENT | + GTK_DIALOG_USE_HEADER_BAR, + mime_type); + heading = _("Password-protected archives are not yet supported. " + "This list contains applications that can open the archive."); + + gtk_app_chooser_dialog_set_heading (GTK_APP_CHOOSER_DIALOG (dialog), heading); + + g_signal_connect (dialog, "response", G_CALLBACK (on_app_chooser_response), data); + gtk_widget_show (dialog); + + return G_SOURCE_REMOVE; +} + +/* This is used to open compressed files that are not supported by gnome-autoar + * in another application + */ +void +handle_unsupported_compressed_file (GtkWindow *parent_window, + GFile *compressed_file) +{ + HandleUnsupportedFileData *data; + + data = g_slice_new0 (HandleUnsupportedFileData); + data->parent_window = parent_window; + data->file = nautilus_file_get (compressed_file); + + invoke_main_context_sync (NULL, open_file_in_application, data); + + nautilus_file_unref (data->file); + g_slice_free (HandleUnsupportedFileData, data); + + return; +} + +typedef struct +{ + ContextInvokeData parent_type; + GtkWindow *parent_window; + const gchar *basename; + GtkEntry *passphrase_entry; + gchar *passphrase; +} PassphraseRequestData; + +static void +on_request_passphrase_cb (GtkDialog *dialog, + gint response_id, + gpointer user_data) +{ + PassphraseRequestData *data = user_data; + + if (response_id != GTK_RESPONSE_CANCEL && + response_id != GTK_RESPONSE_DELETE_EVENT) + { + data->passphrase = g_strdup (gtk_editable_get_text (GTK_EDITABLE (data->passphrase_entry))); + } + + gtk_window_destroy (GTK_WINDOW (dialog)); + invoke_main_context_completed (data); +} + +static gboolean +run_passphrase_dialog (gpointer user_data) +{ + PassphraseRequestData *data = user_data; + g_autofree gchar *label_str = NULL; + g_autoptr (GtkBuilder) builder = NULL; + GObject *dialog; + GObject *label; + + builder = gtk_builder_new_from_resource ("/org/gnome/nautilus/ui/nautilus-operations-ui-manager-request-passphrase.ui"); + dialog = gtk_builder_get_object (builder, "request_passphrase_dialog"); + label = gtk_builder_get_object (builder, "label"); + data->passphrase_entry = GTK_ENTRY (gtk_builder_get_object (builder, "entry")); + + label_str = g_strdup_printf (_("“%s” is password-protected."), data->basename); + gtk_label_set_text (GTK_LABEL (label), label_str); + + g_signal_connect (dialog, "response", G_CALLBACK (on_request_passphrase_cb), data); + gtk_window_set_transient_for (GTK_WINDOW (dialog), data->parent_window); + gtk_widget_show (GTK_WIDGET (dialog)); + + return G_SOURCE_REMOVE; +} + +gchar * +extract_ask_passphrase (GtkWindow *parent_window, + const gchar *archive_basename) +{ + PassphraseRequestData *data; + gchar *passphrase; + + data = g_new0 (PassphraseRequestData, 1); + data->parent_window = parent_window; + data->basename = archive_basename; + invoke_main_context_sync (NULL, run_passphrase_dialog, data); + + passphrase = g_steal_pointer (&data->passphrase); + g_free (data); + + return passphrase; +} diff --git a/src/nautilus-operations-ui-manager.h b/src/nautilus-operations-ui-manager.h new file mode 100644 index 0000000..3bd4512 --- /dev/null +++ b/src/nautilus-operations-ui-manager.h @@ -0,0 +1,34 @@ +#pragma once + +#include +#include + +#define BUTTON_ACTIVATION_DELAY_IN_SECONDS 2 + +typedef struct { + int id; + char *new_name; + gboolean apply_to_all; +} FileConflictResponse; + +void file_conflict_response_free (FileConflictResponse *data); + +FileConflictResponse * copy_move_conflict_ask_user_action (GtkWindow *parent_window, + gboolean should_start_inactive, + GFile *src, + GFile *dest, + GFile *dest_dir, + gchar *suggestion); + +enum +{ + CONFLICT_RESPONSE_SKIP = 1, + CONFLICT_RESPONSE_REPLACE = 2, + CONFLICT_RESPONSE_RENAME = 3, +}; + +void handle_unsupported_compressed_file (GtkWindow *parent_window, + GFile *compressed_file); + +gchar *extract_ask_passphrase (GtkWindow *parent_window, + const gchar *archive_basename); diff --git a/src/nautilus-pathbar.c b/src/nautilus-pathbar.c new file mode 100644 index 0000000..0c880f3 --- /dev/null +++ b/src/nautilus-pathbar.c @@ -0,0 +1,1216 @@ +/* nautilus-pathbar.c + * Copyright (C) 2004 Red Hat, Inc., Jonathan Blandford + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Library General Public + * License as published by the Free Software Foundation; either + * version 2 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Library General Public License for more details. + * + * You should have received a copy of the GNU Library General Public + * License along with this library; if not, see . + */ + + +#include +#include +#include +#include +#include + +#include "nautilus-pathbar.h" +#include "nautilus-properties-window.h" + +#include "nautilus-enums.h" +#include "nautilus-enum-types.h" +#include "nautilus-file.h" +#include "nautilus-file-utilities.h" +#include "nautilus-global-preferences.h" +#include "nautilus-icon-names.h" +#include "nautilus-trash-monitor.h" +#include "nautilus-ui-utilities.h" + +#include "nautilus-window-slot-dnd.h" + +enum +{ + OPEN_LOCATION, + LAST_SIGNAL +}; + +typedef enum +{ + NORMAL_BUTTON, + OTHER_LOCATIONS_BUTTON, + ROOT_BUTTON, + ADMIN_ROOT_BUTTON, + HOME_BUTTON, + STARRED_BUTTON, + RECENT_BUTTON, + MOUNT_BUTTON, + TRASH_BUTTON, +} ButtonType; + +#define BUTTON_DATA(x) ((ButtonData *) (x)) + +static guint path_bar_signals[LAST_SIGNAL] = { 0 }; + +#define NAUTILUS_PATH_BAR_BUTTON_ELLISPIZE_MINIMUM_CHARS 7 + +typedef struct +{ + GtkWidget *button; + ButtonType type; + char *dir_name; + GFile *path; + NautilusFile *file; + unsigned int file_changed_signal_id; + + GtkWidget *image; + GtkWidget *label; + GtkWidget *separator; + GtkWidget *container; + + NautilusPathBar *path_bar; + + guint ignore_changes : 1; + guint is_root : 1; +} ButtonData; + +struct _NautilusPathBar +{ + GtkBox parent_instance; + + GtkWidget *scrolled; + GtkWidget *buttons_box; + + GFile *current_path; + gpointer current_button_data; + + GList *button_list; + + GActionGroup *action_group; + + NautilusFile *context_menu_file; + GtkPopoverMenu *current_view_menu_popover; + GtkWidget *current_view_menu_button; + GtkWidget *button_menu_popover; + GMenu *current_view_menu; + GMenu *extensions_section; + GMenu *templates_submenu; + GMenu *button_menu; + + gchar *os_name; +}; + +G_DEFINE_TYPE (NautilusPathBar, nautilus_path_bar, GTK_TYPE_BOX); + +static void nautilus_path_bar_update_button_state (ButtonData *button_data, + gboolean current_dir); +static void nautilus_path_bar_update_path (NautilusPathBar *self, + GFile *file_path); + +static void unschedule_pop_up_context_menu (NautilusPathBar *self); +static void action_pathbar_open_item_new_window (GSimpleAction *action, + GVariant *state, + gpointer user_data); +static void action_pathbar_open_item_new_tab (GSimpleAction *action, + GVariant *state, + gpointer user_data); +static void action_pathbar_properties (GSimpleAction *action, + GVariant *state, + gpointer user_data); +static void pop_up_pathbar_context_menu (NautilusPathBar *self, + NautilusFile *file); +static void nautilus_path_bar_clear_buttons (NautilusPathBar *self); + +const GActionEntry path_bar_actions[] = +{ + { "open-item-new-tab", action_pathbar_open_item_new_tab }, + { "open-item-new-window", action_pathbar_open_item_new_window }, + { "properties", action_pathbar_properties} +}; + + +static void +action_pathbar_open_item_new_tab (GSimpleAction *action, + GVariant *state, + gpointer user_data) +{ + NautilusPathBar *self; + GFile *location; + + self = NAUTILUS_PATH_BAR (user_data); + + if (self->context_menu_file == NULL) + { + return; + } + + location = nautilus_file_get_location (self->context_menu_file); + + if (location) + { + g_signal_emit (user_data, path_bar_signals[OPEN_LOCATION], 0, location, + NAUTILUS_OPEN_FLAG_NEW_TAB | NAUTILUS_OPEN_FLAG_DONT_MAKE_ACTIVE); + g_object_unref (location); + } +} + +static void +action_pathbar_open_item_new_window (GSimpleAction *action, + GVariant *state, + gpointer user_data) +{ + NautilusPathBar *self; + GFile *location; + + self = NAUTILUS_PATH_BAR (user_data); + + if (self->context_menu_file == NULL) + { + return; + } + + location = nautilus_file_get_location (self->context_menu_file); + + if (location) + { + g_signal_emit (user_data, path_bar_signals[OPEN_LOCATION], 0, location, NAUTILUS_OPEN_FLAG_NEW_WINDOW); + g_object_unref (location); + } +} + +static void +action_pathbar_properties (GSimpleAction *action, + GVariant *state, + gpointer user_data) +{ + NautilusPathBar *self; + GList *files; + + self = NAUTILUS_PATH_BAR (user_data); + + g_return_if_fail (NAUTILUS_IS_FILE (self->context_menu_file)); + + files = g_list_append (NULL, nautilus_file_ref (self->context_menu_file)); + + nautilus_properties_window_present (files, GTK_WIDGET (self), NULL, NULL, + NULL); + + nautilus_file_list_free (files); +} + +static void +on_adjustment_changed (GtkAdjustment *adjustment, + NautilusPathBar *self) +{ + /* Automatically scroll to the end, to reveal the current folder. */ + g_autoptr (AdwAnimation) anim = NULL; + anim = adw_timed_animation_new (GTK_WIDGET (self), + gtk_adjustment_get_value (adjustment), + gtk_adjustment_get_upper (adjustment), + 800, + adw_property_animation_target_new (G_OBJECT (adjustment), "value")); + adw_timed_animation_set_easing (ADW_TIMED_ANIMATION (anim), ADW_EASE_OUT_CUBIC); + adw_animation_play (anim); +} + +static void +on_page_size_changed (GtkAdjustment *adjustment) +{ + /* When window is resized, immediately set new value, otherwise we would get + * an underflow gradient for an moment. */ + gtk_adjustment_set_value (adjustment, gtk_adjustment_get_upper (adjustment)); +} + +static gboolean +bind_current_view_menu_model_to_popover (NautilusPathBar *self) +{ + gtk_popover_menu_set_menu_model (self->current_view_menu_popover, + G_MENU_MODEL (self->current_view_menu)); + return G_SOURCE_REMOVE; +} + +static void +nautilus_path_bar_init (NautilusPathBar *self) +{ + GtkAdjustment *adjustment; + GtkBuilder *builder; + g_autoptr (GError) error = NULL; + + self->os_name = g_get_os_info (G_OS_INFO_KEY_NAME); + + self->scrolled = gtk_scrolled_window_new (); + /* Scroll horizontally only and don't use internal scrollbar. */ + gtk_scrolled_window_set_policy (GTK_SCROLLED_WINDOW (self->scrolled), + /* hscrollbar-policy */ GTK_POLICY_EXTERNAL, + /* vscrollbar-policy */ GTK_POLICY_NEVER); + gtk_widget_set_hexpand (self->scrolled, TRUE); + gtk_box_append (GTK_BOX (self), self->scrolled); + + adjustment = gtk_scrolled_window_get_hadjustment (GTK_SCROLLED_WINDOW (self->scrolled)); + g_signal_connect (adjustment, "changed", G_CALLBACK (on_adjustment_changed), self); + g_signal_connect (adjustment, "notify::page-size", G_CALLBACK (on_page_size_changed), self); + + self->buttons_box = gtk_box_new (GTK_ORIENTATION_HORIZONTAL, 0); + gtk_scrolled_window_set_child (GTK_SCROLLED_WINDOW (self->scrolled), self->buttons_box); + + self->current_view_menu_button = gtk_menu_button_new (); + gtk_widget_add_css_class (self->current_view_menu_button, "flat"); + gtk_menu_button_set_child (GTK_MENU_BUTTON (self->current_view_menu_button), + gtk_image_new_from_icon_name ("view-more-symbolic")); + gtk_box_append (GTK_BOX (self), self->current_view_menu_button); + + gtk_widget_set_tooltip_text (self->current_view_menu_button, _("Current Folder Menu")); + + builder = gtk_builder_new (); + + /* Add context menu for pathbar buttons */ + gtk_builder_add_from_resource (builder, + "/org/gnome/nautilus/ui/nautilus-pathbar-context-menu.ui", + &error); + if (error != NULL) + { + g_error ("Failed to add pathbar-context-menu.ui: %s", error->message); + } + self->button_menu = g_object_ref_sink (G_MENU (gtk_builder_get_object (builder, "button-menu"))); + self->button_menu_popover = gtk_popover_menu_new_from_model (G_MENU_MODEL (self->button_menu)); + gtk_widget_set_parent (self->button_menu_popover, GTK_WIDGET (self)); + gtk_popover_set_has_arrow (GTK_POPOVER (self->button_menu_popover), FALSE); + gtk_widget_set_halign (self->button_menu_popover, GTK_ALIGN_START); + + /* Add current location menu, which shares features with the view's background context menu */ + self->current_view_menu = g_object_ref_sink (G_MENU (gtk_builder_get_object (builder, "current-view-menu"))); + self->extensions_section = g_object_ref (G_MENU (gtk_builder_get_object (builder, "background-extensions-section"))); + self->templates_submenu = g_object_ref (G_MENU (gtk_builder_get_object (builder, "templates-submenu"))); + self->current_view_menu_popover = g_object_ref_sink (GTK_POPOVER_MENU (gtk_popover_menu_new_from_model (NULL))); + + g_object_unref (builder); + + gtk_menu_button_set_popover (GTK_MENU_BUTTON (self->current_view_menu_button), + GTK_WIDGET (self->current_view_menu_popover)); + bind_current_view_menu_model_to_popover (self); + + gtk_widget_set_name (GTK_WIDGET (self), "NautilusPathBar"); + gtk_widget_add_css_class (GTK_WIDGET (self), "linked"); + + /* Action group */ + self->action_group = G_ACTION_GROUP (g_simple_action_group_new ()); + g_action_map_add_action_entries (G_ACTION_MAP (self->action_group), + path_bar_actions, + G_N_ELEMENTS (path_bar_actions), + self); + gtk_widget_insert_action_group (GTK_WIDGET (self), + "pathbar", + G_ACTION_GROUP (self->action_group)); +} + +static void +nautilus_path_bar_finalize (GObject *object) +{ + NautilusPathBar *self; + + self = NAUTILUS_PATH_BAR (object); + + g_clear_object (&self->current_view_menu); + g_clear_object (&self->extensions_section); + g_clear_object (&self->templates_submenu); + g_clear_object (&self->button_menu); + g_clear_object (&self->current_view_menu_popover); + g_free (self->os_name); + + unschedule_pop_up_context_menu (NAUTILUS_PATH_BAR (object)); + + G_OBJECT_CLASS (nautilus_path_bar_parent_class)->finalize (object); +} + +static void +nautilus_path_bar_dispose (GObject *object) +{ + NautilusPathBar *self = NAUTILUS_PATH_BAR (object); + + nautilus_path_bar_clear_buttons (self); + + G_OBJECT_CLASS (nautilus_path_bar_parent_class)->dispose (object); +} + +static const char * +get_dir_name (ButtonData *button_data) +{ + switch (button_data->type) + { + case ROOT_BUTTON: + { + if (button_data->path_bar != NULL && + button_data->path_bar->os_name != NULL) + { + return button_data->path_bar->os_name; + } + /* Translators: This is the label used in the pathbar when seeing + * the root directory (also known as /) */ + return _("Operating System"); + } + + case ADMIN_ROOT_BUTTON: + { + /* Translators: This is the filesystem root directory (also known + * as /) when seen as administrator */ + return _("Administrator Root"); + } + + case HOME_BUTTON: + { + return _("Home"); + } + + case OTHER_LOCATIONS_BUTTON: + { + return _("Other Locations"); + } + + case STARRED_BUTTON: + { + return _("Starred"); + } + + default: + { + return button_data->dir_name; + } + } +} + +static void +button_data_free (ButtonData *button_data) +{ + g_object_unref (button_data->path); + g_free (button_data->dir_name); + if (button_data->file != NULL) + { + g_signal_handler_disconnect (button_data->file, + button_data->file_changed_signal_id); + nautilus_file_monitor_remove (button_data->file, button_data); + nautilus_file_unref (button_data->file); + } + + g_free (button_data); +} + +static void +nautilus_path_bar_class_init (NautilusPathBarClass *path_bar_class) +{ + GObjectClass *gobject_class; + + gobject_class = (GObjectClass *) path_bar_class; + + gobject_class->finalize = nautilus_path_bar_finalize; + gobject_class->dispose = nautilus_path_bar_dispose; + + path_bar_signals [OPEN_LOCATION] = + g_signal_new ("open-location", + G_OBJECT_CLASS_TYPE (path_bar_class), + G_SIGNAL_RUN_FIRST | G_SIGNAL_RUN_LAST, + 0, + NULL, NULL, NULL, + G_TYPE_NONE, 2, + G_TYPE_FILE, + NAUTILUS_TYPE_OPEN_FLAGS); +} + +void +nautilus_path_bar_set_extensions_background_menu (NautilusPathBar *self, + GMenuModel *menu) +{ + g_return_if_fail (NAUTILUS_IS_PATH_BAR (self)); + + nautilus_gmenu_set_from_model (self->extensions_section, menu); +} + +void +nautilus_path_bar_set_templates_menu (NautilusPathBar *self, + GMenuModel *menu) +{ + gint i; + + g_return_if_fail (NAUTILUS_IS_PATH_BAR (self)); + + if (!gtk_widget_is_visible (GTK_WIDGET (self->current_view_menu_popover))) + { + /* Workaround to avoid leaking duplicated GtkStack pages each time the + * templates menu is set. Unbinding the model is the only way to clear + * all children. After that's done, on idle, we rebind it. + * See https://gitlab.gnome.org/GNOME/nautilus/-/issues/1705 */ + gtk_popover_menu_set_menu_model (self->current_view_menu_popover, NULL); + } + + nautilus_gmenu_set_from_model (self->templates_submenu, menu); + g_idle_add ((GSourceFunc) bind_current_view_menu_model_to_popover, self); + + i = nautilus_g_menu_model_find_by_string (G_MENU_MODEL (self->current_view_menu), + "nautilus-menu-item", + "templates-submenu"); + nautilus_g_menu_replace_string_in_item (self->current_view_menu, i, + "hidden-when", + (menu == NULL) ? "action-missing" : NULL); +} + +/* Public functions and their helpers */ +static void +nautilus_path_bar_clear_buttons (NautilusPathBar *self) +{ + while (self->button_list != NULL) + { + ButtonData *button_data; + + button_data = BUTTON_DATA (self->button_list->data); + + gtk_box_remove (GTK_BOX (self->buttons_box), button_data->container); + + self->button_list = g_list_remove (self->button_list, button_data); + button_data_free (button_data); + } +} + +void +nautilus_path_bar_show_current_location_menu (NautilusPathBar *self) +{ + g_return_if_fail (NAUTILUS_IS_PATH_BAR (self)); + + gtk_menu_button_popup (GTK_MENU_BUTTON (self->current_view_menu_button)); +} + +static void +button_clicked_cb (GtkButton *button, + gpointer data) +{ + ButtonData *button_data; + NautilusPathBar *self; + + button_data = BUTTON_DATA (data); + if (button_data->ignore_changes) + { + return; + } + + self = button_data->path_bar; + + if (g_file_equal (button_data->path, self->current_path)) + { + return; + } + else + { + g_signal_emit (self, path_bar_signals[OPEN_LOCATION], 0, + button_data->path, + 0); + } +} + +static void +real_pop_up_pathbar_context_menu (NautilusPathBar *self) +{ + gtk_popover_popup (GTK_POPOVER (self->button_menu_popover)); +} + +static void +pathbar_popup_file_attributes_ready (NautilusFile *file, + gpointer data) +{ + NautilusPathBar *self; + + g_return_if_fail (NAUTILUS_IS_PATH_BAR (data)); + + self = NAUTILUS_PATH_BAR (data); + + g_return_if_fail (file == self->context_menu_file); + + real_pop_up_pathbar_context_menu (self); +} + +static void +unschedule_pop_up_context_menu (NautilusPathBar *self) +{ + if (self->context_menu_file != NULL) + { + g_return_if_fail (NAUTILUS_IS_FILE (self->context_menu_file)); + nautilus_file_cancel_call_when_ready (self->context_menu_file, + pathbar_popup_file_attributes_ready, + self); + g_clear_pointer (&self->context_menu_file, nautilus_file_unref); + } +} + +static void +schedule_pop_up_context_menu (NautilusPathBar *self, + NautilusFile *file) +{ + g_return_if_fail (NAUTILUS_IS_FILE (file)); + + if (file == self->context_menu_file) + { + if (nautilus_file_check_if_ready (file, + NAUTILUS_FILE_ATTRIBUTE_INFO | + NAUTILUS_FILE_ATTRIBUTE_MOUNT | + NAUTILUS_FILE_ATTRIBUTE_FILESYSTEM_INFO)) + { + real_pop_up_pathbar_context_menu (self); + } + } + else + { + unschedule_pop_up_context_menu (self); + + self->context_menu_file = nautilus_file_ref (file); + nautilus_file_call_when_ready (self->context_menu_file, + NAUTILUS_FILE_ATTRIBUTE_INFO | + NAUTILUS_FILE_ATTRIBUTE_MOUNT | + NAUTILUS_FILE_ATTRIBUTE_FILESYSTEM_INFO, + pathbar_popup_file_attributes_ready, + self); + } +} + +static void +pop_up_pathbar_context_menu (NautilusPathBar *self, + NautilusFile *file) +{ + if (file != NULL) + { + schedule_pop_up_context_menu (self, file); + } +} + + +static void +on_click_gesture_pressed (GtkGestureClick *gesture, + gint n_press, + gdouble x, + gdouble y, + gpointer user_data) +{ + ButtonData *button_data; + NautilusPathBar *self; + guint current_button; + GdkModifierType state; + double x_in_pathbar, y_in_pathbar; + + if (n_press != 1) + { + return; + } + + button_data = BUTTON_DATA (user_data); + self = button_data->path_bar; + current_button = gtk_gesture_single_get_current_button (GTK_GESTURE_SINGLE (gesture)); + state = gtk_event_controller_get_current_event_state (GTK_EVENT_CONTROLLER (gesture)); + + gtk_widget_translate_coordinates (GTK_WIDGET (button_data->button), + GTK_WIDGET (self), + x, y, + &x_in_pathbar, &y_in_pathbar); + + switch (current_button) + { + case GDK_BUTTON_MIDDLE: + { + if ((state & gtk_accelerator_get_default_mod_mask ()) == 0) + { + g_signal_emit (self, path_bar_signals[OPEN_LOCATION], 0, + button_data->path, + NAUTILUS_OPEN_FLAG_NEW_TAB | NAUTILUS_OPEN_FLAG_DONT_MAKE_ACTIVE); + } + } + break; + + case GDK_BUTTON_SECONDARY: + { + if (g_file_equal (button_data->path, self->current_path)) + { + break; + } + else + { + gtk_popover_set_pointing_to (GTK_POPOVER (self->button_menu_popover), + &(GdkRectangle){x_in_pathbar, y_in_pathbar, 0, 0}); + pop_up_pathbar_context_menu (self, button_data->file); + } + } + break; + + case GDK_BUTTON_PRIMARY: + { + if ((state & GDK_CONTROL_MASK) != 0) + { + g_signal_emit (button_data->path_bar, path_bar_signals[OPEN_LOCATION], 0, + button_data->path, + NAUTILUS_OPEN_FLAG_NEW_WINDOW); + } + else + { + /* GtkButton will claim the primary button presses and emit the + * "clicked" signal. Handle it in the singal callback, not here. + */ + return; + } + } + break; + + default: + { + /* Ignore other buttons in this gesture. */ + return; + } + break; + } + + /* Both middle- and secondary-clicking the title bar can have interesting + * effects (minimizing the window, popping up a window manager menu, etc.), + * and this avoids all that. + */ + gtk_gesture_set_state (GTK_GESTURE (gesture), GTK_EVENT_SEQUENCE_CLAIMED); +} + +static GIcon * +get_gicon_for_mount (ButtonData *button_data) +{ + GIcon *icon; + GMount *mount; + + icon = NULL; + mount = nautilus_get_mounted_mount_for_root (button_data->path); + + if (mount != NULL) + { + icon = g_mount_get_symbolic_icon (mount); + g_object_unref (mount); + } + + return icon; +} + +static GIcon * +get_gicon (ButtonData *button_data) +{ + switch (button_data->type) + { + case ROOT_BUTTON: + case ADMIN_ROOT_BUTTON: + { + return g_themed_icon_new (NAUTILUS_ICON_FILESYSTEM); + } + + case HOME_BUTTON: + { + return g_themed_icon_new (NAUTILUS_ICON_HOME); + } + + case MOUNT_BUTTON: + { + return get_gicon_for_mount (button_data); + } + + case STARRED_BUTTON: + { + return g_themed_icon_new ("starred-symbolic"); + } + + case RECENT_BUTTON: + { + return g_themed_icon_new ("document-open-recent-symbolic"); + } + + case OTHER_LOCATIONS_BUTTON: + { + return g_themed_icon_new ("list-add-symbolic"); + } + + case TRASH_BUTTON: + { + return nautilus_trash_monitor_get_symbolic_icon (); + } + + default: + { + return NULL; + } + } + + return NULL; +} + +static void +nautilus_path_bar_update_button_appearance (ButtonData *button_data, + gboolean current_dir) +{ + const gchar *dir_name = get_dir_name (button_data); + gint min_chars = NAUTILUS_PATH_BAR_BUTTON_ELLISPIZE_MINIMUM_CHARS; + GIcon *icon; + + if (button_data->label != NULL) + { + gtk_label_set_text (GTK_LABEL (button_data->label), dir_name); + + gtk_widget_set_tooltip_text (button_data->button, dir_name); + + if (current_dir) + { + /* We want to avoid ellipsizing the current directory name, but + * still need to set a limit. */ + min_chars = 4 * min_chars; + } + + /* Labels can ellipsize until they become a single ellipsis character. + * We don't want that, so we must set a minimum. + * + * However, for labels shorter than the minimum, setting this minimum + * width would make them unnecessarily wide. In that case, just make it + * not ellipsize instead. + * + * Due to variable width fonts, labels can be shorter than the space + * that would be reserved by setting a minimum amount of characters. + * Compensate for this with a tolerance of +50% characters. + */ + if (g_utf8_strlen (dir_name, -1) > min_chars * 1.5) + { + gtk_label_set_width_chars (GTK_LABEL (button_data->label), min_chars); + gtk_label_set_ellipsize (GTK_LABEL (button_data->label), PANGO_ELLIPSIZE_MIDDLE); + } + else + { + gtk_label_set_width_chars (GTK_LABEL (button_data->label), -1); + gtk_label_set_ellipsize (GTK_LABEL (button_data->label), PANGO_ELLIPSIZE_NONE); + } + } + + icon = get_gicon (button_data); + if (icon != NULL) + { + gtk_image_set_from_gicon (GTK_IMAGE (button_data->image), icon); + gtk_widget_show (GTK_WIDGET (button_data->image)); + g_object_unref (icon); + } + else + { + gtk_widget_hide (GTK_WIDGET (button_data->image)); + } +} + +static void +nautilus_path_bar_update_button_state (ButtonData *button_data, + gboolean current_dir) +{ + if (button_data->label != NULL) + { + gtk_label_set_label (GTK_LABEL (button_data->label), NULL); + } + + nautilus_path_bar_update_button_appearance (button_data, current_dir); +} + +static void +setup_button_type (ButtonData *button_data, + NautilusPathBar *self, + GFile *location) +{ + g_autoptr (GMount) mount = NULL; + g_autofree gchar *uri = NULL; + + if (nautilus_is_root_directory (location)) + { + button_data->type = ROOT_BUTTON; + } + else if (nautilus_is_home_directory (location)) + { + button_data->type = HOME_BUTTON; + button_data->is_root = TRUE; + } + else if (nautilus_is_recent_directory (location)) + { + button_data->type = RECENT_BUTTON; + button_data->is_root = TRUE; + } + else if (nautilus_is_starred_directory (location)) + { + button_data->type = STARRED_BUTTON; + button_data->is_root = TRUE; + } + else if ((mount = nautilus_get_mounted_mount_for_root (location)) != NULL) + { + button_data->dir_name = g_mount_get_name (mount); + button_data->type = MOUNT_BUTTON; + button_data->is_root = TRUE; + } + else if (nautilus_is_other_locations_directory (location)) + { + button_data->type = OTHER_LOCATIONS_BUTTON; + button_data->is_root = TRUE; + } + else if (strcmp ((uri = g_file_get_uri (location)), "admin:///") == 0) + { + button_data->type = ADMIN_ROOT_BUTTON; + button_data->is_root = TRUE; + } + else if (strcmp (uri, "trash:///") == 0) + { + button_data->type = TRASH_BUTTON; + button_data->is_root = TRUE; + } + else + { + button_data->type = NORMAL_BUTTON; + } +} + +static void +button_data_file_changed (NautilusFile *file, + ButtonData *button_data) +{ + GtkWidget *ancestor; + GFile *location; + GFile *current_location; + GFile *parent; + GFile *button_parent; + ButtonData *current_button_data; + char *display_name; + NautilusPathBar *self; + gboolean renamed; + gboolean child; + gboolean current_dir; + + ancestor = gtk_widget_get_ancestor (button_data->button, NAUTILUS_TYPE_PATH_BAR); + if (ancestor == NULL) + { + return; + } + self = NAUTILUS_PATH_BAR (ancestor); + + g_return_if_fail (self->current_path != NULL); + g_return_if_fail (self->current_button_data != NULL); + + current_button_data = self->current_button_data; + + location = nautilus_file_get_location (file); + if (!g_file_equal (button_data->path, location)) + { + parent = g_file_get_parent (location); + button_parent = g_file_get_parent (button_data->path); + + renamed = (parent != NULL && button_parent != NULL) && + g_file_equal (parent, button_parent); + + if (parent != NULL) + { + g_object_unref (parent); + } + if (button_parent != NULL) + { + g_object_unref (button_parent); + } + + if (renamed) + { + button_data->path = g_object_ref (location); + } + else + { + /* the file has been moved. + * If it was below the currently displayed location, remove it. + * If it was not below the currently displayed location, update the path bar + */ + child = g_file_has_prefix (button_data->path, + self->current_path); + + if (child) + { + /* moved file inside current path hierarchy */ + g_object_unref (location); + location = g_file_get_parent (button_data->path); + current_location = g_object_ref (self->current_path); + } + else + { + /* moved current path, or file outside current path hierarchy. + * Update path bar to new locations. + */ + current_location = nautilus_file_get_location (current_button_data->file); + } + + nautilus_path_bar_update_path (self, location); + nautilus_path_bar_set_path (self, current_location); + g_object_unref (location); + g_object_unref (current_location); + return; + } + } + else if (nautilus_file_is_gone (file)) + { + gint idx, position; + + /* if the current or a parent location are gone, clear all the buttons, + * the view will set the new path. + */ + current_location = nautilus_file_get_location (current_button_data->file); + + if (g_file_has_prefix (current_location, location) || + g_file_equal (current_location, location)) + { + nautilus_path_bar_clear_buttons (self); + } + else if (g_file_has_prefix (location, current_location)) + { + /* remove this and the following buttons */ + position = g_list_position (self->button_list, + g_list_find (self->button_list, button_data)); + + if (position != -1) + { + for (idx = 0; idx <= position; idx++) + { + ButtonData *data; + + data = BUTTON_DATA (self->button_list->data); + + gtk_box_remove (GTK_BOX (self->buttons_box), data->container); + self->button_list = g_list_remove (self->button_list, data); + button_data_free (data); + } + } + } + + g_object_unref (current_location); + g_object_unref (location); + return; + } + g_object_unref (location); + + /* MOUNTs use the GMount as the name, so don't update for those */ + if (button_data->type != MOUNT_BUTTON) + { + display_name = nautilus_file_get_display_name (file); + if (g_strcmp0 (display_name, button_data->dir_name) != 0) + { + g_free (button_data->dir_name); + button_data->dir_name = g_strdup (display_name); + } + + g_free (display_name); + } + current_dir = g_file_equal (self->current_path, button_data->path); + nautilus_path_bar_update_button_appearance (button_data, current_dir); +} + +static ButtonData * +make_button_data (NautilusPathBar *self, + NautilusFile *file, + gboolean current_dir) +{ + GFile *path; + GtkWidget *child = NULL; + GtkEventController *controller; + ButtonData *button_data; + + path = nautilus_file_get_location (file); + + /* Is it a special button? */ + button_data = g_new0 (ButtonData, 1); + + setup_button_type (button_data, self, path); + button_data->button = gtk_button_new (); + gtk_widget_set_focus_on_click (button_data->button, FALSE); + gtk_widget_set_name (button_data->button, "NautilusPathButton"); + + /* TODO update button type when xdg directories change */ + + button_data->image = gtk_image_new (); + + switch (button_data->type) + { + case ROOT_BUTTON: + case ADMIN_ROOT_BUTTON: + case HOME_BUTTON: + case MOUNT_BUTTON: + case TRASH_BUTTON: + case RECENT_BUTTON: + case STARRED_BUTTON: + case OTHER_LOCATIONS_BUTTON: + { + button_data->label = gtk_label_new (NULL); + child = gtk_box_new (GTK_ORIENTATION_HORIZONTAL, 6); + button_data->container = gtk_box_new (GTK_ORIENTATION_HORIZONTAL, 6); + gtk_box_append (GTK_BOX (button_data->container), button_data->button); + + gtk_box_append (GTK_BOX (child), button_data->image); + gtk_box_append (GTK_BOX (child), button_data->label); + } + break; + + case NORMAL_BUTTON: + /* Fall through */ + default: + { + GtkWidget *separator_label; + + separator_label = gtk_label_new (G_DIR_SEPARATOR_S); + gtk_style_context_add_class (gtk_widget_get_style_context (separator_label), "dim-label"); + button_data->label = gtk_label_new (NULL); + child = gtk_box_new (GTK_ORIENTATION_HORIZONTAL, 2); + button_data->container = gtk_box_new (GTK_ORIENTATION_HORIZONTAL, 0); + gtk_box_append (GTK_BOX (button_data->container), separator_label); + gtk_box_append (GTK_BOX (button_data->container), button_data->button); + + gtk_box_append (GTK_BOX (child), button_data->label); + } + break; + } + + if (current_dir) + { + gtk_style_context_add_class (gtk_widget_get_style_context (button_data->button), + "current-dir"); + gtk_widget_set_hexpand (button_data->button, TRUE); + gtk_widget_set_halign (button_data->label, GTK_ALIGN_START); + } + + if (button_data->label != NULL) + { + PangoAttrList *attrs; + + gtk_label_set_single_line_mode (GTK_LABEL (button_data->label), TRUE); + + attrs = pango_attr_list_new (); + pango_attr_list_insert (attrs, pango_attr_weight_new (PANGO_WEIGHT_BOLD)); + gtk_label_set_attributes (GTK_LABEL (button_data->label), attrs); + pango_attr_list_unref (attrs); + + if (!current_dir) + { + gtk_style_context_add_class (gtk_widget_get_style_context (button_data->label), "dim-label"); + gtk_style_context_add_class (gtk_widget_get_style_context (button_data->image), "dim-label"); + } + } + + if (button_data->path == NULL) + { + button_data->path = g_object_ref (path); + } + if (button_data->dir_name == NULL) + { + button_data->dir_name = nautilus_file_get_display_name (file); + } + if (button_data->file == NULL) + { + button_data->file = nautilus_file_ref (file); + nautilus_file_monitor_add (button_data->file, button_data, + NAUTILUS_FILE_ATTRIBUTES_FOR_ICON); + button_data->file_changed_signal_id = + g_signal_connect (button_data->file, "changed", + G_CALLBACK (button_data_file_changed), + button_data); + } + + gtk_button_set_child (GTK_BUTTON (button_data->button), child); + gtk_widget_show (button_data->container); + + button_data->path_bar = self; + + nautilus_path_bar_update_button_state (button_data, current_dir); + + g_signal_connect (button_data->button, "clicked", G_CALLBACK (button_clicked_cb), button_data); + + /* A gesture is needed here, because GtkButton doesn’t react to middle- or + * secondary-clicking. + */ + controller = GTK_EVENT_CONTROLLER (gtk_gesture_click_new ()); + gtk_widget_add_controller (button_data->button, controller); + gtk_gesture_single_set_button (GTK_GESTURE_SINGLE (controller), 0); + g_signal_connect (controller, "pressed", + G_CALLBACK (on_click_gesture_pressed), button_data); + + nautilus_drag_slot_proxy_init (button_data->button, button_data->file, NULL); + + g_object_unref (path); + + return button_data; +} + +static void +nautilus_path_bar_update_path (NautilusPathBar *self, + GFile *file_path) +{ + NautilusFile *file; + gboolean first_directory; + GList *new_buttons, *l; + ButtonData *button_data; + + g_return_if_fail (NAUTILUS_IS_PATH_BAR (self)); + g_return_if_fail (file_path != NULL); + + first_directory = TRUE; + new_buttons = NULL; + + file = nautilus_file_get (file_path); + + while (file != NULL) + { + NautilusFile *parent_file; + + parent_file = nautilus_file_get_parent (file); + button_data = make_button_data (self, file, first_directory); + nautilus_file_unref (file); + + if (first_directory) + { + first_directory = FALSE; + } + + new_buttons = g_list_prepend (new_buttons, button_data); + + if (parent_file != NULL && + button_data->is_root) + { + nautilus_file_unref (parent_file); + break; + } + + file = parent_file; + } + + nautilus_path_bar_clear_buttons (self); + + /* Buttons are listed in reverse order such that the current location is + * always the first link. */ + self->button_list = g_list_reverse (new_buttons); + + for (l = self->button_list; l; l = l->next) + { + GtkWidget *container; + container = BUTTON_DATA (l->data)->container; + gtk_box_prepend (GTK_BOX (self->buttons_box), container); + } +} + +void +nautilus_path_bar_set_path (NautilusPathBar *self, + GFile *file_path) +{ + ButtonData *button_data; + + g_return_if_fail (NAUTILUS_IS_PATH_BAR (self)); + g_return_if_fail (file_path != NULL); + + nautilus_path_bar_update_path (self, file_path); + button_data = g_list_nth_data (self->button_list, 0); + + if (self->current_path != NULL) + { + g_object_unref (self->current_path); + } + + self->current_path = g_object_ref (file_path); + self->current_button_data = button_data; +} diff --git a/src/nautilus-pathbar.h b/src/nautilus-pathbar.h new file mode 100644 index 0000000..1052e4d --- /dev/null +++ b/src/nautilus-pathbar.h @@ -0,0 +1,34 @@ +/* nautilus-pathbar.h + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Library General Public + * License as published by the Free Software Foundation; either + * version 2 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Library General Public License for more details. + * + * You should have received a copy of the GNU Library General Public + * License along with this library; if not, see . + * + * + */ + +#pragma once + +#include +#include + +#define NAUTILUS_TYPE_PATH_BAR (nautilus_path_bar_get_type ()) +G_DECLARE_FINAL_TYPE (NautilusPathBar, nautilus_path_bar, NAUTILUS, PATH_BAR, GtkBox) + +void nautilus_path_bar_set_path (NautilusPathBar *path_bar, + GFile *file); + +void nautilus_path_bar_set_extensions_background_menu (NautilusPathBar *path_bar, + GMenuModel *menu); +void nautilus_path_bar_set_templates_menu (NautilusPathBar *path_bar, + GMenuModel *menu); +void nautilus_path_bar_show_current_location_menu (NautilusPathBar *path_bar); diff --git a/src/nautilus-places-view.c b/src/nautilus-places-view.c new file mode 100644 index 0000000..e9e7785 --- /dev/null +++ b/src/nautilus-places-view.c @@ -0,0 +1,413 @@ +/* nautilus-places-view.c + * + * Copyright (C) 2015 Georges Basile Stavracas Neto + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License 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 warranty of + * MERCHANTABILITY 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 . + */ + +#include + +#include "nautilus-mime-actions.h" +#include "nautilus-places-view.h" + +#include "gtk/nautilusgtkplacesviewprivate.h" + +#include "nautilus-application.h" +#include "nautilus-file.h" +#include "nautilus-toolbar-menu-sections.h" +#include "nautilus-view.h" +#include "nautilus-window-slot.h" + +typedef struct +{ + GFile *location; + NautilusQuery *search_query; + + GtkWidget *places_view; +} NautilusPlacesViewPrivate; + +struct _NautilusPlacesView +{ + GtkFrameClass parent; +}; + +static void nautilus_places_view_iface_init (NautilusViewInterface *iface); + +G_DEFINE_TYPE_WITH_CODE (NautilusPlacesView, nautilus_places_view, GTK_TYPE_BOX, + G_ADD_PRIVATE (NautilusPlacesView) + G_IMPLEMENT_INTERFACE (NAUTILUS_TYPE_VIEW, nautilus_places_view_iface_init)); + +enum +{ + PROP_0, + PROP_LOCATION, + PROP_SEARCH_QUERY, + PROP_LOADING, + PROP_SEARCHING, + PROP_SELECTION, + PROP_EXTENSIONS_BACKGROUND_MENU, + PROP_TEMPLATES_MENU, + LAST_PROP +}; + +static void +open_location_cb (NautilusPlacesView *view, + GFile *location, + NautilusGtkPlacesOpenFlags open_flags) +{ + NautilusOpenFlags flags; + GtkWidget *slot; + + slot = gtk_widget_get_ancestor (GTK_WIDGET (view), NAUTILUS_TYPE_WINDOW_SLOT); + + switch (open_flags) + { + case NAUTILUS_GTK_PLACES_OPEN_NEW_TAB: + { + flags = NAUTILUS_OPEN_FLAG_NEW_TAB | + NAUTILUS_OPEN_FLAG_DONT_MAKE_ACTIVE; + } + break; + + case NAUTILUS_GTK_PLACES_OPEN_NEW_WINDOW: + { + flags = NAUTILUS_OPEN_FLAG_NEW_WINDOW; + } + break; + + case NAUTILUS_GTK_PLACES_OPEN_NORMAL: /* fall-through */ + default: + { + flags = 0; + } + break; + } + + if (slot) + { + NautilusFile *file; + GtkRoot *window; + char *path; + + path = "other-locations:///"; + file = nautilus_file_get (location); + window = gtk_widget_get_root (GTK_WIDGET (view)); + + nautilus_mime_activate_file (GTK_WINDOW (window), + NAUTILUS_WINDOW_SLOT (slot), + file, + path, + flags); + nautilus_file_unref (file); + } +} + +static void +loading_cb (NautilusView *view) +{ + g_object_notify (G_OBJECT (view), "loading"); +} + +static void +show_error_message_cb (NautilusGtkPlacesView *view, + const gchar *primary, + const gchar *secondary) +{ + GtkWidget *dialog; + GtkRoot *window; + + window = gtk_widget_get_root (GTK_WIDGET (view)); + + dialog = adw_message_dialog_new (GTK_WINDOW (window), primary, secondary); + adw_message_dialog_add_response (ADW_MESSAGE_DIALOG (dialog), + "close", _("_Close")); + + gtk_window_present (GTK_WINDOW (dialog)); +} + +static void +nautilus_places_view_finalize (GObject *object) +{ + NautilusPlacesView *self = (NautilusPlacesView *) object; + NautilusPlacesViewPrivate *priv = nautilus_places_view_get_instance_private (self); + + g_clear_object (&priv->location); + g_clear_object (&priv->search_query); + + G_OBJECT_CLASS (nautilus_places_view_parent_class)->finalize (object); +} + +static void +nautilus_places_view_get_property (GObject *object, + guint prop_id, + GValue *value, + GParamSpec *pspec) +{ + NautilusView *view = NAUTILUS_VIEW (object); + + switch (prop_id) + { + case PROP_LOCATION: + { + g_value_set_object (value, nautilus_view_get_location (view)); + } + break; + + case PROP_SEARCH_QUERY: + { + g_value_set_object (value, nautilus_view_get_search_query (view)); + } + break; + + /* Collect all unused properties and do nothing. Ideally, this wouldn’t + * have to be done in the first place. + */ + case PROP_SEARCHING: + case PROP_SELECTION: + case PROP_EXTENSIONS_BACKGROUND_MENU: + case PROP_TEMPLATES_MENU: + { + } + break; + + default: + { + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); + } + } +} + +static void +nautilus_places_view_set_property (GObject *object, + guint prop_id, + const GValue *value, + GParamSpec *pspec) +{ + NautilusView *view = NAUTILUS_VIEW (object); + + switch (prop_id) + { + case PROP_LOCATION: + { + nautilus_view_set_location (view, g_value_get_object (value)); + } + break; + + case PROP_SEARCH_QUERY: + { + nautilus_view_set_search_query (view, g_value_get_object (value)); + } + break; + + default: + { + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); + } + } +} + +static GFile * +nautilus_places_view_get_location (NautilusView *view) +{ + NautilusPlacesViewPrivate *priv; + + priv = nautilus_places_view_get_instance_private (NAUTILUS_PLACES_VIEW (view)); + + return priv->location; +} + +static void +nautilus_places_view_set_location (NautilusView *view, + GFile *location) +{ + if (location) + { + NautilusPlacesViewPrivate *priv; + gchar *uri; + + priv = nautilus_places_view_get_instance_private (NAUTILUS_PLACES_VIEW (view)); + uri = g_file_get_uri (location); + + /* + * If it's not trying to open the places view itself, simply + * delegates the location to application, which takes care of + * selecting the appropriate view. + */ + if (g_strcmp0 (uri, "other-locations:///") != 0) + { + nautilus_application_open_location_full (NAUTILUS_APPLICATION (g_application_get_default ()), + location, 0, NULL, NULL, NULL); + } + else + { + g_set_object (&priv->location, location); + } + + g_free (uri); + } +} + +static GList * +nautilus_places_view_get_selection (NautilusView *view) +{ + /* STUB */ + return NULL; +} + +static void +nautilus_places_view_set_selection (NautilusView *view, + GList *selection) +{ + /* STUB */ +} + +static NautilusQuery * +nautilus_places_view_get_search_query (NautilusView *view) +{ + NautilusPlacesViewPrivate *priv; + + priv = nautilus_places_view_get_instance_private (NAUTILUS_PLACES_VIEW (view)); + + return priv->search_query; +} + +static void +nautilus_places_view_set_search_query (NautilusView *view, + NautilusQuery *query) +{ + NautilusPlacesViewPrivate *priv; + gchar *text; + + priv = nautilus_places_view_get_instance_private (NAUTILUS_PLACES_VIEW (view)); + + g_set_object (&priv->search_query, query); + + text = query ? nautilus_query_get_text (query) : NULL; + + nautilus_gtk_places_view_set_search_query (NAUTILUS_GTK_PLACES_VIEW (priv->places_view), text); + + g_free (text); +} + +static NautilusToolbarMenuSections * +nautilus_places_view_get_toolbar_menu_sections (NautilusView *view) +{ + return NULL; +} + +static gboolean +nautilus_places_view_is_loading (NautilusView *view) +{ + NautilusPlacesViewPrivate *priv; + + priv = nautilus_places_view_get_instance_private (NAUTILUS_PLACES_VIEW (view)); + + return nautilus_gtk_places_view_get_loading (NAUTILUS_GTK_PLACES_VIEW (priv->places_view)); +} + +static gboolean +nautilus_places_view_is_searching (NautilusView *view) +{ + NautilusPlacesViewPrivate *priv; + + priv = nautilus_places_view_get_instance_private (NAUTILUS_PLACES_VIEW (view)); + + return priv->search_query != NULL; +} + +static guint +nautilus_places_view_get_view_id (NautilusView *view) +{ + return NAUTILUS_VIEW_OTHER_LOCATIONS_ID; +} + +static void +nautilus_places_view_iface_init (NautilusViewInterface *iface) +{ + iface->get_location = nautilus_places_view_get_location; + iface->set_location = nautilus_places_view_set_location; + iface->get_selection = nautilus_places_view_get_selection; + iface->set_selection = nautilus_places_view_set_selection; + iface->get_search_query = nautilus_places_view_get_search_query; + iface->set_search_query = nautilus_places_view_set_search_query; + iface->get_toolbar_menu_sections = nautilus_places_view_get_toolbar_menu_sections; + iface->is_loading = nautilus_places_view_is_loading; + iface->is_searching = nautilus_places_view_is_searching; + iface->get_view_id = nautilus_places_view_get_view_id; +} + +static void +nautilus_places_view_class_init (NautilusPlacesViewClass *klass) +{ + GObjectClass *object_class = G_OBJECT_CLASS (klass); + + object_class->finalize = nautilus_places_view_finalize; + object_class->get_property = nautilus_places_view_get_property; + object_class->set_property = nautilus_places_view_set_property; + + g_object_class_override_property (object_class, PROP_LOADING, "loading"); + g_object_class_override_property (object_class, PROP_SEARCHING, "searching"); + g_object_class_override_property (object_class, PROP_LOCATION, "location"); + g_object_class_override_property (object_class, PROP_SELECTION, "selection"); + g_object_class_override_property (object_class, PROP_SEARCH_QUERY, "search-query"); + g_object_class_override_property (object_class, + PROP_EXTENSIONS_BACKGROUND_MENU, + "extensions-background-menu"); + g_object_class_override_property (object_class, + PROP_TEMPLATES_MENU, + "templates-menu"); +} + +static void +nautilus_places_view_init (NautilusPlacesView *self) +{ + NautilusPlacesViewPrivate *priv; + + priv = nautilus_places_view_get_instance_private (self); + + /* Location */ + priv->location = g_file_new_for_uri ("other-locations:///"); + + /* Places view */ + priv->places_view = nautilus_gtk_places_view_new (); + nautilus_gtk_places_view_set_open_flags (NAUTILUS_GTK_PLACES_VIEW (priv->places_view), + NAUTILUS_OPEN_FLAG_NEW_TAB | NAUTILUS_OPEN_FLAG_NEW_WINDOW | NAUTILUS_OPEN_FLAG_NORMAL); + gtk_widget_set_hexpand (priv->places_view, TRUE); + gtk_widget_set_vexpand (priv->places_view, TRUE); + gtk_widget_show (priv->places_view); + gtk_box_append (GTK_BOX (self), priv->places_view); + + g_signal_connect_object (priv->places_view, "notify::loading", + G_CALLBACK (loading_cb), self, G_CONNECT_SWAPPED); + + g_signal_connect_object (priv->places_view, "open-location", + G_CALLBACK (open_location_cb), self, G_CONNECT_SWAPPED); + + g_signal_connect_object (priv->places_view, "show-error-message", + G_CALLBACK (show_error_message_cb), self, G_CONNECT_SWAPPED); +} + +NautilusPlacesView * +nautilus_places_view_new (void) +{ + NautilusPlacesView *view; + + view = g_object_new (NAUTILUS_TYPE_PLACES_VIEW, NULL); + if (g_object_is_floating (view)) + { + g_object_ref_sink (view); + } + + return view; +} diff --git a/src/nautilus-places-view.h b/src/nautilus-places-view.h new file mode 100644 index 0000000..e961d39 --- /dev/null +++ b/src/nautilus-places-view.h @@ -0,0 +1,32 @@ +/* nautilus-places-view.h + * + * Copyright (C) 2015 Georges Basile Stavracas Neto + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License 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 warranty of + * MERCHANTABILITY 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 . + */ + +#pragma once + +#include +#include + +G_BEGIN_DECLS + +#define NAUTILUS_TYPE_PLACES_VIEW (nautilus_places_view_get_type()) + +G_DECLARE_FINAL_TYPE (NautilusPlacesView, nautilus_places_view, NAUTILUS, PLACES_VIEW, GtkBox) + +NautilusPlacesView* nautilus_places_view_new (void); + +G_END_DECLS diff --git a/src/nautilus-preferences-window.c b/src/nautilus-preferences-window.c new file mode 100644 index 0000000..e8d6663 --- /dev/null +++ b/src/nautilus-preferences-window.c @@ -0,0 +1,411 @@ +/* nautilus-preferences-window.c - Functions to create and show the nautilus + * preference window. + * + * Copyright (C) 2002 Jan Arne Petersen + * Copyright (C) 2016 Carlos Soriano + * + * The Gnome Library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Library General Public License as + * published by the Free Software Foundation; either version 2 of the + * License, or (at your option) any later version. + * + * The Gnome Library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Library General Public License for more details. + * + * You should have received a copy of the GNU Library General Public + * License along with the Gnome Library; see the file COPYING.LIB. If not, + * see . + * + * Authors: Jan Arne Petersen + */ + +#include + +#include "nautilus-preferences-window.h" + +#include +#include +#include + +#include + +#include + +#include "nautilus-column-utilities.h" +#include "nautilus-global-preferences.h" + +/* bool preferences */ +#define NAUTILUS_PREFERENCES_DIALOG_FOLDERS_FIRST_WIDGET \ + "sort_folders_first_switch" +#define NAUTILUS_PREFERENCES_DIALOG_DELETE_PERMANENTLY_WIDGET \ + "show_delete_permanently_switch" +#define NAUTILUS_PREFERENCES_DIALOG_CREATE_LINK_WIDGET \ + "show_create_link_switch" +#define NAUTILUS_PREFERENCES_DIALOG_LIST_VIEW_USE_TREE_WIDGET \ + "use_tree_view_switch" + +/* combo preferences */ +#define NAUTILUS_PREFERENCES_DIALOG_OPEN_ACTION_COMBO \ + "open_action_row" +#define NAUTILUS_PREFERENCES_DIALOG_SEARCH_RECURSIVE_ROW \ + "search_recursive_row" +#define NAUTILUS_PREFERENCES_DIALOG_THUMBNAILS_ROW \ + "thumbnails_row" +#define NAUTILUS_PREFERENCES_DIALOG_COUNT_ROW \ + "count_row" + +static const char * const speed_tradeoff_values[] = +{ + "local-only", "always", "never", + NULL +}; + +static const char * const click_behavior_values[] = {"single", "double", NULL}; + +static const char * const icon_captions_components[] = +{ + "captions_0_comborow", "captions_1_comborow", "captions_2_comborow", NULL +}; + +static GtkWidget *preferences_window = NULL; + +static void list_store_append_string (GListStore *list_store, + const gchar *string) +{ + g_autoptr (GtkStringObject) obj = gtk_string_object_new (string); + g_list_store_append (list_store, obj); +} + +static void free_column_names_array(GPtrArray *column_names) +{ + g_ptr_array_foreach (column_names, (GFunc) g_free, NULL); + g_ptr_array_free (column_names, TRUE); +} + +static void create_icon_caption_combo_row_items(AdwComboRow *combo_row, + GList *columns) +{ + GListStore *list_store = g_list_store_new (GTK_TYPE_STRING_OBJECT); + GList *l; + GPtrArray *column_names; + + column_names = g_ptr_array_new (); + + /* Translators: this is referred to captions under icons. */ + list_store_append_string (list_store, _("None")); + g_ptr_array_add (column_names, g_strdup ("none")); + + for (l = columns; l != NULL; l = l->next) + { + NautilusColumn *column; + char *name; + char *label; + + column = NAUTILUS_COLUMN (l->data); + + g_object_get (G_OBJECT (column), "name", &name, "label", &label, NULL); + + /* Don't show name here, it doesn't make sense */ + if (!strcmp (name, "name")) + { + g_free (name); + g_free (label); + continue; + } + + list_store_append_string (list_store, label); + g_ptr_array_add (column_names, name); + + g_free (label); + } + adw_combo_row_set_model (combo_row, G_LIST_MODEL (list_store)); + g_object_set_data_full (G_OBJECT (combo_row), "column_names", column_names, + (GDestroyNotify) free_column_names_array); +} + +static void icon_captions_changed_callback(AdwComboRow *widget, + GParamSpec *pspec, + gpointer user_data) +{ + GPtrArray *captions; + GtkBuilder *builder; + int i; + + builder = GTK_BUILDER (user_data); + + captions = g_ptr_array_new (); + + for (i = 0; icon_captions_components[i] != NULL; i++) + { + GtkWidget *combo_row; + int selected_index; + GPtrArray *column_names; + char *name; + + combo_row = GTK_WIDGET ( + gtk_builder_get_object (builder, icon_captions_components[i])); + selected_index = adw_combo_row_get_selected (ADW_COMBO_ROW (combo_row)); + + column_names = g_object_get_data (G_OBJECT (combo_row), "column_names"); + + name = g_ptr_array_index (column_names, selected_index); + g_ptr_array_add (captions, name); + } + g_ptr_array_add (captions, NULL); + + g_settings_set_strv (nautilus_icon_view_preferences, + NAUTILUS_PREFERENCES_ICON_VIEW_CAPTIONS, + (const char **) captions->pdata); + g_ptr_array_free (captions, TRUE); +} + +static void update_caption_combo_row(GtkBuilder *builder, + const char *combo_row_name, + const char *name) +{ + GtkWidget *combo_row; + int i; + GPtrArray *column_names; + + combo_row = GTK_WIDGET (gtk_builder_get_object (builder, combo_row_name)); + + g_signal_handlers_block_by_func ( + combo_row, G_CALLBACK (icon_captions_changed_callback), builder); + + column_names = g_object_get_data (G_OBJECT (combo_row), "column_names"); + + for (i = 0; i < column_names->len; ++i) + { + if (!strcmp (name, g_ptr_array_index (column_names, i))) + { + adw_combo_row_set_selected (ADW_COMBO_ROW (combo_row), i); + break; + } + } + + g_signal_handlers_unblock_by_func ( + combo_row, G_CALLBACK (icon_captions_changed_callback), builder); +} + +static void update_icon_captions_from_settings(GtkBuilder *builder) +{ + char **captions; + int i, j; + + captions = g_settings_get_strv (nautilus_icon_view_preferences, + NAUTILUS_PREFERENCES_ICON_VIEW_CAPTIONS); + if (captions == NULL) + { + return; + } + + for (i = 0, j = 0; icon_captions_components[i] != NULL; i++) + { + char *data; + + if (captions[j]) + { + data = captions[j]; + ++j; + } + else + { + data = "none"; + } + + update_caption_combo_row (builder, icon_captions_components[i], data); + } + + g_strfreev (captions); +} + +static void +nautilus_preferences_window_setup_icon_caption_page (GtkBuilder *builder) +{ + GList *columns; + int i; + gboolean writable; + + writable = g_settings_is_writable (nautilus_icon_view_preferences, + NAUTILUS_PREFERENCES_ICON_VIEW_CAPTIONS); + + columns = nautilus_get_common_columns (); + + for (i = 0; icon_captions_components[i] != NULL; i++) + { + GtkWidget *combo_row; + + combo_row = GTK_WIDGET ( + gtk_builder_get_object (builder, icon_captions_components[i])); + + create_icon_caption_combo_row_items (ADW_COMBO_ROW (combo_row), columns); + gtk_widget_set_sensitive (combo_row, writable); + + g_signal_connect_data ( + combo_row, "notify::selected", G_CALLBACK (icon_captions_changed_callback), + g_object_ref (builder), (GClosureNotify) g_object_unref, 0); + } + + nautilus_column_list_free (columns); + + update_icon_captions_from_settings (builder); +} + +static void bind_builder_bool(GtkBuilder *builder, + GSettings *settings, + const char *widget_name, + const char *prefs) +{ + g_settings_bind (settings, prefs, gtk_builder_get_object (builder, widget_name), + "active", G_SETTINGS_BIND_DEFAULT); +} + +static GVariant *combo_row_mapping_set(const GValue *gvalue, + const GVariantType *expected_type, + gpointer user_data) +{ + const gchar **values = user_data; + + return g_variant_new_string (values[g_value_get_uint (gvalue)]); +} + +static gboolean combo_row_mapping_get(GValue *gvalue, + GVariant *variant, + gpointer user_data) +{ + const gchar **values = user_data; + const gchar *value; + + value = g_variant_get_string (variant, NULL); + + for (int i = 0; values[i]; i++) + { + if (g_strcmp0 (value, values[i]) == 0) + { + g_value_set_uint (gvalue, i); + + return TRUE; + } + } + + return FALSE; +} + +static void bind_builder_combo_row(GtkBuilder *builder, + GSettings *settings, + const char *widget_name, + const char *prefs, + const char **values) +{ + g_settings_bind_with_mapping (settings, prefs, gtk_builder_get_object (builder, widget_name), + "selected", G_SETTINGS_BIND_DEFAULT, + combo_row_mapping_get, combo_row_mapping_set, + (gpointer) values, NULL); +} + +static void setup_combo (GtkBuilder *builder, + const char *widget_name, + const char **strings) +{ + AdwComboRow *combo_row; + GListStore *list_store; + + combo_row = (AdwComboRow *) gtk_builder_get_object (builder, widget_name); + g_assert (ADW_IS_COMBO_ROW (combo_row)); + + list_store = g_list_store_new (GTK_TYPE_STRING_OBJECT); + + for (gsize i = 0; strings[i]; i++) + { + list_store_append_string (list_store, strings[i]); + } + + adw_combo_row_set_model (combo_row, G_LIST_MODEL (list_store)); +} + +static void nautilus_preferences_window_setup(GtkBuilder *builder, + GtkWindow *parent_window) +{ + GtkWidget *window; + + setup_combo (builder, NAUTILUS_PREFERENCES_DIALOG_OPEN_ACTION_COMBO, + (const char *[]) { _("Single click"), _("Double click"), NULL }); + setup_combo (builder, NAUTILUS_PREFERENCES_DIALOG_SEARCH_RECURSIVE_ROW, + (const char *[]) { _("On this computer only"), _("All locations"), _("Never"), NULL }); + setup_combo (builder, NAUTILUS_PREFERENCES_DIALOG_THUMBNAILS_ROW, + (const char *[]) { _("On this computer only"), _("All files"), _("Never"), NULL }); + setup_combo (builder, NAUTILUS_PREFERENCES_DIALOG_COUNT_ROW, + (const char *[]) { _("On this computer only"), _("All folders"), _("Never"), NULL }); + + /* setup preferences */ + bind_builder_bool (builder, gtk_filechooser_preferences, + NAUTILUS_PREFERENCES_DIALOG_FOLDERS_FIRST_WIDGET, + NAUTILUS_PREFERENCES_SORT_DIRECTORIES_FIRST); + bind_builder_bool (builder, nautilus_list_view_preferences, + NAUTILUS_PREFERENCES_DIALOG_LIST_VIEW_USE_TREE_WIDGET, + NAUTILUS_PREFERENCES_LIST_VIEW_USE_TREE); + bind_builder_bool (builder, nautilus_preferences, + NAUTILUS_PREFERENCES_DIALOG_CREATE_LINK_WIDGET, + NAUTILUS_PREFERENCES_SHOW_CREATE_LINK); + bind_builder_bool (builder, nautilus_preferences, + NAUTILUS_PREFERENCES_DIALOG_DELETE_PERMANENTLY_WIDGET, + NAUTILUS_PREFERENCES_SHOW_DELETE_PERMANENTLY); + + bind_builder_combo_row (builder, nautilus_preferences, + NAUTILUS_PREFERENCES_DIALOG_OPEN_ACTION_COMBO, + NAUTILUS_PREFERENCES_CLICK_POLICY, + (const char **) click_behavior_values); + bind_builder_combo_row (builder, nautilus_preferences, + NAUTILUS_PREFERENCES_DIALOG_SEARCH_RECURSIVE_ROW, + NAUTILUS_PREFERENCES_RECURSIVE_SEARCH, + (const char **) speed_tradeoff_values); + bind_builder_combo_row (builder, nautilus_preferences, + NAUTILUS_PREFERENCES_DIALOG_THUMBNAILS_ROW, + NAUTILUS_PREFERENCES_SHOW_FILE_THUMBNAILS, + (const char **) speed_tradeoff_values); + bind_builder_combo_row (builder, nautilus_preferences, + NAUTILUS_PREFERENCES_DIALOG_COUNT_ROW, + NAUTILUS_PREFERENCES_SHOW_DIRECTORY_ITEM_COUNTS, + (const char **) speed_tradeoff_values); + + nautilus_preferences_window_setup_icon_caption_page (builder); + + /* UI callbacks */ + window = GTK_WIDGET (gtk_builder_get_object (builder, "preferences_window")); + preferences_window = window; + + gtk_window_set_icon_name (GTK_WINDOW (preferences_window), APPLICATION_ID); + + g_object_add_weak_pointer (G_OBJECT (window), (gpointer *) &preferences_window); + + gtk_window_set_transient_for (GTK_WINDOW (preferences_window), parent_window); + + gtk_widget_show (preferences_window); +} + +void nautilus_preferences_window_show(GtkWindow *window) +{ + GtkBuilder *builder; + g_autoptr (GError) error = NULL; + + if (preferences_window != NULL) + { + gtk_window_present (GTK_WINDOW (preferences_window)); + return; + } + + builder = gtk_builder_new (); + + gtk_builder_add_from_resource ( + builder, "/org/gnome/nautilus/ui/nautilus-preferences-window.ui", &error); + if (error != NULL) + { + g_error ("Failed to add nautilus-preferences-window.ui: %s", error->message); + } + + nautilus_preferences_window_setup (builder, window); + + g_object_unref (builder); +} diff --git a/src/nautilus-preferences-window.h b/src/nautilus-preferences-window.h new file mode 100644 index 0000000..dfd64d6 --- /dev/null +++ b/src/nautilus-preferences-window.h @@ -0,0 +1,34 @@ + +/* nautilus-preferences-window.h - Function to show the nautilus preference + window. + + Copyright (C) 2002 Jan Arne Petersen + Copyright (C) 2016 Carlos Soriano + + The Gnome Library is free software; you can redistribute it and/or + modify it under the terms of the GNU Library General Public License as + published by the Free Software Foundation; either version 2 of the + License, or (at your option) any later version. + + The Gnome Library is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Library General Public License for more details. + + You should have received a copy of the GNU Library General Public + License along with the Gnome Library; see the file COPYING.LIB. If not, + see . + + Authors: Jan Arne Petersen +*/ + +#pragma once + +#include +#include + +G_BEGIN_DECLS + +void nautilus_preferences_window_show(GtkWindow *window); + +G_END_DECLS \ No newline at end of file diff --git a/src/nautilus-previewer.c b/src/nautilus-previewer.c new file mode 100644 index 0000000..66074ab --- /dev/null +++ b/src/nautilus-previewer.c @@ -0,0 +1,207 @@ +/* + * nautilus-previewer: nautilus previewer DBus wrapper + * + * Copyright (C) 2011, Red Hat, Inc. + * + * Nautilus is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License as + * published by the Free Software Foundation; either version 2 of the + * License, or (at your option) any later version. + * + * Nautilus is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * 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 . + * + * Author: Cosimo Cecchi + * + */ + +#include "config.h" + +#include "nautilus-previewer.h" + +#include "nautilus-files-view.h" +#include "nautilus-window.h" +#include "nautilus-window-slot.h" + +#define DEBUG_FLAG NAUTILUS_DEBUG_PREVIEWER +#include "nautilus-debug.h" + +#include + +#define PREVIEWER_DBUS_NAME "org.gnome.NautilusPreviewer" +#define PREVIEWER2_DBUS_IFACE "org.gnome.NautilusPreviewer2" +#define PREVIEWER_DBUS_PATH "/org/gnome/NautilusPreviewer" + +static GDBusProxy *previewer_v2_proxy = NULL; + +static gboolean +ensure_previewer_v2_proxy (void) +{ + if (previewer_v2_proxy == NULL) + { + g_autoptr (GError) error = NULL; + GDBusConnection *connection = g_application_get_dbus_connection (g_application_get_default ()); + + previewer_v2_proxy = g_dbus_proxy_new_sync (connection, + G_DBUS_PROXY_FLAGS_DO_NOT_AUTO_START_AT_CONSTRUCTION, + NULL, + PREVIEWER_DBUS_NAME, + PREVIEWER_DBUS_PATH, + PREVIEWER2_DBUS_IFACE, + NULL, + &error); + + if (error != NULL) + { + DEBUG ("Unable to create NautilusPreviewer2 proxy: %s", error->message); + return FALSE; + } + } + + return TRUE; +} + +static void +previewer2_method_ready_cb (GObject *source, + GAsyncResult *res, + gpointer user_data) +{ + GDBusProxy *proxy = G_DBUS_PROXY (source); + g_autoptr (GError) error = NULL; + + g_dbus_proxy_call_finish (proxy, res, &error); + + if (error != NULL) + { + DEBUG ("Unable to call method on NautilusPreviewer: %s", error->message); + } +} + +void +nautilus_previewer_call_show_file (const gchar *uri, + const gchar *window_handle, + guint xid, + gboolean close_if_already_visible) +{ + if (!ensure_previewer_v2_proxy ()) + { + return; + } + + g_dbus_proxy_call (previewer_v2_proxy, + "ShowFile", + g_variant_new ("(ssb)", + uri, window_handle, close_if_already_visible), + G_DBUS_CALL_FLAGS_NONE, + -1, + NULL, + previewer2_method_ready_cb, + NULL); +} + +void +nautilus_previewer_call_close (void) +{ + if (!ensure_previewer_v2_proxy ()) + { + return; + } + + /* don't autostart the previewer if it's not running */ + g_dbus_proxy_call (previewer_v2_proxy, + "Close", + NULL, + G_DBUS_CALL_FLAGS_NO_AUTO_START, + -1, + NULL, + previewer2_method_ready_cb, + NULL); +} + +static void +previewer_selection_event (GDBusConnection *connection, + const gchar *sender_name, + const gchar *object_path, + const gchar *interface_name, + const gchar *signal_name, + GVariant *parameters, + gpointer user_data) +{ + GApplication *application = g_application_get_default (); + GList *l, *windows = gtk_application_get_windows (GTK_APPLICATION (application)); + NautilusWindow *window = NULL; + NautilusWindowSlot *slot; + NautilusView *view; + GtkDirectionType direction; + + for (l = windows; l != NULL; l = l->next) + { + if (NAUTILUS_IS_WINDOW (l->data)) + { + window = l->data; + break; + } + } + + if (window == NULL) + { + return; + } + + slot = nautilus_window_get_active_slot (window); + view = nautilus_window_slot_get_current_view (slot); + + if (!NAUTILUS_IS_FILES_VIEW (view)) + { + return; + } + + g_variant_get (parameters, "(u)", &direction); + nautilus_files_view_preview_selection_event (NAUTILUS_FILES_VIEW (view), direction); +} + +guint +nautilus_previewer_connect_selection_event (GDBusConnection *connection) +{ + return g_dbus_connection_signal_subscribe (connection, + PREVIEWER_DBUS_NAME, + PREVIEWER2_DBUS_IFACE, + "SelectionEvent", + PREVIEWER_DBUS_PATH, + NULL, + G_DBUS_SIGNAL_FLAGS_NONE, + previewer_selection_event, + NULL, + NULL); +} + +void +nautilus_previewer_disconnect_selection_event (GDBusConnection *connection, + guint event_id) +{ + g_dbus_connection_signal_unsubscribe (connection, event_id); +} + +gboolean +nautilus_previewer_is_visible (void) +{ + g_autoptr (GVariant) variant = NULL; + + if (!ensure_previewer_v2_proxy ()) + { + return FALSE; + } + + variant = g_dbus_proxy_get_cached_property (previewer_v2_proxy, "Visible"); + if (variant) + { + return g_variant_get_boolean (variant); + } + + return FALSE; +} diff --git a/src/nautilus-previewer.h b/src/nautilus-previewer.h new file mode 100644 index 0000000..73270fa --- /dev/null +++ b/src/nautilus-previewer.h @@ -0,0 +1,42 @@ +/* + * nautilus-previewer: nautilus previewer DBus wrapper + * + * Copyright (C) 2011, Red Hat, Inc. + * + * Nautilus is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License as + * published by the Free Software Foundation; either version 2 of the + * License, or (at your option) any later version. + * + * Nautilus is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * 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 . + * + * Author: Cosimo Cecchi + * + */ + +#pragma once + +#include +#include + +G_BEGIN_DECLS + +void nautilus_previewer_call_show_file (const gchar *uri, + const gchar *window_handle, + guint xid, + gboolean close_if_already_visible); +void nautilus_previewer_call_close (void); + +gboolean nautilus_previewer_is_visible (void); + +guint nautilus_previewer_connect_selection_event (GDBusConnection *connection); +void nautilus_previewer_disconnect_selection_event (GDBusConnection *connection, + guint event_id); + +G_END_DECLS diff --git a/src/nautilus-profile.c b/src/nautilus-profile.c new file mode 100644 index 0000000..3f6c640 --- /dev/null +++ b/src/nautilus-profile.c @@ -0,0 +1,69 @@ +/* + * Copyright (C) 2005 William Jon McCann + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 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 warranty of + * MERCHANTABILITY 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 . + * + * Authors: William Jon McCann + * + */ + +#include "config.h" + +#include +#include +#include +#include +#include +#include + +#include +#include + +#include "nautilus-profile.h" + +void +_nautilus_profile_log (const char *func, + const char *note, + const char *format, + ...) +{ + va_list args; + char *str; + char *formatted; + + if (format == NULL) + { + formatted = g_strdup (""); + } + else + { + va_start (args, format); + formatted = g_strdup_vprintf (format, args); + va_end (args); + } + + if (func != NULL) + { + str = g_strdup_printf ("MARK: %s %s: %s %s", g_get_prgname (), func, note ? note : "", formatted); + } + else + { + str = g_strdup_printf ("MARK: %s: %s %s", g_get_prgname (), note ? note : "", formatted); + } + + g_free (formatted); + + g_access (str, F_OK); + g_free (str); +} diff --git a/src/nautilus-profile.h b/src/nautilus-profile.h new file mode 100644 index 0000000..47f8131 --- /dev/null +++ b/src/nautilus-profile.h @@ -0,0 +1,53 @@ +/* + * Copyright (C) 2005 William Jon McCann + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 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 warranty of + * MERCHANTABILITY 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 . + * + * Authors: William Jon McCann + * + * Can be profiled like so: + * strace -ttt -f -o /tmp/logfile.strace nautilus + * python plot-timeline.py -o prettygraph.png /tmp/logfile.strace + * + * See: http://www.gnome.org/~federico/news-2006-03.html#09 + */ + +#pragma once + +#include + +G_BEGIN_DECLS + +#ifdef ENABLE_PROFILING +#ifdef G_HAVE_ISO_VARARGS +#define nautilus_profile_start(...) _nautilus_profile_log (G_STRFUNC, "start", __VA_ARGS__) +#define nautilus_profile_end(...) _nautilus_profile_log (G_STRFUNC, "end", __VA_ARGS__) +#define nautilus_profile_msg(...) _nautilus_profile_log (NULL, NULL, __VA_ARGS__) +#elif defined(G_HAVE_GNUC_VARARGS) +#define nautilus_profile_start(format...) _nautilus_profile_log (G_STRFUNC, "start", format) +#define nautilus_profile_end(format...) _nautilus_profile_log (G_STRFUNC, "end", format) +#define nautilus_profile_msg(format...) _nautilus_profile_log (NULL, NULL, format) +#endif +#else +#define nautilus_profile_start(...) +#define nautilus_profile_end(...) +#define nautilus_profile_msg(...) +#endif + +void _nautilus_profile_log (const char *func, + const char *note, + const char *format, + ...) G_GNUC_PRINTF (3, 4); + +G_END_DECLS \ No newline at end of file diff --git a/src/nautilus-program-choosing.c b/src/nautilus-program-choosing.c new file mode 100644 index 0000000..ceaee0a --- /dev/null +++ b/src/nautilus-program-choosing.c @@ -0,0 +1,611 @@ +/* nautilus-program-choosing.c - functions for selecting and activating + * programs for opening/viewing particular files. + * + * Copyright (C) 2000 Eazel, Inc. + * + * The Gnome Library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Library General Public License as + * published by the Free Software Foundation; either version 2 of the + * License, or (at your option) any later version. + * + * The Gnome Library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Library General Public License for more details. + * + * You should have received a copy of the GNU Library General Public + * License along with the Gnome Library; see the file COPYING.LIB. If not, + * see . + * + * Author: John Sullivan + */ + +#include +#include "nautilus-program-choosing.h" + +#include "nautilus-global-preferences.h" +#include "nautilus-icon-info.h" +#include "nautilus-ui-utilities.h" +#include "nautilus-window.h" +#include +#include +#include +#include +#include +#include + +#include + +static void +add_file_to_recent (NautilusFile *file, + GAppInfo *application) +{ + GtkRecentData recent_data; + char *uri; + + uri = nautilus_file_get_activation_uri (file); + if (uri == NULL) + { + uri = nautilus_file_get_uri (file); + } + + /* do not add trash:// etc */ + if (eel_uri_is_trash (uri) || + eel_uri_is_search (uri) || + eel_uri_is_recent (uri)) + { + g_free (uri); + return; + } + + recent_data.display_name = NULL; + recent_data.description = NULL; + + recent_data.mime_type = nautilus_file_get_mime_type (file); + recent_data.app_name = g_strdup (g_get_application_name ()); + recent_data.app_exec = g_strdup (g_app_info_get_commandline (application)); + + recent_data.groups = NULL; + recent_data.is_private = FALSE; + + gtk_recent_manager_add_full (gtk_recent_manager_get_default (), + uri, &recent_data); + + g_free (recent_data.mime_type); + g_free (recent_data.app_name); + g_free (recent_data.app_exec); + + g_free (uri); +} +void +nautilus_launch_application_for_mount (GAppInfo *app_info, + GMount *mount, + GtkWindow *parent_window) +{ + GFile *root; + NautilusFile *file; + GList *files; + + root = g_mount_get_root (mount); + file = nautilus_file_get (root); + g_object_unref (root); + + files = g_list_append (NULL, file); + nautilus_launch_application (app_info, + files, + parent_window); + + g_list_free_full (files, (GDestroyNotify) nautilus_file_unref); +} + +/** + * nautilus_launch_application: + * + * Fork off a process to launch an application with a given file as a + * parameter. Provide a parent window for error dialogs. + * + * @application: The application to be launched. + * @uris: The files whose locations should be passed as a parameter to the application. + * @parent_window: A window to use as the parent for any error dialogs. + */ +void +nautilus_launch_application (GAppInfo *application, + GList *files, + GtkWindow *parent_window) +{ + GList *uris, *l; + + uris = NULL; + for (l = files; l != NULL; l = l->next) + { + uris = g_list_prepend (uris, nautilus_file_get_activation_uri (l->data)); + } + uris = g_list_reverse (uris); + nautilus_launch_application_by_uri (application, uris, + parent_window); + g_list_free_full (uris, g_free); +} + +static GdkAppLaunchContext * +get_launch_context (GtkWindow *parent_window) +{ + GdkDisplay *display; + GdkAppLaunchContext *launch_context; + + if (parent_window != NULL) + { + display = gtk_widget_get_display (GTK_WIDGET (parent_window)); + } + else + { + display = gdk_display_get_default (); + } + + launch_context = gdk_display_get_app_launch_context (display); + + return launch_context; +} + +void +nautilus_launch_application_by_uri (GAppInfo *application, + GList *uris, + GtkWindow *parent_window) +{ + char *uri; + GList *locations, *l; + GFile *location; + NautilusFile *file; + gboolean result; + GError *error; + g_autoptr (GdkAppLaunchContext) launch_context = NULL; + NautilusIconInfo *icon; + int count, total; + + g_assert (uris != NULL); + + /* count the number of uris with local paths */ + count = 0; + total = g_list_length (uris); + locations = NULL; + for (l = uris; l != NULL; l = l->next) + { + uri = l->data; + + location = g_file_new_for_uri (uri); + if (g_file_is_native (location)) + { + count++; + } + locations = g_list_prepend (locations, location); + } + locations = g_list_reverse (locations); + + launch_context = get_launch_context (parent_window); + + file = nautilus_file_get_by_uri (uris->data); + icon = nautilus_file_get_icon (file, + 48, gtk_widget_get_scale_factor (GTK_WIDGET (parent_window)), + 0); + nautilus_file_unref (file); + if (icon) + { + gdk_app_launch_context_set_icon_name (launch_context, + nautilus_icon_info_get_used_name (icon)); + g_object_unref (icon); + } + + error = NULL; + + if (count == total) + { + /* All files are local, so we can use g_app_info_launch () with + * the file list we constructed before. + */ + result = g_app_info_launch (application, + locations, + G_APP_LAUNCH_CONTEXT (launch_context), + &error); + } + else + { + /* Some files are non local, better use g_app_info_launch_uris (). + */ + result = g_app_info_launch_uris (application, + uris, + G_APP_LAUNCH_CONTEXT (launch_context), + &error); + } + + if (result) + { + for (l = uris; l != NULL; l = l->next) + { + file = nautilus_file_get_by_uri (l->data); + add_file_to_recent (file, application); + nautilus_file_unref (file); + } + } + + g_list_free_full (locations, g_object_unref); +} + +static void +launch_application_from_command_internal (const gchar *full_command, + GdkDisplay *display, + gboolean use_terminal) +{ + GAppInfoCreateFlags flags; + g_autoptr (GError) error = NULL; + g_autoptr (GAppInfo) app = NULL; + + flags = G_APP_INFO_CREATE_NONE; + if (use_terminal) + { + flags = G_APP_INFO_CREATE_NEEDS_TERMINAL; + } + + app = g_app_info_create_from_commandline (full_command, NULL, flags, &error); + if (app != NULL && !(use_terminal && display == NULL)) + { + g_autoptr (GdkAppLaunchContext) context = NULL; + + context = gdk_display_get_app_launch_context (display); + + g_app_info_launch (app, NULL, G_APP_LAUNCH_CONTEXT (context), &error); + } + + if (error != NULL) + { + g_message ("Could not start application: %s", error->message); + } +} + +/** + * nautilus_launch_application_from_command: + * + * Fork off a process to launch an application with a given uri as + * a parameter. + * + * @command_string: The application to be launched, with any desired + * command-line options. + * @...: Passed as parameters to the application after quoting each of them. + */ +void +nautilus_launch_application_from_command (GdkDisplay *display, + const char *command_string, + gboolean use_terminal, + ...) +{ + char *full_command, *tmp; + char *quoted_parameter; + char *parameter; + va_list ap; + + full_command = g_strdup (command_string); + + va_start (ap, use_terminal); + + while ((parameter = va_arg (ap, char *)) != NULL) + { + quoted_parameter = g_shell_quote (parameter); + tmp = g_strconcat (full_command, " ", quoted_parameter, NULL); + g_free (quoted_parameter); + + g_free (full_command); + full_command = tmp; + } + + va_end (ap); + + launch_application_from_command_internal (full_command, display, use_terminal); + + g_free (full_command); +} + +/** + * nautilus_launch_application_from_command: + * + * Fork off a process to launch an application with a given uri as + * a parameter. + * + * @command_string: The application to be launched, with any desired + * command-line options. + * @parameters: Passed as parameters to the application after quoting each of them. + */ +void +nautilus_launch_application_from_command_array (GdkDisplay *display, + const char *command_string, + gboolean use_terminal, + const char * const *parameters) +{ + char *full_command, *tmp; + char *quoted_parameter; + const char * const *p; + + full_command = g_strdup (command_string); + + if (parameters != NULL) + { + for (p = parameters; *p != NULL; p++) + { + quoted_parameter = g_shell_quote (*p); + tmp = g_strconcat (full_command, " ", quoted_parameter, NULL); + g_free (quoted_parameter); + + g_free (full_command); + full_command = tmp; + } + } + + launch_application_from_command_internal (full_command, display, use_terminal); + + g_free (full_command); +} + +void +nautilus_launch_desktop_file (const char *desktop_file_uri, + const GList *parameter_uris, + GtkWindow *parent_window) +{ + GError *error; + char *message, *desktop_file_path; + const GList *p; + GList *files; + int total, count; + GFile *file, *desktop_file; + GDesktopAppInfo *app_info; + GdkAppLaunchContext *context; + + /* Don't allow command execution from remote locations + * to partially mitigate the security + * risk of executing arbitrary commands. + */ + desktop_file = g_file_new_for_uri (desktop_file_uri); + desktop_file_path = g_file_get_path (desktop_file); + if (!g_file_is_native (desktop_file)) + { + g_free (desktop_file_path); + g_object_unref (desktop_file); + show_dialog (_("Sorry, but you cannot execute commands from a remote site."), + _("This is disabled due to security considerations."), + parent_window, + GTK_MESSAGE_ERROR); + + return; + } + g_object_unref (desktop_file); + + app_info = g_desktop_app_info_new_from_filename (desktop_file_path); + g_free (desktop_file_path); + if (app_info == NULL) + { + show_dialog (_("There was an error launching the application."), + NULL, + parent_window, + GTK_MESSAGE_ERROR); + return; + } + + /* count the number of uris with local paths */ + count = 0; + total = g_list_length ((GList *) parameter_uris); + files = NULL; + for (p = parameter_uris; p != NULL; p = p->next) + { + file = g_file_new_for_uri ((const char *) p->data); + if (g_file_is_native (file)) + { + count++; + } + files = g_list_prepend (files, file); + } + + /* check if this app only supports local files */ + if (g_app_info_supports_files (G_APP_INFO (app_info)) && + !g_app_info_supports_uris (G_APP_INFO (app_info)) && + parameter_uris != NULL) + { + if (count == 0) + { + /* all files are non-local */ + show_dialog (_("This drop target only supports local files."), + _("To open non-local files copy them to a local folder and then drop them again."), + parent_window, + GTK_MESSAGE_ERROR); + + g_list_free_full (files, g_object_unref); + g_object_unref (app_info); + return; + } + else if (count != total) + { + /* some files are non-local */ + show_dialog (_("This drop target only supports local files."), + _("To open non-local files copy them to a local folder and then" + " drop them again. The local files you dropped have already been opened."), + parent_window, + GTK_MESSAGE_WARNING); + } + } + + error = NULL; + context = gdk_display_get_app_launch_context (gtk_widget_get_display (GTK_WIDGET (parent_window))); + /* TODO: Ideally we should accept a timestamp here instead of using GDK_CURRENT_TIME */ + gdk_app_launch_context_set_timestamp (context, GDK_CURRENT_TIME); + if (count == total) + { + /* All files are local, so we can use g_app_info_launch () with + * the file list we constructed before. + */ + g_app_info_launch (G_APP_INFO (app_info), + files, + G_APP_LAUNCH_CONTEXT (context), + &error); + } + else + { + /* Some files are non local, better use g_app_info_launch_uris (). + */ + g_app_info_launch_uris (G_APP_INFO (app_info), + (GList *) parameter_uris, + G_APP_LAUNCH_CONTEXT (context), + &error); + } + if (error != NULL) + { + message = g_strconcat (_("Details: "), error->message, NULL); + show_dialog (_("There was an error launching the application."), + message, + parent_window, + GTK_MESSAGE_ERROR); + + g_error_free (error); + g_free (message); + } + + g_list_free_full (files, g_object_unref); + g_object_unref (context); + g_object_unref (app_info); +} + +/* HAX + * + * TODO: remove everything below once it’s doable from GTK+. + * + * Context: https://bugzilla.gnome.org/show_bug.cgi?id=781132 and + * https://bugzilla.gnome.org/show_bug.cgi?id=779312 + * + * In a sandboxed environment, this is needed to able to get the actual + * result of the operation, since gtk_show_uri_on_window () neither blocks + * nor returns a useful value. + */ + +static void +on_launch_default_for_uri (GObject *source, + GAsyncResult *result, + gpointer data) +{ + GTask *task; + NautilusWindow *window; + gboolean success; + GError *error = NULL; + + task = data; + window = g_task_get_source_object (task); + + success = g_app_info_launch_default_for_uri_finish (result, &error); + + if (window) + { + nautilus_window_unexport_handle (window); + } + + if (success) + { + g_task_return_boolean (task, success); + } + else + { + g_task_return_error (task, error); + } + + /* Reffed in the call to nautilus_window_export_handle */ + g_object_unref (task); +} + +static void +on_window_handle_export (NautilusWindow *window, + const char *handle_str, + guint xid, + gpointer user_data) +{ + GTask *task = user_data; + GAppLaunchContext *context = g_task_get_task_data (task); + const char *uri; + + uri = g_object_get_data (G_OBJECT (context), "uri"); + + g_app_launch_context_setenv (context, "PARENT_WINDOW_ID", handle_str); + + g_app_info_launch_default_for_uri_async (uri, + context, + g_task_get_cancellable (task), + on_launch_default_for_uri, + task); +} + +static void +launch_default_for_uri_thread_func (GTask *task, + gpointer source_object, + gpointer task_data, + GCancellable *cancellable) +{ + GAppLaunchContext *launch_context; + const char *uri; + gboolean success; + GError *error = NULL; + + launch_context = task_data; + uri = g_object_get_data (G_OBJECT (launch_context), "uri"); + success = g_app_info_launch_default_for_uri (uri, launch_context, &error); + + if (success) + { + g_task_return_boolean (task, success); + } + else + { + g_task_return_error (task, error); + } +} + +void +nautilus_launch_default_for_uri_async (const char *uri, + GtkWindow *parent_window, + GCancellable *cancellable, + GAsyncReadyCallback callback, + gpointer callback_data) +{ + g_autoptr (GdkAppLaunchContext) launch_context = NULL; + g_autoptr (GTask) task = NULL; + + g_return_if_fail (uri != NULL); + + launch_context = get_launch_context (parent_window); + task = g_task_new (parent_window, cancellable, callback, callback_data); + + gdk_app_launch_context_set_timestamp (launch_context, GDK_CURRENT_TIME); + + g_object_set_data_full (G_OBJECT (launch_context), + "uri", g_strdup (uri), g_free); + g_task_set_task_data (task, + g_object_ref (launch_context), g_object_unref); + + if (parent_window != NULL) + { + gboolean handle_exported; + + handle_exported = nautilus_window_export_handle (NAUTILUS_WINDOW (parent_window), + on_window_handle_export, + g_object_ref (task)); + + if (handle_exported) + { + /* Launching will now be handled from the callback */ + return; + } + } + + g_task_run_in_thread (task, launch_default_for_uri_thread_func); +} + +gboolean +nautilus_launch_default_for_uri_finish (GAsyncResult *result, + GError **error) +{ + g_return_val_if_fail (G_IS_ASYNC_RESULT (result), FALSE); + + return g_task_propagate_boolean (G_TASK (result), error); +} + +/* END OF HAX */ diff --git a/src/nautilus-program-choosing.h b/src/nautilus-program-choosing.h new file mode 100644 index 0000000..00ac6fe --- /dev/null +++ b/src/nautilus-program-choosing.h @@ -0,0 +1,59 @@ + +/* nautilus-program-choosing.h - functions for selecting and activating + programs for opening/viewing particular files. + + Copyright (C) 2000 Eazel, Inc. + + The Gnome Library is free software; you can redistribute it and/or + modify it under the terms of the GNU Library General Public License as + published by the Free Software Foundation; either version 2 of the + License, or (at your option) any later version. + + The Gnome Library is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Library General Public License for more details. + + You should have received a copy of the GNU Library General Public + License along with the Gnome Library; see the file COPYING.LIB. If not, + see . + + Author: John Sullivan +*/ + +#pragma once + +#include +#include +#include "nautilus-file.h" + +typedef void (*NautilusApplicationChoiceCallback) (GAppInfo *application, + gpointer callback_data); + +void nautilus_launch_application (GAppInfo *application, + GList *files, + GtkWindow *parent_window); +void nautilus_launch_application_by_uri (GAppInfo *application, + GList *uris, + GtkWindow *parent_window); +void nautilus_launch_application_for_mount (GAppInfo *app_info, + GMount *mount, + GtkWindow *parent_window); +void nautilus_launch_application_from_command (GdkDisplay *display, + const char *command_string, + gboolean use_terminal, + ...) G_GNUC_NULL_TERMINATED; +void nautilus_launch_application_from_command_array (GdkDisplay *display, + const char *command_string, + gboolean use_terminal, + const char * const * parameters); +void nautilus_launch_desktop_file (const char *desktop_file_uri, + const GList *parameter_uris, + GtkWindow *parent_window); +void nautilus_launch_default_for_uri_async (const char *uri, + GtkWindow *parent_window, + GCancellable *cancellable, + GAsyncReadyCallback callback, + gpointer callback_data); +gboolean nautilus_launch_default_for_uri_finish (GAsyncResult *result, + GError **error); diff --git a/src/nautilus-progress-indicator.c b/src/nautilus-progress-indicator.c new file mode 100644 index 0000000..c49be9f --- /dev/null +++ b/src/nautilus-progress-indicator.c @@ -0,0 +1,548 @@ +/* + * Copyright (C) 2022 The GNOME project contributors + * + * SPDX-License-Identifier: GPL-2.0-or-later + */ + +#include "nautilus-progress-indicator.h" + +#include "nautilus-file-operations.h" +#include "nautilus-progress-info-manager.h" +#include "nautilus-progress-info-widget.h" +#include "nautilus-window.h" + +#define OPERATION_MINIMUM_TIME 2 /*s */ +#define NEEDS_ATTENTION_ANIMATION_TIMEOUT 2000 /*ms */ +#define REMOVE_FINISHED_OPERATIONS_TIEMOUT 3 /*s */ + +struct _NautilusProgressIndicator +{ + AdwBin parent_instance; + + guint start_operations_timeout_id; + guint remove_finished_operations_timeout_id; + guint operations_button_attention_timeout_id; + + GtkWidget *operations_button; + GtkWidget *operations_popover; + GtkWidget *operations_list; + GListStore *progress_infos_model; + GtkWidget *operations_revealer; + GtkWidget *operations_icon; + + NautilusProgressInfoManager *progress_manager; +}; + +G_DEFINE_FINAL_TYPE (NautilusProgressIndicator, nautilus_progress_indicator, ADW_TYPE_BIN); + + +static void update_operations (NautilusProgressIndicator *self); + +static gboolean +should_show_progress_info (NautilusProgressInfo *info) +{ + return nautilus_progress_info_get_total_elapsed_time (info) + + nautilus_progress_info_get_remaining_time (info) > OPERATION_MINIMUM_TIME; +} + +static GList * +get_filtered_progress_infos (NautilusProgressIndicator *self) +{ + GList *l; + GList *filtered_progress_infos; + GList *progress_infos; + + progress_infos = nautilus_progress_info_manager_get_all_infos (self->progress_manager); + filtered_progress_infos = NULL; + + for (l = progress_infos; l != NULL; l = l->next) + { + if (should_show_progress_info (l->data)) + { + filtered_progress_infos = g_list_append (filtered_progress_infos, l->data); + } + } + + return filtered_progress_infos; +} + +static gboolean +should_hide_operations_button (NautilusProgressIndicator *self) +{ + GList *progress_infos; + GList *l; + + progress_infos = get_filtered_progress_infos (self); + + for (l = progress_infos; l != NULL; l = l->next) + { + if (nautilus_progress_info_get_total_elapsed_time (l->data) + + nautilus_progress_info_get_remaining_time (l->data) > OPERATION_MINIMUM_TIME && + !nautilus_progress_info_get_is_cancelled (l->data) && + !nautilus_progress_info_get_is_finished (l->data)) + { + return FALSE; + } + } + + g_list_free (progress_infos); + + return TRUE; +} + +static gboolean +on_remove_finished_operations_timeout (NautilusProgressIndicator *self) +{ + nautilus_progress_info_manager_remove_finished_or_cancelled_infos (self->progress_manager); + if (should_hide_operations_button (self)) + { + gtk_revealer_set_reveal_child (GTK_REVEALER (self->operations_revealer), + FALSE); + } + else + { + update_operations (self); + } + + self->remove_finished_operations_timeout_id = 0; + + return G_SOURCE_REMOVE; +} + +static void +unschedule_remove_finished_operations (NautilusProgressIndicator *self) +{ + if (self->remove_finished_operations_timeout_id != 0) + { + g_source_remove (self->remove_finished_operations_timeout_id); + self->remove_finished_operations_timeout_id = 0; + } +} + +static void +schedule_remove_finished_operations (NautilusProgressIndicator *self) +{ + if (self->remove_finished_operations_timeout_id == 0) + { + self->remove_finished_operations_timeout_id = + g_timeout_add_seconds (REMOVE_FINISHED_OPERATIONS_TIEMOUT, + (GSourceFunc) on_remove_finished_operations_timeout, + self); + } +} + +static void +remove_operations_button_attention_style (NautilusProgressIndicator *self) +{ + gtk_widget_remove_css_class (self->operations_button, + "nautilus-operations-button-needs-attention"); +} + +static gboolean +on_remove_operations_button_attention_style_timeout (NautilusProgressIndicator *self) +{ + remove_operations_button_attention_style (self); + self->operations_button_attention_timeout_id = 0; + + return G_SOURCE_REMOVE; +} + +static void +unschedule_operations_button_attention_style (NautilusProgressIndicator *self) +{ + if (self->operations_button_attention_timeout_id != 0) + { + g_source_remove (self->operations_button_attention_timeout_id); + self->operations_button_attention_timeout_id = 0; + } +} + +static void +add_operations_button_attention_style (NautilusProgressIndicator *self) +{ + unschedule_operations_button_attention_style (self); + remove_operations_button_attention_style (self); + + gtk_widget_add_css_class (self->operations_button, + "nautilus-operations-button-needs-attention"); + self->operations_button_attention_timeout_id = g_timeout_add (NEEDS_ATTENTION_ANIMATION_TIMEOUT, + (GSourceFunc) on_remove_operations_button_attention_style_timeout, + self); +} + +static void +on_progress_info_cancelled (NautilusProgressIndicator *self) +{ + /* Update the pie chart progress */ + gtk_widget_queue_draw (self->operations_icon); + + if (!nautilus_progress_manager_has_viewers (self->progress_manager)) + { + schedule_remove_finished_operations (self); + } +} + +static void +on_progress_info_progress_changed (NautilusProgressIndicator *self) +{ + /* Update the pie chart progress */ + gtk_widget_queue_draw (self->operations_icon); +} + +static void +on_progress_info_finished (NautilusProgressIndicator *self, + NautilusProgressInfo *info) +{ + NautilusWindow *window; + gchar *main_label; + GFile *folder_to_open; + + window = NAUTILUS_WINDOW (gtk_widget_get_root (GTK_WIDGET (self))); + + /* Update the pie chart progress */ + gtk_widget_queue_draw (self->operations_icon); + + if (!nautilus_progress_manager_has_viewers (self->progress_manager)) + { + schedule_remove_finished_operations (self); + } + + folder_to_open = nautilus_progress_info_get_destination (info); + /* If destination is null, don't show a notification. This happens when the + * operation is a trash operation, which we already show a diferent kind of + * notification */ + if (!gtk_widget_is_visible (self->operations_popover) && + folder_to_open != NULL) + { + add_operations_button_attention_style (self); + main_label = nautilus_progress_info_get_status (info); + nautilus_window_show_operation_notification (window, + main_label, + folder_to_open); + g_free (main_label); + } + + g_clear_object (&folder_to_open); +} + +static void +disconnect_progress_infos (NautilusProgressIndicator *self) +{ + GList *progress_infos; + GList *l; + + progress_infos = nautilus_progress_info_manager_get_all_infos (self->progress_manager); + for (l = progress_infos; l != NULL; l = l->next) + { + g_signal_handlers_disconnect_by_data (l->data, self); + } +} + +static void +update_operations (NautilusProgressIndicator *self) +{ + GList *progress_infos; + GList *l; + gboolean should_show_progress_button = FALSE; + + disconnect_progress_infos (self); + g_list_store_remove_all (self->progress_infos_model); + + progress_infos = get_filtered_progress_infos (self); + for (l = progress_infos; l != NULL; l = l->next) + { + should_show_progress_button = should_show_progress_button || + should_show_progress_info (l->data); + + g_signal_connect_object (l->data, "finished", + G_CALLBACK (on_progress_info_finished), self, G_CONNECT_SWAPPED); + g_signal_connect_object (l->data, "cancelled", + G_CALLBACK (on_progress_info_cancelled), self, G_CONNECT_SWAPPED); + g_signal_connect_object (l->data, "progress-changed", + G_CALLBACK (on_progress_info_progress_changed), self, G_CONNECT_SWAPPED); + g_list_store_append (self->progress_infos_model, l->data); + } + + g_list_free (progress_infos); + + if (should_show_progress_button && + !gtk_revealer_get_reveal_child (GTK_REVEALER (self->operations_revealer))) + { + add_operations_button_attention_style (self); + gtk_revealer_set_reveal_child (GTK_REVEALER (self->operations_revealer), + TRUE); + gtk_widget_queue_draw (self->operations_icon); + } + + /* Since we removed the info widgets, we need to restore the focus */ + if (gtk_widget_get_visible (self->operations_popover)) + { + gtk_widget_grab_focus (self->operations_popover); + } +} + +static gboolean +on_progress_info_started_timeout (NautilusProgressIndicator *self) +{ + GList *progress_infos; + GList *filtered_progress_infos; + + update_operations (self); + + /* In case we didn't show the operations button because the operation total + * time stimation is not good enough, update again to make sure we don't miss + * a long time operation because of that */ + + progress_infos = nautilus_progress_info_manager_get_all_infos (self->progress_manager); + filtered_progress_infos = get_filtered_progress_infos (self); + if (!nautilus_progress_manager_are_all_infos_finished_or_cancelled (self->progress_manager) && + g_list_length (progress_infos) != g_list_length (filtered_progress_infos)) + { + g_list_free (filtered_progress_infos); + return G_SOURCE_CONTINUE; + } + else + { + g_list_free (filtered_progress_infos); + self->start_operations_timeout_id = 0; + return G_SOURCE_REMOVE; + } +} + +static void +schedule_operations_start (NautilusProgressIndicator *self) +{ + if (self->start_operations_timeout_id == 0) + { + /* Timeout is a little more than what we require for a stimated operation + * total time, to make sure the stimated total time is correct */ + self->start_operations_timeout_id = + g_timeout_add (SECONDS_NEEDED_FOR_APROXIMATE_TRANSFER_RATE * 1000 + 500, + (GSourceFunc) on_progress_info_started_timeout, + self); + } +} + +static void +unschedule_operations_start (NautilusProgressIndicator *self) +{ + if (self->start_operations_timeout_id != 0) + { + g_source_remove (self->start_operations_timeout_id); + self->start_operations_timeout_id = 0; + } +} + +static void +on_progress_info_started (NautilusProgressInfo *info, + NautilusProgressIndicator *self) +{ + g_signal_handlers_disconnect_by_data (info, self); + schedule_operations_start (self); +} + +static void +on_new_progress_info (NautilusProgressInfoManager *manager, + NautilusProgressInfo *info, + NautilusProgressIndicator *self) +{ + g_signal_connect (info, "started", + G_CALLBACK (on_progress_info_started), self); +} + +static void +on_operations_icon_draw (GtkDrawingArea *drawing_area, + cairo_t *cr, + int width, + int height, + NautilusProgressIndicator *self) +{ + GtkWidget *widget = GTK_WIDGET (drawing_area); + gfloat elapsed_progress = 0; + gint remaining_progress = 0; + gint total_progress; + gdouble ratio; + GList *progress_infos; + GList *l; + gboolean all_cancelled; + GdkRGBA background; + GdkRGBA foreground; + GtkStyleContext *style_context; + + style_context = gtk_widget_get_style_context (widget); + gtk_style_context_get_color (style_context, &foreground); + background = foreground; + background.alpha *= 0.3; + + all_cancelled = TRUE; + progress_infos = get_filtered_progress_infos (self); + for (l = progress_infos; l != NULL; l = l->next) + { + if (!nautilus_progress_info_get_is_cancelled (l->data)) + { + all_cancelled = FALSE; + remaining_progress += nautilus_progress_info_get_remaining_time (l->data); + elapsed_progress += nautilus_progress_info_get_elapsed_time (l->data); + } + } + + g_list_free (progress_infos); + + total_progress = remaining_progress + elapsed_progress; + + if (all_cancelled) + { + ratio = 1.0; + } + else + { + if (total_progress > 0) + { + ratio = MAX (0.05, elapsed_progress / total_progress); + } + else + { + ratio = 0.05; + } + } + + + width = gtk_widget_get_allocated_width (widget); + height = gtk_widget_get_allocated_height (widget); + + gdk_cairo_set_source_rgba (cr, &background); + cairo_arc (cr, + width / 2.0, height / 2.0, + MIN (width, height) / 2.0, + 0, 2 * G_PI); + cairo_fill (cr); + cairo_move_to (cr, width / 2.0, height / 2.0); + gdk_cairo_set_source_rgba (cr, &foreground); + cairo_arc (cr, + width / 2.0, height / 2.0, + MIN (width, height) / 2.0, + -G_PI / 2.0, ratio * 2 * G_PI - G_PI / 2.0); + + cairo_fill (cr); +} + +static void +on_operations_popover_notify_visible (NautilusProgressIndicator *self, + GParamSpec *pspec, + GObject *popover) +{ + if (gtk_widget_get_visible (GTK_WIDGET (popover))) + { + unschedule_remove_finished_operations (self); + nautilus_progress_manager_add_viewer (self->progress_manager, + G_OBJECT (self)); + } + else + { + nautilus_progress_manager_remove_viewer (self->progress_manager, + G_OBJECT (self)); + } +} + +static void +on_progress_has_viewers_changed (NautilusProgressInfoManager *manager, + NautilusProgressIndicator *self) +{ + if (nautilus_progress_manager_has_viewers (manager)) + { + unschedule_remove_finished_operations (self); + return; + } + + if (nautilus_progress_manager_are_all_infos_finished_or_cancelled (manager)) + { + unschedule_remove_finished_operations (self); + schedule_remove_finished_operations (self); + } +} + +static GtkWidget * +operations_list_create_widget (GObject *item, + gpointer user_data) +{ + NautilusProgressInfo *info = NAUTILUS_PROGRESS_INFO (item); + GtkWidget *widget; + + widget = nautilus_progress_info_widget_new (info); + gtk_widget_show (widget); + + return widget; +} + +static void +nautilus_progress_indicator_constructed (GObject *object) +{ + NautilusProgressIndicator *self = NAUTILUS_PROGRESS_INDICATOR (object); + + self->progress_manager = nautilus_progress_info_manager_dup_singleton (); + g_signal_connect (self->progress_manager, "new-progress-info", + G_CALLBACK (on_new_progress_info), self); + g_signal_connect (self->progress_manager, "has-viewers-changed", + G_CALLBACK (on_progress_has_viewers_changed), self); + + self->progress_infos_model = g_list_store_new (NAUTILUS_TYPE_PROGRESS_INFO); + gtk_list_box_bind_model (GTK_LIST_BOX (self->operations_list), + G_LIST_MODEL (self->progress_infos_model), + (GtkListBoxCreateWidgetFunc) operations_list_create_widget, + NULL, + NULL); + update_operations (self); + + g_signal_connect (self->operations_popover, "show", + (GCallback) gtk_widget_grab_focus, NULL); + g_signal_connect_swapped (self->operations_popover, "closed", + (GCallback) gtk_widget_grab_focus, self); +} + +static void +nautilus_progress_indicator_finalize (GObject *obj) +{ + NautilusProgressIndicator *self = NAUTILUS_PROGRESS_INDICATOR (obj); + + disconnect_progress_infos (self); + unschedule_remove_finished_operations (self); + unschedule_operations_start (self); + unschedule_operations_button_attention_style (self); + + g_clear_object (&self->progress_infos_model); + g_signal_handlers_disconnect_by_data (self->progress_manager, self); + g_clear_object (&self->progress_manager); + + G_OBJECT_CLASS (nautilus_progress_indicator_parent_class)->finalize (obj); +} + +static void +nautilus_progress_indicator_class_init (NautilusProgressIndicatorClass *klass) +{ + GObjectClass *object_class = G_OBJECT_CLASS (klass); + GtkWidgetClass *widget_class = GTK_WIDGET_CLASS (klass); + + object_class->constructed = nautilus_progress_indicator_constructed; + object_class->finalize = nautilus_progress_indicator_finalize; + + gtk_widget_class_set_template_from_resource (widget_class, + "/org/gnome/nautilus/ui/nautilus-progress-indicator.ui"); + gtk_widget_class_bind_template_child (widget_class, NautilusProgressIndicator, operations_button); + gtk_widget_class_bind_template_child (widget_class, NautilusProgressIndicator, operations_icon); + gtk_widget_class_bind_template_child (widget_class, NautilusProgressIndicator, operations_popover); + gtk_widget_class_bind_template_child (widget_class, NautilusProgressIndicator, operations_list); + gtk_widget_class_bind_template_child (widget_class, NautilusProgressIndicator, operations_revealer); + + gtk_widget_class_bind_template_callback (widget_class, on_operations_popover_notify_visible); +} + +static void +nautilus_progress_indicator_init (NautilusProgressIndicator *self) +{ + gtk_widget_init_template (GTK_WIDGET (self)); + + gtk_drawing_area_set_draw_func (GTK_DRAWING_AREA (self->operations_icon), + (GtkDrawingAreaDrawFunc) on_operations_icon_draw, + self, + NULL); +} diff --git a/src/nautilus-progress-indicator.h b/src/nautilus-progress-indicator.h new file mode 100644 index 0000000..9b69ac0 --- /dev/null +++ b/src/nautilus-progress-indicator.h @@ -0,0 +1,20 @@ +/* + * Copyright (C) 2022 The GNOME project contributors + * + * SPDX-License-Identifier: GPL-2.0-or-later + */ + +#pragma once + +#include +#include + +#include "nautilus-window-slot.h" + +G_BEGIN_DECLS + +#define NAUTILUS_TYPE_PROGRESS_INDICATOR (nautilus_progress_indicator_get_type()) + +G_DECLARE_FINAL_TYPE (NautilusProgressIndicator, nautilus_progress_indicator, NAUTILUS, PROGRESS_INDICATOR, AdwBin) + +G_END_DECLS diff --git a/src/nautilus-progress-info-manager.c b/src/nautilus-progress-info-manager.c new file mode 100644 index 0000000..88e76cf --- /dev/null +++ b/src/nautilus-progress-info-manager.c @@ -0,0 +1,239 @@ +/* + * Nautilus + * + * Copyright (C) 2011 Red Hat, Inc. + * + * Nautilus is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License as + * published by the Free Software Foundation; either version 2 of the + * License, or (at your option) any later version. + * + * Nautilus is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public + * License along with this program; see the file COPYING. If not, + * see . + * + * Author: Cosimo Cecchi + */ + +#include + +#include "nautilus-progress-info-manager.h" + +struct _NautilusProgressInfoManager +{ + GObject parent_instance; + + GList *progress_infos; + GList *current_viewers; +}; + +enum +{ + NEW_PROGRESS_INFO, + HAS_VIEWERS_CHANGED, + LAST_SIGNAL +}; + +static NautilusProgressInfoManager *singleton = NULL; + +static guint signals[LAST_SIGNAL] = { 0, }; + +G_DEFINE_TYPE (NautilusProgressInfoManager, nautilus_progress_info_manager, + G_TYPE_OBJECT); + +static void remove_viewer (NautilusProgressInfoManager *self, + GObject *viewer); + +static void +nautilus_progress_info_manager_finalize (GObject *obj) +{ + GList *l; + NautilusProgressInfoManager *self = NAUTILUS_PROGRESS_INFO_MANAGER (obj); + + if (self->progress_infos != NULL) + { + g_list_free_full (self->progress_infos, g_object_unref); + } + + for (l = self->current_viewers; l != NULL; l = l->next) + { + g_object_weak_unref (l->data, (GWeakNotify) remove_viewer, self); + } + g_list_free (self->current_viewers); + + G_OBJECT_CLASS (nautilus_progress_info_manager_parent_class)->finalize (obj); +} + +static GObject * +nautilus_progress_info_manager_constructor (GType type, + guint n_props, + GObjectConstructParam *props) +{ + GObject *retval; + + if (singleton != NULL) + { + return G_OBJECT (g_object_ref (singleton)); + } + + retval = G_OBJECT_CLASS (nautilus_progress_info_manager_parent_class)->constructor + (type, n_props, props); + + singleton = NAUTILUS_PROGRESS_INFO_MANAGER (retval); + g_object_add_weak_pointer (retval, (gpointer) & singleton); + + return retval; +} + +static void +nautilus_progress_info_manager_init (NautilusProgressInfoManager *self) +{ +} + +static void +nautilus_progress_info_manager_class_init (NautilusProgressInfoManagerClass *klass) +{ + GObjectClass *oclass; + + oclass = G_OBJECT_CLASS (klass); + oclass->constructor = nautilus_progress_info_manager_constructor; + oclass->finalize = nautilus_progress_info_manager_finalize; + + signals[NEW_PROGRESS_INFO] = + g_signal_new ("new-progress-info", + G_TYPE_FROM_CLASS (klass), + G_SIGNAL_RUN_LAST, + 0, NULL, NULL, + g_cclosure_marshal_VOID__OBJECT, + G_TYPE_NONE, + 1, + NAUTILUS_TYPE_PROGRESS_INFO); + + signals[HAS_VIEWERS_CHANGED] = + g_signal_new ("has-viewers-changed", + G_TYPE_FROM_CLASS (klass), + G_SIGNAL_RUN_LAST, + 0, NULL, NULL, + g_cclosure_marshal_VOID__VOID, + G_TYPE_NONE, + 0); +} + +NautilusProgressInfoManager * +nautilus_progress_info_manager_dup_singleton (void) +{ + return g_object_new (NAUTILUS_TYPE_PROGRESS_INFO_MANAGER, NULL); +} + +void +nautilus_progress_info_manager_add_new_info (NautilusProgressInfoManager *self, + NautilusProgressInfo *info) +{ + if (g_list_find (self->progress_infos, info) != NULL) + { + g_warning ("Adding two times the same progress info object to the manager"); + return; + } + + self->progress_infos = + g_list_prepend (self->progress_infos, g_object_ref (info)); + + g_signal_emit (self, signals[NEW_PROGRESS_INFO], 0, info); +} + +void +nautilus_progress_info_manager_remove_finished_or_cancelled_infos (NautilusProgressInfoManager *self) +{ + GList *l; + GList *next; + + l = self->progress_infos; + while (l != NULL) + { + next = l->next; + if (nautilus_progress_info_get_is_finished (l->data) || + nautilus_progress_info_get_is_cancelled (l->data)) + { + self->progress_infos = g_list_remove (self->progress_infos, + l->data); + } + l = next; + } +} + +GList * +nautilus_progress_info_manager_get_all_infos (NautilusProgressInfoManager *self) +{ + return self->progress_infos; +} + +gboolean +nautilus_progress_manager_are_all_infos_finished_or_cancelled (NautilusProgressInfoManager *self) +{ + GList *l; + + for (l = self->progress_infos; l != NULL; l = l->next) + { + if (!(nautilus_progress_info_get_is_finished (l->data) || + nautilus_progress_info_get_is_cancelled (l->data))) + { + return FALSE; + } + } + + return TRUE; +} + +static void +remove_viewer (NautilusProgressInfoManager *self, + GObject *viewer) +{ + self->current_viewers = g_list_remove (self->current_viewers, viewer); + + if (self->current_viewers == NULL) + { + g_signal_emit (self, signals[HAS_VIEWERS_CHANGED], 0); + } +} + +void +nautilus_progress_manager_add_viewer (NautilusProgressInfoManager *self, + GObject *viewer) +{ + GList *viewers; + + viewers = self->current_viewers; + if (g_list_find (viewers, viewer) == NULL) + { + g_object_weak_ref (viewer, (GWeakNotify) remove_viewer, self); + viewers = g_list_append (viewers, viewer); + self->current_viewers = viewers; + + if (g_list_length (viewers) == 1) + { + g_signal_emit (self, signals[HAS_VIEWERS_CHANGED], 0); + } + } +} + +void +nautilus_progress_manager_remove_viewer (NautilusProgressInfoManager *self, + GObject *viewer) +{ + if (g_list_find (self->current_viewers, viewer) != NULL) + { + g_object_weak_unref (viewer, (GWeakNotify) remove_viewer, self); + remove_viewer (self, viewer); + } +} + +gboolean +nautilus_progress_manager_has_viewers (NautilusProgressInfoManager *self) +{ + return self->current_viewers != NULL; +} diff --git a/src/nautilus-progress-info-manager.h b/src/nautilus-progress-info-manager.h new file mode 100644 index 0000000..9ffc6f4 --- /dev/null +++ b/src/nautilus-progress-info-manager.h @@ -0,0 +1,44 @@ +/* + * Nautilus + * + * Copyright (C) 2011 Red Hat, Inc. + * + * Nautilus is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License as + * published by the Free Software Foundation; either version 2 of the + * License, or (at your option) any later version. + * + * Nautilus is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public + * License along with this program; see the file COPYING. If not, + * see . + * + * Author: Cosimo Cecchi + */ + +#pragma once + +#include + +#include "nautilus-progress-info.h" + +#define NAUTILUS_TYPE_PROGRESS_INFO_MANAGER nautilus_progress_info_manager_get_type() +G_DECLARE_FINAL_TYPE (NautilusProgressInfoManager, nautilus_progress_info_manager, NAUTILUS, PROGRESS_INFO_MANAGER, GObject) + +NautilusProgressInfoManager* nautilus_progress_info_manager_dup_singleton (void); + +void nautilus_progress_info_manager_add_new_info (NautilusProgressInfoManager *self, + NautilusProgressInfo *info); +GList *nautilus_progress_info_manager_get_all_infos (NautilusProgressInfoManager *self); +void nautilus_progress_info_manager_remove_finished_or_cancelled_infos (NautilusProgressInfoManager *self); +gboolean nautilus_progress_manager_are_all_infos_finished_or_cancelled (NautilusProgressInfoManager *self); + +void nautilus_progress_manager_add_viewer (NautilusProgressInfoManager *self, GObject *viewer); +void nautilus_progress_manager_remove_viewer (NautilusProgressInfoManager *self, GObject *viewer); +gboolean nautilus_progress_manager_has_viewers (NautilusProgressInfoManager *self); + +G_END_DECLS \ No newline at end of file diff --git a/src/nautilus-progress-info-widget.c b/src/nautilus-progress-info-widget.c new file mode 100644 index 0000000..b5be930 --- /dev/null +++ b/src/nautilus-progress-info-widget.c @@ -0,0 +1,223 @@ +/* + * nautilus-progress-info-widget.h: file operation progress user interface. + * + * Copyright (C) 2007, 2011 Red Hat, Inc. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License as + * published by the Free Software Foundation; either version 2 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 warranty of + * MERCHANTABILITY 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 . + * + * Authors: Alexander Larsson + * Cosimo Cecchi + * + */ + +#include + +#include "nautilus-progress-info-widget.h" +struct _NautilusProgressInfoWidgetPrivate +{ + NautilusProgressInfo *info; + + GtkWidget *status; /* GtkLabel */ + GtkWidget *details; /* GtkLabel */ + GtkWidget *progress_bar; + GtkWidget *button; +}; + +enum +{ + PROP_INFO = 1, + NUM_PROPERTIES +}; + +static GParamSpec *properties[NUM_PROPERTIES] = { NULL }; + +G_DEFINE_TYPE_WITH_PRIVATE (NautilusProgressInfoWidget, nautilus_progress_info_widget, + GTK_TYPE_GRID); + +static void +info_finished (NautilusProgressInfoWidget *self) +{ + gtk_button_set_icon_name (GTK_BUTTON (self->priv->button), "object-select-symbolic"); + gtk_widget_set_sensitive (self->priv->button, FALSE); +} + +static void +info_cancelled (NautilusProgressInfoWidget *self) +{ + gtk_widget_set_sensitive (self->priv->button, FALSE); +} + +static void +update_data (NautilusProgressInfoWidget *self) +{ + char *status, *details; + char *markup; + + status = nautilus_progress_info_get_status (self->priv->info); + gtk_label_set_text (GTK_LABEL (self->priv->status), status); + g_free (status); + + details = nautilus_progress_info_get_details (self->priv->info); + markup = g_markup_printf_escaped ("%s", details); + gtk_label_set_markup (GTK_LABEL (self->priv->details), markup); + g_free (details); + g_free (markup); +} + +static void +update_progress (NautilusProgressInfoWidget *self) +{ + double progress; + + progress = nautilus_progress_info_get_progress (self->priv->info); + if (progress < 0) + { + gtk_progress_bar_pulse (GTK_PROGRESS_BAR (self->priv->progress_bar)); + } + else + { + gtk_progress_bar_set_fraction (GTK_PROGRESS_BAR (self->priv->progress_bar), progress); + } +} + +static void +button_clicked (GtkWidget *button, + NautilusProgressInfoWidget *self) +{ + if (!nautilus_progress_info_get_is_finished (self->priv->info)) + { + nautilus_progress_info_cancel (self->priv->info); + } +} + +static void +nautilus_progress_info_widget_dispose (GObject *obj) +{ + NautilusProgressInfoWidget *self = NAUTILUS_PROGRESS_INFO_WIDGET (obj); + + if (self->priv->info != NULL) + { + g_signal_handlers_disconnect_by_data (self->priv->info, self); + } + g_clear_object (&self->priv->info); + + G_OBJECT_CLASS (nautilus_progress_info_widget_parent_class)->dispose (obj); +} + +static void +nautilus_progress_info_widget_constructed (GObject *obj) +{ + NautilusProgressInfoWidget *self = NAUTILUS_PROGRESS_INFO_WIDGET (obj); + + G_OBJECT_CLASS (nautilus_progress_info_widget_parent_class)->constructed (obj); + + if (nautilus_progress_info_get_is_finished (self->priv->info)) + { + gtk_button_set_icon_name (GTK_BUTTON (self->priv->button), "object-select-symbolic"); + } + + gtk_widget_set_sensitive (self->priv->button, + !nautilus_progress_info_get_is_finished (self->priv->info) && + !nautilus_progress_info_get_is_cancelled (self->priv->info)); + + g_signal_connect_swapped (self->priv->info, + "changed", + G_CALLBACK (update_data), self); + g_signal_connect_swapped (self->priv->info, + "progress-changed", + G_CALLBACK (update_progress), self); + g_signal_connect_swapped (self->priv->info, + "finished", + G_CALLBACK (info_finished), self); + g_signal_connect_swapped (self->priv->info, + "cancelled", + G_CALLBACK (info_cancelled), self); + + update_data (self); + update_progress (self); +} + +static void +nautilus_progress_info_widget_set_property (GObject *object, + guint property_id, + const GValue *value, + GParamSpec *pspec) +{ + NautilusProgressInfoWidget *self = NAUTILUS_PROGRESS_INFO_WIDGET (object); + + switch (property_id) + { + case PROP_INFO: + { + self->priv->info = g_value_dup_object (value); + } + break; + + default: + { + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec); + } + break; + } +} + +static void +nautilus_progress_info_widget_init (NautilusProgressInfoWidget *self) +{ + self->priv = nautilus_progress_info_widget_get_instance_private (self); + + gtk_widget_init_template (GTK_WIDGET (self)); + + g_signal_connect (self->priv->button, "clicked", + G_CALLBACK (button_clicked), self); +} + +static void +nautilus_progress_info_widget_class_init (NautilusProgressInfoWidgetClass *klass) +{ + GObjectClass *oclass; + GtkWidgetClass *widget_class; + + widget_class = GTK_WIDGET_CLASS (klass); + oclass = G_OBJECT_CLASS (klass); + oclass->set_property = nautilus_progress_info_widget_set_property; + oclass->constructed = nautilus_progress_info_widget_constructed; + oclass->dispose = nautilus_progress_info_widget_dispose; + + properties[PROP_INFO] = + g_param_spec_object ("info", + "NautilusProgressInfo", + "The NautilusProgressInfo associated with this widget", + NAUTILUS_TYPE_PROGRESS_INFO, + G_PARAM_WRITABLE | G_PARAM_CONSTRUCT_ONLY | + G_PARAM_STATIC_STRINGS); + + g_object_class_install_properties (oclass, NUM_PROPERTIES, properties); + + gtk_widget_class_set_template_from_resource (widget_class, + "/org/gnome/nautilus/ui/nautilus-progress-info-widget.ui"); + + gtk_widget_class_bind_template_child_private (widget_class, NautilusProgressInfoWidget, status); + gtk_widget_class_bind_template_child_private (widget_class, NautilusProgressInfoWidget, details); + gtk_widget_class_bind_template_child_private (widget_class, NautilusProgressInfoWidget, progress_bar); + gtk_widget_class_bind_template_child_private (widget_class, NautilusProgressInfoWidget, button); +} + +GtkWidget * +nautilus_progress_info_widget_new (NautilusProgressInfo *info) +{ + return g_object_new (NAUTILUS_TYPE_PROGRESS_INFO_WIDGET, + "info", info, + NULL); +} diff --git a/src/nautilus-progress-info-widget.h b/src/nautilus-progress-info-widget.h new file mode 100644 index 0000000..62fe7ca --- /dev/null +++ b/src/nautilus-progress-info-widget.h @@ -0,0 +1,57 @@ +/* + * nautilus-progress-info-widget.h: file operation progress user interface. + * + * Copyright (C) 2007, 2011 Red Hat, Inc. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License as + * published by the Free Software Foundation; either version 2 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 warranty of + * MERCHANTABILITY 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 . + * + * Authors: Alexander Larsson + * Cosimo Cecchi + * + */ + +#pragma once + +#include + +#include "nautilus-progress-info.h" + +#define NAUTILUS_TYPE_PROGRESS_INFO_WIDGET nautilus_progress_info_widget_get_type() +#define NAUTILUS_PROGRESS_INFO_WIDGET(obj) \ + (G_TYPE_CHECK_INSTANCE_CAST ((obj), NAUTILUS_TYPE_PROGRESS_INFO_WIDGET, NautilusProgressInfoWidget)) +#define NAUTILUS_PROGRESS_INFO_WIDGET_CLASS(klass) \ + (G_TYPE_CHECK_CLASS_CAST ((klass), NAUTILUS_TYPE_PROGRESS_INFO_WIDGET, NautilusProgressInfoWidgetClass)) +#define NAUTILUS_IS_PROGRESS_INFO_WIDGET(obj) \ + (G_TYPE_CHECK_INSTANCE_TYPE ((obj), NAUTILUS_TYPE_PROGRESS_INFO_WIDGET)) +#define NAUTILUS_IS_PROGRESS_INFO_WIDGET_CLASS(klass) \ + (G_TYPE_CHECK_CLASS_TYPE ((klass), NAUTILUS_TYPE_PROGRESS_INFO_WIDGET)) +#define NAUTILUS_PROGRESS_INFO_WIDGET_GET_CLASS(obj) \ + (G_TYPE_INSTANCE_GET_CLASS ((obj), NAUTILUS_TYPE_PROGRESS_INFO_WIDGET, NautilusProgressInfoWidgetClass)) + +typedef struct _NautilusProgressInfoWidgetPrivate NautilusProgressInfoWidgetPrivate; + +typedef struct { + GtkGrid parent; + + /* private */ + NautilusProgressInfoWidgetPrivate *priv; +} NautilusProgressInfoWidget; + +typedef struct { + GtkGridClass parent_class; +} NautilusProgressInfoWidgetClass; + +GType nautilus_progress_info_widget_get_type (void); + +GtkWidget * nautilus_progress_info_widget_new (NautilusProgressInfo *info); \ No newline at end of file diff --git a/src/nautilus-progress-info.c b/src/nautilus-progress-info.c new file mode 100644 index 0000000..521bd71 --- /dev/null +++ b/src/nautilus-progress-info.c @@ -0,0 +1,760 @@ +/* + * nautilus-progress-info.h: file operation progress info. + * + * Copyright (C) 2007 Red Hat, Inc. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License as + * published by the Free Software Foundation; either version 2 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 warranty of + * MERCHANTABILITY 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 . + * + * Author: Alexander Larsson + */ + +#include +#include +#include +#include +#include +#include "nautilus-progress-info.h" +#include "nautilus-progress-info-manager.h" +#include "nautilus-icon-info.h" + +enum +{ + CHANGED, + PROGRESS_CHANGED, + STARTED, + FINISHED, + CANCELLED, + LAST_SIGNAL +}; + +#define SIGNAL_DELAY_MSEC 100 + +static guint signals[LAST_SIGNAL] = { 0 }; + +struct _NautilusProgressInfo +{ + GObject parent_instance; + + GCancellable *cancellable; + guint cancellable_id; + + GTimer *progress_timer; + + char *status; + char *details; + double progress; + gdouble remaining_time; + gdouble elapsed_time; + gboolean activity_mode; + gboolean started; + gboolean finished; + gboolean paused; + + GSource *idle_source; + gboolean source_is_now; + + gboolean start_at_idle; + gboolean finish_at_idle; + gboolean cancel_at_idle; + gboolean changed_at_idle; + gboolean progress_at_idle; + + GFile *destination; +}; + +G_LOCK_DEFINE_STATIC (progress_info); + +G_DEFINE_TYPE (NautilusProgressInfo, nautilus_progress_info, G_TYPE_OBJECT) + +static void set_details (NautilusProgressInfo *info, + const char *details); +static void set_status (NautilusProgressInfo *info, + const char *status); + +static void +nautilus_progress_info_finalize (GObject *object) +{ + NautilusProgressInfo *info; + + info = NAUTILUS_PROGRESS_INFO (object); + + g_free (info->status); + g_free (info->details); + g_clear_pointer (&info->progress_timer, g_timer_destroy); + g_cancellable_disconnect (info->cancellable, info->cancellable_id); + g_object_unref (info->cancellable); + g_clear_object (&info->destination); + + if (G_OBJECT_CLASS (nautilus_progress_info_parent_class)->finalize) + { + (*G_OBJECT_CLASS (nautilus_progress_info_parent_class)->finalize)(object); + } +} + +static void +nautilus_progress_info_dispose (GObject *object) +{ + NautilusProgressInfo *info; + + info = NAUTILUS_PROGRESS_INFO (object); + + G_LOCK (progress_info); + + /* Destroy source in dispose, because the callback + * could come here before the destroy, which should + * ressurect the object for a while */ + if (info->idle_source) + { + g_source_destroy (info->idle_source); + g_source_unref (info->idle_source); + info->idle_source = NULL; + } + G_UNLOCK (progress_info); +} + +static void +nautilus_progress_info_class_init (NautilusProgressInfoClass *klass) +{ + GObjectClass *gobject_class = G_OBJECT_CLASS (klass); + + gobject_class->finalize = nautilus_progress_info_finalize; + gobject_class->dispose = nautilus_progress_info_dispose; + + signals[CHANGED] = + g_signal_new ("changed", + NAUTILUS_TYPE_PROGRESS_INFO, + G_SIGNAL_RUN_LAST, + 0, + NULL, NULL, + g_cclosure_marshal_VOID__VOID, + G_TYPE_NONE, 0); + + signals[PROGRESS_CHANGED] = + g_signal_new ("progress-changed", + NAUTILUS_TYPE_PROGRESS_INFO, + G_SIGNAL_RUN_LAST, + 0, + NULL, NULL, + g_cclosure_marshal_VOID__VOID, + G_TYPE_NONE, 0); + + signals[STARTED] = + g_signal_new ("started", + NAUTILUS_TYPE_PROGRESS_INFO, + G_SIGNAL_RUN_LAST, + 0, + NULL, NULL, + g_cclosure_marshal_VOID__VOID, + G_TYPE_NONE, 0); + + signals[FINISHED] = + g_signal_new ("finished", + NAUTILUS_TYPE_PROGRESS_INFO, + G_SIGNAL_RUN_LAST, + 0, + NULL, NULL, + g_cclosure_marshal_VOID__VOID, + G_TYPE_NONE, 0); + + signals[CANCELLED] = + g_signal_new ("cancelled", + NAUTILUS_TYPE_PROGRESS_INFO, + G_SIGNAL_RUN_LAST, + 0, + NULL, NULL, + g_cclosure_marshal_VOID__VOID, + G_TYPE_NONE, 0); +} + +static gboolean +idle_callback (gpointer data) +{ + NautilusProgressInfo *info = data; + gboolean start_at_idle; + gboolean finish_at_idle; + gboolean changed_at_idle; + gboolean progress_at_idle; + gboolean cancelled_at_idle; + GSource *source; + + G_LOCK (progress_info); + + source = g_main_current_source (); + + /* Protect agains races where the source has + * been destroyed on another thread while it + * was being dispatched. + * Similar to what gdk_threads_add_idle does. + */ + if (g_source_is_destroyed (source)) + { + G_UNLOCK (progress_info); + return FALSE; + } + + /* We hadn't destroyed the source, so take a ref. + * This might ressurect the object from dispose, but + * that should be ok. + */ + g_object_ref (info); + + g_assert (source == info->idle_source); + + g_source_unref (source); + info->idle_source = NULL; + + start_at_idle = info->start_at_idle; + finish_at_idle = info->finish_at_idle; + changed_at_idle = info->changed_at_idle; + progress_at_idle = info->progress_at_idle; + cancelled_at_idle = info->cancel_at_idle; + + info->start_at_idle = FALSE; + info->finish_at_idle = FALSE; + info->changed_at_idle = FALSE; + info->progress_at_idle = FALSE; + info->cancel_at_idle = FALSE; + + G_UNLOCK (progress_info); + + if (start_at_idle) + { + g_signal_emit (info, + signals[STARTED], + 0); + } + + if (changed_at_idle) + { + g_signal_emit (info, + signals[CHANGED], + 0); + } + + if (progress_at_idle) + { + g_signal_emit (info, + signals[PROGRESS_CHANGED], + 0); + } + + if (finish_at_idle) + { + g_signal_emit (info, + signals[FINISHED], + 0); + } + + if (cancelled_at_idle) + { + g_signal_emit (info, + signals[CANCELLED], + 0); + } + + g_object_unref (info); + + return FALSE; +} + + +/* Called with lock held */ +static void +queue_idle (NautilusProgressInfo *info, + gboolean now) +{ + if (info->idle_source == NULL || + (now && !info->source_is_now)) + { + if (info->idle_source) + { + g_source_destroy (info->idle_source); + g_source_unref (info->idle_source); + info->idle_source = NULL; + } + + info->source_is_now = now; + if (now) + { + info->idle_source = g_idle_source_new (); + } + else + { + info->idle_source = g_timeout_source_new (SIGNAL_DELAY_MSEC); + } + g_source_set_callback (info->idle_source, idle_callback, info, NULL); + g_source_attach (info->idle_source, NULL); + } +} + +static void +on_canceled (GCancellable *cancellable, + NautilusProgressInfo *info) +{ + G_LOCK (progress_info); + set_details (info, _("Canceled")); + info->cancel_at_idle = TRUE; + g_timer_stop (info->progress_timer); + queue_idle (info, TRUE); + G_UNLOCK (progress_info); +} + +static void +nautilus_progress_info_init (NautilusProgressInfo *info) +{ + NautilusProgressInfoManager *manager; + + info->cancellable = g_cancellable_new (); + info->cancellable_id = g_cancellable_connect (info->cancellable, + G_CALLBACK (on_canceled), + info, + NULL); + + manager = nautilus_progress_info_manager_dup_singleton (); + nautilus_progress_info_manager_add_new_info (manager, info); + g_object_unref (manager); + info->progress_timer = g_timer_new (); +} + +NautilusProgressInfo * +nautilus_progress_info_new (void) +{ + NautilusProgressInfo *info; + + info = g_object_new (NAUTILUS_TYPE_PROGRESS_INFO, NULL); + + return info; +} + +char * +nautilus_progress_info_get_status (NautilusProgressInfo *info) +{ + char *res; + + G_LOCK (progress_info); + + if (info->status) + { + res = g_strdup (info->status); + } + else + { + res = g_strdup (_("Preparing")); + } + + G_UNLOCK (progress_info); + + return res; +} + +char * +nautilus_progress_info_get_details (NautilusProgressInfo *info) +{ + char *res; + + G_LOCK (progress_info); + + if (info->details) + { + res = g_strdup (info->details); + } + else + { + res = g_strdup (_("Preparing")); + } + + G_UNLOCK (progress_info); + + return res; +} + +double +nautilus_progress_info_get_progress (NautilusProgressInfo *info) +{ + double res; + + G_LOCK (progress_info); + + if (info->activity_mode) + { + res = -1.0; + } + else + { + res = info->progress; + } + + G_UNLOCK (progress_info); + + return res; +} + +void +nautilus_progress_info_cancel (NautilusProgressInfo *info) +{ + GCancellable *cancellable; + + cancellable = nautilus_progress_info_get_cancellable (info); + g_cancellable_cancel (cancellable); + g_object_unref (cancellable); +} + +GCancellable * +nautilus_progress_info_get_cancellable (NautilusProgressInfo *info) +{ + GCancellable *c; + + G_LOCK (progress_info); + + c = g_object_ref (info->cancellable); + + G_UNLOCK (progress_info); + + return c; +} + +gboolean +nautilus_progress_info_get_is_cancelled (NautilusProgressInfo *info) +{ + gboolean cancelled; + + G_LOCK (progress_info); + cancelled = g_cancellable_is_cancelled (info->cancellable); + G_UNLOCK (progress_info); + + return cancelled; +} + +gboolean +nautilus_progress_info_get_is_started (NautilusProgressInfo *info) +{ + gboolean res; + + G_LOCK (progress_info); + + res = info->started; + + G_UNLOCK (progress_info); + + return res; +} + +gboolean +nautilus_progress_info_get_is_finished (NautilusProgressInfo *info) +{ + gboolean res; + + G_LOCK (progress_info); + + res = info->finished; + + G_UNLOCK (progress_info); + + return res; +} + +gboolean +nautilus_progress_info_get_is_paused (NautilusProgressInfo *info) +{ + gboolean res; + + G_LOCK (progress_info); + + res = info->paused; + + G_UNLOCK (progress_info); + + return res; +} + +void +nautilus_progress_info_pause (NautilusProgressInfo *info) +{ + G_LOCK (progress_info); + + if (!info->paused) + { + info->paused = TRUE; + g_timer_stop (info->progress_timer); + } + + G_UNLOCK (progress_info); +} + +void +nautilus_progress_info_resume (NautilusProgressInfo *info) +{ + G_LOCK (progress_info); + + if (info->paused) + { + info->paused = FALSE; + g_timer_continue (info->progress_timer); + } + + G_UNLOCK (progress_info); +} + +void +nautilus_progress_info_start (NautilusProgressInfo *info) +{ + G_LOCK (progress_info); + + if (!info->started) + { + info->started = TRUE; + g_timer_start (info->progress_timer); + + info->start_at_idle = TRUE; + queue_idle (info, TRUE); + } + + G_UNLOCK (progress_info); +} + +void +nautilus_progress_info_finish (NautilusProgressInfo *info) +{ + G_LOCK (progress_info); + + if (!info->finished) + { + info->finished = TRUE; + g_timer_stop (info->progress_timer); + + info->finish_at_idle = TRUE; + queue_idle (info, TRUE); + } + + G_UNLOCK (progress_info); +} + +static void +set_status (NautilusProgressInfo *info, + const char *status) +{ + g_free (info->status); + info->status = g_strdup (status); + + info->changed_at_idle = TRUE; + queue_idle (info, FALSE); +} + +void +nautilus_progress_info_take_status (NautilusProgressInfo *info, + char *status) +{ + G_LOCK (progress_info); + + if (g_strcmp0 (info->status, status) != 0 && + !g_cancellable_is_cancelled (info->cancellable)) + { + set_status (info, status); + } + + G_UNLOCK (progress_info); + + g_free (status); +} + +void +nautilus_progress_info_set_status (NautilusProgressInfo *info, + const char *status) +{ + G_LOCK (progress_info); + + if (g_strcmp0 (info->status, status) != 0 && + !g_cancellable_is_cancelled (info->cancellable)) + { + set_status (info, status); + } + + G_UNLOCK (progress_info); +} + +static void +set_details (NautilusProgressInfo *info, + const char *details) +{ + g_free (info->details); + info->details = g_strdup (details); + + info->changed_at_idle = TRUE; + queue_idle (info, FALSE); +} + +void +nautilus_progress_info_take_details (NautilusProgressInfo *info, + char *details) +{ + G_LOCK (progress_info); + + if (g_strcmp0 (info->details, details) != 0 && + !g_cancellable_is_cancelled (info->cancellable)) + { + set_details (info, details); + } + + G_UNLOCK (progress_info); + + g_free (details); +} + +void +nautilus_progress_info_set_details (NautilusProgressInfo *info, + const char *details) +{ + G_LOCK (progress_info); + + if (g_strcmp0 (info->details, details) != 0 && + !g_cancellable_is_cancelled (info->cancellable)) + { + set_details (info, details); + } + + G_UNLOCK (progress_info); +} + +void +nautilus_progress_info_pulse_progress (NautilusProgressInfo *info) +{ + G_LOCK (progress_info); + + info->activity_mode = TRUE; + info->progress = 0.0; + info->progress_at_idle = TRUE; + queue_idle (info, FALSE); + + G_UNLOCK (progress_info); +} + +void +nautilus_progress_info_set_progress (NautilusProgressInfo *info, + double current, + double total) +{ + double current_percent; + + if (total <= 0) + { + current_percent = 1.0; + } + else + { + current_percent = current / total; + + if (current_percent < 0) + { + current_percent = 0; + } + + if (current_percent > 1.0) + { + current_percent = 1.0; + } + } + + G_LOCK (progress_info); + + if ((info->activity_mode || /* emit on switch from activity mode */ + fabs (current_percent - info->progress) > 0.005) && /* Emit on change of 0.5 percent */ + !g_cancellable_is_cancelled (info->cancellable)) + { + info->activity_mode = FALSE; + info->progress = current_percent; + info->progress_at_idle = TRUE; + queue_idle (info, FALSE); + } + + G_UNLOCK (progress_info); +} + +void +nautilus_progress_info_set_remaining_time (NautilusProgressInfo *info, + gdouble time) +{ + G_LOCK (progress_info); + info->remaining_time = time; + G_UNLOCK (progress_info); +} + +gdouble +nautilus_progress_info_get_remaining_time (NautilusProgressInfo *info) +{ + gint remaining_time; + + G_LOCK (progress_info); + remaining_time = info->remaining_time; + G_UNLOCK (progress_info); + + return remaining_time; +} + +void +nautilus_progress_info_set_elapsed_time (NautilusProgressInfo *info, + gdouble time) +{ + G_LOCK (progress_info); + info->elapsed_time = time; + G_UNLOCK (progress_info); +} + +gdouble +nautilus_progress_info_get_elapsed_time (NautilusProgressInfo *info) +{ + gint elapsed_time; + + G_LOCK (progress_info); + elapsed_time = info->elapsed_time; + G_UNLOCK (progress_info); + + return elapsed_time; +} + +gdouble +nautilus_progress_info_get_total_elapsed_time (NautilusProgressInfo *info) +{ + gdouble elapsed_time; + + G_LOCK (progress_info); + elapsed_time = g_timer_elapsed (info->progress_timer, NULL); + G_UNLOCK (progress_info); + + return elapsed_time; +} + +void +nautilus_progress_info_set_destination (NautilusProgressInfo *info, + GFile *file) +{ + G_LOCK (progress_info); + g_clear_object (&info->destination); + info->destination = g_object_ref (file); + G_UNLOCK (progress_info); +} + +GFile * +nautilus_progress_info_get_destination (NautilusProgressInfo *info) +{ + GFile *destination = NULL; + + G_LOCK (progress_info); + if (info->destination) + { + destination = g_object_ref (info->destination); + } + G_UNLOCK (progress_info); + + return destination; +} diff --git a/src/nautilus-progress-info.h b/src/nautilus-progress-info.h new file mode 100644 index 0000000..8f274b8 --- /dev/null +++ b/src/nautilus-progress-info.h @@ -0,0 +1,82 @@ +/* + nautilus-progress-info.h: file operation progress info. + + Copyright (C) 2007 Red Hat, Inc. + + This program is free software; you can redistribute it and/or + modify it under the terms of the GNU General Public License as + published by the Free Software Foundation; either version 2 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 warranty of + MERCHANTABILITY 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 . + + Author: Alexander Larsson +*/ + +#pragma once + +#include +#include + +#define NAUTILUS_TYPE_PROGRESS_INFO (nautilus_progress_info_get_type ()) + +G_DECLARE_FINAL_TYPE (NautilusProgressInfo, nautilus_progress_info, NAUTILUS, PROGRESS_INFO, GObject) + +/* Signals: + "changed" - status or details changed + "progress-changed" - the percentage progress changed (or we pulsed if in activity_mode + "started" - emited on job start + "finished" - emitted when job is done + + All signals are emitted from idles in main loop. + All methods are threadsafe. + */ + +NautilusProgressInfo *nautilus_progress_info_new (void); + +GList * nautilus_get_all_progress_info (void); + +char * nautilus_progress_info_get_status (NautilusProgressInfo *info); +char * nautilus_progress_info_get_details (NautilusProgressInfo *info); +double nautilus_progress_info_get_progress (NautilusProgressInfo *info); +GCancellable *nautilus_progress_info_get_cancellable (NautilusProgressInfo *info); +void nautilus_progress_info_cancel (NautilusProgressInfo *info); +gboolean nautilus_progress_info_get_is_started (NautilusProgressInfo *info); +gboolean nautilus_progress_info_get_is_finished (NautilusProgressInfo *info); +gboolean nautilus_progress_info_get_is_paused (NautilusProgressInfo *info); +gboolean nautilus_progress_info_get_is_cancelled (NautilusProgressInfo *info); + +void nautilus_progress_info_start (NautilusProgressInfo *info); +void nautilus_progress_info_finish (NautilusProgressInfo *info); +void nautilus_progress_info_pause (NautilusProgressInfo *info); +void nautilus_progress_info_resume (NautilusProgressInfo *info); +void nautilus_progress_info_set_status (NautilusProgressInfo *info, + const char *status); +void nautilus_progress_info_take_status (NautilusProgressInfo *info, + char *status); +void nautilus_progress_info_set_details (NautilusProgressInfo *info, + const char *details); +void nautilus_progress_info_take_details (NautilusProgressInfo *info, + char *details); +void nautilus_progress_info_set_progress (NautilusProgressInfo *info, + double current, + double total); +void nautilus_progress_info_pulse_progress (NautilusProgressInfo *info); + +void nautilus_progress_info_set_remaining_time (NautilusProgressInfo *info, + gdouble time); +gdouble nautilus_progress_info_get_remaining_time (NautilusProgressInfo *info); +void nautilus_progress_info_set_elapsed_time (NautilusProgressInfo *info, + gdouble time); +gdouble nautilus_progress_info_get_elapsed_time (NautilusProgressInfo *info); +gdouble nautilus_progress_info_get_total_elapsed_time (NautilusProgressInfo *info); + +void nautilus_progress_info_set_destination (NautilusProgressInfo *info, + GFile *file); +GFile *nautilus_progress_info_get_destination (NautilusProgressInfo *info); \ No newline at end of file diff --git a/src/nautilus-progress-persistence-handler.c b/src/nautilus-progress-persistence-handler.c new file mode 100644 index 0000000..b10795d --- /dev/null +++ b/src/nautilus-progress-persistence-handler.c @@ -0,0 +1,384 @@ +/* + * nautilus-progress-persistence-handler.c: file operation progress notification handler. + * + * Copyright (C) 2007, 2011, 2015 Red Hat, Inc. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License as + * published by the Free Software Foundation; either version 2 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 warranty of + * MERCHANTABILITY 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 . + * + * Authors: Alexander Larsson + * Cosimo Cecchi + * Carlos Soriano + * + */ + +#include + +#include "nautilus-progress-persistence-handler.h" + +#include "nautilus-application.h" +#include "nautilus-progress-info-widget.h" + +#include + +#include "nautilus-progress-info.h" +#include "nautilus-progress-info-manager.h" + +struct _NautilusProgressPersistenceHandler +{ + GObject parent_instance; + + NautilusProgressInfoManager *manager; + + NautilusApplication *app; + guint active_infos; +}; + +G_DEFINE_TYPE (NautilusProgressPersistenceHandler, nautilus_progress_persistence_handler, G_TYPE_OBJECT); + +/* Our policy for showing progress notification is the following: + * - file operations that end within two seconds do not get notified in any way + * - if no file operations are running, and one passes the two seconds + * timeout, a window is displayed with the progress + * - if the window is closed, we show a resident notification, depending on + * the capabilities of the notification daemon running in the session + * - if some file operations are running, and another one passes the two seconds + * timeout, and the window is showing, we add it to the window directly + * - in the same case, but when the window is not showing, we update the resident + * notification, changing its message + * - when one file operation finishes, if it's not the last one, we only update the + * resident notification's message + * - in the same case, if it's the last one, we close the resident notification, + * and trigger a transient one + * - in the same case, but the window was showing, we just hide the window + */ + +static gboolean server_has_persistence (void); + +static void +show_file_transfers (NautilusProgressPersistenceHandler *self) +{ + GFile *home; + + home = g_file_new_for_path (g_get_home_dir ()); + nautilus_application_open_location (self->app, home, NULL, NULL); + + g_object_unref (home); +} + +static void +action_show_file_transfers (GSimpleAction *action, + GVariant *parameter, + gpointer user_data) +{ + NautilusProgressPersistenceHandler *self; + + self = NAUTILUS_PROGRESS_PERSISTENCE_HANDLER (user_data); + show_file_transfers (self); +} + +static GActionEntry progress_persistence_entries[] = +{ + { "show-file-transfers", action_show_file_transfers, NULL, NULL, NULL } +}; + +static void +progress_persistence_handler_update_notification (NautilusProgressPersistenceHandler *self) +{ + GNotification *notification; + gchar *body; + + if (!server_has_persistence ()) + { + return; + } + + notification = g_notification_new (_("File Operations")); + g_notification_set_default_action (notification, "app.show-file-transfers"); + g_notification_add_button (notification, _("Show Details"), + "app.show-file-transfers"); + + body = g_strdup_printf (ngettext ("%'d file operation active", + "%'d file operations active", + self->active_infos), + self->active_infos); + g_notification_set_body (notification, body); + + nautilus_application_send_notification (self->app, + "progress", notification); + + g_object_unref (notification); + g_free (body); +} + +void +nautilus_progress_persistence_handler_make_persistent (NautilusProgressPersistenceHandler *self) +{ + GList *windows; + + windows = nautilus_application_get_windows (self->app); + if (self->active_infos > 0 && windows == NULL) + { + progress_persistence_handler_update_notification (self); + } +} + +static void +progress_persistence_handler_show_complete_notification (NautilusProgressPersistenceHandler *self) +{ + GNotification *complete_notification; + + if (!server_has_persistence ()) + { + return; + } + + complete_notification = g_notification_new (_("File Operations")); + g_notification_set_body (complete_notification, + _("All file operations have been completed")); + nautilus_application_send_notification (self->app, + "transfer-complete", + complete_notification); + + g_object_unref (complete_notification); +} + +static void +progress_persistence_handler_hide_notification (NautilusProgressPersistenceHandler *self) +{ + if (!server_has_persistence ()) + { + return; + } + + nautilus_application_withdraw_notification (self->app, + "progress"); +} + +static void +progress_info_finished_cb (NautilusProgressInfo *info, + NautilusProgressPersistenceHandler *self) +{ + GtkWindow *last_active_window; + + self->active_infos--; + + last_active_window = gtk_application_get_active_window (GTK_APPLICATION (self->app)); + + if (self->active_infos > 0) + { + if (last_active_window == NULL) + { + progress_persistence_handler_update_notification (self); + } + } + else + { + if ((last_active_window == NULL) || !gtk_window_is_active (last_active_window)) + { + progress_persistence_handler_hide_notification (self); + progress_persistence_handler_show_complete_notification (self); + } + } +} + +static void +handle_new_progress_info (NautilusProgressPersistenceHandler *self, + NautilusProgressInfo *info) +{ + GList *windows; + g_signal_connect (info, "finished", + G_CALLBACK (progress_info_finished_cb), self); + + self->active_infos++; + windows = nautilus_application_get_windows (self->app); + + if (windows == NULL) + { + progress_persistence_handler_update_notification (self); + } +} + +typedef struct +{ + NautilusProgressInfo *info; + NautilusProgressPersistenceHandler *self; +} TimeoutData; + +static void +timeout_data_free (TimeoutData *data) +{ + g_clear_object (&data->self); + g_clear_object (&data->info); + + g_slice_free (TimeoutData, data); +} + +static TimeoutData * +timeout_data_new (NautilusProgressPersistenceHandler *self, + NautilusProgressInfo *info) +{ + TimeoutData *retval; + + retval = g_slice_new0 (TimeoutData); + retval->self = g_object_ref (self); + retval->info = g_object_ref (info); + + return retval; +} + +static gboolean +new_op_started_timeout (TimeoutData *data) +{ + NautilusProgressInfo *info = data->info; + NautilusProgressPersistenceHandler *self = data->self; + + if (nautilus_progress_info_get_is_paused (info)) + { + return TRUE; + } + + if (!nautilus_progress_info_get_is_finished (info)) + { + handle_new_progress_info (self, info); + } + + timeout_data_free (data); + + return FALSE; +} + +static void +release_application (NautilusProgressInfo *info, + NautilusProgressPersistenceHandler *self) +{ + /* release the GApplication hold we acquired */ + g_application_release (g_application_get_default ()); +} + +static void +progress_info_started_cb (NautilusProgressInfo *info, + NautilusProgressPersistenceHandler *self) +{ + TimeoutData *data; + + /* hold GApplication so we never quit while there's an operation pending */ + g_application_hold (g_application_get_default ()); + + g_signal_connect (info, "finished", + G_CALLBACK (release_application), self); + + data = timeout_data_new (self, info); + + /* timeout for the progress window to appear */ + g_timeout_add_seconds (2, + (GSourceFunc) new_op_started_timeout, + data); +} + +static void +new_progress_info_cb (NautilusProgressInfoManager *manager, + NautilusProgressInfo *info, + NautilusProgressPersistenceHandler *self) +{ + g_signal_connect (info, "started", + G_CALLBACK (progress_info_started_cb), self); +} + +static void +nautilus_progress_persistence_handler_dispose (GObject *obj) +{ + NautilusProgressPersistenceHandler *self = NAUTILUS_PROGRESS_PERSISTENCE_HANDLER (obj); + + g_clear_object (&self->manager); + + G_OBJECT_CLASS (nautilus_progress_persistence_handler_parent_class)->dispose (obj); +} + +static gboolean +server_has_persistence (void) +{ + static gboolean retval = FALSE; + GDBusConnection *conn; + GVariant *result; + char **cap, **caps; + static gboolean initialized = FALSE; + + if (initialized) + { + return retval; + } + initialized = TRUE; + + conn = g_application_get_dbus_connection (g_application_get_default ()); + result = g_dbus_connection_call_sync (conn, + "org.freedesktop.Notifications", + "/org/freedesktop/Notifications", + "org.freedesktop.Notifications", + "GetCapabilities", + g_variant_new ("()"), + G_VARIANT_TYPE ("(as)"), + G_DBUS_CALL_FLAGS_NONE, + -1, NULL, NULL); + + if (result == NULL) + { + return FALSE; + } + + g_variant_get (result, "(^a&s)", &caps); + + for (cap = caps; *cap != NULL; cap++) + { + if (g_strcmp0 ("persistence", *cap) == 0) + { + retval = TRUE; + } + } + + g_free (caps); + g_variant_unref (result); + + return retval; +} + +static void +nautilus_progress_persistence_handler_init (NautilusProgressPersistenceHandler *self) +{ + self->manager = nautilus_progress_info_manager_dup_singleton (); + g_signal_connect (self->manager, "new-progress-info", + G_CALLBACK (new_progress_info_cb), self); +} + +static void +nautilus_progress_persistence_handler_class_init (NautilusProgressPersistenceHandlerClass *klass) +{ + GObjectClass *oclass; + + oclass = G_OBJECT_CLASS (klass); + oclass->dispose = nautilus_progress_persistence_handler_dispose; +} + +NautilusProgressPersistenceHandler * +nautilus_progress_persistence_handler_new (GObject *app) +{ + NautilusProgressPersistenceHandler *self; + + self = g_object_new (NAUTILUS_TYPE_PROGRESS_PERSISTENCE_HANDLER, NULL); + self->app = NAUTILUS_APPLICATION (app); + + g_action_map_add_action_entries (G_ACTION_MAP (self->app), + progress_persistence_entries, G_N_ELEMENTS (progress_persistence_entries), + self); + return self; +} diff --git a/src/nautilus-progress-persistence-handler.h b/src/nautilus-progress-persistence-handler.h new file mode 100644 index 0000000..3fce9df --- /dev/null +++ b/src/nautilus-progress-persistence-handler.h @@ -0,0 +1,37 @@ +/* + * nautilus-progress-persistence-handler.h: file operation progress systray icon or notification handler. + * + * Copyright (C) 2007, 2011 Red Hat, Inc. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License as + * published by the Free Software Foundation; either version 2 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 warranty of + * MERCHANTABILITY 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 . + * + * Authors: Alexander Larsson + * Cosimo Cecchi + * + */ + +#pragma once + +#include + +G_BEGIN_DECLS + +#define NAUTILUS_TYPE_PROGRESS_PERSISTENCE_HANDLER nautilus_progress_persistence_handler_get_type() +G_DECLARE_FINAL_TYPE (NautilusProgressPersistenceHandler, nautilus_progress_persistence_handler, NAUTILUS, PROGRESS_PERSISTENCE_HANDLER, GObject) + +/* @app is actually a NautilusApplication, but we need to avoid circular dependencies */ +NautilusProgressPersistenceHandler * nautilus_progress_persistence_handler_new (GObject *app); +void nautilus_progress_persistence_handler_make_persistent (NautilusProgressPersistenceHandler *self); + +G_END_DECLS \ No newline at end of file diff --git a/src/nautilus-properties-window.c b/src/nautilus-properties-window.c new file mode 100644 index 0000000..32fd42e --- /dev/null +++ b/src/nautilus-properties-window.c @@ -0,0 +1,4457 @@ +/* fm-properties-window.c - window that lets user modify file properties + * + * Copyright (C) 2000 Eazel, Inc. + * + * The Gnome Library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Library General Public License as + * published by the Free Software Foundation; either version 2 of the + * License, or (at your option) any later version. + * + * The Gnome Library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Library General Public License for more details. + * + * You should have received a copy of the GNU Library General Public + * License along with the Gnome Library; see the file COPYING.LIB. If not, + * see . + * + * Authors: Darin Adler + */ + +#include "nautilus-properties-window.h" + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#define GNOME_DESKTOP_USE_UNSTABLE_API +#include + +#include "nautilus-application.h" +#include "nautilus-dbus-launcher.h" +#include "nautilus-enums.h" +#include "nautilus-error-reporting.h" +#include "nautilus-file-operations.h" +#include "nautilus-file-utilities.h" +#include "nautilus-global-preferences.h" +#include "nautilus-icon-info.h" +#include "nautilus-metadata.h" +#include "nautilus-mime-actions.h" +#include "nautilus-module.h" +#include "nautilus-properties-model.h" +#include "nautilus-properties-item.h" +#include "nautilus-signaller.h" +#include "nautilus-tag-manager.h" +#include "nautilus-ui-utilities.h" + +static GHashTable *pending_lists; + +typedef struct +{ + NautilusFile *file; + char *owner; + GtkWindow *window; + unsigned int timeout; + gboolean cancelled; +} OwnerChange; + +typedef struct +{ + NautilusFile *file; + char *group; + GtkWindow *window; + unsigned int timeout; + gboolean cancelled; +} GroupChange; + +struct _NautilusPropertiesWindow +{ + AdwWindow parent_instance; + + GList *original_files; + GList *target_files; + + AdwWindowTitle *window_title; + + GtkStack *page_stack; + + /* Basic page */ + + GtkStack *icon_stack; + GtkWidget *icon_image; + GtkWidget *icon_button; + GtkWidget *icon_button_image; + GtkWidget *icon_chooser; + + GtkWidget *star_button; + + GtkLabel *name_value_label; + GtkWidget *type_value_label; + GtkLabel *type_file_system_label; + GtkWidget *size_value_label; + GtkWidget *contents_box; + GtkWidget *contents_value_label; + GtkWidget *free_space_value_label; + + GtkWidget *disk_list_box; + GtkLevelBar *disk_space_level_bar; + GtkWidget *disk_space_used_value; + GtkWidget *disk_space_free_value; + GtkWidget *disk_space_capacity_value; + + GtkWidget *locations_list_box; + GtkWidget *link_target_row; + GtkWidget *link_target_value_label; + GtkWidget *contents_spinner; + guint update_directory_contents_timeout_id; + guint update_files_timeout_id; + GtkWidget *parent_folder_row; + GtkWidget *parent_folder_value_label; + + GtkWidget *trashed_list_box; + GtkWidget *trashed_on_value_label; + GtkWidget *original_folder_value_label; + + GtkWidget *times_list_box; + GtkWidget *modified_row; + GtkWidget *modified_value_label; + GtkWidget *created_row; + GtkWidget *created_value_label; + GtkWidget *accessed_row; + GtkWidget *accessed_value_label; + + GtkWidget *permissions_navigation_row; + GtkWidget *permissions_value_label; + + GtkWidget *extension_models_list_box; + + /* Permissions page */ + + GtkWidget *permissions_stack; + + GtkWidget *unknown_permissions_page; + + GtkWidget *bottom_prompt_seperator; + GtkWidget *not_the_owner_label; + + AdwComboRow *owner_row; + AdwComboRow *owner_access_row; + AdwComboRow *owner_folder_access_row; + AdwComboRow *owner_file_access_row; + + AdwComboRow *group_row; + AdwComboRow *group_access_row; + AdwComboRow *group_folder_access_row; + AdwComboRow *group_file_access_row; + + AdwComboRow *others_access_row; + AdwComboRow *others_folder_access_row; + AdwComboRow *others_file_access_row; + + AdwComboRow *execution_row; + GtkSwitch *execution_switch; + + GtkWidget *security_context_list_box; + GtkWidget *security_context_value_label; + + GtkWidget *change_permissions_button_box; + GtkWidget *change_permissions_button; + + GroupChange *group_change; + OwnerChange *owner_change; + + GList *permission_rows; + GList *change_permission_combos; + GHashTable *initial_permissions; + gboolean has_recursive_apply; + + GList *value_fields; + + GList *mime_list; + + gboolean deep_count_finished; + GList *deep_count_files; + guint deep_count_spinner_timeout_id; + + guint long_operation_underway; + + GList *changed_files; + + guint64 volume_capacity; + guint64 volume_free; + guint64 volume_used; +}; + +typedef enum +{ + NO_FILES_OR_FOLDERS = (0), + FILES_ONLY = (1 << 0), + FOLDERS_ONLY = (1 << 1), + FILES_AND_FOLDERS = FILES_ONLY | FOLDERS_ONLY, +} FilterType; + +enum +{ + UNIX_PERM_SUID = S_ISUID, + UNIX_PERM_SGID = S_ISGID, + UNIX_PERM_STICKY = 01000, /* S_ISVTX not defined on all systems */ + UNIX_PERM_USER_READ = S_IRUSR, + UNIX_PERM_USER_WRITE = S_IWUSR, + UNIX_PERM_USER_EXEC = S_IXUSR, + UNIX_PERM_USER_ALL = S_IRUSR | S_IWUSR | S_IXUSR, + UNIX_PERM_GROUP_READ = S_IRGRP, + UNIX_PERM_GROUP_WRITE = S_IWGRP, + UNIX_PERM_GROUP_EXEC = S_IXGRP, + UNIX_PERM_GROUP_ALL = S_IRGRP | S_IWGRP | S_IXGRP, + UNIX_PERM_OTHER_READ = S_IROTH, + UNIX_PERM_OTHER_WRITE = S_IWOTH, + UNIX_PERM_OTHER_EXEC = S_IXOTH, + UNIX_PERM_OTHER_ALL = S_IROTH | S_IWOTH | S_IXOTH +}; + +typedef enum +{ + PERMISSION_NONE = (0), + PERMISSION_READ = (1 << 0), + PERMISSION_WRITE = (1 << 1), + PERMISSION_EXEC = (1 << 2), + PERMISSION_INCONSISTENT = (1 << 3) +} PermissionValue; + +typedef enum +{ + PERMISSION_USER, + PERMISSION_GROUP, + PERMISSION_OTHER, + NUM_PERMISSION_TYPE +} PermissionType; + +/** Contains permissions for files and folders for each PermissionType */ +typedef struct +{ + NautilusPropertiesWindow *window; + + PermissionValue folder_permissions[NUM_PERMISSION_TYPE]; + PermissionValue file_permissions[NUM_PERMISSION_TYPE]; + PermissionValue file_exec_permissions; + gboolean has_files; + gboolean has_folders; + gboolean can_set_all_folder_permission; + gboolean can_set_all_file_permission; + gboolean can_set_any_file_permission; + gboolean is_multi_file_window; +} TargetPermissions; + +/* NautilusPermissionEntry - helper struct for permission AdwComboRow */ + +#define NAUTILUS_TYPE_PERMISSION_ENTRY (nautilus_permission_entry_get_type ()) +G_DECLARE_FINAL_TYPE (NautilusPermissionEntry, nautilus_permission_entry, + NAUTILUS, PERMISSION_ENTRY, GObject) + +enum +{ + PROP_NAME = 1, + NUM_PROPERTIES +}; + +struct _NautilusPermissionEntry +{ + GObject parent; + + char *name; + PermissionValue permission_value; +}; + +G_DEFINE_TYPE (NautilusPermissionEntry, + nautilus_permission_entry, + G_TYPE_OBJECT) + +static void +nautilus_permission_entry_init (NautilusPermissionEntry *self) +{ + self->name = NULL; + self->permission_value = PERMISSION_NONE; +} + +static void +nautilus_permission_entry_finalize (GObject *object) +{ + NautilusPermissionEntry *self = NAUTILUS_PERMISSION_ENTRY (object); + + g_free (self->name); + + G_OBJECT_CLASS (nautilus_permission_entry_parent_class)->finalize (object); +} + +static void +nautilus_permission_entry_get_property (GObject *object, + guint prop_id, + GValue *value, + GParamSpec *pspec) +{ + NautilusPermissionEntry *self = NAUTILUS_PERMISSION_ENTRY (object); + + switch (prop_id) + { + case PROP_NAME: + { + g_value_set_string (value, self->name); + } + break; + + default: + { + g_assert_not_reached (); + } + break; + } +} + +static void +nautilus_permission_entry_class_init (NautilusPermissionEntryClass *klass) +{ + GObjectClass *gobject_class = G_OBJECT_CLASS (klass); + + gobject_class->finalize = nautilus_permission_entry_finalize; + gobject_class->get_property = nautilus_permission_entry_get_property; + + g_object_class_install_property (gobject_class, + PROP_NAME, + g_param_spec_string ("name", "", "", + NULL, + G_PARAM_READABLE)); +} + +static gchar * +permission_value_to_string (PermissionValue permission_value, + gboolean describes_folder) +{ + if (permission_value & PERMISSION_INCONSISTENT) + { + return "---"; + } + else if (permission_value & PERMISSION_READ) + { + if (permission_value & PERMISSION_WRITE) + { + if (!describes_folder) + { + return _("Read and write"); + } + else if (permission_value & PERMISSION_EXEC) + { + return _("Create and delete files"); + } + else + { + return _("Read/write, no access"); + } + } + else + { + if (!describes_folder) + { + return _("Read-only"); + } + else if (permission_value & PERMISSION_EXEC) + { + return _("Access files"); + } + else + { + return _("List files only"); + } + } + } + else + { + if (permission_value & PERMISSION_WRITE) + { + if (!describes_folder || permission_value & PERMISSION_EXEC) + { + return _("Write-only"); + } + else + { + return _("Write-only, no access"); + } + } + else + { + if (describes_folder && permission_value & PERMISSION_EXEC) + { + return _("Access-only"); + } + else + { + /* Translators: this is referred to the permissions the user has in a directory. */ + return _("None"); + } + } + } +} + +/* end NautilusPermissionEntry */ + +enum +{ + COLUMN_NAME, + COLUMN_VALUE, + COLUMN_USE_ORIGINAL, + COLUMN_ID, + NUM_COLUMNS +}; + +typedef struct +{ + GList *original_files; + GList *target_files; + GtkWidget *parent_widget; + GtkWindow *parent_window; + char *startup_id; + char *pending_key; + GHashTable *pending_files; + NautilusPropertiesWindowCallback callback; + gpointer callback_data; + NautilusPropertiesWindow *window; + gboolean cancelled; +} StartupData; + +#define DIRECTORY_CONTENTS_UPDATE_INTERVAL 200 /* milliseconds */ +#define FILES_UPDATE_INTERVAL 200 /* milliseconds */ + +/* + * A timeout before changes through the user/group combo box will be applied. + * When quickly changing owner/groups (i.e. by keyboard or scroll wheel), + * this ensures that the GUI doesn't end up unresponsive. + * + * Both combos react on changes by scheduling a new change and unscheduling + * or cancelling old pending changes. + */ +#define CHOWN_CHGRP_TIMEOUT 300 /* milliseconds */ + +static void schedule_directory_contents_update (NautilusPropertiesWindow *self); +static void directory_contents_value_field_update (NautilusPropertiesWindow *self); +static void file_changed_callback (NautilusFile *file, + gpointer user_data); +static void update_execution_row (GtkWidget *row, + TargetPermissions *target_perm); +static void update_permission_row (AdwComboRow *row, + TargetPermissions *target_perm); +static void value_field_update (GtkLabel *field, + NautilusPropertiesWindow *self); +static void properties_window_update (NautilusPropertiesWindow *self, + GList *files); +static void is_directory_ready_callback (NautilusFile *file, + gpointer data); +static void cancel_group_change_callback (GroupChange *change); +static void cancel_owner_change_callback (OwnerChange *change); +static void update_owner_row (AdwComboRow *row, + TargetPermissions *target_perm); +static void update_group_row (AdwComboRow *row, + TargetPermissions *target_perm); +static void select_image_button_callback (GtkWidget *widget, + NautilusPropertiesWindow *self); +static void set_icon (const char *icon_path, + NautilusPropertiesWindow *self); +static void remove_pending (StartupData *data, + gboolean cancel_call_when_ready, + gboolean cancel_timed_wait); +static void refresh_extension_model_pages (NautilusPropertiesWindow *self); +static gboolean is_root_directory (NautilusFile *file); + +G_DEFINE_TYPE (NautilusPropertiesWindow, nautilus_properties_window, ADW_TYPE_WINDOW); + +static gboolean +is_multi_file_window (NautilusPropertiesWindow *self) +{ + GList *l; + int count; + + count = 0; + + for (l = self->original_files; l != NULL; l = l->next) + { + if (!nautilus_file_is_gone (NAUTILUS_FILE (l->data))) + { + count++; + if (count > 1) + { + return TRUE; + } + } + } + + return FALSE; +} + +static NautilusFile * +get_original_file (NautilusPropertiesWindow *self) +{ + g_return_val_if_fail (!is_multi_file_window (self), NULL); + + if (self->original_files == NULL) + { + return NULL; + } + + return NAUTILUS_FILE (self->original_files->data); +} + +static NautilusFile * +get_target_file_for_original_file (NautilusFile *file) +{ + NautilusFile *target_file; + g_autoptr (GFile) location = NULL; + g_autofree char *uri_to_display = NULL; + + uri_to_display = nautilus_file_get_uri (file); + location = g_file_new_for_uri (uri_to_display); + target_file = nautilus_file_get (location); + + return target_file; +} + +static NautilusFile * +get_target_file (NautilusPropertiesWindow *self) +{ + return NAUTILUS_FILE (self->target_files->data); +} + +static void +navigate_main_page (NautilusPropertiesWindow *self, + GParamSpec *params, + GtkWidget *widget) +{ + gtk_stack_set_visible_child_name (self->page_stack, "main"); +} + +static void +navigate_permissions_page (NautilusPropertiesWindow *self, + GParamSpec *params, + GtkWidget *widget) +{ + gtk_stack_set_visible_child_name (self->page_stack, "permissions"); +} + +static void +navigate_extension_model_page (NautilusPropertiesWindow *self, + GParamSpec *params, + AdwPreferencesRow *row) +{ + gtk_stack_set_visible_child (self->page_stack, + g_object_get_data (G_OBJECT (row), + "nautilus-extension-properties-page")); +} + +static void +get_image_for_properties_window (NautilusPropertiesWindow *self, + char **icon_name, + GdkPaintable **icon_paintable) +{ + g_autoptr (NautilusIconInfo) icon = NULL; + GList *l; + gint icon_scale; + + icon_scale = gtk_widget_get_scale_factor (GTK_WIDGET (self)); + + for (l = self->original_files; l != NULL; l = l->next) + { + NautilusFile *file; + g_autoptr (NautilusIconInfo) new_icon = NULL; + + file = NAUTILUS_FILE (l->data); + + if (!icon) + { + icon = nautilus_file_get_icon (file, NAUTILUS_GRID_ICON_SIZE_MEDIUM, icon_scale, + NAUTILUS_FILE_ICON_FLAGS_USE_THUMBNAILS | + NAUTILUS_FILE_ICON_FLAGS_USE_MOUNT_ICON); + } + else + { + new_icon = nautilus_file_get_icon (file, NAUTILUS_GRID_ICON_SIZE_MEDIUM, icon_scale, + NAUTILUS_FILE_ICON_FLAGS_USE_THUMBNAILS | + NAUTILUS_FILE_ICON_FLAGS_USE_MOUNT_ICON); + if (!new_icon || new_icon != icon) + { + g_object_unref (icon); + icon = NULL; + break; + } + } + } + + if (!icon) + { + g_autoptr (GIcon) gicon = g_themed_icon_new ("text-x-generic"); + + icon = nautilus_icon_info_lookup (gicon, NAUTILUS_GRID_ICON_SIZE_MEDIUM, icon_scale); + } + + if (icon_name != NULL) + { + *icon_name = g_strdup (nautilus_icon_info_get_used_name (icon)); + } + + if (icon_paintable != NULL) + { + *icon_paintable = nautilus_icon_info_get_paintable (icon); + } +} + + +static void +update_properties_window_icon (NautilusPropertiesWindow *self) +{ + g_autoptr (GdkPaintable) paintable = NULL; + g_autofree char *name = NULL; + gint pixel_size; + + get_image_for_properties_window (self, &name, &paintable); + + if (name != NULL) + { + gtk_window_set_icon_name (GTK_WINDOW (self), name); + } + + pixel_size = MAX (gdk_paintable_get_intrinsic_width (paintable), + gdk_paintable_get_intrinsic_width (paintable)); + + gtk_image_set_from_paintable (GTK_IMAGE (self->icon_image), paintable); + gtk_image_set_from_paintable (GTK_IMAGE (self->icon_button_image), paintable); + gtk_image_set_pixel_size (GTK_IMAGE (self->icon_image), pixel_size); + gtk_image_set_pixel_size (GTK_IMAGE (self->icon_button_image), pixel_size); +} + +/* utility to test if a uri refers to a local image */ +static gboolean +uri_is_local_image (const char *uri) +{ + g_autoptr (GdkPixbuf) pixbuf = NULL; + g_autofree char *image_path = NULL; + + image_path = g_filename_from_uri (uri, NULL, NULL); + if (image_path == NULL) + { + return FALSE; + } + + pixbuf = gdk_pixbuf_new_from_file (image_path, NULL); + + if (pixbuf == NULL) + { + return FALSE; + } + + return TRUE; +} + +static void +reset_icon (NautilusPropertiesWindow *self) +{ + GList *l; + + for (l = self->original_files; l != NULL; l = l->next) + { + NautilusFile *file; + + file = NAUTILUS_FILE (l->data); + + nautilus_file_set_metadata (file, + NAUTILUS_METADATA_KEY_CUSTOM_ICON, + NULL, NULL); + } +} + +static void +nautilus_properties_window_drag_drop_cb (GtkDropTarget *target, + const GValue *value, + gdouble x, + gdouble y, + gpointer user_data) +{ + GSList *file_list; + gboolean exactly_one; + GtkImage *image; + GtkWindow *window; + + image = GTK_IMAGE (user_data); + window = GTK_WINDOW (gtk_widget_get_root (GTK_WIDGET (image))); + + if (!G_VALUE_HOLDS (value, GDK_TYPE_FILE_LIST)) + { + return; + } + + file_list = g_value_get_boxed (value); + exactly_one = file_list != NULL && g_slist_next (file_list) == NULL; + + if (!exactly_one) + { + show_dialog (_("You cannot assign more than one custom icon at a time!"), + _("Please drop just one image to set a custom icon."), + window, + GTK_MESSAGE_ERROR); + } + else + { + g_autofree gchar *uri = g_file_get_uri (file_list->data); + + if (uri_is_local_image (uri)) + { + set_icon (uri, NAUTILUS_PROPERTIES_WINDOW (window)); + } + else + { + if (!g_file_is_native (file_list->data)) + { + show_dialog (_("The file that you dropped is not local."), + _("You can only use local images as custom icons."), + window, + GTK_MESSAGE_ERROR); + } + else + { + show_dialog (_("The file that you dropped is not an image."), + _("You can only use local images as custom icons."), + window, + GTK_MESSAGE_ERROR); + } + } + } +} + +static void +star_clicked (NautilusPropertiesWindow *self) +{ + NautilusTagManager *tag_manager = nautilus_tag_manager_get (); + NautilusFile *file = get_original_file (self); + g_autofree gchar *uri = nautilus_file_get_uri (file); + + if (nautilus_tag_manager_file_is_starred (tag_manager, uri)) + { + nautilus_tag_manager_unstar_files (tag_manager, G_OBJECT (self), + &(GList){ file, NULL }, NULL, NULL); + } + else + { + nautilus_tag_manager_star_files (tag_manager, G_OBJECT (self), + &(GList){ file, NULL }, NULL, NULL); + } +} + +static void +update_star (NautilusPropertiesWindow *self, + NautilusTagManager *tag_manager) +{ + gboolean is_starred; + g_autofree gchar *file_uri = NULL; + + file_uri = nautilus_file_get_uri (get_target_file (self)); + is_starred = nautilus_tag_manager_file_is_starred (tag_manager, file_uri); + + gtk_button_set_icon_name (GTK_BUTTON (self->star_button), + is_starred ? "starred-symbolic" : "non-starred-symbolic"); + /* Translators: This is a verb for tagging or untagging a file with a star. */ + gtk_widget_set_tooltip_text (self->star_button, is_starred ? _("Unstar") : _("Star")); +} + +static void +on_starred_changed (NautilusTagManager *tag_manager, + GList *changed_files, + gpointer user_data) +{ + NautilusPropertiesWindow *self = user_data; + NautilusFile *file = get_target_file (self); + + if (g_list_find (changed_files, file)) + { + update_star (self, tag_manager); + } +} + +static void +setup_star_button (NautilusPropertiesWindow *self) +{ + NautilusTagManager *tag_manager = nautilus_tag_manager_get (); + NautilusFile *file = get_target_file (self); + g_autoptr (GFile) parent_location = nautilus_file_get_parent_location (file); + + if (parent_location == NULL) + { + return; + } + + if (nautilus_tag_manager_can_star_contents (tag_manager, parent_location)) + { + gtk_widget_show (self->star_button); + update_star (self, tag_manager); + g_signal_connect_object (tag_manager, "starred-changed", + G_CALLBACK (on_starred_changed), self, 0); + } +} + +static void +setup_image_widget (NautilusPropertiesWindow *self, + gboolean is_customizable) +{ + update_properties_window_icon (self); + + if (is_customizable) + { + GtkDropTarget *target; + + /* prepare the image to receive dropped objects to assign custom images */ + target = gtk_drop_target_new (GDK_TYPE_FILE_LIST, GDK_ACTION_COPY); + gtk_widget_add_controller (self->icon_button, GTK_EVENT_CONTROLLER (target)); + g_signal_connect (target, "drop", + G_CALLBACK (nautilus_properties_window_drag_drop_cb), self->icon_button_image); + + g_signal_connect (self->icon_button, "clicked", + G_CALLBACK (select_image_button_callback), self); + gtk_stack_set_visible_child (self->icon_stack, self->icon_button); + } + else + { + gtk_stack_set_visible_child (self->icon_stack, self->icon_image); + } +} + +static void +update_name_field (NautilusPropertiesWindow *self) +{ + g_autoptr (GString) name_str = g_string_new (""); + g_autofree gchar *os_name = NULL; + gchar *name_value; + guint file_counter = 0; + + for (GList *l = self->target_files; l != NULL; l = l->next) + { + NautilusFile *file = NAUTILUS_FILE (l->data); + + if (!nautilus_file_is_gone (file)) + { + g_autofree gchar *file_name = NULL; + + file_counter += 1; + if (file_counter > 1) + { + g_string_append (name_str, ", "); + } + + file_name = nautilus_file_get_display_name (file); + g_string_append (name_str, file_name); + } + } + + if (!is_multi_file_window (self) && is_root_directory (get_original_file (self))) + { + os_name = g_get_os_info (G_OS_INFO_KEY_NAME); + name_value = (os_name != NULL) ? os_name : _("Operating System"); + } + else + { + name_value = name_str->str; + } + + gtk_label_set_text (self->name_value_label, name_value); +} + +/** + * Returns the attribute value if all files in file_list have identical + * attributes, "unknown" if no files exist and NULL otherwise. + */ +static char * +file_list_get_string_attribute (GList *file_list, + const char *attribute_name) +{ + g_autofree char *first_attr = NULL; + + for (GList *l = file_list; l != NULL; l = l->next) + { + NautilusFile *file = NAUTILUS_FILE (l->data); + + if (nautilus_file_is_gone (file)) + { + continue; + } + + if (first_attr == NULL) + { + first_attr = nautilus_file_get_string_attribute_with_default (file, attribute_name); + } + else + { + g_autofree char *attr = NULL; + attr = nautilus_file_get_string_attribute_with_default (file, attribute_name); + if (!g_str_equal (attr, first_attr)) + { + /* Not all files have the same value for attribute_name. */ + return NULL; + } + } + } + + if (first_attr != NULL) + { + return g_steal_pointer (&first_attr); + } + else + { + return g_strdup (_("unknown")); + } +} + +static GtkWidget * +create_extension_group_row (NautilusPropertiesItem *item, + NautilusPropertiesWindow *self) +{ + GtkWidget *row = adw_action_row_new (); + GtkWidget *box = gtk_box_new (GTK_ORIENTATION_VERTICAL, 3); + GtkWidget *name_label = gtk_label_new (NULL); + GtkWidget *value_label = gtk_label_new (NULL); + + gtk_list_box_row_set_selectable (GTK_LIST_BOX_ROW (row), FALSE); + gtk_list_box_row_set_activatable (GTK_LIST_BOX_ROW (row), FALSE); + adw_action_row_add_prefix (ADW_ACTION_ROW (row), box); + + gtk_widget_set_margin_top (box, 7); + gtk_widget_set_margin_bottom (box, 7); + gtk_box_append (GTK_BOX (box), name_label); + gtk_box_append (GTK_BOX (box), value_label); + + g_object_bind_property (item, "name", name_label, "label", G_BINDING_SYNC_CREATE); + gtk_widget_add_css_class (name_label, "caption"); + gtk_widget_add_css_class (name_label, "dim-label"); + gtk_widget_set_halign (name_label, GTK_ALIGN_START); + gtk_label_set_ellipsize (GTK_LABEL (name_label), PANGO_ELLIPSIZE_END); + + g_object_bind_property (item, "value", value_label, "label", G_BINDING_SYNC_CREATE); + gtk_widget_set_halign (value_label, GTK_ALIGN_START); + gtk_label_set_wrap (GTK_LABEL (value_label), TRUE); + gtk_label_set_wrap_mode (GTK_LABEL (value_label), PANGO_WRAP_WORD_CHAR); + gtk_label_set_selectable (GTK_LABEL (value_label), TRUE); + + return row; +} + +static GtkWidget * +add_extension_model_page (NautilusPropertiesModel *model, + NautilusPropertiesWindow *self) +{ + GListModel *list_model = nautilus_properties_model_get_model (model); + GtkWidget *row; + GtkWidget *title; + GtkWidget *header_bar; + GtkWidget *list_box; + GtkWidget *clamp; + GtkWidget *scrolled_window; + GtkWidget *up_button; + GtkWidget *box; + + row = adw_action_row_new (); + g_object_bind_property (model, "title", row, "title", G_BINDING_SYNC_CREATE); + gtk_list_box_row_set_activatable (GTK_LIST_BOX_ROW (row), TRUE); + gtk_list_box_row_set_selectable (GTK_LIST_BOX_ROW (row), FALSE); + adw_action_row_add_suffix (ADW_ACTION_ROW (row), + gtk_image_new_from_icon_name ("go-next-symbolic")); + g_signal_connect_swapped (row, "activated", + G_CALLBACK (navigate_extension_model_page), self); + + title = adw_window_title_new (NULL, NULL); + g_object_bind_property (model, "title", title, "title", G_BINDING_SYNC_CREATE); + + up_button = gtk_button_new_from_icon_name ("go-previous-symbolic"); + g_signal_connect_swapped (up_button, "clicked", G_CALLBACK (navigate_main_page), self); + + header_bar = gtk_header_bar_new (); + gtk_header_bar_set_title_widget (GTK_HEADER_BAR (header_bar), title); + gtk_header_bar_pack_start (GTK_HEADER_BAR (header_bar), up_button); + + list_box = gtk_list_box_new (); + gtk_widget_add_css_class (list_box, "boxed-list"); + gtk_widget_set_valign (list_box, GTK_ALIGN_START); + gtk_list_box_bind_model (GTK_LIST_BOX (list_box), list_model, + (GtkListBoxCreateWidgetFunc) create_extension_group_row, + self, + NULL); + + clamp = adw_clamp_new (); + adw_clamp_set_child (ADW_CLAMP (clamp), list_box); + gtk_widget_set_margin_top (clamp, 18); + gtk_widget_set_margin_bottom (clamp, 18); + gtk_widget_set_margin_start (clamp, 18); + gtk_widget_set_margin_end (clamp, 18); + + scrolled_window = gtk_scrolled_window_new (); + gtk_scrolled_window_set_child (GTK_SCROLLED_WINDOW (scrolled_window), clamp); + gtk_widget_set_vexpand (scrolled_window, TRUE); + + box = gtk_box_new (GTK_ORIENTATION_VERTICAL, 0); + gtk_box_append (GTK_BOX (box), header_bar); + gtk_box_append (GTK_BOX (box), scrolled_window); + gtk_widget_add_css_class (scrolled_window, "background"); + + gtk_stack_add_named (self->page_stack, + box, + NULL); + + g_object_set_data (G_OBJECT (row), "nautilus-extension-properties-page", box); + + return row; +} + +static void +remove_from_dialog (NautilusPropertiesWindow *self, + NautilusFile *file) +{ + int index; + GList *original_link; + GList *target_link; + g_autoptr (NautilusFile) original_file = NULL; + g_autoptr (NautilusFile) target_file = NULL; + + index = g_list_index (self->target_files, file); + if (index == -1) + { + index = g_list_index (self->original_files, file); + g_return_if_fail (index != -1); + } + + original_link = g_list_nth (self->original_files, index); + target_link = g_list_nth (self->target_files, index); + + g_return_if_fail (original_link && target_link); + + original_file = NAUTILUS_FILE (original_link->data); + target_file = NAUTILUS_FILE (target_link->data); + + self->original_files = g_list_delete_link (self->original_files, original_link); + self->target_files = g_list_delete_link (self->target_files, target_link); + + g_hash_table_remove (self->initial_permissions, target_file); + + g_signal_handlers_disconnect_by_func (original_file, + G_CALLBACK (file_changed_callback), + self); + g_signal_handlers_disconnect_by_func (target_file, + G_CALLBACK (file_changed_callback), + self); + + nautilus_file_monitor_remove (original_file, &self->original_files); + nautilus_file_monitor_remove (target_file, &self->target_files); +} + +static gboolean +mime_list_equal (GList *a, + GList *b) +{ + while (a && b) + { + if (strcmp (a->data, b->data)) + { + return FALSE; + } + a = a->next; + b = b->next; + } + + return (a == b); +} + +static GList * +get_mime_list (NautilusPropertiesWindow *self) +{ + return g_list_copy_deep (self->target_files, + (GCopyFunc) nautilus_file_get_mime_type, + NULL); +} + +static gboolean +start_spinner_callback (NautilusPropertiesWindow *self) +{ + gtk_widget_show (self->contents_spinner); + gtk_spinner_start (GTK_SPINNER (self->contents_spinner)); + self->deep_count_spinner_timeout_id = 0; + + return FALSE; +} + +static void +schedule_start_spinner (NautilusPropertiesWindow *self) +{ + if (self->deep_count_spinner_timeout_id == 0) + { + self->deep_count_spinner_timeout_id + = g_timeout_add_seconds (1, + (GSourceFunc) start_spinner_callback, + self); + } +} + +static void +stop_spinner (NautilusPropertiesWindow *self) +{ + gtk_spinner_stop (GTK_SPINNER (self->contents_spinner)); + gtk_widget_hide (self->contents_spinner); + g_clear_handle_id (&self->deep_count_spinner_timeout_id, g_source_remove); +} + +static void +stop_deep_count_for_file (NautilusPropertiesWindow *self, + NautilusFile *file) +{ + if (g_list_find (self->deep_count_files, file)) + { + g_signal_handlers_disconnect_by_func (file, + G_CALLBACK (schedule_directory_contents_update), + self); + nautilus_file_unref (file); + self->deep_count_files = g_list_remove (self->deep_count_files, file); + } +} + +static void +start_deep_count_for_file (NautilusFile *file, + NautilusPropertiesWindow *self) +{ + if (!nautilus_file_is_directory (file)) + { + return; + } + + if (!g_list_find (self->deep_count_files, file)) + { + nautilus_file_ref (file); + self->deep_count_files = g_list_prepend (self->deep_count_files, file); + + nautilus_file_recompute_deep_counts (file); + if (!self->deep_count_finished) + { + g_signal_connect_object (file, + "updated-deep-count-in-progress", + G_CALLBACK (schedule_directory_contents_update), + self, G_CONNECT_SWAPPED); + schedule_start_spinner (self); + } + } +} + +static guint32 vfs_perms[3][3] = +{ + {UNIX_PERM_USER_READ, UNIX_PERM_USER_WRITE, UNIX_PERM_USER_EXEC}, + {UNIX_PERM_GROUP_READ, UNIX_PERM_GROUP_WRITE, UNIX_PERM_GROUP_EXEC}, + {UNIX_PERM_OTHER_READ, UNIX_PERM_OTHER_WRITE, UNIX_PERM_OTHER_EXEC}, +}; + +static guint32 +permission_to_vfs (PermissionType type, + PermissionValue perm) +{ + guint32 vfs_perm; + g_assert (type >= 0 && type < 3); + + vfs_perm = 0; + if (perm & PERMISSION_READ) + { + vfs_perm |= vfs_perms[type][0]; + } + if (perm & PERMISSION_WRITE) + { + vfs_perm |= vfs_perms[type][1]; + } + if (perm & PERMISSION_EXEC) + { + vfs_perm |= vfs_perms[type][2]; + } + + return vfs_perm; +} + +static PermissionValue +permission_from_vfs (PermissionType type, + guint32 vfs_perm) +{ + PermissionValue perm = PERMISSION_NONE; + + g_assert (type >= 0 && type < 3); + + if (vfs_perm & vfs_perms[type][0]) + { + perm |= PERMISSION_READ; + } + if (vfs_perm & vfs_perms[type][1]) + { + perm |= PERMISSION_WRITE; + } + if (vfs_perm & vfs_perms[type][2]) + { + perm |= PERMISSION_EXEC; + } + + return perm; +} + +static PermissionValue +exec_permission_from_vfs (guint32 vfs_perm) +{ + guint32 perm_user = vfs_perm & UNIX_PERM_USER_EXEC; + guint32 perm_group = vfs_perm & UNIX_PERM_GROUP_EXEC; + guint32 perm_other = vfs_perm & UNIX_PERM_OTHER_EXEC; + + if (perm_user && perm_group && perm_other) + { + return PERMISSION_EXEC; + } + else if (perm_user || perm_group || perm_other) + { + return PERMISSION_INCONSISTENT; + } + else + { + return PERMISSION_NONE; + } +} + +static TargetPermissions * +get_target_permissions (NautilusPropertiesWindow *self) +{ + TargetPermissions *p = g_new0 (TargetPermissions, 1); + p->window = self; + p->can_set_all_folder_permission = TRUE; + p->can_set_all_file_permission = TRUE; + + for (GList *entry = self->target_files; entry != NULL; entry = entry->next) + { + guint32 vfs_permissions; + gboolean can_set_permissions; + NautilusFile *file = NAUTILUS_FILE (entry->data); + + if (nautilus_file_is_gone (file) || !nautilus_file_can_get_permissions (file)) + { + continue; + } + + vfs_permissions = nautilus_file_get_permissions (file); + can_set_permissions = nautilus_file_can_set_permissions (file); + + if (nautilus_file_is_directory (file)) + { + /* Gather permissions for each type (owner, group, other) */ + for (PermissionType type = PERMISSION_USER; type < NUM_PERMISSION_TYPE; type += 1) + { + PermissionValue permissions = permission_from_vfs (type, vfs_permissions) + & (PERMISSION_READ | PERMISSION_WRITE | PERMISSION_EXEC); + if (!p->has_folders) + { + /* first found folder, initialize with its permissions */ + p->folder_permissions[type] = permissions; + } + else if (permissions != p->folder_permissions[type]) + { + p->folder_permissions[type] = PERMISSION_INCONSISTENT; + } + } + + p->can_set_all_folder_permission &= can_set_permissions; + p->has_folders = TRUE; + } + else + { + PermissionValue exec_permissions = exec_permission_from_vfs (vfs_permissions); + + if (!p->has_files) + { + p->file_exec_permissions = exec_permissions; + } + else if (exec_permissions != p->file_exec_permissions) + { + p->file_exec_permissions = PERMISSION_INCONSISTENT; + } + for (PermissionType type = PERMISSION_USER ; type < NUM_PERMISSION_TYPE; type += 1) + { + PermissionValue permissions = permission_from_vfs (type, vfs_permissions) + & (PERMISSION_READ | PERMISSION_WRITE); + + if (!p->has_files) + { + /* first found file, initialize with its permissions */ + p->file_permissions[type] = permissions; + } + else if (permissions != p->file_permissions[type]) + { + p->file_permissions[type] = PERMISSION_INCONSISTENT; + } + } + + p->can_set_all_file_permission &= can_set_permissions; + p->can_set_any_file_permission |= can_set_permissions; + p->has_files = TRUE; + } + } + + p->is_multi_file_window = is_multi_file_window (self); + + return p; +} + +static void +update_permissions_navigation_row (NautilusPropertiesWindow *self, + TargetPermissions *target_perm) +{ + if (!target_perm->is_multi_file_window) + { + uid_t user_id = geteuid (); + gid_t group_id = getegid (); + PermissionType permission_type = PERMISSION_OTHER; + const gchar *text; + + if (user_id == nautilus_file_get_uid (get_original_file (self))) + { + permission_type = PERMISSION_USER; + } + else if (group_id == nautilus_file_get_gid (get_original_file (self))) + { + permission_type = PERMISSION_GROUP; + } + + if (nautilus_file_is_directory (get_original_file (self))) + { + text = permission_value_to_string (target_perm->folder_permissions[permission_type], TRUE); + } + else + { + text = permission_value_to_string (target_perm->file_permissions[permission_type], FALSE); + } + + gtk_label_set_text (GTK_LABEL (self->permissions_value_label), text); + } +} + +static void +properties_window_update (NautilusPropertiesWindow *self, + GList *files) +{ + GList *mime_list; + NautilusFile *changed_file; + gboolean dirty_original = FALSE; + gboolean dirty_target = FALSE; + + if (files == NULL) + { + dirty_original = TRUE; + dirty_target = TRUE; + } + + for (GList *tmp = files; tmp != NULL; tmp = tmp->next) + { + changed_file = NAUTILUS_FILE (tmp->data); + + if (changed_file && nautilus_file_is_gone (changed_file)) + { + /* Remove the file from the property dialog */ + remove_from_dialog (self, changed_file); + changed_file = NULL; + + if (self->original_files == NULL) + { + return; + } + } + if (changed_file == NULL || + g_list_find (self->original_files, changed_file)) + { + dirty_original = TRUE; + } + if (changed_file == NULL || + g_list_find (self->target_files, changed_file)) + { + dirty_target = TRUE; + } + } + + if (dirty_original) + { + update_properties_window_icon (self); + update_name_field (self); + + /* If any of the value fields start to depend on the original + * value, value_field_updates should be added here */ + } + + if (dirty_target) + { + g_autofree TargetPermissions *target_perm = get_target_permissions (self); + + update_permissions_navigation_row (self, target_perm); + update_owner_row (self->owner_row, target_perm); + update_group_row (self->group_row, target_perm); + update_execution_row (GTK_WIDGET (self->execution_row), target_perm); + g_list_foreach (self->permission_rows, + (GFunc) update_permission_row, + target_perm); + g_list_foreach (self->value_fields, + (GFunc) value_field_update, + self); + } + + mime_list = get_mime_list (self); + + if (self->mime_list == NULL) + { + self->mime_list = mime_list; + } + else + { + if (!mime_list_equal (self->mime_list, mime_list)) + { + refresh_extension_model_pages (self); + } + + g_list_free_full (self->mime_list, g_free); + self->mime_list = mime_list; + } +} + +static gboolean +update_files_callback (gpointer data) +{ + NautilusPropertiesWindow *self; + + self = NAUTILUS_PROPERTIES_WINDOW (data); + + self->update_files_timeout_id = 0; + + properties_window_update (self, self->changed_files); + + if (self->original_files == NULL) + { + /* Close the window if no files are left */ + gtk_window_destroy (GTK_WINDOW (self)); + } + else + { + nautilus_file_list_free (self->changed_files); + self->changed_files = NULL; + } + + return FALSE; +} + +static void +schedule_files_update (NautilusPropertiesWindow *self) +{ + g_assert (NAUTILUS_IS_PROPERTIES_WINDOW (self)); + + if (self->update_files_timeout_id == 0) + { + self->update_files_timeout_id + = g_timeout_add (FILES_UPDATE_INTERVAL, + update_files_callback, + self); + } +} + +static gboolean +location_show_original (NautilusPropertiesWindow *self) +{ + NautilusFile *file; + + /* there is no way a recent item will be mixed with + * other items so just pick the first file to check */ + file = NAUTILUS_FILE (g_list_nth_data (self->original_files, 0)); + return (file != NULL && !nautilus_file_is_in_recent (file)); +} + +static void +value_field_update (GtkLabel *label, + NautilusPropertiesWindow *self) +{ + GList *file_list; + const char *attribute_name; + g_autofree char *attribute_value = NULL; + gboolean is_where; + + g_assert (GTK_IS_LABEL (label)); + + attribute_name = g_object_get_data (G_OBJECT (label), "file_attribute"); + + is_where = (g_strcmp0 (attribute_name, "where") == 0); + if (is_where && location_show_original (self)) + { + file_list = self->original_files; + } + else + { + file_list = self->target_files; + } + + attribute_value = file_list_get_string_attribute (file_list, + attribute_name); + if (g_str_equal (attribute_name, "detailed_type")) + { + g_autofree char *mime_type = NULL; + gchar *cap_label; + + mime_type = file_list_get_string_attribute (file_list, "mime_type"); + gtk_widget_set_tooltip_text (GTK_WIDGET (label), mime_type); + + cap_label = eel_str_capitalize (attribute_value); + if (cap_label != NULL) + { + g_free (attribute_value); + + attribute_value = cap_label; + } + } + else if (g_str_equal (attribute_name, "size")) + { + g_autofree char *size_detail = NULL; + + size_detail = file_list_get_string_attribute (file_list, "size_detail"); + + gtk_widget_set_tooltip_text (GTK_WIDGET (label), size_detail); + } + + gtk_label_set_text (label, attribute_value); +} + +static guint +hash_string_list (GList *list) +{ + guint hash_value = 0; + + for (GList *node = list; node != NULL; node = node->next) + { + hash_value ^= g_str_hash ((gconstpointer) node->data); + } + + return hash_value; +} + +static gsize +get_first_word_length (const gchar *str) +{ + const gchar *space_pos = g_strstr_len (str, -1, " "); + return space_pos ? space_pos - str : strlen (str); +} + +static void +update_combo_row_dropdown (AdwComboRow *row, + GList *entries) +{ + /* check if dropdown already exist and is up to date by comparing with stored hash. */ + guint current_hash = hash_string_list (entries); + guint stored_hash = GPOINTER_TO_UINT (g_object_get_data (G_OBJECT (row), "dropdown-hash")); + + if (stored_hash != current_hash) + { + /* Recreate the drop down. */ + g_autoptr (GtkStringList) new_model = gtk_string_list_new (NULL); + + for (GList *node = entries; node != NULL; node = node->next) + { + const char *entry = (const char *) node->data; + gtk_string_list_append (new_model, entry); + } + + adw_combo_row_set_model (row, G_LIST_MODEL (new_model)); + + g_object_set_data (G_OBJECT (row), "dropdown-hash", GUINT_TO_POINTER (current_hash)); + } +} + +typedef gboolean CompareOwnershipRowFunc (GListModel *list, + guint position, + const char *str); + +static void +select_ownership_row_entry (AdwComboRow *row, + const char *entry, + CompareOwnershipRowFunc compare_func) +{ + GListModel *list = adw_combo_row_get_model (row); + guint index_to_select = GTK_INVALID_LIST_POSITION; + guint n_entries; + + /* check if entry is already selected */ + gint selected_pos = adw_combo_row_get_selected (row); + + if (selected_pos >= 0 + && compare_func (list, selected_pos, entry)) + { + /* entry already selected */ + return; + } + + /* check if entry exists in model list */ + n_entries = g_list_model_get_n_items (list); + + for (guint position = 0; position < n_entries; position += 1) + { + if (compare_func (list, position, entry)) + { + /* found entry in list, select it */ + index_to_select = position; + break; + } + } + + if (index_to_select == GTK_INVALID_LIST_POSITION) + { + /* entry not in list, add */ + gtk_string_list_append (GTK_STRING_LIST (list), entry); + index_to_select = n_entries; + } + + adw_combo_row_set_selected (row, index_to_select); +} + +static void +ownership_row_set_single_entry (AdwComboRow *row, + const char *entry, + CompareOwnershipRowFunc compare_func) +{ + GListModel *list = adw_combo_row_get_model (row); + gint selected_pos = adw_combo_row_get_selected (row); + + /* check entry not already displayed */ + if (selected_pos < 0 + || g_list_model_get_n_items (list) > 1 + || !compare_func (list, selected_pos, entry)) + { + g_autoptr (GtkStringList) new_model = gtk_string_list_new (NULL); + + /* set current entry as only entry */ + gtk_string_list_append (new_model, entry); + + adw_combo_row_set_model (row, G_LIST_MODEL (new_model)); + adw_combo_row_set_selected (row, 0); + } +} + +static void +group_change_free (GroupChange *change) +{ + nautilus_file_unref (change->file); + g_free (change->group); + g_object_unref (change->window); + + g_free (change); +} + +static void +group_change_callback (NautilusFile *file, + GFile *res_loc, + GError *error, + GroupChange *change) +{ + NautilusPropertiesWindow *self; + + g_assert (NAUTILUS_IS_PROPERTIES_WINDOW (change->window)); + g_assert (NAUTILUS_IS_FILE (change->file)); + g_assert (change->group != NULL); + + if (!change->cancelled) + { + /* Report the error if it's an error. */ + eel_timed_wait_stop ((EelCancelCallback) cancel_group_change_callback, change); + nautilus_report_error_setting_group (change->file, error, change->window); + } + + self = NAUTILUS_PROPERTIES_WINDOW (change->window); + if (self->group_change == change) + { + self->group_change = NULL; + } + + group_change_free (change); +} + +static void +cancel_group_change_callback (GroupChange *change) +{ + g_assert (NAUTILUS_IS_FILE (change->file)); + g_assert (change->group != NULL); + + change->cancelled = TRUE; + nautilus_file_cancel (change->file, (NautilusFileOperationCallback) group_change_callback, change); +} + +static gboolean +schedule_group_change_timeout (GroupChange *change) +{ + g_assert (NAUTILUS_IS_PROPERTIES_WINDOW (change->window)); + g_assert (NAUTILUS_IS_FILE (change->file)); + g_assert (change->group != NULL); + + change->timeout = 0; + + eel_timed_wait_start + ((EelCancelCallback) cancel_group_change_callback, + change, + _("Cancel Group Change?"), + change->window); + + nautilus_file_set_group + (change->file, change->group, + (NautilusFileOperationCallback) group_change_callback, change); + + return FALSE; +} + +static void +schedule_group_change (NautilusPropertiesWindow *self, + NautilusFile *file, + const char *group) +{ + GroupChange *change; + + g_assert (NAUTILUS_IS_PROPERTIES_WINDOW (self)); + g_assert (self->group_change == NULL); + g_assert (NAUTILUS_IS_FILE (file)); + + change = g_new0 (GroupChange, 1); + + change->file = nautilus_file_ref (file); + change->group = g_strdup (group); + change->window = GTK_WINDOW (g_object_ref (self)); + change->timeout = + g_timeout_add (CHOWN_CHGRP_TIMEOUT, + (GSourceFunc) schedule_group_change_timeout, + change); + + self->group_change = change; +} + +static void +unschedule_or_cancel_group_change (NautilusPropertiesWindow *self) +{ + GroupChange *change; + + g_assert (NAUTILUS_IS_PROPERTIES_WINDOW (self)); + + change = self->group_change; + + if (change != NULL) + { + if (change->timeout == 0) + { + /* The operation was started, cancel it and let the operation callback free the change */ + cancel_group_change_callback (change); + eel_timed_wait_stop ((EelCancelCallback) cancel_group_change_callback, change); + } + else + { + g_source_remove (change->timeout); + group_change_free (change); + } + + self->group_change = NULL; + } +} + +/** Apply group owner change on user selection. */ +static void +changed_group_callback (AdwComboRow *row, + GParamSpec *pspec, + NautilusPropertiesWindow *self) +{ + guint selected_pos = adw_combo_row_get_selected (row); + + g_assert (NAUTILUS_IS_PROPERTIES_WINDOW (self)); + + if (selected_pos >= 0) + { + NautilusFile *file = get_target_file (self); + GListModel *list = adw_combo_row_get_model (row); + const gchar *new_group_name = gtk_string_list_get_string (GTK_STRING_LIST (list), selected_pos); + g_autofree char *current_group_name = nautilus_file_get_group_name (file); + + g_assert (new_group_name); + g_assert (current_group_name); + + if (strcmp (new_group_name, current_group_name) != 0) + { + /* Try to change file group. If this fails, complain to user. */ + unschedule_or_cancel_group_change (self); + schedule_group_change (self, file, new_group_name); + } + } +} + +static void +owner_change_free (OwnerChange *change) +{ + nautilus_file_unref (change->file); + g_free (change->owner); + g_object_unref (change->window); + + g_free (change); +} + +static void +owner_change_callback (NautilusFile *file, + GFile *result_location, + GError *error, + OwnerChange *change) +{ + NautilusPropertiesWindow *self; + + g_assert (NAUTILUS_IS_PROPERTIES_WINDOW (change->window)); + g_assert (NAUTILUS_IS_FILE (change->file)); + g_assert (change->owner != NULL); + + if (!change->cancelled) + { + /* Report the error if it's an error. */ + eel_timed_wait_stop ((EelCancelCallback) cancel_owner_change_callback, change); + nautilus_report_error_setting_owner (file, error, change->window); + } + + self = NAUTILUS_PROPERTIES_WINDOW (change->window); + if (self->owner_change == change) + { + self->owner_change = NULL; + } + + owner_change_free (change); +} + +static void +cancel_owner_change_callback (OwnerChange *change) +{ + g_assert (NAUTILUS_IS_FILE (change->file)); + g_assert (change->owner != NULL); + + change->cancelled = TRUE; + nautilus_file_cancel (change->file, (NautilusFileOperationCallback) owner_change_callback, change); +} + +static gboolean +schedule_owner_change_timeout (OwnerChange *change) +{ + g_assert (NAUTILUS_IS_PROPERTIES_WINDOW (change->window)); + g_assert (NAUTILUS_IS_FILE (change->file)); + g_assert (change->owner != NULL); + + change->timeout = 0; + + eel_timed_wait_start + ((EelCancelCallback) cancel_owner_change_callback, + change, + _("Cancel Owner Change?"), + change->window); + + nautilus_file_set_owner + (change->file, change->owner, + (NautilusFileOperationCallback) owner_change_callback, change); + + return FALSE; +} + +static void +schedule_owner_change (NautilusPropertiesWindow *self, + NautilusFile *file, + const char *owner) +{ + OwnerChange *change; + + g_assert (NAUTILUS_IS_PROPERTIES_WINDOW (self)); + g_assert (self->owner_change == NULL); + g_assert (NAUTILUS_IS_FILE (file)); + + change = g_new0 (OwnerChange, 1); + + change->file = nautilus_file_ref (file); + change->owner = g_strdup (owner); + change->window = GTK_WINDOW (g_object_ref (self)); + change->timeout = + g_timeout_add (CHOWN_CHGRP_TIMEOUT, + (GSourceFunc) schedule_owner_change_timeout, + change); + + self->owner_change = change; +} + +static void +unschedule_or_cancel_owner_change (NautilusPropertiesWindow *self) +{ + OwnerChange *change; + + g_assert (NAUTILUS_IS_PROPERTIES_WINDOW (self)); + + change = self->owner_change; + + if (change != NULL) + { + g_assert (NAUTILUS_IS_FILE (change->file)); + + if (change->timeout == 0) + { + /* The operation was started, cancel it and let the operation callback free the change */ + cancel_owner_change_callback (change); + eel_timed_wait_stop ((EelCancelCallback) cancel_owner_change_callback, change); + } + else + { + g_source_remove (change->timeout); + owner_change_free (change); + } + + self->owner_change = NULL; + } +} + +static void +changed_owner_callback (AdwComboRow *row, + GParamSpec *pspec, + NautilusPropertiesWindow *self) +{ + guint selected_pos = adw_combo_row_get_selected (row); + g_assert (NAUTILUS_IS_PROPERTIES_WINDOW (self)); + + if (selected_pos >= 0) + { + NautilusFile *file = get_target_file (self); + + GListModel *list = adw_combo_row_get_model (row); + const gchar *selected_owner_str = gtk_string_list_get_string (GTK_STRING_LIST (list), selected_pos); + gsize owner_name_length = get_first_word_length (selected_owner_str); + g_autofree gchar *new_owner_name = g_strndup (selected_owner_str, owner_name_length); + g_autofree char *current_owner_name = nautilus_file_get_owner_name (file); + + g_assert (NAUTILUS_IS_FILE (file)); + + if (strcmp (new_owner_name, current_owner_name) != 0) + { + /* Try to change file owner. If this fails, complain to user. */ + unschedule_or_cancel_owner_change (self); + schedule_owner_change (self, file, new_owner_name); + } + } +} + +static gboolean +string_list_item_starts_with_word (GListModel *list, + guint position, + const char *word) +{ + const gchar *entry_str = gtk_string_list_get_string (GTK_STRING_LIST (list), position); + + return g_str_has_prefix (entry_str, word) + && strlen (word) == get_first_word_length (entry_str); +} + +static gboolean +string_list_item_equals_string (GListModel *list, + guint position, + const char *string) +{ + const gchar *entry_str = gtk_string_list_get_string (GTK_STRING_LIST (list), position); + + return strcmp (entry_str, string) == 0; +} + +/* Select correct owner if file permissions have changed. */ +static void +update_owner_row (AdwComboRow *row, + TargetPermissions *target_perm) +{ + NautilusPropertiesWindow *self = target_perm->window; + gboolean provide_dropdown = (!target_perm->is_multi_file_window + && nautilus_file_can_set_owner (get_target_file (self))); + gboolean had_dropdown = gtk_widget_is_sensitive (GTK_WIDGET (row)); + + gtk_widget_set_sensitive (GTK_WIDGET (row), provide_dropdown); + + /* check if should provide dropdown */ + if (provide_dropdown) + { + NautilusFile *file = get_target_file (self); + g_autofree char *owner_name = nautilus_file_get_owner_name (file); + GList *users = nautilus_get_user_names (); + + update_combo_row_dropdown (row, users); + + /* display current owner */ + select_ownership_row_entry (row, owner_name, string_list_item_starts_with_word); + + if (!had_dropdown) + { + /* Update file when selection changes. */ + g_signal_connect (row, "notify::selected", + G_CALLBACK (changed_owner_callback), + self); + } + } + else + { + g_autofree char *owner_name = file_list_get_string_attribute (self->target_files, + "owner"); + if (owner_name == NULL) + { + owner_name = g_strdup (_("Multiple")); + } + + g_signal_handlers_disconnect_by_func (row, G_CALLBACK (changed_owner_callback), self); + + ownership_row_set_single_entry (row, owner_name, string_list_item_starts_with_word); + } +} + +/* Select correct group if file permissions have changed. */ +static void +update_group_row (AdwComboRow *row, + TargetPermissions *target_perm) +{ + NautilusPropertiesWindow *self = target_perm->window; + gboolean provide_dropdown = (!target_perm->is_multi_file_window + && nautilus_file_can_set_group (get_target_file (self))); + gboolean had_dropdown = gtk_widget_is_sensitive (GTK_WIDGET (row)); + + gtk_widget_set_sensitive (GTK_WIDGET (row), provide_dropdown); + + if (provide_dropdown) + { + NautilusFile *file = get_target_file (self); + g_autofree char *group_name = nautilus_file_get_group_name (file); + GList *groups = nautilus_file_get_settable_group_names (file); + update_combo_row_dropdown (row, groups); + + /* display current group */ + select_ownership_row_entry (row, group_name, string_list_item_equals_string); + + if (!had_dropdown) + { + /* Update file when selection changes. */ + g_signal_connect (row, "notify::selected", + G_CALLBACK (changed_group_callback), + self); + } + } + else + { + g_autofree char *group_name = file_list_get_string_attribute (self->target_files, + "group"); + if (group_name == NULL) + { + group_name = g_strdup (_("Multiple")); + } + + g_signal_handlers_disconnect_by_func (row, G_CALLBACK (changed_group_callback), self); + + ownership_row_set_single_entry (row, group_name, string_list_item_equals_string); + } +} + +static void +setup_ownership_row (NautilusPropertiesWindow *self, + AdwComboRow *row) +{ + adw_combo_row_set_model (row, G_LIST_MODEL (gtk_string_list_new (NULL))); + + /* Intial setup of list model is handled via update function, called via properties_window_update. */ +} + +static gboolean +file_has_prefix (NautilusFile *file, + GList *prefix_candidates) +{ + GList *p; + g_autoptr (GFile) location = NULL; + + location = nautilus_file_get_location (file); + + for (p = prefix_candidates; p != NULL; p = p->next) + { + g_autoptr (GFile) candidate_location = NULL; + + if (file == p->data) + { + continue; + } + + candidate_location = nautilus_file_get_location (NAUTILUS_FILE (p->data)); + if (g_file_has_prefix (location, candidate_location)) + { + return TRUE; + } + } + + return FALSE; +} + +static void +directory_contents_value_field_update (NautilusPropertiesWindow *self) +{ + NautilusRequestStatus file_status; + g_autofree char *text = NULL; + guint directory_count; + guint file_count; + guint total_count; + guint unreadable_directory_count; + goffset total_size; + NautilusFile *file; + GList *l; + guint file_unreadable; + goffset file_size; + gboolean deep_count_active; + + g_assert (NAUTILUS_IS_PROPERTIES_WINDOW (self)); + + total_count = 0; + total_size = 0; + unreadable_directory_count = FALSE; + + for (l = self->target_files; l; l = l->next) + { + file = NAUTILUS_FILE (l->data); + + if (file_has_prefix (file, self->target_files)) + { + /* don't count nested files twice */ + continue; + } + + if (nautilus_file_is_directory (file)) + { + file_status = nautilus_file_get_deep_counts (file, + &directory_count, + &file_count, + &file_unreadable, + &file_size, + TRUE); + total_count += (file_count + directory_count); + total_size += file_size; + + if (file_unreadable) + { + unreadable_directory_count = TRUE; + } + + if (file_status == NAUTILUS_REQUEST_DONE) + { + stop_deep_count_for_file (self, file); + } + } + else + { + ++total_count; + total_size += nautilus_file_get_size (file); + } + } + + deep_count_active = (self->deep_count_files != NULL); + /* If we've already displayed the total once, don't do another visible + * count-up if the deep_count happens to get invalidated. + * But still display the new total, since it might have changed. + */ + if (self->deep_count_finished && deep_count_active) + { + return; + } + + text = NULL; + + if (total_count == 0) + { + if (!deep_count_active) + { + if (unreadable_directory_count == 0) + { + text = g_strdup (_("Empty folder")); + } + else + { + text = g_strdup (_("Contents unreadable")); + } + } + else + { + text = g_strdup ("…"); + } + } + else + { + g_autofree char *size_str = NULL; + size_str = g_format_size (total_size); + text = g_strdup_printf (ngettext ("%'d item, with size %s", + "%'d items, totalling %s", + total_count), + total_count, size_str); + + if (unreadable_directory_count != 0) + { + g_autofree char *temp = g_steal_pointer (&text); + + text = g_strconcat (temp, "\n", + _("(some contents unreadable)"), + NULL); + } + } + + gtk_label_set_text (GTK_LABEL (self->contents_value_label), + text); + + if (!deep_count_active) + { + self->deep_count_finished = TRUE; + stop_spinner (self); + } +} + +static gboolean +update_directory_contents_callback (gpointer data) +{ + NautilusPropertiesWindow *self; + + self = NAUTILUS_PROPERTIES_WINDOW (data); + + self->update_directory_contents_timeout_id = 0; + directory_contents_value_field_update (self); + + return FALSE; +} + +static void +schedule_directory_contents_update (NautilusPropertiesWindow *self) +{ + g_assert (NAUTILUS_IS_PROPERTIES_WINDOW (self)); + + if (self->update_directory_contents_timeout_id == 0) + { + self->update_directory_contents_timeout_id + = g_timeout_add (DIRECTORY_CONTENTS_UPDATE_INTERVAL, + update_directory_contents_callback, + self); + } +} + +static void +setup_contents_field (NautilusPropertiesWindow *self) +{ + g_list_foreach (self->target_files, + (GFunc) start_deep_count_for_file, + self); + + /* Fill in the initial value. */ + directory_contents_value_field_update (self); +} + +static gboolean +is_root_directory (NautilusFile *file) +{ + g_autoptr (GFile) location = NULL; + gboolean result; + + location = nautilus_file_get_location (file); + result = nautilus_is_root_directory (location); + + return result; +} + +static gboolean +is_network_directory (NautilusFile *file) +{ + g_autofree char *file_uri = NULL; + + file_uri = nautilus_file_get_uri (file); + + return strcmp (file_uri, "network:///") == 0; +} + +static gboolean +is_burn_directory (NautilusFile *file) +{ + g_autofree char *file_uri = NULL; + + file_uri = nautilus_file_get_uri (file); + + return strcmp (file_uri, "burn:///") == 0; +} + + +static gboolean +is_volume_properties (NautilusPropertiesWindow *self) +{ + NautilusFile *file; + gboolean success = FALSE; + + if (is_multi_file_window (self)) + { + return FALSE; + } + + file = get_original_file (self); + + if (file == NULL) + { + return FALSE; + } + + if (is_root_directory (file) && nautilus_application_is_sandboxed ()) + { + return FALSE; + } + + if (nautilus_file_can_unmount (file)) + { + return TRUE; + } + + success = is_root_directory (file); + +#ifdef TODO_GIO + /* Look at is_mountpoint for activation uri */ +#endif + + return success; +} + +static gboolean +should_show_custom_icon_buttons (NautilusPropertiesWindow *self) +{ + if (is_multi_file_window (self) || is_volume_properties (self) || + is_root_directory (get_original_file (self))) + { + return FALSE; + } + + return TRUE; +} + +static gboolean +is_single_file_type (NautilusPropertiesWindow *self) +{ + if (is_multi_file_window (self)) + { + g_autofree gchar *mime_type = NULL; + GList *l = self->original_files; + + mime_type = nautilus_file_get_mime_type (NAUTILUS_FILE (l->data)); + for (l = l->next; l != NULL; l = l->next) + { + g_autofree gchar *next_mime_type = NULL; + + if (nautilus_file_is_gone (NAUTILUS_FILE (l->data))) + { + continue; + } + + next_mime_type = nautilus_file_get_mime_type (NAUTILUS_FILE (l->data)); + if (g_strcmp0 (next_mime_type, mime_type) != 0) + { + return FALSE; + } + } + } + + return TRUE; +} + +static gboolean +should_show_file_type (NautilusPropertiesWindow *self) +{ + if (!is_single_file_type (self)) + { + return FALSE; + } + + if (!is_multi_file_window (self) + && (nautilus_file_is_in_trash (get_target_file (self)) || + nautilus_file_is_directory (get_original_file (self)) || + is_network_directory (get_target_file (self)) || + is_burn_directory (get_target_file (self)) || + is_volume_properties (self))) + { + return FALSE; + } + + return TRUE; +} + +static gboolean +should_show_location_info (NautilusPropertiesWindow *self) +{ + GList *l; + + for (l = self->original_files; l != NULL; l = l->next) + { + if (nautilus_file_is_in_trash (NAUTILUS_FILE (l->data)) || + is_root_directory (NAUTILUS_FILE (l->data)) || + is_network_directory (NAUTILUS_FILE (l->data)) || + is_burn_directory (NAUTILUS_FILE (l->data))) + { + return FALSE; + } + } + + return TRUE; +} + +static gboolean +should_show_trashed_info (NautilusPropertiesWindow *self) +{ + GList *l; + + for (l = self->original_files; l != NULL; l = l->next) + { + if (!nautilus_file_is_in_trash (NAUTILUS_FILE (l->data))) + { + return FALSE; + } + } + + return TRUE; +} + +static gboolean +should_show_accessed_date (NautilusPropertiesWindow *self) +{ + /* Accessed date for directory seems useless. If we some + * day decide that it is useful, we should separately + * consider whether it's useful for "trash:". + */ + if (nautilus_file_list_are_all_folders (self->target_files) + || is_multi_file_window (self)) + { + return FALSE; + } + + return TRUE; +} + +static gboolean +should_show_modified_date (NautilusPropertiesWindow *self) +{ + return !is_multi_file_window (self); +} + +static gboolean +should_show_created_date (NautilusPropertiesWindow *self) +{ + return !is_multi_file_window (self); +} + +static gboolean +should_show_link_target (NautilusPropertiesWindow *self) +{ + if (!is_multi_file_window (self) + && nautilus_file_is_symbolic_link (get_target_file (self))) + { + return TRUE; + } + + return FALSE; +} + +static gboolean +should_show_free_space (NautilusPropertiesWindow *self) +{ + if (!is_multi_file_window (self) + && (nautilus_file_is_in_trash (get_target_file (self)) || + is_network_directory (get_target_file (self)) || + nautilus_file_is_in_recent (get_target_file (self)) || + is_burn_directory (get_target_file (self)) || + is_volume_properties (self))) + { + return FALSE; + } + + if (nautilus_file_list_are_all_folders (self->target_files)) + { + return TRUE; + } + + return FALSE; +} + +static gboolean +should_show_volume_usage (NautilusPropertiesWindow *self) +{ + return is_volume_properties (self); +} + +static void +setup_volume_information (NautilusPropertiesWindow *self) +{ + NautilusFile *file; + g_autofree gchar *capacity = NULL; + g_autofree gchar *used = NULL; + g_autofree gchar *free = NULL; + const char *fs_type; + g_autofree gchar *uri = NULL; + g_autoptr (GFile) location = NULL; + g_autoptr (GFileInfo) info = NULL; + + capacity = g_format_size (self->volume_capacity); + free = g_format_size (self->volume_free); + used = g_format_size (self->volume_used); + + file = get_original_file (self); + + uri = nautilus_file_get_activation_uri (file); + + gtk_label_set_text (GTK_LABEL (self->disk_space_used_value), used); + gtk_label_set_text (GTK_LABEL (self->disk_space_free_value), free); + gtk_label_set_text (GTK_LABEL (self->disk_space_capacity_value), capacity); + + location = g_file_new_for_uri (uri); + info = g_file_query_filesystem_info (location, G_FILE_ATTRIBUTE_FILESYSTEM_TYPE, + NULL, NULL); + if (info) + { + fs_type = g_file_info_get_attribute_string (info, G_FILE_ATTRIBUTE_FILESYSTEM_TYPE); + + /* We shouldn't be using filesystem::type, it's not meant for UI. + * https://gitlab.gnome.org/GNOME/nautilus/-/issues/98 + * + * Until we fix that issue, workaround this common outrageous case. */ + if (g_strcmp0 (fs_type, "msdos") == 0) + { + fs_type = "FAT"; + } + + if (fs_type != NULL) + { + /* Translators: %s will be filled with a filesystem type, such as 'ext4' or 'msdos'. */ + g_autofree gchar *fs_label = g_strdup_printf (_("%s Filesystem"), fs_type); + gchar *cap_label = eel_str_capitalize (fs_label); + if (cap_label != NULL) + { + g_free (fs_label); + fs_label = cap_label; + } + + gtk_label_set_text (self->type_file_system_label, fs_label); + gtk_widget_show (GTK_WIDGET (self->type_file_system_label)); + } + } + + gtk_level_bar_set_value (self->disk_space_level_bar, (double) self->volume_used / (double) self->volume_capacity); + /* display color changing based on filled level */ + gtk_level_bar_add_offset_value (self->disk_space_level_bar, GTK_LEVEL_BAR_OFFSET_FULL, 0.0); +} + +static void +setup_volume_usage_widget (NautilusPropertiesWindow *self) +{ + NautilusFile *file; + g_autofree gchar *uri = NULL; + g_autoptr (GFile) location = NULL; + g_autoptr (GFileInfo) info = NULL; + + file = get_original_file (self); + + uri = nautilus_file_get_activation_uri (file); + + location = g_file_new_for_uri (uri); + info = g_file_query_filesystem_info (location, "filesystem::*", NULL, NULL); + + if (info) + { + self->volume_capacity = g_file_info_get_attribute_uint64 (info, G_FILE_ATTRIBUTE_FILESYSTEM_SIZE); + self->volume_free = g_file_info_get_attribute_uint64 (info, G_FILE_ATTRIBUTE_FILESYSTEM_FREE); + if (g_file_info_has_attribute (info, G_FILE_ATTRIBUTE_FILESYSTEM_USED)) + { + self->volume_used = g_file_info_get_attribute_uint64 (info, G_FILE_ATTRIBUTE_FILESYSTEM_USED); + } + else + { + self->volume_used = self->volume_capacity - self->volume_free; + } + } + else + { + self->volume_capacity = 0; + self->volume_free = 0; + self->volume_used = 0; + } + + if (self->volume_capacity > 0) + { + setup_volume_information (self); + } +} + +static void +open_parent_folder (NautilusPropertiesWindow *self) +{ + g_autoptr (GFile) parent_location = NULL; + + parent_location = nautilus_file_get_parent_location (get_target_file (self)); + g_return_if_fail (parent_location != NULL); + + nautilus_application_open_location_full (NAUTILUS_APPLICATION (g_application_get_default ()), + parent_location, + NAUTILUS_OPEN_FLAG_NEW_WINDOW, + &(GList){get_original_file (self), NULL}, + NULL, NULL); +} + +static void +open_link_target (NautilusPropertiesWindow *self) +{ + g_autofree gchar *link_target_uri = NULL; + g_autoptr (GFile) link_target_location = NULL; + g_autoptr (NautilusFile) link_target_file = NULL; + g_autoptr (GFile) parent_location = NULL; + + link_target_uri = nautilus_file_get_symbolic_link_target_uri (get_target_file (self)); + g_return_if_fail (link_target_uri != NULL); + link_target_location = g_file_new_for_uri (link_target_uri); + link_target_file = nautilus_file_get (link_target_location); + parent_location = nautilus_file_get_parent_location (link_target_file); + g_return_if_fail (parent_location != NULL); + + nautilus_application_open_location_full (NAUTILUS_APPLICATION (g_application_get_default ()), + parent_location, + NAUTILUS_OPEN_FLAG_NEW_WINDOW, + &(GList){link_target_file, NULL}, + NULL, NULL); +} + +static void +open_in_disks (NautilusPropertiesWindow *self) +{ + NautilusDBusLauncher *launcher = nautilus_dbus_launcher_get (); + g_autoptr (GMount) mount = NULL; + g_autoptr (GVolume) volume = NULL; + g_autofree gchar *device_identifier = NULL; + GVariant *parameters; + + mount = nautilus_file_get_mount (get_original_file (self)); + volume = (mount != NULL) ? g_mount_get_volume (mount) : NULL; + + if (volume != NULL) + { + device_identifier = g_volume_get_identifier (volume, + G_VOLUME_IDENTIFIER_KIND_UNIX_DEVICE); + } + else + { + g_autoptr (GFile) location = NULL; + g_autofree gchar *path = NULL; + g_autoptr (GUnixMountEntry) mount_entry = NULL; + + location = nautilus_file_get_location (get_original_file (self)); + path = g_file_get_path (location); + mount_entry = (path != NULL) ? g_unix_mount_at (path, NULL) : NULL; + if (mount_entry != NULL) + { + device_identifier = g_strdup (g_unix_mount_get_device_path (mount_entry)); + } + } + + if (device_identifier != NULL) + { + parameters = g_variant_new_parsed ("(objectpath '/org/gnome/DiskUtility', " + "@aay [], {'options': <{'block-device': <%s>}> })", + device_identifier); + } + else + { + parameters = g_variant_new_parsed ("(objectpath '/org/gnome/DiskUtility', @aay [], @a{sv} {})"); + } + + nautilus_dbus_launcher_call (launcher, + NAUTILUS_DBUS_LAUNCHER_DISKS, + "CommandLine", parameters, + GTK_WINDOW (self)); +} + +static void +add_updatable_label (NautilusPropertiesWindow *self, + GtkWidget *label, + const char *file_attribute) +{ + g_object_set_data_full (G_OBJECT (label), "file_attribute", + g_strdup (file_attribute), g_free); + + self->value_fields = g_list_prepend (self->value_fields, label); +} + +static void +setup_basic_page (NautilusPropertiesWindow *self) +{ + gboolean should_show_locations_list_box = FALSE; + + /* Icon pixmap */ + + setup_image_widget (self, should_show_custom_icon_buttons (self)); + + self->icon_chooser = NULL; + + if (!is_multi_file_window (self)) + { + setup_star_button (self); + } + + update_name_field (self); + + if (should_show_volume_usage (self)) + { + gtk_widget_show (self->disk_list_box); + setup_volume_usage_widget (self); + } + + if (should_show_file_type (self)) + { + gtk_widget_show (self->type_value_label); + add_updatable_label (self, self->type_value_label, "detailed_type"); + } + + if (should_show_link_target (self)) + { + gtk_widget_show (self->link_target_row); + add_updatable_label (self, self->link_target_value_label, "link_target"); + + should_show_locations_list_box = TRUE; + } + + if (is_multi_file_window (self) || + nautilus_file_is_directory (get_target_file (self))) + { + /* We have a more efficient way to measure used space in volumes. */ + if (!is_volume_properties (self)) + { + gtk_widget_show (self->contents_box); + setup_contents_field (self); + } + } + else + { + gtk_widget_show (self->size_value_label); + add_updatable_label (self, self->size_value_label, "size"); + } + + if (should_show_location_info (self)) + { + gtk_widget_show (self->parent_folder_row); + add_updatable_label (self, self->parent_folder_value_label, "where"); + + should_show_locations_list_box = TRUE; + } + + if (should_show_trashed_info (self)) + { + gtk_widget_show (self->trashed_list_box); + add_updatable_label (self, self->original_folder_value_label, "trash_orig_path"); + add_updatable_label (self, self->trashed_on_value_label, "trashed_on_full"); + } + + if (should_show_modified_date (self)) + { + gtk_widget_show (self->times_list_box); + gtk_widget_show (self->modified_row); + add_updatable_label (self, self->modified_value_label, "date_modified_full"); + } + + if (should_show_created_date (self)) + { + gtk_widget_show (self->created_row); + gtk_widget_show (self->times_list_box); + add_updatable_label (self, self->created_value_label, "date_created_full"); + } + + if (should_show_accessed_date (self)) + { + gtk_widget_show (self->times_list_box); + gtk_widget_show (self->accessed_row); + add_updatable_label (self, self->accessed_value_label, "date_accessed_full"); + } + + if (should_show_free_space (self)) + { + /* We have a more efficient way to measure free space in volumes. */ + if (!is_volume_properties (self)) + { + gtk_widget_show (self->free_space_value_label); + add_updatable_label (self, self->free_space_value_label, "free_space"); + } + } + + if (should_show_locations_list_box) + { + gtk_widget_show (self->locations_list_box); + } +} + +static FilterType +files_get_filter_type (NautilusPropertiesWindow *self) +{ + FilterType filter_type = NO_FILES_OR_FOLDERS; + + for (GList *l = self->target_files; l != NULL && filter_type != FILES_AND_FOLDERS; l = l->next) + { + NautilusFile *file = NAUTILUS_FILE (l->data); + if (nautilus_file_is_directory (file)) + { + filter_type |= FOLDERS_ONLY; + } + else + { + filter_type |= FILES_ONLY; + } + } + + return filter_type; +} + +static gboolean +file_matches_filter_type (NautilusFile *file, + FilterType filter_type) +{ + gboolean is_directory = nautilus_file_is_directory (file); + + switch (filter_type) + { + case FILES_AND_FOLDERS: + { + return TRUE; + } + + case FILES_ONLY: + { + return !is_directory; + } + + case FOLDERS_ONLY: + { + return is_directory; + } + + default: + { + return FALSE; + } + } +} + +static gboolean +files_has_changable_permissions_directory (NautilusPropertiesWindow *self) +{ + GList *l; + gboolean changable = FALSE; + + for (l = self->target_files; l != NULL; l = l->next) + { + NautilusFile *file; + file = NAUTILUS_FILE (l->data); + if (nautilus_file_is_directory (file) && + nautilus_file_can_get_permissions (file) && + nautilus_file_can_set_permissions (file)) + { + changable = TRUE; + } + else + { + changable = FALSE; + break; + } + } + + return changable; +} + +static void +start_long_operation (NautilusPropertiesWindow *self) +{ + if (self->long_operation_underway == 0) + { + /* start long operation */ + gtk_widget_set_cursor_from_name (GTK_WIDGET (self), "wait"); + } + self->long_operation_underway++; +} + +static void +end_long_operation (NautilusPropertiesWindow *self) +{ + if (gtk_native_get_surface (GTK_NATIVE (self)) != NULL && + self->long_operation_underway == 1) + { + /* finished !! */ + gtk_widget_set_cursor (GTK_WIDGET (self), NULL); + } + self->long_operation_underway--; +} + +static void +permission_change_callback (NautilusFile *file, + GFile *res_loc, + GError *error, + gpointer callback_data) +{ + g_autoptr (NautilusPropertiesWindow) self = NAUTILUS_PROPERTIES_WINDOW (callback_data); + g_assert (self != NULL); + + end_long_operation (self); + + /* Report the error if it's an error. */ + nautilus_report_error_setting_permissions (file, error, GTK_WINDOW (self)); +} + +static void +update_permissions (NautilusPropertiesWindow *self, + guint32 vfs_new_perm, + guint32 vfs_mask, + FilterType filter_type, + gboolean use_original) +{ + for (GList *l = self->target_files; l != NULL; l = l->next) + { + NautilusFile *file = NAUTILUS_FILE (l->data); + guint32 permissions; + + if (!nautilus_file_can_get_permissions (file)) + { + continue; + } + + if (!nautilus_file_can_get_permissions (file) || !file_matches_filter_type (file, filter_type)) + { + continue; + } + + permissions = nautilus_file_get_permissions (file); + if (use_original) + { + gpointer ptr; + if (g_hash_table_lookup_extended (self->initial_permissions, + file, NULL, &ptr)) + { + permissions = (permissions & ~vfs_mask) | (GPOINTER_TO_INT (ptr) & vfs_mask); + } + } + else + { + permissions = (permissions & ~vfs_mask) | vfs_new_perm; + } + + start_long_operation (self); + g_object_ref (self); + nautilus_file_set_permissions + (file, permissions, + permission_change_callback, + self); + } +} + +static void +execution_bit_changed (NautilusPropertiesWindow *self, + GParamSpec *params, + GtkWidget *widget) +{ + const guint32 permission_mask = UNIX_PERM_USER_EXEC | UNIX_PERM_GROUP_EXEC | UNIX_PERM_OTHER_EXEC; + const FilterType filter_type = FILES_ONLY; + + /* if activated from switch, switch state is already toggled, thus invert value via XOR. */ + gboolean active = gtk_switch_get_state (self->execution_switch) ^ GTK_IS_SWITCH (widget); + gboolean set_executable = !active; + + update_permissions (self, + set_executable ? permission_mask : 0, + permission_mask, + filter_type, + FALSE); +} + +static gboolean +should_show_exectution_switch (NautilusPropertiesWindow *self) +{ + g_autofree gchar *mime_type = NULL; + + if (is_multi_file_window (self)) + { + return FALSE; + } + + mime_type = nautilus_file_get_mime_type (get_target_file (self)); + return g_content_type_can_be_executable (mime_type); +} + +static void +update_execution_row (GtkWidget *row, + TargetPermissions *target_perm) +{ + NautilusPropertiesWindow *self = target_perm->window; + + if (!should_show_exectution_switch (self)) + { + gtk_widget_hide (GTK_WIDGET (self->execution_row)); + } + else + { + g_signal_handlers_block_by_func (self->execution_switch, + G_CALLBACK (execution_bit_changed), + self); + + gtk_switch_set_state (self->execution_switch, + target_perm->file_exec_permissions == PERMISSION_EXEC); + + g_signal_handlers_unblock_by_func (self->execution_switch, + G_CALLBACK (execution_bit_changed), + self); + + gtk_widget_set_sensitive (row, + target_perm->can_set_any_file_permission); + + gtk_widget_show (GTK_WIDGET (self->execution_row)); + } +} + +static void +on_permission_row_change (AdwComboRow *row, + GParamSpec *pspec, + NautilusPropertiesWindow *self) +{ + GListModel *list = adw_combo_row_get_model (row); + guint position = adw_combo_row_get_selected (row); + g_autoptr (NautilusPermissionEntry) entry = NULL; + FilterType filter_type; + gboolean use_original; + PermissionType type; + PermissionValue mask; + guint32 vfs_new_perm, vfs_mask; + + g_assert (NAUTILUS_IS_PROPERTIES_WINDOW (self)); + + if (position == GTK_INVALID_LIST_POSITION) + { + return; + } + + filter_type = GPOINTER_TO_INT (g_object_get_data (G_OBJECT (row), "filter-type")); + type = GPOINTER_TO_INT (g_object_get_data (G_OBJECT (row), "permission-type")); + + mask = PERMISSION_READ | PERMISSION_WRITE | ((filter_type == FOLDERS_ONLY) * PERMISSION_EXEC); + vfs_mask = permission_to_vfs (type, mask); + + entry = g_list_model_get_item (list, position); + vfs_new_perm = permission_to_vfs (type, entry->permission_value); + use_original = entry->permission_value & PERMISSION_INCONSISTENT; + + update_permissions (self, vfs_new_perm, vfs_mask, + filter_type, use_original); +} + +static void +list_store_append_nautilus_permission_entry (GListStore *list, + PermissionValue permission_value, + gboolean describes_folder) +{ + g_autoptr (NautilusPermissionEntry) entry = g_object_new (NAUTILUS_TYPE_PERMISSION_ENTRY, NULL); + + entry->name = g_strdup (permission_value_to_string (permission_value, describes_folder)); + entry->permission_value = permission_value; + + g_list_store_append (list, entry); +} + +static gint +get_permission_value_list_position (GListModel *list, + PermissionValue wanted_permissions) +{ + const guint n_entries = g_list_model_get_n_items (list); + + for (guint position = 0; position < n_entries; position += 1) + { + g_autoptr (NautilusPermissionEntry) entry = g_list_model_get_item (list, position); + if (entry->permission_value == wanted_permissions) + { + return position; + } + } + + return -1; +} + +static void +update_permission_row (AdwComboRow *row, + TargetPermissions *target_perm) +{ + NautilusPropertiesWindow *self = target_perm->window; + PermissionType type; + PermissionValue permissions_to_show; + FilterType filter_type; + gboolean is_folder; + GListModel *model; + gint position; + + model = adw_combo_row_get_model (row); + + filter_type = GPOINTER_TO_INT (g_object_get_data (G_OBJECT (row), "filter-type")); + is_folder = (FOLDERS_ONLY == filter_type); + type = GPOINTER_TO_INT (g_object_get_data (G_OBJECT (row), "permission-type")); + + permissions_to_show = is_folder ? target_perm->folder_permissions[type] : + target_perm->file_permissions[type] & ~PERMISSION_EXEC; + + g_signal_handlers_block_by_func (G_OBJECT (row), + G_CALLBACK (on_permission_row_change), + self); + + position = get_permission_value_list_position (model, permissions_to_show); + + if (position == GTK_INVALID_LIST_POSITION) + { + /* configured permissions not listed, create new entry */ + position = g_list_model_get_n_items (model); + list_store_append_nautilus_permission_entry (G_LIST_STORE (model), permissions_to_show, is_folder); + } + + adw_combo_row_set_selected (row, position); + + /* Also enable if no files found (for recursive + * file changes when only selecting folders) */ + gtk_widget_set_sensitive (GTK_WIDGET (row), is_folder ? + target_perm->can_set_all_folder_permission : + target_perm->can_set_all_file_permission); + + g_signal_handlers_unblock_by_func (G_OBJECT (row), + G_CALLBACK (on_permission_row_change), + self); +} + +static void +setup_permissions_combo_box (GtkComboBox *combo, + PermissionType type, + FilterType filter_type) +{ + g_autoptr (GtkListStore) store = NULL; + GtkCellRenderer *cell; + GtkTreeIter iter; + + store = gtk_list_store_new (NUM_COLUMNS, G_TYPE_STRING, G_TYPE_INT, G_TYPE_BOOLEAN, G_TYPE_STRING); + gtk_combo_box_set_model (combo, GTK_TREE_MODEL (store)); + gtk_combo_box_set_id_column (GTK_COMBO_BOX (combo), COLUMN_ID); + + g_object_set_data (G_OBJECT (combo), "filter-type", GINT_TO_POINTER (filter_type)); + g_object_set_data (G_OBJECT (combo), "permission-type", GINT_TO_POINTER (type)); + + if (filter_type == FOLDERS_ONLY) + { + if (type != PERMISSION_USER) + { + gtk_list_store_append (store, &iter); + gtk_list_store_set (store, &iter, + /* Translators: this is referred to the permissions + * the user has in a directory. + */ + COLUMN_NAME, _("None"), + COLUMN_VALUE, PERMISSION_NONE, + COLUMN_ID, "none", + -1); + } + gtk_list_store_append (store, &iter); + gtk_list_store_set (store, &iter, + COLUMN_NAME, _("List files only"), + COLUMN_VALUE, PERMISSION_READ, + COLUMN_ID, "r", + -1); + gtk_list_store_append (store, &iter); + gtk_list_store_set (store, &iter, + COLUMN_NAME, _("Access files"), + COLUMN_VALUE, PERMISSION_READ | PERMISSION_EXEC, + COLUMN_ID, "rx", + -1); + gtk_list_store_append (store, &iter); + gtk_list_store_set (store, &iter, + COLUMN_NAME, _("Create and delete files"), + COLUMN_VALUE, PERMISSION_READ | PERMISSION_EXEC | PERMISSION_WRITE, + COLUMN_ID, "rwx", + -1); + } + else + { + if (type != PERMISSION_USER) + { + gtk_list_store_append (store, &iter); + gtk_list_store_set (store, &iter, + COLUMN_NAME, _("None"), + COLUMN_VALUE, PERMISSION_NONE, + COLUMN_ID, "none", + -1); + } + gtk_list_store_append (store, &iter); + gtk_list_store_set (store, &iter, + COLUMN_NAME, _("Read-only"), + COLUMN_VALUE, PERMISSION_READ, + COLUMN_ID, "r", + -1); + gtk_list_store_append (store, &iter); + gtk_list_store_set (store, &iter, + COLUMN_NAME, _("Read and write"), + COLUMN_VALUE, PERMISSION_READ | PERMISSION_WRITE, + COLUMN_ID, "rw", + -1); + } + + cell = gtk_cell_renderer_text_new (); + gtk_cell_layout_pack_start (GTK_CELL_LAYOUT (combo), cell, TRUE); + gtk_cell_layout_set_attributes (GTK_CELL_LAYOUT (combo), cell, + "text", COLUMN_NAME, + NULL); +} + +static gboolean +all_can_get_permissions (GList *file_list) +{ + GList *l; + for (l = file_list; l != NULL; l = l->next) + { + NautilusFile *file; + + file = NAUTILUS_FILE (l->data); + + if (!nautilus_file_can_get_permissions (file)) + { + return FALSE; + } + } + + return TRUE; +} + +static gboolean +all_can_set_permissions (GList *file_list) +{ + GList *l; + for (l = file_list; l != NULL; l = l->next) + { + NautilusFile *file; + + file = NAUTILUS_FILE (l->data); + + if (!nautilus_file_can_set_permissions (file)) + { + return FALSE; + } + } + + return TRUE; +} + +static GHashTable * +get_initial_permissions (GList *file_list) +{ + GHashTable *ret; + GList *l; + + ret = g_hash_table_new (g_direct_hash, + g_direct_equal); + + for (l = file_list; l != NULL; l = l->next) + { + guint32 permissions; + NautilusFile *file; + + file = NAUTILUS_FILE (l->data); + + permissions = nautilus_file_get_permissions (file); + g_hash_table_insert (ret, file, + GINT_TO_POINTER (permissions)); + } + + return ret; +} + +static GListModel * +create_permission_list_model (PermissionType type, + FilterType filter_type) +{ + GListStore *store = g_list_store_new (NAUTILUS_TYPE_PERMISSION_ENTRY); + + if (type != PERMISSION_USER) + { + list_store_append_nautilus_permission_entry (store, PERMISSION_NONE, /* unused */ FALSE); + } + + if (filter_type == FOLDERS_ONLY) + { + list_store_append_nautilus_permission_entry (store, PERMISSION_READ, TRUE); + list_store_append_nautilus_permission_entry (store, PERMISSION_READ | PERMISSION_EXEC, TRUE); + list_store_append_nautilus_permission_entry (store, PERMISSION_READ | PERMISSION_EXEC | PERMISSION_WRITE, TRUE); + } + else + { + list_store_append_nautilus_permission_entry (store, PERMISSION_READ, FALSE); + list_store_append_nautilus_permission_entry (store, PERMISSION_READ | PERMISSION_WRITE, FALSE); + } + + return G_LIST_MODEL (store); +} + +static void +create_permissions_row (NautilusPropertiesWindow *self, + AdwComboRow *row, + PermissionType permission_type, + FilterType filter_type) +{ + g_autoptr (GtkExpression) expression = NULL; + g_autoptr (GListModel) model = NULL; + + expression = gtk_property_expression_new (NAUTILUS_TYPE_PERMISSION_ENTRY, NULL, "name"); + adw_combo_row_set_expression (row, expression); + + gtk_widget_show (GTK_WIDGET (row)); + + g_object_set_data (G_OBJECT (row), "permission-type", GINT_TO_POINTER (permission_type)); + g_object_set_data (G_OBJECT (row), "filter-type", GINT_TO_POINTER (filter_type)); + model = create_permission_list_model (permission_type, filter_type); + adw_combo_row_set_model (row, model); + + self->permission_rows = g_list_prepend (self->permission_rows, row); + g_signal_connect (row, "notify::selected", G_CALLBACK (on_permission_row_change), self); +} + +static void +create_simple_permissions (NautilusPropertiesWindow *self) +{ + FilterType filter_type = files_get_filter_type (self); + + g_assert (filter_type != NO_FILES_OR_FOLDERS); + + setup_ownership_row (self, self->owner_row); + setup_ownership_row (self, self->group_row); + + if (filter_type == FILES_AND_FOLDERS) + { + /* owner */ + create_permissions_row (self, self->owner_folder_access_row, + PERMISSION_USER, FOLDERS_ONLY); + create_permissions_row (self, self->owner_file_access_row, + PERMISSION_USER, FILES_ONLY); + /* group */ + create_permissions_row (self, self->group_folder_access_row, + PERMISSION_GROUP, FOLDERS_ONLY); + create_permissions_row (self, self->group_file_access_row, + PERMISSION_GROUP, FILES_ONLY); + /* others */ + create_permissions_row (self, self->others_folder_access_row, + PERMISSION_OTHER, FOLDERS_ONLY); + create_permissions_row (self, self->others_file_access_row, + PERMISSION_OTHER, FILES_ONLY); + } + else + { + create_permissions_row (self, self->owner_access_row, + PERMISSION_USER, filter_type); + create_permissions_row (self, self->group_access_row, + PERMISSION_GROUP, filter_type); + create_permissions_row (self, self->others_access_row, + PERMISSION_OTHER, filter_type); + } + + /* Connect execution bit switch, independent of whether it will be visible or not. */ + g_signal_connect_swapped (self->execution_row, "activated", + G_CALLBACK (execution_bit_changed), + self); + g_signal_connect_swapped (self->execution_switch, "notify::active", + G_CALLBACK (execution_bit_changed), + self); +} + +static void +set_recursive_permissions_done (gboolean success, + gpointer callback_data) +{ + g_autoptr (NautilusPropertiesWindow) self = NAUTILUS_PROPERTIES_WINDOW (callback_data); + end_long_operation (self); +} + +static void +on_change_permissions_response (GtkDialog *dialog, + int response, + NautilusPropertiesWindow *self) +{ + guint32 file_permission, file_permission_mask; + guint32 dir_permission, dir_permission_mask; + guint32 vfs_mask, vfs_new_perm; + GtkWidget *combo; + gboolean use_original; + FilterType filter_type; + GList *l; + GtkTreeModel *model; + GtkTreeIter iter; + PermissionType type; + int new_perm, mask; + + if (response != GTK_RESPONSE_OK) + { + g_clear_pointer (&self->change_permission_combos, g_list_free); + gtk_window_destroy (GTK_WINDOW (dialog)); + return; + } + + file_permission = 0; + file_permission_mask = 0; + dir_permission = 0; + dir_permission_mask = 0; + + /* Simple mode, minus exec checkbox */ + for (l = self->change_permission_combos; l != NULL; l = l->next) + { + combo = l->data; + + if (!gtk_combo_box_get_active_iter (GTK_COMBO_BOX (combo), &iter)) + { + continue; + } + + type = GPOINTER_TO_INT (g_object_get_data (G_OBJECT (combo), "permission-type")); + filter_type = GPOINTER_TO_INT (g_object_get_data (G_OBJECT (combo), "filter-type")); + + model = gtk_combo_box_get_model (GTK_COMBO_BOX (combo)); + gtk_tree_model_get (model, &iter, + COLUMN_VALUE, &new_perm, + COLUMN_USE_ORIGINAL, &use_original, -1); + if (use_original) + { + continue; + } + vfs_new_perm = permission_to_vfs (type, new_perm); + + mask = PERMISSION_READ | PERMISSION_WRITE; + if (filter_type == FOLDERS_ONLY) + { + mask |= PERMISSION_EXEC; + } + vfs_mask = permission_to_vfs (type, mask); + + if (filter_type == FOLDERS_ONLY) + { + dir_permission_mask |= vfs_mask; + dir_permission |= vfs_new_perm; + } + else + { + file_permission_mask |= vfs_mask; + file_permission |= vfs_new_perm; + } + } + + for (l = self->target_files; l != NULL; l = l->next) + { + NautilusFile *file; + + file = NAUTILUS_FILE (l->data); + + if (nautilus_file_is_directory (file) && + nautilus_file_can_set_permissions (file)) + { + g_autofree gchar *uri = NULL; + + uri = nautilus_file_get_uri (file); + start_long_operation (self); + g_object_ref (self); + nautilus_file_set_permissions_recursive (uri, + file_permission, + file_permission_mask, + dir_permission, + dir_permission_mask, + set_recursive_permissions_done, + self); + } + } + g_clear_pointer (&self->change_permission_combos, g_list_free); + gtk_window_destroy (GTK_WINDOW (dialog)); +} + +static void +set_active_from_umask (GtkComboBox *combo, + PermissionType type, + FilterType filter_type) +{ + mode_t initial; + mode_t mask; + mode_t p; + const char *id; + + if (filter_type == FOLDERS_ONLY) + { + initial = (S_IRWXU | S_IRWXG | S_IRWXO); + } + else + { + initial = (S_IRUSR | S_IWUSR | S_IRGRP | S_IWGRP | S_IROTH | S_IWOTH); + } + + umask (mask = umask (0)); + + p = ~mask & initial; + + if (type == PERMISSION_USER) + { + p &= ~(S_IRWXG | S_IRWXO); + if ((p & S_IRWXU) == S_IRWXU) + { + id = "rwx"; + } + else if ((p & (S_IRUSR | S_IWUSR)) == (S_IRUSR | S_IWUSR)) + { + id = "rw"; + } + else if ((p & (S_IRUSR | S_IXUSR)) == (S_IRUSR | S_IXUSR)) + { + id = "rx"; + } + else if ((p & S_IRUSR) == S_IRUSR) + { + id = "r"; + } + else + { + id = "none"; + } + } + else if (type == PERMISSION_GROUP) + { + p &= ~(S_IRWXU | S_IRWXO); + if ((p & S_IRWXG) == S_IRWXG) + { + id = "rwx"; + } + else if ((p & (S_IRGRP | S_IWGRP)) == (S_IRGRP | S_IWGRP)) + { + id = "rw"; + } + else if ((p & (S_IRGRP | S_IXGRP)) == (S_IRGRP | S_IXGRP)) + { + id = "rx"; + } + else if ((p & S_IRGRP) == S_IRGRP) + { + id = "r"; + } + else + { + id = "none"; + } + } + else + { + p &= ~(S_IRWXU | S_IRWXG); + if ((p & S_IRWXO) == S_IRWXO) + { + id = "rwx"; + } + else if ((p & (S_IROTH | S_IWOTH)) == (S_IROTH | S_IWOTH)) + { + id = "rw"; + } + else if ((p & (S_IROTH | S_IXOTH)) == (S_IROTH | S_IXOTH)) + { + id = "rx"; + } + else if ((p & S_IROTH) == S_IROTH) + { + id = "r"; + } + else + { + id = "none"; + } + } + + gtk_combo_box_set_active_id (combo, id); +} + +static void +on_change_permissions_clicked (GtkWidget *button, + NautilusPropertiesWindow *self) +{ + GtkWidget *dialog; + GtkComboBox *combo; + g_autoptr (GtkBuilder) change_permissions_builder = NULL; + + change_permissions_builder = gtk_builder_new_from_resource ("/org/gnome/nautilus/ui/nautilus-file-properties-change-permissions.ui"); + + dialog = GTK_WIDGET (gtk_builder_get_object (change_permissions_builder, "change_permissions_dialog")); + gtk_window_set_transient_for (GTK_WINDOW (dialog), GTK_WINDOW (self)); + + /* Owner Permissions */ + combo = GTK_COMBO_BOX (gtk_builder_get_object (change_permissions_builder, "file_owner_combo")); + setup_permissions_combo_box (combo, PERMISSION_USER, FILES_ONLY); + self->change_permission_combos = g_list_prepend (self->change_permission_combos, + combo); + set_active_from_umask (combo, PERMISSION_USER, FILES_ONLY); + + combo = GTK_COMBO_BOX (gtk_builder_get_object (change_permissions_builder, "folder_owner_combo")); + setup_permissions_combo_box (combo, PERMISSION_USER, FOLDERS_ONLY); + self->change_permission_combos = g_list_prepend (self->change_permission_combos, + combo); + set_active_from_umask (combo, PERMISSION_USER, FOLDERS_ONLY); + + /* Group Permissions */ + combo = GTK_COMBO_BOX (gtk_builder_get_object (change_permissions_builder, "file_group_combo")); + setup_permissions_combo_box (combo, PERMISSION_GROUP, FILES_ONLY); + self->change_permission_combos = g_list_prepend (self->change_permission_combos, + combo); + set_active_from_umask (combo, PERMISSION_GROUP, FILES_ONLY); + + combo = GTK_COMBO_BOX (gtk_builder_get_object (change_permissions_builder, "folder_group_combo")); + setup_permissions_combo_box (combo, PERMISSION_GROUP, FOLDERS_ONLY); + self->change_permission_combos = g_list_prepend (self->change_permission_combos, + combo); + set_active_from_umask (combo, PERMISSION_GROUP, FOLDERS_ONLY); + + /* Others Permissions */ + combo = GTK_COMBO_BOX (gtk_builder_get_object (change_permissions_builder, "file_other_combo")); + setup_permissions_combo_box (combo, PERMISSION_OTHER, FILES_ONLY); + self->change_permission_combos = g_list_prepend (self->change_permission_combos, + combo); + set_active_from_umask (combo, PERMISSION_OTHER, FILES_ONLY); + + combo = GTK_COMBO_BOX (gtk_builder_get_object (change_permissions_builder, "folder_other_combo")); + setup_permissions_combo_box (combo, PERMISSION_OTHER, FOLDERS_ONLY); + self->change_permission_combos = g_list_prepend (self->change_permission_combos, + combo); + set_active_from_umask (combo, PERMISSION_OTHER, FOLDERS_ONLY); + + g_signal_connect (dialog, "response", G_CALLBACK (on_change_permissions_response), self); + gtk_widget_show (dialog); +} + +static void +setup_permissions_page (NautilusPropertiesWindow *self) +{ + GList *file_list; + + file_list = self->original_files; + + self->initial_permissions = NULL; + + if (all_can_get_permissions (file_list) && all_can_get_permissions (self->target_files)) + { + self->initial_permissions = get_initial_permissions (self->target_files); + self->has_recursive_apply = files_has_changable_permissions_directory (self); + + if (!all_can_set_permissions (file_list)) + { + gtk_widget_show (self->not_the_owner_label); + gtk_widget_show (self->bottom_prompt_seperator); + } + + gtk_stack_set_visible_child_name (GTK_STACK (self->permissions_stack), "permissions-box"); + create_simple_permissions (self); + +#ifdef HAVE_SELINUX + gtk_widget_show (self->security_context_list_box); + + /* Stash a copy of the file attribute name in this field for the callback's sake. */ + g_object_set_data_full (G_OBJECT (self->security_context_value_label), "file_attribute", + g_strdup ("selinux_context"), g_free); + + self->value_fields = g_list_prepend (self->value_fields, + self->security_context_value_label); +#endif + + if (self->has_recursive_apply) + { + gtk_widget_show (self->change_permissions_button_box); + g_signal_connect (self->change_permissions_button, "clicked", + G_CALLBACK (on_change_permissions_clicked), + self); + } + } + else + { + /* + * This if block only gets executed if its a single file window, + * in which case the label text needs to be different from the + * default label text. The default label text for a multifile + * window is set in nautilus-properties-window.ui so no else block. + */ + if (!is_multi_file_window (self)) + { + g_autofree gchar *file_name = NULL; + g_autofree gchar *prompt_text = NULL; + + file_name = nautilus_file_get_display_name (get_target_file (self)); + prompt_text = g_strdup_printf (_("The permissions of “%s” could not be determined."), file_name); + adw_status_page_set_description (ADW_STATUS_PAGE (self->unknown_permissions_page), prompt_text); + } + + gtk_stack_set_visible_child_name (GTK_STACK (self->permissions_stack), "permission-indeterminable"); + } +} + +static void +refresh_extension_model_pages (NautilusPropertiesWindow *self) +{ + g_autoptr (GListStore) extensions_list = g_list_store_new (NAUTILUS_TYPE_PROPERTIES_MODEL); + g_autolist (NautilusPropertiesModel) all_models = NULL; + g_autolist (GObject) providers = + nautilus_module_get_extensions_for_type (NAUTILUS_TYPE_PROPERTIES_MODEL_PROVIDER); + + for (GList *l = providers; l != NULL; l = l->next) + { + GList *models = nautilus_properties_model_provider_get_models (l->data, self->original_files); + + all_models = g_list_concat (all_models, models); + } + + for (GList *l = all_models; l != NULL; l = l->next) + { + g_list_store_append (extensions_list, NAUTILUS_PROPERTIES_MODEL (l->data)); + } + + gtk_widget_set_visible (self->extension_models_list_box, + g_list_model_get_n_items (G_LIST_MODEL (extensions_list)) > 0); + + gtk_list_box_bind_model (GTK_LIST_BOX (self->extension_models_list_box), + G_LIST_MODEL (extensions_list), + (GtkListBoxCreateWidgetFunc) add_extension_model_page, + self, + NULL); +} + +static gboolean +should_show_permissions (NautilusPropertiesWindow *self) +{ + GList *l; + + /* Don't show permissions for Trash and Computer since they're not + * really file system objects. + */ + for (l = self->original_files; l != NULL; l = l->next) + { + if (nautilus_file_is_in_trash (NAUTILUS_FILE (l->data)) || + nautilus_file_is_in_recent (NAUTILUS_FILE (l->data))) + { + return FALSE; + } + } + + return TRUE; +} + +static char * +get_pending_key (GList *file_list) +{ + GList *uris = NULL; + GList *l; + GString *key; + char *ret; + + uris = NULL; + for (l = file_list; l != NULL; l = l->next) + { + uris = g_list_prepend (uris, nautilus_file_get_uri (NAUTILUS_FILE (l->data))); + } + uris = g_list_sort (uris, (GCompareFunc) strcmp); + + key = g_string_new (""); + for (l = uris; l != NULL; l = l->next) + { + g_string_append (key, l->data); + g_string_append (key, ";"); + } + + g_list_free_full (uris, g_free); + + ret = key->str; + g_string_free (key, FALSE); + + return ret; +} + +static StartupData * +startup_data_new (GList *original_files, + GList *target_files, + const char *pending_key, + GtkWidget *parent_widget, + GtkWindow *parent_window, + const char *startup_id, + NautilusPropertiesWindowCallback callback, + gpointer callback_data, + NautilusPropertiesWindow *window) +{ + StartupData *data; + GList *l; + + data = g_new0 (StartupData, 1); + data->original_files = nautilus_file_list_copy (original_files); + data->target_files = nautilus_file_list_copy (target_files); + data->parent_widget = parent_widget; + data->parent_window = parent_window; + data->startup_id = g_strdup (startup_id); + data->pending_key = g_strdup (pending_key); + data->pending_files = g_hash_table_new (g_direct_hash, + g_direct_equal); + data->callback = callback; + data->callback_data = callback_data; + data->window = window; + + for (l = data->target_files; l != NULL; l = l->next) + { + g_hash_table_insert (data->pending_files, l->data, l->data); + } + + return data; +} + +static void +startup_data_free (StartupData *data) +{ + nautilus_file_list_free (data->original_files); + nautilus_file_list_free (data->target_files); + g_hash_table_destroy (data->pending_files); + g_free (data->pending_key); + g_free (data->startup_id); + g_free (data); +} + +static void +file_changed_callback (NautilusFile *file, + gpointer user_data) +{ + NautilusPropertiesWindow *self = NAUTILUS_PROPERTIES_WINDOW (user_data); + + if (!g_list_find (self->changed_files, file)) + { + nautilus_file_ref (file); + self->changed_files = g_list_prepend (self->changed_files, file); + schedule_files_update (self); + } +} + +static NautilusPropertiesWindow * +create_properties_window (StartupData *startup_data) +{ + NautilusPropertiesWindow *window; + GList *l; + + window = NAUTILUS_PROPERTIES_WINDOW (g_object_new (NAUTILUS_TYPE_PROPERTIES_WINDOW, + NULL)); + + window->original_files = nautilus_file_list_copy (startup_data->original_files); + + window->target_files = nautilus_file_list_copy (startup_data->target_files); + + if (startup_data->parent_widget) + { + gtk_window_set_display (GTK_WINDOW (window), + gtk_widget_get_display (startup_data->parent_widget)); + } + + if (startup_data->parent_window) + { + gtk_window_set_transient_for (GTK_WINDOW (window), startup_data->parent_window); + } + + if (startup_data->startup_id) + { + gtk_window_set_startup_id (GTK_WINDOW (window), startup_data->startup_id); + } + + /* Start monitoring the file attributes we display. Note that some + * of the attributes are for the original file, and some for the + * target files. + */ + + for (l = window->original_files; l != NULL; l = l->next) + { + NautilusFile *file; + NautilusFileAttributes attributes; + + file = NAUTILUS_FILE (l->data); + + attributes = + NAUTILUS_FILE_ATTRIBUTES_FOR_ICON | + NAUTILUS_FILE_ATTRIBUTE_INFO; + + nautilus_file_monitor_add (file, + &window->original_files, + attributes); + } + + for (l = window->target_files; l != NULL; l = l->next) + { + NautilusFile *file; + NautilusFileAttributes attributes; + + file = NAUTILUS_FILE (l->data); + + attributes = 0; + if (nautilus_file_is_directory (file)) + { + attributes |= NAUTILUS_FILE_ATTRIBUTE_DEEP_COUNTS; + } + + attributes |= NAUTILUS_FILE_ATTRIBUTE_INFO; + nautilus_file_monitor_add (file, &window->target_files, attributes); + } + + for (l = window->target_files; l != NULL; l = l->next) + { + g_signal_connect_object (NAUTILUS_FILE (l->data), + "changed", + G_CALLBACK (file_changed_callback), + G_OBJECT (window), + 0); + } + + for (l = window->original_files; l != NULL; l = l->next) + { + g_signal_connect_object (NAUTILUS_FILE (l->data), + "changed", + G_CALLBACK (file_changed_callback), + G_OBJECT (window), + 0); + } + + /* Create the pages. */ + setup_basic_page (window); + + if (should_show_permissions (window)) + { + setup_permissions_page (window); + gtk_widget_show (window->permissions_navigation_row); + } + + if (should_show_exectution_switch (window)) + { + gtk_widget_show (GTK_WIDGET (window->execution_row)); + } + + /* Add available extension models pages */ + refresh_extension_model_pages (window); + + /* Update from initial state */ + properties_window_update (window, NULL); + + return window; +} + +static GList * +get_target_file_list (GList *original_files) +{ + return g_list_copy_deep (original_files, + (GCopyFunc) get_target_file_for_original_file, + NULL); +} + +static void +properties_window_finish (StartupData *data) +{ + gboolean cancel_timed_wait; + + if (data->parent_widget != NULL) + { + g_signal_handlers_disconnect_by_data (data->parent_widget, + data); + } + if (data->window != NULL) + { + g_signal_handlers_disconnect_by_data (data->window, + data); + } + + cancel_timed_wait = (data->window == NULL && !data->cancelled); + remove_pending (data, TRUE, cancel_timed_wait); + + startup_data_free (data); +} + +static void +cancel_create_properties_window_callback (gpointer callback_data) +{ + StartupData *data; + + data = callback_data; + data->cancelled = TRUE; + + properties_window_finish (data); +} + +static void +parent_widget_destroyed_callback (GtkWidget *widget, + gpointer callback_data) +{ + g_assert (widget == ((StartupData *) callback_data)->parent_widget); + + properties_window_finish ((StartupData *) callback_data); +} + +static void +cancel_call_when_ready_callback (gpointer key, + gpointer value, + gpointer user_data) +{ + nautilus_file_cancel_call_when_ready + (NAUTILUS_FILE (key), + is_directory_ready_callback, + user_data); +} + +static void +remove_pending (StartupData *startup_data, + gboolean cancel_call_when_ready, + gboolean cancel_timed_wait) +{ + if (cancel_call_when_ready) + { + g_hash_table_foreach (startup_data->pending_files, + cancel_call_when_ready_callback, + startup_data); + } + if (cancel_timed_wait) + { + eel_timed_wait_stop + (cancel_create_properties_window_callback, startup_data); + } + if (startup_data->pending_key != NULL) + { + g_hash_table_remove (pending_lists, startup_data->pending_key); + } +} + +static gboolean +widget_on_destroy (GtkWidget *widget, + gpointer user_data) +{ + StartupData *data = (StartupData *) user_data; + + + if (data->callback != NULL) + { + data->callback (data->callback_data); + } + + properties_window_finish (data); + + return GDK_EVENT_PROPAGATE; +} + +static void +is_directory_ready_callback (NautilusFile *file, + gpointer data) +{ + StartupData *startup_data; + + startup_data = data; + + g_hash_table_remove (startup_data->pending_files, file); + + if (g_hash_table_size (startup_data->pending_files) == 0) + { + NautilusPropertiesWindow *new_window; + + new_window = create_properties_window (startup_data); + + startup_data->window = new_window; + + remove_pending (startup_data, FALSE, TRUE); + + gtk_window_present (GTK_WINDOW (new_window)); + g_signal_connect (GTK_WIDGET (new_window), "destroy", + G_CALLBACK (widget_on_destroy), startup_data); + + /* We wish the label to be selectable, but not selected by default. */ + gtk_label_select_region (GTK_LABEL (new_window->name_value_label), -1, -1); + } +} + +void +nautilus_properties_window_present (GList *original_files, + GtkWidget *parent_widget, + const gchar *startup_id, + NautilusPropertiesWindowCallback callback, + gpointer callback_data) +{ + GList *l, *next; + GtkWindow *parent_window; + StartupData *startup_data; + g_autolist (NautilusFile) target_files = NULL; + g_autofree char *pending_key = NULL; + + g_return_if_fail (original_files != NULL); + g_return_if_fail (parent_widget == NULL || GTK_IS_WIDGET (parent_widget)); + + if (pending_lists == NULL) + { + pending_lists = g_hash_table_new (g_str_hash, g_str_equal); + } + + pending_key = get_pending_key (original_files); + + /* Look to see if we're already waiting for a window for this file. */ + if (g_hash_table_lookup (pending_lists, pending_key) != NULL) + { + /* FIXME: No callback is done if this happen. In practice, it's a quite + * corner case + */ + return; + } + + target_files = get_target_file_list (original_files); + + if (parent_widget) + { + parent_window = GTK_WINDOW (gtk_widget_get_ancestor (parent_widget, GTK_TYPE_WINDOW)); + } + else + { + parent_window = NULL; + } + + startup_data = startup_data_new (original_files, + target_files, + pending_key, + parent_widget, + parent_window, + startup_id, + callback, + callback_data, + NULL); + + /* Wait until we can tell whether it's a directory before showing, since + * some one-time layout decisions depend on that info. + */ + + g_hash_table_insert (pending_lists, startup_data->pending_key, startup_data->pending_key); + if (parent_widget) + { + g_signal_connect (parent_widget, "destroy", + G_CALLBACK (parent_widget_destroyed_callback), startup_data); + } + + eel_timed_wait_start + (cancel_create_properties_window_callback, + startup_data, + _("Creating Properties window."), + parent_window == NULL ? NULL : GTK_WINDOW (parent_window)); + + for (l = startup_data->target_files; l != NULL; l = next) + { + next = l->next; + nautilus_file_call_when_ready + (NAUTILUS_FILE (l->data), + NAUTILUS_FILE_ATTRIBUTE_INFO, + is_directory_ready_callback, + startup_data); + } +} + +static void +real_dispose (GObject *object) +{ + NautilusPropertiesWindow *self; + + self = NAUTILUS_PROPERTIES_WINDOW (object); + + unschedule_or_cancel_group_change (self); + unschedule_or_cancel_owner_change (self); + + g_list_foreach (self->original_files, + (GFunc) nautilus_file_monitor_remove, + &self->original_files); + g_clear_list (&self->original_files, (GDestroyNotify) nautilus_file_unref); + + g_list_foreach (self->target_files, + (GFunc) nautilus_file_monitor_remove, + &self->target_files); + g_clear_list (&self->target_files, (GDestroyNotify) nautilus_file_unref); + + g_clear_list (&self->changed_files, (GDestroyNotify) nautilus_file_unref); + + g_clear_handle_id (&self->deep_count_spinner_timeout_id, g_source_remove); + + while (self->deep_count_files) + { + stop_deep_count_for_file (self, self->deep_count_files->data); + } + + g_clear_list (&self->permission_rows, NULL); + + g_clear_list (&self->change_permission_combos, NULL); + + g_clear_pointer (&self->initial_permissions, g_hash_table_destroy); + + g_clear_list (&self->value_fields, NULL); + + g_clear_handle_id (&self->update_directory_contents_timeout_id, g_source_remove); + g_clear_handle_id (&self->update_files_timeout_id, g_source_remove); + + G_OBJECT_CLASS (nautilus_properties_window_parent_class)->dispose (object); +} + +static void +real_finalize (GObject *object) +{ + NautilusPropertiesWindow *self; + + self = NAUTILUS_PROPERTIES_WINDOW (object); + + g_list_free_full (self->mime_list, g_free); + + G_OBJECT_CLASS (nautilus_properties_window_parent_class)->finalize (object); +} + +/* icon selection callback to set the image of the file object to the selected file */ +static void +set_icon (const char *icon_uri, + NautilusPropertiesWindow *self) +{ + NautilusFile *file; + g_autofree gchar *icon_path = NULL; + + g_assert (icon_uri != NULL); + g_assert (NAUTILUS_IS_PROPERTIES_WINDOW (self)); + + icon_path = g_filename_from_uri (icon_uri, NULL, NULL); + /* we don't allow remote URIs */ + if (icon_path != NULL) + { + GList *l; + + for (l = self->original_files; l != NULL; l = l->next) + { + g_autofree gchar *file_uri = NULL; + g_autoptr (GFile) file_location = NULL; + g_autoptr (GFile) icon_location = NULL; + g_autofree gchar *real_icon_uri = NULL; + + file = NAUTILUS_FILE (l->data); + file_uri = nautilus_file_get_uri (file); + file_location = nautilus_file_get_location (file); + icon_location = g_file_new_for_uri (icon_uri); + + /* ’Tis a little bit of a misnomer. Actually a path. */ + real_icon_uri = g_file_get_relative_path (icon_location, + file_location); + + if (real_icon_uri == NULL) + { + real_icon_uri = g_strdup (icon_uri); + } + + nautilus_file_set_metadata (file, NAUTILUS_METADATA_KEY_CUSTOM_ICON, NULL, real_icon_uri); + } + } +} + +static void +custom_icon_file_chooser_response_cb (GtkDialog *dialog, + gint response, + NautilusPropertiesWindow *self) +{ + switch (response) + { + case GTK_RESPONSE_NO: + { + reset_icon (self); + } + break; + + case GTK_RESPONSE_OK: + { + g_autoptr (GFile) location = NULL; + g_autofree gchar *uri = NULL; + + location = gtk_file_chooser_get_file (GTK_FILE_CHOOSER (dialog)); + if (location != NULL) + { + uri = g_file_get_uri (location); + set_icon (uri, self); + } + else + { + reset_icon (self); + } + } + break; + + default: + { + } + break; + } + + gtk_widget_hide (GTK_WIDGET (dialog)); +} + +static void +select_image_button_callback (GtkWidget *widget, + NautilusPropertiesWindow *self) +{ + GtkWidget *dialog; + GtkFileFilter *filter; + GList *l; + NautilusFile *file; + gboolean revert_is_sensitive; + + g_assert (NAUTILUS_IS_PROPERTIES_WINDOW (self)); + + dialog = self->icon_chooser; + + if (dialog == NULL) + { + g_autoptr (GFile) pictures_location = NULL; + dialog = gtk_file_chooser_dialog_new (_("Select Custom Icon"), GTK_WINDOW (self), + GTK_FILE_CHOOSER_ACTION_OPEN, + _("_Revert"), GTK_RESPONSE_NO, + _("_Cancel"), GTK_RESPONSE_CANCEL, + _("_Open"), GTK_RESPONSE_OK, + NULL); + pictures_location = g_file_new_for_path (g_get_user_special_dir (G_USER_DIRECTORY_PICTURES)); + gtk_file_chooser_add_shortcut_folder (GTK_FILE_CHOOSER (dialog), + pictures_location, + NULL); + gtk_window_set_destroy_with_parent (GTK_WINDOW (dialog), TRUE); + gtk_window_set_modal (GTK_WINDOW (dialog), TRUE); + + filter = gtk_file_filter_new (); + gtk_file_filter_add_pixbuf_formats (filter); + gtk_file_chooser_set_filter (GTK_FILE_CHOOSER (dialog), filter); + + self->icon_chooser = dialog; + + g_object_add_weak_pointer (G_OBJECT (dialog), + (gpointer *) &self->icon_chooser); + } + + /* it's likely that the user wants to pick an icon that is inside a local directory */ + if (g_list_length (self->original_files) == 1) + { + file = NAUTILUS_FILE (self->original_files->data); + + if (nautilus_file_is_directory (file)) + { + g_autoptr (GFile) image_location = NULL; + + image_location = nautilus_file_get_location (file); + + if (image_location != NULL) + { + gtk_file_chooser_set_current_folder (GTK_FILE_CHOOSER (dialog), + image_location, + NULL); + } + } + } + + revert_is_sensitive = FALSE; + for (l = self->original_files; l != NULL; l = l->next) + { + g_autofree gchar *image_path = NULL; + + file = NAUTILUS_FILE (l->data); + image_path = nautilus_file_get_metadata (file, NAUTILUS_METADATA_KEY_CUSTOM_ICON, NULL); + revert_is_sensitive = (image_path != NULL); + + if (revert_is_sensitive) + { + break; + } + } + gtk_dialog_set_response_sensitive (GTK_DIALOG (dialog), GTK_RESPONSE_NO, revert_is_sensitive); + + g_signal_connect (dialog, "response", + G_CALLBACK (custom_icon_file_chooser_response_cb), self); + gtk_widget_show (dialog); +} + +static void +nautilus_properties_window_class_init (NautilusPropertiesWindowClass *klass) +{ + GtkWidgetClass *widget_class; + GObjectClass *oclass; + + widget_class = GTK_WIDGET_CLASS (klass); + oclass = G_OBJECT_CLASS (klass); + oclass->dispose = real_dispose; + oclass->finalize = real_finalize; + + gtk_widget_class_add_binding (widget_class, + GDK_KEY_Escape, 0, + (GtkShortcutFunc) gtk_window_close, NULL); + + gtk_widget_class_set_template_from_resource (widget_class, "/org/gnome/nautilus/ui/nautilus-properties-window.ui"); + + gtk_widget_class_bind_template_child (widget_class, NautilusPropertiesWindow, page_stack); + gtk_widget_class_bind_template_child (widget_class, NautilusPropertiesWindow, icon_stack); + gtk_widget_class_bind_template_child (widget_class, NautilusPropertiesWindow, icon_image); + gtk_widget_class_bind_template_child (widget_class, NautilusPropertiesWindow, icon_button); + gtk_widget_class_bind_template_child (widget_class, NautilusPropertiesWindow, icon_button_image); + gtk_widget_class_bind_template_child (widget_class, NautilusPropertiesWindow, star_button); + gtk_widget_class_bind_template_child (widget_class, NautilusPropertiesWindow, name_value_label); + gtk_widget_class_bind_template_child (widget_class, NautilusPropertiesWindow, type_value_label); + gtk_widget_class_bind_template_child (widget_class, NautilusPropertiesWindow, type_file_system_label); + gtk_widget_class_bind_template_child (widget_class, NautilusPropertiesWindow, size_value_label); + gtk_widget_class_bind_template_child (widget_class, NautilusPropertiesWindow, contents_box); + gtk_widget_class_bind_template_child (widget_class, NautilusPropertiesWindow, contents_value_label); + gtk_widget_class_bind_template_child (widget_class, NautilusPropertiesWindow, contents_spinner); + gtk_widget_class_bind_template_child (widget_class, NautilusPropertiesWindow, bottom_prompt_seperator); + gtk_widget_class_bind_template_child (widget_class, NautilusPropertiesWindow, disk_list_box); + gtk_widget_class_bind_template_child (widget_class, NautilusPropertiesWindow, disk_space_level_bar); + gtk_widget_class_bind_template_child (widget_class, NautilusPropertiesWindow, disk_space_used_value); + gtk_widget_class_bind_template_child (widget_class, NautilusPropertiesWindow, disk_space_free_value); + gtk_widget_class_bind_template_child (widget_class, NautilusPropertiesWindow, disk_space_capacity_value); + gtk_widget_class_bind_template_child (widget_class, NautilusPropertiesWindow, locations_list_box); + gtk_widget_class_bind_template_child (widget_class, NautilusPropertiesWindow, link_target_row); + gtk_widget_class_bind_template_child (widget_class, NautilusPropertiesWindow, link_target_value_label); + gtk_widget_class_bind_template_child (widget_class, NautilusPropertiesWindow, parent_folder_row); + gtk_widget_class_bind_template_child (widget_class, NautilusPropertiesWindow, parent_folder_value_label); + gtk_widget_class_bind_template_child (widget_class, NautilusPropertiesWindow, trashed_list_box); + gtk_widget_class_bind_template_child (widget_class, NautilusPropertiesWindow, trashed_on_value_label); + gtk_widget_class_bind_template_child (widget_class, NautilusPropertiesWindow, original_folder_value_label); + gtk_widget_class_bind_template_child (widget_class, NautilusPropertiesWindow, times_list_box); + gtk_widget_class_bind_template_child (widget_class, NautilusPropertiesWindow, modified_row); + gtk_widget_class_bind_template_child (widget_class, NautilusPropertiesWindow, modified_value_label); + gtk_widget_class_bind_template_child (widget_class, NautilusPropertiesWindow, created_row); + gtk_widget_class_bind_template_child (widget_class, NautilusPropertiesWindow, created_value_label); + gtk_widget_class_bind_template_child (widget_class, NautilusPropertiesWindow, accessed_row); + gtk_widget_class_bind_template_child (widget_class, NautilusPropertiesWindow, accessed_value_label); + gtk_widget_class_bind_template_child (widget_class, NautilusPropertiesWindow, permissions_navigation_row); + gtk_widget_class_bind_template_child (widget_class, NautilusPropertiesWindow, permissions_value_label); + gtk_widget_class_bind_template_child (widget_class, NautilusPropertiesWindow, extension_models_list_box); + gtk_widget_class_bind_template_child (widget_class, NautilusPropertiesWindow, free_space_value_label); + gtk_widget_class_bind_template_child (widget_class, NautilusPropertiesWindow, permissions_stack); + gtk_widget_class_bind_template_child (widget_class, NautilusPropertiesWindow, not_the_owner_label); + gtk_widget_class_bind_template_child (widget_class, NautilusPropertiesWindow, unknown_permissions_page); + gtk_widget_class_bind_template_child (widget_class, NautilusPropertiesWindow, owner_row); + gtk_widget_class_bind_template_child (widget_class, NautilusPropertiesWindow, owner_access_row); + gtk_widget_class_bind_template_child (widget_class, NautilusPropertiesWindow, owner_folder_access_row); + gtk_widget_class_bind_template_child (widget_class, NautilusPropertiesWindow, owner_file_access_row); + gtk_widget_class_bind_template_child (widget_class, NautilusPropertiesWindow, group_row); + gtk_widget_class_bind_template_child (widget_class, NautilusPropertiesWindow, group_access_row); + gtk_widget_class_bind_template_child (widget_class, NautilusPropertiesWindow, group_folder_access_row); + gtk_widget_class_bind_template_child (widget_class, NautilusPropertiesWindow, group_file_access_row); + gtk_widget_class_bind_template_child (widget_class, NautilusPropertiesWindow, others_access_row); + gtk_widget_class_bind_template_child (widget_class, NautilusPropertiesWindow, others_folder_access_row); + gtk_widget_class_bind_template_child (widget_class, NautilusPropertiesWindow, others_file_access_row); + gtk_widget_class_bind_template_child (widget_class, NautilusPropertiesWindow, execution_row); + gtk_widget_class_bind_template_child (widget_class, NautilusPropertiesWindow, execution_switch); + gtk_widget_class_bind_template_child (widget_class, NautilusPropertiesWindow, security_context_list_box); + gtk_widget_class_bind_template_child (widget_class, NautilusPropertiesWindow, security_context_value_label); + gtk_widget_class_bind_template_child (widget_class, NautilusPropertiesWindow, change_permissions_button_box); + gtk_widget_class_bind_template_child (widget_class, NautilusPropertiesWindow, change_permissions_button); + + gtk_widget_class_bind_template_callback (widget_class, star_clicked); + gtk_widget_class_bind_template_callback (widget_class, open_in_disks); + gtk_widget_class_bind_template_callback (widget_class, open_parent_folder); + gtk_widget_class_bind_template_callback (widget_class, open_link_target); + gtk_widget_class_bind_template_callback (widget_class, navigate_main_page); + gtk_widget_class_bind_template_callback (widget_class, navigate_permissions_page); +} + +static void +nautilus_properties_window_init (NautilusPropertiesWindow *self) +{ + gtk_widget_init_template (GTK_WIDGET (self)); +} diff --git a/src/nautilus-properties-window.h b/src/nautilus-properties-window.h new file mode 100644 index 0000000..4b769bd --- /dev/null +++ b/src/nautilus-properties-window.h @@ -0,0 +1,41 @@ + +/* fm-properties-window.h - interface for window that lets user modify + icon properties + + Copyright (C) 2000 Eazel, Inc. + + The Gnome Library is free software; you can redistribute it and/or + modify it under the terms of the GNU Library General Public License as + published by the Free Software Foundation; either version 2 of the + License, or (at your option) any later version. + + The Gnome Library is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Library General Public License for more details. + + You should have received a copy of the GNU Library General Public + License along with the Gnome Library; see the file COPYING.LIB. If not, + see . + + Authors: Darin Adler +*/ + +#pragma once + +#include +#include + +#define NAUTILUS_TYPE_PROPERTIES_WINDOW (nautilus_properties_window_get_type ()) + +G_DECLARE_FINAL_TYPE (NautilusPropertiesWindow, nautilus_properties_window, + NAUTILUS, PROPERTIES_WINDOW, + AdwWindow) + +typedef void (* NautilusPropertiesWindowCallback) (gpointer callback_data); + +void nautilus_properties_window_present (GList *files, + GtkWidget *parent_widget, + const gchar *startup_id, + NautilusPropertiesWindowCallback callback, + gpointer callback_data); diff --git a/src/nautilus-query-editor.c b/src/nautilus-query-editor.c new file mode 100644 index 0000000..a7fe84f --- /dev/null +++ b/src/nautilus-query-editor.c @@ -0,0 +1,844 @@ +/* + * Copyright (C) 2005 Red Hat, Inc. + * + * Nautilus is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License as + * published by the Free Software Foundation; either version 2 of the + * License, or (at your option) any later version. + * + * Nautilus is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public + * License along with this program; see the file COPYING. If not, + * see . + * + * Author: Alexander Larsson + * Georges Basile Stavracas Neto + * + */ + +#include "nautilus-query-editor.h" + +#include +#include +#include +#include +#include + +#include "nautilus-file.h" +#include "nautilus-file-utilities.h" +#include "nautilus-global-preferences.h" +#include "nautilus-search-directory.h" +#include "nautilus-search-popover.h" +#include "nautilus-mime-actions.h" +#include "nautilus-ui-utilities.h" + +struct _NautilusQueryEditor +{ + GtkWidget parent_instance; + + GtkWidget *tags_box; + GtkWidget *text; + GtkWidget *clear_icon; + GtkWidget *popover; + GtkWidget *dropdown_button; + + GtkWidget *mime_types_tag; + GtkWidget *date_range_tag; + + guint search_changed_timeout_id; + gboolean change_frozen; + + GFile *location; + + NautilusQuery *query; +}; + +enum +{ + ACTIVATED, + FOCUS_VIEW, + CHANGED, + CANCEL, + LAST_SIGNAL +}; + +enum +{ + PROP_0, + PROP_LOCATION, + PROP_QUERY, + LAST_PROP +}; + +static guint signals[LAST_SIGNAL]; + +static void entry_activate_cb (GtkWidget *entry, + NautilusQueryEditor *editor); +static void entry_changed_cb (GtkWidget *entry, + NautilusQueryEditor *editor); +static void nautilus_query_editor_changed (NautilusQueryEditor *editor); + +G_DEFINE_TYPE (NautilusQueryEditor, nautilus_query_editor, GTK_TYPE_WIDGET); + +/* A hunt-and-peck typist types at 25-35 words per minute, which means 342 to 480ms between strokes. + * An average touch typist types at 50-70 wpm, which means 171 to 240ms "under ideal conditions". + * A 150ms default search triggering delay is too short even for fast typists in general, + * so wait 400ms after typing, to improve performance by not spamming search engines: */ +#define SEARCH_CHANGED_TIMEOUT 400 + +static void +update_fts_sensitivity (NautilusQueryEditor *editor) +{ + gboolean fts_sensitive = TRUE; + + if (editor->location) + { + g_autoptr (NautilusFile) file = NULL; + g_autofree gchar *uri = NULL; + + file = nautilus_file_get (editor->location); + uri = g_file_get_uri (editor->location); + + fts_sensitive = !nautilus_file_is_other_locations (file) && + !g_str_has_prefix (uri, "network://") && + !(nautilus_file_is_remote (file) && + location_settings_search_get_recursive_for_location (editor->location) == NAUTILUS_QUERY_RECURSIVE_NEVER); + nautilus_search_popover_set_fts_sensitive (NAUTILUS_SEARCH_POPOVER (editor->popover), + fts_sensitive); + } +} + +static void +recursive_search_preferences_changed (GSettings *settings, + gchar *key, + NautilusQueryEditor *editor) +{ + NautilusQueryRecursive recursive; + + if (!editor->query) + { + return; + } + + recursive = location_settings_search_get_recursive (); + if (recursive != nautilus_query_get_recursive (editor->query)) + { + nautilus_query_set_recursive (editor->query, recursive); + nautilus_query_editor_changed (editor); + } + + update_fts_sensitivity (editor); +} + + +static void +nautilus_query_editor_dispose (GObject *object) +{ + NautilusQueryEditor *editor; + + editor = NAUTILUS_QUERY_EDITOR (object); + + g_clear_handle_id (&editor->search_changed_timeout_id, g_source_remove); + + gtk_widget_unparent (gtk_widget_get_first_child (GTK_WIDGET (editor))); + g_clear_pointer (&editor->tags_box, gtk_widget_unparent); + g_clear_pointer (&editor->text, gtk_widget_unparent); + g_clear_pointer (&editor->dropdown_button, gtk_widget_unparent); + g_clear_pointer (&editor->clear_icon, gtk_widget_unparent); + + g_clear_object (&editor->location); + g_clear_object (&editor->query); + + g_signal_handlers_disconnect_by_func (nautilus_preferences, + recursive_search_preferences_changed, + object); + + G_OBJECT_CLASS (nautilus_query_editor_parent_class)->dispose (object); +} + +static gboolean +nautilus_query_editor_grab_focus (GtkWidget *widget) +{ + NautilusQueryEditor *editor; + + editor = NAUTILUS_QUERY_EDITOR (widget); + + if (gtk_widget_get_visible (widget) && !gtk_widget_is_focus (editor->text)) + { + return gtk_text_grab_focus_without_selecting (GTK_TEXT (editor->text)); + } + + return FALSE; +} + +static void +nautilus_query_editor_get_property (GObject *object, + guint prop_id, + GValue *value, + GParamSpec *pspec) +{ + NautilusQueryEditor *editor; + + editor = NAUTILUS_QUERY_EDITOR (object); + + switch (prop_id) + { + case PROP_LOCATION: + { + g_value_set_object (value, editor->location); + } + break; + + case PROP_QUERY: + { + g_value_set_object (value, editor->query); + } + break; + + default: + { + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); + } + } +} + +static void +nautilus_query_editor_set_property (GObject *object, + guint prop_id, + const GValue *value, + GParamSpec *pspec) +{ + NautilusQueryEditor *self; + + self = NAUTILUS_QUERY_EDITOR (object); + + switch (prop_id) + { + case PROP_LOCATION: + { + nautilus_query_editor_set_location (self, g_value_get_object (value)); + } + break; + + case PROP_QUERY: + { + nautilus_query_editor_set_query (self, g_value_get_object (value)); + } + break; + + default: + { + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); + } + } +} + +static void +nautilus_query_editor_finalize (GObject *object) +{ + G_OBJECT_CLASS (nautilus_query_editor_parent_class)->finalize (object); +} + +static void +nautilus_query_editor_class_init (NautilusQueryEditorClass *class) +{ + GObjectClass *gobject_class; + GtkWidgetClass *widget_class; + g_autoptr (GtkShortcut) shortcut = NULL; + + gobject_class = G_OBJECT_CLASS (class); + gobject_class->finalize = nautilus_query_editor_finalize; + gobject_class->dispose = nautilus_query_editor_dispose; + gobject_class->get_property = nautilus_query_editor_get_property; + gobject_class->set_property = nautilus_query_editor_set_property; + + widget_class = GTK_WIDGET_CLASS (class); + widget_class->grab_focus = nautilus_query_editor_grab_focus; + + signals[CHANGED] = + g_signal_new ("changed", + G_TYPE_FROM_CLASS (class), + G_SIGNAL_RUN_LAST, + 0, + NULL, NULL, + g_cclosure_marshal_generic, + G_TYPE_NONE, 2, NAUTILUS_TYPE_QUERY, G_TYPE_BOOLEAN); + + signals[CANCEL] = + g_signal_new ("cancel", + G_TYPE_FROM_CLASS (class), + G_SIGNAL_RUN_LAST | G_SIGNAL_ACTION, + 0, + NULL, NULL, + g_cclosure_marshal_VOID__VOID, + G_TYPE_NONE, 0); + + signals[ACTIVATED] = + g_signal_new ("activated", + G_TYPE_FROM_CLASS (class), + G_SIGNAL_RUN_LAST | G_SIGNAL_ACTION, + 0, + NULL, NULL, + g_cclosure_marshal_VOID__VOID, + G_TYPE_NONE, 0); + + signals[FOCUS_VIEW] = + g_signal_new ("focus-view", + G_TYPE_FROM_CLASS (class), + G_SIGNAL_RUN_LAST | G_SIGNAL_ACTION, + 0, + NULL, NULL, + g_cclosure_marshal_VOID__VOID, + G_TYPE_NONE, 0); + + shortcut = gtk_shortcut_new (gtk_keyval_trigger_new (GDK_KEY_Down, 0), + gtk_signal_action_new ("focus-view")); + gtk_widget_class_add_shortcut (widget_class, shortcut); + + gtk_widget_class_add_binding_signal (widget_class, + GDK_KEY_Escape, 0, "cancel", + NULL); + + /** + * NautilusQueryEditor::location: + * + * The current location of the query editor. + */ + g_object_class_install_property (gobject_class, + PROP_LOCATION, + g_param_spec_object ("location", + "Location of the search", + "The current location of the editor", + G_TYPE_FILE, + G_PARAM_READWRITE)); + + /** + * NautilusQueryEditor::query: + * + * The current query of the query editor. It it always synchronized + * with the filter popover's query. + */ + g_object_class_install_property (gobject_class, + PROP_QUERY, + g_param_spec_object ("query", + "Query of the search", + "The query that the editor is handling", + NAUTILUS_TYPE_QUERY, + G_PARAM_READWRITE)); + + gtk_widget_class_set_layout_manager_type (widget_class, GTK_TYPE_BOX_LAYOUT); + gtk_widget_class_set_css_name (widget_class, "entry"); + gtk_widget_class_set_accessible_role (widget_class, GTK_ACCESSIBLE_ROLE_SEARCH_BOX); +} + +GFile * +nautilus_query_editor_get_location (NautilusQueryEditor *editor) +{ + g_return_val_if_fail (NAUTILUS_IS_QUERY_EDITOR (editor), NULL); + + return g_object_ref (editor->location); +} + +static void +create_query (NautilusQueryEditor *editor) +{ + NautilusQuery *query; + g_autoptr (NautilusFile) file = NULL; + gboolean fts_enabled; + + g_return_if_fail (editor->query == NULL); + + fts_enabled = nautilus_search_popover_get_fts_enabled (NAUTILUS_SEARCH_POPOVER (editor->popover)); + + if (editor->location == NULL) + { + return; + } + + file = nautilus_file_get (editor->location); + query = nautilus_query_new (); + + nautilus_query_set_search_content (query, fts_enabled); + + nautilus_query_set_text (query, gtk_editable_get_text (GTK_EDITABLE (editor->text))); + nautilus_query_set_location (query, editor->location); + + /* We only set the query using the global setting for recursivity here, + * it's up to the search engine to check weather it can proceed with + * deep search in the current directory or not. */ + nautilus_query_set_recursive (query, location_settings_search_get_recursive ()); + + nautilus_query_editor_set_query (editor, query); +} + +static void +entry_activate_cb (GtkWidget *entry, + NautilusQueryEditor *editor) +{ + g_signal_emit (editor, signals[ACTIVATED], 0); +} + +static gboolean +entry_changed_internal (NautilusQueryEditor *editor) +{ + const gchar *text = gtk_editable_get_text (GTK_EDITABLE (editor->text)); + gboolean is_empty = (text == NULL && *text == '\0'); + + editor->search_changed_timeout_id = 0; + + gtk_widget_set_child_visible (editor->clear_icon, !is_empty); + + if (editor->query == NULL) + { + create_query (editor); + } + else + { + g_autofree gchar *stripped_text = g_strstrip (g_strdup (text)); + nautilus_query_set_text (editor->query, stripped_text); + } + + nautilus_query_editor_changed (editor); + + return G_SOURCE_REMOVE; +} + +static void +entry_changed_cb (GtkWidget *entry, + NautilusQueryEditor *editor) +{ + if (editor->change_frozen) + { + return; + } + + g_clear_handle_id (&editor->search_changed_timeout_id, g_source_remove); + editor->search_changed_timeout_id = g_timeout_add (SEARCH_CHANGED_TIMEOUT, + G_SOURCE_FUNC (entry_changed_internal), + editor); +} + +static GtkWidget * +create_tag (NautilusQueryEditor *self, + const gchar *text, + GCallback reset_callback) +{ + GtkWidget *tag; + GtkWidget *label; + GtkWidget *button; + + tag = gtk_box_new (GTK_ORIENTATION_HORIZONTAL, 6); + gtk_widget_set_margin_end (tag, 6); + gtk_widget_set_name (tag, "NautilusQueryEditorTag"); + + label = gtk_label_new (text); + gtk_widget_add_css_class (label, "caption-heading"); + gtk_widget_set_margin_start (label, 12); + gtk_box_append (GTK_BOX (tag), label); + + button = gtk_button_new (); + gtk_button_set_icon_name (GTK_BUTTON (button), "window-close-symbolic"); + gtk_widget_add_css_class (button, "flat"); + gtk_widget_add_css_class (button, "circular"); + g_signal_connect_object (button, "clicked", + reset_callback, self->popover, G_CONNECT_SWAPPED); + gtk_box_append (GTK_BOX (tag), button); + + return tag; +} + +static void +search_popover_date_range_changed_cb (NautilusSearchPopover *popover, + GPtrArray *date_range, + gpointer user_data) +{ + NautilusQueryEditor *editor; + + editor = NAUTILUS_QUERY_EDITOR (user_data); + + if (editor->query == NULL) + { + create_query (editor); + } + + if (editor->date_range_tag != NULL) + { + gtk_box_remove (GTK_BOX (editor->tags_box), editor->date_range_tag); + editor->date_range_tag = NULL; + } + + if (date_range) + { + g_autofree gchar *text_for_date_range = NULL; + + text_for_date_range = get_text_for_date_range (date_range, TRUE); + editor->date_range_tag = create_tag (editor, + text_for_date_range, + G_CALLBACK (nautilus_search_popover_reset_date_range)); + gtk_box_append (GTK_BOX (editor->tags_box), editor->date_range_tag); + } + + nautilus_query_set_date_range (editor->query, date_range); + + nautilus_query_editor_changed (editor); + gtk_widget_set_visible (editor->tags_box, + (gtk_widget_get_first_child (editor->tags_box) != NULL)); +} + +static void +search_popover_mime_type_changed_cb (NautilusSearchPopover *popover, + gint mimetype_group, + const gchar *mimetype, + gpointer user_data) +{ + NautilusQueryEditor *editor; + g_autoptr (GPtrArray) mimetypes = NULL; + + editor = NAUTILUS_QUERY_EDITOR (user_data); + + if (editor->query == NULL) + { + create_query (editor); + } + + if (editor->mime_types_tag != NULL) + { + gtk_box_remove (GTK_BOX (editor->tags_box), editor->mime_types_tag); + editor->mime_types_tag = NULL; + } + + /* group 0 is anything */ + if (mimetype_group == 0) + { + mimetypes = nautilus_mime_types_group_get_mimetypes (mimetype_group); + } + else if (mimetype_group > 0) + { + mimetypes = nautilus_mime_types_group_get_mimetypes (mimetype_group); + editor->mime_types_tag = create_tag (editor, + nautilus_mime_types_group_get_name (mimetype_group), + G_CALLBACK (nautilus_search_popover_reset_mime_types)); + gtk_box_append (GTK_BOX (editor->tags_box), editor->mime_types_tag); + } + else + { + g_autofree gchar *display_name = NULL; + + mimetypes = g_ptr_array_new_full (1, g_free); + g_ptr_array_add (mimetypes, g_strdup (mimetype)); + + display_name = g_content_type_get_description (mimetype); + editor->mime_types_tag = create_tag (editor, + display_name, + G_CALLBACK (nautilus_search_popover_reset_mime_types)); + gtk_box_append (GTK_BOX (editor->tags_box), editor->mime_types_tag); + } + nautilus_query_set_mime_types (editor->query, mimetypes); + + nautilus_query_editor_changed (editor); + gtk_widget_set_visible (editor->tags_box, + (gtk_widget_get_first_child (editor->tags_box) != NULL)); +} + +static void +search_popover_time_type_changed_cb (NautilusSearchPopover *popover, + NautilusQuerySearchType data, + gpointer user_data) +{ + NautilusQueryEditor *editor; + + editor = NAUTILUS_QUERY_EDITOR (user_data); + + if (editor->query == NULL) + { + create_query (editor); + } + + nautilus_query_set_search_type (editor->query, data); + + nautilus_query_editor_changed (editor); +} + +static void +search_popover_fts_changed_cb (GObject *popover, + GParamSpec *pspec, + gpointer user_data) +{ + NautilusQueryEditor *editor; + + editor = NAUTILUS_QUERY_EDITOR (user_data); + + if (editor->query == NULL) + { + create_query (editor); + } + + nautilus_query_set_search_content (editor->query, + nautilus_search_popover_get_fts_enabled (NAUTILUS_SEARCH_POPOVER (popover))); + + nautilus_query_editor_changed (editor); +} + +static void +on_clear_icon_pressed (GtkGestureClick *gesture, + int n_press, + double x, + double y, + NautilusQueryEditor *self) +{ + gtk_gesture_set_state (GTK_GESTURE (gesture), GTK_EVENT_SEQUENCE_CLAIMED); +} + +static void +on_clear_icon_released (GtkGestureClick *gesture, + int n_press, + double x, + double y, + NautilusQueryEditor *self) +{ + gtk_editable_set_text (GTK_EDITABLE (self->text), ""); +} + +static void +nautilus_query_editor_init (NautilusQueryEditor *editor) +{ + gboolean rtl = (gtk_widget_get_direction (GTK_WIDGET (editor)) == GTK_TEXT_DIR_RTL); + GtkWidget *image; + GtkEventController *controller; + + gtk_widget_set_name (GTK_WIDGET (editor), "NautilusQueryEditor"); + gtk_widget_add_css_class (GTK_WIDGET (editor), "search"); + + g_signal_connect (nautilus_preferences, + "changed::recursive-search", + G_CALLBACK (recursive_search_preferences_changed), + editor); + + /* create the search entry */ + image = gtk_image_new_from_icon_name ("system-search-symbolic"); + g_object_set (image, "accessible-role", GTK_ACCESSIBLE_ROLE_PRESENTATION, NULL); + gtk_widget_set_margin_start (image, 4); + gtk_widget_set_margin_end (image, 6); + gtk_widget_set_parent (image, GTK_WIDGET (editor)); + + editor->tags_box = gtk_box_new (GTK_ORIENTATION_HORIZONTAL, 0); + gtk_widget_hide (editor->tags_box); + gtk_widget_set_parent (editor->tags_box, GTK_WIDGET (editor)); + + editor->text = gtk_text_new (); + gtk_widget_set_hexpand (editor->text, TRUE); + gtk_widget_set_parent (editor->text, GTK_WIDGET (editor)); + + editor->clear_icon = gtk_image_new_from_icon_name (rtl ? "edit-clear-rtl-symbolic" : + "edit-clear-symbolic"); + g_object_set (editor->clear_icon, "accessible-role", GTK_ACCESSIBLE_ROLE_PRESENTATION, NULL); + gtk_widget_set_tooltip_text (editor->clear_icon, _("Clear entry")); + gtk_widget_set_child_visible (editor->clear_icon, FALSE); + gtk_widget_set_parent (editor->clear_icon, GTK_WIDGET (editor)); + + controller = GTK_EVENT_CONTROLLER (gtk_gesture_click_new ()); + g_signal_connect (controller, "pressed", G_CALLBACK (on_clear_icon_pressed), editor); + g_signal_connect (controller, "released", G_CALLBACK (on_clear_icon_released), editor); + gtk_widget_add_controller (editor->clear_icon, controller); + + /* setup the search popover */ + editor->popover = nautilus_search_popover_new (); + + g_signal_connect (editor->popover, "show", + G_CALLBACK (gtk_widget_grab_focus), NULL); + g_signal_connect_swapped (editor->popover, "closed", + G_CALLBACK (gtk_widget_grab_focus), editor); + + g_object_bind_property (editor, "query", + editor->popover, "query", + G_BINDING_DEFAULT); + + /* setup the filter menu button */ + editor->dropdown_button = gtk_menu_button_new (); + gtk_menu_button_set_icon_name (GTK_MENU_BUTTON (editor->dropdown_button), "funnel-symbolic"); + gtk_menu_button_set_popover (GTK_MENU_BUTTON (editor->dropdown_button), editor->popover); + gtk_widget_set_parent (editor->dropdown_button, GTK_WIDGET (editor)); + gtk_widget_add_css_class (editor->dropdown_button, "circular"); + + g_signal_connect (editor->text, "activate", + G_CALLBACK (entry_activate_cb), editor); + g_signal_connect (editor->text, "changed", + G_CALLBACK (entry_changed_cb), editor); + g_signal_connect (editor->popover, "date-range", + G_CALLBACK (search_popover_date_range_changed_cb), editor); + g_signal_connect (editor->popover, "mime-type", + G_CALLBACK (search_popover_mime_type_changed_cb), editor); + g_signal_connect (editor->popover, "time-type", + G_CALLBACK (search_popover_time_type_changed_cb), editor); + g_signal_connect (editor->popover, "notify::fts-enabled", + G_CALLBACK (search_popover_fts_changed_cb), editor); +} + +static void +nautilus_query_editor_changed (NautilusQueryEditor *editor) +{ + if (editor->change_frozen) + { + return; + } + + g_signal_emit (editor, signals[CHANGED], 0, editor->query, TRUE); +} + +NautilusQuery * +nautilus_query_editor_get_query (NautilusQueryEditor *editor) +{ + g_return_val_if_fail (NAUTILUS_IS_QUERY_EDITOR (editor), NULL); + + if (editor->text == NULL) + { + return NULL; + } + + return editor->query; +} + +GtkWidget * +nautilus_query_editor_new (void) +{ + return GTK_WIDGET (g_object_new (NAUTILUS_TYPE_QUERY_EDITOR, NULL)); +} + +void +nautilus_query_editor_set_location (NautilusQueryEditor *editor, + GFile *location) +{ + g_autoptr (NautilusDirectory) directory = NULL; + NautilusDirectory *base_model; + gboolean should_notify; + + g_return_if_fail (NAUTILUS_IS_QUERY_EDITOR (editor)); + + /* The client could set us a location that is actually a search directory, + * like what happens with the slot when updating the query editor location. + * However here we want the real location used as a model for the search, + * not the search directory invented uri. */ + directory = nautilus_directory_get (location); + if (NAUTILUS_IS_SEARCH_DIRECTORY (directory)) + { + g_autoptr (GFile) real_location = NULL; + + base_model = nautilus_search_directory_get_base_model (NAUTILUS_SEARCH_DIRECTORY (directory)); + real_location = nautilus_directory_get_location (base_model); + + should_notify = g_set_object (&editor->location, real_location); + } + else + { + should_notify = g_set_object (&editor->location, location); + } + + if (editor->query == NULL) + { + create_query (editor); + } + nautilus_query_set_location (editor->query, editor->location); + + update_fts_sensitivity (editor); + + if (should_notify) + { + g_object_notify (G_OBJECT (editor), "location"); + } +} + +void +nautilus_query_editor_set_query (NautilusQueryEditor *self, + NautilusQuery *query) +{ + g_autofree char *text = NULL; + g_autofree char *current_text = NULL; + + g_return_if_fail (NAUTILUS_IS_QUERY_EDITOR (self)); + + if (query != NULL) + { + text = nautilus_query_get_text (query); + } + + if (!text) + { + text = g_strdup (""); + } + + self->change_frozen = TRUE; + + current_text = g_strdup (gtk_editable_get_text (GTK_EDITABLE (self->text))); + current_text = g_strstrip (current_text); + if (!g_str_equal (current_text, text)) + { + gtk_editable_set_text (GTK_EDITABLE (self->text), text); + gtk_editable_set_position (GTK_EDITABLE (self->text), -1); + } + + if (g_set_object (&self->query, query)) + { + g_object_notify (G_OBJECT (self), "query"); + } + + self->change_frozen = FALSE; +} + +void +nautilus_query_editor_set_text (NautilusQueryEditor *self, + const gchar *text) +{ + g_return_if_fail (NAUTILUS_IS_QUERY_EDITOR (self)); + g_return_if_fail (text != NULL); + + /* The handler of the entry will take care of everything */ + gtk_editable_set_text (GTK_EDITABLE (self->text), text); +} + +static gboolean +nautilus_gtk_search_entry_is_keynav_event (guint keyval, + GdkModifierType state) +{ + if (keyval == GDK_KEY_Tab || keyval == GDK_KEY_KP_Tab || + keyval == GDK_KEY_Up || keyval == GDK_KEY_KP_Up || + keyval == GDK_KEY_Down || keyval == GDK_KEY_KP_Down || + keyval == GDK_KEY_Left || keyval == GDK_KEY_KP_Left || + keyval == GDK_KEY_Right || keyval == GDK_KEY_KP_Right || + keyval == GDK_KEY_Home || keyval == GDK_KEY_KP_Home || + keyval == GDK_KEY_End || keyval == GDK_KEY_KP_End || + keyval == GDK_KEY_Page_Up || keyval == GDK_KEY_KP_Page_Up || + keyval == GDK_KEY_Page_Down || keyval == GDK_KEY_KP_Page_Down || + ((state & (GDK_CONTROL_MASK | GDK_ALT_MASK)) != 0)) + { + return TRUE; + } + + /* Other navigation events should get automatically + * ignored as they will not change the content of the entry + */ + return FALSE; +} + +gboolean +nautilus_query_editor_handle_event (NautilusQueryEditor *self, + GtkEventControllerKey *controller, + guint keyval, + GdkModifierType state) +{ + g_return_val_if_fail (NAUTILUS_IS_QUERY_EDITOR (self), GDK_EVENT_PROPAGATE); + g_return_val_if_fail (controller != NULL, GDK_EVENT_PROPAGATE); + + /* Conditions are copied straight from GTK. */ + if (nautilus_gtk_search_entry_is_keynav_event (keyval, state) || + keyval == GDK_KEY_space || + keyval == GDK_KEY_Menu) + { + return GDK_EVENT_PROPAGATE; + } + + return gtk_event_controller_key_forward (controller, self->text); +} diff --git a/src/nautilus-query-editor.h b/src/nautilus-query-editor.h new file mode 100644 index 0000000..e071f94 --- /dev/null +++ b/src/nautilus-query-editor.h @@ -0,0 +1,79 @@ +/* + * Copyright (C) 2005 Red Hat, Inc. + * + * Nautilus is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License as + * published by the Free Software Foundation; either version 2 of the + * License, or (at your option) any later version. + * + * Nautilus is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public + * License along with this program; see the file COPYING. If not, + * see . + * + * Author: Alexander Larsson + * + */ + +#pragma once + +#include + +#include "nautilus-types.h" + +#define NAUTILUS_TYPE_QUERY_EDITOR nautilus_query_editor_get_type() + +G_DECLARE_FINAL_TYPE (NautilusQueryEditor, nautilus_query_editor, NAUTILUS, QUERY_EDITOR, GtkWidget) + +GtkWidget *nautilus_query_editor_new (void); + +/** + * nautilus_query_editor_get_query: + * + * @editor: A #NautilusQueryEditor instance. + * + * Returns: (nullable) (transfer full): The #NautilusQuery for the editor. + */ +NautilusQuery *nautilus_query_editor_get_query (NautilusQueryEditor *editor); +/** + * nautilus_query_editor_set_query: + * + * @editor: A #NautilusQueryEditor instance. + * @query: (nullable) (transfer full): The #NautilusQuery for the search. + */ +void nautilus_query_editor_set_query (NautilusQueryEditor *editor, + NautilusQuery *query); +/** + * nautilus_query_editor_get_location: + * + * @editor: A #NautilusQueryEditor instance. + * + * Returns: (nullable) (transfer full): The location of the current search. + */ +GFile *nautilus_query_editor_get_location (NautilusQueryEditor *editor); +/** + * nautilus_query_editor_set_location: + * + * @editor: A #NautilusQueryEditor instance. + * @location: (nullable) (transfer full): The location in which the search will take place. + */ +void nautilus_query_editor_set_location (NautilusQueryEditor *editor, + GFile *location); +/** + * nautilus_query_editor_set_text: + * + * @editor: A #NautilusQueryEditor instance. + * @text: (not nullable) (transfer none): The search text. + */ +void nautilus_query_editor_set_text (NautilusQueryEditor *editor, + const gchar *text); + +gboolean +nautilus_query_editor_handle_event (NautilusQueryEditor *self, + GtkEventControllerKey *controller, + guint keyval, + GdkModifierType state); diff --git a/src/nautilus-query.c b/src/nautilus-query.c new file mode 100644 index 0000000..e3b64c9 --- /dev/null +++ b/src/nautilus-query.c @@ -0,0 +1,692 @@ +/* + * Copyright (C) 2005 Novell, Inc. + * + * Nautilus is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License as + * published by the Free Software Foundation; either version 2 of the + * License, or (at your option) any later version. + * + * Nautilus is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public + * License along with this program; see the file COPYING. If not, + * see . + * + * Author: Anders Carlsson + * + */ + +#include "nautilus-query.h" + +#include +#include + +#include "nautilus-enum-types.h" +#include "nautilus-file-utilities.h" +#include "nautilus-global-preferences.h" + +#define RANK_SCALE_FACTOR 100 +#define MIN_RANK 10.0 +#define MAX_RANK 50.0 + +struct _NautilusQuery +{ + GObject parent; + + char *text; + GFile *location; + GPtrArray *mime_types; + gboolean show_hidden; + GPtrArray *date_range; + NautilusQueryRecursive recursive; + NautilusQuerySearchType search_type; + NautilusQuerySearchContent search_content; + + gboolean searching; + char **prepared_words; + GMutex prepared_words_mutex; +}; + +static void nautilus_query_class_init (NautilusQueryClass *class); +static void nautilus_query_init (NautilusQuery *query); + +G_DEFINE_TYPE (NautilusQuery, nautilus_query, G_TYPE_OBJECT); + +enum +{ + PROP_0, + PROP_DATE_RANGE, + PROP_LOCATION, + PROP_MIMETYPES, + PROP_RECURSIVE, + PROP_SEARCH_TYPE, + PROP_SEARCHING, + PROP_SHOW_HIDDEN, + PROP_TEXT, + LAST_PROP +}; + +static void +finalize (GObject *object) +{ + NautilusQuery *query; + + query = NAUTILUS_QUERY (object); + + g_free (query->text); + g_strfreev (query->prepared_words); + g_clear_object (&query->location); + g_clear_pointer (&query->date_range, g_ptr_array_unref); + g_mutex_clear (&query->prepared_words_mutex); + + G_OBJECT_CLASS (nautilus_query_parent_class)->finalize (object); +} + +static void +nautilus_query_get_property (GObject *object, + guint prop_id, + GValue *value, + GParamSpec *pspec) +{ + NautilusQuery *self = NAUTILUS_QUERY (object); + + switch (prop_id) + { + case PROP_DATE_RANGE: + { + g_value_set_pointer (value, self->date_range); + } + break; + + case PROP_LOCATION: + { + g_value_set_object (value, self->location); + } + break; + + case PROP_MIMETYPES: + { + g_value_set_pointer (value, self->mime_types); + } + break; + + case PROP_RECURSIVE: + { + g_value_set_enum (value, self->recursive); + } + break; + + case PROP_SEARCH_TYPE: + { + g_value_set_enum (value, self->search_type); + } + break; + + case PROP_SEARCHING: + { + g_value_set_boolean (value, self->searching); + } + break; + + case PROP_SHOW_HIDDEN: + { + g_value_set_boolean (value, self->show_hidden); + } + break; + + case PROP_TEXT: + { + g_value_set_string (value, self->text); + } + break; + + default: + { + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); + } + } +} + +static void +nautilus_query_set_property (GObject *object, + guint prop_id, + const GValue *value, + GParamSpec *pspec) +{ + NautilusQuery *self = NAUTILUS_QUERY (object); + + switch (prop_id) + { + case PROP_DATE_RANGE: + { + nautilus_query_set_date_range (self, g_value_get_pointer (value)); + } + break; + + case PROP_LOCATION: + { + nautilus_query_set_location (self, g_value_get_object (value)); + } + break; + + case PROP_MIMETYPES: + { + nautilus_query_set_mime_types (self, g_value_get_pointer (value)); + } + break; + + case PROP_RECURSIVE: + { + nautilus_query_set_recursive (self, g_value_get_enum (value)); + } + break; + + case PROP_SEARCH_TYPE: + { + nautilus_query_set_search_type (self, g_value_get_enum (value)); + } + break; + + case PROP_SEARCHING: + { + nautilus_query_set_searching (self, g_value_get_boolean (value)); + } + break; + + case PROP_SHOW_HIDDEN: + { + nautilus_query_set_show_hidden_files (self, g_value_get_boolean (value)); + } + break; + + case PROP_TEXT: + { + nautilus_query_set_text (self, g_value_get_string (value)); + } + break; + + default: + { + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); + } + } +} + +static void +nautilus_query_class_init (NautilusQueryClass *class) +{ + GObjectClass *gobject_class; + + gobject_class = G_OBJECT_CLASS (class); + gobject_class->finalize = finalize; + gobject_class->get_property = nautilus_query_get_property; + gobject_class->set_property = nautilus_query_set_property; + + /** + * NautilusQuery::date-range: + * + * The date range of the query. + * + */ + g_object_class_install_property (gobject_class, + PROP_DATE_RANGE, + g_param_spec_pointer ("date-range", + "Date range of the query", + "The range date of the query", + G_PARAM_READWRITE)); + + /** + * NautilusQuery::location: + * + * The location of the query. + * + */ + g_object_class_install_property (gobject_class, + PROP_LOCATION, + g_param_spec_object ("location", + "Location of the query", + "The location of the query", + G_TYPE_FILE, + G_PARAM_READWRITE)); + + /** + * NautilusQuery::mimetypes: (type GPtrArray) (element-type gchar*) + * + * MIME types the query holds. An empty array means "Any type". + * + */ + g_object_class_install_property (gobject_class, + PROP_MIMETYPES, + g_param_spec_pointer ("mimetypes", + "MIME types of the query", + "The MIME types of the query", + G_PARAM_READWRITE)); + + /** + * NautilusQuery::recursive: + * + * Whether the query is being performed on subdirectories or not. + * + */ + g_object_class_install_property (gobject_class, + PROP_RECURSIVE, + g_param_spec_enum ("recursive", + "Whether the query is being performed on subdirectories", + "Whether the query is being performed on subdirectories or not", + NAUTILUS_TYPE_QUERY_RECURSIVE, + NAUTILUS_QUERY_RECURSIVE_ALWAYS, + G_PARAM_READWRITE)); + + /** + * NautilusQuery::search-type: + * + * The search type of the query. + * + */ + g_object_class_install_property (gobject_class, + PROP_SEARCH_TYPE, + g_param_spec_enum ("search-type", + "Type of the query", + "The type of the query", + NAUTILUS_TYPE_QUERY_SEARCH_TYPE, + NAUTILUS_QUERY_SEARCH_TYPE_LAST_MODIFIED, + G_PARAM_READWRITE)); + + /** + * NautilusQuery::searching: + * + * Whether the query is being performed or not. + * + */ + g_object_class_install_property (gobject_class, + PROP_SEARCHING, + g_param_spec_boolean ("searching", + "Whether the query is being performed", + "Whether the query is being performed or not", + FALSE, + G_PARAM_READWRITE)); + + /** + * NautilusQuery::show-hidden: + * + * Whether the search should include hidden files. + * + */ + g_object_class_install_property (gobject_class, + PROP_SHOW_HIDDEN, + g_param_spec_boolean ("show-hidden", + "Show hidden files", + "Whether the search should show hidden files", + FALSE, + G_PARAM_READWRITE)); + + /** + * NautilusQuery::text: + * + * The search string. + * + */ + g_object_class_install_property (gobject_class, + PROP_TEXT, + g_param_spec_string ("text", + "Text of the search", + "The text string of the search", + NULL, + G_PARAM_READWRITE)); +} + +static void +nautilus_query_init (NautilusQuery *query) +{ + query->location = g_file_new_for_path (g_get_home_dir ()); + query->mime_types = g_ptr_array_new (); + query->show_hidden = TRUE; + query->search_type = g_settings_get_enum (nautilus_preferences, "search-filter-time-type"); + query->search_content = NAUTILUS_QUERY_SEARCH_CONTENT_SIMPLE; + g_mutex_init (&query->prepared_words_mutex); +} + +static gchar * +prepare_string_for_compare (const gchar *string) +{ + gchar *normalized, *res; + + normalized = g_utf8_normalize (string, -1, G_NORMALIZE_NFD); + res = g_utf8_strdown (normalized, -1); + g_free (normalized); + + return res; +} + +gdouble +nautilus_query_matches_string (NautilusQuery *query, + const gchar *string) +{ + gchar *prepared_string, *ptr; + gboolean found; + gdouble retval; + gint idx, nonexact_malus; + + if (!query->text) + { + return -1; + } + + g_mutex_lock (&query->prepared_words_mutex); + if (!query->prepared_words) + { + prepared_string = prepare_string_for_compare (query->text); + query->prepared_words = g_strsplit (prepared_string, " ", -1); + g_free (prepared_string); + } + + prepared_string = prepare_string_for_compare (string); + found = TRUE; + ptr = NULL; + nonexact_malus = 0; + + for (idx = 0; query->prepared_words[idx] != NULL; idx++) + { + if ((ptr = strstr (prepared_string, query->prepared_words[idx])) == NULL) + { + found = FALSE; + break; + } + + nonexact_malus += strlen (ptr) - strlen (query->prepared_words[idx]); + } + g_mutex_unlock (&query->prepared_words_mutex); + + if (!found) + { + g_free (prepared_string); + return -1; + } + + /* The rank value depends on the numbers of letters before and after the match. + * To make the prefix matches prefered over sufix ones, the number of letters + * after the match is divided by a factor, so that it decreases the rank by a + * smaller amount. + */ + retval = MAX (MIN_RANK, MAX_RANK - (gdouble) (ptr - prepared_string) - (gdouble) nonexact_malus / RANK_SCALE_FACTOR); + g_free (prepared_string); + + return retval; +} + +NautilusQuery * +nautilus_query_new (void) +{ + return g_object_new (NAUTILUS_TYPE_QUERY, NULL); +} + + +char * +nautilus_query_get_text (NautilusQuery *query) +{ + g_return_val_if_fail (NAUTILUS_IS_QUERY (query), NULL); + + return g_strdup (query->text); +} + +void +nautilus_query_set_text (NautilusQuery *query, + const char *text) +{ + g_return_if_fail (NAUTILUS_IS_QUERY (query)); + + g_free (query->text); + query->text = g_strstrip (g_strdup (text)); + + g_mutex_lock (&query->prepared_words_mutex); + g_strfreev (query->prepared_words); + query->prepared_words = NULL; + g_mutex_unlock (&query->prepared_words_mutex); + + g_object_notify (G_OBJECT (query), "text"); +} + +GFile * +nautilus_query_get_location (NautilusQuery *query) +{ + g_return_val_if_fail (NAUTILUS_IS_QUERY (query), NULL); + + return g_object_ref (query->location); +} + +void +nautilus_query_set_location (NautilusQuery *query, + GFile *location) +{ + g_return_if_fail (NAUTILUS_IS_QUERY (query)); + + if (g_set_object (&query->location, location)) + { + g_object_notify (G_OBJECT (query), "location"); + } +} + +/** + * nautilus_query_get_mime_type: + * @query: A #NautilusQuery + * + * Retrieves the current MIME Types filter from @query. Its content must not be + * modified. It can be read by multiple threads. + * + * Returns: (transfer container) A #GPtrArray reference with MIME type name strings. + */ +GPtrArray * +nautilus_query_get_mime_types (NautilusQuery *query) +{ + g_return_val_if_fail (NAUTILUS_IS_QUERY (query), NULL); + + return g_ptr_array_ref (query->mime_types); +} + +/** + * nautilus_query_set_mime_types: + * @query: A #NautilusQuery + * @mime_types: (transfer none): A #GPtrArray of MIME type strings + * + * Set a new MIME types filter for @query. Once set, the filter must not be + * modified, and it can only be replaced by setting another filter. + * + * Search engines that are already running for a previous filter will ignore the + * new filter. So, the caller must ensure that the search will be reloaded + * afterwards. + */ +void +nautilus_query_set_mime_types (NautilusQuery *query, + GPtrArray *mime_types) +{ + g_return_if_fail (NAUTILUS_IS_QUERY (query)); + g_return_if_fail (mime_types != NULL); + + g_clear_pointer (&query->mime_types, g_ptr_array_unref); + query->mime_types = g_ptr_array_ref (mime_types); + + g_object_notify (G_OBJECT (query), "mimetypes"); +} + +gboolean +nautilus_query_get_show_hidden_files (NautilusQuery *query) +{ + g_return_val_if_fail (NAUTILUS_IS_QUERY (query), FALSE); + + return query->show_hidden; +} + +void +nautilus_query_set_show_hidden_files (NautilusQuery *query, + gboolean show_hidden) +{ + g_return_if_fail (NAUTILUS_IS_QUERY (query)); + + if (query->show_hidden != show_hidden) + { + query->show_hidden = show_hidden; + g_object_notify (G_OBJECT (query), "show-hidden"); + } +} + +char * +nautilus_query_to_readable_string (NautilusQuery *query) +{ + if (!query || !query->text || query->text[0] == '\0') + { + return g_strdup (_("Search")); + } + + return g_strdup_printf (_("Search for “%s”"), query->text); +} + +NautilusQuerySearchContent +nautilus_query_get_search_content (NautilusQuery *query) +{ + g_return_val_if_fail (NAUTILUS_IS_QUERY (query), -1); + + return query->search_content; +} + +void +nautilus_query_set_search_content (NautilusQuery *query, + NautilusQuerySearchContent content) +{ + g_return_if_fail (NAUTILUS_IS_QUERY (query)); + + if (query->search_content != content) + { + query->search_content = content; + g_object_notify (G_OBJECT (query), "search-type"); + } +} + +NautilusQuerySearchType +nautilus_query_get_search_type (NautilusQuery *query) +{ + g_return_val_if_fail (NAUTILUS_IS_QUERY (query), -1); + + return query->search_type; +} + +void +nautilus_query_set_search_type (NautilusQuery *query, + NautilusQuerySearchType type) +{ + g_return_if_fail (NAUTILUS_IS_QUERY (query)); + + if (query->search_type != type) + { + query->search_type = type; + g_object_notify (G_OBJECT (query), "search-type"); + } +} + +/** + * nautilus_query_get_date_range: + * @query: a #NautilusQuery + * + * Retrieves the #GptrArray composed of #GDateTime representing the date range. + * This function is thread safe. + * + * Returns: (transfer full): the #GptrArray composed of #GDateTime representing the date range. + */ +GPtrArray * +nautilus_query_get_date_range (NautilusQuery *query) +{ + static GMutex mutex; + + g_return_val_if_fail (NAUTILUS_IS_QUERY (query), NULL); + + g_mutex_lock (&mutex); + if (query->date_range) + { + g_ptr_array_ref (query->date_range); + } + g_mutex_unlock (&mutex); + + return query->date_range; +} + +void +nautilus_query_set_date_range (NautilusQuery *query, + GPtrArray *date_range) +{ + g_return_if_fail (NAUTILUS_IS_QUERY (query)); + + g_clear_pointer (&query->date_range, g_ptr_array_unref); + if (date_range) + { + query->date_range = g_ptr_array_ref (date_range); + } + + g_object_notify (G_OBJECT (query), "date-range"); +} + +gboolean +nautilus_query_get_searching (NautilusQuery *query) +{ + g_return_val_if_fail (NAUTILUS_IS_QUERY (query), FALSE); + + return query->searching; +} + +void +nautilus_query_set_searching (NautilusQuery *query, + gboolean searching) +{ + g_return_if_fail (NAUTILUS_IS_QUERY (query)); + + searching = !!searching; + + if (query->searching != searching) + { + query->searching = searching; + + g_object_notify (G_OBJECT (query), "searching"); + } +} + +NautilusQueryRecursive +nautilus_query_get_recursive (NautilusQuery *query) +{ + g_return_val_if_fail (NAUTILUS_IS_QUERY (query), + NAUTILUS_QUERY_RECURSIVE_ALWAYS); + + return query->recursive; +} + +void +nautilus_query_set_recursive (NautilusQuery *query, + NautilusQueryRecursive recursive) +{ + g_return_if_fail (NAUTILUS_IS_QUERY (query)); + + if (query->recursive != recursive) + { + query->recursive = recursive; + + g_object_notify (G_OBJECT (query), "recursive"); + } +} + +gboolean +nautilus_query_is_empty (NautilusQuery *query) +{ + if (!query) + { + return TRUE; + } + + if (!query->date_range && + (!query->text || (query->text && query->text[0] == '\0')) && + query->mime_types->len == 0) + { + return TRUE; + } + + return FALSE; +} diff --git a/src/nautilus-query.h b/src/nautilus-query.h new file mode 100644 index 0000000..47f053f --- /dev/null +++ b/src/nautilus-query.h @@ -0,0 +1,89 @@ +/* + * Copyright (C) 2005 Novell, Inc. + * + * Nautilus is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License as + * published by the Free Software Foundation; either version 2 of the + * License, or (at your option) any later version. + * + * Nautilus is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public + * License along with this program; see the file COPYING. If not, + * see . + * + * Author: Anders Carlsson + * + */ + +#pragma once + +#include +#include + +typedef enum { + NAUTILUS_QUERY_SEARCH_TYPE_LAST_ACCESS, + NAUTILUS_QUERY_SEARCH_TYPE_LAST_MODIFIED, + NAUTILUS_QUERY_SEARCH_TYPE_CREATED +} NautilusQuerySearchType; + +typedef enum { + NAUTILUS_QUERY_SEARCH_CONTENT_SIMPLE, + NAUTILUS_QUERY_SEARCH_CONTENT_FULL_TEXT, +} NautilusQuerySearchContent; + +typedef enum { + NAUTILUS_QUERY_RECURSIVE_NEVER, + NAUTILUS_QUERY_RECURSIVE_ALWAYS, + NAUTILUS_QUERY_RECURSIVE_LOCAL_ONLY, + NAUTILUS_QUERY_RECURSIVE_INDEXED_ONLY, +} NautilusQueryRecursive; + +#define NAUTILUS_TYPE_QUERY (nautilus_query_get_type ()) + +G_DECLARE_FINAL_TYPE (NautilusQuery, nautilus_query, NAUTILUS, QUERY, GObject) + +NautilusQuery* nautilus_query_new (void); + +char * nautilus_query_get_text (NautilusQuery *query); +void nautilus_query_set_text (NautilusQuery *query, const char *text); + +gboolean nautilus_query_get_show_hidden_files (NautilusQuery *query); +void nautilus_query_set_show_hidden_files (NautilusQuery *query, gboolean show_hidden); + +GFile* nautilus_query_get_location (NautilusQuery *query); +void nautilus_query_set_location (NautilusQuery *query, + GFile *location); + +GPtrArray * nautilus_query_get_mime_types (NautilusQuery *query); +void nautilus_query_set_mime_types (NautilusQuery *query, GPtrArray *mime_types); + +NautilusQuerySearchContent nautilus_query_get_search_content (NautilusQuery *query); +void nautilus_query_set_search_content (NautilusQuery *query, + NautilusQuerySearchContent content); + +NautilusQuerySearchType nautilus_query_get_search_type (NautilusQuery *query); +void nautilus_query_set_search_type (NautilusQuery *query, + NautilusQuerySearchType type); + +GPtrArray* nautilus_query_get_date_range (NautilusQuery *query); +void nautilus_query_set_date_range (NautilusQuery *query, + GPtrArray *date_range); + +NautilusQueryRecursive nautilus_query_get_recursive (NautilusQuery *query); +void nautilus_query_set_recursive (NautilusQuery *query, + NautilusQueryRecursive recursive); + +gboolean nautilus_query_get_searching (NautilusQuery *query); + +void nautilus_query_set_searching (NautilusQuery *query, + gboolean searching); + +gdouble nautilus_query_matches_string (NautilusQuery *query, const gchar *string); + +char * nautilus_query_to_readable_string (NautilusQuery *query); + +gboolean nautilus_query_is_empty (NautilusQuery *query); diff --git a/src/nautilus-rename-file-popover-controller.c b/src/nautilus-rename-file-popover-controller.c new file mode 100644 index 0000000..0f3b329 --- /dev/null +++ b/src/nautilus-rename-file-popover-controller.c @@ -0,0 +1,439 @@ +/* nautilus-rename-file-popover-controller.c + * + * Copyright (C) 2016 the Nautilus developers + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License as + * published by the Free Software Foundation; either version 2 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 warranty of + * MERCHANTABILITY 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 . + * + */ + +#include + +#include + +#include "nautilus-rename-file-popover-controller.h" + +#include "nautilus-directory.h" +#include "nautilus-file-private.h" + + +#define RENAME_ENTRY_MIN_CHARS 30 +#define RENAME_ENTRY_MAX_CHARS 50 + +struct _NautilusRenameFilePopoverController +{ + NautilusFileNameWidgetController parent_instance; + + NautilusFile *target_file; + gboolean target_is_folder; + + GtkWidget *rename_file_popover; + GtkWidget *name_entry; + GtkWidget *title_label; + + gulong closed_handler_id; + gulong file_changed_handler_id; + gulong key_press_event_handler_id; +}; + +G_DEFINE_TYPE (NautilusRenameFilePopoverController, nautilus_rename_file_popover_controller, NAUTILUS_TYPE_FILE_NAME_WIDGET_CONTROLLER) + +static void +disconnect_signal_handlers (NautilusRenameFilePopoverController *self) +{ + g_assert (NAUTILUS_IS_RENAME_FILE_POPOVER_CONTROLLER (self)); + + g_clear_signal_handler (&self->closed_handler_id, self->rename_file_popover); + g_clear_signal_handler (&self->file_changed_handler_id, self->target_file); + g_clear_signal_handler (&self->key_press_event_handler_id, self->name_entry); +} + +static void +reset_state (NautilusRenameFilePopoverController *self) +{ + g_assert (NAUTILUS_IS_RENAME_FILE_POPOVER_CONTROLLER (self)); + + disconnect_signal_handlers (self); + + g_clear_object (&self->target_file); + + gtk_popover_popdown (GTK_POPOVER (self->rename_file_popover)); +} + +static void +rename_file_popover_controller_on_closed (GtkPopover *popover, + gpointer user_data) +{ + NautilusRenameFilePopoverController *controller; + + controller = NAUTILUS_RENAME_FILE_POPOVER_CONTROLLER (user_data); + + reset_state (controller); + + g_signal_emit_by_name (controller, "cancelled"); +} + +static gboolean +nautilus_rename_file_popover_controller_name_is_valid (NautilusFileNameWidgetController *controller, + gchar *name, + gchar **error_message) +{ + NautilusRenameFilePopoverController *self; + gboolean is_valid; + + self = NAUTILUS_RENAME_FILE_POPOVER_CONTROLLER (controller); + + is_valid = TRUE; + if (strlen (name) == 0) + { + is_valid = FALSE; + } + else if (strstr (name, "/") != NULL) + { + is_valid = FALSE; + if (self->target_is_folder) + { + *error_message = _("Folder names cannot contain “/”."); + } + else + { + *error_message = _("File names cannot contain “/”."); + } + } + else if (strcmp (name, ".") == 0) + { + is_valid = FALSE; + if (self->target_is_folder) + { + *error_message = _("A folder cannot be called “.”."); + } + else + { + *error_message = _("A file cannot be called “.”."); + } + } + else if (strcmp (name, "..") == 0) + { + is_valid = FALSE; + if (self->target_is_folder) + { + *error_message = _("A folder cannot be called “..”."); + } + else + { + *error_message = _("A file cannot be called “..”."); + } + } + else if (nautilus_file_name_widget_controller_is_name_too_long (controller, name)) + { + is_valid = FALSE; + if (self->target_is_folder) + { + *error_message = _("Folder name is too long."); + } + else + { + *error_message = _("File name is too long."); + } + } + + if (is_valid && g_str_has_prefix (name, ".")) + { + /* We must warn about the side effect */ + if (self->target_is_folder) + { + *error_message = _("Folders with “.” at the beginning of their name are hidden."); + } + else + { + *error_message = _("Files with “.” at the beginning of their name are hidden."); + } + } + + return is_valid; +} + +static gboolean +nautilus_rename_file_popover_controller_ignore_existing_file (NautilusFileNameWidgetController *controller, + NautilusFile *existing_file) +{ + NautilusRenameFilePopoverController *self; + g_autofree gchar *display_name = NULL; + + self = NAUTILUS_RENAME_FILE_POPOVER_CONTROLLER (controller); + + display_name = nautilus_file_get_display_name (existing_file); + + return nautilus_file_compare_display_name (self->target_file, display_name) == 0; +} + +static gboolean +name_entry_on_f2_pressed (GtkWidget *widget, + NautilusRenameFilePopoverController *self) +{ + guint text_length; + gint start_pos; + gint end_pos; + gboolean all_selected; + + text_length = (guint) gtk_entry_get_text_length (GTK_ENTRY (widget)); + if (text_length == 0) + { + return GDK_EVENT_STOP; + } + + gtk_editable_get_selection_bounds (GTK_EDITABLE (widget), + &start_pos, &end_pos); + + all_selected = (start_pos == 0) && ((guint) end_pos == text_length); + if (!all_selected || !nautilus_file_is_regular_file (self->target_file)) + { + gtk_editable_select_region (GTK_EDITABLE (widget), 0, -1); + } + else + { + gint start_offset; + gint end_offset; + + /* Select the name part without the file extension */ + eel_filename_get_rename_region (gtk_editable_get_text (GTK_EDITABLE (widget)), + &start_offset, &end_offset); + gtk_editable_select_region (GTK_EDITABLE (widget), + start_offset, end_offset); + } + + return GDK_EVENT_STOP; +} + +static gboolean +name_entry_on_undo (GtkWidget *widget, + NautilusRenameFilePopoverController *self) +{ + g_autofree gchar *edit_name = NULL; + + edit_name = nautilus_file_get_edit_name (self->target_file); + + gtk_editable_set_text (GTK_EDITABLE (widget), edit_name); + + gtk_editable_select_region (GTK_EDITABLE (widget), 0, -1); + + return GDK_EVENT_STOP; +} + +static gboolean +on_event_controller_key_key_pressed (GtkEventControllerKey *controller, + guint keyval, + guint keycode, + GdkModifierType state, + gpointer user_data) +{ + GtkWidget *widget; + NautilusRenameFilePopoverController *self; + + widget = gtk_event_controller_get_widget (GTK_EVENT_CONTROLLER (controller)); + self = NAUTILUS_RENAME_FILE_POPOVER_CONTROLLER (user_data); + + if (keyval == GDK_KEY_F2) + { + return name_entry_on_f2_pressed (widget, self); + } + else if (keyval == GDK_KEY_z && (state & GDK_CONTROL_MASK) != 0) + { + return name_entry_on_undo (widget, self); + } + + return GDK_EVENT_PROPAGATE; +} + +static void +target_file_on_changed (NautilusFile *file, + gpointer user_data) +{ + NautilusRenameFilePopoverController *controller; + + controller = NAUTILUS_RENAME_FILE_POPOVER_CONTROLLER (user_data); + + if (nautilus_file_is_gone (file)) + { + reset_state (controller); + + g_signal_emit_by_name (controller, "cancelled"); + } +} + +NautilusRenameFilePopoverController * +nautilus_rename_file_popover_controller_new (GtkWidget *relative_to) +{ + NautilusRenameFilePopoverController *self; + g_autoptr (GtkBuilder) builder = NULL; + GtkWidget *rename_file_popover; + GtkWidget *error_revealer; + GtkWidget *error_label; + GtkWidget *name_entry; + GtkWidget *activate_button; + GtkWidget *title_label; + + builder = gtk_builder_new_from_resource ("/org/gnome/nautilus/ui/nautilus-rename-file-popover.ui"); + rename_file_popover = GTK_WIDGET (gtk_builder_get_object (builder, "rename_file_popover")); + error_revealer = GTK_WIDGET (gtk_builder_get_object (builder, "error_revealer")); + error_label = GTK_WIDGET (gtk_builder_get_object (builder, "error_label")); + name_entry = GTK_WIDGET (gtk_builder_get_object (builder, "name_entry")); + activate_button = GTK_WIDGET (gtk_builder_get_object (builder, "rename_button")); + title_label = GTK_WIDGET (gtk_builder_get_object (builder, "title_label")); + + self = g_object_new (NAUTILUS_TYPE_RENAME_FILE_POPOVER_CONTROLLER, + "error-revealer", error_revealer, + "error-label", error_label, + "name-entry", name_entry, + "activate-button", activate_button, + NULL); + + self->rename_file_popover = g_object_ref_sink (rename_file_popover); + self->name_entry = name_entry; + self->title_label = title_label; + + gtk_popover_set_default_widget (GTK_POPOVER (rename_file_popover), name_entry); + + gtk_widget_set_parent (rename_file_popover, relative_to); + + return self; +} + +NautilusFile * +nautilus_rename_file_popover_controller_get_target_file (NautilusRenameFilePopoverController *self) +{ + g_return_val_if_fail (NAUTILUS_IS_RENAME_FILE_POPOVER_CONTROLLER (self), NULL); + + return self->target_file; +} + +void +nautilus_rename_file_popover_controller_show_for_file (NautilusRenameFilePopoverController *self, + NautilusFile *target_file, + GdkRectangle *pointing_to) +{ + g_autoptr (NautilusDirectory) containing_directory = NULL; + GtkEventController *controller; + g_autofree gchar *edit_name = NULL; + gint n_chars; + + g_assert (NAUTILUS_IS_RENAME_FILE_POPOVER_CONTROLLER (self)); + g_assert (NAUTILUS_IS_FILE (target_file)); + + reset_state (self); + + self->target_file = g_object_ref (target_file); + + if (!nautilus_file_is_self_owned (self->target_file)) + { + g_autoptr (NautilusFile) parent = NULL; + + parent = nautilus_file_get_parent (self->target_file); + containing_directory = nautilus_directory_get_for_file (parent); + } + else + { + containing_directory = nautilus_directory_get_for_file (self->target_file); + } + + nautilus_file_name_widget_controller_set_containing_directory (NAUTILUS_FILE_NAME_WIDGET_CONTROLLER (self), + containing_directory); + + self->target_is_folder = nautilus_file_is_directory (self->target_file); + + self->closed_handler_id = g_signal_connect (self->rename_file_popover, + "closed", + G_CALLBACK (rename_file_popover_controller_on_closed), + self); + + self->file_changed_handler_id = g_signal_connect (self->target_file, + "changed", + G_CALLBACK (target_file_on_changed), + self); + + controller = gtk_event_controller_key_new (); + gtk_widget_add_controller (self->name_entry, controller); + g_signal_connect (controller, "key-pressed", + G_CALLBACK (on_event_controller_key_key_pressed), self); + + gtk_label_set_text (GTK_LABEL (self->title_label), + self->target_is_folder ? _("Rename Folder") : + _("Rename File")); + + edit_name = nautilus_file_get_edit_name (self->target_file); + + gtk_editable_set_text (GTK_EDITABLE (self->name_entry), edit_name); + + gtk_popover_set_pointing_to (GTK_POPOVER (self->rename_file_popover), pointing_to); + + gtk_popover_popup (GTK_POPOVER (self->rename_file_popover)); + + if (nautilus_file_is_regular_file (self->target_file)) + { + gint start_offset; + gint end_offset; + + /* Select the name part without the file extension */ + eel_filename_get_rename_region (edit_name, + &start_offset, &end_offset); + gtk_editable_select_region (GTK_EDITABLE (self->name_entry), + start_offset, end_offset); + } + + n_chars = g_utf8_strlen (edit_name, -1); + gtk_editable_set_width_chars (GTK_EDITABLE (self->name_entry), + MIN (MAX (n_chars, RENAME_ENTRY_MIN_CHARS), + RENAME_ENTRY_MAX_CHARS)); +} + +static void +on_name_accepted (NautilusFileNameWidgetController *controller) +{ + NautilusRenameFilePopoverController *self; + + self = NAUTILUS_RENAME_FILE_POPOVER_CONTROLLER (controller); + + reset_state (self); +} + +static void +nautilus_rename_file_popover_controller_init (NautilusRenameFilePopoverController *self) +{ + g_signal_connect_after (self, "name-accepted", G_CALLBACK (on_name_accepted), self); +} + +static void +nautilus_rename_file_popover_controller_finalize (GObject *object) +{ + NautilusRenameFilePopoverController *self; + + self = NAUTILUS_RENAME_FILE_POPOVER_CONTROLLER (object); + + reset_state (self); + + g_clear_pointer (&self->rename_file_popover, gtk_widget_unparent); + + G_OBJECT_CLASS (nautilus_rename_file_popover_controller_parent_class)->finalize (object); +} + +static void +nautilus_rename_file_popover_controller_class_init (NautilusRenameFilePopoverControllerClass *klass) +{ + GObjectClass *object_class = G_OBJECT_CLASS (klass); + NautilusFileNameWidgetControllerClass *parent_class = NAUTILUS_FILE_NAME_WIDGET_CONTROLLER_CLASS (klass); + + object_class->finalize = nautilus_rename_file_popover_controller_finalize; + + parent_class->name_is_valid = nautilus_rename_file_popover_controller_name_is_valid; + parent_class->ignore_existing_file = nautilus_rename_file_popover_controller_ignore_existing_file; +} diff --git a/src/nautilus-rename-file-popover-controller.h b/src/nautilus-rename-file-popover-controller.h new file mode 100644 index 0000000..d9b229e --- /dev/null +++ b/src/nautilus-rename-file-popover-controller.h @@ -0,0 +1,37 @@ +/* nautilus-rename-file-popover-controller.h + * + * Copyright (C) 2016 the Nautilus developers + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License as + * published by the Free Software Foundation; either version 2 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 warranty of + * MERCHANTABILITY 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 . + * + */ + +#pragma once + +#include +#include + +#include "nautilus-file-name-widget-controller.h" +#include "nautilus-file.h" + +#define NAUTILUS_TYPE_RENAME_FILE_POPOVER_CONTROLLER nautilus_rename_file_popover_controller_get_type () +G_DECLARE_FINAL_TYPE (NautilusRenameFilePopoverController, nautilus_rename_file_popover_controller, NAUTILUS, RENAME_FILE_POPOVER_CONTROLLER, NautilusFileNameWidgetController) + +NautilusRenameFilePopoverController * nautilus_rename_file_popover_controller_new (GtkWidget *relative_to); + +NautilusFile * nautilus_rename_file_popover_controller_get_target_file (NautilusRenameFilePopoverController *controller); + +void nautilus_rename_file_popover_controller_show_for_file (NautilusRenameFilePopoverController *controller, + NautilusFile *target_file, + GdkRectangle *pointing_to); diff --git a/src/nautilus-search-directory-file.c b/src/nautilus-search-directory-file.c new file mode 100644 index 0000000..d118bab --- /dev/null +++ b/src/nautilus-search-directory-file.c @@ -0,0 +1,313 @@ +/* + * nautilus-search-directory-file.c: Subclass of NautilusFile to help implement the + * searches + * + * Copyright (C) 2005 Novell, Inc. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License as + * published by the Free Software Foundation; either version 2 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 warranty of + * MERCHANTABILITY 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 . + * + * Author: Anders Carlsson + */ + +#include "nautilus-search-directory-file.h" + +#include +#include +#include +#include + +#include "nautilus-directory-notify.h" +#include "nautilus-directory-private.h" +#include "nautilus-enums.h" +#include "nautilus-file-private.h" +#include "nautilus-file-utilities.h" +#include "nautilus-keyfile-metadata.h" +#include "nautilus-query.h" +#include "nautilus-search-directory.h" + +struct _NautilusSearchDirectoryFile +{ + NautilusFile parent_instance; + + gchar *metadata_filename; +}; + +G_DEFINE_TYPE (NautilusSearchDirectoryFile, nautilus_search_directory_file, NAUTILUS_TYPE_FILE); + + +static void +search_directory_file_monitor_add (NautilusFile *file, + gconstpointer client, + NautilusFileAttributes attributes) +{ + /* No need for monitoring, we always emit changed when files + * are added/removed, and no other metadata changes */ + + /* Update display name, in case this didn't happen yet */ + nautilus_search_directory_file_update_display_name (NAUTILUS_SEARCH_DIRECTORY_FILE (file)); +} + +static void +search_directory_file_monitor_remove (NautilusFile *file, + gconstpointer client) +{ + /* Do nothing here, we don't have any monitors */ +} + +static void +search_directory_file_call_when_ready (NautilusFile *file, + NautilusFileAttributes file_attributes, + NautilusFileCallback callback, + gpointer callback_data) +{ + /* Update display name, in case this didn't happen yet */ + nautilus_search_directory_file_update_display_name (NAUTILUS_SEARCH_DIRECTORY_FILE (file)); + + if (callback != NULL) + { + /* All data for directory-as-file is always up to date */ + (*callback)(file, callback_data); + } +} + +static void +search_directory_file_cancel_call_when_ready (NautilusFile *file, + NautilusFileCallback callback, + gpointer callback_data) +{ + /* Do nothing here, we don't have any pending calls */ +} + +static gboolean +search_directory_file_check_if_ready (NautilusFile *file, + NautilusFileAttributes attributes) +{ + return TRUE; +} + +static gboolean +search_directory_file_get_item_count (NautilusFile *file, + guint *count, + gboolean *count_unreadable) +{ + GList *file_list; + + if (count) + { + NautilusDirectory *directory; + + directory = nautilus_file_get_directory (file); + file_list = nautilus_directory_get_file_list (directory); + + *count = g_list_length (file_list); + + nautilus_file_list_free (file_list); + } + + return TRUE; +} + +static NautilusRequestStatus +search_directory_file_get_deep_counts (NautilusFile *file, + guint *directory_count, + guint *file_count, + guint *unreadable_directory_count, + goffset *total_size) +{ + NautilusDirectory *directory; + NautilusFile *dir_file; + GList *file_list, *l; + guint dirs, files; + GFileType type; + + directory = nautilus_file_get_directory (file); + file_list = nautilus_directory_get_file_list (directory); + + dirs = files = 0; + for (l = file_list; l != NULL; l = l->next) + { + dir_file = NAUTILUS_FILE (l->data); + type = nautilus_file_get_file_type (dir_file); + if (type == G_FILE_TYPE_DIRECTORY) + { + dirs++; + } + else + { + files++; + } + } + + if (directory_count != NULL) + { + *directory_count = dirs; + } + if (file_count != NULL) + { + *file_count = files; + } + if (unreadable_directory_count != NULL) + { + *unreadable_directory_count = 0; + } + if (total_size != NULL) + { + /* FIXME: Maybe we want to calculate this? */ + *total_size = 0; + } + + nautilus_file_list_free (file_list); + + return NAUTILUS_REQUEST_DONE; +} + +static char * +search_directory_file_get_where_string (NautilusFile *file) +{ + return g_strdup (_("Search")); +} + +static void +search_directory_file_set_metadata (NautilusFile *file, + const char *key, + const char *value) +{ + NautilusSearchDirectoryFile *search_file; + + search_file = NAUTILUS_SEARCH_DIRECTORY_FILE (file); + nautilus_keyfile_metadata_set_string (file, + search_file->metadata_filename, + "directory", key, value); +} + +static void +search_directory_file_set_metadata_as_list (NautilusFile *file, + const char *key, + char **value) +{ + NautilusSearchDirectoryFile *search_file; + + search_file = NAUTILUS_SEARCH_DIRECTORY_FILE (file); + nautilus_keyfile_metadata_set_stringv (file, + search_file->metadata_filename, + "directory", key, (const gchar **) value); +} + +void +nautilus_search_directory_file_update_display_name (NautilusSearchDirectoryFile *search_file) +{ + NautilusFile *file; + NautilusDirectory *directory; + NautilusSearchDirectory *search_dir; + NautilusQuery *query; + char *display_name; + gboolean changed; + + + display_name = NULL; + file = NAUTILUS_FILE (search_file); + directory = nautilus_file_get_directory (file); + if (directory != NULL) + { + search_dir = NAUTILUS_SEARCH_DIRECTORY (directory); + query = nautilus_search_directory_get_query (search_dir); + + if (query != NULL) + { + display_name = nautilus_query_to_readable_string (query); + g_object_unref (query); + } + } + + if (display_name == NULL) + { + display_name = g_strdup (_("Search")); + } + + changed = nautilus_file_set_display_name (file, display_name, NULL, TRUE); + if (changed) + { + nautilus_file_emit_changed (file); + } + + g_free (display_name); +} + +static void +nautilus_search_directory_file_init (NautilusSearchDirectoryFile *search_file) +{ + NautilusFile *file; + gchar *xdg_dir; + + file = NAUTILUS_FILE (search_file); + + xdg_dir = nautilus_get_user_directory (); + search_file->metadata_filename = g_build_filename (xdg_dir, + "search-metadata", + NULL); + g_free (xdg_dir); + + file->details->got_file_info = TRUE; + file->details->mime_type = g_ref_string_new_intern ("x-directory/normal"); + file->details->type = G_FILE_TYPE_DIRECTORY; + file->details->size = 0; + + file->details->file_info_is_up_to_date = TRUE; + + file->details->custom_icon = NULL; + file->details->activation_uri = NULL; + + file->details->directory_count = 0; + file->details->got_directory_count = TRUE; + file->details->directory_count_is_up_to_date = TRUE; + + nautilus_file_set_display_name (file, _("Search"), NULL, TRUE); +} + +static void +nautilus_search_directory_file_finalize (GObject *object) +{ + NautilusSearchDirectoryFile *search_file; + + search_file = NAUTILUS_SEARCH_DIRECTORY_FILE (object); + + g_free (search_file->metadata_filename); + + G_OBJECT_CLASS (nautilus_search_directory_file_parent_class)->finalize (object); +} + +static void +nautilus_search_directory_file_class_init (NautilusSearchDirectoryFileClass *klass) +{ + GObjectClass *object_class; + NautilusFileClass *file_class; + + object_class = G_OBJECT_CLASS (klass); + file_class = NAUTILUS_FILE_CLASS (klass); + + object_class->finalize = nautilus_search_directory_file_finalize; + + file_class->default_file_type = G_FILE_TYPE_DIRECTORY; + + file_class->monitor_add = search_directory_file_monitor_add; + file_class->monitor_remove = search_directory_file_monitor_remove; + file_class->call_when_ready = search_directory_file_call_when_ready; + file_class->cancel_call_when_ready = search_directory_file_cancel_call_when_ready; + file_class->check_if_ready = search_directory_file_check_if_ready; + file_class->get_item_count = search_directory_file_get_item_count; + file_class->get_deep_counts = search_directory_file_get_deep_counts; + file_class->get_where_string = search_directory_file_get_where_string; + file_class->set_metadata = search_directory_file_set_metadata; + file_class->set_metadata_as_list = search_directory_file_set_metadata_as_list; +} diff --git a/src/nautilus-search-directory-file.h b/src/nautilus-search-directory-file.h new file mode 100644 index 0000000..05c0ec0 --- /dev/null +++ b/src/nautilus-search-directory-file.h @@ -0,0 +1,32 @@ +/* + nautilus-search-directory-file.h: Subclass of NautilusFile to implement the + the case of the search directory + + Copyright (C) 2003 Red Hat, Inc. + + This program is free software; you can redistribute it and/or + modify it under the terms of the GNU General Public License as + published by the Free Software Foundation; either version 2 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 warranty of + MERCHANTABILITY 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 . + + Author: Alexander Larsson +*/ + +#pragma once + +#include "nautilus-file.h" + +#define NAUTILUS_TYPE_SEARCH_DIRECTORY_FILE nautilus_search_directory_file_get_type () +G_DECLARE_FINAL_TYPE (NautilusSearchDirectoryFile, nautilus_search_directory_file, + NAUTILUS, SEARCH_DIRECTORY_FILE, + NautilusFile) + +void nautilus_search_directory_file_update_display_name (NautilusSearchDirectoryFile *search_file); diff --git a/src/nautilus-search-directory.c b/src/nautilus-search-directory.c new file mode 100644 index 0000000..24545fa --- /dev/null +++ b/src/nautilus-search-directory.c @@ -0,0 +1,1080 @@ +/* + * Copyright (C) 2005 Novell, Inc + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License as + * published by the Free Software Foundation; either version 2 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 warranty of + * MERCHANTABILITY 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 . + * + * Author: Anders Carlsson + */ + +#include "nautilus-search-directory.h" + +#include +#include +#include +#include +#include + +#include "nautilus-directory-private.h" +#include "nautilus-file-private.h" +#include "nautilus-file-utilities.h" +#include "nautilus-file.h" +#include "nautilus-query.h" +#include "nautilus-search-directory-file.h" +#include "nautilus-search-engine-model.h" +#include "nautilus-search-engine.h" +#include "nautilus-search-provider.h" + +struct _NautilusSearchDirectory +{ + NautilusDirectory parent_instance; + + NautilusQuery *query; + + NautilusSearchEngine *engine; + + gboolean search_running; + /* When the search directory is stopped or cancelled, we migth wait + * until all data and signals from previous search are stopped and removed + * from the search engine. While this situation happens we don't want to connect + * clients to our signals, and we will wait until the search data and signals + * are valid and ready. + * The worst thing that can happens if we don't do this is that new clients + * migth get the information of old searchs if they are waiting_for_file_list. + * But that shouldn't be a big deal since old clients have the old information. + * But anyway it's currently unused for this case since the only client is + * nautilus-view and is not waiting_for_file_list :) . + * + * The other use case is for letting clients know if information of the directory + * is outdated or not valid. This might happens for automatic + * scheduled timeouts. */ + gboolean search_ready_and_valid; + + GList *files; + GHashTable *files_hash; + + GList *monitor_list; + GList *callback_list; + GList *pending_callback_list; + + GBinding *binding; + + NautilusDirectory *base_model; +}; + +typedef struct +{ + gboolean monitor_hidden_files; + NautilusFileAttributes monitor_attributes; + + gconstpointer client; +} SearchMonitor; + +typedef struct +{ + NautilusSearchDirectory *search_directory; + + NautilusDirectoryCallback callback; + gpointer callback_data; + + NautilusFileAttributes wait_for_attributes; + gboolean wait_for_file_list; + GList *file_list; + GHashTable *non_ready_hash; +} SearchCallback; + +enum +{ + PROP_0, + PROP_BASE_MODEL, + PROP_QUERY, + NUM_PROPERTIES +}; + +G_DEFINE_TYPE_WITH_CODE (NautilusSearchDirectory, nautilus_search_directory, NAUTILUS_TYPE_DIRECTORY, + nautilus_ensure_extension_points (); + /* It looks like you’re implementing an extension point. + * Did you modify nautilus_ensure_extension_builtins() accordingly? + * + * • Yes + * • Doing it right now + */ + g_io_extension_point_implement (NAUTILUS_DIRECTORY_PROVIDER_EXTENSION_POINT_NAME, + g_define_type_id, + NAUTILUS_SEARCH_DIRECTORY_PROVIDER_NAME, + 0)); + +static GParamSpec *properties[NUM_PROPERTIES] = { NULL, }; + +static void search_engine_hits_added (NautilusSearchEngine *engine, + GList *hits, + NautilusSearchDirectory *self); +static void search_engine_error (NautilusSearchEngine *engine, + const char *error, + NautilusSearchDirectory *self); +static void search_callback_file_ready_callback (NautilusFile *file, + gpointer data); +static void file_changed (NautilusFile *file, + NautilusSearchDirectory *self); + +static void +reset_file_list (NautilusSearchDirectory *self) +{ + GList *list, *monitor_list; + NautilusFile *file; + SearchMonitor *monitor; + + /* Remove file connections */ + for (list = self->files; list != NULL; list = list->next) + { + file = list->data; + + /* Disconnect change handler */ + g_signal_handlers_disconnect_by_func (file, file_changed, self); + + /* Remove monitors */ + for (monitor_list = self->monitor_list; monitor_list; + monitor_list = monitor_list->next) + { + monitor = monitor_list->data; + nautilus_file_monitor_remove (file, monitor); + } + } + + nautilus_file_list_free (self->files); + self->files = NULL; + + g_hash_table_remove_all (self->files_hash); +} + +static void +set_hidden_files (NautilusSearchDirectory *self) +{ + GList *l; + SearchMonitor *monitor; + gboolean monitor_hidden = FALSE; + + for (l = self->monitor_list; l != NULL; l = l->next) + { + monitor = l->data; + monitor_hidden |= monitor->monitor_hidden_files; + + if (monitor_hidden) + { + break; + } + } + + nautilus_query_set_show_hidden_files (self->query, monitor_hidden); +} + +static void +start_search (NautilusSearchDirectory *self) +{ + NautilusSearchEngineModel *model_provider; + + if (!self->query) + { + return; + } + + if (self->search_running) + { + return; + } + + if (!self->monitor_list && !self->pending_callback_list) + { + return; + } + + /* We need to start the search engine */ + self->search_running = TRUE; + self->search_ready_and_valid = FALSE; + + set_hidden_files (self); + nautilus_search_provider_set_query (NAUTILUS_SEARCH_PROVIDER (self->engine), + self->query); + + model_provider = nautilus_search_engine_get_model_provider (self->engine); + nautilus_search_engine_model_set_model (model_provider, self->base_model); + + reset_file_list (self); + + nautilus_search_provider_start (NAUTILUS_SEARCH_PROVIDER (self->engine)); +} + +static void +stop_search (NautilusSearchDirectory *self) +{ + if (!self->search_running) + { + return; + } + + self->search_running = FALSE; + nautilus_search_provider_stop (NAUTILUS_SEARCH_PROVIDER (self->engine)); + + reset_file_list (self); +} + +static void +file_changed (NautilusFile *file, + NautilusSearchDirectory *self) +{ + GList list; + + list.data = file; + list.next = NULL; + + nautilus_directory_emit_files_changed (NAUTILUS_DIRECTORY (self), &list); +} + +static void +search_monitor_add (NautilusDirectory *directory, + gconstpointer client, + gboolean monitor_hidden_files, + NautilusFileAttributes file_attributes, + NautilusDirectoryCallback callback, + gpointer callback_data) +{ + GList *list; + SearchMonitor *monitor; + NautilusSearchDirectory *self; + NautilusFile *file; + + self = NAUTILUS_SEARCH_DIRECTORY (directory); + + monitor = g_new0 (SearchMonitor, 1); + monitor->monitor_hidden_files = monitor_hidden_files; + monitor->monitor_attributes = file_attributes; + monitor->client = client; + + self->monitor_list = g_list_prepend (self->monitor_list, monitor); + + if (callback != NULL) + { + (*callback)(directory, self->files, callback_data); + } + + for (list = self->files; list != NULL; list = list->next) + { + file = list->data; + + /* Add monitors */ + nautilus_file_monitor_add (file, monitor, file_attributes); + } + + start_search (self); +} + +static void +search_monitor_remove_file_monitors (SearchMonitor *monitor, + NautilusSearchDirectory *self) +{ + GList *list; + NautilusFile *file; + + for (list = self->files; list != NULL; list = list->next) + { + file = list->data; + + nautilus_file_monitor_remove (file, monitor); + } +} + +static void +search_monitor_destroy (SearchMonitor *monitor, + NautilusSearchDirectory *self) +{ + search_monitor_remove_file_monitors (monitor, self); + + g_free (monitor); +} + +static void +search_monitor_remove (NautilusDirectory *directory, + gconstpointer client) +{ + NautilusSearchDirectory *self; + SearchMonitor *monitor; + GList *list; + + self = NAUTILUS_SEARCH_DIRECTORY (directory); + + for (list = self->monitor_list; list != NULL; list = list->next) + { + monitor = list->data; + + if (monitor->client == client) + { + self->monitor_list = g_list_delete_link (self->monitor_list, list); + + search_monitor_destroy (monitor, self); + + break; + } + } + + if (!self->monitor_list) + { + stop_search (self); + } +} + +static void +cancel_call_when_ready (gpointer key, + gpointer value, + gpointer user_data) +{ + SearchCallback *search_callback; + NautilusFile *file; + + file = key; + search_callback = user_data; + + nautilus_file_cancel_call_when_ready (file, search_callback_file_ready_callback, + search_callback); +} + +static void +search_callback_destroy (SearchCallback *search_callback) +{ + if (search_callback->non_ready_hash) + { + g_hash_table_foreach (search_callback->non_ready_hash, cancel_call_when_ready, search_callback); + g_hash_table_destroy (search_callback->non_ready_hash); + } + + nautilus_file_list_free (search_callback->file_list); + + g_free (search_callback); +} + +static void +search_callback_invoke_and_destroy (SearchCallback *search_callback) +{ + search_callback->callback (NAUTILUS_DIRECTORY (search_callback->search_directory), + search_callback->file_list, + search_callback->callback_data); + + search_callback->search_directory->callback_list = + g_list_remove (search_callback->search_directory->callback_list, search_callback); + + search_callback_destroy (search_callback); +} + +static void +search_callback_file_ready_callback (NautilusFile *file, + gpointer data) +{ + SearchCallback *search_callback = data; + + g_hash_table_remove (search_callback->non_ready_hash, file); + + if (g_hash_table_size (search_callback->non_ready_hash) == 0) + { + search_callback_invoke_and_destroy (search_callback); + } +} + +static void +search_callback_add_file_callbacks (SearchCallback *callback) +{ + GList *file_list_copy, *list; + NautilusFile *file; + + file_list_copy = g_list_copy (callback->file_list); + + for (list = file_list_copy; list != NULL; list = list->next) + { + file = list->data; + + nautilus_file_call_when_ready (file, + callback->wait_for_attributes, + search_callback_file_ready_callback, + callback); + } + g_list_free (file_list_copy); +} + +static SearchCallback * +search_callback_find (NautilusSearchDirectory *self, + NautilusDirectoryCallback callback, + gpointer callback_data) +{ + SearchCallback *search_callback; + GList *list; + + for (list = self->callback_list; list != NULL; list = list->next) + { + search_callback = list->data; + + if (search_callback->callback == callback && + search_callback->callback_data == callback_data) + { + return search_callback; + } + } + + return NULL; +} + +static SearchCallback * +search_callback_find_pending (NautilusSearchDirectory *self, + NautilusDirectoryCallback callback, + gpointer callback_data) +{ + SearchCallback *search_callback; + GList *list; + + for (list = self->pending_callback_list; list != NULL; list = list->next) + { + search_callback = list->data; + + if (search_callback->callback == callback && + search_callback->callback_data == callback_data) + { + return search_callback; + } + } + + return NULL; +} + +static GHashTable * +file_list_to_hash_table (GList *file_list) +{ + GList *list; + GHashTable *table; + + if (!file_list) + { + return NULL; + } + + table = g_hash_table_new (NULL, NULL); + + for (list = file_list; list != NULL; list = list->next) + { + g_hash_table_insert (table, list->data, list->data); + } + + return table; +} + +static void +search_call_when_ready (NautilusDirectory *directory, + NautilusFileAttributes file_attributes, + gboolean wait_for_file_list, + NautilusDirectoryCallback callback, + gpointer callback_data) +{ + NautilusSearchDirectory *self; + SearchCallback *search_callback; + + self = NAUTILUS_SEARCH_DIRECTORY (directory); + + search_callback = search_callback_find (self, callback, callback_data); + if (search_callback == NULL) + { + search_callback = search_callback_find_pending (self, callback, callback_data); + } + + if (search_callback) + { + g_warning ("tried to add a new callback while an old one was pending"); + return; + } + + search_callback = g_new0 (SearchCallback, 1); + search_callback->search_directory = self; + search_callback->callback = callback; + search_callback->callback_data = callback_data; + search_callback->wait_for_attributes = file_attributes; + search_callback->wait_for_file_list = wait_for_file_list; + + if (wait_for_file_list && !self->search_ready_and_valid) + { + /* Add it to the pending callback list, which will be + * processed when the directory has valid data from the new + * search and all data and signals from previous searchs is removed. */ + self->pending_callback_list = + g_list_prepend (self->pending_callback_list, search_callback); + + /* We might need to start the search engine */ + start_search (self); + } + else + { + search_callback->file_list = nautilus_file_list_copy (self->files); + search_callback->non_ready_hash = file_list_to_hash_table (self->files); + + if (!search_callback->non_ready_hash) + { + /* If there are no ready files, we invoke the callback + * with an empty list. + */ + search_callback_invoke_and_destroy (search_callback); + } + else + { + self->callback_list = g_list_prepend (self->callback_list, search_callback); + search_callback_add_file_callbacks (search_callback); + } + } +} + +static void +search_cancel_callback (NautilusDirectory *directory, + NautilusDirectoryCallback callback, + gpointer callback_data) +{ + NautilusSearchDirectory *self; + SearchCallback *search_callback; + + self = NAUTILUS_SEARCH_DIRECTORY (directory); + search_callback = search_callback_find (self, callback, callback_data); + + if (search_callback) + { + self->callback_list = g_list_remove (self->callback_list, search_callback); + + search_callback_destroy (search_callback); + + goto done; + } + + /* Check for a pending callback */ + search_callback = search_callback_find_pending (self, callback, callback_data); + + if (search_callback) + { + self->pending_callback_list = g_list_remove (self->pending_callback_list, search_callback); + + search_callback_destroy (search_callback); + } + +done: + if (!self->callback_list && !self->pending_callback_list) + { + stop_search (self); + } +} + +static void +search_callback_add_pending_file_callbacks (SearchCallback *callback) +{ + callback->file_list = nautilus_file_list_copy (callback->search_directory->files); + callback->non_ready_hash = file_list_to_hash_table (callback->search_directory->files); + + search_callback_add_file_callbacks (callback); +} + +static void +search_directory_add_pending_files_callbacks (NautilusSearchDirectory *self) +{ + /* Add all file callbacks */ + g_list_foreach (self->pending_callback_list, + (GFunc) search_callback_add_pending_file_callbacks, NULL); + self->callback_list = g_list_concat (self->callback_list, + self->pending_callback_list); + + g_list_free (self->pending_callback_list); + self->pending_callback_list = NULL; +} + +static void +on_search_directory_search_ready_and_valid (NautilusSearchDirectory *self) +{ + search_directory_add_pending_files_callbacks (self); + self->search_ready_and_valid = TRUE; +} + +static void +search_engine_hits_added (NautilusSearchEngine *engine, + GList *hits, + NautilusSearchDirectory *self) +{ + GList *hit_list; + GList *file_list; + NautilusFile *file; + SearchMonitor *monitor; + GList *monitor_list; + + file_list = NULL; + + for (hit_list = hits; hit_list != NULL; hit_list = hit_list->next) + { + NautilusSearchHit *hit = hit_list->data; + const char *uri; + + uri = nautilus_search_hit_get_uri (hit); + + nautilus_search_hit_compute_scores (hit, self->query); + + file = nautilus_file_get_by_uri (uri); + nautilus_file_set_search_relevance (file, nautilus_search_hit_get_relevance (hit)); + nautilus_file_set_search_fts_snippet (file, nautilus_search_hit_get_fts_snippet (hit)); + + for (monitor_list = self->monitor_list; monitor_list; monitor_list = monitor_list->next) + { + monitor = monitor_list->data; + + /* Add monitors */ + nautilus_file_monitor_add (file, monitor, monitor->monitor_attributes); + } + + g_signal_connect (file, "changed", G_CALLBACK (file_changed), self), + + file_list = g_list_prepend (file_list, file); + g_hash_table_add (self->files_hash, file); + } + + self->files = g_list_concat (self->files, file_list); + + nautilus_directory_emit_files_added (NAUTILUS_DIRECTORY (self), file_list); + + file = nautilus_directory_get_corresponding_file (NAUTILUS_DIRECTORY (self)); + nautilus_file_emit_changed (file); + nautilus_file_unref (file); + + search_directory_add_pending_files_callbacks (self); +} + +static void +search_engine_error (NautilusSearchEngine *engine, + const char *error_message, + NautilusSearchDirectory *self) +{ + GError *error; + + error = g_error_new_literal (G_IO_ERROR, G_IO_ERROR_FAILED, + error_message); + nautilus_directory_emit_load_error (NAUTILUS_DIRECTORY (self), + error); + g_error_free (error); +} + +static void +search_engine_finished (NautilusSearchEngine *engine, + NautilusSearchProviderStatus status, + NautilusSearchDirectory *self) +{ + /* If the search engine is going to restart means it finished an old search + * that was stopped or cancelled. + * Don't emit the done loading signal in this case, since this means the search + * directory tried to start a new search before all the search providers were finished + * in the search engine. + * If we emit the done-loading signal in this situation the client will think + * that it finished the current search, not an old one like it's actually + * happening. */ + if (status == NAUTILUS_SEARCH_PROVIDER_STATUS_NORMAL) + { + on_search_directory_search_ready_and_valid (self); + nautilus_directory_emit_done_loading (NAUTILUS_DIRECTORY (self)); + } + else if (status == NAUTILUS_SEARCH_PROVIDER_STATUS_RESTARTING) + { + /* Remove file monitors of the files from an old search that just + * actually finished */ + reset_file_list (self); + } +} + +static void +search_force_reload (NautilusDirectory *directory) +{ + NautilusSearchDirectory *self; + NautilusFile *file; + + self = NAUTILUS_SEARCH_DIRECTORY (directory); + + if (!self->query) + { + return; + } + + self->search_ready_and_valid = FALSE; + + /* Remove file monitors */ + reset_file_list (self); + stop_search (self); + + file = nautilus_directory_get_corresponding_file (directory); + nautilus_file_invalidate_all_attributes (file); + nautilus_file_unref (file); +} + +static gboolean +search_are_all_files_seen (NautilusDirectory *directory) +{ + NautilusSearchDirectory *self; + + self = NAUTILUS_SEARCH_DIRECTORY (directory); + + return (!self->query || + self->search_ready_and_valid); +} + +static gboolean +search_contains_file (NautilusDirectory *directory, + NautilusFile *file) +{ + NautilusSearchDirectory *self; + + self = NAUTILUS_SEARCH_DIRECTORY (directory); + return (g_hash_table_lookup (self->files_hash, file) != NULL); +} + +static GList * +search_get_file_list (NautilusDirectory *directory) +{ + NautilusSearchDirectory *self; + + self = NAUTILUS_SEARCH_DIRECTORY (directory); + + return nautilus_file_list_copy (self->files); +} + + +static gboolean +search_is_editable (NautilusDirectory *directory) +{ + return FALSE; +} + +static gboolean +real_handles_location (GFile *location) +{ + g_autofree gchar *uri = NULL; + + uri = g_file_get_uri (location); + + return eel_uri_is_search (uri); +} + +static void +search_set_property (GObject *object, + guint property_id, + const GValue *value, + GParamSpec *pspec) +{ + NautilusSearchDirectory *self = NAUTILUS_SEARCH_DIRECTORY (object); + + switch (property_id) + { + case PROP_BASE_MODEL: + { + nautilus_search_directory_set_base_model (self, g_value_get_object (value)); + } + break; + + case PROP_QUERY: + { + nautilus_search_directory_set_query (self, g_value_get_object (value)); + } + break; + + default: + { + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec); + } + break; + } +} + +static void +search_get_property (GObject *object, + guint property_id, + GValue *value, + GParamSpec *pspec) +{ + NautilusSearchDirectory *self = NAUTILUS_SEARCH_DIRECTORY (object); + + switch (property_id) + { + case PROP_BASE_MODEL: + { + g_value_set_object (value, nautilus_search_directory_get_base_model (self)); + } + break; + + case PROP_QUERY: + { + g_value_take_object (value, nautilus_search_directory_get_query (self)); + } + break; + + default: + { + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec); + } + break; + } +} + +static void +clear_base_model (NautilusSearchDirectory *self) +{ + if (self->base_model != NULL) + { + nautilus_directory_file_monitor_remove (self->base_model, + &self->base_model); + g_clear_object (&self->base_model); + } +} + +static void +search_connect_engine (NautilusSearchDirectory *self) +{ + g_signal_connect (self->engine, "hits-added", + G_CALLBACK (search_engine_hits_added), + self); + g_signal_connect (self->engine, "error", + G_CALLBACK (search_engine_error), + self); + g_signal_connect (self->engine, "finished", + G_CALLBACK (search_engine_finished), + self); +} + +static void +search_disconnect_engine (NautilusSearchDirectory *self) +{ + g_signal_handlers_disconnect_by_func (self->engine, + search_engine_hits_added, + self); + g_signal_handlers_disconnect_by_func (self->engine, + search_engine_error, + self); + g_signal_handlers_disconnect_by_func (self->engine, + search_engine_finished, + self); +} + +static void +search_dispose (GObject *object) +{ + NautilusSearchDirectory *self; + GList *list; + + self = NAUTILUS_SEARCH_DIRECTORY (object); + + clear_base_model (self); + + /* Remove search monitors */ + if (self->monitor_list) + { + for (list = self->monitor_list; list != NULL; list = list->next) + { + search_monitor_destroy ((SearchMonitor *) list->data, self); + } + + g_list_free (self->monitor_list); + self->monitor_list = NULL; + } + + reset_file_list (self); + + if (self->callback_list) + { + /* Remove callbacks */ + g_list_foreach (self->callback_list, + (GFunc) search_callback_destroy, NULL); + g_list_free (self->callback_list); + self->callback_list = NULL; + } + + if (self->pending_callback_list) + { + g_list_foreach (self->pending_callback_list, + (GFunc) search_callback_destroy, NULL); + g_list_free (self->pending_callback_list); + self->pending_callback_list = NULL; + } + + g_clear_object (&self->query); + stop_search (self); + search_disconnect_engine (self); + + g_clear_object (&self->engine); + + G_OBJECT_CLASS (nautilus_search_directory_parent_class)->dispose (object); +} + +static void +search_finalize (GObject *object) +{ + NautilusSearchDirectory *self; + + self = NAUTILUS_SEARCH_DIRECTORY (object); + + g_hash_table_destroy (self->files_hash); + + G_OBJECT_CLASS (nautilus_search_directory_parent_class)->finalize (object); +} + +static void +nautilus_search_directory_init (NautilusSearchDirectory *self) +{ + self->query = NULL; + self->files_hash = g_hash_table_new (g_direct_hash, g_direct_equal); + + self->engine = nautilus_search_engine_new (); + search_connect_engine (self); +} + +static void +nautilus_search_directory_class_init (NautilusSearchDirectoryClass *class) +{ + NautilusDirectoryClass *directory_class = NAUTILUS_DIRECTORY_CLASS (class); + GObjectClass *oclass = G_OBJECT_CLASS (class); + + oclass->dispose = search_dispose; + oclass->finalize = search_finalize; + oclass->get_property = search_get_property; + oclass->set_property = search_set_property; + + directory_class->are_all_files_seen = search_are_all_files_seen; + directory_class->contains_file = search_contains_file; + directory_class->force_reload = search_force_reload; + directory_class->call_when_ready = search_call_when_ready; + directory_class->cancel_callback = search_cancel_callback; + + directory_class->file_monitor_add = search_monitor_add; + directory_class->file_monitor_remove = search_monitor_remove; + + directory_class->get_file_list = search_get_file_list; + directory_class->is_editable = search_is_editable; + directory_class->handles_location = real_handles_location; + + properties[PROP_BASE_MODEL] = + g_param_spec_object ("base-model", + "The base model", + "The base directory model for this directory", + NAUTILUS_TYPE_DIRECTORY, + G_PARAM_READWRITE); + properties[PROP_QUERY] = + g_param_spec_object ("query", + "The query", + "The query for this search directory", + NAUTILUS_TYPE_QUERY, + G_PARAM_READWRITE); + + g_object_class_install_properties (oclass, NUM_PROPERTIES, properties); +} + +void +nautilus_search_directory_set_base_model (NautilusSearchDirectory *self, + NautilusDirectory *base_model) +{ + if (self->base_model == base_model) + { + return; + } + + if (self->query != NULL) + { + GFile *query_location, *model_location; + gboolean is_equal; + + query_location = nautilus_query_get_location (self->query); + model_location = nautilus_directory_get_location (base_model); + + is_equal = g_file_equal (model_location, query_location); + + g_object_unref (model_location); + g_object_unref (query_location); + + if (!is_equal) + { + return; + } + } + + clear_base_model (self); + self->base_model = nautilus_directory_ref (base_model); + + if (self->base_model != NULL) + { + nautilus_directory_file_monitor_add (base_model, &self->base_model, + TRUE, NAUTILUS_FILE_ATTRIBUTE_INFO, + NULL, NULL); + } + + g_object_notify_by_pspec (G_OBJECT (self), properties[PROP_BASE_MODEL]); +} + +NautilusDirectory * +nautilus_search_directory_get_base_model (NautilusSearchDirectory *self) +{ + return self->base_model; +} + +char * +nautilus_search_directory_generate_new_uri (void) +{ + static int counter = 0; + char *uri; + + uri = g_strdup_printf (EEL_SEARCH_URI "//%d/", counter++); + + return uri; +} + +void +nautilus_search_directory_set_query (NautilusSearchDirectory *self, + NautilusQuery *query) +{ + NautilusFile *file; + NautilusQuery *old_query; + + old_query = self->query; + + if (self->query != query) + { + self->query = g_object_ref (query); + + g_clear_pointer (&self->binding, g_binding_unbind); + + if (query) + { + self->binding = g_object_bind_property (self->engine, "running", + query, "searching", + G_BINDING_DEFAULT | G_BINDING_SYNC_CREATE); + } + + g_object_notify_by_pspec (G_OBJECT (self), properties[PROP_QUERY]); + + g_clear_object (&old_query); + } + + file = nautilus_directory_get_existing_corresponding_file (NAUTILUS_DIRECTORY (self)); + if (file != NULL) + { + nautilus_search_directory_file_update_display_name (NAUTILUS_SEARCH_DIRECTORY_FILE (file)); + } + nautilus_file_unref (file); +} + +NautilusQuery * +nautilus_search_directory_get_query (NautilusSearchDirectory *self) +{ + if (self->query != NULL) + { + return g_object_ref (self->query); + } + + return NULL; +} diff --git a/src/nautilus-search-directory.h b/src/nautilus-search-directory.h new file mode 100644 index 0000000..abd5b0b --- /dev/null +++ b/src/nautilus-search-directory.h @@ -0,0 +1,43 @@ +/* + nautilus-search-directory.h: Subclass of NautilusDirectory to implement + a virtual directory consisting of the search directory and the search + icons + + Copyright (C) 2005 Novell, Inc + + This program is free software; you can redistribute it and/or + modify it under the terms of the GNU General Public License as + published by the Free Software Foundation; either version 2 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 warranty of + MERCHANTABILITY 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 . +*/ + +#pragma once + +#include "nautilus-directory.h" + +#include "nautilus-types.h" + +#define NAUTILUS_SEARCH_DIRECTORY_PROVIDER_NAME "search-directory-provider" +#define NAUTILUS_TYPE_SEARCH_DIRECTORY (nautilus_search_directory_get_type ()) + +G_DECLARE_FINAL_TYPE (NautilusSearchDirectory, nautilus_search_directory, + NAUTILUS, SEARCH_DIRECTORY, NautilusDirectory) + +char *nautilus_search_directory_generate_new_uri (void); + +NautilusQuery *nautilus_search_directory_get_query (NautilusSearchDirectory *self); +void nautilus_search_directory_set_query (NautilusSearchDirectory *self, + NautilusQuery *query); + +NautilusDirectory * + nautilus_search_directory_get_base_model (NautilusSearchDirectory *self); +void nautilus_search_directory_set_base_model (NautilusSearchDirectory *self, + NautilusDirectory *base_model); diff --git a/src/nautilus-search-engine-model.c b/src/nautilus-search-engine-model.c new file mode 100644 index 0000000..46f2a0b --- /dev/null +++ b/src/nautilus-search-engine-model.c @@ -0,0 +1,391 @@ +/* + * Copyright (C) 2005 Red Hat, Inc + * + * Nautilus is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License as + * published by the Free Software Foundation; either version 2 of the + * License, or (at your option) any later version. + * + * Nautilus is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public + * License along with this program; see the file COPYING. If not, + * see . + * + * Author: Alexander Larsson + * + */ + +#include +#include "nautilus-search-hit.h" +#include "nautilus-search-provider.h" +#include "nautilus-search-engine-model.h" +#include "nautilus-directory.h" +#include "nautilus-directory-private.h" +#include "nautilus-file.h" +#include "nautilus-ui-utilities.h" +#define DEBUG_FLAG NAUTILUS_DEBUG_SEARCH +#include "nautilus-debug.h" + +#include +#include +#include + +struct _NautilusSearchEngineModel +{ + GObject parent; + + NautilusQuery *query; + + GList *hits; + NautilusDirectory *directory; + + gboolean query_pending; + guint finished_id; +}; + +enum +{ + PROP_0, + PROP_RUNNING, + LAST_PROP +}; + +static void nautilus_search_provider_init (NautilusSearchProviderInterface *iface); + +G_DEFINE_TYPE_WITH_CODE (NautilusSearchEngineModel, + nautilus_search_engine_model, + G_TYPE_OBJECT, + G_IMPLEMENT_INTERFACE (NAUTILUS_TYPE_SEARCH_PROVIDER, + nautilus_search_provider_init)) + +static void +finalize (GObject *object) +{ + NautilusSearchEngineModel *model; + + model = NAUTILUS_SEARCH_ENGINE_MODEL (object); + + if (model->hits != NULL) + { + g_list_free_full (model->hits, g_object_unref); + model->hits = NULL; + } + + if (model->finished_id != 0) + { + g_source_remove (model->finished_id); + model->finished_id = 0; + } + + g_clear_object (&model->directory); + g_clear_object (&model->query); + + G_OBJECT_CLASS (nautilus_search_engine_model_parent_class)->finalize (object); +} + +static gboolean +search_finished (NautilusSearchEngineModel *model) +{ + model->finished_id = 0; + + if (model->hits != NULL) + { + DEBUG ("Model engine hits added"); + nautilus_search_provider_hits_added (NAUTILUS_SEARCH_PROVIDER (model), + model->hits); + g_list_free_full (model->hits, g_object_unref); + model->hits = NULL; + } + + model->query_pending = FALSE; + + g_object_notify (G_OBJECT (model), "running"); + + DEBUG ("Model engine finished"); + nautilus_search_provider_finished (NAUTILUS_SEARCH_PROVIDER (model), + NAUTILUS_SEARCH_PROVIDER_STATUS_NORMAL); + g_object_unref (model); + + return FALSE; +} + +static void +search_finished_idle (NautilusSearchEngineModel *model) +{ + if (model->finished_id != 0) + { + return; + } + + model->finished_id = g_idle_add ((GSourceFunc) search_finished, model); +} + +static void +model_directory_ready_cb (NautilusDirectory *directory, + GList *list, + gpointer user_data) +{ + NautilusSearchEngineModel *model = user_data; + g_autoptr (GPtrArray) mime_types = NULL; + gchar *uri, *display_name; + GList *files, *hits, *l; + NautilusFile *file; + gdouble match; + gboolean found; + NautilusSearchHit *hit; + GDateTime *initial_date; + GDateTime *end_date; + GPtrArray *date_range; + + files = nautilus_directory_get_file_list (directory); + mime_types = nautilus_query_get_mime_types (model->query); + hits = NULL; + + for (l = files; l != NULL; l = l->next) + { + g_autoptr (GDateTime) mtime = NULL; + g_autoptr (GDateTime) atime = NULL; + g_autoptr (GDateTime) ctime = NULL; + + file = l->data; + + display_name = nautilus_file_get_display_name (file); + match = nautilus_query_matches_string (model->query, display_name); + found = (match > -1); + + if (found && mime_types->len > 0) + { + found = FALSE; + + for (gint i = 0; i < mime_types->len; i++) + { + if (nautilus_file_is_mime_type (file, g_ptr_array_index (mime_types, i))) + { + found = TRUE; + break; + } + } + } + + mtime = g_date_time_new_from_unix_local (nautilus_file_get_mtime (file)); + atime = g_date_time_new_from_unix_local (nautilus_file_get_atime (file)); + ctime = g_date_time_new_from_unix_local (nautilus_file_get_btime (file)); + + date_range = nautilus_query_get_date_range (model->query); + if (found && date_range != NULL) + { + NautilusQuerySearchType type; + GDateTime *target_date; + + type = nautilus_query_get_search_type (model->query); + initial_date = g_ptr_array_index (date_range, 0); + end_date = g_ptr_array_index (date_range, 1); + + switch (type) + { + case NAUTILUS_QUERY_SEARCH_TYPE_LAST_ACCESS: + { + target_date = atime; + } + break; + + case NAUTILUS_QUERY_SEARCH_TYPE_LAST_MODIFIED: + { + target_date = mtime; + } + break; + + case NAUTILUS_QUERY_SEARCH_TYPE_CREATED: + { + target_date = ctime; + } + break; + + default: + { + target_date = NULL; + } + } + + found = nautilus_date_time_is_between_dates (target_date, + initial_date, + end_date); + g_ptr_array_unref (date_range); + } + + if (found) + { + uri = nautilus_file_get_uri (file); + hit = nautilus_search_hit_new (uri); + nautilus_search_hit_set_fts_rank (hit, match); + nautilus_search_hit_set_modification_time (hit, mtime); + nautilus_search_hit_set_access_time (hit, atime); + nautilus_search_hit_set_creation_time (hit, ctime); + + hits = g_list_prepend (hits, hit); + + g_free (uri); + } + + g_free (display_name); + } + + nautilus_file_list_free (files); + model->hits = hits; + + search_finished (model); +} + +static void +nautilus_search_engine_model_start (NautilusSearchProvider *provider) +{ + NautilusSearchEngineModel *model; + + model = NAUTILUS_SEARCH_ENGINE_MODEL (provider); + + if (model->query_pending) + { + return; + } + + DEBUG ("Model engine start"); + + g_object_ref (model); + model->query_pending = TRUE; + + g_object_notify (G_OBJECT (provider), "running"); + + if (model->directory == NULL) + { + search_finished_idle (model); + return; + } + + nautilus_directory_call_when_ready (model->directory, + NAUTILUS_FILE_ATTRIBUTE_INFO, + TRUE, model_directory_ready_cb, model); +} + +static void +nautilus_search_engine_model_stop (NautilusSearchProvider *provider) +{ + NautilusSearchEngineModel *model; + + model = NAUTILUS_SEARCH_ENGINE_MODEL (provider); + + if (model->query_pending) + { + DEBUG ("Model engine stop"); + + nautilus_directory_cancel_callback (model->directory, + model_directory_ready_cb, model); + search_finished_idle (model); + } + + g_clear_object (&model->directory); +} + +static void +nautilus_search_engine_model_set_query (NautilusSearchProvider *provider, + NautilusQuery *query) +{ + NautilusSearchEngineModel *model; + + model = NAUTILUS_SEARCH_ENGINE_MODEL (provider); + + g_object_ref (query); + g_clear_object (&model->query); + model->query = query; +} + +static gboolean +nautilus_search_engine_model_is_running (NautilusSearchProvider *provider) +{ + NautilusSearchEngineModel *model; + + model = NAUTILUS_SEARCH_ENGINE_MODEL (provider); + + return model->query_pending; +} + +static void +nautilus_search_provider_init (NautilusSearchProviderInterface *iface) +{ + iface->set_query = nautilus_search_engine_model_set_query; + iface->start = nautilus_search_engine_model_start; + iface->stop = nautilus_search_engine_model_stop; + iface->is_running = nautilus_search_engine_model_is_running; +} + +static void +nautilus_search_engine_model_get_property (GObject *object, + guint prop_id, + GValue *value, + GParamSpec *pspec) +{ + NautilusSearchProvider *self = NAUTILUS_SEARCH_PROVIDER (object); + + switch (prop_id) + { + case PROP_RUNNING: + { + g_value_set_boolean (value, nautilus_search_engine_model_is_running (self)); + } + break; + + default: + { + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); + } + } +} + +static void +nautilus_search_engine_model_class_init (NautilusSearchEngineModelClass *class) +{ + GObjectClass *gobject_class; + + gobject_class = G_OBJECT_CLASS (class); + gobject_class->finalize = finalize; + gobject_class->get_property = nautilus_search_engine_model_get_property; + + /** + * NautilusSearchEngine::running: + * + * Whether the search engine is running a search. + */ + g_object_class_override_property (gobject_class, PROP_RUNNING, "running"); +} + +static void +nautilus_search_engine_model_init (NautilusSearchEngineModel *engine) +{ +} + +NautilusSearchEngineModel * +nautilus_search_engine_model_new (void) +{ + NautilusSearchEngineModel *engine; + + engine = g_object_new (NAUTILUS_TYPE_SEARCH_ENGINE_MODEL, NULL); + + return engine; +} + +void +nautilus_search_engine_model_set_model (NautilusSearchEngineModel *model, + NautilusDirectory *directory) +{ + g_clear_object (&model->directory); + model->directory = nautilus_directory_ref (directory); +} + +NautilusDirectory * +nautilus_search_engine_model_get_model (NautilusSearchEngineModel *model) +{ + return model->directory; +} diff --git a/src/nautilus-search-engine-model.h b/src/nautilus-search-engine-model.h new file mode 100644 index 0000000..4babc54 --- /dev/null +++ b/src/nautilus-search-engine-model.h @@ -0,0 +1,32 @@ +/* + * Copyright (C) 2005 Red Hat, Inc + * + * Nautilus is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License as + * published by the Free Software Foundation; either version 2 of the + * License, or (at your option) any later version. + * + * Nautilus is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public + * License along with this program; see the file COPYING. If not, + * see . + * + * Author: Alexander Larsson + * + */ + +#pragma once + +#include "nautilus-directory.h" + +#define NAUTILUS_TYPE_SEARCH_ENGINE_MODEL (nautilus_search_engine_model_get_type ()) +G_DECLARE_FINAL_TYPE (NautilusSearchEngineModel, nautilus_search_engine_model, NAUTILUS, SEARCH_ENGINE_MODEL, GObject) + +NautilusSearchEngineModel* nautilus_search_engine_model_new (void); +void nautilus_search_engine_model_set_model (NautilusSearchEngineModel *model, + NautilusDirectory *directory); +NautilusDirectory * nautilus_search_engine_model_get_model (NautilusSearchEngineModel *model); \ No newline at end of file diff --git a/src/nautilus-search-engine-private.h b/src/nautilus-search-engine-private.h new file mode 100644 index 0000000..e5f989f --- /dev/null +++ b/src/nautilus-search-engine-private.h @@ -0,0 +1,31 @@ +/* + * Copyright (C) 2018 Canonical Ltd. + * + * Nautilus is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License as + * published by the Free Software Foundation; either version 2 of the + * License, or (at your option) any later version. + * + * Nautilus is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public + * License along with this program; see the file COPYING. If not, + * see . + * + * Author: Marco Trevisan + * + */ + +#pragma once + +#include "nautilus-query.h" + +typedef enum { + NAUTILUS_SEARCH_ENGINE_TYPE_NON_INDEXED, + NAUTILUS_SEARCH_ENGINE_TYPE_INDEXED, +} NautilusSearchEngineType; + +gboolean is_recursive_search (NautilusSearchEngineType engine_type, NautilusQueryRecursive recursive, GFile *location); diff --git a/src/nautilus-search-engine-recent.c b/src/nautilus-search-engine-recent.c new file mode 100644 index 0000000..a31b9d8 --- /dev/null +++ b/src/nautilus-search-engine-recent.c @@ -0,0 +1,465 @@ +/* + * Copyright (C) 2018 Canonical Ltd + * + * Nautilus is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License as + * published by the Free Software Foundation; either version 3 of the + * License, or (at your option) any later version. + * + * Nautilus is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public + * License along with this program; see the file COPYING. If not, + * see . + * + * Author: Marco Trevisan + */ + +#include +#include "nautilus-search-hit.h" +#include "nautilus-search-provider.h" +#include "nautilus-search-engine-recent.h" +#include "nautilus-search-engine-private.h" +#include "nautilus-ui-utilities.h" +#define DEBUG_FLAG NAUTILUS_DEBUG_SEARCH +#include "nautilus-debug.h" + +#include +#include +#include + +#define FILE_ATTRIBS G_FILE_ATTRIBUTE_STANDARD_IS_HIDDEN "," \ + G_FILE_ATTRIBUTE_STANDARD_IS_BACKUP "," \ + G_FILE_ATTRIBUTE_ACCESS_CAN_READ "," \ + G_FILE_ATTRIBUTE_TIME_MODIFIED "," \ + G_FILE_ATTRIBUTE_TIME_ACCESS "," \ + G_FILE_ATTRIBUTE_TIME_CREATED + +struct _NautilusSearchEngineRecent +{ + GObject parent_instance; + + NautilusQuery *query; + gboolean running; + GCancellable *cancellable; + GtkRecentManager *recent_manager; + guint add_hits_idle_id; +}; + +static void nautilus_search_provider_init (NautilusSearchProviderInterface *iface); + +G_DEFINE_TYPE_WITH_CODE (NautilusSearchEngineRecent, + nautilus_search_engine_recent, + G_TYPE_OBJECT, + G_IMPLEMENT_INTERFACE (NAUTILUS_TYPE_SEARCH_PROVIDER, + nautilus_search_provider_init)) + +enum +{ + PROP_0, + PROP_RUNNING, + LAST_PROP +}; + + +NautilusSearchEngineRecent * +nautilus_search_engine_recent_new (void) +{ + return g_object_new (NAUTILUS_TYPE_SEARCH_ENGINE_RECENT, NULL); +} + +static void +nautilus_search_engine_recent_finalize (GObject *object) +{ + NautilusSearchEngineRecent *self = NAUTILUS_SEARCH_ENGINE_RECENT (object); + + g_clear_handle_id (&self->add_hits_idle_id, g_source_remove); + g_cancellable_cancel (self->cancellable); + + g_clear_object (&self->query); + g_clear_object (&self->cancellable); + + G_OBJECT_CLASS (nautilus_search_engine_recent_parent_class)->finalize (object); +} + +typedef struct +{ + NautilusSearchEngineRecent *recent; + GList *hits; +} SearchHitsData; + + +static gboolean +search_thread_add_hits_idle (gpointer user_data) +{ + SearchHitsData *search_hits = user_data; + g_autoptr (NautilusSearchEngineRecent) self = search_hits->recent; + NautilusSearchProvider *provider = NAUTILUS_SEARCH_PROVIDER (self); + + self->add_hits_idle_id = 0; + + if (!g_cancellable_is_cancelled (self->cancellable)) + { + nautilus_search_provider_hits_added (provider, search_hits->hits); + DEBUG ("Recent engine add hits"); + } + + self->running = FALSE; + g_list_free_full (search_hits->hits, g_object_unref); + g_clear_object (&self->cancellable); + g_free (search_hits); + + nautilus_search_provider_finished (provider, + NAUTILUS_SEARCH_PROVIDER_STATUS_NORMAL); + g_object_notify (G_OBJECT (provider), "running"); + + return FALSE; +} + +static void +search_add_hits_idle (NautilusSearchEngineRecent *self, + GList *hits) +{ + SearchHitsData *search_hits; + + if (self->add_hits_idle_id != 0) + { + g_list_free_full (hits, g_object_unref); + return; + } + + search_hits = g_new0 (SearchHitsData, 1); + search_hits->recent = g_object_ref (self); + search_hits->hits = hits; + + self->add_hits_idle_id = g_idle_add (search_thread_add_hits_idle, search_hits); +} + +static gboolean +is_file_valid_recursive (NautilusSearchEngineRecent *self, + GFile *file, + GDateTime **mtime, + GDateTime **atime, + GDateTime **ctime, + GError **error) +{ + g_autoptr (GFileInfo) file_info = NULL; + + file_info = g_file_query_info (file, FILE_ATTRIBS, + G_FILE_QUERY_INFO_NONE, + self->cancellable, error); + if (*error != NULL) + { + return FALSE; + } + + if (!g_file_info_get_attribute_boolean (file_info, + G_FILE_ATTRIBUTE_ACCESS_CAN_READ)) + { + return FALSE; + } + + if (mtime && atime && ctime) + { + *mtime = g_file_info_get_modification_date_time (file_info); + *atime = g_file_info_get_access_date_time (file_info); + *ctime = g_file_info_get_creation_date_time (file_info); + } + + if (!nautilus_query_get_show_hidden_files (self->query)) + { + if (!g_file_info_get_is_hidden (file_info) && + !g_file_info_get_is_backup (file_info)) + { + g_autoptr (GFile) parent = g_file_get_parent (file); + + if (parent) + { + return is_file_valid_recursive (self, parent, + NULL, NULL, NULL, + error); + } + } + else + { + return FALSE; + } + } + + return TRUE; +} + +static gpointer +recent_thread_func (gpointer user_data) +{ + g_autoptr (NautilusSearchEngineRecent) self = NAUTILUS_SEARCH_ENGINE_RECENT (user_data); + g_autoptr (GPtrArray) date_range = NULL; + g_autoptr (GFile) query_location = NULL; + g_autoptr (GPtrArray) mime_types = NULL; + GList *recent_items; + GList *hits; + GList *l; + + g_return_val_if_fail (self->query, NULL); + + hits = NULL; + recent_items = gtk_recent_manager_get_items (self->recent_manager); + mime_types = nautilus_query_get_mime_types (self->query); + date_range = nautilus_query_get_date_range (self->query); + query_location = nautilus_query_get_location (self->query); + + for (l = recent_items; l != NULL; l = l->next) + { + GtkRecentInfo *info = l->data; + g_autoptr (GFile) file = NULL; + const gchar *uri; + const gchar *name; + gdouble rank; + + uri = gtk_recent_info_get_uri (info); + file = g_file_new_for_uri (uri); + + if (!g_file_has_prefix (file, query_location)) + { + continue; + } + + if (g_cancellable_is_cancelled (self->cancellable)) + { + break; + } + + name = gtk_recent_info_get_display_name (info); + rank = nautilus_query_matches_string (self->query, name); + + if (rank <= 0) + { + g_autofree char *short_name = gtk_recent_info_get_short_name (info); + rank = nautilus_query_matches_string (self->query, short_name); + } + + if (rank > 0) + { + NautilusSearchHit *hit; + g_autoptr (GDateTime) mtime = NULL; + g_autoptr (GDateTime) atime = NULL; + g_autoptr (GDateTime) ctime = NULL; + g_autoptr (GError) error = NULL; + + if (!gtk_recent_info_is_local (info)) + { + continue; + } + + if (!is_file_valid_recursive (self, file, &mtime, &atime, &ctime, &error)) + { + if (g_error_matches (error, G_IO_ERROR, G_IO_ERROR_CANCELLED)) + { + break; + } + + if (error != NULL && + !g_error_matches (error, G_IO_ERROR, G_IO_ERROR_EXISTS)) + { + g_debug ("Impossible to read recent file info: %s", + error->message); + } + + continue; + } + + if (mime_types->len > 0) + { + const gchar *mime_type = gtk_recent_info_get_mime_type (info); + gboolean found = FALSE; + + for (gint i = 0; mime_type != NULL && i < mime_types->len; i++) + { + if (g_content_type_is_a (mime_type, g_ptr_array_index (mime_types, i))) + { + found = TRUE; + break; + } + } + + if (!found) + { + continue; + } + } + + if (date_range != NULL) + { + NautilusQuerySearchType type; + GDateTime *target_date; + GDateTime *initial_date; + GDateTime *end_date; + + initial_date = g_ptr_array_index (date_range, 0); + end_date = g_ptr_array_index (date_range, 1); + type = nautilus_query_get_search_type (self->query); + + switch (type) + { + case NAUTILUS_QUERY_SEARCH_TYPE_LAST_ACCESS: + { + target_date = atime; + } + break; + + case NAUTILUS_QUERY_SEARCH_TYPE_LAST_MODIFIED: + { + target_date = mtime; + } + break; + + case NAUTILUS_QUERY_SEARCH_TYPE_CREATED: + { + target_date = ctime; + } + break; + + default: + { + target_date = NULL; + } + } + + if (!nautilus_date_time_is_between_dates (target_date, + initial_date, + end_date)) + { + continue; + } + } + + hit = nautilus_search_hit_new (uri); + nautilus_search_hit_set_fts_rank (hit, rank); + nautilus_search_hit_set_modification_time (hit, mtime); + nautilus_search_hit_set_access_time (hit, atime); + nautilus_search_hit_set_creation_time (hit, ctime); + + hits = g_list_prepend (hits, hit); + } + } + + search_add_hits_idle (self, hits); + + g_list_free_full (recent_items, (GDestroyNotify) gtk_recent_info_unref); + + return NULL; +} + +static void +nautilus_search_engine_recent_start (NautilusSearchProvider *provider) +{ + NautilusSearchEngineRecent *self = NAUTILUS_SEARCH_ENGINE_RECENT (provider); + g_autoptr (GFile) location = NULL; + g_autoptr (GThread) thread = NULL; + + g_return_if_fail (self->query); + g_return_if_fail (self->cancellable == NULL); + + location = nautilus_query_get_location (self->query); + + if (!is_recursive_search (NAUTILUS_SEARCH_ENGINE_TYPE_INDEXED, + nautilus_query_get_recursive (self->query), + location)) + { + search_add_hits_idle (self, NULL); + return; + } + + self->running = TRUE; + self->cancellable = g_cancellable_new (); + thread = g_thread_new ("nautilus-search-recent", recent_thread_func, + g_object_ref (self)); + + g_object_notify (G_OBJECT (provider), "running"); +} + +static void +nautilus_search_engine_recent_stop (NautilusSearchProvider *provider) +{ + NautilusSearchEngineRecent *self = NAUTILUS_SEARCH_ENGINE_RECENT (provider); + + if (self->cancellable != NULL) + { + DEBUG ("Recent engine stop"); + g_cancellable_cancel (self->cancellable); + } + + self->running = FALSE; +} + +static void +nautilus_search_engine_recent_set_query (NautilusSearchProvider *provider, + NautilusQuery *query) +{ + NautilusSearchEngineRecent *self = NAUTILUS_SEARCH_ENGINE_RECENT (provider); + + g_clear_object (&self->query); + self->query = g_object_ref (query); +} + +static gboolean +nautilus_search_engine_recent_is_running (NautilusSearchProvider *provider) +{ + NautilusSearchEngineRecent *self = NAUTILUS_SEARCH_ENGINE_RECENT (provider); + + return self->running; +} + +static void +nautilus_search_engine_recent_get_property (GObject *object, + guint prop_id, + GValue *value, + GParamSpec *pspec) +{ + NautilusSearchProvider *provider = NAUTILUS_SEARCH_PROVIDER (object); + + switch (prop_id) + { + case PROP_RUNNING: + { + gboolean running; + running = nautilus_search_engine_recent_is_running (provider); + g_value_set_boolean (value, running); + } + break; + + default: + { + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); + } + } +} + +static void +nautilus_search_provider_init (NautilusSearchProviderInterface *iface) +{ + iface->set_query = nautilus_search_engine_recent_set_query; + iface->start = nautilus_search_engine_recent_start; + iface->stop = nautilus_search_engine_recent_stop; + iface->is_running = nautilus_search_engine_recent_is_running; +} + +static void +nautilus_search_engine_recent_class_init (NautilusSearchEngineRecentClass *klass) +{ + GObjectClass *object_class = G_OBJECT_CLASS (klass); + + object_class->finalize = nautilus_search_engine_recent_finalize; + object_class->get_property = nautilus_search_engine_recent_get_property; + + g_object_class_override_property (object_class, PROP_RUNNING, "running"); +} + +static void +nautilus_search_engine_recent_init (NautilusSearchEngineRecent *self) +{ + self->recent_manager = gtk_recent_manager_get_default (); +} diff --git a/src/nautilus-search-engine-recent.h b/src/nautilus-search-engine-recent.h new file mode 100644 index 0000000..a690de4 --- /dev/null +++ b/src/nautilus-search-engine-recent.h @@ -0,0 +1,33 @@ +/* + * Copyright (C) 2018 Canonical Ltd + * + * Nautilus is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License as + * published by the Free Software Foundation; either version 3 of the + * License, or (at your option) any later version. + * + * Nautilus is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public + * License along with this program; see the file COPYING. If not, + * see . + * + * Author: Marco Trevisan + */ + +#pragma once + +#include + +G_BEGIN_DECLS + +#define NAUTILUS_TYPE_SEARCH_ENGINE_RECENT (nautilus_search_engine_recent_get_type ()) + +G_DECLARE_FINAL_TYPE (NautilusSearchEngineRecent, nautilus_search_engine_recent, NAUTILUS, SEARCH_ENGINE_RECENT, GObject); + +NautilusSearchEngineRecent *nautilus_search_engine_recent_new (void); + +G_END_DECLS diff --git a/src/nautilus-search-engine-simple.c b/src/nautilus-search-engine-simple.c new file mode 100644 index 0000000..4fa1a07 --- /dev/null +++ b/src/nautilus-search-engine-simple.c @@ -0,0 +1,614 @@ +/* + * Copyright (C) 2005 Red Hat, Inc + * + * Nautilus is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License as + * published by the Free Software Foundation; either version 2 of the + * License, or (at your option) any later version. + * + * Nautilus is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public + * License along with this program; see the file COPYING. If not, + * see . + * + * Author: Alexander Larsson + * + */ + +#include +#include "nautilus-search-engine-simple.h" + +#include "nautilus-search-engine-private.h" +#include "nautilus-search-hit.h" +#include "nautilus-search-provider.h" +#include "nautilus-ui-utilities.h" +#define DEBUG_FLAG NAUTILUS_DEBUG_SEARCH +#include "nautilus-debug.h" + +#include +#include +#include + +#define BATCH_SIZE 500 + +enum +{ + PROP_0, + PROP_RUNNING, + NUM_PROPERTIES +}; + +typedef struct +{ + NautilusSearchEngineSimple *engine; + GCancellable *cancellable; + + GPtrArray *mime_types; + GList *found_list; + + GQueue *directories; /* GFiles */ + + GHashTable *visited; + + gint n_processed_files; + GList *hits; + + NautilusQuery *query; + + gint processing_id; + GMutex idle_mutex; + /* The following data can be accessed from different threads + * and needs to lock the mutex + */ + GQueue *idle_queue; + gboolean finished; +} SearchThreadData; + + +struct _NautilusSearchEngineSimple +{ + GObject parent_instance; + NautilusQuery *query; + + SearchThreadData *active_search; +}; + +static void nautilus_search_provider_init (NautilusSearchProviderInterface *iface); + +G_DEFINE_TYPE_WITH_CODE (NautilusSearchEngineSimple, + nautilus_search_engine_simple, + G_TYPE_OBJECT, + G_IMPLEMENT_INTERFACE (NAUTILUS_TYPE_SEARCH_PROVIDER, + nautilus_search_provider_init)) + +static void +finalize (GObject *object) +{ + NautilusSearchEngineSimple *simple = NAUTILUS_SEARCH_ENGINE_SIMPLE (object); + g_clear_object (&simple->query); + + G_OBJECT_CLASS (nautilus_search_engine_simple_parent_class)->finalize (object); +} + +static SearchThreadData * +search_thread_data_new (NautilusSearchEngineSimple *engine, + NautilusQuery *query) +{ + SearchThreadData *data; + GFile *location; + + data = g_new0 (SearchThreadData, 1); + + data->engine = g_object_ref (engine); + data->directories = g_queue_new (); + data->visited = g_hash_table_new_full (g_str_hash, g_str_equal, g_free, NULL); + data->query = g_object_ref (query); + + location = nautilus_query_get_location (query); + + g_queue_push_tail (data->directories, location); + data->mime_types = nautilus_query_get_mime_types (query); + + data->cancellable = g_cancellable_new (); + + g_mutex_init (&data->idle_mutex); + data->idle_queue = g_queue_new (); + + return data; +} + +static void +search_thread_data_free (SearchThreadData *data) +{ + GList *hits; + + g_queue_foreach (data->directories, + (GFunc) g_object_unref, NULL); + g_queue_free (data->directories); + g_hash_table_destroy (data->visited); + g_object_unref (data->cancellable); + g_object_unref (data->query); + g_clear_pointer (&data->mime_types, g_ptr_array_unref); + g_list_free_full (data->hits, g_object_unref); + g_object_unref (data->engine); + g_mutex_clear (&data->idle_mutex); + + while ((hits = g_queue_pop_head (data->idle_queue))) + { + g_list_free_full (hits, g_object_unref); + } + g_queue_free (data->idle_queue); + + g_free (data); +} + +static gboolean +search_thread_done (SearchThreadData *data) +{ + NautilusSearchEngineSimple *engine = data->engine; + + if (g_cancellable_is_cancelled (data->cancellable)) + { + DEBUG ("Simple engine finished and cancelled"); + } + else + { + DEBUG ("Simple engine finished"); + } + engine->active_search = NULL; + nautilus_search_provider_finished (NAUTILUS_SEARCH_PROVIDER (engine), + NAUTILUS_SEARCH_PROVIDER_STATUS_NORMAL); + + g_object_notify (G_OBJECT (engine), "running"); + + search_thread_data_free (data); + + return G_SOURCE_REMOVE; +} + +static void +search_thread_process_hits_idle (SearchThreadData *data, + GList *hits) +{ + if (!g_cancellable_is_cancelled (data->cancellable)) + { + DEBUG ("Simple engine add hits"); + nautilus_search_provider_hits_added (NAUTILUS_SEARCH_PROVIDER (data->engine), + hits); + } +} + +static gboolean +search_thread_process_idle (gpointer user_data) +{ + SearchThreadData *thread_data; + GList *hits; + + thread_data = user_data; + + g_mutex_lock (&thread_data->idle_mutex); + hits = g_queue_pop_head (thread_data->idle_queue); + /* Even if the cancellable is cancelled, we need to make sure the search + * thread has aknowledge it, and therefore not using the thread data after + * freeing it. The search thread will mark as finished whenever the search + * is finished or cancelled. + * Nonetheless, we should stop yielding results if the search was cancelled + */ + if (thread_data->finished) + { + if (hits == NULL || g_cancellable_is_cancelled (thread_data->cancellable)) + { + g_mutex_unlock (&thread_data->idle_mutex); + + if (hits) + { + g_list_free_full (hits, g_object_unref); + } + search_thread_done (thread_data); + + return G_SOURCE_REMOVE; + } + } + + g_mutex_unlock (&thread_data->idle_mutex); + + if (hits) + { + search_thread_process_hits_idle (thread_data, hits); + g_list_free_full (hits, g_object_unref); + } + + return G_SOURCE_CONTINUE; +} + +static void +finish_search_thread (SearchThreadData *thread_data) +{ + g_mutex_lock (&thread_data->idle_mutex); + thread_data->finished = TRUE; + g_mutex_unlock (&thread_data->idle_mutex); + + /* If no results were processed, direclty finish the search, in the main + * thread. + */ + if (thread_data->processing_id == 0) + { + g_idle_add (G_SOURCE_FUNC (search_thread_done), thread_data); + } +} + +static void +process_batch_in_idle (SearchThreadData *thread_data, + GList *hits) +{ + g_return_if_fail (hits != NULL); + + g_mutex_lock (&thread_data->idle_mutex); + g_queue_push_tail (thread_data->idle_queue, hits); + g_mutex_unlock (&thread_data->idle_mutex); + + if (thread_data->processing_id == 0) + { + thread_data->processing_id = g_idle_add (search_thread_process_idle, thread_data); + } +} + +static void +send_batch_in_idle (SearchThreadData *thread_data) +{ + thread_data->n_processed_files = 0; + + if (thread_data->hits) + { + process_batch_in_idle (thread_data, thread_data->hits); + } + thread_data->hits = NULL; +} + +#define STD_ATTRIBUTES \ + G_FILE_ATTRIBUTE_STANDARD_NAME "," \ + G_FILE_ATTRIBUTE_STANDARD_DISPLAY_NAME "," \ + G_FILE_ATTRIBUTE_STANDARD_IS_BACKUP "," \ + G_FILE_ATTRIBUTE_STANDARD_IS_HIDDEN "," \ + G_FILE_ATTRIBUTE_STANDARD_TYPE "," \ + G_FILE_ATTRIBUTE_TIME_MODIFIED "," \ + G_FILE_ATTRIBUTE_TIME_ACCESS "," \ + G_FILE_ATTRIBUTE_TIME_CREATED "," \ + G_FILE_ATTRIBUTE_ID_FILE + +static void +visit_directory (GFile *dir, + SearchThreadData *data) +{ + g_autoptr (GPtrArray) date_range = NULL; + NautilusQuerySearchType type; + NautilusQueryRecursive recursive; + GFileEnumerator *enumerator; + GFileInfo *info; + GFile *child; + const char *mime_type, *display_name; + gdouble match; + gboolean is_hidden, found; + const char *id; + gboolean visited; + GDateTime *initial_date; + GDateTime *end_date; + gchar *uri; + + enumerator = g_file_enumerate_children (dir, + data->mime_types->len > 0 ? + STD_ATTRIBUTES "," + G_FILE_ATTRIBUTE_STANDARD_CONTENT_TYPE + : + STD_ATTRIBUTES + , + G_FILE_QUERY_INFO_NOFOLLOW_SYMLINKS, + data->cancellable, NULL); + + if (enumerator == NULL) + { + return; + } + + type = nautilus_query_get_search_type (data->query); + recursive = nautilus_query_get_recursive (data->query); + date_range = nautilus_query_get_date_range (data->query); + + while ((info = g_file_enumerator_next_file (enumerator, data->cancellable, NULL)) != NULL) + { + g_autoptr (GDateTime) mtime = NULL; + g_autoptr (GDateTime) atime = NULL; + g_autoptr (GDateTime) ctime = NULL; + + display_name = g_file_info_get_display_name (info); + if (display_name == NULL) + { + goto next; + } + + is_hidden = g_file_info_get_is_hidden (info) || g_file_info_get_is_backup (info); + if (is_hidden && !nautilus_query_get_show_hidden_files (data->query)) + { + goto next; + } + + child = g_file_get_child (dir, g_file_info_get_name (info)); + match = nautilus_query_matches_string (data->query, display_name); + found = (match > -1); + + if (found && data->mime_types->len > 0) + { + mime_type = g_file_info_get_content_type (info); + found = FALSE; + + for (gint i = 0; i < data->mime_types->len; i++) + { + if (g_content_type_is_a (mime_type, g_ptr_array_index (data->mime_types, i))) + { + found = TRUE; + break; + } + } + } + + mtime = g_file_info_get_modification_date_time (info); + atime = g_file_info_get_access_date_time (info); + ctime = g_file_info_get_creation_date_time (info); + + if (found && date_range != NULL) + { + GDateTime *target_date; + + initial_date = g_ptr_array_index (date_range, 0); + end_date = g_ptr_array_index (date_range, 1); + + switch (type) + { + case NAUTILUS_QUERY_SEARCH_TYPE_LAST_ACCESS: + { + target_date = atime; + } + break; + + case NAUTILUS_QUERY_SEARCH_TYPE_LAST_MODIFIED: + { + target_date = mtime; + } + break; + + case NAUTILUS_QUERY_SEARCH_TYPE_CREATED: + { + target_date = ctime; + } + break; + + default: + { + target_date = NULL; + } + } + + found = nautilus_date_time_is_between_dates (target_date, + initial_date, + end_date); + } + + if (found) + { + NautilusSearchHit *hit; + + uri = g_file_get_uri (child); + hit = nautilus_search_hit_new (uri); + g_free (uri); + nautilus_search_hit_set_fts_rank (hit, match); + nautilus_search_hit_set_modification_time (hit, mtime); + nautilus_search_hit_set_access_time (hit, atime); + nautilus_search_hit_set_creation_time (hit, ctime); + + data->hits = g_list_prepend (data->hits, hit); + } + + data->n_processed_files++; + if (data->n_processed_files > BATCH_SIZE) + { + send_batch_in_idle (data); + } + + if (recursive != NAUTILUS_QUERY_RECURSIVE_NEVER && + g_file_info_get_file_type (info) == G_FILE_TYPE_DIRECTORY && + is_recursive_search (NAUTILUS_SEARCH_ENGINE_TYPE_NON_INDEXED, + recursive, child)) + { + id = g_file_info_get_attribute_string (info, G_FILE_ATTRIBUTE_ID_FILE); + visited = FALSE; + if (id) + { + if (g_hash_table_lookup_extended (data->visited, + id, NULL, NULL)) + { + visited = TRUE; + } + else + { + g_hash_table_insert (data->visited, g_strdup (id), NULL); + } + } + + if (!visited) + { + g_queue_push_tail (data->directories, g_object_ref (child)); + } + } + + g_object_unref (child); +next: + g_object_unref (info); + } + + g_object_unref (enumerator); +} + + +static gpointer +search_thread_func (gpointer user_data) +{ + SearchThreadData *data; + GFile *dir; + GFileInfo *info; + const char *id; + + data = user_data; + + /* Insert id for toplevel directory into visited */ + dir = g_queue_peek_head (data->directories); + info = g_file_query_info (dir, G_FILE_ATTRIBUTE_ID_FILE, 0, data->cancellable, NULL); + if (info) + { + id = g_file_info_get_attribute_string (info, G_FILE_ATTRIBUTE_ID_FILE); + if (id) + { + g_hash_table_insert (data->visited, g_strdup (id), NULL); + } + g_object_unref (info); + } + + while (!g_cancellable_is_cancelled (data->cancellable) && + (dir = g_queue_pop_head (data->directories)) != NULL) + { + visit_directory (dir, data); + g_object_unref (dir); + } + + if (!g_cancellable_is_cancelled (data->cancellable)) + { + send_batch_in_idle (data); + } + + finish_search_thread (data); + + return NULL; +} + +static void +nautilus_search_engine_simple_start (NautilusSearchProvider *provider) +{ + NautilusSearchEngineSimple *simple; + SearchThreadData *data; + GThread *thread; + + simple = NAUTILUS_SEARCH_ENGINE_SIMPLE (provider); + + if (simple->active_search != NULL) + { + return; + } + + DEBUG ("Simple engine start"); + + data = search_thread_data_new (simple, simple->query); + + thread = g_thread_new ("nautilus-search-simple", search_thread_func, data); + simple->active_search = data; + + g_object_notify (G_OBJECT (provider), "running"); + + g_thread_unref (thread); +} + +static void +nautilus_search_engine_simple_stop (NautilusSearchProvider *provider) +{ + NautilusSearchEngineSimple *simple = NAUTILUS_SEARCH_ENGINE_SIMPLE (provider); + + if (simple->active_search != NULL) + { + DEBUG ("Simple engine stop"); + g_cancellable_cancel (simple->active_search->cancellable); + } +} + +static void +nautilus_search_engine_simple_set_query (NautilusSearchProvider *provider, + NautilusQuery *query) +{ + NautilusSearchEngineSimple *simple = NAUTILUS_SEARCH_ENGINE_SIMPLE (provider); + + g_clear_object (&simple->query); + + simple->query = g_object_ref (query); +} + +static gboolean +nautilus_search_engine_simple_is_running (NautilusSearchProvider *provider) +{ + NautilusSearchEngineSimple *simple; + + simple = NAUTILUS_SEARCH_ENGINE_SIMPLE (provider); + + return simple->active_search != NULL; +} + +static void +nautilus_search_engine_simple_get_property (GObject *object, + guint arg_id, + GValue *value, + GParamSpec *pspec) +{ + NautilusSearchEngineSimple *engine = NAUTILUS_SEARCH_ENGINE_SIMPLE (object); + + switch (arg_id) + { + case PROP_RUNNING: + { + g_value_set_boolean (value, nautilus_search_engine_simple_is_running (NAUTILUS_SEARCH_PROVIDER (engine))); + } + break; + } +} + +static void +nautilus_search_provider_init (NautilusSearchProviderInterface *iface) +{ + iface->set_query = nautilus_search_engine_simple_set_query; + iface->start = nautilus_search_engine_simple_start; + iface->stop = nautilus_search_engine_simple_stop; + iface->is_running = nautilus_search_engine_simple_is_running; +} + +static void +nautilus_search_engine_simple_class_init (NautilusSearchEngineSimpleClass *class) +{ + GObjectClass *gobject_class; + + gobject_class = G_OBJECT_CLASS (class); + gobject_class->finalize = finalize; + gobject_class->get_property = nautilus_search_engine_simple_get_property; + + /** + * NautilusSearchEngine::running: + * + * Whether the search engine is running a search. + */ + g_object_class_override_property (gobject_class, PROP_RUNNING, "running"); +} + +static void +nautilus_search_engine_simple_init (NautilusSearchEngineSimple *engine) +{ + engine->query = NULL; + engine->active_search = NULL; +} + +NautilusSearchEngineSimple * +nautilus_search_engine_simple_new (void) +{ + NautilusSearchEngineSimple *engine; + + engine = g_object_new (NAUTILUS_TYPE_SEARCH_ENGINE_SIMPLE, NULL); + + return engine; +} diff --git a/src/nautilus-search-engine-simple.h b/src/nautilus-search-engine-simple.h new file mode 100644 index 0000000..ff3c401 --- /dev/null +++ b/src/nautilus-search-engine-simple.h @@ -0,0 +1,34 @@ +/* + * Copyright (C) 2005 Red Hat, Inc + * + * Nautilus is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License as + * published by the Free Software Foundation; either version 2 of the + * License, or (at your option) any later version. + * + * Nautilus is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public + * License along with this program; see the file COPYING. If not, + * see . + * + * Author: Alexander Larsson + * + */ + +#include + +#pragma once + +G_BEGIN_DECLS + +#define NAUTILUS_TYPE_SEARCH_ENGINE_SIMPLE (nautilus_search_engine_simple_get_type ()) + +G_DECLARE_FINAL_TYPE (NautilusSearchEngineSimple, nautilus_search_engine_simple, NAUTILUS, SEARCH_ENGINE_SIMPLE, GObject); + +NautilusSearchEngineSimple* nautilus_search_engine_simple_new (void); + +G_END_DECLS diff --git a/src/nautilus-search-engine-tracker.c b/src/nautilus-search-engine-tracker.c new file mode 100644 index 0000000..b4ae92a --- /dev/null +++ b/src/nautilus-search-engine-tracker.c @@ -0,0 +1,627 @@ +/* + * Copyright (C) 2005 Mr Jamie McCracken + * + * Nautilus is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License as + * published by the Free Software Foundation; either version 2 of the + * License, or (at your option) any later version. + * + * Nautilus is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public + * License along with this program; see the file COPYING. If not, + * see . + * + * Author: Jamie McCracken + * + */ + +#include +#include "nautilus-search-engine-tracker.h" + +#include "nautilus-search-engine-private.h" +#include "nautilus-search-hit.h" +#include "nautilus-search-provider.h" +#include "nautilus-tracker-utilities.h" +#define DEBUG_FLAG NAUTILUS_DEBUG_SEARCH +#include "nautilus-debug.h" + +#include +#include +#include + +struct _NautilusSearchEngineTracker +{ + GObject parent_instance; + + TrackerSparqlConnection *connection; + NautilusQuery *query; + + gboolean query_pending; + GQueue *hits_pending; + + gboolean recursive; + gboolean fts_enabled; + + GCancellable *cancellable; +}; + +enum +{ + PROP_0, + PROP_RUNNING, + LAST_PROP +}; + +static void nautilus_search_provider_init (NautilusSearchProviderInterface *iface); + +G_DEFINE_TYPE_WITH_CODE (NautilusSearchEngineTracker, + nautilus_search_engine_tracker, + G_TYPE_OBJECT, + G_IMPLEMENT_INTERFACE (NAUTILUS_TYPE_SEARCH_PROVIDER, + nautilus_search_provider_init)) + +static void +finalize (GObject *object) +{ + NautilusSearchEngineTracker *tracker; + + tracker = NAUTILUS_SEARCH_ENGINE_TRACKER (object); + + if (tracker->cancellable) + { + g_cancellable_cancel (tracker->cancellable); + g_clear_object (&tracker->cancellable); + } + + g_clear_object (&tracker->query); + g_queue_free_full (tracker->hits_pending, g_object_unref); + /* This is a singleton, no need to unref. */ + tracker->connection = NULL; + + G_OBJECT_CLASS (nautilus_search_engine_tracker_parent_class)->finalize (object); +} + +#define BATCH_SIZE 100 + +static void +check_pending_hits (NautilusSearchEngineTracker *tracker, + gboolean force_send) +{ + GList *hits = NULL; + NautilusSearchHit *hit; + + DEBUG ("Tracker engine add hits"); + + if (!force_send && + g_queue_get_length (tracker->hits_pending) < BATCH_SIZE) + { + return; + } + + while ((hit = g_queue_pop_head (tracker->hits_pending))) + { + hits = g_list_prepend (hits, hit); + } + + nautilus_search_provider_hits_added (NAUTILUS_SEARCH_PROVIDER (tracker), hits); + g_list_free_full (hits, g_object_unref); +} + +static void +search_finished (NautilusSearchEngineTracker *tracker, + GError *error) +{ + DEBUG ("Tracker engine finished"); + + if (error == NULL) + { + check_pending_hits (tracker, TRUE); + } + else + { + g_queue_foreach (tracker->hits_pending, (GFunc) g_object_unref, NULL); + g_queue_clear (tracker->hits_pending); + } + + tracker->query_pending = FALSE; + + g_object_notify (G_OBJECT (tracker), "running"); + + if (error && !g_error_matches (error, G_IO_ERROR, G_IO_ERROR_CANCELLED)) + { + DEBUG ("Tracker engine error %s", error->message); + nautilus_search_provider_error (NAUTILUS_SEARCH_PROVIDER (tracker), error->message); + } + else + { + nautilus_search_provider_finished (NAUTILUS_SEARCH_PROVIDER (tracker), + NAUTILUS_SEARCH_PROVIDER_STATUS_NORMAL); + if (g_error_matches (error, G_IO_ERROR, G_IO_ERROR_CANCELLED)) + { + DEBUG ("Tracker engine finished and cancelled"); + } + else + { + DEBUG ("Tracker engine finished correctly"); + } + } + + g_object_unref (tracker); +} + +static void cursor_callback (GObject *object, + GAsyncResult *result, + gpointer user_data); + +static void +cursor_next (NautilusSearchEngineTracker *tracker, + TrackerSparqlCursor *cursor) +{ + tracker_sparql_cursor_next_async (cursor, + tracker->cancellable, + cursor_callback, + tracker); +} + +static void +cursor_callback (GObject *object, + GAsyncResult *result, + gpointer user_data) +{ + NautilusSearchEngineTracker *tracker; + GError *error = NULL; + TrackerSparqlCursor *cursor; + NautilusSearchHit *hit; + const char *uri; + const char *mtime_str; + const char *atime_str; + const char *ctime_str; + const gchar *snippet; + GTimeVal tv; + gdouble rank, match; + gboolean success; + gchar *basename; + + tracker = NAUTILUS_SEARCH_ENGINE_TRACKER (user_data); + + cursor = TRACKER_SPARQL_CURSOR (object); + success = tracker_sparql_cursor_next_finish (cursor, result, &error); + + if (!success) + { + search_finished (tracker, error); + + g_clear_error (&error); + g_clear_object (&cursor); + + return; + } + + /* We iterate result by result, not n at a time. */ + uri = tracker_sparql_cursor_get_string (cursor, 0, NULL); + rank = tracker_sparql_cursor_get_double (cursor, 1); + mtime_str = tracker_sparql_cursor_get_string (cursor, 2, NULL); + ctime_str = tracker_sparql_cursor_get_string (cursor, 3, NULL); + atime_str = tracker_sparql_cursor_get_string (cursor, 4, NULL); + basename = g_path_get_basename (uri); + + hit = nautilus_search_hit_new (uri); + match = nautilus_query_matches_string (tracker->query, basename); + nautilus_search_hit_set_fts_rank (hit, rank + match); + g_free (basename); + + if (tracker->fts_enabled) + { + snippet = tracker_sparql_cursor_get_string (cursor, 5, NULL); + if (snippet != NULL) + { + g_autofree gchar *escaped = NULL; + g_autoptr (GString) buffer = NULL; + /* Escape for markup, before adding our own markup. */ + escaped = g_markup_escape_text (snippet, -1); + buffer = g_string_new (escaped); + g_string_replace (buffer, "_NAUTILUS_SNIPPET_DELIM_START_", "", 0); + g_string_replace (buffer, "_NAUTILUS_SNIPPET_DELIM_END_", "", 0); + + nautilus_search_hit_set_fts_snippet (hit, buffer->str); + } + } + + if (g_time_val_from_iso8601 (mtime_str, &tv)) + { + GDateTime *date; + date = g_date_time_new_from_timeval_local (&tv); + nautilus_search_hit_set_modification_time (hit, date); + g_date_time_unref (date); + } + else + { + g_warning ("unable to parse mtime: %s", mtime_str); + } + if (g_time_val_from_iso8601 (atime_str, &tv)) + { + GDateTime *date; + date = g_date_time_new_from_timeval_local (&tv); + nautilus_search_hit_set_access_time (hit, date); + g_date_time_unref (date); + } + else + { + g_warning ("unable to parse atime: %s", atime_str); + } + if (g_time_val_from_iso8601 (ctime_str, &tv)) + { + GDateTime *date; + date = g_date_time_new_from_timeval_local (&tv); + nautilus_search_hit_set_creation_time (hit, date); + g_date_time_unref (date); + } + else + { + g_warning ("unable to parse ctime: %s", ctime_str); + } + + g_queue_push_head (tracker->hits_pending, hit); + check_pending_hits (tracker, FALSE); + + /* Get next */ + cursor_next (tracker, cursor); +} + +static void +query_callback (GObject *object, + GAsyncResult *result, + gpointer user_data) +{ + NautilusSearchEngineTracker *tracker; + TrackerSparqlConnection *connection; + TrackerSparqlCursor *cursor; + GError *error = NULL; + + tracker = NAUTILUS_SEARCH_ENGINE_TRACKER (user_data); + + connection = TRACKER_SPARQL_CONNECTION (object); + cursor = tracker_sparql_connection_query_finish (connection, + result, + &error); + + if (error != NULL) + { + search_finished (tracker, error); + g_error_free (error); + } + else + { + cursor_next (tracker, cursor); + } +} + +static gboolean +search_finished_idle (gpointer user_data) +{ + NautilusSearchEngineTracker *tracker = user_data; + + DEBUG ("Tracker engine finished idle"); + + search_finished (tracker, NULL); + + return FALSE; +} + +/* This is used to compensate rank if fts:rank is not set (resp. fts:match is + * not used). The value was determined experimentally. I am convinced that + * fts:rank is currently always set to 5.0 in case of filename match. + */ +#define FILENAME_RANK "5.0" + +static void +nautilus_search_engine_tracker_start (NautilusSearchProvider *provider) +{ + NautilusSearchEngineTracker *tracker; + gchar *query_text, *search_text, *location_uri, *downcase; + GFile *location; + GString *sparql; + g_autoptr (GPtrArray) mimetypes = NULL; + GPtrArray *date_range; + + tracker = NAUTILUS_SEARCH_ENGINE_TRACKER (provider); + + if (tracker->query_pending) + { + return; + } + + DEBUG ("Tracker engine start"); + g_object_ref (tracker); + tracker->query_pending = TRUE; + + g_object_notify (G_OBJECT (provider), "running"); + + if (tracker->connection == NULL) + { + g_idle_add (search_finished_idle, provider); + return; + } + + tracker->fts_enabled = nautilus_query_get_search_content (tracker->query); + + query_text = nautilus_query_get_text (tracker->query); + downcase = g_utf8_strdown (query_text, -1); + search_text = tracker_sparql_escape_string (downcase); + g_free (query_text); + g_free (downcase); + + location = nautilus_query_get_location (tracker->query); + location_uri = location ? g_file_get_uri (location) : NULL; + mimetypes = nautilus_query_get_mime_types (tracker->query); + + sparql = g_string_new ("SELECT DISTINCT" + " ?url" + " xsd:double(COALESCE(?rank2, ?rank1)) AS ?rank" + " nfo:fileLastModified(?file)" + " nfo:fileCreated(?file)" + " nfo:fileLastAccessed(?file)"); + + if (tracker->fts_enabled && *search_text) + { + g_string_append (sparql, + "fts:snippet(?content," + " '_NAUTILUS_SNIPPET_DELIM_START_'," + " '_NAUTILUS_SNIPPET_DELIM_END_', " + " '…'," + " 20)"); + } + + g_string_append (sparql, "FROM tracker:FileSystem "); + + if (tracker->fts_enabled) + { + g_string_append (sparql, "FROM tracker:Documents "); + } + + g_string_append (sparql, + "\nWHERE {" + " ?file a nfo:FileDataObject;" + " nfo:fileLastModified ?mtime;" + " nfo:fileLastAccessed ?atime;" + " nie:dataSource/tracker:available true;" + " nie:url ?url." + " OPTIONAL { ?file nfo:fileCreated ?ctime.}"); + + if (mimetypes->len > 0) + { + g_string_append (sparql, + " ?content nie:isStoredAs ?file;" + " nie:mimeType ?mime"); + } + + if (tracker->fts_enabled && *search_text) + { + /* Use fts:match only for content search to not lose some filename results due to stop words. */ + g_string_append_printf (sparql, + " { " + " ?content nie:isStoredAs ?file ." + " ?content fts:match \"%s*\" ." + " BIND(fts:rank(?content) AS ?rank1) ." + " } UNION", + search_text); + } + + g_string_append_printf (sparql, + " {" + " ?file nfo:fileName ?filename ." + " FILTER(fn:contains(fn:lower-case(?filename), '%s')) ." + " BIND(" FILENAME_RANK " AS ?rank2) ." + " }", + search_text); + + g_string_append_printf (sparql, " . FILTER( "); + + if (!tracker->recursive) + { + g_string_append_printf (sparql, "tracker:uri-is-parent('%s', ?url)", location_uri); + } + else + { + /* STRSTARTS is faster than tracker:uri-is-descendant(). + * See https://gitlab.gnome.org/GNOME/tracker/-/issues/243 + */ + g_string_append_printf (sparql, "STRSTARTS(?url, '%s/')", location_uri); + } + + date_range = nautilus_query_get_date_range (tracker->query); + if (date_range) + { + NautilusQuerySearchType type; + gchar *initial_date_format; + gchar *end_date_format; + GDateTime *initial_date; + GDateTime *end_date; + GDateTime *shifted_end_date; + + initial_date = g_ptr_array_index (date_range, 0); + end_date = g_ptr_array_index (date_range, 1); + /* As we do for other searches, we want to make the end date inclusive. + * For that, add a day to it */ + shifted_end_date = g_date_time_add_days (end_date, 1); + + type = nautilus_query_get_search_type (tracker->query); + initial_date_format = g_date_time_format_iso8601 (initial_date); + end_date_format = g_date_time_format_iso8601 (shifted_end_date); + + g_string_append (sparql, " && "); + + if (type == NAUTILUS_QUERY_SEARCH_TYPE_LAST_ACCESS) + { + g_string_append_printf (sparql, "?atime >= \"%s\"^^xsd:dateTime", initial_date_format); + g_string_append_printf (sparql, " && ?atime <= \"%s\"^^xsd:dateTime", end_date_format); + } + else if (type == NAUTILUS_QUERY_SEARCH_TYPE_LAST_MODIFIED) + { + g_string_append_printf (sparql, "?mtime >= \"%s\"^^xsd:dateTime", initial_date_format); + g_string_append_printf (sparql, " && ?mtime <= \"%s\"^^xsd:dateTime", end_date_format); + } + else + { + g_string_append_printf (sparql, "?ctime >= \"%s\"^^xsd:dateTime", initial_date_format); + g_string_append_printf (sparql, " && ?ctime <= \"%s\"^^xsd:dateTime", end_date_format); + } + + + g_free (initial_date_format); + g_free (end_date_format); + g_ptr_array_unref (date_range); + } + + if (mimetypes->len > 0) + { + g_string_append (sparql, " && ("); + + for (gint i = 0; i < mimetypes->len; i++) + { + if (i != 0) + { + g_string_append (sparql, " || "); + } + + g_string_append_printf (sparql, "fn:contains(?mime, '%s')", + (gchar *) g_ptr_array_index (mimetypes, i)); + } + g_string_append (sparql, ")\n"); + } + + g_string_append (sparql, ")} ORDER BY DESC (?rank)"); + + tracker->cancellable = g_cancellable_new (); + tracker_sparql_connection_query_async (tracker->connection, + sparql->str, + tracker->cancellable, + query_callback, + tracker); + g_string_free (sparql, TRUE); + + g_free (search_text); + g_free (location_uri); + g_object_unref (location); +} + +static void +nautilus_search_engine_tracker_stop (NautilusSearchProvider *provider) +{ + NautilusSearchEngineTracker *tracker; + + tracker = NAUTILUS_SEARCH_ENGINE_TRACKER (provider); + + if (tracker->query_pending) + { + DEBUG ("Tracker engine stop"); + g_cancellable_cancel (tracker->cancellable); + g_clear_object (&tracker->cancellable); + tracker->query_pending = FALSE; + + g_object_notify (G_OBJECT (provider), "running"); + } +} + +static void +nautilus_search_engine_tracker_set_query (NautilusSearchProvider *provider, + NautilusQuery *query) +{ + g_autoptr (GFile) location = NULL; + NautilusSearchEngineTracker *tracker; + + tracker = NAUTILUS_SEARCH_ENGINE_TRACKER (provider); + location = nautilus_query_get_location (query); + + g_clear_object (&tracker->query); + + tracker->query = g_object_ref (query); + tracker->recursive = is_recursive_search (NAUTILUS_SEARCH_ENGINE_TYPE_INDEXED, + nautilus_query_get_recursive (query), + location); +} + +static gboolean +nautilus_search_engine_tracker_is_running (NautilusSearchProvider *provider) +{ + NautilusSearchEngineTracker *tracker; + + tracker = NAUTILUS_SEARCH_ENGINE_TRACKER (provider); + + return tracker->query_pending; +} + +static void +nautilus_search_provider_init (NautilusSearchProviderInterface *iface) +{ + iface->set_query = nautilus_search_engine_tracker_set_query; + iface->start = nautilus_search_engine_tracker_start; + iface->stop = nautilus_search_engine_tracker_stop; + iface->is_running = nautilus_search_engine_tracker_is_running; +} + +static void +nautilus_search_engine_tracker_get_property (GObject *object, + guint prop_id, + GValue *value, + GParamSpec *pspec) +{ + NautilusSearchProvider *self = NAUTILUS_SEARCH_PROVIDER (object); + + switch (prop_id) + { + case PROP_RUNNING: + { + g_value_set_boolean (value, nautilus_search_engine_tracker_is_running (self)); + } + break; + + default: + { + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); + } + } +} + +static void +nautilus_search_engine_tracker_class_init (NautilusSearchEngineTrackerClass *class) +{ + GObjectClass *gobject_class; + + gobject_class = G_OBJECT_CLASS (class); + gobject_class->finalize = finalize; + gobject_class->get_property = nautilus_search_engine_tracker_get_property; + + /** + * NautilusSearchEngine::running: + * + * Whether the search engine is running a search. + */ + g_object_class_override_property (gobject_class, PROP_RUNNING, "running"); +} + +static void +nautilus_search_engine_tracker_init (NautilusSearchEngineTracker *engine) +{ + GError *error = NULL; + + engine->hits_pending = g_queue_new (); + + engine->connection = nautilus_tracker_get_miner_fs_connection (&error); + if (error) + { + g_warning ("Could not establish a connection to Tracker: %s", error->message); + g_error_free (error); + } +} + + +NautilusSearchEngineTracker * +nautilus_search_engine_tracker_new (void) +{ + return g_object_new (NAUTILUS_TYPE_SEARCH_ENGINE_TRACKER, NULL); +} diff --git a/src/nautilus-search-engine-tracker.h b/src/nautilus-search-engine-tracker.h new file mode 100644 index 0000000..efc3038 --- /dev/null +++ b/src/nautilus-search-engine-tracker.h @@ -0,0 +1,29 @@ +/* + * Copyright (C) 2005 Mr Jamie McCracken + * + * Nautilus is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License as + * published by the Free Software Foundation; either version 2 of the + * License, or (at your option) any later version. + * + * Nautilus is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public + * License along with this program; see the file COPYING. If not, + * see . + * + * Author: Jamie McCracken (jamiemcc@gnome.org) + * + */ + +#pragma once + +#include "nautilus-search-engine.h" + +#define NAUTILUS_TYPE_SEARCH_ENGINE_TRACKER (nautilus_search_engine_tracker_get_type ()) +G_DECLARE_FINAL_TYPE (NautilusSearchEngineTracker, nautilus_search_engine_tracker, NAUTILUS, SEARCH_ENGINE_TRACKER, GObject) + +NautilusSearchEngineTracker* nautilus_search_engine_tracker_new (void); \ No newline at end of file diff --git a/src/nautilus-search-engine.c b/src/nautilus-search-engine.c new file mode 100644 index 0000000..48bfa1f --- /dev/null +++ b/src/nautilus-search-engine.c @@ -0,0 +1,588 @@ +/* + * Copyright (C) 2005 Novell, Inc. + * + * Nautilus is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License as + * published by the Free Software Foundation; either version 2 of the + * License, or (at your option) any later version. + * + * Nautilus is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public + * License along with this program; see the file COPYING. If not, + * see . + * + * Author: Anders Carlsson + * + */ + +#include +#include "nautilus-search-engine.h" +#include "nautilus-search-engine-private.h" + +#include "nautilus-file-utilities.h" +#include "nautilus-search-engine-model.h" +#include +#define DEBUG_FLAG NAUTILUS_DEBUG_SEARCH +#include "nautilus-debug.h" +#include "nautilus-search-engine-recent.h" +#include "nautilus-search-engine-simple.h" +#include "nautilus-search-engine-tracker.h" + +typedef struct +{ + NautilusSearchEngineTracker *tracker; + NautilusSearchEngineRecent *recent; + NautilusSearchEngineSimple *simple; + NautilusSearchEngineModel *model; + + GHashTable *uris; + guint providers_running; + guint providers_finished; + guint providers_error; + + gboolean running; + gboolean restart; +} NautilusSearchEnginePrivate; + +enum +{ + PROP_0, + PROP_RUNNING, + LAST_PROP +}; + +static void nautilus_search_provider_init (NautilusSearchProviderInterface *iface); + +static gboolean nautilus_search_engine_is_running (NautilusSearchProvider *provider); + +G_DEFINE_TYPE_WITH_CODE (NautilusSearchEngine, + nautilus_search_engine, + G_TYPE_OBJECT, + G_ADD_PRIVATE (NautilusSearchEngine) + G_IMPLEMENT_INTERFACE (NAUTILUS_TYPE_SEARCH_PROVIDER, + nautilus_search_provider_init)) + +static void +nautilus_search_engine_set_query (NautilusSearchProvider *provider, + NautilusQuery *query) +{ + NautilusSearchEngine *engine; + NautilusSearchEnginePrivate *priv; + + engine = NAUTILUS_SEARCH_ENGINE (provider); + priv = nautilus_search_engine_get_instance_private (engine); + + nautilus_search_provider_set_query (NAUTILUS_SEARCH_PROVIDER (priv->tracker), query); + nautilus_search_provider_set_query (NAUTILUS_SEARCH_PROVIDER (priv->recent), query); + nautilus_search_provider_set_query (NAUTILUS_SEARCH_PROVIDER (priv->model), query); + nautilus_search_provider_set_query (NAUTILUS_SEARCH_PROVIDER (priv->simple), query); +} + +static void +search_engine_start_real_setup (NautilusSearchEngine *engine) +{ + NautilusSearchEnginePrivate *priv; + + priv = nautilus_search_engine_get_instance_private (engine); + + priv->providers_running = 0; + priv->providers_finished = 0; + priv->providers_error = 0; + + priv->restart = FALSE; + + DEBUG ("Search engine start real setup"); + + g_object_ref (engine); +} + +static void +search_engine_start_real_tracker (NautilusSearchEngine *engine) +{ + NautilusSearchEnginePrivate *priv; + + priv = nautilus_search_engine_get_instance_private (engine); + + priv->providers_running++; + nautilus_search_provider_start (NAUTILUS_SEARCH_PROVIDER (priv->tracker)); +} + +static void +search_engine_start_real_recent (NautilusSearchEngine *engine) +{ + NautilusSearchEnginePrivate *priv; + + priv = nautilus_search_engine_get_instance_private (engine); + + priv->providers_running++; + nautilus_search_provider_start (NAUTILUS_SEARCH_PROVIDER (priv->recent)); +} + +static void +search_engine_start_real_model (NautilusSearchEngine *engine) +{ + NautilusSearchEnginePrivate *priv; + + priv = nautilus_search_engine_get_instance_private (engine); + if (nautilus_search_engine_model_get_model (priv->model)) + { + priv->providers_running++; + nautilus_search_provider_start (NAUTILUS_SEARCH_PROVIDER (priv->model)); + } +} + +static void +search_engine_start_real_simple (NautilusSearchEngine *engine) +{ + NautilusSearchEnginePrivate *priv; + + priv = nautilus_search_engine_get_instance_private (engine); + priv->providers_running++; + + nautilus_search_provider_start (NAUTILUS_SEARCH_PROVIDER (priv->simple)); +} + +static void +search_engine_start_real (NautilusSearchEngine *engine, + NautilusSearchEngineTarget target_engine) +{ + search_engine_start_real_setup (engine); + + switch (target_engine) + { + case NAUTILUS_SEARCH_ENGINE_TRACKER_ENGINE: + { + search_engine_start_real_tracker (engine); + } + break; + + case NAUTILUS_SEARCH_ENGINE_RECENT_ENGINE: + { + search_engine_start_real_recent (engine); + } + break; + + case NAUTILUS_SEARCH_ENGINE_MODEL_ENGINE: + { + search_engine_start_real_model (engine); + } + break; + + case NAUTILUS_SEARCH_ENGINE_SIMPLE_ENGINE: + { + search_engine_start_real_simple (engine); + } + break; + + case NAUTILUS_SEARCH_ENGINE_ALL_ENGINES: + default: + { + search_engine_start_real_tracker (engine); + search_engine_start_real_recent (engine); + search_engine_start_real_model (engine); + search_engine_start_real_simple (engine); + } + } +} + +void +nautilus_search_engine_start_by_target (NautilusSearchProvider *provider, + NautilusSearchEngineTarget target_engine) +{ + NautilusSearchEngine *engine; + NautilusSearchEnginePrivate *priv; + gint num_finished; + + engine = NAUTILUS_SEARCH_ENGINE (provider); + priv = nautilus_search_engine_get_instance_private (engine); + + DEBUG ("Search engine start"); + + num_finished = priv->providers_error + priv->providers_finished; + + if (priv->running) + { + if (num_finished == priv->providers_running && + priv->restart) + { + search_engine_start_real (engine, target_engine); + } + + return; + } + + priv->running = TRUE; + + g_object_notify (G_OBJECT (provider), "running"); + + if (num_finished < priv->providers_running) + { + priv->restart = TRUE; + } + else + { + search_engine_start_real (engine, target_engine); + } +} + + + +static void +nautilus_search_engine_start (NautilusSearchProvider *provider) +{ + NautilusSearchEngine *engine; + NautilusSearchEnginePrivate *priv; + gint num_finished; + + engine = NAUTILUS_SEARCH_ENGINE (provider); + priv = nautilus_search_engine_get_instance_private (engine); + + DEBUG ("Search engine start"); + + num_finished = priv->providers_error + priv->providers_finished; + + if (priv->running) + { + if (num_finished == priv->providers_running && + priv->restart) + { + search_engine_start_real (engine, NAUTILUS_SEARCH_ENGINE_ALL_ENGINES); + } + + return; + } + + priv->running = TRUE; + + g_object_notify (G_OBJECT (provider), "running"); + + if (num_finished < priv->providers_running) + { + priv->restart = TRUE; + } + else + { + search_engine_start_real (engine, NAUTILUS_SEARCH_ENGINE_ALL_ENGINES); + } +} + +static void +nautilus_search_engine_stop (NautilusSearchProvider *provider) +{ + NautilusSearchEngine *engine; + NautilusSearchEnginePrivate *priv; + + engine = NAUTILUS_SEARCH_ENGINE (provider); + priv = nautilus_search_engine_get_instance_private (engine); + + DEBUG ("Search engine stop"); + + nautilus_search_provider_stop (NAUTILUS_SEARCH_PROVIDER (priv->tracker)); + nautilus_search_provider_stop (NAUTILUS_SEARCH_PROVIDER (priv->recent)); + nautilus_search_provider_stop (NAUTILUS_SEARCH_PROVIDER (priv->model)); + nautilus_search_provider_stop (NAUTILUS_SEARCH_PROVIDER (priv->simple)); + + priv->running = FALSE; + priv->restart = FALSE; + + g_object_notify (G_OBJECT (provider), "running"); +} + +static void +search_provider_hits_added (NautilusSearchProvider *provider, + GList *hits, + NautilusSearchEngine *engine) +{ + NautilusSearchEnginePrivate *priv; + GList *added = NULL; + GList *l; + + priv = nautilus_search_engine_get_instance_private (engine); + + if (!priv->running || priv->restart) + { + DEBUG ("Ignoring hits-added, since engine is %s", + !priv->running ? "not running" : "waiting to restart"); + return; + } + + for (l = hits; l != NULL; l = l->next) + { + NautilusSearchHit *hit = l->data; + int count; + const char *uri; + + uri = nautilus_search_hit_get_uri (hit); + count = GPOINTER_TO_INT (g_hash_table_lookup (priv->uris, uri)); + if (count == 0) + { + added = g_list_prepend (added, hit); + } + g_hash_table_replace (priv->uris, g_strdup (uri), GINT_TO_POINTER (++count)); + } + if (added != NULL) + { + added = g_list_reverse (added); + nautilus_search_provider_hits_added (NAUTILUS_SEARCH_PROVIDER (engine), added); + g_list_free (added); + } +} + +static void +check_providers_status (NautilusSearchEngine *engine) +{ + NautilusSearchEnginePrivate *priv; + gint num_finished; + + priv = nautilus_search_engine_get_instance_private (engine); + num_finished = priv->providers_error + priv->providers_finished; + + if (num_finished < priv->providers_running) + { + return; + } + + if (num_finished == priv->providers_error) + { + DEBUG ("Search engine error"); + nautilus_search_provider_error (NAUTILUS_SEARCH_PROVIDER (engine), + _("Unable to complete the requested search")); + } + else + { + if (priv->restart) + { + DEBUG ("Search engine finished and restarting"); + } + else + { + DEBUG ("Search engine finished"); + } + nautilus_search_provider_finished (NAUTILUS_SEARCH_PROVIDER (engine), + priv->restart ? NAUTILUS_SEARCH_PROVIDER_STATUS_RESTARTING : + NAUTILUS_SEARCH_PROVIDER_STATUS_NORMAL); + } + + priv->running = FALSE; + g_object_notify (G_OBJECT (engine), "running"); + + g_hash_table_remove_all (priv->uris); + + if (priv->restart) + { + nautilus_search_engine_start (NAUTILUS_SEARCH_PROVIDER (engine)); + } + + g_object_unref (engine); +} + +static void +search_provider_error (NautilusSearchProvider *provider, + const char *error_message, + NautilusSearchEngine *engine) +{ + NautilusSearchEnginePrivate *priv; + + DEBUG ("Search provider error: %s", error_message); + + priv = nautilus_search_engine_get_instance_private (engine); + priv->providers_error++; + + check_providers_status (engine); +} + +static void +search_provider_finished (NautilusSearchProvider *provider, + NautilusSearchProviderStatus status, + NautilusSearchEngine *engine) +{ + NautilusSearchEnginePrivate *priv; + + DEBUG ("Search provider finished"); + + priv = nautilus_search_engine_get_instance_private (engine); + priv->providers_finished++; + + check_providers_status (engine); +} + +static void +connect_provider_signals (NautilusSearchEngine *engine, + NautilusSearchProvider *provider) +{ + g_signal_connect (provider, "hits-added", + G_CALLBACK (search_provider_hits_added), + engine); + g_signal_connect (provider, "finished", + G_CALLBACK (search_provider_finished), + engine); + g_signal_connect (provider, "error", + G_CALLBACK (search_provider_error), + engine); +} + +static gboolean +nautilus_search_engine_is_running (NautilusSearchProvider *provider) +{ + NautilusSearchEngine *engine; + NautilusSearchEnginePrivate *priv; + + engine = NAUTILUS_SEARCH_ENGINE (provider); + priv = nautilus_search_engine_get_instance_private (engine); + + return priv->running; +} + +static void +nautilus_search_provider_init (NautilusSearchProviderInterface *iface) +{ + iface->set_query = nautilus_search_engine_set_query; + iface->start = nautilus_search_engine_start; + iface->stop = nautilus_search_engine_stop; + iface->is_running = nautilus_search_engine_is_running; +} + +static void +nautilus_search_engine_finalize (GObject *object) +{ + NautilusSearchEngine *engine; + NautilusSearchEnginePrivate *priv; + + engine = NAUTILUS_SEARCH_ENGINE (object); + priv = nautilus_search_engine_get_instance_private (engine); + + g_hash_table_destroy (priv->uris); + + g_clear_object (&priv->tracker); + g_clear_object (&priv->recent); + g_clear_object (&priv->model); + g_clear_object (&priv->simple); + + G_OBJECT_CLASS (nautilus_search_engine_parent_class)->finalize (object); +} + +static void +nautilus_search_engine_get_property (GObject *object, + guint prop_id, + GValue *value, + GParamSpec *pspec) +{ + NautilusSearchProvider *self = NAUTILUS_SEARCH_PROVIDER (object); + + switch (prop_id) + { + case PROP_RUNNING: + { + g_value_set_boolean (value, nautilus_search_engine_is_running (self)); + } + break; + + default: + { + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); + } + } +} + +static void +nautilus_search_engine_class_init (NautilusSearchEngineClass *class) +{ + GObjectClass *object_class; + + object_class = (GObjectClass *) class; + + object_class->finalize = nautilus_search_engine_finalize; + object_class->get_property = nautilus_search_engine_get_property; + + /** + * NautilusSearchEngine::running: + * + * Whether the search engine is running a search. + */ + g_object_class_override_property (object_class, PROP_RUNNING, "running"); +} + +static void +nautilus_search_engine_init (NautilusSearchEngine *engine) +{ + NautilusSearchEnginePrivate *priv; + + priv = nautilus_search_engine_get_instance_private (engine); + priv->uris = g_hash_table_new_full (g_str_hash, g_str_equal, g_free, NULL); + + priv->tracker = nautilus_search_engine_tracker_new (); + connect_provider_signals (engine, NAUTILUS_SEARCH_PROVIDER (priv->tracker)); + + priv->model = nautilus_search_engine_model_new (); + connect_provider_signals (engine, NAUTILUS_SEARCH_PROVIDER (priv->model)); + + priv->simple = nautilus_search_engine_simple_new (); + connect_provider_signals (engine, NAUTILUS_SEARCH_PROVIDER (priv->simple)); + + priv->recent = nautilus_search_engine_recent_new (); + connect_provider_signals (engine, NAUTILUS_SEARCH_PROVIDER (priv->recent)); +} + +NautilusSearchEngine * +nautilus_search_engine_new (void) +{ + NautilusSearchEngine *engine; + + engine = g_object_new (NAUTILUS_TYPE_SEARCH_ENGINE, NULL); + + return engine; +} + +NautilusSearchEngineModel * +nautilus_search_engine_get_model_provider (NautilusSearchEngine *engine) +{ + NautilusSearchEnginePrivate *priv; + + priv = nautilus_search_engine_get_instance_private (engine); + + return priv->model; +} + +gboolean +is_recursive_search (NautilusSearchEngineType engine_type, + NautilusQueryRecursive recursive, + GFile *location) +{ + switch (recursive) + { + case NAUTILUS_QUERY_RECURSIVE_NEVER: + { + return FALSE; + } + + case NAUTILUS_QUERY_RECURSIVE_ALWAYS: + { + return TRUE; + } + + case NAUTILUS_QUERY_RECURSIVE_INDEXED_ONLY: + { + return engine_type == NAUTILUS_SEARCH_ENGINE_TYPE_INDEXED; + } + + case NAUTILUS_QUERY_RECURSIVE_LOCAL_ONLY: + { + g_autoptr (GFileInfo) file_system_info = NULL; + + file_system_info = g_file_query_filesystem_info (location, + G_FILE_ATTRIBUTE_FILESYSTEM_REMOTE, + NULL, NULL); + if (file_system_info != NULL) + { + return !g_file_info_get_attribute_boolean (file_system_info, + G_FILE_ATTRIBUTE_FILESYSTEM_REMOTE); + } + } + } + + return TRUE; +} diff --git a/src/nautilus-search-engine.h b/src/nautilus-search-engine.h new file mode 100644 index 0000000..33c3644 --- /dev/null +++ b/src/nautilus-search-engine.h @@ -0,0 +1,48 @@ +/* + * Copyright (C) 2005 Novell, Inc. + * + * Nautilus is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License as + * published by the Free Software Foundation; either version 2 of the + * License, or (at your option) any later version. + * + * Nautilus is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public + * License along with this program; see the file COPYING. If not, + * see . + * + * Author: Anders Carlsson + * + */ + +#pragma once + +#include + +#include "nautilus-directory.h" +#include "nautilus-search-engine-model.h" +#include "nautilus-search-provider.h" + +G_BEGIN_DECLS + +#define NAUTILUS_TYPE_SEARCH_ENGINE (nautilus_search_engine_get_type ()) + +G_DECLARE_DERIVABLE_TYPE (NautilusSearchEngine, nautilus_search_engine, NAUTILUS, SEARCH_ENGINE, GObject) + +struct _NautilusSearchEngineClass +{ + GObjectClass parent_class; +}; + +NautilusSearchEngine *nautilus_search_engine_new (void); +NautilusSearchEngineModel * + nautilus_search_engine_get_model_provider (NautilusSearchEngine *engine); + +G_END_DECLS + +void nautilus_search_engine_start_by_target (NautilusSearchProvider *provider, + NautilusSearchEngineTarget taregt_engine); \ No newline at end of file diff --git a/src/nautilus-search-hit.c b/src/nautilus-search-hit.c new file mode 100644 index 0000000..6efaa56 --- /dev/null +++ b/src/nautilus-search-hit.c @@ -0,0 +1,482 @@ +/* + * Copyright (C) 2012 Red Hat, Inc. + * + * Nautilus is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License as + * published by the Free Software Foundation; either version 2 of the + * License, or (at your option) any later version. + * + * Nautilus is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public + * License along with this program; see the file COPYING. If not, + * see . + * + */ + +#include + +#include +#include + +#include "nautilus-search-hit.h" +#include "nautilus-query.h" +#define DEBUG_FLAG NAUTILUS_DEBUG_SEARCH_HIT +#include "nautilus-debug.h" + +struct _NautilusSearchHit +{ + GObject parent_instance; + + char *uri; + + GDateTime *modification_time; + GDateTime *access_time; + GDateTime *creation_time; + gdouble fts_rank; + gchar *fts_snippet; + + gdouble relevance; +}; + +enum +{ + PROP_URI = 1, + PROP_RELEVANCE, + PROP_MODIFICATION_TIME, + PROP_ACCESS_TIME, + PROP_CREATION_TIME, + PROP_FTS_RANK, + PROP_FTS_SNIPPET, + NUM_PROPERTIES +}; + +G_DEFINE_TYPE (NautilusSearchHit, nautilus_search_hit, G_TYPE_OBJECT) + +void +nautilus_search_hit_compute_scores (NautilusSearchHit *hit, + NautilusQuery *query) +{ + GDateTime *now; + GFile *query_location; + GFile *hit_location; + GTimeSpan m_diff = G_MAXINT64; + GTimeSpan a_diff = G_MAXINT64; + GTimeSpan t_diff = G_MAXINT64; + gdouble recent_bonus = 0.0; + gdouble proximity_bonus = 0.0; + gdouble match_bonus = 0.0; + + query_location = nautilus_query_get_location (query); + hit_location = g_file_new_for_uri (hit->uri); + + if (g_file_has_prefix (hit_location, query_location)) + { + GFile *parent, *location; + guint dir_count = 0; + + parent = g_file_get_parent (hit_location); + + while (!g_file_equal (parent, query_location)) + { + dir_count++; + location = parent; + parent = g_file_get_parent (location); + g_object_unref (location); + } + g_object_unref (parent); + + if (dir_count < 10) + { + proximity_bonus = 10000.0 - 1000.0 * dir_count; + } + } + g_object_unref (hit_location); + + now = g_date_time_new_now_local (); + if (hit->modification_time != NULL) + { + m_diff = g_date_time_difference (now, hit->modification_time); + } + if (hit->access_time != NULL) + { + a_diff = g_date_time_difference (now, hit->access_time); + } + m_diff /= G_TIME_SPAN_DAY; + a_diff /= G_TIME_SPAN_DAY; + t_diff = MIN (m_diff, a_diff); + if (t_diff > 90) + { + recent_bonus = 0.0; + } + else if (t_diff > 30) + { + recent_bonus = 10.0; + } + else if (t_diff > 14) + { + recent_bonus = 30.0; + } + else if (t_diff > 7) + { + recent_bonus = 50.0; + } + else if (t_diff > 1) + { + recent_bonus = 70.0; + } + else + { + recent_bonus = 100.0; + } + + if (hit->fts_rank > 0) + { + match_bonus = MIN (500, 10.0 * hit->fts_rank); + } + else + { + match_bonus = 0.0; + } + + hit->relevance = recent_bonus + proximity_bonus + match_bonus; + DEBUG ("Hit %s computed relevance %.2f (%.2f + %.2f + %.2f)", hit->uri, hit->relevance, + proximity_bonus, recent_bonus, match_bonus); + + g_date_time_unref (now); + g_object_unref (query_location); +} + +const char * +nautilus_search_hit_get_uri (NautilusSearchHit *hit) +{ + return hit->uri; +} + +gdouble +nautilus_search_hit_get_relevance (NautilusSearchHit *hit) +{ + return hit->relevance; +} + +const gchar * +nautilus_search_hit_get_fts_snippet (NautilusSearchHit *hit) +{ + return hit->fts_snippet; +} + +static void +nautilus_search_hit_set_uri (NautilusSearchHit *hit, + const char *uri) +{ + g_free (hit->uri); + hit->uri = g_strdup (uri); +} + +void +nautilus_search_hit_set_fts_rank (NautilusSearchHit *hit, + gdouble rank) +{ + hit->fts_rank = rank; +} + +void +nautilus_search_hit_set_modification_time (NautilusSearchHit *hit, + GDateTime *date) +{ + if (hit->modification_time != NULL) + { + g_date_time_unref (hit->modification_time); + } + if (date != NULL) + { + hit->modification_time = g_date_time_ref (date); + } + else + { + hit->modification_time = NULL; + } +} + +void +nautilus_search_hit_set_access_time (NautilusSearchHit *hit, + GDateTime *date) +{ + if (hit->access_time != NULL) + { + g_date_time_unref (hit->access_time); + } + if (date != NULL) + { + hit->access_time = g_date_time_ref (date); + } + else + { + hit->access_time = NULL; + } +} + +void +nautilus_search_hit_set_creation_time (NautilusSearchHit *hit, + GDateTime *date) +{ + if (hit->creation_time != NULL) + { + g_date_time_unref (hit->creation_time); + } + if (date != NULL) + { + hit->creation_time = g_date_time_ref (date); + } + else + { + hit->creation_time = NULL; + } +} + +void +nautilus_search_hit_set_fts_snippet (NautilusSearchHit *hit, + const gchar *snippet) +{ + g_free (hit->fts_snippet); + + hit->fts_snippet = g_strdup (snippet); +} + +static void +nautilus_search_hit_set_property (GObject *object, + guint arg_id, + const GValue *value, + GParamSpec *pspec) +{ + NautilusSearchHit *hit; + + hit = NAUTILUS_SEARCH_HIT (object); + + switch (arg_id) + { + case PROP_RELEVANCE: + { + hit->relevance = g_value_get_double (value); + } + break; + + case PROP_FTS_RANK: + { + nautilus_search_hit_set_fts_rank (hit, g_value_get_double (value)); + } + break; + + case PROP_URI: + { + nautilus_search_hit_set_uri (hit, g_value_get_string (value)); + } + break; + + case PROP_MODIFICATION_TIME: + { + nautilus_search_hit_set_modification_time (hit, g_value_get_boxed (value)); + } + break; + + case PROP_ACCESS_TIME: + { + nautilus_search_hit_set_access_time (hit, g_value_get_boxed (value)); + } + break; + + case PROP_CREATION_TIME: + { + nautilus_search_hit_set_creation_time (hit, g_value_get_boxed (value)); + } + break; + + case PROP_FTS_SNIPPET: + { + g_free (hit->fts_snippet); + hit->fts_snippet = g_strdup (g_value_get_string (value)); + } + break; + + default: + { + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, arg_id, pspec); + } + break; + } +} + +static void +nautilus_search_hit_get_property (GObject *object, + guint arg_id, + GValue *value, + GParamSpec *pspec) +{ + NautilusSearchHit *hit; + + hit = NAUTILUS_SEARCH_HIT (object); + + switch (arg_id) + { + case PROP_RELEVANCE: + { + g_value_set_double (value, hit->relevance); + } + break; + + case PROP_FTS_RANK: + { + g_value_set_double (value, hit->fts_rank); + } + break; + + case PROP_URI: + { + g_value_set_string (value, hit->uri); + } + break; + + case PROP_MODIFICATION_TIME: + { + g_value_set_boxed (value, hit->modification_time); + } + break; + + case PROP_ACCESS_TIME: + { + g_value_set_boxed (value, hit->access_time); + } + break; + + case PROP_CREATION_TIME: + { + g_value_set_boxed (value, hit->creation_time); + } + break; + + case PROP_FTS_SNIPPET: + { + g_value_set_string (value, hit->fts_snippet); + } + break; + + default: + { + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, arg_id, pspec); + } + break; + } +} + +static void +nautilus_search_hit_finalize (GObject *object) +{ + NautilusSearchHit *hit = NAUTILUS_SEARCH_HIT (object); + + g_free (hit->uri); + + if (hit->access_time != NULL) + { + g_date_time_unref (hit->access_time); + } + if (hit->modification_time != NULL) + { + g_date_time_unref (hit->modification_time); + } + if (hit->creation_time != NULL) + { + g_date_time_unref (hit->creation_time); + } + + g_free (hit->fts_snippet); + + G_OBJECT_CLASS (nautilus_search_hit_parent_class)->finalize (object); +} + +static void +nautilus_search_hit_class_init (NautilusSearchHitClass *class) +{ + GObjectClass *object_class; + + object_class = (GObjectClass *) class; + + object_class->finalize = nautilus_search_hit_finalize; + object_class->get_property = nautilus_search_hit_get_property; + object_class->set_property = nautilus_search_hit_set_property; + + g_object_class_install_property (object_class, + PROP_URI, + g_param_spec_string ("uri", + "URI", + "URI", + NULL, + G_PARAM_CONSTRUCT_ONLY | G_PARAM_WRITABLE | G_PARAM_READABLE)); + g_object_class_install_property (object_class, + PROP_MODIFICATION_TIME, + g_param_spec_boxed ("modification-time", + "Modification time", + "Modification time", + G_TYPE_DATE_TIME, + G_PARAM_READWRITE | G_PARAM_CONSTRUCT)); + g_object_class_install_property (object_class, + PROP_ACCESS_TIME, + g_param_spec_boxed ("access-time", + "acess time", + "access time", + G_TYPE_DATE_TIME, + G_PARAM_READWRITE | G_PARAM_CONSTRUCT)); + + g_object_class_install_property (object_class, + PROP_CREATION_TIME, + g_param_spec_boxed ("creation-time", + "creation time", + "creation time", + G_TYPE_DATE_TIME, + G_PARAM_READWRITE | G_PARAM_CONSTRUCT)); + g_object_class_install_property (object_class, + PROP_RELEVANCE, + g_param_spec_double ("relevance", + NULL, + NULL, + -G_MAXDOUBLE, G_MAXDOUBLE, + 0, + G_PARAM_READWRITE)); + g_object_class_install_property (object_class, + PROP_FTS_RANK, + g_param_spec_double ("fts-rank", + NULL, + NULL, + -G_MAXDOUBLE, G_MAXDOUBLE, + 0, + G_PARAM_READWRITE)); + g_object_class_install_property (object_class, + PROP_FTS_SNIPPET, + g_param_spec_string ("fts-snippet", + "fts-snippet", + "fts-snippet", + NULL, + G_PARAM_READWRITE)); +} + +static void +nautilus_search_hit_init (NautilusSearchHit *hit) +{ + hit = G_TYPE_INSTANCE_GET_PRIVATE (hit, + NAUTILUS_TYPE_SEARCH_HIT, + NautilusSearchHit); +} + +NautilusSearchHit * +nautilus_search_hit_new (const char *uri) +{ + NautilusSearchHit *hit; + + hit = g_object_new (NAUTILUS_TYPE_SEARCH_HIT, + "uri", uri, + NULL); + + return hit; +} diff --git a/src/nautilus-search-hit.h b/src/nautilus-search-hit.h new file mode 100644 index 0000000..ec07465 --- /dev/null +++ b/src/nautilus-search-hit.h @@ -0,0 +1,50 @@ +/* + * Copyright (C) 2012 Red Hat, Inc. + * + * Nautilus is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License as + * published by the Free Software Foundation; either version 2 of the + * License, or (at your option) any later version. + * + * Nautilus is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public + * License along with this program; see the file COPYING. If not, + * see . + * + */ + +#pragma once + +#include +#include "nautilus-query.h" + +G_BEGIN_DECLS + +#define NAUTILUS_TYPE_SEARCH_HIT (nautilus_search_hit_get_type ()) + +G_DECLARE_FINAL_TYPE (NautilusSearchHit, nautilus_search_hit, NAUTILUS, SEARCH_HIT, GObject); + +NautilusSearchHit * nautilus_search_hit_new (const char *uri); + +void nautilus_search_hit_set_fts_rank (NautilusSearchHit *hit, + gdouble fts_rank); +void nautilus_search_hit_set_modification_time (NautilusSearchHit *hit, + GDateTime *date); +void nautilus_search_hit_set_access_time (NautilusSearchHit *hit, + GDateTime *date); +void nautilus_search_hit_set_creation_time (NautilusSearchHit *hit, + GDateTime *date); +void nautilus_search_hit_set_fts_snippet (NautilusSearchHit *hit, + const gchar *snippet); +void nautilus_search_hit_compute_scores (NautilusSearchHit *hit, + NautilusQuery *query); + +const char * nautilus_search_hit_get_uri (NautilusSearchHit *hit); +gdouble nautilus_search_hit_get_relevance (NautilusSearchHit *hit); +const gchar * nautilus_search_hit_get_fts_snippet (NautilusSearchHit *hit); + +G_END_DECLS diff --git a/src/nautilus-search-popover.c b/src/nautilus-search-popover.c new file mode 100644 index 0000000..e8e3136 --- /dev/null +++ b/src/nautilus-search-popover.c @@ -0,0 +1,1092 @@ +/* nautilus-search-popover.c + * + * Copyright (C) 2015 Georges Basile Stavracas Neto + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License 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 warranty of + * MERCHANTABILITY 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 . + */ + +#include "nautilus-enum-types.h" +#include "nautilus-search-popover.h" +#include "nautilus-mime-actions.h" + +#include +#include "nautilus-file.h" +#include "nautilus-ui-utilities.h" +#include "nautilus-global-preferences.h" + + #define SEARCH_FILTER_MAX_YEARS 5 + +struct _NautilusSearchPopover +{ + GtkPopover parent; + + GtkWidget *around_revealer; + GtkWidget *around_stack; + GtkWidget *calendar; + GtkWidget *clear_date_button; + GtkWidget *dates_listbox; + GtkWidget *date_entry; + GtkWidget *date_stack; + GtkWidget *select_date_button; + GtkWidget *select_date_button_label; + GtkWidget *type_label; + GtkWidget *type_listbox; + GtkWidget *type_stack; + GtkWidget *last_used_button; + GtkWidget *last_modified_button; + GtkWidget *created_button; + GtkWidget *full_text_search_button; + GtkWidget *filename_search_button; + + NautilusQuery *query; + GtkSingleSelection *other_types_model; + + gboolean fts_enabled; +}; + +static void show_date_selection_widgets (NautilusSearchPopover *popover, + gboolean visible); + +static void show_other_types_dialog (NautilusSearchPopover *popover); + +static void update_date_label (NautilusSearchPopover *popover, + GPtrArray *date_range); + +G_DEFINE_TYPE (NautilusSearchPopover, nautilus_search_popover, GTK_TYPE_POPOVER) + +enum +{ + PROP_0, + PROP_QUERY, + PROP_FTS_ENABLED, + LAST_PROP +}; + +enum +{ + MIME_TYPE, + TIME_TYPE, + DATE_RANGE, + LAST_SIGNAL +}; + +static guint signals[LAST_SIGNAL]; + + +/* Callbacks */ + +static void +calendar_day_selected (GtkCalendar *calendar, + NautilusSearchPopover *popover) +{ + GDateTime *date; + GPtrArray *date_range; + + date = gtk_calendar_get_date (calendar); + + date_range = g_ptr_array_new_full (2, (GDestroyNotify) g_date_time_unref); + g_ptr_array_add (date_range, g_date_time_ref (date)); + g_ptr_array_add (date_range, g_date_time_ref (date)); + update_date_label (popover, date_range); + g_signal_emit_by_name (popover, "date-range", date_range); + + g_ptr_array_unref (date_range); + g_date_time_unref (date); +} + +/* Range on dates are partially implemented. For now just use it for differentation + * between a exact day or a range of a first day until now. + */ +static void +setup_date (NautilusSearchPopover *popover, + NautilusQuery *query) +{ + GPtrArray *date_range; + GDateTime *date_initial; + + date_range = nautilus_query_get_date_range (query); + + if (date_range) + { + date_initial = g_ptr_array_index (date_range, 0); + + g_signal_handlers_block_by_func (popover->calendar, calendar_day_selected, popover); + + gtk_calendar_select_day (GTK_CALENDAR (popover->calendar), date_initial); + + update_date_label (popover, date_range); + + g_signal_handlers_unblock_by_func (popover->calendar, calendar_day_selected, popover); + } +} + +static void +query_date_changed (GObject *object, + GParamSpec *pspec, + NautilusSearchPopover *popover) +{ + setup_date (popover, NAUTILUS_QUERY (object)); +} + +static void +clear_date_button_clicked (GtkButton *button, + NautilusSearchPopover *popover) +{ + nautilus_search_popover_reset_date_range (popover); +} + +static void +date_entry_activate (GtkEntry *entry, + NautilusSearchPopover *popover) +{ + if (gtk_entry_get_text_length (entry) > 0) + { + GDateTime *now; + GDateTime *date_time; + GDate *date; + + date = g_date_new (); + g_date_set_parse (date, gtk_editable_get_text (GTK_EDITABLE (entry))); + + /* Invalid date silently does nothing */ + if (!g_date_valid (date)) + { + g_date_free (date); + return; + } + + now = g_date_time_new_now_local (); + date_time = g_date_time_new_local (g_date_get_year (date), + g_date_get_month (date), + g_date_get_day (date), + 0, + 0, + 0); + + /* Future dates also silently fails */ + if (g_date_time_compare (date_time, now) != 1) + { + GPtrArray *date_range; + + date_range = g_ptr_array_new_full (2, (GDestroyNotify) g_date_time_unref); + g_ptr_array_add (date_range, g_date_time_ref (date_time)); + g_ptr_array_add (date_range, g_date_time_ref (date_time)); + update_date_label (popover, date_range); + show_date_selection_widgets (popover, FALSE); + g_signal_emit_by_name (popover, "date-range", date_range); + + g_ptr_array_unref (date_range); + } + + g_date_time_unref (now); + g_date_time_unref (date_time); + g_date_free (date); + } +} + +static void +dates_listbox_row_activated (GtkListBox *listbox, + GtkListBoxRow *row, + NautilusSearchPopover *popover) +{ + GDateTime *date; + GDateTime *now; + GPtrArray *date_range = NULL; + + now = g_date_time_new_now_local (); + date = g_object_get_data (G_OBJECT (row), "date"); + if (date) + { + date_range = g_ptr_array_new_full (2, (GDestroyNotify) g_date_time_unref); + g_ptr_array_add (date_range, g_date_time_ref (date)); + g_ptr_array_add (date_range, g_date_time_ref (now)); + } + update_date_label (popover, date_range); + show_date_selection_widgets (popover, FALSE); + g_signal_emit_by_name (popover, "date-range", date_range); + + if (date_range) + { + g_ptr_array_unref (date_range); + } + g_date_time_unref (now); +} + +static void +listbox_header_func (GtkListBoxRow *row, + GtkListBoxRow *before, + NautilusSearchPopover *popover) +{ + gboolean show_separator; + + show_separator = GPOINTER_TO_INT (g_object_get_data (G_OBJECT (row), "show-separator")); + + if (show_separator) + { + GtkWidget *separator; + + separator = gtk_separator_new (GTK_ORIENTATION_HORIZONTAL); + gtk_widget_show (separator); + + gtk_list_box_row_set_header (row, separator); + } +} + +static void +select_date_button_clicked (GtkButton *button, + NautilusSearchPopover *popover) +{ + /* Hide the type selection widgets when date selection + * widgets are shown. + */ + gtk_stack_set_visible_child_name (GTK_STACK (popover->type_stack), "type-button"); + + show_date_selection_widgets (popover, TRUE); +} + +static void +select_type_button_clicked (GtkButton *button, + NautilusSearchPopover *popover) +{ + GtkListBoxRow *selected_row; + + selected_row = gtk_list_box_get_selected_row (GTK_LIST_BOX (popover->type_listbox)); + + gtk_stack_set_visible_child_name (GTK_STACK (popover->type_stack), "type-list"); + if (selected_row != NULL) + { + gtk_widget_grab_focus (GTK_WIDGET (selected_row)); + } + + /* Hide the date selection widgets when the type selection + * listbox is shown. + */ + show_date_selection_widgets (popover, FALSE); +} + +static void +toggle_calendar_icon_clicked (GtkEntry *entry, + GtkEntryIconPosition position, + NautilusSearchPopover *popover) +{ + const gchar *current_visible_child; + const gchar *child; + const gchar *icon_name; + const gchar *tooltip; + + current_visible_child = gtk_stack_get_visible_child_name (GTK_STACK (popover->around_stack)); + + if (g_strcmp0 (current_visible_child, "date-list") == 0) + { + child = "date-calendar"; + icon_name = "view-list-symbolic"; + tooltip = _("Show a list to select the date"); + } + else + { + child = "date-list"; + icon_name = "x-office-calendar-symbolic"; + tooltip = _("Show a calendar to select the date"); + } + + gtk_stack_set_visible_child_name (GTK_STACK (popover->around_stack), child); + gtk_entry_set_icon_from_icon_name (entry, GTK_ENTRY_ICON_SECONDARY, icon_name); + gtk_entry_set_icon_tooltip_text (entry, GTK_ENTRY_ICON_SECONDARY, tooltip); +} + +static void +types_listbox_row_activated (GtkListBox *listbox, + GtkListBoxRow *row, + NautilusSearchPopover *popover) +{ + gint group; + + group = GPOINTER_TO_INT (g_object_get_data (G_OBJECT (row), "mimetype-group")); + + /* The -1 group stands for the "Other Types" group, for which + * we should show the mimetype dialog. + */ + if (group == -1) + { + show_other_types_dialog (popover); + } + else + { + gtk_label_set_label (GTK_LABEL (popover->type_label), + nautilus_mime_types_group_get_name (group)); + + g_signal_emit_by_name (popover, "mime-type", group, NULL); + } + + gtk_stack_set_visible_child_name (GTK_STACK (popover->type_stack), "type-button"); +} + +static void +search_time_type_changed (GtkCheckButton *button, + NautilusSearchPopover *popover) +{ + NautilusQuerySearchType type = -1; + + if (gtk_check_button_get_active (GTK_CHECK_BUTTON (popover->last_modified_button))) + { + type = NAUTILUS_QUERY_SEARCH_TYPE_LAST_MODIFIED; + } + else if (gtk_check_button_get_active (GTK_CHECK_BUTTON (popover->last_used_button))) + { + type = NAUTILUS_QUERY_SEARCH_TYPE_LAST_ACCESS; + } + else + { + type = NAUTILUS_QUERY_SEARCH_TYPE_CREATED; + } + + g_settings_set_enum (nautilus_preferences, "search-filter-time-type", type); + + g_signal_emit_by_name (popover, "time-type", type, NULL); +} + +static void +search_fts_mode_changed (GtkToggleButton *button, + NautilusSearchPopover *popover) +{ + if (gtk_toggle_button_get_active (GTK_TOGGLE_BUTTON (popover->full_text_search_button)) && + popover->fts_enabled == FALSE) + { + popover->fts_enabled = TRUE; + g_settings_set_boolean (nautilus_preferences, NAUTILUS_PREFERENCES_FTS_ENABLED, TRUE); + g_object_notify (G_OBJECT (popover), "fts-enabled"); + } + else if (gtk_toggle_button_get_active (GTK_TOGGLE_BUTTON (popover->filename_search_button)) && + popover->fts_enabled == TRUE) + { + popover->fts_enabled = FALSE; + g_settings_set_boolean (nautilus_preferences, NAUTILUS_PREFERENCES_FTS_ENABLED, FALSE); + g_object_notify (G_OBJECT (popover), "fts-enabled"); + } +} + +/* Auxiliary methods */ + +static GtkWidget * +create_row_for_label (const gchar *text, + gboolean show_separator) +{ + GtkWidget *row; + GtkWidget *label; + + row = gtk_list_box_row_new (); + + g_object_set_data (G_OBJECT (row), "show-separator", GINT_TO_POINTER (show_separator)); + + label = g_object_new (GTK_TYPE_LABEL, + "label", text, + "hexpand", TRUE, + "xalign", 0.0, + "margin-start", 6, + NULL); + + gtk_list_box_row_set_child (GTK_LIST_BOX_ROW (row), label); + gtk_widget_show (row); + + return row; +} + +static void +fill_fuzzy_dates_listbox (NautilusSearchPopover *popover) +{ + GDateTime *maximum_dt, *now; + GtkWidget *row; + GDateTime *current_date; + GPtrArray *date_range; + gint days, max_days; + + days = 1; + maximum_dt = g_date_time_new_from_unix_local (0); + now = g_date_time_new_now_local (); + max_days = SEARCH_FILTER_MAX_YEARS * 365; + + /* Add the no date filter element first */ + row = create_row_for_label (_("Any time"), TRUE); + gtk_list_box_insert (GTK_LIST_BOX (popover->dates_listbox), row, -1); + + /* This is a tricky loop. The main intention here is that each + * timeslice (day, week, month) have 2 or 3 entries. + * + * For the first appearance of each timeslice, there is made a + * check in order to be sure that there is no offset added to days. + */ + while (days <= max_days) + { + gchar *label; + gint normalized; + gint step; + + if (days < 7) + { + /* days */ + normalized = days; + step = 2; + } + else if (days < 30) + { + /* weeks */ + normalized = days / 7; + if (normalized == 1) + { + days = 7; + } + step = 7; + } + else if (days < 365) + { + /* months */ + normalized = days / 30; + if (normalized == 1) + { + days = 30; + } + step = 90; + } + else + { + /* years */ + normalized = days / 365; + if (normalized == 1) + { + days = 365; + } + step = 365; + } + + current_date = g_date_time_add_days (now, -days); + date_range = g_ptr_array_new_full (2, (GDestroyNotify) g_date_time_unref); + g_ptr_array_add (date_range, g_date_time_ref (current_date)); + g_ptr_array_add (date_range, g_date_time_ref (now)); + label = get_text_for_date_range (date_range, FALSE); + row = create_row_for_label (label, normalized == 1); + g_object_set_data_full (G_OBJECT (row), + "date", + g_date_time_ref (current_date), + (GDestroyNotify) g_date_time_unref); + + gtk_list_box_insert (GTK_LIST_BOX (popover->dates_listbox), row, -1); + + g_free (label); + g_date_time_unref (current_date); + g_ptr_array_unref (date_range); + + days += step; + } + + g_date_time_unref (maximum_dt); + g_date_time_unref (now); +} + +static void +fill_types_listbox (NautilusSearchPopover *popover) +{ + GtkWidget *row; + int i; + gint n_groups; + + n_groups = nautilus_mime_types_get_number_of_groups (); + /* Mimetypes */ + for (i = 0; i < n_groups; i++) + { + /* On the third row, which is right below "Folders", there should be an + * separator to logically group the types. + */ + row = create_row_for_label (nautilus_mime_types_group_get_name (i), i == 3); + g_object_set_data (G_OBJECT (row), "mimetype-group", GINT_TO_POINTER (i)); + + gtk_list_box_insert (GTK_LIST_BOX (popover->type_listbox), row, -1); + } + + /* Other types */ + row = create_row_for_label (_("Other Type…"), TRUE); + g_object_set_data (G_OBJECT (row), "mimetype-group", GINT_TO_POINTER (-1)); + gtk_list_box_insert (GTK_LIST_BOX (popover->type_listbox), row, -1); +} + +static void +show_date_selection_widgets (NautilusSearchPopover *popover, + gboolean visible) +{ + gtk_stack_set_visible_child_name (GTK_STACK (popover->date_stack), + visible ? "date-entry" : "date-button"); + gtk_stack_set_visible_child_name (GTK_STACK (popover->around_stack), "date-list"); + gtk_entry_set_icon_from_icon_name (GTK_ENTRY (popover->date_entry), + GTK_ENTRY_ICON_SECONDARY, + "x-office-calendar-symbolic"); + + gtk_widget_set_visible (popover->around_revealer, visible); + + gtk_revealer_set_reveal_child (GTK_REVEALER (popover->around_revealer), visible); +} + +static void +on_other_types_dialog_response (GtkDialog *dialog, + gint response_id, + NautilusSearchPopover *popover) +{ + if (response_id == GTK_RESPONSE_OK) + { + GtkStringObject *item; + const char *mimetype; + g_autofree gchar *description = NULL; + + item = gtk_single_selection_get_selected_item (popover->other_types_model); + mimetype = gtk_string_object_get_string (item); + description = g_content_type_get_description (mimetype); + + gtk_label_set_label (GTK_LABEL (popover->type_label), description); + + g_signal_emit_by_name (popover, "mime-type", -1, mimetype); + + gtk_stack_set_visible_child_name (GTK_STACK (popover->type_stack), "type-button"); + } + + g_clear_object (&popover->other_types_model); + gtk_window_destroy (GTK_WINDOW (dialog)); +} + +static void +on_other_types_activate (GtkListView *self, + guint position, + gpointer user_data) +{ + GtkDialog *dialog = GTK_DIALOG (user_data); + + gtk_dialog_response (dialog, GTK_RESPONSE_OK); +} + +static void +on_other_types_bind (GtkSignalListItemFactory *factory, + GtkListItem *listitem, + gpointer user_data) +{ + GtkLabel *label; + GtkStringObject *item; + g_autofree gchar *description = NULL; + + label = GTK_LABEL (gtk_list_item_get_child (listitem)); + item = GTK_STRING_OBJECT (gtk_list_item_get_item (listitem)); + + description = g_content_type_get_description (gtk_string_object_get_string (item)); + gtk_label_set_text (label, description); +} + +static void +on_other_types_setup (GtkSignalListItemFactory *factory, + GtkListItem *listitem, + gpointer user_data) +{ + GtkWidget *label; + + label = gtk_label_new (NULL); + gtk_widget_set_halign (label, GTK_ALIGN_START); + gtk_widget_set_margin_start (label, 12); + gtk_widget_set_margin_end (label, 12); + gtk_widget_set_margin_top (label, 6); + gtk_widget_set_margin_bottom (label, 6); + gtk_list_item_set_child (listitem, label); +} + +static gchar * +join_type_and_description (GtkStringObject *object, + gpointer user_data) +{ + const gchar *content_type = gtk_string_object_get_string (object); + g_autofree gchar *description = g_content_type_get_description (content_type); + + return g_strdup_printf ("%s %s", content_type, description); +} + +static void +show_other_types_dialog (NautilusSearchPopover *popover) +{ + GtkStringList *string_model; + GtkStringSorter *sorter; + GtkSortListModel *sort_model; + GtkStringFilter *filter; + GtkFilterListModel *filter_model; + g_autoptr (GList) mime_infos = NULL; + GtkWidget *dialog; + GtkWidget *content_area; + GtkWidget *search_entry; + GtkWidget *scrolled; + GtkListItemFactory *factory; + GtkWidget *listview; + GtkRoot *toplevel; + + string_model = gtk_string_list_new (NULL); + mime_infos = g_content_types_get_registered (); + for (GList *l = mime_infos; l != NULL; l = l->next) + { + gtk_string_list_append (string_model, l->data); + } + sorter = gtk_string_sorter_new (gtk_property_expression_new (GTK_TYPE_STRING_OBJECT, NULL, "string")); + sort_model = gtk_sort_list_model_new (G_LIST_MODEL (string_model), GTK_SORTER (sorter)); + filter = gtk_string_filter_new (gtk_cclosure_expression_new (G_TYPE_STRING, + NULL, 0, NULL, + G_CALLBACK (join_type_and_description), + NULL, NULL)); + filter_model = gtk_filter_list_model_new (G_LIST_MODEL (sort_model), GTK_FILTER (filter)); + popover->other_types_model = gtk_single_selection_new (G_LIST_MODEL (filter_model)); + + toplevel = gtk_widget_get_root (GTK_WIDGET (popover)); + dialog = gtk_dialog_new_with_buttons (_("Select type"), + GTK_WINDOW (toplevel), + GTK_DIALOG_MODAL | GTK_DIALOG_DESTROY_WITH_PARENT | GTK_DIALOG_USE_HEADER_BAR, + _("_Cancel"), GTK_RESPONSE_CANCEL, + _("Select"), GTK_RESPONSE_OK, + NULL); + gtk_dialog_set_default_response (GTK_DIALOG (dialog), GTK_RESPONSE_OK); + gtk_window_set_default_size (GTK_WINDOW (dialog), 400, 600); + + /* If there are 0 results, make action insensitive */ + g_object_bind_property (filter_model, + "n-items", + gtk_dialog_get_widget_for_response (GTK_DIALOG (dialog), GTK_RESPONSE_OK), + "sensitive", + G_BINDING_DEFAULT); + + content_area = gtk_dialog_get_content_area (GTK_DIALOG (dialog)); + + search_entry = gtk_search_entry_new (); + gtk_search_entry_set_key_capture_widget (GTK_SEARCH_ENTRY (search_entry), content_area); + g_object_bind_property (search_entry, "text", filter, "search", G_BINDING_SYNC_CREATE); + gtk_box_append (GTK_BOX (content_area), search_entry); + gtk_widget_set_margin_start (search_entry, 12); + gtk_widget_set_margin_end (search_entry, 12); + gtk_widget_set_margin_top (search_entry, 6); + gtk_widget_set_margin_bottom (search_entry, 6); + + gtk_box_append (GTK_BOX (content_area), gtk_separator_new (GTK_ORIENTATION_VERTICAL)); + + scrolled = gtk_scrolled_window_new (); + gtk_scrolled_window_set_policy (GTK_SCROLLED_WINDOW (scrolled), + GTK_POLICY_AUTOMATIC, + GTK_POLICY_AUTOMATIC); + + gtk_widget_set_vexpand (scrolled, TRUE); + gtk_box_append (GTK_BOX (content_area), scrolled); + + factory = gtk_signal_list_item_factory_new (); + g_signal_connect (factory, "setup", G_CALLBACK (on_other_types_setup), NULL); + g_signal_connect (factory, "bind", G_CALLBACK (on_other_types_bind), NULL); + + listview = gtk_list_view_new (GTK_SELECTION_MODEL (g_object_ref (popover->other_types_model)), + factory); + g_signal_connect (listview, "activate", G_CALLBACK (on_other_types_activate), dialog); + + gtk_scrolled_window_set_child (GTK_SCROLLED_WINDOW (scrolled), listview); + + g_signal_connect (dialog, "response", G_CALLBACK (on_other_types_dialog_response), popover); + gtk_widget_show (dialog); +} + +static void +update_date_label (NautilusSearchPopover *popover, + GPtrArray *date_range) +{ + if (date_range) + { + gint days; + GDateTime *initial_date; + GDateTime *end_date; + GDateTime *now; + gchar *label; + + now = g_date_time_new_now_local (); + initial_date = g_ptr_array_index (date_range, 0); + end_date = g_ptr_array_index (date_range, 0); + days = g_date_time_difference (end_date, initial_date) / G_TIME_SPAN_DAY; + + label = get_text_for_date_range (date_range, TRUE); + + gtk_editable_set_text (GTK_EDITABLE (popover->date_entry), days < 1 ? label : ""); + + gtk_widget_show (popover->clear_date_button); + gtk_label_set_label (GTK_LABEL (popover->select_date_button_label), label); + + g_date_time_unref (now); + g_free (label); + } + else + { + gtk_label_set_label (GTK_LABEL (popover->select_date_button_label), + _("Select Dates…")); + gtk_editable_set_text (GTK_EDITABLE (popover->date_entry), ""); + gtk_widget_hide (popover->clear_date_button); + } +} + +void +nautilus_search_popover_set_fts_sensitive (NautilusSearchPopover *popover, + gboolean sensitive) +{ + gtk_widget_set_sensitive (popover->full_text_search_button, sensitive); + gtk_widget_set_sensitive (popover->filename_search_button, sensitive); +} + +static void +nautilus_search_popover_closed (GtkPopover *popover) +{ + NautilusSearchPopover *self = NAUTILUS_SEARCH_POPOVER (popover); + GDateTime *now; + + /* Always switch back to the initial states */ + gtk_stack_set_visible_child_name (GTK_STACK (self->type_stack), "type-button"); + show_date_selection_widgets (self, FALSE); + + /* If we're closing an ongoing query, the popover must not + * clear the current settings. + */ + if (self->query) + { + return; + } + + now = g_date_time_new_now_local (); + + /* Reselect today at the calendar */ + g_signal_handlers_block_by_func (self->calendar, calendar_day_selected, self); + + gtk_calendar_select_day (GTK_CALENDAR (self->calendar), now); + + g_signal_handlers_unblock_by_func (self->calendar, calendar_day_selected, self); +} + +static void +nautilus_search_popover_get_property (GObject *object, + guint prop_id, + GValue *value, + GParamSpec *pspec) +{ + NautilusSearchPopover *self; + + self = NAUTILUS_SEARCH_POPOVER (object); + + switch (prop_id) + { + case PROP_QUERY: + { + g_value_set_object (value, self->query); + } + break; + + case PROP_FTS_ENABLED: + { + g_value_set_boolean (value, self->fts_enabled); + } + break; + + default: + { + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); + } + } +} + +static void +nautilus_search_popover_set_property (GObject *object, + guint prop_id, + const GValue *value, + GParamSpec *pspec) +{ + NautilusSearchPopover *self; + + self = NAUTILUS_SEARCH_POPOVER (object); + + switch (prop_id) + { + case PROP_QUERY: + { + nautilus_search_popover_set_query (self, g_value_get_object (value)); + } + break; + + case PROP_FTS_ENABLED: + { + self->fts_enabled = g_value_get_boolean (value); + } + break; + + default: + { + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); + } + } +} + + +static void +nautilus_search_popover_class_init (NautilusSearchPopoverClass *klass) +{ + GtkPopoverClass *popover_class = GTK_POPOVER_CLASS (klass); + GtkWidgetClass *widget_class = GTK_WIDGET_CLASS (klass); + GObjectClass *object_class = G_OBJECT_CLASS (klass); + + object_class->get_property = nautilus_search_popover_get_property; + object_class->set_property = nautilus_search_popover_set_property; + + popover_class->closed = nautilus_search_popover_closed; + + signals[DATE_RANGE] = g_signal_new ("date-range", + NAUTILUS_TYPE_SEARCH_POPOVER, + G_SIGNAL_RUN_LAST, + 0, + NULL, + NULL, + g_cclosure_marshal_generic, + G_TYPE_NONE, + 1, + G_TYPE_PTR_ARRAY); + + signals[MIME_TYPE] = g_signal_new ("mime-type", + NAUTILUS_TYPE_SEARCH_POPOVER, + G_SIGNAL_RUN_LAST, + 0, + NULL, + NULL, + g_cclosure_marshal_generic, + G_TYPE_NONE, + 2, + G_TYPE_INT, + G_TYPE_STRING); + + signals[TIME_TYPE] = g_signal_new ("time-type", + NAUTILUS_TYPE_SEARCH_POPOVER, + G_SIGNAL_RUN_LAST, + 0, + NULL, + NULL, + g_cclosure_marshal_generic, + G_TYPE_NONE, + 1, + G_TYPE_INT); + + /** + * NautilusSearchPopover::query: + * + * The current #NautilusQuery being edited. + */ + g_object_class_install_property (object_class, + PROP_QUERY, + g_param_spec_object ("query", + "Query of the popover", + "The current query being edited", + NAUTILUS_TYPE_QUERY, + G_PARAM_READWRITE)); + + g_object_class_install_property (object_class, + PROP_FTS_ENABLED, + g_param_spec_boolean ("fts-enabled", + "fts enabled", + "fts enabled", + FALSE, + G_PARAM_READWRITE)); + + gtk_widget_class_set_template_from_resource (widget_class, "/org/gnome/nautilus/ui/nautilus-search-popover.ui"); + + gtk_widget_class_bind_template_child (widget_class, NautilusSearchPopover, around_revealer); + gtk_widget_class_bind_template_child (widget_class, NautilusSearchPopover, around_stack); + gtk_widget_class_bind_template_child (widget_class, NautilusSearchPopover, clear_date_button); + gtk_widget_class_bind_template_child (widget_class, NautilusSearchPopover, calendar); + gtk_widget_class_bind_template_child (widget_class, NautilusSearchPopover, dates_listbox); + gtk_widget_class_bind_template_child (widget_class, NautilusSearchPopover, date_entry); + gtk_widget_class_bind_template_child (widget_class, NautilusSearchPopover, date_stack); + gtk_widget_class_bind_template_child (widget_class, NautilusSearchPopover, select_date_button); + gtk_widget_class_bind_template_child (widget_class, NautilusSearchPopover, select_date_button_label); + gtk_widget_class_bind_template_child (widget_class, NautilusSearchPopover, type_label); + gtk_widget_class_bind_template_child (widget_class, NautilusSearchPopover, type_listbox); + gtk_widget_class_bind_template_child (widget_class, NautilusSearchPopover, type_stack); + gtk_widget_class_bind_template_child (widget_class, NautilusSearchPopover, last_used_button); + gtk_widget_class_bind_template_child (widget_class, NautilusSearchPopover, last_modified_button); + gtk_widget_class_bind_template_child (widget_class, NautilusSearchPopover, created_button); + gtk_widget_class_bind_template_child (widget_class, NautilusSearchPopover, full_text_search_button); + gtk_widget_class_bind_template_child (widget_class, NautilusSearchPopover, filename_search_button); + + gtk_widget_class_bind_template_callback (widget_class, calendar_day_selected); + gtk_widget_class_bind_template_callback (widget_class, clear_date_button_clicked); + gtk_widget_class_bind_template_callback (widget_class, date_entry_activate); + gtk_widget_class_bind_template_callback (widget_class, dates_listbox_row_activated); + gtk_widget_class_bind_template_callback (widget_class, select_date_button_clicked); + gtk_widget_class_bind_template_callback (widget_class, select_type_button_clicked); + gtk_widget_class_bind_template_callback (widget_class, toggle_calendar_icon_clicked); + gtk_widget_class_bind_template_callback (widget_class, types_listbox_row_activated); + gtk_widget_class_bind_template_callback (widget_class, search_time_type_changed); + gtk_widget_class_bind_template_callback (widget_class, search_fts_mode_changed); +} + +static void +nautilus_search_popover_init (NautilusSearchPopover *self) +{ + NautilusQuerySearchType filter_time_type; + + gtk_widget_init_template (GTK_WIDGET (self)); + + /* Fuzzy dates listbox */ + gtk_list_box_set_header_func (GTK_LIST_BOX (self->dates_listbox), + (GtkListBoxUpdateHeaderFunc) listbox_header_func, + self, + NULL); + + fill_fuzzy_dates_listbox (self); + + /* Types listbox */ + gtk_list_box_set_header_func (GTK_LIST_BOX (self->type_listbox), + (GtkListBoxUpdateHeaderFunc) listbox_header_func, + self, + NULL); + + fill_types_listbox (self); + + gtk_list_box_select_row (GTK_LIST_BOX (self->type_listbox), + gtk_list_box_get_row_at_index (GTK_LIST_BOX (self->type_listbox), 0)); + + filter_time_type = g_settings_get_enum (nautilus_preferences, "search-filter-time-type"); + if (filter_time_type == NAUTILUS_QUERY_SEARCH_TYPE_LAST_MODIFIED) + { + gtk_check_button_set_active (GTK_CHECK_BUTTON (self->last_modified_button), TRUE); + gtk_check_button_set_active (GTK_CHECK_BUTTON (self->last_used_button), FALSE); + gtk_check_button_set_active (GTK_CHECK_BUTTON (self->created_button), FALSE); + } + else if (filter_time_type == NAUTILUS_QUERY_SEARCH_TYPE_LAST_ACCESS) + { + gtk_check_button_set_active (GTK_CHECK_BUTTON (self->last_modified_button), FALSE); + gtk_check_button_set_active (GTK_CHECK_BUTTON (self->last_used_button), TRUE); + gtk_check_button_set_active (GTK_CHECK_BUTTON (self->created_button), FALSE); + } + else + { + gtk_check_button_set_active (GTK_CHECK_BUTTON (self->last_modified_button), FALSE); + gtk_check_button_set_active (GTK_CHECK_BUTTON (self->last_used_button), FALSE); + gtk_check_button_set_active (GTK_CHECK_BUTTON (self->created_button), TRUE); + } + + self->fts_enabled = g_settings_get_boolean (nautilus_preferences, + NAUTILUS_PREFERENCES_FTS_ENABLED); + if (self->fts_enabled) + { + gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (self->full_text_search_button), TRUE); + } + else + { + gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (self->filename_search_button), TRUE); + } +} + +GtkWidget * +nautilus_search_popover_new (void) +{ + return g_object_new (NAUTILUS_TYPE_SEARCH_POPOVER, NULL); +} + +/** + * nautilus_search_popover_get_query: + * @popover: a #NautilusSearchPopover + * + * Gets the current query for @popover. + * + * Returns: (transfer none): the current #NautilusQuery from @popover. + */ +NautilusQuery * +nautilus_search_popover_get_query (NautilusSearchPopover *popover) +{ + g_return_val_if_fail (NAUTILUS_IS_SEARCH_POPOVER (popover), NULL); + + return popover->query; +} + +/** + * nautilus_search_popover_set_query: + * @popover: a #NautilusSearchPopover + * @query (nullable): a #NautilusQuery + * + * Sets the current query for @popover. + * + * Returns: + */ +void +nautilus_search_popover_set_query (NautilusSearchPopover *popover, + NautilusQuery *query) +{ + NautilusQuery *previous_query; + + g_return_if_fail (NAUTILUS_IS_SEARCH_POPOVER (popover)); + + previous_query = popover->query; + + if (popover->query != query) + { + /* Disconnect signals and bindings from the old query */ + if (previous_query) + { + g_signal_handlers_disconnect_by_func (previous_query, query_date_changed, popover); + } + + g_set_object (&popover->query, query); + + if (query) + { + /* Date */ + setup_date (popover, query); + + g_signal_connect (query, + "notify::date", + G_CALLBACK (query_date_changed), + popover); + } + else + { + nautilus_search_popover_reset_mime_types (popover); + nautilus_search_popover_reset_date_range (popover); + } + } +} + +void +nautilus_search_popover_reset_mime_types (NautilusSearchPopover *popover) +{ + g_return_if_fail (NAUTILUS_IS_SEARCH_POPOVER (popover)); + + gtk_list_box_select_row (GTK_LIST_BOX (popover->type_listbox), + gtk_list_box_get_row_at_index (GTK_LIST_BOX (popover->type_listbox), 0)); + + gtk_label_set_label (GTK_LABEL (popover->type_label), + nautilus_mime_types_group_get_name (0)); + g_signal_emit_by_name (popover, "mime-type", 0, NULL); + gtk_stack_set_visible_child_name (GTK_STACK (popover->type_stack), "type-button"); +} + +void +nautilus_search_popover_reset_date_range (NautilusSearchPopover *popover) +{ + g_return_if_fail (NAUTILUS_IS_SEARCH_POPOVER (popover)); + + gtk_list_box_select_row (GTK_LIST_BOX (popover->dates_listbox), + gtk_list_box_get_row_at_index (GTK_LIST_BOX (popover->dates_listbox), 0)); + + update_date_label (popover, NULL); + show_date_selection_widgets (popover, FALSE); + g_signal_emit_by_name (popover, "date-range", NULL); +} + +gboolean +nautilus_search_popover_get_fts_enabled (NautilusSearchPopover *popover) +{ + return popover->fts_enabled; +} diff --git a/src/nautilus-search-popover.h b/src/nautilus-search-popover.h new file mode 100644 index 0000000..2137592 --- /dev/null +++ b/src/nautilus-search-popover.h @@ -0,0 +1,52 @@ +/* nautilus-search-popover.h + * + * Copyright (C) 2015 Georges Basile Stavracas Neto + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License 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 warranty of + * MERCHANTABILITY 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 . + */ + +#pragma once + +#include +#include + +#include "nautilus-query.h" + +G_BEGIN_DECLS + +typedef enum { + NAUTILUS_SEARCH_FILTER_CONTENT, /* Full text or filename */ + NAUTILUS_SEARCH_FILTER_DATE, /* When */ + NAUTILUS_SEARCH_FILTER_LAST, /* Last modified or last used */ + NAUTILUS_SEARCH_FILTER_TYPE /* What */ +} NautilusSearchFilter; + +#define NAUTILUS_TYPE_SEARCH_POPOVER (nautilus_search_popover_get_type()) + +G_DECLARE_FINAL_TYPE (NautilusSearchPopover, nautilus_search_popover, NAUTILUS, SEARCH_POPOVER, GtkPopover) + +GtkWidget* nautilus_search_popover_new (void); + +NautilusQuery* nautilus_search_popover_get_query (NautilusSearchPopover *popover); + +void nautilus_search_popover_set_query (NautilusSearchPopover *popover, + NautilusQuery *query); +void nautilus_search_popover_reset_date_range (NautilusSearchPopover *popover); +void nautilus_search_popover_reset_mime_types (NautilusSearchPopover *popover); + +gboolean nautilus_search_popover_get_fts_enabled (NautilusSearchPopover *popover); +void nautilus_search_popover_set_fts_sensitive (NautilusSearchPopover *popover, + gboolean sensitive); + +G_END_DECLS \ No newline at end of file diff --git a/src/nautilus-search-provider.c b/src/nautilus-search-provider.c new file mode 100644 index 0000000..9a4a655 --- /dev/null +++ b/src/nautilus-search-provider.c @@ -0,0 +1,145 @@ +/* + * Copyright (C) 2012 Red Hat, Inc. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Library General Public + * License as published by the Free Software Foundation; either + * version 2 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Library General Public License for more details. + * + * You should have received a copy of the GNU Library General Public + * License along with this library; if not, see . + * + */ + +#include +#include "nautilus-search-provider.h" +#include "nautilus-enum-types.h" + +#include + +enum +{ + HITS_ADDED, + FINISHED, + ERROR, + LAST_SIGNAL +}; + +static guint signals[LAST_SIGNAL]; + +G_DEFINE_INTERFACE (NautilusSearchProvider, nautilus_search_provider, G_TYPE_OBJECT) + +static void +nautilus_search_provider_default_init (NautilusSearchProviderInterface *iface) +{ + /** + * NautilusSearchProvider::running: + * + * Whether the provider is running a search. + */ + g_object_interface_install_property (iface, + g_param_spec_boolean ("running", + "Whether the provider is running", + "Whether the provider is running a search", + FALSE, + G_PARAM_READABLE)); + + signals[HITS_ADDED] = g_signal_new ("hits-added", + NAUTILUS_TYPE_SEARCH_PROVIDER, + G_SIGNAL_RUN_LAST, + G_STRUCT_OFFSET (NautilusSearchProviderInterface, hits_added), + NULL, NULL, + g_cclosure_marshal_VOID__POINTER, + G_TYPE_NONE, 1, + G_TYPE_POINTER); + + signals[FINISHED] = g_signal_new ("finished", + NAUTILUS_TYPE_SEARCH_PROVIDER, + G_SIGNAL_RUN_LAST, + G_STRUCT_OFFSET (NautilusSearchProviderInterface, finished), + NULL, NULL, + g_cclosure_marshal_VOID__ENUM, + G_TYPE_NONE, 1, + NAUTILUS_TYPE_SEARCH_PROVIDER_STATUS); + + signals[ERROR] = g_signal_new ("error", + NAUTILUS_TYPE_SEARCH_PROVIDER, + G_SIGNAL_RUN_LAST, + G_STRUCT_OFFSET (NautilusSearchProviderInterface, error), + NULL, NULL, + g_cclosure_marshal_VOID__STRING, + G_TYPE_NONE, 1, + G_TYPE_STRING); +} + +void +nautilus_search_provider_set_query (NautilusSearchProvider *provider, + NautilusQuery *query) +{ + g_return_if_fail (NAUTILUS_IS_SEARCH_PROVIDER (provider)); + g_return_if_fail (NAUTILUS_SEARCH_PROVIDER_GET_IFACE (provider)->set_query != NULL); + g_return_if_fail (NAUTILUS_IS_QUERY (query)); + + NAUTILUS_SEARCH_PROVIDER_GET_IFACE (provider)->set_query (provider, query); +} + +void +nautilus_search_provider_start (NautilusSearchProvider *provider) +{ + g_return_if_fail (NAUTILUS_IS_SEARCH_PROVIDER (provider)); + g_return_if_fail (NAUTILUS_SEARCH_PROVIDER_GET_IFACE (provider)->start != NULL); + + NAUTILUS_SEARCH_PROVIDER_GET_IFACE (provider)->start (provider); +} + +void +nautilus_search_provider_stop (NautilusSearchProvider *provider) +{ + g_return_if_fail (NAUTILUS_IS_SEARCH_PROVIDER (provider)); + g_return_if_fail (NAUTILUS_SEARCH_PROVIDER_GET_IFACE (provider)->stop != NULL); + + NAUTILUS_SEARCH_PROVIDER_GET_IFACE (provider)->stop (provider); +} + +void +nautilus_search_provider_hits_added (NautilusSearchProvider *provider, + GList *hits) +{ + g_return_if_fail (NAUTILUS_IS_SEARCH_PROVIDER (provider)); + + g_signal_emit (provider, signals[HITS_ADDED], 0, hits); +} + +void +nautilus_search_provider_finished (NautilusSearchProvider *provider, + NautilusSearchProviderStatus status) +{ + g_return_if_fail (NAUTILUS_IS_SEARCH_PROVIDER (provider)); + + g_signal_emit (provider, signals[FINISHED], 0, status); +} + +void +nautilus_search_provider_error (NautilusSearchProvider *provider, + const char *error_message) +{ + g_return_if_fail (NAUTILUS_IS_SEARCH_PROVIDER (provider)); + + g_warning ("Provider %s failed with error %s\n", + G_OBJECT_TYPE_NAME (provider), error_message); + g_signal_emit (provider, signals[ERROR], 0, error_message); +} + +gboolean +nautilus_search_provider_is_running (NautilusSearchProvider *provider) +{ + g_return_val_if_fail (NAUTILUS_IS_SEARCH_PROVIDER (provider), FALSE); + g_return_val_if_fail (NAUTILUS_SEARCH_PROVIDER_GET_IFACE (provider)->is_running, FALSE); + + return NAUTILUS_SEARCH_PROVIDER_GET_IFACE (provider)->is_running (provider); +} diff --git a/src/nautilus-search-provider.h b/src/nautilus-search-provider.h new file mode 100644 index 0000000..a1ff6f3 --- /dev/null +++ b/src/nautilus-search-provider.h @@ -0,0 +1,101 @@ +/* + * Copyright (C) 2012 Red Hat, Inc. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Library General Public + * License as published by the Free Software Foundation; either + * version 2 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Library General Public License for more details. + * + * You should have received a copy of the GNU Library General Public + * License along with this library; if not, see . + */ + +#pragma once + +#include +#include "nautilus-query.h" +#include "nautilus-search-hit.h" + +G_BEGIN_DECLS + +typedef enum { + NAUTILUS_SEARCH_PROVIDER_STATUS_NORMAL, + NAUTILUS_SEARCH_PROVIDER_STATUS_RESTARTING +} NautilusSearchProviderStatus; + +typedef enum { + NAUTILUS_SEARCH_ENGINE_ALL_ENGINES, + NAUTILUS_SEARCH_ENGINE_TRACKER_ENGINE, + NAUTILUS_SEARCH_ENGINE_RECENT_ENGINE, + NAUTILUS_SEARCH_ENGINE_MODEL_ENGINE, + NAUTILUS_SEARCH_ENGINE_SIMPLE_ENGINE, +} NautilusSearchEngineTarget; + +#define NAUTILUS_TYPE_SEARCH_PROVIDER (nautilus_search_provider_get_type ()) + +G_DECLARE_INTERFACE (NautilusSearchProvider, nautilus_search_provider, NAUTILUS, SEARCH_PROVIDER, GObject) + +struct _NautilusSearchProviderInterface { + GTypeInterface g_iface; + + /* VTable */ + void (*set_query) (NautilusSearchProvider *provider, NautilusQuery *query); + void (*start) (NautilusSearchProvider *provider); + void (*stop) (NautilusSearchProvider *provider); + + /* Signals */ + void (*hits_added) (NautilusSearchProvider *provider, GList *hits); + /* This signal has a status parameter because it's necesary to discern + * when the search engine finished normally or wheter it finished in a + * different situation that will cause the engine to do some action after + * finishing. + * + * For example, the search engine restarts itself if the client starts a + * new search before all the search providers finished its current ongoing search. + * + * A real use case of this is when the user change quickly the query of the search, + * the search engine stops all the search providers, but given that each search + * provider has its own thread it will be actually stopped in a unknown time. + * To fix that, the search engine marks itself for restarting if the client + * starts a new search and not all providers finished. Then it will emit + * its finished signal and restart all providers with the new search. + * + * That can cause that when the search engine emits its finished signal, + * it actually relates to old searchs that it stopped and not the one + * the client started lately. + * The client doesn't have a way to know wheter the finished signal + * relates to its current search or with an old search. + * + * To fix this situation, provide with the signal a status parameter, that + * provides a hint of how the search engine stopped or if it is going to realize + * some action afterwards, like restarting. + */ + void (*finished) (NautilusSearchProvider *provider, + NautilusSearchProviderStatus status); + void (*error) (NautilusSearchProvider *provider, const char *error_message); + gboolean (*is_running) (NautilusSearchProvider *provider); +}; + +GType nautilus_search_provider_get_type (void) G_GNUC_CONST; + +/* Interface Functions */ +void nautilus_search_provider_set_query (NautilusSearchProvider *provider, + NautilusQuery *query); +void nautilus_search_provider_start (NautilusSearchProvider *provider); +void nautilus_search_provider_stop (NautilusSearchProvider *provider); + +void nautilus_search_provider_hits_added (NautilusSearchProvider *provider, + GList *hits); +void nautilus_search_provider_finished (NautilusSearchProvider *provider, + NautilusSearchProviderStatus status); +void nautilus_search_provider_error (NautilusSearchProvider *provider, + const char *error_message); + +gboolean nautilus_search_provider_is_running (NautilusSearchProvider *provider); + +G_END_DECLS \ No newline at end of file diff --git a/src/nautilus-self-check-functions.c b/src/nautilus-self-check-functions.c new file mode 100644 index 0000000..6de4d70 --- /dev/null +++ b/src/nautilus-self-check-functions.c @@ -0,0 +1,37 @@ +/* + * Nautilus + * + * Copyright (C) 1999, 2000 Eazel, Inc. + * + * Nautilus is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License as + * published by the Free Software Foundation; either version 2 of the + * License, or (at your option) any later version. + * + * Nautilus is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * 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 . + * + * Author: Darin Adler + */ + +/* nautilus-self-check-functions.c: Wrapper for all self check functions + * in Nautilus proper. + */ + +#include + +#if !defined (NAUTILUS_OMIT_SELF_CHECK) + +#include "nautilus-self-check-functions.h" + +void nautilus_run_self_checks(void) +{ + NAUTILUS_FOR_EACH_SELF_CHECK_FUNCTION (NAUTILUS_CALL_SELF_CHECK_FUNCTION) +} + +#endif /* ! NAUTILUS_OMIT_SELF_CHECK */ diff --git a/src/nautilus-self-check-functions.h b/src/nautilus-self-check-functions.h new file mode 100644 index 0000000..ccbea39 --- /dev/null +++ b/src/nautilus-self-check-functions.h @@ -0,0 +1,46 @@ + +/* + * Nautilus + * + * Copyright (C) 1999, 2000 Eazel, Inc. + * + * Nautilus is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License as + * published by the Free Software Foundation; either version 2 of the + * License, or (at your option) any later version. + * + * Nautilus is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * 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 . + * + * Author: Darin Adler + */ + +/* nautilus-self-check-functions.h: Wrapper and prototypes for all self + * check functions in Nautilus proper. + */ + +#pragma once + +void nautilus_run_self_checks (void); + +/* Putting the prototypes for these self-check functions in each + header file for the files they are defined in would make compiling + the self-check framework take way too long (since one file would + have to include everything). + + So we put the list of functions here instead. + + Instead of just putting prototypes here, we put this macro that + can be used to do operations on the whole list of functions. +*/ + +#define NAUTILUS_FOR_EACH_SELF_CHECK_FUNCTION(macro) \ +/* Add new self-check functions to the list above this line. */ + +/* Generate prototypes for all the functions. */ +NAUTILUS_FOR_EACH_SELF_CHECK_FUNCTION (NAUTILUS_SELF_CHECK_FUNCTION_PROTOTYPE) diff --git a/src/nautilus-shell-search-provider.c b/src/nautilus-shell-search-provider.c new file mode 100644 index 0000000..55e9e1a --- /dev/null +++ b/src/nautilus-shell-search-provider.c @@ -0,0 +1,875 @@ +/* + * nautilus-shell-search-provider.c - Implementation of a GNOME Shell + * search provider + * + * Copyright (C) 2012 Red Hat, Inc. + * + * Nautilus is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License as + * published by the Free Software Foundation; either version 2 of the + * License, or (at your option) any later version. + * + * Nautilus is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * 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 . + * + * Authors: Cosimo Cecchi + * + */ + +#include + +#include +#include +#include +#include + +#include "nautilus-file.h" +#include "nautilus-file-utilities.h" +#include "nautilus-search-engine.h" +#include "nautilus-search-provider.h" +#include "nautilus-ui-utilities.h" + +#include "nautilus-application.h" +#include "nautilus-bookmark-list.h" +#include "nautilus-shell-search-provider-generated.h" +#include "nautilus-shell-search-provider.h" + +typedef struct +{ + NautilusShellSearchProvider *self; + + NautilusSearchEngine *engine; + NautilusQuery *query; + + GHashTable *hits; + GDBusMethodInvocation *invocation; + + gint64 start_time; +} PendingSearch; + +struct _NautilusShellSearchProvider +{ + GObject parent; + + NautilusShellSearchProvider2 *skeleton; + + PendingSearch *current_search; + + GList *metas_requests; + GHashTable *metas_cache; +}; + +G_DEFINE_TYPE (NautilusShellSearchProvider, nautilus_shell_search_provider, G_TYPE_OBJECT) + +static gchar * +get_display_name (NautilusShellSearchProvider *self, + NautilusFile *file) +{ + GFile *location; + NautilusBookmark *bookmark; + NautilusBookmarkList *bookmarks; + + bookmarks = nautilus_application_get_bookmarks (NAUTILUS_APPLICATION (g_application_get_default ())); + + location = nautilus_file_get_location (file); + bookmark = nautilus_bookmark_list_item_with_location (bookmarks, location, NULL); + g_object_unref (location); + + if (bookmark) + { + return g_strdup (nautilus_bookmark_get_name (bookmark)); + } + else + { + return nautilus_file_get_display_name (file); + } +} + +static GIcon * +get_gicon (NautilusShellSearchProvider *self, + NautilusFile *file) +{ + GFile *location; + NautilusBookmark *bookmark; + NautilusBookmarkList *bookmarks; + + bookmarks = nautilus_application_get_bookmarks (NAUTILUS_APPLICATION (g_application_get_default ())); + + location = nautilus_file_get_location (file); + bookmark = nautilus_bookmark_list_item_with_location (bookmarks, location, NULL); + g_object_unref (location); + + if (bookmark) + { + return nautilus_bookmark_get_icon (bookmark); + } + else + { + return nautilus_file_get_gicon (file, 0); + } +} + +static void +pending_search_free (PendingSearch *search) +{ + g_hash_table_destroy (search->hits); + g_clear_object (&search->query); + g_clear_object (&search->engine); + g_clear_object (&search->invocation); + + g_slice_free (PendingSearch, search); +} + +static void +pending_search_finish (PendingSearch *search, + GDBusMethodInvocation *invocation, + GVariant *result) +{ + NautilusShellSearchProvider *self = search->self; + + g_dbus_method_invocation_return_value (invocation, result); + + if (search == self->current_search) + { + self->current_search = NULL; + } + + g_application_release (g_application_get_default ()); + pending_search_free (search); +} + +static void +cancel_current_search (NautilusShellSearchProvider *self) +{ + if (self->current_search != NULL) + { + g_debug ("*** Cancel current search"); + nautilus_search_provider_stop (NAUTILUS_SEARCH_PROVIDER (self->current_search->engine)); + } +} + +static void +cancel_current_search_ignoring_partial_results (NautilusShellSearchProvider *self) +{ + if (self->current_search != NULL) + { + cancel_current_search (self); + g_signal_handlers_disconnect_by_data (G_OBJECT (self->current_search->engine), + self->current_search); + pending_search_finish (self->current_search, self->current_search->invocation, + g_variant_new ("(as)", NULL)); + } +} + +static void +search_hits_added_cb (NautilusSearchEngine *engine, + GList *hits, + gpointer user_data) +{ + PendingSearch *search = user_data; + GList *l; + NautilusSearchHit *hit; + const gchar *hit_uri; + + g_debug ("*** Search engine hits added"); + + for (l = hits; l != NULL; l = l->next) + { + hit = l->data; + nautilus_search_hit_compute_scores (hit, search->query); + hit_uri = nautilus_search_hit_get_uri (hit); + g_debug (" %s", hit_uri); + + g_hash_table_replace (search->hits, g_strdup (hit_uri), g_object_ref (hit)); + } +} + +static gint +search_hit_compare_relevance (gconstpointer a, + gconstpointer b) +{ + NautilusSearchHit *hit_a, *hit_b; + gdouble relevance_a, relevance_b; + + hit_a = NAUTILUS_SEARCH_HIT ((gpointer) a); + hit_b = NAUTILUS_SEARCH_HIT ((gpointer) b); + + relevance_a = nautilus_search_hit_get_relevance (hit_a); + relevance_b = nautilus_search_hit_get_relevance (hit_b); + + if (relevance_a > relevance_b) + { + return -1; + } + else if (relevance_a == relevance_b) + { + return 0; + } + + return 1; +} + +static void +search_finished_cb (NautilusSearchEngine *engine, + NautilusSearchProviderStatus status, + gpointer user_data) +{ + PendingSearch *search = user_data; + GList *hits, *l; + NautilusSearchHit *hit; + GVariantBuilder builder; + gint64 current_time; + + current_time = g_get_monotonic_time (); + g_debug ("*** Search engine search finished - time elapsed %dms", + (gint) ((current_time - search->start_time) / 1000)); + + hits = g_hash_table_get_values (search->hits); + hits = g_list_sort (hits, search_hit_compare_relevance); + + g_variant_builder_init (&builder, G_VARIANT_TYPE ("as")); + + for (l = hits; l != NULL; l = l->next) + { + hit = l->data; + g_variant_builder_add (&builder, "s", nautilus_search_hit_get_uri (hit)); + } + + g_list_free (hits); + pending_search_finish (search, search->invocation, + g_variant_new ("(as)", &builder)); +} + +static void +search_error_cb (NautilusSearchEngine *engine, + const gchar *error_message, + gpointer user_data) +{ + NautilusShellSearchProvider *self = user_data; + PendingSearch *search = self->current_search; + + g_debug ("*** Search engine search error"); + pending_search_finish (search, search->invocation, + g_variant_new ("(as)", NULL)); +} + +typedef struct +{ + gchar *uri; + gchar *string_for_compare; +} SearchHitCandidate; + +static void +search_hit_candidate_free (SearchHitCandidate *candidate) +{ + g_free (candidate->uri); + g_free (candidate->string_for_compare); + + g_slice_free (SearchHitCandidate, candidate); +} + +static SearchHitCandidate * +search_hit_candidate_new (const gchar *uri, + const gchar *name) +{ + SearchHitCandidate *candidate = g_slice_new0 (SearchHitCandidate); + + candidate->uri = g_strdup (uri); + candidate->string_for_compare = g_strdup (name); + + return candidate; +} + +static void +search_add_volumes_and_bookmarks (PendingSearch *search) +{ + NautilusSearchHit *hit; + NautilusBookmark *bookmark; + const gchar *name; + gchar *string, *uri; + gdouble match; + GList *l, *m, *drives, *volumes, *mounts, *mounts_to_check, *candidates; + GDrive *drive; + GVolume *volume; + GMount *mount; + GFile *location; + SearchHitCandidate *candidate; + NautilusBookmarkList *bookmarks; + GList *all_bookmarks; + GVolumeMonitor *volume_monitor; + + bookmarks = nautilus_application_get_bookmarks (NAUTILUS_APPLICATION (g_application_get_default ())); + all_bookmarks = nautilus_bookmark_list_get_all (bookmarks); + volume_monitor = g_volume_monitor_get (); + candidates = NULL; + + /* first add bookmarks */ + for (l = all_bookmarks; l != NULL; l = l->next) + { + bookmark = NAUTILUS_BOOKMARK (l->data); + name = nautilus_bookmark_get_name (bookmark); + if (name == NULL) + { + continue; + } + + uri = nautilus_bookmark_get_uri (bookmark); + candidate = search_hit_candidate_new (uri, name); + candidates = g_list_prepend (candidates, candidate); + + g_free (uri); + } + + /* home dir */ + uri = nautilus_get_home_directory_uri (); + candidate = search_hit_candidate_new (uri, _("Home")); + candidates = g_list_prepend (candidates, candidate); + g_free (uri); + + /* trash */ + candidate = search_hit_candidate_new ("trash:///", _("Trash")); + candidates = g_list_prepend (candidates, candidate); + + /* now add mounts */ + mounts_to_check = NULL; + + /* first check all connected drives */ + drives = g_volume_monitor_get_connected_drives (volume_monitor); + for (l = drives; l != NULL; l = l->next) + { + drive = l->data; + volumes = g_drive_get_volumes (drive); + + for (m = volumes; m != NULL; m = m->next) + { + volume = m->data; + mount = g_volume_get_mount (volume); + if (mount != NULL) + { + mounts_to_check = g_list_prepend (mounts_to_check, mount); + } + } + + g_list_free_full (volumes, g_object_unref); + } + g_list_free_full (drives, g_object_unref); + + /* then volumes that don't have a drive */ + volumes = g_volume_monitor_get_volumes (volume_monitor); + for (l = volumes; l != NULL; l = l->next) + { + volume = l->data; + drive = g_volume_get_drive (volume); + + if (drive == NULL) + { + mount = g_volume_get_mount (volume); + if (mount != NULL) + { + mounts_to_check = g_list_prepend (mounts_to_check, mount); + } + } + g_clear_object (&drive); + } + g_list_free_full (volumes, g_object_unref); + + /* then mounts that have no volume */ + mounts = g_volume_monitor_get_mounts (volume_monitor); + for (l = mounts; l != NULL; l = l->next) + { + mount = l->data; + + if (g_mount_is_shadowed (mount)) + { + continue; + } + + volume = g_mount_get_volume (mount); + if (volume == NULL) + { + mounts_to_check = g_list_prepend (mounts_to_check, g_object_ref (mount)); + } + g_clear_object (&volume); + } + g_list_free_full (mounts, g_object_unref); + + /* actually add mounts to candidates */ + for (l = mounts_to_check; l != NULL; l = l->next) + { + mount = l->data; + + string = g_mount_get_name (mount); + if (string == NULL) + { + continue; + } + + location = g_mount_get_default_location (mount); + uri = g_file_get_uri (location); + candidate = search_hit_candidate_new (uri, string); + candidates = g_list_prepend (candidates, candidate); + + g_free (uri); + g_free (string); + g_object_unref (location); + } + g_list_free_full (mounts_to_check, g_object_unref); + + /* now do the actual string matching */ + candidates = g_list_reverse (candidates); + + for (l = candidates; l != NULL; l = l->next) + { + candidate = l->data; + match = nautilus_query_matches_string (search->query, + candidate->string_for_compare); + + if (match > -1) + { + hit = nautilus_search_hit_new (candidate->uri); + nautilus_search_hit_set_fts_rank (hit, match); + nautilus_search_hit_compute_scores (hit, search->query); + g_hash_table_replace (search->hits, g_strdup (candidate->uri), hit); + } + } + g_list_free_full (candidates, (GDestroyNotify) search_hit_candidate_free); + g_object_unref (volume_monitor); +} + +static NautilusQuery * +shell_query_new (gchar **terms) +{ + NautilusQuery *query; + g_autoptr (GFile) home = NULL; + g_autofree gchar *terms_joined = NULL; + + terms_joined = g_strjoinv (" ", terms); + home = g_file_new_for_path (g_get_home_dir ()); + + query = nautilus_query_new (); + nautilus_query_set_text (query, terms_joined); + nautilus_query_set_location (query, home); + + return query; +} + +static void +execute_search (NautilusShellSearchProvider *self, + GDBusMethodInvocation *invocation, + gchar **terms) +{ + NautilusQuery *query; + PendingSearch *pending_search; + + cancel_current_search (self); + + /* don't attempt searches for a single character */ + if (g_strv_length (terms) == 1 && + g_utf8_strlen (terms[0], -1) == 1) + { + g_dbus_method_invocation_return_value (invocation, g_variant_new ("(as)", NULL)); + return; + } + + query = shell_query_new (terms); + nautilus_query_set_recursive (query, NAUTILUS_QUERY_RECURSIVE_INDEXED_ONLY); + nautilus_query_set_show_hidden_files (query, FALSE); + + pending_search = g_slice_new0 (PendingSearch); + pending_search->invocation = g_object_ref (invocation); + pending_search->hits = g_hash_table_new_full (g_str_hash, g_str_equal, g_free, g_object_unref); + pending_search->query = query; + pending_search->engine = nautilus_search_engine_new (); + pending_search->start_time = g_get_monotonic_time (); + pending_search->self = self; + + g_signal_connect (pending_search->engine, "hits-added", + G_CALLBACK (search_hits_added_cb), pending_search); + g_signal_connect (pending_search->engine, "finished", + G_CALLBACK (search_finished_cb), pending_search); + g_signal_connect (pending_search->engine, "error", + G_CALLBACK (search_error_cb), pending_search); + + self->current_search = pending_search; + g_application_hold (g_application_get_default ()); + + search_add_volumes_and_bookmarks (pending_search); + + /* start searching */ + g_debug ("*** Search engine search started"); + nautilus_search_provider_set_query (NAUTILUS_SEARCH_PROVIDER (pending_search->engine), + query); + nautilus_search_provider_start (NAUTILUS_SEARCH_PROVIDER (pending_search->engine)); +} + +static gboolean +handle_get_initial_result_set (NautilusShellSearchProvider2 *skeleton, + GDBusMethodInvocation *invocation, + gchar **terms, + gpointer user_data) +{ + NautilusShellSearchProvider *self = user_data; + + g_debug ("****** GetInitialResultSet"); + execute_search (self, invocation, terms); + return TRUE; +} + +static gboolean +handle_get_subsearch_result_set (NautilusShellSearchProvider2 *skeleton, + GDBusMethodInvocation *invocation, + gchar **previous_results, + gchar **terms, + gpointer user_data) +{ + NautilusShellSearchProvider *self = user_data; + + g_debug ("****** GetSubSearchResultSet"); + execute_search (self, invocation, terms); + return TRUE; +} + +typedef struct +{ + NautilusShellSearchProvider *self; + + gint64 start_time; + NautilusFileListHandle *handle; + GDBusMethodInvocation *invocation; + + gchar **uris; +} ResultMetasData; + +static void +result_metas_data_free (ResultMetasData *data) +{ + g_clear_pointer (&data->handle, nautilus_file_list_cancel_call_when_ready); + g_clear_object (&data->self); + g_clear_object (&data->invocation); + g_strfreev (data->uris); + + g_slice_free (ResultMetasData, data); +} + +static void +result_metas_return_from_cache (ResultMetasData *data) +{ + GVariantBuilder builder; + GVariant *meta; + gint64 current_time; + gint idx; + + g_variant_builder_init (&builder, G_VARIANT_TYPE ("aa{sv}")); + + if (data->uris) + { + for (idx = 0; data->uris[idx] != NULL; idx++) + { + meta = g_hash_table_lookup (data->self->metas_cache, + data->uris[idx]); + g_variant_builder_add_value (&builder, meta); + } + } + + current_time = g_get_monotonic_time (); + g_debug ("*** GetResultMetas completed - time elapsed %dms", + (gint) ((current_time - data->start_time) / 1000)); + + g_dbus_method_invocation_return_value (data->invocation, + g_variant_new ("(aa{sv})", &builder)); +} + +static void +result_metas_return_empty (ResultMetasData *data) +{ + g_clear_pointer (&data->uris, g_strfreev); + result_metas_return_from_cache (data); + result_metas_data_free (data); +} + +static void +cancel_result_meta_requests (NautilusShellSearchProvider *self) +{ + g_debug ("*** Cancel Results Meta requests"); + + g_list_free_full (self->metas_requests, + (GDestroyNotify) result_metas_return_empty); + self->metas_requests = NULL; +} + +static void +result_list_attributes_ready_cb (GList *file_list, + gpointer user_data) +{ + ResultMetasData *data = user_data; + GVariantBuilder meta; + NautilusFile *file; + GFile *file_location; + GList *l; + gchar *uri, *display_name; + gchar *path, *description; + gchar *thumbnail_path; + GIcon *gicon; + GFile *location; + GVariant *meta_variant; + gint icon_scale; + + icon_scale = gdk_monitor_get_scale_factor (g_list_model_get_item (gdk_display_get_monitors (gdk_display_get_default ()), 0)); + + for (l = file_list; l != NULL; l = l->next) + { + file = l->data; + g_variant_builder_init (&meta, G_VARIANT_TYPE ("a{sv}")); + + uri = nautilus_file_get_uri (file); + display_name = get_display_name (data->self, file); + file_location = nautilus_file_get_location (file); + path = g_file_get_path (file_location); + description = path ? g_path_get_dirname (path) : NULL; + + g_variant_builder_add (&meta, "{sv}", + "id", g_variant_new_string (uri)); + g_variant_builder_add (&meta, "{sv}", + "name", g_variant_new_string (display_name)); + /* Some backends like trash:/// don't have a path, so we show the uri itself. */ + g_variant_builder_add (&meta, "{sv}", + "description", g_variant_new_string (description ? description : uri)); + + gicon = NULL; + thumbnail_path = nautilus_file_get_thumbnail_path (file); + + if (thumbnail_path != NULL) + { + location = g_file_new_for_path (thumbnail_path); + gicon = g_file_icon_new (location); + + g_free (thumbnail_path); + g_object_unref (location); + } + else + { + gicon = get_gicon (data->self, file); + } + + if (gicon == NULL) + { + gicon = G_ICON (nautilus_file_get_icon_texture (file, 128, + icon_scale, + NAUTILUS_FILE_ICON_FLAGS_USE_THUMBNAILS)); + } + + g_variant_builder_add (&meta, "{sv}", + "icon", g_icon_serialize (gicon)); + g_object_unref (gicon); + + meta_variant = g_variant_builder_end (&meta); + g_hash_table_insert (data->self->metas_cache, + g_strdup (uri), g_variant_ref_sink (meta_variant)); + + g_free (display_name); + g_free (path); + g_free (description); + g_free (uri); + } + + data->handle = NULL; + data->self->metas_requests = g_list_remove (data->self->metas_requests, data); + + result_metas_return_from_cache (data); + result_metas_data_free (data); +} + +static gboolean +handle_get_result_metas (NautilusShellSearchProvider2 *skeleton, + GDBusMethodInvocation *invocation, + gchar **results, + gpointer user_data) +{ + NautilusShellSearchProvider *self = user_data; + GList *missing_files = NULL; + const gchar *uri; + ResultMetasData *data; + gint idx; + + g_debug ("****** GetResultMetas"); + + for (idx = 0; results[idx] != NULL; idx++) + { + uri = results[idx]; + + if (!g_hash_table_lookup (self->metas_cache, uri)) + { + missing_files = g_list_prepend (missing_files, nautilus_file_get_by_uri (uri)); + } + } + + data = g_slice_new0 (ResultMetasData); + data->self = g_object_ref (self); + data->invocation = g_object_ref (invocation); + data->start_time = g_get_monotonic_time (); + data->uris = g_strdupv (results); + + if (missing_files == NULL) + { + result_metas_return_from_cache (data); + result_metas_data_free (data); + return TRUE; + } + + nautilus_file_list_call_when_ready (missing_files, + NAUTILUS_FILE_ATTRIBUTES_FOR_ICON, + &data->handle, + result_list_attributes_ready_cb, + data); + self->metas_requests = g_list_prepend (self->metas_requests, data); + nautilus_file_list_free (missing_files); + return TRUE; +} + +typedef struct +{ + GFile *file; + NautilusShellSearchProvider2 *skeleton; + GDBusMethodInvocation *invocation; +} ShowURIData; + +static void +show_uri_callback (GObject *source_object, + GAsyncResult *result, + gpointer user_data) +{ + ShowURIData *data = user_data; + + if (!gtk_show_uri_full_finish (NULL, result, NULL)) + { + g_application_open (g_application_get_default (), &data->file, 1, ""); + } + + nautilus_shell_search_provider2_complete_activate_result (data->skeleton, data->invocation); + + g_object_unref (data->file); + g_free (data); +} + +static gboolean +handle_activate_result (NautilusShellSearchProvider2 *skeleton, + GDBusMethodInvocation *invocation, + gchar *result, + gchar **terms, + guint32 timestamp, + gpointer user_data) +{ + ShowURIData *data; + + data = g_new (ShowURIData, 1); + data->file = g_file_new_for_uri (result); + data->skeleton = skeleton; + data->invocation = invocation; + + gtk_show_uri_full (NULL, result, timestamp, NULL, show_uri_callback, data); + + return TRUE; +} + +static gboolean +handle_launch_search (NautilusShellSearchProvider2 *skeleton, + GDBusMethodInvocation *invocation, + gchar **terms, + guint32 timestamp, + gpointer user_data) +{ + GApplication *app = g_application_get_default (); + g_autoptr (NautilusQuery) query = shell_query_new (terms); + NautilusQueryRecursive recursive = location_settings_search_get_recursive (); + + /* + * If no recursive search is enabled, we still want to be able to + * show the same results we presented in the overview when nautilus + * is explicitly launched to access to more results, and thus we perform + * a query showing results coming from index-based search engines. + * Otherwise we respect the global setting for recursivity. + */ + if (recursive == NAUTILUS_QUERY_RECURSIVE_NEVER) + { + nautilus_query_set_recursive (query, + NAUTILUS_QUERY_RECURSIVE_INDEXED_ONLY); + } + else + { + nautilus_query_set_recursive (query, recursive); + } + + nautilus_application_search (NAUTILUS_APPLICATION (app), query); + + nautilus_shell_search_provider2_complete_launch_search (skeleton, invocation); + return TRUE; +} + +static void +search_provider_dispose (GObject *obj) +{ + NautilusShellSearchProvider *self = NAUTILUS_SHELL_SEARCH_PROVIDER (obj); + + g_clear_object (&self->skeleton); + g_hash_table_destroy (self->metas_cache); + cancel_current_search_ignoring_partial_results (self); + cancel_result_meta_requests (self); + + G_OBJECT_CLASS (nautilus_shell_search_provider_parent_class)->dispose (obj); +} + +static void +nautilus_shell_search_provider_init (NautilusShellSearchProvider *self) +{ + self->metas_cache = g_hash_table_new_full (g_str_hash, g_str_equal, + g_free, (GDestroyNotify) g_variant_unref); + + self->skeleton = nautilus_shell_search_provider2_skeleton_new (); + + g_signal_connect (self->skeleton, "handle-get-initial-result-set", + G_CALLBACK (handle_get_initial_result_set), self); + g_signal_connect (self->skeleton, "handle-get-subsearch-result-set", + G_CALLBACK (handle_get_subsearch_result_set), self); + g_signal_connect (self->skeleton, "handle-get-result-metas", + G_CALLBACK (handle_get_result_metas), self); + g_signal_connect (self->skeleton, "handle-activate-result", + G_CALLBACK (handle_activate_result), self); + g_signal_connect (self->skeleton, "handle-launch-search", + G_CALLBACK (handle_launch_search), self); +} + +static void +nautilus_shell_search_provider_class_init (NautilusShellSearchProviderClass *klass) +{ + GObjectClass *oclass = G_OBJECT_CLASS (klass); + + oclass->dispose = search_provider_dispose; +} + +NautilusShellSearchProvider * +nautilus_shell_search_provider_new (void) +{ + return g_object_new (nautilus_shell_search_provider_get_type (), + NULL); +} + +gboolean +nautilus_shell_search_provider_register (NautilusShellSearchProvider *self, + GDBusConnection *connection, + GError **error) +{ + return g_dbus_interface_skeleton_export (G_DBUS_INTERFACE_SKELETON (self->skeleton), + connection, + "/org/gnome/Nautilus" PROFILE "/SearchProvider", error); +} + +void +nautilus_shell_search_provider_unregister (NautilusShellSearchProvider *self) +{ + g_dbus_interface_skeleton_unexport (G_DBUS_INTERFACE_SKELETON (self->skeleton)); +} diff --git a/src/nautilus-shell-search-provider.h b/src/nautilus-shell-search-provider.h new file mode 100644 index 0000000..8eaa675 --- /dev/null +++ b/src/nautilus-shell-search-provider.h @@ -0,0 +1,39 @@ +/* + * nautilus-shell-search-provider.h - Implementation of a GNOME Shell + * search provider + * + * Copyright (C) 2012 Red Hat, Inc. + * + * Nautilus is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License as + * published by the Free Software Foundation; either version 2 of the + * License, or (at your option) any later version. + * + * Nautilus is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * 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 . + * + * Authors: Cosimo Cecchi + * + */ + +#pragma once + +#define NAUTILUS_TYPE_SHELL_SEARCH_PROVIDER nautilus_shell_search_provider_get_type() +#define NAUTILUS_SHELL_SEARCH_PROVIDER(obj) \ + (G_TYPE_CHECK_INSTANCE_CAST ((obj), NAUTILUS_TYPE_SHELL_SEARCH_PROVIDER, NautilusShellSearchProvider)) + +typedef struct _NautilusShellSearchProvider NautilusShellSearchProvider; +typedef GObjectClass NautilusShellSearchProviderClass; + +GType nautilus_shell_search_provider_get_type (void); +NautilusShellSearchProvider * nautilus_shell_search_provider_new (void); + +gboolean nautilus_shell_search_provider_register (NautilusShellSearchProvider *self, + GDBusConnection *connection, + GError **error); +void nautilus_shell_search_provider_unregister (NautilusShellSearchProvider *self); \ No newline at end of file diff --git a/src/nautilus-signaller.c b/src/nautilus-signaller.c new file mode 100644 index 0000000..4020cb2 --- /dev/null +++ b/src/nautilus-signaller.c @@ -0,0 +1,93 @@ +/* + * Nautilus + * + * Copyright (C) 1999, 2000 Eazel, Inc. + * + * Nautilus is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 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 warranty of + * MERCHANTABILITY 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 . + * + * Author: John Sullivan + */ + +/* nautilus-signaller.h: Class to manage nautilus-wide signals that don't + * correspond to any particular object. + */ + +#include +#include "nautilus-signaller.h" + +#include + +typedef GObject NautilusSignaller; +typedef GObjectClass NautilusSignallerClass; + +enum +{ + HISTORY_LIST_CHANGED, + POPUP_MENU_CHANGED, + MIME_DATA_CHANGED, + LAST_SIGNAL +}; + +static guint signals[LAST_SIGNAL]; + +static GType nautilus_signaller_get_type (void); + +G_DEFINE_TYPE (NautilusSignaller, nautilus_signaller, G_TYPE_OBJECT); + +GObject * +nautilus_signaller_get_current (void) +{ + static GObject *global_signaller = NULL; + + if (global_signaller == NULL) + { + global_signaller = g_object_new (nautilus_signaller_get_type (), NULL); + } + + return global_signaller; +} + +static void +nautilus_signaller_init (NautilusSignaller *signaller) +{ +} + +static void +nautilus_signaller_class_init (NautilusSignallerClass *class) +{ + signals[HISTORY_LIST_CHANGED] = + g_signal_new ("history-list-changed", + G_TYPE_FROM_CLASS (class), + G_SIGNAL_RUN_LAST, + 0, + NULL, NULL, + g_cclosure_marshal_VOID__VOID, + G_TYPE_NONE, 0); + signals[POPUP_MENU_CHANGED] = + g_signal_new ("popup-menu-changed", + G_TYPE_FROM_CLASS (class), + G_SIGNAL_RUN_LAST, + 0, + NULL, NULL, + g_cclosure_marshal_VOID__VOID, + G_TYPE_NONE, 0); + signals[MIME_DATA_CHANGED] = + g_signal_new ("mime-data-changed", + G_TYPE_FROM_CLASS (class), + G_SIGNAL_RUN_LAST, + 0, + NULL, NULL, + g_cclosure_marshal_VOID__VOID, + G_TYPE_NONE, 0); +} diff --git a/src/nautilus-signaller.h b/src/nautilus-signaller.h new file mode 100644 index 0000000..df9aeea --- /dev/null +++ b/src/nautilus-signaller.h @@ -0,0 +1,41 @@ + +/* + * Nautilus + * + * Copyright (C) 1999, 2000 Eazel, Inc. + * + * Nautilus is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 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 warranty of + * MERCHANTABILITY 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 . + * + * Author: John Sullivan + */ + +/* nautilus-signaller.h: Class to manage nautilus-wide signals that don't + * correspond to any particular object. + */ + +#pragma once + +#include + +/* NautilusSignaller is a class that manages signals between + disconnected Nautilus code. Nautilus objects connect to these signals + so that other objects can cause them to be emitted later, without + the connecting and emit-causing objects needing to know about each + other. It seems a shame to have to invent a subclass and a special + object just for this purpose. Perhaps there's a better way to do + this kind of thing. +*/ + +/* Get the one and only NautilusSignaller to connect with or emit signals for */ +GObject *nautilus_signaller_get_current (void); \ No newline at end of file diff --git a/src/nautilus-special-location-bar.c b/src/nautilus-special-location-bar.c new file mode 100644 index 0000000..ae97645 --- /dev/null +++ b/src/nautilus-special-location-bar.c @@ -0,0 +1,324 @@ +/* + * Copyright (C) 2012 Red Hat, Inc. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 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 warranty of + * MERCHANTABILITY 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 . + * + */ + +#include "config.h" + +#include +#include +#include + +#include "nautilus-dbus-launcher.h" +#include "nautilus-global-preferences.h" +#include "nautilus-special-location-bar.h" +#include "nautilus-enum-types.h" + +struct _NautilusSpecialLocationBar +{ + AdwBin parent_instance; + + GtkWidget *label; + GtkWidget *learn_more_label; + GtkWidget *button; + int button_response; + NautilusSpecialLocation special_location; +}; + +enum +{ + PROP_0, + PROP_SPECIAL_LOCATION, +}; + +enum +{ + SPECIAL_LOCATION_SHARING_RESPONSE = 1, + SPECIAL_LOCATION_TRASH_RESPONSE = 2, +}; + +G_DEFINE_TYPE (NautilusSpecialLocationBar, nautilus_special_location_bar, ADW_TYPE_BIN) + +static void +on_info_bar_response (GtkInfoBar *infobar, + gint response_id, + gpointer user_data) +{ + NautilusSpecialLocationBar *bar = user_data; + GtkRoot *window = gtk_widget_get_root (GTK_WIDGET (bar)); + + switch (bar->button_response) + { + case SPECIAL_LOCATION_SHARING_RESPONSE: + { + GVariant *parameters; + + parameters = g_variant_new_parsed ("('launch-panel', [<('sharing', @av [])>], " + "@a{sv} {})"); + nautilus_dbus_launcher_call (nautilus_dbus_launcher_get (), + NAUTILUS_DBUS_LAUNCHER_SETTINGS, + "Activate", + parameters, GTK_WINDOW (window)); + } + break; + + case SPECIAL_LOCATION_TRASH_RESPONSE: + { + GVariant *parameters; + + parameters = g_variant_new_parsed ("('launch-panel', [<('usage', @av [])>], " + "@a{sv} {})"); + nautilus_dbus_launcher_call (nautilus_dbus_launcher_get (), + NAUTILUS_DBUS_LAUNCHER_SETTINGS, + "Activate", + parameters, GTK_WINDOW (window)); + } + break; + + default: + { + g_assert_not_reached (); + } + } +} + +static gchar * +parse_old_files_age_preferences_value (void) +{ + guint old_files_age = g_settings_get_uint (gnome_privacy_preferences, "old-files-age"); + + switch (old_files_age) + { + case 0: + { + return g_strdup (_("Items in Trash older than 1 hour are automatically deleted")); + } + + default: + { + return g_strdup_printf (ngettext ("Items in Trash older than %d day are automatically deleted", + "Items in Trash older than %d days are automatically deleted", + old_files_age), + old_files_age); + } + } +} + +static void +old_files_age_preferences_changed (GSettings *settings, + gchar *key, + gpointer user_data) +{ + NautilusSpecialLocationBar *bar; + g_autofree gchar *message = NULL; + + g_assert (NAUTILUS_IS_SPECIAL_LOCATION_BAR (user_data)); + + bar = NAUTILUS_SPECIAL_LOCATION_BAR (user_data); + + message = parse_old_files_age_preferences_value (); + + gtk_label_set_text (GTK_LABEL (bar->label), message); +} + +static void +set_special_location (NautilusSpecialLocationBar *bar, + NautilusSpecialLocation location) +{ + char *message; + char *learn_more_markup = NULL; + char *button_label = NULL; + + switch (location) + { + case NAUTILUS_SPECIAL_LOCATION_TEMPLATES: + { + message = g_strdup (_("Put files in this folder to use them as templates for new documents.")); + learn_more_markup = g_strdup (_("Learn more…")); + } + break; + + case NAUTILUS_SPECIAL_LOCATION_SCRIPTS: + { + message = g_strdup (_("Executable files in this folder will appear in the Scripts menu.")); + } + break; + + case NAUTILUS_SPECIAL_LOCATION_SHARING: + { + message = g_strdup (_("Turn on File Sharing to share the contents of this folder over the network.")); + button_label = _("Sharing Settings"); + bar->button_response = SPECIAL_LOCATION_SHARING_RESPONSE; + } + break; + + case NAUTILUS_SPECIAL_LOCATION_TRASH: + { + message = parse_old_files_age_preferences_value (); + button_label = _("_Settings"); + bar->button_response = SPECIAL_LOCATION_TRASH_RESPONSE; + + g_signal_connect_object (gnome_privacy_preferences, + "changed::old-files-age", + G_CALLBACK (old_files_age_preferences_changed), + bar, 0); + } + break; + + default: + { + g_assert_not_reached (); + } + } + + gtk_label_set_text (GTK_LABEL (bar->label), message); + g_free (message); + + gtk_widget_show (bar->label); + + if (learn_more_markup) + { + gtk_label_set_markup (GTK_LABEL (bar->learn_more_label), + learn_more_markup); + gtk_widget_show (bar->learn_more_label); + g_free (learn_more_markup); + } + else + { + gtk_widget_hide (bar->learn_more_label); + } + + if (button_label) + { + gtk_button_set_label (GTK_BUTTON (bar->button), button_label); + gtk_widget_show (bar->button); + } + else + { + gtk_widget_hide (bar->button); + } +} + +static void +nautilus_special_location_bar_set_property (GObject *object, + guint prop_id, + const GValue *value, + GParamSpec *pspec) +{ + NautilusSpecialLocationBar *bar; + + bar = NAUTILUS_SPECIAL_LOCATION_BAR (object); + + switch (prop_id) + { + case PROP_SPECIAL_LOCATION: + { + set_special_location (bar, g_value_get_enum (value)); + } + break; + + default: + { + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); + } + break; + } +} + +static void +nautilus_special_location_bar_get_property (GObject *object, + guint prop_id, + GValue *value, + GParamSpec *pspec) +{ + NautilusSpecialLocationBar *bar; + + bar = NAUTILUS_SPECIAL_LOCATION_BAR (object); + + switch (prop_id) + { + case PROP_SPECIAL_LOCATION: + { + g_value_set_enum (value, bar->special_location); + } + break; + + default: + { + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); + } + break; + } +} + +static void +nautilus_special_location_bar_class_init (NautilusSpecialLocationBarClass *klass) +{ + GObjectClass *object_class; + + object_class = G_OBJECT_CLASS (klass); + object_class->get_property = nautilus_special_location_bar_get_property; + object_class->set_property = nautilus_special_location_bar_set_property; + + g_object_class_install_property (object_class, + PROP_SPECIAL_LOCATION, + g_param_spec_enum ("special-location", + "special-location", + "special-location", + NAUTILUS_TYPE_SPECIAL_LOCATION, + NAUTILUS_SPECIAL_LOCATION_TEMPLATES, + G_PARAM_READWRITE | G_PARAM_CONSTRUCT)); +} + +static void +nautilus_special_location_bar_init (NautilusSpecialLocationBar *bar) +{ + GtkWidget *info_bar; + PangoAttrList *attrs; + GtkWidget *button; + + info_bar = gtk_info_bar_new (); + gtk_info_bar_set_message_type (GTK_INFO_BAR (info_bar), GTK_MESSAGE_QUESTION); + gtk_widget_show (info_bar); + adw_bin_set_child (ADW_BIN (bar), info_bar); + + attrs = pango_attr_list_new (); + pango_attr_list_insert (attrs, pango_attr_weight_new (PANGO_WEIGHT_BOLD)); + bar->label = gtk_label_new (NULL); + gtk_label_set_attributes (GTK_LABEL (bar->label), attrs); + pango_attr_list_unref (attrs); + + gtk_label_set_ellipsize (GTK_LABEL (bar->label), PANGO_ELLIPSIZE_END); + gtk_info_bar_add_child (GTK_INFO_BAR (info_bar), bar->label); + + button = gtk_info_bar_add_button (GTK_INFO_BAR (info_bar), "", GTK_RESPONSE_OK); + bar->button = button; + + bar->learn_more_label = gtk_label_new (NULL); + gtk_widget_set_hexpand (bar->learn_more_label, TRUE); + gtk_widget_set_halign (bar->learn_more_label, GTK_ALIGN_END); + gtk_info_bar_add_child (GTK_INFO_BAR (info_bar), bar->learn_more_label); + + g_signal_connect (info_bar, "response", G_CALLBACK (on_info_bar_response), bar); +} + +GtkWidget * +nautilus_special_location_bar_new (NautilusSpecialLocation location) +{ + return g_object_new (NAUTILUS_TYPE_SPECIAL_LOCATION_BAR, + "special-location", location, + NULL); +} diff --git a/src/nautilus-special-location-bar.h b/src/nautilus-special-location-bar.h new file mode 100644 index 0000000..52d67e3 --- /dev/null +++ b/src/nautilus-special-location-bar.h @@ -0,0 +1,39 @@ +/* + * Copyright (C) 2012 Red Hat, Inc. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 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 warranty of + * MERCHANTABILITY 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 . + * + */ + +#pragma once + +#include +#include +#include + +G_BEGIN_DECLS + +#define NAUTILUS_TYPE_SPECIAL_LOCATION_BAR (nautilus_special_location_bar_get_type ()) +G_DECLARE_FINAL_TYPE (NautilusSpecialLocationBar, nautilus_special_location_bar, NAUTILUS, SPECIAL_LOCATION_BAR, AdwBin) + +typedef enum { + NAUTILUS_SPECIAL_LOCATION_TEMPLATES, + NAUTILUS_SPECIAL_LOCATION_SCRIPTS, + NAUTILUS_SPECIAL_LOCATION_SHARING, + NAUTILUS_SPECIAL_LOCATION_TRASH, +} NautilusSpecialLocation; + +GtkWidget *nautilus_special_location_bar_new (NautilusSpecialLocation location); + +G_END_DECLS diff --git a/src/nautilus-star-cell.c b/src/nautilus-star-cell.c new file mode 100644 index 0000000..9f21028 --- /dev/null +++ b/src/nautilus-star-cell.c @@ -0,0 +1,173 @@ +/* + * Copyright (C) 2022 António Fernandes + * + * SPDX-License-Identifier: GPL-3.0-or-later + */ + +#include "nautilus-star-cell.h" +#include "nautilus-tag-manager.h" + +struct _NautilusStarCell +{ + NautilusViewCell parent_instance; + + GSignalGroup *item_signal_group; + + GtkImage *star; +}; + +G_DEFINE_TYPE (NautilusStarCell, nautilus_star_cell, NAUTILUS_TYPE_VIEW_CELL) + +static void +on_star_click_released (GtkGestureClick *gesture, + gint n_press, + gdouble x, + gdouble y, + gpointer user_data) +{ + NautilusStarCell *self = user_data; + NautilusTagManager *tag_manager = nautilus_tag_manager_get (); + g_autoptr (NautilusViewItem) item = NULL; + NautilusFile *file; + g_autofree gchar *uri = NULL; + + item = nautilus_view_cell_get_item (NAUTILUS_VIEW_CELL (self)); + g_return_if_fail (item != NULL); + file = nautilus_view_item_get_file (item); + uri = nautilus_file_get_uri (file); + + if (nautilus_tag_manager_file_is_starred (tag_manager, uri)) + { + nautilus_tag_manager_unstar_files (tag_manager, + G_OBJECT (item), + &(GList){ file, NULL }, + NULL, + NULL); + gtk_widget_remove_css_class (GTK_WIDGET (self->star), "added"); + } + else + { + nautilus_tag_manager_star_files (tag_manager, + G_OBJECT (item), + &(GList){ file, NULL }, + NULL, + NULL); + gtk_widget_add_css_class (GTK_WIDGET (self->star), "added"); + } + + gtk_gesture_set_state (GTK_GESTURE (gesture), GTK_EVENT_SEQUENCE_CLAIMED); +} + +static void +update_star (GtkImage *star, + NautilusFile *file) +{ + gboolean is_starred; + g_autofree gchar *file_uri = NULL; + + g_return_if_fail (NAUTILUS_IS_FILE (file)); + + file_uri = nautilus_file_get_uri (file); + is_starred = nautilus_tag_manager_file_is_starred (nautilus_tag_manager_get (), + file_uri); + + gtk_image_set_from_icon_name (star, is_starred ? "starred-symbolic" : "non-starred-symbolic"); +} + +static void +on_file_changed (NautilusStarCell *self) +{ + g_autoptr (NautilusViewItem) item = NULL; + NautilusFile *file; + g_autofree gchar *string = NULL; + + item = nautilus_view_cell_get_item (NAUTILUS_VIEW_CELL (self)); + g_return_if_fail (item != NULL); + file = nautilus_view_item_get_file (item); + + update_star (self->star, file); +} + +static void +on_starred_changed (NautilusTagManager *tag_manager, + GList *changed_files, + gpointer user_data) +{ + NautilusStarCell *self = user_data; + g_autoptr (NautilusViewItem) item = NULL; + NautilusFile *file; + + item = nautilus_view_cell_get_item (NAUTILUS_VIEW_CELL (self)); + if (item == NULL) + { + return; + } + + file = nautilus_view_item_get_file (item); + if (g_list_find (changed_files, file)) + { + update_star (self->star, file); + } +} + +static void +nautilus_star_cell_init (NautilusStarCell *self) +{ + GtkWidget *star; + GtkGesture *gesture; + + /* Create star icon */ + star = gtk_image_new (); + gtk_widget_set_halign (star, GTK_ALIGN_END); + gtk_widget_set_valign (star, GTK_ALIGN_CENTER); + gtk_widget_add_css_class (star, "dim-label"); + gtk_widget_add_css_class (star, "star"); + adw_bin_set_child (ADW_BIN (self), star); + self->star = GTK_IMAGE (star); + + /* Make it clickable */ + gesture = gtk_gesture_click_new (); + gtk_gesture_single_set_button (GTK_GESTURE_SINGLE (gesture), GDK_BUTTON_PRIMARY); + g_signal_connect (gesture, "released", G_CALLBACK (on_star_click_released), self); + gtk_widget_add_controller (star, GTK_EVENT_CONTROLLER (gesture)); + + /* Update on tag changes */ + g_signal_connect_object (nautilus_tag_manager_get (), "starred-changed", + G_CALLBACK (on_starred_changed), self, 0); + + /* Connect automatically to an item. */ + self->item_signal_group = g_signal_group_new (NAUTILUS_TYPE_VIEW_ITEM); + g_signal_group_connect_swapped (self->item_signal_group, "file-changed", + (GCallback) on_file_changed, self); + g_signal_connect_object (self->item_signal_group, "bind", + (GCallback) on_file_changed, self, + G_CONNECT_SWAPPED); + g_object_bind_property (self, "item", + self->item_signal_group, "target", + G_BINDING_SYNC_CREATE); +} + +static void +nautilus_star_cell_finalize (GObject *object) +{ + NautilusStarCell *self = (NautilusStarCell *) object; + + g_object_unref (self->item_signal_group); + G_OBJECT_CLASS (nautilus_star_cell_parent_class)->finalize (object); +} + +static void +nautilus_star_cell_class_init (NautilusStarCellClass *klass) +{ + GObjectClass *object_class = G_OBJECT_CLASS (klass); + + object_class->finalize = nautilus_star_cell_finalize; +} + +NautilusViewCell * +nautilus_star_cell_new (NautilusListBase *view) +{ + return NAUTILUS_VIEW_CELL (g_object_new (NAUTILUS_TYPE_STAR_CELL, + "view", view, + NULL)); +} diff --git a/src/nautilus-star-cell.h b/src/nautilus-star-cell.h new file mode 100644 index 0000000..415a745 --- /dev/null +++ b/src/nautilus-star-cell.h @@ -0,0 +1,19 @@ +/* + * Copyright (C) 2022 António Fernandes + * + * SPDX-License-Identifier: GPL-3.0-or-later + */ + +#pragma once + +#include "nautilus-view-cell.h" + +G_BEGIN_DECLS + +#define NAUTILUS_TYPE_STAR_CELL (nautilus_star_cell_get_type()) + +G_DECLARE_FINAL_TYPE (NautilusStarCell, nautilus_star_cell, NAUTILUS, STAR_CELL, NautilusViewCell) + +NautilusViewCell * nautilus_star_cell_new (NautilusListBase *view); + +G_END_DECLS diff --git a/src/nautilus-starred-directory.c b/src/nautilus-starred-directory.c new file mode 100644 index 0000000..0fedbfb --- /dev/null +++ b/src/nautilus-starred-directory.c @@ -0,0 +1,573 @@ +/* nautilus-starred-directory.c + * + * Copyright (C) 2017 Alexandru Pandelea + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 2 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 warranty of + * MERCHANTABILITY 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 . + */ + +#include "nautilus-starred-directory.h" +#include "nautilus-tag-manager.h" +#include "nautilus-file-utilities.h" +#include "nautilus-directory-private.h" +#include + +struct _NautilusFavoriteDirectory +{ + NautilusDirectory parent_slot; + + GList *files; + + GList *monitor_list; + GList *callback_list; + GList *pending_callback_list; +}; + +typedef struct +{ + gboolean monitor_hidden_files; + NautilusFileAttributes monitor_attributes; + + gconstpointer client; +} FavoriteMonitor; + +typedef struct +{ + NautilusFavoriteDirectory *starred_directory; + + NautilusDirectoryCallback callback; + gpointer callback_data; + + NautilusFileAttributes wait_for_attributes; + gboolean wait_for_file_list; + GList *file_list; +} FavoriteCallback; + +G_DEFINE_TYPE_WITH_CODE (NautilusFavoriteDirectory, nautilus_starred_directory, NAUTILUS_TYPE_DIRECTORY, + nautilus_ensure_extension_points (); + /* It looks like you’re implementing an extension point. + * Did you modify nautilus_ensure_extension_builtins() accordingly? + * + * • Yes + * • Doing it right now + */ + g_io_extension_point_implement (NAUTILUS_DIRECTORY_PROVIDER_EXTENSION_POINT_NAME, + g_define_type_id, + NAUTILUS_STARRED_DIRECTORY_PROVIDER_NAME, + 0)); + +static void +file_changed (NautilusFile *file, + NautilusFavoriteDirectory *starred) +{ + GList list; + + list.data = file; + list.next = NULL; + + nautilus_directory_emit_files_changed (NAUTILUS_DIRECTORY (starred), &list); +} + +static void +disconnect_and_unmonitor_file (NautilusFile *file, + NautilusFavoriteDirectory *self) +{ + /* Disconnect change handler */ + g_signal_handlers_disconnect_by_func (file, file_changed, self); + + /* Remove monitors */ + for (GList *m = self->monitor_list; m != NULL; m = m->next) + { + nautilus_file_monitor_remove (file, m->data); + } +} + +static void +nautilus_starred_directory_update_files (NautilusFavoriteDirectory *self) +{ + NautilusTagManager *tag_manager = nautilus_tag_manager_get (); + GList *l; + GList *tmp_l; + GList *new_starred_files; + GList *monitor_list; + FavoriteMonitor *monitor; + NautilusFile *file; + GHashTable *uri_table; + GList *files_added; + GList *files_removed; + gchar *uri; + + files_added = NULL; + files_removed = NULL; + + uri_table = g_hash_table_new_full (g_str_hash, + g_str_equal, + (GDestroyNotify) g_free, + NULL); + + for (l = self->files; l != NULL; l = l->next) + { + g_hash_table_add (uri_table, nautilus_file_get_uri (NAUTILUS_FILE (l->data))); + } + + new_starred_files = nautilus_tag_manager_get_starred_files (tag_manager); + + for (l = new_starred_files; l != NULL; l = l->next) + { + if (!g_hash_table_contains (uri_table, l->data)) + { + file = nautilus_file_get_by_uri ((gchar *) l->data); + + for (monitor_list = self->monitor_list; monitor_list; monitor_list = monitor_list->next) + { + monitor = monitor_list->data; + + /* Add monitors */ + nautilus_file_monitor_add (file, monitor, monitor->monitor_attributes); + } + + g_signal_connect (file, "changed", G_CALLBACK (file_changed), self); + + files_added = g_list_prepend (files_added, file); + } + } + + l = self->files; + while (l != NULL) + { + uri = nautilus_file_get_uri (NAUTILUS_FILE (l->data)); + + if (!nautilus_tag_manager_file_is_starred (tag_manager, uri)) + { + files_removed = g_list_prepend (files_removed, + nautilus_file_ref (NAUTILUS_FILE (l->data))); + + disconnect_and_unmonitor_file (NAUTILUS_FILE (l->data), self); + + if (l == self->files) + { + self->files = g_list_delete_link (self->files, l); + l = self->files; + } + else + { + tmp_l = l->prev; + self->files = g_list_delete_link (self->files, l); + l = tmp_l->next; + } + } + else + { + l = l->next; + } + + g_free (uri); + } + + if (files_added) + { + nautilus_directory_emit_files_added (NAUTILUS_DIRECTORY (self), files_added); + + for (l = files_added; l != NULL; l = l->next) + { + self->files = g_list_prepend (self->files, nautilus_file_ref (NAUTILUS_FILE (l->data))); + } + } + + if (files_removed) + { + nautilus_directory_emit_files_changed (NAUTILUS_DIRECTORY (self), files_removed); + } + + nautilus_file_list_free (files_added); + nautilus_file_list_free (files_removed); + g_hash_table_destroy (uri_table); +} + +static void +on_starred_files_changed (NautilusTagManager *tag_manager, + GList *changed_files, + gpointer user_data) +{ + NautilusFavoriteDirectory *self; + + self = NAUTILUS_STARRED_DIRECTORY (user_data); + + nautilus_starred_directory_update_files (self); +} + +static gboolean +real_contains_file (NautilusDirectory *directory, + NautilusFile *file) +{ + g_autofree gchar *uri = NULL; + + uri = nautilus_file_get_uri (file); + + return nautilus_tag_manager_file_is_starred (nautilus_tag_manager_get (), uri); +} + +static gboolean +real_is_editable (NautilusDirectory *directory) +{ + return FALSE; +} + +static void +real_call_when_ready (NautilusDirectory *directory, + NautilusFileAttributes file_attributes, + gboolean wait_for_file_list, + NautilusDirectoryCallback callback, + gpointer callback_data) +{ + GList *file_list; + NautilusFavoriteDirectory *starred; + + starred = NAUTILUS_STARRED_DIRECTORY (directory); + + file_list = nautilus_file_list_copy (starred->files); + + callback (NAUTILUS_DIRECTORY (directory), + file_list, + callback_data); +} + +static gboolean +real_are_all_files_seen (NautilusDirectory *directory) +{ + return TRUE; +} + +static void +real_file_monitor_add (NautilusDirectory *directory, + gconstpointer client, + gboolean monitor_hidden_files, + NautilusFileAttributes file_attributes, + NautilusDirectoryCallback callback, + gpointer callback_data) +{ + GList *list; + FavoriteMonitor *monitor; + NautilusFavoriteDirectory *starred; + NautilusFile *file; + + starred = NAUTILUS_STARRED_DIRECTORY (directory); + + monitor = g_new0 (FavoriteMonitor, 1); + monitor->monitor_hidden_files = monitor_hidden_files; + monitor->monitor_attributes = file_attributes; + monitor->client = client; + + starred->monitor_list = g_list_prepend (starred->monitor_list, monitor); + + if (callback != NULL) + { + (*callback)(directory, starred->files, callback_data); + } + + for (list = starred->files; list != NULL; list = list->next) + { + file = list->data; + + /* Add monitors */ + nautilus_file_monitor_add (file, monitor, file_attributes); + } +} + +static void +starred_monitor_destroy (FavoriteMonitor *monitor, + NautilusFavoriteDirectory *starred) +{ + GList *l; + NautilusFile *file; + + for (l = starred->files; l != NULL; l = l->next) + { + file = l->data; + + nautilus_file_monitor_remove (file, monitor); + } + + g_free (monitor); +} + +static void +real_monitor_remove (NautilusDirectory *directory, + gconstpointer client) +{ + NautilusFavoriteDirectory *starred; + FavoriteMonitor *monitor; + GList *list; + + starred = NAUTILUS_STARRED_DIRECTORY (directory); + + for (list = starred->monitor_list; list != NULL; list = list->next) + { + monitor = list->data; + + if (monitor->client != client) + { + continue; + } + + starred->monitor_list = g_list_delete_link (starred->monitor_list, list); + + starred_monitor_destroy (monitor, starred); + + break; + } +} + +static gboolean +real_handles_location (GFile *location) +{ + g_autofree gchar *uri = NULL; + + uri = g_file_get_uri (location); + + if (eel_uri_is_starred (uri)) + { + return TRUE; + } + + return FALSE; +} + +static FavoriteCallback * +starred_callback_find_pending (NautilusFavoriteDirectory *starred, + NautilusDirectoryCallback callback, + gpointer callback_data) +{ + FavoriteCallback *starred_callback; + GList *list; + + for (list = starred->pending_callback_list; list != NULL; list = list->next) + { + starred_callback = list->data; + + if (starred_callback->callback == callback && + starred_callback->callback_data == callback_data) + { + return starred_callback; + } + } + + return NULL; +} + +static FavoriteCallback * +starred_callback_find (NautilusFavoriteDirectory *starred, + NautilusDirectoryCallback callback, + gpointer callback_data) +{ + FavoriteCallback *starred_callback; + GList *list; + + for (list = starred->callback_list; list != NULL; list = list->next) + { + starred_callback = list->data; + + if (starred_callback->callback == callback && + starred_callback->callback_data == callback_data) + { + return starred_callback; + } + } + + return NULL; +} + +static void +starred_callback_destroy (FavoriteCallback *starred_callback) +{ + nautilus_file_list_free (starred_callback->file_list); + + g_free (starred_callback); +} + +static void +real_cancel_callback (NautilusDirectory *directory, + NautilusDirectoryCallback callback, + gpointer callback_data) +{ + NautilusFavoriteDirectory *starred; + FavoriteCallback *starred_callback; + + starred = NAUTILUS_STARRED_DIRECTORY (directory); + starred_callback = starred_callback_find (starred, callback, callback_data); + + if (starred_callback) + { + starred->callback_list = g_list_remove (starred->callback_list, starred_callback); + + starred_callback_destroy (starred_callback); + + return; + } + + /* Check for a pending callback */ + starred_callback = starred_callback_find_pending (starred, callback, callback_data); + + if (starred_callback) + { + starred->pending_callback_list = g_list_remove (starred->pending_callback_list, starred_callback); + + starred_callback_destroy (starred_callback); + } +} + +static GList * +real_get_file_list (NautilusDirectory *directory) +{ + NautilusFavoriteDirectory *starred; + + starred = NAUTILUS_STARRED_DIRECTORY (directory); + + return nautilus_file_list_copy (starred->files); +} + +static void +nautilus_starred_directory_set_files (NautilusFavoriteDirectory *self) +{ + GList *starred_files; + NautilusFile *file; + GList *l; + GList *file_list; + FavoriteMonitor *monitor; + GList *monitor_list; + + file_list = NULL; + + starred_files = nautilus_tag_manager_get_starred_files (nautilus_tag_manager_get ()); + + for (l = starred_files; l != NULL; l = l->next) + { + file = nautilus_file_get_by_uri ((gchar *) l->data); + + g_signal_connect (file, "changed", G_CALLBACK (file_changed), self); + + for (monitor_list = self->monitor_list; monitor_list; monitor_list = monitor_list->next) + { + monitor = monitor_list->data; + + /* Add monitors */ + nautilus_file_monitor_add (file, monitor, monitor->monitor_attributes); + } + + file_list = g_list_prepend (file_list, file); + } + + nautilus_directory_emit_files_added (NAUTILUS_DIRECTORY (self), file_list); + + self->files = file_list; +} + +static void +real_force_reload (NautilusDirectory *directory) +{ + NautilusFavoriteDirectory *self = NAUTILUS_STARRED_DIRECTORY (directory); + + /* Unset current file list */ + g_list_foreach (self->files, (GFunc) disconnect_and_unmonitor_file, self); + g_clear_list (&self->files, g_object_unref); + + /* Set a fresh file list */ + nautilus_starred_directory_set_files (self); +} + +static void +nautilus_starred_directory_finalize (GObject *object) +{ + NautilusFavoriteDirectory *self; + + self = NAUTILUS_STARRED_DIRECTORY (object); + + g_signal_handlers_disconnect_by_func (nautilus_tag_manager_get (), + on_starred_files_changed, + self); + + nautilus_file_list_free (self->files); + + G_OBJECT_CLASS (nautilus_starred_directory_parent_class)->finalize (object); +} + +static void +nautilus_starred_directory_dispose (GObject *object) +{ + NautilusFavoriteDirectory *starred; + GList *l; + + starred = NAUTILUS_STARRED_DIRECTORY (object); + + /* Remove file connections */ + g_list_foreach (starred->files, (GFunc) disconnect_and_unmonitor_file, starred); + + /* Remove search monitors */ + if (starred->monitor_list) + { + for (l = starred->monitor_list; l != NULL; l = l->next) + { + starred_monitor_destroy ((FavoriteMonitor *) l->data, starred); + } + + g_list_free (starred->monitor_list); + starred->monitor_list = NULL; + } + + G_OBJECT_CLASS (nautilus_starred_directory_parent_class)->dispose (object); +} + +static void +nautilus_starred_directory_class_init (NautilusFavoriteDirectoryClass *klass) +{ + GObjectClass *oclass; + NautilusDirectoryClass *directory_class; + + oclass = G_OBJECT_CLASS (klass); + directory_class = NAUTILUS_DIRECTORY_CLASS (klass); + + oclass->finalize = nautilus_starred_directory_finalize; + oclass->dispose = nautilus_starred_directory_dispose; + + directory_class->handles_location = real_handles_location; + directory_class->contains_file = real_contains_file; + directory_class->is_editable = real_is_editable; + directory_class->force_reload = real_force_reload; + directory_class->call_when_ready = real_call_when_ready; + directory_class->are_all_files_seen = real_are_all_files_seen; + directory_class->file_monitor_add = real_file_monitor_add; + directory_class->file_monitor_remove = real_monitor_remove; + directory_class->cancel_callback = real_cancel_callback; + directory_class->get_file_list = real_get_file_list; +} + +NautilusFavoriteDirectory * +nautilus_starred_directory_new (void) +{ + NautilusFavoriteDirectory *self; + + self = g_object_new (NAUTILUS_TYPE_STARRED_DIRECTORY, NULL); + + return self; +} + +static void +nautilus_starred_directory_init (NautilusFavoriteDirectory *self) +{ + g_signal_connect (nautilus_tag_manager_get (), + "starred-changed", + (GCallback) on_starred_files_changed, + self); + + nautilus_starred_directory_set_files (self); +} diff --git a/src/nautilus-starred-directory.h b/src/nautilus-starred-directory.h new file mode 100644 index 0000000..0ced846 --- /dev/null +++ b/src/nautilus-starred-directory.h @@ -0,0 +1,33 @@ +/* nautilus-starred-directory.h + * + * Copyright (C) 2017 Alexandru Pandelea + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 2 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 warranty of + * MERCHANTABILITY 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 . + */ + +#pragma once + +#include "nautilus-directory.h" + +G_BEGIN_DECLS + +#define NAUTILUS_STARRED_DIRECTORY_PROVIDER_NAME "starred-directory-provider" + +#define NAUTILUS_TYPE_STARRED_DIRECTORY (nautilus_starred_directory_get_type ()) + +G_DECLARE_FINAL_TYPE (NautilusFavoriteDirectory, nautilus_starred_directory, NAUTILUS, STARRED_DIRECTORY, NautilusDirectory); + +NautilusFavoriteDirectory* nautilus_starred_directory_new (void); + +G_END_DECLS \ No newline at end of file diff --git a/src/nautilus-tag-manager.c b/src/nautilus-tag-manager.c new file mode 100644 index 0000000..959eee8 --- /dev/null +++ b/src/nautilus-tag-manager.c @@ -0,0 +1,1019 @@ +/* nautilus-tag-manager.c + * + * Copyright (C) 2017 Alexandru Pandelea + * Copyright (C) 2020 Sam Thursfield + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 2 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 warranty of + * MERCHANTABILITY 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 . + */ + +#include "nautilus-tag-manager.h" +#include "nautilus-file.h" +#include "nautilus-file-undo-operations.h" +#include "nautilus-file-undo-manager.h" +#include "nautilus-tracker-utilities.h" +#define DEBUG_FLAG NAUTILUS_DEBUG_TAG_MANAGER +#include "nautilus-debug.h" + +#include +#include + +#include "config.h" + +struct _NautilusTagManager +{ + GObject object; + + gboolean database_ok; + TrackerSparqlConnection *db; + TrackerNotifier *notifier; + + TrackerSparqlStatement *query_starred_files; + TrackerSparqlStatement *query_file_is_starred; + + GHashTable *starred_file_uris; + GFile *home; + + GCancellable *cancellable; +}; + +G_DEFINE_TYPE (NautilusTagManager, nautilus_tag_manager, G_TYPE_OBJECT); + +static NautilusTagManager *tag_manager = NULL; + +/* See nautilus_tag_manager_new_dummy() documentation for details. */ +static gboolean make_dummy_instance = FALSE; + +typedef struct +{ + NautilusTagManager *tag_manager; + GTask *task; + GList *selection; + gboolean star; +} UpdateData; + +enum +{ + STARRED_CHANGED, + LAST_SIGNAL +}; + +#define QUERY_STARRED_FILES \ + "SELECT ?file " \ + "{ " \ + " ?file a nautilus:File ; " \ + " nautilus:starred true . " \ + "}" + +#define QUERY_FILE_IS_STARRED \ + "ASK " \ + "{ " \ + " ~file a nautilus:File ; " \ + " nautilus:starred true . " \ + "}" + +static guint signals[LAST_SIGNAL]; + +/* Limit to 10MB output from Tracker -- surely, nobody has over a million starred files. */ +#define TRACKER2_MAX_IMPORT_BYTES 10 * 1024 * 1024 + +static gchar * +tracker2_migration_stamp (void) +{ + return g_build_filename (g_get_user_data_dir (), "nautilus", "tracker2-migration-complete", NULL); +} + +static void +start_query_or_update (TrackerSparqlConnection *db, + GString *query, + GAsyncReadyCallback callback, + gpointer user_data, + gboolean is_query, + GCancellable *cancellable) +{ + g_autoptr (GError) error = NULL; + + if (!db) + { + g_message ("nautilus-tag-manager: No Tracker connection"); + return; + } + + if (is_query) + { + tracker_sparql_connection_query_async (db, + query->str, + cancellable, + callback, + user_data); + } + else + { + tracker_sparql_connection_update_async (db, + query->str, + cancellable, + callback, + user_data); + } +} + +static void +on_update_callback (GObject *object, + GAsyncResult *result, + gpointer user_data) +{ + TrackerSparqlConnection *db; + GError *error; + UpdateData *data; + + data = user_data; + + error = NULL; + + db = TRACKER_SPARQL_CONNECTION (object); + + tracker_sparql_connection_update_finish (db, result, &error); + + if (error == NULL) + { + /* FIXME: make sure data->tag_manager->starred_file_uris is up to date */ + + if (!nautilus_file_undo_manager_is_operating ()) + { + NautilusFileUndoInfo *undo_info; + + undo_info = nautilus_file_undo_info_starred_new (data->selection, data->star); + nautilus_file_undo_manager_set_action (undo_info); + + g_object_unref (undo_info); + } + + g_signal_emit_by_name (data->tag_manager, "starred-changed", nautilus_file_list_copy (data->selection)); + + g_task_return_boolean (data->task, TRUE); + g_object_unref (data->task); + } + else if (error && error->code == G_IO_ERROR_CANCELLED) + { + g_error_free (error); + } + else + { + g_warning ("error updating tags: %s", error->message); + g_task_return_error (data->task, error); + g_object_unref (data->task); + } + + nautilus_file_list_free (data->selection); + g_free (data); +} + +/** + * nautilus_tag_manager_get_starred_files: + * @self: The tag manager singleton + * + * Returns: (element-type gchar*) (transfer container): A list of the starred urls. + */ +GList * +nautilus_tag_manager_get_starred_files (NautilusTagManager *self) +{ + GHashTableIter starred_iter; + gchar *starred_uri; + GList *starred_file_uris = NULL; + + g_hash_table_iter_init (&starred_iter, self->starred_file_uris); + while (g_hash_table_iter_next (&starred_iter, (gpointer *) &starred_uri, NULL)) + { + g_autoptr (GFile) file = g_file_new_for_uri (starred_uri); + + /* Skip files ouside $HOME, because we don't support starring these yet. + * See comment on nautilus_tag_manager_can_star_contents() */ + if (g_file_has_prefix (file, self->home)) + { + starred_file_uris = g_list_prepend (starred_file_uris, starred_uri); + } + } + + return starred_file_uris; +} + +static void +on_get_starred_files_cursor_callback (GObject *object, + GAsyncResult *result, + gpointer user_data) +{ + TrackerSparqlCursor *cursor; + g_autoptr (GError) error = NULL; + const gchar *url; + gboolean success; + NautilusTagManager *self; + GList *changed_files; + NautilusFile *file; + + cursor = TRACKER_SPARQL_CURSOR (object); + + self = NAUTILUS_TAG_MANAGER (user_data); + + success = tracker_sparql_cursor_next_finish (cursor, result, &error); + + if (!success) + { + if (error != NULL) + { + g_warning ("Error on getting all tags cursor callback: %s", error->message); + } + + g_clear_object (&cursor); + return; + } + + url = tracker_sparql_cursor_get_string (cursor, 0, NULL); + + g_hash_table_add (self->starred_file_uris, g_strdup (url)); + + file = nautilus_file_get_by_uri (url); + + if (file) + { + changed_files = g_list_prepend (NULL, file); + + g_signal_emit_by_name (self, "starred-changed", changed_files); + + nautilus_file_list_free (changed_files); + } + else + { + DEBUG ("File %s is starred but not found", url); + } + + tracker_sparql_cursor_next_async (cursor, + self->cancellable, + on_get_starred_files_cursor_callback, + self); +} + +static void +on_get_starred_files_query_callback (GObject *object, + GAsyncResult *result, + gpointer user_data) +{ + TrackerSparqlCursor *cursor; + g_autoptr (GError) error = NULL; + TrackerSparqlStatement *statement; + NautilusTagManager *self; + + self = NAUTILUS_TAG_MANAGER (user_data); + statement = TRACKER_SPARQL_STATEMENT (object); + + cursor = tracker_sparql_statement_execute_finish (statement, + result, + &error); + + if (error != NULL) + { + if (error->code != G_IO_ERROR_CANCELLED) + { + g_warning ("Error on getting starred files: %s", error->message); + } + } + else + { + tracker_sparql_cursor_next_async (cursor, + self->cancellable, + on_get_starred_files_cursor_callback, + user_data); + } +} + +static void +nautilus_tag_manager_query_starred_files (NautilusTagManager *self, + GCancellable *cancellable) +{ + if (!self->database_ok) + { + g_message ("nautilus-tag-manager: No Tracker connection"); + return; + } + + tracker_sparql_statement_execute_async (self->query_starred_files, + cancellable, + on_get_starred_files_query_callback, + self); +} + +static GString * +nautilus_tag_manager_delete_tag (NautilusTagManager *self, + GList *selection) +{ + GString *query; + NautilusFile *file; + GList *l; + + query = g_string_new ("DELETE DATA {"); + + for (l = selection; l != NULL; l = l->next) + { + g_autofree gchar *uri = NULL; + + file = l->data; + + uri = nautilus_file_get_uri (file); + g_string_append_printf (query, + " <%s> a nautilus:File ; " + " nautilus:starred true . ", + uri); + } + + g_string_append (query, "}"); + + return query; +} + +static GString * +nautilus_tag_manager_insert_tag (NautilusTagManager *self, + GList *selection) +{ + GString *query; + NautilusFile *file; + GList *l; + + query = g_string_new ("INSERT DATA {"); + + for (l = selection; l != NULL; l = l->next) + { + g_autofree gchar *uri = NULL; + + file = l->data; + + uri = nautilus_file_get_uri (file); + g_string_append_printf (query, + " <%s> a nautilus:File ; " + " nautilus:starred true . ", + uri); + } + + g_string_append (query, "}"); + + return query; +} + +gboolean +nautilus_tag_manager_file_is_starred (NautilusTagManager *self, + const gchar *file_uri) +{ + return g_hash_table_contains (self->starred_file_uris, file_uri); +} + +void +nautilus_tag_manager_star_files (NautilusTagManager *self, + GObject *object, + GList *selection, + GAsyncReadyCallback callback, + GCancellable *cancellable) +{ + GString *query; + g_autoptr (GError) error = NULL; + GTask *task; + UpdateData *update_data; + + DEBUG ("Starring %i files", g_list_length (selection)); + + task = g_task_new (object, cancellable, callback, NULL); + + query = nautilus_tag_manager_insert_tag (self, selection); + + update_data = g_new0 (UpdateData, 1); + update_data->task = task; + update_data->tag_manager = self; + update_data->selection = nautilus_file_list_copy (selection); + update_data->star = TRUE; + + start_query_or_update (self->db, + query, + on_update_callback, + update_data, + FALSE, + cancellable); + + g_string_free (query, TRUE); +} + +void +nautilus_tag_manager_unstar_files (NautilusTagManager *self, + GObject *object, + GList *selection, + GAsyncReadyCallback callback, + GCancellable *cancellable) +{ + GString *query; + GTask *task; + UpdateData *update_data; + + DEBUG ("Unstarring %i files", g_list_length (selection)); + + task = g_task_new (object, cancellable, callback, NULL); + + query = nautilus_tag_manager_delete_tag (self, selection); + + update_data = g_new0 (UpdateData, 1); + update_data->task = task; + update_data->tag_manager = self; + update_data->selection = nautilus_file_list_copy (selection); + update_data->star = FALSE; + + start_query_or_update (self->db, + query, + on_update_callback, + update_data, + FALSE, + cancellable); + + g_string_free (query, TRUE); +} + +static void +on_tracker_notifier_events (TrackerNotifier *notifier, + gchar *service, + gchar *graph, + GPtrArray *events, + gpointer user_data) +{ + TrackerNotifierEvent *event; + NautilusTagManager *self; + int i; + const gchar *file_url; + GError *error = NULL; + TrackerSparqlCursor *cursor; + gboolean query_has_results = FALSE; + gboolean starred; + GList *changed_files; + NautilusFile *changed_file; + + self = NAUTILUS_TAG_MANAGER (user_data); + + for (i = 0; i < events->len; i++) + { + event = g_ptr_array_index (events, i); + + file_url = tracker_notifier_event_get_urn (event); + changed_file = NULL; + + DEBUG ("Got event for file %s", file_url); + + tracker_sparql_statement_bind_string (self->query_file_is_starred, "file", file_url); + cursor = tracker_sparql_statement_execute (self->query_file_is_starred, + NULL, + &error); + + if (cursor) + { + query_has_results = tracker_sparql_cursor_next (cursor, NULL, &error); + } + + if (error || !cursor || !query_has_results) + { + g_warning ("Couldn't query the starred files database: '%s'", error ? error->message : "(null error)"); + g_clear_error (&error); + return; + } + + starred = tracker_sparql_cursor_get_boolean (cursor, 0); + if (starred) + { + gboolean inserted = g_hash_table_add (self->starred_file_uris, g_strdup (file_url)); + + if (inserted) + { + DEBUG ("Added %s to starred files list", file_url); + changed_file = nautilus_file_get_by_uri (file_url); + } + } + else + { + gboolean removed = g_hash_table_remove (self->starred_file_uris, file_url); + + if (removed) + { + DEBUG ("Removed %s from starred files list", file_url); + changed_file = nautilus_file_get_by_uri (file_url); + } + } + + if (changed_file) + { + changed_files = g_list_prepend (NULL, changed_file); + + g_signal_emit_by_name (self, "starred-changed", changed_files); + + nautilus_file_list_free (changed_files); + } + + g_object_unref (cursor); + } +} + +static void +nautilus_tag_manager_finalize (GObject *object) +{ + NautilusTagManager *self; + + self = NAUTILUS_TAG_MANAGER (object); + + if (self->notifier != NULL) + { + g_signal_handlers_disconnect_by_func (self->notifier, + G_CALLBACK (on_tracker_notifier_events), + self); + } + + g_cancellable_cancel (self->cancellable); + g_clear_object (&self->cancellable); + g_clear_object (&self->notifier); + g_clear_object (&self->db); + g_clear_object (&self->query_file_is_starred); + g_clear_object (&self->query_starred_files); + + g_hash_table_destroy (self->starred_file_uris); + g_clear_object (&self->home); + + G_OBJECT_CLASS (nautilus_tag_manager_parent_class)->finalize (object); +} + +static void +nautilus_tag_manager_class_init (NautilusTagManagerClass *klass) +{ + GObjectClass *oclass; + + oclass = G_OBJECT_CLASS (klass); + + oclass->finalize = nautilus_tag_manager_finalize; + + signals[STARRED_CHANGED] = g_signal_new ("starred-changed", + NAUTILUS_TYPE_TAG_MANAGER, + G_SIGNAL_RUN_LAST, + 0, + NULL, + NULL, + g_cclosure_marshal_VOID__POINTER, + G_TYPE_NONE, + 1, + G_TYPE_POINTER); +} + +/** + * nautilus_tag_manager_new: + * + * Returns: (transfer full): the #NautilusTagManager singleton object. + */ +NautilusTagManager * +nautilus_tag_manager_new (void) +{ + if (tag_manager != NULL) + { + return g_object_ref (tag_manager); + } + + tag_manager = g_object_new (NAUTILUS_TYPE_TAG_MANAGER, NULL); + g_object_add_weak_pointer (G_OBJECT (tag_manager), (gpointer) & tag_manager); + + return tag_manager; +} + +/** + * nautilus_tag_manager_new_dummy: + * + * Creates a dummy tag manager without database. + * + * Useful only for tests where the tag manager is needed but not being tested + * and we don't want to fail the tests due to irrelevant D-Bus failures. + * + * Returns: (transfer full): the #NautilusTagManager singleton object. + */ +NautilusTagManager * +nautilus_tag_manager_new_dummy (void) +{ + make_dummy_instance = TRUE; + return nautilus_tag_manager_new (); +} + +/** + * nautilus_tag_manager_get: + * + * Returns: (transfer none): the #NautilusTagManager singleton object. + */ +NautilusTagManager * +nautilus_tag_manager_get (void) +{ + return tag_manager; +} + +static gboolean +setup_database (NautilusTagManager *self, + GCancellable *cancellable, + GError **error) +{ + const gchar *datadir; + g_autofree gchar *store_path = NULL; + g_autofree gchar *ontology_path = NULL; + g_autoptr (GFile) store = NULL; + g_autoptr (GFile) ontology = NULL; + + /* Open private database to store nautilus:starred property. */ + + datadir = NAUTILUS_DATADIR; + + store_path = g_build_filename (g_get_user_data_dir (), "nautilus", "tags", NULL); + ontology_path = g_build_filename (datadir, "ontology", NULL); + + store = g_file_new_for_path (store_path); + ontology = g_file_new_for_path (ontology_path); + + self->db = tracker_sparql_connection_new (TRACKER_SPARQL_CONNECTION_FLAGS_NONE, + store, + ontology, + cancellable, + error); + + if (*error) + { + return FALSE; + } + + /* Prepare reusable queries. */ + self->query_file_is_starred = tracker_sparql_connection_query_statement (self->db, + QUERY_FILE_IS_STARRED, + cancellable, + error); + + if (*error) + { + return FALSE; + } + + self->query_starred_files = tracker_sparql_connection_query_statement (self->db, + QUERY_STARRED_FILES, + cancellable, + error); + + if (*error) + { + return FALSE; + } + + return TRUE; +} + +static void +nautilus_tag_manager_init (NautilusTagManager *self) +{ + g_autoptr (GError) error = NULL; + + self->starred_file_uris = g_hash_table_new_full (g_str_hash, + g_str_equal, + (GDestroyNotify) g_free, + /* values are keys */ + NULL); + self->home = g_file_new_for_path (g_get_home_dir ()); + + if (make_dummy_instance) + { + /* Skip database initiation for nautilus_tag_manager_new_dummy(). */ + return; + } + + self->cancellable = g_cancellable_new (); + self->database_ok = setup_database (self, self->cancellable, &error); + if (error) + { + g_warning ("Unable to initialize tag manager: %s", error->message); + return; + } + + self->notifier = tracker_sparql_connection_create_notifier (self->db); + + nautilus_tag_manager_query_starred_files (self, self->cancellable); + + g_signal_connect (self->notifier, + "events", + G_CALLBACK (on_tracker_notifier_events), + self); +} + +gboolean +nautilus_tag_manager_can_star_contents (NautilusTagManager *self, + GFile *directory) +{ + /* We only allow files to be starred inside the home directory for now. + * This avoids the starred files database growing too big. + * See https://gitlab.gnome.org/GNOME/nautilus/-/merge_requests/553#note_903108 + */ + return g_file_has_prefix (directory, self->home) || g_file_equal (directory, self->home); +} + +static void +update_moved_uris_callback (GObject *object, + GAsyncResult *result, + gpointer user_data) +{ + g_autoptr (GError) error = NULL; + g_autoptr (GPtrArray) new_uris = user_data; + + tracker_sparql_connection_update_finish (TRACKER_SPARQL_CONNECTION (object), + result, + &error); + + if (error != NULL && error->code != G_IO_ERROR_CANCELLED) + { + g_warning ("Error updating moved uris: %s", error->message); + } + else + { + g_autolist (NautilusFile) updated_files = NULL; + + for (guint i = 0; i < new_uris->len; i++) + { + gchar *new_uri = g_ptr_array_index (new_uris, i); + + updated_files = g_list_prepend (updated_files, nautilus_file_get_by_uri (new_uri)); + } + + g_signal_emit_by_name (tag_manager, "starred-changed", updated_files); + } +} + +/** + * nautilus_tag_manager_update_moved_uris: + * @self: The tag manager singleton + * @src: The original location as a #GFile + * @dest: The new location as a #GFile + * + * Checks whether the rename/move operation (@src to @dest) has modified + * the URIs of any starred files, and updates the database accordingly. + */ +void +nautilus_tag_manager_update_moved_uris (NautilusTagManager *self, + GFile *src, + GFile *dest) +{ + GHashTableIter starred_iter; + gchar *starred_uri; + g_autoptr (GPtrArray) old_uris = NULL; + g_autoptr (GPtrArray) new_uris = NULL; + g_autoptr (GString) query = NULL; + + if (!self->database_ok) + { + g_message ("nautilus-tag-manager: No Tracker connection"); + return; + } + + old_uris = g_ptr_array_new (); + new_uris = g_ptr_array_new_with_free_func (g_free); + + g_hash_table_iter_init (&starred_iter, self->starred_file_uris); + while (g_hash_table_iter_next (&starred_iter, (gpointer *) &starred_uri, NULL)) + { + g_autoptr (GFile) starred_location = NULL; + g_autofree gchar *relative_path = NULL; + + starred_location = g_file_new_for_uri (starred_uri); + + if (g_file_equal (starred_location, src)) + { + /* The moved file/folder is starred */ + g_ptr_array_add (old_uris, starred_uri); + g_ptr_array_add (new_uris, g_file_get_uri (dest)); + continue; + } + + relative_path = g_file_get_relative_path (src, starred_location); + if (relative_path != NULL) + { + /* The starred file/folder is descendant of the moved/renamed directory */ + g_autoptr (GFile) new_location = NULL; + + new_location = g_file_resolve_relative_path (dest, relative_path); + + g_ptr_array_add (old_uris, starred_uri); + g_ptr_array_add (new_uris, g_file_get_uri (new_location)); + } + } + + if (new_uris->len == 0) + { + /* No starred files are affected by this move/rename */ + return; + } + + DEBUG ("Updating moved URI for %i starred files", new_uris->len); + + query = g_string_new ("DELETE DATA {"); + + for (guint i = 0; i < old_uris->len; i++) + { + gchar *old_uri = g_ptr_array_index (old_uris, i); + g_string_append_printf (query, + " <%s> a nautilus:File ; " + " nautilus:starred true . ", + old_uri); + } + + g_string_append (query, "} ; INSERT DATA {"); + + for (guint i = 0; i < new_uris->len; i++) + { + gchar *new_uri = g_ptr_array_index (new_uris, i); + g_string_append_printf (query, + " <%s> a nautilus:File ; " + " nautilus:starred true . ", + new_uri); + } + + g_string_append (query, "}"); + + /* Forward the new_uris list to later pass in the ::files-changed signal. + * There is no need to pass the old_uris because the file model is updated + * independently; we need only inform the view where to display stars now. + */ + tracker_sparql_connection_update_async (self->db, + query->str, + self->cancellable, + update_moved_uris_callback, + g_steal_pointer (&new_uris)); +} + +static void +process_tracker2_data_cb (GObject *source_object, + GAsyncResult *res, + gpointer user_data) +{ + NautilusTagManager *self = NAUTILUS_TAG_MANAGER (source_object); + g_autofree gchar *path = tracker2_migration_stamp (); + g_autoptr (GError) error = NULL; + + tracker_sparql_connection_update_finish (self->db, res, &error); + + if (!error) + { + DEBUG ("Data migration was successful. Creating stamp %s", path); + + g_file_set_contents (path, "", -1, &error); + if (error) + { + g_warning ("Failed to create %s after migration: %s", path, error->message); + } + } + else + { + g_warning ("Error during data migration: %s", error->message); + } +} + +static void +process_tracker2_data (NautilusTagManager *self, + GBytes *key_file_data) +{ + g_autoptr (GKeyFile) key_file = NULL; + g_autoptr (GError) error = NULL; + gchar **groups, **group; + GList *selection = NULL; + NautilusFile *file; + + key_file = g_key_file_new (); + g_key_file_load_from_bytes (key_file, + key_file_data, + G_KEY_FILE_NONE, + &error); + g_bytes_unref (key_file_data); + + if (error) + { + g_warning ("Tracker 2 migration: Failed to parse key file data: %s", error->message); + return; + } + + groups = g_key_file_get_groups (key_file, NULL); + + for (group = groups; *group != NULL; group++) + { + file = nautilus_file_get_by_uri (*group); + + if (file) + { + DEBUG ("Tracker 2 migration: starring %s", *group); + selection = g_list_prepend (selection, file); + } + else + { + DEBUG ("Tracker 2 migration: couldn't get NautilusFile for %s", *group); + } + } + + nautilus_tag_manager_star_files (self, + G_OBJECT (self), + selection, + process_tracker2_data_cb, + self->cancellable); + + g_free (groups); +} + +static void +export_tracker2_data_cb (GObject *source_object, + GAsyncResult *res, + gpointer user_data) +{ + GInputStream *stream = G_INPUT_STREAM (source_object); + NautilusTagManager *self = NAUTILUS_TAG_MANAGER (user_data); + g_autoptr (GError) error = NULL; + GBytes *key_file_data; + + key_file_data = g_input_stream_read_bytes_finish (stream, res, &error); + + if (key_file_data) + { + process_tracker2_data (self, key_file_data); + } + else + { + g_warning ("Tracker2 migration: Failed to read data from pipe: %s", error->message); + } +} + +static void +child_watch_cb (GPid pid, + gint status, + gpointer user_data) +{ + DEBUG ("Child %" G_PID_FORMAT " exited %s", pid, + g_spawn_check_exit_status (status, NULL) ? "normally" : "abnormally"); + g_spawn_close_pid (pid); +} + +static void +export_tracker2_data (NautilusTagManager *self) +{ + gchar *argv[] = {"tracker3", "export", "--2to3", "files-starred", "--keyfile", NULL}; + gint stdout_fd; + GPid child_pid; + g_autoptr (GError) error = NULL; + gboolean success; + g_autoptr (GInputStream) stream = NULL; + GSpawnFlags flags; + + flags = G_SPAWN_DO_NOT_REAP_CHILD | + G_SPAWN_STDERR_TO_DEV_NULL | + G_SPAWN_SEARCH_PATH; + success = g_spawn_async_with_pipes (NULL, + argv, + NULL, + flags, + NULL, + NULL, + &child_pid, + NULL, + &stdout_fd, + NULL, + &error); + if (!success) + { + g_warning ("Tracker 2 migration: Couldn't run `tracker3`: %s", error->message); + return; + } + + g_child_watch_add (child_pid, child_watch_cb, NULL); + + stream = g_unix_input_stream_new (stdout_fd, TRUE); + g_input_stream_read_bytes_async (stream, + TRACKER2_MAX_IMPORT_BYTES, + G_PRIORITY_LOW, + self->cancellable, + export_tracker2_data_cb, + self); +} + +void +nautilus_tag_manager_maybe_migrate_tracker2_data (NautilusTagManager *self) +{ + g_autofree gchar *path = tracker2_migration_stamp (); + + if (g_file_test (path, G_FILE_TEST_EXISTS)) + { + DEBUG ("Tracker 2 migration: already completed."); + } + else + { + DEBUG ("Tracker 2 migration: starting."); + export_tracker2_data (self); + } +} diff --git a/src/nautilus-tag-manager.h b/src/nautilus-tag-manager.h new file mode 100644 index 0000000..5bfc3c7 --- /dev/null +++ b/src/nautilus-tag-manager.h @@ -0,0 +1,61 @@ +/* nautilus-tag-manager.h + * + * Copyright (C) 2017 Alexandru Pandelea + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 2 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 warranty of + * MERCHANTABILITY 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 . + */ + +#pragma once + +#include +#include +#include + +G_BEGIN_DECLS + +#define NAUTILUS_TYPE_TAG_MANAGER (nautilus_tag_manager_get_type ()) + +G_DECLARE_FINAL_TYPE (NautilusTagManager, nautilus_tag_manager, NAUTILUS, TAG_MANAGER, GObject); + +NautilusTagManager* nautilus_tag_manager_new (void); +NautilusTagManager* nautilus_tag_manager_new_dummy (void); +NautilusTagManager* nautilus_tag_manager_get (void); + +GList* nautilus_tag_manager_get_starred_files (NautilusTagManager *self); + +void nautilus_tag_manager_star_files (NautilusTagManager *self, + GObject *object, + GList *selection, + GAsyncReadyCallback callback, + GCancellable *cancellable); + +void nautilus_tag_manager_unstar_files (NautilusTagManager *self, + GObject *object, + GList *selection, + GAsyncReadyCallback callback, + GCancellable *cancellable); + + +gboolean nautilus_tag_manager_file_is_starred (NautilusTagManager *self, + const gchar *file_uri); + +gboolean nautilus_tag_manager_can_star_contents (NautilusTagManager *self, + GFile *directory); +void nautilus_tag_manager_update_moved_uris (NautilusTagManager *tag_manager, + GFile *src, + GFile *dest); + +void nautilus_tag_manager_maybe_migrate_tracker2_data (NautilusTagManager *self); + +G_END_DECLS diff --git a/src/nautilus-thumbnails.c b/src/nautilus-thumbnails.c new file mode 100644 index 0000000..790b4e3 --- /dev/null +++ b/src/nautilus-thumbnails.c @@ -0,0 +1,598 @@ +/* + * nautilus-thumbnails.h: Thumbnail code for icon factory. + * + * Copyright (C) 2000, 2001 Eazel, Inc. + * Copyright (C) 2002, 2003 Red Hat, Inc. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License as + * published by the Free Software Foundation; either version 2 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 warranty of + * MERCHANTABILITY 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 . + * + * Author: Andy Hertzfeld + */ + +#include +#include "nautilus-thumbnails.h" + +#define GNOME_DESKTOP_USE_UNSTABLE_API + +#include "nautilus-directory-notify.h" +#include "nautilus-global-preferences.h" +#include "nautilus-file-utilities.h" +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#define DEBUG_FLAG NAUTILUS_DEBUG_THUMBNAILS +#include "nautilus-debug.h" + +#include "nautilus-file-private.h" + +/* Should never be a reasonable actual mtime */ +#define INVALID_MTIME 0 + +/* Cool-off period between last file modification time and thumbnail creation */ +#define THUMBNAIL_CREATION_DELAY_SECS 3 + +static void thumbnail_thread_func (GTask *task, + gpointer source_object, + gpointer task_data, + GCancellable *cancellable); + +/* structure used for making thumbnails, associating a uri with where the thumbnail is to be stored */ + +typedef struct +{ + char *image_uri; + char *mime_type; + time_t original_file_mtime; +} NautilusThumbnailInfo; + +/* + * Thumbnail thread state. + */ + +/* The id of the idle handler used to start the thumbnail thread, or 0 if no + * idle handler is currently registered. */ +static guint thumbnail_thread_starter_id = 0; + +/* Our mutex used when accessing data shared between the main thread and the + * thumbnail thread, i.e. the thumbnail_thread_is_running flag and the + * thumbnails_to_make list. */ +static GMutex thumbnails_mutex; + +/* A flag to indicate whether a thumbnail thread is running, so we don't + * start more than one. Lock thumbnails_mutex when accessing this. */ +static volatile gboolean thumbnail_thread_is_running = FALSE; + +/* The list of NautilusThumbnailInfo structs containing information about the + * thumbnails we are making. Lock thumbnails_mutex when accessing this. */ +static volatile GQueue thumbnails_to_make = G_QUEUE_INIT; + +/* Quickly check if uri is in thumbnails_to_make list */ +static GHashTable *thumbnails_to_make_hash = NULL; + +/* The currently thumbnailed icon. it also exists in the thumbnails_to_make list + * to avoid adding it again. Lock thumbnails_mutex when accessing this. */ +static NautilusThumbnailInfo *currently_thumbnailing = NULL; + +static gboolean +get_file_mtime (const char *file_uri, + time_t *mtime) +{ + GFile *file; + GFileInfo *info; + gboolean ret; + + ret = FALSE; + *mtime = INVALID_MTIME; + + file = g_file_new_for_uri (file_uri); + info = g_file_query_info (file, G_FILE_ATTRIBUTE_TIME_MODIFIED, 0, NULL, NULL); + if (info) + { + if (g_file_info_has_attribute (info, G_FILE_ATTRIBUTE_TIME_MODIFIED)) + { + *mtime = g_file_info_get_attribute_uint64 (info, G_FILE_ATTRIBUTE_TIME_MODIFIED); + ret = TRUE; + } + + g_object_unref (info); + } + g_object_unref (file); + + return ret; +} + +static void +free_thumbnail_info (NautilusThumbnailInfo *info) +{ + g_free (info->image_uri); + g_free (info->mime_type); + g_free (info); +} + +static GnomeDesktopThumbnailFactory * +get_thumbnail_factory (void) +{ + static GnomeDesktopThumbnailFactory *thumbnail_factory = NULL; + + if (thumbnail_factory == NULL) + { + GdkDisplay *display = gdk_display_get_default (); + GListModel *monitors = gdk_display_get_monitors (display); + gint max_scale = 1; + GnomeDesktopThumbnailSize size; + + for (guint i = 0; i < g_list_model_get_n_items (monitors); i++) + { + g_autoptr (GdkMonitor) monitor = g_list_model_get_item (monitors, i); + + max_scale = MAX (max_scale, gdk_monitor_get_scale_factor (monitor)); + } + + if (max_scale <= 1) + { + size = GNOME_DESKTOP_THUMBNAIL_SIZE_LARGE; + } + else if (max_scale <= 2) + { + size = GNOME_DESKTOP_THUMBNAIL_SIZE_XLARGE; + } + else + { + size = GNOME_DESKTOP_THUMBNAIL_SIZE_XXLARGE; + } + + thumbnail_factory = gnome_desktop_thumbnail_factory_new (size); + } + + return thumbnail_factory; +} + + +/* This function is added as a very low priority idle function to start the + * thread to create any needed thumbnails. It is added with a very low priority + * so that it doesn't delay showing the directory in the icon/list views. + * We want to show the files in the directory as quickly as possible. */ +static gboolean +thumbnail_thread_starter_cb (gpointer data) +{ + GTask *task; + + DEBUG ("(Main Thread) Creating thumbnails thread\n"); + + /* We set a flag to indicate the thread is running, so we don't create + * a new one. We don't need to lock a mutex here, as the thumbnail + * thread isn't running yet. And we know we won't create the thread + * twice, as we also check thumbnail_thread_starter_id before + * scheduling this idle function. */ + thumbnail_thread_is_running = TRUE; + task = g_task_new (NULL, NULL, NULL, NULL); + g_task_run_in_thread (task, thumbnail_thread_func); + thumbnail_thread_starter_id = 0; + + g_object_unref (task); + + return FALSE; +} + +void +nautilus_thumbnail_remove_from_queue (const char *file_uri) +{ + GList *node; + + DEBUG ("(Remove from queue) Locking mutex\n"); + + g_mutex_lock (&thumbnails_mutex); + + /********************************* + * MUTEX LOCKED + *********************************/ + + if (thumbnails_to_make_hash) + { + node = g_hash_table_lookup (thumbnails_to_make_hash, file_uri); + + if (node && node->data != currently_thumbnailing) + { + g_hash_table_remove (thumbnails_to_make_hash, file_uri); + free_thumbnail_info (node->data); + g_queue_delete_link ((GQueue *) &thumbnails_to_make, node); + } + } + + /********************************* + * MUTEX UNLOCKED + *********************************/ + + DEBUG ("(Remove from queue) Unlocking mutex\n"); + + g_mutex_unlock (&thumbnails_mutex); +} + +void +nautilus_thumbnail_prioritize (const char *file_uri) +{ + GList *node; + + DEBUG ("(Prioritize) Locking mutex\n"); + + g_mutex_lock (&thumbnails_mutex); + + /********************************* + * MUTEX LOCKED + *********************************/ + + if (thumbnails_to_make_hash) + { + node = g_hash_table_lookup (thumbnails_to_make_hash, file_uri); + + if (node && node->data != currently_thumbnailing) + { + g_queue_unlink ((GQueue *) &thumbnails_to_make, node); + g_queue_push_head_link ((GQueue *) &thumbnails_to_make, node); + } + } + + /********************************* + * MUTEX UNLOCKED + *********************************/ + + DEBUG ("(Prioritize) Unlocking mutex\n"); + + g_mutex_unlock (&thumbnails_mutex); +} + + +/*************************************************************************** + * Thumbnail Thread Functions. + ***************************************************************************/ + + +/* This is a one-shot idle callback called from the main loop to call + * notify_file_changed() for a thumbnail. It frees the uri afterwards. + * We do this in an idle callback as I don't think nautilus_file_changed() is + * thread-safe. */ +static gboolean +thumbnail_thread_notify_file_changed (gpointer image_uri) +{ + NautilusFile *file; + + file = nautilus_file_get_by_uri ((char *) image_uri); + + DEBUG ("(Thumbnail Thread) Notifying file changed file:%p uri: %s\n", file, (char *) image_uri); + + if (file != NULL) + { + nautilus_file_set_is_thumbnailing (file, FALSE); + nautilus_file_invalidate_attributes (file, + NAUTILUS_FILE_ATTRIBUTE_THUMBNAIL | + NAUTILUS_FILE_ATTRIBUTE_INFO); + nautilus_file_unref (file); + } + g_free (image_uri); + + return FALSE; +} + +static GHashTable * +get_types_table (void) +{ + static GHashTable *image_mime_types = NULL; + GSList *format_list, *l; + char **types; + int i; + + if (image_mime_types == NULL) + { + image_mime_types = + g_hash_table_new_full (g_str_hash, g_str_equal, + g_free, NULL); + + format_list = gdk_pixbuf_get_formats (); + for (l = format_list; l; l = l->next) + { + types = gdk_pixbuf_format_get_mime_types (l->data); + + for (i = 0; types[i] != NULL; i++) + { + g_hash_table_insert (image_mime_types, + types [i], + GUINT_TO_POINTER (1)); + } + + g_free (types); + } + + g_slist_free (format_list); + } + + return image_mime_types; +} + +static gboolean +pixbuf_can_load_type (const char *mime_type) +{ + GHashTable *image_mime_types; + + image_mime_types = get_types_table (); + if (g_hash_table_lookup (image_mime_types, mime_type)) + { + return TRUE; + } + + return FALSE; +} + +gboolean +nautilus_thumbnail_is_mimetype_limited_by_size (const char *mime_type) +{ + return pixbuf_can_load_type (mime_type); +} + +gboolean +nautilus_can_thumbnail (NautilusFile *file) +{ + GnomeDesktopThumbnailFactory *factory; + gboolean res; + char *uri; + time_t mtime; + char *mime_type; + + uri = nautilus_file_get_uri (file); + mime_type = nautilus_file_get_mime_type (file); + mtime = nautilus_file_get_mtime (file); + + factory = get_thumbnail_factory (); + res = gnome_desktop_thumbnail_factory_can_thumbnail (factory, + uri, + mime_type, + mtime); + g_free (mime_type); + g_free (uri); + + return res; +} + +void +nautilus_create_thumbnail (NautilusFile *file) +{ + time_t file_mtime = 0; + NautilusThumbnailInfo *info; + NautilusThumbnailInfo *existing_info; + GList *existing, *node; + + nautilus_file_set_is_thumbnailing (file, TRUE); + + info = g_new0 (NautilusThumbnailInfo, 1); + info->image_uri = nautilus_file_get_uri (file); + info->mime_type = nautilus_file_get_mime_type (file); + + /* Hopefully the NautilusFile will already have the image file mtime, + * so we can just use that. Otherwise we have to get it ourselves. */ + if (file->details->got_file_info && + file->details->file_info_is_up_to_date && + file->details->mtime != 0) + { + file_mtime = file->details->mtime; + } + else + { + get_file_mtime (info->image_uri, &file_mtime); + } + + info->original_file_mtime = file_mtime; + + + DEBUG ("(Main Thread) Locking mutex\n"); + + g_mutex_lock (&thumbnails_mutex); + + /********************************* + * MUTEX LOCKED + *********************************/ + + if (thumbnails_to_make_hash == NULL) + { + thumbnails_to_make_hash = g_hash_table_new (g_str_hash, + g_str_equal); + } + + /* Check if it is already in the list of thumbnails to make. */ + existing = g_hash_table_lookup (thumbnails_to_make_hash, info->image_uri); + if (existing == NULL) + { + /* Add the thumbnail to the list. */ + DEBUG ("(Main Thread) Adding thumbnail: %s\n", + info->image_uri); + g_queue_push_tail ((GQueue *) &thumbnails_to_make, info); + node = g_queue_peek_tail_link ((GQueue *) &thumbnails_to_make); + g_hash_table_insert (thumbnails_to_make_hash, + info->image_uri, + node); + /* If the thumbnail thread isn't running, and we haven't + * scheduled an idle function to start it up, do that now. + * We don't want to start it until all the other work is done, + * so the GUI will be updated as quickly as possible.*/ + if (thumbnail_thread_is_running == FALSE && + thumbnail_thread_starter_id == 0) + { + thumbnail_thread_starter_id = g_idle_add_full (G_PRIORITY_LOW, thumbnail_thread_starter_cb, NULL, NULL); + } + } + else + { + DEBUG ("(Main Thread) Updating non-current mtime: %s\n", + info->image_uri); + + /* The file in the queue might need a new original mtime */ + existing_info = existing->data; + existing_info->original_file_mtime = info->original_file_mtime; + free_thumbnail_info (info); + } + + /********************************* + * MUTEX UNLOCKED + *********************************/ + + DEBUG ("(Main Thread) Unlocking mutex\n"); + + g_mutex_unlock (&thumbnails_mutex); +} + +/* thumbnail_thread is invoked as a separate thread to to make thumbnails. */ +static void +thumbnail_thread_func (GTask *task, + gpointer source_object, + gpointer task_data, + GCancellable *cancellable) +{ + GnomeDesktopThumbnailFactory *thumbnail_factory; + NautilusThumbnailInfo *info = NULL; + GdkPixbuf *pixbuf; + time_t current_orig_mtime = 0; + time_t current_time; + GList *node; + GError *error = NULL; + + thumbnail_factory = get_thumbnail_factory (); + + /* We loop until there are no more thumbails to make, at which point + * we exit the thread. */ + for (;;) + { + DEBUG ("(Thumbnail Thread) Locking mutex\n"); + + g_mutex_lock (&thumbnails_mutex); + + /********************************* + * MUTEX LOCKED + *********************************/ + + /* Pop the last thumbnail we just made off the head of the + * list and free it. I did this here so we only have to lock + * the mutex once per thumbnail, rather than once before + * creating it and once after. + * Don't pop the thumbnail off the queue if the original file + * mtime of the request changed. Then we need to redo the thumbnail. + */ + if (currently_thumbnailing && + currently_thumbnailing->original_file_mtime == current_orig_mtime) + { + g_assert (info == currently_thumbnailing); + node = g_hash_table_lookup (thumbnails_to_make_hash, info->image_uri); + g_assert (node != NULL); + g_hash_table_remove (thumbnails_to_make_hash, info->image_uri); + free_thumbnail_info (info); + g_queue_delete_link ((GQueue *) &thumbnails_to_make, node); + } + currently_thumbnailing = NULL; + + /* If there are no more thumbnails to make, reset the + * thumbnail_thread_is_running flag, unlock the mutex, and + * exit the thread. */ + if (g_queue_is_empty ((GQueue *) &thumbnails_to_make)) + { + DEBUG ("(Thumbnail Thread) Exiting\n"); + + thumbnail_thread_is_running = FALSE; + g_mutex_unlock (&thumbnails_mutex); + return; + } + + /* Get the next one to make. We leave it on the list until it + * is created so the main thread doesn't add it again while we + * are creating it. */ + info = g_queue_peek_head ((GQueue *) &thumbnails_to_make); + currently_thumbnailing = info; + current_orig_mtime = info->original_file_mtime; + /********************************* + * MUTEX UNLOCKED + *********************************/ + + DEBUG ("(Thumbnail Thread) Unlocking mutex\n"); + + g_mutex_unlock (&thumbnails_mutex); + + time (¤t_time); + + /* Don't try to create a thumbnail if the file was modified recently. + * This prevents constant re-thumbnailing of changing files. */ + if (current_time < current_orig_mtime + THUMBNAIL_CREATION_DELAY_SECS && + current_time >= current_orig_mtime) + { + DEBUG ("(Thumbnail Thread) Skipping: %s\n", + info->image_uri); + + /* Reschedule thumbnailing via a change notification */ + g_timeout_add_seconds (1, thumbnail_thread_notify_file_changed, + g_strdup (info->image_uri)); + continue; + } + + /* Create the thumbnail. */ + DEBUG ("(Thumbnail Thread) Creating thumbnail: %s\n", + info->image_uri); + + pixbuf = gnome_desktop_thumbnail_factory_generate_thumbnail (thumbnail_factory, + info->image_uri, + info->mime_type, + NULL, + &error); + + if (pixbuf) + { + DEBUG ("(Thumbnail Thread) Saving thumbnail: %s\n", + info->image_uri); + + gnome_desktop_thumbnail_factory_save_thumbnail (thumbnail_factory, + pixbuf, + info->image_uri, + current_orig_mtime, + NULL, + &error); + if (error) + { + DEBUG ("(Thumbnail Thread) Saving thumbnail failed: %s (%s)\n", + info->image_uri, error->message); + g_clear_error (&error); + } + g_object_unref (pixbuf); + } + else + { + DEBUG ("(Thumbnail Thread) Thumbnail failed: %s (%s)\n", + info->image_uri, error->message); + g_clear_error (&error); + + gnome_desktop_thumbnail_factory_create_failed_thumbnail (thumbnail_factory, + info->image_uri, + current_orig_mtime, + NULL, NULL); + } + /* We need to call nautilus_file_changed(), but I don't think that is + * thread safe. So add an idle handler and do it from the main loop. */ + g_idle_add_full (G_PRIORITY_HIGH_IDLE, + thumbnail_thread_notify_file_changed, + g_strdup (info->image_uri), NULL); + } +} diff --git a/src/nautilus-thumbnails.h b/src/nautilus-thumbnails.h new file mode 100644 index 0000000..6babe68 --- /dev/null +++ b/src/nautilus-thumbnails.h @@ -0,0 +1,35 @@ +/* + nautilus-thumbnails.h: Thumbnail code for icon factory. + + Copyright (C) 2000 Eazel, Inc. + + This program is free software; you can redistribute it and/or + modify it under the terms of the GNU General Public License as + published by the Free Software Foundation; either version 2 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 warranty of + MERCHANTABILITY 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 . + + Author: Andy Hertzfeld +*/ + +#pragma once + +#include +#include "nautilus-file.h" + +/* Returns NULL if there's no thumbnail yet. */ +void nautilus_create_thumbnail (NautilusFile *file); +gboolean nautilus_can_thumbnail (NautilusFile *file); +gboolean nautilus_thumbnail_is_mimetype_limited_by_size + (const char *mime_type); + +/* Queue handling: */ +void nautilus_thumbnail_remove_from_queue (const char *file_uri); +void nautilus_thumbnail_prioritize (const char *file_uri); \ No newline at end of file diff --git a/src/nautilus-toolbar-menu-sections.h b/src/nautilus-toolbar-menu-sections.h new file mode 100644 index 0000000..8f5a986 --- /dev/null +++ b/src/nautilus-toolbar-menu-sections.h @@ -0,0 +1,30 @@ +/* + * Copyright (C) 2016 Neil Herald + * + * Nautilus is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License as + * published by the Free Software Foundation; either version 2 of the + * License, or (at your option) any later version. + * + * Nautilus is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * 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 . + */ + +#pragma once + +#include + +G_BEGIN_DECLS + +typedef struct _NautilusToolbarMenuSections NautilusToolbarMenuSections; + +struct _NautilusToolbarMenuSections { + GMenuModel *sort_section; +}; + +G_END_DECLS diff --git a/src/nautilus-toolbar.c b/src/nautilus-toolbar.c new file mode 100644 index 0000000..aad0fd3 --- /dev/null +++ b/src/nautilus-toolbar.c @@ -0,0 +1,644 @@ +/* + * Nautilus + * + * Copyright (C) 2011, Red Hat, Inc. + * + * Nautilus is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License as + * published by the Free Software Foundation; either version 2 of the + * License, or (at your option) any later version. + * + * Nautilus is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * 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 . + * + * Author: Cosimo Cecchi + * + */ + +#include "nautilus-toolbar.h" + +#include +#include + +#include "nautilus-application.h" +#include "nautilus-bookmark.h" +#include "nautilus-file-undo-manager.h" +#include "nautilus-global-preferences.h" +#include "nautilus-history-controls.h" +#include "nautilus-location-entry.h" +#include "nautilus-pathbar.h" +#include "nautilus-progress-indicator.h" +#include "nautilus-view-controls.h" +#include "nautilus-ui-utilities.h" +#include "nautilus-window.h" + +struct _NautilusToolbar +{ + AdwBin parent_instance; + + NautilusWindow *window; + + GtkWidget *path_bar_container; + GtkWidget *location_entry_container; + GtkWidget *search_container; + GtkWidget *toolbar_switcher; + GtkWidget *path_bar; + GtkWidget *location_entry; + + gboolean show_location_entry; + + GtkWidget *app_button; + GMenuModel *undo_redo_section; + + GtkWidget *sidebar_button; + gboolean show_sidebar_button; + gboolean sidebar_button_active; + + gboolean show_toolbar_children; + + GtkWidget *search_button; + + GtkWidget *location_entry_close_button; + + /* active slot & bindings */ + NautilusWindowSlot *window_slot; + GBinding *search_binding; +}; + +enum +{ + PROP_0, + PROP_SHOW_LOCATION_ENTRY, + PROP_WINDOW_SLOT, + PROP_SEARCHING, + PROP_SHOW_SIDEBAR_BUTTON, + PROP_SIDEBAR_BUTTON_ACTIVE, + PROP_SHOW_TOOLBAR_CHILDREN, + NUM_PROPERTIES +}; + +static GParamSpec *properties[NUM_PROPERTIES] = { NULL, }; + +G_DEFINE_TYPE (NautilusToolbar, nautilus_toolbar, ADW_TYPE_BIN); + +static void nautilus_toolbar_set_window_slot_real (NautilusToolbar *self, + NautilusWindowSlot *slot); +static void +toolbar_update_appearance (NautilusToolbar *self) +{ + gboolean show_location_entry; + + show_location_entry = self->show_location_entry || + g_settings_get_boolean (nautilus_preferences, + NAUTILUS_PREFERENCES_ALWAYS_USE_LOCATION_ENTRY); + + if (self->window_slot != NULL && + nautilus_window_slot_get_searching (self->window_slot)) + { + gtk_stack_set_visible_child_name (GTK_STACK (self->toolbar_switcher), "search"); + } + else if (show_location_entry) + { + gtk_stack_set_visible_child_name (GTK_STACK (self->toolbar_switcher), "location"); + } + else + { + gtk_stack_set_visible_child_name (GTK_STACK (self->toolbar_switcher), "pathbar"); + } +} + +static void +update_action (NautilusToolbar *self, + const char *action_name, + gboolean enabled) +{ + GtkWidget *window; + GAction *action; + + window = gtk_widget_get_ancestor (GTK_WIDGET (self), NAUTILUS_TYPE_WINDOW); + + /* Activate/deactivate */ + action = g_action_map_lookup_action (G_ACTION_MAP (window), action_name); + g_simple_action_set_enabled (G_SIMPLE_ACTION (action), enabled); +} + +static void +undo_manager_changed (NautilusToolbar *self) +{ + NautilusFileUndoInfo *info; + NautilusFileUndoManagerState undo_state; + gboolean undo_active; + gboolean redo_active; + g_autofree gchar *undo_label = NULL; + g_autofree gchar *redo_label = NULL; + g_autofree gchar *undo_description = NULL; + g_autofree gchar *redo_description = NULL; + gboolean is_undo; + g_autoptr (GMenu) updated_section = g_menu_new (); + g_autoptr (GMenuItem) undo_menu_item = NULL; + g_autoptr (GMenuItem) redo_menu_item = NULL; + + /* Look up the last action from the undo manager, and get the text that + * describes it, e.g. "Undo Create Folder"/"Redo Create Folder" + */ + info = nautilus_file_undo_manager_get_action (); + undo_state = nautilus_file_undo_manager_get_state (); + undo_active = redo_active = FALSE; + if (info != NULL && undo_state > NAUTILUS_FILE_UNDO_MANAGER_STATE_NONE) + { + is_undo = undo_state == NAUTILUS_FILE_UNDO_MANAGER_STATE_UNDO; + + /* The last action can either be undone/redone. Activate the corresponding + * menu item and deactivate the other + */ + undo_active = is_undo; + redo_active = !is_undo; + nautilus_file_undo_info_get_strings (info, &undo_label, &undo_description, + &redo_label, &redo_description); + } + + /* Set the label of the undo and redo menu items, and activate them appropriately + */ + if (!undo_active || undo_label == NULL) + { + g_free (undo_label); + undo_label = g_strdup (_("_Undo")); + } + undo_menu_item = g_menu_item_new (undo_label, "win.undo"); + g_menu_append_item (updated_section, undo_menu_item); + update_action (self, "undo", undo_active); + + if (!redo_active || redo_label == NULL) + { + g_free (redo_label); + redo_label = g_strdup (_("_Redo")); + } + redo_menu_item = g_menu_item_new (redo_label, "win.redo"); + g_menu_append_item (updated_section, redo_menu_item); + update_action (self, "redo", redo_active); + + nautilus_gmenu_set_from_model (G_MENU (self->undo_redo_section), + G_MENU_MODEL (updated_section)); +} + +static void +on_location_entry_close (GtkWidget *close_button, + NautilusToolbar *self) +{ + nautilus_toolbar_set_show_location_entry (self, FALSE); +} + +static void +on_location_entry_focus_leave (GtkEventControllerFocus *controller, + gpointer user_data) +{ + NautilusToolbar *toolbar; + GtkWidget *focus_widget; + + toolbar = NAUTILUS_TOOLBAR (user_data); + + /* The location entry is a transient: it should hide when it loses focus. + * + * However, if we lose focus because the window itself lost focus, then the + * location entry should persist, because this may happen due to the user + * switching keyboard layout/input method; or they may want to copy/drop + * an path from another window/app. We detect this case by looking at the + * focus widget of the window (GtkRoot). + */ + + focus_widget = gtk_root_get_focus (gtk_widget_get_root (GTK_WIDGET (toolbar))); + if (focus_widget != NULL && + gtk_widget_is_ancestor (focus_widget, GTK_WIDGET (toolbar->location_entry))) + { + return; + } + + nautilus_toolbar_set_show_location_entry (toolbar, FALSE); +} + +static void +nautilus_toolbar_constructed (GObject *object) +{ + NautilusToolbar *self = NAUTILUS_TOOLBAR (object); + GtkEventController *controller; + + self->path_bar = GTK_WIDGET (g_object_new (NAUTILUS_TYPE_PATH_BAR, NULL)); + gtk_box_append (GTK_BOX (self->path_bar_container), + self->path_bar); + + self->location_entry = nautilus_location_entry_new (); + gtk_box_append (GTK_BOX (self->location_entry_container), + self->location_entry); + self->location_entry_close_button = gtk_button_new_from_icon_name ("window-close-symbolic"); + gtk_box_append (GTK_BOX (self->location_entry_container), + self->location_entry_close_button); + g_signal_connect (self->location_entry_close_button, "clicked", + G_CALLBACK (on_location_entry_close), self); + + controller = gtk_event_controller_focus_new (); + gtk_widget_add_controller (self->location_entry, controller); + g_signal_connect (controller, "leave", + G_CALLBACK (on_location_entry_focus_leave), self); + + /* Setting a max width on one entry to effectively set a max expansion for + * the whole title widget. */ + gtk_editable_set_max_width_chars (GTK_EDITABLE (self->location_entry), 88); + + gtk_widget_show (GTK_WIDGET (self)); + toolbar_update_appearance (self); +} + +static void +nautilus_toolbar_init (NautilusToolbar *self) +{ + g_type_ensure (NAUTILUS_TYPE_HISTORY_CONTROLS); + g_type_ensure (NAUTILUS_TYPE_PROGRESS_INDICATOR); + g_type_ensure (NAUTILUS_TYPE_VIEW_CONTROLS); + + gtk_widget_init_template (GTK_WIDGET (self)); +} + +void +nautilus_toolbar_on_window_constructed (NautilusToolbar *self) +{ + /* undo_manager_changed manipulates the window actions, so set it up + * after the window and it's actions have been constructed + */ + g_signal_connect_object (nautilus_file_undo_manager_get (), + "undo-changed", + G_CALLBACK (undo_manager_changed), + self, + G_CONNECT_SWAPPED); + + undo_manager_changed (self); +} + +static void +nautilus_toolbar_get_property (GObject *object, + guint property_id, + GValue *value, + GParamSpec *pspec) +{ + NautilusToolbar *self = NAUTILUS_TOOLBAR (object); + + switch (property_id) + { + case PROP_SHOW_LOCATION_ENTRY: + { + g_value_set_boolean (value, self->show_location_entry); + } + break; + + case PROP_WINDOW_SLOT: + { + g_value_set_object (value, self->window_slot); + } + break; + + case PROP_SEARCHING: + { + g_value_set_boolean (value, gtk_toggle_button_get_active (GTK_TOGGLE_BUTTON (self->search_button))); + } + break; + + case PROP_SHOW_SIDEBAR_BUTTON: + { + g_value_set_boolean (value, self->show_sidebar_button); + } + break; + + case PROP_SIDEBAR_BUTTON_ACTIVE: + { + g_value_set_boolean (value, self->sidebar_button_active); + } + break; + + case PROP_SHOW_TOOLBAR_CHILDREN: + { + g_value_set_boolean (value, self->show_toolbar_children); + } + break; + + default: + { + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec); + } + break; + } +} + +static void +on_window_slot_destroyed (gpointer data, + GObject *where_the_object_was) +{ + NautilusToolbar *self; + + self = NAUTILUS_TOOLBAR (data); + + /* The window slot was finalized, and the binding has already been removed. + * Null it here, so that dispose() does not trip over itself when removing it. + */ + self->search_binding = NULL; + + nautilus_toolbar_set_window_slot_real (self, NULL); +} + +static void +nautilus_toolbar_set_property (GObject *object, + guint property_id, + const GValue *value, + GParamSpec *pspec) +{ + NautilusToolbar *self = NAUTILUS_TOOLBAR (object); + + switch (property_id) + { + case PROP_SHOW_LOCATION_ENTRY: + { + nautilus_toolbar_set_show_location_entry (self, g_value_get_boolean (value)); + } + break; + + case PROP_WINDOW_SLOT: + { + nautilus_toolbar_set_window_slot (self, g_value_get_object (value)); + } + break; + + case PROP_SEARCHING: + { + gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (self->search_button), + g_value_get_boolean (value)); + } + break; + + case PROP_SHOW_SIDEBAR_BUTTON: + { + self->show_sidebar_button = g_value_get_boolean (value); + } + break; + + case PROP_SIDEBAR_BUTTON_ACTIVE: + { + self->sidebar_button_active = g_value_get_boolean (value); + } + break; + + case PROP_SHOW_TOOLBAR_CHILDREN: + { + self->show_toolbar_children = g_value_get_boolean (value); + } + break; + + default: + { + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec); + } + break; + } +} + +static void +nautilus_toolbar_dispose (GObject *object) +{ + NautilusToolbar *self; + + self = NAUTILUS_TOOLBAR (object); + + g_clear_pointer (&self->search_binding, g_binding_unbind); + + G_OBJECT_CLASS (nautilus_toolbar_parent_class)->dispose (object); +} + +static void +nautilus_toolbar_finalize (GObject *obj) +{ + NautilusToolbar *self = NAUTILUS_TOOLBAR (obj); + + g_signal_handlers_disconnect_by_func (nautilus_preferences, + toolbar_update_appearance, self); + + if (self->window_slot != NULL) + { + g_signal_handlers_disconnect_by_data (self->window_slot, self); + g_object_weak_unref (G_OBJECT (self->window_slot), + on_window_slot_destroyed, self); + self->window_slot = NULL; + } + + G_OBJECT_CLASS (nautilus_toolbar_parent_class)->finalize (obj); +} + +static void +nautilus_toolbar_class_init (NautilusToolbarClass *klass) +{ + GObjectClass *oclass; + GtkWidgetClass *widget_class; + + widget_class = GTK_WIDGET_CLASS (klass); + oclass = G_OBJECT_CLASS (klass); + oclass->get_property = nautilus_toolbar_get_property; + oclass->set_property = nautilus_toolbar_set_property; + oclass->dispose = nautilus_toolbar_dispose; + oclass->finalize = nautilus_toolbar_finalize; + oclass->constructed = nautilus_toolbar_constructed; + + properties[PROP_SHOW_LOCATION_ENTRY] = + g_param_spec_boolean ("show-location-entry", + "Whether to show the location entry", + "Whether to show the location entry instead of the pathbar", + FALSE, + G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS); + + properties [PROP_WINDOW_SLOT] = + g_param_spec_object ("window-slot", + "Window slot currently active", + "Window slot currently acive", + NAUTILUS_TYPE_WINDOW_SLOT, + (G_PARAM_READWRITE | + G_PARAM_STATIC_STRINGS)); + + properties [PROP_SEARCHING] = + g_param_spec_boolean ("searching", + "Current view is searching", + "Whether the current view is searching or not", + FALSE, + G_PARAM_READWRITE); + + properties[PROP_SHOW_SIDEBAR_BUTTON] = + g_param_spec_boolean ("show-sidebar-button", NULL, NULL, FALSE, + G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS); + properties[PROP_SIDEBAR_BUTTON_ACTIVE] = + g_param_spec_boolean ("sidebar-button-active", NULL, NULL, FALSE, + G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS); + properties[PROP_SHOW_TOOLBAR_CHILDREN] = + g_param_spec_boolean ("show-toolbar-children", NULL, NULL, TRUE, + G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS); + + g_object_class_install_properties (oclass, NUM_PROPERTIES, properties); + + gtk_widget_class_set_template_from_resource (widget_class, + "/org/gnome/nautilus/ui/nautilus-toolbar.ui"); + + gtk_widget_class_bind_template_child (widget_class, NautilusToolbar, app_button); + gtk_widget_class_bind_template_child (widget_class, NautilusToolbar, undo_redo_section); + gtk_widget_class_bind_template_child (widget_class, NautilusToolbar, toolbar_switcher); + gtk_widget_class_bind_template_child (widget_class, NautilusToolbar, search_container); + gtk_widget_class_bind_template_child (widget_class, NautilusToolbar, path_bar_container); + gtk_widget_class_bind_template_child (widget_class, NautilusToolbar, location_entry_container); + + gtk_widget_class_bind_template_child (widget_class, NautilusToolbar, search_button); + + gtk_widget_class_set_accessible_role (widget_class, GTK_ACCESSIBLE_ROLE_TOOLBAR); +} + +GtkWidget * +nautilus_toolbar_new (void) +{ + return g_object_new (NAUTILUS_TYPE_TOOLBAR, + NULL); +} + +GtkWidget * +nautilus_toolbar_get_path_bar (NautilusToolbar *self) +{ + return self->path_bar; +} + +GtkWidget * +nautilus_toolbar_get_location_entry (NautilusToolbar *self) +{ + return self->location_entry; +} + +void +nautilus_toolbar_set_show_location_entry (NautilusToolbar *self, + gboolean show_location_entry) +{ + if (show_location_entry != self->show_location_entry) + { + self->show_location_entry = show_location_entry; + toolbar_update_appearance (self); + + g_object_notify_by_pspec (G_OBJECT (self), properties[PROP_SHOW_LOCATION_ENTRY]); + } +} + +static void +box_remove_all_children (GtkBox *box) +{ + GtkWidget *child; + while ((child = gtk_widget_get_first_child (GTK_WIDGET (box))) != NULL) + { + gtk_box_remove (GTK_BOX (box), child); + } +} + +static void +slot_on_extensions_background_menu_changed (NautilusToolbar *self, + GParamSpec *param, + NautilusWindowSlot *slot) +{ + g_autoptr (GMenuModel) menu = NULL; + + menu = nautilus_window_slot_get_extensions_background_menu (slot); + nautilus_path_bar_set_extensions_background_menu (NAUTILUS_PATH_BAR (self->path_bar), + menu); +} + +static void +slot_on_templates_menu_changed (NautilusToolbar *self, + GParamSpec *param, + NautilusWindowSlot *slot) +{ + g_autoptr (GMenuModel) menu = NULL; + + menu = nautilus_window_slot_get_templates_menu (slot); + nautilus_path_bar_set_templates_menu (NAUTILUS_PATH_BAR (self->path_bar), + menu); +} + +/* Called from on_window_slot_destroyed(), since bindings and signal handlers + * are automatically removed once the slot goes away. + */ +static void +nautilus_toolbar_set_window_slot_real (NautilusToolbar *self, + NautilusWindowSlot *slot) +{ + g_autoptr (GList) children = NULL; + + self->window_slot = slot; + + if (self->window_slot != NULL) + { + g_object_weak_ref (G_OBJECT (self->window_slot), + on_window_slot_destroyed, + self); + + self->search_binding = g_object_bind_property (self->window_slot, "searching", + self, "searching", + G_BINDING_DEFAULT | G_BINDING_SYNC_CREATE); + + g_signal_connect_swapped (self->window_slot, "notify::extensions-background-menu", + G_CALLBACK (slot_on_extensions_background_menu_changed), self); + g_signal_connect_swapped (self->window_slot, "notify::templates-menu", + G_CALLBACK (slot_on_templates_menu_changed), self); + g_signal_connect_swapped (self->window_slot, "notify::searching", + G_CALLBACK (toolbar_update_appearance), self); + } + + box_remove_all_children (GTK_BOX (self->search_container)); + + if (self->window_slot != NULL) + { + gtk_box_append (GTK_BOX (self->search_container), + GTK_WIDGET (nautilus_window_slot_get_query_editor (self->window_slot))); + } + + toolbar_update_appearance (self); + + g_object_notify_by_pspec (G_OBJECT (self), properties[PROP_WINDOW_SLOT]); + g_object_notify_by_pspec (G_OBJECT (self), properties[PROP_SEARCHING]); +} + +void +nautilus_toolbar_set_window_slot (NautilusToolbar *self, + NautilusWindowSlot *window_slot) +{ + g_return_if_fail (NAUTILUS_IS_TOOLBAR (self)); + g_return_if_fail (window_slot == NULL || NAUTILUS_IS_WINDOW_SLOT (window_slot)); + + if (self->window_slot == window_slot) + { + return; + } + + g_clear_pointer (&self->search_binding, g_binding_unbind); + + if (self->window_slot != NULL) + { + g_signal_handlers_disconnect_by_data (self->window_slot, self); + g_object_weak_unref (G_OBJECT (self->window_slot), + on_window_slot_destroyed, self); + } + + nautilus_toolbar_set_window_slot_real (self, window_slot); +} + +gboolean +nautilus_toolbar_is_menu_visible (NautilusToolbar *self) +{ + GtkWidget *menu; + + g_return_val_if_fail (NAUTILUS_IS_TOOLBAR (self), FALSE); + + menu = GTK_WIDGET (gtk_menu_button_get_popover (GTK_MENU_BUTTON (self->app_button))); + g_return_val_if_fail (menu != NULL, FALSE); + + return gtk_widget_is_visible (menu); +} diff --git a/src/nautilus-toolbar.h b/src/nautilus-toolbar.h new file mode 100644 index 0000000..a61a3cb --- /dev/null +++ b/src/nautilus-toolbar.h @@ -0,0 +1,54 @@ + +/* + * Nautilus + * + * Copyright (C) 2011, Red Hat, Inc. + * + * Nautilus is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License as + * published by the Free Software Foundation; either version 2 of the + * License, or (at your option) any later version. + * + * Nautilus is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * 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 . + * + * Author: Cosimo Cecchi + * + */ + +#pragma once + +#include +#include + +#include "nautilus-window-slot.h" + +G_BEGIN_DECLS + +#define NAUTILUS_TYPE_TOOLBAR nautilus_toolbar_get_type() + +G_DECLARE_FINAL_TYPE (NautilusToolbar, nautilus_toolbar, NAUTILUS, TOOLBAR, AdwBin) + +GtkWidget *nautilus_toolbar_new (void); + +GtkWidget *nautilus_toolbar_get_path_bar (NautilusToolbar *self); +GtkWidget *nautilus_toolbar_get_location_entry (NautilusToolbar *self); + +void nautilus_toolbar_set_show_location_entry (NautilusToolbar *self, + gboolean show_location_entry); + +void nautilus_toolbar_set_active_slot (NautilusToolbar *toolbar, + NautilusWindowSlot *slot); + +gboolean nautilus_toolbar_is_menu_visible (NautilusToolbar *toolbar); + +void nautilus_toolbar_on_window_constructed (NautilusToolbar *toolbar); + +void nautilus_toolbar_set_window_slot (NautilusToolbar *self, + NautilusWindowSlot *window_slot); +G_END_DECLS diff --git a/src/nautilus-tracker-utilities.c b/src/nautilus-tracker-utilities.c new file mode 100644 index 0000000..b2e894a --- /dev/null +++ b/src/nautilus-tracker-utilities.c @@ -0,0 +1,148 @@ +/* nautilus-tracker-utilities.c + * + * Copyright 2019 Carlos Soriano + * Copyright 2020 Sam Thursfield + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License 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 warranty of + * MERCHANTABILITY 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 . + * + * SPDX-License-Identifier: GPL-3.0-or-later + */ + +#include "config.h" +#include "nautilus-tracker-utilities.h" + +/* Shared global connection to Tracker Miner FS */ +static const gchar *tracker_miner_fs_busname = NULL; +static TrackerSparqlConnection *tracker_miner_fs_connection = NULL; +static GError *tracker_miner_fs_error = NULL; + +static void +local_tracker_miner_fs_ready (GObject *source, + GAsyncResult *res, + gpointer user_data) +{ + tracker_miner_fs_connection = tracker_sparql_connection_new_finish (res, &tracker_miner_fs_error); + if (tracker_miner_fs_error != NULL) + { + g_critical ("Could not start local Tracker indexer at %s: %s", tracker_miner_fs_busname, tracker_miner_fs_error->message); + } +} + +static void +start_local_tracker_miner_fs (void) +{ + const gchar *busname = APPLICATION_ID ".Tracker3.Miner.Files"; + + g_message ("Starting %s", busname); + tracker_sparql_connection_bus_new_async (busname, NULL, NULL, NULL, local_tracker_miner_fs_ready, NULL); + + tracker_miner_fs_busname = busname; +} + +static gboolean +inside_flatpak (void) +{ + return g_file_test ("/.flatpak-info", G_FILE_TEST_EXISTS); +} + +static void +host_tracker_miner_fs_ready (GObject *source, + GAsyncResult *res, + gpointer user_data) +{ + tracker_miner_fs_connection = tracker_sparql_connection_bus_new_finish (res, &tracker_miner_fs_error); + if (tracker_miner_fs_error) + { + g_warning ("Unable to create connection for session-wide Tracker indexer: %s", (tracker_miner_fs_error)->message); + if (inside_flatpak ()) + { + g_clear_error (&tracker_miner_fs_error); + start_local_tracker_miner_fs (); + } + } +} + +void +nautilus_tracker_setup_miner_fs_connection (void) +{ + static gsize tried_tracker_init = FALSE; + + if (tracker_miner_fs_connection != NULL) + { + /* The connection was already established */ + return; + } + + if (g_once_init_enter (&tried_tracker_init)) + { + const gchar *busname = "org.freedesktop.Tracker3.Miner.Files"; + + g_message ("Connecting to %s", busname); + tracker_sparql_connection_bus_new_async (busname, NULL, NULL, NULL, host_tracker_miner_fs_ready, NULL); + + tracker_miner_fs_busname = busname; + + g_once_init_leave (&tried_tracker_init, TRUE); + } +} + +/** + * nautilus_tracker_setup_host_miner_fs_connection_sync: + * + * This function is only meant to be used within tests. + * This version of this setup function intentionally blocks to help with tests. + * + */ +void +nautilus_tracker_setup_host_miner_fs_connection_sync (void) +{ + g_autoptr (GError) error = NULL; + const gchar *busname = "org.freedesktop.Tracker3.Miner.Files"; + + g_message ("Starting %s", busname); + tracker_miner_fs_connection = tracker_sparql_connection_bus_new (busname, NULL, NULL, &error); + if (error != NULL) + { + g_critical ("Could not start local Tracker indexer at %s: %s", busname, error->message); + return; + } + + tracker_miner_fs_busname = busname; +} + +/** + * nautilus_tracker_get_miner_fs_connection: + * @error: return location for a #GError + * + * This function returns a global singleton #TrackerSparqlConnection, or %NULL + * if either we couldn't connect to Tracker Miner FS or the connection is still + * pending. + * + * The returned object is a globally shared singleton which should NOT be + * unreffed. + * + * Returns: a #TrackerSparqlConnection, or %NULL + */ +TrackerSparqlConnection * +nautilus_tracker_get_miner_fs_connection (GError **error) +{ + nautilus_tracker_setup_miner_fs_connection (); + + if (tracker_miner_fs_error && error) + { + *error = g_error_copy (tracker_miner_fs_error); + } + + return tracker_miner_fs_connection; +} diff --git a/src/nautilus-tracker-utilities.h b/src/nautilus-tracker-utilities.h new file mode 100644 index 0000000..d79c2b6 --- /dev/null +++ b/src/nautilus-tracker-utilities.h @@ -0,0 +1,31 @@ +/* nautilus-tracker-utilities.h + * + * Copyright 2019 Carlos Soriano + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License 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 warranty of + * MERCHANTABILITY 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 . + * + * SPDX-License-Identifier: GPL-3.0-or-later + */ + + +#pragma once + +#include +#include + +TrackerSparqlConnection * nautilus_tracker_get_miner_fs_connection (GError **error); +void nautilus_tracker_setup_miner_fs_connection (void); + +/* nautilus_tracker_setup_host_miner_fs_connection_sync() is for testing purposes only */ +void nautilus_tracker_setup_host_miner_fs_connection_sync (void); diff --git a/src/nautilus-trash-monitor.c b/src/nautilus-trash-monitor.c new file mode 100644 index 0000000..366116f --- /dev/null +++ b/src/nautilus-trash-monitor.c @@ -0,0 +1,262 @@ +/* + * nautilus-trash-monitor.c: Nautilus trash state watcher. + * + * Copyright (C) 2000, 2001 Eazel, Inc. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License as + * published by the Free Software Foundation; either version 2 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 warranty of + * MERCHANTABILITY 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 . + * + * Author: Pavel Cisler + */ + +#include "nautilus-trash-monitor.h" + +#include + +#define UPDATE_RATE_SECONDS 1 + +struct _NautilusTrashMonitor +{ + GObject object; + + gboolean empty; + GFileMonitor *file_monitor; + gboolean pending; + gint timeout_id; +}; + +enum +{ + TRASH_STATE_CHANGED, + LAST_SIGNAL +}; + +static guint signals[LAST_SIGNAL]; +static NautilusTrashMonitor *nautilus_trash_monitor = NULL; + +G_DEFINE_TYPE (NautilusTrashMonitor, nautilus_trash_monitor, G_TYPE_OBJECT) + +static void +nautilus_trash_monitor_finalize (GObject *object) +{ + NautilusTrashMonitor *trash_monitor; + + trash_monitor = NAUTILUS_TRASH_MONITOR (object); + + if (trash_monitor->timeout_id > 0) + { + g_source_remove (trash_monitor->timeout_id); + trash_monitor->timeout_id = 0; + } + + if (trash_monitor->file_monitor) + { + g_object_unref (trash_monitor->file_monitor); + } + + G_OBJECT_CLASS (nautilus_trash_monitor_parent_class)->finalize (object); +} + +static void +nautilus_trash_monitor_class_init (NautilusTrashMonitorClass *klass) +{ + GObjectClass *object_class; + + object_class = G_OBJECT_CLASS (klass); + + object_class->finalize = nautilus_trash_monitor_finalize; + + signals[TRASH_STATE_CHANGED] = g_signal_new + ("trash-state-changed", + G_TYPE_FROM_CLASS (object_class), + G_SIGNAL_RUN_LAST, + 0, + NULL, NULL, + g_cclosure_marshal_VOID__BOOLEAN, + G_TYPE_NONE, 1, + G_TYPE_BOOLEAN); +} + +static void +update_empty_info (NautilusTrashMonitor *trash_monitor, + gboolean is_empty) +{ + if (trash_monitor->empty == is_empty) + { + return; + } + + trash_monitor->empty = is_empty; + + /* trash got empty or full, notify everyone who cares */ + g_signal_emit (trash_monitor, + signals[TRASH_STATE_CHANGED], 0, + trash_monitor->empty); +} + +/* Use G_FILE_ATTRIBUTE_TRASH_ITEM_COUNT since we only want to know whether the + * trash is empty or not, not access its children. This is available for the + * trash backend since it uses a cache. In this way we prevent flooding the + * trash backend with enumeration requests when trashing > 1000 files + */ +static void +trash_query_info_cb (GObject *source, + GAsyncResult *res, + gpointer user_data) +{ + NautilusTrashMonitor *trash_monitor = user_data; + GFileInfo *info; + guint32 item_count; + gboolean is_empty = TRUE; + + info = g_file_query_info_finish (G_FILE (source), res, NULL); + + if (info != NULL) + { + item_count = g_file_info_get_attribute_uint32 (info, + G_FILE_ATTRIBUTE_TRASH_ITEM_COUNT); + is_empty = item_count == 0; + + g_object_unref (info); + } + + update_empty_info (trash_monitor, is_empty); + + g_object_unref (trash_monitor); +} + +static void schedule_update_info (NautilusTrashMonitor *trash_monitor); + +static gboolean +schedule_update_info_cb (gpointer data) +{ + NautilusTrashMonitor *trash_monitor = data; + + trash_monitor->timeout_id = 0; + if (trash_monitor->pending) + { + trash_monitor->pending = FALSE; + schedule_update_info (trash_monitor); + } + + return G_SOURCE_REMOVE; +} + +static void +schedule_update_info (NautilusTrashMonitor *trash_monitor) +{ + GFile *location; + + /* Rate limit the updates to not flood the gvfsd-trash when too many changes + * happended in a short time. + */ + if (trash_monitor->timeout_id > 0) + { + trash_monitor->pending = TRUE; + return; + } + + location = g_file_new_for_uri ("trash:///"); + g_file_query_info_async (location, + G_FILE_ATTRIBUTE_TRASH_ITEM_COUNT, + G_FILE_QUERY_INFO_NONE, + G_PRIORITY_DEFAULT, NULL, + trash_query_info_cb, g_object_ref (trash_monitor)); + + trash_monitor->timeout_id = g_timeout_add_seconds (UPDATE_RATE_SECONDS, + schedule_update_info_cb, + trash_monitor); + g_object_unref (location); +} + +static void +file_changed (GFileMonitor *monitor, + GFile *child, + GFile *other_file, + GFileMonitorEvent event_type, + gpointer user_data) +{ + NautilusTrashMonitor *trash_monitor; + + trash_monitor = NAUTILUS_TRASH_MONITOR (user_data); + + schedule_update_info (trash_monitor); +} + +static void +nautilus_trash_monitor_init (NautilusTrashMonitor *trash_monitor) +{ + GFile *location; + + trash_monitor->empty = TRUE; + + location = g_file_new_for_uri ("trash:///"); + + trash_monitor->file_monitor = g_file_monitor_file (location, 0, NULL, NULL); + trash_monitor->pending = FALSE; + trash_monitor->timeout_id = 0; + + g_signal_connect (trash_monitor->file_monitor, "changed", + (GCallback) file_changed, trash_monitor); + + g_object_unref (location); + + schedule_update_info (trash_monitor); +} + +static void +clear_trash_monitor_on_shutdown (void) +{ + g_clear_object (&nautilus_trash_monitor); +} + +NautilusTrashMonitor * +nautilus_trash_monitor_get (void) +{ + if (nautilus_trash_monitor == NULL) + { + /* not running yet, start it up */ + + nautilus_trash_monitor = NAUTILUS_TRASH_MONITOR + (g_object_new (NAUTILUS_TYPE_TRASH_MONITOR, NULL)); + eel_debug_call_at_shutdown (clear_trash_monitor_on_shutdown); + } + + return nautilus_trash_monitor; +} + +gboolean +nautilus_trash_monitor_is_empty (void) +{ + NautilusTrashMonitor *monitor; + + monitor = nautilus_trash_monitor_get (); + return monitor->empty; +} + +GIcon * +nautilus_trash_monitor_get_symbolic_icon (void) +{ + gboolean empty; + + empty = nautilus_trash_monitor_is_empty (); + + if (empty) + { + return g_themed_icon_new ("user-trash-symbolic"); + } + else + { + return g_themed_icon_new ("user-trash-full-symbolic"); + } +} diff --git a/src/nautilus-trash-monitor.h b/src/nautilus-trash-monitor.h new file mode 100644 index 0000000..1b81a4b --- /dev/null +++ b/src/nautilus-trash-monitor.h @@ -0,0 +1,35 @@ + +/* + nautilus-trash-monitor.h: Nautilus trash state watcher. + + Copyright (C) 2000 Eazel, Inc. + + This program is free software; you can redistribute it and/or + modify it under the terms of the GNU General Public License as + published by the Free Software Foundation; either version 2 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 warranty of + MERCHANTABILITY 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 . + + Author: Pavel Cisler +*/ + +#pragma once + +#include + +#define NAUTILUS_TYPE_TRASH_MONITOR (nautilus_trash_monitor_get_type ()) + +G_DECLARE_FINAL_TYPE (NautilusTrashMonitor, nautilus_trash_monitor, + NAUTILUS, TRASH_MONITOR, + GObject) + +NautilusTrashMonitor *nautilus_trash_monitor_get (void); +gboolean nautilus_trash_monitor_is_empty (void); +GIcon *nautilus_trash_monitor_get_symbolic_icon (void); diff --git a/src/nautilus-types.h b/src/nautilus-types.h new file mode 100644 index 0000000..6a13ebb --- /dev/null +++ b/src/nautilus-types.h @@ -0,0 +1,47 @@ +/* Copyright (C) 2018 Ernestas Kulik + * + * This file is part of Nautilus. + * + * Nautilus is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Nautilus is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Nautilus. If not, see . + */ + +/* This here header contains Nautilus type definitions. + * + * It is advisable to include this in headers or when you + * only need the name of a type (i.e. not calling any functions from the + * associated header). Doing so will help avoid circular inclusions and + * pointless rebuilds should the header ever change. + */ + +#pragma once + +#include "nautilus-enums.h" + +/* Keep sorted alphabetically. */ + +typedef struct _NautilusBookmark NautilusBookmark; +typedef struct _NautilusBookmarkList NautilusBookmarkList; +typedef struct _NautilusClipboard NautilusClipboard; +typedef struct _NautilusDirectory NautilusDirectory; +typedef struct NautilusFile NautilusFile; +typedef struct NautilusFileQueue NautilusFileQueue; +typedef struct _NautilusIconInfo NautilusIconInfo; +typedef struct _NautilusListBase NautilusListBase; +typedef struct NautilusMonitor NautilusMonitor; +typedef struct _NautilusQuery NautilusQuery; +typedef struct _NautilusQueryEditor NautilusQueryEditor; +typedef struct _NautilusToolbarMenuSections NautilusToolbarMenuSections; +typedef struct _NautilusView NautilusView; +typedef struct _NautilusWindow NautilusWindow; +typedef struct _NautilusWindowSlot NautilusWindowSlot; diff --git a/src/nautilus-ui-utilities.c b/src/nautilus-ui-utilities.c new file mode 100644 index 0000000..d874224 --- /dev/null +++ b/src/nautilus-ui-utilities.c @@ -0,0 +1,420 @@ +/* nautilus-ui-utilities.c - helper functions for GtkUIManager stuff + * + * Copyright (C) 2004 Red Hat, Inc. + * + * The Gnome Library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Library General Public License as + * published by the Free Software Foundation; either version 2 of the + * License, or (at your option) any later version. + * + * The Gnome Library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Library General Public License for more details. + * + * You should have received a copy of the GNU Library General Public + * License along with the Gnome Library; see the file COPYING.LIB. If not, + * see . + * + * Authors: Alexander Larsson + */ + +#include + +#include "nautilus-ui-utilities.h" +#include "nautilus-icon-info.h" +#include "nautilus-application.h" +#include + +#include +#include +#include +#include + +/** + * nautilus_gmenu_set_from_model: + * @target_menu: the #GMenu to be filled + * @source_model: (nullable): a #GMenuModel to copy items from + * + * This will replace the content of @target_menu with a copy of all items from + * @source_model. + * + * If the @source_model is empty (i.e., its item count is 0), or if it is %NULL, + * then the @target_menu is left empty. + */ +void +nautilus_gmenu_set_from_model (GMenu *target_menu, + GMenuModel *source_model) +{ + g_return_if_fail (G_IS_MENU (target_menu)); + g_return_if_fail (source_model == NULL || G_IS_MENU_MODEL (source_model)); + + /* First, empty the menu... */ + g_menu_remove_all (target_menu); + + /* ...then, repopulate it (maybe). */ + if (source_model != NULL) + { + gint n_items; + + n_items = g_menu_model_get_n_items (source_model); + for (gint i = 0; i < n_items; i++) + { + g_autoptr (GMenuItem) item = NULL; + item = g_menu_item_new_from_model (source_model, i); + g_menu_append_item (target_menu, item); + } + } +} + +/** + * nautilus_g_menu_model_find_by_string: + * @model: the #GMenuModel with items to search + * @attribute: the menu item attribute to compare with + * @string: the string to match the value of @attribute + * + * This will search for an item in the model which has got the @attribute and + * whose value is equal to @string. + * + * It is assumed that @attribute has the a GVariant format string "s". + * + * Returns: The index of the first match in the model, or -1 if no item matches. + */ +gint +nautilus_g_menu_model_find_by_string (GMenuModel *model, + const gchar *attribute, + const gchar *string) +{ + gint item_index = -1; + gint n_items; + + n_items = g_menu_model_get_n_items (model); + for (gint i = 0; i < n_items; i++) + { + g_autofree gchar *value = NULL; + if (g_menu_model_get_item_attribute (model, i, attribute, "s", &value) && + g_strcmp0 (value, string) == 0) + { + item_index = i; + break; + } + } + return item_index; +} + +/** + * nautilus_g_menu_replace_string_in_item: + * @menu: the #GMenu to modify + * @i: the position of the item to change + * @attribute: the menu item attribute to change + * @string: the string to change the value of @attribute to + * + * This will replace the item at @position with a new item which is identical + * except that it has @attribute set to @string. + * + * This is useful e.g. when want to change the menu model of a #GtkPopover and + * you have a pointer to its menu model but not to the popover itself, so you + * can't just set a new model. With this method, the GtkPopover is notified of + * changes in its model and updates its contents accordingly. + * + * It is assumed that @attribute has the a GVariant format string "s". + */ +void +nautilus_g_menu_replace_string_in_item (GMenu *menu, + gint i, + const gchar *attribute, + const gchar *string) +{ + g_autoptr (GMenuItem) item = NULL; + + g_return_if_fail (i != -1); + item = g_menu_item_new_from_model (G_MENU_MODEL (menu), i); + g_return_if_fail (item != NULL); + + if (string != NULL) + { + g_menu_item_set_attribute (item, attribute, "s", string); + } + else + { + g_menu_item_set_attribute (item, attribute, NULL); + } + + g_menu_remove (menu, i); + g_menu_insert_item (menu, i, item); +} + +static GdkPixbuf *filmholes_left = NULL; +static GdkPixbuf *filmholes_right = NULL; + +static gboolean +ensure_filmholes (void) +{ + if (filmholes_left == NULL) + { + filmholes_left = gdk_pixbuf_new_from_resource ("/org/gnome/nautilus/icons/filmholes.png", NULL); + } + if (filmholes_right == NULL && + filmholes_left != NULL) + { + filmholes_right = gdk_pixbuf_flip (filmholes_left, TRUE); + } + + return (filmholes_left && filmholes_right); +} + +void +nautilus_ui_frame_video (GtkSnapshot *snapshot, + gdouble width, + gdouble height) +{ + g_autoptr (GdkTexture) left_texture = NULL; + g_autoptr (GdkTexture) right_texture = NULL; + int holes_width, holes_height; + + if (!ensure_filmholes ()) + { + return; + } + + holes_width = gdk_pixbuf_get_width (filmholes_left); + holes_height = gdk_pixbuf_get_height (filmholes_left); + + /* Left */ + gtk_snapshot_push_repeat (snapshot, + &GRAPHENE_RECT_INIT (0, 0, holes_width, height), + NULL); + left_texture = gdk_texture_new_for_pixbuf (filmholes_left); + gtk_snapshot_append_texture (snapshot, + left_texture, + &GRAPHENE_RECT_INIT (0, 0, holes_width, holes_height)); + gtk_snapshot_pop (snapshot); + + /* Right */ + gtk_snapshot_push_repeat (snapshot, + &GRAPHENE_RECT_INIT (width - holes_width, 0, holes_width, height), + NULL); + right_texture = gdk_texture_new_for_pixbuf (filmholes_right); + gtk_snapshot_append_texture (snapshot, + right_texture, + &GRAPHENE_RECT_INIT (width - holes_width, 0, holes_width, holes_height)); + gtk_snapshot_pop (snapshot); +} + +gboolean +nautilus_date_time_is_between_dates (GDateTime *date, + GDateTime *initial_date, + GDateTime *end_date) +{ + gboolean in_between; + + /* Silently ignore errors */ + if (date == NULL || g_date_time_to_unix (date) == 0) + { + return FALSE; + } + + /* For the end date, we want to make end_date inclusive, + * for that the difference between the start of the day and the in_between + * has to be more than -1 day + */ + in_between = g_date_time_difference (date, initial_date) > 0 && + g_date_time_difference (end_date, date) / G_TIME_SPAN_DAY > -1; + + return in_between; +} + +static const gchar * +get_text_for_days_ago (gint days, + gboolean prefix_with_since) +{ + if (days < 7) + { + /* days */ + return prefix_with_since ? + ngettext ("Since %d day ago", "Since %d days ago", days) : + ngettext ("%d day ago", "%d days ago", days); + } + if (days < 30) + { + /* weeks */ + return prefix_with_since ? + ngettext ("Since last week", "Since %d weeks ago", days / 7) : + ngettext ("Last week", "%d weeks ago", days / 7); + } + if (days < 365) + { + /* months */ + return prefix_with_since ? + ngettext ("Since last month", "Since %d months ago", days / 30) : + ngettext ("Last month", "%d months ago", days / 30); + } + + /* years */ + return prefix_with_since ? + ngettext ("Since last year", "Since %d years ago", days / 365) : + ngettext ("Last year", "%d years ago", days / 365); +} + +gchar * +get_text_for_date_range (GPtrArray *date_range, + gboolean prefix_with_since) +{ + gint days; + gint normalized; + GDateTime *initial_date; + GDateTime *end_date; + gchar *formatted_date; + gchar *label; + + if (!date_range) + { + return NULL; + } + + initial_date = g_ptr_array_index (date_range, 0); + end_date = g_ptr_array_index (date_range, 1); + days = g_date_time_difference (end_date, initial_date) / G_TIME_SPAN_DAY; + formatted_date = g_date_time_format (initial_date, "%x"); + + if (days < 1) + { + label = g_strdup (formatted_date); + } + else + { + if (days < 7) + { + /* days */ + normalized = days; + } + else if (days < 30) + { + /* weeks */ + normalized = days / 7; + } + else if (days < 365) + { + /* months */ + normalized = days / 30; + } + else + { + /* years */ + normalized = days / 365; + } + +#pragma GCC diagnostic push +#pragma GCC diagnostic ignored "-Wformat-nonliteral" + label = g_strdup_printf (get_text_for_days_ago (days, + prefix_with_since), + normalized); +#pragma GCC diagnostic pop + } + + g_free (formatted_date); + + return label; +} + +AdwMessageDialog * +show_dialog (const gchar *primary_text, + const gchar *secondary_text, + GtkWindow *parent, + GtkMessageType type) +{ + GtkWidget *dialog; + + g_return_val_if_fail (parent != NULL, NULL); + + dialog = adw_message_dialog_new (parent, primary_text, secondary_text); + adw_message_dialog_add_response (ADW_MESSAGE_DIALOG (dialog), "ok", _("_OK")); + adw_message_dialog_set_default_response (ADW_MESSAGE_DIALOG (dialog), "ok"); + + gtk_window_present (GTK_WINDOW (dialog)); + + return ADW_MESSAGE_DIALOG (dialog); +} + +static void +notify_unmount_done (GMountOperation *op, + const gchar *message) +{ + NautilusApplication *application; + gchar *notification_id; + + application = nautilus_application_get_default (); + notification_id = g_strdup_printf ("nautilus-mount-operation-%p", op); + nautilus_application_withdraw_notification (application, notification_id); + + if (message != NULL) + { + GNotification *unplug; + GIcon *icon; + gchar **strings; + + strings = g_strsplit (message, "\n", 0); + icon = g_themed_icon_new ("media-removable-symbolic"); + unplug = g_notification_new (strings[0]); + g_notification_set_body (unplug, strings[1]); + g_notification_set_icon (unplug, icon); + + nautilus_application_send_notification (application, notification_id, unplug); + g_object_unref (unplug); + g_object_unref (icon); + g_strfreev (strings); + } + + g_free (notification_id); +} + +static void +notify_unmount_show (GMountOperation *op, + const gchar *message) +{ + NautilusApplication *application; + GNotification *unmount; + gchar *notification_id; + GIcon *icon; + gchar **strings; + + application = nautilus_application_get_default (); + strings = g_strsplit (message, "\n", 0); + icon = g_themed_icon_new ("media-removable"); + + unmount = g_notification_new (strings[0]); + g_notification_set_body (unmount, strings[1]); + g_notification_set_icon (unmount, icon); + g_notification_set_priority (unmount, G_NOTIFICATION_PRIORITY_URGENT); + + notification_id = g_strdup_printf ("nautilus-mount-operation-%p", op); + nautilus_application_send_notification (application, notification_id, unmount); + g_object_unref (unmount); + g_object_unref (icon); + g_strfreev (strings); + g_free (notification_id); +} + +void +show_unmount_progress_cb (GMountOperation *op, + const gchar *message, + gint64 time_left, + gint64 bytes_left, + gpointer user_data) +{ + if (bytes_left == 0) + { + notify_unmount_done (op, message); + } + else + { + notify_unmount_show (op, message); + } +} + +void +show_unmount_progress_aborted_cb (GMountOperation *op, + gpointer user_data) +{ + notify_unmount_done (op, NULL); +} diff --git a/src/nautilus-ui-utilities.h b/src/nautilus-ui-utilities.h new file mode 100644 index 0000000..f3df67f --- /dev/null +++ b/src/nautilus-ui-utilities.h @@ -0,0 +1,59 @@ + +/* nautilus-ui-utilities.h - helper functions for GtkUIManager stuff + + Copyright (C) 2004 Red Hat, Inc. + + The Gnome Library is free software; you can redistribute it and/or + modify it under the terms of the GNU Library General Public License as + published by the Free Software Foundation; either version 2 of the + License, or (at your option) any later version. + + The Gnome Library is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Library General Public License for more details. + + You should have received a copy of the GNU Library General Public + License along with the Gnome Library; see the file COPYING.LIB. If not, + see . + + Authors: Alexander Larsson +*/ + +#pragma once + +#include +#include + +void nautilus_gmenu_set_from_model (GMenu *target_menu, + GMenuModel *source_model); +gint nautilus_g_menu_model_find_by_string (GMenuModel *model, + const gchar *attribute, + const gchar *string); +void nautilus_g_menu_replace_string_in_item (GMenu *menu, + gint i, + const gchar *attribute, + const gchar *string); + +void nautilus_ui_frame_video (GtkSnapshot *snapshot, + gdouble width, + gdouble height); + +gboolean nautilus_date_time_is_between_dates (GDateTime *date, + GDateTime *initial_date, + GDateTime *end_date); +gchar * get_text_for_date_range (GPtrArray *date_range, + gboolean prefix_with_since); + +AdwMessageDialog * show_dialog (const gchar *primary_text, + const gchar *secondary_text, + GtkWindow *parent, + GtkMessageType type); + +void show_unmount_progress_cb (GMountOperation *op, + const gchar *message, + gint64 time_left, + gint64 bytes_left, + gpointer user_data); +void show_unmount_progress_aborted_cb (GMountOperation *op, + gpointer user_data); diff --git a/src/nautilus-undo-private.h b/src/nautilus-undo-private.h new file mode 100644 index 0000000..2a9be28 --- /dev/null +++ b/src/nautilus-undo-private.h @@ -0,0 +1,30 @@ + +/* xxx + * + * Copyright (C) 2000 Eazel, Inc. + * + * Author: Gene Z. Ragan + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Library General Public + * License as published by the Free Software Foundation; either + * version 2 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Library General Public License for more details. + * + * You should have received a copy of the GNU Library General Public + * License along with this library; if not, see . + */ + +#pragma once + +#include "nautilus-undo.h" +#include "nautilus-undo-manager.h" +#include + +NautilusUndoManager * nautilus_undo_get_undo_manager (GObject *attached_object); +void nautilus_undo_attach_undo_manager (GObject *object, + NautilusUndoManager *manager); \ No newline at end of file diff --git a/src/nautilus-vfs-directory.c b/src/nautilus-vfs-directory.c new file mode 100644 index 0000000..c2bdb11 --- /dev/null +++ b/src/nautilus-vfs-directory.c @@ -0,0 +1,121 @@ +/* + * nautilus-vfs-directory.c: Subclass of NautilusDirectory to help implement the + * virtual trash directory. + * + * Copyright (C) 1999, 2000 Eazel, Inc. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License as + * published by the Free Software Foundation; either version 2 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 warranty of + * MERCHANTABILITY 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 . + * + * Author: Darin Adler + */ + +#include +#include "nautilus-vfs-directory.h" + +#include "nautilus-directory-private.h" +#include "nautilus-file-private.h" + +G_DEFINE_TYPE (NautilusVFSDirectory, nautilus_vfs_directory, NAUTILUS_TYPE_DIRECTORY); + +static void +nautilus_vfs_directory_init (NautilusVFSDirectory *directory) +{ +} + +static void +vfs_call_when_ready (NautilusDirectory *directory, + NautilusFileAttributes file_attributes, + gboolean wait_for_file_list, + NautilusDirectoryCallback callback, + gpointer callback_data) +{ + g_assert (NAUTILUS_IS_VFS_DIRECTORY (directory)); + + nautilus_directory_call_when_ready_internal + (directory, + NULL, + file_attributes, + wait_for_file_list, + callback, + NULL, + callback_data); +} + +static void +vfs_cancel_callback (NautilusDirectory *directory, + NautilusDirectoryCallback callback, + gpointer callback_data) +{ + g_assert (NAUTILUS_IS_VFS_DIRECTORY (directory)); + + nautilus_directory_cancel_callback_internal + (directory, + NULL, + callback, + NULL, + callback_data); +} + +static void +vfs_file_monitor_add (NautilusDirectory *directory, + gconstpointer client, + gboolean monitor_hidden_files, + NautilusFileAttributes file_attributes, + NautilusDirectoryCallback callback, + gpointer callback_data) +{ + g_assert (NAUTILUS_IS_VFS_DIRECTORY (directory)); + g_assert (client != NULL); + + nautilus_directory_monitor_add_internal + (directory, NULL, + client, + monitor_hidden_files, + file_attributes, + callback, callback_data); +} + +static void +vfs_file_monitor_remove (NautilusDirectory *directory, + gconstpointer client) +{ + g_assert (NAUTILUS_IS_VFS_DIRECTORY (directory)); + g_assert (client != NULL); + + nautilus_directory_monitor_remove_internal (directory, NULL, client); +} + +static void +vfs_force_reload (NautilusDirectory *directory) +{ + NautilusFileAttributes all_attributes; + + g_assert (NAUTILUS_IS_DIRECTORY (directory)); + + all_attributes = nautilus_file_get_all_attributes (); + nautilus_directory_force_reload_internal (directory, + all_attributes); +} + +static void +nautilus_vfs_directory_class_init (NautilusVFSDirectoryClass *klass) +{ + NautilusDirectoryClass *directory_class = NAUTILUS_DIRECTORY_CLASS (klass); + + directory_class->call_when_ready = vfs_call_when_ready; + directory_class->cancel_callback = vfs_cancel_callback; + directory_class->file_monitor_add = vfs_file_monitor_add; + directory_class->file_monitor_remove = vfs_file_monitor_remove; + directory_class->force_reload = vfs_force_reload; +} diff --git a/src/nautilus-vfs-directory.h b/src/nautilus-vfs-directory.h new file mode 100644 index 0000000..4bdd6ff --- /dev/null +++ b/src/nautilus-vfs-directory.h @@ -0,0 +1,49 @@ +/* + nautilus-vfs-directory.h: Subclass of NautilusDirectory to implement the + the case of a VFS directory. + + Copyright (C) 1999, 2000 Eazel, Inc. + + This program is free software; you can redistribute it and/or + modify it under the terms of the GNU General Public License as + published by the Free Software Foundation; either version 2 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 warranty of + MERCHANTABILITY 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 . + + Author: Darin Adler +*/ + +#pragma once + +#include "nautilus-directory.h" + +#define NAUTILUS_TYPE_VFS_DIRECTORY nautilus_vfs_directory_get_type() +#define NAUTILUS_VFS_DIRECTORY(obj) \ + (G_TYPE_CHECK_INSTANCE_CAST ((obj), NAUTILUS_TYPE_VFS_DIRECTORY, NautilusVFSDirectory)) +#define NAUTILUS_VFS_DIRECTORY_CLASS(klass) \ + (G_TYPE_CHECK_CLASS_CAST ((klass), NAUTILUS_TYPE_VFS_DIRECTORY, NautilusVFSDirectoryClass)) +#define NAUTILUS_IS_VFS_DIRECTORY(obj) \ + (G_TYPE_CHECK_INSTANCE_TYPE ((obj), NAUTILUS_TYPE_VFS_DIRECTORY)) +#define NAUTILUS_IS_VFS_DIRECTORY_CLASS(klass) \ + (G_TYPE_CHECK_CLASS_TYPE ((klass), NAUTILUS_TYPE_VFS_DIRECTORY)) +#define NAUTILUS_VFS_DIRECTORY_GET_CLASS(obj) \ + (G_TYPE_INSTANCE_GET_CLASS ((obj), NAUTILUS_TYPE_VFS_DIRECTORY, NautilusVFSDirectoryClass)) + +typedef struct NautilusVFSDirectoryDetails NautilusVFSDirectoryDetails; + +typedef struct { + NautilusDirectory parent_slot; +} NautilusVFSDirectory; + +typedef struct { + NautilusDirectoryClass parent_slot; +} NautilusVFSDirectoryClass; + +GType nautilus_vfs_directory_get_type (void); \ No newline at end of file diff --git a/src/nautilus-vfs-file.c b/src/nautilus-vfs-file.c new file mode 100644 index 0000000..9e6038c --- /dev/null +++ b/src/nautilus-vfs-file.c @@ -0,0 +1,734 @@ +/* + * nautilus-vfs-file.c: Subclass of NautilusFile to help implement the + * virtual trash directory. + * + * Copyright (C) 1999, 2000, 2001 Eazel, Inc. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License as + * published by the Free Software Foundation; either version 2 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 warranty of + * MERCHANTABILITY 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 . + * + * Author: Darin Adler + */ + +#include +#include "nautilus-vfs-file.h" + +#include "nautilus-directory-notify.h" +#include "nautilus-directory-private.h" +#include "nautilus-file-private.h" +#include + +G_DEFINE_TYPE (NautilusVFSFile, nautilus_vfs_file, NAUTILUS_TYPE_FILE); + +static void +vfs_file_monitor_add (NautilusFile *file, + gconstpointer client, + NautilusFileAttributes attributes) +{ + NautilusDirectory *directory; + + directory = nautilus_file_get_directory (file); + + nautilus_directory_monitor_add_internal (directory, file, client, TRUE, + attributes, NULL, NULL); +} + +static void +vfs_file_monitor_remove (NautilusFile *file, + gconstpointer client) +{ + NautilusDirectory *directory; + + directory = nautilus_file_get_directory (file); + + nautilus_directory_monitor_remove_internal (directory, file, client); +} + +static void +vfs_file_call_when_ready (NautilusFile *file, + NautilusFileAttributes file_attributes, + NautilusFileCallback callback, + gpointer callback_data) +{ + NautilusDirectory *directory; + + directory = nautilus_file_get_directory (file); + + nautilus_directory_call_when_ready_internal (directory, file, file_attributes, + FALSE, NULL, callback, callback_data); +} + +static void +vfs_file_cancel_call_when_ready (NautilusFile *file, + NautilusFileCallback callback, + gpointer callback_data) +{ + NautilusDirectory *directory; + + directory = nautilus_file_get_directory (file); + + nautilus_directory_cancel_callback_internal (directory, file, NULL, + callback, callback_data); +} + +static gboolean +vfs_file_check_if_ready (NautilusFile *file, + NautilusFileAttributes file_attributes) +{ + NautilusDirectory *directory; + + directory = nautilus_file_get_directory (file); + + return nautilus_directory_check_if_ready_internal (directory, file, + file_attributes); +} + +static void +set_metadata_get_info_callback (GObject *source_object, + GAsyncResult *res, + gpointer callback_data) +{ + NautilusFile *file; + GFileInfo *new_info; + GError *error; + + file = callback_data; + + error = NULL; + new_info = g_file_query_info_finish (G_FILE (source_object), res, &error); + if (new_info != NULL) + { + if (nautilus_file_update_info (file, new_info)) + { + nautilus_file_changed (file); + } + g_object_unref (new_info); + } + nautilus_file_unref (file); + if (error) + { + g_error_free (error); + } +} + +static void +set_metadata_callback (GObject *source_object, + GAsyncResult *result, + gpointer callback_data) +{ + NautilusFile *file; + GError *error; + gboolean res; + + file = callback_data; + + error = NULL; + res = g_file_set_attributes_finish (G_FILE (source_object), + result, + NULL, + &error); + + if (res) + { + g_file_query_info_async (G_FILE (source_object), + NAUTILUS_FILE_DEFAULT_ATTRIBUTES, + 0, + G_PRIORITY_DEFAULT, + NULL, + set_metadata_get_info_callback, file); + } + else + { + nautilus_file_unref (file); + g_error_free (error); + } +} + +static void +vfs_file_set_metadata (NautilusFile *file, + const char *key, + const char *value) +{ + GFileInfo *info; + GFile *location; + char *gio_key; + + info = g_file_info_new (); + + gio_key = g_strconcat ("metadata::", key, NULL); + if (value != NULL) + { + g_file_info_set_attribute_string (info, gio_key, value); + } + else + { + /* Unset the key */ + g_file_info_set_attribute (info, gio_key, + G_FILE_ATTRIBUTE_TYPE_INVALID, + NULL); + } + g_free (gio_key); + + location = nautilus_file_get_location (file); + g_file_set_attributes_async (location, + info, + 0, + G_PRIORITY_DEFAULT, + NULL, + set_metadata_callback, + nautilus_file_ref (file)); + g_object_unref (location); + g_object_unref (info); +} + +static void +vfs_file_set_metadata_as_list (NautilusFile *file, + const char *key, + char **value) +{ + GFile *location; + GFileInfo *info; + char *gio_key; + + info = g_file_info_new (); + + gio_key = g_strconcat ("metadata::", key, NULL); + if (value == NULL) + { + g_file_info_set_attribute (info, gio_key, G_FILE_ATTRIBUTE_TYPE_INVALID, NULL); + } + else + { + g_file_info_set_attribute_stringv (info, gio_key, value); + } + g_free (gio_key); + + location = nautilus_file_get_location (file); + g_file_set_attributes_async (location, + info, + 0, + G_PRIORITY_DEFAULT, + NULL, + set_metadata_callback, + nautilus_file_ref (file)); + g_object_unref (info); + g_object_unref (location); +} + +static gboolean +vfs_file_get_date (NautilusFile *file, + NautilusDateType date_type, + time_t *date) +{ + time_t atime; + time_t mtime; + time_t btime; + time_t recency; + time_t trash_time; + + atime = nautilus_file_get_atime (file); + mtime = nautilus_file_get_mtime (file); + btime = nautilus_file_get_btime (file); + recency = nautilus_file_get_recency (file); + trash_time = nautilus_file_get_trash_time (file); + + switch (date_type) + { + case NAUTILUS_DATE_TYPE_ACCESSED: + { + /* Before we have info on a file, the date is unknown. */ + if (atime == 0) + { + return FALSE; + } + if (date != NULL) + { + *date = atime; + } + return TRUE; + } + + case NAUTILUS_DATE_TYPE_MODIFIED: + { + /* Before we have info on a file, the date is unknown. */ + if (mtime == 0) + { + return FALSE; + } + if (date != NULL) + { + *date = mtime; + } + return TRUE; + } + + case NAUTILUS_DATE_TYPE_CREATED: + { + /* Before we have info on a file, the date is unknown. */ + if (btime == 0) + { + return FALSE; + } + if (date != NULL) + { + *date = btime; + } + return TRUE; + } + + case NAUTILUS_DATE_TYPE_TRASHED: + { + /* Before we have info on a file, the date is unknown. */ + if (trash_time == 0) + { + return FALSE; + } + if (date != NULL) + { + *date = trash_time; + } + return TRUE; + } + + case NAUTILUS_DATE_TYPE_RECENCY: + { + /* Before we have info on a file, the date is unknown. */ + if (recency == 0) + { + return FALSE; + } + if (date != NULL) + { + *date = recency; + } + return TRUE; + } + } + return FALSE; +} + +static char * +vfs_file_get_where_string (NautilusFile *file) +{ + GFile *activation_location; + NautilusFile *location; + char *where_string; + + if (!nautilus_file_is_in_recent (file)) + { + location = nautilus_file_ref (file); + } + else + { + activation_location = nautilus_file_get_activation_location (file); + location = nautilus_file_get (activation_location); + g_object_unref (activation_location); + } + + where_string = nautilus_file_get_parent_uri_for_display (location); + + nautilus_file_unref (location); + return where_string; +} + +static void +vfs_file_mount_callback (GObject *source_object, + GAsyncResult *res, + gpointer callback_data) +{ + NautilusFileOperation *op; + GFile *mounted_on; + GError *error; + + op = callback_data; + + error = NULL; + mounted_on = g_file_mount_mountable_finish (G_FILE (source_object), + res, &error); + nautilus_file_operation_complete (op, mounted_on, error); + if (mounted_on) + { + g_object_unref (mounted_on); + } + if (error) + { + g_error_free (error); + } +} + + +static void +vfs_file_mount (NautilusFile *file, + GMountOperation *mount_op, + GCancellable *cancellable, + NautilusFileOperationCallback callback, + gpointer callback_data) +{ + GFileType type; + NautilusFileOperation *op; + GError *error; + GFile *location; + + type = nautilus_file_get_file_type (file); + if (type != G_FILE_TYPE_MOUNTABLE) + { + if (callback) + { + error = NULL; + g_set_error_literal (&error, G_IO_ERROR, G_IO_ERROR_NOT_SUPPORTED, + _("This file cannot be mounted")); + callback (file, NULL, error, callback_data); + g_error_free (error); + } + return; + } + + op = nautilus_file_operation_new (file, callback, callback_data); + if (cancellable) + { + g_object_unref (op->cancellable); + op->cancellable = g_object_ref (cancellable); + } + + location = nautilus_file_get_location (file); + g_file_mount_mountable (location, + 0, + mount_op, + op->cancellable, + vfs_file_mount_callback, + op); + g_object_unref (location); +} + +static void +vfs_file_unmount_callback (GObject *source_object, + GAsyncResult *res, + gpointer callback_data) +{ + NautilusFileOperation *op; + gboolean unmounted; + GError *error; + + op = callback_data; + + error = NULL; + unmounted = g_file_unmount_mountable_with_operation_finish (G_FILE (source_object), + res, &error); + + if (!unmounted && + error->domain == G_IO_ERROR && + (error->code == G_IO_ERROR_FAILED_HANDLED || + error->code == G_IO_ERROR_CANCELLED)) + { + g_error_free (error); + error = NULL; + } + + nautilus_file_operation_complete (op, G_FILE (source_object), error); + if (error) + { + g_error_free (error); + } +} + +static void +vfs_file_unmount (NautilusFile *file, + GMountOperation *mount_op, + GCancellable *cancellable, + NautilusFileOperationCallback callback, + gpointer callback_data) +{ + NautilusFileOperation *op; + GFile *location; + + op = nautilus_file_operation_new (file, callback, callback_data); + if (cancellable) + { + g_object_unref (op->cancellable); + op->cancellable = g_object_ref (cancellable); + } + + location = nautilus_file_get_location (file); + g_file_unmount_mountable_with_operation (location, + G_MOUNT_UNMOUNT_NONE, + mount_op, + op->cancellable, + vfs_file_unmount_callback, + op); + g_object_unref (location); +} + +static void +vfs_file_eject_callback (GObject *source_object, + GAsyncResult *res, + gpointer callback_data) +{ + NautilusFileOperation *op; + gboolean ejected; + GError *error; + + op = callback_data; + + error = NULL; + ejected = g_file_eject_mountable_with_operation_finish (G_FILE (source_object), + res, &error); + + if (!ejected && + error->domain == G_IO_ERROR && + (error->code == G_IO_ERROR_FAILED_HANDLED || + error->code == G_IO_ERROR_CANCELLED)) + { + g_error_free (error); + error = NULL; + } + + nautilus_file_operation_complete (op, G_FILE (source_object), error); + if (error) + { + g_error_free (error); + } +} + +static void +vfs_file_eject (NautilusFile *file, + GMountOperation *mount_op, + GCancellable *cancellable, + NautilusFileOperationCallback callback, + gpointer callback_data) +{ + NautilusFileOperation *op; + GFile *location; + + op = nautilus_file_operation_new (file, callback, callback_data); + if (cancellable) + { + g_object_unref (op->cancellable); + op->cancellable = g_object_ref (cancellable); + } + + location = nautilus_file_get_location (file); + g_file_eject_mountable_with_operation (location, + G_MOUNT_UNMOUNT_NONE, + mount_op, + op->cancellable, + vfs_file_eject_callback, + op); + g_object_unref (location); +} + +static void +vfs_file_start_callback (GObject *source_object, + GAsyncResult *res, + gpointer callback_data) +{ + NautilusFileOperation *op; + gboolean started; + GError *error; + + op = callback_data; + + error = NULL; + started = g_file_start_mountable_finish (G_FILE (source_object), + res, &error); + + if (!started && + error->domain == G_IO_ERROR && + (error->code == G_IO_ERROR_FAILED_HANDLED || + error->code == G_IO_ERROR_CANCELLED)) + { + g_error_free (error); + error = NULL; + } + + nautilus_file_operation_complete (op, G_FILE (source_object), error); + if (error) + { + g_error_free (error); + } +} + + +static void +vfs_file_start (NautilusFile *file, + GMountOperation *mount_op, + GCancellable *cancellable, + NautilusFileOperationCallback callback, + gpointer callback_data) +{ + GFileType type; + NautilusFileOperation *op; + GError *error; + GFile *location; + + type = nautilus_file_get_file_type (file); + if (type != G_FILE_TYPE_MOUNTABLE) + { + if (callback) + { + error = NULL; + g_set_error_literal (&error, G_IO_ERROR, G_IO_ERROR_NOT_SUPPORTED, + _("This file cannot be started")); + callback (file, NULL, error, callback_data); + g_error_free (error); + } + return; + } + + op = nautilus_file_operation_new (file, callback, callback_data); + if (cancellable) + { + g_object_unref (op->cancellable); + op->cancellable = g_object_ref (cancellable); + } + + location = nautilus_file_get_location (file); + g_file_start_mountable (location, + 0, + mount_op, + op->cancellable, + vfs_file_start_callback, + op); + g_object_unref (location); +} + +static void +vfs_file_stop_callback (GObject *source_object, + GAsyncResult *res, + gpointer callback_data) +{ + NautilusFileOperation *op; + gboolean stopped; + GError *error; + + op = callback_data; + + error = NULL; + stopped = g_file_stop_mountable_finish (G_FILE (source_object), + res, &error); + + if (!stopped && + error->domain == G_IO_ERROR && + (error->code == G_IO_ERROR_FAILED_HANDLED || + error->code == G_IO_ERROR_CANCELLED)) + { + g_error_free (error); + error = NULL; + } + + nautilus_file_operation_complete (op, G_FILE (source_object), error); + if (error) + { + g_error_free (error); + } +} + +static void +vfs_file_stop (NautilusFile *file, + GMountOperation *mount_op, + GCancellable *cancellable, + NautilusFileOperationCallback callback, + gpointer callback_data) +{ + NautilusFileOperation *op; + GFile *location; + + op = nautilus_file_operation_new (file, callback, callback_data); + if (cancellable) + { + g_object_unref (op->cancellable); + op->cancellable = g_object_ref (cancellable); + } + + location = nautilus_file_get_location (file); + g_file_stop_mountable (location, + G_MOUNT_UNMOUNT_NONE, + mount_op, + op->cancellable, + vfs_file_stop_callback, + op); + g_object_unref (location); +} + +static void +vfs_file_poll_callback (GObject *source_object, + GAsyncResult *res, + gpointer callback_data) +{ + NautilusFileOperation *op; + gboolean stopped; + GError *error; + + op = callback_data; + + error = NULL; + stopped = g_file_poll_mountable_finish (G_FILE (source_object), + res, &error); + + if (!stopped && + error->domain == G_IO_ERROR && + (error->code == G_IO_ERROR_FAILED_HANDLED || + error->code == G_IO_ERROR_CANCELLED)) + { + g_error_free (error); + error = NULL; + } + + nautilus_file_operation_complete (op, G_FILE (source_object), error); + if (error) + { + g_error_free (error); + } +} + +static void +vfs_file_poll_for_media (NautilusFile *file) +{ + NautilusFileOperation *op; + GFile *location; + + op = nautilus_file_operation_new (file, NULL, NULL); + + location = nautilus_file_get_location (file); + g_file_poll_mountable (location, + op->cancellable, + vfs_file_poll_callback, + op); + g_object_unref (location); +} + +static void +nautilus_vfs_file_init (NautilusVFSFile *file) +{ +} + +static void +nautilus_vfs_file_class_init (NautilusVFSFileClass *klass) +{ + NautilusFileClass *file_class = NAUTILUS_FILE_CLASS (klass); + + file_class->monitor_add = vfs_file_monitor_add; + file_class->monitor_remove = vfs_file_monitor_remove; + file_class->call_when_ready = vfs_file_call_when_ready; + file_class->cancel_call_when_ready = vfs_file_cancel_call_when_ready; + file_class->check_if_ready = vfs_file_check_if_ready; + file_class->get_date = vfs_file_get_date; + file_class->get_where_string = vfs_file_get_where_string; + file_class->set_metadata = vfs_file_set_metadata; + file_class->set_metadata_as_list = vfs_file_set_metadata_as_list; + file_class->mount = vfs_file_mount; + file_class->unmount = vfs_file_unmount; + file_class->eject = vfs_file_eject; + file_class->start = vfs_file_start; + file_class->stop = vfs_file_stop; + file_class->poll_for_media = vfs_file_poll_for_media; +} diff --git a/src/nautilus-vfs-file.h b/src/nautilus-vfs-file.h new file mode 100644 index 0000000..cbb21ab --- /dev/null +++ b/src/nautilus-vfs-file.h @@ -0,0 +1,49 @@ +/* + nautilus-vfs-file.h: Subclass of NautilusFile to implement the + the case of a VFS file. + + Copyright (C) 1999, 2000 Eazel, Inc. + + This program is free software; you can redistribute it and/or + modify it under the terms of the GNU General Public License as + published by the Free Software Foundation; either version 2 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 warranty of + MERCHANTABILITY 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 . + + Author: Darin Adler +*/ + +#pragma once + +#include "nautilus-file.h" + +#define NAUTILUS_TYPE_VFS_FILE nautilus_vfs_file_get_type() +#define NAUTILUS_VFS_FILE(obj) \ + (G_TYPE_CHECK_INSTANCE_CAST ((obj), NAUTILUS_TYPE_VFS_FILE, NautilusVFSFile)) +#define NAUTILUS_VFS_FILE_CLASS(klass) \ + (G_TYPE_CHECK_CLASS_CAST ((klass), NAUTILUS_TYPE_VFS_FILE, NautilusVFSFileClass)) +#define NAUTILUS_IS_VFS_FILE(obj) \ + (G_TYPE_CHECK_INSTANCE_TYPE ((obj), NAUTILUS_TYPE_VFS_FILE)) +#define NAUTILUS_IS_VFS_FILE_CLASS(klass) \ + (G_TYPE_CHECK_CLASS_TYPE ((klass), NAUTILUS_TYPE_VFS_FILE)) +#define NAUTILUS_VFS_FILE_GET_CLASS(obj) \ + (G_TYPE_INSTANCE_GET_CLASS ((obj), NAUTILUS_TYPE_VFS_FILE, NautilusVFSFileClass)) + +typedef struct NautilusVFSFileDetails NautilusVFSFileDetails; + +typedef struct { + NautilusFile parent_slot; +} NautilusVFSFile; + +typedef struct { + NautilusFileClass parent_slot; +} NautilusVFSFileClass; + +GType nautilus_vfs_file_get_type (void); \ No newline at end of file diff --git a/src/nautilus-video-mime-types.h b/src/nautilus-video-mime-types.h new file mode 100644 index 0000000..e0d4aac --- /dev/null +++ b/src/nautilus-video-mime-types.h @@ -0,0 +1,65 @@ +/* generated with mime-type-include.sh in the totem module, don't edit or + commit in the nautilus module without filing a bug against totem */ +static const char *video_mime_types[] = { +"application/mxf", +"application/ogg", +"application/ram", +"application/sdp", +"application/vnd.apple.mpegurl", +"application/vnd.ms-wpl", +"application/vnd.rn-realmedia", +"application/x-extension-m4a", +"application/x-extension-mp4", +"application/x-flash-video", +"application/x-matroska", +"application/x-netshow-channel", +"application/x-ogg", +"application/x-quicktimeplayer", +"application/x-shorten", +"image/vnd.rn-realpix", +"image/x-pict", +"misc/ultravox", +"text/x-google-video-pointer", +"video/3gp", +"video/3gpp", +"video/dv", +"video/divx", +"video/fli", +"video/flv", +"video/mp2t", +"video/mp4", +"video/mp4v-es", +"video/mpeg", +"video/msvideo", +"video/ogg", +"video/quicktime", +"video/vivo", +"video/vnd.divx", +"video/vnd.mpegurl", +"video/vnd.rn-realvideo", +"video/vnd.vivo", +"video/webm", +"video/x-anim", +"video/x-avi", +"video/x-flc", +"video/x-fli", +"video/x-flic", +"video/x-flv", +"video/x-m4v", +"video/x-matroska", +"video/x-mpeg", +"video/x-mpeg2", +"video/x-ms-asf", +"video/x-ms-asx", +"video/x-msvideo", +"video/x-ms-wm", +"video/x-ms-wmv", +"video/x-ms-wmx", +"video/x-ms-wvx", +"video/x-nsv", +"video/x-ogm+ogg", +"video/x-theora+ogg", +"video/x-totem-stream", +"audio/x-pn-realaudio", +NULL +}; diff --git a/src/nautilus-view-cell.c b/src/nautilus-view-cell.c new file mode 100644 index 0000000..6f28fd8 --- /dev/null +++ b/src/nautilus-view-cell.c @@ -0,0 +1,190 @@ +/* + * Copyright (C) 2022 2022 António Fernandes + * + * SPDX-License-Identifier: GPL-3.0-or-later + */ + +#include "nautilus-view-cell.h" +#include "nautilus-list-base.h" + +/** + * NautilusViewCell: + * + * Abstract class of widgets tailored to be set as #GtkListItem:child in a view + * which subclasses #NautilusListBase. + * + * Subclass constructors should take a pointer to the #NautilusListBase view. + * + * The view is responsible for setting #NautilusViewCell:item. This can be done + * using a GBinding from #GtkListItem:item to #NautilusViewCell:item. + */ + +typedef struct _NautilusViewCellPrivate NautilusViewCellPrivate; +struct _NautilusViewCellPrivate +{ + AdwBin parent_instance; + + NautilusListBase *view; /* Unowned */ + NautilusViewItem *item; /* Owned reference */ + + gboolean called_once; +}; + + +G_DEFINE_ABSTRACT_TYPE_WITH_PRIVATE (NautilusViewCell, nautilus_view_cell, ADW_TYPE_BIN) + +enum +{ + PROP_0, + PROP_VIEW, + PROP_ITEM, + N_PROPS +}; + +static GParamSpec *properties[N_PROPS] = { NULL, }; + +static void +nautilus_view_cell_get_property (GObject *object, + guint prop_id, + GValue *value, + GParamSpec *pspec) +{ + NautilusViewCell *self = NAUTILUS_VIEW_CELL (object); + NautilusViewCellPrivate *priv = nautilus_view_cell_get_instance_private (self); + + switch (prop_id) + { + case PROP_VIEW: + { + g_value_set_object (value, priv->view); + } + break; + + case PROP_ITEM: + { + g_value_set_object (value, priv->item); + } + break; + + default: + { + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); + } + } +} + +static void +nautilus_view_cell_set_property (GObject *object, + guint prop_id, + const GValue *value, + GParamSpec *pspec) +{ + NautilusViewCell *self = NAUTILUS_VIEW_CELL (object); + NautilusViewCellPrivate *priv = nautilus_view_cell_get_instance_private (self); + + switch (prop_id) + { + case PROP_VIEW: + { + priv->view = g_value_get_object (value); + } + break; + + case PROP_ITEM: + { + g_set_object (&priv->item, g_value_get_object (value)); + } + break; + + default: + { + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); + } + } +} + +static void +nautilus_view_cell_init (NautilusViewCell *self) +{ + gtk_widget_set_name (GTK_WIDGET (self), "NautilusViewCell"); +} + +static void +nautilus_view_cell_finalize (GObject *object) +{ + NautilusViewCell *self = NAUTILUS_VIEW_CELL (object); + NautilusViewCellPrivate *priv = nautilus_view_cell_get_instance_private (self); + + g_clear_object (&priv->item); + + G_OBJECT_CLASS (nautilus_view_cell_parent_class)->finalize (object); +} + +static void +nautilus_view_cell_class_init (NautilusViewCellClass *klass) +{ + GObjectClass *object_class = G_OBJECT_CLASS (klass); + + object_class->finalize = nautilus_view_cell_finalize; + object_class->get_property = nautilus_view_cell_get_property; + object_class->set_property = nautilus_view_cell_set_property; + + properties[PROP_VIEW] = g_param_spec_object ("view", + "", "", + NAUTILUS_TYPE_LIST_BASE, + G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY | G_PARAM_STATIC_STRINGS); + properties[PROP_ITEM] = g_param_spec_object ("item", + "", "", + NAUTILUS_TYPE_VIEW_ITEM, + G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS); + + g_object_class_install_properties (object_class, N_PROPS, properties); +} + +gboolean +nautilus_view_cell_once (NautilusViewCell *self) +{ + NautilusViewCellPrivate *priv = nautilus_view_cell_get_instance_private (self); + + if (priv->called_once) + { + return FALSE; + } + priv->called_once = TRUE; + + return TRUE; +} + +NautilusListBase * +nautilus_view_cell_get_view (NautilusViewCell *self) +{ + NautilusListBase *view; + + g_return_val_if_fail (NAUTILUS_IS_VIEW_CELL (self), NULL); + + g_object_get (self, "view", &view, NULL); + + return view; +} + +void +nautilus_view_cell_set_item (NautilusViewCell *self, + NautilusViewItem *item) +{ + g_return_if_fail (NAUTILUS_IS_VIEW_CELL (self)); + g_return_if_fail (item == NULL || NAUTILUS_IS_VIEW_ITEM (item)); + + g_object_set (self, "item", item, NULL); +} + +NautilusViewItem * +nautilus_view_cell_get_item (NautilusViewCell *self) +{ + NautilusViewItem *item; + + g_return_val_if_fail (NAUTILUS_IS_VIEW_CELL (self), NULL); + + g_object_get (self, "item", &item, NULL); + + return item; +} diff --git a/src/nautilus-view-cell.h b/src/nautilus-view-cell.h new file mode 100644 index 0000000..78297b8 --- /dev/null +++ b/src/nautilus-view-cell.h @@ -0,0 +1,31 @@ +/* + * Copyright (C) 2022 António Fernandes + * + * SPDX-License-Identifier: GPL-3.0-or-later + */ + +#pragma once + +#include + +#include "nautilus-types.h" +#include "nautilus-view-item.h" + +G_BEGIN_DECLS + +#define NAUTILUS_TYPE_VIEW_CELL (nautilus_view_cell_get_type()) + +G_DECLARE_DERIVABLE_TYPE (NautilusViewCell, nautilus_view_cell, NAUTILUS, VIEW_CELL, AdwBin) + +struct _NautilusViewCellClass +{ + AdwBinClass parent_class; +}; + +NautilusListBase *nautilus_view_cell_get_view (NautilusViewCell *self); +void nautilus_view_cell_set_item (NautilusViewCell *self, + NautilusViewItem *item); +NautilusViewItem *nautilus_view_cell_get_item (NautilusViewCell *self); +gboolean nautilus_view_cell_once (NautilusViewCell *self); + +G_END_DECLS diff --git a/src/nautilus-view-controls.c b/src/nautilus-view-controls.c new file mode 100644 index 0000000..bd8e6d7 --- /dev/null +++ b/src/nautilus-view-controls.c @@ -0,0 +1,187 @@ +/* + * Copyright (C) 2022 The GNOME project contributors + * + * SPDX-License-Identifier: GPL-2.0-or-later + */ + +#include "nautilus-view-controls.h" + +#include "nautilus-toolbar-menu-sections.h" +#include "nautilus-window.h" + +struct _NautilusViewControls +{ + AdwBin parent_instance; + + GtkWidget *view_split_button; + GMenuModel *view_menu; + + NautilusWindowSlot *window_slot; +}; + +G_DEFINE_FINAL_TYPE (NautilusViewControls, nautilus_view_controls, ADW_TYPE_BIN); + + +enum +{ + PROP_0, + PROP_WINDOW_SLOT, + N_PROPS +}; + +static GParamSpec *properties[N_PROPS]; + +static void +on_slot_toolbar_menu_sections_changed (NautilusViewControls *self, + GParamSpec *param, + NautilusWindowSlot *slot) +{ + NautilusToolbarMenuSections *new_sections; + g_autoptr (GMenuItem) zoom_item = NULL; + g_autoptr (GMenuItem) sort_item = NULL; + + new_sections = nautilus_window_slot_get_toolbar_menu_sections (slot); + + gtk_widget_set_sensitive (self->view_split_button, (new_sections != NULL)); + if (new_sections == NULL) + { + return; + } + + /* Let's assume that sort section is the first item + * in view_menu, as per nautilus-toolbar.ui. */ + + sort_item = g_menu_item_new_from_model (self->view_menu, 0); + g_menu_remove (G_MENU (self->view_menu), 0); + g_menu_item_set_section (sort_item, new_sections->sort_section); + g_menu_insert_item (G_MENU (self->view_menu), 0, sort_item); +} + +static void +disconnect_toolbar_menu_sections_change_handler (NautilusViewControls *self) +{ + if (self->window_slot == NULL) + { + return; + } + + g_signal_handlers_disconnect_by_func (self->window_slot, + G_CALLBACK (on_slot_toolbar_menu_sections_changed), + self); +} + + +static void +nautilus_view_controls_set_window_slot (NautilusViewControls *self, + NautilusWindowSlot *window_slot) +{ + g_return_if_fail (NAUTILUS_IS_VIEW_CONTROLS (self)); + g_return_if_fail (window_slot == NULL || NAUTILUS_IS_WINDOW_SLOT (window_slot)); + + if (self->window_slot == window_slot) + { + return; + } + + disconnect_toolbar_menu_sections_change_handler (self); + + self->window_slot = window_slot; + + if (self->window_slot != NULL) + { + on_slot_toolbar_menu_sections_changed (self, NULL, self->window_slot); + g_signal_connect_swapped (self->window_slot, "notify::toolbar-menu-sections", + G_CALLBACK (on_slot_toolbar_menu_sections_changed), self); + } + + g_object_notify_by_pspec (G_OBJECT (self), properties[PROP_WINDOW_SLOT]); +} + +static void +nautilus_view_controls_get_property (GObject *object, + guint prop_id, + GValue *value, + GParamSpec *pspec) +{ + NautilusViewControls *self = NAUTILUS_VIEW_CONTROLS (object); + + switch (prop_id) + { + case PROP_WINDOW_SLOT: + { + g_value_set_object (value, G_OBJECT (self->window_slot)); + break; + } + + default: + { + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); + } + } +} + +static void +nautilus_view_controls_set_property (GObject *object, + guint prop_id, + const GValue *value, + GParamSpec *pspec) +{ + NautilusViewControls *self = NAUTILUS_VIEW_CONTROLS (object); + + switch (prop_id) + { + case PROP_WINDOW_SLOT: + { + nautilus_view_controls_set_window_slot (self, NAUTILUS_WINDOW_SLOT (g_value_get_object (value))); + break; + } + + default: + { + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); + } + } +} + +static void +nautilus_view_controls_finalize (GObject *obj) +{ + NautilusViewControls *self = NAUTILUS_VIEW_CONTROLS (obj); + + if (self->window_slot != NULL) + { + g_signal_handlers_disconnect_by_data (self->window_slot, self); + self->window_slot = NULL; + } + + G_OBJECT_CLASS (nautilus_view_controls_parent_class)->finalize (obj); +} + +static void +nautilus_view_controls_class_init (NautilusViewControlsClass *klass) +{ + GObjectClass *object_class = G_OBJECT_CLASS (klass); + GtkWidgetClass *widget_class = GTK_WIDGET_CLASS (klass); + + object_class->finalize = nautilus_view_controls_finalize; + object_class->get_property = nautilus_view_controls_get_property; + object_class->set_property = nautilus_view_controls_set_property; + + properties[PROP_WINDOW_SLOT] = g_param_spec_object ("window-slot", + NULL, NULL, + NAUTILUS_TYPE_WINDOW_SLOT, + G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS | G_PARAM_EXPLICIT_NOTIFY); + + g_object_class_install_properties (object_class, N_PROPS, properties); + + gtk_widget_class_set_template_from_resource (widget_class, + "/org/gnome/nautilus/ui/nautilus-view-controls.ui"); + gtk_widget_class_bind_template_child (widget_class, NautilusViewControls, view_menu); + gtk_widget_class_bind_template_child (widget_class, NautilusViewControls, view_split_button); +} + +static void +nautilus_view_controls_init (NautilusViewControls *self) +{ + gtk_widget_init_template (GTK_WIDGET (self)); +} diff --git a/src/nautilus-view-controls.h b/src/nautilus-view-controls.h new file mode 100644 index 0000000..1b54737 --- /dev/null +++ b/src/nautilus-view-controls.h @@ -0,0 +1,20 @@ +/* + * Copyright (C) 2022 The GNOME project contributors + * + * SPDX-License-Identifier: GPL-2.0-or-later + */ + +#pragma once + +#include +#include + +#include "nautilus-window-slot.h" + +G_BEGIN_DECLS + +#define NAUTILUS_TYPE_VIEW_CONTROLS (nautilus_view_controls_get_type()) + +G_DECLARE_FINAL_TYPE (NautilusViewControls, nautilus_view_controls, NAUTILUS, VIEW_CONTROLS, AdwBin) + +G_END_DECLS diff --git a/src/nautilus-view-item.c b/src/nautilus-view-item.c new file mode 100644 index 0000000..84423fa --- /dev/null +++ b/src/nautilus-view-item.c @@ -0,0 +1,280 @@ +/* + * Copyright (C) 2022 The GNOME project contributors + * + * SPDX-License-Identifier: GPL-3.0-or-later + */ + +#include "nautilus-view-item.h" + +struct _NautilusViewItem +{ + GObject parent_instance; + guint icon_size; + gboolean is_cut; + gboolean drag_accept; + NautilusFile *file; + GtkWidget *item_ui; +}; + +G_DEFINE_TYPE (NautilusViewItem, nautilus_view_item, G_TYPE_OBJECT) + +enum +{ + PROP_0, + PROP_FILE, + PROP_ICON_SIZE, + PROP_IS_CUT, + PROP_DRAG_ACCEPT, + PROP_ITEM_UI, + N_PROPS +}; + +static GParamSpec *properties[N_PROPS] = { NULL, }; + +enum +{ + FILE_CHANGED, + LAST_SIGNAL +}; + +static guint signals[LAST_SIGNAL]; + +static void +nautilus_view_item_dispose (GObject *object) +{ + NautilusViewItem *self = NAUTILUS_VIEW_ITEM (object); + + g_clear_object (&self->item_ui); + + G_OBJECT_CLASS (nautilus_view_item_parent_class)->dispose (object); +} + +static void +nautilus_view_item_finalize (GObject *object) +{ + NautilusViewItem *self = NAUTILUS_VIEW_ITEM (object); + + g_clear_object (&self->file); + + G_OBJECT_CLASS (nautilus_view_item_parent_class)->finalize (object); +} + +static void +nautilus_view_item_get_property (GObject *object, + guint prop_id, + GValue *value, + GParamSpec *pspec) +{ + NautilusViewItem *self = NAUTILUS_VIEW_ITEM (object); + + switch (prop_id) + { + case PROP_FILE: + { + g_value_set_object (value, self->file); + } + break; + + case PROP_ICON_SIZE: + { + g_value_set_int (value, self->icon_size); + } + break; + + case PROP_IS_CUT: + { + g_value_set_boolean (value, self->is_cut); + } + break; + + case PROP_DRAG_ACCEPT: + { + g_value_set_boolean (value, self->drag_accept); + } + break; + + case PROP_ITEM_UI: + { + g_value_set_object (value, self->item_ui); + } + break; + + default: + { + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); + } + } +} + +static void +nautilus_view_item_set_property (GObject *object, + guint prop_id, + const GValue *value, + GParamSpec *pspec) +{ + NautilusViewItem *self = NAUTILUS_VIEW_ITEM (object); + + switch (prop_id) + { + case PROP_FILE: + { + self->file = g_value_dup_object (value); + } + break; + + case PROP_ICON_SIZE: + { + self->icon_size = g_value_get_int (value); + } + break; + + case PROP_IS_CUT: + { + self->is_cut = g_value_get_boolean (value); + } + break; + + case PROP_DRAG_ACCEPT: + { + self->drag_accept = g_value_get_boolean (value); + } + break; + + case PROP_ITEM_UI: + { + g_set_object (&self->item_ui, g_value_get_object (value)); + } + break; + + default: + { + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); + } + } +} + +static void +nautilus_view_item_init (NautilusViewItem *self) +{ +} + +static void +nautilus_view_item_class_init (NautilusViewItemClass *klass) +{ + GObjectClass *object_class = G_OBJECT_CLASS (klass); + + object_class->dispose = nautilus_view_item_dispose; + object_class->finalize = nautilus_view_item_finalize; + object_class->get_property = nautilus_view_item_get_property; + object_class->set_property = nautilus_view_item_set_property; + + properties[PROP_ICON_SIZE] = g_param_spec_int ("icon-size", + "", "", + NAUTILUS_LIST_ICON_SIZE_SMALL, + NAUTILUS_GRID_ICON_SIZE_EXTRA_LARGE, + NAUTILUS_GRID_ICON_SIZE_LARGE, + G_PARAM_READWRITE | G_PARAM_CONSTRUCT | G_PARAM_STATIC_STRINGS); + properties[PROP_IS_CUT] = g_param_spec_boolean ("is-cut", + "", "", + FALSE, + G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS); + properties[PROP_DRAG_ACCEPT] = g_param_spec_boolean ("drag-accept", + "", "", + FALSE, + G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS); + properties[PROP_FILE] = g_param_spec_object ("file", + "", "", + NAUTILUS_TYPE_FILE, + G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY | G_PARAM_STATIC_STRINGS); + properties[PROP_ITEM_UI] = g_param_spec_object ("item-ui", + "", "", + GTK_TYPE_WIDGET, + G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS); + g_object_class_install_properties (object_class, N_PROPS, properties); + + signals[FILE_CHANGED] = g_signal_new ("file-changed", + G_TYPE_FROM_CLASS (klass), + G_SIGNAL_RUN_LAST, + 0, + NULL, NULL, + g_cclosure_marshal_VOID__VOID, + G_TYPE_NONE, 0); +} + +NautilusViewItem * +nautilus_view_item_new (NautilusFile *file, + guint icon_size) +{ + return g_object_new (NAUTILUS_TYPE_VIEW_ITEM, + "file", file, + "icon-size", icon_size, + NULL); +} + +guint +nautilus_view_item_get_icon_size (NautilusViewItem *self) +{ + g_return_val_if_fail (NAUTILUS_IS_VIEW_ITEM (self), -1); + + return self->icon_size; +} + +void +nautilus_view_item_set_icon_size (NautilusViewItem *self, + guint icon_size) +{ + g_return_if_fail (NAUTILUS_IS_VIEW_ITEM (self)); + + g_object_set (self, "icon-size", icon_size, NULL); +} + +void +nautilus_view_item_set_cut (NautilusViewItem *self, + gboolean is_cut) +{ + g_return_if_fail (NAUTILUS_IS_VIEW_ITEM (self)); + + g_object_set (self, "is-cut", is_cut, NULL); +} + +void +nautilus_view_item_set_drag_accept (NautilusViewItem *self, + gboolean drag_accept) +{ + g_return_if_fail (NAUTILUS_IS_VIEW_ITEM (self)); + + g_object_set (self, "drag-accept", drag_accept, NULL); +} + +NautilusFile * +nautilus_view_item_get_file (NautilusViewItem *self) +{ + g_return_val_if_fail (NAUTILUS_IS_VIEW_ITEM (self), NULL); + + return self->file; +} + +GtkWidget * +nautilus_view_item_get_item_ui (NautilusViewItem *self) +{ + g_return_val_if_fail (NAUTILUS_IS_VIEW_ITEM (self), NULL); + + return self->item_ui; +} + +void +nautilus_view_item_set_item_ui (NautilusViewItem *self, + GtkWidget *item_ui) +{ + g_return_if_fail (NAUTILUS_IS_VIEW_ITEM (self)); + + g_object_set (self, "item-ui", item_ui, NULL); +} + +void +nautilus_view_item_file_changed (NautilusViewItem *self) +{ + g_return_if_fail (NAUTILUS_IS_VIEW_ITEM (self)); + + g_signal_emit (self, signals[FILE_CHANGED], 0); +} diff --git a/src/nautilus-view-item.h b/src/nautilus-view-item.h new file mode 100644 index 0000000..9bdaff1 --- /dev/null +++ b/src/nautilus-view-item.h @@ -0,0 +1,40 @@ +/* + * Copyright (C) 2022 The GNOME project contributors + * + * SPDX-License-Identifier: GPL-3.0-or-later + */ + +#pragma once + +#include +#include + +#include "nautilus-file.h" + +G_BEGIN_DECLS + +#define NAUTILUS_TYPE_VIEW_ITEM (nautilus_view_item_get_type()) + +G_DECLARE_FINAL_TYPE (NautilusViewItem, nautilus_view_item, NAUTILUS, VIEW_ITEM, GObject) + +NautilusViewItem * nautilus_view_item_new (NautilusFile *file, + guint icon_size); + +void nautilus_view_item_set_icon_size (NautilusViewItem *self, + guint icon_size); + +guint nautilus_view_item_get_icon_size (NautilusViewItem *self); +void nautilus_view_item_set_cut (NautilusViewItem *self, + gboolean is_cut); +void nautilus_view_item_set_drag_accept (NautilusViewItem *self, + gboolean drag_accept); + +NautilusFile * nautilus_view_item_get_file (NautilusViewItem *self); + +void nautilus_view_item_set_item_ui (NautilusViewItem *self, + GtkWidget *item_ui); + +GtkWidget * nautilus_view_item_get_item_ui (NautilusViewItem *self); +void nautilus_view_item_file_changed (NautilusViewItem *self); + +G_END_DECLS diff --git a/src/nautilus-view-model.c b/src/nautilus-view-model.c new file mode 100644 index 0000000..2cb1c47 --- /dev/null +++ b/src/nautilus-view-model.c @@ -0,0 +1,422 @@ +#include "nautilus-view-model.h" +#include "nautilus-view-item.h" +#include "nautilus-global-preferences.h" + +struct _NautilusViewModel +{ + GObject parent_instance; + + GHashTable *map_files_to_model; + GListStore *internal_model; + GtkMultiSelection *selection_model; + GtkSorter *sorter; + gulong sorter_changed_id; +}; + +static GType +nautilus_view_model_get_item_type (GListModel *list) +{ + return NAUTILUS_TYPE_VIEW_ITEM; +} + +static guint +nautilus_view_model_get_n_items (GListModel *list) +{ + NautilusViewModel *self = NAUTILUS_VIEW_MODEL (list); + + if (self->internal_model == NULL) + { + return 0; + } + + return g_list_model_get_n_items (G_LIST_MODEL (self->internal_model)); +} + +static gpointer +nautilus_view_model_get_item (GListModel *list, + guint position) +{ + NautilusViewModel *self = NAUTILUS_VIEW_MODEL (list); + + if (self->internal_model == NULL) + { + return NULL; + } + + return g_list_model_get_item (G_LIST_MODEL (self->internal_model), position); +} + +static void +nautilus_view_model_list_model_init (GListModelInterface *iface) +{ + iface->get_item_type = nautilus_view_model_get_item_type; + iface->get_n_items = nautilus_view_model_get_n_items; + iface->get_item = nautilus_view_model_get_item; +} + + +static gboolean +nautilus_view_model_is_selected (GtkSelectionModel *model, + guint position) +{ + NautilusViewModel *self = NAUTILUS_VIEW_MODEL (model); + GtkSelectionModel *selection_model = GTK_SELECTION_MODEL (self->selection_model); + + return gtk_selection_model_is_selected (selection_model, position); +} + +static GtkBitset * +nautilus_view_model_get_selection_in_range (GtkSelectionModel *model, + guint pos, + guint n_items) +{ + NautilusViewModel *self = NAUTILUS_VIEW_MODEL (model); + GtkSelectionModel *selection_model = GTK_SELECTION_MODEL (self->selection_model); + + return gtk_selection_model_get_selection_in_range (selection_model, pos, n_items); +} + +static gboolean +nautilus_view_model_set_selection (GtkSelectionModel *model, + GtkBitset *selected, + GtkBitset *mask) +{ + NautilusViewModel *self = NAUTILUS_VIEW_MODEL (model); + GtkSelectionModel *selection_model = GTK_SELECTION_MODEL (self->selection_model); + gboolean res; + + res = gtk_selection_model_set_selection (selection_model, selected, mask); + + return res; +} + + +static void +nautilus_view_model_selection_model_init (GtkSelectionModelInterface *iface) +{ + iface->is_selected = nautilus_view_model_is_selected; + iface->get_selection_in_range = nautilus_view_model_get_selection_in_range; + iface->set_selection = nautilus_view_model_set_selection; +} + +G_DEFINE_TYPE_WITH_CODE (NautilusViewModel, nautilus_view_model, G_TYPE_OBJECT, + G_IMPLEMENT_INTERFACE (G_TYPE_LIST_MODEL, + nautilus_view_model_list_model_init) + G_IMPLEMENT_INTERFACE (GTK_TYPE_SELECTION_MODEL, + nautilus_view_model_selection_model_init)) + +enum +{ + PROP_0, + PROP_SORTER, + N_PROPS +}; + +static GParamSpec *properties[N_PROPS] = { NULL, }; + +static void +dispose (GObject *object) +{ + NautilusViewModel *self = NAUTILUS_VIEW_MODEL (object); + + if (self->selection_model != NULL) + { + g_signal_handlers_disconnect_by_func (self->selection_model, + gtk_selection_model_selection_changed, + self); + g_object_unref (self->selection_model); + self->selection_model = NULL; + } + + if (self->internal_model != NULL) + { + g_signal_handlers_disconnect_by_func (self->internal_model, + g_list_model_items_changed, + self); + g_object_unref (self->internal_model); + self->internal_model = NULL; + } + + g_clear_signal_handler (&self->sorter_changed_id, self->sorter); + + G_OBJECT_CLASS (nautilus_view_model_parent_class)->dispose (object); +} + +static void +finalize (GObject *object) +{ + NautilusViewModel *self = NAUTILUS_VIEW_MODEL (object); + + G_OBJECT_CLASS (nautilus_view_model_parent_class)->finalize (object); + + g_hash_table_destroy (self->map_files_to_model); + g_clear_object (&self->sorter); +} + +static void +get_property (GObject *object, + guint prop_id, + GValue *value, + GParamSpec *pspec) +{ + NautilusViewModel *self = NAUTILUS_VIEW_MODEL (object); + + switch (prop_id) + { + case PROP_SORTER: + { + g_value_set_object (value, nautilus_view_model_get_sorter (self)); + } + break; + + default: + { + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); + } + } +} + +static void +set_property (GObject *object, + guint prop_id, + const GValue *value, + GParamSpec *pspec) +{ + NautilusViewModel *self = NAUTILUS_VIEW_MODEL (object); + + switch (prop_id) + { + case PROP_SORTER: + { + nautilus_view_model_set_sorter (self, g_value_get_object (value)); + } + break; + + default: + { + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); + } + } +} + +static void +constructed (GObject *object) +{ + NautilusViewModel *self = NAUTILUS_VIEW_MODEL (object); + + G_OBJECT_CLASS (nautilus_view_model_parent_class)->constructed (object); + + self->internal_model = g_list_store_new (NAUTILUS_TYPE_VIEW_ITEM); + self->selection_model = gtk_multi_selection_new (g_object_ref (G_LIST_MODEL (self->internal_model))); + self->map_files_to_model = g_hash_table_new (NULL, NULL); + + g_signal_connect_swapped (self->internal_model, "items-changed", + G_CALLBACK (g_list_model_items_changed), self); + g_signal_connect_swapped (self->selection_model, "selection-changed", + G_CALLBACK (gtk_selection_model_selection_changed), self); +} + +static void +nautilus_view_model_class_init (NautilusViewModelClass *klass) +{ + GObjectClass *object_class = G_OBJECT_CLASS (klass); + + object_class->dispose = dispose; + object_class->finalize = finalize; + object_class->get_property = get_property; + object_class->set_property = set_property; + object_class->constructed = constructed; + + properties[PROP_SORTER] = + g_param_spec_object ("sorter", + "", "", + GTK_TYPE_SORTER, + G_PARAM_READWRITE | G_PARAM_EXPLICIT_NOTIFY | G_PARAM_STATIC_STRINGS); + + g_object_class_install_properties (object_class, N_PROPS, properties); +} + +static void +nautilus_view_model_init (NautilusViewModel *self) +{ +} + +static gint +compare_data_func (gconstpointer a, + gconstpointer b, + gpointer user_data) +{ + NautilusViewModel *self = NAUTILUS_VIEW_MODEL (user_data); + + if (self->sorter == NULL) + { + return GTK_ORDERING_EQUAL; + } + + return gtk_sorter_compare (self->sorter, (gpointer) a, (gpointer) b); +} + +static void +on_sorter_changed (GtkSorter *sorter, + GtkSorterChange change, + gpointer user_data) +{ + NautilusViewModel *self = NAUTILUS_VIEW_MODEL (user_data); + + g_list_store_sort (self->internal_model, compare_data_func, self); +} + +NautilusViewModel * +nautilus_view_model_new (void) +{ + return g_object_new (NAUTILUS_TYPE_VIEW_MODEL, NULL); +} + +GtkSorter * +nautilus_view_model_get_sorter (NautilusViewModel *self) +{ + return self->sorter; +} + +void +nautilus_view_model_set_sorter (NautilusViewModel *self, + GtkSorter *sorter) +{ + if (self->sorter != NULL) + { + g_clear_signal_handler (&self->sorter_changed_id, self->sorter); + } + + if (g_set_object (&self->sorter, sorter)) + { + g_object_notify_by_pspec (G_OBJECT (self), properties[PROP_SORTER]); + } + + if (self->sorter != NULL) + { + self->sorter_changed_id = g_signal_connect (self->sorter, "changed", + G_CALLBACK (on_sorter_changed), self); + g_list_store_sort (self->internal_model, compare_data_func, self); + } +} + +GQueue * +nautilus_view_model_get_items_from_files (NautilusViewModel *self, + GQueue *files) +{ + GList *l; + guint n_items; + GQueue *items; + + n_items = g_list_model_get_n_items (G_LIST_MODEL (self->internal_model)); + items = g_queue_new (); + for (l = g_queue_peek_head_link (files); l != NULL; l = l->next) + { + NautilusFile *file1; + + file1 = NAUTILUS_FILE (l->data); + for (guint i = 0; i < n_items; i++) + { + g_autoptr (NautilusViewItem) item = NULL; + NautilusFile *file2; + g_autofree gchar *file1_uri = NULL; + g_autofree gchar *file2_uri = NULL; + + item = g_list_model_get_item (G_LIST_MODEL (self->internal_model), i); + file2 = nautilus_view_item_get_file (item); + file1_uri = nautilus_file_get_uri (file1); + file2_uri = nautilus_file_get_uri (file2); + if (g_strcmp0 (file1_uri, file2_uri) == 0) + { + g_queue_push_tail (items, item); + break; + } + } + } + + return items; +} + +NautilusViewItem * +nautilus_view_model_get_item_from_file (NautilusViewModel *self, + NautilusFile *file) +{ + return g_hash_table_lookup (self->map_files_to_model, file); +} + +void +nautilus_view_model_remove_item (NautilusViewModel *self, + NautilusViewItem *item) +{ + guint i; + + if (g_list_store_find (self->internal_model, item, &i)) + { + NautilusFile *file; + + file = nautilus_view_item_get_file (item); + g_list_store_remove (self->internal_model, i); + g_hash_table_remove (self->map_files_to_model, file); + } +} + +void +nautilus_view_model_remove_all_items (NautilusViewModel *self) +{ + g_list_store_remove_all (self->internal_model); + g_hash_table_remove_all (self->map_files_to_model); +} + +void +nautilus_view_model_add_item (NautilusViewModel *self, + NautilusViewItem *item) +{ + g_hash_table_insert (self->map_files_to_model, + nautilus_view_item_get_file (item), + item); + g_list_store_insert_sorted (self->internal_model, item, compare_data_func, self); +} + +void +nautilus_view_model_add_items (NautilusViewModel *self, + GQueue *items) +{ + g_autofree gpointer *array = NULL; + GList *l; + int i = 0; + + /* Sort items before adding them to the internal model. This ensures that + * the first sorted item is become the initial focus and scroll anchor. */ + g_queue_sort (items, compare_data_func, self); + + array = g_malloc_n (g_queue_get_length (items), + sizeof (NautilusViewItem *)); + + for (l = g_queue_peek_head_link (items); l != NULL; l = l->next) + { + array[i] = l->data; + g_hash_table_insert (self->map_files_to_model, + nautilus_view_item_get_file (l->data), + l->data); + i++; + } + + g_list_store_splice (self->internal_model, + g_list_model_get_n_items (G_LIST_MODEL (self->internal_model)), + 0, array, g_queue_get_length (items)); + + g_list_store_sort (self->internal_model, compare_data_func, self); +} + +guint +nautilus_view_model_get_index (NautilusViewModel *self, + NautilusViewItem *item) +{ + guint i = G_MAXUINT; + gboolean found; + + found = g_list_store_find (self->internal_model, item, &i); + g_warn_if_fail (found); + + return i; +} diff --git a/src/nautilus-view-model.h b/src/nautilus-view-model.h new file mode 100644 index 0000000..839c6d2 --- /dev/null +++ b/src/nautilus-view-model.h @@ -0,0 +1,34 @@ +#pragma once + +#include +#include "nautilus-file.h" +#include "nautilus-view-item.h" + +G_BEGIN_DECLS + +#define NAUTILUS_TYPE_VIEW_MODEL (nautilus_view_model_get_type()) + +G_DECLARE_FINAL_TYPE (NautilusViewModel, nautilus_view_model, NAUTILUS, VIEW_MODEL, GObject) + +NautilusViewModel * nautilus_view_model_new (void); + +GtkSorter *nautilus_view_model_get_sorter (NautilusViewModel *self); +void nautilus_view_model_set_sorter (NautilusViewModel *self, + GtkSorter *sorter); +NautilusViewItem * nautilus_view_model_get_item_from_file (NautilusViewModel *self, + NautilusFile *file); +GQueue * nautilus_view_model_get_items_from_files (NautilusViewModel *self, + GQueue *files); +/* Don't use inside a loop, use nautilus_view_model_remove_all_items instead. */ +void nautilus_view_model_remove_item (NautilusViewModel *self, + NautilusViewItem *item); +void nautilus_view_model_remove_all_items (NautilusViewModel *self); +/* Don't use inside a loop, use nautilus_view_model_add_items instead. */ +void nautilus_view_model_add_item (NautilusViewModel *self, + NautilusViewItem *item); +void nautilus_view_model_add_items (NautilusViewModel *self, + GQueue *items); +guint nautilus_view_model_get_index (NautilusViewModel *self, + NautilusViewItem *item); + +G_END_DECLS diff --git a/src/nautilus-view.c b/src/nautilus-view.c new file mode 100644 index 0000000..5ee424c --- /dev/null +++ b/src/nautilus-view.c @@ -0,0 +1,369 @@ +/* nautilus-view.c + * + * Copyright (C) 2015 Georges Basile Stavracas Neto + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License 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 warranty of + * MERCHANTABILITY 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 . + */ + +#include "config.h" + +#include "nautilus-view.h" +#include + +G_DEFINE_INTERFACE (NautilusView, nautilus_view, GTK_TYPE_WIDGET) + +static void +nautilus_view_default_init (NautilusViewInterface *iface) +{ + /** + * NautilusView::loading: + * + * %TRUE if the view is loading the location, %FALSE otherwise. + */ + g_object_interface_install_property (iface, + g_param_spec_boolean ("loading", + "Current view is loading", + "Whether the current view is loading the location or not", + FALSE, + G_PARAM_READABLE)); + + /** + * NautilusView::searching: + * + * %TRUE if the view is searching, %FALSE otherwise. + */ + g_object_interface_install_property (iface, + g_param_spec_boolean ("searching", + "Current view is searching", + "Whether the current view is searching or not", + FALSE, + G_PARAM_READABLE)); + + /** + * NautilusView::location: + * + * The current location of the view. + */ + g_object_interface_install_property (iface, + g_param_spec_object ("location", + "Location displayed by the view", + "The current location displayed by the view", + G_TYPE_FILE, + G_PARAM_READWRITE)); + + /** + * NautilusView::selection: + * + * The current selection of the view. + */ + g_object_interface_install_property (iface, + g_param_spec_pointer ("selection", + "Selection of the view", + "The current selection of the view", + G_PARAM_READWRITE)); + + /** + * NautilusView::search-query: + * + * The search query being performed, or NULL. + */ + g_object_interface_install_property (iface, + g_param_spec_object ("search-query", + "Search query being performed", + "The search query being performed on the view", + NAUTILUS_TYPE_QUERY, + G_PARAM_READWRITE)); + + /** + * NautilusView::extensions-background-menu: + * + * Menu for the background click of extensions + */ + g_object_interface_install_property (iface, + g_param_spec_object ("extensions-background-menu", + "Menu for the background click of extensions", + "Menu for the background click of extensions", + G_TYPE_MENU_MODEL, + G_PARAM_READWRITE)); + /** + * NautilusView::templates-menu: + * + * Menu of templates + */ + g_object_interface_install_property (iface, + g_param_spec_object ("templates-menu", + "Menu of templates", + "Menu of templates", + G_TYPE_MENU_MODEL, + G_PARAM_READWRITE)); +} + +/** + * nautilus_view_get_icon_name: + * @view: a #NautilusView + * + * Retrieves the icon name that represents @view. + * + * Returns: (transfer none): an icon name + */ +const gchar * +nautilus_view_get_icon_name (guint view_id) +{ + if (view_id == NAUTILUS_VIEW_GRID_ID) + { + return "view-grid-symbolic"; + } + else if (view_id == NAUTILUS_VIEW_LIST_ID || view_id == NAUTILUS_VIEW_OTHER_LOCATIONS_ID) + { + return "view-list-symbolic"; + } + else + { + return NULL; + } +} + +/** + * nautilus_view_get_tooltip: + * @view: a #NautilusView + * + * Retrieves the static string that represents @view. + * + * Returns: (transfer none): a static string + */ +const gchar * +nautilus_view_get_tooltip (guint view_id) +{ + if (view_id == NAUTILUS_VIEW_GRID_ID) + { + return _("Grid View"); + } + else if (view_id == NAUTILUS_VIEW_LIST_ID) + { + return _("List View"); + } + else if (view_id == NAUTILUS_VIEW_OTHER_LOCATIONS_ID) + { + return _("List View"); + } + else + { + return NULL; + } +} + +/** + * nautilus_view_get_view_id: + * @view: a #NautilusView + * + * Retrieves the view id that represents the @view type. + * + * Returns: a guint representing the view type + */ +guint +nautilus_view_get_view_id (NautilusView *view) +{ + g_return_val_if_fail (NAUTILUS_VIEW_GET_IFACE (view)->get_view_id, NAUTILUS_VIEW_INVALID_ID); + + return NAUTILUS_VIEW_GET_IFACE (view)->get_view_id (view); +} + +/** + * nautilus_view_get_toolbar_menu_sections: + * @view: a #NautilusView + * + * Retrieves the menu sections to show in the main toolbar menu when this view + * is active + * + * Returns: (transfer none): a #NautilusToolbarMenuSections with the sections to + * be displayed + */ +NautilusToolbarMenuSections * +nautilus_view_get_toolbar_menu_sections (NautilusView *view) +{ + g_return_val_if_fail (NAUTILUS_VIEW_GET_IFACE (view)->get_toolbar_menu_sections, NULL); + + return NAUTILUS_VIEW_GET_IFACE (view)->get_toolbar_menu_sections (view); +} + +GMenuModel * +nautilus_view_get_extensions_background_menu (NautilusView *view) +{ + g_return_val_if_fail (NAUTILUS_VIEW_GET_IFACE (view)->get_extensions_background_menu, NULL); + + return NAUTILUS_VIEW_GET_IFACE (view)->get_extensions_background_menu (view); +} + +/* Protected */ +void +nautilus_view_set_extensions_background_menu (NautilusView *view, + GMenuModel *menu) +{ + g_return_if_fail (NAUTILUS_VIEW_GET_IFACE (view)->set_extensions_background_menu); + + NAUTILUS_VIEW_GET_IFACE (view)->set_extensions_background_menu (view, menu); +} + +GMenuModel * +nautilus_view_get_templates_menu (NautilusView *view) +{ + g_return_val_if_fail (NAUTILUS_VIEW_GET_IFACE (view)->get_templates_menu, NULL); + + return NAUTILUS_VIEW_GET_IFACE (view)->get_templates_menu (view); +} + +/* Protected */ +void +nautilus_view_set_templates_menu (NautilusView *view, + GMenuModel *menu) +{ + g_return_if_fail (NAUTILUS_VIEW_GET_IFACE (view)->set_templates_menu); + + NAUTILUS_VIEW_GET_IFACE (view)->set_templates_menu (view, menu); +} + +/** + * nautilus_view_get_search_query: + * @view: a #NautilusView + * + * Retrieves the current current location of @view. + * + * Returns: (transfer none): a #GFile + */ +GFile * +nautilus_view_get_location (NautilusView *view) +{ + g_return_val_if_fail (NAUTILUS_VIEW_GET_IFACE (view)->get_location, NULL); + + return NAUTILUS_VIEW_GET_IFACE (view)->get_location (view); +} + +/** + * nautilus_view_set_location: + * @view: a #NautilusView + * @location: the location displayed by @view + * + * Sets the location of @view. + * + * Returns: + */ +void +nautilus_view_set_location (NautilusView *view, + GFile *location) +{ + g_return_if_fail (NAUTILUS_VIEW_GET_IFACE (view)->set_location); + + NAUTILUS_VIEW_GET_IFACE (view)->set_location (view, location); +} + +/** + * nautilus_view_get_selection: + * @view: a #NautilusView + * + * Get the current selection of the view. + * + * Returns: (transfer full) (type GFile): a newly allocated list + * of the currently selected files. + */ +GList * +nautilus_view_get_selection (NautilusView *view) +{ + g_return_val_if_fail (NAUTILUS_VIEW_GET_IFACE (view)->get_selection, NULL); + + return NAUTILUS_VIEW_GET_IFACE (view)->get_selection (view); +} + +/** + * nautilus_view_set_selection: + * @view: a #NautilusView + * @selection: (nullable): a list of files + * + * Sets the current selection of the view. + * + * Returns: + */ +void +nautilus_view_set_selection (NautilusView *view, + GList *selection) +{ + g_return_if_fail (NAUTILUS_VIEW_GET_IFACE (view)->set_selection); + + NAUTILUS_VIEW_GET_IFACE (view)->set_selection (view, selection); +} + +/** + * nautilus_view_get_search_query: + * @view: a #NautilusView + * + * Retrieves the current search query displayed by @view. + * + * Returns: (transfer none): a # + */ +NautilusQuery * +nautilus_view_get_search_query (NautilusView *view) +{ + g_return_val_if_fail (NAUTILUS_VIEW_GET_IFACE (view)->get_search_query, NULL); + + return NAUTILUS_VIEW_GET_IFACE (view)->get_search_query (view); +} + +/** + * nautilus_view_set_search_query: + * @view: a #NautilusView + * @query: the search query to be performed, or %NULL + * + * Sets the current search query performed by @view. + * + * Returns: + */ +void +nautilus_view_set_search_query (NautilusView *view, + NautilusQuery *query) +{ + g_return_if_fail (NAUTILUS_VIEW_GET_IFACE (view)->set_search_query); + + NAUTILUS_VIEW_GET_IFACE (view)->set_search_query (view, query); +} + +/** + * nautilus_view_is_loading: + * @view: a #NautilusView + * + * Whether @view is loading the current location. + * + * Returns: %TRUE if @view is loading, %FALSE otherwise. + */ +gboolean +nautilus_view_is_loading (NautilusView *view) +{ + g_return_val_if_fail (NAUTILUS_VIEW_GET_IFACE (view)->is_loading, FALSE); + + return NAUTILUS_VIEW_GET_IFACE (view)->is_loading (view); +} + +/** + * nautilus_view_is_searching: + * @view: a #NautilusView + * + * Whether @view is searching. + * + * Returns: %TRUE if @view is searching, %FALSE otherwise. + */ +gboolean +nautilus_view_is_searching (NautilusView *view) +{ + g_return_val_if_fail (NAUTILUS_VIEW_GET_IFACE (view)->is_searching, FALSE); + + return NAUTILUS_VIEW_GET_IFACE (view)->is_searching (view); +} diff --git a/src/nautilus-view.h b/src/nautilus-view.h new file mode 100644 index 0000000..9a8911e --- /dev/null +++ b/src/nautilus-view.h @@ -0,0 +1,121 @@ +/* nautilus-view.h + * + * Copyright (C) 2015 Georges Basile Stavracas Neto + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License 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 warranty of + * MERCHANTABILITY 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 . + */ + +#pragma once + +#include +#include + +#include "nautilus-query.h" +#include "nautilus-toolbar-menu-sections.h" + +/* Keep values in sync with the org.gnome.nautilus.FolderView schema enums: */ +#define NAUTILUS_VIEW_LIST_ID 1 +#define NAUTILUS_VIEW_GRID_ID 2 +/* Special ids, not used by GSettings schemas: */ +#define NAUTILUS_VIEW_INVALID_ID 0 +#define NAUTILUS_VIEW_OTHER_LOCATIONS_ID 3 + +G_BEGIN_DECLS + +#define NAUTILUS_TYPE_VIEW (nautilus_view_get_type ()) + +G_DECLARE_INTERFACE (NautilusView, nautilus_view, NAUTILUS, VIEW, GtkWidget) + +struct _NautilusViewInterface +{ + GTypeInterface parent; + + guint (*get_view_id) (NautilusView *view); + /* + * Returns the menu sections that should be shown in the toolbar menu + * when this view is active. Implementations must not return %NULL + */ + NautilusToolbarMenuSections * (*get_toolbar_menu_sections) (NautilusView *view); + + /* + * Returns the menu for the background click of extensions. + */ + GMenuModel * (*get_extensions_background_menu) (NautilusView *view); + + void (*set_extensions_background_menu) (NautilusView *view, + GMenuModel *menu); + /* + * Returns the menu for templates. + */ + GMenuModel * (*get_templates_menu) (NautilusView *view); + + void (*set_templates_menu) (NautilusView *view, + GMenuModel *menu); + /* Current location of the view */ + GFile* (*get_location) (NautilusView *view); + void (*set_location) (NautilusView *view, + GFile *location); + + /* Selection */ + GList* (*get_selection) (NautilusView *view); + void (*set_selection) (NautilusView *view, + GList *selection); + + /* Search */ + NautilusQuery* (*get_search_query) (NautilusView *view); + void (*set_search_query) (NautilusView *view, + NautilusQuery *query); + + /* Whether the current view is loading the location */ + gboolean (*is_loading) (NautilusView *view); + + /* Whether the current view is searching or not */ + gboolean (*is_searching) (NautilusView *view); +}; + +const gchar * nautilus_view_get_icon_name (guint view_id); + +const gchar * nautilus_view_get_tooltip (guint view_id); + +guint nautilus_view_get_view_id (NautilusView *view); + +NautilusToolbarMenuSections * nautilus_view_get_toolbar_menu_sections (NautilusView *view); + +GFile * nautilus_view_get_location (NautilusView *view); + +void nautilus_view_set_location (NautilusView *view, + GFile *location); + +GList * nautilus_view_get_selection (NautilusView *view); + +void nautilus_view_set_selection (NautilusView *view, + GList *selection); + +NautilusQuery * nautilus_view_get_search_query (NautilusView *view); + +void nautilus_view_set_search_query (NautilusView *view, + NautilusQuery *query); + +gboolean nautilus_view_is_loading (NautilusView *view); + +gboolean nautilus_view_is_searching (NautilusView *view); + +void nautilus_view_set_templates_menu (NautilusView *view, + GMenuModel *menu); +GMenuModel * nautilus_view_get_templates_menu (NautilusView *view); +void nautilus_view_set_extensions_background_menu (NautilusView *view, + GMenuModel *menu); +GMenuModel * nautilus_view_get_extensions_background_menu (NautilusView *view); + +G_END_DECLS diff --git a/src/nautilus-window-slot-dnd.c b/src/nautilus-window-slot-dnd.c new file mode 100644 index 0000000..b05af1a --- /dev/null +++ b/src/nautilus-window-slot-dnd.c @@ -0,0 +1,346 @@ +/* + * nautilus-window-slot-dnd.c - Handle DnD for widgets acting as + * NautilusWindowSlot proxies + * + * Copyright (C) 2000, 2001 Eazel, Inc. + * Copyright (C) 2010, Red Hat, Inc. + * + * The Gnome Library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Library General Public License as + * published by the Free Software Foundation; either version 2 of the + * License, or (at your option) any later version. + * + * The Gnome Library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Library General Public License for more details. + * + * You should have received a copy of the GNU Library General Public + * License along with the Gnome Library; see the file COPYING.LIB. If not, + * see . + * + * Authors: Pavel Cisler , + * Ettore Perazzoli + */ + +#include + +#include "nautilus-application.h" +#include "nautilus-files-view-dnd.h" +#include "nautilus-window-slot-dnd.h" + +#ifdef GDK_WINDOWING_X11 +#include +#endif + +typedef struct +{ + NautilusFile *target_file; + NautilusWindowSlot *target_slot; + GtkWidget *widget; + + graphene_point_t hover_start_point; + guint switch_location_timer; +} NautilusDragSlotProxyInfo; + +static void +switch_location (NautilusDragSlotProxyInfo *drag_info) +{ + GFile *location; + GtkRoot *window; + + if (drag_info->target_file == NULL) + { + return; + } + + window = gtk_widget_get_root (drag_info->widget); + g_assert (NAUTILUS_IS_WINDOW (window)); + + location = nautilus_file_get_location (drag_info->target_file); + nautilus_application_open_location_full (NAUTILUS_APPLICATION (g_application_get_default ()), + location, NAUTILUS_OPEN_FLAG_DONT_MAKE_ACTIVE, + NULL, NAUTILUS_WINDOW (window), NULL); + g_object_unref (location); +} + +static gboolean +slot_proxy_switch_location_timer (gpointer user_data) +{ + NautilusDragSlotProxyInfo *drag_info = user_data; + + drag_info->switch_location_timer = 0; + + switch_location (drag_info); + + return FALSE; +} + +static void +slot_proxy_check_switch_location_timer (NautilusDragSlotProxyInfo *drag_info) +{ + if (drag_info->switch_location_timer) + { + return; + } + + drag_info->switch_location_timer = g_timeout_add (HOVER_TIMEOUT, + slot_proxy_switch_location_timer, + drag_info); +} + +static void +slot_proxy_remove_switch_location_timer (NautilusDragSlotProxyInfo *drag_info) +{ + if (drag_info->switch_location_timer != 0) + { + g_source_remove (drag_info->switch_location_timer); + drag_info->switch_location_timer = 0; + } +} + +static GdkDragAction +slot_proxy_drag_motion (GtkDropTarget *target, + gdouble x, + gdouble y, + gpointer user_data) +{ + NautilusDragSlotProxyInfo *drag_info; + NautilusWindowSlot *target_slot; + GtkRoot *window; + GdkDragAction action; + char *target_uri; + GFile *location; + const GValue *value; + graphene_point_t start; + + drag_info = user_data; + + action = 0; + + value = gtk_drop_target_get_value (target); + if (value == NULL) + { + return 0; + } + + window = gtk_widget_get_root (drag_info->widget); + g_assert (NAUTILUS_IS_WINDOW (window)); + + target_uri = NULL; + if (drag_info->target_file != NULL) + { + target_uri = nautilus_file_get_uri (drag_info->target_file); + } + else + { + if (drag_info->target_slot != NULL) + { + target_slot = drag_info->target_slot; + } + else + { + target_slot = nautilus_window_get_active_slot (NAUTILUS_WINDOW (window)); + } + + if (target_slot != NULL) + { + location = nautilus_window_slot_get_location (target_slot); + target_uri = g_file_get_uri (location); + } + } + + if (target_uri != NULL) + { + NautilusFile *file; + NautilusDirectory *directory; + gboolean can; + file = nautilus_file_get_existing_by_uri (target_uri); + directory = nautilus_directory_get_for_file (file); + can = nautilus_file_can_write (file) && nautilus_directory_is_editable (directory); + nautilus_directory_unref (directory); + g_object_unref (file); + if (!can) + { + action = 0; + goto out; + } + + if (G_VALUE_HOLDS (value, GDK_TYPE_FILE_LIST)) + { + GSList *items = g_value_get_boxed (value); + action = nautilus_dnd_get_preferred_action (file, items->data); + } + } + + g_free (target_uri); + +out: + start = drag_info->hover_start_point; + if (gtk_drag_check_threshold (drag_info->widget, start.x, start.y, x, y)) + { + slot_proxy_remove_switch_location_timer (drag_info); + slot_proxy_check_switch_location_timer (drag_info); + drag_info->hover_start_point.x = x; + drag_info->hover_start_point.y = y; + } + + return action; +} + +static void +drag_info_free (gpointer user_data) +{ + NautilusDragSlotProxyInfo *drag_info = user_data; + + g_clear_object (&drag_info->target_file); + g_clear_object (&drag_info->target_slot); + + g_slice_free (NautilusDragSlotProxyInfo, drag_info); +} + +static void +drag_info_clear (NautilusDragSlotProxyInfo *drag_info) +{ + slot_proxy_remove_switch_location_timer (drag_info); +} + +static void +slot_proxy_drag_leave (GtkDropTarget *target, + gpointer user_data) +{ + NautilusDragSlotProxyInfo *drag_info; + + drag_info = user_data; + + drag_info_clear (drag_info); +} + +static void +slot_proxy_handle_drop (GtkDropTarget *target, + const GValue *value, + gdouble x, + gdouble y, + gpointer user_data) +{ + GtkRoot *window; + NautilusWindowSlot *target_slot; + NautilusFilesView *target_view; + char *target_uri; + GList *uri_list = NULL; + GFile *location; + NautilusDragSlotProxyInfo *drag_info; + + drag_info = user_data; + + window = gtk_widget_get_root (drag_info->widget); + g_assert (NAUTILUS_IS_WINDOW (window)); + + if (drag_info->target_slot != NULL) + { + target_slot = drag_info->target_slot; + } + else + { + target_slot = nautilus_window_get_active_slot (NAUTILUS_WINDOW (window)); + } + + target_uri = NULL; + if (drag_info->target_file != NULL) + { + target_uri = nautilus_file_get_uri (drag_info->target_file); + } + else if (target_slot != NULL) + { + location = nautilus_window_slot_get_location (target_slot); + target_uri = g_file_get_uri (location); + } + + target_view = NULL; + if (target_slot != NULL) + { + NautilusView *view; + + view = nautilus_window_slot_get_current_view (target_slot); + + if (view && NAUTILUS_IS_FILES_VIEW (view)) + { + target_view = NAUTILUS_FILES_VIEW (view); + } + } + + if (target_slot != NULL && target_view != NULL) + { + if (G_VALUE_HOLDS (value, GDK_TYPE_FILE_LIST)) + { + GSList *items = g_value_get_boxed (value); + GdkDragAction actions; + + for (GSList *l = items; l != NULL; l = l->next) + { + uri_list = g_list_prepend (uri_list, g_file_get_uri (l->data)); + } + + actions = gdk_drop_get_actions (gtk_drop_target_get_current_drop (target)); + + #ifdef GDK_WINDOWING_X11 + if (GDK_IS_X11_DISPLAY (gtk_widget_get_display (GTK_WIDGET (window)))) + { + /* Temporary workaround until the below GTK MR (or equivalend fix) + * is merged. Without this fix, the preferred action isn't set correctly. + * https://gitlab.gnome.org/GNOME/gtk/-/merge_requests/4982 */ + GdkDrag *drag = gdk_drop_get_drag (gtk_drop_target_get_current_drop (target)); + actions = gdk_drag_get_selected_action (drag); + } + #endif + + nautilus_files_view_drop_proxy_received_uris (target_view, + uri_list, + target_uri, + actions); + g_list_free_full (uri_list, g_free); + } + } + g_free (target_uri); + + drag_info_clear (drag_info); +} + +void +nautilus_drag_slot_proxy_init (GtkWidget *widget, + NautilusFile *target_file, + NautilusWindowSlot *target_slot) +{ + NautilusDragSlotProxyInfo *drag_info; + GtkDropTarget *target; + + g_assert (GTK_IS_WIDGET (widget)); + + drag_info = g_slice_new0 (NautilusDragSlotProxyInfo); + + g_object_set_data_full (G_OBJECT (widget), "drag-slot-proxy-data", drag_info, + drag_info_free); + + if (target_file != NULL) + { + drag_info->target_file = nautilus_file_ref (target_file); + } + + if (target_slot != NULL) + { + drag_info->target_slot = g_object_ref (target_slot); + } + + drag_info->widget = widget; + /* TODO: Implement GDK_ACTION_ASK */ + target = gtk_drop_target_new (G_TYPE_INVALID, GDK_ACTION_ALL); + + gtk_drop_target_set_preload (target, TRUE); + /* TODO: Implement GDK_TYPE_STRING */ + gtk_drop_target_set_gtypes (target, (GType[1]) { GDK_TYPE_FILE_LIST }, 1); + g_signal_connect (target, "enter", G_CALLBACK (slot_proxy_drag_motion), drag_info); + g_signal_connect (target, "motion", G_CALLBACK (slot_proxy_drag_motion), drag_info); + g_signal_connect (target, "drop", G_CALLBACK (slot_proxy_handle_drop), drag_info); + g_signal_connect (target, "leave", G_CALLBACK (slot_proxy_drag_leave), drag_info); + gtk_widget_add_controller (widget, GTK_EVENT_CONTROLLER (target)); +} diff --git a/src/nautilus-window-slot-dnd.h b/src/nautilus-window-slot-dnd.h new file mode 100644 index 0000000..bd77969 --- /dev/null +++ b/src/nautilus-window-slot-dnd.h @@ -0,0 +1,37 @@ +/* + * nautilus-window-slot-dnd.c - Handle DnD for widgets acting as + * NautilusWindowSlot proxies + * + * Copyright (C) 2000, 2001 Eazel, Inc. + * Copyright (C) 2010, Red Hat, Inc. + * + * The Gnome Library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Library General Public License as + * published by the Free Software Foundation; either version 2 of the + * License, or (at your option) any later version. + * + * The Gnome Library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Library General Public License for more details. + * + * You should have received a copy of the GNU Library General Public + * License along with the Gnome Library; see the file COPYING.LIB. If not, + * see . + * + * Authors: Pavel Cisler , + * Ettore Perazzoli + */ + +#pragma once + +#include +#include + +#include "nautilus-dnd.h" + +#include "nautilus-window-slot.h" + +void nautilus_drag_slot_proxy_init (GtkWidget *widget, + NautilusFile *target_file, + NautilusWindowSlot *target_slot); diff --git a/src/nautilus-window-slot.c b/src/nautilus-window-slot.c new file mode 100644 index 0000000..64dc5fc --- /dev/null +++ b/src/nautilus-window-slot.c @@ -0,0 +1,3417 @@ +/* + * nautilus-window-slot.c: Nautilus window slot + * + * Copyright (C) 2008 Free Software Foundation, Inc. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License as + * published by the Free Software Foundation; either version 2 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 warranty of + * MERCHANTABILITY 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 . + * + * Author: Christian Neumair + */ + +#include "config.h" + +#include "nautilus-window-slot.h" + +#include "nautilus-application.h" +#include "nautilus-bookmark.h" +#include "nautilus-bookmark-list.h" +#include "nautilus-files-view.h" +#include "nautilus-mime-actions.h" +#include "nautilus-places-view.h" +#include "nautilus-query-editor.h" +#include "nautilus-special-location-bar.h" +#include "nautilus-toolbar.h" +#include "nautilus-view.h" +#include "nautilus-window.h" +#include "nautilus-x-content-bar.h" + +#include + +#include "nautilus-file.h" +#include "nautilus-file-utilities.h" +#include "nautilus-global-preferences.h" +#include "nautilus-module.h" +#include "nautilus-monitor.h" +#include "nautilus-profile.h" +#include "nautilus-ui-utilities.h" +#include + +enum +{ + PROP_ACTIVE = 1, + PROP_WINDOW, + PROP_ICON_NAME, + PROP_TOOLBAR_MENU_SECTIONS, + PROP_EXTENSIONS_BACKGROUND_MENU, + PROP_TEMPLATES_MENU, + PROP_LOADING, + PROP_SEARCHING, + PROP_SELECTION, + PROP_LOCATION, + PROP_TOOLTIP, + PROP_ALLOW_STOP, + PROP_TITLE, + NUM_PROPERTIES +}; + +#define FILE_SHARING_SCHEMA_ID "org.gnome.desktop.file-sharing" + +struct _NautilusWindowSlot +{ + GtkBox parent_instance; + + NautilusWindow *window; + + gboolean active : 1; + guint loading : 1; + + /* slot contains + * 1) an vbox containing extra_location_widgets + * 2) the view + */ + GtkWidget *extra_location_widgets; + + /* Slot actions */ + GActionGroup *slot_action_group; + + /* Current location. */ + GFile *location; + gchar *title; + + /* Viewed file */ + NautilusView *content_view; + NautilusView *new_content_view; + NautilusFile *viewed_file; + gboolean viewed_file_seen; + gboolean viewed_file_in_trash; + + /* Information about bookmarks and history list */ + NautilusBookmark *current_location_bookmark; + NautilusBookmark *last_location_bookmark; + GList *back_list; + GList *forward_list; + + /* Query editor */ + NautilusQueryEditor *query_editor; + NautilusQuery *pending_search_query; + gulong qe_changed_id; + gulong qe_cancel_id; + gulong qe_activated_id; + gulong qe_focus_view_id; + + GtkLabel *search_info_label; + GtkRevealer *search_info_label_revealer; + + /* Load state */ + GCancellable *find_mount_cancellable; + /* It could be either the view is loading the files or the search didn't + * finish. Used for showing a spinner to provide feedback to the user. */ + gboolean allow_stop; + gboolean needs_reload; + + /* New location. */ + GFile *pending_location; + NautilusLocationChangeType location_change_type; + guint location_change_distance; + char *pending_scroll_to; + GList *pending_selection; + NautilusFile *pending_file_to_activate; + NautilusFile *determine_view_file; + GCancellable *mount_cancellable; + GError *mount_error; + gboolean tried_mount; + gint view_mode_before_search; + gint view_mode_before_places; + + /* Menus */ + GMenuModel *extensions_background_menu; + GMenuModel *templates_menu; + + /* View bindings */ + GBinding *searching_binding; + GBinding *selection_binding; + GBinding *extensions_background_menu_binding; + GBinding *templates_menu_binding; + gboolean searching; + GList *selection; +}; + +G_DEFINE_TYPE (NautilusWindowSlot, nautilus_window_slot, GTK_TYPE_BOX); + +static GParamSpec *properties[NUM_PROPERTIES] = { NULL, }; + +static void nautilus_window_slot_force_reload (NautilusWindowSlot *self); +static void change_view (NautilusWindowSlot *self); +static void hide_query_editor (NautilusWindowSlot *self); +static void nautilus_window_slot_sync_actions (NautilusWindowSlot *self); +static void nautilus_window_slot_connect_new_content_view (NautilusWindowSlot *self); +static void nautilus_window_slot_disconnect_content_view (NautilusWindowSlot *self); +static gboolean nautilus_window_slot_content_view_matches (NautilusWindowSlot *self, + guint id); +static NautilusView *nautilus_window_slot_get_view_for_location (NautilusWindowSlot *self, + GFile *location); +static void nautilus_window_slot_set_content_view (NautilusWindowSlot *self, + guint id); +static void nautilus_window_slot_set_loading (NautilusWindowSlot *self, + gboolean loading); +char *nautilus_window_slot_get_location_uri (NautilusWindowSlot *self); +static void nautilus_window_slot_set_search_visible (NautilusWindowSlot *self, + gboolean visible); +static gboolean nautilus_window_slot_get_search_visible (NautilusWindowSlot *self); +static void nautilus_window_slot_set_location (NautilusWindowSlot *self, + GFile *location); +static void update_search_information (NautilusWindowSlot *self); +static void real_set_extensions_background_menu (NautilusWindowSlot *self, + GMenuModel *menu); +static GMenuModel *real_get_extensions_background_menu (NautilusWindowSlot *self); +static void real_set_templates_menu (NautilusWindowSlot *self, + GMenuModel *menu); +static GMenuModel *real_get_templates_menu (NautilusWindowSlot *self); +static void nautilus_window_slot_setup_extra_location_widgets (NautilusWindowSlot *self); + +void +free_navigation_state (gpointer data) +{ + NautilusNavigationState *navigation_state = data; + + g_list_free_full (navigation_state->back_list, g_object_unref); + g_list_free_full (navigation_state->forward_list, g_object_unref); + nautilus_file_unref (navigation_state->file); + g_clear_object (&navigation_state->current_location_bookmark); + + g_free (navigation_state); +} + +void +nautilus_window_slot_restore_navigation_state (NautilusWindowSlot *self, + NautilusNavigationState *data) +{ + self->back_list = g_steal_pointer (&data->back_list); + + self->forward_list = g_steal_pointer (&data->forward_list); + + self->view_mode_before_search = data->view_before_search; + + g_set_object (&self->current_location_bookmark, data->current_location_bookmark); + + self->location_change_type = NAUTILUS_LOCATION_CHANGE_RELOAD; +} + +NautilusNavigationState * +nautilus_window_slot_get_navigation_state (NautilusWindowSlot *self) +{ + NautilusNavigationState *data; + GList *back_list; + GList *forward_list; + + if (self->location == NULL) + { + return NULL; + } + + back_list = g_list_copy_deep (self->back_list, + (GCopyFunc) g_object_ref, + NULL); + forward_list = g_list_copy_deep (self->forward_list, + (GCopyFunc) g_object_ref, + NULL); + + /* This data is used to restore a tab after it was closed. + * In order to do that we need to keep the history, what was + * the view mode before search and a reference to the file. + * A GFile isn't enough, as the NautilusFile also keeps a + * reference to the search directory */ + data = g_new0 (NautilusNavigationState, 1); + data->back_list = back_list; + data->forward_list = forward_list; + data->file = nautilus_file_get (self->location); + data->view_before_search = self->view_mode_before_search; + g_set_object (&data->current_location_bookmark, self->current_location_bookmark); + + return data; +} + +static NautilusView * +nautilus_window_slot_get_view_for_location (NautilusWindowSlot *self, + GFile *location) +{ + g_autoptr (NautilusFile) file = NULL; + NautilusView *view; + guint view_id; + + file = nautilus_file_get (location); + view = NULL; + view_id = NAUTILUS_VIEW_INVALID_ID; + + if (nautilus_file_is_other_locations (file)) + { + view = NAUTILUS_VIEW (nautilus_places_view_new ()); + + /* Save the current view, so we can go back after places view */ + if (NAUTILUS_IS_FILES_VIEW (self->content_view)) + { + self->view_mode_before_places = nautilus_view_get_view_id (self->content_view); + } + + return view; + } + + /* If we are in search, try to use by default list view. */ + if (nautilus_file_is_in_search (file)) + { + /* If it's already set, is because we already made the change to search mode, + * so the view mode of the current view will be the one search is using, + * which is not the one we are interested in */ + if (self->view_mode_before_search == NAUTILUS_VIEW_INVALID_ID && + NAUTILUS_IS_FILES_VIEW (self->content_view)) + { + self->view_mode_before_search = nautilus_view_get_view_id (self->content_view); + } + view_id = g_settings_get_enum (nautilus_preferences, NAUTILUS_PREFERENCES_SEARCH_VIEW); + } + else if (self->content_view != NULL) + { + /* If there is already a view, just use the view mode that it's currently using, or + * if we were on search before, use what we were using before entering + * search mode */ + if (self->view_mode_before_search != NAUTILUS_VIEW_INVALID_ID) + { + view_id = self->view_mode_before_search; + self->view_mode_before_search = NAUTILUS_VIEW_INVALID_ID; + } + else if (NAUTILUS_IS_PLACES_VIEW (self->content_view)) + { + view_id = self->view_mode_before_places; + self->view_mode_before_places = NAUTILUS_VIEW_INVALID_ID; + } + else + { + view_id = nautilus_view_get_view_id (self->content_view); + } + } + + /* If there is not previous view in this slot, use the default view mode + * from preferences */ + if (view_id == NAUTILUS_VIEW_INVALID_ID) + { + view_id = g_settings_get_enum (nautilus_preferences, NAUTILUS_PREFERENCES_DEFAULT_FOLDER_VIEWER); + } + if (view_id == NAUTILUS_VIEW_INVALID_ID) + { + g_warning ("Invalid value stored for 'default-folder-viewer' key for " + "the 'org.gnome.nautilus.preferences' schemas. Installed " + "schemas may be outdated. Falling back to 'list-view'."); + view_id = NAUTILUS_VIEW_LIST_ID; + } + + /* Try to reuse the current view */ + if (nautilus_window_slot_content_view_matches (self, view_id)) + { + view = self->content_view; + } + else + { + view = NAUTILUS_VIEW (nautilus_files_view_new (view_id, self)); + } + + return view; +} + +static gboolean +nautilus_window_slot_content_view_matches (NautilusWindowSlot *self, + guint id) +{ + if (self->content_view == NULL) + { + return FALSE; + } + + if (id != NAUTILUS_VIEW_INVALID_ID) + { + return nautilus_view_get_view_id (self->content_view) == id; + } + else + { + return FALSE; + } +} + +static void +update_search_visible (NautilusWindowSlot *self) +{ + NautilusQuery *query; + NautilusView *view; + + view = nautilus_window_slot_get_current_view (self); + /* If we changed location just to another search location, for example, + * when changing the query, just keep the search visible. + * Make sure the search is visible though, since we could be returning + * from a previous search location when using the history */ + if (nautilus_view_is_searching (view)) + { + nautilus_window_slot_set_search_visible (self, TRUE); + return; + } + + query = nautilus_query_editor_get_query (self->query_editor); + if (query) + { + /* If the view is not searching, but search is visible, and the + * query is empty, we don't hide it. Some users enable the search + * and then change locations, then they search. */ + if (!nautilus_query_is_empty (query)) + { + nautilus_window_slot_set_search_visible (self, FALSE); + } + } + + if (self->pending_search_query) + { + nautilus_window_slot_search (self, g_object_ref (self->pending_search_query)); + } +} + +static void +nautilus_window_slot_sync_actions (NautilusWindowSlot *self) +{ + NautilusView *view; + GAction *action; + GVariant *variant; + + if (!nautilus_window_slot_get_active (self)) + { + return; + } + + if (self->content_view == NULL || self->new_content_view != NULL) + { + return; + } + + /* Check if we need to close the search or show search after changing the location. + * Needs to be done after the change has been done, if not, a loop happens, + * because setting the search enabled or not actually opens a location */ + update_search_visible (self); + + /* Files view mode */ + view = nautilus_window_slot_get_current_view (self); + action = g_action_map_lookup_action (G_ACTION_MAP (self->slot_action_group), "files-view-mode"); + g_simple_action_set_enabled (G_SIMPLE_ACTION (action), NAUTILUS_IS_FILES_VIEW (view)); + if (g_action_get_enabled (action)) + { + variant = g_variant_new_uint32 (nautilus_view_get_view_id (view)); + g_action_change_state (action, variant); + } + action = g_action_map_lookup_action (G_ACTION_MAP (self->slot_action_group), "files-view-mode-toggle"); + g_simple_action_set_enabled (G_SIMPLE_ACTION (action), NAUTILUS_IS_FILES_VIEW (view)); +} + +static void +query_editor_cancel_callback (NautilusQueryEditor *editor, + NautilusWindowSlot *self) +{ + nautilus_window_slot_set_search_visible (self, FALSE); +} + +static void +query_editor_activated_callback (NautilusQueryEditor *editor, + NautilusWindowSlot *self) +{ + if (self->content_view != NULL) + { + if (NAUTILUS_IS_FILES_VIEW (self->content_view)) + { + nautilus_files_view_activate_selection (NAUTILUS_FILES_VIEW (self->content_view)); + } + } +} + +static void +query_editor_focus_view_callback (NautilusQueryEditor *editor, + NautilusWindowSlot *self) +{ + if (self->content_view != NULL) + { + gtk_widget_grab_focus (GTK_WIDGET (self->content_view)); + } +} + +static void +query_editor_changed_callback (NautilusQueryEditor *editor, + NautilusQuery *query, + gboolean reload, + NautilusWindowSlot *self) +{ + NautilusView *view; + + view = nautilus_window_slot_get_current_view (self); + + nautilus_view_set_search_query (view, query); + nautilus_window_slot_open_location_full (self, nautilus_view_get_location (view), 0, NULL); +} + +static void +hide_query_editor (NautilusWindowSlot *self) +{ + NautilusView *view; + + view = nautilus_window_slot_get_current_view (self); + + g_clear_signal_handler (&self->qe_changed_id, self->query_editor); + g_clear_signal_handler (&self->qe_cancel_id, self->query_editor); + g_clear_signal_handler (&self->qe_activated_id, self->query_editor); + g_clear_signal_handler (&self->qe_focus_view_id, self->query_editor); + + nautilus_query_editor_set_query (self->query_editor, NULL); + + if (nautilus_view_is_searching (view)) + { + g_autolist (NautilusFile) selection = NULL; + + selection = nautilus_view_get_selection (view); + + nautilus_view_set_search_query (view, NULL); + nautilus_window_slot_open_location_full (self, + nautilus_view_get_location (view), + 0, + selection); + } + + if (nautilus_window_slot_get_active (self)) + { + gtk_widget_grab_focus (GTK_WIDGET (self->window)); + } +} + +static GFile * +nautilus_window_slot_get_current_location (NautilusWindowSlot *self) +{ + if (self->pending_location != NULL) + { + return self->pending_location; + } + + if (self->location != NULL) + { + return self->location; + } + + return NULL; +} + +static void +show_query_editor (NautilusWindowSlot *self) +{ + NautilusView *view; + + view = nautilus_window_slot_get_current_view (self); + if (view == NULL) + { + return; + } + + if (nautilus_view_is_searching (view)) + { + NautilusQuery *query; + + query = nautilus_view_get_search_query (view); + + if (query != NULL) + { + nautilus_query_editor_set_query (self->query_editor, query); + } + } + + gtk_widget_grab_focus (GTK_WIDGET (self->query_editor)); + + if (self->qe_changed_id == 0) + { + self->qe_changed_id = + g_signal_connect (self->query_editor, "changed", + G_CALLBACK (query_editor_changed_callback), self); + } + if (self->qe_cancel_id == 0) + { + self->qe_cancel_id = + g_signal_connect (self->query_editor, "cancel", + G_CALLBACK (query_editor_cancel_callback), self); + } + if (self->qe_activated_id == 0) + { + self->qe_activated_id = + g_signal_connect (self->query_editor, "activated", + G_CALLBACK (query_editor_activated_callback), self); + } + if (self->qe_focus_view_id == 0) + { + self->qe_focus_view_id = + g_signal_connect (self->query_editor, "focus-view", + G_CALLBACK (query_editor_focus_view_callback), self); + } +} + +static void +nautilus_window_slot_set_search_visible (NautilusWindowSlot *self, + gboolean visible) +{ + GAction *action; + + action = g_action_map_lookup_action (G_ACTION_MAP (self->slot_action_group), + "search-visible"); + g_action_change_state (action, g_variant_new_boolean (visible)); +} + +static gboolean +nautilus_window_slot_get_search_visible (NautilusWindowSlot *self) +{ + GAction *action; + GVariant *state; + gboolean searching; + + action = g_action_map_lookup_action (G_ACTION_MAP (self->slot_action_group), + "search-visible"); + state = g_action_get_state (action); + searching = g_variant_get_boolean (state); + + g_variant_unref (state); + + return searching; +} + +void +nautilus_window_slot_search (NautilusWindowSlot *self, + NautilusQuery *query) +{ + NautilusView *view; + + g_clear_object (&self->pending_search_query); + + view = nautilus_window_slot_get_current_view (self); + /* We could call this when the location is still being checked in the + * window slot. For that, save the search we want to do for once we have + * a view set up */ + if (view) + { + nautilus_window_slot_set_search_visible (self, TRUE); + nautilus_query_editor_set_query (self->query_editor, query); + nautilus_view_set_search_query (view, query); + } + else + { + self->pending_search_query = g_object_ref (query); + } +} + +gboolean +nautilus_window_slot_handle_event (NautilusWindowSlot *self, + GtkEventControllerKey *controller, + guint keyval, + GdkModifierType state) +{ + gboolean retval; + GAction *action; + + retval = FALSE; + action = g_action_map_lookup_action (G_ACTION_MAP (self->slot_action_group), + "search-visible"); + + if (keyval == GDK_KEY_Escape || + keyval == GDK_KEY_BackSpace) + { + g_autoptr (GVariant) action_state = NULL; + + action_state = g_action_get_state (action); + + if (!g_variant_get_boolean (action_state)) + { + return GDK_EVENT_PROPAGATE; + } + else if (keyval == GDK_KEY_Escape) + { + nautilus_window_slot_set_search_visible (self, FALSE); + return GDK_EVENT_STOP; + } + } + + /* If the action is not enabled, don't try to handle search */ + if (g_action_get_enabled (action)) + { + retval = nautilus_query_editor_handle_event (self->query_editor, + controller, + keyval, + state); + } + + if (retval) + { + nautilus_window_slot_set_search_visible (self, TRUE); + } + + return retval; +} + +static void +nautilus_window_slot_remove_extra_location_widgets (NautilusWindowSlot *self) +{ + GtkWidget *widget; + while ((widget = gtk_widget_get_first_child (self->extra_location_widgets)) != NULL) + { + gtk_box_remove (GTK_BOX (self->extra_location_widgets), widget); + } +} + +static void +nautilus_window_slot_add_extra_location_widget (NautilusWindowSlot *self, + GtkWidget *widget) +{ + gtk_box_append (GTK_BOX (self->extra_location_widgets), widget); + gtk_widget_show (self->extra_location_widgets); +} + +static void +nautilus_window_slot_set_searching (NautilusWindowSlot *self, + gboolean searching) +{ + self->searching = searching; + g_object_notify_by_pspec (G_OBJECT (self), properties[PROP_SEARCHING]); +} + +static void +nautilus_window_slot_set_selection (NautilusWindowSlot *self, + GList *selection) +{ + self->selection = selection; + g_object_notify_by_pspec (G_OBJECT (self), properties[PROP_SELECTION]); +} + +static void +real_set_extensions_background_menu (NautilusWindowSlot *self, + GMenuModel *menu) +{ + g_set_object (&self->extensions_background_menu, menu); +} + +static void +real_set_templates_menu (NautilusWindowSlot *self, + GMenuModel *menu) +{ + g_set_object (&self->templates_menu, menu); +} + +static void +nautilus_window_slot_set_property (GObject *object, + guint property_id, + const GValue *value, + GParamSpec *pspec) +{ + NautilusWindowSlot *self = NAUTILUS_WINDOW_SLOT (object); + + switch (property_id) + { + case PROP_ACTIVE: + { + nautilus_window_slot_set_active (self, g_value_get_boolean (value)); + } + break; + + case PROP_WINDOW: + { + nautilus_window_slot_set_window (self, g_value_get_object (value)); + } + break; + + case PROP_LOCATION: + { + nautilus_window_slot_set_location (self, g_value_get_object (value)); + } + break; + + case PROP_SEARCHING: + { + nautilus_window_slot_set_searching (self, g_value_get_boolean (value)); + } + break; + + case PROP_EXTENSIONS_BACKGROUND_MENU: + { + real_set_extensions_background_menu (self, g_value_get_object (value)); + } + break; + + case PROP_TEMPLATES_MENU: + { + real_set_templates_menu (self, g_value_get_object (value)); + } + break; + + case PROP_SELECTION: + { + nautilus_window_slot_set_selection (self, g_value_get_pointer (value)); + } + break; + + default: + { + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec); + } + break; + } +} + +static GMenuModel * +real_get_extensions_background_menu (NautilusWindowSlot *self) +{ + return self->extensions_background_menu; +} + +GMenuModel * +nautilus_window_slot_get_extensions_background_menu (NautilusWindowSlot *self) +{ + GMenuModel *menu = NULL; + + g_object_get (self, "extensions-background-menu", &menu, NULL); + + return menu; +} + +static GMenuModel * +real_get_templates_menu (NautilusWindowSlot *self) +{ + return self->templates_menu; +} + +GMenuModel * +nautilus_window_slot_get_templates_menu (NautilusWindowSlot *self) +{ + GMenuModel *menu = NULL; + + g_object_get (self, "templates-menu", &menu, NULL); + + return menu; +} + +static void +nautilus_window_slot_get_property (GObject *object, + guint property_id, + GValue *value, + GParamSpec *pspec) +{ + NautilusWindowSlot *self = NAUTILUS_WINDOW_SLOT (object); + switch (property_id) + { + case PROP_ACTIVE: + { + g_value_set_boolean (value, nautilus_window_slot_get_active (self)); + } + break; + + case PROP_WINDOW: + { + g_value_set_object (value, self->window); + } + break; + + case PROP_ICON_NAME: + { + g_value_set_static_string (value, nautilus_window_slot_get_icon_name (self)); + } + break; + + case PROP_TOOLBAR_MENU_SECTIONS: + { + g_value_set_object (value, nautilus_window_slot_get_toolbar_menu_sections (self)); + } + break; + + case PROP_EXTENSIONS_BACKGROUND_MENU: + { + g_value_set_object (value, real_get_extensions_background_menu (self)); + } + break; + + case PROP_TEMPLATES_MENU: + { + g_value_set_object (value, real_get_templates_menu (self)); + } + break; + + case PROP_LOADING: + { + g_value_set_boolean (value, nautilus_window_slot_get_loading (self)); + } + break; + + case PROP_LOCATION: + { + g_value_set_object (value, nautilus_window_slot_get_current_location (self)); + } + break; + + case PROP_SEARCHING: + { + g_value_set_boolean (value, nautilus_window_slot_get_searching (self)); + } + break; + + case PROP_TOOLTIP: + { + g_value_set_static_string (value, nautilus_window_slot_get_tooltip (self)); + } + break; + + case PROP_ALLOW_STOP: + { + g_value_set_boolean (value, nautilus_window_slot_get_allow_stop (self)); + } + break; + + case PROP_TITLE: + { + g_value_set_string (value, nautilus_window_slot_get_title (self)); + } + break; + + default: + { + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec); + } + break; + } +} + +gboolean +nautilus_window_slot_get_searching (NautilusWindowSlot *self) +{ + return self->searching; +} + +GList * +nautilus_window_slot_get_selection (NautilusWindowSlot *self) +{ + return self->selection; +} + +static void +nautilus_window_slot_constructed (GObject *object) +{ + NautilusWindowSlot *self = NAUTILUS_WINDOW_SLOT (object); + GtkWidget *extras_vbox; + GtkStyleContext *style_context; + + G_OBJECT_CLASS (nautilus_window_slot_parent_class)->constructed (object); + + gtk_orientable_set_orientation (GTK_ORIENTABLE (self), + GTK_ORIENTATION_VERTICAL); + gtk_widget_show (GTK_WIDGET (self)); + + extras_vbox = gtk_box_new (GTK_ORIENTATION_VERTICAL, 0); + self->extra_location_widgets = extras_vbox; + gtk_box_append (GTK_BOX (self), extras_vbox); + gtk_widget_show (extras_vbox); + + self->query_editor = NAUTILUS_QUERY_EDITOR (nautilus_query_editor_new ()); + /* We want to keep alive the query editor betwen additions and removals on the + * UI, specifically when the toolbar adds or removes it */ + g_object_ref_sink (self->query_editor); + gtk_widget_show (GTK_WIDGET (self->query_editor)); + + self->search_info_label = GTK_LABEL (gtk_label_new (NULL)); + self->search_info_label_revealer = GTK_REVEALER (gtk_revealer_new ()); + + gtk_revealer_set_child (GTK_REVEALER (self->search_info_label_revealer), + GTK_WIDGET (self->search_info_label)); + gtk_box_append (GTK_BOX (self), + GTK_WIDGET (self->search_info_label_revealer)); + + gtk_widget_show (GTK_WIDGET (self->search_info_label)); + gtk_widget_show (GTK_WIDGET (self->search_info_label_revealer)); + + style_context = gtk_widget_get_style_context (GTK_WIDGET (self->search_info_label)); + gtk_style_context_add_class (style_context, "search-information"); + + g_object_bind_property (self, "location", + self->query_editor, "location", + G_BINDING_DEFAULT); + + self->title = g_strdup (_("Loading…")); + g_object_notify_by_pspec (G_OBJECT (self), properties[PROP_TITLE]); +} + +static void +action_search_visible (GSimpleAction *action, + GVariant *state, + gpointer user_data) +{ + NautilusWindowSlot *self; + GVariant *current_state; + + self = NAUTILUS_WINDOW_SLOT (user_data); + current_state = g_action_get_state (G_ACTION (action)); + if (g_variant_get_boolean (current_state) != g_variant_get_boolean (state)) + { + g_simple_action_set_state (action, state); + + if (g_variant_get_boolean (state)) + { + show_query_editor (self); + nautilus_window_slot_set_searching (self, TRUE); + } + else + { + hide_query_editor (self); + nautilus_window_slot_set_searching (self, FALSE); + } + + update_search_information (self); + } + + g_variant_unref (current_state); +} + +static void +change_files_view_mode (NautilusWindowSlot *self, + guint view_id) +{ + const gchar *preferences_key; + + if (!nautilus_window_slot_content_view_matches (self, view_id)) + { + nautilus_window_slot_set_content_view (self, view_id); + } + preferences_key = nautilus_view_is_searching (nautilus_window_slot_get_current_view (self)) ? + NAUTILUS_PREFERENCES_SEARCH_VIEW : + NAUTILUS_PREFERENCES_DEFAULT_FOLDER_VIEWER; + + g_settings_set_enum (nautilus_preferences, preferences_key, view_id); +} + +static void +action_files_view_mode_toggle (GSimpleAction *action, + GVariant *value, + gpointer user_data) +{ + NautilusWindowSlot *self; + guint current_view_id; + + self = NAUTILUS_WINDOW_SLOT (user_data); + if (!NAUTILUS_IS_FILES_VIEW (self->content_view)) + { + return; + } + + current_view_id = nautilus_view_get_view_id (self->content_view); + if (current_view_id == NAUTILUS_VIEW_LIST_ID) + { + change_files_view_mode (self, NAUTILUS_VIEW_GRID_ID); + } + else + { + change_files_view_mode (self, NAUTILUS_VIEW_LIST_ID); + } +} + +static void +action_files_view_mode (GSimpleAction *action, + GVariant *value, + gpointer user_data) +{ + NautilusWindowSlot *self; + guint view_id; + + view_id = g_variant_get_uint32 (value); + self = NAUTILUS_WINDOW_SLOT (user_data); + + if (!NAUTILUS_IS_FILES_VIEW (nautilus_window_slot_get_current_view (self))) + { + return; + } + + change_files_view_mode (self, view_id); + + g_simple_action_set_state (action, value); +} + +const GActionEntry slot_entries[] = +{ + { "files-view-mode", NULL, "u", "uint32 " G_STRINGIFY (NAUTILUS_VIEW_INVALID_ID), action_files_view_mode }, + { "files-view-mode-toggle", action_files_view_mode_toggle }, + { "search-visible", NULL, NULL, "false", action_search_visible }, +}; + +static void +update_search_information (NautilusWindowSlot *self) +{ + GFile *location; + + if (!nautilus_window_slot_get_searching (self)) + { + gtk_revealer_set_reveal_child (self->search_info_label_revealer, FALSE); + + return; + } + + location = nautilus_window_slot_get_current_location (self); + + if (location) + { + g_autoptr (NautilusFile) file = NULL; + gchar *label; + g_autofree gchar *uri = NULL; + + file = nautilus_file_get (location); + label = NULL; + uri = g_file_get_uri (location); + + if (nautilus_file_is_other_locations (file)) + { + label = _("Searching locations only"); + } + else if (g_str_has_prefix (uri, "network://")) + { + label = _("Searching network locations only"); + } + else if (nautilus_file_is_remote (file) && + location_settings_search_get_recursive_for_location (location) == NAUTILUS_QUERY_RECURSIVE_NEVER) + { + label = _("Remote location — only searching the current folder"); + } + else if (location_settings_search_get_recursive_for_location (location) == NAUTILUS_QUERY_RECURSIVE_NEVER) + { + label = _("Only searching the current folder"); + } + + gtk_label_set_label (self->search_info_label, label); + gtk_revealer_set_reveal_child (self->search_info_label_revealer, + label != NULL); + } +} + +static void +recursive_search_preferences_changed (GSettings *settings, + gchar *key, + gpointer callback_data) +{ + NautilusWindowSlot *self; + + self = callback_data; + + update_search_information (self); +} + +static void +remove_old_trash_files_preferences_changed (GSettings *settings, + gchar *key, + gpointer callback_data) +{ + NautilusWindowSlot *self; + GFile *location; + g_autoptr (NautilusDirectory) directory = NULL; + + g_assert (NAUTILUS_IS_WINDOW_SLOT (callback_data)); + + self = NAUTILUS_WINDOW_SLOT (callback_data); + location = nautilus_window_slot_get_current_location (self); + directory = nautilus_directory_get (location); + + if (nautilus_directory_is_in_trash (directory)) + { + if (g_settings_get_boolean (gnome_privacy_preferences, "remove-old-trash-files")) + { + nautilus_window_slot_setup_extra_location_widgets (self); + } + else + { + nautilus_window_slot_remove_extra_location_widgets (self); + } + } +} + +static void +nautilus_window_slot_init (NautilusWindowSlot *self) +{ + GApplication *app; + const gchar *search_visible_accels[] = + { + "f", + "Search", + NULL + }; + + app = g_application_get_default (); + + g_signal_connect_object (nautilus_preferences, + "changed::recursive-search", + G_CALLBACK (recursive_search_preferences_changed), + self, 0); + + g_signal_connect_object (gnome_privacy_preferences, + "changed::remove-old-trash-files", + G_CALLBACK (remove_old_trash_files_preferences_changed), + self, 0); + + self->slot_action_group = G_ACTION_GROUP (g_simple_action_group_new ()); + g_action_map_add_action_entries (G_ACTION_MAP (self->slot_action_group), + slot_entries, + G_N_ELEMENTS (slot_entries), + self); + gtk_widget_insert_action_group (GTK_WIDGET (self), + "slot", + G_ACTION_GROUP (self->slot_action_group)); + + nautilus_application_set_accelerator (app, + "slot.files-view-mode(uint32 " G_STRINGIFY (NAUTILUS_VIEW_LIST_ID) ")", + "1"); + nautilus_application_set_accelerator (app, + "slot.files-view-mode(uint32 " G_STRINGIFY (NAUTILUS_VIEW_GRID_ID) ")", + "2"); + nautilus_application_set_accelerators (app, "slot.search-visible", search_visible_accels); + + self->view_mode_before_search = NAUTILUS_VIEW_INVALID_ID; + self->view_mode_before_places = NAUTILUS_VIEW_INVALID_ID; +} + +#define DEBUG_FLAG NAUTILUS_DEBUG_WINDOW +#include "nautilus-debug.h" + +static void begin_location_change (NautilusWindowSlot *slot, + GFile *location, + GFile *previous_location, + GList *new_selection, + NautilusLocationChangeType type, + guint distance, + const char *scroll_pos); +static void free_location_change (NautilusWindowSlot *self); +static void end_location_change (NautilusWindowSlot *self); +static void got_file_info_for_view_selection_callback (NautilusFile *file, + gpointer callback_data); +static gboolean setup_view (NautilusWindowSlot *self, + NautilusView *view); +static void load_new_location (NautilusWindowSlot *slot, + GFile *location, + GList *selection, + NautilusFile *file_to_activate, + gboolean tell_current_content_view, + gboolean tell_new_content_view); + +void +nautilus_window_slot_open_location_full (NautilusWindowSlot *self, + GFile *location, + NautilusOpenFlags flags, + GList *new_selection) +{ + GFile *old_location; + g_autolist (NautilusFile) old_selection = NULL; + + old_selection = NULL; + old_location = nautilus_window_slot_get_location (self); + + if (self->content_view) + { + old_selection = nautilus_view_get_selection (self->content_view); + } + if (old_location && g_file_equal (old_location, location) && + nautilus_file_selection_equal (old_selection, new_selection)) + { + goto done; + } + + begin_location_change (self, location, old_location, new_selection, + NAUTILUS_LOCATION_CHANGE_STANDARD, 0, NULL); + +done: + nautilus_profile_end (NULL); +} + +static GList * +check_select_old_location_containing_folder (GList *new_selection, + GFile *location, + GFile *previous_location) +{ + GFile *from_folder, *parent; + + /* If there is no new selection and the new location is + * a (grand)parent of the old location then we automatically + * select the folder the previous location was in */ + if (new_selection == NULL && previous_location != NULL && + g_file_has_prefix (previous_location, location)) + { + from_folder = g_object_ref (previous_location); + parent = g_file_get_parent (from_folder); + while (parent != NULL && !g_file_equal (parent, location)) + { + g_object_unref (from_folder); + from_folder = parent; + parent = g_file_get_parent (from_folder); + } + + if (parent != NULL) + { + new_selection = g_list_prepend (NULL, nautilus_file_get (from_folder)); + g_object_unref (parent); + } + + g_object_unref (from_folder); + } + + return new_selection; +} + +static void +check_force_reload (GFile *location, + NautilusLocationChangeType type) +{ + NautilusDirectory *directory; + NautilusFile *file; + gboolean force_reload; + + /* The code to force a reload is here because if we do it + * after determining an initial view (in the components), then + * we end up fetching things twice. + */ + directory = nautilus_directory_get (location); + file = nautilus_file_get (location); + + if (type == NAUTILUS_LOCATION_CHANGE_RELOAD) + { + force_reload = TRUE; + } + else + { + force_reload = !g_file_is_native (location); + } + + /* We need to invalidate file attributes as well due to how mounting works + * in the window slot and to avoid other caching issues. + * Read handle_mount_if_needed for one example */ + if (force_reload) + { + nautilus_file_invalidate_all_attributes (file); + nautilus_directory_force_reload (directory); + } + + nautilus_directory_unref (directory); + nautilus_file_unref (file); +} + +static void +save_scroll_position_for_history (NautilusWindowSlot *self) +{ + /* Set current_bookmark scroll pos */ + if (self->current_location_bookmark != NULL && + self->content_view != NULL && + NAUTILUS_IS_FILES_VIEW (self->content_view)) + { + char *current_pos; + + current_pos = nautilus_files_view_get_first_visible_file (NAUTILUS_FILES_VIEW (self->content_view)); + nautilus_bookmark_set_scroll_pos (self->current_location_bookmark, current_pos); + g_free (current_pos); + } +} + +/* + * begin_location_change + * + * Change a window slot's location. + * @window: The NautilusWindow whose location should be changed. + * @location: A url specifying the location to load + * @previous_location: The url that was previously shown in the window that initialized the change, if any + * @new_selection: The initial selection to present after loading the location + * @type: Which type of location change is this? Standard, back, forward, or reload? + * @distance: If type is back or forward, the index into the back or forward chain. If + * type is standard or reload, this is ignored, and must be 0. + * @scroll_pos: The file to scroll to when the location is loaded. + * + * This is the core function for changing the location of a window. Every change to the + * location begins here. + */ +static void +begin_location_change (NautilusWindowSlot *self, + GFile *location, + GFile *previous_location, + GList *new_selection, + NautilusLocationChangeType type, + guint distance, + const char *scroll_pos) +{ + g_assert (self != NULL); + g_assert (location != NULL); + g_assert (type == NAUTILUS_LOCATION_CHANGE_BACK + || type == NAUTILUS_LOCATION_CHANGE_FORWARD + || distance == 0); + + nautilus_profile_start (NULL); + + /* Avoid to update status from the current view in our async calls */ + nautilus_window_slot_disconnect_content_view (self); + /* We are going to change the location, so make sure we stop any loading + * or searching of the previous view, so we avoid to be slow */ + nautilus_window_slot_stop_loading (self); + + nautilus_window_slot_set_allow_stop (self, TRUE); + + new_selection = check_select_old_location_containing_folder (new_selection, location, previous_location); + + g_assert (self->pending_location == NULL); + + self->pending_location = g_object_ref (location); + self->location_change_type = type; + self->location_change_distance = distance; + self->tried_mount = FALSE; + self->pending_selection = nautilus_file_list_copy (new_selection); + + self->pending_scroll_to = g_strdup (scroll_pos); + + check_force_reload (location, type); + + save_scroll_position_for_history (self); + + /* Get the info needed to make decisions about how to open the new location */ + self->determine_view_file = nautilus_file_get (location); + g_assert (self->determine_view_file != NULL); + + nautilus_file_call_when_ready (self->determine_view_file, + NAUTILUS_FILE_ATTRIBUTE_INFO | + NAUTILUS_FILE_ATTRIBUTE_MOUNT, + got_file_info_for_view_selection_callback, + self); + + nautilus_profile_end (NULL); +} + +static void +nautilus_window_slot_set_location (NautilusWindowSlot *self, + GFile *location) +{ + GFile *old_location; + + if (self->location && + g_file_equal (location, self->location)) + { + /* The location name could be updated even if the location + * wasn't changed. This is the case for a search. + */ + nautilus_window_slot_update_title (self); + return; + } + + old_location = self->location; + self->location = g_object_ref (location); + + if (nautilus_window_slot_get_active (self)) + { + nautilus_window_sync_location_widgets (self->window); + } + + nautilus_window_slot_update_title (self); + + if (old_location) + { + g_object_unref (old_location); + } + + g_object_notify_by_pspec (G_OBJECT (self), properties[PROP_LOCATION]); +} + +static void +viewed_file_changed_callback (NautilusFile *file, + NautilusWindowSlot *self) +{ + GFile *new_location; + gboolean is_in_trash, was_in_trash; + + g_assert (NAUTILUS_IS_FILE (file)); + g_assert (NAUTILUS_IS_WINDOW_SLOT (self)); + + g_assert (file == self->viewed_file); + + if (!nautilus_file_is_not_yet_confirmed (file)) + { + self->viewed_file_seen = TRUE; + } + + was_in_trash = self->viewed_file_in_trash; + + self->viewed_file_in_trash = is_in_trash = nautilus_file_is_in_trash (file); + + if (nautilus_file_is_gone (file) || (is_in_trash && !was_in_trash)) + { + if (self->viewed_file_seen) + { + GFile *go_to_file; + GFile *parent; + GFile *location; + GMount *mount; + + parent = NULL; + location = nautilus_file_get_location (file); + + if (g_file_is_native (location)) + { + mount = nautilus_get_mounted_mount_for_root (location); + + if (mount == NULL) + { + parent = g_file_get_parent (location); + } + + g_clear_object (&mount); + } + + if (parent != NULL) + { + /* auto-show existing parent */ + go_to_file = nautilus_find_existing_uri_in_hierarchy (parent); + } + else + { + go_to_file = g_file_new_for_path (g_get_home_dir ()); + } + + nautilus_window_slot_open_location_full (self, go_to_file, 0, NULL); + + g_clear_object (&parent); + g_object_unref (go_to_file); + g_object_unref (location); + } + } + else + { + new_location = nautilus_file_get_location (file); + nautilus_window_slot_set_location (self, new_location); + g_object_unref (new_location); + } +} + +static void +nautilus_window_slot_go_home (NautilusWindowSlot *self, + NautilusOpenFlags flags) +{ + GFile *home; + + g_return_if_fail (NAUTILUS_IS_WINDOW_SLOT (self)); + + home = g_file_new_for_path (g_get_home_dir ()); + nautilus_window_slot_open_location_full (self, home, flags, NULL); + g_object_unref (home); +} + +static void +nautilus_window_slot_set_viewed_file (NautilusWindowSlot *self, + NautilusFile *file) +{ + NautilusFileAttributes attributes; + + if (self->viewed_file == file) + { + return; + } + + nautilus_file_ref (file); + + if (self->viewed_file != NULL) + { + g_signal_handlers_disconnect_by_func (self->viewed_file, + G_CALLBACK (viewed_file_changed_callback), + self); + nautilus_file_monitor_remove (self->viewed_file, + self); + } + + if (file != NULL) + { + attributes = NAUTILUS_FILE_ATTRIBUTE_INFO; + nautilus_file_monitor_add (file, self, attributes); + + g_signal_connect_object (file, "changed", + G_CALLBACK (viewed_file_changed_callback), self, 0); + } + + nautilus_file_unref (self->viewed_file); + self->viewed_file = file; +} + +typedef struct +{ + GCancellable *cancellable; + NautilusWindowSlot *slot; +} MountNotMountedData; + +static void +mount_not_mounted_callback (GObject *source_object, + GAsyncResult *res, + gpointer user_data) +{ + MountNotMountedData *data; + NautilusWindowSlot *self; + GError *error; + GCancellable *cancellable; + + data = user_data; + self = data->slot; + cancellable = data->cancellable; + g_free (data); + + if (g_cancellable_is_cancelled (cancellable)) + { + /* Cancelled, don't call back */ + g_object_unref (cancellable); + return; + } + + self->mount_cancellable = NULL; + + self->determine_view_file = nautilus_file_get (self->pending_location); + + error = NULL; + if (!g_file_mount_enclosing_volume_finish (G_FILE (source_object), res, &error)) + { + self->mount_error = error; + got_file_info_for_view_selection_callback (self->determine_view_file, self); + self->mount_error = NULL; + g_error_free (error); + } + else + { + nautilus_file_invalidate_all_attributes (self->determine_view_file); + nautilus_file_call_when_ready (self->determine_view_file, + NAUTILUS_FILE_ATTRIBUTE_INFO | + NAUTILUS_FILE_ATTRIBUTE_MOUNT, + got_file_info_for_view_selection_callback, + self); + } + + g_object_unref (cancellable); +} + +static void +nautilus_window_slot_display_view_selection_failure (NautilusWindow *window, + NautilusFile *file, + GFile *location, + GError *error) +{ + char *error_message; + char *detail_message; + char *scheme_string; + char *file_path; + + /* Some sort of failure occurred. How 'bout we tell the user? */ + + error_message = g_strdup (_("Oops! Something went wrong.")); + detail_message = NULL; + if (error == NULL) + { + if (nautilus_file_is_directory (file)) + { + detail_message = g_strdup (_("Unable to display the contents of this folder.")); + } + else + { + detail_message = g_strdup (_("This location doesn’t appear to be a folder.")); + } + } + else if (error->domain == G_IO_ERROR) + { + switch (error->code) + { + case G_IO_ERROR_NOT_FOUND: + { + file_path = g_file_get_path (location); + if (file_path != NULL) + { + detail_message = g_strdup_printf (_("Unable to find “%s”. Please check the spelling and try again."), + file_path); + } + else + { + detail_message = g_strdup (_("Unable to find the requested file. Please check the spelling and try again.")); + } + g_free (file_path); + } + break; + + case G_IO_ERROR_NOT_SUPPORTED: + { + scheme_string = g_file_get_uri_scheme (location); + if (scheme_string != NULL) + { + detail_message = g_strdup_printf (_("“%s” locations are not supported."), + scheme_string); + } + else + { + detail_message = g_strdup (_("Unable to handle this kind of location.")); + } + g_free (scheme_string); + } + break; + + case G_IO_ERROR_NOT_MOUNTED: + { + detail_message = g_strdup (_("Unable to access the requested location.")); + } + break; + + case G_IO_ERROR_PERMISSION_DENIED: + { + detail_message = g_strdup (_("Don’t have permission to access the requested location.")); + } + break; + + case G_IO_ERROR_HOST_NOT_FOUND: + { + /* This case can be hit for user-typed strings like "foo" due to + * the code that guesses web addresses when there's no initial "/". + * But this case is also hit for legitimate web addresses when + * the proxy is set up wrong. + */ + detail_message = g_strdup (_("Unable to find the requested location. Please check the spelling or the network settings.")); + } + break; + + case G_IO_ERROR_CONNECTION_REFUSED: + { + /* This case can be hit when server application is not installed + * or is inactive in the system user is trying to connect to. + */ + detail_message = g_strdup (_("The server has refused the connection. Typically this means that the firewall is blocking access or that the remote service is not running.")); + } + break; + + case G_IO_ERROR_CANCELLED: + case G_IO_ERROR_FAILED_HANDLED: + { + goto done; + } + + default: + { + } + break; + } + } + + if (detail_message == NULL) + { + detail_message = g_strdup_printf (_("Unhandled error message: %s"), error->message); + } + + show_dialog (error_message, detail_message, GTK_WINDOW (window), GTK_MESSAGE_ERROR); + +done: + g_free (error_message); + g_free (detail_message); +} + +/* FIXME: This works in the folowwing way. begin_location_change tries to get the + * information of the file directly. + * If the nautilus file finds that there is an error trying to get its + * information and the error match that the file is not mounted, it sets an + * internal attribute with the error then we try to mount it here. + * + * However, files are cached, and if the file doesn't get finalized in a location + * change, because needs to be in the navigation history or is a bookmark, and the + * file is not the root of the mount point, which is tracked by a volume monitor, + * and it gets unmounted aftwerwards, the file doesn't realize it's unmounted, and + * therefore this trick to open an unmounted file will fail the next time the user + * tries to open. + * For that, we need to always invalidate the file attributes when a location is + * changed, which is done in check_force_reload. + * A better way would be to make sure any children of the mounted root gets + * akwnoledge by it either by adding a reference to its parent volume monitor + * or with another solution. */ +static gboolean +handle_mount_if_needed (NautilusWindowSlot *self, + NautilusFile *file) +{ + NautilusWindow *window; + GMountOperation *mount_op; + MountNotMountedData *data; + GFile *location; + GError *error = NULL; + gboolean needs_mount_handling = FALSE; + + window = nautilus_window_slot_get_window (self); + if (self->mount_error) + { + error = g_error_copy (self->mount_error); + } + else if (nautilus_file_get_file_info_error (file) != NULL) + { + error = g_error_copy (nautilus_file_get_file_info_error (file)); + } + + if (error && error->domain == G_IO_ERROR && error->code == G_IO_ERROR_NOT_MOUNTED && + !self->tried_mount) + { + self->tried_mount = TRUE; + + mount_op = gtk_mount_operation_new (GTK_WINDOW (window)); + g_mount_operation_set_password_save (mount_op, G_PASSWORD_SAVE_FOR_SESSION); + location = nautilus_file_get_location (file); + data = g_new0 (MountNotMountedData, 1); + data->cancellable = g_cancellable_new (); + data->slot = self; + self->mount_cancellable = data->cancellable; + g_file_mount_enclosing_volume (location, 0, mount_op, self->mount_cancellable, + mount_not_mounted_callback, data); + g_object_unref (location); + g_object_unref (mount_op); + + needs_mount_handling = TRUE; + } + + g_clear_error (&error); + + return needs_mount_handling; +} + +static gboolean +handle_regular_file_if_needed (NautilusWindowSlot *self, + NautilusFile *file) +{ + NautilusFile *parent_file; + gboolean needs_regular_file_handling = FALSE; + parent_file = nautilus_file_get_parent (file); + if ((parent_file != NULL) && + nautilus_file_get_file_type (file) == G_FILE_TYPE_REGULAR) + { + g_clear_pointer (&self->pending_selection, nautilus_file_list_free); + g_clear_object (&self->pending_location); + g_clear_object (&self->pending_file_to_activate); + g_free (self->pending_scroll_to); + + self->pending_location = nautilus_file_get_parent_location (file); + if (nautilus_mime_file_extracts (file)) + { + self->pending_file_to_activate = nautilus_file_ref (file); + } + else + { + self->pending_selection = g_list_prepend (NULL, nautilus_file_ref (file)); + } + self->determine_view_file = nautilus_file_ref (parent_file); + self->pending_scroll_to = nautilus_file_get_uri (file); + + nautilus_file_invalidate_all_attributes (self->determine_view_file); + nautilus_file_call_when_ready (self->determine_view_file, + NAUTILUS_FILE_ATTRIBUTE_INFO | + NAUTILUS_FILE_ATTRIBUTE_MOUNT, + got_file_info_for_view_selection_callback, + self); + + needs_regular_file_handling = TRUE; + } + + nautilus_file_unref (parent_file); + + return needs_regular_file_handling; +} + +static void +got_file_info_for_view_selection_callback (NautilusFile *file, + gpointer callback_data) +{ + GError *error = NULL; + NautilusWindow *window; + NautilusWindowSlot *self; + NautilusFile *viewed_file; + NautilusView *view; + GFile *location; + NautilusApplication *app; + + self = callback_data; + window = nautilus_window_slot_get_window (self); + + g_assert (self->determine_view_file == file); + self->determine_view_file = NULL; + + nautilus_profile_start (NULL); + + if (handle_mount_if_needed (self, file)) + { + goto done; + } + + if (handle_regular_file_if_needed (self, file)) + { + goto done; + } + + if (self->mount_error) + { + error = g_error_copy (self->mount_error); + } + else if (nautilus_file_get_file_info_error (file) != NULL) + { + error = g_error_copy (nautilus_file_get_file_info_error (file)); + } + + location = self->pending_location; + + /* desktop and other-locations GFile operations report G_IO_ERROR_NOT_SUPPORTED, + * but it's not an actual error for Nautilus */ + if (!error || g_error_matches (error, G_IO_ERROR, G_IO_ERROR_NOT_SUPPORTED)) + { + view = nautilus_window_slot_get_view_for_location (self, location); + setup_view (self, view); + } + else + { + if (error == NULL) + { + error = g_error_new (G_IO_ERROR, G_IO_ERROR_NOT_FOUND, + _("Unable to load location")); + } + nautilus_window_slot_display_view_selection_failure (window, + file, + location, + error); + + if (!gtk_widget_get_visible (GTK_WIDGET (window))) + { + /* Destroy never-had-a-chance-to-be-seen window. This case + * happens when a new window cannot display its initial URI. + */ + /* if this is the only window, we don't want to quit, so we redirect it to home */ + + app = NAUTILUS_APPLICATION (g_application_get_default ()); + + if (g_list_length (nautilus_application_get_windows (app)) == 1) + { + /* the user could have typed in a home directory that doesn't exist, + * in which case going home would cause an infinite loop, so we + * better test for that */ + + if (!nautilus_is_root_directory (location)) + { + if (!nautilus_is_home_directory (location)) + { + nautilus_window_slot_go_home (self, FALSE); + } + else + { + GFile *root; + + root = g_file_new_for_path ("/"); + /* the last fallback is to go to a known place that can't be deleted! */ + nautilus_window_slot_open_location_full (self, location, 0, NULL); + g_object_unref (root); + } + } + else + { + gtk_window_destroy (GTK_WINDOW (window)); + } + } + else + { + /* Since this is a window, destroying it will also unref it. */ + gtk_window_destroy (GTK_WINDOW (window)); + } + } + else + { + GFile *slot_location; + + /* Clean up state of already-showing window */ + end_location_change (self); + slot_location = nautilus_window_slot_get_location (self); + + /* XXX FIXME VOODOO TODO: + * Context: https://gitlab.gnome.org/GNOME/nautilus/issues/562 + * (and the associated MR) + * + * This used to just close the slot, which, in combination with + * the transient error dialog, caused Mutter to have a heart attack + * and die when the slot happened to be the only one remaining. + * The following condition can hold true in (at least) two cases: + * + * 1. We are inside the “Other Locations” view and are opening + * a broken bookmark, which causes the window slot to get replaced + * with one that handles the location, and is, understandably, + * empty. + * 2. We open a broken bookmark in a new window, which works almost + * the same, in that it has no open location. + * + * Ernestas: I’m leaning towards having an in-view message about the + * failure, which avoids dialogs and magically disappearing + * slots/tabs/windows (also allowing to go back to the + * previous location), but a dialog is quicker to inform + * about the failure. + * XXX + */ + if (slot_location == NULL) + { + nautilus_window_slot_go_home (self, 0); + } + else + { + /* We disconnected this, so we need to re-connect it */ + viewed_file = nautilus_file_get (slot_location); + nautilus_window_slot_set_viewed_file (self, viewed_file); + nautilus_file_unref (viewed_file); + + /* Leave the location bar showing the bad location that the user + * typed (or maybe achieved by dragging or something). Many times + * the mistake will just be an easily-correctable typo. The user + * can choose "Refresh" to get the original URI back in the location bar. + */ + } + } + } + +done: + g_clear_error (&error); + + nautilus_file_unref (file); + nautilus_profile_end (NULL); +} + +/* Load a view into the window, either reusing the old one or creating + * a new one. This happens when you want to load a new location, or just + * switch to a different view. + * If pending_location is set we're loading a new location and + * pending_location/selection will be used. If not, we're just switching + * view, and the current location will be used. + */ +static gboolean +setup_view (NautilusWindowSlot *self, + NautilusView *view) +{ + gboolean ret = TRUE; + GFile *old_location; + nautilus_profile_start (NULL); + + nautilus_window_slot_disconnect_content_view (self); + + self->new_content_view = view; + + nautilus_window_slot_connect_new_content_view (self); + + /* Forward search selection and state before loading the new model */ + old_location = self->content_view ? nautilus_view_get_location (self->content_view) : NULL; + + /* Actually load the pending location and selection: */ + if (self->pending_location != NULL) + { + load_new_location (self, + self->pending_location, + self->pending_selection, + self->pending_file_to_activate, + FALSE, + TRUE); + + nautilus_file_list_free (self->pending_selection); + self->pending_selection = NULL; + } + else if (old_location != NULL) + { + g_autolist (NautilusFile) selection = NULL; + + selection = nautilus_view_get_selection (self->content_view); + + load_new_location (self, + old_location, + selection, + NULL, + FALSE, + TRUE); + } + else + { + ret = FALSE; + goto out; + } + + change_view (self); + gtk_widget_show (GTK_WIDGET (self->window)); + +out: + nautilus_profile_end (NULL); + + return ret; +} + +static void +load_new_location (NautilusWindowSlot *self, + GFile *location, + GList *selection, + NautilusFile *file_to_activate, + gboolean tell_current_content_view, + gboolean tell_new_content_view) +{ + NautilusView *view; + g_assert (self != NULL); + g_assert (location != NULL); + + view = NULL; + nautilus_profile_start (NULL); + /* Note, these may recurse into report_load_underway */ + if (self->content_view != NULL && tell_current_content_view) + { + view = self->content_view; + nautilus_view_set_location (self->content_view, location); + } + + if (self->new_content_view != NULL && tell_new_content_view && + (!tell_current_content_view || + self->new_content_view != self->content_view)) + { + view = self->new_content_view; + nautilus_view_set_location (self->new_content_view, location); + } + if (view) + { + nautilus_view_set_selection (view, selection); + if (file_to_activate != NULL) + { + g_autoptr (GAppInfo) app_info = NULL; + const gchar *app_id; + + g_return_if_fail (NAUTILUS_IS_FILES_VIEW (view)); + app_info = nautilus_mime_get_default_application_for_file (file_to_activate); + app_id = g_app_info_get_id (app_info); + if (g_strcmp0 (app_id, NAUTILUS_DESKTOP_ID) == 0) + { + nautilus_files_view_activate_file (NAUTILUS_FILES_VIEW (view), + file_to_activate, 0); + } + } + } + + nautilus_profile_end (NULL); +} + +static void +end_location_change (NautilusWindowSlot *self) +{ + char *uri; + uri = nautilus_window_slot_get_location_uri (self); + if (uri) + { + DEBUG ("Finished loading window for uri %s", uri); + g_free (uri); + } + + nautilus_window_slot_set_allow_stop (self, FALSE); + + /* Now we can free details->pending_scroll_to, since the load_complete + * callback already has been emitted. + */ + g_free (self->pending_scroll_to); + self->pending_scroll_to = NULL; + + free_location_change (self); +} + +static void +free_location_change (NautilusWindowSlot *self) +{ + g_clear_object (&self->pending_location); + g_clear_object (&self->pending_file_to_activate); + nautilus_file_list_free (self->pending_selection); + self->pending_selection = NULL; + + /* Don't free details->pending_scroll_to, since thats needed until + * the load_complete callback. + */ + + if (self->mount_cancellable != NULL) + { + g_cancellable_cancel (self->mount_cancellable); + self->mount_cancellable = NULL; + } + + if (self->determine_view_file != NULL) + { + nautilus_file_cancel_call_when_ready + (self->determine_view_file, + got_file_info_for_view_selection_callback, self); + self->determine_view_file = NULL; + } +} + +/* This sets up a new view, for the current location, with the provided id. Used + * whenever the user changes the type of view to use. + * + * Note that the current view will be thrown away, even if it has the same id. + * Callers may first check if !nautilus_window_slot_content_view_matches(). + */ +static void +nautilus_window_slot_set_content_view (NautilusWindowSlot *self, + guint id) +{ + NautilusFilesView *view; + g_autolist (NautilusFile) selection = NULL; + char *uri; + g_assert (self != NULL); + + uri = nautilus_window_slot_get_location_uri (self); + DEBUG ("Change view of window %s to %d", uri, id); + g_free (uri); + + selection = nautilus_view_get_selection (self->content_view); + view = nautilus_files_view_new (id, self); + + nautilus_window_slot_stop_loading (self); + + nautilus_window_slot_set_allow_stop (self, TRUE); + + if (g_list_length (selection) == 0 && NAUTILUS_IS_FILES_VIEW (self->content_view)) + { + /* If there is no selection, queue a scroll to the same icon that + * is currently visible */ + self->pending_scroll_to = nautilus_files_view_get_first_visible_file (NAUTILUS_FILES_VIEW (self->content_view)); + } + + self->location_change_type = NAUTILUS_LOCATION_CHANGE_RELOAD; + + if (!setup_view (self, NAUTILUS_VIEW (view))) + { + /* Just load the homedir. */ + nautilus_window_slot_go_home (self, FALSE); + } +} + +void +nautilus_window_slot_back_or_forward (NautilusWindowSlot *self, + gboolean back, + guint distance) +{ + GList *list; + guint len; + NautilusBookmark *bookmark; + g_autoptr (GFile) location = NULL; + GFile *old_location; + g_autofree char *scroll_pos = NULL; + + list = back ? self->back_list : self->forward_list; + len = g_list_length (list); + + /* If we can't move in the direction at all, just return. */ + if (list == NULL) + { + return; + } + + /* If the distance to move is off the end of the list, go to the end + * of the list. */ + if (distance >= len) + { + distance = len - 1; + } + + bookmark = g_list_nth_data (list, distance); + location = nautilus_bookmark_get_location (bookmark); + old_location = nautilus_window_slot_get_location (self); + scroll_pos = nautilus_bookmark_get_scroll_pos (bookmark); + + begin_location_change (self, + location, old_location, + NULL, + back ? NAUTILUS_LOCATION_CHANGE_BACK : NAUTILUS_LOCATION_CHANGE_FORWARD, + distance, + scroll_pos); +} + +/* reload the contents of the window */ +static void +nautilus_window_slot_force_reload (NautilusWindowSlot *self) +{ + GFile *location; + char *current_pos; + g_autolist (NautilusFile) selection = NULL; + + g_assert (NAUTILUS_IS_WINDOW_SLOT (self)); + + location = nautilus_window_slot_get_location (self); + if (location == NULL) + { + return; + } + + /* peek_slot_field (window, location) can be free'd during the processing + * of begin_location_change, so make a copy + */ + g_object_ref (location); + current_pos = NULL; + + if (self->new_content_view) + { + selection = nautilus_view_get_selection (self->content_view); + + if (NAUTILUS_IS_FILES_VIEW (self->new_content_view)) + { + current_pos = nautilus_files_view_get_first_visible_file (NAUTILUS_FILES_VIEW (self->content_view)); + } + } + begin_location_change + (self, location, location, selection, + NAUTILUS_LOCATION_CHANGE_RELOAD, 0, current_pos); + g_free (current_pos); + g_object_unref (location); +} + +void +nautilus_window_slot_queue_reload (NautilusWindowSlot *self) +{ + g_assert (NAUTILUS_IS_WINDOW_SLOT (self)); + + if (nautilus_window_slot_get_location (self) == NULL) + { + return; + } + + if (self->pending_location != NULL + || self->content_view == NULL + || nautilus_view_is_loading (self->content_view)) + { + /* there is a reload in flight */ + self->needs_reload = TRUE; + return; + } + + nautilus_window_slot_force_reload (self); +} + +static void +nautilus_window_slot_clear_forward_list (NautilusWindowSlot *self) +{ + g_assert (NAUTILUS_IS_WINDOW_SLOT (self)); + + g_list_free_full (self->forward_list, g_object_unref); + self->forward_list = NULL; +} + +static void +nautilus_window_slot_clear_back_list (NautilusWindowSlot *self) +{ + g_assert (NAUTILUS_IS_WINDOW_SLOT (self)); + + g_list_free_full (self->back_list, g_object_unref); + self->back_list = NULL; +} + +static void +nautilus_window_slot_update_bookmark (NautilusWindowSlot *self, + NautilusFile *file) +{ + gboolean recreate; + GFile *new_location; + new_location = nautilus_file_get_location (file); + + if (self->current_location_bookmark == NULL) + { + recreate = TRUE; + } + else + { + GFile *bookmark_location; + bookmark_location = nautilus_bookmark_get_location (self->current_location_bookmark); + recreate = !g_file_equal (bookmark_location, new_location); + g_object_unref (bookmark_location); + } + + if (recreate) + { + char *display_name = NULL; + + /* We've changed locations, must recreate bookmark for current location. */ + g_clear_object (&self->last_location_bookmark); + self->last_location_bookmark = self->current_location_bookmark; + + display_name = nautilus_file_get_display_name (file); + self->current_location_bookmark = nautilus_bookmark_new (new_location, display_name); + g_free (display_name); + } + + g_object_unref (new_location); +} + +static void +check_bookmark_location_matches (NautilusBookmark *bookmark, + GFile *location) +{ + GFile *bookmark_location; + char *bookmark_uri, *uri; + + bookmark_location = nautilus_bookmark_get_location (bookmark); + if (!g_file_equal (location, bookmark_location)) + { + bookmark_uri = g_file_get_uri (bookmark_location); + uri = g_file_get_uri (location); + g_warning ("bookmark uri is %s, but expected %s", bookmark_uri, uri); + g_free (uri); + g_free (bookmark_uri); + } + g_object_unref (bookmark_location); +} + +/* Debugging function used to verify that the last_location_bookmark + * is in the state we expect when we're about to use it to update the + * Back or Forward list. + */ +static void +check_last_bookmark_location_matches_slot (NautilusWindowSlot *self) +{ + check_bookmark_location_matches (self->last_location_bookmark, + nautilus_window_slot_get_location (self)); +} + +static void +handle_go_direction (NautilusWindowSlot *self, + GFile *location, + gboolean forward) +{ + GList **list_ptr, **other_list_ptr; + GList *list, *other_list, *link; + NautilusBookmark *bookmark; + gint i; + list_ptr = (forward) ? (&self->forward_list) : (&self->back_list); + other_list_ptr = (forward) ? (&self->back_list) : (&self->forward_list); + list = *list_ptr; + other_list = *other_list_ptr; + + /* Move items from the list to the other list. */ + g_assert (g_list_length (list) > self->location_change_distance); + check_bookmark_location_matches (g_list_nth_data (list, self->location_change_distance), + location); + g_assert (nautilus_window_slot_get_location (self) != NULL); + + /* Move current location to list */ + check_last_bookmark_location_matches_slot (self); + + /* Use the first bookmark in the history list rather than creating a new one. */ + other_list = g_list_prepend (other_list, self->last_location_bookmark); + g_object_ref (other_list->data); + + /* Move extra links from the list to the other list */ + for (i = 0; i < self->location_change_distance; ++i) + { + bookmark = NAUTILUS_BOOKMARK (list->data); + list = g_list_remove (list, bookmark); + other_list = g_list_prepend (other_list, bookmark); + } + + /* One bookmark falls out of back/forward lists and becomes viewed location */ + link = list; + list = g_list_remove_link (list, link); + g_object_unref (link->data); + g_list_free_1 (link); + + *list_ptr = list; + *other_list_ptr = other_list; +} + +static void +handle_go_elsewhere (NautilusWindowSlot *self, + GFile *location) +{ + GFile *slot_location; + /* Clobber the entire forward list, and move displayed location to back list */ + nautilus_window_slot_clear_forward_list (self); + + slot_location = nautilus_window_slot_get_location (self); + + if (slot_location != NULL) + { + /* If we're returning to the same uri somehow, don't put this uri on back list. + * This also avoids a problem where set_displayed_location + * didn't update last_location_bookmark since the uri didn't change. + */ + if (!g_file_equal (slot_location, location)) + { + /* Store bookmark for current location in back list, unless there is no current location */ + check_last_bookmark_location_matches_slot (self); + /* Use the first bookmark in the history list rather than creating a new one. */ + self->back_list = g_list_prepend (self->back_list, + self->last_location_bookmark); + g_object_ref (self->back_list->data); + } + } +} + +static void +update_history (NautilusWindowSlot *self, + NautilusLocationChangeType type, + GFile *new_location) +{ + switch (type) + { + case NAUTILUS_LOCATION_CHANGE_STANDARD: + { + handle_go_elsewhere (self, new_location); + return; + } + + case NAUTILUS_LOCATION_CHANGE_RELOAD: + { + /* for reload there is no work to do */ + return; + } + + case NAUTILUS_LOCATION_CHANGE_BACK: + { + handle_go_direction (self, new_location, FALSE); + return; + } + + case NAUTILUS_LOCATION_CHANGE_FORWARD: + { + handle_go_direction (self, new_location, TRUE); + return; + } + } + g_return_if_fail (FALSE); +} + +typedef struct +{ + NautilusWindowSlot *slot; + GCancellable *cancellable; + GMount *mount; +} FindMountData; + +static void +nautilus_window_slot_show_x_content_bar (NautilusWindowSlot *self, + GMount *mount, + const char * const *x_content_types) +{ + GtkWidget *bar; + + g_assert (NAUTILUS_IS_WINDOW_SLOT (self)); + + if (!should_handle_content_types (x_content_types)) + { + return; + } + + bar = nautilus_x_content_bar_new (mount, x_content_types); + gtk_widget_show (bar); + nautilus_window_slot_add_extra_location_widget (self, bar); +} + +static void +found_content_type_cb (const char **x_content_types, + gpointer user_data) +{ + NautilusWindowSlot *self; + FindMountData *data = user_data; + self = data->slot; + if (g_cancellable_is_cancelled (data->cancellable)) + { + goto out; + } + + + if (x_content_types != NULL && x_content_types[0] != NULL) + { + nautilus_window_slot_show_x_content_bar (self, data->mount, (const char * const *) x_content_types); + } + + self->find_mount_cancellable = NULL; + +out: + g_object_unref (data->mount); + g_object_unref (data->cancellable); + g_free (data); +} + +static void +found_mount_cb (GObject *source_object, + GAsyncResult *res, + gpointer user_data) +{ + FindMountData *data = user_data; + NautilusWindowSlot *self; + GMount *mount; + self = NAUTILUS_WINDOW_SLOT (data->slot); + if (g_cancellable_is_cancelled (data->cancellable)) + { + goto out; + } + + mount = g_file_find_enclosing_mount_finish (G_FILE (source_object), + res, + NULL); + if (mount != NULL) + { + data->mount = mount; + nautilus_get_x_content_types_for_mount_async (mount, + found_content_type_cb, + data->cancellable, + data); + return; + } + + self->find_mount_cancellable = NULL; + +out: + g_object_unref (data->cancellable); + g_free (data); +} + +static void +nautilus_window_slot_show_special_location_bar (NautilusWindowSlot *self, + NautilusSpecialLocation special_location) +{ + GtkWidget *bar; + + bar = nautilus_special_location_bar_new (special_location); + gtk_widget_show (bar); + + nautilus_window_slot_add_extra_location_widget (self, bar); +} + +static void +nautilus_window_slot_update_for_new_location (NautilusWindowSlot *self) +{ + GFile *new_location; + NautilusFile *file; + new_location = self->pending_location; + self->pending_location = NULL; + + file = nautilus_file_get (new_location); + nautilus_window_slot_update_bookmark (self, file); + + update_history (self, self->location_change_type, new_location); + + /* Create a NautilusFile for this location, so we can catch it + * if it goes away. + */ + nautilus_window_slot_set_viewed_file (self, file); + self->viewed_file_seen = !nautilus_file_is_not_yet_confirmed (file); + self->viewed_file_in_trash = nautilus_file_is_in_trash (file); + nautilus_file_unref (file); + + nautilus_window_slot_set_location (self, new_location); + + /* Sync the actions for this new location. */ + nautilus_window_slot_sync_actions (self); +} + +static void +view_started_loading (NautilusWindowSlot *self, + NautilusView *view) +{ + if (view == self->content_view) + { + nautilus_window_slot_set_allow_stop (self, TRUE); + } + + /* Only grab focus if the menu isn't showing. Otherwise the menu disappears + * e.g. when the user toggles Show Hidden Files + */ + if (!nautilus_toolbar_is_menu_visible (NAUTILUS_TOOLBAR (nautilus_window_get_toolbar (self->window)))) + { + gtk_widget_grab_focus (GTK_WIDGET (self->window)); + } + + gtk_widget_show (GTK_WIDGET (self->window)); + + nautilus_window_slot_set_loading (self, TRUE); +} + +static void +view_ended_loading (NautilusWindowSlot *self, + NautilusView *view) +{ + if (view == self->content_view) + { + if (NAUTILUS_IS_FILES_VIEW (view) && self->pending_scroll_to != NULL) + { + nautilus_files_view_scroll_to_file (NAUTILUS_FILES_VIEW (self->content_view), self->pending_scroll_to); + } + + end_location_change (self); + } + + if (self->needs_reload) + { + nautilus_window_slot_queue_reload (self); + self->needs_reload = FALSE; + } + + nautilus_window_slot_set_allow_stop (self, FALSE); + + nautilus_window_slot_set_loading (self, FALSE); +} + +static void +view_is_loading_changed_cb (GObject *object, + GParamSpec *pspec, + NautilusWindowSlot *self) +{ + NautilusView *view; + + view = NAUTILUS_VIEW (object); + + nautilus_profile_start (NULL); + + if (nautilus_view_is_loading (view)) + { + view_started_loading (self, view); + } + else + { + view_ended_loading (self, view); + } + + nautilus_profile_end (NULL); +} + +static gboolean +nautilus_file_is_public_share_folder (NautilusFile *file) +{ + if (nautilus_file_is_user_special_directory (file, G_USER_DIRECTORY_PUBLIC_SHARE)) + { + return TRUE; + } + if (g_strcmp0 (g_get_home_dir (), g_get_user_special_dir (G_USER_DIRECTORY_PUBLIC_SHARE))) + { + /* In order to match the behavior of gnome-user-share the ~/Public folder + * is considered to be the public sharing folder when XDG_PUBLICSHARE_DIR + * is set to the home folder. */ + g_autoptr (GFile) public_folder = g_file_new_build_filename (g_get_home_dir (), "Public", NULL); + g_autoptr (GFile) location = nautilus_file_get_location (file); + + return g_file_equal (public_folder, location); + } + return FALSE; +} + +static void +nautilus_window_slot_setup_extra_location_widgets (NautilusWindowSlot *self) +{ + GFile *location; + FindMountData *data; + NautilusDirectory *directory; + NautilusFile *file; + GFile *scripts_file; + char *scripts_path; + + scripts_path = nautilus_get_scripts_directory_path (); + location = nautilus_window_slot_get_current_location (self); + + if (location == NULL) + { + return; + } + + directory = nautilus_directory_get (location); + + scripts_file = g_file_new_for_path (scripts_path); + g_free (scripts_path); + + file = nautilus_file_get (location); + + if (nautilus_should_use_templates_directory () && + nautilus_file_is_user_special_directory (file, G_USER_DIRECTORY_TEMPLATES)) + { + nautilus_window_slot_show_special_location_bar (self, NAUTILUS_SPECIAL_LOCATION_TEMPLATES); + } + else if (g_file_equal (location, scripts_file)) + { + nautilus_window_slot_show_special_location_bar (self, NAUTILUS_SPECIAL_LOCATION_SCRIPTS); + } + else if (check_schema_available (FILE_SHARING_SCHEMA_ID) && nautilus_file_is_public_share_folder (file)) + { + nautilus_window_slot_show_special_location_bar (self, NAUTILUS_SPECIAL_LOCATION_SHARING); + } + else if (nautilus_directory_is_in_trash (directory) && + g_settings_get_boolean (gnome_privacy_preferences, "remove-old-trash-files")) + { + nautilus_window_slot_show_special_location_bar (self, NAUTILUS_SPECIAL_LOCATION_TRASH); + } + + g_object_unref (scripts_file); + nautilus_file_unref (file); + + /* need the mount to determine if we should put up the x-content cluebar */ + if (self->find_mount_cancellable != NULL) + { + g_cancellable_cancel (self->find_mount_cancellable); + self->find_mount_cancellable = NULL; + } + + data = g_new (FindMountData, 1); + data->slot = self; + data->cancellable = g_cancellable_new (); + data->mount = NULL; + + self->find_mount_cancellable = data->cancellable; + g_file_find_enclosing_mount_async (location, + G_PRIORITY_DEFAULT, + data->cancellable, + found_mount_cb, + data); + + nautilus_directory_unref (directory); +} + +static void +nautilus_window_slot_connect_new_content_view (NautilusWindowSlot *self) +{ + if (self->new_content_view) + { + g_signal_connect (self->new_content_view, + "notify::loading", + G_CALLBACK (view_is_loading_changed_cb), + self); + } +} + +static void +nautilus_window_slot_disconnect_content_view (NautilusWindowSlot *self) +{ + if (self->content_view) + { + /* disconnect old view */ + g_signal_handlers_disconnect_by_func (self->content_view, + G_CALLBACK (view_is_loading_changed_cb), + self); + } +} + +static void +nautilus_window_slot_switch_new_content_view (NautilusWindowSlot *self) +{ + GtkWidget *widget; + gboolean reusing_view; + reusing_view = self->new_content_view && + gtk_widget_get_parent (GTK_WIDGET (self->new_content_view)) != NULL; + /* We are either reusing the view, so new_content_view and content_view + * are the same, or the new_content_view is invalid */ + if (self->new_content_view == NULL || reusing_view) + { + goto done; + } + + if (self->content_view != NULL) + { + g_binding_unbind (self->searching_binding); + g_binding_unbind (self->selection_binding); + g_binding_unbind (self->extensions_background_menu_binding); + g_binding_unbind (self->templates_menu_binding); + widget = GTK_WIDGET (self->content_view); + gtk_box_remove (GTK_BOX (self), widget); + g_clear_object (&self->content_view); + } + + if (self->new_content_view != NULL) + { + self->content_view = self->new_content_view; + self->new_content_view = NULL; + + widget = GTK_WIDGET (self->content_view); + gtk_box_append (GTK_BOX (self), widget); + gtk_widget_set_vexpand (widget, TRUE); + gtk_widget_show (widget); + self->searching_binding = g_object_bind_property (self->content_view, "searching", + self, "searching", + G_BINDING_DEFAULT | G_BINDING_SYNC_CREATE); + self->selection_binding = g_object_bind_property (self->content_view, "selection", + self, "selection", + G_BINDING_DEFAULT | G_BINDING_SYNC_CREATE); + self->extensions_background_menu_binding = g_object_bind_property (self->content_view, "extensions-background-menu", + self, "extensions-background-menu", + G_BINDING_DEFAULT | G_BINDING_SYNC_CREATE); + self->templates_menu_binding = g_object_bind_property (self->content_view, "templates-menu", + self, "templates-menu", + G_BINDING_DEFAULT | G_BINDING_SYNC_CREATE); + g_object_notify_by_pspec (G_OBJECT (self), properties[PROP_ICON_NAME]); + g_object_notify_by_pspec (G_OBJECT (self), properties[PROP_TOOLBAR_MENU_SECTIONS]); + g_object_notify_by_pspec (G_OBJECT (self), properties[PROP_EXTENSIONS_BACKGROUND_MENU]); + g_object_notify_by_pspec (G_OBJECT (self), properties[PROP_TEMPLATES_MENU]); + g_object_notify_by_pspec (G_OBJECT (self), properties[PROP_TOOLTIP]); + } + +done: + /* Clean up, so we don't confuse having a new_content_view available or + * just that we didn't care about it here */ + self->new_content_view = NULL; +} + +/* This is called when we have decided we can actually change to the new view/location situation. */ +static void +change_view (NautilusWindowSlot *self) +{ + /* Switch to the new content view. + * Destroy the extra location widgets first, since they might hold + * a pointer to the old view, which will possibly be destroyed inside + * nautilus_window_slot_switch_new_content_view(). + */ + nautilus_window_slot_remove_extra_location_widgets (self); + nautilus_window_slot_switch_new_content_view (self); + + if (self->pending_location != NULL) + { + /* Tell the window we are finished. */ + nautilus_window_slot_update_for_new_location (self); + } + + /* Now that we finished switching to the new location, + * add back the extra location widgets. + */ + nautilus_window_slot_setup_extra_location_widgets (self); +} + +static void +nautilus_window_slot_dispose (GObject *object) +{ + NautilusWindowSlot *self; + self = NAUTILUS_WINDOW_SLOT (object); + + g_signal_handlers_disconnect_by_data (nautilus_preferences, self); + + nautilus_window_slot_clear_forward_list (self); + nautilus_window_slot_clear_back_list (self); + + nautilus_window_slot_remove_extra_location_widgets (self); + + g_clear_pointer (&self->searching_binding, g_binding_unbind); + g_clear_pointer (&self->selection_binding, g_binding_unbind); + g_clear_pointer (&self->extensions_background_menu_binding, g_binding_unbind); + g_clear_pointer (&self->templates_menu_binding, g_binding_unbind); + + g_clear_object (&self->templates_menu); + g_clear_object (&self->extensions_background_menu); + + if (self->content_view) + { + gtk_box_remove (GTK_BOX (self), GTK_WIDGET (self->content_view)); + g_clear_object (&self->content_view); + } + + if (self->new_content_view) + { + gtk_box_remove (GTK_BOX (self), GTK_WIDGET (self->new_content_view)); + g_clear_object (&self->new_content_view); + } + + nautilus_window_slot_set_viewed_file (self, NULL); + + g_clear_object (&self->location); + g_clear_object (&self->pending_file_to_activate); + g_clear_pointer (&self->pending_selection, nautilus_file_list_free); + + g_clear_object (&self->current_location_bookmark); + g_clear_object (&self->last_location_bookmark); + g_clear_object (&self->slot_action_group); + g_clear_object (&self->pending_search_query); + + g_clear_pointer (&self->find_mount_cancellable, g_cancellable_cancel); + + if (self->query_editor) + { + g_clear_object (&self->query_editor); + } + + free_location_change (self); + + G_OBJECT_CLASS (nautilus_window_slot_parent_class)->dispose (object); +} + +static void +nautilus_window_slot_finalize (GObject *object) +{ + NautilusWindowSlot *self; + self = NAUTILUS_WINDOW_SLOT (object); + g_clear_pointer (&self->title, g_free); + + G_OBJECT_CLASS (nautilus_window_slot_parent_class)->finalize (object); +} + +static gboolean +nautilus_window_slot_grab_focus (GtkWidget *widget) +{ + NautilusWindowSlot *self; + self = NAUTILUS_WINDOW_SLOT (widget); + + if (nautilus_window_slot_get_search_visible (self)) + { + return gtk_widget_grab_focus (GTK_WIDGET (self->query_editor)); + } + else if (self->content_view != NULL) + { + return gtk_widget_grab_focus (GTK_WIDGET (self->content_view)); + } + else if (self->new_content_view != NULL) + { + return gtk_widget_grab_focus (GTK_WIDGET (self->new_content_view)); + } + + return GTK_WIDGET_CLASS (nautilus_window_slot_parent_class)->grab_focus (widget); +} + +static void +nautilus_window_slot_class_init (NautilusWindowSlotClass *klass) +{ + GObjectClass *oclass = G_OBJECT_CLASS (klass); + GtkWidgetClass *widget_class = GTK_WIDGET_CLASS (klass); + + oclass->dispose = nautilus_window_slot_dispose; + oclass->finalize = nautilus_window_slot_finalize; + oclass->constructed = nautilus_window_slot_constructed; + oclass->set_property = nautilus_window_slot_set_property; + oclass->get_property = nautilus_window_slot_get_property; + + widget_class->grab_focus = nautilus_window_slot_grab_focus; + + properties[PROP_ACTIVE] = + g_param_spec_boolean ("active", + "Whether the slot is active", + "Whether the slot is the active slot of the window", + FALSE, + G_PARAM_READWRITE); + + properties[PROP_LOADING] = + g_param_spec_boolean ("loading", + "Whether the slot loading", + "Whether the slot is loading a new location", + FALSE, + G_PARAM_READABLE); + + properties[PROP_SEARCHING] = + g_param_spec_boolean ("searching", + "Whether the current view of the slot is searching", + "Whether the current view of the slot is searching. Proxy property from the view", + FALSE, + G_PARAM_READWRITE); + + properties[PROP_SELECTION] = + g_param_spec_pointer ("selection", + "Selection of the current view of the slot", + "The selection of the current view of the slot. Proxy property from the view", + G_PARAM_READWRITE); + + properties[PROP_WINDOW] = + g_param_spec_object ("window", + "The NautilusWindow", + "The NautilusWindow this slot is part of", + NAUTILUS_TYPE_WINDOW, + G_PARAM_READWRITE | G_PARAM_CONSTRUCT); + + properties[PROP_ICON_NAME] = + g_param_spec_string ("icon-name", + "Icon that represents the slot", + "The icon that represents the slot", + NULL, + G_PARAM_READABLE); + + properties[PROP_TOOLBAR_MENU_SECTIONS] = + g_param_spec_pointer ("toolbar-menu-sections", + "Menu sections for the toolbar menu", + "The menu sections to add to the toolbar menu for this slot", + G_PARAM_READABLE); + + properties[PROP_EXTENSIONS_BACKGROUND_MENU] = + g_param_spec_object ("extensions-background-menu", + "Background menu of extensions", + "Proxy property from the view for the background menu for extensions", + G_TYPE_MENU_MODEL, + G_PARAM_READWRITE); + + properties[PROP_TEMPLATES_MENU] = + g_param_spec_object ("templates-menu", + "Templates menu", + "Proxy property from the view for the templates menu", + G_TYPE_MENU_MODEL, + G_PARAM_READWRITE); + + properties[PROP_LOCATION] = + g_param_spec_object ("location", + "Current location visible on the slot", + "Either the location that is used currently, or the pending location. Clients will see the same value they set, and therefore it will be cosistent from clients point of view.", + G_TYPE_FILE, + G_PARAM_READWRITE); + + properties[PROP_TOOLTIP] = + g_param_spec_string ("tooltip", + "Tooltip that represents the slot", + "The tooltip that represents the slot", + NULL, + G_PARAM_READWRITE); + + properties[PROP_ALLOW_STOP] = + g_param_spec_boolean ("allow-stop", "", "", + FALSE, + G_PARAM_READABLE | G_PARAM_STATIC_STRINGS); + + properties[PROP_TITLE] = + g_param_spec_string ("title", "", "", + NULL, + G_PARAM_READABLE | G_PARAM_STATIC_STRINGS); + + g_object_class_install_properties (oclass, NUM_PROPERTIES, properties); +} + +GFile * +nautilus_window_slot_get_location (NautilusWindowSlot *self) +{ + g_return_val_if_fail (NAUTILUS_IS_WINDOW_SLOT (self), NULL); + + return self->location; +} + +GFile * +nautilus_window_slot_get_pending_location (NautilusWindowSlot *self) +{ + g_return_val_if_fail (NAUTILUS_IS_WINDOW_SLOT (self), NULL); + + return self->pending_location; +} + +const gchar * +nautilus_window_slot_get_title (NautilusWindowSlot *self) +{ + return self->title; +} + +char * +nautilus_window_slot_get_location_uri (NautilusWindowSlot *self) +{ + g_assert (NAUTILUS_IS_WINDOW_SLOT (self)); + + if (self->location) + { + return g_file_get_uri (self->location); + } + return NULL; +} + +NautilusWindow * +nautilus_window_slot_get_window (NautilusWindowSlot *self) +{ + g_assert (NAUTILUS_IS_WINDOW_SLOT (self)); + + return self->window; +} + +void +nautilus_window_slot_set_window (NautilusWindowSlot *self, + NautilusWindow *window) +{ + g_assert (NAUTILUS_IS_WINDOW_SLOT (self)); + g_assert (NAUTILUS_IS_WINDOW (window)); + + if (self->window != window) + { + self->window = window; + g_object_notify_by_pspec (G_OBJECT (self), properties[PROP_WINDOW]); + } +} + +/* nautilus_window_slot_update_title: + * + * Re-calculate the slot title. + * Called when the location or view has changed. + * @slot: The NautilusWindowSlot in question. + * + */ +void +nautilus_window_slot_update_title (NautilusWindowSlot *self) +{ + NautilusWindow *window; + g_autofree char *title = NULL; + title = nautilus_compute_title_for_location (self->location); + window = nautilus_window_slot_get_window (self); + + if (g_strcmp0 (title, self->title) != 0) + { + g_free (self->title); + self->title = g_steal_pointer (&title); + + g_object_notify_by_pspec (G_OBJECT (self), properties[PROP_TITLE]); + + nautilus_window_sync_title (window, self); + } +} + +gboolean +nautilus_window_slot_get_allow_stop (NautilusWindowSlot *self) +{ + return self->allow_stop; +} + +void +nautilus_window_slot_set_allow_stop (NautilusWindowSlot *self, + gboolean allow) +{ + NautilusWindow *window; + g_assert (NAUTILUS_IS_WINDOW_SLOT (self)); + + self->allow_stop = allow; + + window = nautilus_window_slot_get_window (self); + nautilus_window_sync_allow_stop (window, self); + + g_object_notify_by_pspec (G_OBJECT (self), properties[PROP_ALLOW_STOP]); +} + +void +nautilus_window_slot_stop_loading (NautilusWindowSlot *self) +{ + GFile *location; + NautilusDirectory *directory; + location = nautilus_window_slot_get_location (self); + directory = nautilus_directory_get (self->location); + + if (NAUTILUS_IS_FILES_VIEW (self->content_view)) + { + nautilus_files_view_stop_loading (NAUTILUS_FILES_VIEW (self->content_view)); + } + + nautilus_directory_unref (directory); + + if (self->pending_location != NULL && + location != NULL && + self->content_view != NULL && + NAUTILUS_IS_FILES_VIEW (self->content_view)) + { + /* No need to tell the new view - either it is the + * same as the old view, in which case it will already + * be told, or it is the very pending change we wish + * to cancel. + */ + g_autolist (NautilusFile) selection = NULL; + + selection = nautilus_view_get_selection (self->content_view); + load_new_location (self, + location, + selection, + NULL, + TRUE, + FALSE); + } + + end_location_change (self); + + if (self->new_content_view) + { + g_object_unref (self->new_content_view); + self->new_content_view = NULL; + } +} + +NautilusView * +nautilus_window_slot_get_current_view (NautilusWindowSlot *self) +{ + if (self->content_view != NULL) + { + return self->content_view; + } + else if (self->new_content_view) + { + return self->new_content_view; + } + + return NULL; +} + +NautilusBookmark * +nautilus_window_slot_get_bookmark (NautilusWindowSlot *self) +{ + return self->current_location_bookmark; +} + +GList * +nautilus_window_slot_get_back_history (NautilusWindowSlot *self) +{ + return self->back_list; +} + +GList * +nautilus_window_slot_get_forward_history (NautilusWindowSlot *self) +{ + return self->forward_list; +} + +NautilusWindowSlot * +nautilus_window_slot_new (NautilusWindow *window) +{ + return g_object_new (NAUTILUS_TYPE_WINDOW_SLOT, + "window", window, + NULL); +} + +const gchar * +nautilus_window_slot_get_icon_name (NautilusWindowSlot *self) +{ + guint current_view_id; + g_return_val_if_fail (NAUTILUS_IS_WINDOW_SLOT (self), NULL); + + if (self->content_view == NULL) + { + return ""; + } + + current_view_id = nautilus_view_get_view_id (NAUTILUS_VIEW (self->content_view)); + switch (current_view_id) + { + case NAUTILUS_VIEW_LIST_ID: + { + return nautilus_view_get_icon_name (NAUTILUS_VIEW_GRID_ID); + } + break; + + case NAUTILUS_VIEW_GRID_ID: + { + return nautilus_view_get_icon_name (NAUTILUS_VIEW_LIST_ID); + } + break; + + case NAUTILUS_VIEW_OTHER_LOCATIONS_ID: + { + return nautilus_view_get_icon_name (NAUTILUS_VIEW_OTHER_LOCATIONS_ID); + } + break; + + default: + { + return NULL; + } + } +} + +const gchar * +nautilus_window_slot_get_tooltip (NautilusWindowSlot *self) +{ + guint current_view_id; + g_return_val_if_fail (NAUTILUS_IS_WINDOW_SLOT (self), NULL); + + if (self->content_view == NULL) + { + return NULL; + } + + current_view_id = nautilus_view_get_view_id (NAUTILUS_VIEW (self->content_view)); + switch (current_view_id) + { + case NAUTILUS_VIEW_LIST_ID: + { + return nautilus_view_get_tooltip (NAUTILUS_VIEW_GRID_ID); + } + break; + + case NAUTILUS_VIEW_GRID_ID: + { + return nautilus_view_get_tooltip (NAUTILUS_VIEW_LIST_ID); + } + break; + + case NAUTILUS_VIEW_OTHER_LOCATIONS_ID: + { + return nautilus_view_get_tooltip (NAUTILUS_VIEW_OTHER_LOCATIONS_ID); + } + break; + + default: + { + return NULL; + } + } +} + +NautilusToolbarMenuSections * +nautilus_window_slot_get_toolbar_menu_sections (NautilusWindowSlot *self) +{ + NautilusView *view; + + g_return_val_if_fail (NAUTILUS_IS_WINDOW_SLOT (self), NULL); + + view = nautilus_window_slot_get_current_view (self); + + return view ? nautilus_view_get_toolbar_menu_sections (view) : NULL; +} + +gboolean +nautilus_window_slot_get_active (NautilusWindowSlot *self) +{ + g_return_val_if_fail (NAUTILUS_IS_WINDOW_SLOT (self), FALSE); + + return self->active; +} + +void +nautilus_window_slot_set_active (NautilusWindowSlot *self, + gboolean active) +{ + NautilusWindow *window; + + g_return_if_fail (NAUTILUS_IS_WINDOW_SLOT (self)); + + if (self->active != active) + { + self->active = active; + + if (active) + { + AdwTabView *tab_view; + AdwTabPage *page; + + window = self->window; + + tab_view = nautilus_window_get_tab_view (window); + page = adw_tab_view_get_page (tab_view, GTK_WIDGET (self)); + + adw_tab_view_set_selected_page (tab_view, page); + + /* sync window to new slot */ + nautilus_window_sync_allow_stop (window, self); + nautilus_window_sync_title (window, self); + nautilus_window_sync_location_widgets (window); + nautilus_window_slot_sync_actions (self); + + gtk_widget_insert_action_group (GTK_WIDGET (window), "slot", self->slot_action_group); + } + else + { + window = nautilus_window_slot_get_window (self); + g_assert (self == nautilus_window_get_active_slot (window)); + + gtk_widget_insert_action_group (GTK_WIDGET (window), "slot", NULL); + } + + g_object_notify_by_pspec (G_OBJECT (self), properties[PROP_ACTIVE]); + } +} + +static void +nautilus_window_slot_set_loading (NautilusWindowSlot *self, + gboolean loading) +{ + g_return_if_fail (NAUTILUS_IS_WINDOW_SLOT (self)); + + self->loading = loading; + + g_object_notify_by_pspec (G_OBJECT (self), properties[PROP_LOADING]); +} + +gboolean +nautilus_window_slot_get_loading (NautilusWindowSlot *self) +{ + g_return_val_if_fail (NAUTILUS_IS_WINDOW_SLOT (self), FALSE); + + return self->loading; +} + +NautilusQueryEditor * +nautilus_window_slot_get_query_editor (NautilusWindowSlot *self) +{ + g_return_val_if_fail (NAUTILUS_IS_WINDOW_SLOT (self), NULL); + + return self->query_editor; +} diff --git a/src/nautilus-window-slot.h b/src/nautilus-window-slot.h new file mode 100644 index 0000000..364c637 --- /dev/null +++ b/src/nautilus-window-slot.h @@ -0,0 +1,120 @@ +/* + nautilus-window-slot.h: Nautilus window slot + + Copyright (C) 2008 Free Software Foundation, Inc. + + This program is free software; you can redistribute it and/or + modify it under the terms of the GNU General Public License as + published by the Free Software Foundation; either version 2 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 warranty of + MERCHANTABILITY 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 . + + Author: Christian Neumair +*/ + +#pragma once + +#include +#include +#include + +#include "nautilus-types.h" + +typedef enum { + NAUTILUS_LOCATION_CHANGE_STANDARD, + NAUTILUS_LOCATION_CHANGE_BACK, + NAUTILUS_LOCATION_CHANGE_FORWARD, + NAUTILUS_LOCATION_CHANGE_RELOAD +} NautilusLocationChangeType; + +#define NAUTILUS_TYPE_WINDOW_SLOT (nautilus_window_slot_get_type ()) +G_DECLARE_FINAL_TYPE (NautilusWindowSlot, nautilus_window_slot, NAUTILUS, WINDOW_SLOT, GtkBox) + +typedef struct +{ + NautilusFile *file; + gint view_before_search; + GList *back_list; + GList *forward_list; + NautilusBookmark *current_location_bookmark; +} NautilusNavigationState; + +NautilusWindowSlot * nautilus_window_slot_new (NautilusWindow *window); + +NautilusWindow * nautilus_window_slot_get_window (NautilusWindowSlot *slot); +void nautilus_window_slot_set_window (NautilusWindowSlot *slot, + NautilusWindow *window); + +void nautilus_window_slot_open_location_full (NautilusWindowSlot *slot, + GFile *location, + NautilusOpenFlags flags, + GList *new_selection); + +GFile * nautilus_window_slot_get_location (NautilusWindowSlot *slot); +GFile * nautilus_window_slot_get_pending_location (NautilusWindowSlot *slot); + +NautilusBookmark *nautilus_window_slot_get_bookmark (NautilusWindowSlot *slot); + +GList * nautilus_window_slot_get_back_history (NautilusWindowSlot *slot); +GList * nautilus_window_slot_get_forward_history (NautilusWindowSlot *slot); + +gboolean nautilus_window_slot_get_allow_stop (NautilusWindowSlot *slot); +void nautilus_window_slot_set_allow_stop (NautilusWindowSlot *slot, + gboolean allow_stop); +void nautilus_window_slot_stop_loading (NautilusWindowSlot *slot); + +const gchar *nautilus_window_slot_get_title (NautilusWindowSlot *slot); +void nautilus_window_slot_update_title (NautilusWindowSlot *slot); + +gboolean nautilus_window_slot_handle_event (NautilusWindowSlot *slot, + GtkEventControllerKey *controller, + guint keyval, + GdkModifierType state); + +void nautilus_window_slot_queue_reload (NautilusWindowSlot *slot); + +const gchar* nautilus_window_slot_get_icon_name (NautilusWindowSlot *slot); + +const gchar* nautilus_window_slot_get_tooltip (NautilusWindowSlot *slot); + +NautilusToolbarMenuSections * nautilus_window_slot_get_toolbar_menu_sections (NautilusWindowSlot *slot); + +GMenuModel* nautilus_window_slot_get_templates_menu (NautilusWindowSlot *self); + +GMenuModel* nautilus_window_slot_get_extensions_background_menu (NautilusWindowSlot *self); + +gboolean nautilus_window_slot_get_active (NautilusWindowSlot *slot); + +void nautilus_window_slot_set_active (NautilusWindowSlot *slot, + gboolean active); +gboolean nautilus_window_slot_get_loading (NautilusWindowSlot *slot); + +gboolean nautilus_window_slot_get_searching (NautilusWindowSlot *slot); + +GList* nautilus_window_slot_get_selection (NautilusWindowSlot *slot); + +void nautilus_window_slot_search (NautilusWindowSlot *slot, + NautilusQuery *query); + +void nautilus_window_slot_restore_navigation_state (NautilusWindowSlot *self, + NautilusNavigationState *data); + +NautilusNavigationState* nautilus_window_slot_get_navigation_state (NautilusWindowSlot *self); + +NautilusQueryEditor *nautilus_window_slot_get_query_editor (NautilusWindowSlot *self); + +/* Only used by slot-dnd */ +NautilusView* nautilus_window_slot_get_current_view (NautilusWindowSlot *slot); + +void nautilus_window_slot_back_or_forward (NautilusWindowSlot *slot, + gboolean back, + guint distance); + +void free_navigation_state (gpointer data); diff --git a/src/nautilus-window.c b/src/nautilus-window.c new file mode 100644 index 0000000..0b966c0 --- /dev/null +++ b/src/nautilus-window.c @@ -0,0 +1,2328 @@ +/* + * Nautilus + * + * Copyright (C) 1999, 2000, 2004 Red Hat, Inc. + * Copyright (C) 1999, 2000, 2001 Eazel, Inc. + * + * Nautilus is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public + * License as published by the Free Software Foundation; either + * version 2 of the License, or (at your option) any later version. + * + * Nautilus is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * 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 . + * + * Authors: Elliot Lee + * John Sullivan + * Alexander Larsson + */ + +/* nautilus-window.c: Implementation of the main window object */ + +#include "nautilus-window.h" + +#include +#include +#include +#include +#include +#include +#include +#include + +#ifdef GDK_WINDOWING_WAYLAND +#include +#endif + +#ifdef GDK_WINDOWING_X11 +#include +#endif + +#define DEBUG_FLAG NAUTILUS_DEBUG_WINDOW +#include "nautilus-debug.h" + +#include "gtk/nautilusgtkplacessidebarprivate.h" + +#include "nautilus-application.h" +#include "nautilus-bookmark-list.h" +#include "nautilus-clipboard.h" +#include "nautilus-dnd.h" +#include "nautilus-enums.h" +#include "nautilus-file-operations.h" +#include "nautilus-file-undo-manager.h" +#include "nautilus-file-utilities.h" +#include "nautilus-global-preferences.h" +#include "nautilus-location-entry.h" +#include "nautilus-metadata.h" +#include "nautilus-mime-actions.h" +#include "nautilus-module.h" +#include "nautilus-pathbar.h" +#include "nautilus-profile.h" +#include "nautilus-signaller.h" +#include "nautilus-toolbar.h" +#include "nautilus-trash-monitor.h" +#include "nautilus-ui-utilities.h" +#include "nautilus-window-slot.h" + +/* Forward and back buttons on the mouse */ +static gboolean mouse_extra_buttons = TRUE; +static int mouse_forward_button = 9; +static int mouse_back_button = 8; + +static void mouse_back_button_changed (gpointer callback_data); +static void mouse_forward_button_changed (gpointer callback_data); +static void use_extra_mouse_buttons_changed (gpointer callback_data); +static void nautilus_window_initialize_actions (NautilusWindow *window); +static GtkWidget *nautilus_window_ensure_location_entry (NautilusWindow *window); +static void nautilus_window_back_or_forward (NautilusWindow *window, + gboolean back, + guint distance); + +/* Sanity check: highest mouse button value I could find was 14. 5 is our + * lower threshold (well-documented to be the one of the button events for the + * scrollwheel), so it's hardcoded in the functions below. However, if you have + * a button that registers higher and want to map it, file a bug and + * we'll move the bar. Makes you wonder why the X guys don't have + * defined values for these like the XKB stuff, huh? + */ +#define UPPER_MOUSE_LIMIT 14 + +struct _NautilusWindow +{ + AdwApplicationWindow parent_instance; + + AdwTabView *tab_view; + AdwTabPage *menu_page; + + GList *slots; + NautilusWindowSlot *active_slot; /* weak reference */ + + GtkWidget *content_flap; + + /* Side Pane */ + GtkWidget *places_sidebar; /* the actual GtkPlacesSidebar */ + GVolume *selected_volume; /* the selected volume in the sidebar popup callback */ + GFile *selected_file; /* the selected file in the sidebar popup callback */ + + /* Notifications */ + AdwToastOverlay *toast_overlay; + + /* Toolbar */ + GtkWidget *toolbar; + gboolean temporary_navigation_bar; + + /* focus widget before the location bar has been shown temporarily */ + GtkWidget *last_focus_widget; + + /* Handle when exported */ + gchar *export_handle; + + guint sidebar_width_handler_id; + gulong bookmarks_id; + + GQueue *tab_data_queue; + + /* Pad controller which holds a reference to the window. Kept around to + * break reference-counting cycles during finalization. */ + GtkPadController *pad_controller; +}; + +enum +{ + SLOT_ADDED, + SLOT_REMOVED, + LAST_SIGNAL +}; + +static guint signals[LAST_SIGNAL] = { 0 }; + +G_DEFINE_TYPE (NautilusWindow, nautilus_window, ADW_TYPE_APPLICATION_WINDOW); + +enum +{ + PROP_0, + PROP_ACTIVE_SLOT, + N_PROPS +}; + +static GParamSpec *properties[N_PROPS]; + +static const GtkPadActionEntry pad_actions[] = +{ + { GTK_PAD_ACTION_BUTTON, 0, -1, N_("Parent folder"), "up" }, + { GTK_PAD_ACTION_BUTTON, 1, -1, N_("Home"), "go-home" }, + { GTK_PAD_ACTION_BUTTON, 2, -1, N_("New tab"), "new-tab" }, + { GTK_PAD_ACTION_BUTTON, 3, -1, N_("Close current view"), "close-current-view" }, + { GTK_PAD_ACTION_BUTTON, 4, -1, N_("Back"), "back" }, + { GTK_PAD_ACTION_BUTTON, 5, -1, N_("Forward"), "forward" }, +}; + +static void +action_close_current_view (GSimpleAction *action, + GVariant *state, + gpointer user_data) +{ + NautilusWindow *window = user_data; + AdwTabPage *page = window->menu_page; + + if (adw_tab_view_get_n_pages (window->tab_view) <= 1) + { + nautilus_window_close (window); + return; + } + + if (page == NULL) + { + page = adw_tab_view_get_selected_page (window->tab_view); + } + + adw_tab_view_close_page (window->tab_view, page); +} + +static void +action_go_home (GSimpleAction *action, + GVariant *state, + gpointer user_data) +{ + NautilusWindow *window; + GFile *home; + + window = NAUTILUS_WINDOW (user_data); + home = g_file_new_for_path (g_get_home_dir ()); + + nautilus_window_open_location_full (window, home, 0, NULL, NULL); + + g_object_unref (home); +} + +static void +action_go_starred (GSimpleAction *action, + GVariant *state, + gpointer user_data) +{ + NautilusWindow *window; + g_autoptr (GFile) starred = NULL; + + window = NAUTILUS_WINDOW (user_data); + starred = g_file_new_for_uri ("starred:///"); + + nautilus_window_open_location_full (window, starred, 0, NULL, NULL); +} + +static void +action_reload (GSimpleAction *action, + GVariant *state, + gpointer user_data) +{ + NautilusWindowSlot *slot; + + slot = nautilus_window_get_active_slot (NAUTILUS_WINDOW (user_data)); + nautilus_window_slot_queue_reload (slot); +} + +static void +action_stop (GSimpleAction *action, + GVariant *state, + gpointer user_data) +{ + NautilusWindow *window; + NautilusWindowSlot *slot; + + window = NAUTILUS_WINDOW (user_data); + slot = nautilus_window_get_active_slot (window); + + nautilus_window_slot_stop_loading (slot); +} + +static void +action_up (GSimpleAction *action, + GVariant *state, + gpointer user_data) +{ + NautilusWindowSlot *slot; + GFile *parent, *location; + + slot = nautilus_window_get_active_slot (NAUTILUS_WINDOW (user_data)); + location = nautilus_window_slot_get_location (slot); + + if (location != NULL) + { + parent = g_file_get_parent (location); + if (parent != NULL) + { + nautilus_window_open_location_full (NAUTILUS_WINDOW (user_data), + parent, + 0, + NULL, NULL); + } + + g_clear_object (&parent); + } +} + +static void +action_back (GSimpleAction *action, + GVariant *state, + gpointer user_data) +{ + nautilus_window_back_or_forward (NAUTILUS_WINDOW (user_data), TRUE, 0); +} + +static void +action_forward (GSimpleAction *action, + GVariant *state, + gpointer user_data) +{ + nautilus_window_back_or_forward (NAUTILUS_WINDOW (user_data), FALSE, 0); +} + +static void +action_back_n (GSimpleAction *action, + GVariant *parameter, + gpointer user_data) +{ + nautilus_window_back_or_forward (NAUTILUS_WINDOW (user_data), + TRUE, + g_variant_get_uint32 (parameter)); +} + +static void +action_forward_n (GSimpleAction *action, + GVariant *parameter, + gpointer user_data) +{ + nautilus_window_back_or_forward (NAUTILUS_WINDOW (user_data), + FALSE, + g_variant_get_uint32 (parameter)); +} + +static void +action_bookmark_current_location (GSimpleAction *action, + GVariant *state, + gpointer user_data) +{ + NautilusWindow *window = user_data; + NautilusApplication *app = NAUTILUS_APPLICATION (g_application_get_default ()); + NautilusWindowSlot *slot; + + slot = nautilus_window_get_active_slot (window); + nautilus_bookmark_list_append (nautilus_application_get_bookmarks (app), + nautilus_window_slot_get_bookmark (slot)); +} + +static void +action_new_tab (GSimpleAction *action, + GVariant *state, + gpointer user_data) +{ + nautilus_window_new_tab (NAUTILUS_WINDOW (user_data)); +} + +static void +action_enter_location (GSimpleAction *action, + GVariant *state, + gpointer user_data) +{ + NautilusWindow *window = user_data; + + nautilus_window_ensure_location_entry (window); +} + +static void +action_tab_move_left (GSimpleAction *action, + GVariant *state, + gpointer user_data) +{ + NautilusWindow *window = user_data; + AdwTabPage *page = window->menu_page; + + if (page == NULL) + { + page = adw_tab_view_get_selected_page (window->tab_view); + } + + adw_tab_view_reorder_backward (window->tab_view, page); +} + +static void +action_tab_move_right (GSimpleAction *action, + GVariant *state, + gpointer user_data) +{ + NautilusWindow *window = user_data; + AdwTabPage *page = window->menu_page; + + if (page == NULL) + { + page = adw_tab_view_get_selected_page (window->tab_view); + } + + adw_tab_view_reorder_forward (window->tab_view, page); +} + +static void +action_go_to_tab (GSimpleAction *action, + GVariant *value, + gpointer user_data) +{ + NautilusWindow *window = NAUTILUS_WINDOW (user_data); + gint16 num; + + num = g_variant_get_int32 (value); + if (num < adw_tab_view_get_n_pages (window->tab_view)) + { + AdwTabPage *page = adw_tab_view_get_nth_page (window->tab_view, num); + + adw_tab_view_set_selected_page (window->tab_view, page); + } +} + +static void +prompt_for_location (NautilusWindow *window, + const char *path) +{ + GtkWidget *entry; + + entry = nautilus_window_ensure_location_entry (window); + nautilus_location_entry_set_special_text (NAUTILUS_LOCATION_ENTRY (entry), + path); + gtk_editable_set_position (GTK_EDITABLE (entry), -1); +} + +static void +action_prompt_for_location_root (GSimpleAction *action, + GVariant *state, + gpointer user_data) +{ + prompt_for_location (NAUTILUS_WINDOW (user_data), "/"); +} + +static void +action_prompt_for_location_home (GSimpleAction *action, + GVariant *state, + gpointer user_data) +{ + prompt_for_location (NAUTILUS_WINDOW (user_data), "~"); +} + +static void +action_redo (GSimpleAction *action, + GVariant *state, + gpointer user_data) +{ + NautilusWindow *window = user_data; + + nautilus_file_undo_manager_redo (GTK_WINDOW (window), NULL); +} + +static void +action_undo (GSimpleAction *action, + GVariant *state, + gpointer user_data) +{ + NautilusWindow *window = user_data; + + nautilus_file_undo_manager_undo (GTK_WINDOW (window), NULL); +} + +static void +action_toggle_state_view_button (GSimpleAction *action, + GVariant *state, + gpointer user_data) +{ + GVariant *current_state; + + current_state = g_action_get_state (G_ACTION (action)); + g_action_change_state (G_ACTION (action), + g_variant_new_boolean (!g_variant_get_boolean (current_state))); + g_variant_unref (current_state); +} + +static void +action_show_current_location_menu (GSimpleAction *action, + GVariant *state, + gpointer user_data) +{ + NautilusWindow *window = user_data; + GtkWidget *path_bar; + + path_bar = nautilus_toolbar_get_path_bar (NAUTILUS_TOOLBAR (window->toolbar)); + + nautilus_path_bar_show_current_location_menu (NAUTILUS_PATH_BAR (path_bar)); +} + +static void +action_open_location (GSimpleAction *action, + GVariant *state, + gpointer user_data) +{ + NautilusWindow *window = NAUTILUS_WINDOW (user_data); + g_autoptr (GFile) folder_to_open = NULL; + + folder_to_open = g_file_new_for_uri (g_variant_get_string (state, NULL)); + + nautilus_window_open_location_full (window, folder_to_open, 0, NULL, NULL); +} + +static void +on_location_changed (NautilusWindow *window) +{ + nautilus_gtk_places_sidebar_set_location (NAUTILUS_GTK_PLACES_SIDEBAR (window->places_sidebar), + nautilus_window_slot_get_location (nautilus_window_get_active_slot (window))); +} + +static void +on_slot_location_changed (NautilusWindowSlot *slot, + GParamSpec *pspec, + NautilusWindow *window) +{ + if (nautilus_window_get_active_slot (window) == slot) + { + on_location_changed (window); + } +} + +static void +tab_view_setup_menu_cb (AdwTabView *tab_view, + AdwTabPage *page, + NautilusWindow *window) +{ + GAction *move_tab_left_action; + GAction *move_tab_right_action; + int position, n_pages; + + if (page != NULL) + { + position = adw_tab_view_get_page_position (tab_view, page); + n_pages = adw_tab_view_get_n_pages (tab_view); + } + + move_tab_left_action = g_action_map_lookup_action (G_ACTION_MAP (window), + "tab-move-left"); + move_tab_right_action = g_action_map_lookup_action (G_ACTION_MAP (window), + "tab-move-right"); + + g_simple_action_set_enabled (G_SIMPLE_ACTION (move_tab_left_action), + page == NULL || position > 0); + g_simple_action_set_enabled (G_SIMPLE_ACTION (move_tab_right_action), + page == NULL || position < n_pages - 1); + + window->menu_page = page; +} + +static void +tab_view_notify_selected_page_cb (AdwTabView *tab_view, + GParamSpec *pspec, + NautilusWindow *window) +{ + AdwTabPage *page; + NautilusWindowSlot *slot; + GtkWidget *widget; + + page = adw_tab_view_get_selected_page (tab_view); + widget = adw_tab_page_get_child (page); + + g_assert (widget != NULL); + + /* find slot corresponding to the target page */ + slot = NAUTILUS_WINDOW_SLOT (widget); + g_assert (slot != NULL); + + nautilus_window_set_active_slot (nautilus_window_slot_get_window (slot), + slot); +} + +static void +connect_slot (NautilusWindow *window, + NautilusWindowSlot *slot) +{ + g_signal_connect (slot, "notify::location", + G_CALLBACK (on_slot_location_changed), window); +} + +static void +disconnect_slot (NautilusWindow *window, + NautilusWindowSlot *slot) +{ + g_signal_handlers_disconnect_by_data (slot, window); +} + +static NautilusWindowSlot * +nautilus_window_create_and_init_slot (NautilusWindow *window, + NautilusOpenFlags flags) +{ + NautilusWindowSlot *slot; + + slot = nautilus_window_slot_new (window); + nautilus_window_initialize_slot (window, slot, flags); + + return slot; +} + +static gboolean +location_to_tooltip (GBinding *binding, + const GValue *input, + GValue *output, + NautilusWindowSlot *slot) +{ + GFile *location = g_value_get_object (input); + g_autofree gchar *location_name = NULL; + + if (location == NULL) + { + return TRUE; + } + + /* Set the tooltip on the label's parent (the tab label hbox), + * so it covers all of the tab label. + */ + location_name = g_file_get_parse_name (location); + + if (eel_uri_is_search (location_name)) + { + g_value_set_string (output, nautilus_window_slot_get_title (slot)); + } + else + { + g_value_set_string (output, location_name); + } + + return TRUE; +} + +void +nautilus_window_initialize_slot (NautilusWindow *window, + NautilusWindowSlot *slot, + NautilusOpenFlags flags) +{ + AdwTabPage *page, *current; + + g_assert (NAUTILUS_IS_WINDOW (window)); + g_assert (NAUTILUS_IS_WINDOW_SLOT (slot)); + + connect_slot (window, slot); + + current = adw_tab_view_get_selected_page (window->tab_view); + page = adw_tab_view_add_page (window->tab_view, GTK_WIDGET (slot), current); + + g_object_bind_property (slot, "allow-stop", + page, "loading", + G_BINDING_SYNC_CREATE); + g_object_bind_property (slot, "title", + page, "title", + G_BINDING_SYNC_CREATE); + g_object_bind_property_full (slot, "location", + page, "tooltip", + G_BINDING_SYNC_CREATE, + (GBindingTransformFunc) location_to_tooltip, + NULL, slot, NULL); +} + +void +nautilus_window_open_location_full (NautilusWindow *window, + GFile *location, + NautilusOpenFlags flags, + GList *selection, + NautilusWindowSlot *target_slot) +{ + NautilusWindowSlot *active_slot; + + /* Assert that we are not managing new windows */ + g_assert (!(flags & NAUTILUS_OPEN_FLAG_NEW_WINDOW)); + + active_slot = nautilus_window_get_active_slot (window); + if (!target_slot) + { + target_slot = active_slot; + } + + if (target_slot == NULL || (flags & NAUTILUS_OPEN_FLAG_NEW_TAB) != 0) + { + target_slot = nautilus_window_create_and_init_slot (window, flags); + } + + /* Make the opened location the one active if we weren't ask for the + * oposite, since it's the most usual use case */ + if (!(flags & NAUTILUS_OPEN_FLAG_DONT_MAKE_ACTIVE)) + { + gtk_window_present (GTK_WINDOW (window)); + nautilus_window_set_active_slot (window, target_slot); + } + + nautilus_window_slot_open_location_full (target_slot, location, flags, selection); +} + +static void +unset_focus_widget (NautilusWindow *window) +{ + if (window->last_focus_widget != NULL) + { + g_object_remove_weak_pointer (G_OBJECT (window->last_focus_widget), + (gpointer *) &window->last_focus_widget); + window->last_focus_widget = NULL; + } +} + +static void +remember_focus_widget (NautilusWindow *window) +{ + GtkWidget *focus_widget; + + focus_widget = gtk_window_get_focus (GTK_WINDOW (window)); + if (focus_widget != NULL) + { + unset_focus_widget (window); + + window->last_focus_widget = focus_widget; + g_object_add_weak_pointer (G_OBJECT (focus_widget), + (gpointer *) &(window->last_focus_widget)); + } +} + +static gboolean +nautilus_window_grab_focus (GtkWidget *widget) +{ + NautilusWindowSlot *slot; + + slot = nautilus_window_get_active_slot (NAUTILUS_WINDOW (widget)); + + if (slot != NULL) + { + return gtk_widget_grab_focus (GTK_WIDGET (slot)); + } + + return GTK_WIDGET_CLASS (nautilus_window_parent_class)->grab_focus (widget); +} + +static void +restore_focus_widget (NautilusWindow *window) +{ + if (window->last_focus_widget != NULL) + { + gtk_widget_grab_focus (window->last_focus_widget); + unset_focus_widget (window); + } +} + +static void +location_entry_cancel_callback (GtkWidget *widget, + NautilusWindow *window) +{ + nautilus_toolbar_set_show_location_entry (NAUTILUS_TOOLBAR (window->toolbar), FALSE); + + restore_focus_widget (window); +} + +static void +location_entry_location_changed_callback (GtkWidget *widget, + GFile *location, + NautilusWindow *window) +{ + nautilus_toolbar_set_show_location_entry (NAUTILUS_TOOLBAR (window->toolbar), FALSE); + + restore_focus_widget (window); + + nautilus_window_open_location_full (window, location, 0, NULL, NULL); +} + +static void +remove_slot_from_window (NautilusWindowSlot *slot, + NautilusWindow *window) +{ + g_return_if_fail (NAUTILUS_IS_WINDOW_SLOT (slot)); + g_return_if_fail (NAUTILUS_WINDOW (window)); + + DEBUG ("Removing slot %p", slot); + + disconnect_slot (window, slot); + window->slots = g_list_remove (window->slots, slot); + g_signal_emit (window, signals[SLOT_REMOVED], 0, slot); +} + +void +nautilus_window_new_tab (NautilusWindow *window) +{ + NautilusWindowSlot *current_slot; + GFile *location; + g_autofree gchar *uri = NULL; + + current_slot = nautilus_window_get_active_slot (window); + location = nautilus_window_slot_get_location (current_slot); + + if (location != NULL) + { + uri = g_file_get_uri (location); + if (eel_uri_is_search (uri)) + { + location = g_file_new_for_path (g_get_home_dir ()); + } + else + { + g_object_ref (location); + } + + nautilus_window_open_location_full (window, location, + NAUTILUS_OPEN_FLAG_NEW_TAB, + NULL, NULL); + g_object_unref (location); + } +} + +static void +update_cursor (NautilusWindow *window) +{ + NautilusWindowSlot *slot; + + slot = nautilus_window_get_active_slot (window); + + if (slot != NULL && + nautilus_window_slot_get_allow_stop (slot)) + { + gtk_widget_set_cursor_from_name (GTK_WIDGET (window), "progress"); + } + else + { + gtk_widget_set_cursor (GTK_WIDGET (window), NULL); + } +} + +void +nautilus_window_reset_menus (NautilusWindow *window) +{ + nautilus_window_sync_allow_stop (window, nautilus_window_get_active_slot (window)); +} + +void +nautilus_window_sync_allow_stop (NautilusWindow *window, + NautilusWindowSlot *slot) +{ + GAction *stop_action; + GAction *reload_action; + gboolean allow_stop, slot_is_active, slot_allow_stop; + + stop_action = g_action_map_lookup_action (G_ACTION_MAP (window), + "stop"); + reload_action = g_action_map_lookup_action (G_ACTION_MAP (window), + "reload"); + allow_stop = g_action_get_enabled (stop_action); + + slot_allow_stop = nautilus_window_slot_get_allow_stop (slot); + slot_is_active = (slot == nautilus_window_get_active_slot (window)); + + + if (!slot_is_active || + allow_stop != slot_allow_stop) + { + if (slot_is_active) + { + g_simple_action_set_enabled (G_SIMPLE_ACTION (stop_action), slot_allow_stop); + g_simple_action_set_enabled (G_SIMPLE_ACTION (reload_action), !slot_allow_stop); + } + if (gtk_widget_get_realized (GTK_WIDGET (window))) + { + update_cursor (window); + } + } +} + +AdwTabView * +nautilus_window_get_tab_view (NautilusWindow *window) +{ + g_return_val_if_fail (NAUTILUS_IS_WINDOW (window), NULL); + + return window->tab_view; +} + +/* Callback used when the places sidebar changes location; we need to change the displayed folder */ +static void +open_location_cb (NautilusWindow *window, + GFile *location, + NautilusGtkPlacesOpenFlags open_flags) +{ + NautilusOpenFlags flags; + NautilusApplication *application; + + switch (open_flags) + { + case NAUTILUS_GTK_PLACES_OPEN_NEW_TAB: + { + flags = NAUTILUS_OPEN_FLAG_NEW_TAB | + NAUTILUS_OPEN_FLAG_DONT_MAKE_ACTIVE; + } + break; + + case NAUTILUS_GTK_PLACES_OPEN_NEW_WINDOW: + { + flags = NAUTILUS_OPEN_FLAG_NEW_WINDOW; + } + break; + + case NAUTILUS_GTK_PLACES_OPEN_NORMAL: /* fall-through */ + default: + { + flags = 0; + } + break; + } + + application = NAUTILUS_APPLICATION (g_application_get_default ()); + /* FIXME: We shouldn't need to provide the window, but seems gtk_application_get_active_window + * is not working properly in GtkApplication, so we cannot rely on that... + */ + nautilus_application_open_location_full (application, location, flags, + NULL, window, NULL); +} + +/* Callback used when the places sidebar needs us to present an error message */ +static void +places_sidebar_show_error_message_cb (NautilusGtkPlacesSidebar *sidebar, + const char *primary, + const char *secondary, + gpointer user_data) +{ + NautilusWindow *window = NAUTILUS_WINDOW (user_data); + + show_dialog (primary, secondary, GTK_WINDOW (window), GTK_MESSAGE_ERROR); +} + +static void +places_sidebar_show_other_locations_with_flags (NautilusWindow *window, + NautilusGtkPlacesOpenFlags open_flags) +{ + GFile *location; + + location = g_file_new_for_uri ("other-locations:///"); + + open_location_cb (window, location, open_flags); + + g_object_unref (location); +} + +static void +places_sidebar_show_starred_location (NautilusWindow *window, + NautilusGtkPlacesOpenFlags open_flags) +{ + GFile *location; + + location = g_file_new_for_uri ("starred:///"); + + open_location_cb (window, location, open_flags); + + g_object_unref (location); +} + +/* Callback used when the places sidebar needs to know the drag action to suggest */ +static GdkDragAction +places_sidebar_drag_action_requested_cb (NautilusGtkPlacesSidebar *sidebar, + NautilusFile *dest_file, + GList *source_file_list) +{ + return nautilus_dnd_get_preferred_action (dest_file, source_file_list->data); +} +#if 0 && NAUTILUS_DND_NEEDS_GTK4_REIMPLEMENTATION +/* Callback used when the places sidebar needs us to pop up a menu with possible drag actions */ +static GdkDragAction +places_sidebar_drag_action_ask_cb (NautilusGtkPlacesSidebar *sidebar, + GdkDragAction actions, + gpointer user_data) +{ + return nautilus_drag_drop_action_ask (GTK_WIDGET (sidebar), actions); +} +#endif +static GList * +build_uri_list_from_gfile_list (GSList *file_list) +{ + GList *result; + GSList *l; + + result = NULL; + + for (l = file_list; l; l = l->next) + { + GFile *file = l->data; + char *uri; + + uri = g_file_get_uri (file); + result = g_list_prepend (result, uri); + } + + return g_list_reverse (result); +} + +/* Callback used when the places sidebar has URIs dropped into it. We do a normal file operation for them. */ +static void +places_sidebar_drag_perform_drop_cb (NautilusGtkPlacesSidebar *sidebar, + GFile *dest_file, + GSList *source_file_list, + GdkDragAction action, + gpointer user_data) +{ + char *dest_uri; + GList *source_uri_list; + + dest_uri = g_file_get_uri (dest_file); + source_uri_list = build_uri_list_from_gfile_list (source_file_list); + + nautilus_file_operations_copy_move (source_uri_list, dest_uri, action, GTK_WIDGET (sidebar), NULL, NULL, NULL); + + g_free (dest_uri); + g_list_free_full (source_uri_list, g_free); +} + +static void +action_restore_tab (GSimpleAction *action, + GVariant *state, + gpointer user_data) +{ + NautilusWindow *window = NAUTILUS_WINDOW (user_data); + NautilusOpenFlags flags; + g_autoptr (GFile) location = NULL; + NautilusWindowSlot *slot; + NautilusNavigationState *data; + + if (g_queue_get_length (window->tab_data_queue) == 0) + { + return; + } + + flags = NAUTILUS_OPEN_FLAG_NEW_TAB | NAUTILUS_OPEN_FLAG_DONT_MAKE_ACTIVE; + + data = g_queue_pop_head (window->tab_data_queue); + + location = nautilus_file_get_location (data->file); + + slot = nautilus_window_create_and_init_slot (window, flags); + + nautilus_window_slot_open_location_full (slot, location, flags, NULL); + nautilus_window_slot_restore_navigation_state (slot, data); + + free_navigation_state (data); +} + +static void +action_toggle_sidebar (GSimpleAction *action, + GVariant *state, + gpointer user_data) +{ + NautilusWindow *window = NAUTILUS_WINDOW (user_data); + gboolean revealed; + + revealed = adw_flap_get_reveal_flap (ADW_FLAP (window->content_flap)); + adw_flap_set_reveal_flap (ADW_FLAP (window->content_flap), !revealed); +} + + +static guint +get_window_xid (NautilusWindow *window) +{ +#ifdef GDK_WINDOWING_X11 + if (GDK_IS_X11_DISPLAY (gtk_widget_get_display (GTK_WIDGET (window)))) + { + GdkSurface *gdk_surface = gtk_native_get_surface (GTK_NATIVE (window)); + return (guint) gdk_x11_surface_get_xid (gdk_surface); + } +#endif + return 0; +} + +static void +nautilus_window_set_up_sidebar (NautilusWindow *window) +{ + nautilus_gtk_places_sidebar_set_open_flags (NAUTILUS_GTK_PLACES_SIDEBAR (window->places_sidebar), + (NAUTILUS_GTK_PLACES_OPEN_NORMAL + | NAUTILUS_GTK_PLACES_OPEN_NEW_TAB + | NAUTILUS_GTK_PLACES_OPEN_NEW_WINDOW)); + + g_signal_connect_swapped (window->places_sidebar, "open-location", + G_CALLBACK (open_location_cb), window); + g_signal_connect (window->places_sidebar, "show-error-message", + G_CALLBACK (places_sidebar_show_error_message_cb), window); + + g_signal_connect (window->places_sidebar, "drag-action-requested", + G_CALLBACK (places_sidebar_drag_action_requested_cb), window); +#if 0 && NAUTILUS_DND_NEEDS_GTK4_REIMPLEMENTATION + g_signal_connect (window->places_sidebar, "drag-action-ask", + G_CALLBACK (places_sidebar_drag_action_ask_cb), window); + #endif + g_signal_connect (window->places_sidebar, "drag-perform-drop", + G_CALLBACK (places_sidebar_drag_perform_drop_cb), window); +} + +void +nautilus_window_slot_close (NautilusWindow *window, + NautilusWindowSlot *slot) +{ + NautilusNavigationState *data; + AdwTabPage *page; + + DEBUG ("Requesting to remove slot %p from window %p", slot, window); + if (window == NULL || slot == NULL) + { + return; + } + + data = nautilus_window_slot_get_navigation_state (slot); + if (data != NULL) + { + g_queue_push_head (window->tab_data_queue, data); + } + + remove_slot_from_window (slot, window); + + page = adw_tab_view_get_page (window->tab_view, GTK_WIDGET (slot)); + /* this will destroy the slot */ + adw_tab_view_close_page (window->tab_view, page); + + /* If that was the last slot in the window, close the window. */ + if (window->slots == NULL) + { + DEBUG ("Last slot removed, closing the window"); + nautilus_window_close (window); + } +} + +static void +nautilus_window_sync_bookmarks (NautilusWindow *window) +{ + gboolean can_bookmark = FALSE; + NautilusWindowSlot *slot; + NautilusBookmarkList *bookmarks; + GAction *action; + GFile *location; + + slot = window->active_slot; + location = slot != NULL ? nautilus_window_slot_get_location (slot) : NULL; + + if (location != NULL) + { + bookmarks = nautilus_application_get_bookmarks + (NAUTILUS_APPLICATION (gtk_window_get_application (GTK_WINDOW (window)))); + can_bookmark = nautilus_bookmark_list_can_bookmark_location (bookmarks, location); + } + + action = g_action_map_lookup_action (G_ACTION_MAP (window), "bookmark-current-location"); + g_simple_action_set_enabled (G_SIMPLE_ACTION (action), can_bookmark); +} + +void +nautilus_window_sync_location_widgets (NautilusWindow *window) +{ + NautilusWindowSlot *slot; + GFile *location; + GAction *action; + gboolean enabled; + + slot = window->active_slot; + /* This function is called by the active slot. */ + g_assert (slot != NULL); + + location = nautilus_window_slot_get_location (slot); + + /* Change the location bar and path bar to match the current location. */ + if (location != NULL) + { + GtkWidget *location_entry; + GtkWidget *path_bar; + + location_entry = nautilus_toolbar_get_location_entry (NAUTILUS_TOOLBAR (window->toolbar)); + nautilus_location_entry_set_location (NAUTILUS_LOCATION_ENTRY (location_entry), location); + + path_bar = nautilus_toolbar_get_path_bar (NAUTILUS_TOOLBAR (window->toolbar)); + nautilus_path_bar_set_path (NAUTILUS_PATH_BAR (path_bar), location); + } + + enabled = nautilus_window_slot_get_back_history (slot) != NULL; + action = g_action_map_lookup_action (G_ACTION_MAP (window), "back"); + g_simple_action_set_enabled (G_SIMPLE_ACTION (action), enabled); + action = g_action_map_lookup_action (G_ACTION_MAP (window), "back-n"); + g_simple_action_set_enabled (G_SIMPLE_ACTION (action), enabled); + + enabled = nautilus_window_slot_get_forward_history (slot) != NULL; + action = g_action_map_lookup_action (G_ACTION_MAP (window), "forward"); + g_simple_action_set_enabled (G_SIMPLE_ACTION (action), enabled); + action = g_action_map_lookup_action (G_ACTION_MAP (window), "forward-n"); + g_simple_action_set_enabled (G_SIMPLE_ACTION (action), enabled); + + nautilus_window_sync_bookmarks (window); +} + +static GtkWidget * +nautilus_window_ensure_location_entry (NautilusWindow *window) +{ + GtkWidget *location_entry; + + remember_focus_widget (window); + + nautilus_toolbar_set_show_location_entry (NAUTILUS_TOOLBAR (window->toolbar), TRUE); + + location_entry = nautilus_toolbar_get_location_entry (NAUTILUS_TOOLBAR (window->toolbar)); + gtk_widget_grab_focus (location_entry); + + return location_entry; +} + +static gchar * +toast_undo_deleted_get_label (NautilusFileUndoInfo *undo_info) +{ + GList *files; + gchar *file_label; + gchar *label; + gint length; + + files = nautilus_file_undo_info_trash_get_files (NAUTILUS_FILE_UNDO_INFO_TRASH (undo_info)); + length = g_list_length (files); + if (length == 1) + { + file_label = g_file_get_basename (files->data); + /* Translators: only one item has been deleted and %s is its name. */ + label = g_markup_printf_escaped (_("“%s” moved to trash"), file_label); + g_free (file_label); + } + else + { + /* Translators: one or more items might have been deleted, and %d + * is the count. */ + label = g_markup_printf_escaped (ngettext ("%d file deleted", "%d files deleted", length), length); + } + + return label; +} + +static gchar * +toast_undo_unstar_get_label (NautilusFileUndoInfo *undo_info) +{ + GList *files; + gchar *label; + gint length; + + files = nautilus_file_undo_info_starred_get_files (NAUTILUS_FILE_UNDO_INFO_STARRED (undo_info)); + length = g_list_length (files); + if (length == 1) + { + g_autofree gchar *file_label = NULL; + + file_label = nautilus_file_get_display_name (files->data); + /* Translators: one item has been unstarred and %s is its name. */ + label = g_markup_printf_escaped (_("“%s” unstarred"), file_label); + } + else + { + /* Translators: one or more items have been unstarred, and %d + * is the count. */ + label = g_markup_printf_escaped (ngettext ("%d file unstarred", "%d files unstarred", length), length); + } + + return label; +} + +static void +nautilus_window_on_undo_changed (NautilusFileUndoManager *manager, + NautilusWindow *window) +{ + NautilusFileUndoInfo *undo_info; + NautilusFileUndoManagerState state; + AdwToast *toast; + + undo_info = nautilus_file_undo_manager_get_action (); + state = nautilus_file_undo_manager_get_state (); + + if (undo_info != NULL && + state == NAUTILUS_FILE_UNDO_MANAGER_STATE_UNDO) + { + gboolean popup_toast = FALSE; + g_autofree gchar *label = NULL; + + if (nautilus_file_undo_info_get_op_type (undo_info) == NAUTILUS_FILE_UNDO_OP_MOVE_TO_TRASH) + { + g_autoptr (GList) files = NULL; + + files = nautilus_file_undo_info_trash_get_files (NAUTILUS_FILE_UNDO_INFO_TRASH (undo_info)); + + /* Don't pop up a notification if user canceled the operation or the focus + * is not in the this window. This is an easy way to know from which window + * was the delete operation made */ + if (files != NULL && gtk_window_is_active (GTK_WINDOW (window))) + { + popup_toast = TRUE; + label = toast_undo_deleted_get_label (undo_info); + } + } + else if (nautilus_file_undo_info_get_op_type (undo_info) == NAUTILUS_FILE_UNDO_OP_STARRED) + { + NautilusWindowSlot *active_slot; + GFile *location; + + active_slot = nautilus_window_get_active_slot (window); + location = nautilus_window_slot_get_location (active_slot); + /* Don't pop up a notification if the focus is not in the this + * window. This is an easy way to know from which window was the + * unstart operation made */ + if (eel_uri_is_starred (g_file_get_uri (location)) && + gtk_window_is_active (GTK_WINDOW (window)) && + !nautilus_file_undo_info_starred_is_starred (NAUTILUS_FILE_UNDO_INFO_STARRED (undo_info))) + { + popup_toast = TRUE; + label = toast_undo_unstar_get_label (undo_info); + } + } + + if (popup_toast) + { + toast = adw_toast_new (label); + adw_toast_set_button_label (toast, _("Undo")); + adw_toast_set_action_name (toast, "win.undo"); + adw_toast_set_priority (toast, ADW_TOAST_PRIORITY_HIGH); + adw_toast_overlay_add_toast (window->toast_overlay, toast); + } + } +} + +void +nautilus_window_show_operation_notification (NautilusWindow *window, + gchar *main_label, + GFile *folder_to_open) +{ + gchar *button_label; + gchar *folder_name; + NautilusFile *folder; + GVariant *target; + GFile *current_location; + AdwToast *toast; + + if (window->active_slot == NULL) + { + return; + } + + toast = adw_toast_new (main_label); + adw_toast_set_priority (toast, ADW_TOAST_PRIORITY_HIGH); + + current_location = nautilus_window_slot_get_location (window->active_slot); + if (gtk_window_is_active (GTK_WINDOW (window))) + { + if (!g_file_equal (folder_to_open, current_location)) + { + target = g_variant_new_take_string (g_file_get_uri (folder_to_open)); + folder = nautilus_file_get (folder_to_open); + folder_name = nautilus_file_get_display_name (folder); + button_label = g_strdup_printf (_("Open %s"), folder_name); + adw_toast_set_button_label (toast, button_label); + adw_toast_set_action_name (toast, "win.open-location"); + adw_toast_set_action_target_value (toast, target); + nautilus_file_unref (folder); + g_free (folder_name); + g_free (button_label); + } + + adw_toast_overlay_add_toast (window->toast_overlay, toast); + } +} + +static void +on_path_bar_open_location (NautilusWindow *window, + GFile *location, + NautilusOpenFlags open_flags) +{ + if (open_flags & NAUTILUS_OPEN_FLAG_NEW_WINDOW) + { + nautilus_application_open_location_full (NAUTILUS_APPLICATION (g_application_get_default ()), + location, NAUTILUS_OPEN_FLAG_NEW_WINDOW, NULL, NULL, NULL); + } + else + { + nautilus_window_open_location_full (window, location, open_flags, NULL, NULL); + } +} + +GtkWidget * +nautilus_window_get_toolbar (NautilusWindow *window) +{ + g_return_val_if_fail (NAUTILUS_IS_WINDOW (window), NULL); + + return window->toolbar; +} + +static void +setup_toolbar (NautilusWindow *window) +{ + GtkWidget *path_bar; + GtkWidget *location_entry; + + /* connect to the pathbar signals */ + path_bar = nautilus_toolbar_get_path_bar (NAUTILUS_TOOLBAR (window->toolbar)); + + g_signal_connect_swapped (path_bar, "open-location", + G_CALLBACK (on_path_bar_open_location), window); + + /* connect to the location entry signals */ + location_entry = nautilus_toolbar_get_location_entry (NAUTILUS_TOOLBAR (window->toolbar)); + + g_signal_connect_object (location_entry, "location-changed", + G_CALLBACK (location_entry_location_changed_callback), window, 0); + g_signal_connect_object (location_entry, "cancel", + G_CALLBACK (location_entry_cancel_callback), window, 0); +} + +static gboolean +tab_view_close_page_cb (AdwTabView *view, + AdwTabPage *page, + NautilusWindow *window) +{ + NautilusWindowSlot *slot; + + slot = NAUTILUS_WINDOW_SLOT (adw_tab_page_get_child (page)); + + nautilus_window_slot_close (window, slot); + + return GDK_EVENT_PROPAGATE; +} + +static void +tab_view_page_detached_cb (AdwTabView *tab_view, + AdwTabPage *page, + gint position, + NautilusWindow *window) +{ + NautilusWindowSlot *slot = NAUTILUS_WINDOW_SLOT (adw_tab_page_get_child (page)); + + /* If the tab has been moved to another window, we need to remove the slot + * from the current window here. Otherwise, if the tab has been closed, then + * we have*/ + if (g_list_find (window->slots, slot)) + { + remove_slot_from_window (slot, window); + } +} + +static void +tab_view_page_attached_cb (AdwTabView *tab_view, + AdwTabPage *page, + gint position, + NautilusWindow *window) +{ + NautilusWindowSlot *slot = NAUTILUS_WINDOW_SLOT (adw_tab_page_get_child (page)); + + nautilus_window_slot_set_window (slot, window); + window->slots = g_list_append (window->slots, slot); + g_signal_emit (window, signals[SLOT_ADDED], 0, slot); +} + +static AdwTabView * +tab_view_create_window_cb (AdwTabView *tab_view, + NautilusWindow *window) +{ + NautilusApplication *app; + NautilusWindow *new_window; + + app = NAUTILUS_APPLICATION (g_application_get_default ()); + new_window = nautilus_application_create_window (app); + gtk_window_set_display (GTK_WINDOW (new_window), + gtk_widget_get_display (GTK_WIDGET (tab_view))); + + gtk_window_present (GTK_WINDOW (new_window)); + + return new_window->tab_view; +} + +static void +setup_tab_view (NautilusWindow *window) +{ + g_signal_connect (window->tab_view, "close-page", + G_CALLBACK (tab_view_close_page_cb), + window); + g_signal_connect (window->tab_view, "setup-menu", + G_CALLBACK (tab_view_setup_menu_cb), + window); + g_signal_connect (window->tab_view, "notify::selected-page", + G_CALLBACK (tab_view_notify_selected_page_cb), + window); + g_signal_connect (window->tab_view, "create-window", + G_CALLBACK (tab_view_create_window_cb), + window); + g_signal_connect (window->tab_view, "page-attached", + G_CALLBACK (tab_view_page_attached_cb), + window); + g_signal_connect (window->tab_view, "page-detached", + G_CALLBACK (tab_view_page_detached_cb), + window); +} + +const GActionEntry win_entries[] = +{ + { "back", action_back }, + { "forward", action_forward }, + { "back-n", action_back_n, "u" }, + { "forward-n", action_forward_n, "u" }, + { "up", action_up }, + { "view-menu", action_toggle_state_view_button, NULL, "false", NULL }, + { "current-location-menu", action_show_current_location_menu }, + { "open-location", action_open_location, "s" }, + { "reload", action_reload }, + { "stop", action_stop }, + { "new-tab", action_new_tab }, + { "enter-location", action_enter_location }, + { "bookmark-current-location", action_bookmark_current_location }, + { "undo", action_undo }, + { "redo", action_redo }, + /* Only accesible by shorcuts */ + { "close-current-view", action_close_current_view }, + { "go-home", action_go_home }, + { "go-starred", action_go_starred }, + { "tab-move-left", action_tab_move_left }, + { "tab-move-right", action_tab_move_right }, + { "prompt-root-location", action_prompt_for_location_root }, + { "prompt-home-location", action_prompt_for_location_home }, + { "go-to-tab", NULL, "i", "0", action_go_to_tab }, + { "restore-tab", action_restore_tab }, + { "toggle-sidebar", action_toggle_sidebar }, +}; + +static void +nautilus_window_initialize_actions (NautilusWindow *window) +{ + GApplication *app; + GAction *action; + gchar detailed_action[80]; + gchar accel[80]; + gint i; + + g_action_map_add_action_entries (G_ACTION_MAP (window), + win_entries, G_N_ELEMENTS (win_entries), + window); + +#define ACCELS(...) ((const char *[]) { __VA_ARGS__, NULL }) + + app = g_application_get_default (); + nautilus_application_set_accelerators (app, "win.back", ACCELS ("Left", "Back")); + nautilus_application_set_accelerators (app, "win.forward", ACCELS ("Right", "Forward")); + nautilus_application_set_accelerators (app, "win.enter-location", ACCELS ("l", "Go", "OpenURL")); + nautilus_application_set_accelerator (app, "win.new-tab", "t"); + nautilus_application_set_accelerator (app, "win.close-current-view", "w"); + + /* Special case reload, since users are used to use two shortcuts instead of one */ + nautilus_application_set_accelerators (app, "win.reload", ACCELS ("F5", "r", "Refresh", "Reload")); + nautilus_application_set_accelerator (app, "win.stop", "Stop"); + + nautilus_application_set_accelerator (app, "win.undo", "z"); + nautilus_application_set_accelerator (app, "win.redo", "z"); + /* Only accesible by shorcuts */ + nautilus_application_set_accelerators (app, "win.bookmark-current-location", ACCELS ("d", "AddFavorite")); + nautilus_application_set_accelerator (app, "win.up", "Up"); + nautilus_application_set_accelerators (app, "win.go-home", ACCELS ("Home", "HomePage", "Start")); + nautilus_application_set_accelerator (app, "win.go-starred", "Favorites"); + nautilus_application_set_accelerator (app, "win.tab-move-left", "Page_Up"); + nautilus_application_set_accelerator (app, "win.tab-move-right", "Page_Down"); + nautilus_application_set_accelerators (app, "win.prompt-root-location", ACCELS ("slash", "KP_Divide")); + /* Support keyboard layouts which have a dead tilde key but not a tilde key. */ + nautilus_application_set_accelerators (app, "win.prompt-home-location", ACCELS ("asciitilde", "dead_tilde")); + nautilus_application_set_accelerator (app, "win.current-location-menu", "F10"); + nautilus_application_set_accelerator (app, "win.restore-tab", "t"); + nautilus_application_set_accelerator (app, "win.toggle-sidebar", "F9"); + + /* Alt+N for the first 9 tabs */ + for (i = 0; i < 9; ++i) + { + g_snprintf (detailed_action, sizeof (detailed_action), "win.go-to-tab(%i)", i); + g_snprintf (accel, sizeof (accel), "%i", i + 1); + nautilus_application_set_accelerator (app, detailed_action, accel); + } + +#undef ACCELS + + action = g_action_map_lookup_action (G_ACTION_MAP (window), "toggle-sidebar"); + g_object_bind_property (window->content_flap, "folded", + action, "enabled", G_BINDING_SYNC_CREATE); +} + + +static void +nautilus_window_constructed (GObject *self) +{ + NautilusWindow *window; + NautilusApplication *application; + + window = NAUTILUS_WINDOW (self); + + nautilus_profile_start (NULL); + + G_OBJECT_CLASS (nautilus_window_parent_class)->constructed (self); + + application = NAUTILUS_APPLICATION (g_application_get_default ()); + gtk_window_set_application (GTK_WINDOW (window), GTK_APPLICATION (application)); + + setup_toolbar (window); + + gtk_window_set_default_size (GTK_WINDOW (window), + NAUTILUS_WINDOW_DEFAULT_WIDTH, + NAUTILUS_WINDOW_DEFAULT_HEIGHT); + + setup_tab_view (window); + nautilus_window_set_up_sidebar (window); + + + g_signal_connect_object (nautilus_file_undo_manager_get (), "undo-changed", + G_CALLBACK (nautilus_window_on_undo_changed), self, + G_CONNECT_AFTER); + + /* Is required that the UI is constructed before initializating the actions, since + * some actions trigger UI widgets to show/hide. */ + nautilus_window_initialize_actions (window); + + window->bookmarks_id = g_signal_connect_object (nautilus_application_get_bookmarks (application), + "changed", + G_CALLBACK (nautilus_window_sync_bookmarks), + window, G_CONNECT_SWAPPED); + + nautilus_toolbar_on_window_constructed (NAUTILUS_TOOLBAR (window->toolbar)); + + nautilus_profile_end (NULL); +} + +static void +nautilus_window_dispose (GObject *object) +{ + NautilusWindow *window; + GtkApplication *application; + GList *slots_copy; + + window = NAUTILUS_WINDOW (object); + application = gtk_window_get_application (GTK_WINDOW (window)); + + DEBUG ("Destroying window"); + + /* close all slots safely */ + slots_copy = g_list_copy (window->slots); + g_list_foreach (slots_copy, (GFunc) remove_slot_from_window, window); + g_list_free (slots_copy); + + /* the slots list should now be empty */ + g_assert (window->slots == NULL); + + g_clear_weak_pointer (&window->active_slot); + + if (application != NULL) + { + g_clear_signal_handler (&window->bookmarks_id, + nautilus_application_get_bookmarks (NAUTILUS_APPLICATION (application))); + } + + nautilus_window_unexport_handle (window); + + G_OBJECT_CLASS (nautilus_window_parent_class)->dispose (object); +} + +static void +nautilus_window_finalize (GObject *object) +{ + NautilusWindow *window; + + window = NAUTILUS_WINDOW (object); + + if (window->sidebar_width_handler_id != 0) + { + g_source_remove (window->sidebar_width_handler_id); + window->sidebar_width_handler_id = 0; + } + + g_clear_object (&window->selected_file); + g_clear_object (&window->selected_volume); + + g_signal_handlers_disconnect_by_func (nautilus_file_undo_manager_get (), + G_CALLBACK (nautilus_window_on_undo_changed), + window); + + g_queue_free_full (window->tab_data_queue, free_navigation_state); + + /* nautilus_window_close() should have run */ + g_assert (window->slots == NULL); + + G_OBJECT_CLASS (nautilus_window_parent_class)->finalize (object); +} + +static void +nautilus_window_save_geometry (NautilusWindow *window) +{ + gint width; + gint height; + GVariant *initial_size; + + gtk_window_get_default_size (GTK_WINDOW (window), &width, &height); + initial_size = g_variant_new_parsed ("(%i, %i)", width, height); + + g_settings_set_value (nautilus_window_state, + NAUTILUS_WINDOW_STATE_INITIAL_SIZE, + initial_size); +} + +void +nautilus_window_close (NautilusWindow *window) +{ + g_return_if_fail (NAUTILUS_IS_WINDOW (window)); + + nautilus_window_save_geometry (window); + nautilus_window_set_active_slot (window, NULL); + + /* The pad controller hold a reference to the window, creating a cycle. + * Usually, reference cycles are resolved in dispose(), but GTK removes the + * controllers in finalize(), so our only option is to manually remove it + * here before starting the destruction of the window. */ + if (window->pad_controller != NULL) + { + gtk_widget_remove_controller (GTK_WIDGET (window), + GTK_EVENT_CONTROLLER (window->pad_controller)); + g_clear_weak_pointer (&window->pad_controller); + } + + gtk_window_destroy (GTK_WINDOW (window)); +} + +void +nautilus_window_set_active_slot (NautilusWindow *window, + NautilusWindowSlot *new_slot) +{ + NautilusWindowSlot *old_slot; + + g_assert (NAUTILUS_IS_WINDOW (window)); + + if (new_slot) + { + g_assert ((window == nautilus_window_slot_get_window (new_slot))); + } + + old_slot = nautilus_window_get_active_slot (window); + + if (old_slot == new_slot) + { + return; + } + + DEBUG ("Setting new slot %p as active, old slot inactive %p", new_slot, old_slot); + + /* make old slot inactive if it exists (may be NULL after init, for example) */ + if (old_slot != NULL) + { + /* inform slot & view */ + nautilus_window_slot_set_active (old_slot, FALSE); + } + + g_set_weak_pointer (&window->active_slot, new_slot); + + /* make new slot active, if it exists */ + if (new_slot) + { + /* inform slot & view */ + nautilus_window_slot_set_active (new_slot, TRUE); + + on_location_changed (window); + } + + g_object_notify_by_pspec (G_OBJECT (window), properties[PROP_ACTIVE_SLOT]); +} + +static void +nautilus_window_realize (GtkWidget *widget) +{ + GTK_WIDGET_CLASS (nautilus_window_parent_class)->realize (widget); + update_cursor (NAUTILUS_WINDOW (widget)); +} + +static gboolean +nautilus_window_key_capture (GtkEventControllerKey *controller, + unsigned int keyval, + unsigned int keycode, + GdkModifierType state, + gpointer user_data) +{ + GtkWidget *widget; + GtkWidget *focus_widget; + + widget = gtk_event_controller_get_widget (GTK_EVENT_CONTROLLER (controller)); + focus_widget = gtk_window_get_focus (GTK_WINDOW (widget)); + if (focus_widget != NULL && GTK_IS_EDITABLE (focus_widget)) + { + /* if we have input focus on a GtkEditable (e.g. a GtkEntry), forward + * the event to it before activating accelerators. This allows, e.g., + * typing a tilde without activating the prompt-home-location action. + */ + if (gtk_event_controller_key_forward (controller, focus_widget)) + { + return GDK_EVENT_STOP; + } + } + + return GDK_EVENT_PROPAGATE; +} + +static gboolean +nautilus_window_key_bubble (GtkEventControllerKey *controller, + unsigned int keyval, + unsigned int keycode, + GdkModifierType state, + gpointer user_data) +{ + GtkWidget *widget; + NautilusWindow *window; + + widget = gtk_event_controller_get_widget (GTK_EVENT_CONTROLLER (controller)); + window = NAUTILUS_WINDOW (widget); + if (window->active_slot != NULL && + nautilus_window_slot_handle_event (window->active_slot, controller, keyval, state)) + { + return GDK_EVENT_STOP; + } + + return GDK_EVENT_PROPAGATE; +} + +void +nautilus_window_sync_title (NautilusWindow *window, + NautilusWindowSlot *slot) +{ + g_return_if_fail (NAUTILUS_IS_WINDOW (window)); + g_return_if_fail (NAUTILUS_IS_WINDOW_SLOT (slot)); + + if (slot == nautilus_window_get_active_slot (window)) + { + gtk_window_set_title (GTK_WINDOW (window), nautilus_window_slot_get_title (slot)); + } +} + +#ifdef GDK_WINDOWING_WAYLAND +typedef struct +{ + NautilusWindow *window; + NautilusWindowHandleExported callback; + gpointer user_data; +} WaylandWindowHandleExportedData; + +static void +wayland_window_handle_exported (GdkToplevel *toplevel, + const char *wayland_handle_str, + gpointer user_data) +{ + WaylandWindowHandleExportedData *data = user_data; + + data->window->export_handle = g_strdup_printf ("wayland:%s", wayland_handle_str); + data->callback (data->window, data->window->export_handle, 0, data->user_data); +} +#endif + +gboolean +nautilus_window_export_handle (NautilusWindow *window, + NautilusWindowHandleExported callback, + gpointer user_data) +{ + guint xid = get_window_xid (window); + + if (window->export_handle != NULL) + { + callback (window, window->export_handle, xid, user_data); + return TRUE; + } + +#ifdef GDK_WINDOWING_X11 + if (GDK_IS_X11_DISPLAY (gtk_widget_get_display (GTK_WIDGET (window)))) + { + window->export_handle = g_strdup_printf ("x11:%x", xid); + callback (window, window->export_handle, xid, user_data); + + return TRUE; + } +#endif +#ifdef GDK_WINDOWING_WAYLAND + if (GDK_IS_WAYLAND_DISPLAY (gtk_widget_get_display (GTK_WIDGET (window)))) + { + GdkSurface *gdk_surface = gtk_native_get_surface (GTK_NATIVE (window)); + WaylandWindowHandleExportedData *data; + + data = g_new0 (WaylandWindowHandleExportedData, 1); + data->window = window; + data->callback = callback; + data->user_data = user_data; + + if (!gdk_wayland_toplevel_export_handle (GDK_WAYLAND_TOPLEVEL (gdk_surface), + wayland_window_handle_exported, + data, + g_free)) + { + g_free (data); + return FALSE; + } + else + { + return TRUE; + } + } +#endif + + g_warning ("Couldn't export handle, unsupported windowing system"); + + return FALSE; +} + +void +nautilus_window_unexport_handle (NautilusWindow *window) +{ + if (window->export_handle == NULL) + { + return; + } + +#ifdef GDK_WINDOWING_WAYLAND + if (GDK_IS_WAYLAND_DISPLAY (gtk_widget_get_display (GTK_WIDGET (window)))) + { + GdkSurface *gdk_surface = gtk_native_get_surface (GTK_NATIVE (window)); + if (GDK_IS_WAYLAND_TOPLEVEL (gdk_surface)) + { + gdk_wayland_toplevel_unexport_handle (GDK_WAYLAND_TOPLEVEL (gdk_surface)); + } + } +#endif + + g_clear_pointer (&window->export_handle, g_free); +} + +/** + * nautilus_window_show: + * @widget: GtkWidget + * + * Call parent and then show/hide window items + * base on user prefs. + */ +static void +nautilus_window_show (GtkWidget *widget) +{ + GTK_WIDGET_CLASS (nautilus_window_parent_class)->show (widget); +} + +NautilusWindowSlot * +nautilus_window_get_active_slot (NautilusWindow *window) +{ + g_assert (NAUTILUS_IS_WINDOW (window)); + + return window->active_slot; +} + +GList * +nautilus_window_get_slots (NautilusWindow *window) +{ + g_assert (NAUTILUS_IS_WINDOW (window)); + + return window->slots; +} + +static void +on_is_maximized_changed (GObject *object, + GParamSpec *pspec, + gpointer user_data) +{ + gboolean is_maximized; + + is_maximized = gtk_window_is_maximized (GTK_WINDOW (object)); + + g_settings_set_boolean (nautilus_window_state, + NAUTILUS_WINDOW_STATE_MAXIMIZED, is_maximized); +} + +static gboolean +nautilus_window_close_request (GtkWindow *window) +{ + nautilus_window_close (NAUTILUS_WINDOW (window)); + return FALSE; +} + +static void +nautilus_window_back_or_forward (NautilusWindow *window, + gboolean back, + guint distance) +{ + NautilusWindowSlot *slot; + + slot = nautilus_window_get_active_slot (window); + + if (slot != NULL) + { + nautilus_window_slot_back_or_forward (slot, back, distance); + } +} + +void +nautilus_window_back_or_forward_in_new_tab (NautilusWindow *window, + NautilusNavigationDirection direction) +{ + GFile *location; + NautilusWindowSlot *window_slot; + NautilusWindowSlot *new_slot; + NautilusNavigationState *state; + + window_slot = nautilus_window_get_active_slot (window); + new_slot = nautilus_window_slot_new (window); + state = nautilus_window_slot_get_navigation_state (window_slot); + + /* Manually fix up the back / forward lists and location. + * This way we don't have to unnecessary load the current location + * and then load back / forward */ + switch (direction) + { + case NAUTILUS_NAVIGATION_DIRECTION_BACK: + { + state->forward_list = g_list_prepend (state->forward_list, state->current_location_bookmark); + state->current_location_bookmark = state->back_list->data; + state->back_list = state->back_list->next; + } + break; + + case NAUTILUS_NAVIGATION_DIRECTION_FORWARD: + { + state->back_list = g_list_prepend (state->back_list, state->current_location_bookmark); + state->current_location_bookmark = state->forward_list->data; + state->forward_list = state->forward_list->next; + } + break; + + default: + { + g_assert_not_reached (); + } + } + + location = nautilus_bookmark_get_location (state->current_location_bookmark); + nautilus_window_initialize_slot (window, new_slot, NAUTILUS_OPEN_FLAG_NEW_TAB); + nautilus_window_slot_open_location_full (new_slot, location, 0, NULL); + nautilus_window_slot_restore_navigation_state (new_slot, state); + + free_navigation_state (state); +} + +static void +on_click_gesture_pressed (GtkGestureClick *gesture, + gint n_press, + gdouble x, + gdouble y, + gpointer user_data) +{ + GtkWidget *widget; + NautilusWindow *window; + guint button; + + widget = gtk_event_controller_get_widget (GTK_EVENT_CONTROLLER (gesture)); + window = NAUTILUS_WINDOW (widget); + button = gtk_gesture_single_get_current_button (GTK_GESTURE_SINGLE (gesture)); + + if (mouse_extra_buttons && (button == mouse_back_button)) + { + nautilus_window_back_or_forward (window, TRUE, 0); + } + else if (mouse_extra_buttons && (button == mouse_forward_button)) + { + nautilus_window_back_or_forward (window, FALSE, 0); + } +} + +static void +mouse_back_button_changed (gpointer callback_data) +{ + int new_back_button; + + new_back_button = g_settings_get_int (nautilus_preferences, NAUTILUS_PREFERENCES_MOUSE_BACK_BUTTON); + + /* Bounds checking */ + if (new_back_button < 6 || new_back_button > UPPER_MOUSE_LIMIT) + { + return; + } + + mouse_back_button = new_back_button; +} + +static void +mouse_forward_button_changed (gpointer callback_data) +{ + int new_forward_button; + + new_forward_button = g_settings_get_int (nautilus_preferences, NAUTILUS_PREFERENCES_MOUSE_FORWARD_BUTTON); + + /* Bounds checking */ + if (new_forward_button < 6 || new_forward_button > UPPER_MOUSE_LIMIT) + { + return; + } + + mouse_forward_button = new_forward_button; +} + +static void +use_extra_mouse_buttons_changed (gpointer callback_data) +{ + mouse_extra_buttons = g_settings_get_boolean (nautilus_preferences, NAUTILUS_PREFERENCES_MOUSE_USE_EXTRA_BUTTONS); +} + +static void +nautilus_window_init (NautilusWindow *window) +{ + GtkWindowGroup *window_group; + GtkEventController *controller; + GtkPadController *pad_controller; + + g_type_ensure (NAUTILUS_TYPE_TOOLBAR); + g_type_ensure (NAUTILUS_TYPE_GTK_PLACES_SIDEBAR); + gtk_widget_init_template (GTK_WIDGET (window)); + + g_signal_connect_object (window->places_sidebar, + "show-other-locations-with-flags", + G_CALLBACK (places_sidebar_show_other_locations_with_flags), + window, + G_CONNECT_SWAPPED); + g_signal_connect_object (window->places_sidebar, + "show-starred-location", + G_CALLBACK (places_sidebar_show_starred_location), + window, + G_CONNECT_SWAPPED); + + g_signal_connect (window, "notify::maximized", + G_CALLBACK (on_is_maximized_changed), NULL); + + window->slots = NULL; + window->active_slot = NULL; + + gtk_style_context_add_class (gtk_widget_get_style_context (GTK_WIDGET (window)), + "nautilus-window"); + + window_group = gtk_window_group_new (); + gtk_window_group_add_window (window_group, GTK_WINDOW (window)); + g_object_unref (window_group); + + window->tab_data_queue = g_queue_new (); + + /* Attention: this creates a reference cycle: the pad controller owns a + * reference to the window (as an action group) and the window (as a widget) + * owns a reference to the pad controller. To break this, we must remove + * the controller from the window before destroying the window. But we need + * to know the controller is still alive before trying to remove it, so a + * weak reference is added. */ + pad_controller = gtk_pad_controller_new (G_ACTION_GROUP (window), NULL); + g_set_weak_pointer (&window->pad_controller, pad_controller); + gtk_pad_controller_set_action_entries (window->pad_controller, + pad_actions, G_N_ELEMENTS (pad_actions)); + gtk_widget_add_controller (GTK_WIDGET (window), + GTK_EVENT_CONTROLLER (window->pad_controller)); + + controller = GTK_EVENT_CONTROLLER (gtk_gesture_click_new ()); + gtk_widget_add_controller (GTK_WIDGET (window), controller); + gtk_event_controller_set_propagation_phase (controller, GTK_PHASE_CAPTURE); + gtk_gesture_single_set_button (GTK_GESTURE_SINGLE (controller), 0); + g_signal_connect (controller, "pressed", + G_CALLBACK (on_click_gesture_pressed), NULL); + + controller = gtk_event_controller_key_new (); + gtk_widget_add_controller (GTK_WIDGET (window), controller); + gtk_event_controller_set_propagation_phase (controller, GTK_PHASE_CAPTURE); + g_signal_connect (controller, "key-pressed", + G_CALLBACK (nautilus_window_key_capture), NULL); + + controller = gtk_event_controller_key_new (); + gtk_widget_add_controller (GTK_WIDGET (window), controller); + gtk_event_controller_set_propagation_phase (controller, GTK_PHASE_BUBBLE); + g_signal_connect (controller, "key-pressed", + G_CALLBACK (nautilus_window_key_bubble), NULL); +} + +static void +nautilus_window_get_property (GObject *object, + guint prop_id, + GValue *value, + GParamSpec *pspec) +{ + NautilusWindow *self = NAUTILUS_WINDOW (object); + + switch (prop_id) + { + case PROP_ACTIVE_SLOT: + { + g_value_set_object (value, G_OBJECT (nautilus_window_get_active_slot (self))); + } + break; + + default: + { + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); + } + } +} + +static void +nautilus_window_set_property (GObject *object, + guint prop_id, + const GValue *value, + GParamSpec *pspec) +{ + NautilusWindow *self = NAUTILUS_WINDOW (object); + + switch (prop_id) + { + case PROP_ACTIVE_SLOT: + { + nautilus_window_set_active_slot (self, NAUTILUS_WINDOW_SLOT (g_value_get_object (value))); + } + break; + + default: + { + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); + } + } +} + +static void +nautilus_window_class_init (NautilusWindowClass *class) +{ + GObjectClass *oclass = G_OBJECT_CLASS (class); + GtkWidgetClass *wclass = GTK_WIDGET_CLASS (class); + GtkWindowClass *winclass = GTK_WINDOW_CLASS (class); + + oclass->dispose = nautilus_window_dispose; + oclass->finalize = nautilus_window_finalize; + oclass->constructed = nautilus_window_constructed; + oclass->get_property = nautilus_window_get_property; + oclass->set_property = nautilus_window_set_property; + + wclass->show = nautilus_window_show; + wclass->realize = nautilus_window_realize; + wclass->grab_focus = nautilus_window_grab_focus; + + winclass->close_request = nautilus_window_close_request; + + properties[PROP_ACTIVE_SLOT] = + g_param_spec_object ("active-slot", + NULL, NULL, + NAUTILUS_TYPE_WINDOW_SLOT, + G_PARAM_READWRITE | G_PARAM_EXPLICIT_NOTIFY | G_PARAM_STATIC_STRINGS); + + g_object_class_install_properties (oclass, N_PROPS, properties); + + gtk_widget_class_set_template_from_resource (wclass, + "/org/gnome/nautilus/ui/nautilus-window.ui"); + gtk_widget_class_bind_template_child (wclass, NautilusWindow, toolbar); + gtk_widget_class_bind_template_child (wclass, NautilusWindow, content_flap); + gtk_widget_class_bind_template_child (wclass, NautilusWindow, places_sidebar); + gtk_widget_class_bind_template_child (wclass, NautilusWindow, toast_overlay); + gtk_widget_class_bind_template_child (wclass, NautilusWindow, tab_view); + + signals[SLOT_ADDED] = + g_signal_new ("slot-added", + G_TYPE_FROM_CLASS (class), + G_SIGNAL_RUN_LAST | G_SIGNAL_ACTION, + 0, + NULL, NULL, + g_cclosure_marshal_VOID__OBJECT, + G_TYPE_NONE, 1, NAUTILUS_TYPE_WINDOW_SLOT); + signals[SLOT_REMOVED] = + g_signal_new ("slot-removed", + G_TYPE_FROM_CLASS (class), + G_SIGNAL_RUN_LAST | G_SIGNAL_ACTION, + 0, + NULL, NULL, + g_cclosure_marshal_VOID__OBJECT, + G_TYPE_NONE, 1, NAUTILUS_TYPE_WINDOW_SLOT); + + g_signal_connect_swapped (nautilus_preferences, + "changed::" NAUTILUS_PREFERENCES_MOUSE_BACK_BUTTON, + G_CALLBACK (mouse_back_button_changed), + NULL); + + g_signal_connect_swapped (nautilus_preferences, + "changed::" NAUTILUS_PREFERENCES_MOUSE_FORWARD_BUTTON, + G_CALLBACK (mouse_forward_button_changed), + NULL); + + g_signal_connect_swapped (nautilus_preferences, + "changed::" NAUTILUS_PREFERENCES_MOUSE_USE_EXTRA_BUTTONS, + G_CALLBACK (use_extra_mouse_buttons_changed), + NULL); +} + +NautilusWindow * +nautilus_window_new (void) +{ + return g_object_new (NAUTILUS_TYPE_WINDOW, + "icon-name", APPLICATION_ID, + NULL); +} + +void +nautilus_window_show_about_dialog (NautilusWindow *window) +{ + g_autofree gchar *module_names = nautilus_module_get_installed_module_names (); + g_autofree gchar *debug_info = NULL; + + const gchar *designers[] = + { + "The GNOME Project", + NULL + }; + const gchar *developers[] = + { + "The contributors to the Nautilus project", + NULL + }; + const gchar *documenters[] = + { + "GNOME Documentation Team", + "Sun Microsystems", + NULL + }; + + if (module_names == NULL) + { + debug_info = g_strdup (_("No plugins currently installed.")); + } + else + { + debug_info = g_strconcat (_("Currently installed plugins:"), "\n\n", + module_names, "\n\n", + _("For bug testing only, the following command can be used:"), "\n" + "NAUTILUS_DISABLE_PLUGINS=TRUE nautilus", NULL); + } + + adw_show_about_window (window ? GTK_WINDOW (window) : NULL, + "application-name", _("Files"), + "application-icon", APPLICATION_ID, + "developer-name", _("The GNOME Project"), + "version", VERSION, + "website", "https://wiki.gnome.org/action/show/Apps/Files", + "issue-url", "https://gitlab.gnome.org/GNOME/nautilus/-/issues/new", + "debug-info", debug_info, + "copyright", "© 1999 The Files Authors", + "license-type", GTK_LICENSE_GPL_3_0, + "designers", designers, + "developers", developers, + "documenters", documenters, + /* Translators should localize the following string + * which will be displayed at the bottom of the about + * box to give credit to the translator(s). + */ + "translator-credits", _("translator-credits"), + NULL); +} + +void +nautilus_window_search (NautilusWindow *window, + NautilusQuery *query) +{ + NautilusWindowSlot *active_slot; + + active_slot = nautilus_window_get_active_slot (window); + if (active_slot) + { + nautilus_window_slot_search (active_slot, query); + } + else + { + g_warning ("Trying search on a slot but no active slot present"); + } +} diff --git a/src/nautilus-window.h b/src/nautilus-window.h new file mode 100644 index 0000000..2f7add9 --- /dev/null +++ b/src/nautilus-window.h @@ -0,0 +1,121 @@ + +/* + * Nautilus + * + * Copyright (C) 1999, 2000 Red Hat, Inc. + * Copyright (C) 1999, 2000, 2001 Eazel, Inc. + * + * Nautilus is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License as + * published by the Free Software Foundation; either version 2 of the + * License, or (at your option) any later version. + * + * Nautilus is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * 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 . + * + * Authors: Elliot Lee + * Darin Adler + * + */ +/* nautilus-window.h: Interface of the main window object */ + +#pragma once + +#include +#include + +#include "nautilus-types.h" + +G_BEGIN_DECLS + +#define NAUTILUS_TYPE_WINDOW (nautilus_window_get_type ()) +G_DECLARE_FINAL_TYPE (NautilusWindow, nautilus_window, NAUTILUS, WINDOW, AdwApplicationWindow); + +typedef gboolean (* NautilusWindowGoToCallback) (NautilusWindow *window, + GFile *location, + GError *error, + gpointer user_data); + +typedef void (* NautilusWindowHandleExported) (NautilusWindow *window, + const char *handle, + guint xid, + gpointer user_data); + +/* window geometry */ +/* Min values are very small, and a Nautilus window at this tiny size is *almost* + * completely unusable. However, if all the extra bits (sidebar, location bar, etc) + * are turned off, you can see an icon or two at this size. See bug 5946. + */ + +#define NAUTILUS_WINDOW_MIN_WIDTH 200 +#define NAUTILUS_WINDOW_MIN_HEIGHT 200 +#define NAUTILUS_WINDOW_DEFAULT_WIDTH 890 +#define NAUTILUS_WINDOW_DEFAULT_HEIGHT 550 + +typedef enum +{ + NAUTILUS_NAVIGATION_DIRECTION_NONE, + NAUTILUS_NAVIGATION_DIRECTION_BACK, + NAUTILUS_NAVIGATION_DIRECTION_FORWARD +} NautilusNavigationDirection; + +NautilusWindow * nautilus_window_new (void); +void nautilus_window_close (NautilusWindow *window); + +void nautilus_window_open_location_full (NautilusWindow *window, + GFile *location, + NautilusOpenFlags flags, + GList *selection, + NautilusWindowSlot *target_slot); + +void nautilus_window_new_tab (NautilusWindow *window); +NautilusWindowSlot * nautilus_window_get_active_slot (NautilusWindow *window); +void nautilus_window_set_active_slot (NautilusWindow *window, + NautilusWindowSlot *slot); +GList * nautilus_window_get_slots (NautilusWindow *window); +void nautilus_window_slot_close (NautilusWindow *window, + NautilusWindowSlot *slot); + +void nautilus_window_sync_location_widgets (NautilusWindow *window); + +void nautilus_window_reset_menus (NautilusWindow *window); + +AdwTabView * nautilus_window_get_tab_view (NautilusWindow *window); + +void nautilus_window_show_about_dialog (NautilusWindow *window); + +GtkWidget *nautilus_window_get_toolbar (NautilusWindow *window); + +/* sync window GUI with current slot. Used when changing slots, + * and when updating the slot state. + */ +void nautilus_window_sync_allow_stop (NautilusWindow *window, + NautilusWindowSlot *slot); +void nautilus_window_sync_title (NautilusWindow *window, + NautilusWindowSlot *slot); + +void nautilus_window_show_operation_notification (NautilusWindow *window, + gchar *main_label, + GFile *folder_to_open); + +void nautilus_window_search (NautilusWindow *window, + NautilusQuery *query); + +void nautilus_window_initialize_slot (NautilusWindow *window, + NautilusWindowSlot *slot, + NautilusOpenFlags flags); + +gboolean nautilus_window_export_handle (NautilusWindow *window, + NautilusWindowHandleExported callback, + gpointer user_data); +void nautilus_window_unexport_handle (NautilusWindow *window); + +void nautilus_window_back_or_forward_in_new_tab (NautilusWindow *window, + NautilusNavigationDirection back); + +G_END_DECLS diff --git a/src/nautilus-x-content-bar.c b/src/nautilus-x-content-bar.c new file mode 100644 index 0000000..689e129 --- /dev/null +++ b/src/nautilus-x-content-bar.c @@ -0,0 +1,357 @@ +/* + * Copyright (C) 2008 Red Hat, Inc. + * Copyright (C) 2006 Paolo Borelli + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 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 warranty of + * MERCHANTABILITY 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 . + * + * Authors: David Zeuthen + * Paolo Borelli + * + */ + +#include "config.h" + +#include +#include +#include + +#include "nautilus-x-content-bar.h" +#include "nautilus-icon-info.h" +#include "nautilus-file-utilities.h" +#include "nautilus-program-choosing.h" + +struct _NautilusXContentBar +{ + AdwBin parent_instance; + GtkWidget *label; + + char **x_content_types; + GMount *mount; +}; + +enum +{ + PROP_0, + PROP_MOUNT, + PROP_X_CONTENT_TYPES, +}; + +enum +{ + CONTENT_BAR_RESPONSE_APP = 1 +}; + +G_DEFINE_TYPE (NautilusXContentBar, nautilus_x_content_bar, ADW_TYPE_BIN) + +static void +content_bar_response_cb (GtkInfoBar *infobar, + gint response_id, + gpointer user_data) +{ + GAppInfo *default_app; + NautilusXContentBar *bar = user_data; + + if (response_id < 0) + { + return; + } + + if (bar->x_content_types == NULL || + bar->mount == NULL) + { + return; + } + + /* FIXME */ + default_app = g_app_info_get_default_for_type (bar->x_content_types[response_id], FALSE); + if (default_app != NULL) + { + nautilus_launch_application_for_mount (default_app, bar->mount, + GTK_WINDOW (gtk_widget_get_root (GTK_WIDGET (bar)))); + g_object_unref (default_app); + } +} + +static void +nautilus_x_content_bar_set_x_content_types (NautilusXContentBar *bar, + const char * const *x_content_types) +{ + char *message = NULL; + guint num_types; + guint n; + GPtrArray *types; + GPtrArray *apps; + GAppInfo *default_app; + + g_strfreev (bar->x_content_types); + + if (!should_handle_content_types (x_content_types)) + { + g_warning ("Content types in content types bar cannot be handled. Check before creating the content bar if they can be handled."); + return; + } + + types = g_ptr_array_new (); + apps = g_ptr_array_new (); + g_ptr_array_set_free_func (apps, g_object_unref); + for (n = 0; x_content_types[n] != NULL; n++) + { + if (!should_handle_content_type (x_content_types[n])) + { + continue; + } + + default_app = g_app_info_get_default_for_type (x_content_types[n], FALSE); + g_ptr_array_add (types, g_strdup (x_content_types[n])); + g_ptr_array_add (apps, default_app); + } + + num_types = types->len; + g_ptr_array_add (types, NULL); + + bar->x_content_types = (char **) g_ptr_array_free (types, FALSE); + + switch (num_types) + { + case 1: + { + message = get_message_for_content_type (bar->x_content_types[0]); + } + break; + + case 2: + { + message = get_message_for_two_content_types ((const char * const *) bar->x_content_types); + } + break; + + default: + { + message = g_strdup (_("Open with:")); + } + break; + } + + gtk_label_set_text (GTK_LABEL (bar->label), message); + g_free (message); + + gtk_widget_show (bar->label); + + for (n = 0; bar->x_content_types[n] != NULL; n++) + { + const char *name; + GIcon *icon; + GtkWidget *image; + GtkWidget *info_bar; + GtkWidget *button; + GAppInfo *app; + gboolean has_app; + guint i; + GtkWidget *box; + + default_app = g_ptr_array_index (apps, n); + has_app = FALSE; + + for (i = 0; i < n; i++) + { + app = g_ptr_array_index (apps, i); + if (g_app_info_equal (app, default_app)) + { + has_app = TRUE; + break; + } + } + + if (has_app) + { + continue; + } + + icon = g_app_info_get_icon (default_app); + if (icon != NULL) + { + image = gtk_image_new_from_gicon (icon); + } + else + { + image = NULL; + } + + name = g_app_info_get_name (default_app); + info_bar = adw_bin_get_child (ADW_BIN (bar)); + button = gtk_info_bar_add_button (GTK_INFO_BAR (info_bar), name, n); + box = gtk_box_new (GTK_ORIENTATION_HORIZONTAL, 6); + + if (image != NULL) + { + gtk_box_append (GTK_BOX (box), image); + } + gtk_box_append (GTK_BOX (box), gtk_label_new (name)); + + gtk_button_set_child (GTK_BUTTON (button), box); + + gtk_widget_show (button); + } + + g_ptr_array_free (apps, TRUE); +} + +static void +nautilus_x_content_bar_set_mount (NautilusXContentBar *bar, + GMount *mount) +{ + if (bar->mount != NULL) + { + g_object_unref (bar->mount); + } + bar->mount = mount != NULL ? g_object_ref (mount) : NULL; +} + + +static void +nautilus_x_content_bar_set_property (GObject *object, + guint prop_id, + const GValue *value, + GParamSpec *pspec) +{ + NautilusXContentBar *bar = NAUTILUS_X_CONTENT_BAR (object); + + switch (prop_id) + { + case PROP_MOUNT: + { + nautilus_x_content_bar_set_mount (bar, G_MOUNT (g_value_get_object (value))); + } + break; + + case PROP_X_CONTENT_TYPES: + { + nautilus_x_content_bar_set_x_content_types (bar, g_value_get_boxed (value)); + } + break; + + default: + { + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); + } + break; + } +} + +static void +nautilus_x_content_bar_get_property (GObject *object, + guint prop_id, + GValue *value, + GParamSpec *pspec) +{ + NautilusXContentBar *bar = NAUTILUS_X_CONTENT_BAR (object); + + switch (prop_id) + { + case PROP_MOUNT: + { + g_value_set_object (value, bar->mount); + } + break; + + case PROP_X_CONTENT_TYPES: + { + g_value_set_boxed (value, &bar->x_content_types); + } + break; + + default: + { + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); + } + break; + } +} + +static void +nautilus_x_content_bar_finalize (GObject *object) +{ + NautilusXContentBar *bar = NAUTILUS_X_CONTENT_BAR (object); + + g_strfreev (bar->x_content_types); + if (bar->mount != NULL) + { + g_object_unref (bar->mount); + } + + G_OBJECT_CLASS (nautilus_x_content_bar_parent_class)->finalize (object); +} + +static void +nautilus_x_content_bar_class_init (NautilusXContentBarClass *klass) +{ + GObjectClass *object_class; + + object_class = G_OBJECT_CLASS (klass); + object_class->get_property = nautilus_x_content_bar_get_property; + object_class->set_property = nautilus_x_content_bar_set_property; + object_class->finalize = nautilus_x_content_bar_finalize; + + g_object_class_install_property (object_class, + PROP_MOUNT, + g_param_spec_object ( + "mount", + "The GMount to run programs for", + "The GMount to run programs for", + G_TYPE_MOUNT, + G_PARAM_READWRITE | G_PARAM_CONSTRUCT)); + + g_object_class_install_property (object_class, + PROP_X_CONTENT_TYPES, + g_param_spec_boxed ("x-content-types", + "The x-content types for the cluebar", + "The x-content types for the cluebar", + G_TYPE_STRV, + G_PARAM_READWRITE | G_PARAM_CONSTRUCT)); +} + +static void +nautilus_x_content_bar_init (NautilusXContentBar *bar) +{ + GtkWidget *info_bar; + PangoAttrList *attrs; + + info_bar = gtk_info_bar_new (); + gtk_info_bar_set_message_type (GTK_INFO_BAR (info_bar), GTK_MESSAGE_QUESTION); + gtk_widget_show (info_bar); + adw_bin_set_child (ADW_BIN (bar), info_bar); + + attrs = pango_attr_list_new (); + pango_attr_list_insert (attrs, pango_attr_weight_new (PANGO_WEIGHT_BOLD)); + bar->label = gtk_label_new (NULL); + gtk_label_set_attributes (GTK_LABEL (bar->label), attrs); + pango_attr_list_unref (attrs); + + gtk_label_set_ellipsize (GTK_LABEL (bar->label), PANGO_ELLIPSIZE_END); + gtk_info_bar_add_child (GTK_INFO_BAR (info_bar), bar->label); + + g_signal_connect (info_bar, "response", + G_CALLBACK (content_bar_response_cb), + bar); +} + +GtkWidget * +nautilus_x_content_bar_new (GMount *mount, + const char * const *x_content_types) +{ + return g_object_new (NAUTILUS_TYPE_X_CONTENT_BAR, + "mount", mount, + "x-content-types", x_content_types, + NULL); +} diff --git a/src/nautilus-x-content-bar.h b/src/nautilus-x-content-bar.h new file mode 100644 index 0000000..11f05ec --- /dev/null +++ b/src/nautilus-x-content-bar.h @@ -0,0 +1,38 @@ +/* + * Copyright (C) 2008 Red Hat, Inc. + * Copyright (C) 2006 Paolo Borelli + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 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 warranty of + * MERCHANTABILITY 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 . + * + * Authors: David Zeuthen + * Paolo Borelli + * + */ + +#pragma once + +#include +#include +#include + +G_BEGIN_DECLS + +#define NAUTILUS_TYPE_X_CONTENT_BAR (nautilus_x_content_bar_get_type ()) + +G_DECLARE_FINAL_TYPE (NautilusXContentBar, nautilus_x_content_bar, NAUTILUS, X_CONTENT_BAR, AdwBin) + +GtkWidget *nautilus_x_content_bar_new (GMount *mount, + const char * const *x_content_types); + +G_END_DECLS diff --git a/src/resources/Checkerboard.png b/src/resources/Checkerboard.png new file mode 100644 index 0000000..3e43acd Binary files /dev/null and b/src/resources/Checkerboard.png differ diff --git a/src/resources/gtk/help-overlay.ui b/src/resources/gtk/help-overlay.ui new file mode 100644 index 0000000..42bad9f --- /dev/null +++ b/src/resources/gtk/help-overlay.ui @@ -0,0 +1,406 @@ + + + + True + + + True + shortcuts + 12 + + + True + General + + + True + New window + <Primary>N + + + + + True + Close window or tab + <Primary>W + + + + + True + Quit + <Primary>Q + + + + + True + Search + <Primary>F + + + + + True + Bookmark current location + <Primary>D + + + + + True + Show help + F1 + + + + + True + Shortcuts + <Primary>question + + + + + True + Undo + <Primary>z + + + + + True + Redo + <shift><Primary>z + + + + + + + True + Opening + + + True + Open + Return <Primary>O + + + + + True + Open in new tab + <Primary>Return + + + + + True + Open in new window + <shift>Return + + + + + True + Open item location (search and recent only) + <alt><Primary>O + + + + + True + Open with default application + <Primary>O <alt>Down + + + + + + + True + Tabs + + + True + New tab + <Primary>T + + + + + True + Go to previous tab + <Primary>Page_Up + + + + + True + Go to next tab + <Primary>Page_Down + + + + + True + Open tab + <alt>0...8 + + + + + True + Move tab left + <shift><Primary>Page_Up + + + + + True + Move tab right + <shift><Primary>Page_Down + + + + + True + Restore tab + <shift><Primary>T + + + + + + + True + Navigation + + + True + Go back + <alt>Left + + + + + True + Go forward + <alt>Right + + + + + True + Go up + <alt>Up + + + + + True + Go down + <alt>Down + + + + + True + Go to home folder + <alt>Home + + + + + True + Enter location + <Primary>L + + + + + True + Location bar with root location + slash + + + + + True + Location bar with home location + asciitilde + + + + + + + True + View + + + True + Zoom in + <Primary>plus + + + + + True + Zoom out + <Primary>minus + + + + + True + Reset zoom + <Primary>0 + + + + + True + Refresh view + F5 <Primary>R + + + + + True + Show/hide hidden files + <Primary>H + + + + + True + Show/hide sidebar + F9 + + + + + True + Show/hide action menu + F10 + + + + + True + List view + <Primary>1 + + + + + True + Grid view + <Primary>2 + + + + + + + True + Editing + + + True + Create folder + <shift><Primary>N + + + + + True + Rename + F2 + + + + + True + Move to trash + Delete + + + + + True + Delete permanently + <shift>Delete + + + + + True + Create link to copied item + <Primary>M + + + + + True + Create link to selected item + <Primary><shift>M + + + + + True + Cut + <Primary>X + + + + + True + Copy + <Primary>C + + + + + True + Paste + <Primary>V + + + + + True + Select all + <Primary>A + + + + + True + Invert selection + <shift><Primary>I + + + + + True + Select items matching + <Primary>S + + + + + True + Show item properties + <Primary>I <alt>Return + + + + + + + + diff --git a/src/resources/icons/external-link-symbolic.svg b/src/resources/icons/external-link-symbolic.svg new file mode 100644 index 0000000..a24ed4b --- /dev/null +++ b/src/resources/icons/external-link-symbolic.svg @@ -0,0 +1,2 @@ + + diff --git a/src/resources/icons/funnel-symbolic.svg b/src/resources/icons/funnel-symbolic.svg new file mode 100644 index 0000000..6e01001 --- /dev/null +++ b/src/resources/icons/funnel-symbolic.svg @@ -0,0 +1,2 @@ + + diff --git a/src/resources/icons/quotation-symbolic.svg b/src/resources/icons/quotation-symbolic.svg new file mode 100644 index 0000000..cc92cdd --- /dev/null +++ b/src/resources/icons/quotation-symbolic.svg @@ -0,0 +1,2 @@ + + diff --git a/src/resources/nautilus.gresource.xml b/src/resources/nautilus.gresource.xml new file mode 100644 index 0000000..bfe8b2b --- /dev/null +++ b/src/resources/nautilus.gresource.xml @@ -0,0 +1,45 @@ + + + + ui/nautilus-preferences-window.ui + ui/nautilus-search-popover.ui + ui/nautilus-app-chooser.ui + ui/nautilus-pathbar-context-menu.ui + ui/nautilus-toolbar.ui + ui/nautilus-history-controls.ui + ui/nautilus-progress-indicator.ui + ui/nautilus-view-controls.ui + ui/nautilus-toolbar-view-menu.ui + ui/nautilus-column-chooser.ui + ui/nautilus-list-view-column-editor.ui + ui/nautilus-create-folder-dialog.ui + ui/nautilus-compress-dialog.ui + ui/nautilus-rename-file-popover.ui + ui/nautilus-files-view-context-menus.ui + ui/nautilus-progress-info-widget.ui + ui/nautilus-window.ui + gtk/help-overlay.ui + ui/nautilus-batch-rename-dialog.ui + ui/nautilus-properties-window.ui + ui/nautilus-file-properties-change-permissions.ui + ui/nautilus-file-conflict-dialog.ui + ui/nautilus-files-view-select-items.ui + ui/nautilus-files-view.ui + ui/nautilus-operations-ui-manager-request-passphrase.ui + ui/nautilus-grid-cell.ui + ui/nautilus-name-cell.ui + ../gtk/nautilusgtksidebarrow.ui + ../gtk/nautilusgtkplacesview.ui + ../gtk/nautilusgtkplacesviewrow.ui + ../../icons/filmholes.png + style.css + style-hc.css + text-x-preview.png + Checkerboard.png + + + icons/quotation-symbolic.svg + icons/funnel-symbolic.svg + icons/external-link-symbolic.svg + + diff --git a/src/resources/style-hc.css b/src/resources/style-hc.css new file mode 100644 index 0000000..54e2cff --- /dev/null +++ b/src/resources/style-hc.css @@ -0,0 +1,16 @@ +#NautilusPathBar { + box-shadow: inset 0 0 0 1px @borders; +} + +#NautilusPathButton:not(:hover):not(:drop(active)), +#NautilusPathButton.current-dir:not(:drop(active)) { + box-shadow: none; +} + +#NautilusQueryEditorTag { + box-shadow: inset 0 0 0 1px @borders; +} + +.disk-space-free { + color: alpha(currentColor, 0.3); +} diff --git a/src/resources/style.css b/src/resources/style.css new file mode 100644 index 0000000..54cf229 --- /dev/null +++ b/src/resources/style.css @@ -0,0 +1,301 @@ +/* Toolbar */ + +@keyframes needs_attention_keyframes { + 0% { } + 10% { background-color: @accent_bg_color; border-radius: 999999px; } + 100% { } +} + +.nautilus-operations-button-needs-attention { + animation: needs_attention_keyframes 2s ease-in-out; +} +.nautilus-operations-button-needs-attention-multiple { + animation: needs_attention_keyframes 3s ease-in-out; + animation-iteration-count: 3; +} + +/* Remove white background and highlight on hover which GTK adds by default + * to GtkListBox. TODO: Switch to GtkListView and drop this CSS hack. */ +.operations-list, +.operations-list > :hover { + background: none; +} + +/* Path bar */ + +#NautilusPathBar { + background-color: alpha(currentColor, 0.1); + border-radius: 6px; +} +#NautilusPathBar > menubutton { + margin: 0px; +} + +#NautilusPathBar > scrolledwindow undershoot.left { + background: linear-gradient(to right, @headerbar_shade_color 6px, alpha(@headerbar_shade_color, 0) 24px); + border-left: solid 1px @borders; +} +#NautilusPathBar > scrolledwindow undershoot.right { + background: linear-gradient(to left, @headerbar_shade_color 6px, alpha(@headerbar_shade_color, 0) 24px); + border-right: solid 1px @borders; +} + +/* Match sidebar's rounded corners on the "start" side. */ +#NautilusPathBar > scrolledwindow:dir(ltr) undershoot.left { + border-radius: 6px 0px 0px 6px; +} +#NautilusPathBar > scrolledwindow:dir(rtl) undershoot.right { + border-radius: 0px 6px 6px 0px; +} + +#NautilusPathButton { + margin: 3px; + border-radius: 4px; + padding-top: 0px; + padding-bottom: 0px; +} + +#NautilusPathButton:not(:hover), +#NautilusPathButton.current-dir +{ + background: none; +} + +#NautilusPathButton:not(.current-dir):not(:backdrop):hover label, +#NautilusPathButton:not(.current-dir):not(:backdrop):hover image { + opacity: 1; +} + +/* Search bar */ + +#NautilusQueryEditor > * { + margin-top: 5px; + margin-bottom: 5px; +} + +#NautilusQueryEditorTag > button, +#NautilusQueryEditor > menubutton > button { + min-width: 24px; + min-height: 24px; + margin: 0px; +} + +#NautilusQueryEditorTag { + background-color: alpha(currentColor, 0.1); + border-radius: 100px; +} + +/* Mimic the style of GtkEntry icons, but keep button background if pressed. */ +#NautilusQueryEditor > menubutton > button:not(:checked) { + background: none; +} +#NautilusQueryEditor > menubutton > button:not(:hover):not(:checked) { + opacity: 0.7; +} + +/* Floating status bar */ +.floating-bar { + padding: 3px; + background-color: @view_bg_color; + box-shadow: 0 0 0 1px @borders; + border-radius: 8px 0 0 0; +} + +.floating-bar.bottom.left { /* axes left border and border radius */ + border-top-left-radius: 0; +} +.floating-bar.bottom.right { /* axes right border and border radius */ + border-top-right-radius: 0; +} + +.floating-bar:backdrop { + background-color: @view_bg_color; + border-color: @unfocused_borders; +} + +.floating-bar button { + padding: 0px; +} + +.disk-space-free { + color: alpha(currentColor, 0.15); +} +.disk-space-used { + color: @accent_bg_color; +} + +.search-information { + background-color: @accent_bg_color; + color:white; + padding:2px; +} + +.batch-rename-preview { + border-top: solid @borders 1px; +} +.conflict-row { + background: @warning_bg_color; + color: @warning_fg_color; +} + +/* Grid view */ +.nautilus-grid-view gridview { + padding: 15px; +} + +.nautilus-grid-view gridview > child { + padding: 0px; + border-radius: 12px; +} +.nautilus-grid-view #NautilusViewCell { + padding: 6px; + border-radius: 12px; +} + +/* Column view */ + +/* Setup padding on the list. Horizontal padding must be set on the columnview + * for it to calculate column widths correctly. */ +.nautilus-list-view columnview { + padding-left: 24px; + padding-right: 24px; +} +.nautilus-list-view columnview > listview { + padding-top: 12px; + padding-bottom: 24px; +} + +/* Use negative margins to extend rubberbanding area into the columnview's + * padding, then apply positive margin on rows to reestablish positioning. */ +.nautilus-list-view columnview > listview { + margin-left: -24px; + margin-right: -24px; +} +.nautilus-list-view columnview > listview > row { + margin-left: 24px; + margin-right: 24px; +} + +.nautilus-list-view columnview > listview > row { + border-radius: 6px; + margin-top: 4px; + margin-bottom: 4px; +} + +.nautilus-list-view.compact columnview > listview > row { + margin-top: 2px; + margin-bottom: 2px; +} + +/* GTK unconditionally sets padding on GtkColumnViewCell, even with .data-table. + * We don't want this to hpappen because we have event controllers on the child, + * which should thus cover the whole area of the row. */ +.nautilus-list-view columnview > listview > row > cell { + padding: 0px; +} + +.nautilus-list-view #NautilusViewCell { + padding: 6px; +} + +/* We want drop feedback on the whole row. Disable per-cell feedback */ +.nautilus-list-view #NautilusViewCell:drop(active) { + box-shadow: none; +} + +.nautilus-list-view.compact #NautilusViewCell { + padding-top: 3px; + padding-bottom: 3px; +} + +.nautilus-list-view:not(.compact) image.star { + padding: 6px; +} + +.nautilus-list-view menubutton.fts-snippet > button { + border-radius: 100px; + padding-top: 2px; + padding-bottom: 2px; + padding-left: 12px; + padding-right: 12px; +} +.nautilus-list-view menubutton.fts-snippet > popover > * { + padding: 18px; +} + +/* Both views */ +.nautilus-list-view:drop(active), +.nautilus-grid-view:drop(active) { + box-shadow: none; +} + +.nautilus-list-view columnview > listview > row.activatable:hover, +.nautilus-grid-view gridview > child.activatable:hover { + background-color: alpha(currentColor, .04); +} + +.nautilus-list-view columnview > listview > row.activatable:active, +.nautilus-grid-view gridview > child.activatable:active { + background-color: alpha(currentColor, .08); +} + +.nautilus-list-view columnview > listview > row:selected, +.nautilus-grid-view gridview > child:selected { + background-color: alpha(@accent_bg_color, .15); +} + +.nautilus-list-view columnview > listview > row.activatable:selected:hover, +.nautilus-grid-view gridview > child.activatable:selected:hover { + background-color: alpha(@accent_bg_color, .20); +} + +.nautilus-list-view columnview > listview > row.activatable:selected:active, +.nautilus-grid-view gridview > child.activatable:selected:active { + background-color: alpha(@accent_bg_color, .25); +} + +.view .thumbnail { + background: url('/org/gnome/nautilus/Checkerboard.png') repeat; + border-radius: 2px; + /* Draw a shin and outline to meld better with full-color icons */ + box-shadow: 0px 0px 0px 1px @shade_color, + 0px 2px 0px 0px @shade_color; +} + +.view picture { + filter: drop-shadow(0px 1px 1px rgba(0,0,0,0.3)); +} + +.view statuspage { + opacity: 0.50; +} + +.view .cut { + opacity: 0.55; +} + +.view image.star:hover { + opacity: 1; +} + +@keyframes rotate_star { + from { -gtk-icon-transform: rotate(-72deg); } + to {} +} + +.view image.star.added { + animation: rotate_star 0.4s ease; +} + +#NautilusAppChooser treeview { + min-height: 36px; + -gtk-icon-size: 32px; +} + +label.encrypted_zip { + background-image: -gtk-icontheme('system-lock-screen-symbolic'); + background-position: right center; + background-repeat: no-repeat; + background-size: 16px 16px; + padding-right: 22px; +} diff --git a/src/resources/text-x-preview.png b/src/resources/text-x-preview.png new file mode 100644 index 0000000..0d45ff9 Binary files /dev/null and b/src/resources/text-x-preview.png differ diff --git a/src/resources/ui/nautilus-app-chooser.ui b/src/resources/ui/nautilus-app-chooser.ui new file mode 100644 index 0000000..e30beac --- /dev/null +++ b/src/resources/ui/nautilus-app-chooser.ui @@ -0,0 +1,120 @@ + + + + + diff --git a/src/resources/ui/nautilus-batch-rename-dialog.ui b/src/resources/ui/nautilus-batch-rename-dialog.ui new file mode 100644 index 0000000..7da1e95 --- /dev/null +++ b/src/resources/ui/nautilus-batch-rename-dialog.ui @@ -0,0 +1,395 @@ + + + + +
+ Automatic Numbers + + 1, 2, 3, 4 + dialog.add-numbering-no-zero-pad-tag + + + 01, 02, 03, 04 + dialog.add-numbering-one-zero-pad-tag + + + 001, 002, 003, 004 + dialog.add-numbering-two-zero-pad-tag + +
+
+ Metadata + + Creation Date + dialog.add-creation-date-tag + action-disabled + + + Camera Model + dialog.add-equipment-tag + action-disabled + + + Season Number + dialog.add-season-tag + action-disabled + + + Episode Number + dialog.add-episode-tag + action-disabled + + + Track Number + dialog.add-track-number-tag + action-disabled + + + Artist Name + dialog.add-artist-name-tag + action-disabled + + + Title + dialog.add-title-tag + action-disabled + + + Album Name + dialog.add-album-name-tag + action-disabled + +
+
+ + Original File Name + dialog.add-original-file-name-tag + +
+
+ +
+ + Original Name (Ascending) + dialog.numbering-order-changed + name-ascending + + + Original Name (Descending) + dialog.numbering-order-changed + name-descending + + + First Modified + dialog.numbering-order-changed + first-modified + + + Last Modified + dialog.numbering-order-changed + last-modified + +
+
+ + + object-select-symbolic + +
diff --git a/src/resources/ui/nautilus-column-chooser.ui b/src/resources/ui/nautilus-column-chooser.ui new file mode 100644 index 0000000..24cce3d --- /dev/null +++ b/src/resources/ui/nautilus-column-chooser.ui @@ -0,0 +1,105 @@ + + + + + + + + + + + + + + + + + diff --git a/src/resources/ui/nautilus-compress-dialog.ui b/src/resources/ui/nautilus-compress-dialog.ui new file mode 100644 index 0000000..732afee --- /dev/null +++ b/src/resources/ui/nautilus-compress-dialog.ui @@ -0,0 +1,98 @@ + + + + + Create Compressed Archive + False + True + True + 1 + 500 + 210 + + + vertical + 30 + 30 + 30 + 30 + 390 + center + 6 + + + Archive name + 0 + + + + + + + + 12 + + + + name_label + + True + 30 + + + + + + + + + + + + 4 + 4 + 0 + + + + + + + False + Password + 6 + 0 + + + + + + passphrase_label + + False + Enter a password here. + password + False + view-conceal + + + + + + + Cancel + + + + + Create + False + + + + activate_button + cancel_button + + + + diff --git a/src/resources/ui/nautilus-create-folder-dialog.ui b/src/resources/ui/nautilus-create-folder-dialog.ui new file mode 100644 index 0000000..699180d --- /dev/null +++ b/src/resources/ui/nautilus-create-folder-dialog.ui @@ -0,0 +1,58 @@ + + + + + False + True + True + 1 + 450 + + + vertical + 18 + 12 + 18 + 18 + 6 + + + 0 + + + + + + name_label + + + + + + + + 4 + 4 + 0 + + + + + + + + + Cancel + + + + + False + + + + ok_button + cancel_button + + + diff --git a/src/resources/ui/nautilus-file-conflict-dialog.ui b/src/resources/ui/nautilus-file-conflict-dialog.ui new file mode 100644 index 0000000..8993fb6 --- /dev/null +++ b/src/resources/ui/nautilus-file-conflict-dialog.ui @@ -0,0 +1,147 @@ + + + + + diff --git a/src/resources/ui/nautilus-file-properties-change-permissions.ui b/src/resources/ui/nautilus-file-properties-change-permissions.ui new file mode 100644 index 0000000..623b7db --- /dev/null +++ b/src/resources/ui/nautilus-file-properties-change-permissions.ui @@ -0,0 +1,157 @@ + + + + + Change Permissions for Enclosed Files + True + True + 1 + + + _Cancel + True + + + + + C_hange + True + + + + + vertical + + + + center + 6 + 6 + 6 + 6 + vertical + 6 + 12 + + + Files + + + 1 + 0 + + + + + + Folders + + + 2 + 0 + + + + + + Owner + 1 + + + 0 + 1 + + + + + + + 1 + 1 + + + + + + + 2 + 1 + + + + + + Group + 1 + + + 0 + 2 + + + + + + + 1 + 2 + + + + + + + 2 + 2 + + + + + + + 1 + 3 + + + + + + + 2 + 3 + + + + + + Others + 1 + + + 0 + 3 + + + + + + + + + cancel + change + + + diff --git a/src/resources/ui/nautilus-files-view-context-menus.ui b/src/resources/ui/nautilus-files-view-context-menus.ui new file mode 100644 index 0000000..87f0c41 --- /dev/null +++ b/src/resources/ui/nautilus-files-view-context-menus.ui @@ -0,0 +1,260 @@ + + + + + + New _Folder… + view.new-folder + + + New _Document + templates-submenu + + + + Open _With… + view.open-current-directory-with-other-application + + + Open in Consol_e + view.current-directory-console + action-disabled + +
+ + _Paste + view.paste + + + Paste as _Link + view.create-link + action-disabled + + + Select _All + view.select-all + + + _Visible Columns… + view.visible-columns + action-missing + +
+
+ + Empty _Trash + view.empty-trash + action-disabled + +
+
+
+ + P_roperties + view.current-directory-properties + action-disabled + +
+
+ +
+
+ + _Extract + view.extract-here + action-disabled + + + E_xtract to… + view.extract-to + action-disabled + + + Open _With… + view.open-with-other-application + action-disabled + open_with_in_main_menu + + + _Run as a Program + view.run-in-terminal + action-disabled + + + _Open + open_in_view_submenu + +
+ + Open + view.open-with-default-application + action-disabled + + + Open In New _Tab + view.open-item-new-tab + action-disabled + + + Open In New _Window + view.open-item-new-window + action-disabled + +
+
+ + Open _With… + view.open-with-other-application + action-disabled + + + Open in Consol_e + view.console + action-disabled + +
+ +
+ + _Open Item Location + view.open-item-location + action-disabled + + + _Scripts + scripts-submenu +
+
+ + _Open Scripts Folder + view.open-scripts-folder + action-disabled + +
+ +
+
+ + _Mount + view.mount-volume + action-disabled + + + _Unmount + view.unmount-volume + action-disabled + + + _Eject + view.eject-volume + action-disabled + + + _Start + view.start-volume + action-disabled + + + _Stop + view.stop-volume + action-disabled + + + _Detect Media + view.detect-media + action-disabled + +
+
+ + Cu_t + view.cut + + + _Copy + view.copy + + + Move to… + view.move-to + + + Copy to… + view.copy-to + +
+
+ + Rena_me… + view.rename + + + _Paste Into Folder + view.paste-into + action-disabled + + + Create _Link + view.create-link-in-place + action-disabled + + + C_ompress… + view.compress + action-disabled + + + Set as Background… + view.set-as-wallpaper + action-disabled + + + Email… + view.send-email + action-disabled + + + Mo_ve to Trash + view.move-to-trash + action-disabled + + + _Delete from Trash + view.delete-from-trash + action-disabled + + + _Delete Permanently… + view.delete-permanently-menu-item + action-disabled + + + _Delete Permanently… + view.permanent-delete-permanently-menu-item + action-disabled + + + _Restore From Trash + view.restore-from-trash + action-disabled + + + _Remove from Recent + view.remove-from-recent + action-disabled + + + Unstar + view.unstar + action-disabled + +
+
+
+ + P_roperties + view.properties + +
+
+
diff --git a/src/resources/ui/nautilus-files-view-select-items.ui b/src/resources/ui/nautilus-files-view-select-items.ui new file mode 100644 index 0000000..614ed2a --- /dev/null +++ b/src/resources/ui/nautilus-files-view-select-items.ui @@ -0,0 +1,54 @@ + + + + + Select Items Matching + True + 1 + + + _Cancel + True + + + + + _Select + True + + + + + 18 + 18 + 18 + 18 + 6 + + + start + Pattern + + + + + + + + True + True + + + + + start + + + + + + cancel + select + + + diff --git a/src/resources/ui/nautilus-files-view.ui b/src/resources/ui/nautilus-files-view.ui new file mode 100644 index 0000000..eab5bdd --- /dev/null +++ b/src/resources/ui/nautilus-files-view.ui @@ -0,0 +1,42 @@ + + + + + diff --git a/src/resources/ui/nautilus-grid-cell.ui b/src/resources/ui/nautilus-grid-cell.ui new file mode 100644 index 0000000..1a92040 --- /dev/null +++ b/src/resources/ui/nautilus-grid-cell.ui @@ -0,0 +1,109 @@ + + + + + diff --git a/src/resources/ui/nautilus-history-controls.ui b/src/resources/ui/nautilus-history-controls.ui new file mode 100644 index 0000000..0fd0ce5 --- /dev/null +++ b/src/resources/ui/nautilus-history-controls.ui @@ -0,0 +1,28 @@ + + + + + + diff --git a/src/resources/ui/nautilus-list-view-column-editor.ui b/src/resources/ui/nautilus-list-view-column-editor.ui new file mode 100644 index 0000000..c4cab58 --- /dev/null +++ b/src/resources/ui/nautilus-list-view-column-editor.ui @@ -0,0 +1,41 @@ + + + + + True + 360 + 440 + + + vertical + + + + + Visible Columns + + + + + + + 18 + 18 + 18 + 18 + vertical + 18 + + + Choose the order of information to appear in this folder: + 0 + 0 + True + + + + + + + + diff --git a/src/resources/ui/nautilus-name-cell.ui b/src/resources/ui/nautilus-name-cell.ui new file mode 100644 index 0000000..246aa3a --- /dev/null +++ b/src/resources/ui/nautilus-name-cell.ui @@ -0,0 +1,112 @@ + + + + + diff --git a/src/resources/ui/nautilus-operations-ui-manager-request-passphrase.ui b/src/resources/ui/nautilus-operations-ui-manager-request-passphrase.ui new file mode 100644 index 0000000..8c87ed1 --- /dev/null +++ b/src/resources/ui/nautilus-operations-ui-manager-request-passphrase.ui @@ -0,0 +1,49 @@ + + + + + Password Required + True + True + 1 + + + 20 + 20 + 20 + 20 + + + 60 + True + + + + + True + end + True + Enter password… + False + password + + + + + + + _Cancel + True + + + + + Extract + + + + cancel_button + extract_button + + + diff --git a/src/resources/ui/nautilus-pathbar-context-menu.ui b/src/resources/ui/nautilus-pathbar-context-menu.ui new file mode 100644 index 0000000..c1f9a2c --- /dev/null +++ b/src/resources/ui/nautilus-pathbar-context-menu.ui @@ -0,0 +1,73 @@ + + + + + + Open in New _Window + pathbar.open-item-new-window + + + Open in New _Tab + pathbar.open-item-new-tab + + + _Properties + pathbar.properties + + + + + New _Folder… + view.new-folder + + + New _Document + templates-submenu + + + + Open _With… + view.open-current-directory-with-other-application + + + Open in Consol_e + view.current-directory-console + action-disabled + +
+ + R_eload + win.reload + action-disabled + + + St_op + win.stop + action-disabled + + + Add to _Bookmarks + win.bookmark-current-location + + + _Copy Location + view.copy-current-location + +
+
+
+ + Empty _Trash + view.empty-trash + action-disabled + +
+
+ + P_roperties + view.current-directory-properties + action-disabled + +
+
+
diff --git a/src/resources/ui/nautilus-preferences-window.ui b/src/resources/ui/nautilus-preferences-window.ui new file mode 100644 index 0000000..abee4f2 --- /dev/null +++ b/src/resources/ui/nautilus-preferences-window.ui @@ -0,0 +1,164 @@ + + + + + False + True + + + General + True + + + General + True + + + sort_folders_first_switch + 0 + Sort _Folders Before Files + 0 + True + True + + + center + + + + + + + use_tree_view_switch + 0 + _Expandable Folders in List View + 0 + True + False + + + center + + + + + + + 0 + Action to Open Items + 0 + True + True + + + + + + + Optional Context Menu Actions + Show more actions in the menus. Keyboard shortcuts can be used even if the actions are not shown. + True + + + show_create_link_switch + 0 + Create _Link + 0 + True + True + + + center + + + + + + + show_delete_permanently_switch + 0 + _Delete Permanently + 0 + True + True + + + center + + + + + + + + + Performance + These features may cause slowdowns and excess network usage, especially when browsing files outside this computer, such as on a remote server. + True + + + 0 + Search in Subfolders + 0 + True + True + + + + + 0 + Show Thumbnails + 0 + True + True + + + + + 0 + Count Number of Files in Folders + 0 + True + True + + + + + + + Add information to be displayed beneath file and folder names. More information will appear when zooming closer. + Grid View Captions + True + + + 0 + First + 0 + True + True + + + + + 0 + Second + 0 + True + True + + + + + 0 + Third + 0 + True + True + + + + + + + + diff --git a/src/resources/ui/nautilus-progress-indicator.ui b/src/resources/ui/nautilus-progress-indicator.ui new file mode 100644 index 0000000..a44f8d1 --- /dev/null +++ b/src/resources/ui/nautilus-progress-indicator.ui @@ -0,0 +1,70 @@ + + + +
+ Sort + +
+
+ + _Visible Columns… + view.visible-columns + action-missing + +
+
+ + + + never + 270 + True + + + 6 + 6 + 6 + 6 + none + False + + + + + + + + +
diff --git a/src/resources/ui/nautilus-progress-info-widget.ui b/src/resources/ui/nautilus-progress-info-widget.ui new file mode 100644 index 0000000..db18d99 --- /dev/null +++ b/src/resources/ui/nautilus-progress-info-widget.ui @@ -0,0 +1,75 @@ + + + + + window-close-symbolic + + + diff --git a/src/resources/ui/nautilus-properties-window.ui b/src/resources/ui/nautilus-properties-window.ui new file mode 100644 index 0000000..b72fae9 --- /dev/null +++ b/src/resources/ui/nautilus-properties-window.ui @@ -0,0 +1,913 @@ + + + + + diff --git a/src/resources/ui/nautilus-rename-file-popover.ui b/src/resources/ui/nautilus-rename-file-popover.ui new file mode 100644 index 0000000..205a85c --- /dev/null +++ b/src/resources/ui/nautilus-rename-file-popover.ui @@ -0,0 +1,54 @@ + + + + + + + 18 + 18 + 18 + 18 + vertical + + + 12 + + + + + + + New Filename + + 12 + + + + + + + 12 + 0 + True + 0 + + + + + + + _Rename + False + end + True + + + + + + + diff --git a/src/resources/ui/nautilus-search-popover.ui b/src/resources/ui/nautilus-search-popover.ui new file mode 100644 index 0000000..08ce86a --- /dev/null +++ b/src/resources/ui/nautilus-search-popover.ui @@ -0,0 +1,336 @@ + + + + + + vertical + + + + + + + + + + + + + + + + diff --git a/src/resources/ui/nautilus-toolbar-view-menu.ui b/src/resources/ui/nautilus-toolbar-view-menu.ui new file mode 100644 index 0000000..ffdd3ac --- /dev/null +++ b/src/resources/ui/nautilus-toolbar-view-menu.ui @@ -0,0 +1,63 @@ + + + + + + view.sort + ('name',false) + _A-Z + action-disabled + + + view.sort + ('name',true) + _Z-A + action-disabled + + + view.sort + ('date_modified',true) + Last _Modified + action-disabled + + + view.sort + ('date_modified',false) + _First Modified + action-disabled + + + view.sort + ('size',true) + _Size + action-disabled + + + view.sort + ('type',false) + _Type + action-disabled + + + view.sort + ('trashed_on',true) + Last _Trashed + action-disabled + last_trashed + + + view.sort + ('recency',true) + Recency + action-disabled + recency + + + view.sort + ('search_relevance',true) + Relevance + action-disabled + relevance + + + diff --git a/src/resources/ui/nautilus-toolbar.ui b/src/resources/ui/nautilus-toolbar.ui new file mode 100644 index 0000000..cdccb89 --- /dev/null +++ b/src/resources/ui/nautilus-toolbar.ui @@ -0,0 +1,227 @@ + + + + +
+ + New Window + app.clone-window + window-new-symbolic + + + New Tab + win.new-tab + tab-new-symbolic + +
+
+ Icon Size + inline-buttons + + zoom-out + + + zoom-in + +
+
+ + + _Undo + win.undo + + + _Redo + win.undo + +
+
+ + Show _Hidden Files + view.show-hidden-files + +
+
+ + _Preferences + app.preferences + + + _Keyboard Shortcuts + app.show-help-overlay + + + _Help + app.help + + + _About Files + app.about + +
+
+ +
diff --git a/src/resources/ui/nautilus-view-controls.ui b/src/resources/ui/nautilus-view-controls.ui new file mode 100644 index 0000000..2231d66 --- /dev/null +++ b/src/resources/ui/nautilus-view-controls.ui @@ -0,0 +1,42 @@ + + + +
+ Sort + +
+
+ + _Visible Columns… + view.visible-columns + action-missing + +
+
+ +
diff --git a/src/resources/ui/nautilus-window.ui b/src/resources/ui/nautilus-window.ui new file mode 100644 index 0000000..d8db7f7 --- /dev/null +++ b/src/resources/ui/nautilus-window.ui @@ -0,0 +1,107 @@ + + + + +
+ + _New Tab + win.new-tab + +
+
+ + Move Tab _Left + win.tab-move-left + + + Move Tab _Right + win.tab-move-right + +
+
+ + _Close Tab + win.close-current-view + +
+
+ +
-- cgit v1.2.3