Inkscape
Vector Graphics Editor
Loading...
Searching...
No Matches
recent-files.cpp
Go to the documentation of this file.
1// SPDX-License-Identifier: GPL-2.0-or-later
2/*
3 * Create a list of recentyly used files.
4 *
5 * Copyright 2025 Martin Owens <doctormo@geek-2.com>
6 * Copyright 2024, 2025 Tavmjong Bah <tavmjong@free.fr>
7 *
8 * Released under GNU GPL v2+, read the file 'COPYING' for more information.
9 */
10
11#include <algorithm>
12#include <cassert>
13
14#include "recent-files.h"
15#include "io/fix-broken-links.h"
16
17namespace Inkscape {
18
26std::vector<Glib::RefPtr<Gtk::RecentInfo>> getInkscapeRecentFiles(unsigned max_files)
27{
28 std::vector<std::pair<std::string, Glib::ustring>> output;
29
30 auto recent_manager = Gtk::RecentManager::get_default();
31 // All recent files, not necessarily inkscape only (std::vector)
32 auto recent_files = recent_manager->get_items();
33
34 // Remove non-inkscape files.
35 std::erase_if(recent_files, [](auto const &recent_file) -> bool {
36 // Note: Do not check if the file exists, to avoid long delays. See https://gitlab.com/inkscape/inkscape/-/issues/2348.
37 bool valid_file =
38 recent_file->has_application(g_get_prgname()) ||
39 recent_file->has_application("org.inkscape.Inkscape") ||
40 recent_file->has_application("inkscape") ||
41 recent_file->has_application("inkscape.exe");
42 return !valid_file;
43 });
44
45 // Ensure that display uri's are unique. It is possible that an XBEL file
46 // has multiple entries for the same file as a path can be written in equivalent
47 // ways: i.e. with a ';' or '%3B', or with a drive name of 'c' or 'C' on Windows.
48 // These entries may have the same display uri's. This causes segfaults in
49 // getShortendPathmap().
50 auto sort_comparator_uri =
51 [](auto const a, auto const b) -> bool { return a->get_uri_display() < b->get_uri_display(); };
52 std::sort (recent_files.begin(), recent_files.end(), sort_comparator_uri);
53
54 auto unique_comparator_uri =
55 [](auto const a, auto const b) -> bool { return a->get_uri_display() == b->get_uri_display(); };
56 auto it_u = std::unique (recent_files.begin(), recent_files.end(), unique_comparator_uri);
57 recent_files.erase(it_u, recent_files.end());
58
59 // Sort by "last modified" time, which puts the most recently opened files first.
60 std::sort(std::begin(recent_files), std::end(recent_files), [](auto const &a, auto const &b) -> bool {
61 // a should precede b if a->get_modified() is later than b->get_modified()
62 return a->get_modified().compare(b->get_modified()) > 0;
63 });
64
65 // Truncate to user-specified max_files.
66 if (max_files && recent_files.size() > max_files) {
67 recent_files.resize(max_files);
68 }
69
70 return recent_files;
71}
72
77std::map<Glib::ustring, std::string> getShortenedPathMap(std::vector<Glib::RefPtr<Gtk::RecentInfo>> const &recent_files)
78{
79 // Create a map of path to shortened path, and prefill.
80 std::map<Glib::ustring, std::string> shortened_path_map;
81 std::vector<Glib::RefPtr<Gtk::RecentInfo>> copy = recent_files;
82 for (auto recent_file : copy) {
83 shortened_path_map[recent_file->get_uri_display()] = recent_file->get_display_name();
84 }
85
86 // Look for duplicate short names. These are the only ones that matter here.
87 auto equal_comparator = [](auto const a, auto const b) -> bool { return a->get_display_name() == b->get_display_name(); };
88 auto it = copy.begin();
89
90 while (it != (copy.end() - 1)) {
91 it = std::adjacent_find(it, copy.end(), equal_comparator);
92 if (it != copy.end()) {
93
94 // Found duplicate display name!
95 std::vector<Glib::ustring> display_uris;
96 display_uris.emplace_back(( * it )->get_uri_display());
97 display_uris.emplace_back(( *(it+1))->get_uri_display());
98
99 std::vector<std::vector<std::string>> path_parts;
100 path_parts.emplace_back(Inkscape::splitPath((* it )->get_uri_display()));
101 path_parts.emplace_back(Inkscape::splitPath((*(it+1))->get_uri_display()));
102
103 // Find first directory difference from root down.
104 auto max_size = std::min(path_parts[0].size(), path_parts[1].size());
105 unsigned i = 0;
106 for (; i < max_size; ++i) {
107 if (path_parts[0][i] != path_parts[1][i]) {
108 break;
109 }
110 }
111 assert(i < max_size); // Paths are assured to always have a difference.
112
113 // Override map of path to shortened path.
114 for (int j = 0; j < 2; j++) {
115
116 auto display_uri = display_uris[j]; // We always use display_uri as map index.
117 // Size is always one first element such as '/' or 'C:\\' and the last element is the filename
118 auto size = path_parts[j].size();
119
120 if (size <= 3) {
121 // If file is in root directory or child of root directory, just use display uri.
122 shortened_path_map[display_uri] = display_uri;
123 } else if (i == size - 1) {
124 // If difference is at last path part (file name), use that.
125 shortened_path_map[display_uri] = path_parts[j].back();
126 } else if (i == size - 2) {
127
128 // If difference is last directory level (file name), use that + file name.
129 shortened_path_map[display_uri] =
130 Glib::ustring::compose ("..%1%2%3%4",
131 G_DIR_SEPARATOR_S,
132 path_parts[j][size-2],
133 G_DIR_SEPARATOR_S,
134 path_parts[j][size-1]);
135 } else if (i == 1) {
136 // parts[j][i] is actually a root folder
137 shortened_path_map[display_uri] =
138 Glib::ustring::compose ("%1%2%3..%4%5",
139 path_parts[j][0],
140 path_parts[j][i],
141 G_DIR_SEPARATOR_S,
142 G_DIR_SEPARATOR_S,
143 path_parts[j][size-1]);
144 } else {
145 shortened_path_map[display_uri] =
146 Glib::ustring::compose ("..%1%2%3..%4%5",
147 G_DIR_SEPARATOR_S,
148 path_parts[j][i],
149 G_DIR_SEPARATOR_S,
150 G_DIR_SEPARATOR_S,
151 path_parts[j][size-1]);
152 }
153 }
154 } else {
155 // At end!
156 break;
157 }
158
159 // Test next entry.
160 ++it;
161 }
162
163 return shortened_path_map;
164}
165
166} // namespace Inkscape
167
168/*
169 Local Variables:
170 mode:c++
171 c-file-style:"stroustrup"
172 c-file-offsets:((innamespace . 0)(inline-open . 0)(case-label . +))
173 indent-tabs-mode:nil
174 fill-column:99
175 End:
176*/
177// vim: filetype=cpp:expandtab:shiftwidth=4:tabstop=8:softtabstop=4 :
Helper class to stream background task notifications as a series of messages.
std::vector< std::string > splitPath(std::string const &path)
std::vector< Glib::RefPtr< Gtk::RecentInfo > > getInkscapeRecentFiles(unsigned max_files)
Generate a vector of recently used Inkscape files.
std::map< Glib::ustring, std::string > getShortenedPathMap(std::vector< Glib::RefPtr< Gtk::RecentInfo > > const &recent_files)
Generate the shortened labeles for a list of recently used files.