Ejemplo n.º 1
0
Archivo: gui.py Proyecto: NobbZ/disco
class MainWindow(QtGui.QMainWindow):

   def __init__(self, parent=None):
      QtGui.QMainWindow.__init__(self, parent)
      self.ui = Ui_MainWindow()
      self.ui.setupUi(self)

      self.nextWorkerInput = ["[]", "1"]
      self.bestPropScore = -1
      self.clear_worker_table()

      self.DataCollector = JsonReader(self)
      self.connect(self.DataCollector, QtCore.SIGNAL("received_data"), self.received)
      self.connect(self.DataCollector, QtCore.SIGNAL("worker_updated"), self.update_worker)
      self.connect(self.DataCollector, QtCore.SIGNAL("round_started"), self.start_round)
      self.connect(self.DataCollector, QtCore.SIGNAL("worker_input_changed"), self.update_worker_input)
      self.connect(self.DataCollector, QtCore.SIGNAL("problem_state_changed"), self.update_problem_state)
      self.connect(self.DataCollector, QtCore.SIGNAL("all_data"), self.update_all)
      self.DataCollector.start()

      # proposition tab
      QtCore.QObject.connect(self.ui.cbxId, QtCore.SIGNAL("currentIndexChanged(int)"), self.cbxId_indexChanged)

      self.paintBox = PaintBox(self.ui.tabProposition)
      sizePolicy = QtGui.QSizePolicy(QtGui.QSizePolicy.Expanding, QtGui.QSizePolicy.Expanding)
      sizePolicy.setHorizontalStretch(0)
      sizePolicy.setVerticalStretch(0)
      sizePolicy.setHeightForWidth(self.paintBox.sizePolicy().hasHeightForWidth())
      self.paintBox.setSizePolicy(sizePolicy)
      self.ui.gridLayout_4.addWidget(self.paintBox, 1, 0, 1, 1)

      # io tab
      QtCore.QObject.connect(self.ui.btnSend, QtCore.SIGNAL("clicked()"), self.btnSend_clicked)
      QtCore.QObject.connect(self.ui.edtSend, QtCore.SIGNAL("returnPressed()"), self.btnSend_clicked)

   def closeEvent(self, e):
      self.send(json.dumps({'action': 'quit program'}))
      self.DataCollector.terminate() # TODO: "This function is dangerous and its use is discouraged"
      self.DataCollector.wait()
      e.accept()
      app.exit()


   ###############################
   ##    main menu / buttons    ##
   ###############################

   ## io tab
   def btnSend_clicked(self):
      msg = self.ui.edtSend.text()
      self.send(msg)
      self.ui.edtSend.clear()

   def cbxId_indexChanged(self, idx):
      ## DEBUG
      #sys.stderr.write(">> cbxId_indexChanged %d\n" % idx)
      #sys.stderr.flush()
      self.display_proposition_by_cbxId_index(idx)


   #######################
   ##    JSON events    ##
   #######################

   def update_worker(self, id, proposition, propCaption, propScore, processedScore, problemScore, blocked, working):
      ## DEBUG
      #sys.stderr.write("[json] update_worker:\n")
      #sys.stderr.write("       id         : %s\n" % id)
      #sys.stderr.write("       proposition: %s\n" % proposition)
      #sys.stderr.write("       caption    : %s\n" % propCaption)
      #sys.stderr.write("       score      : %s\n" % propScore)
      #sys.stderr.flush()
      if id in self.worker:
         (workerName, bestProposition, bestPropCaption, bestPropScore) = self.worker[id]
         self.worker[id] = (workerName, proposition, propCaption, propScore)
         if (blocked == "no"):
            ## valid proposition: check if better
            if propScore > self.bestPropScore:
               self.bestPropScore = propScore
               ## auto switch to best proposition
               newCbxIndex = self.ui.cbxId.findText(id)
               if newCbxIndex == self.ui.cbxId.currentIndex():
                  self.display_proposition_by_cbxId_index(newCbxIndex)
               else:
                  self.ui.cbxId.setCurrentIndex(newCbxIndex)
         else:
            if propScore < 0:
               ## invalid proposition: find and display the next better proposition
               if 1 == 1 or self.ui.cbxId.text() == id:
                  nextBestId = id
                  nextBestScore = -1
                  for i in self.worker.keys():
                     (n, p, c, s) = self.worker[i]
                     if s > nextBestScore:
                        nextBestId = i
                        nextBestScore = s
                  self.bestPropScore = nextBestScore
                  ## switch to next better proposition
                  newCbxIndex = self.ui.cbxId.findText(nextBestId)
                  if newCbxIndex == self.ui.cbxId.currentIndex():
                     self.display_proposition_by_cbxId_index(newCbxIndex)
                  else:
                     self.ui.cbxId.setCurrentIndex(newCbxIndex)

   def start_round(self, round):
      ## DEBUG
      #sys.stderr.write("[json] start_round:\n")
      #sys.stderr.write("       round: %s\n" % round)
      #sys.stderr.flush()
      ## activate proposition tab
      self.ui.tabWidget.setCurrentIndex(2)
      self.paintBox.setSquareSize(int(self.nextWorkerInput[1]))
      self.ui.lblInput.setText("<html><body>"
                               + "<p><b>Square: </b>" + self.nextWorkerInput[1] + "</p>"
                               + "<p><b>Squares </b>(" + str(self.nextWorkerInput[0].count(",")) + ")<b>: </b>" + self.nextWorkerInput[0].replace(",", ", ") + "</p></body></html>")
      self.bestPropScore = -1
      self.ui.lblWorker.setText("")
      self.ui.lblScore.setText("")
      for workerId in self.worker.keys():
         (name, proposition, propCaption, propScore) = self.worker[workerId]
         self.worker[workerId] = (name, "[]", "", 0)
      self.paintBox.drawProposition("[]")

   def update_worker_input(self, workerInput):
      ## DEBUG
      #sys.stderr.write("[json] update_worker_input:\n")
      #sys.stderr.write("       workerInput: %s\n" % workerInput)
      #sys.stderr.flush()
      self.nextWorkerInput = workerInput

   def update_problem_state(self, problemState):
      self.ui.lblState.setText(problemState.replace(",", ", "))

   def update_all(self, running, workerList, problemList, problemIdx, round, workerInput, problemState):
      ## DEBUG
      #sys.stderr.write("[json] update_all:\n")
      #sys.stderr.write("       running   : %s\n" % running)
      #sys.stderr.write("       workerList: %s\n" % workerList)
      #sys.stderr.write("       ...\n")
      #sys.stderr.flush()
      self.clear_worker_table()
      for id, name, group, proposition, caption, score, processedScore, problemScore, blocked, working in workerList:
         self.add_worker(id, name, group, proposition, caption, score, processedScore, problemScore, blocked, working)
      self.update_worker_input(workerInput)
      self.update_problem_state(problemState)
      if running:
         self.start_round(round)


   #############################
   ##    private functions    ##
   #############################

   def send(self, msg):
      self.ui.txtRecv.appendHtml("<span style='font-weight:bold;color:red'>send:</span> "
                                 + escape_html(msg).rstrip("\n").replace("\n","<br />"))
      print(msg)
      sys.stdout.flush()

   def received(self, msg):
      self.ui.txtRecv.appendHtml("<span style='font-weight:bold;color:blue'>recv:</span> "
                                 + escape_html(msg).rstrip("\n").replace("\n","<br />"))

   def clear_worker_table(self):
      self.worker = {}
      self.ui.cbxId.clear()

   def add_worker(self, id, name, group, proposition, propCaption, propScore, processedScore, problemScore, blocked, working):
      self.worker[id] = (name, proposition, propCaption, propScore)
      self.ui.cbxId.clear()
      self.ui.cbxId.addItems(sorted(self.worker.keys()))

   def display_proposition_by_cbxId_index(self, idx):
      ## DEBUG
      #sys.stderr.write(">> display_proposition_by_cbxId_index %d\n" % idx)
      #sys.stderr.flush()
      if idx > -1:
         workerId = str(self.ui.cbxId.itemText(idx))
         if workerId in self.worker:
            ## DEBUG
            #sys.stderr.write(">>                    %s\n" % workerId)
            #sys.stderr.flush()
            (workerName, lastProposition, lastPropCaption, lastPropScore) = self.worker[workerId]
            self.ui.lblWorker.setText("<html><body><p style='font-size:12pt'><b>" + workerName + "</b></p></body></html>")
            self.ui.lblScore.setText("<html><body><p style='font-size:12pt'><b>Score: </b>" + str(lastPropScore) + "</p></body></html>")
            self.paintBox.drawProposition(lastProposition)
