1 /* browse.c: tape browser dialog box
2    Copyright (c) 2002-2004 Philip Kendall
3    Copyright (c) 2015 Sergio Baldoví
4    Copyright (c) 2015 Stuart Brady
5 
6    This program is free software; you can redistribute it and/or modify
7    it under the terms of the GNU General Public License as published by
8    the Free Software Foundation; either version 2 of the License, or
9    (at your option) any later version.
10 
11    This program is distributed in the hope that it will be useful,
12    but WITHOUT ANY WARRANTY; without even the implied warranty of
13    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
14    GNU General Public License for more details.
15 
16    You should have received a copy of the GNU General Public License along
17    with this program; if not, write to the Free Software Foundation, Inc.,
18    51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
19 
20    Author contact information:
21 
22    E-mail: philip-fuse@shadowmagic.org.uk
23 
24 */
25 
26 #include <config.h>
27 
28 #include <stdlib.h>
29 #include <string.h>
30 
31 #include <gdk/gdkkeysyms.h>
32 #include <gtk/gtk.h>
33 
34 #include "compat.h"
35 #include "fuse.h"
36 #include "gtkinternals.h"
37 #include "menu.h"
38 #include "tape.h"
39 #include "ui/ui.h"
40 
41 static int create_dialog( void );
42 static void add_block_details( libspectrum_tape_block *block,
43 			       void *user_data );
44 static void select_row( GtkTreeView *treeview, GtkTreePath *path,
45                         GtkTreeViewColumn *col, gpointer user_data );
46 void mark_row( GtkTreeModel *model, int row );
47 static void browse_done( GtkWidget *widget, gpointer data );
48 static gboolean delete_dialog( GtkWidget *widget, GdkEvent *event,
49 			       gpointer user_data );
50 
51 static GdkPixbuf *tape_marker_pixbuf;
52 
53 static GtkWidget
54   *dialog,			/* The dialog box itself */
55   *blocks,			/* The list of blocks */
56   *modified_label;		/* The label saying if the tape has been
57 				   modified */
58 
59 static int dialog_created;	/* Have we created the dialog box yet? */
60 
61 /* List columns */
62 enum
63 {
64   COL_PIX = 0,       /* Pixmap */
65   COL_BLOCK,         /* Block type */
66   COL_DATA,          /* Data detail */
67   NUM_COLS
68 };
69 
70 void
menu_media_tape_browse(GtkAction * gtk_action GCC_UNUSED,gpointer data GCC_UNUSED)71 menu_media_tape_browse( GtkAction *gtk_action GCC_UNUSED,
72                         gpointer data GCC_UNUSED )
73 {
74   /* Firstly, stop emulation */
75   fuse_emulation_pause();
76 
77   if( !dialog_created )
78     if( create_dialog() ) { fuse_emulation_unpause(); return; }
79 
80   if( ui_tape_browser_update( UI_TAPE_BROWSER_NEW_TAPE, NULL ) ) {
81     fuse_emulation_unpause();
82     return;
83   }
84 
85   gtk_widget_show_all( dialog );
86 
87   /* Carry on with emulation */
88   fuse_emulation_unpause();
89 }
90 
91 static GtkWidget *
create_block_list(void)92 create_block_list( void )
93 {
94   GtkWidget *view;
95   GtkCellRenderer *renderer;
96   GtkTreeModel *model;
97   GtkListStore *store;
98 
99   view = gtk_tree_view_new();
100 
101   /* Add columns */
102   renderer = gtk_cell_renderer_pixbuf_new();
103   gtk_tree_view_insert_column_with_attributes( GTK_TREE_VIEW( view ),
104                                                -1,
105                                                NULL,
106                                                renderer,
107                                                "pixbuf", COL_PIX,
108                                                NULL );
109 
110   renderer = gtk_cell_renderer_text_new();
111   gtk_tree_view_insert_column_with_attributes( GTK_TREE_VIEW( view ),
112                                                -1,
113                                                "Block type",
114                                                renderer,
115                                                "text", COL_BLOCK,
116                                                NULL );
117 
118   renderer = gtk_cell_renderer_text_new();
119   gtk_tree_view_insert_column_with_attributes( GTK_TREE_VIEW( view ),
120                                                -1,
121                                                "Data",
122                                                renderer,
123                                                "text", COL_DATA,
124                                                NULL );
125 
126   /* Create data model */
127   store = gtk_list_store_new( NUM_COLS, GDK_TYPE_PIXBUF, G_TYPE_STRING,
128                               G_TYPE_STRING );
129 
130   model = GTK_TREE_MODEL( store );
131   gtk_tree_view_set_model( GTK_TREE_VIEW( view ), model );
132   g_object_unref( model );
133 
134   /* Fast move tape */
135   g_signal_connect( G_OBJECT( view ), "row-activated",
136                     G_CALLBACK( select_row ), model );
137 
138   return view;
139 }
140 
141 static int
create_dialog(void)142 create_dialog( void )
143 {
144   GtkWidget *scrolled_window, *content_area;
145 
146   /* Give me a new dialog box */
147   dialog = gtkstock_dialog_new( "Fuse - Browse Tape",
148 				G_CALLBACK( delete_dialog ) );
149   content_area = gtk_dialog_get_content_area( GTK_DIALOG( dialog ) );
150 
151   /* And a scrolled window to pack the list into */
152   scrolled_window = gtk_scrolled_window_new( NULL, NULL );
153   gtk_scrolled_window_set_policy( GTK_SCROLLED_WINDOW( scrolled_window ),
154 				  GTK_POLICY_NEVER, GTK_POLICY_AUTOMATIC );
155   gtk_box_pack_start( GTK_BOX( content_area ), scrolled_window, TRUE, TRUE, 0 );
156 
157   /* The tape marker pixbuf */
158   tape_marker_pixbuf = gdk_pixbuf_new_from_xpm_data( gtkpixmap_tape_marker );
159   /* FIXME: unref this at exit */
160 
161   /* And the list itself */
162   blocks = create_block_list();
163   gtk_container_add( GTK_CONTAINER( scrolled_window ), GTK_WIDGET( blocks ) );
164 
165   /* And the "tape modified" label */
166   modified_label = gtk_label_new( "" );
167   gtk_box_pack_start( GTK_BOX( content_area ), modified_label, FALSE, FALSE, 0 );
168 
169   /* Create the OK button */
170   gtkstock_create_close( dialog, NULL, G_CALLBACK( browse_done ), FALSE );
171 
172   /* Make the window big enough to show at least some data */
173   gtk_window_set_default_size( GTK_WINDOW( dialog ), -1, 250 );
174 
175   dialog_created = 1;
176 
177   return 0;
178 }
179 
180 int
ui_tape_browser_update(ui_tape_browser_update_type change GCC_UNUSED,libspectrum_tape_block * block GCC_UNUSED)181 ui_tape_browser_update( ui_tape_browser_update_type change GCC_UNUSED,
182                         libspectrum_tape_block *block GCC_UNUSED )
183 {
184   int error, current_block;
185   GtkTreeModel *model;
186 
187   if( !dialog_created ) return 0;
188 
189   fuse_emulation_pause();
190 
191   model = gtk_tree_view_get_model( GTK_TREE_VIEW( blocks ) );
192   gtk_list_store_clear( GTK_LIST_STORE( model ) );
193 
194   error = tape_foreach( add_block_details, model );
195   if( error ) {
196     fuse_emulation_unpause();
197     return 1;
198   }
199 
200   current_block = tape_get_current_block();
201 
202   if( current_block != -1 )
203     mark_row( model, current_block );
204 
205   if( tape_modified ) {
206     gtk_label_set_text( GTK_LABEL( modified_label ), "Tape modified" );
207   } else {
208     gtk_label_set_text( GTK_LABEL( modified_label ), "Tape not modified" );
209   }
210 
211   fuse_emulation_unpause();
212 
213   return 0;
214 }
215 
216 static void
add_block_details(libspectrum_tape_block * block,void * user_data)217 add_block_details( libspectrum_tape_block *block, void *user_data )
218 {
219   gchar block_type[80];
220   gchar data_detail[80];
221   GtkTreeIter iter;
222   GtkTreeModel *model = user_data;
223 
224   libspectrum_tape_block_description( block_type, 80, block );
225   tape_block_details( data_detail, 80, block );
226 
227   /* Append a new row and fill data */
228   gtk_list_store_append( GTK_LIST_STORE( model ), &iter );
229   gtk_list_store_set( GTK_LIST_STORE( model ), &iter,
230                       COL_PIX, NULL,
231                       COL_BLOCK, block_type,
232                       COL_DATA, data_detail,
233                       -1 );
234 }
235 
236 /* Called when a row is selected */
237 static void
select_row(GtkTreeView * treeview GCC_UNUSED,GtkTreePath * path,GtkTreeViewColumn * col GCC_UNUSED,gpointer user_data)238 select_row( GtkTreeView *treeview GCC_UNUSED, GtkTreePath *path,
239             GtkTreeViewColumn *col GCC_UNUSED, gpointer user_data )
240 {
241   int current_block;
242   int *indices;
243   int row;
244   GtkTreeIter iter;
245   GtkTreeModel *model = user_data;
246 
247   /* Get selected row */
248   row = -1;
249   indices = gtk_tree_path_get_indices( path );
250   if( indices ) row = indices[0];
251 
252   /* Don't do anything if the current block was clicked on */
253   current_block = tape_get_current_block();
254   if( row == current_block ) return;
255 
256   /* Otherwise, select the new block */
257   tape_select_block_no_update( row );
258 
259   /* Mark selected block */
260   if( current_block != -1 ) {
261     gtk_tree_model_get_iter( GTK_TREE_MODEL( model ), &iter, path );
262 
263     gtk_list_store_set( GTK_LIST_STORE( model ), &iter,
264                         COL_PIX, tape_marker_pixbuf,
265                         -1 );
266   }
267 
268   /* Unmark former block */
269   if( current_block != -1 ) {
270     path = gtk_tree_path_new_from_indices( current_block, -1 );
271 
272     if( !gtk_tree_model_get_iter( GTK_TREE_MODEL( model ), &iter, path ) ) {
273       gtk_tree_path_free( path );
274       return;
275     }
276 
277     gtk_tree_path_free( path );
278 
279     gtk_list_store_set( GTK_LIST_STORE( model ), &iter,
280                         COL_PIX, NULL,
281                         -1 );
282   }
283 }
284 
285 void
mark_row(GtkTreeModel * model,int row)286 mark_row( GtkTreeModel *model, int row )
287 {
288   GtkTreeIter iter;
289   GtkTreePath *path;
290 
291   path = gtk_tree_path_new_from_indices( row, -1 );
292 
293   if( !gtk_tree_model_get_iter( model, &iter, path ) ) {
294     gtk_tree_path_free( path );
295     return;
296   }
297 
298   gtk_tree_path_free( path );
299 
300   gtk_list_store_set( GTK_LIST_STORE( model ), &iter,
301                       COL_PIX, tape_marker_pixbuf,
302                       -1 );
303 }
304 
305 /* Called if the OK button is clicked */
306 static void
browse_done(GtkWidget * widget GCC_UNUSED,gpointer data GCC_UNUSED)307 browse_done( GtkWidget *widget GCC_UNUSED, gpointer data GCC_UNUSED )
308 {
309   dialog_created = 0;
310   gtk_widget_destroy( dialog );
311 }
312 
313 /* Catch attempts to delete the window and just hide it instead */
314 static gboolean
delete_dialog(GtkWidget * widget,GdkEvent * event GCC_UNUSED,gpointer user_data GCC_UNUSED)315 delete_dialog( GtkWidget *widget, GdkEvent *event GCC_UNUSED,
316 	       gpointer user_data GCC_UNUSED )
317 {
318   dialog_created = 0;
319   gtk_widget_destroy( dialog );
320   return TRUE;
321 }
322