1 /*
2  *  insertnum.c
3  *
4  *  Copyright 2010 Dimitar Toshkov Zhekov <dimitar.zhekov@gmail.com>
5  *
6  *  This program is free software; you can redistribute it and/or modify
7  *  it under the terms of the GNU General Public License as published by
8  *  the Free Software Foundation; either version 2 of the License, or
9  *  (at your option) any later version.
10  *
11  *  This program is distributed in the hope that it will be useful,
12  *  but WITHOUT ANY WARRANTY; without even the implied warranty of
13  *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14  *  GNU General Public License for more details.
15  *
16  *  You should have received a copy of the GNU General Public License
17  *  along with this program. If not, see <http://www.gnu.org/licenses/>.
18  */
19 
20 #ifdef HAVE_CONFIG_H
21 # include "config.h"
22 #endif
23 
24 #include <ctype.h>
25 #include <limits.h>
26 #include <stdlib.h>
27 #include <string.h>
28 
29 #include <geanyplugin.h>
30 
31 #ifndef GTK_COMPAT_H
32 #define GtkComboBoxText GtkComboBox
33 #define GTK_COMBO_BOX_TEXT GTK_COMBO_BOX
34 #define gtk_combo_box_text_new_with_entry gtk_combo_box_entry_new_text
35 #define gtk_combo_box_text_append_text gtk_combo_box_append_text
36 #endif
37 
38 GeanyPlugin	*geany_plugin;
39 GeanyData	*geany_data;
40 
41 PLUGIN_VERSION_CHECK(224)
42 
43 PLUGIN_SET_TRANSLATABLE_INFO(LOCALEDIR, GETTEXT_PACKAGE,
44 	_("Insert Numbers"), _("Insert/Fill columns with numbers."
45 	"\nThis plugin currently has no maintainer. Would you like to help"
46 	" by contributing to this plugin?"),
47 	"0.2.2", "Dimitar Toshkov Zhekov <dimitar.zhekov@gmail.com>")
48 
49 /* Keybinding(s) */
50 enum
51 {
52 	INSERT_NUMBERS_KB,
53 	COUNT_KB
54 };
55 
56 /* when altering the RANGE_ or MAX_LINES, make sure that RANGE_ * MAX_LINES
57    fit in gint64, and that RANGE_LEN is enough for RANGE_ digits and sign */
58 #define RANGE_MIN (-2147483647 - 1)
59 #define RANGE_MAX 2147483647
60 #define RANGE_LEN 11
61 #define RANGE_TOOLTIP "-2147483648..2147483647"
62 #define MAX_LINES 250000
63 
64 typedef struct _InsertNumbersDialog
65 {
66 	GtkWidget *dialog;
67 	GtkWidget *start, *step;
68 	GtkWidget *base, *lower;
69 	GtkWidget *prefix, *zero;
70 } InsertNumbersDialog;
71 
72 typedef gboolean (*entry_valid)(const gchar *text);
73 
74 static GtkWidget *main_menu_item = NULL;
75 static gint start_pos, start_line;
76 static gint end_pos, end_line;
77 /* input data */
78 static gint64 start_value;
79 static gint64 step_value;
80 static gint base_value;
81 static gboolean lower_case = 0;
82 static gboolean base_prefix = 0;
83 static gboolean pad_zeros = 0;
84 
plugin_beep(void)85 static void plugin_beep(void)
86 {
87 	if (geany_data->prefs->beep_on_errors)
88 		gdk_beep();
89 }
90 
can_insert_numbers(void)91 static gboolean can_insert_numbers(void)
92 {
93 	GeanyDocument *doc = document_get_current();
94 
95 	if (doc && !doc->readonly)
96 	{
97 		ScintillaObject *sci = doc->editor->sci;
98 
99 		if (sci_has_selection(sci) && (sci_get_selection_mode(sci) == SC_SEL_RECTANGLE ||
100 			sci_get_selection_mode(sci) == SC_SEL_THIN))
101 		{
102 			start_pos = sci_get_selection_start(sci);
103 			start_line = sci_get_line_from_position(sci, start_pos);
104 			end_pos = sci_get_selection_end(sci);
105 			end_line = sci_get_line_from_position(sci, end_pos);
106 
107 			return end_line - start_line < MAX_LINES;
108 		}
109 	}
110 
111 	return FALSE;
112 }
113 
update_display(void)114 static void update_display(void)
115 {
116 	while (gtk_events_pending())
117 		gtk_main_iteration();
118 }
119 
120 #define sci_point_x_from_position(sci, position) \
121 	scintilla_send_message(sci, SCI_POINTXFROMPOSITION, 0, position)
122 #define sci_get_pos_at_line_sel_start(sci, line) \
123 	scintilla_send_message(sci, SCI_GETLINESELSTARTPOSITION, line, 0)
124 
insert_numbers(gboolean * cancel)125 static void insert_numbers(gboolean *cancel)
126 {
127 	/* editor */
128 	ScintillaObject *sci = document_get_current()->editor->sci;
129 	gint xinsert = sci_point_x_from_position(sci, start_pos);
130 	gint xend = sci_point_x_from_position(sci, end_pos);
131 	gint *line_pos = g_new(gint, end_line - start_line + 1);
132 	gint line, i;
133 	/* generator */
134 	gint64 start = start_value;
135 	gint64 value;
136 	unsigned count = 0;
137 	size_t prefix_len = 0;
138 	int plus = 0, minus;
139 	size_t length, lend;
140 	char pad, aax;
141 	gchar *buffer;
142 
143 	if (xend < xinsert)
144 		xinsert = xend;
145 
146 	ui_progress_bar_start(_("Counting..."));
147 	/* lines shorter than the current selection are skipped */
148 	for (line = start_line, i = 0; line <= end_line; line++, i++)
149 	{
150 		if (sci_point_x_from_position(sci,
151 			scintilla_send_message(sci, SCI_GETLINEENDPOSITION, line, 0)) >= xinsert)
152 		{
153 			line_pos[i] = sci_get_pos_at_line_sel_start(sci, line) -
154 				sci_get_position_from_line(sci, line);
155 			count++;
156 		}
157 		else
158 			line_pos[i] = -1;
159 
160 		if (cancel && i % 2500 == 0)
161 		{
162 			update_display();
163 			if (*cancel)
164 			{
165 				ui_progress_bar_stop();
166 				g_free(line_pos);
167 				return;
168 			}
169 		}
170 	}
171 
172 	switch (base_value * base_prefix)
173 	{
174 		case 8 : prefix_len = 1; break;
175 		case 16 : prefix_len = 2; break;
176 		case 10 : plus++;
177 	}
178 
179 	value = start + (count - 1) * step_value;
180 	minus = start < 0 || value < 0;
181 	lend = plus || (pad_zeros ? minus : value < 0);
182 	while (value /= base_value) lend++;
183 	value = start;
184 	length = plus || (pad_zeros ? minus : value < 0);
185 	while (value /= base_value) length++;
186 	length = prefix_len + (length > lend ? length : lend) + 1;
187 
188 	buffer = g_new(gchar, length + 1);
189 	buffer[length] = '\0';
190 	pad = pad_zeros ? '0' : ' ';
191 	aax = (lower_case ? 'a' : 'A') - 10;
192 
193 	gtk_progress_bar_set_text(GTK_PROGRESS_BAR(geany->main_widgets->progressbar),
194 		_("Preparing..."));
195 	update_display();
196 	sci_start_undo_action(sci);
197 	sci_replace_sel(sci, "");
198 
199 	gtk_progress_bar_set_text(GTK_PROGRESS_BAR(geany->main_widgets->progressbar),
200 		_("Inserting..."));
201 	for (line = start_line, i = 0; line <= end_line; line++, i++)
202 	{
203 		gchar *beg, *end;
204 		gint insert_pos;
205 
206 		if (line_pos[i] < 0)
207 			continue;
208 
209 		beg = buffer;
210 		end = buffer + length;
211 		value = ABS(start);
212 
213 		do
214 		{
215 			unsigned digit = value % base_value;
216 			*--end = digit + (digit < 10 ? '0' : aax);
217 		} while (value /= base_value);
218 
219 		if (pad_zeros)
220 		{
221 			if (start < 0) *beg++ = '-';
222 			else if (plus) *beg++ = '+';
223 			else if (minus) *beg++ = ' ';
224 			memcpy(beg, "0x", prefix_len);
225 			beg += prefix_len;
226 		}
227 		else
228 		{
229 			if (start < 0) *--end = '-';
230 			else if (plus) *--end = '+';
231 			end -= prefix_len;
232 			memcpy(end, "0x", prefix_len);
233 		}
234 
235 		memset(beg, pad, end - beg);
236 		insert_pos = sci_get_position_from_line(sci, line) + line_pos[i];
237 		sci_insert_text(sci, insert_pos, buffer);
238 		start += step_value;
239 
240 		if (cancel && i % 1000 == 0)
241 		{
242 			update_display();
243 			if (*cancel)
244 			{
245 				scintilla_send_message(sci, SCI_GOTOPOS, insert_pos + length, 0);
246 				break;
247 			}
248 		}
249 	}
250 
251 	sci_end_undo_action(sci);
252 	g_free(buffer);
253 	g_free(line_pos);
254 	ui_progress_bar_stop();
255 }
256 
257 /* interface */
on_base_insert_text(G_GNUC_UNUSED GtkEntry * entry,const gchar * text,gint length,G_GNUC_UNUSED gint * position,G_GNUC_UNUSED gpointer data)258 static void on_base_insert_text(G_GNUC_UNUSED GtkEntry *entry, const gchar *text, gint length,
259 	G_GNUC_UNUSED gint *position, G_GNUC_UNUSED gpointer data)
260 {
261 	gint i;
262 
263 	if (length == -1)
264 		length = strlen(text);
265 
266 	for (i = 0; i < length; i++)
267 	{
268 		if (!isdigit(text[i]))
269 		{
270 			g_signal_stop_emission_by_name(G_OBJECT(entry), "insert-text");
271 			break;
272 		}
273 	}
274 }
275 
on_insert_numbers_response(G_GNUC_UNUSED GtkDialog * dialog,G_GNUC_UNUSED gint response_id,G_GNUC_UNUSED gpointer user_data)276 static void on_insert_numbers_response(G_GNUC_UNUSED GtkDialog *dialog,
277 	G_GNUC_UNUSED gint response_id, G_GNUC_UNUSED gpointer user_data)
278 {
279 	*(gboolean *) user_data = TRUE;
280 }
281 
on_insert_numbers_ok_clicked(G_GNUC_UNUSED GtkButton * button,gpointer user_data)282 static void on_insert_numbers_ok_clicked(G_GNUC_UNUSED GtkButton *button, gpointer user_data)
283 {
284 	InsertNumbersDialog *d = (InsertNumbersDialog *) user_data;
285 	GtkWidget *bad_entry = NULL;
286 
287 	start_value = gtk_spin_button_get_value(GTK_SPIN_BUTTON(d->start));
288 	step_value = gtk_spin_button_get_value(GTK_SPIN_BUTTON(d->step));
289 	base_value = atoi(gtk_entry_get_text(GTK_ENTRY(d->base)));
290 	lower_case = gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(d->lower));
291 	base_prefix = gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(d->prefix));
292 	pad_zeros = gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(d->zero));
293 
294 	if (!step_value)
295 		bad_entry = d->step;
296 	else if (base_value < 2 || base_value > 36)
297 		bad_entry = d->base;
298 
299 	if (bad_entry)
300 	{
301 		plugin_beep();
302 		gtk_widget_grab_focus(bad_entry);
303 		return;
304 	}
305 
306 	gtk_dialog_response(GTK_DIALOG(d->dialog), GTK_RESPONSE_ACCEPT);
307 }
308 
set_entry(GtkWidget * entry,gint maxlen,GtkWidget * label,const gchar * tooltip)309 static void set_entry(GtkWidget *entry, gint maxlen, GtkWidget *label, const gchar *tooltip)
310 {
311 	gtk_entry_set_max_length(GTK_ENTRY(entry), maxlen);
312 	gtk_entry_set_activates_default(GTK_ENTRY(entry), TRUE);
313 	gtk_label_set_mnemonic_widget(GTK_LABEL(label), entry);
314 	gtk_widget_set_tooltip_text(entry, tooltip);
315 }
316 
317 #if !GTK_CHECK_VERSION(3, 0, 0)
318 #define GtkGrid GtkTable
319 #define GTK_GRID GTK_TABLE
320 #define gtk_grid_attach(grid, child, left, top, width, height) \
321 	gtk_table_attach_defaults(grid, child, left, left + width, top, top + height)
322 #define gtk_grid_set_row_spacing gtk_table_set_row_spacings
323 #define gtk_grid_set_column_spacing gtk_table_set_col_spacings
324 #endif
325 
on_insert_numbers_activate(G_GNUC_UNUSED GtkMenuItem * menuitem,G_GNUC_UNUSED gpointer gdata)326 static void on_insert_numbers_activate(G_GNUC_UNUSED GtkMenuItem *menuitem, G_GNUC_UNUSED
327 	gpointer gdata)
328 {
329 	InsertNumbersDialog d;
330 	GtkWidget *vbox, *label, *upper, *space, *button;
331 	GtkGrid *grid;
332 	GtkComboBoxText *combo;
333 	const char *case_tip = _("For base 11 and above");
334 	gchar *base_text;
335 	gint result;
336 
337 	d.dialog = gtk_dialog_new_with_buttons(_("Insert Numbers"),
338 		GTK_WINDOW(geany->main_widgets->window),
339 		GTK_DIALOG_MODAL | GTK_DIALOG_DESTROY_WITH_PARENT,
340 		GTK_STOCK_CANCEL, GTK_RESPONSE_REJECT, NULL);
341 	vbox = ui_dialog_vbox_new(GTK_DIALOG(d.dialog));
342 	gtk_box_set_spacing(GTK_BOX(vbox), 9);
343 
344 #if GTK_CHECK_VERSION(3, 0, 0)
345 	grid = GTK_GRID(gtk_grid_new());
346 #else
347 	grid = GTK_TABLE(gtk_table_new(3, 6, FALSE));
348 #endif
349 	gtk_grid_set_row_spacing(grid, 6);
350 	gtk_grid_set_column_spacing(grid, 6);
351 	gtk_box_pack_start(GTK_BOX(vbox), GTK_WIDGET(grid), TRUE, TRUE, 0);
352 
353 	label = gtk_label_new_with_mnemonic(_("_Start:"));
354 	gtk_grid_attach(grid, label, 0, 0, 1, 1);
355 	d.start = gtk_spin_button_new_with_range(RANGE_MIN, RANGE_MAX, 1);
356 	set_entry(d.start, RANGE_LEN, label, RANGE_TOOLTIP);
357 	gtk_grid_attach(grid, d.start, 1, 0, 2, 1);
358 	label = gtk_label_new_with_mnemonic(_("S_tep:"));
359 	gtk_grid_attach(grid, label, 3, 0, 1, 1);
360 	d.step = gtk_spin_button_new_with_range(RANGE_MIN, RANGE_MAX, 1);
361 	set_entry(d.step, RANGE_LEN, label, RANGE_TOOLTIP);
362 	gtk_grid_attach(grid, d.step, 4, 0, 2, 1);
363 
364 	label = gtk_label_new_with_mnemonic(_("_Base:"));
365 	gtk_grid_attach(grid, label, 0, 1, 1, 1),
366 	combo = GTK_COMBO_BOX_TEXT(gtk_combo_box_text_new_with_entry());
367 	d.base = gtk_bin_get_child(GTK_BIN(combo));
368 	set_entry(d.base, 2, label, "2..36");
369 	g_signal_connect(d.base, "insert-text", G_CALLBACK(on_base_insert_text), NULL);
370 	gtk_combo_box_text_append_text(combo, "2");
371 	gtk_combo_box_text_append_text(combo, "8");
372 	gtk_combo_box_text_append_text(combo, "10");
373 	gtk_combo_box_text_append_text(combo, "16");
374 #if GTK_CHECK_VERSION(3, 0, 0)
375 	gtk_grid_attach(grid, GTK_WIDGET(combo), 1, 1, 2, 1);
376 	gtk_widget_set_hexpand(GTK_WIDGET(combo), TRUE);
377 #else
378 	gtk_table_attach(grid, GTK_WIDGET(combo), 1, 3, 1, 2, GTK_EXPAND | GTK_FILL, 0, 0, 0);
379 #endif
380 	label = gtk_label_new(_("Letters:"));
381 	gtk_widget_set_tooltip_text(label, case_tip);
382 	gtk_grid_attach(grid, label, 3, 1, 1, 1);
383 	upper = gtk_radio_button_new_with_mnemonic(NULL, _("_Upper"));
384 	gtk_widget_set_tooltip_text(upper, case_tip);
385 	gtk_grid_attach(grid, upper, 4, 1, 1, 1);
386 	d.lower = gtk_radio_button_new_from_widget(GTK_RADIO_BUTTON(upper));
387 	gtk_widget_set_tooltip_text(label, case_tip);
388 	label = gtk_label_new_with_mnemonic(_("_Lower"));
389 	gtk_widget_set_tooltip_text(label, case_tip);
390 	gtk_container_add(GTK_CONTAINER(d.lower), label);
391 	gtk_grid_attach(grid, d.lower, 5, 1, 1, 1);
392 
393 	d.prefix = gtk_check_button_new_with_mnemonic(_("Base _prefix"));
394 	gtk_widget_set_tooltip_text(d.prefix,
395 		_("0 for octal, 0x for hex, + for positive decimal"));
396 	gtk_grid_attach(grid, d.prefix, 1, 2, 2, 1);
397 	label = gtk_label_new(_("Padding:"));
398 	gtk_grid_attach(grid, label, 3, 2, 1, 1);
399 	space = gtk_radio_button_new_with_mnemonic(NULL, _("Sp_ace"));
400 	gtk_grid_attach(grid, space, 4, 2, 1, 1);
401 	d.zero = gtk_radio_button_new_from_widget(GTK_RADIO_BUTTON(space));
402 	label = gtk_label_new_with_mnemonic(_("_Zero"));
403 	gtk_container_add(GTK_CONTAINER(d.zero), label);
404 	gtk_grid_attach(grid, d.zero, 5, 2, 1, 1);
405 
406 	button = gtk_button_new_from_stock(GTK_STOCK_OK);
407 	g_signal_connect(button, "clicked", G_CALLBACK(on_insert_numbers_ok_clicked), &d);
408 	gtk_box_pack_end(GTK_BOX(gtk_dialog_get_action_area(GTK_DIALOG(d.dialog))), button,
409 		TRUE, TRUE, 0);
410 #if GTK_CHECK_VERSION(2, 18, 0)
411 	gtk_widget_set_can_default(button, TRUE);
412 #else
413 	GTK_WIDGET_SET_FLAGS(button, GTK_CAN_DEFAULT);
414 #endif
415 	gtk_widget_grab_default(button);
416 
417 	gtk_spin_button_set_value(GTK_SPIN_BUTTON(d.start), start_value);
418 	gtk_spin_button_set_value(GTK_SPIN_BUTTON(d.step), step_value);
419 	base_text = g_strdup_printf("%d", base_value);
420 	gtk_entry_set_text(GTK_ENTRY(d.base), base_text);
421 	g_free(base_text);
422 	gtk_button_clicked(GTK_BUTTON(lower_case ? d.lower : upper));
423 	gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(d.prefix), base_prefix);
424 	gtk_button_clicked(GTK_BUTTON(pad_zeros ? d.zero : space));
425 
426 	gtk_widget_show_all(d.dialog);
427 	result = gtk_dialog_run(GTK_DIALOG(d.dialog));
428 
429 	if (result == GTK_RESPONSE_ACCEPT)
430 	{
431 		if (can_insert_numbers())
432 		{
433 			if (end_line - start_line < 1000)
434 			{
435 				/* quick version */
436 				gtk_widget_hide(d.dialog);
437 				insert_numbers(NULL);
438 			}
439 			else
440 			{
441 				gboolean cancel = FALSE;
442 
443 				gtk_widget_set_sensitive(GTK_WIDGET(grid), FALSE);
444 				gtk_widget_set_sensitive(button, FALSE);
445 				update_display();
446 				g_signal_connect(d.dialog, "response",
447 					G_CALLBACK(on_insert_numbers_response), &cancel);
448 				insert_numbers(&cancel);
449 			}
450 		}
451 		else
452 			plugin_beep();	/* reloaded or something */
453 	}
454 
455 	gtk_widget_destroy(d.dialog);
456 }
457 
on_insert_numbers_key(G_GNUC_UNUSED guint key_id)458 static void on_insert_numbers_key(G_GNUC_UNUSED guint key_id)
459 {
460 	if (can_insert_numbers())
461 		on_insert_numbers_activate(NULL, NULL);
462 }
463 
on_tools_show(G_GNUC_UNUSED GtkMenuItem * menuitem,G_GNUC_UNUSED gpointer gdata)464 static void on_tools_show(G_GNUC_UNUSED GtkMenuItem *menuitem, G_GNUC_UNUSED gpointer gdata)
465 {
466 	gtk_widget_set_sensitive(main_menu_item, can_insert_numbers());
467 }
468 
plugin_init(G_GNUC_UNUSED GeanyData * data)469 void plugin_init(G_GNUC_UNUSED GeanyData *data)
470 {
471 	GeanyKeyGroup *plugin_key_group;
472 
473 	plugin_key_group = plugin_set_key_group(geany_plugin, "insert_numbers", COUNT_KB, NULL);
474 
475 	start_value = 1;
476 	step_value = 1;
477 	base_value = 10;
478 
479 	main_menu_item = gtk_menu_item_new_with_mnemonic(_("Insert _Numbers..."));
480 	gtk_widget_show(main_menu_item);
481 	gtk_container_add(GTK_CONTAINER(geany->main_widgets->tools_menu), main_menu_item);
482 	g_signal_connect(main_menu_item, "activate", G_CALLBACK(on_insert_numbers_activate),
483 		NULL);
484 
485 	keybindings_set_item(plugin_key_group, INSERT_NUMBERS_KB, on_insert_numbers_key,
486 		0, 0, "insert_numbers", _("Insert Numbers..."), main_menu_item);
487 
488 	plugin_signal_connect(geany_plugin, G_OBJECT(geany->main_widgets->tools_menu), "show",
489 		FALSE, (GCallback) on_tools_show, NULL);
490 }
491 
plugin_cleanup(void)492 void plugin_cleanup(void)
493 {
494 	gtk_widget_destroy(main_menu_item);
495 }
496