Ejemplo n.º 2
0
class MainWindow(QtGui.QMainWindow):
    def __init__(self, parent=None):
        QtGui.QMainWindow.__init__(self, parent)
        self.ui = Ui_MainWindow()
        self.ui.setupUi(self)

        self.nextWorkerInput = ["[]", "1"]
        self.bestPropScore = -1
        self.clear_worker_table()

        self.DataCollector = JsonReader(self)
        self.connect(self.DataCollector, QtCore.SIGNAL("received_data"),
                     self.received)
        self.connect(self.DataCollector, QtCore.SIGNAL("worker_updated"),
                     self.update_worker)
        self.connect(self.DataCollector, QtCore.SIGNAL("round_started"),
                     self.start_round)
        self.connect(self.DataCollector, QtCore.SIGNAL("worker_input_changed"),
                     self.update_worker_input)
        self.connect(self.DataCollector,
                     QtCore.SIGNAL("problem_state_changed"),
                     self.update_problem_state)
        self.connect(self.DataCollector, QtCore.SIGNAL("all_data"),
                     self.update_all)
        self.DataCollector.start()

        # proposition tab
        QtCore.QObject.connect(self.ui.cbxId,
                               QtCore.SIGNAL("currentIndexChanged(int)"),
                               self.cbxId_indexChanged)

        self.paintBox = PaintBox(self.ui.tabProposition)
        sizePolicy = QtGui.QSizePolicy(QtGui.QSizePolicy.Expanding,
                                       QtGui.QSizePolicy.Expanding)
        sizePolicy.setHorizontalStretch(0)
        sizePolicy.setVerticalStretch(0)
        sizePolicy.setHeightForWidth(
            self.paintBox.sizePolicy().hasHeightForWidth())
        self.paintBox.setSizePolicy(sizePolicy)
        self.ui.gridLayout_4.addWidget(self.paintBox, 1, 0, 1, 1)

        # io tab
        QtCore.QObject.connect(self.ui.btnSend, QtCore.SIGNAL("clicked()"),
                               self.btnSend_clicked)
        QtCore.QObject.connect(self.ui.edtSend,
                               QtCore.SIGNAL("returnPressed()"),
                               self.btnSend_clicked)

    def closeEvent(self, e):
        self.send(json.dumps({'action': 'quit program'}))
        self.DataCollector.terminate(
        )  # TODO: "This function is dangerous and its use is discouraged"
        self.DataCollector.wait()
        e.accept()
        app.exit()

    ###############################
    ##    main menu / buttons    ##
    ###############################

    ## io tab
    def btnSend_clicked(self):
        msg = self.ui.edtSend.text()
        self.send(msg)
        self.ui.edtSend.clear()

    def cbxId_indexChanged(self, idx):
        ## DEBUG
        #sys.stderr.write(">> cbxId_indexChanged %d\n" % idx)
        #sys.stderr.flush()
        self.display_proposition_by_cbxId_index(idx)

    #######################
    ##    JSON events    ##
    #######################

    def update_worker(self, id, proposition, propCaption, propScore,
                      processedScore, problemScore, blocked, working):
        ## DEBUG
        #sys.stderr.write("[json] update_worker:\n")
        #sys.stderr.write("       id         : %s\n" % id)
        #sys.stderr.write("       proposition: %s\n" % proposition)
        #sys.stderr.write("       caption    : %s\n" % propCaption)
        #sys.stderr.write("       score      : %s\n" % propScore)
        #sys.stderr.flush()
        if id in self.worker:
            (workerName, bestProposition, bestPropCaption,
             bestPropScore) = self.worker[id]
            self.worker[id] = (workerName, proposition, propCaption, propScore)
            if (blocked == "no"):
                ## valid proposition: check if better
                if propScore > self.bestPropScore:
                    self.bestPropScore = propScore
                    ## auto switch to best proposition
                    newCbxIndex = self.ui.cbxId.findText(id)
                    if newCbxIndex == self.ui.cbxId.currentIndex():
                        self.display_proposition_by_cbxId_index(newCbxIndex)
                    else:
                        self.ui.cbxId.setCurrentIndex(newCbxIndex)
            else:
                if propScore < 0:
                    ## invalid proposition: find and display the next better proposition
                    if 1 == 1 or self.ui.cbxId.text() == id:
                        nextBestId = id
                        nextBestScore = -1
                        for i in self.worker.keys():
                            (n, p, c, s) = self.worker[i]
                            if s > nextBestScore:
                                nextBestId = i
                                nextBestScore = s
                        self.bestPropScore = nextBestScore
                        ## switch to next better proposition
                        newCbxIndex = self.ui.cbxId.findText(nextBestId)
                        if newCbxIndex == self.ui.cbxId.currentIndex():
                            self.display_proposition_by_cbxId_index(
                                newCbxIndex)
                        else:
                            self.ui.cbxId.setCurrentIndex(newCbxIndex)

    def start_round(self, round):
        ## DEBUG
        #sys.stderr.write("[json] start_round:\n")
        #sys.stderr.write("       round: %s\n" % round)
        #sys.stderr.flush()
        ## activate proposition tab
        self.ui.tabWidget.setCurrentIndex(2)
        self.paintBox.setSquareSize(int(self.nextWorkerInput[1]))
        self.ui.lblInput.setText("<html><body>" + "<p><b>Square: </b>" +
                                 self.nextWorkerInput[1] + "</p>" +
                                 "<p><b>Squares </b>(" +
                                 str(self.nextWorkerInput[0].count(",")) +
                                 ")<b>: </b>" +
                                 self.nextWorkerInput[0].replace(",", ", ") +
                                 "</p></body></html>")
        self.bestPropScore = -1
        self.ui.lblWorker.setText("")
        self.ui.lblScore.setText("")
        for workerId in self.worker.keys():
            (name, proposition, propCaption, propScore) = self.worker[workerId]
            self.worker[workerId] = (name, "[]", "", 0)
        self.paintBox.drawProposition("[]")

    def update_worker_input(self, workerInput):
        ## DEBUG
        #sys.stderr.write("[json] update_worker_input:\n")
        #sys.stderr.write("       workerInput: %s\n" % workerInput)
        #sys.stderr.flush()
        self.nextWorkerInput = workerInput

    def update_problem_state(self, problemState):
        self.ui.lblState.setText(problemState.replace(",", ", "))

    def update_all(self, running, workerList, problemList, problemIdx, round,
                   workerInput, problemState):
        ## DEBUG
        #sys.stderr.write("[json] update_all:\n")
        #sys.stderr.write("       running   : %s\n" % running)
        #sys.stderr.write("       workerList: %s\n" % workerList)
        #sys.stderr.write("       ...\n")
        #sys.stderr.flush()
        self.clear_worker_table()
        for id, name, group, proposition, caption, score, processedScore, problemScore, blocked, working in workerList:
            self.add_worker(id, name, group, proposition, caption, score,
                            processedScore, problemScore, blocked, working)
        self.update_worker_input(workerInput)
        self.update_problem_state(problemState)
        if running:
            self.start_round(round)

    #############################
    ##    private functions    ##
    #############################

    def send(self, msg):
        self.ui.txtRecv.appendHtml(
            "<span style='font-weight:bold;color:red'>send:</span> " +
            escape_html(msg).rstrip("\n").replace("\n", "<br />"))
        print(msg)
        sys.stdout.flush()

    def received(self, msg):
        self.ui.txtRecv.appendHtml(
            "<span style='font-weight:bold;color:blue'>recv:</span> " +
            escape_html(msg).rstrip("\n").replace("\n", "<br />"))

    def clear_worker_table(self):
        self.worker = {}
        self.ui.cbxId.clear()

    def add_worker(self, id, name, group, proposition, propCaption, propScore,
                   processedScore, problemScore, blocked, working):
        self.worker[id] = (name, proposition, propCaption, propScore)
        self.ui.cbxId.clear()
        self.ui.cbxId.addItems(sorted(self.worker.keys()))

    def display_proposition_by_cbxId_index(self, idx):
        ## DEBUG
        #sys.stderr.write(">> display_proposition_by_cbxId_index %d\n" % idx)
        #sys.stderr.flush()
        if idx > -1:
            workerId = str(self.ui.cbxId.itemText(idx))
            if workerId in self.worker:
                ## DEBUG
                #sys.stderr.write(">>                    %s\n" % workerId)
                #sys.stderr.flush()
                (workerName, lastProposition, lastPropCaption,
                 lastPropScore) = self.worker[workerId]
                self.ui.lblWorker.setText(
                    "<html><body><p style='font-size:12pt'><b>" + workerName +
                    "</b></p></body></html>")
                self.ui.lblScore.setText(
                    "<html><body><p style='font-size:12pt'><b>Score: </b>" +
                    str(lastPropScore) + "</p></body></html>")
                self.paintBox.drawProposition(lastProposition)
