You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

357 lines
11 KiB

10 years ago
10 years ago
10 years ago
10 years ago
10 years ago
10 years ago
10 years ago
10 years ago
10 years ago
10 years ago
10 years ago
10 years ago
10 years ago
10 years ago
10 years ago
10 years ago
10 years ago
10 years ago
10 years ago
10 years ago
10 years ago
10 years ago
10 years ago
10 years ago
10 years ago
  1. package com.sothr.imagetools.ui.controller
  2. import java.io.{File, IOException}
  3. import java.util.ArrayList
  4. import java.util.Scanner
  5. import javafx.application.Platform
  6. import javafx.event.ActionEvent
  7. import javafx.fxml.FXML
  8. import javafx.scene.control.{Label, ProgressBar}
  9. import javafx.scene.text.{Text, TextAlignment}
  10. import javafx.scene.web.WebView
  11. import javafx.scene.{Group, Node, Scene}
  12. import javafx.stage.{DirectoryChooser, Stage, StageStyle}
  13. import akka.actor._
  14. import com.sothr.imagetools.engine.image.{SimilarImages, Image}
  15. import com.sothr.imagetools.engine.util.{PropertiesService, ResourceLoader}
  16. import com.sothr.imagetools.engine._
  17. import com.sothr.imagetools.ui.component.ImageTileFactory
  18. import grizzled.slf4j.Logging
  19. import org.markdown4j.Markdown4jProcessor
  20. import scala.concurrent._
  21. import scala.util.{Failure, Success}
  22. import ExecutionContext.Implicits.global
  23. /**
  24. * Main Application controller
  25. *
  26. * Created by drew on 12/31/13.
  27. */
  28. class AppController extends Logging {
  29. //Define controls
  30. @FXML var rootPane: javafx.scene.layout.AnchorPane = null
  31. @FXML var rootMenuBar: javafx.scene.control.MenuBar = null
  32. @FXML var imageTilePane: javafx.scene.layout.TilePane = null
  33. @FXML var tagListView: javafx.scene.control.ListView[String] = null
  34. // Labels
  35. @FXML var selectedDirectoryLabel: javafx.scene.control.Label = null
  36. @FXML var progressLabel: javafx.scene.control.Label = null
  37. // Others
  38. @FXML var progressBar: javafx.scene.control.ProgressBar = null
  39. // Engine
  40. val engine: Engine = new ConcurrentEngine()
  41. // Current State
  42. var currentDirectory: String = "."
  43. @FXML def initialize() = {
  44. if (PropertiesService.has("lastPath")) {
  45. currentDirectory = PropertiesService.get("lastPath", ".")
  46. selectedDirectoryLabel.setText(PropertiesService.get("lastPath", ""))
  47. //setup the engine listener
  48. val system: ActorSystem = AppConfig.getAppActorSystem
  49. val guiListenerProps: Props = Props.create(classOf[GUIEngineListener])
  50. val guiListener: ActorRef = system.actorOf(guiListenerProps)
  51. // configure the listener
  52. guiListener ! SetupListener(progressBar, progressLabel)
  53. // tell the engine to use our listener
  54. this.engine.setProcessedListener(guiListener)
  55. this.engine.setSimilarityListener(guiListener)
  56. // Initialize the progress label
  57. guiListener ! SubmitMessage("Initialized System... Ready!")
  58. }
  59. //test
  60. //val testImage = new Image()
  61. //testImage.setThumbnailPath("test.jpg")
  62. //testImage.setImagePath("test.jpg")
  63. //for (i <- 1 to 100) {
  64. // imageTilePane.getChildren.add(ImageTileFactory.get(testImage))
  65. //}
  66. //val list = FXCollections.observableArrayList[String]()
  67. //for (i <- 1 to 100) {
  68. // list.add(s"test-item ${i}")
  69. //}
  70. //tagListView.setItems(list)
  71. }
  72. //region MenuItem Actions
  73. @FXML
  74. def helpAction(event: ActionEvent) = {
  75. showExternalHTMLUtilityDialog("http://www.sothr.com")
  76. }
  77. @FXML
  78. def aboutAction(event: ActionEvent) = {
  79. debug("Displaying about screen")
  80. var aboutMessage = "Simple About Message"
  81. try {
  82. val scanner = new Scanner(ResourceLoader.get().getResourceStream("documents/about.md"))
  83. aboutMessage = ""
  84. while (scanner.hasNextLine) {
  85. aboutMessage += scanner.nextLine().trim() + "\n"
  86. }
  87. debug(s"Parsed About Message: '$aboutMessage'")
  88. } catch {
  89. case ioe: IOException =>
  90. error("Unable to read about file")
  91. }
  92. showMarkdownUtilityDialog("About", aboutMessage, 400.0, 300.0)
  93. debug("Showing About Dialog")
  94. }
  95. @FXML
  96. def closeAction(event: ActionEvent) = {
  97. debug("Closing application from the menu bar")
  98. val stage: Stage = this.rootMenuBar.getScene.getWindow.asInstanceOf[Stage]
  99. stage.close()
  100. }
  101. //endregion
  102. //region buttons
  103. @FXML
  104. def browseFolders(event: ActionEvent) = {
  105. val chooser = new DirectoryChooser()
  106. chooser.setTitle("ImageTools Browser")
  107. val defaultDirectory = new File(currentDirectory)
  108. chooser.setInitialDirectory(defaultDirectory)
  109. val window = this.rootPane.getScene.getWindow
  110. val selectedDirectory = chooser.showDialog(window)
  111. info(s"Selected Directory: ${selectedDirectory.getAbsolutePath}")
  112. selectedDirectoryLabel.setText(selectedDirectory.getAbsolutePath)
  113. currentDirectory = selectedDirectory.getAbsolutePath
  114. PropertiesService.set("lastPath", selectedDirectory.getAbsolutePath)
  115. }
  116. @FXML
  117. def showAllImages(event: ActionEvent) = {
  118. imageTilePane.getChildren.setAll(new ArrayList[Node]())
  119. val f: Future[List[Image]] = Future {
  120. engine.getImagesForDirectory(currentDirectory)
  121. }
  122. f onComplete {
  123. case Success(images) =>
  124. info(s"Displaying ${images.length} images")
  125. // This is used so that JavaFX updates on the proper thread
  126. // This is important since UI updates can only happen on that thread
  127. Platform.runLater(new Runnable() {
  128. override def run() {
  129. for (image <- images) {
  130. debug(s"Adding image ${image.toString} to app")
  131. imageTilePane.getChildren.add(ImageTileFactory.get(image))
  132. }
  133. }
  134. })
  135. case Failure(t) =>
  136. error("An Error Occurred", t)
  137. }
  138. }
  139. @FXML
  140. def showSimilarImages(event: ActionEvent) = {
  141. imageTilePane.getChildren.setAll(new ArrayList[Node]())
  142. val f: Future[List[SimilarImages]] = Future {
  143. engine.getSimilarImagesForDirectory(currentDirectory)
  144. }
  145. f onComplete {
  146. case Success(similarImages) =>
  147. info(s"Displaying ${similarImages.length} similar images")
  148. Platform.runLater(new Runnable() {
  149. override def run() {
  150. for (similarImage <- similarImages) {
  151. debug(s"Adding similar images ${similarImage.rootImage.toString} to app")
  152. imageTilePane.getChildren.add(ImageTileFactory.get(similarImage.rootImage))
  153. similarImage.similarImages.foreach(image => imageTilePane.getChildren.add(ImageTileFactory.get(image)))
  154. }
  155. }
  156. })
  157. case Failure(t) =>
  158. error("An Error Occurred", t)
  159. }
  160. }
  161. //endregion
  162. //todo: include a templating engine for rendering information
  163. //todo: show a dialog that is rendered from markdown content
  164. def showMarkdownUtilityDialog(title: String, markdown: String, width: Double = 800.0, height: Double = 600.0) = {
  165. val htmlBody = new Markdown4jProcessor().process(markdown)
  166. showHTMLUtilityDialog(title, htmlBody, width, height)
  167. }
  168. /**
  169. * Render HTML content to a utility dialog. No input or output, just raw rendered content through a webkit engine.
  170. *
  171. * @param title Title of the dialog
  172. * @param htmlBody Body to render
  173. * @param width Desired width of the dialog
  174. * @param height Desired height of the dialog
  175. */
  176. def showHTMLUtilityDialog(title: String, htmlBody: String, width: Double = 800.0, height: Double = 600.0) = {
  177. val dialog: Stage = new Stage()
  178. dialog.initStyle(StageStyle.UTILITY)
  179. val parent: Group = new Group()
  180. //setup the HTML view
  181. val htmlView = new WebView
  182. htmlView.getEngine.loadContent(htmlBody)
  183. htmlView.setMinWidth(width)
  184. htmlView.setMinHeight(height)
  185. htmlView.setPrefWidth(width)
  186. htmlView.setPrefHeight(height)
  187. parent.getChildren.add(htmlView)
  188. val scene: Scene = new Scene(parent)
  189. dialog.setScene(scene)
  190. dialog.setResizable(false)
  191. dialog.setTitle(title)
  192. dialog.show()
  193. }
  194. def showExternalHTMLUtilityDialog(url: String) = {
  195. val dialog: Stage = new Stage()
  196. dialog.initStyle(StageStyle.UTILITY)
  197. val parent: Group = new Group()
  198. //setup the HTML view
  199. val htmlView = new WebView
  200. htmlView.getEngine.load(url)
  201. //htmlView.setMinWidth(width)
  202. //htmlView.setMinHeight(height)
  203. //htmlView.setPrefWidth(width)
  204. //htmlView.setPrefHeight(height)
  205. parent.getChildren.add(htmlView)
  206. val scene: Scene = new Scene(parent)
  207. dialog.setScene(scene)
  208. dialog.setResizable(false)
  209. dialog.setTitle(htmlView.getEngine.getTitle)
  210. dialog.show()
  211. }
  212. /**
  213. * Show a plain text utility dialog
  214. *
  215. * @param message Message to display
  216. * @param wrapWidth When to wrap
  217. * @param alignment How it should be aligned
  218. */
  219. def showUtilityDialog(title: String,
  220. message: String,
  221. wrapWidth: Double = 300.0,
  222. xOffset: Double = 25.0,
  223. yOffset: Double = 25.0,
  224. alignment: TextAlignment = TextAlignment.JUSTIFY) = {
  225. val dialog: Stage = new Stage()
  226. dialog.initStyle(StageStyle.UTILITY)
  227. val parent: Group = new Group()
  228. // fill the text box
  229. val messageText = new Text()
  230. messageText.setText(message)
  231. messageText.setWrappingWidth(wrapWidth)
  232. messageText.setX(xOffset)
  233. messageText.setY(yOffset)
  234. messageText.setTextAlignment(TextAlignment.JUSTIFY)
  235. parent.getChildren.add(messageText)
  236. val scene: Scene = new Scene(parent)
  237. dialog.setScene(scene)
  238. dialog.setResizable(false)
  239. dialog.setMinWidth(wrapWidth + xOffset * 2)
  240. dialog.setTitle(title)
  241. dialog.show()
  242. }
  243. def print(): String = {
  244. "This method works"
  245. }
  246. }
  247. //region EngineListener
  248. case class SetupListener(progressBar: ProgressBar, progressLabel: Label)
  249. /**
  250. * Actor for logging output information
  251. */
  252. class GUIEngineListener extends EngineListener with ActorLogging {
  253. var progressBar: javafx.scene.control.ProgressBar = null
  254. var progressLabel: javafx.scene.control.Label = null
  255. var isStarted = false
  256. var isFinished = false
  257. override def receive: Actor.Receive = {
  258. case command: SetupListener => setupListener(command)
  259. case command: SubmitMessage => handleMessage(command)
  260. case command: ScannedFileCount => handleScannedFileCount(command)
  261. case command: ComparedFileCount => handleComparedFileCount(command)
  262. case _ => log.info("received unknown message")
  263. }
  264. def setupListener(command: SetupListener) = {
  265. this.progressBar = command.progressBar
  266. this.progressLabel = command.progressLabel
  267. }
  268. override def handleComparedFileCount(command: ComparedFileCount): Unit = {
  269. Platform.runLater(new Runnable() {
  270. override def run(): Unit = {
  271. if (command.message != null) {
  272. log.debug(command.message)
  273. progressLabel.setText(command.message)
  274. } else {
  275. progressLabel.setText(s"Processed ${command.count}/${command.total}")
  276. }
  277. log.debug("Processed {}/{}", command.count, command.total)
  278. progressBar.setProgress(command.count.toFloat/command.total)
  279. }
  280. })
  281. }
  282. override def handleScannedFileCount(command: ScannedFileCount): Unit = {
  283. Platform.runLater(new Runnable() {
  284. override def run(): Unit = {
  285. if (command.message != null) {
  286. log.debug(command.message)
  287. progressLabel.setText(command.message)
  288. } else {
  289. progressLabel.setText(s"Scanned ${command.count}/${command.total} For Similarities")
  290. }
  291. log.debug("Scanned {}/{} For Similarities", command.count, command.total)
  292. progressBar.setProgress(command.count.toFloat/command.total)
  293. }
  294. })
  295. }
  296. override def handleMessage(command: SubmitMessage): Unit = {
  297. Platform.runLater(new Runnable() {
  298. override def run(): Unit = {
  299. log.debug(command.message)
  300. progressLabel.setText(command.message)
  301. }
  302. })
  303. }
  304. }
  305. //endregion