예제 #1
0
 public ImageLoader(string imageSetFolderPath, string relativePath, FileInfo fileInfo, DataEntryHandler dataHandler)
 {
     this.fileInfo           = fileInfo;
     this.dataHandler        = dataHandler;
     this.imageSetFolderPath = imageSetFolderPath;
     this.relativePath       = relativePath;
 }
예제 #2
0
        private DateTimePicker CreateDateTimePicker(ControlRow control)
        {
            DateTimePicker dateTimePicker = new DateTimePicker()
            {
                ToolTip     = control.Tooltip,
                Width       = control.Width,
                CultureInfo = System.Globalization.CultureInfo.CreateSpecificCulture("en-US")
            };

            DataEntryHandler.Configure(dateTimePicker, Constant.ControlDefault.DateTimeValue.DateTime);
            dateTimePicker.GotFocus  += this.Control_GotFocus;
            dateTimePicker.LostFocus += this.Control_LostFocus;
            return(dateTimePicker);
        }
        private void Window_Loaded(object sender, RoutedEventArgs e)
        {
            // Set up a progress handler that will update the progress bar
            this.InitalizeProgressHandler(this.BusyCancelIndicator);

            // Set up the initial UI and values
            // Get the image filename and image and display them
            this.FileName.Content   = this.ImageToCorrect.File;
            this.FileName.ToolTip   = this.ImageToCorrect.File;
            this.SampleImage.Source = this.ImageToCorrect.LoadBitmap(this.fileDatabase.FolderPath, out bool isCorruptOrMissing);

            // Configure datetime picker to the initial date on the images plus callbacks
            this.initialDate          = this.ImageToCorrect.DateTimeIncorporatingOffset;
            this.OriginalDate.Content = DateTimeHandler.ToStringDisplayDateTime(this.initialDate);
            DataEntryHandler.Configure(this.DateTimePicker, this.initialDate.DateTime);
            this.DateTimePicker.ValueChanged += this.DateTimePicker_ValueChanged;
        }
        private void Window_Loaded(object sender, RoutedEventArgs e)
        {
            // Set up a progress handler that will update the progress bar
            this.InitalizeProgressHandler(this.BusyCancelIndicator);

            // Set up the initial UI and values
            this.latestImageDateTime   = DateTimeOffset.MinValue;
            this.earliestImageDateTime = DateTimeOffset.MaxValue;

            // Search the images for the two images with the earliest and latest data/time date
            ImageRow latestImageRow   = null;
            ImageRow earliestImageRow = null;

            foreach (ImageRow image in this.fileDatabase.FileTable)
            {
                DateTimeOffset currentImageDateTime = image.DateTimeIncorporatingOffset;

                // If the current image's date is later, then it is a candidate latest image
                if (currentImageDateTime >= this.latestImageDateTime)
                {
                    latestImageRow           = image;
                    this.latestImageDateTime = currentImageDateTime;
                }

                // If the current image's date is earlier, then it is a candidate earliest image
                if (currentImageDateTime <= this.earliestImageDateTime)
                {
                    earliestImageRow           = image;
                    this.earliestImageDateTime = currentImageDateTime;
                }
            }

            // At this point, we should have succeeded getting the oldest and newest data/time

            // Configure the earliest date (in datetime picker) and its image
            this.earliestImageName.Content = earliestImageRow.File;
            this.earliestImageDate.Content = DateTimeHandler.ToStringDisplayDateTime(this.earliestImageDateTime);
            this.imageEarliest.Source      = earliestImageRow.LoadBitmap(this.fileDatabase.FolderPath, out bool isCorruptOrMissing);

            // Configure the latest date (in datetime picker) and its image
            this.latestImageName.Content = latestImageRow.File;
            DataEntryHandler.Configure(this.dateTimePickerLatestDateTime, this.latestImageDateTime.DateTime);
            this.dateTimePickerLatestDateTime.ValueChanged += this.DateTimePicker_ValueChanged;
            this.imageLatest.Source = latestImageRow.LoadBitmap(this.fileDatabase.FolderPath, out isCorruptOrMissing);
        }
