Update Turkish translation
[dasher.git] / Src / Gtk2 / dasher_editor.cpp
blob8b99384bde1166a9eba5162cbc1908a5965ca48f
1 #ifdef HAVE_CONFIG_H
2 #include <config.h>
3 #endif
5 #include <cstring>
6 #include <glib/gi18n.h>
7 #ifdef HAVE_GIO
8 #include <gio/gio.h>
9 #endif
10 #include <gtk/gtk.h>
12 #include "dasher_editor.h"
13 #include "dasher_editor_private.h"
14 #include "dasher_editor_external.h"
15 #include "dasher_lock_dialogue.h"
16 #include "dasher_main.h"
17 #include "GtkDasherControl.h"
18 #include "../DasherCore/ControlManager.h"
20 #if GTK_CHECK_VERSION (3,0,0)
21 G_DEFINE_TYPE(DasherEditor, dasher_editor, GTK_TYPE_BOX);
22 #else
23 G_DEFINE_TYPE(DasherEditor, dasher_editor, GTK_TYPE_VBOX);
24 #endif
26 /* Signals */
27 enum {
28 FILENAME_CHANGED,
29 BUFFER_CHANGED,
30 CONTEXT_CHANGED,
31 SIGNAL_NUM
34 static guint dasher_editor_signals[SIGNAL_NUM];
36 static void dasher_editor_finalize(GObject *pObject);
38 static void dasher_editor_internal_handle_font(DasherEditor *pSelf, const gchar *szFont);
40 void dasher_editor_initialise(DasherEditor *pSelf, DasherAppSettings *pAppSettings, GtkDasherControl *pDasherCtrl, GtkBuilder *pXML, const gchar *szFullPath);
42 /* Private methods */
43 static void dasher_editor_internal_select_all(DasherEditor *pSelf);
45 static void dasher_editor_internal_command_new(DasherEditor *pSelf);
46 static void dasher_editor_internal_command_open(DasherEditor *pSelf);
47 static void dasher_editor_internal_command_save(DasherEditor *pSelf, gboolean bPrompt, gboolean bAppend);
48 static void edit_find(bool bForwards, Dasher::CControlManager::EditDistance iDist, DasherEditorPrivate *pPrivate, GtkTextIter *pPos);
50 #ifdef HAVE_GIO
51 static void dasher_editor_internal_gvfs_print_error(DasherEditor *pSelf, GError *error, const char *myfilename);
52 static GFileOutputStream *append_or_replace_file(GFile *file, bool append, GError **error);
53 static gboolean dasher_editor_internal_gvfs_open_file(DasherEditor *pSelf, const char *filename, gchar ** buffer, gsize *size);
54 static gboolean dasher_editor_internal_gvfs_save_file(DasherEditor *pSelf, const char *filename, gchar * buffer, gsize length, bool append);
55 #else
56 static gboolean dasher_editor_internal_unix_vfs_open_file(DasherEditor *pSelf, const char *filename, gchar ** buffer, gsize *size);
57 static gboolean dasher_editor_internal_unix_vfs_save_file(DasherEditor *pSelf, const char *filename, gchar * buffer, gsize length, bool append);
58 #endif
60 static void dasher_editor_internal_set_filename(DasherEditor *pSelf, const gchar *szFilename);
62 static void dasher_editor_internal_new_buffer(DasherEditor *pSelf, const gchar *szFilename);
64 static void dasher_editor_internal_generate_filename(DasherEditor *pSelf);
65 static void dasher_editor_internal_open(DasherEditor *pSelf, const gchar *szFilename);
66 static bool dasher_editor_internal_save_as(DasherEditor *pSelf, const gchar *szFilename, bool bAppend);
67 static void dasher_editor_internal_create_buffer(DasherEditor *pSelf);
68 static void dasher_editor_internal_clipboard(DasherEditor *pSelf, clipboard_action act);
70 /* To be obsoleted by movement to GTK buffers */
71 void dasher_editor_internal_mark_changed(DasherEditor *pSelf, GtkTextIter *pIter, GtkTextMark *pMark);
73 /* Todo: possibly tidy up the need to have this public (quit in dasher_main possibly too connected) */
74 gboolean dasher_editor_internal_file_changed(DasherEditor *pSelf);
77 // Private methods not in class
78 extern "C" void delete_children_callback(GtkWidget *pWidget, gpointer pUserData);
79 extern "C" void main_window_realized(DasherMain *pMain, gpointer pUserData);
80 extern "C" void action_button_callback(GtkWidget *pWidget, gpointer pUserData);
81 extern "C" void mark_set_handler(GtkWidget *widget, GtkTextIter *pIter, GtkTextMark *pMark, gpointer pUserData);
82 extern "C" void handle_stop_event(GtkDasherControl *pDasherControl, gpointer data);
83 extern "C" void handle_request_settings(GtkDasherControl * pDasherControl, gpointer data);
84 extern "C" void gtk2_edit_delete_callback(GtkDasherControl *pDasherControl, const gchar *szText, int iOffset, gpointer user_data);
85 extern "C" void gtk2_edit_output_callback(GtkDasherControl *pDasherControl, const gchar *szText, int iOffset, gpointer user_data);
87 static gboolean
88 isdirect(DasherAppSettings *pAppSettings) {
89 return pAppSettings->GetLong(APP_LP_STYLE) == APP_STYLE_DIRECT;
92 static void
93 dasher_editor_class_init(DasherEditorClass *pClass) {
94 g_type_class_add_private(pClass, sizeof(DasherEditorPrivate));
96 GObjectClass *pObjectClass = (GObjectClass *) pClass;
97 pObjectClass->finalize = dasher_editor_finalize;
99 /* Setup signals */
100 dasher_editor_signals[FILENAME_CHANGED] = g_signal_new("filename-changed", G_TYPE_FROM_CLASS(pClass),
101 static_cast < GSignalFlags > (G_SIGNAL_RUN_FIRST | G_SIGNAL_ACTION),
102 G_STRUCT_OFFSET(DasherEditorClass, filename_changed),
103 NULL, NULL, g_cclosure_marshal_VOID__VOID,
104 G_TYPE_NONE, 0);
106 dasher_editor_signals[BUFFER_CHANGED] = g_signal_new("buffer-changed", G_TYPE_FROM_CLASS(pClass),
107 static_cast < GSignalFlags > (G_SIGNAL_RUN_FIRST | G_SIGNAL_ACTION),
108 G_STRUCT_OFFSET(DasherEditorClass, buffer_changed),
109 NULL, NULL, g_cclosure_marshal_VOID__VOID,
110 G_TYPE_NONE, 0);
112 dasher_editor_signals[CONTEXT_CHANGED] = g_signal_new("context-changed", G_TYPE_FROM_CLASS(pClass),
113 static_cast < GSignalFlags > (G_SIGNAL_RUN_FIRST | G_SIGNAL_ACTION),
114 G_STRUCT_OFFSET(DasherEditorClass, context_changed),
115 NULL, NULL, g_cclosure_marshal_VOID__VOID,
116 G_TYPE_NONE, 0);
119 pClass->initialise = NULL;
120 pClass->command = NULL;
121 pClass->clear = NULL;
122 pClass->get_all_text = NULL;
123 pClass->get_new_text = NULL;
124 pClass->output = NULL;
125 pClass->delete_text = NULL;
126 pClass->get_context = NULL;
127 pClass->get_offset = NULL;
128 pClass->ctrl_move = NULL;
129 pClass->ctrl_delete = NULL;
130 pClass->edit_convert = NULL;
131 pClass->edit_protect = NULL;
132 pClass->handle_parameter_change = NULL;
133 pClass->handle_stop = NULL;
134 pClass->handle_start = NULL;
135 pClass->grab_focus = NULL;
136 pClass->file_changed = NULL;
137 pClass->get_filename = NULL;
139 pClass->filename_changed = NULL;
140 pClass->buffer_changed = NULL;
141 pClass->context_changed = NULL;
144 pParentClass->output = dasher_editor_output;
145 pParentClass->delete_text = dasher_editor_delete;
146 pParentClass->get_context = dasher_editor_get_context;
147 pParentClass->get_offset = dasher_editor_get_offset;
149 pParentClass->file_changed = dasher_editor_internal_file_changed;
153 static void
154 dasher_editor_init(DasherEditor *pSelf) {
155 DasherEditorPrivate *pPrivate = DASHER_EDITOR_GET_PRIVATE(pSelf);
157 pPrivate->pTextView = GTK_TEXT_VIEW(gtk_text_view_new());
158 gtk_text_view_set_wrap_mode(pPrivate->pTextView, GTK_WRAP_WORD);
159 pPrivate->pBuffer = gtk_text_view_get_buffer(pPrivate->pTextView);
160 pPrivate->pTextClipboard = gtk_clipboard_get(GDK_SELECTION_CLIPBOARD);
161 pPrivate->pPrimarySelection = gtk_clipboard_get(GDK_SELECTION_PRIMARY);
162 GtkTextIter oStartIter;
163 gtk_text_buffer_get_start_iter(pPrivate->pBuffer, &oStartIter);
164 pPrivate->pNewMark =
165 gtk_text_buffer_create_mark(pPrivate->pBuffer, NULL, &oStartIter, TRUE);
166 pPrivate->szFilename = NULL;
167 pPrivate->bFileModified = FALSE;
168 pPrivate->bInControlAction = FALSE;
170 GtkWidget *pScrolledWindow = gtk_scrolled_window_new(NULL, NULL);
172 gtk_container_add(GTK_CONTAINER(pScrolledWindow),
173 GTK_WIDGET(pPrivate->pTextView));
175 gtk_scrolled_window_set_policy(GTK_SCROLLED_WINDOW(pScrolledWindow),
176 GTK_POLICY_AUTOMATIC,
177 GTK_POLICY_AUTOMATIC);
179 gtk_scrolled_window_set_shadow_type(GTK_SCROLLED_WINDOW(pScrolledWindow),
180 GTK_SHADOW_IN);
182 gtk_box_pack_start(GTK_BOX(&(pSelf->box)),
183 pScrolledWindow, true, true, 0);
185 gtk_widget_show_all(GTK_WIDGET(&(pSelf->box)));
188 static void
189 dasher_editor_finalize(GObject *pObject) {
190 DasherEditorPrivate *pPrivate = DASHER_EDITOR_GET_PRIVATE(pObject);
192 dasher_editor_external_finalize(pObject);
194 if(pPrivate->szFilename)
195 g_free(pPrivate->szFilename);
198 /* Public methods */
199 DasherEditor*
200 dasher_editor_new(void)
202 #if GTK_CHECK_VERSION (3,0,0)
203 return
204 DASHER_EDITOR(g_object_new(DASHER_TYPE_EDITOR, "orientation", GTK_ORIENTATION_VERTICAL, NULL));
205 #else
206 return
207 DASHER_EDITOR(g_object_new(DASHER_TYPE_EDITOR, NULL));
208 #endif
211 void
212 dasher_editor_initialise(DasherEditor *pSelf, DasherAppSettings *pAppSettings, GtkDasherControl *pDasherCtrl, GtkBuilder *pXML, const gchar *szFullPath) {
213 DasherEditorPrivate *pPrivate = DASHER_EDITOR_GET_PRIVATE(pSelf);
215 pPrivate->pAppSettings = pAppSettings;
216 pPrivate->pDasherCtrl = pDasherCtrl;
218 dasher_editor_internal_handle_font(pSelf,
219 pPrivate->pAppSettings->GetString(APP_SP_EDIT_FONT).c_str());
221 dasher_editor_external_create_buffer(pSelf);
222 // TODO: is this still needed?
223 dasher_editor_internal_create_buffer(pSelf);
225 // TODO: see note in command_new method
226 if(szFullPath)
227 dasher_editor_internal_open(pSelf, szFullPath);
228 else {
229 dasher_editor_internal_generate_filename(pSelf);
230 dasher_editor_clear(pSelf);
234 static void
235 dasher_editor_internal_clipboard(DasherEditor *pSelf, clipboard_action act) {
236 DasherEditorPrivate *pPrivate = DASHER_EDITOR_GET_PRIVATE(pSelf);
238 GtkTextIter *start = new GtkTextIter;
239 GtkTextIter *end = new GtkTextIter;
241 gtk_text_buffer_get_iter_at_offset(GTK_TEXT_BUFFER(pPrivate->pBuffer), start, 0);
242 gtk_text_buffer_get_iter_at_offset(GTK_TEXT_BUFFER(pPrivate->pBuffer), end, -1);
244 gchar *the_text = gtk_text_buffer_get_text(pPrivate->pBuffer, start, end, TRUE);
246 switch (act) {
247 case CLIPBOARD_CUT:
248 gtk_text_buffer_cut_clipboard(pPrivate->pBuffer, pPrivate->pTextClipboard, TRUE);
249 break;
250 case CLIPBOARD_COPY:
251 gtk_text_buffer_copy_clipboard(pPrivate->pBuffer, pPrivate->pTextClipboard);
252 break;
253 case CLIPBOARD_PASTE:
254 gtk_text_buffer_paste_clipboard(pPrivate->pBuffer, pPrivate->pTextClipboard, NULL, TRUE);
255 break;
256 case CLIPBOARD_COPYALL:
257 gtk_clipboard_set_text(pPrivate->pTextClipboard, the_text, strlen(the_text));
258 gtk_clipboard_set_text(pPrivate->pPrimarySelection, the_text, strlen(the_text));
260 break;
261 case CLIPBOARD_SELECTALL:
262 dasher_editor_internal_select_all(pSelf);
263 break;
264 case CLIPBOARD_CLEAR:
265 gtk_text_buffer_set_text(pPrivate->pBuffer, "", 0);
266 break;
268 g_free(the_text);
270 delete start;
271 delete end;
274 void
275 dasher_editor_handle_stop(DasherEditor *pSelf) {
278 void
279 dasher_editor_handle_start(DasherEditor *pSelf) {
280 // The edit box keeps track of where we started
282 // TODO: This should be filtered through the buffer, rather than directly to the edit box
283 // set_mark();
286 void
287 dasher_editor_toggle_direct_mode(DasherEditor *pSelf) {
288 // Should be able to play function pointer games here to avoid
289 // isdirect() calls in dasher_editor_output() and friends.
291 DasherEditorPrivate *pPrivate = DASHER_EDITOR_GET_PRIVATE(pSelf);
293 dasher_editor_external_toggle_direct_mode(pSelf, isdirect(pPrivate->pAppSettings));
296 void
297 dasher_editor_clear(DasherEditor *pSelf) {
298 DasherEditorPrivate *pPrivate = DASHER_EDITOR_GET_PRIVATE(pSelf);
300 GtkTextIter start, end;
302 gtk_text_buffer_get_iter_at_offset(pPrivate->pBuffer, &start, 0);
303 gtk_text_buffer_get_iter_at_offset(pPrivate->pBuffer, &end, -1);
305 gtk_text_buffer_delete(pPrivate->pBuffer, &start, &end);
307 /* TODO: this probably shouldn't emit a signal */
308 //ACL but since it did...internal_buffer emitted "buffer_changed" signal,
309 // which was picked up by callback registered by editor_internal, which
310 // then emitted a "buffer_changed" signal from the editor_internal. So
311 // emit directly from the editor_internal...
312 g_signal_emit_by_name(G_OBJECT(pSelf), "buffer_changed", G_OBJECT(pSelf), NULL, NULL);
314 pPrivate->iCurrentState = 0;
315 pPrivate->iLastOffset = 0;
318 void
319 dasher_editor_grab_focus(DasherEditor *pSelf) {
320 DasherEditorPrivate *pPrivate = DASHER_EDITOR_GET_PRIVATE(pSelf);
322 if(pPrivate->pTextView)
323 gtk_widget_grab_focus(GTK_WIDGET(pPrivate->pTextView));
327 static void
328 dasher_editor_internal_create_buffer(DasherEditor *pSelf) {
329 DasherEditorPrivate *pPrivate = DASHER_EDITOR_GET_PRIVATE(pSelf);
332 pPrivate->pOutputTag = gtk_text_buffer_create_tag(pPrivate->pBuffer, NULL, NULL);
334 pPrivate->pHiddenTag = gtk_text_buffer_create_tag(pPrivate->pBuffer, NULL, "invisible", TRUE, NULL);
336 pPrivate->pVisibleTag = gtk_text_buffer_create_tag(pPrivate->pBuffer, NULL, "foreground", "red", NULL);
338 pPrivate->bConversionMode = FALSE;
339 pPrivate->iLastOffset = 1;
340 pPrivate->iCurrentState = 0;
342 g_signal_connect(G_OBJECT(pPrivate->pBuffer), "mark-set", G_CALLBACK(mark_set_handler), pSelf);
345 // void
346 // dasher_editor_output(DasherEditor *pSelf, const gchar *szText, int iOffset)
347 // if(DASHER_EDITOR_GET_CLASS(pSelf)->output)
348 // DASHER_EDITOR_GET_CLASS(pSelf)->output(pSelf, szText, iOffset);
349 // }
350 void
351 dasher_editor_output(DasherEditor *pSelf, const gchar *szText, int iOffset) {
352 DasherEditorPrivate *pPrivate = DASHER_EDITOR_GET_PRIVATE(pSelf);
354 if (isdirect(pPrivate->pAppSettings))
355 return dasher_editor_external_output(pSelf, szText, iOffset);
357 gtk_text_buffer_delete_selection(pPrivate->pBuffer, false, true );
359 GtkTextIter sIter;
360 gtk_text_buffer_get_iter_at_mark(pPrivate->pBuffer, &sIter, gtk_text_buffer_get_insert(pPrivate->pBuffer));
362 GtkTextTag *pCurrentTag = NULL;
364 if(!pPrivate->bConversionMode)
365 pCurrentTag = pPrivate->pOutputTag;
366 else {
367 switch(pPrivate->iCurrentState) {
368 case 0:
369 pCurrentTag = pPrivate->pVisibleTag;
370 break;
371 case 1:
372 pCurrentTag = pPrivate->pOutputTag;
373 break;
377 if(!pCurrentTag)
378 return;
380 gtk_text_buffer_insert_with_tags(pPrivate->pBuffer, &sIter, szText, -1, pCurrentTag, NULL);
382 gtk_text_view_scroll_mark_onscreen(pPrivate->pTextView, gtk_text_buffer_get_insert(pPrivate->pBuffer));
384 pPrivate->bFileModified = TRUE;
387 // void
388 // dasher_editor_delete(DasherEditor *pSelf, int iLength, int iOffset) {
389 // if(DASHER_EDITOR_GET_CLASS(pSelf)->delete_text)
390 // DASHER_EDITOR_GET_CLASS(pSelf)->delete_text(pSelf, iLength, iOffset);
391 // }
392 void
393 dasher_editor_delete(DasherEditor *pSelf, int iLength, int iOffset) {
394 DasherEditorPrivate *pPrivate = DASHER_EDITOR_GET_PRIVATE(pSelf);
396 if (isdirect(pPrivate->pAppSettings))
397 return dasher_editor_external_delete(pSelf, iLength, iOffset);
399 GtkTextIter end;
401 //Dasher offset 0 = "the first character"; Gtk Text Buffer offset 0
402 // = "the cursor position just before the first character" (and we want
403 // the cursor position just after)
404 gtk_text_buffer_get_iter_at_offset(pPrivate->pBuffer, &end, iOffset+1);
406 GtkTextIter start = end;
408 gtk_text_iter_backward_chars(&start, iLength);
409 // g_bIgnoreCursorMove = true;
410 gtk_text_buffer_delete(pPrivate->pBuffer, &start, &end);
411 gtk_text_view_scroll_mark_onscreen(pPrivate->pTextView, gtk_text_buffer_get_insert(pPrivate->pBuffer));
412 // g_bIgnoreCursorMove = false;
414 pPrivate->bFileModified = TRUE;
417 // const gchar *
418 // dasher_editor_get_context(DasherEditor *pSelf, int iOffset, int iLength) {
419 // if(DASHER_EDITOR_GET_CLASS(pSelf)->get_context)
420 // return DASHER_EDITOR_GET_CLASS(pSelf)->get_context(pSelf, iOffset, iLeng
421 // else
422 // return NULL;
423 // }
424 std::string dasher_editor_get_context(DasherEditor *pSelf, int iOffset, int iLength) {
425 DasherEditorPrivate *pPrivate = DASHER_EDITOR_GET_PRIVATE(pSelf);
427 if (isdirect(pPrivate->pAppSettings))
428 return dasher_editor_external_get_context(pSelf, iOffset, iLength);
430 GtkTextIter start;
431 GtkTextIter end; // Refers to end of context, which is start of selection!
433 gtk_text_buffer_get_iter_at_offset(pPrivate->pBuffer, &start, iOffset);
434 gtk_text_buffer_get_iter_at_offset(pPrivate->pBuffer, &end, iOffset + iLength);
436 auto text = gtk_text_buffer_get_text( pPrivate->pBuffer, &start, &end, false );
437 if (text != nullptr) {
438 std::string context = text;
439 g_free(text);
440 return context;
442 return "";
445 std::string dasher_editor_get_text_around_cursor(DasherEditor *pSelf, Dasher::CControlManager::EditDistance distance) {
446 DasherEditorPrivate *pPrivate = DASHER_EDITOR_GET_PRIVATE(pSelf);
447 if (isdirect(pPrivate->pAppSettings))
448 return dasher_editor_external_get_text_around_cursor(pSelf, distance);
450 // TODO: handle the external editor.
451 GtkTextIter end_pos, start_pos;
452 gtk_text_buffer_get_iter_at_mark(pPrivate->pBuffer, &end_pos, gtk_text_buffer_get_insert(pPrivate->pBuffer));
453 start_pos = end_pos;
454 edit_find(true /* forward */, distance, pPrivate, &end_pos);
455 edit_find(false /* backward */, distance, pPrivate, &start_pos);
456 return gtk_text_buffer_get_slice(pPrivate->pBuffer, &start_pos, &end_pos, FALSE /* hidden chars */);
459 // gint
460 // dasher_editor_get_offset(DasherEditor *pSelf) {
461 // if(DASHER_EDITOR_GET_CLASS(pSelf)->get_offset)
462 // return DASHER_EDITOR_GET_CLASS(pSelf)->get_offset(pSelf);
463 // else
464 // return 0;
465 // }
466 gint
467 dasher_editor_get_offset(DasherEditor *pSelf) {
468 DasherEditorPrivate *pPrivate = DASHER_EDITOR_GET_PRIVATE(pSelf);
470 if (isdirect(pPrivate->pAppSettings))
471 return dasher_editor_external_get_offset(pSelf);
473 GtkTextIter iter1,iter2;
474 gtk_text_buffer_get_iter_at_mark(pPrivate->pBuffer, &iter1, gtk_text_buffer_get_insert(pPrivate->pBuffer));
475 gtk_text_buffer_get_iter_at_mark(pPrivate->pBuffer, &iter2, gtk_text_buffer_get_selection_bound(pPrivate->pBuffer));
476 return std::min(gtk_text_iter_get_offset(&iter1),gtk_text_iter_get_offset(&iter2));
479 void dasher_editor_internal_mark_changed(DasherEditor *pSelf, GtkTextIter *pIter, GtkTextMark *pMark) {
480 const char *szMarkName(gtk_text_mark_get_name(pMark));
481 if(szMarkName && !strcmp(szMarkName,"insert")) {
482 DasherEditorPrivate *pPrivate = DASHER_EDITOR_GET_PRIVATE(pSelf);
483 //ACL: used to emit "offset_changed" signal from buffer, which was picked up
484 // by a callback registered by editor_internal, which then emitted a context_changed
485 // signal from the editor_internal. So just emit the context_changed directly...
486 if (!pPrivate->bInControlAction //tho not if it's the result of a control-mode edit/delete
487 && !gtk_dasher_control_get_game_mode(pPrivate->pDasherCtrl)) //and not in game mode
488 g_signal_emit_by_name(G_OBJECT(pSelf), "context_changed", G_OBJECT(pSelf), NULL, NULL);
493 static void
494 dasher_editor_internal_generate_filename(DasherEditor *pSelf) {
495 DasherEditorPrivate *pPrivate = DASHER_EDITOR_GET_PRIVATE(pSelf);
497 gchar *szNewFilename = NULL;
499 if( pPrivate->pAppSettings->GetBool(APP_BP_TIME_STAMP )) {
500 // Build a filename based on the current time and date
501 tm *t_struct;
502 time_t ctime;
503 char cwd[1000];
504 char tbuffer[200];
506 ctime = time(NULL);
508 t_struct = localtime(&ctime);
510 getcwd(cwd, 1000);
511 snprintf(tbuffer, 200, "dasher-%04d%02d%02d-%02d%02d.txt", (t_struct->tm_year + 1900), (t_struct->tm_mon + 1), t_struct->tm_mday, t_struct->tm_hour, t_struct->tm_min);
513 szNewFilename = g_build_path("/", cwd, tbuffer, NULL);
516 dasher_editor_internal_set_filename(pSelf, szNewFilename);
518 g_free(szNewFilename);
521 static void
522 dasher_editor_internal_open(DasherEditor *pSelf, const gchar *szFilename) {
523 DasherEditorPrivate *pPrivate = DASHER_EDITOR_GET_PRIVATE(pSelf);
525 gsize size;
526 gchar *buffer;
528 #ifdef HAVE_GIO
529 if(!dasher_editor_internal_gvfs_open_file(pSelf, szFilename, &buffer, &size)) {
530 return;
532 #else
533 if(!dasher_editor_internal_unix_vfs_open_file(pSelf, szFilename, &buffer, &size)) {
534 return;
536 #endif
538 // FIXME - REIMPLEMENT (shouldn't happen through core)
539 // dasher_clear();
541 if(size != 0) {
542 // Don't attempt to insert new text if the file is empty as it makes
543 // GTK cry
544 if(!g_utf8_validate(buffer, size, NULL)) { // PRLW: size as gssize = signed int
545 // It's not UTF8, so we do the best we can...
547 // If there are zero bytes in the file then we have a problem -
548 // for now, just assert that we can't load these files.
549 for(gsize i = 0; i < size; ++i)
550 if(buffer[i] == 0) {
551 // GtkWidget *pErrorBox = gtk_message_dialog_new(GTK_WINDOW(window),
552 // GTK_DIALOG_MODAL,
553 // GTK_MESSAGE_ERROR,
554 // GTK_BUTTONS_OK,
555 // "Could not open the file \"%s\". Please note that Dasher cannot load files containing binary data, which may be the cause of this error.\n",
556 // myfilename);
557 GtkWidget *pErrorBox = gtk_message_dialog_new(NULL,
558 GTK_DIALOG_MODAL,
559 GTK_MESSAGE_ERROR,
560 GTK_BUTTONS_OK,
561 "Could not open the file \"%s\". Please note that Dasher cannot load files containing binary data, which may be the cause of this error.\n",
562 szFilename);
563 gtk_dialog_run(GTK_DIALOG(pErrorBox));
564 gtk_widget_destroy(pErrorBox);
565 return;
568 pPrivate->bFileModified = TRUE;
570 gsize iNewSize;
571 gchar *buffer2 = g_strdup(g_locale_to_utf8(buffer, size, NULL, &iNewSize, NULL));
573 // TODO: This function probably needs more thought
575 // const gchar *pEnd;
576 //gboolean bValid = g_utf8_validate(buffer2, -1, &pEnd);
578 g_free(buffer);
579 buffer = buffer2;
580 size = iNewSize;
582 gtk_text_buffer_insert_at_cursor(GTK_TEXT_BUFFER(pPrivate->pBuffer), buffer, size);
583 gtk_text_view_scroll_mark_onscreen(GTK_TEXT_VIEW(pPrivate->pTextView), gtk_text_buffer_get_insert(GTK_TEXT_BUFFER(pPrivate->pBuffer)));
586 dasher_editor_internal_set_filename(pSelf, szFilename);
589 static bool
590 dasher_editor_internal_save_as(DasherEditor *pSelf, const gchar *szFilename, bool bAppend) {
591 DasherEditorPrivate *pPrivate = DASHER_EDITOR_GET_PRIVATE(pSelf);
593 unsigned long long length;
594 gchar *inbuffer, *outbuffer = NULL;
595 // gsize bytes_read, bytes_written;
596 gsize bytes_written;
597 // GError *error = NULL;
598 GtkTextIter *start, *end;
599 // GIConv cd;
601 start = new GtkTextIter;
602 end = new GtkTextIter;
604 gtk_text_buffer_get_iter_at_offset(GTK_TEXT_BUFFER(pPrivate->pBuffer), start, 0);
605 gtk_text_buffer_get_iter_at_offset(GTK_TEXT_BUFFER(pPrivate->pBuffer), end, -1);
607 inbuffer = gtk_text_iter_get_slice(start, end);
609 // g_message("String %s", inbuffer);
611 //length = gtk_text_iter_get_offset(end) - gtk_text_iter_get_offset(start);
612 //length = gtk_text_buffer_get_byte_count(GTK_TEXT_BUFFER(the_text_buffer));
614 // I'm pretty certain that this is null terminated, but not 100%
615 length = strlen(inbuffer);
617 // g_message("Length is %d", length);
619 outbuffer = (char *)malloc((length + 1) * sizeof(gchar));
620 memcpy((void *)outbuffer, (void *)inbuffer, length * sizeof(gchar));
621 outbuffer[length] = 0;
622 g_free(inbuffer);
623 inbuffer = outbuffer;
624 bytes_written = length;
626 #ifdef HAVE_GIO
627 if(!dasher_editor_internal_gvfs_save_file(pSelf, szFilename, outbuffer, bytes_written, bAppend)) {
628 return false;
630 #else
631 if(!dasher_editor_internal_unix_vfs_save_file(pSelf, szFilename, outbuffer, bytes_written, bAppend)) {
632 return false;
634 #endif
636 pPrivate->bFileModified = FALSE;
638 dasher_editor_internal_set_filename(pSelf, szFilename);
640 return true;
643 GtkTextBuffer *dasher_editor_game_text_buffer(DasherEditor *pSelf) {
644 DasherEditorPrivate *pPrivate = DASHER_EDITOR_GET_PRIVATE(pSelf);
645 return pPrivate->pBuffer;
648 gboolean
649 dasher_editor_command(DasherEditor *pSelf, const gchar *szCommand) {
650 if(!strcmp(szCommand, "action_new")) { //select_new_file
651 dasher_editor_internal_command_new(pSelf);
652 return TRUE;
655 if(!strcmp(szCommand, "action_open")) { //select open file
656 dasher_editor_internal_command_open(pSelf);
657 return TRUE;
660 if(!strcmp(szCommand, "action_save")) { //save_file
661 dasher_editor_internal_command_save(pSelf, FALSE, FALSE);
662 return TRUE;
665 if(!strcmp(szCommand, "action_saveas")) { // select_save_file_as
666 dasher_editor_internal_command_save(pSelf, TRUE, FALSE);
667 return TRUE;
670 if(!strcmp(szCommand, "action_append")) { // select_append_file
671 dasher_editor_internal_command_save(pSelf, TRUE, TRUE);
672 return TRUE;
675 if(!strcmp(szCommand, "action_cut")) { // clipboard_cut
676 dasher_editor_internal_clipboard(pSelf, CLIPBOARD_CUT);
677 return TRUE;
680 if(!strcmp(szCommand, "action_copy")) { // clipboard_copy
681 dasher_editor_internal_clipboard(pSelf, CLIPBOARD_COPY);
682 return TRUE;
685 if(!strcmp(szCommand, "action_copyall")) { // clipboard_copyall
686 dasher_editor_internal_clipboard(pSelf, CLIPBOARD_COPYALL);
687 return TRUE;
690 if(!strcmp(szCommand, "action_paste")) { // clipboard_paste
691 dasher_editor_internal_clipboard(pSelf, CLIPBOARD_PASTE);
692 return TRUE;
695 // TODO: This isn't actually accessible from anywhere
696 if(!strcmp(szCommand, "action_selectall")) { // clipboard_paste
697 dasher_editor_internal_clipboard(pSelf, CLIPBOARD_SELECTALL);
698 return TRUE;
701 return FALSE;
705 void
706 dasher_editor_internal_handle_font(DasherEditor *pSelf, const gchar *szFont) {
707 if(strcmp(szFont, "")) {
708 DasherEditorPrivate *pPrivate = DASHER_EDITOR_GET_PRIVATE(pSelf);
710 PangoFontDescription *pFD = pango_font_description_from_string(szFont);
711 #if GTK_CHECK_VERSION(3, 0, 0)
712 gtk_widget_override_font(GTK_WIDGET(pPrivate->pTextView), pFD);
713 #else
714 gtk_widget_modify_font(GTK_WIDGET(pPrivate->pTextView), pFD);
715 #endif
720 gboolean
721 dasher_editor_file_changed(DasherEditor *pSelf) {
722 DasherEditorPrivate *pPrivate = DASHER_EDITOR_GET_PRIVATE(pSelf);
723 return pPrivate->bFileModified;
726 const gchar *
727 dasher_editor_get_filename(DasherEditor *pSelf) {
728 DasherEditorPrivate *pPrivate = DASHER_EDITOR_GET_PRIVATE(pSelf);
730 return pPrivate->szFilename;
733 // TODO: We shouldn't need to know about the buffer here - make this a method of the buffer set
734 std::string
735 dasher_editor_get_all_text(DasherEditor *pSelf) {
736 DasherEditorPrivate *pPrivate = DASHER_EDITOR_GET_PRIVATE(pSelf);
737 if (pPrivate == NULL) {
738 // This function can be called before pPrivate is set.
739 return "";
741 GtkTextIter oStart;
742 GtkTextIter oEnd;
744 gtk_text_buffer_get_start_iter(pPrivate->pBuffer, &oStart);
745 gtk_text_buffer_get_end_iter(pPrivate->pBuffer, &oEnd);
747 pPrivate->pNewMark = gtk_text_buffer_create_mark(pPrivate->pBuffer, NULL, &oEnd, TRUE);
749 std::string all_text;
750 auto text = gtk_text_buffer_get_text(pPrivate->pBuffer, &oStart, &oEnd, false );
751 if (text != nullptr) {
752 all_text = text;
753 g_free(text);
755 return all_text;
758 const gchar *
759 dasher_editor_get_new_text(DasherEditor *pSelf) {
760 // TODO: Implement this properly
761 DasherEditorPrivate *pPrivate = DASHER_EDITOR_GET_PRIVATE(pSelf);
763 GtkTextIter oStart;
764 GtkTextIter oEnd;
766 gtk_text_buffer_get_end_iter(pPrivate->pBuffer, &oEnd);
767 gtk_text_buffer_get_iter_at_mark(pPrivate->pBuffer, &oStart, pPrivate->pNewMark);
769 const gchar *szRetVal = gtk_text_buffer_get_text(pPrivate->pBuffer, &oStart, &oEnd, false );
771 pPrivate->pNewMark = gtk_text_buffer_create_mark(pPrivate->pBuffer, NULL, &oEnd, TRUE);
773 return szRetVal;
777 /* Private methods */
778 static void
779 dasher_editor_internal_select_all(DasherEditor *pSelf) {
780 DasherEditorPrivate *pPrivate = DASHER_EDITOR_GET_PRIVATE(pSelf);
781 GtkTextIter *start, *end;
783 start = new GtkTextIter;
784 end = new GtkTextIter;
786 gtk_text_buffer_get_iter_at_offset(GTK_TEXT_BUFFER(pPrivate->pBuffer), start, 0);
787 gtk_text_buffer_get_iter_at_offset(GTK_TEXT_BUFFER(pPrivate->pBuffer), end, -1);
789 GtkTextMark *selection = gtk_text_buffer_get_mark(pPrivate->pBuffer, "selection_bound");
790 GtkTextMark *cursor = gtk_text_buffer_get_mark(pPrivate->pBuffer, "insert");
792 gtk_text_buffer_move_mark(pPrivate->pBuffer, selection, start);
793 gtk_text_buffer_move_mark(pPrivate->pBuffer, cursor, end);
795 delete start;
796 delete end;
799 static void
800 dasher_editor_internal_command_new(DasherEditor *pSelf) {
801 dasher_editor_internal_new_buffer(pSelf, NULL);
804 static void
805 dasher_editor_internal_command_open(DasherEditor *pSelf) {
806 DasherEditorPrivate *pPrivate = DASHER_EDITOR_GET_PRIVATE(pSelf);
808 GtkWidget *pTopLevel = gtk_widget_get_toplevel(GTK_WIDGET(pPrivate->pTextView));
809 GtkWidget *filesel = gtk_file_chooser_dialog_new(_("Select File"),
810 GTK_WINDOW(pTopLevel),
811 GTK_FILE_CHOOSER_ACTION_OPEN,
812 _("_Open"),
813 GTK_RESPONSE_ACCEPT,
814 _("_Cancel"),
815 GTK_RESPONSE_CANCEL, NULL);
817 #ifdef HAVE_GIO
818 gtk_file_chooser_set_local_only(GTK_FILE_CHOOSER(filesel), FALSE);
819 #endif
821 if(gtk_dialog_run(GTK_DIALOG(filesel)) == GTK_RESPONSE_ACCEPT) {
822 #ifdef HAVE_GIO
823 char *filename = gtk_file_chooser_get_uri(GTK_FILE_CHOOSER(filesel));
824 #else
825 char *filename = gtk_file_chooser_get_filename(GTK_FILE_CHOOSER(filesel));
826 #endif
827 dasher_editor_internal_new_buffer(pSelf, filename);
828 g_free(filename);
831 gtk_widget_destroy(filesel);
834 static void
835 dasher_editor_internal_command_save(DasherEditor *pSelf, gboolean bPrompt, gboolean bAppend) {
836 DasherEditorPrivate *pPrivate = DASHER_EDITOR_GET_PRIVATE(pSelf);
838 gchar *szFilename = NULL;
840 // Hmm... this makes no sense - surely this always evaluates to true?
841 if(bPrompt || !szFilename) {
842 GtkWidget *pTopLevel = gtk_widget_get_toplevel(GTK_WIDGET(pPrivate->pTextView));
843 GtkWidget *filesel = gtk_file_chooser_dialog_new(_("Select File"),
844 GTK_WINDOW(pTopLevel),
845 GTK_FILE_CHOOSER_ACTION_SAVE,
846 _("_Save"),
847 GTK_RESPONSE_ACCEPT,
848 _("_Cancel"),
849 GTK_RESPONSE_CANCEL, NULL);
851 #ifdef HAVE_GIO
852 gtk_file_chooser_set_local_only(GTK_FILE_CHOOSER(filesel), FALSE);
853 #endif
855 if(gtk_dialog_run(GTK_DIALOG(filesel)) == GTK_RESPONSE_ACCEPT) {
856 #ifdef HAVE_GIO
857 szFilename = gtk_file_chooser_get_uri(GTK_FILE_CHOOSER(filesel));
858 #else
859 szFilename = gtk_file_chooser_get_filename(GTK_FILE_CHOOSER(filesel));
860 #endif
862 else {
863 gtk_widget_destroy(filesel);
864 return;
867 gtk_widget_destroy(filesel);
869 else {
870 szFilename = g_strdup(pPrivate->szFilename);
873 dasher_editor_internal_save_as(pSelf, szFilename, bAppend);
874 g_free(szFilename);
877 #ifdef HAVE_GIO
878 static void
879 dasher_editor_internal_gvfs_print_error(DasherEditor *pSelf, GError *error, const char *myfilename) {
880 // Turns a GVFS error into English
881 GtkWidget *error_dialog;
882 error_dialog = gtk_message_dialog_new(NULL, GTK_DIALOG_MODAL, GTK_MESSAGE_ERROR, GTK_BUTTONS_OK, "Could not open the file \"%s\"\n%s\n", myfilename, error->message);
883 gtk_dialog_set_default_response(GTK_DIALOG(error_dialog), GTK_RESPONSE_OK);
884 gtk_window_set_resizable(GTK_WINDOW(error_dialog), FALSE);
885 gtk_dialog_run(GTK_DIALOG(error_dialog));
886 gtk_widget_destroy(error_dialog);
887 return;
890 static gboolean
891 dasher_editor_internal_gvfs_open_file(DasherEditor *pSelf, const char *uri, gchar **buffer, gsize *size)
893 GFile *file;
894 GFileInputStream *read_handle;
895 GError *error;
896 GFileInfo *info;
897 gssize bytes_read;
899 file = g_file_new_for_uri (uri);
901 read_handle = g_file_read (file, NULL, &error);
903 /* If URI didn't work, try path */
904 if (read_handle == NULL)
905 { // PRLW: g_object_unref isn't actually stipulated by g_file_new_for_uri
906 g_object_unref (file);
907 file = g_file_new_for_path (uri);
908 read_handle = g_file_read (file, NULL, &error);
911 if (read_handle == NULL)
913 dasher_editor_internal_gvfs_print_error (pSelf, error, uri);
914 g_object_unref (file);
915 return FALSE;
918 info = g_file_query_info (file, G_FILE_ATTRIBUTE_STANDARD_SIZE,
919 G_FILE_QUERY_INFO_NONE, NULL, &error);
921 if (info == NULL)
923 dasher_editor_internal_gvfs_print_error (pSelf, error, uri);
924 g_input_stream_close (G_INPUT_STREAM(read_handle), NULL, &error);
925 g_object_unref (read_handle);
926 g_object_unref (file);
927 return FALSE;
930 // XXX PRLW: cases info > max(size) as max(size) < max(uint64),
931 // and bytes_read < size aren't handled.
932 *size = g_file_info_get_attribute_uint64(info,G_FILE_ATTRIBUTE_STANDARD_SIZE);
933 *buffer = (gchar *) g_malloc (*size);
934 bytes_read = g_input_stream_read (G_INPUT_STREAM(read_handle), *buffer,
935 *size, NULL, &error);
937 if (bytes_read == -1)
939 dasher_editor_internal_gvfs_print_error (pSelf, error, uri);
940 g_input_stream_close (G_INPUT_STREAM(read_handle), NULL, &error);
941 g_object_unref (read_handle);
942 g_object_unref (info);
943 g_object_unref (file);
944 return FALSE;
947 g_input_stream_close (G_INPUT_STREAM(read_handle), NULL, NULL);
948 g_object_unref (read_handle);
949 g_object_unref (info);
950 g_object_unref (file);
951 return TRUE;
954 static GFileOutputStream *append_or_replace_file(GFile *file, bool append, GError **error)
956 GFileOutputStream *write_handle;
957 if (append)
958 write_handle = g_file_append_to (file, G_FILE_CREATE_NONE, NULL, error);
959 else
960 write_handle = g_file_replace (file, NULL, FALSE, G_FILE_CREATE_NONE,
961 NULL, error);
963 return write_handle;
966 static gboolean
967 dasher_editor_internal_gvfs_save_file(DasherEditor *pSelf, const char *uri, gchar *buffer, gsize length, bool append)
969 GFile *file;
970 GFileOutputStream *write_handle;
971 GError *error = NULL;
972 gssize bytes_written;
974 file = g_file_new_for_uri (uri);
976 write_handle = append_or_replace_file (file, append, &error);
978 /* If URI didn't work, try path */
979 if (write_handle == NULL)
981 g_object_unref (file);
982 file = g_file_new_for_path (uri);
983 write_handle = append_or_replace_file (file, append, &error);
986 if (write_handle == NULL)
988 dasher_editor_internal_gvfs_print_error (pSelf, error, uri);
989 g_object_unref (file);
990 return FALSE;
993 if (append)
995 if (!g_seekable_seek(G_SEEKABLE(write_handle),0,G_SEEK_END, NULL, &error))
997 dasher_editor_internal_gvfs_print_error (pSelf, error, uri);
998 g_object_unref (write_handle);
999 g_object_unref (file);
1000 return FALSE;
1004 bytes_written = g_output_stream_write (G_OUTPUT_STREAM(write_handle), buffer,
1005 length, NULL, &error);
1006 // XXX PRLW: case bytes_written < length not handled.
1007 if (bytes_written == -1)
1009 dasher_editor_internal_gvfs_print_error (pSelf, error, uri);
1010 g_output_stream_close (G_OUTPUT_STREAM(write_handle), NULL, NULL);
1011 g_object_unref (write_handle);
1012 g_object_unref (file);
1013 return FALSE;
1016 g_output_stream_close (G_OUTPUT_STREAM(write_handle), NULL, NULL);
1017 g_object_unref (write_handle);
1018 g_object_unref (file);
1019 return TRUE;
1022 #else /* not HAVE_GIO */
1024 static gboolean
1025 dasher_editor_internal_unix_vfs_open_file(DasherEditor *pSelf, const char *myfilename, gchar **buffer, gsize *size) {
1026 GtkWidget *error_dialog;
1028 struct stat file_stat;
1029 FILE *fp;
1031 stat(myfilename, &file_stat);
1032 fp = fopen(myfilename, "r");
1034 if(fp == NULL || S_ISDIR(file_stat.st_mode)) {
1035 // error_dialog = gtk_message_dialog_new(GTK_WINDOW(window), GTK_DIALOG_MODAL, GTK_MESSAGE_ERROR, GTK_BUTTONS_OK, "Could not open the file \"%s\".\n", myfilename);
1036 error_dialog = gtk_message_dialog_new(NULL, GTK_DIALOG_MODAL, GTK_MESSAGE_ERROR, GTK_BUTTONS_OK, "Could not open the file \"%s\".\n", myfilename);
1037 gtk_dialog_set_default_response(GTK_DIALOG(error_dialog), GTK_RESPONSE_OK);
1038 gtk_window_set_resizable(GTK_WINDOW(error_dialog), FALSE);
1039 gtk_dialog_run(GTK_DIALOG(error_dialog));
1040 gtk_widget_destroy(error_dialog);
1041 return FALSE;
1044 *size = file_stat.st_size; // PRLW: is off_t = uint64_t, size is size_t, MD
1045 *buffer = (gchar *) g_malloc(*size);
1046 fread(*buffer, *size, 1, fp);
1047 fclose(fp);
1048 return TRUE;
1051 static gboolean
1052 dasher_editor_internal_unix_vfs_save_file(DasherEditor *pSelf, const char *myfilename, gchar *buffer, gsize length, bool append) {
1053 int opened = 1;
1054 GtkWidget *error_dialog;
1056 FILE *fp;
1058 if(append == true) {
1059 fp = fopen(myfilename, "a");
1061 if(fp == NULL) {
1062 opened = 0;
1065 else {
1066 fp = fopen(myfilename, "w");
1067 if(fp == NULL) {
1068 opened = 0;
1072 if(!opened) {
1073 // error_dialog = gtk_message_dialog_new(GTK_WINDOW(window), GTK_DIALOG_MODAL, GTK_MESSAGE_ERROR, GTK_BUTTONS_OK, "Could not save the file \"%s\".\n", myfilename);
1074 error_dialog = gtk_message_dialog_new(NULL, GTK_DIALOG_MODAL, GTK_MESSAGE_ERROR, GTK_BUTTONS_OK, "Could not save the file \"%s\".\n", myfilename);
1075 gtk_dialog_set_default_response(GTK_DIALOG(error_dialog), GTK_RESPONSE_OK);
1076 gtk_window_set_resizable(GTK_WINDOW(error_dialog), FALSE);
1077 gtk_dialog_run(GTK_DIALOG(error_dialog));
1078 gtk_widget_destroy(error_dialog);
1079 return false;
1082 fwrite(buffer, 1, length, fp);
1083 fclose(fp);
1084 return true;
1086 #endif
1088 static void
1089 dasher_editor_internal_set_filename(DasherEditor *pSelf, const gchar *szFilename) {
1090 DasherEditorPrivate *pPrivate = DASHER_EDITOR_GET_PRIVATE(pSelf);
1092 if(pPrivate->szFilename)
1093 g_free((void *)pPrivate->szFilename);
1095 if(szFilename)
1096 pPrivate->szFilename = g_strdup(szFilename);
1097 else
1098 pPrivate->szFilename = NULL;
1100 g_signal_emit_by_name(G_OBJECT(pSelf), "filename_changed", G_OBJECT(pSelf), NULL, NULL);
1103 void
1104 dasher_editor_edit_convert(DasherEditor *pSelf) {
1105 DasherEditorPrivate *pPrivate = DASHER_EDITOR_GET_PRIVATE(pSelf);
1107 if(!(pPrivate->bConversionMode))
1108 return;
1110 GtkTextIter sStartIter;
1111 GtkTextIter sEndIter;
1112 gtk_text_buffer_get_iter_at_offset(pPrivate->pBuffer, &sStartIter, pPrivate->iLastOffset);
1113 gtk_text_buffer_get_iter_at_offset(pPrivate->pBuffer, &sEndIter, -1);
1114 gtk_text_buffer_apply_tag(pPrivate->pBuffer, pPrivate->pHiddenTag, &sStartIter, &sEndIter);
1116 pPrivate->iCurrentState = 1;
1117 pPrivate->iLastOffset = gtk_text_buffer_get_char_count(pPrivate->pBuffer);
1120 void
1121 dasher_editor_edit_protect(DasherEditor *pSelf) {
1122 DasherEditorPrivate *pPrivate = DASHER_EDITOR_GET_PRIVATE(pSelf);
1124 if(!(pPrivate->bConversionMode))
1125 return;
1127 pPrivate->iCurrentState = 0;
1128 pPrivate->iLastOffset = gtk_text_buffer_get_char_count(pPrivate->pBuffer);
1131 // Backward version of gtk_text_iter_forward_to_line_end.
1132 static void text_iter_backward_to_line_end(GtkTextIter *pPos) {
1133 // If already at the end of a paragraph back up to the first non
1134 // paragraph character.
1135 while (gtk_text_iter_ends_line(pPos)) {
1136 if (!gtk_text_iter_backward_char(pPos)) {
1137 return;
1140 while (!gtk_text_iter_ends_line(pPos)) {
1141 if (!gtk_text_iter_backward_char (pPos)) {
1142 return; // Start of the buffer.
1147 // If 'pPos' is on whitespace, advance it until the next non-whitespace character.
1148 static void skip_whitespace_forward(GtkTextIter *pPos) {
1149 while (g_unichar_isspace(gtk_text_iter_get_char(pPos))) {
1150 if (!gtk_text_iter_forward_char (pPos)) {
1151 return; // Start of the buffer.
1156 static void edit_find(bool bForwards, Dasher::CControlManager::EditDistance iDist, DasherEditorPrivate *pPrivate, GtkTextIter *pPos) {
1157 if(bForwards) {
1158 switch(iDist) {
1159 case Dasher::CControlManager::EDIT_CHAR:
1160 gtk_text_iter_forward_char(pPos);
1161 break;
1162 case Dasher::CControlManager::EDIT_WORD:
1163 gtk_text_iter_forward_word_end(pPos);
1164 skip_whitespace_forward(pPos);
1165 break;
1166 case Dasher::CControlManager::EDIT_LINE:
1167 if(!gtk_text_view_forward_display_line_end(GTK_TEXT_VIEW(pPrivate->pTextView), pPos))
1169 gtk_text_view_forward_display_line (GTK_TEXT_VIEW(pPrivate->pTextView), pPos);
1170 gtk_text_view_forward_display_line_end(GTK_TEXT_VIEW(pPrivate->pTextView), pPos);
1172 break;
1173 case Dasher::CControlManager::EDIT_SENTENCE:
1174 // Use the Pango sentence end rules.
1175 gtk_text_iter_forward_sentence_end(pPos);
1176 skip_whitespace_forward(pPos);
1177 break;
1178 case Dasher::CControlManager::EDIT_PARAGRAPH:
1179 // Moves to the next \n, \r, \r\n or the Unicode paragraph separator character.
1180 gtk_text_iter_forward_to_line_end(pPos);
1181 skip_whitespace_forward(pPos);
1182 break;
1183 case Dasher::CControlManager::EDIT_FILE:
1184 gtk_text_iter_forward_to_end(pPos);
1185 break;
1186 case Dasher::CControlManager::EDIT_SELECTION:
1187 break;
1190 else {
1191 switch(iDist) {
1192 case Dasher::CControlManager::EDIT_CHAR:
1193 gtk_text_iter_backward_char(pPos);
1194 break;
1195 case Dasher::CControlManager::EDIT_WORD:
1196 gtk_text_iter_backward_word_start(pPos);
1197 break;
1198 case Dasher::CControlManager::EDIT_LINE:
1199 if(!gtk_text_view_backward_display_line_start(GTK_TEXT_VIEW(pPrivate->pTextView), pPos))
1200 gtk_text_view_backward_display_line(GTK_TEXT_VIEW(pPrivate->pTextView), pPos);
1201 break;
1202 case Dasher::CControlManager::EDIT_SENTENCE:
1203 gtk_text_iter_backward_sentence_start(pPos);
1204 break;
1205 case Dasher::CControlManager::EDIT_PARAGRAPH:
1206 // Moves to the previous \n, \r, \r\n or the Unicode paragraph separator character.
1207 text_iter_backward_to_line_end(pPos);
1208 break;
1209 case Dasher::CControlManager::EDIT_FILE:
1210 gtk_text_buffer_get_start_iter(pPrivate->pBuffer, pPos);
1211 break;
1212 case Dasher::CControlManager::EDIT_SELECTION:
1213 break;
1218 gint
1219 dasher_editor_ctrl_delete(DasherEditor *pSelf, bool bForwards, Dasher::CControlManager::EditDistance iDist) {
1220 // XXX or return dasher_editor_get_offset?
1221 DasherEditorPrivate *pPrivate = DASHER_EDITOR_GET_PRIVATE(pSelf);
1223 GtkTextIter sPosStart;
1224 GtkTextIter sPosEnd;
1226 gtk_text_buffer_get_iter_at_mark(pPrivate->pBuffer, &sPosStart, gtk_text_buffer_get_insert(pPrivate->pBuffer));
1228 sPosEnd = sPosStart;
1229 edit_find(bForwards, iDist, pPrivate, &sPosStart);
1230 gint iRet = gtk_text_iter_get_offset(&sPosStart);
1231 pPrivate->bInControlAction = true;
1232 gtk_text_buffer_delete(pPrivate->pBuffer, &sPosStart, &sPosEnd);
1233 pPrivate->bInControlAction = false;
1234 gtk_text_view_scroll_mark_onscreen(GTK_TEXT_VIEW(pPrivate->pTextView), gtk_text_buffer_get_insert(pPrivate->pBuffer));
1235 return iRet;
1238 gint
1239 dasher_editor_ctrl_move(DasherEditor *pSelf, bool bForwards, Dasher::CControlManager::EditDistance iDist) {
1240 DasherEditorPrivate *pPrivate = DASHER_EDITOR_GET_PRIVATE(pSelf);
1242 if (isdirect(pPrivate->pAppSettings)) {
1243 dasher_editor_external_move(pSelf, bForwards, iDist);
1244 return 0;
1246 GtkTextIter sPos;
1248 gtk_text_buffer_get_iter_at_mark(pPrivate->pBuffer, &sPos, gtk_text_buffer_get_insert(pPrivate->pBuffer));
1250 edit_find(bForwards, iDist, pPrivate, &sPos);
1251 gint iRet = gtk_text_iter_get_offset(&sPos);
1252 pPrivate->bInControlAction = true;
1253 gtk_text_buffer_place_cursor(pPrivate->pBuffer, &sPos);
1254 pPrivate->bInControlAction = false;
1255 gtk_text_view_scroll_mark_onscreen(GTK_TEXT_VIEW(pPrivate->pTextView), gtk_text_buffer_get_insert(pPrivate->pBuffer));
1256 return iRet;
1260 static void
1261 dasher_editor_internal_new_buffer(DasherEditor *pSelf, const gchar *szFilename) {
1262 /* TODO: eventually rewrite this without references to external functions */
1264 if(szFilename) {
1265 dasher_editor_internal_open(pSelf, szFilename);
1267 else {
1268 dasher_editor_internal_generate_filename(pSelf);
1269 dasher_editor_clear(pSelf);
1272 // g_signal_emit_by_name(G_OBJECT(pSelf), "buffer_changed", G_OBJECT(pSelf), NULL, NULL);
1275 void
1276 dasher_editor_handle_parameter_change(DasherEditor *pSelf, gint iParameter) {
1277 DasherEditorPrivate *pPrivate = DASHER_EDITOR_GET_PRIVATE(pSelf);
1278 switch(iParameter) {
1279 case APP_SP_EDIT_FONT:
1280 dasher_editor_internal_handle_font(pSelf,
1281 pPrivate->pAppSettings->GetString(APP_SP_EDIT_FONT).c_str());
1282 break;
1286 /* Callback Functions */
1288 extern "C" void
1289 delete_children_callback(GtkWidget *pWidget, gpointer pUserData) {
1290 gtk_widget_destroy(pWidget);
1293 extern "C" void
1294 main_window_realized(DasherMain *pMain, gpointer pUserData) {
1297 extern "C" void mark_set_handler(GtkWidget *widget, GtkTextIter *pIter, GtkTextMark *pMark, gpointer pUserData) {
1298 dasher_editor_internal_mark_changed(DASHER_EDITOR(pUserData), pIter, pMark);