1 /*
2 * Copyright (C) 2013-2021 Graeme Gott <graeme@gottcode.org>
3 *
4 * This library is free software; you can redistribute it and/or modify
5 * it under the terms of the GNU General Public License as published by
6 * the Free Software Foundation, either version 2 of the License, or
7 * (at your option) any later version.
8 *
9 * This library is distributed in the hope that it will be useful,
10 * but WITHOUT ANY WARRANTY; without even the implied warranty of
11 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 * GNU General Public License for more details.
13 *
14 * You should have received a copy of the GNU General Public License
15 * along with this library. If not, see <http://www.gnu.org/licenses/>.
16 */
17
18 #include "search-action.h"
19
20 #include "query.h"
21 #include "settings.h"
22
23 using namespace WhiskerMenu;
24
25 //-----------------------------------------------------------------------------
26
SearchAction()27 SearchAction::SearchAction() :
28 m_is_regex(false),
29 m_show_description(true),
30 m_regex(nullptr)
31 {
32 set_icon("folder-saved-search");
33 update_text();
34 }
35
36 //-----------------------------------------------------------------------------
37
SearchAction(const gchar * name,const gchar * pattern,const gchar * command,bool is_regex,bool show_description)38 SearchAction::SearchAction(const gchar* name, const gchar* pattern, const gchar* command, bool is_regex, bool show_description) :
39 m_name(name ? name : ""),
40 m_pattern(pattern ? pattern : ""),
41 m_command(command ? command : ""),
42 m_is_regex(is_regex),
43 m_show_description(show_description),
44 m_regex(nullptr)
45 {
46 set_icon("folder-saved-search");
47 update_text();
48 }
49
50 //-----------------------------------------------------------------------------
51
~SearchAction()52 SearchAction::~SearchAction()
53 {
54 if (m_regex)
55 {
56 g_regex_unref(m_regex);
57 }
58 }
59
60 //-----------------------------------------------------------------------------
61
search(const Query & query)62 unsigned int SearchAction::search(const Query& query)
63 {
64 if (m_pattern.empty() || m_command.empty())
65 {
66 return false;
67 }
68
69 m_expanded_command.clear();
70
71 const gchar* haystack = query.raw_query().c_str();
72 unsigned int found = !m_is_regex ? match_prefix(haystack) : match_regex(haystack);
73
74 const bool show_description = wm_settings->launcher_show_description && (wm_settings->view_mode != Settings::ViewAsIcons);
75 if ((found != UINT_MAX) && (m_show_description != show_description))
76 {
77 m_show_description = show_description;
78 update_text();
79 }
80
81 return found;
82 }
83
84 //-----------------------------------------------------------------------------
85
match_prefix(const gchar * haystack)86 unsigned int SearchAction::match_prefix(const gchar* haystack)
87 {
88 if (!g_str_has_prefix(haystack, m_pattern.c_str()))
89 {
90 return UINT_MAX;
91 }
92
93 gchar* trimmed = g_strdup(haystack + m_pattern.length());
94 trimmed = g_strstrip(trimmed);
95
96 gchar* uri = nullptr;
97
98 m_expanded_command = m_command;
99 std::string::size_type pos = 0, lastpos = m_expanded_command.length() - 1;
100 while ((pos = m_expanded_command.find('%', pos)) != std::string::npos)
101 {
102 if (pos == lastpos)
103 {
104 break;
105 }
106
107 switch (m_expanded_command[pos + 1])
108 {
109 case 's':
110 m_expanded_command.replace(pos, 2, trimmed);
111 pos += strlen(trimmed) + 1;
112 break;
113
114 case 'S':
115 m_expanded_command.replace(pos, 2, haystack);
116 pos += strlen(haystack) + 1;
117 break;
118
119 case 'u':
120 if (!uri)
121 {
122 uri = g_uri_escape_string(trimmed, nullptr, true);
123 }
124 m_expanded_command.replace(pos, 2, uri);
125 pos += strlen(uri) + 1;
126 break;
127
128 case '%':
129 m_expanded_command.erase(pos, 1);
130 pos += 1;
131 break;
132
133 default:
134 m_expanded_command.erase(pos, 2);
135 break;
136 }
137 }
138
139 g_free(trimmed);
140 g_free(uri);
141
142 return m_pattern.length();
143 }
144
145 //-----------------------------------------------------------------------------
146
match_regex(const gchar * haystack)147 unsigned int SearchAction::match_regex(const gchar* haystack)
148 {
149 unsigned int found = UINT_MAX;
150
151 if (!m_regex)
152 {
153 m_regex = g_regex_new(m_pattern.c_str(), G_REGEX_OPTIMIZE, GRegexMatchFlags(0), nullptr);
154 if (!m_regex)
155 {
156 return found;
157 }
158 }
159 GMatchInfo* match = nullptr;
160 if (g_regex_match(m_regex, haystack, GRegexMatchFlags(0), &match))
161 {
162 gchar* expanded = g_match_info_expand_references(match, m_command.c_str(), nullptr);
163 if (expanded)
164 {
165 m_expanded_command = expanded;
166 g_free(expanded);
167 found = m_pattern.length();
168 }
169 }
170 if (match)
171 {
172 g_match_info_free(match);
173 }
174
175 return found;
176 }
177
178 //-----------------------------------------------------------------------------
179
run(GdkScreen * screen) const180 void SearchAction::run(GdkScreen* screen) const
181 {
182 spawn(screen, m_expanded_command.c_str(), nullptr, false, nullptr);
183 }
184
185 //-----------------------------------------------------------------------------
186
set_name(const gchar * name)187 void SearchAction::set_name(const gchar* name)
188 {
189 if (!name || (m_name == name))
190 {
191 return;
192 }
193
194 m_name = name;
195 wm_settings->set_modified();
196
197 m_show_description = wm_settings->launcher_show_description && (wm_settings->view_mode != Settings::ViewAsIcons);
198 update_text();
199 }
200
201 //-----------------------------------------------------------------------------
202
set_pattern(const gchar * pattern)203 void SearchAction::set_pattern(const gchar* pattern)
204 {
205 if (!pattern || (m_pattern == pattern))
206 {
207 return;
208 }
209
210 m_pattern = pattern;
211 wm_settings->set_modified();
212
213 if (m_regex)
214 {
215 g_regex_unref(m_regex);
216 m_regex = nullptr;
217 }
218 }
219
220 //-----------------------------------------------------------------------------
221
set_command(const gchar * command)222 void SearchAction::set_command(const gchar* command)
223 {
224 if (!command || (m_command == command))
225 {
226 return;
227 }
228
229 m_command = command;
230 wm_settings->set_modified();
231 }
232
233 //-----------------------------------------------------------------------------
234
set_is_regex(bool is_regex)235 void SearchAction::set_is_regex(bool is_regex)
236 {
237 if (m_is_regex == is_regex)
238 {
239 return;
240 }
241
242 m_is_regex = is_regex;
243 wm_settings->set_modified();
244 }
245
246 //-----------------------------------------------------------------------------
247
update_text()248 void SearchAction::update_text()
249 {
250 const gchar* direction = (gtk_widget_get_default_direction() != GTK_TEXT_DIR_RTL) ? "\342\200\216" : "\342\200\217";
251 const gchar* description = _("Search Action");
252 if (m_show_description)
253 {
254 set_text(g_markup_printf_escaped("%s<b>%s</b>\n%s%s", direction, m_name.c_str(), direction, description));
255 }
256 else
257 {
258 set_text(g_markup_printf_escaped("%s%s", direction, m_name.c_str()));
259 }
260 set_tooltip(description);
261 }
262
263 //-----------------------------------------------------------------------------
264