예제 #5
0
        // Receive an event containing new image processing parameters.
        // Store these parameters and then try to update the image
        public async void AdjustImage_EventHandler(object sender, ImageAdjusterEventArgs e)
        {
            if (e == null)
            {
                // Shouldn't happen, but...
                return;
            }

            string path = DataEntryHandler.TryGetFilePathFromGlobalDataHandler();

            if (String.IsNullOrEmpty(path))
            {
                // The file cannot be opened or is not displayable.
                // Signal change in image state, which essentially says there is no displayable image to adjust (consumed by ImageAdjuster)
                this.OnImageStateChanged(new ImageStateEventArgs(false)); //  Signal change in image state (consumed by ImageAdjuster)
                return;
            }

            if (e.OpenExternalViewer)
            {
                // The event says to open an external photo viewer. Try to do so.
                // Note that we don't do any image processing on this event if if this is the case.
                if (ProcessExecution.TryProcessStart(path) == false)
                {
                    // Can't open the image file with an external view. Note that file must exist at this point as we checked for that above.
                    Dialogs.MarkableCanvasCantOpenExternalPhotoViewerDialog(Util.GlobalReferences.MainWindow, Path.GetExtension(path));
                }
                return;
            }

            // Process the image based on the current image processing arguments.
            if (e.ForceUpdate == false && (e.Contrast == this.lastContrast && e.Brightness == this.lastBrightness && e.DetectEdges == this.lastDetectEdges && e.Sharpen == this.lastSharpen && e.UseGamma == this.lastUseGamma && e.GammaValue == this.lastGammaValue))
            {
                // If there is no change from the last time we processed an image, abort as it would not make any difference to what the user sees
                return;
            }
            this.contrast    = e.Contrast;
            this.brightness  = e.Brightness;
            this.detectEdges = e.DetectEdges;
            this.sharpen     = e.Sharpen;
            this.useGamma    = e.UseGamma;
            this.gammaValue  = e.GammaValue;
            this.timerImageProcessingUpdate.Start();
            await UpdateAndProcessImage().ConfigureAwait(true);
        }
예제 #6
0
        // Update the image according to the image processing parameters.
        private async Task UpdateAndProcessImage()
        {
            // If its processing the image, try again later (via the time),
            if (this.Processing)
            {
                return;
            }
            try
            {
                string path = DataEntryHandler.TryGetFilePathFromGlobalDataHandler();;
                if (String.IsNullOrEmpty(path))
                {
                    // If we cannot get a valid file, there is no image to manipulate.
                    // So abort and signal a change in image state that says there is no displayable image to adjust (consumed by ImageAdjuster)
                    this.OnImageStateChanged(new ImageStateEventArgs(false));
                }

                // Set the state to Processing is used to indicate that other attempts to process the image should be aborted util this is done.
                this.Processing = true;
                using (MemoryStream imageStream = new MemoryStream(File.ReadAllBytes(path)))
                {
                    // Remember the currently selected image processing states, so we can compare them later for changes
                    this.lastBrightness  = this.brightness;
                    this.lastContrast    = this.contrast;
                    this.lastSharpen     = this.sharpen;
                    this.lastDetectEdges = this.detectEdges;
                    this.lastUseGamma    = this.useGamma;
                    this.lastGammaValue  = this.gammaValue;
                    BitmapFrame bf = await ImageProcess.StreamToImageProcessedBitmap(imageStream, this.brightness, this.contrast, this.sharpen, this.detectEdges, this.useGamma, this.gammaValue).ConfigureAwait(true);

                    if (bf != null)
                    {
                        this.ImageToDisplay.Source = await ImageProcess.StreamToImageProcessedBitmap(imageStream, this.brightness, this.contrast, this.sharpen, this.detectEdges, this.useGamma, this.gammaValue).ConfigureAwait(true);
                    }
                }
            }
            catch
            {
                // We failed on this image. To avoid this happening again,
                // Signal change in image state, which essentially says there is no adjustable image (consumed by ImageAdjuster)
                this.OnImageStateChanged(new ImageStateEventArgs(false));
            }
            this.Processing = false;
        }
