/** \file text.c * multi line text entry widget */ /* XTrkCad - Model Railroad CAD * Copyright (C) 2005 Dave Bullis * * 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, write to the Free Software * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. */ #include #include #include #include #define GTK_DISABLE_SINGLE_INCLUDES #define GDK_DISABLE_DEPRECATED #define GTK_DISABLE_DEPRECATED #define GSEAL_ENABLE #include #include #include "i18n.h" #include "gtkint.h" struct PrintData { wText_p tb; gint lines_per_page; gdouble font_size; gchar **lines; gint total_lines; gint total_pages; }; #define HEADER_HEIGHT 20.0 #define HEADER_GAP 8.5 /* ***************************************************************************** * * Multi-line Text Boxes * ***************************************************************************** */ struct wText_t { WOBJ_COMMON wPos_t width, height; int changed; GtkWidget *text; }; /** * Reset a text entry by clearing text and resetting the readonly and the * change flag. * * \param bt IN text entry * \return */ void wTextClear(wText_p bt) { GtkTextBuffer *tb; tb = gtk_text_view_get_buffer(GTK_TEXT_VIEW(bt->text)); gtk_text_buffer_set_text(tb, "", -1); if (bt->option & BO_READONLY) { gtk_text_view_set_editable(GTK_TEXT_VIEW(bt->text), FALSE); } bt->changed = FALSE; } /** * Append text to the end of the entry field's text buffer. * * \param bt IN text buffer * \param text IN text to append * \return */ void wTextAppend(wText_p bt, const char *text) { GtkTextBuffer *tb; GtkTextIter ti1; GtkTextMark *tm; if (bt->text == 0) { abort(); } tb = gtk_text_view_get_buffer(GTK_TEXT_VIEW(bt->text)); // convert to utf-8 text = wlibConvertInput(text); // append to end of buffer gtk_text_buffer_get_end_iter(tb, &ti1); gtk_text_buffer_insert(tb, &ti1, text, -1); if ( bt->option & BT_TOP ) { // and scroll to start of text gtk_text_buffer_get_start_iter(tb, &ti1); } else { // and scroll to end of text gtk_text_buffer_get_end_iter(tb, &ti1); } tm = gtk_text_buffer_create_mark(tb, NULL, &ti1, TRUE ); gtk_text_view_scroll_mark_onscreen (GTK_TEXT_VIEW(bt->text), tm ); gtk_text_buffer_delete_mark( tb, tm ); bt->changed = FALSE; } /** * Get the text from a text buffer in system codepage * The caller is responsible for free'ing the allocated storage. * * Dont convert from UTF8 * * \param bt IN the text widget * \return pointer to the converted text */ static char *wlibGetText(wText_p bt) { GtkTextBuffer *tb; GtkTextIter ti1, ti2; char *cp, *cp1, *res; if (bt->text == 0) { abort(); } tb = gtk_text_view_get_buffer(GTK_TEXT_VIEW(bt->text)); gtk_text_buffer_get_bounds(tb, &ti1, &ti2); cp = gtk_text_buffer_get_text(tb, &ti1, &ti2, FALSE); //cp1 = wlibConvertOutput(cp); res = strdup(cp); g_free(cp); return res; } /** * Save the text from the widget to a file * * \param bt IN the text widget * \param fileName IN name of save file * \return TRUE is success, FALSE if not */ wBool_t wTextSave(wText_p bt, const char *fileName) { FILE *f; char *cp; f = fopen(fileName, "w"); if (f==NULL) { wNoticeEx(NT_ERROR, fileName, "Ok", NULL); return FALSE; } cp = wlibGetText(bt); fwrite(cp, 1, strlen(cp), f); free(cp); fclose(f); return TRUE; } /** * Begin the printing by retrieving the contents of the text box and * count the lines of text. * * \param operation IN the GTK print operation * \param context IN print context * \param pd IN data structure for user data * */ static void begin_print(GtkPrintOperation *operation, GtkPrintContext *context, struct PrintData *pd) { gchar *contents; gdouble height; contents = wlibGetText(pd->tb); pd->lines = g_strsplit(contents, "\n", 0); /* Count the total number of lines in the file. */ /* ignore the header lines */ pd->total_lines = 6; while (pd->lines[pd->total_lines] != NULL) { pd->total_lines++; } /* Based on the height of the page and font size, calculate how many lines can be * rendered on a single page. A padding of 3 is placed between lines as well. * Space for page header, table header and footer lines is subtracted from the total size */ height = gtk_print_context_get_height(context) - (pd->font_size + 3) - 2 * (HEADER_HEIGHT + HEADER_GAP); pd->lines_per_page = floor(height / (pd->font_size + 3)); pd->total_pages = (pd->total_lines - 1) / pd->lines_per_page + 1; gtk_print_operation_set_n_pages(operation, pd->total_pages); free(contents); } /** * Draw the page, which includes a header with the file name and page number along * with one page of text with a font of "Monospace 10". * * \param operation IN the GTK print operation * \param context IN print context * \param page_nr IN page to print * \param pd IN data structure for user data * * */ static void draw_page(GtkPrintOperation *operation, GtkPrintContext *context, gint page_nr, struct PrintData *pd) { cairo_t *cr; PangoLayout *layout; gdouble width, text_height, height; gint line, i, text_width, layout_height; PangoFontDescription *desc; gchar *page_str; cr = gtk_print_context_get_cairo_context(context); width = gtk_print_context_get_width(context); layout = gtk_print_context_create_pango_layout(context); desc = pango_font_description_from_string("Monospace"); pango_font_description_set_size(desc, pd->font_size * PANGO_SCALE); /* * render the header line with document type parts list on left and * first line of layout title on right */ pango_layout_set_font_description(layout, desc); pango_layout_set_text(layout, pd->lines[ 0 ], -1); // document type pango_layout_set_width(layout, -1); pango_layout_set_alignment(layout, PANGO_ALIGN_LEFT); pango_layout_get_size(layout, NULL, &layout_height); text_height = (gdouble) layout_height / PANGO_SCALE; cairo_move_to(cr, 0, (HEADER_HEIGHT - text_height) / 2); pango_cairo_show_layout(cr, layout); pango_layout_set_text(layout, pd->lines[ 2 ], -1); // layout title pango_layout_get_size(layout, &text_width, NULL); pango_layout_set_alignment(layout, PANGO_ALIGN_RIGHT); cairo_move_to(cr, width - (text_width / PANGO_SCALE), (HEADER_HEIGHT - text_height) / 2); pango_cairo_show_layout(cr, layout); /* Render the column header */ cairo_move_to(cr, 0, HEADER_HEIGHT + HEADER_GAP + pd->font_size + 3); pango_layout_set_text(layout, pd->lines[ 6 ], -1); pango_cairo_show_layout(cr, layout); cairo_rel_move_to(cr, 0, pd->font_size + 3); pango_layout_set_text(layout, pd->lines[ 7 ], -1); pango_cairo_show_layout(cr, layout); /* Render the page text with the specified font and size. */ cairo_rel_move_to(cr, 0, pd->font_size + 3); line = page_nr * pd->lines_per_page + 8; for (i = 0; i < pd->lines_per_page && line < pd->total_lines; i++) { pango_layout_set_text(layout, pd->lines[line], -1); pango_cairo_show_layout(cr, layout); cairo_rel_move_to(cr, 0, pd->font_size + 3); line++; } /* * Render the footer line with date on the left and page number * on the right */ pango_layout_set_text(layout, pd->lines[ 5 ], -1); // date pango_layout_set_width(layout, -1); pango_layout_set_alignment(layout, PANGO_ALIGN_LEFT); pango_layout_get_size(layout, NULL, &layout_height); text_height = (gdouble) layout_height / PANGO_SCALE; height = gtk_print_context_get_height(context); cairo_move_to(cr, 0, height - ((HEADER_HEIGHT - text_height) / 2)); pango_cairo_show_layout(cr, layout); page_str = g_strdup_printf(_("%d of %d"), page_nr + 1, pd->total_pages); // page number pango_layout_set_text(layout, page_str, -1); pango_layout_get_size(layout, &text_width, NULL); pango_layout_set_alignment(layout, PANGO_ALIGN_RIGHT); cairo_move_to(cr, width - (text_width / PANGO_SCALE), height - ((HEADER_HEIGHT - text_height) / 2)); pango_cairo_show_layout(cr, layout); g_free(page_str); g_object_unref(layout); pango_font_description_free(desc); } /** * Clean up after the printing operation since it is done. * * \param operation IN the GTK print operation * \param context IN print context * \param pd IN data structure for user data * * */ static void end_print(GtkPrintOperation *operation, GtkPrintContext *context, struct PrintData *pd) { g_strfreev(pd->lines); free(pd); } /** * Print the content of a multi line text box. This function is only used * for printing the parts list. So it makes some assumptions on the structure * and the content. Change if the multi line entry is changed. * The deprecated gtk_text is not supported by this function. * * Thanks to Andrew Krause's book for a good starting point. * * \param bt IN the text field * \return TRUE on success, FALSE on error */ wBool_t wTextPrint( wText_p bt) { GtkPrintOperation *operation; GtkWidget *dialog; GError *error = NULL; gint res; struct PrintData *data; /* Create a new print operation, applying saved print settings if they exist. */ operation = gtk_print_operation_new(); WlibApplySettings(operation); data = malloc(sizeof(struct PrintData)); data->font_size = 10.0; data->tb = bt; g_signal_connect(G_OBJECT(operation), "begin_print", G_CALLBACK(begin_print), (gpointer) data); g_signal_connect(G_OBJECT(operation), "draw_page", G_CALLBACK(draw_page), (gpointer) data); g_signal_connect(G_OBJECT(operation), "end_print", G_CALLBACK(end_print), (gpointer) data); /* Run the default print operation that will print the selected file. */ res = gtk_print_operation_run(operation, GTK_PRINT_OPERATION_ACTION_PRINT_DIALOG, GTK_WINDOW(gtkMainW->gtkwin), &error); /* If the print operation was accepted, save the new print settings. */ if (res == GTK_PRINT_OPERATION_RESULT_APPLY) { WlibSaveSettings(operation); } /* Otherwise, report that the print operation has failed. */ else if (error) { dialog = gtk_message_dialog_new(GTK_WINDOW(gtkMainW->gtkwin), GTK_DIALOG_DESTROY_WITH_PARENT, GTK_MESSAGE_ERROR, GTK_BUTTONS_CLOSE, "%s",error->message); g_error_free(error); gtk_dialog_run(GTK_DIALOG(dialog)); gtk_widget_destroy(dialog); } g_object_ref_sink(operation); g_object_unref(operation); return TRUE; } /** * Get the length of text * * \param bt IN the text widget * \return length of string including terminating \0 */ int wTextGetSize(wText_p bt) { char *cp = wlibGetText(bt); int len = strlen(cp); free(cp); return len + 1; } /** * Get the text * * \param bt IN the text widget * \param text IN the buffer * \param len IN maximum number of bytes to return * \return */ void wTextGetText(wText_p bt, char *text, int len) { char *cp; cp = wlibGetText(bt); strncpy(text, cp, len); if (len > 0) { text[len - 1] = '\0'; } free(cp); } /** * Get the read-only state of the text entry * * \param bt IN the text widget * \param ro IN read only flag * \return */ void wTextSetReadonly(wText_p bt, wBool_t ro) { gtk_text_view_set_editable(GTK_TEXT_VIEW(bt->text), !ro); if (ro) { bt->option |= BO_READONLY; } else { bt->option &= ~BO_READONLY; } } /** * check whether text has been modified * * \param bt IN the text widget * \return TRUE of changed, FALSE otherwise */ wBool_t wTextGetModified(wText_p bt) { return bt->changed; } /** * set the size of the text widget * * \param bt IN the text widget * \param w IN width * \param h IN height * \return */ void wTextSetSize(wText_p bt, wPos_t w, wPos_t h) { gtk_widget_set_size_request(bt->widget, w, h); bt->w = w; bt->h = h; } /** * calculate the required size of the widget based on number of lines and columns * \todo this calculation is based on a fixed size font and is not useful in a generic setup * * \param bt IN the text widget * \param rows IN text rows * \param cols IN text columns * \param width OUT width in pixel * \param height OUT height in pixel * \return */ void wTextComputeSize(wText_p bt, int rows, int cols, wPos_t *width, wPos_t *height) { *width = rows * 7; *height = cols * 14; } /** * set the position of the text widget ???? * * \param bt IN the text widget * \param pos IN position * \return */ void wTextSetPosition(wText_p bt, int pos) { /* TODO */ } /** * signal handler for changed signal * * \param widget IN * \param bt IN text entry field * \return */ static void textChanged(GtkWidget *widget, wText_p bt) { if (bt == 0) { return; } bt->changed = TRUE; } /** * Create a multiline text entry field * * \param parent IN parent window * \param x IN x position * \param Y IN y position * \param helpStr IN balloon help string * \param labelStr IN Button label ??? * \param option IN * \param width IN * \param valueP IN Current color ??? * \param action IN Button callback procedure * \param data IN ??? * \return bb handle for created text widget */ wText_p wTextCreate(wWin_p parent, wPos_t x, wPos_t y, const char *helpStr, const char *labelStr, long option, wPos_t width, wPos_t height) { wText_p bt; GtkTextBuffer *tb; // create the widget bt = wlibAlloc(parent, B_MULTITEXT, x, y, labelStr, sizeof *bt, NULL); bt->width = width; bt->height = height; bt->option = option; wlibComputePos((wControl_p)bt); // create a scroll window with scroll bars that are automatically created bt->widget = gtk_scrolled_window_new(NULL, NULL); gtk_scrolled_window_set_policy(GTK_SCROLLED_WINDOW(bt->widget), GTK_POLICY_AUTOMATIC, GTK_POLICY_AUTOMATIC); // create a text view and place it inside the scroll widget bt->text = gtk_text_view_new(); if (bt->text == 0) { abort(); } gtk_container_add(GTK_CONTAINER(bt->widget), bt->text); // get the text buffer and add a bold tag to it tb = gtk_text_view_get_buffer(GTK_TEXT_VIEW(bt->text)); gtk_text_buffer_create_tag(tb, "bold", "weight", PANGO_WEIGHT_BOLD, NULL); // this seems to assume some fixed size fonts, not really helpful if (option&BT_CHARUNITS) { width *= 7; height *= 14; } // show the widgets gtk_widget_show(bt->text); gtk_widget_show(bt->widget); // set the size??? gtk_widget_set_size_request(GTK_WIDGET(bt->widget), width+15/*requisition.width*/, height); // configure read-only mode if (bt->option&BO_READONLY) { gtk_text_view_set_editable(GTK_TEXT_VIEW(bt->text), FALSE); gtk_text_view_set_cursor_visible(GTK_TEXT_VIEW(bt->text), FALSE); } if (labelStr) { bt->labelW = wlibAddLabel((wControl_p)bt, labelStr); } wlibAddHelpString(bt->widget, helpStr); gtk_text_view_set_wrap_mode(GTK_TEXT_VIEW(bt->text), GTK_WRAP_WORD); g_signal_connect(G_OBJECT(tb), "changed", G_CALLBACK(textChanged), bt); // place the widget in a fixed position of the parent gtk_fixed_put(GTK_FIXED(parent->widget), bt->widget, bt->realX, bt->realY); wlibControlGetSize((wControl_p)bt); wlibAddButton((wControl_p)bt); // done, return the finished widget return bt; }