<commit_before>package com.davebsoft.sctw.ui
import _root_.scala.swing.GridBagPanel._
import _root_.com.davebsoft.sctw.util.PopupListener
import _root_.scala.swing.event.ButtonClicked
import _root_.scala.xml.{NodeSeq, Node}

import java.awt.event.{MouseEvent, ActionEvent, MouseAdapter, ActionListener}
import java.awt.image.BufferedImage
import java.awt.{Color, Desktop, Dimension, Insets, Font}
import java.awt.event.{KeyEvent, KeyAdapter}
import java.net.{URI, URL}
import java.util.Comparator
import javax.swing.{JTable, JTextPane, JButton, JLabel, ImageIcon, Icon, SwingWorker, JMenu, JPopupMenu, JMenuItem, JToolBar}
import javax.swing.event._
import javax.swing.table.{DefaultTableCellRenderer, TableRowSorter, TableCellRenderer}
import scala.swing._
import filter.TagsRepository

/**
 * Details of the currently-selected tweet.
 * @author <NAME>
 */

class TweetDetailPanel(table: JTable, filtersPane: FiltersPane) extends GridBagPanel {
    
  var picLabel: Label = _
  var userDescription: TextArea = _
  var largeTweet: JTextPane = _
  private val THUMBNAIL_SIZE = 48
  val transparentPic = new ImageIcon(new BufferedImage(THUMBNAIL_SIZE, THUMBNAIL_SIZE, 
    BufferedImage.TYPE_INT_ARGB))
  var bigPicFrame: Frame = _
  var bigPicLabel: Label = _
  var showingUrl: String = _

  private class CustomConstraints extends Constraints {
    gridy = 0; anchor = Anchor.SouthWest; insets = new Insets(0, 4, 0, 0)
  }
  
  largeTweet = new LargeTweet(filtersPane, table)
  //largeTweet.setBackground(StatusPane.this.background)
  
  peer.add(largeTweet, new Constraints{
    insets = new Insets(5,1,5,1)
    grid = (0,0); gridwidth=2; fill = GridBagPanel.Fill.Both;
  }.peer)

  picLabel = new Label
  picLabel.peer.addMouseListener(new MouseAdapter {
    override def mouseClicked(e: MouseEvent) = {
      showBigPicture
    }
  })
  add(picLabel, new CustomConstraints {
    grid = (0,1); gridheight = 2;  
  })

  userDescription = new TextArea {
    background = TweetDetailPanel.this.background
    lineWrap = true
    wordWrap = true
    editable = false
  }
  add(userDescription, new CustomConstraints {
    grid = (1,1); gridheight=2; fill = GridBagPanel.Fill.Both; weightx = 1; weighty = 1;
  })
  
  val statusTableModel = table.getModel.asInstanceOf[StatusTableModel]

  table.getSelectionModel.addListSelectionListener(new ListSelectionListener {
    def valueChanged(e: ListSelectionEvent) = {
      if (! e.getValueIsAdjusting) {
        if (table.getSelectedRowCount == 1) {
          try {
            val modelRowIndex = table.convertRowIndexToModel(table.getSelectedRow)
            val status = statusTableModel.getStatusAt(modelRowIndex)
            showStatusDetails(status)
          } catch {
            case ex: IndexOutOfBoundsException => println(ex)
          }
        } else {
          clearStatusDetails
        }
      }
    }
  })
  
  private def showStatusDetails(status: NodeSeq) {
    val user = status \ "user"
    userDescription.text = (user \ "name").text + " • " +
            (user \ "location").text + " • " + (user \ "description").text
    largeTweet.setText(HtmlFormatter.createTweetHtml((status \ "text").text, 
      (status \ "in_reply_to_status_id").text)) 
    val picUrl = (user \ "profile_image_url").text
    if (! picUrl.equals(showingUrl)) {
      showingUrl = picUrl
      val u = new URL(picUrl)
      picLabel.icon = transparentPic
      new SwingWorker[Icon, Object] {
        val urlToShow = showingUrl
        def doInBackground = new ImageIcon(u)
        override def done = {
          if (urlToShow == showingUrl) { // If user is moving quickly there may be several threads
            val icon = get
            if (icon.getIconHeight <= THUMBNAIL_SIZE) picLabel.icon = icon // Ignore broken, too-big thumbnails 
            setBigPicLabelIcon
          }
        }
      }.execute
    }
  }
<commit_msg>Use actors to fetch photos in background<commit_after>package com.davebsoft.sctw.ui
import _root_.scala.swing.GridBagPanel._
import _root_.com.davebsoft.sctw.util.PopupListener
import _root_.scala.swing.event.ButtonClicked
import _root_.scala.xml.{NodeSeq, Node}

import java.awt.event.{MouseEvent, ActionEvent, MouseAdapter, ActionListener}
import java.awt.image.BufferedImage
import java.awt.{Color, Desktop, Dimension, Insets, Font}
import java.awt.event.{KeyEvent, KeyAdapter}
import java.net.{URI, URL}
import java.util.Comparator
import javax.swing._
import javax.swing.event._
import javax.swing.table.{DefaultTableCellRenderer, TableRowSorter, TableCellRenderer}
import scala.swing._
import filter.TagsRepository

/**
 * Details of the currently-selected tweet.
 * @author <NAME>
 */

class TweetDetailPanel(table: JTable, filtersPane: FiltersPane) extends GridBagPanel {
    
