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