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