public void CreateControls(FileDatabase database, DataEntryHandler dataEntryPropagator) { // Depending on how the user interacts with the file import process image set loading can be aborted after controls are generated and then // another image set loaded. Any existing controls therefore need to be cleared. this.ControlGrid.Children.Clear(); this.Controls.Clear(); this.ControlsByDataLabel.Clear(); DataEntryDateTime dateTimeControl = null; DataEntryUtcOffset utcOffsetControl = null; List<DataEntryControl> visibleControls = new List<DataEntryControl>(); foreach (ControlRow control in database.Controls) { // no point in generating a control if it doesn't render in the UX if (control.Visible == false) { continue; } if (control.Type == Constant.DatabaseColumn.DateTime) { dateTimeControl = new DataEntryDateTime(control, this); visibleControls.Add(dateTimeControl); } else if (control.Type == Constant.DatabaseColumn.File || control.Type == Constant.DatabaseColumn.RelativePath || control.Type == Constant.Control.Note) { // standard controls rendering as notes aren't editable by the user List<string> autocompletions = null; bool readOnly = control.Type != Constant.Control.Note; if (readOnly == false) { autocompletions = new List<string>(database.GetDistinctValuesInFileDataColumn(control.DataLabel)); } DataEntryNote noteControl = new DataEntryNote(control, autocompletions, this); noteControl.ContentReadOnly = readOnly; visibleControls.Add(noteControl); } else if (control.Type == Constant.Control.Flag || control.Type == Constant.DatabaseColumn.DeleteFlag) { DataEntryFlag flagControl = new DataEntryFlag(control, this); visibleControls.Add(flagControl); } else if (control.Type == Constant.Control.Counter) { DataEntryCounter counterControl = new DataEntryCounter(control, this); visibleControls.Add(counterControl); } else if (control.Type == Constant.Control.FixedChoice || control.Type == Constant.DatabaseColumn.ImageQuality) { DataEntryChoice choiceControl = new DataEntryChoice(control, this); visibleControls.Add(choiceControl); } else if (control.Type == Constant.DatabaseColumn.UtcOffset) { utcOffsetControl = new DataEntryUtcOffset(control, this); visibleControls.Add(utcOffsetControl); } else { Debug.Fail(String.Format("Unhandled control type {0}.", control.Type)); continue; } } if ((dateTimeControl != null) && (utcOffsetControl != null)) { dateTimeControl.ShowUtcOffset(); visibleControls.Remove(utcOffsetControl); } foreach (DataEntryControl control in visibleControls) { this.ControlGrid.Children.Add(control.Container); this.Controls.Add(control); this.ControlsByDataLabel.Add(control.DataLabel, control); } dataEntryPropagator.SetDataEntryCallbacks(this.ControlsByDataLabel); }
private async Task CloseImageSetAsync() { if (this.IsFileDatabaseAvailable()) { // persist image set properties if an image set has been opened if (this.dataHandler.FileDatabase.CurrentlySelectedFileCount > 0) { // revert to custom selections to all if (this.dataHandler.FileDatabase.ImageSet.FileSelection == FileSelection.Custom) { this.dataHandler.FileDatabase.ImageSet.FileSelection = FileSelection.All; } // sync image set properties if (this.MarkableCanvas != null) { this.dataHandler.FileDatabase.ImageSet.Options.SetFlag(ImageSetOptions.Magnifier, this.MarkableCanvas.MagnifyingGlassEnabled); } if (this.dataHandler.ImageCache != null && this.dataHandler.ImageCache.Current != null) { this.dataHandler.FileDatabase.ImageSet.MostRecentFileID = this.dataHandler.ImageCache.Current.ID; } // write image set properties to the database this.dataHandler.FileDatabase.SyncImageSetToDatabase(); // ensure custom filter operator is synchronized in state for writing to user's registry this.state.CustomSelectionTermCombiningOperator = this.dataHandler.FileDatabase.CustomSelection.TermCombiningOperator; } // discard the image set and reset UX for no open image set/no selected files this.dataHandler.Dispose(); this.dataHandler = null; await this.EnableOrDisableMenusAndControlsAsync(); } this.state.Reset(); this.MenuEditRedo.IsEnabled = this.state.UndoRedoChain.CanRedo; this.MenuEditUndo.IsEnabled = this.state.UndoRedoChain.CanUndo; }
/// <summary> /// Load the specified database template and then the associated file database. /// </summary> /// <param name="templateDatabasePath">Fully qualified path to the template database file.</param> /// <returns>true only if both the template and image database file are loaded (regardless of whether any images were loaded), false otherwise</returns> /// <remarks>This method doesn't particularly need to be public. But making it private imposes substantial complexity in invoking it via PrivateObject /// in unit tests.</remarks> public async Task<bool> TryOpenTemplateAndBeginLoadFoldersAsync(string templateDatabasePath) { // Try to create or open the template database TemplateDatabase templateDatabase; if (TemplateDatabase.TryCreateOrOpen(templateDatabasePath, out templateDatabase) == false) { // notify the user the template couldn't be loaded MessageBox messageBox = new MessageBox("Carnassial could not load the template.", this); messageBox.Message.Problem = "Carnassial could not load " + Path.GetFileName(templateDatabasePath) + Environment.NewLine; messageBox.Message.Reason = "\u2022 The template was created with the Timelapse template editor instead of the Carnassial editor." + Environment.NewLine; messageBox.Message.Reason = "\u2022 The template may be corrupted or somehow otherwise invalid."; messageBox.Message.Solution = String.Format("You may have to recreate the template, restore it from the {0} folder, or use another copy of it if you have one.", Constant.File.BackupFolder); messageBox.Message.Result = "Carnassial won't do anything. You can try to select another template file."; messageBox.Message.Hint = "If the template can't be opened in a SQLite database editor the file is corrupt."; messageBox.Message.StatusImage = MessageBoxImage.Error; messageBox.ShowDialog(); this.state.MostRecentImageSets.TryRemove(templateDatabasePath); this.MenuFileRecentImageSets_Refresh(); return false; } // Try to get the file database file path // importImages will be true if it's a new image database file (meaning the user will be prompted import some images) string fileDatabaseFilePath; bool addFiles; if (this.TrySelectDatabaseFile(templateDatabasePath, out fileDatabaseFilePath, out addFiles) == false) { // No image database file was selected templateDatabase.Dispose(); return false; } // Before running from an existing file database, check the controls in the template database are compatible with those // of the file database. FileDatabase fileDatabase; if (FileDatabase.TryCreateOrOpen(fileDatabaseFilePath, templateDatabase, this.state.OrderFilesByDateTime, this.state.CustomSelectionTermCombiningOperator, out fileDatabase) == false) { // notify the user the database couldn't be loaded MessageBox messageBox = new MessageBox("Carnassial could not load the database.", this); messageBox.Message.Problem = "Carnassial could not load " + Path.GetFileName(fileDatabaseFilePath) + Environment.NewLine; messageBox.Message.Reason = "\u2022 The database was created with Timelapse instead of Carnassial." + Environment.NewLine; messageBox.Message.Reason = "\u2022 The database may be corrupted or somehow otherwise invalid."; messageBox.Message.Solution = String.Format("You may have to recreate the database, restore it from the {0} folder, or use another copy of it if you have one.", Constant.File.BackupFolder); messageBox.Message.Result = "Carnassial won't do anything. You can try to select another template or database file."; messageBox.Message.Hint = "If the database can't be opened in a SQLite database editor the file is corrupt."; messageBox.Message.StatusImage = MessageBoxImage.Error; messageBox.ShowDialog(); return false; } templateDatabase.Dispose(); if (fileDatabase.ControlSynchronizationIssues.Count > 0) { TemplateSynchronization templatesNotCompatibleDialog = new TemplateSynchronization(fileDatabase.ControlSynchronizationIssues, this); bool? result = templatesNotCompatibleDialog.ShowDialog(); if (result == true) { // user indicated not to update to the current template so exit. Application.Current.Shutdown(); return false; } // user indicated to run with the stale copy of the template found in the image database } // valid template and file database loaded // generate and render the data entry controls regardless of whether there are actually any files in the file database. this.dataHandler = new DataEntryHandler(fileDatabase); this.DataEntryControls.CreateControls(fileDatabase, this.dataHandler); this.SetUserInterfaceCallbacks(); this.MenuFileRecentImageSets_Refresh(); this.state.MostRecentFileAddFolderPath = fileDatabase.FolderPath; this.state.MostRecentImageSets.SetMostRecent(templateDatabasePath); this.Title = Path.GetFileName(fileDatabase.FilePath) + " - " + Constant.MainWindowBaseTitle; // If this is a new file database, try to load files (if any) from the folder... if (addFiles) { FolderLoad folderLoad = new FolderLoad(); folderLoad.FolderPaths.Add(this.FolderPath); await this.TryBeginFolderLoadAsync(folderLoad); } await this.OnFolderLoadingCompleteAsync(false); return true; }
public void CreateReuseControlsAndPropagate() { List<DatabaseExpectations> databaseExpectations = new List<DatabaseExpectations>() { new DatabaseExpectations() { FileName = Constant.File.DefaultFileDatabaseFileName, TemplateDatabaseFileName = TestConstant.File.DefaultTemplateDatabaseFileName, ExpectedColumns = TestConstant.DefaultFileDataColumns, ExpectedControls = TestConstant.DefaultFileDataColumns.Count - 6 } }; foreach (DatabaseExpectations databaseExpectation in databaseExpectations) { FileDatabase fileDatabase = this.CreateFileDatabase(databaseExpectation.TemplateDatabaseFileName, databaseExpectation.FileName); DataEntryHandler dataHandler = new DataEntryHandler(fileDatabase); DataEntryControls controls = new DataEntryControls(); controls.CreateControls(fileDatabase, dataHandler); Assert.IsTrue(controls.ControlsByDataLabel.Count == databaseExpectation.ExpectedControls, "Expected {0} controls to be generated but {1} were.", databaseExpectation.ExpectedControls, controls.ControlsByDataLabel.Count); // check copies aren't possible when the image enumerator's not pointing to an image foreach (DataEntryControl control in controls.Controls) { Assert.IsFalse(dataHandler.IsCopyForwardPossible(control)); Assert.IsFalse(dataHandler.IsCopyFromLastNonEmptyValuePossible(control)); } // check only copy forward is possible when enumerator's on first image List<FileExpectations> fileExpectations = this.PopulateDefaultDatabase(fileDatabase); Assert.IsTrue(dataHandler.ImageCache.MoveNext()); List<DataEntryControl> copyableControls = controls.Controls.Where(control => control.Copyable).ToList(); foreach (DataEntryControl control in copyableControls) { Assert.IsTrue(dataHandler.IsCopyForwardPossible(control)); Assert.IsFalse(dataHandler.IsCopyFromLastNonEmptyValuePossible(control)); } List<DataEntryControl> notCopyableControls = controls.Controls.Where(control => control.Copyable == false).ToList(); foreach (DataEntryControl control in notCopyableControls) { Assert.IsFalse(dataHandler.IsCopyForwardPossible(control)); Assert.IsFalse(dataHandler.IsCopyFromLastNonEmptyValuePossible(control)); } // check only copy last is possible when enumerator's on last image // check also copy last is not possible if no previous instance of the field has been filled out while (dataHandler.ImageCache.CurrentRow < fileExpectations.Count - 1) { Assert.IsTrue(dataHandler.ImageCache.MoveNext()); } foreach (DataEntryControl control in copyableControls) { Assert.IsFalse(dataHandler.IsCopyForwardPossible(control)); if (control.DataLabel == TestConstant.CarnivoreDatabaseColumn.Pelage || control.DataLabel == TestConstant.DefaultDatabaseColumn.ChoiceNotVisible || control.DataLabel == TestConstant.DefaultDatabaseColumn.ChoiceWithCustomDataLabel || control.DataLabel == TestConstant.DefaultDatabaseColumn.Choice3 || control.DataLabel == TestConstant.DefaultDatabaseColumn.Counter3 || control.DataLabel == TestConstant.DefaultDatabaseColumn.NoteWithCustomDataLabel) { Assert.IsFalse(dataHandler.IsCopyFromLastNonEmptyValuePossible(control)); } else { Assert.IsTrue(dataHandler.IsCopyFromLastNonEmptyValuePossible(control)); } } // propagation methods not covered due to requirement of UX interaction // dataHandler.CopyForward(control); // dataHandler.CopyFromLastValue(control); // dataHandler.CopyToAll(control); // verify roundtrip of fields subject to copy/paste and analysis assignment Assert.IsTrue(dataHandler.ImageCache.TryMoveToFile(0)); ImageRow firstFile = fileDatabase.Files[0]; FileExpectations firstFileExpectations = fileExpectations[0]; Dictionary<string, object> firstFileValuesByDataLabel = firstFile.AsDictionary(); Assert.IsTrue(firstFileValuesByDataLabel.Count == databaseExpectation.ExpectedColumns.Count); foreach (KeyValuePair<string, object> fileValue in firstFileValuesByDataLabel) { DataEntryControl control; if (controls.ControlsByDataLabel.TryGetValue(fileValue.Key, out control)) { control.SetValue(firstFileValuesByDataLabel[fileValue.Key]); } } TimeZoneInfo imageSetTimeZone = fileDatabase.ImageSet.GetTimeZone(); firstFileExpectations.Verify(firstFile, imageSetTimeZone); // verify roundtrip of fields via display string foreach (KeyValuePair<string, DataEntryControl> control in controls.ControlsByDataLabel) { string displayString = firstFile.GetValueDisplayString(control.Value); control.Value.SetContentAndTooltip(displayString); } firstFileExpectations.Verify(firstFile, imageSetTimeZone); // verify availability of database strings foreach (string dataLabel in databaseExpectation.ExpectedColumns) { string databaseString = firstFile.GetValueDatabaseString(dataLabel); } } }