Ejemplo n.º 3
0
class MainWindow(QtGui.QMainWindow):

   def __init__(self, parent=None):
      QtGui.QMainWindow.__init__(self, parent)
      self.ui = Ui_MainWindow()
      self.ui.setupUi(self)

      # status bar
      self.labelProblemSpec  = QtGui.QLabel()
      self.labelProblemTime  = QtGui.QLabel()
      self.labelCurrentRound = QtGui.QLabel()
      self.labelWorkerInput  = QtGui.QLabel()
      self.ui.statusbar.addWidget(self.labelProblemSpec,  1)
      self.ui.statusbar.addWidget(self.labelProblemTime,  1)
      self.ui.statusbar.addWidget(self.labelCurrentRound, 1)
      self.ui.statusbar.addWidget(self.labelWorkerInput,  1)

      # set menu shortcuts
      self.ui.actionLoadGameState.setShortcut(QtGui.QKeySequence(self.tr("Ctrl+O")))
      self.ui.actionSaveGameState.setShortcut(QtGui.QKeySequence(self.tr("Ctrl+S")))
      self.ui.actionQuit.setShortcut(QtGui.QKeySequence(self.tr("Ctrl+Q")))
      self.ui.actionStartRound.setShortcut(QtGui.QKeySequence(self.tr("Ctrl+R")))
      self.ui.actionAddScores.setShortcut(QtGui.QKeySequence(self.tr("Ctrl+A")))
      self.ui.actionKillAllWorkers.setShortcut(QtGui.QKeySequence(self.tr("Ctrl+K")))

      self.DataCollector = JsonReader(self)
      self.connect(self.DataCollector, QtCore.SIGNAL("received_data"), self.received)
      self.connect(self.DataCollector, QtCore.SIGNAL("worker_updated"), self.update_worker)
      self.connect(self.DataCollector, QtCore.SIGNAL("round_started"), self.start_round)
      self.connect(self.DataCollector, QtCore.SIGNAL("round_ended"), self.end_round)
      self.connect(self.DataCollector, QtCore.SIGNAL("worker_input_changed"), self.update_worker_input)
      self.connect(self.DataCollector, QtCore.SIGNAL("problem_chosen"), self.choose_problem)
      self.connect(self.DataCollector, QtCore.SIGNAL("all_data"), self.update_all)
      self.connect(self.DataCollector, QtCore.SIGNAL("save_game_state_reply"), self.save_game_state_reply)
      self.connect(self.DataCollector, QtCore.SIGNAL("load_game_state_reply"), self.load_game_state_reply)
      self.DataCollector.start()

      self.problemAnswerTime = 0
      self.roundTimerRemaining = 0
      self.roundTimer = QtCore.QTimer()
      QtCore.QObject.connect(self.roundTimer, QtCore.SIGNAL("timeout()"), self.roundTimer_tick)

      # file menu
      QtCore.QObject.connect(self.ui.actionLoadGameState, QtCore.SIGNAL("triggered()"), self.btnLoadGameState_clicked)
      QtCore.QObject.connect(self.ui.actionSaveGameState, QtCore.SIGNAL("triggered()"), self.btnSaveGameState_clicked)
      QtCore.QObject.connect(self.ui.actionReloadAllData, QtCore.SIGNAL("triggered()"), self.btnReloadAllData_clicked)
      QtCore.QObject.connect(self.ui.actionQuit, QtCore.SIGNAL("triggered()"), self.btnQuit_clicked)

      # round menu
      QtCore.QObject.connect(self.ui.actionStartRound, QtCore.SIGNAL("triggered()"), self.btnStartRound_clicked)
      QtCore.QObject.connect(self.ui.actionAddScores, QtCore.SIGNAL("triggered()"), self.btnAddScores_clicked)
      QtCore.QObject.connect(self.ui.actionKillAllWorkers, QtCore.SIGNAL("triggered()"), self.btnKillAllWorkers_clicked)

      # worker tab
      self.ui.tableWorker.setContextMenuPolicy(QtCore.Qt.CustomContextMenu)
      self.ui.tableWorker.customContextMenuRequested.connect(self.tableWorker_requestContextMenu)

      # io tab
      QtCore.QObject.connect(self.ui.btnSend, QtCore.SIGNAL("clicked()"), self.btnSend_clicked)
      QtCore.QObject.connect(self.ui.edtSend, QtCore.SIGNAL("returnPressed()"), self.btnSend_clicked)

      # worker table header
      thh = self.ui.tableWorker.horizontalHeader()
      thh.setVisible(True)
      thh.resizeSection(0,  50)   # ranking group
      thh.resizeSection(1,  60)   # id
      thh.resizeSection(2, 170)   # name
      thh.resizeSection(3, 230)   # proposition
      thh.resizeSection(4, 100)   # points
      thh.resizeSection(5,  50)   # processed points
      thh.resizeSection(6, 100)   # problem points (accumulated over all rounds on this problem)
      thh.setSortIndicator(1, QtCore.Qt.AscendingOrder)

      tvh = self.ui.tableWorker.verticalHeader()
      tvh.setVisible(True)
      tvh.setResizeMode(QtGui.QHeaderView.Fixed)

      self.reset_problem_list([])
      self.worker_blocked = {}


   def closeEvent(self, e):
      self.send(json.dumps({'action': 'quit program'}))
      self.DataCollector.terminate() # TODO: "This function is dangerous and its use is discouraged"
      self.DataCollector.wait()
      e.accept()
      app.exit()


   ###############################
   ##    main menu / buttons    ##
   ###############################

   ## file menu
   def btnLoadGameState_clicked(self):
      fileName = str(QtGui.QFileDialog.getOpenFileName())
      if fileName != "":
         self.send(json.dumps({'action': 'load game state', 'file path': fileName}))

   def btnSaveGameState_clicked(self):
      fileName = str(QtGui.QFileDialog.getSaveFileName())
      if fileName != "":
         self.send(json.dumps({'action': 'save game state', 'file path': fileName}))

   def btnReloadAllData_clicked(self):
      self.send(json.dumps({'action': 'get all data'}))

   def btnQuit_clicked(self):
      self.close()

   ## problems menu
   def btnChooseProblem_clicked(self, idx, action, oldChecked):
      action.setChecked(oldChecked) # undo auto check
      self.send(json.dumps({'action': 'choose problem', 'problem idx': idx}))

   ## round menu
   def btnStartRound_clicked(self):
      self.send(json.dumps({'action': 'start round'}))

   def btnAddScores_clicked(self):
      self.send(json.dumps({'action': 'add scores'}))
      self.ui.actionAddScores.setEnabled(False)

   def btnKillAllWorkers_clicked(self):
      self.send(json.dumps({'action': 'kill all workers'}))

   ## worker tab
   def tableWorker_requestContextMenu(self, position):
      workerId = str(self.ui.tableWorker.item(self.ui.tableWorker.currentRow(), 1).text())
      # create menu
      menu = QtGui.QMenu()
      actApply = menu.addAction("&Apply proposition")
      actBlock = None
      actUnblock = None
      if self.worker_blocked[workerId]:
         actUnblock = menu.addAction("Un&block worker '" + workerId + "'")
      else:
         actBlock = menu.addAction("&Block worker '" + workerId + "'")
      # execute menu synchronously
      action = menu.exec_(self.ui.tableWorker.viewport().mapToGlobal(position))
      if action != None:
         if action == actApply:
            if QtGui.QMessageBox.information(self, "Apply proposition", "Really apply proposition from " + workerId + "?",
                                             QtGui.QMessageBox.Yes | QtGui.QMessageBox.No,
                                             QtGui.QMessageBox.No) == QtGui.QMessageBox.Yes:
               self.send(json.dumps({'action': 'apply proposition', 'worker id': workerId}))
         elif action == actBlock:
            self.send(json.dumps({'action': 'block worker', 'worker id': workerId}))
         elif action == actUnblock:
            self.send(json.dumps({'action': 'unblock worker', 'worker id': workerId}))

   ## io tab
   def btnSend_clicked(self):
      msg = self.ui.edtSend.text()
      self.send(msg)
      self.ui.edtSend.clear()


   #######################
   ##    Round timer    ##
   #######################

   def roundTimer_tick(self):
      self.roundTimerRemaining -= self.roundTimer.interval()
      if self.roundTimerRemaining <= 0:
         self.roundTimer.stop()
         self.roundTimerRemaining = 0
      self.labelProblemTime.setText("Answer time remaining\n  " +
                                    str(self.roundTimerRemaining/1000) + "s")


   #######################
   ##    JSON events    ##
   #######################

   def update_worker(self, id, proposition, caption, score, processedScore, problemScore, blocked, working):
      row = self.get_worker_table_row(id)
      if proposition == None:
          proposition = ""
      if row != None:
         self.update_worker_by_row(row, id, proposition, caption, score, processedScore, problemScore, blocked, working)

   def start_round(self, round):
      self.ui.actionStartRound.setEnabled(False)
      self.ui.menuProblems.setEnabled(False)
      self.ui.actionAddScores.setEnabled(False)
      self.labelCurrentRound.setText("Round (running)\n  " + str(round))
      self.roundTimerRemaining = self.problemAnswerTime
      self.roundTimer.start(100)

   def end_round(self, round):
      self.ui.actionStartRound.setEnabled(True)
      self.ui.menuProblems.setEnabled(True)
      self.ui.actionAddScores.setEnabled(True)
      self.labelCurrentRound.setText("Round\n  " + str(round))
      self.roundTimerRemaining = 0
      self.roundTimer_tick()

   def update_worker_input(self, workerInput):
      def format_wi_line(line): return shorten_string(28, line)
      wiString = "\n".join(list(map(format_wi_line, workerInput)))
      self.labelWorkerInput.setText("Worker input for next round:\n" + wiString)

   def choose_problem(self, problemIdx):
      self.roundTimer.stop()
      self.reset_problem_list(self.problemList, problemIdx)
      probDesc, probSpec, answerTime, startState = self.problemList[problemIdx]
      self.labelProblemSpec.setText("Problem\n  " + probDesc)
      self.labelProblemTime.setText("Answer time\n  " + str(answerTime/1000.0) + "s")
      self.problemAnswerTime = answerTime
      self.labelCurrentRound.setText("")

   def update_all(self, running, workerList, problemList, problemIdx, round, workerInput, problemState):
      self.clear_worker_table()
      for id, name, group, proposition, caption, score, processedScore, problemScore, blocked, working in workerList:
         self.add_worker(id, name, group, proposition, caption, score, processedScore, problemScore, blocked, working)
      self.update_worker_input(workerInput)
      if running:
         self.start_round(round)
      else:
         self.end_round(round)
      self.problemList = problemList
      self.choose_problem(problemIdx)

   def save_game_state_reply(self, result):
      if result == "ok":
         msg = "Game state successfully saved."
         QtGui.QMessageBox.information(self, "Game state saved", msg, QtGui.QMessageBox.Ok)
      else:
         if   result == "enoent" : msg = "No such file or directory!"
         elif result == "enotdir": msg = "Not a directory!"
         elif result == "enospc" : msg = "No space left on device!"
         elif result == "eacces" : msg = "Permission denied!"
         elif result == "eisdir" : msg = "Illegal operation on a directory!"
         else                    : msg = "Unknown error: " + result
         QtGui.QMessageBox.warning(self, "Error saving game state", msg, QtGui.QMessageBox.Ok)

   def load_game_state_reply(self, result):
      if result == "ok":
         msg = "Game state successfully loaded."
         QtGui.QMessageBox.information(self, "Game state loaded", msg, QtGui.QMessageBox.Ok)
      else:
         if   result == "eformat": msg = "Invalid file format!"
         elif result == "enoent" : msg = "No such file or directory!"
         elif result == "enotdir": msg = "Not a directory!"
         elif result == "eacces" : msg = "Permission denied!"
         elif result == "eisdir" : msg = "Illegal operation on a directory!"
         else                    : msg = "Unknown error: " + result
         QtGui.QMessageBox.warning(self, "Error loading game state", msg, QtGui.QMessageBox.Ok)


   #############################
   ##    private functions    ##
   #############################

   def send(self, msg):
      self.ui.txtRecv.appendHtml("<span style='font-weight:bold;color:red'>send:</span> "
                                 + escape_html(msg).rstrip("\n").replace("\n","<br />"))
      print(msg)
      sys.stdout.flush()

   def received(self, msg):
      self.ui.txtRecv.appendHtml("<span style='font-weight:bold;color:blue'>recv:</span> "
                                 + escape_html(msg).rstrip("\n").replace("\n","<br />"))

   def get_worker_table_row(self, id):
      for row in range(0, self.ui.tableWorker.rowCount()):
         if self.ui.tableWorker.item(row, 1).text() == id:
            return row
      return None

   def clear_worker_table(self):
      self.worker_blocked = {}
      self.ui.tableWorker.clearContents()
      self.ui.tableWorker.setRowCount(0)

   def add_worker(self, id, name, group, proposition, propCaption, score, processedScore, problemScore, blocked, working):
      if proposition == None:
          proposition = ""

      self.worker_blocked[id] = blocked != "no"

      row = self.ui.tableWorker.rowCount()
      self.ui.tableWorker.setRowCount(row + 1)

      self.ui.tableWorker.setSortingEnabled(False)

      item = QtGui.QTableWidgetItem()
      item.setText(group)
      self.ui.tableWorker.setItem(row, 0, item)

      item = QtGui.QTableWidgetItem()
      item.setText(id)
      self.ui.tableWorker.setItem(row, 1, item)

      item = QtGui.QTableWidgetItem()
      item.setText(name)
      self.ui.tableWorker.setItem(row, 2, item)

      item = QtGui.QTableWidgetItem()
      self.ui.tableWorker.setItem(row, 3, item)

      item = CustomTableWidgetItem()
      item.setTextAlignment(QtCore.Qt.AlignRight|QtCore.Qt.AlignVCenter)
      self.ui.tableWorker.setItem(row, 4, item)

      item = CustomTableWidgetItem()
      item.setTextAlignment(QtCore.Qt.AlignRight|QtCore.Qt.AlignVCenter)
      self.ui.tableWorker.setItem(row, 5, item)

      item = CustomTableWidgetItem()
      item.setTextAlignment(QtCore.Qt.AlignRight|QtCore.Qt.AlignVCenter)
      self.ui.tableWorker.setItem(row, 6, item)

      self.update_worker_by_row(row, id, proposition, propCaption, score, processedScore, problemScore, blocked, working)

      self.ui.tableWorker.setSortingEnabled(True)

   def update_worker_by_row(self, row, id, proposition, propCaption, score, processedScore, problemScore, blocked, working):
      isBlocked = blocked != "no"
      blockedIdx = blocked["idx"] if "idx" in blocked else 0

      self.worker_blocked[id] = isBlocked

      self.ui.tableWorker.setSortingEnabled(False)

      brush = QtGui.QBrush(QtGui.QColor(190, 190, 190))
      if self.worker_blocked[id]:
         brush.setStyle(QtCore.Qt.SolidPattern)
      else:
         brush.setStyle(QtCore.Qt.NoBrush)

      self.ui.tableWorker.item(row, 0).setBackground(brush)

      self.ui.tableWorker.item(row, 1).setBackground(brush)

      self.ui.tableWorker.item(row, 2).setBackground(brush)

      item = self.ui.tableWorker.item(row, 3)
      item.setText(propCaption)
      item.setBackground(brush)

      item = self.ui.tableWorker.item(row, 4)
      item.setText(str(score))
      item.setCustomSortData(isBlocked, {False: int(score), True: blockedIdx}[isBlocked])
      item.setBackground(brush)

      item = self.ui.tableWorker.item(row, 5)
      item.setText(str(processedScore))
      item.setCustomSortData(isBlocked, {False: int(processedScore), True: blockedIdx}[isBlocked])
      item.setBackground(brush)

      item = self.ui.tableWorker.item(row, 6)
      item.setText(str(problemScore))
      item.setCustomSortData(isBlocked, {False: int(problemScore), True: blockedIdx}[isBlocked])
      item.setBackground(brush)

      if self.ui.tableWorker.cellWidget(row, 2) == None:
         if working:
            self.ui.tableWorker.setCellWidget(row, 2, WorkingWidget(self))
      else:
         if not working:
            self.ui.tableWorker.removeCellWidget(row, 2)

      self.ui.tableWorker.setSortingEnabled(True)

   def reset_problem_list(self, lst, checkedIdx=None):
      self.problemList = lst
      self.ui.menuProblems.clear()
      if lst == []:
         action = QtGui.QAction(self)
         action.setText("--- no problems ---")
         action.setEnabled(False)
         self.ui.menuProblems.addAction(action)
      else:
         for idx, (description, spec, answerTime, state) in enumerate(lst):
            action = QtGui.QAction(self)
            action.setText(description + "\t" + str(answerTime/1000.0) + "s")
            action.setCheckable(True)
            if checkedIdx == idx:
               action.setChecked(True)
            QtCore.QObject.connect(action, QtCore.SIGNAL("triggered()"),
                                   lambda i=idx, a=action, chk=(checkedIdx==idx):
                                      self.btnChooseProblem_clicked(i, a, chk))
            self.ui.menuProblems.addAction(action)