This week we can start taking a look at the inner working of how Beta 1 could potentially work. This is a more complex update, it can just talk about the structure in a more programming oriented manner.
The Simplified Summary
The logic decided the first elements to add would be the paragraphs and chapters. The important addition here, which came later, was to have the possibility to press “enter” to add the paragraphs.
There were a few bugs I brought up in the creation of the additions, this lead to adjusting the code structure. Over, and over…
Which, as you would understand meant I finally got tired of the 640 by 480 window for NaNoE. It brought, quite quickly I might add, the need for a larger window. The UI stayed the same tiny size, it was just when writing paragraphs that were longer it needed more space by default.
The last thing was the important “enter” command. This took several hours to perfect, it uses a completely different mapping in WPF for commands than I have ever seen.
public partial class App : Application
{
private void TextBox_Loaded(object sender, RoutedEventArgs e)
{
var txt = (TextBox)sender;
txt.Focus();
}
}
Models, view models, functions, database interactions, you name it. I played with all of them, and each just grew too large. I felt there isn’t a need for 20 to 40 lines of code to almost get this to work. The difficulty was literally using the item style for the WritingElement to bind to a function easily.
It turned into a very simple addition to the main App class! This introduced a bug for how I would love the EndView to run.
The structure is finally where the 1st Beta can start to become usable, so in a hopeful manner it could be ready soon, I hope.
Design Discussion
A developer will note it is slightly different from the first post, this just came from simple ideas for how we can use the data and commands. Yes, adding commands. This lead to my first “design flaw,” we don’t need CommandBase anymore. It was easier to just map directly with a System.Windows.Input.ICommand for the WPF data binding.
This can lead to just stepping through the structure. We need a simple interface to use for writing our novels if we use NaNoE. So, it leads to the ideas I’m currently working on.
You will note, we currently stay on the EndView for ourselves. This is the name I gave for the view of the end of the novel. This being since it is the easiest to design with similarities to NaNoE v1. It’s essentially where the idea for the plan came for v2.
You will note, no doubt, that it is a very small system for interaction. We will find positions of notes, or bookmarks in places we need to work quite easily. That is how the initial design came for the structure of the database.
/// <summary>
/// Create database tables
/// </summary>
private void CreateTables()
{
// 0 - Chapter
// 1 - Paragraph
// 2 - Note
// 3 - Bookmark
string elementTableCreate = "CREATE TABLE elements (idbefore int, idafter int, type int, externalid int)";
ExecSQLNonQuery(elementTableCreate);
string paragraphTableCreate = "CREATE TABLE paragraphs (content text, flagged bool)";
ExecSQLNonQuery(paragraphTableCreate);
string noteTableCreate = "CREATE TABLE notes (content text)";
ExecSQLNonQuery(noteTableCreate);
string bookmarkTableCreate = "CREATE TABLE bookmarks (content text)";
ExecSQLNonQuery(bookmarkTableCreate);
}
Using the simple naming convention above we can understand what we use in anywhere. We use elements to map where everything is. Our map of the database elements will begin where idbefore is 0, and lead to the end of the map where idafter is 0.
I debated making it require a table for the chapters, just so you can give your chapters titles. Thought about it a little more, and decided against it. The idea for this system will allow us to eventually add “movement” for ourselves.
So, this means a future feature will be moving written segments of the novel around. Perhaps, you wanted Joe Soap to do the work requirement 3 chapters ago? It would be nice to have movement in the future. Hence, it’s something planned for a future version of the beta eventually.
This definitely means we have a manageable way to, in code, navigate through anything in the novel. This leads to considering the interaction.
The first step would be walking into the MVVM design for the system. The models should be quite simple to understand:
- BookMarkModel
- ModelBase
- NoteModel
- ParagraphModel
- WritingModel
Though, wait, wouldn’t there be a ChapterModel in this case? In the simplest terms it should be easy to understand. Our map has a “type” and only “asks” for data elsewhere if it is an “important” element, so to speak.
We interacted with the look of the views before, so that isn’t too important to share in the update this week. The important thing to share this week is I realized I’m definitely making too many instances in memory of the Commands. Every time it would make a new WritingElement in memory, for instance, it would create a new instance of that class.
class CommandMap
{
/// <summary>
/// Instantiate the commands
/// </summary>
public CommandMap()
{
_runAddChapter = new RunAddChapterCommand();
_runAddParagraph = new RunAddParagraphCommand();
_runAddBookmark = new RunAddBookmarkCommand();
_runAddNote = new RunAddNoteCommand();
_runDelete = new RunDeleteCommand();
_runEdit = new RunEditCommand();
}
/// <summary>
/// Add Chapter Command
/// </summary>
private ICommand _runAddChapter;
public ICommand RunAddChapter
{
get { return _runAddChapter; }
}
/// <summary>
/// Add paragraph command
/// </summary>
private ICommand _runAddParagraph;
public ICommand RunAddParagraph
{
get { return _runAddParagraph; }
}
/// <summary>
/// Add bookmark command
/// </summary>
ICommand _runAddBookmark;
public ICommand RunAddBookmark
{
get { return _runAddBookmark; }
}
/// <summary>
/// Add note command
/// </summary>
private ICommand _runAddNote;
public ICommand RunAddNote
{
get { return _runAddNote; }
}
/// <summary>
/// Delete command
/// </summary>
private ICommand _runDelete;
public ICommand RunDelete
{
get { return _runDelete; }
}
/// <summary>
/// Edit command
/// </summary>
private ICommand _runEdit;
public ICommand RunEdit
{
get { return _runEdit; }
}
}
You will note, I added a reference to the command map in the database manager. Essentially we only interact with the SQLite database for the novel. These commands can fit in quite easily through the WPF data binding. The xaml for a Chapter view can show it quite easily.
<!-- Chapters -->
<DataTemplate DataType="{x:Type models:ModelBase}">
<Grid HorizontalAlignment="Stretch" VerticalAlignment="Stretch">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="1*" />
<ColumnDefinition Width="10" />
<ColumnDefinition Width="100" />
<ColumnDefinition Width="100" />
</Grid.ColumnDefinitions>
<Grid HorizontalAlignment="Stretch" VerticalAlignment="Stretch">
<Label Content="Chapter" Foreground="Blue" FontSize="22"/> </Grid>
<Grid Grid.Column="2">
<Grid.RowDefinitions>
<RowDefinition Height="20" />
<RowDefinition Height="20" />
<RowDefinition Height="20" />
<RowDefinition Height="23" />
</Grid.RowDefinitions>
<Button CommandParameter="{Binding ID}" Command="{Binding Commands.RunAddChapter}" Grid.Row="0" Content="Add Ch. \/" />
<Button CommandParameter="{Binding ID}" Command="{Binding Commands.RunAddParagraph}" Grid.Row="1" Content="Add Pa. \/" />
<Button CommandParameter="{Binding ID}" Command="{Binding Commands.RunDelete}" Grid.Row="2" Content="Del Item" Background="Red"/>
</Grid>
<Grid Grid.Column="3">
<Grid.RowDefinitions>
<RowDefinition Height="20" />
<RowDefinition Height="20" />
<RowDefinition Height="20" />
</Grid.RowDefinitions>
<Button CommandParameter="{Binding ID}" Command="{Binding Commands.RunEdit}" Grid.Row="0" Content="Edit" Background="Yellow"/>
<Button CommandParameter="{Binding ID}" Command="{Binding Commands.RunAddNote}" Grid.Row="1" Content="Add Note \/" Foreground="Pink"/>
<Button CommandParameter="{Binding ID}" Command="{Binding Commands.RunAddBookmark}" Grid.Row="2" Content="Add Bookmark \/" Foreground="Orange" />
</Grid>
</Grid>
</DataTemplate>
The first Grid will be where we put the content for each element in, the other two appear in the columns to add the interaction buttons. Each button gets a binding to the Command on the map, and similarly the element only needs to send its ID as CommandParameter.
Just in case, it will make more sense when you glance at a command yourself.
class RunAddChapterCommand : System.Windows.Input.ICommand
{
public event EventHandler CanExecuteChanged;
public bool CanExecute(object parameter)
{
return true;
}
public void Execute(object parameter)
{
// Where on map
var pos = int.Parse(parameter.ToString());
DBManager.Instance.InsertChapter(pos);
Navigator.Instance.Goto("novelend");
}
}
No doubt you will know that we can execute it anywhere, so in any of our states. We take a look at the CommandParameter, the ID, and then we add the Chapter to that position in this case.
/// <summary>
/// Insert a chapter into the novel
/// </summary>
/// <param name="where">The ID before this position</param>
public void InsertChapter(int where)
{
int idafter = 0;
if (where != 0)
{
var after = ExecSQLQuery("SELECT idafter FROM elements WHERE rowid = " + where, 1);
idafter = int.Parse((after[0])[0].ToString());
}
else
{
where = 0;
}
InsertElement(where, idafter, 0, 0);
}
First we ask “is this the first thing we’re adding to the map?” If it is, we just place it in with “where” pointing to 0, the start of our map. Otherwise, we take a look at the element “where” to see if it has another after it in “idafter.” This would lead to adding an element in for the chapter.
- Where: idbefore
- idafter: idafter
- 0: type
- 0: externalid
This allows us to have a small amount of code to move around with the map.
/// <summary>
/// Insert an element into the novel
/// </summary>
/// <param name="where">The ID where the element will be places</param>
/// <param name="type">What the type of the element is (see CreateTables)</param>
/// <param name="external">External element ID to link to</param>
private void InsertElement(int where, int idafter, int type, int external)
{
ExecSQLNonQuery("INSERT INTO elements (idbefore, idafter, type, externalid)" +
" VALUES (" +
where + "," +
idafter + "," +
type + "," +
external +
")");
var id = GetMaxId("elements");
if (where != 0)
{
ExecSQLNonQuery("UPDATE elements SET idafter = " + id +
" WHERE rowid = " + where);
}
if (idafter != 0)
{
ExecSQLNonQuery("UPDATE elements SET idbefore = " + id +
" WHERE rowid = " + idafter);
}
int post = 0;
for (int i = 0; i < _map.Count; i++)
{
if (_map[i] == where)
{
post = i + 1;
break;
}
}
_map.Insert(post, id);
}
First, we add a new element, then we get its ID for ourselves. We update the “idafter” on the “where” element, and then we update the “idbefore” on where the “idafter” used to be.
Using, and testing, this for several days to find the way to make it work well was a little tedious. That would be why the progress was slightly slow, and a little stumped, this week.
So, the database interaction is simple. We have commands wherever we need them, we have a way to map the elements for ourselves, and I can just stick to working out implementing all the commands and views. That way, it could already be used to write a novel.
There are more thoughts I have, it would just be additional ideas to add for simplicity moving over to NaNoE v2 with your novel. I figure I should add several imports. Import *.nne, *.sqlite from v1, *.docx, to name a few. I say name a few, but I might not think of other formats to implement it for, so suggestions are welcome.
This week is just to share we are finally moving near the completion of NaNoE v2-Beta1. I know I may do slightly weird things with code, but at least sharing the ideas as they start coming out could lead to other developers suggestion adjustments and changes. The plan is to keep it simple and have an easy experience for any user using NaNoE for themselves.