1 /*
2     Copyright (C) 2004, 2011  Dale Mellor
3 
4     This program is free software; you can redistribute it and/or modify
5     it under the terms of the GNU General Public License as published by
6     the Free Software Foundation; either version 3 of the License,  or
7     (at your option) any later version.
8 
9     This program is distributed in the hope that it will be useful,
10     but WITHOUT ANY WARRANTY; without even the implied warranty of
11     MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
12     GNU General Public License for more details.
13 
14     You should have received a copy of the GNU General Public License
15     along with this program.  If not, see <http://www.gnu.org/licenses/>.
16 */
17 
18 #include <config.h>
19 
20 #include "guile-hooks.h"
21 
22 #include <stdlib.h>
23 #include <dirent.h>
24 #include <stdio.h>
25 
26 #include "cube.h"
27 #include "menus.h"
28 
29 #include <libguile.h>
30 
31 #if SCM_MAJOR_VERSION < 2
32 #define scm_to_utf8_string(name) scm_to_locale_string(name)
33 #endif
34 
35 /* This function is called from the menu when the user makes a selection. The
36    data is a string which was registered with the menu system and gives the name
37    of a scheme procedure to execute. */
38 
39 static void
run_scheme(GtkAction * act,SCM exp)40 run_scheme (GtkAction * act, SCM exp)
41 {
42   scm_eval (exp, scm_interaction_environment ());
43 }
44 
45 /* The menu manager */
46 static GtkUIManager *uim;
47 
48 static SCM
gnubik_create_menu(SCM name,SCM loc)49 gnubik_create_menu (SCM name, SCM loc)
50 {
51   char *ml = scm_to_utf8_string (name);
52   char *loc_str = NULL;
53 
54   GtkActionGroup *ag = gtk_action_group_new (ml);
55 
56   GtkActionEntry gae;
57 
58   if (SCM_UNBNDP (loc))
59     loc_str = g_strdup ("/ui/MainMenu/scripts-menu");
60   else
61     loc_str = scm_to_locale_string (loc);
62 
63   gae.name = ml;
64   gae.stock_id = NULL;
65   gae.label = ml;
66   gae.accelerator = NULL;
67   gae.tooltip = NULL;
68   gae.callback = NULL;
69 
70   gtk_action_group_add_actions (ag, &gae, 1, NULL);
71 
72   gtk_ui_manager_insert_action_group (uim, ag, 0);
73 
74   gtk_ui_manager_add_ui (uim,
75 			 gtk_ui_manager_new_merge_id (uim),
76 			 loc_str, ml, ml, GTK_UI_MANAGER_MENU, TRUE);
77 
78   char *menuloc = g_strdup_printf ("%s/%s", loc_str, ml);
79 
80   SCM sml = scm_from_locale_string (menuloc);
81 
82   free (ml);
83   free (menuloc);
84   free (loc_str);
85 
86   return sml;
87 }
88 
89 /*
90   Function callable from scheme (as gnubik-register-script) which allows a
91   script to specify a menu entry and the name of a procedure to call when that
92   menu entry is selected. Note that /Scripts/ is always appended,  so all
93   scripts are forced under the Scripts main menu item.
94 */
95 static SCM
gnubik_register_script(SCM menu_location,SCM callback,SCM loc)96 gnubik_register_script (SCM menu_location, SCM callback, SCM loc)
97 {
98   char *ml = scm_to_utf8_string (menu_location);
99 
100   char *loc_str = scm_to_locale_string (loc);
101 
102   GtkActionGroup *ag = gtk_action_group_new (ml);
103 
104   GtkActionEntry gae;
105   gae.name = ml;
106   gae.stock_id = NULL;
107   gae.label = ml;
108   gae.accelerator = NULL;
109   gae.tooltip = NULL;
110   gae.callback = G_CALLBACK (run_scheme);
111 
112   scm_permanent_object (callback);
113 
114   gtk_action_group_add_actions (ag, &gae, 1, callback);
115 
116   gtk_ui_manager_insert_action_group (uim, ag, 0);
117 
118   gtk_ui_manager_add_ui (uim, gtk_ui_manager_new_merge_id (uim),
119 			 loc_str, ml, ml, GTK_UI_MANAGER_MENUITEM, TRUE);
120 
121   free (loc_str);
122   free (ml);
123 
124   return SCM_UNSPECIFIED;
125 }
126 
127 
128 
129 
130 extern GbkGame *the_game;
131 
132 /* Function callable from scheme as gnubik-cube-state which returns a structure
133    reflecting the current state of the cube. */
134 
135 static SCM
gnubik_cube_state()136 gnubik_cube_state ()
137 {
138   return make_scm_cube (the_game->cube);
139 }
140 
141 
142 
143 /* Function which,  when called from scheme as gnubik-append-moves,
144    appends moves to the queue and then runs the queue. */
145 static SCM
gnubik_append_moves(SCM list)146 gnubik_append_moves (SCM list)
147 {
148   if (!gbk_game_at_end (the_game))
149     gbk_game_delete_moves (the_game, the_game->iter->next);
150 
151   for (; !scm_is_null (list); list = SCM_CDR (list))
152     {
153       struct move_data *move = move_create (scm_to_int (SCM_CADAR (list)),
154 					    scm_to_int (SCM_CAAR (list)),
155 					    scm_to_int (SCM_CADDAR (list)));
156 
157       gbk_game_insert_move (the_game, move, &the_game->end_of_moves);
158 
159       move_unref (move);
160     }
161 
162   gbk_game_replay (the_game);
163 
164   return SCM_UNSPECIFIED;
165 }
166 
167 
168 
169 /* Function to allow a guile script to display a message to the user. */
170 
171 static SCM
gnubik_error_dialog(SCM message)172 gnubik_error_dialog (SCM message)
173 {
174   char *msg = scm_to_utf8_string (message);
175   error_dialog (the_game->toplevel, msg);
176   free (msg);
177 
178   return SCM_UNSPECIFIED;
179 }
180 
181 
182 
183 
184 /* Function to scan the named directory for all files with a .scm extension,  and
185    execute the contents of each file. */
186 static void
read_script_directory(const char * dir_name)187 read_script_directory (const char *dir_name)
188 {
189   static char buffer[1024];
190 
191   DIR *directory = opendir (dir_name);
192 
193   if (directory)
194     {
195       struct dirent *entry;
196 
197       for (entry = readdir (directory); entry; entry = readdir (directory))
198 
199 	if (strcmp (".scm", entry->d_name + strlen (entry->d_name) - 4) == 0)
200 	  {
201 	    snprintf (buffer, 1024, "%s/%s", dir_name, entry->d_name);
202 
203 	    scm_primitive_load (scm_from_locale_string (buffer));
204 	  }
205 
206     closedir (directory);
207   }
208 }
209 
210 
211 
212 
213 /* This function initializes the scheme world for us,  and once the scripts have
214    all been run,  it returns the requested menu structure to the caller. Before
215    running the scripts,  however,  it first makes sure all the pertinent C
216    functions are registered in the guile world. */
217 
218 void
startup_guile_scripts(GtkUIManager * ui_manager)219 startup_guile_scripts (GtkUIManager * ui_manager)
220 {
221   /* Register C functions that the scheme world can access. */
222 
223   scm_c_define_gsubr ("gnubik-create-menu", 1, 1, 0, gnubik_create_menu);
224 
225   scm_c_define_gsubr ("gnubik-register-script",
226 		      3, 0, 0, gnubik_register_script);
227 
228   scm_c_define_gsubr ("gnubik-cube-state", 0, 0, 0, gnubik_cube_state);
229 
230   scm_c_define_gsubr ("gnubik-append-moves", 1, 0, 0, gnubik_append_moves);
231 
232   scm_c_define_gsubr ("gnubik-error-dialog", 1, 0, 0, gnubik_error_dialog);
233 
234   uim = ui_manager;
235 
236   /* Run all the initialization files in .../share/gnubik/guile, and the
237      system scripts in .../share/gnubik/scripts. */
238 
239   read_script_directory (GUILEDIR);
240   read_script_directory (SCRIPTDIR);
241 
242   {
243     gchar *cfd = g_strdup_printf ("%s/gnubik", g_get_user_config_dir ());
244 
245     read_script_directory (cfd);
246 
247     g_free (cfd);
248   }
249 }
250