  var picLabel: Label = _
  var userDescription: TextArea = _
  var largeTweet: JTextPane = _
  private val THUMBNAIL_SIZE = 48
  val transparentPic = new ImageIcon(new BufferedImage(THUMBNAIL_SIZE, THUMBNAIL_SIZE, 
    BufferedImage.TYPE_INT_ARGB))
  var bigPicFrame: Frame = _
  var bigPicLabel: Label = _
  var showingUrl: String = _

  private class CustomConstraints extends Constraints {
    gridy = 0; anchor = Anchor.SouthWest; insets = new Insets(0, 4, 0, 0)
  }
  
  largeTweet = new LargeTweet(filtersPane, table)
  
  peer.add(largeTweet, new Constraints{
    insets = new Insets(5,1,5,1)
    grid = (0,0); gridwidth=2; fill = GridBagPanel.Fill.Both;
  }.peer)

  picLabel = new Label
  picLabel.peer.addMouseListener(new MouseAdapter {
    override def mouseClicked(e: MouseEvent) = {
      showBigPicture
    }
  })
  add(picLabel, new CustomConstraints {
    grid = (0,1); gridheight = 2;  
  })

  userDescription = new TextArea {
    background = TweetDetailPanel.this.background
    lineWrap = true
    wordWrap = true
    editable = false
  }
  add(userDescription, new CustomConstraints {
    grid = (1,1); gridheight=2; fill = GridBagPanel.Fill.Both; weightx = 1; weighty = 1;
  })
  
  val statusTableModel = table.getModel.asInstanceOf[StatusTableModel]

  table.getSelectionModel.addListSelectionListener(new ListSelectionListener {
    def valueChanged(e: ListSelectionEvent) = {
      if (! e.getValueIsAdjusting) {
        if (table.getSelectedRowCount == 1) {
          try {
            val modelRowIndex = table.convertRowIndexToModel(table.getSelectedRow)
            val status = statusTableModel.getStatusAt(modelRowIndex)
            showStatusDetails(status)
          } catch {
            case ex: IndexOutOfBoundsException => println(ex)
          }
        } else {
          clearStatusDetails
        }
      }
    }
  })
  
  private def showStatusDetails(status: NodeSeq) {
    val user = status \ "user"
    userDescription.text = (user \ "name").text + " • " +
            (user \ "location").text + " • " + (user \ "description").text
    largeTweet.setText(HtmlFormatter.createTweetHtml((status \ "text").text, 
      (status \ "in_reply_to_status_id").text))
    val picUrl = (user \ "profile_image_url").text
    showSmallPicture(picUrl)
  }

  import scala.actors.Actor._
 
  case class FetchImage(val url: String)
  case class ImageReady(val url: String, val imageIcon: ImageIcon)
  
  val imageReceiver = actor {
    while(true) receive {
      case ir: ImageReady => {
        SwingUtilities.invokeLater(new Runnable() { def run {
          if (ir.url == showingUrl) { // If user is moving quickly there may be several pending
            if (ir.imageIcon.getIconHeight <= THUMBNAIL_SIZE) picLabel.icon = ir.imageIcon // Ignore broken, too-big thumbnails 
            setBigPicLabelIcon
          }
          }
        })
      }
    }
  }
  
  val picFetcher = actor {
    while(true) receive {
      case fi: FetchImage => 
        if (mailboxSize == 0) imageReceiver ! new ImageReady(fi.url, new ImageIcon(new URL(fi.url)))
        // else ignore this request because of the newer one behind it
    }
  }

  private def showSmallPicture(picUrl: String) {
    if (! picUrl.equals(showingUrl)) {
      showingUrl = picUrl
      picLabel.icon = transparentPic
      picFetcher ! new FetchImage(picUrl)
    }
  }

{'fraction_non_alphanumeric': 0.1000246062992126, 'fraction_numerical': 0.00578248031496063, 'mean_word_length': 3.1708568496664955, 'pattern_counts': {'":': 0, '<': 7, '<?xml version=': 0, '>': 9, 'https://': 0, 'lorem ipsum': 0, 'www.': 0, 'xml': 2}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 0, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '1375528', 'n_tokens_mistral': 2586, 'n_tokens_neox': 2520, 'n_words': 581}