예제 #7
0
        public ImageSetLoader(string imageSetFolderPath, IEnumerable <FileInfo> fileInfos, DataEntryHandler dataHandler)
        {
            // Check the arguments for null
            ThrowIf.IsNullArgument(dataHandler, nameof(dataHandler));

            ImagesSkippedAsFilePathTooLong = new List <string>();

            // Don't add a file if it already exists in the database.
            // Rather than check every file one by one to see if it exists in the database
            // - get all the current files in the database (as existing full paths) in a single database call,
            // - create a new file list (fileInfoArray) that only adds files (as fileInfos) that are NOT present in the database.
            HashSet <string> existingPaths;

            using (FileTable filetable = dataHandler.FileDatabase.SelectAllFiles())
            {
                existingPaths = new HashSet <string>(from file in filetable
                                                     select Path.Combine(imageSetFolderPath, Path.Combine(file.RelativePath, file.File)).ToLowerInvariant());
            }
            FileInfo[] filesToAddInfoArray = null;
            filesToAddInfoArray = (from fileInfo in fileInfos
                                   where existingPaths.Contains(fileInfo.FullName.ToLowerInvariant()) == false
                                   select fileInfo).OrderBy(f => f.FullName).ToArray();

            this.ImagesToLoad = filesToAddInfoArray.Length;

            // The queue will take image rows ready for insertion to the second pass
            // The eventindicates explicitly when the first pass is done.
            ConcurrentQueue <ImageRow> databaseInsertionQueue = new ConcurrentQueue <ImageRow>();

            // We trim as well, as it handles both the case where it already has a trailing \ and when it is missing it.
            // For example, if we opened a template in a top level drive, it would have a following '\'
            string absolutePathPart = imageSetFolderPath.TrimEnd(Path.DirectorySeparatorChar) + @"\";

            // Pass 1
            this.pass1 = new Task(() =>
            {
                List <Task> loadTasks = new List <Task>();

                // Fan out the loader tasks
                foreach (FileInfo fileInfo in filesToAddInfoArray)
                {
                    // Parse the relative path from the full name.
                    // As GetDirectoryName does not end with a \ on a file name, we add the' '\' as needed

                    string directoryName = String.Empty;
                    try
                    {
                        directoryName = Path.GetDirectoryName(fileInfo.FullName);
                        if (directoryName.EndsWith(@"\") == false)
                        {
                            directoryName += @"\";
                        }
                    }
                    catch (System.IO.PathTooLongException)
                    {
                        // If the file path is too long, skip the file.
                        // Also, add its folder name (if it isn't already there) to a list so we can
                        // later show a meaningful error message to the user that these files were skipped.
                        // We do the folder name as otherwise the number of images could be overwhelming.
                        string path = fileInfo.FullName.Substring(0, fileInfo.FullName.LastIndexOf(("\\")));
                        if (ImagesSkippedAsFilePathTooLong.Contains(path) == false)
                        {
                            ImagesSkippedAsFilePathTooLong.Add(path);
                        }
                        continue;
                    }
                    string relativePath = directoryName.Replace(absolutePathPart, string.Empty).TrimEnd(Path.DirectorySeparatorChar);

                    ImageLoader loader = new ImageLoader(imageSetFolderPath, relativePath, fileInfo, dataHandler);

                    Task loaderTask = loader.LoadImageAsync(() =>
                    {
                        // Both of these operations are atomic, the specific number and the specific loader at any given
                        // time may not coorespond.
                        Interlocked.Increment(ref this.imagesLoaded);
                        this.LastLoadComplete = loader;

                        if (loader.RequiresDatabaseInsert)
                        {
                            // This requires database insertion. Enqueue for pass 2
                            // Note that there is no strict ordering here, anything may finish and insert in
                            // any order. By sorting the file infos above, things that sort first in the database should
                            // be done first, BUT THIS MAY REQUIRE ADDITIONAL FINESSE TO KEEP THE EXPLICIT ORDER CORRECT.
                            databaseInsertionQueue.Enqueue(loader.File);
                            Interlocked.Increment(ref this.imagesToInsert);
                        }
                    });
                    loadTasks.Add(loaderTask);
                }
                Task.WaitAll(loadTasks.ToArray());
            });
            // End Pass 1

            // Pass 2
            this.pass2 = new Task(() =>
            {
                // This pass2 starts after pass1 is fully complete
                List <ImageRow> imagesToInsert = databaseInsertionQueue.OrderBy(f => Path.Combine(f.RelativePath, f.File)).ToList();
                dataHandler.FileDatabase.AddFiles(imagesToInsert,
                                                  (ImageRow file, int fileIndex) =>
                {
                    this.LastInsertComplete      = file;
                    this.LastIndexInsertComplete = fileIndex;
                });
            });
            // End Pass 2
        }
예제 #8
0
        // Move the focus (usually because of tabbing or shift-tab)
        // It cycles between the data entry controls and the CopyPrevious button
        private void MoveFocusToNextOrPreviousControlOrCopyPreviousButton(bool moveToPreviousControl)
        {
            // identify the currently selected control
            // if focus is currently set to the canvas this defaults to the first or last control, as appropriate
            int  currentControl = moveToPreviousControl ? this.DataEntryControls.Controls.Count : -1;
            Type type;

            IInputElement focusedElement = FocusManager.GetFocusedElement(this);

            if (focusedElement != null)
            {
                type = focusedElement.GetType();

                // If we are moving the focus from outside to one of the controls in the data panel or the copy previous button,
                // then try to restore the focus to the last control that had the focus.
                if (Constant.Control.KeyboardInputTypes.Contains(type) == false && focusedElement != this.CopyPreviousValuesButton)
                {
                    if (this.lastControlWithFocus != null && this.lastControlWithFocus.IsEnabled == true)
                    {
                        Keyboard.Focus(this.lastControlWithFocus);
                        this.CopyPreviousValuesSetEnableStatePreviewsAndGlowsAsNeeded();
                        return;
                    }
                }

                // Otherwise, try to find the control that has the current focus
                if (Constant.Control.KeyboardInputTypes.Contains(type))
                {
                    if (DataEntryHandler.TryFindFocusedControl(focusedElement, out DataEntryControl focusedControl))
                    {
                        int index = 0;
                        foreach (DataEntryControl control in this.DataEntryControls.Controls)
                        {
                            if (Object.ReferenceEquals(focusedControl, control))
                            {
                                // We found it, so no need to look further
                                currentControl = index;
                                break;
                            }
                            ++index;
                        }
                    }
                }
            }

            // Then move to the next or previous control as available
            Func <int, int> incrementOrDecrement;

            if (moveToPreviousControl)
            {
                incrementOrDecrement = (int index) => { return(--index); };
            }
            else
            {
                incrementOrDecrement = (int index) => { return(++index); };
            }

            for (currentControl = incrementOrDecrement(currentControl);
                 currentControl > -1 && currentControl < this.DataEntryControls.Controls.Count;
                 currentControl = incrementOrDecrement(currentControl))
            {
                DataEntryControl control = this.DataEntryControls.Controls[currentControl];
                if (control.ContentReadOnly == false && control.IsContentControlEnabled == true && this.IsControlIncludedInTabOrder(control))
                {
                    this.lastControlWithFocus = control.Focus(this);
                    // There is a bug with Avalon: when the data control pane is floating the focus does not go to it via the above call
                    // (although it does when its docked).
                    // Setting the focus to the actual content control seems to fix it.
                    control.GetContentControl.Focus();
                    return;
                }
            }

            // if we've gone thorugh all the controls and couldn't set the focus, then we must be at the beginning or at the end.
            if (this.CopyPreviousValuesButton.IsEnabled)
            {
                // So set the focus to the Copy PreviousValuesButton, unless it is disabled.
                this.CopyPreviousValuesButton.Focus();
                this.lastControlWithFocus = this.CopyPreviousValuesButton;
                this.CopyPreviousValuesSetEnableStatePreviewsAndGlowsAsNeeded();
            }
            else
            {
                // Skip the CopyPreviousValuesButton, as it is disabled.
                DataEntryControl candidateControl = moveToPreviousControl ? this.DataEntryControls.Controls.Last() : this.DataEntryControls.Controls.First();
                if (moveToPreviousControl)
                {
                    // Find the LAST control
                    foreach (DataEntryControl control in this.DataEntryControls.Controls)
                    {
                        if (control.ContentReadOnly == false)
                        {
                            candidateControl = control;
                        }
                    }
                }
                else
                {
                    // Find the FIRST control
                    foreach (DataEntryControl control in this.DataEntryControls.Controls)
                    {
                        if (control.ContentReadOnly == false)
                        {
                            candidateControl = control;
                            break;
                        }
                    }
                }
                if (candidateControl != null)
                {
                    this.lastControlWithFocus = candidateControl.Focus(this);
                }
            }
        }