All Downloads are FREE. Search and download functionalities are using the official Maven repository.

org.scalafx.extras.progress_dialog.ProgressStatusDialog.scala Maven / Gradle / Ivy

There is a newer version: 0.10.1
Show newest version
/*
 * Copyright (c) 2011-2024, ScalaFX Project
 * All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions are met:
 *     * Redistributions of source code must retain the above copyright
 *       notice, this list of conditions and the following disclaimer.
 *     * Redistributions in binary form must reproduce the above copyright
 *       notice, this list of conditions and the following disclaimer in the
 *       documentation and/or other materials provided with the distribution.
 *     * Neither the name of the ScalaFX Project nor the
 *       names of its contributors may be used to endorse or promote products
 *       derived from this software without specific prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
 * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
 * DISCLAIMED. IN NO EVENT SHALL THE SCALAFX PROJECT OR ITS CONTRIBUTORS BE LIABLE
 * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
 * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED
 * AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
 * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 */

package org.scalafx.extras.progress_dialog

import javafx.concurrent as jfxc
import org.scalafx.extras
import org.scalafx.extras.*
import org.scalafx.extras.progress_dialog.impl.ProgressStatus
import scalafx.Includes.*
import scalafx.beans.property.*
import scalafx.geometry.{Insets, Pos}
import scalafx.scene.Scene
import scalafx.scene.control.Button
import scalafx.scene.layout.BorderPane
import scalafx.stage.{Stage, Window}

import java.time.Duration

class ProgressStatusDialog(dialogTitle: String, parentWindow: Option[Window]):

  private val elapsedTimeService = new ElapsedTimeService()
  private val progressStatus     = new ProgressStatus()

  progressStatus.model.statusText.value =
    "------------------------------------------------------------------------------"

  val abortFlag: BooleanProperty = BooleanProperty(false)

  private def updateETA(): Unit =
    val strVal =
      val progress = progressStatus.model.progress.value
      if progress <= 0 then
        "?"
      else
        // TODO: prevent jitter of estimate when progress value changes,
        //       compute running average of last predictions or something...
        val et        = elapsedTimeService.elapsedTime.value
        val eta: Long = (et * (1 - progress) / progress).ceil.toLong
        formatDuration(Duration.ofMillis(eta))
    progressStatus.model.etaTimeText.value = strVal

  elapsedTimeService.elapsedTime.onChange { (_, _, newValue) =>
    progressStatus.model.elapsedTimeText.value = formatDuration(Duration.ofMillis(newValue.longValue()))
    updateETA()
  }

  private val abortButton = new Button:
    text = "Abort batch processing"
    padding = Insets(7)
    margin = Insets(7)
    onAction = _ =>
      extras.offFXAndWait {
        // TODO show abort status
        onFX {
          progressStatus.model.statusText.value = "Aborting processing"
        }
        onFX {
          abortFlag.value = true
        }
      }
    disable <== abortFlag
    alignmentInParent = Pos.Center

  private val dialog: Stage = new Stage:
    initOwner(parentWindow.orNull)
    parentWindow.foreach { w =>
      w.delegate match
        case s: javafx.stage.Stage =>
          icons ++= s.icons
        case x =>
          throw new Exception(s"Invalid parent window delegate: $x")
    }
    title = dialogTitle
    resizable = false
    scene = new Scene:
      root = new BorderPane:
        padding = Insets(14)
        center = progressStatus.view
        bottom = abortButton
      parentWindow.foreach(w => stylesheets = w.scene().stylesheets)

    onShown = _ =>
      // TODO: prevent double initialization
      elapsedTimeService.doStart()
    onCloseRequest = e =>
      abortFlag.value = true
      // Do not allow closing the window
      e.consume()

  private def formatDuration(duration: Duration): String =
    val seconds    = duration.getSeconds
    val absSeconds = Math.abs(seconds)
    val positive   = f"${absSeconds / 3600}%d:${absSeconds % 3600 / 60}%02d:${absSeconds % 60}%02d"
    if seconds < 0 then
      "-" + positive
    else
      positive

  private class ElapsedTimeService extends jfxc.ScheduledService[Long]:

    private var startTime: Long = _

    private val _elapsedTime = new ReadOnlyLongWrapper()

    /** Elapsed time in milliseconds */
    val elapsedTime: ReadOnlyLongProperty = _elapsedTime.readOnlyProperty

    this.period = 250.ms

    override def createTask(): jfxc.Task[Long] = () =>
      val ct = System.currentTimeMillis()
      val et = ct - startTime
      onFX { _elapsedTime.value = et }
      et

    def doStart(): Unit =
      this.restart()
      startTime = System.currentTimeMillis()
      onFX {
        _elapsedTime.value = 0
      }

  def window: Window = dialog

  def progress: DoubleProperty = progressStatus.model.progress

  def statusText: StringProperty = progressStatus.model.statusText

  def totalCount: StringProperty = progressStatus.model.totalCountText

  def processedCount: StringProperty = progressStatus.model.processedCountText

  def successfulCount: StringProperty = progressStatus.model.successfulCountText

  def failedCount: StringProperty = progressStatus.model.failedCountText

  def cancelledCount: StringProperty = progressStatus.model.cancelledCountText

  def close(): Unit =
    elapsedTimeService.cancel()
    dialog.close()

  def show(): Unit =
    dialog.show()
end ProgressStatusDialog




© 2015 - 2025 Weber Informatics LLC | Privacy Policy