girara
.pc/fix-gtk-3.4-deprecation-warnings/completion.c
Go to the documentation of this file.
00001 /* See LICENSE file for license and copyright information */
00002 
00003 #include <math.h>
00004 #include <string.h>
00005 #include <stdlib.h>
00006 
00007 #include "completion.h"
00008 #include "internal.h"
00009 #include "session.h"
00010 #include "settings.h"
00011 #include "datastructures.h"
00012 
00013 #if GTK_MAJOR_VERSION == 2
00014 #include "gtk2-compat.h"
00015 #endif
00016 
00017 static GtkEventBox* girara_completion_row_create(girara_session_t*, const char*, const char*, bool);
00018 static void girara_completion_row_set_color(girara_session_t*, GtkEventBox*, int);
00019 
00020 /* completion */
00021 struct girara_internal_completion_entry_s
00022 {
00023   bool group; 
00024   char* value; 
00025   GtkEventBox* widget; 
00026 };
00027 
00031 struct girara_completion_element_s
00032 {
00033   char *value; 
00034   char *description; 
00035 };
00036 
00040 struct girara_completion_group_s
00041 {
00042   char *value; 
00043   girara_list_t *elements; 
00044 };
00045 
00049 struct girara_completion_s
00050 {
00051   girara_list_t *groups; 
00052 };
00053 
00054 typedef struct girara_internal_completion_entry_s girara_internal_completion_entry_t;
00055 
00056 static void
00057 completion_element_free(girara_completion_element_t* element)
00058 {
00059   if (element == NULL) {
00060     return;
00061   }
00062 
00063   /* free element */
00064   g_free(element->value);
00065   g_free(element->description);
00066   g_slice_free(girara_completion_element_t,  element);
00067 }
00068 
00069 static char*
00070 escape(const char* value)
00071 {
00072   if (value == NULL) {
00073     return NULL;
00074   }
00075 
00076   GString* str = g_string_new("");
00077   while (*value != '\0') {
00078     const char c = *value++;
00079     if (strchr("\\ \t\"\'", c) != NULL) {
00080       g_string_append_c(str, '\\');
00081     }
00082     g_string_append_c(str, c);
00083   }
00084 
00085   return g_string_free(str, FALSE);
00086 }
00087 
00088 girara_completion_t*
00089 girara_completion_init()
00090 {
00091   girara_completion_t *completion = g_slice_new(girara_completion_t);
00092   completion->groups = girara_list_new2(
00093       (girara_free_function_t) girara_completion_group_free);
00094 
00095   return completion;
00096 }
00097 
00098 girara_completion_group_t*
00099 girara_completion_group_create(girara_session_t* UNUSED(session), const char* name)
00100 {
00101   girara_completion_group_t* group = g_slice_new(girara_completion_group_t);
00102 
00103   group->value    = name ? g_strdup(name) : NULL;
00104   group->elements = girara_list_new2(
00105       (girara_free_function_t) completion_element_free);
00106 
00107   if (group->elements == NULL) {
00108     g_slice_free(girara_completion_group_t, group);
00109     return NULL;
00110   }
00111 
00112   return group;
00113 }
00114 
00115 void
00116 girara_completion_add_group(girara_completion_t* completion, girara_completion_group_t* group)
00117 {
00118   g_return_if_fail(completion != NULL);
00119   g_return_if_fail(group      != NULL);
00120 
00121   girara_list_append(completion->groups, group);
00122 }
00123 
00124 void
00125 girara_completion_group_free(girara_completion_group_t* group)
00126 {
00127   if (group == NULL) {
00128     return;
00129   }
00130 
00131   g_free(group->value);
00132   girara_list_free(group->elements);
00133   g_slice_free(girara_completion_group_t, group);
00134 }
00135 
00136 void
00137 girara_completion_free(girara_completion_t* completion)
00138 {
00139   g_return_if_fail(completion != NULL);
00140 
00141   girara_list_free(completion->groups);
00142   /* free completion */
00143   g_slice_free(girara_completion_t, completion);
00144 }
00145 
00146 void
00147 girara_completion_group_add_element(girara_completion_group_t* group, const char* name, const char* description)
00148 {
00149   g_return_if_fail(group   != NULL);
00150   g_return_if_fail(name    != NULL);
00151 
00152   girara_completion_element_t* new_element = g_slice_new(girara_completion_element_t);
00153 
00154   new_element->value       = g_strdup(name);
00155   new_element->description = description ?  g_strdup(description) : NULL;
00156 
00157   girara_list_append(group->elements, new_element);
00158 }
00159 
00160 bool
00161 girara_isc_completion(girara_session_t* session, girara_argument_t* argument, girara_event_t* UNUSED(event), unsigned int UNUSED(t))
00162 {
00163   g_return_val_if_fail(session != NULL, false);
00164 
00165   /* get current text */
00166   gchar *input = gtk_editable_get_chars(GTK_EDITABLE(session->gtk.inputbar_entry), 0, -1);
00167   if (input == NULL) {
00168     return false;
00169   }
00170 
00171   const size_t input_length = strlen(input);
00172 
00173   if (input_length == 0 || input[0] != ':') {
00174     g_free(input);
00175     return false;
00176   }
00177 
00178   gchar** elements = NULL;
00179   gint    n_parameter = 0;
00180   if (input_length > 1) {
00181     if (g_shell_parse_argv(input + 1, &n_parameter, &elements, NULL) == FALSE) {
00182       g_free(input);
00183       return FALSE;
00184     }
00185   } else {
00186     elements = g_malloc0(2 * sizeof(char*));
00187     elements[0] = g_strdup("");
00188   }
00189 
00190   if (n_parameter == 1 && input[input_length-1] == ' ') {
00191     n_parameter += 1;
00192   }
00193 
00194   g_free(input);
00195 
00196   /* get current values */
00197   gchar *current_command   = (elements[0] != NULL && elements[0][0] != '\0') ? g_strdup(elements[0]) : NULL;
00198   gchar *current_parameter = (elements[0] != NULL && elements[1] != NULL)    ? g_strdup(elements[1]) : NULL;
00199 
00200   size_t current_command_length = current_command ? strlen(current_command) : 0;
00201 
00202   static GList* entries           = NULL;
00203   static GList* entries_current   = NULL;
00204   static char *previous_command   = NULL;
00205   static char *previous_parameter = NULL;
00206   static bool command_mode        = true;
00207   static size_t previous_length   = 0;
00208 
00209   /* delete old list iff
00210    *   the completion should be hidden
00211    *   the current command differs from the previous one
00212    *   the current parameter differs from the previous one
00213    *   no current command is given
00214    */
00215   if ( (argument->n == GIRARA_HIDE) ||
00216       (current_parameter && previous_parameter && strcmp(current_parameter, previous_parameter)) ||
00217       (current_command && previous_command && strcmp(current_command, previous_command)) ||
00218       input_length != previous_length
00219     )
00220   {
00221     if (session->gtk.results != NULL) {
00222       /* destroy elements */
00223       for (GList* element = entries; element; element = g_list_next(element)) {
00224         girara_internal_completion_entry_t* entry = (girara_internal_completion_entry_t*) element->data;
00225 
00226         if (entry != NULL) {
00227           gtk_widget_destroy(GTK_WIDGET(entry->widget));
00228           g_free(entry->value);
00229           g_slice_free(girara_internal_completion_entry_t, entry);
00230         }
00231       }
00232 
00233       g_list_free(entries);
00234       entries         = NULL;
00235       entries_current = NULL;
00236 
00237       /* delete row box */
00238       gtk_widget_destroy(GTK_WIDGET(session->gtk.results));
00239       session->gtk.results = NULL;
00240     }
00241 
00242     command_mode = true;
00243 
00244     if (argument->n == GIRARA_HIDE) {
00245       g_free(previous_command);
00246       previous_command = NULL;
00247 
00248       g_free(previous_parameter);
00249       previous_parameter = NULL;
00250 
00251       g_strfreev(elements);
00252 
00253       g_free(current_command);
00254       g_free(current_parameter);
00255 
00256       return false;
00257     }
00258   }
00259 
00260   /* create new list iff
00261    *  there is no current list
00262    */
00263   if (session->gtk.results == NULL) {
00264     session->gtk.results = GTK_BOX(gtk_vbox_new(FALSE, 0));
00265 
00266     if (session->gtk.results == NULL) {
00267       g_free(current_command);
00268       g_free(current_parameter);
00269 
00270       g_strfreev(elements);
00271       return false;
00272     }
00273 
00274     if (n_parameter <= 1) {
00275     /* based on commands */
00276       command_mode = true;
00277 
00278       /* create command rows */
00279       GIRARA_LIST_FOREACH(session->bindings.commands, girara_command_t*, iter, command)
00280         if (current_command == NULL ||
00281             (command->command != NULL && !strncmp(current_command, command->command, current_command_length)) ||
00282             (command->abbr != NULL && !strncmp(current_command, command->abbr,    current_command_length))
00283           )
00284         {
00285           /* create entry */
00286           girara_internal_completion_entry_t* entry = g_slice_new(girara_internal_completion_entry_t);
00287           entry->group  = FALSE;
00288           entry->value  = g_strdup(command->command);
00289           entry->widget = girara_completion_row_create(session, command->command, command->description, FALSE);
00290 
00291           entries = g_list_append(entries, entry);
00292 
00293           /* show entry row */
00294           gtk_box_pack_start(session->gtk.results, GTK_WIDGET(entry->widget), FALSE, FALSE, 0);
00295         }
00296       GIRARA_LIST_FOREACH_END(session->bindings.commands, girara_command_t*, iter, command);
00297     }
00298 
00299     /* based on parameters */
00300     if (n_parameter > 1 || g_list_length(entries) == 1) {
00301       /* if only one command exists try to run parameter completion */
00302       if (g_list_length(entries) == 1) {
00303         girara_internal_completion_entry_t* entry = g_list_first(entries)->data;
00304 
00305         /* unset command mode */
00306         command_mode           = false;
00307         current_command        = entry->value;
00308         current_command_length = strlen(current_command);
00309 
00310         /* clear list */
00311         gtk_widget_destroy(GTK_WIDGET(entry->widget));
00312 
00313         entries = g_list_remove(entries, g_list_first(entries)->data);
00314         g_slice_free(girara_internal_completion_entry_t, entry);
00315       }
00316 
00317       /* search matching command */
00318       girara_command_t* command = NULL;
00319       GIRARA_LIST_FOREACH(session->bindings.commands, girara_command_t*, iter, command_it)
00320         if ( (current_command != NULL && command_it->command != NULL && !strncmp(current_command, command_it->command, current_command_length)) ||
00321              (current_command != NULL && command_it->abbr != NULL    && !strncmp(current_command, command_it->abbr,    current_command_length))
00322           )
00323         {
00324           g_free(previous_command);
00325           previous_command = g_strdup(command_it->command);
00326           command = command_it;
00327           break;
00328         }
00329       GIRARA_LIST_FOREACH_END(session->bindings.commands, girara_command_t*, iter, command_it);
00330 
00331       if (command == NULL) {
00332         g_free(current_command);
00333         g_free(current_parameter);
00334 
00335         g_strfreev(elements);
00336         return false;
00337       }
00338 
00339       if (command->completion == NULL) {
00340           girara_internal_completion_entry_t* entry = g_slice_new(girara_internal_completion_entry_t);
00341           entry->group  = FALSE;
00342           entry->value  = g_strdup(command->command);
00343           entry->widget = girara_completion_row_create(session, command->command, command->description, FALSE);
00344 
00345           entries = g_list_append(entries, entry);
00346 
00347           gtk_box_pack_start(session->gtk.results, GTK_WIDGET(entry->widget), FALSE, FALSE, 0);
00348           command_mode = true;
00349       } else {
00350         /* generate completion result
00351          * XXX: the last argument should only be current_paramater ... but
00352          * therefore the completion functions would need to handle NULL correctly
00353          * (see cc_open in zathura). */
00354         girara_completion_t *result = command->completion(session, current_parameter ? current_parameter : "");
00355 
00356         if (result == NULL || result->groups == NULL) {
00357           g_free(current_command);
00358           g_free(current_parameter);
00359 
00360           g_strfreev(elements);
00361           return false;
00362         }
00363 
00364         GIRARA_LIST_FOREACH(result->groups, girara_completion_group_t*, iter, group)
00365           /* create group entry */
00366           if (group->value != NULL) {
00367             girara_internal_completion_entry_t* entry = g_slice_new(girara_internal_completion_entry_t);
00368             entry->group  = TRUE;
00369             entry->value  = g_strdup(group->value);
00370             entry->widget = girara_completion_row_create(session, group->value, NULL, TRUE);
00371 
00372             entries = g_list_append(entries, entry);
00373 
00374             gtk_box_pack_start(session->gtk.results, GTK_WIDGET(entry->widget), FALSE, FALSE, 0);
00375           }
00376 
00377           GIRARA_LIST_FOREACH(group->elements, girara_completion_element_t*, iter2, element)
00378             girara_internal_completion_entry_t* entry = g_slice_new(girara_internal_completion_entry_t);
00379             entry->group  = FALSE;
00380             entry->value  = g_strdup(element->value);
00381             entry->widget = girara_completion_row_create(session, element->value, element->description, FALSE);
00382 
00383             entries = g_list_append(entries, entry);
00384 
00385             gtk_box_pack_start(session->gtk.results, GTK_WIDGET(entry->widget), FALSE, FALSE, 0);
00386 
00387           GIRARA_LIST_FOREACH_END(group->elements, girara_completion_element_t*, iter2, element);
00388         GIRARA_LIST_FOREACH_END(result->groups, girara_completion_group_t*, iter, group);
00389         girara_completion_free(result);
00390 
00391         command_mode = false;
00392       }
00393     }
00394 
00395     if (entries != NULL) {
00396       entries_current = (argument->n == GIRARA_NEXT) ? g_list_last(entries) : entries;
00397       gtk_box_pack_start(session->gtk.box, GTK_WIDGET(session->gtk.results), FALSE, FALSE, 0);
00398       gtk_widget_show(GTK_WIDGET(session->gtk.results));
00399     }
00400   }
00401 
00402   /* update entries */
00403   unsigned int n_elements = g_list_length(entries);
00404   if (entries != NULL && n_elements > 0) {
00405     if (n_elements > 1) {
00406       girara_completion_row_set_color(session, ((girara_internal_completion_entry_t *) entries_current->data)->widget, GIRARA_NORMAL);
00407 
00408       bool next_group = FALSE;
00409 
00410       for (unsigned int i = 0; i < n_elements; i++) {
00411         if (argument->n == GIRARA_NEXT || argument->n == GIRARA_NEXT_GROUP) {
00412           GList* entry = g_list_next(entries_current);
00413           if (entry == NULL) {
00414             entry = g_list_first(entries);
00415           }
00416 
00417           entries_current = entry;
00418         } else if (argument->n == GIRARA_PREVIOUS || argument->n == GIRARA_PREVIOUS_GROUP) {
00419           GList* entry = g_list_previous(entries_current);
00420           if (entry == NULL) {
00421             entry = g_list_last(entries);
00422           }
00423 
00424           entries_current = entry;
00425         }
00426 
00427         if (((girara_internal_completion_entry_t*) entries_current->data)->group) {
00428           if (command_mode == false && (argument->n == GIRARA_NEXT_GROUP || argument->n == GIRARA_PREVIOUS_GROUP)) {
00429             next_group = TRUE;
00430           }
00431           continue;
00432         } else {
00433           if (command_mode == false && (next_group == 0) && (argument->n == GIRARA_NEXT_GROUP || argument->n == GIRARA_PREVIOUS_GROUP)) {
00434             continue;
00435           }
00436           break;
00437         }
00438       }
00439 
00440       girara_completion_row_set_color(session, ((girara_internal_completion_entry_t *) entries_current->data)->widget, GIRARA_HIGHLIGHT);
00441 
00442       /* hide other items */
00443       unsigned int n_completion_items = 15;
00444       girara_setting_get(session, "n-completion-items", &n_completion_items);
00445       unsigned int uh = ceil( n_completion_items / 2);
00446       unsigned int lh = floor(n_completion_items / 2);
00447 
00448       unsigned int current_item = g_list_position(entries, entries_current);
00449 
00450       GList* tmpentry = entries;
00451       for (unsigned int i = 0; i < n_elements; i++) {
00452         if (
00453             (i >= (current_item - lh) && (i <= current_item + uh)) ||
00454             (i < n_completion_items && current_item < lh) ||
00455             (i >= (n_elements - n_completion_items) && (current_item >= (n_elements - uh)))
00456           )
00457         {
00458           gtk_widget_show(GTK_WIDGET(((girara_internal_completion_entry_t*) tmpentry->data)->widget));
00459         } else {
00460           gtk_widget_hide(GTK_WIDGET(((girara_internal_completion_entry_t*) tmpentry->data)->widget));
00461         }
00462 
00463         tmpentry = g_list_next(tmpentry);
00464       }
00465     } else {
00466       gtk_widget_hide(GTK_WIDGET(((girara_internal_completion_entry_t*) (g_list_nth(entries, 0))->data)->widget));
00467     }
00468 
00469     /* update text */
00470     char* temp;
00471     char* escaped_value = escape(((girara_internal_completion_entry_t *) entries_current->data)->value);
00472     if (command_mode == true) {
00473       char* space = (n_elements == 1) ? " " : "";
00474       temp = g_strconcat(":", escaped_value, space, NULL);
00475     } else {
00476       temp = g_strconcat(":", previous_command, " ", escaped_value, NULL);
00477     }
00478 
00479     gtk_entry_set_text(session->gtk.inputbar_entry, temp);
00480     gtk_editable_set_position(GTK_EDITABLE(session->gtk.inputbar_entry), -1);
00481     g_free(escaped_value);
00482 
00483     /* update previous */
00484     g_free(previous_command);
00485     g_free(previous_parameter);
00486     previous_command   = g_strdup((command_mode) ? ((girara_internal_completion_entry_t*) entries_current->data)->value : current_command);
00487     previous_parameter = g_strdup((command_mode) ? current_parameter : ((girara_internal_completion_entry_t*) entries_current->data)->value);
00488     previous_length    = strlen(temp);
00489     g_free(temp);
00490   }
00491 
00492   g_free(current_command);
00493   g_free(current_parameter);
00494 
00495   g_strfreev(elements);
00496 
00497   return false;
00498 }
00499 
00500 static GtkEventBox*
00501 girara_completion_row_create(girara_session_t* session, const char* command, const char* description, bool group)
00502 {
00503   GtkBox      *col = GTK_BOX(gtk_hbox_new(FALSE, 0));
00504   GtkEventBox *row = GTK_EVENT_BOX(gtk_event_box_new());
00505 
00506   GtkLabel *show_command     = GTK_LABEL(gtk_label_new(NULL));
00507   GtkLabel *show_description = GTK_LABEL(gtk_label_new(NULL));
00508 
00509   gtk_misc_set_alignment(GTK_MISC(show_command),     0.0, 0.0);
00510   gtk_misc_set_alignment(GTK_MISC(show_description), 0.0, 0.0);
00511 
00512   if (group == true) {
00513     gtk_misc_set_padding(GTK_MISC(show_command),     2, 4);
00514     gtk_misc_set_padding(GTK_MISC(show_description), 2, 4);
00515   } else {
00516     gtk_misc_set_padding(GTK_MISC(show_command),     1, 1);
00517     gtk_misc_set_padding(GTK_MISC(show_description), 1, 1);
00518   }
00519 
00520   gtk_label_set_use_markup(show_command,     TRUE);
00521   gtk_label_set_use_markup(show_description, TRUE);
00522 
00523   gchar* c = g_markup_printf_escaped(FORMAT_COMMAND,     command ? command : "");
00524   gchar* d = g_markup_printf_escaped(FORMAT_DESCRIPTION, description ? description : "");
00525   gtk_label_set_markup(show_command,     c);
00526   gtk_label_set_markup(show_description, d);
00527   g_free(c);
00528   g_free(d);
00529 
00530   if (group == true) {
00531     gtk_widget_override_color(GTK_WIDGET(show_command),     GTK_STATE_NORMAL, &(session->style.completion_group_foreground));
00532     gtk_widget_override_color(GTK_WIDGET(show_description), GTK_STATE_NORMAL, &(session->style.completion_group_foreground));
00533     gtk_widget_override_background_color(GTK_WIDGET(row),   GTK_STATE_NORMAL, &(session->style.completion_group_background));
00534   } else {
00535     gtk_widget_override_color(GTK_WIDGET(show_command),     GTK_STATE_NORMAL, &(session->style.completion_foreground));
00536     gtk_widget_override_color(GTK_WIDGET(show_description), GTK_STATE_NORMAL, &(session->style.completion_foreground));
00537     gtk_widget_override_background_color(GTK_WIDGET(row),   GTK_STATE_NORMAL, &(session->style.completion_background));
00538  }
00539 
00540   gtk_widget_override_font(GTK_WIDGET(show_command),     session->style.font);
00541   gtk_widget_override_font(GTK_WIDGET(show_description), session->style.font);
00542 
00543   gtk_box_pack_start(GTK_BOX(col), GTK_WIDGET(show_command),     TRUE,  TRUE,  2);
00544   gtk_box_pack_start(GTK_BOX(col), GTK_WIDGET(show_description), FALSE, FALSE, 2);
00545 
00546   gtk_container_add(GTK_CONTAINER(row), GTK_WIDGET(col));
00547   gtk_widget_show_all(GTK_WIDGET(row));
00548 
00549   return row;
00550 }
00551 
00552 static void
00553 girara_completion_row_set_color(girara_session_t* session, GtkEventBox* row, int mode)
00554 {
00555   g_return_if_fail(session != NULL);
00556   g_return_if_fail(row     != NULL);
00557 
00558   GtkBox *col    = GTK_BOX(gtk_bin_get_child(GTK_BIN(row)));
00559   GList* items   = gtk_container_get_children(GTK_CONTAINER(col));
00560   GtkLabel *cmd  = GTK_LABEL(g_list_nth_data(items, 0));
00561   GtkLabel *desc = GTK_LABEL(g_list_nth_data(items, 1));
00562 
00563   if (mode == GIRARA_HIGHLIGHT) {
00564     gtk_widget_override_color(GTK_WIDGET(cmd),            GTK_STATE_NORMAL, &(session->style.completion_highlight_foreground));
00565     gtk_widget_override_color(GTK_WIDGET(desc),           GTK_STATE_NORMAL, &(session->style.completion_highlight_foreground));
00566     gtk_widget_override_background_color(GTK_WIDGET(row), GTK_STATE_NORMAL, &(session->style.completion_highlight_background));
00567   } else {
00568     gtk_widget_override_color(GTK_WIDGET(cmd),            GTK_STATE_NORMAL, &(session->style.completion_foreground));
00569     gtk_widget_override_color(GTK_WIDGET(desc),           GTK_STATE_NORMAL, &(session->style.completion_foreground));
00570     gtk_widget_override_background_color(GTK_WIDGET(row), GTK_STATE_NORMAL, &(session->style.completion_background));
00571   }
00572 
00573   g_list_free(items);
00574 }
 All Data Structures Files Functions Variables Typedefs Enumerations Enumerator Defines