Everything in Vector appears to be working. Not finished, but good enough to start testing.

This commit is contained in:
Scott Duensing 2022-12-09 18:08:46 -06:00
parent 116d469f5c
commit 0a7dfc1184
7 changed files with 162 additions and 103 deletions

View file

@ -24,10 +24,19 @@
#define COMMON_H
#define DEBUG // If we're debugging.
#include <gtk/gtk.h>
#include "array.h"
#ifdef DEBUG
#define debug(...) printf(__VA_ARGS__)
#define MEMWATCH
#else
#define debug(s)
#endif
#include "memwatch.h"

View file

@ -29,6 +29,7 @@
char *utilCreateString(char *format, ...);
char *utilCreateStringVArgs(char *format, va_list args);
void utilEnsureBufferSize(unsigned char **buffer, int *length, int wanted);
gboolean utilFileExists(char *filename);
GdkPixbuf *utilGetPixbufFromMemory(char *data, unsigned int len);
WindowDataT *utilGetWindowData(GtkWidget *window);

View file

@ -70,7 +70,7 @@ EVENT void toolJoeyDevProjectClicked(GtkWidget *object, gpointer userData) {
(void)object;
(void)userData;
printf("Project Clicked!\n");
debug("Project Clicked!\n");
}

View file

@ -62,6 +62,21 @@ char *utilCreateStringVArgs(char *format, va_list args) {
}
void utilEnsureBufferSize(unsigned char **buffer, int *length, int wanted) {
unsigned char *temp = NULL;
if (*length < wanted) {
*length = *length + 1024;
temp = realloc(*buffer, *length);
if (temp == NULL) {
//***TODO*** Something bad happened.
} else {
*buffer = temp;
}
}
}
gboolean utilFileExists(char *filename) {
FILE *f = fopen(filename, "rb");

View file

@ -23,6 +23,7 @@
#include <errno.h>
#include "common.h"
#include "vecparse.h"
#include "utils.h"
#define IS_NUMBER(n) (n & 0x8000 ? FALSE : TRUE)
@ -45,7 +46,6 @@ typedef struct LabelS {
} LabelT;
static void ensureBufferSize(VecByteCodeT *bytecode, int needed);
static int labelGetValue(int lineNumber, VecByteCodeT *bytecode, LabelT *labels, LabelT ***unresolved, char *label);
static void outputByte(VecByteCodeT *bytecode, unsigned short word);
static void outputWord(VecByteCodeT *bytecode, unsigned short word);
@ -58,21 +58,6 @@ static gboolean parserGetXYZ(char **tokenEnd, char ***variables, int *x, int *y
static int variableCollect(char *value, char ***variables);
static void ensureBufferSize(VecByteCodeT *bytecode, int needed) {
unsigned char *temp = NULL;
if (bytecode->bufferSize < bytecode->length + needed) {
bytecode->bufferSize += 1024;
temp = realloc(bytecode->bytes, bytecode->bufferSize);
if (temp == NULL) {
//***TODO*** Something bad happened.
} else {
bytecode->bytes = temp;
}
}
}
static int labelGetValue(int lineNumber, VecByteCodeT *bytecode, LabelT *labels, LabelT ***unresolved, char *label) {
int index;
int found = -1;
@ -105,7 +90,7 @@ static int labelGetValue(int lineNumber, VecByteCodeT *bytecode, LabelT *labels,
static void outputByte(VecByteCodeT *bytecode, unsigned short word) {
unsigned char byte = (unsigned char)word;
ensureBufferSize(bytecode, 1);
utilEnsureBufferSize(&bytecode->bytes, &bytecode->bufferSize, bytecode->length + 1);
// If the word passed in is a variable, set the MSb in the byte as well.
if (word & 0x8000) {
@ -117,7 +102,7 @@ static void outputByte(VecByteCodeT *bytecode, unsigned short word) {
static void outputWord(VecByteCodeT *bytecode, unsigned short word) {
ensureBufferSize(bytecode, 2);
utilEnsureBufferSize(&bytecode->bytes, &bytecode->bufferSize, bytecode->length + 2);
bytecode->bytes[bytecode->length++] = (word & 0xFF00) >> 8;
bytecode->bytes[bytecode->length++] = word & 0x00FF;
}
@ -356,7 +341,7 @@ int vecparser(char *programIn, VecByteCodeT *bytecode) {
} else { // blank line
printf("[%s]\n", token);
debug("[%s]\n", token);
// Is it math?
if (strlen(token) > 1 && token[0] == '%') {
@ -700,18 +685,19 @@ int vecparser(char *programIn, VecByteCodeT *bytecode) {
} // read program line
// Resolve forward label declarations and patch bytecode.
//***DEBUG***
#ifdef DEBUG
for (y1=0; y1<shlen(labels); y1++) {
printf("Resolved - %s\n", labels[y1].key);
debug("Resolved - %s\n", labels[y1].key);
}
for (y1=0; y1<arrlen(unresolved); y1++) {
printf("Unresolved - %s\n", unresolved[y1]->key);
debug("Unresolved - %s\n", unresolved[y1]->key);
}
#endif
for (y1=0; y1<arrlen(unresolved); y1++) {
// Find offset of this unresolved label. We search ourselves so it's case-insensitive.
x2 = -1;
for (x1 = 0; x1 < shlen(labels); x1++) {
printf("Checking label %d of %d - %s == %s\n", y1, (int)arrlen(unresolved), unresolved[y1]->key, labels[x1].key);
debug("Checking label %d of %d - %s == %s\n", y1, (int)arrlen(unresolved), unresolved[y1]->key, labels[x1].key);
if (strcasecmp(unresolved[y1]->key, labels[x1].key) == 0) {
x2 = x1;
break;

View file

@ -38,6 +38,7 @@
#define VICTOR_VERSION "1.00"
#define RENDER_TIMEOUT 5 // In seconds
#define SSM(m, w, l) scintilla_send_message(self->sci, m, w, l)
#define MARGIN_SCRIPT_FOLD_INDEX 1
@ -66,6 +67,8 @@ typedef struct VectorDataS {
char *filename;
char *title;
char *tracename;
char *buffer;
int bufferLength;
} VectorDataT;
@ -187,47 +190,44 @@ EVENT void editorVectorNotify(GtkWidget *sciWidget, gint ctrlID, struct SCNotifi
VectorDataT *self = (VectorDataT *)userData;
int lineNumber = (int)SSM(SCI_LINEFROMPOSITION, (uptr_t)notifyData->position, (sptr_t)0);
int length = SSM(SCI_GETLENGTH, 0, 0);
char *code;
VecByteCodeT byteCode;
(void)sciWidget;
(void)ctrlID;
//printf("Notification %d\n", notifyData->modificationType);
//debug("Notification %d\n", notifyData->modificationType);
switch (notifyData->nmhdr.code) {
case SCN_MODIFIED:
if (notifyData->modificationType & SC_MOD_INSERTTEXT || notifyData->modificationType & SC_MOD_DELETETEXT) {
// Allocate space to fetch code from editor.
code = (char *)malloc(length + 1);
if (!code) return;
utilEnsureBufferSize((unsigned char **)&self->buffer, &self->bufferLength, length);
// Clear error markers.
SSM(SCI_MARKERDELETEALL, MARKER_ERROR_ARROW, 0);
SSM(SCI_MARKERDELETEALL, MARKER_ERROR_HIGHLIGHT, 0);
// Fetch code.
SSM(SCI_GETTEXT, length, (sptr_t)code);
SSM(SCI_GETTEXT, length, (sptr_t)self->buffer);
// Parse code.
byteCode.bytes = NULL;
byteCode.length = 0;
byteCode.bytes = NULL;
byteCode.length = 0;
byteCode.bufferSize = 0;
lineNumber = vecparser(code, &byteCode);
lineNumber = vecparser(self->buffer, &byteCode);
if (lineNumber >= 0) {
// Mark lines that fail to parse.
SSM(SCI_MARKERADD, lineNumber, MARKER_ERROR_ARROW);
SSM(SCI_MARKERADD, lineNumber, MARKER_ERROR_HIGHLIGHT);
} else {
//***DEBUG***
#ifdef DEBUG
FILE *out = fopen("bytecode.bin", "wb");
fwrite(byteCode.bytes, byteCode.length, 1, out);
fclose(out);
#endif
// All good!
renderBytecode(&byteCode, self);
}
// Release bytecode.
if (byteCode.bytes != NULL) DEL(byteCode.bytes);
// Release code.
DEL(code);
// Mark text dirty. SCN_SAVEPOINTLEFT isn't being reliable.
setDirty(self, TRUE);
}
@ -260,7 +260,7 @@ EVENT void fileVectorTraceImageFileSet(GtkWidget *object, gpointer userData) {
VectorDataT *self = (VectorDataT *)userData;
char *temp = NULL;
printf("fileVectorTraceImageFileSet fired\n");
debug("fileVectorTraceImageFileSet fired\n");
temp = (char *)gtk_file_chooser_get_filename(GTK_FILE_CHOOSER(object));
loadTraceImage(self, temp);
DEL(temp);
@ -327,22 +327,38 @@ static void loadTraceImage(VectorDataT *self, char *filename) {
EVENT void menuVectorEditCopy(GtkWidget *object, gpointer userData) {
VectorDataT *self = (VectorDataT *)userData;
(void)object;
SSM(SCI_COPY, 0, 0);
}
EVENT void menuVectorEditCut(GtkWidget *object, gpointer userData) {
VectorDataT *self = (VectorDataT *)userData;
(void)object;
SSM(SCI_CUT, 0, 0);
}
EVENT void menuVectorEditDelete(GtkWidget *object, gpointer userData) {
VectorDataT *self = (VectorDataT *)userData;
(void)object;
SSM(SCI_CLEAR, 0, 0);
}
EVENT void menuVectorEditPaste(GtkWidget *object, gpointer userData) {
VectorDataT *self = (VectorDataT *)userData;
(void)object;
SSM(SCI_PASTE, 0, 0);
}
@ -424,6 +440,7 @@ EVENT void menuVectorFileOpen(GtkWidget *object, gpointer userData) {
in = fopen(self->filename, "rt");
if (in != NULL) {
self->buffer[0] = 0;
while (getline(&line, &len, in) != -1) {
switch (count) {
case 0: // Version Number
@ -440,13 +457,15 @@ EVENT void menuVectorFileOpen(GtkWidget *object, gpointer userData) {
break;
default: // Code for editor
SSM(SCI_ADDTEXT, strlen(line), (sptr_t)line);
utilEnsureBufferSize((unsigned char **)&self->buffer, &self->bufferLength, strlen(self->buffer) + strlen(line));
strcat(self->buffer, line);
break;
}
count++;
}
fclose(in);
if (line != NULL) DEL(line);
SSM(SCI_ADDTEXT, strlen(self->buffer), (sptr_t)self->buffer);
setDirty(self, FALSE); // Do again - loading text marks us dirty.
} else {
//***TODO*** Something bad happened.
@ -528,26 +547,31 @@ EVENT void menuVectorFileSaveAs(GtkWidget *object, gpointer userData) {
EVENT void menuVectorHelpVector(GtkWidget *object, gpointer userData) {
(void)object;
(void)userData;
gtk_show_uri_on_window(NULL, "https://skunkworks.kangaroopunch.com/skunkworks/joeydev/-/wikis/Victor-Vector-Editor", GDK_CURRENT_TIME, NULL);
}
static void renderBytecode(VecByteCodeT *bytecode, VectorDataT *self) {
int x1;
int y1;
int x2;
int y2;
int count;
int i;
float f1;
float f2;
int index = 0;
int *stack = NULL;
int x1;
int y1;
int x2;
int y2;
int count;
int i;
float f1;
float f2;
time_t startTime;
int index = 0;
int *stack = NULL;
GtkWidget *dialog;
#define GET_BYTE (bytecode->bytes[index++])
#define GET_WORD getWord(bytecode, &index)
printf("-----------------------------------\n");
debug("-----------------------------------\n");
self->variables = NULL;
@ -557,7 +581,9 @@ static void renderBytecode(VecByteCodeT *bytecode, VectorDataT *self) {
jlDrawClear(self->jlc);
jlDrawColorSet(self->jlc, 15);
while (index < bytecode->length) {
startTime = time(NULL);
while (index < bytecode->length && difftime(time(NULL), startTime) < RENDER_TIMEOUT) {
switch (bytecode->bytes[index++]) {
case PARSE_NONE:
@ -569,7 +595,7 @@ static void renderBytecode(VecByteCodeT *bytecode, VectorDataT *self) {
y1 = word(self, GET_WORD);
x2 = word(self, GET_WORD);
y2 = word(self, GET_WORD);
printf("Box %d,%d to %d,%d\n", x1, y1, x2, y2);
debug("Box %d,%d to %d,%d\n", x1, y1, x2, y2);
jlDrawBox(self->jlc, x1, y1, x2, y2);
break;
@ -577,25 +603,25 @@ static void renderBytecode(VecByteCodeT *bytecode, VectorDataT *self) {
x1 = word(self, GET_WORD);
arrput(stack, index);
index = x1;
printf("Call %d\n", index);
debug("Call %d\n", index);
break;
case PARSE_CIRCLE:
y2 = word(self, GET_WORD);
x1 = word(self, GET_WORD);
y1 = word(self, GET_WORD);
printf("Circle %d at %d,%d\n", y2, x1, y1);
debug("Circle %d at %d,%d\n", y2, x1, y1);
jlDrawCircle(self->jlc, x1, y1, y2);
break;
case PARSE_CLEAR:
printf("Clear\n");
debug("Clear\n");
jlDrawClear(self->jlc);
break;
case PARSE_COLOR:
x1 = byte(self, GET_BYTE);
printf("Color %d\n", x1);
debug("Color %d\n", x1);
jlDrawColorSet(self->jlc, x1);
break;
@ -608,7 +634,7 @@ static void renderBytecode(VecByteCodeT *bytecode, VectorDataT *self) {
y1 = word(self, GET_WORD);
x2 = word(self, GET_WORD);
y2 = word(self, GET_WORD);
printf("Ellipse %d,%d to %d,%d\n", x1, y1, x2, y2);
debug("Ellipse %d,%d to %d,%d\n", x1, y1, x2, y2);
jlDrawEllipse(self->jlc, x1, y1, x2, y2);
break;
@ -617,66 +643,66 @@ static void renderBytecode(VecByteCodeT *bytecode, VectorDataT *self) {
y1 = word(self, GET_WORD);
x2 = byte(self, GET_BYTE);
if (x2 > 15) {
printf("Fill %d,%d\n", x1, y1);
debug("Fill %d,%d\n", x1, y1);
jlDrawFill(self->jlc, x1, y1);
} else {
printf("Fill %d,%d to %d\n", x1, y1, x2);
debug("Fill %d,%d to %d\n", x1, y1, x2);
jlDrawFillTo(self->jlc, x1, y1, x2);
}
break;
case PARSE_GOTO:
index = word(self, GET_WORD);
printf("Goto %d\n", index);
debug("Goto %d\n", index);
break;
case PARSE_IF:
x1 = word(self, GET_WORD); // arg1
y1 = byte(self, GET_BYTE); // compare
x2 = word(self, GET_WORD); // arg2
printf("If %d ", x1);
debug("If %d ", x1);
y2 = -1;
switch (y1) {
case 0: // ==
printf("==");
debug("==");
if (x1 == x2) y2 = 1;
break;
case 1: // !=
printf("!=");
debug("!=");
if (x1 != x2) y2 = 1;
break;
case 2: // <
printf("<");
debug("<");
if (x1 < x2) y2 = 1;
break;
case 3: // >
printf(">");
debug(">");
if (x1 > x2) y2 = 1;
break;
case 4: // <=
printf("<=");
debug("<=");
if (x1 <= x2) y2 = 1;
break;
case 5: // >=
printf(">=");
debug(">=");
if (x1 >= x2) y2 = 1;
break;
}
printf(" %d ", x2);
debug(" %d ", x2);
x1 = byte(self, GET_BYTE); // goto/call
x2 = word(self, GET_WORD); // label
printf(" %s %d ", (x1 == 0 ? "Goto" : "Call"), x2);
debug(" %s %d ", (x1 == 0 ? "Goto" : "Call"), x2);
if (y2 > 0) {
if (x1 == 1) arrput(stack, index);
index = x2;
printf("(true)");
debug("(true)");
}
printf("\n");
debug("\n");
break;
case PARSE_LABEL:
@ -686,16 +712,16 @@ static void renderBytecode(VecByteCodeT *bytecode, VectorDataT *self) {
count = word(self, GET_WORD);
x1 = word(self, GET_WORD);
y1 = word(self, GET_WORD);
printf("Line %d,%d", x1, y1);
debug("Line %d,%d", x1, y1);
for (i=0; i<count - 1; i++) {
x2 = word(self, GET_WORD);
y2 = word(self, GET_WORD);
printf(" to %d,%d", x2, y2);
debug(" to %d,%d", x2, y2);
jlDrawLine(self->jlc, x1, y1, x2, y2);
x1 = x2;
y1 = y2;
}
printf("\n");
debug("\n");
break;
case PARSE_MATH:
@ -709,7 +735,7 @@ static void renderBytecode(VecByteCodeT *bytecode, VectorDataT *self) {
}
x1 &= 0x7f; // Clear variable flag.
f1 = variable(self, x1);
printf("Math: %d: ", x1);
debug("Math: %d: ", x1);
switch (y1) {
case MATH_NONE:
// Well, none!
@ -717,65 +743,65 @@ static void renderBytecode(VecByteCodeT *bytecode, VectorDataT *self) {
case MATH_ASSIGN:
f1 = f2;
printf("= %f", f2);
debug("= %f", f2);
break;
case MATH_ADD:
f1 += f2;
printf("%f + %f", f1, f2);
debug("%f + %f", f1, f2);
break;
case MATH_SUBTRACT:
f1 -= f2;
printf("%f - %f", f1, f2);
debug("%f - %f", f1, f2);
break;
case MATH_MULTIPLY:
f1 *= f2;
printf("%f * %f", f1, f2);
debug("%f * %f", f1, f2);
break;
case MATH_DIVIDE:
f1 /= f2;
printf("%f / %f", f1, f2);
debug("%f / %f", f1, f2);
break;
case MATH_MOD:
f1 = modff(f1, &f2);
printf("%f mod %f", f1, f2);
debug("%f mod %f", f1, f2);
break;
case MATH_POW:
f1 = powf(f1, f2);
printf("%f pow %f", f1, f2);
debug("%f pow %f", f1, f2);
break;
case MATH_SQRT:
f1 = sqrtf(f2);
printf("%f sqrt %f", f1, f2);
debug("%f sqrt %f", f1, f2);
break;
case MATH_ABS:
f1 = fabsf(f2);
printf("%f abs %f", f1, f2);
debug("%f abs %f", f1, f2);
break;
case MATH_COS:
f1 = cosf(f2);
printf("%f cos %f", f1, f2);
debug("%f cos %f", f1, f2);
break;
case MATH_SIN:
f1 = sinf(f2);
printf("%f sin %f", f1, f2);
debug("%f sin %f", f1, f2);
break;
case MATH_TAN:
f1 = tanf(f2);
printf("%f tan %f", f1, f2);
debug("%f tan %f", f1, f2);
break;
}
printf("\n");
debug("\n");
// Make sure we have enough slots for this variable.
while (arrlen(self->variables) <= x1) {
arrput(self->variables, 0.0f);
@ -788,14 +814,14 @@ static void renderBytecode(VecByteCodeT *bytecode, VectorDataT *self) {
x2 = byte(self, GET_BYTE);
y1 = byte(self, GET_BYTE);
y2 = byte(self, GET_BYTE);
printf("Palette %d as %d,%d,%d\n", x1, x2, y1, y2);
debug("Palette %d as %d,%d,%d\n", x1, x2, y1, y2);
jlPaletteSet(self->jlc, x1, x2, y1, y2);
break;
case PARSE_PLOT:
x1 = word(self, GET_WORD);
y1 = word(self, GET_WORD);
printf("Plot %d,%d\n", x1, y1);
debug("Plot %d,%d\n", x1, y1);
jlDrawPixelSet(self->jlc, x1, y1);
break;
@ -804,13 +830,13 @@ static void renderBytecode(VecByteCodeT *bytecode, VectorDataT *self) {
y1 = word(self, GET_WORD);
x2 = word(self, GET_WORD);
y2 = word(self, GET_WORD);
printf("Rectangle %d,%d to %d,%d\n", x1, y1, x2, y2);
debug("Rectangle %d,%d to %d,%d\n", x1, y1, x2, y2);
jlDrawBoxFilled(self->jlc, x1, y1, x2, y2);
break;
case PARSE_RETURN:
index = arrpop(stack);
printf("Return %d\n", index);
debug("Return %d\n", index);
break;
} // switch
@ -828,6 +854,19 @@ static void renderBytecode(VecByteCodeT *bytecode, VectorDataT *self) {
// Refresh widget.
gtk_widget_queue_draw(self->drawVectorImage);
// Did execution time out?
if (index < bytecode->length) {
dialog = gtk_message_dialog_new(
GTK_WINDOW(self->windowData.window),
GTK_DIALOG_DESTROY_WITH_PARENT | GTK_DIALOG_MODAL,
GTK_MESSAGE_WARNING,
GTK_BUTTONS_OK,
"Rendering is taking too long! Stopped.\n\n(Did you create an infinite loop?)");
gtk_window_set_title(GTK_WINDOW(dialog), "Notice");
gtk_dialog_run(GTK_DIALOG(dialog));
gtk_widget_destroy(dialog);
}
}
@ -930,6 +969,9 @@ void winVectorCreate(void) {
self = NEW(VectorDataT);
self->windowData.closeWindow = winVectorClose;
// Set up working buffer.
utilEnsureBufferSize((unsigned char **)&self->buffer, &self->bufferLength, 1024);
// Load widgets from XML.
widgets[0] = &self->windowData.window;
widgets[1] = &self->boxVectorForEditor;
@ -968,9 +1010,7 @@ void winVectorCreate(void) {
SSM(SCI_SETTABWIDTH, 3, 0);
SSM(SCI_SETMARGINWIDTHN, 0, (int)SSM(SCI_TEXTWIDTH, STYLE_LINENUMBER, (sptr_t)"_99999"));
SSM(SCI_SETMARGINWIDTHN, 1, 16);
SSM(SCI_SETWRAPMODE, SC_WRAP_WORD, 0);
SSM(SCI_SETWRAPVISUALFLAGS, SC_WRAPVISUALFLAG_END, 0);
SSM(SCI_SETWRAPINDENTMODE, SC_WRAPINDENT_INDENT, 0);
SSM(SCI_SETWRAPMODE, SC_WRAP_NONE, 0);
SSM(SCI_SETCARETSTYLE, CARETSTYLE_BLOCK | CARETSTYLE_OVERSTRIKE_BLOCK, 0);
SSM(SCI_SETCARETFORE, 0x00ffff, 0);
SSM(SCI_STYLESETBACK, STYLE_LINENUMBER, 0x222222);
@ -1053,6 +1093,7 @@ static void winVectorDelete(gpointer userData) {
if (self->filename != NULL) DEL(self->filename);
if (self->title != NULL) DEL(self->title);
if (self->tracename != NULL) DEL(self->tracename);
if (self->buffer != NULL) DEL(self->buffer);
DEL(self);
}

View file

@ -25,12 +25,10 @@
</patterns>
</object>
<object class="GtkWindow" id="winVector">
<property name="width-request">1500</property>
<property name="height-request">600</property>
<property name="width-request">1000</property>
<property name="height-request">500</property>
<property name="can-focus">False</property>
<property name="title" translatable="yes">Vector</property>
<property name="default-width">1500</property>
<property name="default-height">600</property>
<signal name="delete-event" handler="winVectorClose" swapped="no"/>
<child>
<object class="GtkBox">
@ -56,36 +54,40 @@
<object class="GtkMenuItem" id="menuVectorFileNew">
<property name="visible">True</property>
<property name="can-focus">False</property>
<property name="label" translatable="yes">_New</property>
<property name="label" translatable="yes">New</property>
<property name="use-underline">True</property>
<signal name="activate" handler="menuVectorFileNew" swapped="no"/>
<accelerator key="n" signal="activate" modifiers="GDK_CONTROL_MASK"/>
</object>
</child>
<child>
<object class="GtkMenuItem" id="menuVectorFileOpen">
<property name="visible">True</property>
<property name="can-focus">False</property>
<property name="label" translatable="yes">_Open...</property>
<property name="label" translatable="yes">Open...</property>
<property name="use-underline">True</property>
<signal name="activate" handler="menuVectorFileOpen" swapped="no"/>
<accelerator key="o" signal="activate" modifiers="GDK_CONTROL_MASK"/>
</object>
</child>
<child>
<object class="GtkMenuItem" id="menuVectorFileSave">
<property name="visible">True</property>
<property name="can-focus">False</property>
<property name="label" translatable="yes">_Save</property>
<property name="label" translatable="yes">Save</property>
<property name="use-underline">True</property>
<signal name="activate" handler="menuVectorFileSave" swapped="no"/>
<accelerator key="s" signal="activate" modifiers="GDK_CONTROL_MASK"/>
</object>
</child>
<child>
<object class="GtkMenuItem" id="menuVectorFileSaveAs">
<property name="visible">True</property>
<property name="can-focus">False</property>
<property name="label" translatable="yes">Save _As...</property>
<property name="label" translatable="yes">Save As...</property>
<property name="use-underline">True</property>
<signal name="activate" handler="menuVectorFileSaveAs" swapped="no"/>
<accelerator key="s" signal="activate" modifiers="GDK_SHIFT_MASK | GDK_CONTROL_MASK"/>
</object>
</child>
<child>
@ -98,9 +100,10 @@
<object class="GtkMenuItem" id="menuVectorFileClose">
<property name="visible">True</property>
<property name="can-focus">False</property>
<property name="label" translatable="yes">C_lose</property>
<property name="label" translatable="yes">Close</property>
<property name="use-underline">True</property>
<signal name="activate" handler="menuVectorFileClose" swapped="no"/>
<accelerator key="F4" signal="activate" modifiers="GDK_MOD1_MASK"/>
</object>
</child>
</object>
@ -124,15 +127,17 @@
<property name="label" translatable="yes">Cut</property>
<property name="use-underline">True</property>
<signal name="activate" handler="menuVectorEditCut" swapped="no"/>
<accelerator key="x" signal="activate" modifiers="GDK_CONTROL_MASK"/>
</object>
</child>
<child>
<object class="GtkMenuItem" id="menuVectorEditCopy">
<property name="visible">True</property>
<property name="can-focus">False</property>
<property name="label" translatable="yes">_Copy</property>
<property name="label" translatable="yes">Copy</property>
<property name="use-underline">True</property>
<signal name="activate" handler="menuVectorEditCopy" swapped="no"/>
<accelerator key="c" signal="activate" modifiers="GDK_CONTROL_MASK"/>
</object>
</child>
<child>
@ -142,6 +147,7 @@
<property name="label" translatable="yes">Paste</property>
<property name="use-underline">True</property>
<signal name="activate" handler="menuVectorEditPaste" swapped="no"/>
<accelerator key="v" signal="activate" modifiers="GDK_CONTROL_MASK"/>
</object>
</child>
<child>
@ -174,6 +180,7 @@
<property name="label" translatable="yes">Vector Editor...</property>
<property name="use-underline">True</property>
<signal name="activate" handler="menuVectorHelpVector" swapped="no"/>
<accelerator key="F1" signal="activate"/>
</object>
</child>
</object>