diff options
Diffstat (limited to 'src/textadept.c')
-rw-r--r-- | src/textadept.c | 429 |
1 files changed, 429 insertions, 0 deletions
diff --git a/src/textadept.c b/src/textadept.c index e05acc36..3a0ca902 100644 --- a/src/textadept.c +++ b/src/textadept.c @@ -5,6 +5,7 @@ #define signal(o, s, c) g_signal_connect(G_OBJECT(o), s, G_CALLBACK(c), 0) +// Textadept GtkWidget *window, *focused_editor, *command_entry, *menubar, *statusbar, *docstatusbar; @@ -18,6 +19,38 @@ static bool w_focus(GtkWidget*, GdkEventFocus *, gpointer); static bool w_keypress(GtkWidget*, GdkEventKey *event, gpointer); static bool w_exit(GtkWidget*, GdkEventAny*, gpointer); +// Project Manager +GtkWidget *pm_view, *pm_entry, *pm_container; +GtkTreeStore *pm_store; + +static int pm_search_equal_func(GtkTreeModel *model, int col, const char *key, + GtkTreeIter *iter, gpointer); +static int pm_sort_iter_compare_func(GtkTreeModel *model, GtkTreeIter *a, + GtkTreeIter *b, gpointer); +static void pm_entry_activated(GtkWidget *widget, gpointer); +static bool pm_keypress(GtkWidget *, GdkEventKey *event, gpointer); +static void pm_row_expanded(GtkTreeView *, GtkTreeIter *iter, + GtkTreePath *path, gpointer); +static void pm_row_collapsed(GtkTreeView *, GtkTreeIter *iter, + GtkTreePath *path, gpointer); +static void pm_row_activated(GtkTreeView *, GtkTreePath *, GtkTreeViewColumn *, + gpointer); +static bool pm_button_press(GtkTreeView *, GdkEventButton *event, gpointer); +static bool pm_popup_menu(GtkWidget *, gpointer); +static void pm_menu_activate(GtkWidget *menu_item, gpointer); + +// Find/Replace +GtkWidget *findbox, *find_entry, *replace_entry; +GtkWidget *fnext_button, *fprev_button, *r_button, *ra_button; +GtkWidget *match_case_opt, *whole_word_opt, /**incremental_opt,*/ *lua_opt; +GtkAttachOptions + normal = static_cast<GtkAttachOptions>(GTK_SHRINK | GTK_FILL), + expand = static_cast<GtkAttachOptions>(GTK_EXPAND | GTK_FILL); + +static bool fe_keypress(GtkWidget*, GdkEventKey *event, gpointer); +static bool re_keypress(GtkWidget*, GdkEventKey *event, gpointer); +static void button_clicked(GtkWidget *button, gpointer); + /** * Runs Textadept. * Inits the Lua State, creates the user interface, and loads the core/init.lua @@ -430,3 +463,399 @@ static bool w_exit(GtkWidget*, GdkEventAny*, gpointer) { gtk_main_quit(); return false; } + +// Project Manager + +/** + * Creates the Project Manager pane. + * It consists of an entry box and a treeview called 'textadept-pm-entry' and + * 'textadept-pm-view' respectively for styling via gtkrc. The treeview model + * consists of a gdk-pixbuf for icons and markup text. + */ +GtkWidget* pm_create_ui() { + pm_container = gtk_vbox_new(false, 1); + + pm_entry = gtk_entry_new(); + gtk_widget_set_name(pm_entry, "textadept-pm-entry"); + gtk_box_pack_start(GTK_BOX(pm_container), pm_entry, false, false, 0); + + pm_store = gtk_tree_store_new(3, G_TYPE_STRING, G_TYPE_STRING, G_TYPE_STRING); + GtkTreeSortable *sortable = GTK_TREE_SORTABLE(pm_store); + gtk_tree_sortable_set_sort_column_id(sortable, 1, GTK_SORT_ASCENDING); + gtk_tree_sortable_set_sort_func(sortable, 1, pm_sort_iter_compare_func, + GINT_TO_POINTER(1), NULL); + + pm_view = gtk_tree_view_new_with_model(GTK_TREE_MODEL(pm_store)); + g_object_unref(pm_store); + gtk_widget_set_name(pm_view, "textadept-pm-view"); + gtk_tree_view_set_headers_visible(GTK_TREE_VIEW(pm_view), false); + gtk_tree_view_set_enable_search(GTK_TREE_VIEW(pm_view), true); + gtk_tree_view_set_search_column(GTK_TREE_VIEW(pm_view), 2); + gtk_tree_view_set_search_equal_func(GTK_TREE_VIEW(pm_view), + pm_search_equal_func, NULL, NULL); + + GtkTreeViewColumn *column = gtk_tree_view_column_new(); + GtkCellRenderer *renderer; + renderer = gtk_cell_renderer_pixbuf_new(); // pixbuf + gtk_tree_view_column_pack_start(column, renderer, FALSE); + gtk_tree_view_column_set_attributes(column, renderer, "stock-id", 0, NULL); + renderer = gtk_cell_renderer_text_new(); // markup text + gtk_tree_view_column_pack_start(column, renderer, TRUE); + gtk_tree_view_column_set_attributes(column, renderer, "markup", 2, NULL); + gtk_tree_view_append_column(GTK_TREE_VIEW(pm_view), column); + + GtkWidget *scrolled = gtk_scrolled_window_new(NULL, NULL); + gtk_scrolled_window_set_policy(GTK_SCROLLED_WINDOW(scrolled), + GTK_POLICY_AUTOMATIC, GTK_POLICY_AUTOMATIC); + gtk_container_add(GTK_CONTAINER(scrolled), pm_view); + gtk_box_pack_start(GTK_BOX(pm_container), scrolled, true, true, 0); + + signal(pm_entry, "activate", pm_entry_activated); + signal(pm_entry, "key_press_event", pm_keypress); + signal(pm_view, "key_press_event", pm_keypress); + signal(pm_view, "row_expanded", pm_row_expanded); + signal(pm_view, "row_collapsed", pm_row_collapsed); + signal(pm_view, "row_activated", pm_row_activated); + signal(pm_view, "button_press_event", pm_button_press); + signal(pm_view, "popup-menu", pm_popup_menu); + return pm_container; +} + +/** + * Requests contents for a Project Manager parent node being opened. + * Since parents have a dummy child by default just to indicate they are indeed + * parents, that dummy child is removed now. + * @param iter The parent GtkTreeIter. + * @param path The parent GtkTreePath. + * @see l_pm_get_contents_for + */ +void pm_open_parent(GtkTreeIter *iter, GtkTreePath *path) { + l_pm_get_full_path(path); + if (l_pm_get_contents_for(NULL, true)) l_pm_populate(iter); + GtkTreeIter child; + char *filename; + gtk_tree_model_iter_nth_child(GTK_TREE_MODEL(pm_store), &child, iter, 0); + gtk_tree_model_get(GTK_TREE_MODEL(pm_store), &child, 1, &filename, -1); + if (strcmp(reinterpret_cast<const char*>(filename), "\0dummy") == 0) + gtk_tree_store_remove(pm_store, &child); + g_free(filename); +} + +/** + * Removes all Project Manager children from a parent node being closed. + * It does add a dummy child by default to indicate the parent is indeed a + * parent. It will be removed when the parent is opened. + * @param iter The parent GtkTreeIter. + */ +void pm_close_parent(GtkTreeIter *iter, GtkTreePath *) { + GtkTreeIter child; + gtk_tree_model_iter_nth_child(GTK_TREE_MODEL(pm_store), &child, iter, 0); + while (gtk_tree_model_iter_has_child(GTK_TREE_MODEL(pm_store), iter)) + gtk_tree_store_remove(pm_store, &child); + gtk_tree_store_append(pm_store, &child, iter); + gtk_tree_store_set(pm_store, &child, 1, "\0dummy", -1); +} + +/** + * Performs the appropriate action on a selected Project Manager node. + * If the node is a collapsed parent, it is expanded; otherwise the parent is + * collapsed. If the node is not a parent at all, a Lua action is performed. + * @see l_pm_perform_action + */ +void pm_activate_selection() { + GtkTreeIter iter; + GtkTreePath *path; + GtkTreeViewColumn *column; + gtk_tree_view_get_cursor(GTK_TREE_VIEW(pm_view), &path, &column); + gtk_tree_model_get_iter(GTK_TREE_MODEL(pm_store), &iter, path); + if (gtk_tree_model_iter_has_child(GTK_TREE_MODEL(pm_store), &iter)) + if (gtk_tree_view_row_expanded(GTK_TREE_VIEW(pm_view), path)) + gtk_tree_view_collapse_row(GTK_TREE_VIEW(pm_view), path); + else + gtk_tree_view_expand_row(GTK_TREE_VIEW(pm_view), path, false); + else { + l_pm_get_full_path(path); + l_pm_perform_action(); + } + gtk_tree_path_free(path); +} + +/** + * Pops up a context menu for the selected Project Manager node. + * @param event The mouse button event. + * @see l_pm_popup_context_menu + */ +void pm_popup_context_menu(GdkEventButton *event) { + l_pm_popup_context_menu(event, G_CALLBACK(pm_menu_activate)); +} + +/** + * Performs a Lua action for a selected Project Manager menu item. + * @param menu_item The menu item. + * @see l_pm_perform_menu_action + */ +void pm_process_selected_menu_item(GtkWidget *menu_item) { + GtkWidget *label = gtk_bin_get_child(GTK_BIN(menu_item)); + const char *text = gtk_label_get_text(GTK_LABEL(label)); + GtkTreePath *path; + GtkTreeViewColumn *column; + gtk_tree_view_get_cursor(GTK_TREE_VIEW(pm_view), &path, &column); + l_pm_get_full_path(path); + l_pm_perform_menu_action(text); +} + +/** + * Toggles the focus between the Project Manager and the current Scintilla + * window. + */ +void pm_toggle_focus() { + gtk_widget_grab_focus( + GTK_WIDGET_HAS_FOCUS(focused_editor) ? pm_entry : focused_editor); +} + +/** + * When searching the Project Manager treeview, matches are tree items that + * contain the search text as a substring. + * @param model The GtkTreeModel for the treeview. + * @param col The column number to use for comparing search text to. + * @param key The search text. + * @param iter The GtkTreeIter for each tree node being compared. + */ +static int pm_search_equal_func(GtkTreeModel *model, int col, const char *key, + GtkTreeIter *iter, gpointer) { + const char *text; + gtk_tree_model_get(model, iter, col, &text, -1); + return strstr(text, key) == NULL; // false is really a match like strcmp +} + +/** + * Sorts the Project Manager treeview case sensitively. + * @param model The GtkTreeModel for the treeview. + * @param a The GtkTreeIter for one tree node being compared. + * @param b The GtkTreeIter for the other tree node being compared. + */ +static int pm_sort_iter_compare_func(GtkTreeModel *model, GtkTreeIter *a, + GtkTreeIter *b, gpointer) { + const char *a_text, *b_text; + gtk_tree_model_get(model, a, 1, &a_text, -1); + gtk_tree_model_get(model, b, 1, &b_text, -1); + if (a_text == NULL && b_text == NULL) return 0; + else if (a_text == NULL) return -1; + else if (b_text == NULL) return 1; + else return strcasecmp(a_text, b_text); +} + +// Signals + +/** + * Signal for the activation of the Project Manager entry. + * Requests contents for the treeview. + * @see l_pm_get_contents_for + */ +static void pm_entry_activated(GtkWidget *widget, gpointer) { + const char *entry_text = gtk_entry_get_text(GTK_ENTRY(widget)); + if (l_pm_get_contents_for(entry_text)) l_pm_populate(); +} + +/** + * Signal for a Project Manager keypress. + * Currently handled keypresses: + * - Ctrl+Tab - Refocuses the Scintilla view. + * - Escape - Refocuses the Scintilla view. + */ +static bool pm_keypress(GtkWidget *, GdkEventKey *event, gpointer) { + if (event->keyval == 0xff09 && event->state == GDK_CONTROL_MASK || + event->keyval == 0xff1b) { + gtk_widget_grab_focus(focused_editor); + return true; + } else return false; +} + +/** + * Signal for a Project Manager parent expansion. + * @see pm_open_parent + */ +static void pm_row_expanded(GtkTreeView *, GtkTreeIter *iter, + GtkTreePath *path, gpointer) { + pm_open_parent(iter, path); +} + +/** + * Signal for a Project Manager parent collapse. + * @see pm_close_parent + */ +static void pm_row_collapsed(GtkTreeView *, GtkTreeIter *iter, + GtkTreePath *path, gpointer) { + pm_close_parent(iter, path); +} + +/** + * Signal for the activation of a Project Manager node. + * @see pm_activate_selection + */ +static void pm_row_activated(GtkTreeView *, GtkTreePath *, GtkTreeViewColumn *, + gpointer) { + pm_activate_selection(); +} + +/** + * Signal for a Project Manager mouse click. + * If it is a right-click, popup a context menu for the selected node. + * @see pm_popup_context_menu + */ +static bool pm_button_press(GtkTreeView *, GdkEventButton *event, gpointer) { + if (event->type != GDK_BUTTON_PRESS || event->button != 3) return false; + pm_popup_context_menu(event); return true; +} + +/** + * Signal for popping up a Project Manager context menu. + * Typically Shift+F10 activates this event. + * @see pm_popup_context_menu + */ +static bool pm_popup_menu(GtkWidget *, gpointer) { + pm_popup_context_menu(NULL); return true; +} + +/** + * Signal for a selected Project Manager menu item. + * @see pm_process_selected_menu_item + */ +static void pm_menu_activate(GtkWidget *menu_item, gpointer) { + pm_process_selected_menu_item(menu_item); +} + +// Find/Replace + +#define attach(w, x1, x2, y1, y2, xo, yo, xp, yp) \ + gtk_table_attach(GTK_TABLE(findbox), w, x1, x2, y1, y2, xo, yo, xp, yp) +#define find_text gtk_entry_get_text(GTK_ENTRY(find_entry)) +#define repl_text gtk_entry_get_text(GTK_ENTRY(replace_entry)) +#define toggled(w) gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(w)) + +/** + * Creates the Find/Replace text frame. + */ +GtkWidget* find_create_ui() { + findbox = gtk_table_new(2, 6, false); + + GtkWidget *flabel = gtk_label_new_with_mnemonic("_Find:"); + GtkWidget *rlabel = gtk_label_new_with_mnemonic("R_eplace:"); + find_entry = gtk_entry_new(); + gtk_widget_set_name(find_entry, "textadept-find-entry"); + replace_entry = gtk_entry_new(); + gtk_widget_set_name(replace_entry, "textadept-replace-entry"); + fnext_button = gtk_button_new_with_mnemonic("Find _Next"); + fprev_button = gtk_button_new_with_mnemonic("Find _Prev"); + r_button = gtk_button_new_with_mnemonic("_Replace"); + ra_button = gtk_button_new_with_mnemonic("Replace _All"); + match_case_opt = gtk_check_button_new_with_mnemonic("_Match case"); + whole_word_opt = gtk_check_button_new_with_mnemonic("_Whole word"); + //incremental_opt = gtk_check_button_new_with_mnemonic("_Incremental"); + lua_opt = gtk_check_button_new_with_mnemonic("_Lua pattern"); + + gtk_label_set_mnemonic_widget(GTK_LABEL(flabel), find_entry); + gtk_label_set_mnemonic_widget(GTK_LABEL(rlabel), replace_entry); + //gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(lua_opt), true); + + attach(find_entry, 1, 2, 0, 1, expand, normal, 5, 0); + attach(replace_entry, 1, 2, 1, 2, expand, normal, 5, 0); + attach(flabel, 0, 1, 0, 1, normal, normal, 5, 0); + attach(rlabel, 0, 1, 1, 2, normal, normal, 5, 0); + attach(fnext_button, 2, 3, 0, 1, normal, normal, 0, 0); + attach(fprev_button, 3, 4, 0, 1, normal, normal, 0, 0); + attach(r_button, 2, 3, 1, 2, normal, normal, 0, 0); + attach(ra_button, 3, 4, 1, 2, normal, normal, 0, 0); + attach(match_case_opt, 4, 5, 0, 1, normal, normal, 5, 0); + attach(whole_word_opt, 4, 5, 1, 2, normal, normal, 5, 0); + //attach(incremental_opt, 5, 6, 0, 1, normal, normal, 5, 0); + attach(lua_opt, 5, 6, 0, 1, normal, normal, 5, 0); + + g_signal_connect(G_OBJECT(find_entry), "key_press_event", + G_CALLBACK(fe_keypress), 0); + g_signal_connect(G_OBJECT(replace_entry), "key_press_event", + G_CALLBACK(re_keypress), 0); + g_signal_connect(G_OBJECT(fnext_button), "clicked", + G_CALLBACK(button_clicked), 0); + g_signal_connect(G_OBJECT(fprev_button), "clicked", + G_CALLBACK(button_clicked), 0); + g_signal_connect(G_OBJECT(r_button), "clicked", + G_CALLBACK(button_clicked), 0); + g_signal_connect(G_OBJECT(ra_button), "clicked", + G_CALLBACK(button_clicked), 0); + + GTK_WIDGET_UNSET_FLAGS(fnext_button, GTK_CAN_FOCUS); + GTK_WIDGET_UNSET_FLAGS(fprev_button, GTK_CAN_FOCUS); + GTK_WIDGET_UNSET_FLAGS(r_button, GTK_CAN_FOCUS); + GTK_WIDGET_UNSET_FLAGS(ra_button, GTK_CAN_FOCUS); + GTK_WIDGET_UNSET_FLAGS(match_case_opt, GTK_CAN_FOCUS); + GTK_WIDGET_UNSET_FLAGS(whole_word_opt, GTK_CAN_FOCUS); + //GTK_WIDGET_UNSET_FLAGS(incremental_opt, GTK_CAN_FOCUS); + GTK_WIDGET_UNSET_FLAGS(lua_opt, GTK_CAN_FOCUS); + + return findbox; +} + +/** + * Toggles the focus between the Find/Replace frame and the current Scintilla + * window. + */ +void find_toggle_focus() { + if (!GTK_WIDGET_HAS_FOCUS(findbox)) { + gtk_widget_show(findbox); + gtk_widget_grab_focus(find_entry); + } else { + gtk_widget_grab_focus(focused_editor); + gtk_widget_hide(findbox); + } +} + +/** + * Builds the integer flags for a Find/Replace depending on the options that are + * checked. + */ +static int get_flags() { + int flags = 0; + if (toggled(match_case_opt)) flags |= SCFIND_MATCHCASE; // 2 + if (toggled(whole_word_opt)) flags |= SCFIND_WHOLEWORD; // 4 + if (toggled(lua_opt)) flags |= 8; + return flags; +} + +/** + * Signal for a Find entry keypress. + * Currently handled keypresses: + * - Enter - Find next or previous. + */ +static bool fe_keypress(GtkWidget *, GdkEventKey *event, gpointer) { + // TODO: if incremental, call l_find() + if (event->keyval == 0xff0d) { + l_find(find_text, get_flags(), true); + return true; + } else return false; +} + +/** + * Signal for a Replace entry keypress. + * Currently handled keypresses: + * - Enter - Find next or previous. + */ +static bool re_keypress(GtkWidget *, GdkEventKey *event, gpointer) { + if (event->keyval == 0xff0d) { + l_find(find_text, get_flags(), true); + return true; + } else return false; +} + +/** + * Signal for a button click. + * Performs the appropriate action depending on the button clicked. + */ +static void button_clicked(GtkWidget *button, gpointer) { + if (button == ra_button) + l_find_replace_all(find_text, repl_text, get_flags()); + else if (button == r_button) { + l_find_replace(repl_text); + l_find(find_text, get_flags(), true); + } else l_find(find_text, get_flags(), button == fnext_button); +} |