/** \file trknote.c * Track notes "postits" */ /* XTrkCad - Model Railroad CAD * Copyright (C) 2005 Dave Bullis, 2018 Martin Fischer * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program 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 this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA */ #include "common.h" #include "cundo.h" #include "custom.h" #include "dynstring.h" #include "fileio.h" #include "misc.h" #include "note.h" #include "param.h" #include "track.h" #include "include/utf8convert.h" EXPORT TRKTYP_T T_NOTE = -1; static wDrawBitMap_p note_bm, link_bm, document_bm; typedef struct { char ***xpm; int OP; char * shortName; char * cmdName; char * helpKey; long acclKey; } trknoteData_t; #include "bitmaps/sticky-note.xpm3" #include "bitmaps/sticky-link.xpm3" #include "bitmaps/sticky-doc.xpm3" static trknoteData_t noteTypes[] = { { sticky_note_xpm3, OP_NOTETEXT, N_("Note"), N_("Text Note"), "cmdTextNote", ACCL_NOTE }, { sticky_link_xpm3, OP_NOTELINK, N_("Link"), N_("Weblink"), "cmdLinkNote", 0L }, { sticky_doc_xpm3, OP_NOTEFILE, N_("Document"), N_("Document"), "cmdFileNote", 0L }, }; static long curNoteType; static unsigned layerSave; static coOrd posSave; #define NOTETYPESCOUNT COUNT(noteTypes) /***************************************************************************** * NOTE OBJECT */ EXPORT track_p NewNote(wIndex_t index, coOrd p, enum noteCommands command ) { track_p t; struct extraDataNote_t * xx; t = NewTrack(index, T_NOTE, 0, sizeof *xx); xx = GET_EXTRA_DATA( t, T_NOTE, extraDataNote_t ); xx->pos = p; xx->op = command; SetBoundingBox(t, p, p); return t; } /** * Draw the icon for a note into the drawing area * * \param t IN note * \param d IN drawing environment * \param color IN color for ico */ static void DrawNote(track_p t, drawCmd_p d, wDrawColor color) { struct extraDataNote_t *xx = GET_EXTRA_DATA( t, T_NOTE, extraDataNote_t ); coOrd p[5]; int type[5]; if ((d->options & DC_SIMPLE) || mainD.scale >= 16) { //while the icon is moved, draw a square with a lopped off corner //because CmdMove draws all selected object into tempSeg and //tempSegDrawFuncs doesn't have a BitMap drawing func DIST_T dist; dist = 0.8 + 0.1*(mainD.scale-16)/4; p[0].x = p[1].x = xx->pos.x - dist; p[2].x = p[3].x = p[4].x = xx->pos.x + dist; p[1].y = p[2].y = xx->pos.y - dist; p[3].y = p[4].y = p[0].y = xx->pos.y + dist; p[3].y = p[3].y - (dist/2); p[4].x = p[4].x - (dist/2); for (int i=0; i<5; i++) { type[i] = 0; } DrawPoly(d, 5, p, type, color, 0, DRAW_CLOSED); DrawPoly(d, 5, p, type, drawColorGold, 0, DRAW_FILL); } else { // draw a bitmap for static object wDrawBitMap_p bm; if (xx->op == OP_NOTELINK ||(inDescribeCmd && curNoteType == OP_NOTELINK)) { bm = link_bm; } else { if (xx->op == OP_NOTEFILE || (inDescribeCmd && curNoteType == OP_NOTEFILE)) { bm = document_bm; } else { bm = note_bm; } } DrawBitMap(d, xx->pos, bm, color); } } static DIST_T DistanceNote(track_p t, coOrd * p) { struct extraDataNote_t *xx = GET_EXTRA_DATA( t, T_NOTE, extraDataNote_t ); DIST_T d; d = FindDistance(*p, xx->pos); if (d < 3.0*(mainD.scale/12.0)) { return d; } return DIST_INF; } static void DeleteNote(track_p t) { struct extraDataNote_t *xx = GET_EXTRA_DATA( t, T_NOTE, extraDataNote_t ); switch (xx->op) { case OP_NOTETEXT: if (xx->noteData.text) { MyFree(xx->noteData.text); } break; case OP_NOTEFILE: if (xx->noteData.fileData.path) { MyFree(xx->noteData.fileData.path); } if (xx->noteData.fileData.title) { MyFree(xx->noteData.fileData.title); } break; case OP_NOTELINK: if (xx->noteData.linkData.title) { MyFree(xx->noteData.linkData.title); } if (xx->noteData.linkData.url) { MyFree(xx->noteData.linkData.url); } break; default: break; } } #if 0 /** * Get the delimited marker for the current note. Markers start and end with * a delimiter. The marker itself is a single digit number. For plain text notes * no marker is used for backwards compatibility * * \param command IN the note's command code * \return a pointer to the marker string. */ static char * GetNoteMarker(enum noteCommands command ) { static char marker[2 * sizeof(DELIMITER) + 3]; switch (command) { case OP_NOTEFILE: case OP_NOTELINK: sprintf(marker, DELIMITER "%d" DELIMITER, command); break; case OP_NOTETEXT: default: *marker = '\0'; break; } return(marker); } #endif /** * Write the note to file. Handles the complete syntax for a note statement * * \param t IN pointer to the note track element * \param f IN file handle for writing * \return TRUE for success */ static BOOL_T WriteNote(track_p t, FILE * f) { struct extraDataNote_t *xx = GET_EXTRA_DATA( t, T_NOTE, extraDataNote_t ); BOOL_T rc = TRUE; #ifdef UTFCONVERT unsigned strings2convert = 1; #endif rc &= fprintf(f, "NOTE %d %u 0 0 %0.6f %0.6f 0 %d", GetTrkIndex(t), GetTrkLayer(t), xx->pos.x, xx->pos.y, xx->op )>0; char *s[2] = { NULL, NULL }; switch (xx->op) { case OP_NOTETEXT: s[0]=ConvertToEscapedText( xx->noteData.text ); break; case OP_NOTELINK: s[0]=ConvertToEscapedText( xx->noteData.linkData.url ); s[1]=ConvertToEscapedText( xx->noteData.linkData.title ); #ifdef UTFCONVERT strings2convert = 2; #endif break; case OP_NOTEFILE: s[0]=ConvertToEscapedText( xx->noteData.fileData.path ); s[1]=ConvertToEscapedText( xx->noteData.fileData.title ); #ifdef UTFCONVERT strings2convert = 2; #endif break; default: CHECKMSG( FALSE, ( "WriteNote: %d", xx->op ) ); } #ifdef UTFCONVERT for ( unsigned int inx = 0; inx < strings2convert; inx++ ) { if ( RequiresConvToUTF8( s[inx] ) ) { wSystemToUTF8 ( s[inx], message, sizeof message ); MyFree( s[inx] ); s[inx] = MyStrdup( message ); } } #endif rc &= fprintf( f, " \"%s\"", s[0] )>0; MyFree(s[0]); if ( s[1] ) { rc &= fprintf( f, " \"%s\"", s[1] )>0; MyFree( s[1] ); } rc &= fprintf( f, "\n" )>0; return rc; } /** * Read a track note aka postit * * \param line */ static BOOL_T ReadTrackNote(char *line) { track_p t; int size; char * cp; struct extraDataNote_t *xx; wIndex_t index; wIndex_t layer; coOrd pos; DIST_T elev; char *noteText; enum noteCommands noteType; char * sText; if (!GetArgs(line + 5, paramVersion < 3 ? "XXpYdc" : paramVersion < 9 ? "dL00pYdc" : "dL00pfdc", &index, &layer, &pos, &elev, &size, &cp)) { return FALSE; } if ( paramVersion >= VERSION_INLINENOTE ) { noteType = size; t = NewNote(index, pos, noteType); SetTrkLayer(t, layer); xx = GET_EXTRA_DATA( t, T_NOTE, extraDataNote_t ); switch (noteType) { case OP_NOTETEXT: if ( !GetArgs( cp, "qc", &sText, &cp ) ) { return FALSE; } #ifdef UTFCONVERT ConvertUTF8ToSystem( sText ); #endif xx->noteData.text = sText; break; case OP_NOTELINK: if ( !GetArgs( cp, "qc", &sText, &cp ) ) { return FALSE; } #ifdef UTFCONVERT ConvertUTF8ToSystem( sText ); #endif xx->noteData.linkData.url = sText; if ( !GetArgs( cp, "qc", &sText, &cp ) ) { return FALSE; } #ifdef UTFCONVERT ConvertUTF8ToSystem( sText ); #endif xx->noteData.linkData.title = sText; break; case OP_NOTEFILE: if ( !GetArgs( cp, "qc", &sText, &cp ) ) { return FALSE; } #ifdef UTFCONVERT ConvertUTF8ToSystem( sText ); #endif xx->noteData.fileData.path = sText; if ( !GetArgs( cp, "qc", &sText, &cp ) ) { return FALSE; } #ifdef UTFCONVERT ConvertUTF8ToSystem( sText ); #endif xx->noteData.fileData.title = sText; xx->noteData.fileData.inArchive = FALSE; break; default: CHECKMSG( FALSE, ( "ReadNote: %d", noteType ) ); } } else { noteText = ReadMultilineText(); noteType = OP_NOTETEXT; if( !strncmp(noteText, DELIMITER, strlen( DELIMITER )) && !strncmp(noteText + strlen(DELIMITER) + 1, DELIMITER, strlen(DELIMITER)) && noteText[strlen(DELIMITER)] - '0' > 0 && noteText[strlen(DELIMITER)] - '0' <= OP_NOTEFILE) { noteType = noteText[strlen(DELIMITER)] - '0'; } t = NewNote(index, pos, noteType); SetTrkLayer(t, layer); xx = GET_EXTRA_DATA( t, T_NOTE, extraDataNote_t ); switch (noteType) { case OP_NOTETEXT: xx->noteData.text = MyStrdup(noteText); break; case OP_NOTELINK: { char *ptr; ptr = strtok(noteText, " "); xx->noteData.linkData.url = MyStrdup(ptr + 2 * strlen(DELIMITER) + 1); xx->noteData.linkData.title = MyStrdup(noteText + strlen(ptr) + 1); break; } case OP_NOTEFILE: { char *ptr; ptr = strtok(noteText + 2 * strlen(DELIMITER) + 1, "\""); xx->noteData.fileData.path = MyStrdup(ptr); xx->noteData.fileData.title = MyStrdup(ptr + strlen(ptr) + 2 ); xx->noteData.fileData.inArchive = FALSE; break; } } MyFree(noteText); } return TRUE; } /** * Handle reading of NOTE * * \param line IN complete line with NOTE statement */ static BOOL_T ReadNote(char * line) { if (strncmp(line, "NOTE MAIN", 9) == 0) { return ReadMainNote(line); } else { return ReadTrackNote(line); } } static void MoveNote(track_p trk, coOrd orig) { struct extraDataNote_t * xx = GET_EXTRA_DATA( trk, T_NOTE, extraDataNote_t ); xx->pos.x += orig.x; xx->pos.y += orig.y; SetBoundingBox(trk, xx->pos, xx->pos); } static void RotateNote(track_p trk, coOrd orig, ANGLE_T angle) { struct extraDataNote_t * xx = GET_EXTRA_DATA( trk, T_NOTE, extraDataNote_t ); Rotate(&xx->pos, orig, angle); SetBoundingBox(trk, xx->pos, xx->pos); } static void RescaleNote(track_p trk, FLOAT_T ratio) { struct extraDataNote_t * xx = GET_EXTRA_DATA( trk, T_NOTE, extraDataNote_t ); xx->pos.x *= ratio; xx->pos.y *= ratio; } static void DescribeNote(track_p trk, char * str, CSIZE_T len) { if (IsLinkNote(trk)) { DescribeLinkNote(trk, str, len); } else { if (IsFileNote(trk)) { DescribeFileNote(trk, str, len); } else { DescribeTextNote(trk, str, len); } } } static void ActivateNote(track_p trk) { if (IsLinkNote(trk) ) { ActivateLinkNote(trk); } if (IsFileNote(trk)) { ActivateFileNote(trk); } } static BOOL_T QueryNote( track_p trk, int query ) { switch ( query ) { case Q_IS_ACTIVATEABLE:; if (IsFileNote(trk)) { return TRUE; } if (IsLinkNote(trk)) { return TRUE; } break; default: return FALSE; } return FALSE; } static wBool_t CompareNote( track_cp trk1, track_cp trk2 ) { struct extraDataNote_t *xx1 = GET_EXTRA_DATA( trk1, T_NOTE, extraDataNote_t ); struct extraDataNote_t *xx2 = GET_EXTRA_DATA( trk2, T_NOTE, extraDataNote_t ); char * cp = message + strlen(message); REGRESS_CHECK_POS( "Pos", xx1, xx2, pos ) REGRESS_CHECK_INT( "Op", xx1, xx2, op ) return TRUE; } static trackCmd_t noteCmds = { "NOTE", DrawNote, DistanceNote, DescribeNote, DeleteNote, WriteNote, ReadNote, MoveNote, RotateNote, RescaleNote, NULL, /* audit */ NULL, /* getAngle */ NULL, /* split */ NULL, /* traverse */ NULL, /* enumerate */ NULL, /* redraw */ NULL, /*trim*/ NULL, /*merge*/ NULL, /*modify*/ NULL, /*getLength*/ NULL, /*getTrackParams*/ NULL, /*moveEndPt*/ QueryNote, /*query*/ NULL, /*ungroup*/ NULL, /*flip*/ NULL, /*drawPositionIndicator*/ NULL, /*advancePositionIndicator*/ NULL, /*checkTraverse*/ NULL, /*makeParallel*/ NULL, /*drawDesc*/ NULL, /*rebuildSegs*/ NULL, /*replayData*/ NULL, /*storeData*/ ActivateNote, CompareNote }; /***************************************************************************** * NOTE COMMAND */ static STATUS_T CmdNote(wAction_t action, coOrd pos) { static coOrd oldPos; static int state_on = FALSE; track_p trk; switch (action) { case C_START: InfoMessage(_("Place a note on the layout")); curNoteType = VP2L(commandContext); return C_CONTINUE; case C_DOWN: state_on = TRUE; oldPos = pos; return C_CONTINUE; case C_MOVE: oldPos = pos; return C_CONTINUE; case C_UP: UndoStart(_("New Note"), "New Note"); state_on = FALSE; switch (curNoteType) { case OP_NOTETEXT: NewTextNoteUI(pos); break; case OP_NOTELINK: NewLinkNoteUI(pos); break; case OP_NOTEFILE: NewFileNoteUI(pos); break; } return C_CONTINUE; case C_REDRAW: if (state_on) { switch (curNoteType) { case OP_NOTETEXT: DrawBitMap(&tempD, oldPos, note_bm, normalColor); break; case OP_NOTELINK: DrawBitMap(&tempD, oldPos, link_bm, normalColor); break; case OP_NOTEFILE: DrawBitMap(&tempD, oldPos, document_bm, normalColor); break; } } return C_CONTINUE; case C_CANCEL: DescribeCancel(); state_on = FALSE; return C_CONTINUE; } return C_INFO; } #include "bitmaps/note.xbm" #include "bitmaps/link.xbm" #include "bitmaps/clip.xbm" // RWS not used #include "bitmaps/note.xpm" void InitTrkNote(wMenu_p menu) { note_bm = wDrawBitMapCreate(mainD.d, note_width, note_width, 8, 8, note_bits); link_bm = wDrawBitMapCreate(mainD.d, note_width, note_width, 8, 8, link_bits); document_bm = wDrawBitMapCreate(mainD.d, note_width, note_width, 8, 8, clip_bits); ButtonGroupBegin(_("Notes"), "cmdNoteCmd", _("Add notes")); for (int i = 0; i < NOTETYPESCOUNT; i++) { trknoteData_t *nt; wIcon_p icon; nt = noteTypes + i; icon = wIconCreatePixMap(nt->xpm[iconSize]); AddMenuButton(menu, CmdNote, nt->helpKey, _(nt->cmdName), icon, LEVEL0_50, IC_STICKY | IC_POPUP2, nt->acclKey, I2VP(nt->OP)); } ButtonGroupEnd(); T_NOTE = InitObject(¬eCmds); }