/* * insertnum.c * * Copyright 2010 Dimitar Toshkov Zhekov * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ #ifdef HAVE_CONFIG_H # include "config.h" #endif #include #include #include #include #include #ifndef GTK_COMPAT_H #define GtkComboBoxText GtkComboBox #define GTK_COMBO_BOX_TEXT GTK_COMBO_BOX #define gtk_combo_box_text_new_with_entry gtk_combo_box_entry_new_text #define gtk_combo_box_text_append_text gtk_combo_box_append_text #endif GeanyPlugin *geany_plugin; GeanyData *geany_data; PLUGIN_VERSION_CHECK(224) PLUGIN_SET_TRANSLATABLE_INFO(LOCALEDIR, GETTEXT_PACKAGE, _("Insert Numbers"), _("Insert/Fill columns with numbers." "\nThis plugin currently has no maintainer. Would you like to help" " by contributing to this plugin?"), "0.2.2", "Dimitar Toshkov Zhekov ") /* Keybinding(s) */ enum { INSERT_NUMBERS_KB, COUNT_KB }; /* when altering the RANGE_ or MAX_LINES, make sure that RANGE_ * MAX_LINES fit in gint64, and that RANGE_LEN is enough for RANGE_ digits and sign */ #define RANGE_MIN (-2147483647 - 1) #define RANGE_MAX 2147483647 #define RANGE_LEN 11 #define RANGE_TOOLTIP "-2147483648..2147483647" #define MAX_LINES 250000 typedef struct _InsertNumbersDialog { GtkWidget *dialog; GtkWidget *start, *step; GtkWidget *base, *lower; GtkWidget *prefix, *zero; } InsertNumbersDialog; typedef gboolean (*entry_valid)(const gchar *text); static GtkWidget *main_menu_item = NULL; static gint start_pos, start_line; static gint end_pos, end_line; /* input data */ static gint64 start_value; static gint64 step_value; static gint base_value; static gboolean lower_case = 0; static gboolean base_prefix = 0; static gboolean pad_zeros = 0; static void plugin_beep(void) { if (geany_data->prefs->beep_on_errors) gdk_beep(); } static gboolean can_insert_numbers(void) { GeanyDocument *doc = document_get_current(); if (doc && !doc->readonly) { ScintillaObject *sci = doc->editor->sci; if (sci_has_selection(sci) && (sci_get_selection_mode(sci) == SC_SEL_RECTANGLE || sci_get_selection_mode(sci) == SC_SEL_THIN)) { start_pos = sci_get_selection_start(sci); start_line = sci_get_line_from_position(sci, start_pos); end_pos = sci_get_selection_end(sci); end_line = sci_get_line_from_position(sci, end_pos); return end_line - start_line < MAX_LINES; } } return FALSE; } static void update_display(void) { while (gtk_events_pending()) gtk_main_iteration(); } #define sci_point_x_from_position(sci, position) \ scintilla_send_message(sci, SCI_POINTXFROMPOSITION, 0, position) #define sci_get_pos_at_line_sel_start(sci, line) \ scintilla_send_message(sci, SCI_GETLINESELSTARTPOSITION, line, 0) static void insert_numbers(gboolean *cancel) { /* editor */ ScintillaObject *sci = document_get_current()->editor->sci; gint xinsert = sci_point_x_from_position(sci, start_pos); gint xend = sci_point_x_from_position(sci, end_pos); gint *line_pos = g_new(gint, end_line - start_line + 1); gint line, i; /* generator */ gint64 start = start_value; gint64 value; unsigned count = 0; size_t prefix_len = 0; int plus = 0, minus; size_t length, lend; char pad, aax; gchar *buffer; if (xend < xinsert) xinsert = xend; ui_progress_bar_start(_("Counting...")); /* lines shorter than the current selection are skipped */ for (line = start_line, i = 0; line <= end_line; line++, i++) { if (sci_point_x_from_position(sci, scintilla_send_message(sci, SCI_GETLINEENDPOSITION, line, 0)) >= xinsert) { line_pos[i] = sci_get_pos_at_line_sel_start(sci, line) - sci_get_position_from_line(sci, line); count++; } else line_pos[i] = -1; if (cancel && i % 2500 == 0) { update_display(); if (*cancel) { ui_progress_bar_stop(); g_free(line_pos); return; } } } switch (base_value * base_prefix) { case 8 : prefix_len = 1; break; case 16 : prefix_len = 2; break; case 10 : plus++; } value = start + (count - 1) * step_value; minus = start < 0 || value < 0; lend = plus || (pad_zeros ? minus : value < 0); while (value /= base_value) lend++; value = start; length = plus || (pad_zeros ? minus : value < 0); while (value /= base_value) length++; length = prefix_len + (length > lend ? length : lend) + 1; buffer = g_new(gchar, length + 1); buffer[length] = '\0'; pad = pad_zeros ? '0' : ' '; aax = (lower_case ? 'a' : 'A') - 10; gtk_progress_bar_set_text(GTK_PROGRESS_BAR(geany->main_widgets->progressbar), _("Preparing...")); update_display(); sci_start_undo_action(sci); sci_replace_sel(sci, ""); gtk_progress_bar_set_text(GTK_PROGRESS_BAR(geany->main_widgets->progressbar), _("Inserting...")); for (line = start_line, i = 0; line <= end_line; line++, i++) { gchar *beg, *end; gint insert_pos; if (line_pos[i] < 0) continue; beg = buffer; end = buffer + length; value = ABS(start); do { unsigned digit = value % base_value; *--end = digit + (digit < 10 ? '0' : aax); } while (value /= base_value); if (pad_zeros) { if (start < 0) *beg++ = '-'; else if (plus) *beg++ = '+'; else if (minus) *beg++ = ' '; memcpy(beg, "0x", prefix_len); beg += prefix_len; } else { if (start < 0) *--end = '-'; else if (plus) *--end = '+'; end -= prefix_len; memcpy(end, "0x", prefix_len); } memset(beg, pad, end - beg); insert_pos = sci_get_position_from_line(sci, line) + line_pos[i]; sci_insert_text(sci, insert_pos, buffer); start += step_value; if (cancel && i % 1000 == 0) { update_display(); if (*cancel) { scintilla_send_message(sci, SCI_GOTOPOS, insert_pos + length, 0); break; } } } sci_end_undo_action(sci); g_free(buffer); g_free(line_pos); ui_progress_bar_stop(); } /* interface */ static void on_base_insert_text(G_GNUC_UNUSED GtkEntry *entry, const gchar *text, gint length, G_GNUC_UNUSED gint *position, G_GNUC_UNUSED gpointer data) { gint i; if (length == -1) length = strlen(text); for (i = 0; i < length; i++) { if (!isdigit(text[i])) { g_signal_stop_emission_by_name(G_OBJECT(entry), "insert-text"); break; } } } static void on_insert_numbers_response(G_GNUC_UNUSED GtkDialog *dialog, G_GNUC_UNUSED gint response_id, G_GNUC_UNUSED gpointer user_data) { *(gboolean *) user_data = TRUE; } static void on_insert_numbers_ok_clicked(G_GNUC_UNUSED GtkButton *button, gpointer user_data) { InsertNumbersDialog *d = (InsertNumbersDialog *) user_data; GtkWidget *bad_entry = NULL; start_value = gtk_spin_button_get_value(GTK_SPIN_BUTTON(d->start)); step_value = gtk_spin_button_get_value(GTK_SPIN_BUTTON(d->step)); base_value = atoi(gtk_entry_get_text(GTK_ENTRY(d->base))); lower_case = gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(d->lower)); base_prefix = gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(d->prefix)); pad_zeros = gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(d->zero)); if (!step_value) bad_entry = d->step; else if (base_value < 2 || base_value > 36) bad_entry = d->base; if (bad_entry) { plugin_beep(); gtk_widget_grab_focus(bad_entry); return; } gtk_dialog_response(GTK_DIALOG(d->dialog), GTK_RESPONSE_ACCEPT); } static void set_entry(GtkWidget *entry, gint maxlen, GtkWidget *label, const gchar *tooltip) { gtk_entry_set_max_length(GTK_ENTRY(entry), maxlen); gtk_entry_set_activates_default(GTK_ENTRY(entry), TRUE); gtk_label_set_mnemonic_widget(GTK_LABEL(label), entry); gtk_widget_set_tooltip_text(entry, tooltip); } #if !GTK_CHECK_VERSION(3, 0, 0) #define GtkGrid GtkTable #define GTK_GRID GTK_TABLE #define gtk_grid_attach(grid, child, left, top, width, height) \ gtk_table_attach_defaults(grid, child, left, left + width, top, top + height) #define gtk_grid_set_row_spacing gtk_table_set_row_spacings #define gtk_grid_set_column_spacing gtk_table_set_col_spacings #endif static void on_insert_numbers_activate(G_GNUC_UNUSED GtkMenuItem *menuitem, G_GNUC_UNUSED gpointer gdata) { InsertNumbersDialog d; GtkWidget *vbox, *label, *upper, *space, *button; GtkGrid *grid; GtkComboBoxText *combo; const char *case_tip = _("For base 11 and above"); gchar *base_text; gint result; d.dialog = gtk_dialog_new_with_buttons(_("Insert Numbers"), GTK_WINDOW(geany->main_widgets->window), GTK_DIALOG_MODAL | GTK_DIALOG_DESTROY_WITH_PARENT, GTK_STOCK_CANCEL, GTK_RESPONSE_REJECT, NULL); vbox = ui_dialog_vbox_new(GTK_DIALOG(d.dialog)); gtk_box_set_spacing(GTK_BOX(vbox), 9); #if GTK_CHECK_VERSION(3, 0, 0) grid = GTK_GRID(gtk_grid_new()); #else grid = GTK_TABLE(gtk_table_new(3, 6, FALSE)); #endif gtk_grid_set_row_spacing(grid, 6); gtk_grid_set_column_spacing(grid, 6); gtk_box_pack_start(GTK_BOX(vbox), GTK_WIDGET(grid), TRUE, TRUE, 0); label = gtk_label_new_with_mnemonic(_("_Start:")); gtk_grid_attach(grid, label, 0, 0, 1, 1); d.start = gtk_spin_button_new_with_range(RANGE_MIN, RANGE_MAX, 1); set_entry(d.start, RANGE_LEN, label, RANGE_TOOLTIP); gtk_grid_attach(grid, d.start, 1, 0, 2, 1); label = gtk_label_new_with_mnemonic(_("S_tep:")); gtk_grid_attach(grid, label, 3, 0, 1, 1); d.step = gtk_spin_button_new_with_range(RANGE_MIN, RANGE_MAX, 1); set_entry(d.step, RANGE_LEN, label, RANGE_TOOLTIP); gtk_grid_attach(grid, d.step, 4, 0, 2, 1); label = gtk_label_new_with_mnemonic(_("_Base:")); gtk_grid_attach(grid, label, 0, 1, 1, 1), combo = GTK_COMBO_BOX_TEXT(gtk_combo_box_text_new_with_entry()); d.base = gtk_bin_get_child(GTK_BIN(combo)); set_entry(d.base, 2, label, "2..36"); g_signal_connect(d.base, "insert-text", G_CALLBACK(on_base_insert_text), NULL); gtk_combo_box_text_append_text(combo, "2"); gtk_combo_box_text_append_text(combo, "8"); gtk_combo_box_text_append_text(combo, "10"); gtk_combo_box_text_append_text(combo, "16"); #if GTK_CHECK_VERSION(3, 0, 0) gtk_grid_attach(grid, GTK_WIDGET(combo), 1, 1, 2, 1); gtk_widget_set_hexpand(GTK_WIDGET(combo), TRUE); #else gtk_table_attach(grid, GTK_WIDGET(combo), 1, 3, 1, 2, GTK_EXPAND | GTK_FILL, 0, 0, 0); #endif label = gtk_label_new(_("Letters:")); gtk_widget_set_tooltip_text(label, case_tip); gtk_grid_attach(grid, label, 3, 1, 1, 1); upper = gtk_radio_button_new_with_mnemonic(NULL, _("_Upper")); gtk_widget_set_tooltip_text(upper, case_tip); gtk_grid_attach(grid, upper, 4, 1, 1, 1); d.lower = gtk_radio_button_new_from_widget(GTK_RADIO_BUTTON(upper)); gtk_widget_set_tooltip_text(label, case_tip); label = gtk_label_new_with_mnemonic(_("_Lower")); gtk_widget_set_tooltip_text(label, case_tip); gtk_container_add(GTK_CONTAINER(d.lower), label); gtk_grid_attach(grid, d.lower, 5, 1, 1, 1); d.prefix = gtk_check_button_new_with_mnemonic(_("Base _prefix")); gtk_widget_set_tooltip_text(d.prefix, _("0 for octal, 0x for hex, + for positive decimal")); gtk_grid_attach(grid, d.prefix, 1, 2, 2, 1); label = gtk_label_new(_("Padding:")); gtk_grid_attach(grid, label, 3, 2, 1, 1); space = gtk_radio_button_new_with_mnemonic(NULL, _("Sp_ace")); gtk_grid_attach(grid, space, 4, 2, 1, 1); d.zero = gtk_radio_button_new_from_widget(GTK_RADIO_BUTTON(space)); label = gtk_label_new_with_mnemonic(_("_Zero")); gtk_container_add(GTK_CONTAINER(d.zero), label); gtk_grid_attach(grid, d.zero, 5, 2, 1, 1); button = gtk_button_new_from_stock(GTK_STOCK_OK); g_signal_connect(button, "clicked", G_CALLBACK(on_insert_numbers_ok_clicked), &d); gtk_box_pack_end(GTK_BOX(gtk_dialog_get_action_area(GTK_DIALOG(d.dialog))), button, TRUE, TRUE, 0); #if GTK_CHECK_VERSION(2, 18, 0) gtk_widget_set_can_default(button, TRUE); #else GTK_WIDGET_SET_FLAGS(button, GTK_CAN_DEFAULT); #endif gtk_widget_grab_default(button); gtk_spin_button_set_value(GTK_SPIN_BUTTON(d.start), start_value); gtk_spin_button_set_value(GTK_SPIN_BUTTON(d.step), step_value); base_text = g_strdup_printf("%d", base_value); gtk_entry_set_text(GTK_ENTRY(d.base), base_text); g_free(base_text); gtk_button_clicked(GTK_BUTTON(lower_case ? d.lower : upper)); gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(d.prefix), base_prefix); gtk_button_clicked(GTK_BUTTON(pad_zeros ? d.zero : space)); gtk_widget_show_all(d.dialog); result = gtk_dialog_run(GTK_DIALOG(d.dialog)); if (result == GTK_RESPONSE_ACCEPT) { if (can_insert_numbers()) { if (end_line - start_line < 1000) { /* quick version */ gtk_widget_hide(d.dialog); insert_numbers(NULL); } else { gboolean cancel = FALSE; gtk_widget_set_sensitive(GTK_WIDGET(grid), FALSE); gtk_widget_set_sensitive(button, FALSE); update_display(); g_signal_connect(d.dialog, "response", G_CALLBACK(on_insert_numbers_response), &cancel); insert_numbers(&cancel); } } else plugin_beep(); /* reloaded or something */ } gtk_widget_destroy(d.dialog); } static void on_insert_numbers_key(G_GNUC_UNUSED guint key_id) { if (can_insert_numbers()) on_insert_numbers_activate(NULL, NULL); } static void on_tools_show(G_GNUC_UNUSED GtkMenuItem *menuitem, G_GNUC_UNUSED gpointer gdata) { gtk_widget_set_sensitive(main_menu_item, can_insert_numbers()); } void plugin_init(G_GNUC_UNUSED GeanyData *data) { GeanyKeyGroup *plugin_key_group; plugin_key_group = plugin_set_key_group(geany_plugin, "insert_numbers", COUNT_KB, NULL); start_value = 1; step_value = 1; base_value = 10; main_menu_item = gtk_menu_item_new_with_mnemonic(_("Insert _Numbers...")); gtk_widget_show(main_menu_item); gtk_container_add(GTK_CONTAINER(geany->main_widgets->tools_menu), main_menu_item); g_signal_connect(main_menu_item, "activate", G_CALLBACK(on_insert_numbers_activate), NULL); keybindings_set_item(plugin_key_group, INSERT_NUMBERS_KB, on_insert_numbers_key, 0, 0, "insert_numbers", _("Insert Numbers..."), main_menu_item); plugin_signal_connect(geany_plugin, G_OBJECT(geany->main_widgets->tools_menu), "show", FALSE, (GCallback) on_tools_show, NULL); } void plugin_cleanup(void) { gtk_widget_destroy(main_menu_item); }