From f765276ea9abd8be286a17fae45509dde749812b Mon Sep 17 00:00:00 2001 From: Greg Burri Date: Mon, 18 Jan 2016 17:33:57 +0100 Subject: [PATCH] Add a status bar. --- Parasitemia/ParasitemiaCore/MainAnalysis.fs | 2 +- Parasitemia/ParasitemiaUI/About.fs | 2 +- Parasitemia/ParasitemiaUI/GUI.fs | 112 ++++++++++++++---- .../ParasitemiaUI/ParasitemiaUI.fsproj | 4 +- Parasitemia/ParasitemiaUI/State.fs | 12 +- .../ParasitemiaUI/XAML/MainWindow.xaml | 20 +++- 6 files changed, 117 insertions(+), 35 deletions(-) diff --git a/Parasitemia/ParasitemiaCore/MainAnalysis.fs b/Parasitemia/ParasitemiaCore/MainAnalysis.fs index 846c067..0faf54a 100644 --- a/Parasitemia/ParasitemiaCore/MainAnalysis.fs +++ b/Parasitemia/ParasitemiaCore/MainAnalysis.fs @@ -23,7 +23,7 @@ let doAnalysis (img: Image) (name: string) (config: Config) (reportPr | Some f -> f percent | _ -> () - let inline buildLogWithName (text: string) = sprintf "(%s) %s" name text + let inline buildLogWithName (text: string) = sprintf "(%s) %s" name text let logWithName mess = Log.User(buildLogWithName mess) let inline logTimeWithName (text: string) (f: unit -> 'a) : 'a = Log.LogWithTime((buildLogWithName text), Severity.USER, f) diff --git a/Parasitemia/ParasitemiaUI/About.fs b/Parasitemia/ParasitemiaUI/About.fs index 5f9ce81..5f1be66 100644 --- a/Parasitemia/ParasitemiaUI/About.fs +++ b/Parasitemia/ParasitemiaUI/About.fs @@ -20,7 +20,7 @@ let showWindow (parent: Window) = let txtAbout: TextBlock = ctrl "txtAbout" let version = System.Reflection.Assembly.GetEntryAssembly().GetName().Version - let txtVersion = sprintf "%d.%d.%d" version.Major version.Minor version.Revision + let txtVersion = sprintf " %d.%d.%d" version.Major version.Minor version.Revision txtAbout.Inlines.FirstInline.ElementEnd.InsertTextInRun(txtVersion) #if DEBUG diff --git a/Parasitemia/ParasitemiaUI/GUI.fs b/Parasitemia/ParasitemiaUI/GUI.fs index 98b13ea..efe01c3 100644 --- a/Parasitemia/ParasitemiaUI/GUI.fs +++ b/Parasitemia/ParasitemiaUI/GUI.fs @@ -33,6 +33,7 @@ let run (defaultConfig: Config) (fileToOpen: string option) = let menuExit: MenuItem = ctrl "menuExit" let menuSaveFile: MenuItem = ctrl "menuSave" + let menuSaveAsFile: MenuItem = ctrl "menuSaveAs" let menuLoadFile: MenuItem = ctrl "menuOpen" let menuNewFile: MenuItem = ctrl "menuNew" let menuAddSourceImage: MenuItem = ctrl "menuAddSourceImage" @@ -42,6 +43,9 @@ let run (defaultConfig: Config) (fileToOpen: string option) = let menuHightlightRBC: MenuItem = ctrl "menuHightlightRBC" let menuAbout: MenuItem = ctrl "menuAbout" + let txtDocumentStatus: TextBlock = ctrl "txtDocumentStatus" + let txtMessageStatus: TextBlock = ctrl "txtMessageStatus" + let txtPatient: TextBox = ctrl "txtPatient" let txtGlobalParasitemia: TextBox = ctrl "txtGlobalParasitemia" @@ -50,7 +54,8 @@ let run (defaultConfig: Config) (fileToOpen: string option) = let scrollViewCurrentImage: ScrollViewer = ctrl "scrollViewCurrentImage" let borderCurrentImage: Border = ctrl "borderCurrentImage" let canvasCurrentImage: Canvas = ctrl "canvasCurrentImage" - let txtImageInformation: TextBlock = ctrl "txtImageInformation" + let txtImageInformation1: TextBlock = ctrl "txtImageInformation1" + let txtImageInformation2: TextBlock = ctrl "txtImageInformation2" let scrollRBC: ScrollViewer = ctrl "scrollRBC" let stackRBC: StackPanel = ctrl "stackRBC" @@ -87,6 +92,17 @@ let run (defaultConfig: Config) (fileToOpen: string option) = frame.txtRBCNumber.Text <- rbc.num.ToString() frame + let updateDocumentStatus () = + txtDocumentStatus.Text <- if state.FilePath = "" then "" else state.FilePath + + let statusMessageTimer = Threading.DispatcherTimer() + statusMessageTimer.Tick.AddHandler(fun obj args -> statusMessageTimer.Stop(); txtMessageStatus.Text <- "") + statusMessageTimer.Interval <- TimeSpan(0, 0, 2) + let displayStatusMessage (message: string) = + txtMessageStatus.Text <- message + statusMessageTimer.Stop() + statusMessageTimer.Start() + let highlightRBCFrame (frame: Views.RBCFrame) (highlight: bool) = let rbc = frame.Tag :?> RBC if highlight @@ -101,33 +117,38 @@ let run (defaultConfig: Config) (fileToOpen: string option) = scrollViewCurrentImage.ScrollToHorizontalOffset(rbc.center.X * currentScale - scrollViewCurrentImage.ViewportWidth / 2. + borderCurrentImage.BorderThickness.Left) scrollViewCurrentImage.ScrollToVerticalOffset(rbc.center.Y * currentScale - scrollViewCurrentImage.ViewportHeight / 2. + borderCurrentImage.BorderThickness.Top) - let parasitemiaText (nbTotal: int, nbInfected: int) : string = + let percentText (nbTotal: int, nb: int) : string = if nbTotal = 0 then "" else - let percent = 100. * (float nbInfected) / (float nbTotal) - sprintf "%.1f %% (%d / %d)" percent nbInfected nbTotal + let percent = 100. * (float nb) / (float nbTotal) + sprintf "%.1f %% (%d / %d)" percent nb nbTotal let updateCurrentImageInformation () = match state.CurrentImage with | Some srcImg -> - let parasitemiaStr = parasitemiaText (state.ImageParasitemia srcImg) - txtImageInformation.Inlines.Clear() - txtImageInformation.Inlines.Add(Documents.Run("Parasitemia: ", FontWeight = FontWeights.Bold)) - txtImageInformation.Inlines.Add(parasitemiaStr) - txtImageInformation.Inlines.Add(Documents.LineBreak()) - - txtImageInformation.Inlines.Add(Documents.Run("Average erytrocyte diameter: ", FontWeight = FontWeights.Bold)) - txtImageInformation.Inlines.Add(Documents.Run(srcImg.config.RBCRadius.ToString())) - txtImageInformation.Inlines.Add(Documents.LineBreak()) - - txtImageInformation.Inlines.Add(Documents.Run("Last analysis: ", FontWeight = FontWeights.Bold)) - txtImageInformation.Inlines.Add(Documents.Run(if srcImg.dateLastAnalysis.Ticks = 0L then "" else srcImg.dateLastAnalysis.ToLocalTime().ToString())) + let parasitemiaStr = percentText (state.ImageParasitemia srcImg) + txtImageInformation1.Inlines.Clear() + txtImageInformation1.Inlines.Add(Documents.Run("Parasitemia: ", FontWeight = FontWeights.Bold)) + txtImageInformation1.Inlines.Add(parasitemiaStr) + txtImageInformation1.Inlines.Add(Documents.LineBreak()) + + txtImageInformation1.Inlines.Add(Documents.Run("Last analysis: ", FontWeight = FontWeights.Bold)) + txtImageInformation1.Inlines.Add(Documents.Run(if srcImg.dateLastAnalysis.Ticks = 0L then "" else srcImg.dateLastAnalysis.ToLocalTime().ToString())) + + txtImageInformation2.Inlines.Clear() + let alteredStr = percentText (state.ImageNbAltered srcImg) + txtImageInformation2.Inlines.Add(Documents.Run("Number of erytrocytes manually altered: ", FontWeight = FontWeights.Bold)) + txtImageInformation2.Inlines.Add(Documents.Run(alteredStr)) + txtImageInformation2.Inlines.Add(Documents.LineBreak()) + + txtImageInformation2.Inlines.Add(Documents.Run("Average erytrocyte diameter: ", FontWeight = FontWeights.Bold)) + txtImageInformation2.Inlines.Add(Documents.Run(srcImg.config.RBCRadius.ToString())) | _ -> () let updateGlobalParasitemia () = - txtGlobalParasitemia.Text <- parasitemiaText state.GlobalParasitemia + txtGlobalParasitemia.Text <- percentText state.GlobalParasitemia let updateViewportPreview () = for preview in stackPreviews.Children |> Seq.cast do @@ -237,23 +258,47 @@ let run (defaultConfig: Config) (fileToOpen: string option) = canvasCurrentImage.Children.[i].Visibility <- Visibility.Hidden | _ -> () + let askDocumentPathToSave () : string option = + let dialog = SaveFileDialog(AddExtension = true, DefaultExt = PiaZ.extension, Filter = PiaZ.filter) + + if state.FilePath <> "" + then + dialog.FileName <- FileInfo(state.FilePath).Name + elif state.PatientID <> "" + then + dialog.FileName <- state.PatientID + PiaZ.extension + + let res = dialog.ShowDialog() + if res.HasValue && res.Value then + Some dialog.FileName + else + None + let saveCurrentDocument () = try if state.FilePath = "" then - let dialog = SaveFileDialog(AddExtension = true, DefaultExt = PiaZ.extension, Filter = PiaZ.filter); - let res = dialog.ShowDialog() - if res.HasValue && res.Value - then - state.FilePath <- dialog.FileName + match askDocumentPathToSave () with + | Some filepath -> + state.FilePath <- filepath state.Save() + | _ -> () else state.Save() + updateDocumentStatus () + displayStatusMessage "Document saved" with | :? IOException as ex -> Log.Error(ex.ToString()) MessageBox.Show(sprintf "The document cannot be save in '%s'" state.FilePath, "Error saving the document", MessageBoxButton.OK, MessageBoxImage.Error) |> ignore + let saveCurrentDocumentAsNewFile () = + match askDocumentPathToSave () with + | Some filepath -> + state.FilePath <- filepath + saveCurrentDocument () + | _ -> () + // Ask the use to save the current document if neccessary. let askSaveCurrent () = if state.AlteredSinceLastSave @@ -343,6 +388,7 @@ let run (defaultConfig: Config) (fileToOpen: string option) = txtPatient.Text <- state.PatientID updatePreviews () updateGlobalParasitemia () + updateDocumentStatus () let loadFile (filepath: string) = askSaveCurrent () @@ -355,15 +401,16 @@ let run (defaultConfig: Config) (fileToOpen: string option) = | :? IOException as ex -> Log.Error(ex.ToString()) state.FilePath <- previousFilePath - MessageBox.Show(sprintf "The document cannot be loaded from '%s'" state.FilePath, "Error saving the document", MessageBoxButton.OK, MessageBoxImage.Error) |> ignore + MessageBox.Show(sprintf "The document cannot be loaded from '%s'" state.FilePath, "Error loading the document", MessageBoxButton.OK, MessageBoxImage.Error) |> ignore - txtPatient.LostFocus.AddHandler(fun obj args -> state.PatientID <- txtPatient.Text) + txtPatient.TextChanged.AddHandler(fun obj args -> state.PatientID <- txtPatient.Text) menuExit.Click.AddHandler(fun obj args -> askSaveCurrent () mainWindow.Root.Close()) menuSaveFile.Click.AddHandler(fun obj args -> saveCurrentDocument ()) + menuSaveAsFile.Click.AddHandler(fun obj args -> saveCurrentDocumentAsNewFile ()) menuLoadFile.Click.AddHandler(fun obj args -> // TODO: if current state not saved and not empty, ask to save it. @@ -385,8 +432,13 @@ let run (defaultConfig: Config) (fileToOpen: string option) = let noSourceImage = state.SourceImages.Count() = 0 for filename in dialog.FileNames do - let srcImg = state.AddSourceImage filename defaultConfig - addPreview srcImg + try + let srcImg = state.AddSourceImage filename defaultConfig + addPreview srcImg + with + | _ as ex -> + Log.Error(ex.ToString()) + MessageBox.Show(sprintf "Unable to read the image from '%s'" filename, "Error adding an image", MessageBoxButton.OK, MessageBoxImage.Error) |> ignore updateGlobalParasitemia () @@ -491,9 +543,17 @@ let run (defaultConfig: Config) (fileToOpen: string option) = borderCurrentImage.ReleaseMouseCapture() args.Handled <- true) + // Shortcuts. + mainWindow.Root.InputBindings.Add( + Input.KeyBinding( + FSharp.ViewModule.FunCommand((fun obj -> saveCurrentDocument ()), (fun obj -> true)), + Input.KeyGesture(Input.Key.S, Input.ModifierKeys.Control))) |> ignore + // Viewport preview. scrollViewCurrentImage.ScrollChanged.AddHandler(fun obj args -> updateViewportPreview ()) + updateDocumentStatus () + mainWindow.Root.Show() match fileToOpen with diff --git a/Parasitemia/ParasitemiaUI/ParasitemiaUI.fsproj b/Parasitemia/ParasitemiaUI/ParasitemiaUI.fsproj index 3b88791..96643e9 100644 --- a/Parasitemia/ParasitemiaUI/ParasitemiaUI.fsproj +++ b/Parasitemia/ParasitemiaUI/ParasitemiaUI.fsproj @@ -26,7 +26,7 @@ bin\Debug\ DEBUG;TRACE 3 - x64 + AnyCPU bin\Debug\ParasitemiaUI.XML false --folder "../../../Images/debug" --output "../../../Images/output" --debug @@ -38,7 +38,7 @@ false DEBUG;TRACE 3 - x64 + AnyCPU bin\Debug\ParasitemiaUI.XML false --output "../../../Images/output" --debug diff --git a/Parasitemia/ParasitemiaUI/State.fs b/Parasitemia/ParasitemiaUI/State.fs index 5b89810..f133640 100644 --- a/Parasitemia/ParasitemiaUI/State.fs +++ b/Parasitemia/ParasitemiaUI/State.fs @@ -30,6 +30,10 @@ type State () = List.length srcImg.rbcs, srcImg.rbcs |> List.fold (fun nbInfected rbc -> if rbc.infected then nbInfected + 1 else nbInfected) 0 + member this.ImageNbAltered (srcImg: SourceImage) : int * int = + List.length srcImg.rbcs, + srcImg.rbcs |> List.fold (fun nbAltered rbc -> if rbc.setManually then nbAltered + 1 else nbAltered) 0 + member this.GlobalParasitemia : int * int = sourceImages |> Seq.fold (fun (nbTotal, nbTotalInfected) srcImg -> @@ -56,16 +60,18 @@ type State () = /// /// Load the current state. 'FilePath' must have been defined. /// - /// If the file cannot be laoded + /// If the file cannot be loaded member this.Load () = let data = PiaZ.load this.FilePath this.PatientID <- data.patientID sourceImages.Clear() sourceImages.InsertRange(0, data.images) - if sourceImages.Count > 0 - then this.CurrentImage <- Some sourceImages.[0] + this.CurrentImage <- if sourceImages.Count > 0 then Some sourceImages.[0] else None alteredSinceLastSave <- false + /// + /// + /// If the image cannot be read member this.AddSourceImage (filePath: string) (defaultConfig: ParasitemiaCore.Config.Config) : SourceImage = let srcImg = { num = sourceImages.Count + 1; config = defaultConfig.Copy(); dateLastAnalysis = DateTime(0L); rbcs = []; img = new Image(filePath) } sourceImages.Add(srcImg) diff --git a/Parasitemia/ParasitemiaUI/XAML/MainWindow.xaml b/Parasitemia/ParasitemiaUI/XAML/MainWindow.xaml index 37ea0c0..931b9b8 100644 --- a/Parasitemia/ParasitemiaUI/XAML/MainWindow.xaml +++ b/Parasitemia/ParasitemiaUI/XAML/MainWindow.xaml @@ -3,13 +3,14 @@ xmlns:d="http://schemas.microsoft.com/expression/blend/2008" xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" mc:Ignorable="d" - x:Name="MainWindow" Height="681.888" Width="787.61" MinHeight="200" MinWidth="300" Title="Parasitemia" Icon="pack://application:,,,/Resources/icon.ico"> + x:Name="MainWindow" Height="700" Width="1000" MinHeight="200" MinWidth="300" Title="Parasitemia" Icon="pack://application:,,,/Resources/icon.ico" ResizeMode="CanResizeWithGrip"> + @@ -26,6 +27,14 @@ + + + + + + + + @@ -69,7 +78,14 @@ - + + + + + + + + -- 2.43.0