gds-render/gds-render-gui.c
Mario Hüttel 17af08b04d GdsOutputrenderer: progress-changed signal: Status message is now freed inside the GdsOutputRenderer.
This is safe because the signals are handled back to back inside the emit function. Therefore, it can be freed directly after emission. This solves the problem of the status message not being freed if no handler is connected to the signal.
2019-08-24 13:49:33 +02:00

680 lines
21 KiB
C

/*
* GDSII-Converter
* Copyright (C) 2018 Mario Hüttel <mario.huettel@gmx.net>
*
* This file is part of GDSII-Converter.
*
* GDSII-Converter is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License version 2 as
* published by the Free Software Foundation.
*
* GDSII-Converter 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 GDSII-Converter. If not, see <http://www.gnu.org/licenses/>.
*/
/**
* @file gds-render-gui.c
* @brief Handling of GUI
* @author Mario Hüttel <mario.huettel@gmx.net>
*/
/** @addtogroup GUI
* @{
*/
#include <stdio.h>
#include <gtk/gtk.h>
#include <gds-render/gds-render-gui.h>
#include <gds-render/gds-utils/gds-parser.h>
#include <gds-render/gds-utils/gds-tree-checker.h>
#include <gds-render/layer/layer-selector.h>
#include <gds-render/widgets/activity-bar.h>
#include <gds-render/cell-selector/tree-store.h>
#include <gds-render/cell-selector/lib-cell-renderer.h>
#include <gds-render/output-renderers/latex-renderer.h>
#include <gds-render/output-renderers/cairo-renderer.h>
#include <gds-render/widgets/conv-settings-dialog.h>
#include <gds-render/geometric/cell-geometrics.h>
#include <gds-render/version.h>
enum gds_render_gui_signal_sig_ids {SIGNAL_WINDOW_CLOSED = 0, SIGNAL_COUNT};
static guint gds_render_gui_signals[SIGNAL_COUNT];
struct gui_button_states {
gboolean rendering_active;
gboolean valid_cell_selected;
};
struct _GdsRenderGui {
/* Parent GObject */
GObject parent;
/* Custom fields */
GtkWindow *main_window;
GtkWidget *convert_button;
GtkWidget *open_button;
GtkWidget *load_layer_button;
GtkWidget *save_layer_button;
GtkTreeStore *cell_tree_store;
GtkWidget *cell_search_entry;
LayerSelector *layer_selector;
GtkTreeView *cell_tree_view;
GList *gds_libraries;
ActivityBar *activity_status_bar;
struct render_settings render_dialog_settings;
ColorPalette *palette;
struct gui_button_states button_state_data;
};
G_DEFINE_TYPE(GdsRenderGui, gds_render_gui, G_TYPE_OBJECT)
/**
* @brief Main window close event
* @param window GtkWindow which is closed
* @param event unused event
* @param user GdsRenderGui instance
* @return Status of the event handling. Always true.
*/
static gboolean on_window_close(gpointer window, GdkEvent *event, gpointer user)
{
GdsRenderGui *self;
self = RENDERER_GUI(user);
/* Don't close window in case of error */
if (!self)
return TRUE;
/* Close Window. Leads to termination of the program/the current instance */
g_clear_object(&self->main_window);
gtk_widget_destroy(GTK_WIDGET(window));
/* Delete loaded library data */
clear_lib_list(&self->gds_libraries);
g_signal_emit(self, gds_render_gui_signals[SIGNAL_WINDOW_CLOSED], 0);
return TRUE;
}
/**
* @brief generate string from gds_time_field
* @param date Date to convert
* @return String with date
*/
static GString *generate_string_from_date(struct gds_time_field *date)
{
GString *str;
str = g_string_new_len(NULL, 50);
g_string_printf(str, "%02u.%02u.%u - %02u:%02u",
(unsigned int)date->day,
(unsigned int)date->month,
(unsigned int)date->year,
(unsigned int)date->hour,
(unsigned int)date->minute);
return str;
}
/**
* @brief Callback function of Load GDS button
* @param button
* @param user GdsRenderGui instance
*/
static void on_load_gds(gpointer button, gpointer user)
{
GList *cell;
GtkTreeIter libiter;
GtkTreeIter celliter;
GList *lib;
struct gds_library *gds_lib;
struct gds_cell *gds_c;
GdsRenderGui *self;
GtkWidget *open_dialog;
GtkFileChooser *file_chooser;
GtkFileFilter *filter;
GtkStyleContext *button_style;
gint dialog_result;
int gds_result;
char *filename;
GString *mod_date;
GString *acc_date;
unsigned int cell_error_level;
self = RENDERER_GUI(user);
if (!self)
return;
open_dialog = gtk_file_chooser_dialog_new("Open GDSII File", self->main_window,
GTK_FILE_CHOOSER_ACTION_OPEN,
"Cancel", GTK_RESPONSE_CANCEL,
"Open GDSII", GTK_RESPONSE_ACCEPT,
NULL);
file_chooser = GTK_FILE_CHOOSER(open_dialog);
/* Add GDS II Filter */
filter = gtk_file_filter_new();
gtk_file_filter_add_pattern(filter, "*.gds");
gtk_file_filter_set_name(filter, "GDSII-Files");
gtk_file_chooser_add_filter(file_chooser, filter);
dialog_result = gtk_dialog_run(GTK_DIALOG(open_dialog));
if (dialog_result != GTK_RESPONSE_ACCEPT)
goto end_destroy;
/* Get File name */
filename = gtk_file_chooser_get_filename(file_chooser);
gtk_tree_store_clear(self->cell_tree_store);
clear_lib_list(&self->gds_libraries);
/* Parse new GDSII file */
gds_result = parse_gds_from_file(filename, &self->gds_libraries);
/* Delete file name afterwards */
g_free(filename);
if (gds_result)
goto end_destroy;
/* remove suggested action from Open button */
button_style = gtk_widget_get_style_context(GTK_WIDGET(button));
gtk_style_context_remove_class(button_style, "suggested-action");
for (lib = self->gds_libraries; lib != NULL; lib = lib->next) {
gds_lib = (struct gds_library *)lib->data;
/* Create top level iter */
gtk_tree_store_append(self->cell_tree_store, &libiter, NULL);
/* Convert dates to String */
mod_date = generate_string_from_date(&gds_lib->mod_time);
acc_date = generate_string_from_date(&gds_lib->access_time);
gtk_tree_store_set(self->cell_tree_store, &libiter,
CELL_SEL_LIBRARY, gds_lib,
CELL_SEL_MODDATE, mod_date->str,
CELL_SEL_ACCESSDATE, acc_date->str,
-1);
/* Check this library. This might take a while */
(void)gds_tree_check_cell_references(gds_lib);
(void)gds_tree_check_reference_loops(gds_lib);
/* Delete GStrings including string data. */
/* Cell store copies String type data items */
g_string_free(mod_date, TRUE);
g_string_free(acc_date, TRUE);
for (cell = gds_lib->cells; cell != NULL; cell = cell->next) {
gds_c = (struct gds_cell *)cell->data;
gtk_tree_store_append(self->cell_tree_store, &celliter, &libiter);
/* Convert dates to String */
mod_date = generate_string_from_date(&gds_c->mod_time);
acc_date = generate_string_from_date(&gds_c->access_time);
/* Get the checking results for this cell */
cell_error_level = 0;
if (gds_c->checks.unresolved_child_count)
cell_error_level |= LIB_CELL_RENDERER_ERROR_WARN;
/* Check if it is completely b0rken */
if (gds_c->checks.affected_by_reference_loop)
cell_error_level |= LIB_CELL_RENDERER_ERROR_ERR;
/* Add cell to tree store model */
gtk_tree_store_set(self->cell_tree_store, &celliter,
CELL_SEL_CELL, gds_c,
CELL_SEL_MODDATE, mod_date->str,
CELL_SEL_ACCESSDATE, acc_date->str,
CELL_SEL_CELL_ERROR_STATE, cell_error_level,
-1);
/* Delete GStrings including string data. */
/* Cell store copies String type data items */
g_string_free(mod_date, TRUE);
g_string_free(acc_date, TRUE);
} /* for cells */
} /* for libraries */
/* Create Layers in Layer Box */
layer_selector_generate_layer_widgets(self->layer_selector, self->gds_libraries);
end_destroy:
/* Destroy dialog and filter */
gtk_widget_destroy(open_dialog);
}
static void process_button_state_changes(GdsRenderGui *self)
{
gboolean convert_button_state = FALSE;
gboolean open_gds_button_state = FALSE;
/* Calculate states */
if (!self->button_state_data.rendering_active) {
open_gds_button_state = TRUE;
if (self->button_state_data.valid_cell_selected)
convert_button_state = TRUE;
}
/* Apply states */
gtk_widget_set_sensitive(self->convert_button, convert_button_state);
gtk_widget_set_sensitive(self->open_button, open_gds_button_state);
}
/**
* @brief Callback for auto coloring button
* @param button
* @param user
*/
static void on_auto_color_clicked(gpointer button, gpointer user)
{
GdsRenderGui *self;
(void)button;
self = RENDERER_GUI(user);
layer_selector_auto_color_layers(self->layer_selector, self->palette, 1.0);
}
static void async_rendering_finished_callback(GdsOutputRenderer *renderer, gpointer gui)
{
GdsRenderGui *self;
self = RENDERER_GUI(gui);
self->button_state_data.rendering_active = FALSE;
process_button_state_changes(self);
activity_bar_set_ready(self->activity_status_bar);
g_object_unref(renderer);
}
static void async_rendering_status_update_callback(GdsOutputRenderer *renderer, const char *status_message, gpointer data)
{
GdsRenderGui *gui;
(void)renderer;
gui = RENDERER_GUI(data);
activity_bar_set_busy(gui->activity_status_bar, status_message);
}
/**
* @brief Convert button callback
* @param button
* @param user
*/
static void on_convert_clicked(gpointer button, gpointer user)
{
(void)button;
GdsRenderGui *self;
GtkTreeSelection *selection;
GtkTreeIter iter;
GtkTreeModel *model;
struct gds_cell *cell_to_render;
GtkWidget *dialog;
RendererSettingsDialog *settings;
GtkFileFilter *filter;
gint res;
char *file_name;
union bounding_box cell_box;
unsigned int height, width;
struct render_settings *sett;
LayerSettings *layer_settings;
GdsOutputRenderer *render_engine;
self = RENDERER_GUI(user);
if (!self)
return;
/* Abort if rendering is already active */
if (self->button_state_data.rendering_active == TRUE)
return;
sett = &self->render_dialog_settings;
/* Get selected cell */
selection = gtk_tree_view_get_selection(self->cell_tree_view);
if (gtk_tree_selection_get_selected(selection, &model, &iter) == FALSE)
return;
gtk_tree_model_get(model, &iter, CELL_SEL_CELL, &cell_to_render, -1);
if (!cell_to_render)
return;
/* Get layers that are rendered */
layer_settings = layer_selector_export_rendered_layer_info(self->layer_selector);
/* Calculate cell size in DB units */
bounding_box_prepare_empty(&cell_box);
calculate_cell_bounding_box(&cell_box, cell_to_render);
/* Calculate size in database units
* Note that the results are bound to be positive,
* so casting them to unsigned int is absolutely valid
*/
height = (unsigned int)(cell_box.vectors.upper_right.y - cell_box.vectors.lower_left.y);
width = (unsigned int)(cell_box.vectors.upper_right.x - cell_box.vectors.lower_left.x);
/* Show settings dialog */
settings = renderer_settings_dialog_new(GTK_WINDOW(self->main_window));
renderer_settings_dialog_set_settings(settings, sett);
renderer_settings_dialog_set_database_unit_scale(settings, cell_to_render->parent_library->unit_in_meters);
renderer_settings_dialog_set_cell_height(settings, height);
renderer_settings_dialog_set_cell_width(settings, width);
g_object_set(G_OBJECT(settings), "cell-name", cell_to_render->name, NULL);
res = gtk_dialog_run(GTK_DIALOG(settings));
if (res == GTK_RESPONSE_OK) {
renderer_settings_dialog_get_settings(settings, sett);
gtk_widget_destroy(GTK_WIDGET(settings));
} else {
gtk_widget_destroy(GTK_WIDGET(settings));
goto ret_layer_destroy;
}
/* save file dialog */
dialog = gtk_file_chooser_dialog_new((sett->renderer == RENDERER_LATEX_TIKZ
? "Save LaTeX File" : "Save PDF"),
GTK_WINDOW(self->main_window), GTK_FILE_CHOOSER_ACTION_SAVE,
"Cancel", GTK_RESPONSE_CANCEL, "Save", GTK_RESPONSE_ACCEPT, NULL);
/* Set file filter according to settings */
filter = gtk_file_filter_new();
switch (sett->renderer) {
case RENDERER_LATEX_TIKZ:
gtk_file_filter_add_pattern(filter, "*.tex");
gtk_file_filter_set_name(filter, "LaTeX-Files");
break;
case RENDERER_CAIROGRAPHICS_PDF:
gtk_file_filter_add_pattern(filter, "*.pdf");
gtk_file_filter_set_name(filter, "PDF-Files");
break;
case RENDERER_CAIROGRAPHICS_SVG:
gtk_file_filter_add_pattern(filter, "*.svg");
gtk_file_filter_set_name(filter, "SVG-Files");
break;
}
gtk_file_chooser_add_filter(GTK_FILE_CHOOSER(dialog), filter);
gtk_file_chooser_set_do_overwrite_confirmation(GTK_FILE_CHOOSER(dialog), TRUE);
res = gtk_dialog_run(GTK_DIALOG(dialog));
if (res == GTK_RESPONSE_ACCEPT) {
file_name = gtk_file_chooser_get_filename(GTK_FILE_CHOOSER(dialog));
gtk_widget_destroy(dialog);
switch (sett->renderer) {
case RENDERER_LATEX_TIKZ:
render_engine =
GDS_RENDER_OUTPUT_RENDERER(latex_renderer_new_with_options(sett->tex_pdf_layers,
sett->tex_standalone));
break;
case RENDERER_CAIROGRAPHICS_SVG:
render_engine = GDS_RENDER_OUTPUT_RENDERER(cairo_renderer_new_svg());
break;
case RENDERER_CAIROGRAPHICS_PDF:
render_engine = GDS_RENDER_OUTPUT_RENDERER(cairo_renderer_new_pdf());
break;
default:
/* Abort rendering */
render_engine = NULL;
break;
}
if (render_engine) {
gds_output_renderer_set_output_file(render_engine, file_name);
gds_output_renderer_set_layer_settings(render_engine, layer_settings);
/* Prevent user from overwriting library or triggering additional conversion */
self->button_state_data.rendering_active = TRUE;
process_button_state_changes(self);
g_signal_connect(render_engine, "async-finished", G_CALLBACK(async_rendering_finished_callback),
self);
activity_bar_set_busy(self->activity_status_bar, "Rendering cell...");
/* TODO: Replace this with asynchronous rendering. However, this fixes issue #19 */
g_signal_connect(render_engine, "progress-changed",
G_CALLBACK(async_rendering_status_update_callback), self);
gds_output_renderer_render_output_async(render_engine, cell_to_render, sett->scale);
//self->button_state_data.rendering_active = FALSE;
//g_object_unref(render_engine);
}
g_free(file_name);
} else {
gtk_widget_destroy(dialog);
}
ret_layer_destroy:
g_object_unref(layer_settings);
}
/**
* @brief cell_tree_view_activated Callback for 'double click' on cell selector element
* @param tree_view The tree view the event occured in
* @param path path to the selected row
* @param column The clicked column
* @param user pointer to GdsRenderGui object
*/
static void cell_tree_view_activated(gpointer tree_view, GtkTreePath *path,
GtkTreeViewColumn *column, gpointer user)
{
(void)tree_view;
(void)path;
(void)column;
on_convert_clicked(NULL, user);
}
/**
* @brief Callback for cell-selection change event
*
* This function activates/deactivates the convert button depending on whether
* a cell is selected for conversion or not
* @param sel
* @param self
*/
static void cell_selection_changed(GtkTreeSelection *sel, GdsRenderGui *self)
{
GtkTreeModel *model = NULL;
GtkTreeIter iter;
if (gtk_tree_selection_get_selected(sel, &model, &iter)) {
/* Node selected. Show button */
self->button_state_data.valid_cell_selected = TRUE;
} else {
self->button_state_data.valid_cell_selected = FALSE;
}
process_button_state_changes(self);
}
static void sort_up_callback(GtkWidget *widget, gpointer user)
{
(void)widget;
GdsRenderGui *self;
self = RENDERER_GUI(user);
if (!self)
return;
layer_selector_force_sort(self->layer_selector, LAYER_SELECTOR_SORT_UP);
}
static void sort_down_callback(GtkWidget *widget, gpointer user)
{
(void)widget;
GdsRenderGui *self;
self = RENDERER_GUI(user);
if (!self)
return;
layer_selector_force_sort(self->layer_selector, LAYER_SELECTOR_SORT_DOWN);
}
static void gds_render_gui_dispose(GObject *gobject)
{
GdsRenderGui *self;
self = RENDERER_GUI(gobject);
clear_lib_list(&self->gds_libraries);
g_clear_object(&self->cell_tree_view);
g_clear_object(&self->convert_button);
g_clear_object(&self->layer_selector);
g_clear_object(&self->cell_tree_store);
g_clear_object(&self->cell_search_entry);
g_clear_object(&self->activity_status_bar);
g_clear_object(&self->palette);
g_clear_object(&self->load_layer_button);
g_clear_object(&self->save_layer_button);
g_clear_object(&self->open_button);
if (self->main_window) {
g_signal_handlers_destroy(self->main_window);
gtk_widget_destroy(GTK_WIDGET(self->main_window));
self->main_window = NULL;
}
/* Chain up */
G_OBJECT_CLASS(gds_render_gui_parent_class)->dispose(gobject);
}
static void gds_render_gui_class_init(GdsRenderGuiClass *klass)
{
GObjectClass *gobject_class = G_OBJECT_CLASS(klass);
gds_render_gui_signals[SIGNAL_WINDOW_CLOSED] =
g_signal_newv("window-closed", RENDERER_TYPE_GUI,
G_SIGNAL_RUN_LAST | G_SIGNAL_NO_RECURSE,
NULL,
NULL,
NULL,
NULL,
G_TYPE_NONE,
0,
NULL);
gobject_class->dispose = gds_render_gui_dispose;
}
GtkWindow *gds_render_gui_get_main_window(GdsRenderGui *gui)
{
return gui->main_window;
}
static void gds_render_gui_init(GdsRenderGui *self)
{
GtkBuilder *main_builder;
GtkWidget *listbox;
GtkHeaderBar *header_bar;
struct tree_stores *cell_selector_stores;
GtkWidget *sort_up_button;
GtkWidget *sort_down_button;
GtkWidget *activity_bar_box;
GtkWidget *auto_color_button;
main_builder = gtk_builder_new_from_resource("/gui/main.glade");
self->cell_tree_view = GTK_TREE_VIEW(gtk_builder_get_object(main_builder, "cell-tree"));
self->cell_search_entry = GTK_WIDGET(gtk_builder_get_object(main_builder, "cell-search"));
cell_selector_stores = setup_cell_selector(self->cell_tree_view, GTK_ENTRY(self->cell_search_entry));
self->cell_tree_store = cell_selector_stores->base_store;
self->main_window = GTK_WINDOW(gtk_builder_get_object(main_builder, "main-window"));
self->open_button = GTK_WIDGET(gtk_builder_get_object(main_builder, "button-load-gds"));
g_signal_connect(self->open_button,
"clicked", G_CALLBACK(on_load_gds), (gpointer)self);
self->convert_button = GTK_WIDGET(gtk_builder_get_object(main_builder, "convert-button"));
g_signal_connect(self->convert_button, "clicked", G_CALLBACK(on_convert_clicked), (gpointer)self);
listbox = GTK_WIDGET(gtk_builder_get_object(main_builder, "layer-list"));
/* Create layer selector */
self->layer_selector = layer_selector_new(GTK_LIST_BOX(listbox));
activity_bar_box = GTK_WIDGET(gtk_builder_get_object(main_builder, "activity-bar"));
/* Callback for selection change of cell selector */
g_signal_connect(G_OBJECT(gtk_tree_view_get_selection(self->cell_tree_view)), "changed",
G_CALLBACK(cell_selection_changed), self);
g_signal_connect(self->cell_tree_view, "row-activated", G_CALLBACK(cell_tree_view_activated), self);
/* Set version in main window subtitle */
header_bar = GTK_HEADER_BAR(gtk_builder_get_object(main_builder, "header-bar"));
gtk_header_bar_set_subtitle(header_bar, _app_version_string);
/* Get layer sorting buttons and set callbacks */
sort_up_button = GTK_WIDGET(gtk_builder_get_object(main_builder, "button-up-sort"));
sort_down_button = GTK_WIDGET(gtk_builder_get_object(main_builder, "button-down-sort"));
g_signal_connect(sort_up_button, "clicked", G_CALLBACK(sort_up_callback), self);
g_signal_connect(sort_down_button, "clicked", G_CALLBACK(sort_down_callback), self);
/* Set buttons for loading and saving */
self->load_layer_button = GTK_WIDGET(gtk_builder_get_object(main_builder, "button-load-mapping"));
self->save_layer_button = GTK_WIDGET(gtk_builder_get_object(main_builder, "button-save-mapping"));
layer_selector_set_load_mapping_button(self->layer_selector, self->load_layer_button, self->main_window);
layer_selector_set_save_mapping_button(self->layer_selector, self->save_layer_button, self->main_window);
/* Connect delete-event */
g_signal_connect(GTK_WIDGET(self->main_window), "delete-event",
G_CALLBACK(on_window_close), self);
/* Create and apply ActivityBar */
self->activity_status_bar = activity_bar_new();
gtk_container_add(GTK_CONTAINER(activity_bar_box), GTK_WIDGET(self->activity_status_bar));
gtk_widget_show(GTK_WIDGET(self->activity_status_bar));
/* Create color palette */
self->palette = color_palette_new_from_resource("/data/color-palette.txt");
auto_color_button = GTK_WIDGET(gtk_builder_get_object(main_builder, "auto-color-button"));
g_signal_connect(auto_color_button, "clicked", G_CALLBACK(on_auto_color_clicked), self);
/* Set default conversion/rendering settings */
self->render_dialog_settings.scale = 1000;
self->render_dialog_settings.renderer = RENDERER_LATEX_TIKZ;
self->render_dialog_settings.tex_pdf_layers = FALSE;
self->render_dialog_settings.tex_standalone = FALSE;
g_object_unref(main_builder);
/* Setup default button sensibility data */
self->button_state_data.rendering_active = FALSE;
self->button_state_data.valid_cell_selected = FALSE;
/* Reference all objects referenced by this object */
g_object_ref(self->activity_status_bar);
g_object_ref(self->main_window);
g_object_ref(self->cell_tree_view);
g_object_ref(self->convert_button);
g_object_ref(self->layer_selector);
g_object_ref(self->cell_tree_store);
g_object_ref(self->cell_search_entry);
g_object_ref(self->palette);
g_object_ref(self->open_button);
g_object_ref(self->load_layer_button);
g_object_ref(self->save_layer_button);
}
GdsRenderGui *gds_render_gui_new()
{
return RENDERER_GUI(g_object_new(RENDERER_TYPE_GUI, NULL));
}
/** @} */