class Panel(QWidget):
    def __init__(self, parent=None, instr=None, lock=None, title="Instrument Panel"):
        # This class derivates from a Qt Widget so we have to call
        # the class builder ".__init__()"
        QWidget.__init__(self)
        # "self" is now a Qt Widget, then we load the user interface
        # generated with QtDesigner and call it self.ui
        self.ui = Keithley6221_Ui.Ui_Panel()
        # Now we have to feed the GUI building method of this object (self.ui)
        # with the current Qt Widget 'self', but the widgets from the design will actually be built as children
        # of the object self.ui
        self.ui.setupUi(self)
        self.setWindowTitle(title)
        self.reserved_access_to_instr = lock
        self.instr = instr
        self.monitor_timer = QTimer()
        # The timer would not wait for the completion of the task otherwise
        self.monitor_timer.setSingleShot(True)
        self.monitor_timer.timeout.connect(self.monitor)
        self.firsttime = 0
        # bug: if the box is checked in the .ui file, the system freezes
        # if self.ui.monitor.isChecked():self.monitor()

    def monitor(self, state=1):
        if state != 1:
            self.monitor_timer.stop()
        elif state and not (self.monitor_timer.isActive()):
            with self.reserved_access_to_instr:
                I = self.instr.query_current_source_amplitude()
                Vcomp = self.instr.query_voltage_compliance()
                outstate = self.instr.query_output_ON()
            self.ui.I_disp.setText(str(I * 1e6) + u" μA")
            self.ui.V_disp.setText(str(Vcomp) + " V")
            self.ui.outputON.setChecked(outstate)
            self.monitor_timer.start(self.ui.refresh_rate.value() * 1000)

    def update_timer_timeout(self, secs):
        # The value must be converted to milliseconds
        self.monitor_timer.setInterval(secs * 1000)

    def change_I(self, value=0):
        with self.reserved_access_to_instr:
            self.instr.set_current_source_amplitude(value * 1e6)

    def change_V_comp(self, value=0):
        with self.reserved_access_to_instr:
            self.instr.set_voltage_compliance(value)

    def switch_output(self, value=False):
        if value:
            with self.reserved_access_to_instr:
                self.instr.output_ON()
        else:
            with self.reserved_access_to_instr:
                self.instr.output_OFF()

    def reset_inst(self):
        with self.reserved_access_to_instr:
            self.instr.reset()
Ejemplo n.º 2
0
class Reader(QThread):
    data = Signal(Meter)
    warning = Signal(str)
    error = Signal(str)

    def __init__(self, port):
        QThread.__init__(self)

        self._port = port
        self._serial = None
        self._parser = Parser()
        self._cancel = False
        self._timeout = None

    def run(self):
        self._timeout = QTimer()
        self._timeout.setInterval(2000)
        self._timeout.timeout.connect(self.__onTimeout)

        try:
            self._serial = serial.Serial(self._port,
                                         baudrate=19200,
                                         bytesize=serial.SEVENBITS,
                                         stopbits=serial.STOPBITS_ONE,
                                         parity=serial.PARITY_ODD,
                                         timeout=1)
            self._serial.dtr = True
            self._serial.rts = False

            while not self._cancel:
                if not self._timeout.isActive():
                    self._timeout.start()
                data = self._serial.readline()
                data = data.strip()
                if len(data) == 12:
                    timestamp = time.time()
                    result = self._parser.parse(data, timestamp)
                    if result is not None:
                        self._timeout.stop()
                        self.data.emit(result)
                    else:
                        self.warning.emit('Invalid data received')
                QApplication.processEvents()
            self._serial.close()
        except serial.SerialException as e:
            self.error.emit(e.message)

    def __onTimeout(self):
        self.warning.emit('No data received')

    def stop(self):
        self._cancel = True
Ejemplo n.º 3
0
 def downlaod(self, url, timeout=60):
     loop = QEventLoop()
     timer = QTimer()
     timer.setSingleShot(True)
     timer.timeout.connect(loop.quit)
     self.loadFinished.connect(loop.quit)
     self.load(QUrl(url))
     timer.start(timeout * 1000)
     loop.exec_()
     if timer.isActive():
         timer.stop()
         return self.html()
     else:
         print 'Request timed out: ' + url
Ejemplo n.º 4
0
 def open(self, url, timeout=60):
     """Wait for download to complete and return result"""
     loop = QEventLoop()
     timer = QTimer()
     timer.setSingleShot(True)
     timer.timeout.connect(loop.quit)
     self.loadFinished.connect(loop.quit)
     self.load(QUrl(url))
     timer.start(timeout * 1000)
     loop.exec_()  # delay here until download finished
     if timer.isActive():
         # downloaded successfully
         timer.stop()
         return self.html()
     else:
         # timed out
         print 'Request timed out:', url
Ejemplo n.º 5
0
 def open(self, url, timeout=60):
     """Wait for download to complete and return result"""
     loop = QEventLoop()
     timer = QTimer()
     timer.setSingleShot(True)
     timer.timeout.connect(loop.quit)
     self.loadFinished.connect(loop.quit)
     self.load(QUrl(url))
     timer.start(timeout * 1000)
     loop.exec_() # delay here until download finished
     if timer.isActive():
         # downloaded successfully
         timer.stop()
         return self.html()
     else:
         # timed out
         print 'Request timed out:', url
Ejemplo n.º 6
0
    def open(self, url, timeout=60):
        """wait for download to complete and return result"""
        loop = QEventLoop()
        timer = QTimer()
        timer.setSingleShot(True)  # True表示触发定时器后,仅执行事件一次
        timer.timeout.connect(loop.quit)  # 若超时,则连接loop.quit,退出事件循环
        self.loadFinished.connect(loop.quit)
        self.load(url)
        timer.start(timeout * 1000)  # 定时器以ms为单位,设置超时时间为60s
        loop.exec_()  # 等待网页加载完成后,在执行后面的代码

        if timer.isActive():
            # downloaded successfully
            timer.stop()
            return self.html()
        else:
            # timed out
            print 'Request timed out:', url
Ejemplo n.º 7
0
class GlobalTimer:
    """All parsing and highlighting is done in main loop thread.
    If parsing is being done for long time, main loop gets blocked.
    Therefore SyntaxHighlighter controls, how long parsign is going, and, if too long,
    schedules timer and releases main loop.
    One global timer is used by all Qutepart instances, because main loop time usage
    must not depend on opened files count
    """
    
    def __init__(self):
        self._timer = QTimer()
        self._timer.setSingleShot(True)
        self._timer.timeout.connect(self._onTimer)
        
        self._scheduledCallbacks = []
    
    def isActive(self):
        return self._timer.isActive()
    
    def scheduleCallback(self, callback):
        if not callback in self._scheduledCallbacks:
            self._scheduledCallbacks.append(callback)
            self._timer.start()
    
    def unScheduleCallback(self, callback):
        if callback in self._scheduledCallbacks:
            self._scheduledCallbacks.remove(callback)
        if not self._scheduledCallbacks:
            self._timer.stop()
    
    def isCallbackScheduled(self, callback):
        return callback in self._scheduledCallbacks
    
    def _onTimer(self):
        if self._scheduledCallbacks:
            callback = self._scheduledCallbacks.pop()
            callback()
        if self._scheduledCallbacks:
            self._timer.start()
Ejemplo n.º 8
0
class GlobalTimer:
    """All parsing and highlighting is done in main loop thread.
    If parsing is being done for long time, main loop gets blocked.
    Therefore SyntaxHighlighter controls, how long parsign is going, and, if too long,
    schedules timer and releases main loop.
    One global timer is used by all Qutepart instances, because main loop time usage
    must not depend on opened files count
    """
    def __init__(self):
        self._timer = QTimer()
        self._timer.setSingleShot(True)
        self._timer.timeout.connect(self._onTimer)

        self._scheduledCallbacks = []

    def isActive(self):
        return self._timer.isActive()

    def scheduleCallback(self, callback):
        if not callback in self._scheduledCallbacks:
            self._scheduledCallbacks.append(callback)
            self._timer.start()

    def unScheduleCallback(self, callback):
        if callback in self._scheduledCallbacks:
            self._scheduledCallbacks.remove(callback)
        if not self._scheduledCallbacks:
            self._timer.stop()

    def isCallbackScheduled(self, callback):
        return callback in self._scheduledCallbacks

    def _onTimer(self):
        if self._scheduledCallbacks:
            callback = self._scheduledCallbacks.pop()
            callback()
        if self._scheduledCallbacks:
            self._timer.start()
Ejemplo n.º 9
0
class DisplayController(QObject):
    """ Controller class for the display app

    Acts as a coordinator between ClimateControlModel and DisplayView
    """
    def __init__(self, parent=None):
        super(DisplayController, self).__init__(parent)
        _, external_version = get_app_version()
        self.__display_app = DisplayView(external_version)
        self.__display_app.init_login_view(UserList().user_names)
        self.__display_app.register_login_callback(self.__login_btn_clicked)
        self.__display_app.close_signal.connect(self.__close)

        self.__model = None
        self.__refresh_graph_timer = QTimer(self)
        self.__refresh_graph_timer.timeout.connect(self.__speculate_data)
        self.temperature_data = collections.OrderedDict()
        self.humidity_data = collections.OrderedDict()
        self.temperature = None
        self.humidity = None
        self.__display_view_initialized = False

    @Slot(str, str)
    def __login_btn_clicked(self, user_name, password):
        """ Slot for the login button

         Tries to create model with the provided user_name and password

        :param str user_name: flow user name
        :param str password: flow user password
        """
        # clear any old status
        self.__display_app.login_view_show_error(None)

        LOGGER.debug("Trying to login with username = {}".format(user_name))

        try:
            # try to initialize model
            self.__model = ClimateControlModel()
            self.__model.connection_status.connect(self.__connection_status_result)
            try:
                self.__model.initialize(user_name, password)
            except LoginFailed:
                self.__display_app.login_view_show_error(UIStrings.LOGIN_FAILED)
            except ControllerNotFound:
                self.__display_app.login_view_show_error(UIStrings.CONTROLLER_DEVICE_NOT_FOUND)
            else:
                # Save username if no exception comes
                UserList().add_name(user_name)
                self.__model.setting_received.connect(self.__get_setting_response)
                # define slot for receiving events from Controller
                self.__model.measurement_changed.connect(self.__measurement_changed)
                self.__model.device_status_changed.connect(self.__device_status_changed)
                self.__model.relay_status_changed.connect(self.__relay_status_changed)
                self.__model.controller_status.connect(self.__controller_status)
                self.__model.latency_changed.connect(self.__latency_changed)
                self.__model.get_settings()
        except FlowLibraryError:
            self.__display_app.login_view_show_error(UIStrings.FLOW_LIBRARY_ERROR)

    @Slot(dict)
    def __get_setting_response(self, result):
        """ Slot for the get setting response from ClimateControlModel

        Initializes display view with received setting or shows error
        :param dict result: dictionary of the setting to be shown on configuration tab
        """
        if not self.__display_view_initialized:
            self.__display_app.close_login_view()
            self.__display_app.init_display_view(result["setting"])
            self.__display_view_initialized = True
            if result["error"]:
                self.__display_app.update_status_bar(UIStrings.SETTING_NOT_RECEIVED)
                LOGGER.error(result["error"])

        else:
            self.__display_app.update_settings(result["setting"])

    @Slot(dict)
    def __connection_status_result(self, connection_status):
        """Slot function for connection status result

        :param dict connection_status: dictionary containing network and internet status
        """
        status = UIStrings.OK
        color = "green"
        if not connection_status["network"]:
            color = "red"
            status = UIStrings.NETWORK_DOWN
        elif not connection_status["internet"]:
            status = UIStrings.INTERNET_DOWN
            color = "red"
        self.__display_app.set_connection_status(status, color)

    @Slot()
    def __close(self):
        """ Slot function when view gets closed

        Tries to de-initialize the model and stop refresh graph timer if active
        """
        if self.__refresh_graph_timer.isActive():
            self.__refresh_graph_timer.stop()

        LOGGER.debug("closing model")
        if self.__model:
            self.__model.close()

    @Slot(MeasurementEvent)
    def __measurement_changed(self, event):
        """ Slot function which receives measurement event from model

        :param MeasurementEvent event: Measurement Event object received from model
        """
        if self.__refresh_graph_timer.isActive():
            self.__refresh_graph_timer.stop()
        self.temperature = event.temperature
        self.humidity = event.humidity
        self.__update_data(self.temperature, self.humidity)
        self.__display_app.plot_graph(self.temperature_data, self.humidity_data)
        self.__refresh_graph_timer.start(REFRESH_GRAPH_TIMEOUT)

    @Slot()
    def __speculate_data(self):
        """ This function is a slot for refresh_graph timeout event

        It updates measurement data with last received measurement values
        """
        self.__update_data(self.temperature, self.humidity)
        self.__display_app.plot_graph(self.temperature_data, self.humidity_data)
        self.__refresh_graph_timer.start(REFRESH_GRAPH_TIMEOUT)

    def __update_data(self, temperature, humidity):
        """  Updates measurement data
        :param float temperature: temperature value
        :param float humidity: humidity value
        """
        current_time = datetime.datetime.now()
        if not (self.temperature_data and self.humidity_data):
            # fill the dictionary with last GRAPH_WINDOW_SIZE seconds of data
            for i in range(GRAPH_WINDOW_SIZE, 0, -1):
                self.temperature_data[current_time - datetime.timedelta(seconds=i)] = 0
                self.humidity_data[current_time - datetime.timedelta(seconds=i)] = 0

        self.temperature_data[current_time] = temperature
        self.humidity_data[current_time] = humidity
        if current_time - self.temperature_data.keys()[0] > datetime.\
                timedelta(seconds=GRAPH_WINDOW_SIZE):
            del self.temperature_data[self.temperature_data.keys()[0]]
            del self.humidity_data[self.humidity_data.keys()[0]]

    @Slot(str)
    def __controller_status(self, status):
        """ Slot function which receives controller device status from model

        It configures refresh graph timer according to controller device status
        :param str status: controller device status
        """
        LOGGER.debug("Controller status received - {}".format(status))
        if self.__display_app:
            self.__display_app.\
                update_device_status(DeviceEnum.controller, status == "ONLINE")
            # Latency is not applicable if controller is OFFLINE
            if status == "OFFLINE":
                self.__display_app.update_latency(None)
                LOGGER.debug("Latency set to NA")

            if status == "ONLINE" and not self.__refresh_graph_timer.isActive():
                self.__refresh_graph_timer.start(REFRESH_GRAPH_TIMEOUT)
                LOGGER.debug("Refresh graph timer started")

            elif status == "OFFLINE" and self.__refresh_graph_timer.isActive():
                self.__refresh_graph_timer.stop()
                LOGGER.debug("Refresh graph timer stopped")

            elif status == "ONLINE" and self.__refresh_graph_timer.isActive():
                LOGGER.debug("Refresh graph timer is already active")

            elif status == "OFFLINE" and not self.__refresh_graph_timer.isActive():
                LOGGER.debug("Refresh graph timer is already inactive")

        else:
            LOGGER.debug("Controller status received before display window is initialized")

    @Slot(DeviceStatusEvent)
    def __device_status_changed(self, event):
        """ Slot function which receives sensor and actuator status from model

        :param DeviceStatusEvent event: DeviceStatusEvent object
        """
        if self.__display_app:
            self.__display_app.update_device_status(DeviceEnum.sensor, event.sensor_alive)
            self.__display_app.update_device_status(DeviceEnum.actuator, event.actuator_alive)

        else:
            LOGGER.debug("Device(sensor and actuator) status received before display window is "
                         "initialized")

    @Slot(RelayStatusEvent)
    def __relay_status_changed(self, event):
        """ Slot function which receives relay status event from model

        :param RelayStatusEvent event: RelayStatusEvent object
        """
        if self.__display_app:
            self.__display_app.update_relay_status(event)

        else:
            LOGGER.debug("Relay status received before display window is initialized")

    @Slot(float)
    def __latency_changed(self, latency):
        """ Slot function which receives latency from model

        :param float latency: latency value
        """
        if self.__display_app:
            self.__display_app.update_latency(latency)
Ejemplo n.º 10
0
class LinesChart(QFrame):

    graph_colors = [Qt.GlobalColor.white, Qt.GlobalColor.blue, Qt.GlobalColor.red, Qt.GlobalColor.darkRed, Qt.GlobalColor.green, Qt.GlobalColor.darkGreen, Qt.GlobalColor.yellow]


    def __init__(self,parent=None):
        super(LinesChart,self).__init__(parent)

        self.timer = QTimer(self)
        self.timer.timeout.connect(self.timer_tick) # timerUpdate)
        self.timer.start(400)
        self.timer_ticks = 0

        self.gride_lines_number = 0
        self._create_pens()
        self.stacked = False
        self.title = "Sic transit gloria mundi"
        self.title_font = self.thinfont = QFont("Arial",10,QFont.Bold)

        self.margin = 10
        self.total_width = 0
        self.legend_labels_bounding_boxes = []

        self.ndx_best_serie = -1
        self.best_serie_intra_ndx = None
        self.no_draw = True # Set to True if we can't draw (because of numerical problems or non initialized data for example)
        self.data = None

        # When set to true, if the x axis values are dates
        # then, they'll be shown as month (so intead of 31/12/2014,
        # you'll get something like 12/14)
        self.x_axis_as_months = False
        self.set_mini_maxi(None, None)
        self.set_horizontal_ruler(None,None)

        self.forced_maxi = None
        self.forced_mini = None

    def set_title(self,t):
        self.title = t

    def timer_tick(self):
        self.timer_ticks += 1
        self.update()

    def set_data(self,x_legends, legends,data):
        """ Sets the data to draw.

        :param x_legends: Labels to write on the X axis
        :param series_legends: Name of the various series.
        :param data: a list of series; each serie is a list of tuples : (x_coordinate, value).
        :return:
        """

        mainlog.debug("({}) line_chart : set_data".format(self.title))
        mainlog.debug(data)
        if len(legends) != len(data):
            raise Exception("The number of legends for data series ({}) is different than the number of series ({})".format(len(legends),len(data)))

        self.legends = legends
        self.x_legends = x_legends
        self.original_data = self.data = data

        # Compute the smallest and greatest value of the dataset

        # if self.forced_maxi == None and self.forced_mini == None:
        self.mini = 9999999999
        self.maxi = 0

        for l in self.data:
            if l:
                # There can be some None in the list, or the list might
                # even by made only of None's !
                filtered = [x or 0 for x in l]
                if filtered:
                    self.mini = min(min( filtered),self.mini)
                    self.maxi = max(max( filtered),self.maxi)

        self.mini, self.maxi = self.clip_mini_maxi( self.mini, self.maxi)
        # if self.mini == self.maxi and self.forced_mini != None and self.forced_maxi != None:
        #     self.mini, self.maxi = self.forced_mini, self.forced_maxi

        # Compute the sum of each "column" of data

        summed = [0] * len( data[0] )
        for l in data:
            if l:
                summed = [(x or 0) + (y or 0) for x,y in zip(summed,l)]
        self.summed = summed

        self.highlighted_serie = None


    def clip_mini_maxi(self, mini, maxi):
        mainlog.debug("({}) Clipping {} {}, forced:{} {}".format( self.title, type(mini), type(maxi), type(self.forced_mini), type(self.forced_maxi)))
        if self.forced_mini != None and self.forced_maxi != None:
            mainlog.debug("Clipping to forced values {} {}".format( self.forced_mini, self.forced_maxi))
            mini, maxi = min( mini, self.forced_mini), max( maxi, self.forced_maxi)
            return mini, maxi
        else:
            mainlog.debug("Clipping no clipping :{} {}".format(mini, maxi))
            return mini, maxi

    def sizeHint(self):
        # QSizePolicy.expanding doesn't exapand as much as I want,
        # so I push myself :-)
        return QSize(4000,300)

    def _compute_legend_lengths(self, painter, legends):

        self.max_legend_label_width = 0
        fm = painter.fontMetrics()
        for label in legends:
            self.max_legend_label_width = max(fm.boundingRect(label).width(),
                                              self.max_legend_label_width)

        self.space_between_legend_labels = 10
        self.max_legend_label_width += self.space_between_legend_labels # a bit of room

        self.legend_labels_per_line = int(self.total_width / self.max_legend_label_width)
        if self.total_width % self.max_legend_label_width > 0:
            self.legend_labels_per_line += 1

        self.legend_lines = len(legends) / self.legend_labels_per_line
        if len(legends) % self.legend_labels_per_line > 0:
            self.legend_lines += 1

        self.legend_height = self.text_height * self.legend_lines


    def _compute_x_centers(self, nb_x, available_width):

        #  ----------------------------- 29
        #  __1__ __2__ __3__ __4__ __5__
        # 29/5 = 5.8
        #   2.9,  8.7, 14.5, 20.3, 26.1

        # mainlog.debug("_compute_x_centers({}, {})".format(nb_x, available_width))

        if nb_x == 0:
            return []

        step_width = float(available_width) / float(nb_x)

        all_x = []
        for i in range(nb_x):
            all_x.append( self.x_base + step_width/2 + i * step_width )

        return all_x

    def _compute_screen_lengths(self,painter,mini,maxi,x_steps):
        """

        :param painter: QPainter
        :param mini: Minimum value that will be displayed on the Y axis
        :param maxi: Maximum value that will be displayed on the Y axis
        :param x_steps: Number of steps on the X axis
        :return:
        """

        if x_steps > 0:
            assert 0 <= mini <= maxi, "We should have 0 <= {} <= {}".format(mini, maxi)


        # Measure regular font height
        fm = painter.fontMetrics()
        h = fm.boundingRect("A9j").height()
        self.text_height = h

        # Measure y_axis labels max width
        self.y_axis_label_width = fm.boundingRect(str(int(self.maxi))+"M").width()

        # Measure title font height
        base_font = painter.font()
        painter.setFont(self.title_font)
        r = QRect(self.margin,0,self.width() - 2*self.margin,self.height())
        bb = painter.boundingRect(r,Qt.AlignLeft+Qt.TextWordWrap,self.title)
        painter.setFont(base_font)


        self.total_width = self.width() - 2*self.margin - self.y_axis_label_width

        # Lengths of the legend
        self._compute_legend_lengths(painter, self.legends)
        self.legend_y_start = self.margin + bb.height()

        # Distance between top of widget and top of chart
        self.yoffset = self.margin + bb.height() + self.legend_height

        self.total_height = self.height() - self.yoffset - self.margin - self.text_height

        # print total_width,total_height

        # Go from actual value to a screen position (without offset, see x/y_base)
        d = max(maxi,maxi - mini)
        self.no_draw = self.total_width < 1 or self.total_height < 1 or d <= 0
        if self.no_draw:
            mainlog.debug("No draw because d:{} (mini {}, maxi {}) total_width:{} total_height:{}".format(d, mini, maxi, self.total_width, self.total_height ))
            return

        self.y_factor = float(min(self.total_height,self.total_width)) / d
        self.y_factor = float(self.total_height) / d

        if x_steps <= 3:
            self.x_factor = float(self.total_width) / float(3-1)

        elif x_steps > 1:
            self.x_factor = float(self.total_width) / float(x_steps - 1)

        else:
            self.x_factor = 0

        # self.x_axis_offset = max(0, (self.total_width -  x_steps * self.x_factor)/2)
        self.x_axis_offset = 0

        # print self.maxi,self.mini,self.y_factor

        # The axis positions
        self.y_base = float(maxi) * self.y_factor + self.yoffset
        self.x_base = self.margin + self.y_axis_label_width + self.x_axis_offset



        # Now we compute the vertical axis step size.
        # Remember the graph is fit in the window

        # First we want each step to display a human firendly
        # value (so, no 12.3462, 24.68... but 10, 20, 30...)
        # And we start with an arbitrary number of steps

        # if self.mini == self.maxi:
        #     steps = 6
        #     self.rounded_step_size = int(10 ** round(math.log10(self.maxi / steps))) or 1
        # else:
        #     steps = min(6, self.maxi - self.mini) or 1
        #     self.rounded_step_size = int(10 ** round(math.log10( (self.maxi - self.mini) / steps))) or 1
        #
        #
        # if self.rounded_step_size > 1:
        #
        #     # MAke sure the step size is small enough to have "steps" steps.
        #
        #     while int(self.maxi / self.rounded_step_size) < steps:
        #         self.rounded_step_size = int(self.rounded_step_size / 2)
        #
        #     # Second, make sure the step is tall enough to let the text in
        #
        #     while h > self.rounded_step_size * self.y_factor:
        #         # mainlog.debug("{} > {} * {}".format(h, self.rounded_step_size, self.y_factor))
        #         self.rounded_step_size = self.rounded_step_size * 2
        #
        # if self.maxi > 0:
        #     step_size = (10 ** int(math.log10(self.maxi - 0.00001))) or 1
        #     mainlog.debug("Maxi={} Actual {}, new {}".format(self.maxi,self.rounded_step_size,step_size))
        #     self.rounded_step_size = step_size
        # else:
        #     mainlog.debug("zero")

        log10 = math.log10( self.maxi)

        # This fix to make sure there is more than one step
        # in case the maxi is aligned on a power of 10.
        # In this case there will be 10 steps.

        if log10 - int(log10) == 0:
            fix = -1
        else:
            fix = 0

        step_size = 10 ** int(math.floor(log10) + fix)

        # If we rely only on power of 10, we might end up
        # with a small number of steps (e.g. 2). That is
        # correct but unpleasant to look at. To avoid
        # this, I increase the size of steps to reach
        # a minimum number of steps.
        # Dividing by two a power of 10 make sure we'll keep
        # "human readable" steps (1/2, 1/4, 1/8,...)

        MINIMUM_NUMBER_OF_STEPS = 4

        while self.maxi / step_size < MINIMUM_NUMBER_OF_STEPS:  # number of steps < MINIMUM_NUMBER_OF_STEPS
            step_size = step_size / 2

        # Second, make sure the step is tall enough to let the text in

        while h > step_size * self.y_factor:
            # mainlog.debug("{} > {} * {}".format(h, self.rounded_step_size, self.y_factor))
            step_size = step_size * 2

        self.rounded_step_size = step_size

        self.x_centers = self._compute_x_centers(len(self.data[0]), self.total_width)

    def _compute_lengths(self,painter):
        self._compute_screen_lengths(painter,self.mini,self.maxi,len(self.data[0]))


    def leaveEvent( self, q_leave_event):
        if self.ndx_best_serie:
            self.ndx_best_serie = -1
            self.update()


    def leaveEvent(self, event):
        self.ndx_best_serie = None
        self.best_serie_intra_ndx = None
        self.update()

    def mouseMoveEvent ( self, q_mouse_event):
        if not self.data or self.no_draw:
            return

        p = q_mouse_event.pos()

        # No need to highlight a serie if there's only one
        # in the graph.

        self.ndx_best_serie = -1
        self.best_serie_intra_ndx = None

        if len(self.data) >= 1:

            ndx,serie = self._locate_legend(p.x(), p.y())
            if not serie:
                # mainlog.debug("Trying to locate on graph")
                serie,self.best_serie_intra_ndx = self.locate_serie(None, p.x(), p.y())
                if serie:
                    #mainlog.debug("Located serie : {}".format(serie))
                    ndx = self.data.index(serie)

                # 2014/04/14 13:45:43 [DEBUG]   Locate legend data_ndx:2 TO 77481144 - 20
                # 2014/04/14 13:45:43 [DEBUG]   mouseMove : Highlighting another serie found ndx:1 locate_ndx:2 77481144
                # 2014/04/14 13:45:43 [DEBUG]   _draw_legend: Highlighting serie ndx=1 77481144

            if serie and self.ndx_best_serie != ndx:
                self.ndx_best_serie = ndx # self.data.index(serie)
                # mainlog.debug("mouseMove : Highlighting another serie found ndx:{} locate_ndx:{} {} loacte:{}".format(self.ndx_best_serie, ndx, id(serie), id(self.data[ndx])))
                self.update()


    def _locate_legend(self, x, y):

        for i in range(len(self.legend_labels_bounding_boxes)):
            data_ndx,bb = self.legend_labels_bounding_boxes[i]
            if bb.contains(x,y):
                # mainlog.debug(u"Locate legend data_ndx:{} {} {} - {}".format( data_ndx,self.legends[data_ndx], id(self.data[data_ndx]),i))
                return data_ndx, self.data[data_ndx]

        return None,None


    def locate_serie(self, painter, x, y):
        """ Find the serie which is closes to the x,y point
        """

        if len(self.x_legends) <= 1:
            return None, None

        # y = self.height() - y

        screen_x_step = float(self.total_width) / len(self.x_legends)

        # The x corrdinates can be outside the graph area.
        # So I use som min and max to have an index in the serie's range
        x_ndx = min(len(self.data[0])-1,
                    int(max(0, (x - self.margin - self.y_axis_label_width) / screen_x_step)))
        x_ndx1 = min( len(self.data[0])-1,
                      x_ndx+1)


        delta_x = x - self.margin - self.y_axis_label_width - (x_ndx * screen_x_step )

        # print "{}-{} -> {} - {} d={}".format(x,y,x_ndx,x_ndx1,delta_x)

        best_d = 999999999
        best_serie = None

        for serie in self.data:
            if serie[x_ndx] is not None and serie[x_ndx1] is not None:
                y0 = serie[x_ndx] * self.y_factor
                y1 = serie[x_ndx1] * self.y_factor

                # Screen slope
                slope = (y1 - y0)  / screen_x_step
                sy =  self.y_base - ( y0 + slope * delta_x)

                d = (sy - y ) ** 2
                # print d

                if d < best_d:
                    best_d = d
                    best_serie = serie

        return best_serie,x_ndx



    def _item_coordinates_lines(self, ndx_serie, ndx_in_serie):

        assert ndx_serie >= 0 and ndx_serie < len(self.data), "Incorrect ndx_serie ({})".format(ndx_serie)
        serie = self.data[ndx_serie]

        x = self.x_centers[ndx_in_serie]
        y_top = float(serie[ndx_in_serie]) * self.y_factor

        return x, y_top


    def _draw_selected_items_in_series(self, painter):
        fm = painter.fontMetrics()

        pen = QPen()
        pen.setWidth(1)
        pen.setColor(Qt.GlobalColor.white)
        painter.setPen(pen)

        # We assume all series have the very same number of values
        # and all values on the same index are drawn on the same X
        # coordinate

        ndx_serie = self.best_serie_intra_ndx

        aesthetic_shift = 5

        if ndx_serie is not None:

            to_draw = []
            for i in range(len(self.data)):
                x, y = self._item_coordinates_lines(i, ndx_serie)
                #mainlog.debug("{} {}".format(ndx_serie,i))
                v = self.data[i][ndx_serie]

                text = str(int(v))

                to_draw.append( (self.y_base - y, text) )

            last_y = 100000
            for y, text in sorted( to_draw, key=lambda z : z[0], reverse=True):
                r = QRect(x + aesthetic_shift,0,1000,1000)
                bb = painter.boundingRect(r,Qt.AlignLeft,text)
                if bb.right() > self.width():
                    x = x - bb.width() - 2*aesthetic_shift# left align

                if y + bb.height() > last_y:
                    y = last_y - bb.height()

                fill_color = QColor(16, 16, 48)
                fill_color.setAlpha(196)
                brush = QBrush(fill_color)
                margin = 2
                r = QRect(x + aesthetic_shift - margin,
                          y - aesthetic_shift - bb.height() - margin,
                          bb.width() + 2*margin,
                          bb.height() + 2*margin)
                painter.fillRect(r, brush)

                painter.drawText(x + aesthetic_shift,
                                 y - aesthetic_shift,
                                 text)

                last_y = y

            x, y = self._item_coordinates_lines(0, ndx_serie)
            qp = QPainterPath()
            qp.moveTo(x, self.y_base)
            qp.lineTo(x, self.y_base - self.total_height)
            painter.drawPath(qp)


    def _draw_serie(self, painter, ndx_serie, color):

        serie = self.data[ndx_serie]

        if not serie:
            return

        qp = QPainterPath()
        # qp.addRect(2,2,total_width-4,total_height-4)

        x = float(0) * self.x_factor
        y = serie[0] * self.y_factor
        qp.moveTo(self.x_centers[0],self.y_base - y)
        # print y_base

        for i in range(1,len(serie)):
            x = float(i) * self.x_factor
            y = float(serie[i]) * self.y_factor

            qp.lineTo(self.x_centers[i], self.y_base-y)

        pen = QPen()
        pen.setColor(color)

        if ndx_serie == self.ndx_best_serie:
            pen.setWidth(6)
        else:
            pen.setWidth(2)

        painter.setPen(pen)
        painter.drawPath(qp)



        # self.max_legend_label_width = 0
        # fm = painter.fontMetrics()
        # for label in legends:
        #     self.max_legend_label_width = max(fm.boundingRect(label).width(),
        #                                       self.max_legend_label_width)

        # self.max_legend_label_width += 10 # a bit of room

        # self.legend_labels_per_line = int(self.total_width / self.max_legend_label_width)
        # self.legend_lines = len(legends) / self.legend_labels_per_line
        # if len(legends) % self.legend_labels_per_line > 0:
        #     self.legend_lines += 1

        # self.legend_height = self.text_height * self.legend_lines

    def _draw_legend2(self, painter, labels):
        text_pen = QPen()
        text_pen.setCapStyle(Qt.RoundCap)
        text_pen.setColor(QColor(200, 200, 200)) # alpha=255=fully opaque
        text_pen.setWidth(1)

        highlighted_text_pen = QPen()
        highlighted_text_pen.setColor(QColor(255, 255, 255)) # alpha=255=fully opaque
        highlighted_text_pen.setWidth(1)

        line_pen = QPen()
        line_pen.setCapStyle(Qt.RoundCap)
        line_pen.setColor(QColor(200, 200, 200)) # alpha=255=fully opaque
        line_pen.setWidth(2)


        row = col = 0

        self.legend_labels_bounding_boxes = []

        for data_ndx, label in sorted( zip(range(len(labels)),labels), key=lambda a:a[1]):

            if label:
                # One can hide a series' legend by using a blank label

                x = self.margin + col * self.max_legend_label_width + 3
                y = self.legend_y_start + row * self.text_height

                # Draw coloured line

                line_pen.setColor(self.graph_colors[data_ndx % len(self.graph_colors)]) # alpha=255=fully opaque
                painter.setPen(line_pen)

                if data_ndx == self.ndx_best_serie:
                    r = QRect(x-3, y-self.text_height+3, self.max_legend_label_width, self.text_height)
                    painter.drawRect(r)
                    painter.setPen(highlighted_text_pen)
                else:
                    painter.drawLine(x,y+3,x+self.max_legend_label_width - 6,y + 3)
                    painter.setPen(text_pen)

                painter.drawText(x,y, label)

                # Remember text bounding box
                r = QRect(x,y - self.text_height, self.max_legend_label_width, self.text_height)
                bb = painter.boundingRect(r,Qt.AlignLeft,label)

                # if label == 'TV':
                #     painter.drawRect(r)

                self.legend_labels_bounding_boxes.append( (data_ndx,r) )


                if not (col < self.legend_labels_per_line - 1):
                    col = 0
                    row += 1
                else:
                    col += 1



    def _draw_legend(self, painter, labels):
        text_pen = QPen()
        text_pen.setCapStyle(Qt.RoundCap)
        text_pen.setColor(QColor(200, 200, 200)) # alpha=255=fully opaque
        text_pen.setWidth(1)

        highlighted_text_pen = QPen()
        highlighted_text_pen.setColor(QColor(255, 255, 255)) # alpha=255=fully opaque
        highlighted_text_pen.setWidth(1)

        line_pen = QPen()
        line_pen.setCapStyle(Qt.RoundCap)
        line_pen.setColor(QColor(200, 200, 200)) # alpha=255=fully opaque
        line_pen.setWidth(2)

        max_width = 0
        fm = painter.fontMetrics()
        for label in labels:
            max_width = max(fm.boundingRect(label).width(), max_width)

        y_room = int(self.height() * 0.5 / self.text_height)

        nb_columns = int( len(labels)  / y_room)
        if len(labels) % y_room > 0:
            nb_columns += 1

        y_room = y_room * self.text_height

        x = self.margin + self.total_width - nb_columns * (max_width + 10)



        # 100 / 20 = 5 entry per column
        # 17 / 5 = 3 => 4 columns
        # print self.height() * 0.5, "  ", len(labels), "   ", nb_columns, " y_room:", y_room

        y = 0
        i = 0
        y_offset = self.yoffset
        self.legend_labels_bounding_boxes = []

        # Draw a background rectanlge behin the legend

        fill_color = QColor(16,16,48)
        fill_color.setAlpha(196)
        brush = QBrush(fill_color)

        h = y_room
        w = nb_columns * (max_width + 10)
        if nb_columns == 1:
            h = len(labels) * self.text_height

        r = QRect(x-3, y_offset - self.text_height , w, h + 6)
        painter.fillRect(r,brush)

        sorted_labels = sorted( zip( range(len(labels)), labels, map(id,self.data)),
                                lambda a,b:cmp(a[1],b[1]))

        # mainlog.debug( sorted_labels)

        # We sort labels on their names (but keep the indices right)
        for data_ndx, label in sorted( zip(range(len(labels)),labels), lambda a,b:cmp(a[1],b[1])):

            # Draw coloured line

            line_pen.setColor(self.graph_colors[data_ndx % len(self.graph_colors)]) # alpha=255=fully opaque
            painter.setPen(line_pen)

            if data_ndx == self.ndx_best_serie:
                r = QRect(x-3, y_offset+ y-self.text_height+3, max_width + 6, self.text_height)
                painter.drawRect(r)
                painter.setPen(highlighted_text_pen)
            else:
                painter.drawLine(x,y_offset+y+3,x+max_width,y_offset+y + 3)
                painter.setPen(text_pen)

            painter.drawText(x,y_offset+y,label)

            # Remember text bounding box
            r = QRect(x,y_offset+y - self.text_height,max_width,self.text_height)

            # if label == "TO":
            #    painter.drawRect(r)

            bb = painter.boundingRect(r,Qt.AlignLeft,label)
            # bb.adjust(-5,-5,+5,+5)
            # print label,label_ndx,r.x(), r.y(), r.width(), r.height()

            self.legend_labels_bounding_boxes.append( (data_ndx,bb) )


            if y >= y_room - self.text_height:
                y = 0
                x += max_width + 10
            else:
                y += self.text_height


            i += 1





    def _create_pens(self):
        self.axis_text_pen = QPen()
        self.axis_text_pen.setCapStyle(Qt.RoundCap)
        self.axis_text_pen.setColor(QColor(200, 200, 200)) # alpha=255=fully opaque
        self.axis_text_pen.setWidth(1)


    def _draw_x_axis_dates(self, painter, l):

        def months_between(a,b):
            """ Nb of months, inclusive.
            """

            if a > b:
                a,b = b,a

            if a.year == b.year:
                return b.month - a.month + 1
            else:
                m = (12 - a.month + 1) + (b.month + 1)
                my = (b.year - a.year - 1) * 12

                return m + my


        if not l or len(l) <= 2:
            return l

        fm = painter.fontMetrics()
        char_width = fm.boundingRect("9").width()
        nbchars = self.total_width / char_width

        nb_days = (l[-1] - l[0]).days
        if nb_days <= 10 and not self.x_axis_as_months:
            return l

        # mainlog.debug("Too many days")

        nb_months = months_between(l[0], l[-1])
        if nb_months < (nbchars / len("MM/YY")):

            old_d = l[0]
            nl = [short_my(old_d)] # Will have the same length as l

            # print l[1:-1]

            for d in l[1:len(l)]:
                if d.month != old_d.month:
                    if d.year != old_d.year:
                        nl.append(short_my(d))
                    else:
                        nl.append(str(d.month))
                    old_d = d
                else:
                    nl.append("")

            if len(l) != len(nl):
                mainlog.error("something is wrong")

            return nl

        mainlog.debug("Too many months")

        nb_years = l[-1].year - l[0].year + 1

        old_d = l[0]
        nl = [short_my(old_d)] # Will have the same length as l

        for d in l[1:len(l)]:
            if d.year != old_d.year:
                nl.append(short_my(d))
                old_d = d
            else:
                nl.append("")

        return nl


    def _draw_x_axis(self, painter, labels):

        if not labels:
            return

        if isinstance(labels[0],date):

            # In this case we expect to have, for example, a label per day
            # Of course that's too many to draw, hence this "dithering"

            dithered_labels = []

            # 12 expresses a "density" which allows us to switch between days and month
            # steps. Ideally we should generalize that to weeks, years...

            if len(labels) > 12:
                dithered_labels.append(labels[0])

                for i in range(1,len(labels)):
                    if labels[i].month != labels[i-1].month:
                        dithered_labels.append(labels[i])
                    else:
                        dithered_labels.append(None)
                labels = dithered_labels

                # mainlog.debug(labels)

            def short_my(d):
                if d:
                    return u"{}/{:0>02}".format(d.month,d.year % 100)
                else:
                    return ""

            # labels = self._draw_x_axis_dates(painter, labels)
            # labels = [short_my(l) for l in labels]

            old_d = labels[0]
            nl = [short_my(old_d)] # Will have the same length as l

            for d in labels[1:len(labels)]:
                if d and d.month != old_d.month:
                    if d.year != old_d.year:
                        nl.append(short_my(d))
                    else:
                        nl.append(str(d.month))
                    old_d = d
                else:
                    nl.append("") # Dithering == cleraing unwanted values

            labels = nl

        max_height = 0
        total_label_width = 0

        fm = painter.fontMetrics()
        for label in labels:
            br = fm.boundingRect(str(label))
            max_height = max(br.height(), max_height)
            total_label_width += br.width()

        painter.setPen(self.axis_text_pen)

        last_x = -1
        last_label = None

        for i in range(len(labels)):
            label = str(labels[i])
            w = fm.boundingRect(label).width()
            x = self.x_centers[i] - w / 2

            # Somehow, the width of an empty string is not 0 :-)
            if label and last_label != label and x > last_x:
                # Avoid that this label overlaps the previous one
                painter.drawText(x, self.y_base + max_height, label)
                last_x = x + w - 1
                last_label = label

            if label:
                painter.drawLine(self.x_centers[i], self.y_base + 2, self.x_centers[i], self.y_base - 2)
            # else:
            #     mainlog.debug("Skipping label={} x={} last_x={}".format(label, x, last_x))

    def _draw_over_grid(self,painter):
        # painter.setRenderHint(QPainter.Antialiasing, False)

        line_color = QColor(60, 20, 200, 255)
        flat_structure = QColor(20,20,255,128)
        line_color = flat_structure

        w = min(self.height(), self.width()) / 20
        m = self.margin / 2

        x0 = m
        x1 = m + w
        x15 = m + 9*w
        x2 = self.width() - m - w
        x3 = self.width() - m

        y0 = m
        y1 = m + w
        y15 = m + 5*w
        y2 = self.height() - m - w
        y3 = self.height() - m


        # ----------------------------------------------------------------------

        d = w / 4
        p = [ (x0,y15+d),
              (x0,y1),
              (x1,y0),

              (x15+2*d,y0),     #         /
              (x15+d,y0+d),     #    ____/
              (x1,y0+d),    #   /
              (x0+d,y1),        #  |
              (x0+d,y15),       #  |
              (x0,y15+d)        # _|
        ]

        qp = QPainterPath()
        qp.moveTo( p[0][0], p[0][1],)
        for i in range(1,len(p)):
            qp.lineTo( p[i][0], p[i][1],)
        qp.closeSubpath()


        painter.fillPath(qp,QBrush(QColor(20,20,255,128)))

        pen = QPen()
        pen.setCapStyle(Qt.RoundCap)

        pen.setColor(line_color) # alpha=255=fully opaque
        pen.setWidth(1)
        painter.setPen(pen)
        painter.drawPath(qp)

        # ---------------------------------------------------------

        qp = QPainterPath()


        p = [ (x0,y1), (x1,y0),
              (x2,y0), (x3,y1),
              (x3,y2), (x2,y3),
              (x1,y3), (x0,y2),
              (x0,y1)]

        qp.moveTo( p[0][0], p[0][1],)
        for i in range(1,len(p)):
            qp.lineTo( p[i][0], p[i][1],)

        pen = QPen()
        pen.setCapStyle(Qt.RoundCap)
        pen.setColor(QColor(0, 0, 80, 196)) # alpha=255=fully opaque
        pen.setWidth(5)
        painter.setPen(pen)

        painter.drawPath(qp)

        pen = QPen()
        pen.setCapStyle(Qt.RoundCap)
        pen.setColor(line_color) # alpha=255=fully opaque
        pen.setWidth(1)
        painter.setPen(pen)

        painter.drawPath(qp)





    def _draw_grid(self,painter, y_base, width, height, steps):

        m = int(self.maxi / self.rounded_step_size)

        if self.timer.isActive():
            self.gride_lines_number = self.timer_ticks

            if self.gride_lines_number >= m:
                self.timer.stop()
        else:
            self.gride_lines_number = m


        for i in range( self.gride_lines_number + 2):
            qp = QPainterPath()
            #mainlog.debug("self.rounded_step_size = {}, self.maxi={}".format(self.rounded_step_size, self.maxi))
            y = y_base - self.rounded_step_size * i * self.y_factor
            qp.moveTo(0,y)

            # A bit of shuffle on y for visual effect
            qp.lineTo(width,y + (i % 3) - 1)

            pen = QPen()
            pen.setCapStyle(Qt.RoundCap)
            pen.setColor(QColor(0, 0, 25)) # alpha=255=fully opaque
            pen.setWidth(5)
            painter.setPen(pen)

            painter.drawPath(qp)

            pen = QPen()
            pen.setCapStyle(Qt.RoundCap)
            pen.setColor(QColor(30, 30, 30)) # alpha=255=fully opaque
            pen.setWidth(1)
            painter.setPen(pen)

            painter.drawPath(qp)




    def _draw_y_axis(self,painter, y_base, width, height, steps):

        text_pen = QPen()
        text_pen.setCapStyle(Qt.RoundCap)
        text_pen.setColor(QColor(200, 200, 200)) # alpha=255=fully opaque
        text_pen.setWidth(1)
        painter.setPen(text_pen)


        if self.rounded_step_size < 1:
            fmt = "{:.1f}"
        else:
            fmt = "{:.0f}"

        if self.rounded_step_size > 0:
            for i in range(int(self.maxi / self.rounded_step_size) + 1):

                y = y_base - self.rounded_step_size * i * self.y_factor
                painter.drawText(self.margin,y, fmt.format(self.rounded_step_size * i))


    def draw_title(self, painter):
        text_pen = QPen()
        text_pen.setCapStyle(Qt.RoundCap)
        text_pen.setColor(QColor(255, 255, 255)) # alpha=255=fully opaque
        text_pen.setWidth(1)
        painter.setPen(text_pen)

        painter.setFont(self.title_font)

        r = QRect(self.margin,0,self.width() - 2*self.margin,self.height())
        painter.drawText(r, Qt.AlignLeft + Qt.TextWordWrap, self.title)


    def draw_no_data(self, painter):
        text_pen = QPen()
        text_pen.setCapStyle(Qt.RoundCap)
        text_pen.setColor(QColor(128, 128, 128)) # alpha=255=fully opaque
        text_pen.setWidth(1)
        painter.setPen(text_pen)

        painter.setFont(self.title_font)
        r = QRect( self.margin, 0, self.width() - 2*self.margin, self.height())
        painter.drawText(r, (Qt.AlignCenter | Qt.AlignLeft) + Qt.TextWordWrap, _("No data"))

    def paintEvent(self,event):


        painter = QPainter(self)
        painter.setRenderHint(QPainter.Antialiasing)
        base_font = painter.font()

        self._compute_lengths(painter)

        if self.no_draw:
            self.draw_title(painter)
            self.draw_no_data(painter)
            return

        self._draw_grid(painter, self.y_base, self.width(), self.total_height, 6)
        # self._draw_over_grid(painter)

        self._draw_legend2(painter, self.legends)
        self._draw_x_axis(painter, self.x_legends)
        self._draw_y_axis(painter, self.y_base, self.width(), self.total_height, 6)
        self.draw_title(painter)
        painter.setFont(base_font)
        if self.data:
            for i in range(len(self.data)):
                self._draw_serie(painter, i, self.graph_colors[i % len(self.graph_colors)])

        if self._horizontal_ruler_y_value != None:
            self._draw_horizontal_ruler(painter,
                                        self._horizontal_ruler_text,
                                        self._horizontal_ruler_y_value,
                                        self._horizontal_ruler_color)

        self._draw_selected_items_in_series(painter)

    # def compute_mini_maxi(self, delta_min):
    #     if self.data:
    #         mini = 99999999999
    #         maxi = 0
    #         for l in self.data:
    #             if l:
    #                 mini = min(min(l),mini)
    #                 maxi = max(max(l),maxi)
    #
    #         if maxi - mini < delta_min:
    #             # mainlog.debug("* "*20 + str(delta_min) )
    #             maxi = mini + delta_min
    #
    #         self.mini, self.maxi = mini, maxi
    #
    #         assert 0 <= self.mini <= self.maxi, "0 <= {} <= {}".format(self.mini, self.maxi)

    def set_mini_maxi(self,mini,maxi):
        """ Set mini/maxi Y values. That's useful to have, for example,
        the 0 value on the graph.

        If mini and maxi are None, the their value will be guessed
        from the data drawn.

        :param mini: Minimum value that will be displayed on the Y axis
        :param maxi: Maximum value that will be displayed on the Y axis
        :return:
        """

        assert (mini == None and maxi == None) or (0 <= mini <= maxi), "0 <= {} <= {}".format( mini, maxi)

        self.forced_maxi = maxi
        self.forced_mini = mini

    def set_horizontal_ruler(self, text, y_value, color = Qt.GlobalColor.green):

        self._horizontal_ruler_text = text
        self._horizontal_ruler_y_value = y_value
        self._horizontal_ruler_color = color

    def _draw_box_under_text(self,painter,x,y,text):
        r = QRect(1,1,1000,1000)
        bb = painter.boundingRect(r,Qt.AlignLeft,text)
        bb = QRect(x,y - bb.height() +2 , bb.width() + 2, bb.height())

        # print("{} {} {} {}".format(bb.x(),bb.y(),bb.width(),bb.height()))

        fill_color = QColor(0,0,0)
        fill_color.setAlpha(170)
        brush = QBrush(fill_color)

        painter.fillRect(bb,brush)


    def _draw_horizontal_ruler(self, painter, text, y_value, color):

        y = y_value * self.y_factor

        pen = QPen()
        pen.setCapStyle(Qt.SquareCap)
        pen.setColor(color)

        qp = QPainterPath()

        r = QRect(1,1,1000,1000)
        bb = painter.boundingRect(r,Qt.AlignLeft,text)
        bb = QRect(self.x_base,self.y_base - y - bb.height(), bb.width() + 2, bb.height())

        # print("{} {} {} {}".format(bb.x(),bb.y(),bb.width(),bb.height()))

        fill_color = QColor(0,0,0)
        fill_color.setAlpha(128)
        brush = QBrush(fill_color)

        painter.fillRect(bb,brush)

        qp.moveTo(self.x_base,self.y_base - y)
        qp.lineTo(self.total_width + self.x_base, self.y_base - y)

        painter.setPen(pen)
        painter.drawPath(qp)

        text_pen = QPen()
        text_pen.setCapStyle(Qt.RoundCap)
        text_pen.setColor(color) # alpha=255=fully opaque
        text_pen.setWidth(1)

        painter.setPen(text_pen)
        painter.drawText(self.x_base,self.y_base - y - 5,text)
Ejemplo n.º 11
0
class Quiz(QFrame):
     
    def __init__(self, options, parent=None):
        super(Quiz, self).__init__(parent)
        
        self.options = options
        
        """Session Info"""
        self.status = QFrame()
        #session message
        self.status.message = QLabel(u'')
        #achievements
        self.status.achievements = Achievements()
        self.status.info = QLabel(u'')
        self.status.progress = QProgressBar()
        self.status.layout = QVBoxLayout()
        #layout
        self.status.layout.addWidget(self.status.info)
        self.status.layout.addWidget(self.status.progress)
        self.status.layout.addWidget(self.status.message)
        self.status.setLayout(self.status.layout)
        #mouse event filter
        self.status.filter = StatusFilter(self.status)
        self.status.setAttribute(Qt.WA_Hover, True)
        self.status.installEventFilter(self.status.filter)

        """Items Info"""
        self.info = QFrame()
        self.info.reading = QLabel(u'')
        self.info.item = QLabel(u'')
        self.info.components = QLabel(u'')

        separator_one = QFrame()
        separator_one.setFrameShape(QFrame.HLine)
        separator_one.setFrameShadow(QFrame.Sunken)
        
        separator_two = QFrame()
        separator_two.setFrameShape(QFrame.HLine)
        separator_two.setFrameShadow(QFrame.Sunken)
        
        self.info.layout = QVBoxLayout()
        self.info.layout.addWidget(self.info.reading)
        self.info.layout.addWidget(separator_one)
        self.info.layout.addWidget(self.info.item)
        self.info.layout.addWidget(separator_two)
        self.info.layout.addWidget(self.info.components)
        self.info.setLayout(self.info.layout)
        
        """Verbose Info"""
        self.allInfo = QFrame()
        self.allInfo.layout = QGridLayout()
        self.allInfo.setLayout(self.allInfo.layout)
        #the rest is (should be) generated on the fly
        
        """Kanji info"""
        self.kanjiInfo = QFrame()
        self.kanjiInfo.layout = QVBoxLayout()
        self.kanjiInfo.info = QLabel(u'')
        self.kanjiInfo.layout.addWidget(self.kanjiInfo.info)
        self.kanjiInfo.setLayout(self.kanjiInfo.layout)
        
        """Kanji groups"""
        self.kanjiGroups = QFrame()
        self.kanjiGroups.layout = QVBoxLayout()
        self.kanjiGroups.info = QLabel(u'')
        self.kanjiGroups.layout.addWidget(self.kanjiGroups.info)
        self.kanjiGroups.setLayout(self.kanjiGroups.layout)
        
        """Global Flags"""
        #self.correct = False
        
        """Quiz Dialog"""
        self.filter = Filter()
        ####    visual components    ###
        self.countdown = QProgressBar()
        self.sentence = QLabel(u'')
        
        self.var_1st = QPushButton(u'')
        self.var_2nd = QPushButton(u'')
        self.var_3rd = QPushButton(u'')
        self.var_4th = QPushButton(u'')

        self.answered = QPushButton(u'')
        self.answered.hide()
                
        ###    layouts    ####
        self.layout_vertical = QVBoxLayout()        #main
        self.layout_horizontal = QHBoxLayout()      #buttons
        
        self.layout_horizontal.addWidget(self.var_1st)
        self.layout_horizontal.addWidget(self.var_2nd)
        self.layout_horizontal.addWidget(self.var_3rd)
        self.layout_horizontal.addWidget(self.var_4th)
        
        self.layout_vertical.addWidget(self.countdown)
        self.layout_vertical.addWidget(self.sentence)
        self.layout_vertical.addLayout(self.layout_horizontal)
        
        self.layout_horizontal.addWidget(self.answered)

        self.setLayout(self.layout_vertical)

        ###    utility components    ###
        self.trayIcon = QSystemTrayIcon(self)
        self.trayMenu = QMenu()
        
        self.gifLoading = QMovie('../res/cube.gif')
        self.gifLoading.frameChanged.connect(self.updateTrayIcon)
        
        ### initializing ###
        self.initializeResources()
        
        ### timers ###
        self.nextQuizTimer = QTimer()
        self.nextQuizTimer.setSingleShot(True)
        self.nextQuizTimer.timeout.connect(self.showQuiz)
        
        self.countdownTimer = QTimer()
        self.countdownTimer.setSingleShot(True)
        self.countdownTimer.timeout.connect(self.timeIsOut)
        
        self.trayUpdater = None
        #self.trayUpdater = RepeatTimer(1.0, self.updateTrayTooltip, self.options.getRepetitionInterval() * 60)
        self.remaining = 0
        
        """Start!"""
        if self.options.isQuizStartingAtLaunch():
            self.waitUntilNextTimeslot()
            self.trayIcon.setToolTip('Quiz has started automatically!')
            self.pauseAction.setText('&Pause')
            self.trayIcon.showMessage('Loading complete! (took ~'+ str(self.loadingTime.seconds) + ' seconds) Quiz underway.', 
                                      'Lo! Quiz already in progress!'  + self.loadingStatus, QSystemTrayIcon.MessageIcon.Warning, 10000)
        else:
            self.trayIcon.setToolTip('Quiz is not initiated!')
            self.trayIcon.showMessage('Loading complete! (took ~' + str(self.loadingTime.seconds) + ' seconds) Standing by.', 
                                      'Quiz has not started yet! If you wish, you could start it manually or enable autostart by default.'  + self.loadingStatus, 
                                      QSystemTrayIcon.MessageIcon.Information, 10000 )
            
        self.setWindowIcon(QIcon(PATH_TO_RES + ICONS + 'suzu.png'))
        """Test calls here:"""
        ###    ...    ###
        #self.connect(self.hooker, SIGNAL('noQdict'), self.noQdict)
        self.gem = self.saveGeometry()
        
    def startUpdatingTrayTooltip(self):
        self.remaining = self.nextQuizTimer.interval()
        
        if self.trayUpdater is not None and self.trayUpdater.isAlive():
            self.trayUpdater.cancel()
            
        self.trayUpdater = RepeatTimer(1.0, self.updateTrayTooltip, self.options.getRepetitionInterval() * 60)
        self.trayUpdater.start()
        
    def updateTrayTooltip(self):
        self.remaining -= UPDATE_FREQ
        self.trayIcon.setToolTip('Next quiz in ' + (str(self.remaining/UPDATE_FREQ) + ' seconds'))

#    def noQdict(self):
#        self.showSessionMessage('Nope, cannot show quick dictionary during actual quiz.')
        
####################################
#    Initialization procedures     #
####################################

    def initializeResources(self):
        
        """Initialize Options"""
#        self.options = Options()
        self.loadingStatus = u''
        
        self.qload = QuickLoad(self.options)
        if self.options.isLoadingOnStart():
            self.qload.exec_()
        
        """Pre-initialization"""
        self.animationTimer = ()
        self.progressTimer = ()
        self.grid_layout =()
        
        """Initialize Statistics"""
        self.stats = Stats()
        
        """Config Here"""
        self.initializeComposition()
        self.initializeComponents()
        self.setMenus()
        self.trayIcon.show()
        #self.startTrayLoading()
        
        """"Initialize Dictionaries    (will take a some time!)"""
        time_start = datetime.now()
        
        self.trayIcon.showMessage('Loading...', 'Initializing dictionaries', QSystemTrayIcon.MessageIcon.Information, 20000 )
        # kanji composition #
        if self.options.isLoadingRadk(): self.rdk = RadkDict()
        else: self.loadingStatus += '--> Radikt disabled!\n'
        # edict dictionary
        if self.options.isLoadingEdict():
            edict_file = resource_filename('cjktools_data', 'dict/je_edict')
            self.edict = auto_format.load_dictionary(edict_file)
        else: 
            self.edict = None
            self.loadingStatus += '--> Edict disabled!\n'
        # kanjidict dictionary #
        if self.options.isLoadingKdict(): self.kjd = kanjidic.Kanjidic()
        else: 
            self.kjd = None
            self.loadingStatus += '--> Kanjidict disabled!\n'
        # Kanji.Odyssey groups #
        self.groups = KanjiGrouper()
        if self.options.isLoadingGroups(): self.groups.loadgroupsFromDump()
        else: self.loadingStatus += '--> Kanji.Odyssey disabled!\n'
        
        """Initializing srs system"""
        self.trayIcon.showMessage('Loading...', 'Initializing databases', QSystemTrayIcon.MessageIcon.Information, 20000 )
        self.srs = srsScheduler()
        if self.options.isLoadingDb(): self.srs.initializeCurrentSession(self.options.getQuizMode(), self.options.getSessionSize())
        else: self.loadingStatus += '--> Database disabled!\n'
        
        """Jmdict lookup"""
        self.jmdict = DictionaryLookup()
        if self.options.isLoadingJmdict(): 
            self.jmdict.loadJmdictFromDumpRegex()
            self.jmdict.joinTables()
        else: self.loadingStatus += '--> Jmdict disabled!\n'
                
        """Manual add dialog"""
        self.manualAddDialog = ManualAdd(self.srs.db)
        
        if self.loadingStatus != '': self.loadingStatus = '\n\n' + self.loadingStatus
        
        time_end = datetime.now()
        self.loadingTime =  time_end - time_start

####################################
#    Composition and appearance    #
####################################

    def initializeComposition(self):
        
        """Main Dialog"""
        self.setWindowFlags(Qt.FramelessWindowHint | Qt.WindowStaysOnTopHint)
        self.setFocusPolicy(Qt.StrongFocus)
        self.setFrameStyle(QFrame.StyledPanel | QFrame.Raised)
        #NB: This font will be used in buttons
        self.setFont(QFont(Fonts.TukusiMyoutyouProLB, self.options.getQuizFontSize()))

        desktop = QApplication.desktop().screenGeometry()
        self.setGeometry(QRect(desktop.width() - H_INDENT, desktop.height() - V_INDENT, D_WIDTH, D_HEIGHT))
        
        self.setStyleSheet("QWidget { background-color: rgb(252, 252, 252); }")
        
        """Info dialog"""
        self.info.setWindowFlags(Qt.FramelessWindowHint | Qt.WindowStaysOnTopHint)
        self.info.setFrameStyle(QFrame.StyledPanel | QFrame.Raised)
        self.info.setGeometry(QRect(desktop.width() - H_INDENT - I_WIDTH - I_INDENT, desktop.height() - V_INDENT, I_WIDTH, I_HEIGHT))
        
        self.info.setStyleSheet("QWidget { background-color: rgb(252, 252, 252); }")
        
        """Verbose info dialog"""
        self.allInfo.setWindowFlags(Qt.FramelessWindowHint | Qt.WindowStaysOnTopHint)
        self.allInfo.setFrameStyle(QFrame.StyledPanel | QFrame.Raised)
        self.allInfo.setGeometry(QRect(desktop.width() - H_INDENT - I_WIDTH - I_INDENT, desktop.height() - V_INDENT, I_WIDTH, I_HEIGHT))
        
        self.allInfo.setStyleSheet("QWidget { background-color: rgb(252, 252, 252); }")
        
        """Session message"""
        self.status.setWindowFlags(Qt.FramelessWindowHint | Qt.WindowStaysOnTopHint)
        self.status.setFrameStyle(QFrame.StyledPanel | QFrame.Raised)
        self.status.setGeometry(QRect(desktop.width() - H_INDENT, desktop.height() - V_INDENT - S_HEIGHT - S_INDENT - S_CORRECTION, S_WIDTH, S_HEIGHT))
        self.status.setMinimumSize(S_WIDTH, S_HEIGHT)
#        self.status.setMinimumWidth(S_WIDTH)
        
        self.status.setStyleSheet("QWidget { background-color: rgb(252, 252, 252); }")
        
        self.setMask(roundCorners(self.rect(),5))
#        self.status.setMask(roundCorners(self.status.rect(),5))
        #self.info.setMask(roundCorners(self.info.rect(),5))
        #self.allInfo.setMask(roundCorners(self.allInfo.rect(),5))
        
        """Kanji info"""
        self.kanjiInfo.setWindowFlags(Qt.FramelessWindowHint | Qt.WindowStaysOnTopHint)
        self.kanjiInfo.setFrameStyle(QFrame.StyledPanel | QFrame.Raised)
        self.kanjiInfo.setGeometry(QRect(desktop.width() - H_INDENT - K_WIDTH - K_INDENT, desktop.height() - V_INDENT, K_WIDTH, K_HEIGHT))
        
        self.kanjiInfo.setStyleSheet("QWidget { background-color: rgb(252, 252, 252); }")
        
        """Kanji groups"""
        self.kanjiGroups.setWindowFlags(Qt.FramelessWindowHint | Qt.WindowStaysOnTopHint)
        self.kanjiGroups.setFrameStyle(QFrame.StyledPanel | QFrame.Raised)
        self.kanjiGroups.setGeometry(QRect(desktop.width() - H_INDENT - G_WIDTH - G_INDENT, desktop.height() - V_INDENT, G_WIDTH, G_HEIGHT))
        
        self.kanjiGroups.setStyleSheet("QWidget { background-color: rgb(250, 250, 250); }")
        
#        self.setMask(roundCorners(self.rect(),5))
#        self.status.setMask(roundCorners(self.status.rect(),5))

    def initializeComponents(self):
        self.countdown.setMaximumHeight(6)
        self.countdown.setRange(0, self.options.getCountdownInterval() * 100)
        self.countdown.setTextVisible(False)
        self.countdown.setStyleSheet("QProgressbar { background-color: rgb(255, 255, 255); }")
        
        #self.setFont(QFont(Fonts.SyoutyouProEl, 40))#self.options.getQuizFontSize()))
        
        self.sentence.setAlignment(Qt.AlignmentFlag.AlignCenter)
        #self.sentence.setFont(QFont(Fonts.HiragiNoMarugotoProW4, self.options.getSentenceFontSize()))
        self.sentence.setFont(QFont(self.options.getSentenceFont(), self.options.getSentenceFontSize()))
        
        self.sentence.setWordWrap(True)
        self.trayIcon.setIcon(QIcon(PATH_TO_RES + TRAY + 'active.png'))
        
        self.status.message.setFont(QFont('Cambria', self.options.getMessageFontSize()))
        self.status.layout.setAlignment(Qt.AlignCenter)
        self.status.message.setWordWrap(False)
        self.status.message.setAlignment(Qt.AlignCenter)
        self.status.layout.setMargin(0)
        
        self.status.info.setHidden(True)
        self.status.progress.setHidden(True)
        self.status.progress.setMaximumHeight(10)
        self.status.progress.setRange(0, self.status.achievements.threshold)
        self.status.layout.setAlignment(Qt.AlignCenter)
        self.status.info.setAlignment(Qt.AlignCenter)
        self.status.info.setFont(QFont(Fonts.RyuminPr5, 13))
        self.status.info.setWordWrap(False)
        
        self.status.gem = self.status.saveGeometry()
        
        self.info.item.setFont(QFont(Fonts.HiragiNoMyoutyouProW3, 36))
        self.info.reading.setFont(QFont(Fonts.HiragiNoMyoutyouProW3, 16))
        self.info.components.setFont((QFont(Fonts.HiragiNoMyoutyouProW3, 14)))
        #self.info.item.setWordWrap(True)
        self.info.components.setWordWrap(True)
        #self.info.layout.setAlignment(Qt.AlignCenter)
        self.info.layout.setMargin(0)
        #self.info.layout.setSizeConstraint(self.info.layout.SetFixedSize)       #NB: would work nice, if the anchor point was in right corner
        
        self.info.reading.setAlignment(Qt.AlignCenter)
        self.info.item.setAlignment(Qt.AlignCenter)
        self.info.components.setAlignment(Qt.AlignCenter)
        
        #self.info.setLayoutDirection(Qt.RightToLeft)
        
        self.info.reading.setStyleSheet("QLabel { color: rgb(155, 155, 155); }")
        self.info.components.setStyleSheet("QLabel { color: rgb(100, 100, 100); }")
        
        self.kanjiInfo.info.setFont(QFont(Fonts.MSMyoutyou, 14.5))
        self.kanjiInfo.info.setAlignment(Qt.AlignCenter)
        self.kanjiInfo.info.setWordWrap(True)
        self.kanjiInfo.layout.setMargin(0)
        
        self.kanjiGroups.info.setFont(QFont(Fonts.MSMyoutyou, 18.5))
        self.kanjiGroups.info.setAlignment(Qt.AlignCenter)
        self.kanjiGroups.info.setWordWrap(True)
        self.kanjiGroups.layout.setMargin(0)
        
        #NB: ...
        self.answered.setMaximumWidth(D_WIDTH)
        self.answered.setFont(QFont('Calibri', 11))


####################################
#        Updating content          #
####################################        
    def updateContent(self):
        
        """Resetting multi-label sentence"""
        if self.grid_layout != ():
            for i in range(0, self.grid_layout.count()):
                    self.grid_layout.itemAt(i).widget().hide()
            self.layout_vertical.removeItem(self.grid_layout)
            self.grid_layout.setParent(None)
            self.update()
        if self.sentence.isHidden():    
            self.sentence.show()
        self.showButtonsQuiz()
        
        """Getting actual content"""
        self.srs.getNextItem()
              
        example = self.srs.getCurrentExample()
        
        # checking for no example case
        if example is None:
            self.manualAddDialog.setProblemKanji(self.srs.getCurrentItemKanji())
            done = self.manualAddDialog.exec_()
            
            if done == 0:
                self.updateContent()
            elif done == 1:
                self.updateContent()
            else:
                pass
        else:
            example = example.replace(self.srs.getWordFromExample(), u"<font color='blue'>" + self.srs.getWordFromExample() + u"</font>")
            
            # checking sentence length
            if len(self.srs.currentExample.sentence) > SENTENCE_MAX: self.sentence.setFont(QFont(self.options.getSentenceFont(), MIN_FONT_SIZE))
            else: self.sentence.setFont(QFont(self.options.getSentenceFont(), self.options.getSentenceFontSize()))
            
            #temporary debug info:
#            print len(example), self.sentence.font()
            
            self.sentence.setText(example)
            
            readings = self.srs.getQuizVariants()

            changeFont = False
            for item in readings:
                if len(item) > BUTTON_KANA_MAX : changeFont = True
                
            try:
                for i in range(0, self.layout_horizontal.count()):
                        if i > 3: break
                        self.layout_horizontal.itemAt(i).widget().setText(u'')
                        
                        if changeFont:
                            self.layout_horizontal.itemAt(i).widget().setStyleSheet('QPushButton { font-family: ' + self.options.getQuizFont() + '; font-size: 11pt; }')
                        else:
                            self.layout_horizontal.itemAt(i).widget().setStyleSheet('QPushButton { font-family: ' + self.options.getQuizFont() + '; font-size: %spt; }' % self.options.getQuizFontSize())
                            
                        self.layout_horizontal.itemAt(i).widget().setText(readings[i])
            except:
                log.debug(u'Not enough quiz variants for ' + self.srs.getCurrentItem())
        
    def getReadyPostLayout(self):
        self.sentence.hide()
        self.update()
        
        self.grid_layout = QGridLayout()
        self.grid_layout.setSpacing(0)
        self.labels = []
        
        columns_mod = 0
        #font size depending on sentence length
        if len(self.srs.currentExample.sentence) > SENTENCE_MAX: font =  QFont(self.options.getSentenceFont(), MIN_FONT_SIZE); columns_mod = 6
        else: font = QFont(self.options.getSentenceFont(), self.options.getSentenceFontSize())
        
        #row, column, rows span, columns span, max columns
        i = 0; j = 0; r = 1; c = 1; n = COLUMNS_MAX + columns_mod
        for word in self.srs.parseCurrentExample():
            label = QLabel(word)
            label.setFont(font)
            
            label.setAttribute(Qt.WA_Hover, True)
            label.installEventFilter(self.filter)
            self.labels.append(label)
            
            if len(label.text()) > 1: c = len(label.text())
            else: c = 1
            #Don't ask, really
            if j + c > n: i = i + 1; j = 0
            
            self.grid_layout.addWidget(self.labels.pop(), i, j, r, c)       #NB: Ehh, pop should remove label from list, shouldn't it?
            
            if j <= n: j = j + c
            else: j = 0; i = i + 1
        
        self.grid_layout.setAlignment(Qt.AlignCenter)
        self.layout_vertical.insertLayout(1, self.grid_layout)

        self.update()
        
    def hideButtonsQuiz(self):
        self.var_1st.hide()
        self.var_2nd.hide()
        self.var_3rd.hide()
        self.var_4th.hide()
        
        self.answered.clicked.connect(self.hideQuizAndWaitForNext)
        self.answered.show()
        
    def showButtonsQuiz(self):
        self.var_1st.show()
        self.var_2nd.show()
        self.var_3rd.show()
        self.var_4th.show()
        
        self.answered.hide()
        self.answered.disconnect()
        
####################################
#        Timers and animations     #
####################################

    def waitUntilNextTimeslot(self):
        #if self.nextQuizTimer.isActive():   self.nextQuizTimer.stop()
        self.nextQuizTimer.start(self.options.getRepetitionInterval() * 60 * 1000)  #options are in minutes    NB: how do neatly I convert minutes to ms?
        self.startUpdatingTrayTooltip()
        
    def beginCountdown(self):
        self.trayIcon.setToolTip('Quiz in progress!')
        self.pauseAction.setText('&Pause')
#        self.pauseAction.setShortcut('P')
        
        self.countdownTimer.start(self.options.getCountdownInterval() * 1000)

        self.progressTimer = RepeatTimer(0.01, self.updateCountdownBar, self.options.getCountdownInterval() * 100)
        self.progressTimer.start()
        
    def updateCountdownBar(self):
        self.countdown.setValue(self.countdown.value() - 1)
        #print self.countdown.value()
        self.countdown.update()         #NB: without .update() recursive repaint crushes qt

    def fade(self):
        if self.windowOpacity() == 1:
            self.animationTimer = RepeatTimer(0.025, self.fadeOut, 40)
            self.animationTimer.start()
        else:
            self.animationTimer = RepeatTimer(0.025, self.fadeIn, 40)
            self.animationTimer.start()
    
    def fadeIn(self):
        self.setWindowOpacity(self.windowOpacity() + 0.1)
        
    def fadeOut(self):
        self.setWindowOpacity(self.windowOpacity() - 0.1)
        
    def stopCountdown(self):
        self.progressTimer.cancel()
        self.countdownTimer.stop()
        self.countdown.setValue(0)
        
####################################
#        Actions and events        #
####################################    
    
    def setMenus(self):
        
        self.showQuizAction = QAction('&Quiz me now!', self, triggered=self.showQuiz)
        self.showQuizAction.setIcon(QIcon(PATH_TO_RES + TRAY + NOW_ICON))
        self.trayMenu.addAction(self.showQuizAction)
        
        self.pauseAction = QAction('&Start quiz!', self, triggered=self.pauseQuiz)
        self.pauseAction.setIcon(QIcon(PATH_TO_RES + TRAY + START_ICON))
        self.trayMenu.addAction(self.pauseAction)
        
        self.trayMenu.addSeparator()
        
        self.quickDictAction = QAction('Quick &dictionary', self, triggered=self.showQuickDict)
        self.quickDictAction.setIcon(QIcon(PATH_TO_RES + TRAY + DICT_ICON))
        self.trayMenu.addAction(self.quickDictAction)
        
        self.optionsAction = QAction('&Options', self, triggered=self.showOptions)
        self.optionsAction.setIcon(QIcon(PATH_TO_RES + TRAY + OPTIONS_ICON))
        self.trayMenu.addAction(self.optionsAction)
        
        self.quickLoadAction = QAction('Quick &load', self, triggered=self.showQuickLoad)
        self.quickLoadAction.setIcon(QIcon(PATH_TO_RES + TRAY + LOAD_ICON))
        self.trayMenu.addAction(self.quickLoadAction)
        
        self.trayMenu.addSeparator()
        
        self.aboutAction = QAction('&About', self, triggered=self.showAbout)
        self.aboutAction.setIcon(QIcon(PATH_TO_RES + TRAY + ABOUT_ICON))
        self.trayMenu.addAction(self.aboutAction)
        
        self.globalStatsAction = QAction('&Global statistics', self, triggered=self.showGlobalStatistics)
        self.globalStatsAction.setIcon(QIcon(PATH_TO_RES + TRAY + STAT_ICON))
        self.trayMenu.addAction(self.globalStatsAction)
        
        self.utilAction = QAction('U&tilities', self, triggered=self.showToolsDialog)
        self.utilAction.setIcon(QIcon(PATH_TO_RES + TRAY + UTILS_ICON))
        self.trayMenu.addAction(self.utilAction)
        
        self.trayMenu.addSeparator()
        
        self.quitAction = QAction('&Exit', self, triggered=self.saveAndExit)
        self.quitAction.setIcon(QIcon(PATH_TO_RES + TRAY + CLOSE_ICON))
        self.trayMenu.addAction(self.quitAction)
        

        self.trayIcon.setContextMenu(self.trayMenu)
        self.trayIcon.activated.connect(self.onTrayIconActivated)

    def onTrayIconActivated(self, reason):
        '''
        if reason == QSystemTrayIcon.DoubleClick:
            print 'tray icon double clicked'
        '''
        if reason == QSystemTrayIcon.Trigger:
            if self.isHidden():
                self.trayIcon.showMessage('Current session statistics:', 'Running time:\t\t' + self.stats.getRunningTime() + 
                                          '\nItems seen:\t\t' + str(self.stats.totalItemSeen) + 
                                          '\nCorrect answers:\t\t' + str(self.stats.answeredCorrect) +
                                          '\nWrong answers:\t\t' + self.stats.getIncorrectAnswersCount() +
                                          '\nCorrect ratio:\t\t' + self.stats.getCorrectRatioPercent() +
                                          #'\nQuiz total time:\t\t' + self.stats.getQuizActive() +
                                          '\nQuiz paused time:\t\t' + self.stats.getPausedTime() +
                                          '\nTotal pondering time:\t' + self.stats.getMusingsTime() +
                                          '\nTotal post-quiz time:\t' + self.stats.getQuizTime() +
                                          '\nAverage pondering:\t' + self.stats.getAverageMusingTime() +
                                          '\nAverage post-quiz:\t' + self.stats.getAveragePostQuizTime(), 
                                          QSystemTrayIcon.MessageIcon.Information, 20000)
    
    def setButtonsActions(self):

        if self.var_1st.text() == self.srs.getCorrectAnswer():
                self.var_1st.clicked.connect(self.correctAnswer)
        else:
                self.var_1st.clicked.connect(self.wrongAnswer)
               
        if self.var_2nd.text() == self.srs.getCorrectAnswer():
                self.var_2nd.clicked.connect(self.correctAnswer)
        else:
                self.var_2nd.clicked.connect(self.wrongAnswer)
                
        if self.var_3rd.text() == self.srs.getCorrectAnswer():
                self.var_3rd.clicked.connect(self.correctAnswer)
        else:
                self.var_3rd.clicked.connect(self.wrongAnswer)
                
        if self.var_4th.text() == self.srs.getCorrectAnswer():
                self.var_4th.clicked.connect(self.correctAnswer)
        else:
                self.var_4th.clicked.connect(self.wrongAnswer)
                
        self.var_1st.setShortcut('1')
        self.var_2nd.setShortcut('2')
        self.var_3rd.setShortcut('3')
        self.var_4th.setShortcut('4')
                
    def resetButtonsActions(self):
        self.var_1st.disconnect()
        self.var_2nd.disconnect()
        self.var_3rd.disconnect()
        self.var_4th.disconnect()
        
    def postAnswerActions(self):
        self.stats.musingsStopped()
        self.stats.postQuizStarted()
        
        self.stopCountdown()
        self.hideButtonsQuiz()
        
        self.getReadyPostLayout()
        
    def refocusQuiz(self):
        self.answered.setShortcut('Space')
        self.activateWindow()
        self.answered.setFocus()
        
    def correctAnswer(self):
        self.postAnswerActions()
        
        self.srs.answeredCorrect()
        self.stats.quizAnsweredCorrect()
        
        self.checkTranslationSize(self.srs.getCurrentSentenceTranslation())

        self.status.achievements.correctAnswer()
        self.showSessionMessage(u'<font color=green>Correct: ' + self.srs.getCorrectAnswer() + '</font>\t|\tNext quiz: ' + self.srs.getNextQuizTime() 
                                + '\t|\t<font color=' + self.srs.getLeitnerGradeAndColor()['color'] +  '>Grade: ' + self.srs.getLeitnerGradeAndColor()['grade'] 
                                + ' (' + self.srs.getLeitnerGradeAndColor()['name'] + ')<font>')
        self.refocusQuiz()
        
    def wrongAnswer(self):
        self.postAnswerActions()
        
        self.srs.answeredWrong()
        self.stats.quizAnsweredWrong()
        
        self.checkTranslationSize(self.srs.getCurrentSentenceTranslation())
        
        self.status.achievements.wrongAnswer()
        self.showSessionMessage(u'<font color=tomato>Wrong! Should be: '+ self.srs.getCorrectAnswer() + '</font>\t|\tNext quiz: ' + self.srs.getNextQuizTime()
                                + '\t|\t<font color=' + self.srs.getLeitnerGradeAndColor()['color'] +  '>Grade: ' + self.srs.getLeitnerGradeAndColor()['grade'] 
                                + ' (' + self.srs.getLeitnerGradeAndColor()['name'] + ')<font>')
        self.refocusQuiz()
            
    def timeIsOut(self):
        self.stats.musingsStopped()
        self.stats.postQuizStarted()
        
        QTimer.singleShot(50, self.hideButtonsQuiz)     #NB: slight artificial lag to prevent recursive repaint crush (when mouse is suddenly over repainted button)
        self.getReadyPostLayout()
        
        self.srs.answeredWrong()
        self.stats.quizAnsweredWrong()

        self.checkTranslationSize(self.srs.getCurrentSentenceTranslation())
        
        self.status.achievements.wrongAnswer()
        self.showSessionMessage(u'<font color=tomato>Timeout! Should be: ' + self.srs.getCorrectAnswer() + '</font>\t|\tNext quiz: ' + self.srs.getNextQuizTime()
                                + '\t|\t<font color=' + self.srs.getLeitnerGradeAndColor()['color'] +  '>Grade: ' + self.srs.getLeitnerGradeAndColor()['grade'] 
                                + ' (' + self.srs.getLeitnerGradeAndColor()['name'] + ')<font>')    
        self.refocusQuiz()
        
    def checkTranslationSize(self, translation):
        if len(translation) > TRANSLATION_CHARS_LIMIT:
            self.answered.setStyleSheet('QPushButton { font-size: 9pt; }')
            
            space_indices = [i for i, value in enumerate(translation) if value == ' ']
            find_nearest_index = lambda value,list : min(list, key = lambda x:abs(x - value))
            nearest_index = find_nearest_index(TRANSLATION_CHARS_LIMIT, space_indices)
            translation = translation[:nearest_index] + '\n' + translation[nearest_index + 1:]
        else:
            self.answered.setStyleSheet('QPushButton { font-size: 11pt; }')
        
        self.answered.setText(translation)
    
    def hideQuizAndWaitForNext(self):
        self.stats.postQuizEnded()
        
        self.status.hide()
        self.info.hide()
        self.allInfo.hide()
        self.resetButtonsActions()
        
        self.setWindowOpacity(1)
        self.fade()
        QTimer.singleShot(1000, self.hide)
        self.waitUntilNextTimeslot()
        self.updater.mayUpdate = True
 
    def pauseQuiz(self):
        if self.isHidden():
            if self.pauseAction.text() == '&Pause':
                self.nextQuizTimer.stop()
                self.pauseAction.setText('&Unpause')
#                self.pauseAction.setShortcut('U')
                self.trayIcon.setToolTip('Quiz paused!')
                
                self.trayIcon.setIcon(QIcon(PATH_TO_RES + TRAY + 'inactive.png'))
                self.trayUpdater.cancel()
                self.pauseAction.setIcon(QIcon(PATH_TO_RES + TRAY + START_ICON))
                self.stats.pauseStarted()
                
                self.updater.mayUpdate = True
                
            elif self.pauseAction.text() == '&Start quiz!':
                self.waitUntilNextTimeslot()
                self.pauseAction.setText('&Pause')
#                self.pauseAction.setShortcut('P')
                self.trayIcon.setToolTip('Quiz in progress!')
                
                self.trayIcon.setIcon(QIcon(PATH_TO_RES + TRAY + 'active.png'))
                self.pauseAction.setIcon(QIcon(PATH_TO_RES + TRAY + PAUSE_ICON))
            else:
                self.waitUntilNextTimeslot()
                self.pauseAction.setText('&Pause')
#                self.pauseAction.setShortcut('P')
                self.trayIcon.setToolTip('Quiz in progress!')
                
                self.trayIcon.setIcon(QIcon(PATH_TO_RES + TRAY + 'active.png'))
                self.pauseAction.setIcon(QIcon(PATH_TO_RES + TRAY + PAUSE_ICON))
                self.stats.pauseEnded()
                
                self.updater.mayUpdate = False
        else:
            self.showSessionMessage(u'Sorry, cannot pause while quiz in progress!')
 
    def showQuiz(self):
        if self.isHidden():
            self.updateContent()
            self.setButtonsActions()
            
            #self.restoreGeometry(self.gem)
            self.trayIcon.setIcon(QIcon(PATH_TO_RES + TRAY + 'active.png'))
            
            self.show()
            self.setWindowOpacity(0)
            self.fade()

            self.countdown.setValue(self.options.getCountdownInterval() * 100)
            self.beginCountdown()
            self.stats.musingsStarted()
            
            if self.nextQuizTimer.isActive():   self.nextQuizTimer.stop()
            self.updater.mayUpdate = False
        else:
            self.showSessionMessage(u'Quiz is already underway!')
         
    def showOptions(self):
        self.optionsDialog.show()
        
    def showAbout(self):
        self.about.show()
        
    def showQuickDict(self):
        self.qdict.showQDict = True
        
    def showGlobalStatistics(self):
        self.statistics.show()
        
    def showToolsDialog(self):
        self.tools.show()
        
    def showQuickLoad(self):
        self.qload.show()
        
    def startTrayLoading(self):
        self.gifLoading.start()
        #self.iconTimer = QTimer()
        #self.iconTimer.timeout.connect(self.updateTrayIcon)
        #self.iconTimer.start(100)
        
    def stopTrayLoading(self):
        self.gifLoading.stop()
        
    def updateTrayIcon(self):
        self.trayIcon.setIcon(self.gifLoading.currentPixmap())
        
    def showSessionMessage(self, message):
        """Shows info message"""
        self.status.message.setText(message)
        if self.status.achievements.achieved is not None:
            self.status.info.setText(self.status.achievements.achieved[1] + '\t( ' + self.status.achievements.achieved[0] + ' )')
            self.status.progress.hide()
            self.status.move(self.status.x(), self.status.y() - 15)
            self.status.info.show()
#            self.status.setMask(roundCorners(self.status.rect(),5))
        else:
            self.status.info.setText(u'')
            self.status.info.hide()
            self.status.restoreGeometry(self.status.gem)
#            self.status.setMask(roundCorners(self.status.rect(),5))
#        print self.status.y()
        self.status.adjustSize()
        self.status.show()
 
    def saveAndExit(self):
        self.hide()
        self.status.hide()
        self.allInfo.hide()
        self.kanjiInfo.hide()
        
        self.trayIcon.showMessage('Shutting down...', 'Saving session', QSystemTrayIcon.MessageIcon.Information, 20000 )
        
        if self.countdownTimer.isActive():
                self.countdownTimer.stop()
        if self.nextQuizTimer.isActive():
                self.nextQuizTimer.stop()
        if self.progressTimer != () and self.progressTimer.isAlive():
                self.progressTimer.cancel()
        if self.trayUpdater is not None and self.trayUpdater.isAlive() :
                self.trayUpdater.cancel()    
            
        self.rehash.checkSessionResults()
        
        self.srs.endCurrentSession(self.stats)
        self.trayIcon.hide()

        self.hooker.stop()

        self.updater.stop()
        self.optionsDialog.close()
        self.about.close()
        self.qdict.close()
        self.close()        
        
    def addReferences(self, about, options, qdict, updater, tools, statistics, web, rehash):
        self.about = about
        self.optionsDialog = options
        self.qdict = qdict
        self.updater = updater
        self.tools = tools
        self.statistics = statistics
        self.status.web = web
        self.rehash = rehash
        
    def initGlobalHotkeys(self):
        def toggleWidgetFlag(): self.qdict.showQDict = True
        self.hooker = GlobalHotkeyManager(toggleWidgetFlag , 'Q')
        self.hooker.setDaemon(True)
        self.hooker.start()
        
    def showEvent(self, event):
        self.restoreGeometry(self.gem)
Ejemplo n.º 12
0
class Quiz(QFrame):
    def __init__(self, parent=None):
        super(Quiz, self).__init__(parent)
        """Session Info"""
        self.status = QFrame()
        ##session message
        self.status.message = QLabel(u'')
        self.status.layout = QHBoxLayout()
        self.status.layout.addWidget(self.status.message)
        self.status.setLayout(self.status.layout)
        ##mouse event filter
        #self.status.filter = StatusFilter()
        self.status.setAttribute(Qt.WA_Hover, True)
        #self.status.installEventFilter(self.status.filter)
        """Items Info"""
        self.info = QFrame()
        self.info.reading = QLabel(u'')
        self.info.item = QLabel(u'')
        self.info.components = QLabel(u'')

        self.info.translation = QLabel(u'')
        self.info.translation.setAlignment(Qt.AlignCenter)
        self.info.translation.setWordWrap(True)

        #        separator_one = QFrame()
        #        separator_one.setFrameShape(QFrame.HLine)
        #        separator_one.setFrameShadow(QFrame.Sunken)
        #
        #        separator_two = QFrame()
        #        separator_two.setFrameShape(QFrame.HLine)
        #        separator_two.setFrameShadow(QFrame.Sunken)

        self.info.layout = QVBoxLayout()
        self.info.layout.addWidget(self.info.translation)
        #        self.info.layout.addWidget(self.info.reading)
        #        self.info.layout.addWidget(separator_one)
        #        self.info.layout.addWidget(self.info.item)
        #        self.info.layout.addWidget(separator_two)
        #        self.info.layout.addWidget(self.info.components)
        self.info.setLayout(self.info.layout)
        """Verbose Info"""
        self.allInfo = QFrame()
        self.allInfo.layout = QGridLayout()
        self.allInfo.setLayout(self.allInfo.layout)
        #the rest is (should be) generated on the fly
        """Global Flags"""
        #self.correct = False
        """Quiz Dialog"""
        self.filter = Filter()
        ####    visual components    ###
        self.countdown = QProgressBar()
        self.sentence = QLabel(u'')

        #        self.var_1st = QPushButton(u'')
        #        self.var_2nd = QPushButton(u'')
        #        self.var_3rd = QPushButton(u'')
        #        self.var_4th = QPushButton(u'')

        self.isKnown = QPushButton(u'Good')
        self.isNotKnown = QPushButton(u'Again')

        self.answered = QPushButton(u'')
        self.answered.hide()

        ###    layouts    ####
        self.layout_vertical = QVBoxLayout()  #main
        self.layout_horizontal = QHBoxLayout()  #buttons

        #        self.layout_horizontal.addWidget(self.var_1st)
        #        self.layout_horizontal.addWidget(self.var_2nd)
        #        self.layout_horizontal.addWidget(self.var_3rd)
        #        self.layout_horizontal.addWidget(self.var_4th)

        self.layout_horizontal.addWidget(self.isKnown)
        self.layout_horizontal.addWidget(self.isNotKnown)

        self.layout_vertical.addWidget(self.countdown)
        self.layout_vertical.addWidget(self.sentence)
        self.layout_vertical.addLayout(self.layout_horizontal)

        self.layout_horizontal.addWidget(self.answered)

        self.setLayout(self.layout_vertical)

        ###    utility components    ###
        self.trayIcon = QSystemTrayIcon(self)
        self.trayMenu = QMenu()

        self.gifLoading = QMovie('../res/cube.gif')
        self.gifLoading.frameChanged.connect(self.updateTrayIcon)

        self.nextQuizTimer = QTimer()
        self.nextQuizTimer.setSingleShot(True)
        self.nextQuizTimer.timeout.connect(self.showQuiz)

        self.countdownTimer = QTimer()
        self.countdownTimer.setSingleShot(True)
        self.countdownTimer.timeout.connect(self.timeIsOut)

        ### initializing ###
        self.initializeResources()
        """Start!"""
        if self.options.isQuizStartingAtLaunch():
            self.waitUntilNextTimeslot()
            self.trayIcon.setToolTip('Quiz has started automatically!')
            self.pauseAction.setText('&Pause')
            self.trayIcon.showMessage(
                'Loading complete! (took ~' + str(self.loadingTime.seconds) +
                ' seconds) Quiz underway.', 'Lo! Quiz already in progress!',
                QSystemTrayIcon.MessageIcon.Warning, 10000)
        else:
            self.trayIcon.setToolTip('Quiz is not initiated!')
            self.trayIcon.showMessage(
                'Loading complete! (took ~' + str(self.loadingTime.seconds) +
                ' seconds) Standing by.',
                'Quiz has not started yet! If you wish, you could start it manually or enable autostart by default.',
                QSystemTrayIcon.MessageIcon.Information, 10000)
        """Test calls here:"""
        ###    ...    ###
        #self.connect(self.hooker, SIGNAL('noQdict'), self.noQdict)

    def noQdict(self):
        self.showSessionMessage(
            'Nope, cannot show quick dictionary during actual quiz.')

####################################
#    Initialization procedures     #
####################################

    def initializeResources(self):
        """Pre-initialization"""
        self.animationTimer = ()
        self.progressTimer = ()
        self.grid_layout = ()
        """Initialize Options"""
        self.options = Options()
        """Initialize Statistics"""
        self.stats = Stats()
        """Config Here"""
        self.initializeComposition()
        self.initializeComponents()
        self.setMenus()
        self.trayIcon.show()
        #self.startTrayLoading()
        """"Initialize Dictionaries    (will take a some time!)"""
        time_start = datetime.now()
        self.dict = EdictParser()
        self.dict.loadDict()

        self.morphy = get_morph(PATH_TO_RES + DICT_EN)

        self.trayIcon.showMessage(
            'Loading...', 'Initializing dictionaries',
            QSystemTrayIcon.MessageIcon.Information,
            20000)  #TODO: change into loading dialog... or not
        """Initializing srs system"""
        self.trayIcon.showMessage('Loading...', 'Initializing databases',
                                  QSystemTrayIcon.MessageIcon.Information,
                                  20000)
        self.srs = srsScheduler()
        self.srs.initializeAll()
        self.srs.initializeCurrentSession(self.options.getSessionSize())
        """Global hotkeys hook"""
        #TODO: add multiple hotkeys and fix stop()
        #self.hooker = GlobalHotkeyManager(toggleQDictFlag, 'Q')
        #        self.hooker = GlobalHotkeyManager(toggleWidgetFlag(self.qdict), 'Q')
        #        self.hooker.setDaemon(True) #temporarily, should work using stop()
        #        self.hooker.start()

        time_end = datetime.now()
        self.loadingTime = time_end - time_start

####################################
#    Composition and appearance    #
####################################

    def initializeComposition(self):
        """Main Dialog"""
        self.setWindowFlags(Qt.FramelessWindowHint | Qt.WindowStaysOnTopHint)
        self.setFocusPolicy(Qt.StrongFocus)
        self.setFrameStyle(QFrame.StyledPanel | QFrame.Raised)
        #Font will appear in buttons
        self.setFont(
            QFont(self.options.getQuizFont(), self.options.getQuizFontSize()))

        desktop = QApplication.desktop().screenGeometry()
        self.setGeometry(
            QRect(desktop.width() - H_INDENT,
                  desktop.height() - V_INDENT, D_WIDTH, D_HEIGHT))

        self.setStyleSheet("QWidget { background-color: rgb(255, 255, 255); }")
        """Info dialog"""
        self.info.setWindowFlags(Qt.FramelessWindowHint
                                 | Qt.WindowStaysOnTopHint)
        self.info.setFrameStyle(QFrame.StyledPanel | QFrame.Raised)
        self.info.setGeometry(
            QRect(desktop.width() - H_INDENT - I_WIDTH - I_INDENT,
                  desktop.height() - V_INDENT, I_WIDTH, I_HEIGHT))
        self.info.setFixedSize(I_WIDTH, I_HEIGHT)

        self.info.setStyleSheet(
            "QWidget { background-color: rgb(255, 255, 255); }")
        """Verbose info dialog"""
        self.allInfo.setWindowFlags(Qt.FramelessWindowHint
                                    | Qt.WindowStaysOnTopHint)
        self.allInfo.setFrameStyle(QFrame.StyledPanel | QFrame.Raised)
        self.allInfo.setGeometry(
            QRect(desktop.width() - H_INDENT - I_WIDTH - I_INDENT,
                  desktop.height() - V_INDENT, I_WIDTH, I_HEIGHT))

        self.allInfo.setStyleSheet(
            "QWidget { background-color: rgb(255, 255, 255); }")
        """Session message"""
        self.status.setWindowFlags(Qt.FramelessWindowHint
                                   | Qt.WindowStaysOnTopHint)
        self.status.setFrameStyle(QFrame.StyledPanel | QFrame.Raised)
        self.status.setGeometry(
            QRect(
                desktop.width() - H_INDENT,
                desktop.height() - V_INDENT - S_HEIGHT - S_INDENT -
                S_CORRECTION, S_WIDTH, S_HEIGHT))

        self.status.setStyleSheet(
            "QWidget { background-color: rgb(255, 255, 255); }")

        self.setMask(roundCorners(self.rect(), 5))
        self.status.setMask(roundCorners(self.status.rect(), 5))
        #self.info.setMask(roundCorners(self.info.rect(),5))
        #self.allInfo.setMask(roundCorners(self.allInfo.rect(),5))

    def initializeComponents(self):
        self.countdown.setMaximumHeight(6)
        self.countdown.setRange(0, self.options.getCountdownInterval() * 100)
        self.countdown.setTextVisible(False)
        self.countdown.setStyleSheet(
            "QProgressbar { background-color: rgb(255, 255, 255); }")

        #self.setFont(QFont(Fonts.SyoutyouProEl, 40))#self.options.getQuizFontSize()))

        self.sentence.setAlignment(Qt.AlignmentFlag.AlignCenter)
        #self.sentence.setFont(QFont(Fonts.HiragiNoMarugotoProW4, self.options.getSentenceFontSize()))
        self.sentence.setFont(
            QFont(self.options.getSentenceFont(),
                  self.options.getSentenceFontSize()))

        self.sentence.setWordWrap(True)
        self.trayIcon.setIcon(QIcon(PATH_TO_RES + TRAY + 'active.png'))

        self.status.message.setFont(
            QFont('Cambria', self.options.getMessageFontSize()))
        self.status.layout.setAlignment(Qt.AlignCenter)
        self.status.message.setWordWrap(False)
        self.status.layout.setMargin(0)

        self.info.item.setFont(QFont(Fonts.HiragiNoMyoutyouProW3, 36))
        self.info.reading.setFont(QFont(Fonts.HiragiNoMyoutyouProW3, 16))
        self.info.components.setFont((QFont(Fonts.HiragiNoMyoutyouProW3, 14)))
        #self.info.item.setWordWrap(True)
        self.info.components.setWordWrap(True)
        #self.info.layout.setAlignment(Qt.AlignCenter)
        self.info.layout.setMargin(0)
        #self.info.layout.setSizeConstraint(self.info.layout.SetFixedSize)       #NB: would work nice, if the anchor point was in right corner

        self.info.reading.setAlignment(Qt.AlignCenter)
        self.info.item.setAlignment(Qt.AlignCenter)
        self.info.components.setAlignment(Qt.AlignCenter)

        #self.info.setLayoutDirection(Qt.RightToLeft)

        self.info.reading.setStyleSheet(
            "QLabel { color: rgb(155, 155, 155); }")
        self.info.components.setStyleSheet(
            "QLabel { color: rgb(100, 100, 100); }")

        self.info.gem = self.info.saveGeometry()

####################################
#        Updating content          #
####################################

    def updateContent(self):
        """Resetting multi-label sentence"""
        if self.grid_layout != ():
            for i in range(0, self.grid_layout.count()):
                self.grid_layout.itemAt(i).widget().hide()
            self.layout_vertical.removeItem(self.grid_layout)
            self.grid_layout.setParent(None)
            self.update()
        if self.sentence.isHidden():
            self.sentence.show()
        self.showButtonsQuiz()
        """Getting actual content"""
        self.srs.getNextItem()

        start = datetime.now()  #testing
        #example = self.srs.getCurrentExample().replace(self.srs.getWordFromExample(), u"<font color='blue'>" + self.srs.getWordFromExample() + u"</font>")
        example = self.srs.getCurrentExample().replace(
            self.srs.getCurrentItem(),
            u"<font color='blue'>" + self.srs.getCurrentItem() + u"</font>")
        print datetime.now() - start  #testing
        self.sentence.setText(example)

        #        start = datetime.now()  #testing
        #        readings = self.srs.getQuizVariants()
        #        print datetime.now() - start    #testing
        '''
        changeFont = False
        for item in readings:
            if len(item) > 5 : changeFont = True
            
        if changeFont: self.setStyleSheet('QWidget { font-size: 11pt; }')
        else:   self.setStyleSheet('QWidget { font-size: %spt; }' % self.options.getQuizFontSize())
        '''
        '''
        if len(readings) == 4:                  #NB: HERE LIES THE GREAT ERROR
            self.var_1st.setText(readings[0])
            self.var_2nd.setText(readings[1])
            self.var_3rd.setText(readings[2])
            self.var_4th.setText(readings[3])
        '''

#        try:
#            for i in range(0, self.layout_horizontal.count()):
#                    if i > 3: break
#                    self.layout_horizontal.itemAt(i).widget().setText(u'')
#                    #self.layout_horizontal.itemAt(i).setStyleSheet('QPushButton { font-size: 11pt; }')
#                    self.layout_horizontal.itemAt(i).widget().setText(readings[i])
#        except:
#            print 'Not enough quiz variants'
#TODO: log this

    def getReadyPostLayout(self):
        self.sentence.hide()
        self.update()

        self.grid_layout = QGridLayout()
        self.grid_layout.setSpacing(0)
        self.labels = []

        columns_mod = 0

        if len(self.srs.currentExample['eng']) > SENTENCE_MAX:
            font = QFont(self.options.getSentenceFont(), MIN_FONT_SIZE)
            columns_mod = 6
        else:
            font = QFont(self.options.getSentenceFont(),
                         self.options.getSentenceFontSize())

        #row, column, rows span, columns span, max columns
        i = 0
        j = 0
        r = 1
        c = 1
        n = COLUMNS_MAX + columns_mod
        for word in self.srs.parseCurrentExample():
            label = QLabel(word)
            #            label.setFont(QFont(self.options.getSentenceFont(), self.options.getSentenceFontSize()))
            label.setFont(font)

            label.setAttribute(Qt.WA_Hover, True)
            label.installEventFilter(self.filter)
            self.labels.append(label)

            if len(label.text()) > 1: c = len(label.text())
            else: c = 1
            #Don't ask, really
            if j + c > n:
                i = i + 1
                j = 0

            self.grid_layout.addWidget(self.labels.pop(), i, j, r, c)

            if j <= n: j = j + c
            else:
                j = 0
                i = i + 1

        self.grid_layout.setAlignment(Qt.AlignCenter)
        self.layout_vertical.insertLayout(1, self.grid_layout)

        self.update()

    def hideButtonsQuiz(self):
        self.isKnown.hide()
        self.isNotKnown.hide()

        self.answered.clicked.connect(self.hideQuizAndWaitForNext)
        self.answered.show()

    def showButtonsQuiz(self):
        self.isKnown.show()
        self.isNotKnown.show()

        self.answered.hide()
        self.answered.disconnect()

####################################
#        Timers and animations     #
####################################

    def waitUntilNextTimeslot(self):
        #if self.nextQuizTimer.isActive():   self.nextQuizTimer.stop()
        self.nextQuizTimer.start(
            self.options.getRepetitionInterval() * 60 * 1000
        )  #options are in minutes    NB: how do neatly I convert minutes to ms?

    def beginCountdown(self):
        self.trayIcon.setToolTip('Quiz in progress!')
        self.pauseAction.setText('&Pause')
        self.pauseAction.setShortcut('P')

        self.countdownTimer.start(self.options.getCountdownInterval() * 1000)

        self.progressTimer = RepeatTimer(
            0.01, self.updateCountdownBar,
            self.options.getCountdownInterval() * 100)
        self.progressTimer.start()

    def updateCountdownBar(self):
        self.countdown.setValue(self.countdown.value() - 1)
        #print self.countdown.value()
        self.countdown.update(
        )  #NB: without .update() recursive repaint crushes qt

    def fade(self):
        if self.windowOpacity() == 1:
            self.animationTimer = RepeatTimer(0.025, self.fadeOut, 40)
            self.animationTimer.start()
        else:
            self.animationTimer = RepeatTimer(0.025, self.fadeIn, 40)
            self.animationTimer.start()

    def fadeIn(self):
        self.setWindowOpacity(self.windowOpacity() + 0.1)

    def fadeOut(self):
        self.setWindowOpacity(self.windowOpacity() - 0.1)

    def stopCountdown(self):
        self.progressTimer.cancel()
        self.countdownTimer.stop()
        self.countdown.setValue(0)

####################################
#        Actions and events        #
####################################

    def setMenus(self):
        self.trayMenu.addAction(
            QAction('&Quiz me now!',
                    self,
                    shortcut="Q",
                    triggered=self.showQuiz))
        self.pauseAction = QAction('&Start quiz!',
                                   self,
                                   shortcut="S",
                                   triggered=self.pauseQuiz)
        self.trayMenu.addAction(self.pauseAction)
        self.trayMenu.addSeparator()
        self.trayMenu.addAction(
            QAction('Quick &dictionary',
                    self,
                    shortcut="D",
                    triggered=self.showQuickDict))
        self.trayMenu.addAction(
            QAction('&Global &statistics',
                    self,
                    shortcut="G",
                    triggered=self.showGlobalStatistics))
        self.trayMenu.addAction(
            QAction('&Options', self, shortcut="O",
                    triggered=self.showOptions))
        self.trayMenu.addAction(
            QAction('&About', self, shortcut="A", triggered=self.showAbout))
        self.trayMenu.addSeparator()
        self.trayMenu.addAction(
            QAction('&Exit', self, shortcut="E", triggered=self.saveAndExit))

        self.trayIcon.setContextMenu(self.trayMenu)
        self.trayIcon.activated.connect(self.onTrayIconActivated)

    #TODO: show session statistics
    def onTrayIconActivated(self, reason):
        '''
        if reason == QSystemTrayIcon.DoubleClick:
            print 'tray icon double clicked'
        '''
        if reason == QSystemTrayIcon.Trigger:
            if self.isHidden():
                self.trayIcon.showMessage(
                    'Current session statistics:',
                    'Running time:\t\t' + self.stats.getRunningTime() +
                    '\nItems seen:\t\t' + str(self.stats.totalItemSeen) +
                    '\nCorrect answers:\t\t' +
                    str(self.stats.answeredCorrect) + '\nWrong answers:\t\t' +
                    self.stats.getIncorrectAnswersCount() +
                    '\nCorrect ratio:\t\t' +
                    self.stats.getCorrectRatioPercent() +
                    #'\nQuiz total time:\t\t' + self.stats.getQuizActive() +
                    '\nQuiz paused time:\t\t' + self.stats.getPausedTime() +
                    '\nTotal pondering time:\t' + self.stats.getMusingsTime() +
                    '\nTotal post-quiz time:\t' + self.stats.getQuizTime() +
                    '\nAverage pondering:\t' +
                    self.stats.getAverageMusingTime() +
                    '\nAverage post-quiz:\t' +
                    self.stats.getAveragePostQuizTime(),
                    QSystemTrayIcon.MessageIcon.Information,
                    20000)

    def setButtonsActions(self):
        self.isKnown.clicked.connect(self.correctAnswer)
        self.isNotKnown.clicked.connect(self.wrongAnswer)

    def resetButtonsActions(self):
        self.isKnown.disconnect()
        self.isNotKnown.disconnect()

    def postAnswerActions(self):
        self.stats.musingsStopped()
        self.stats.postQuizStarted()

        self.stopCountdown()
        self.hideButtonsQuiz()

        self.getReadyPostLayout()

    def checkTranslationSize(self, translation):
        if len(translation) > TRANSLATION_CHARS_LIMIT:
            self.answered.setStyleSheet('QPushButton { font-size: 9pt; }')

            space_indices = [
                i for i, value in enumerate(translation) if value == ' '
            ]
            find_nearest_index = lambda value, list: min(
                list, key=lambda x: abs(x - value))
            nearest_index = find_nearest_index(TRANSLATION_CHARS_LIMIT,
                                               space_indices)
            translation = translation[:nearest_index] + '\n' + translation[
                nearest_index + 1:]
        else:
            self.answered.setStyleSheet('QPushButton { font-size: 11pt; }')

        self.answered.setText(translation)

    def correctAnswer(self):
        '''
        self.stats.musingsStopped()
        self.stats.postQuizStarted()
        
        self.stopCountdown()
        self.hideButtonsQuiz()
        
        self.getReadyPostLayout()
        '''
        self.postAnswerActions()

        self.srs.answeredCorrect()
        self.stats.quizAnsweredCorrect()
        #self.answered.setText(u"<font='Cambria'>" + self.srs.getCurrentSentenceTranslation() + "</font>")
        #        self.answered.setText(self.srs.getCurrentSentenceTranslation())

        self.checkTranslationSize(self.srs.getCurrentSentenceTranslation())

        #self.answered.setFont(QFont('Calibri', 11))
        self.showSessionMessage(
            u'<font color=green>Correct: OK</font>\t|\tNext quiz: ' +
            self.srs.getNextQuizTime() + '\t|\t<font color=' +
            self.srs.getLeitnerGradeAndColor()['color'] + '>Grade: ' +
            self.srs.getLeitnerGradeAndColor()['grade'] + ' (' +
            self.srs.getLeitnerGradeAndColor()['name'] + ')<font>')

        #self.answered.setShortcut('5')
        #self.setFocus()

    def wrongAnswer(self):
        '''
        self.stats.musingsStopped()
        self.stats.postQuizStarted()
        
        self.stopCountdown()
        self.hideButtonsQuiz()
        
        self.getReadyPostLayout()
        '''
        self.postAnswerActions()

        self.srs.answeredWrong()
        self.stats.quizAnsweredWrong()

        #        self.answered.setText(self.srs.getCurrentSentenceTranslation())

        self.checkTranslationSize(self.srs.getCurrentSentenceTranslation())

        #self.answered.setFont(QFont('Calibri', 11))
        #self.showSessionMessage(u"Wrong! Should be: <font style='font-family:" + Fonts.MSMyoutyou + "'>"
        #+ self.srs.getCorrectAnswer() + "</font> - Next quiz: " + self.srs.getNextQuizTime())
        self.showSessionMessage(
            u'<font color=tomato>Bad</font>\t|\tNext quiz: ' +
            self.srs.getNextQuizTime() + '\t|\t<font color=' +
            self.srs.getLeitnerGradeAndColor()['color'] + '>Grade: ' +
            self.srs.getLeitnerGradeAndColor()['grade'] + ' (' +
            self.srs.getLeitnerGradeAndColor()['name'] + ')<font>')

    def timeIsOut(self):
        self.stats.musingsStopped()
        self.stats.postQuizStarted()

        QTimer.singleShot(
            50, self.hideButtonsQuiz
        )  #NB: slight artificial lag to prevent recursive repaint crush, when mouse is suddenly over appearing button
        self.getReadyPostLayout()

        self.srs.answeredWrong()
        self.stats.quizAnsweredWrong()

        #self.showSessionMessage(u'Time is out! Correct answer is:' + self.srs.getCorrectAnswer())
        #        self.answered.setFont(QFont('Calibri', 11))
        #        self.answered.setText(self.srs.getCurrentSentenceTranslation())

        self.checkTranslationSize(self.srs.getCurrentSentenceTranslation())

        self.showSessionMessage(
            u'<font color=tomato>Timeout!</font>\t|\tNext quiz: ' +
            self.srs.getNextQuizTime() + '\t|\t<font color=' +
            self.srs.getLeitnerGradeAndColor()['color'] + '>Grade: ' +
            self.srs.getLeitnerGradeAndColor()['grade'] + ' (' +
            self.srs.getLeitnerGradeAndColor()['name'] + ')<font>')

    def hideQuizAndWaitForNext(self):
        self.stats.postQuizEnded()

        self.status.hide()
        self.info.hide()
        self.allInfo.hide()
        self.resetButtonsActions()

        self.setWindowOpacity(1)
        self.fade()
        QTimer.singleShot(1000, self.hide)
        self.waitUntilNextTimeslot()
        #self.updater.mayUpdate = True

    def pauseQuiz(self):
        if self.isHidden():
            if self.pauseAction.text() == '&Pause':
                self.nextQuizTimer.stop()
                self.pauseAction.setText('&Unpause')
                self.pauseAction.setShortcut('U')
                self.trayIcon.setToolTip('Quiz paused!')

                self.trayIcon.setIcon(
                    QIcon(PATH_TO_RES + TRAY + 'inactive.png'))
                self.stats.pauseStarted()

                #self.updater.mayUpdate = True

            elif self.pauseAction.text() == '&Start quiz!':
                self.waitUntilNextTimeslot()
                self.pauseAction.setText('&Pause')
                self.pauseAction.setShortcut('P')
                self.trayIcon.setToolTip('Quiz in progress!')

                self.trayIcon.setIcon(QIcon(PATH_TO_RES + TRAY + 'active.png'))
            else:
                self.waitUntilNextTimeslot()
                self.pauseAction.setText('&Pause')
                self.pauseAction.setShortcut('P')
                self.trayIcon.setToolTip('Quiz in progress!')

                self.trayIcon.setIcon(QIcon(PATH_TO_RES + TRAY + 'active.png'))
                self.stats.pauseEnded()

                #self.updater.mayUpdate = False
        else:
            self.showSessionMessage(
                u'Sorry, cannot pause while quiz in progress!')

    def showQuiz(self):
        if self.isHidden():
            self.updateContent()
            self.setButtonsActions()

            self.show()
            self.setWindowOpacity(0)
            self.fade()

            self.countdown.setValue(self.options.getCountdownInterval() * 100)
            self.beginCountdown()
            self.stats.musingsStarted()

            if self.nextQuizTimer.isActive(): self.nextQuizTimer.stop()
            #self.updater.mayUpdate = False
        else:
            self.showSessionMessage(u'Quiz is already underway!')

    def showOptions(self):
        self.optionsDialog.show()

    def showAbout(self):
        self.about.show()

    def showQuickDict(self):
        self.qdict.showQDict = True

    def showGlobalStatistics(self):
        print '...'

    def startTrayLoading(self):
        self.gifLoading.start()
        #self.iconTimer = QTimer()
        #self.iconTimer.timeout.connect(self.updateTrayIcon)
        #self.iconTimer.start(100)

    def stopTrayLoading(self):
        self.gifLoading.stop()

    def updateTrayIcon(self):
        self.trayIcon.setIcon(self.gifLoading.currentPixmap())

    def showSessionMessage(self, message):
        """Shows info message"""
        self.status.message.setText(message)
        self.status.show()
        #self.setFocus() #NB: does not work

    def saveAndExit(self):
        self.hide()
        self.status.hide()
        self.allInfo.hide()
        self.trayIcon.showMessage('Shutting down...', 'Saving session',
                                  QSystemTrayIcon.MessageIcon.Information,
                                  20000)
        #self.startTrayLoading()

        if self.countdownTimer.isActive():
            self.countdownTimer.stop()
        if self.nextQuizTimer.isActive():
            self.nextQuizTimer.stop()
        if self.progressTimer != () and self.progressTimer.isAlive():
            self.progressTimer.cancel()

        self.srs.endCurrentSession()
        self.trayIcon.hide()

        #        self.hooker.stop()

        #self.updater.stop()
        self.optionsDialog.close()
        self.about.close()
        #self.qdict.close()
        self.close()

    def addReferences(self, about, options, qdict, updater):
        self.about = about
        self.optionsDialog = options
        self.qdict = qdict
        self.updater = updater

    def initGlobalHotkeys(self):
        def toggleWidgetFlag():
            self.qdict.showQDict = True

        self.hooker = GlobalHotkeyManager(toggleWidgetFlag, 'Q')
        self.hooker.setDaemon(True)
        self.hooker.start()
Ejemplo n.º 13
0
class MainWindow(object):
    '''
    Contains the implementation for building and displaying the 
    application's main window.
    '''

    def __init__(self, model, alg):
        '''
        Constructs the GUI and initializes internal parameters.
        '''
        self._window = QMainWindow()
        self._window.setWindowTitle("Reverse A*")
        self._worldWidget = WorldWidget(model, alg)
        self._model = model
        self._alg = alg
        self._spdSetting = 0
        self._timer = QTimer()
        #Every time the timer times out, invoke the _onStep method.
        self._timer.timeout.connect(self._onStep)        
        self._buildGUI()
        self._window.show()

    def _buildGUI(self):
        '''
        Construct the GUI widgets and layouts.
        '''
        centerWidget = QWidget()
        self._window.setCentralWidget(centerWidget)
        
        worldLayout = QHBoxLayout()
        worldLayout.addWidget(self._worldWidget)
        grpBx = QGroupBox("2D World")
        grpBx.setLayout(worldLayout)
        grpBx.setSizePolicy(QSizePolicy.Expanding, QSizePolicy.Expanding)
        
        ctrlPan = self._buildControlPanel()
        layout = QHBoxLayout()
        layout.addWidget(ctrlPan)
        layout.addWidget(grpBx)
        layout.setAlignment(ctrlPan, Qt.AlignLeft | Qt.AlignTop)
        centerWidget.setLayout(layout)

        
    def _buildControlPanel(self):
        '''
        Create all buttons, labels, etc for the application control elements
        '''
        layout = QVBoxLayout()
        layout.addWidget(self._buildSetupPanel())
        layout.addWidget(self._buildSpeedPanel())
        layout.addWidget(self._buildResultsPanel())
        layout.addWidget(self._buildRenderingOptions())
        layout.setAlignment(Qt.AlignLeft | Qt.AlignTop)
        
        ctrlWidget = QWidget(self._window)
        ctrlWidget.setLayout(layout)
        ctrlWidget.setSizePolicy(QSizePolicy.Fixed, QSizePolicy.Fixed)
        return ctrlWidget        
    
    def _buildSetupPanel(self):
        '''
        Creates the sub-panel containing control widgets for re-initializing 
        the world on demand.
        '''
        self._percentLbl = QLabel("%")
        self._setupBtn = QPushButton("Setup", self._window)
        self._setupBtn.clicked.connect(self._onSetup)
        
        self._percentObstacleSldr = QSlider(Qt.Horizontal, self._window)
        self._percentObstacleSldr.setTickPosition(QSlider.TickPosition.TicksBelow)
        self._percentObstacleSldr.setTickInterval(10)
        self._percentObstacleSldr.setMinimum(0)
        self._percentObstacleSldr.setMaximum(100)
        self._percentObstacleSldr.valueChanged.connect(self._onPercentSlideChange)
        self._percentObstacleSldr.setValue(33)
        
        layout = QGridLayout()
        layout.addWidget(self._setupBtn, 0, 0, 1, 2)
        layout.addWidget(QLabel("Percent Occupied:"), 1, 0)
        layout.addWidget(self._percentLbl, 1, 1)
        layout.addWidget(self._percentObstacleSldr, 2, 0, 1, 2)
        
        grpBx = QGroupBox("Setup Controls")
        grpBx.setLayout(layout)
        grpBx.setSizePolicy(QSizePolicy.Minimum, QSizePolicy.Fixed)
        return grpBx        
        
    def _buildSpeedPanel(self):
        '''
        Creates the sub-panel containing control widgets for controlling the 
        speed of execution of the algorithm.
        '''
        self._runBtn = QPushButton("Run", self._window)
        self._stepBtn = QPushButton("Step Once", self._window)        
        self._runBtn.clicked.connect(self._onRun)
        self._stepBtn.clicked.connect(self._onStep)        
        
        slowRadio = QRadioButton('Slow', self._window)
        medRadio = QRadioButton('Medium', self._window)
        fastRadio = QRadioButton('Fast', self._window)
        notVisRadio = QRadioButton('Not visible', self._window)
        slowRadio.setChecked(True)        
        
        self._speedGroup = QButtonGroup(self._window)
        self._speedGroup.addButton(slowRadio, 0)
        self._speedGroup.addButton(medRadio, 1)
        self._speedGroup.addButton(fastRadio, 2)
        self._speedGroup.addButton(notVisRadio, 3)
        self._speedGroup.buttonClicked.connect(self._onSpeedChange)
          
        layout = QVBoxLayout()
        layout.addWidget(self._runBtn)
        layout.addWidget(self._stepBtn)
        layout.addWidget(slowRadio)
        layout.addWidget(medRadio)
        layout.addWidget(fastRadio)
        layout.addWidget(notVisRadio)
        
        grpBx = QGroupBox("Run Controls")
        grpBx.setLayout(layout)
        grpBx.setSizePolicy(QSizePolicy.Minimum, QSizePolicy.Fixed)
        return grpBx
    
    def _buildResultsPanel(self):
        '''
        Creates the sub-panel containing displays widgets for informing the 
        user on the results of running the algorithm.
        '''        
        self._doneLbl = QLabel("No", self._window)
        self._solvableLbl = QLabel("Yes", self._window)
        
        #_doneLbl is highlighted green upon successful algorithm completion
        pal = self._doneLbl.palette()
        pal.setColor(QPalette.Window, Qt.green)
        self._doneLbl.setPalette(pal)

        #_solvableLbl is highlighted red if the world model isn't solvable
        pal = self._solvableLbl.palette()
        pal.setColor(QPalette.Window, Qt.red)
        self._solvableLbl.setPalette(pal)          
        
        layout = QGridLayout()
        layout.addWidget(QLabel("Path Found:"), 0, 0)
        layout.addWidget(self._doneLbl, 0, 1)
        layout.addWidget(QLabel("Is Solvable:"), 1, 0)
        layout.addWidget(self._solvableLbl, 1, 1)
        
        grpBx = QGroupBox("Results")
        grpBx.setLayout(layout)
        grpBx.setSizePolicy(QSizePolicy.Minimum, QSizePolicy.Fixed)
        return grpBx
    
    def _buildRenderingOptions(self):
        '''
        Creates the sub-panel containing control widgets for setting options
        in how the world is rendered on the GUI.
        '''        
        self._openChk = QCheckBox("Active Cells")
        self._visitedChk = QCheckBox("Visited Cells")
        self._pathChk = QCheckBox("Draw Path")
        self._costChk = QCheckBox("Draw Estimated Costs")
        
        pal = self._openChk.palette()
        pal.setColor(QPalette.WindowText, Qt.green)
        self._openChk.setPalette(pal)
        
        pal = self._visitedChk.palette()
        pal.setColor(QPalette.WindowText, Qt.cyan)
        self._visitedChk.setPalette(pal)
        
        pal = self._pathChk.palette()
        pal.setColor(QPalette.WindowText, Qt.red)
        self._pathChk.setPalette(pal)
        
        self._visitedChk.setChecked(True)
        self._pathChk.setChecked(True)
        self._costChk.setChecked(True)
        
        self._openChk.stateChanged.connect(self._renderingOptionChanged)
        self._visitedChk.stateChanged.connect(self._renderingOptionChanged)
        self._pathChk.stateChanged.connect(self._renderingOptionChanged)
        self._costChk.stateChanged.connect(self._renderingOptionChanged)
        
        layout = QVBoxLayout()
        layout.addWidget(self._openChk)
        layout.addWidget(self._visitedChk)
        layout.addWidget(self._pathChk)
        layout.addWidget(self._costChk)
        
        grpBx = QGroupBox("Rendering Options")
        grpBx.setLayout(layout)
        grpBx.setSizePolicy(QSizePolicy.Minimum, QSizePolicy.Fixed)
        return grpBx        
    
    @Slot()
    def _renderingOptionChanged(self, value):
        '''
        When any rendering option is changed this method is invoked.  It polls
        the GUI for the selected setting values and passes them to the 2D
        world widget.
        '''
        self._worldWidget.setDrawActiveCells(self._openChk.isChecked())
        self._worldWidget.setDrawVisitedCells(self._visitedChk.isChecked())
        self._worldWidget.setDrawPath(self._pathChk.isChecked())
        self._worldWidget.setDrawCosts(self._costChk.isChecked())
        self._worldWidget.repaint()
    
    @Slot()
    def _onPercentSlideChange(self, value):
        '''
        Invoked every time the percent slider is changed.  Displays the percent
        value on the GUI.
        '''
        
        #Add extra padding to the front of the string to help prevent
        #gui layout resizing
        if value < 10:
            self._percentLbl.setText("  " + str(value) + "%")
        elif value < 100:
            self._percentLbl.setText(" " + str(value) + "%")
        else:
            self._percentLbl.setText(str(value) + "%")
    
    @Slot()
    def _onSpeedChange(self, value):
        '''
        Invoked every time one of the speed setting radio buttons are selected.
        Resets the algorithm iterating callback timer if it's currently running.
        '''
        self._spdSetting = self._speedGroup.checkedId()
        if self._timer.isActive():
            self._resetTimer()            
         
    @Slot()
    def _onSetup(self):
        '''
        Invoked when the setup button is pushed.  Re-initializes the world model
        and the algorithm.
        '''
        self._timer.stop()
        self._runBtn.setText('Run')
        self._model.reset(self._percentObstacleSldr.value() / 100.0)
        self._alg.reset()
        self._doneLbl.setText("No")
        self._solvableLbl.setText("Yes")
        self._doneLbl.setAutoFillBackground(False)
        self._solvableLbl.setAutoFillBackground(False)
        self._worldWidget.repaint()
    
    @Slot()
    def _onRun(self):
        '''
        Invoked when the run button is pushed.  Toggles the algorithm iterating
        timer on and off.
        '''
        if self._timer.isActive():
            self._timer.stop()
            self._runBtn.setText("Run")
        else:
            self._resetTimer()
            self._runBtn.setText("Stop")
    
    @Slot()
    def _onStep(self):
        '''
        Invoked on every 'step once' call and on every timer timeout.  Iterates
        one step of the algorithm and then checks for termination conditions
        such as the algorithm being done or solvable.
        '''
        self._alg.step()
        self._worldWidget.repaint()
        
        if self._alg.isDone() or not self._alg.isSolvable():
            self._timer.stop()
            self._runBtn.setText('Run')
        
        self._checkTerminalConditions()
            
    def _checkTerminalConditions(self):
        '''
        Sets the 'results' labels based on the algorithm results.
        '''
        if self._alg.isDone():
            self._doneLbl.setText("Yes")
            self._doneLbl.setAutoFillBackground(True)

        if not self._alg.isSolvable():
            self._solvableLbl.setAutoFillBackground(True)
            self._solvableLbl.setText("No")

    def _resetTimer(self):
        '''
        When the algorithm run speed is modified by the user this resets the
        algorithm timer.
        '''
        if self._spdSetting == 3:
            while not self._alg.isDone() and self._alg.isSolvable():
                self._alg.step()
                
            self._worldWidget.repaint()
            self._timer.stop()
            self._runBtn.setText("Run")
            
            self._checkTerminalConditions()            
        else:
            timeOut = 1
            if self._spdSetting == 0:
                timeOut = 500
            elif self._spdSetting == 1:
                timeOut = 250
            elif self._spdSetting == 2:
                timeOut = 1            
            self._timer.start(timeOut)
Ejemplo n.º 14
0
class GameRunToAnthill(QWidget):
    #--------------------- initialization ---------------------
    def __init__(self):
        super(GameRunToAnthill, self).__init__()
        self.init_game('init')
        self.repaint()

    def init_game(self, type_of_init):
        self.game_started = False
        if type_of_init != 'restart':
            self.initScreenSize()
            self.fieldInit()
            self.windowInit()
            self.otherInit()
            self.timerInit()
            self.messageInit()
        else:
            self.timer.stop()
            self.field.focus_point = QPoint(0, 0)
            self.message.noMessage()

        self.antsInit()
        self.bordersInit()
        self.barriersInit()
        self.backgroundInit()
        self.zombieInit()
        self.updateState()

    def initScreenSize(self):
        self.show()
        self.close()

    def fieldInit(self):
        self.field = FieldProperty()
        self.field.focus_point = QPoint(0, 0)
        self.field.height = 500
        self.field.width = 8000
        self.field.number_of_barriers = 120
        self.field.start_line_x = 50
        self.field.finish_line_x = self.field.width - 80
        self.field.number_of_background_pictures = 0
        self.field.field_motion_step = 2
        self.field.number_of_ants = 1
        self.field.number_of_zombie_ants = 1

    def windowInit(self):
        self.setMaximumHeight(self.field.height + self.field.focus_point.y())
        self.setGeometry(2, 30, min(self.maximumWidth(), self.field.width), self.maximumHeight())
        self.setWindowTitle('Run to anthill')

    def otherInit(self):
        self.pressed_keys = set()
    def timerInit(self):
        self.reverse_calculation = AntReverseCalculation()

        self.timer = QTimer(self)
        self.timer.setInterval(10)
        QObject.connect(self.timer, SIGNAL('timeout()'), self.doGameStep)

    def messageInit(self):
        self.message = AntMessage()
        self.message.add('second_0', '.\\pictures\\messages\\go.png')
        self.message.add('second_1', '.\\pictures\\messages\\second_1.png')
        self.message.add('second_2', '.\\pictures\\messages\\second_2.png')
        self.message.add('second_3', '.\\pictures\\messages\\second_3.png')
        self.message.add('pause', '.\\pictures\\messages\\pause.png')
        self.message.add('tie', '.\\pictures\\messages\\tie.png')

    def antsInit(self):
        names = tuple(('red', 'blue', 'green'))
        keys = tuple((tuple((Qt.Key_W, Qt.Key_S, Qt.Key_A, Qt.Key_D)),
                      tuple((Qt.Key_Up, Qt.Key_Down, Qt.Key_Left, Qt.Key_Right)),
                      tuple((Qt.Key_I, Qt.Key_K, Qt.Key_J, Qt.Key_L))))
        self.ants = list()
        for index in range(self.field.number_of_ants):
            self.ants.append(Ant(names[index]))
            self.ants[index].picture.load('.\\pictures\\ants\\' + names[index] + '_ant.png')
            self.ants[index].motion.point = QPoint(0,
                self.field.height // (self.field.number_of_ants + self.field.number_of_zombie_ants + 1) * (index + 1))
            self.ants[index].motion.setKeyUp(keys[index][0])
            self.ants[index].motion.setKeyDown(keys[index][1])
            self.ants[index].motion.setKeyLeft(keys[index][2])
            self.ants[index].motion.setKeyRight(keys[index][3])
            self.message.add(self.ants[index].name + '_ant_wins', '.\\pictures\\messages\\'
                             + self.ants[index].name + '_ant_wins.png')

    def bordersInit(self):
        self.borders = AntBordersForRace()
        self.borders.height = 16
        self.borders.width = 64
        self.borders.append(AntObjectsWithWorkZoneByXOptimisation())
        self.borders.append(AntObjectsWithWorkZoneByXOptimisation())
        for x in range(4):
            self.borders[0].addPicture('.\\pictures\\borders\\border_' + str(x) + '.bmp')
            self.borders[1].addPicture('.\\pictures\\borders\\border_' + str(x) + '.bmp')

        for x in range(0, self.field.width, self.borders.width):
            self.borders[0].addObject(QPoint(x, 0), 0)
            self.borders[1].addObject(QPoint(x, self.field.height - self.borders.height), 1)
        self.borders[0].setRandomPictures()
        self.borders[1].setRandomPictures()

    def barriersInit(self):
        self.barriers = AntObjectsWithWorkZoneByXOptimisation()
        self.barriers.addPicture('.\\pictures\\barriers\\box.bmp')
        self.barriers.addPicture('.\\pictures\\barriers\\heisenberg.bmp')
        self.barriers.createRandomObjects(self.field.number_of_barriers,
                                          self.field.start_line_x + self.field.distance_before_first_barrier,
                                          self.field.finish_line_x,
                                          self.borders.height, self.field.height - self.borders.height)

    def backgroundInit(self):
        self.background_pictures = AntObjectsWithWorkZoneByXOptimisation()
        self.background_pictures.addPicture('.\\pictures\\background\\grass.png')
        for index in range(9):
            self.background_pictures.addPicture('.\\pictures\\background\\leaf_' + str(index) + '.png')
        self.background_pictures.createRandomObjects(self.field.number_of_background_pictures,
                                                     0, self.field.finish_line_x,
                                                     self.borders.height, self.field.height - self.borders.height)

    def zombieInit(self):
        self.message.add('zombie_waking_up', '.\\pictures\\messages\\zombie_waking_up.png')
        names = tuple(('red_zombie', 'blue_zombie', 'green_zombie'))
        for index in range(self.field.number_of_zombie_ants):
            self.ants.append(ZombieAnt(names[index], 30, 60))
            self.ants[-1].zombie = True
            self.ants[-1].picture.load('.\\pictures\\zombie_ants\\' + names[index] + '_ant.png')
            self.ants[-1].motion.point = QPoint(0,
                self.field.height // (self.field.number_of_ants + self.field.number_of_zombie_ants + 1) *
                (index + self.field.number_of_ants + 1))

            self.message.add(self.ants[-1].name + '_ant_wins',
                             '.\\pictures\\messages\\' + self.ants[-1].name + '_ant_wins.png')

        #--------------------- drawing ---------------------

    def paintBackground(self, painter):
        painter.setPen(Qt.white)
        painter.setBrush(Qt.white)
        painter.drawRect(0, 0, self.width(), self.height())

        for background_picture_item in self.background_pictures.workZone():
            background_picture = background_picture_item['object']
            painter.drawPixmap(background_picture.point + self.field.focus_point, background_picture.picture)

    def paintAnts(self, painter):
        for ant in self.ants:
            painter.drawPixmap(ant.motion.point + self.field.focus_point, ant.picture)

    def paintBarriers(self, painter):
        for barrier_item in self.barriers.workZone():
            barrier = barrier_item['object']
            painter.drawPixmap(barrier.point + self.field.focus_point, barrier.picture)

    def paintBorders(self, painter):
        for index in range(len(self.borders)):
            for border_item in self.borders[index].workZone():
                border = border_item['object']
                painter.drawPixmap(border.point + self.field.focus_point, border.picture)

    def paintStartAndFinishLine(self, painter):
        start = QPixmap()
        start.load('.\\pictures\\start-finish\\start.bmp')
        finish = QPixmap()
        finish.load('.\\pictures\\start-finish\\finish.bmp')

        for y in range(0, self.field.height, start.height()):
            painter.drawPixmap(QPoint(self.field.start_line_x, y) + self.field.focus_point, start)
            painter.drawPixmap(QPoint(self.field.finish_line_x, y) + self.field.focus_point, finish)

    def paintMessage(self, painter):
        if self.message.isThereMessage():
            point = QPoint((self.width() - self.message.getMessagePicture().width()) // 2,
                           (self.height() - self.message.getMessagePicture().height()) // 2)
            painter.drawPixmap(point, self.message.getMessagePicture())

    def paintEvent(self, event):
        painter = QPainter(self)

        self.updateState()

        self.paintBackground(painter)
        self.paintStartAndFinishLine(painter)
        self.paintAnts(painter)
        self.paintBarriers(painter)
        self.paintBorders(painter)
        self.paintMessage(painter)

    #--------------------- updating ---------------------

    def updateFieldFocusPoint(self):
        for ant in self.ants:
            if (self.width() - (ant.motion.point.x() + self.field.focus_point.x())) \
                    < self.field.critical_distance_before_screen_end:
                self.field.focus_point.setX(self.field.focus_point.x() - self.field.field_motion_step)
                return
            """if (ant.motion.point.x() + self.field.focus_point.x()) \
                    < self.field.critical_distance_before_screen_end:
                self.field.focus_point.setX(self.field.focus_point.x() + self.field.field_motion_step)
                return"""

    def updateConstants(self):
        self.field.critical_distance_before_screen_end = self.width() // 5 * 2

    def updateState(self):
        self.updateFieldFocusPoint()

        min_x = 0 - self.field.focus_point.x()
        max_x = self.width() - self.field.focus_point.x()
        self.barriers.updateWorkZone(min_x, max_x)
        self.background_pictures.updateWorkZone(min_x, max_x)
        for index in range(len(self.borders)):
            self.borders[index].updateWorkZone(min_x, max_x)

        self.updateConstants()

    def moveZombie(self):
        for ant in self.ants:
            if ant.zombie:
                self._moveAnt(ant, ant.move)

    def buildZombiePath(self):
        self.message.sendMessage('zombie_waking_up')
        self.repaint()
        for ant in self.ants:
            if ant.zombie:
                ant.findWayToVerticalLine(self.field.finish_line_x + 100, self.isAntIntersectAnything)
        self.message.noMessage()
        self.repaint()

    #--------------------- Game control ---------------------

    def reverseCalculationWorker(self, seconds_before_start):
        self.message.sendMessage('second_' + str(seconds_before_start))
        if seconds_before_start == 0:
            self.timer.start()
            QTimer.singleShot(1000, self.message.noMessage)
        self.repaint()

    def fastStart(self):
        self.game_started = True
        self.buildZombiePath()
        self.reverse_calculation.startReverseCalculation(0, self.reverseCalculationWorker)

    def start(self):
        self.game_started = True
        self.buildZombiePath()
        if self.timer.isActive():
            self.fastStart()
        else:
            self.reverse_calculation.startReverseCalculation(3, self.reverseCalculationWorker)

    def stop(self):
        self.timer.stop()
        self.message.sendMessage('pause')
        self.repaint()

    def restart(self):
        self.init_game('restart')
        self.repaint()

    def doGameStep(self):
        self.updateState()
        self.processPressedKeys()
        self.moveZombie()
        self.checkFinishLine()
        self.repaint()

    #--------------------- I'm afraid. We need to use ... math! ---------------------

    def _areTwoAntFinished(self):
        finished_count = 0
        for ant in self.ants:
            if self.isAntFinished(ant):
                finished_count += 1

        return finished_count >= 2

    def checkFinishLine(self):
        if self.message.getMessageText() in tuple(ant.name + '_ant_wins' for ant in self.ants) or \
           self.message.getMessageText() == 'tie':
                return

        if self._areTwoAntFinished():
            self.message.sendMessage('tie')
            return

        for ant in self.ants:
            if self.isAntFinished(ant):
                self.message.sendMessage(ant.name + '_ant_wins')

    def isAntFinished(self, ant):
        return ant.motion.point.x() + ant.width() >= self.field.finish_line_x

    def isAntIntersectBarriers(self, ant, check_only_work_zone=False):
        return isAntIntersectAntObjectsWithWorkZone(ant, self.barriers, check_only_work_zone)

    def isAntIntersectBorders(self, ant, check_only_work_zone=False):
        return isAntIntersectAntObjectsWithWorkZone(ant, self.borders[0], check_only_work_zone) or \
               isAntIntersectAntObjectsWithWorkZone(ant, self.borders[1], check_only_work_zone)

    def isAntIntersectAnything(self, ant, check_only_work_zone=False):
        return self.isAntIntersectBarriers(ant, check_only_work_zone) or \
               self.isAntIntersectBorders(ant, check_only_work_zone) or \
               isAntIntersectOtherAnts(ant, self.ants)

    #--------------------- Key processing ---------------------

    def _moveAnt(self, ant, doStep, diagonal_direction=False):
        if diagonal_direction:
            max_step_value = ant.motion.step_value // 2
        else:
            max_step_value = ant.motion.step_value

        for step_value in range(max_step_value):
            doStep(1)
            if self.isAntIntersectAnything(ant, True):
                ant.motion.cancelLastStep()
                return

    def _processPressedMotionKey(self, ant, key, doStep, diagonal_direction):
        if key not in self.pressed_keys:
            return

        self._moveAnt(ant, doStep, diagonal_direction)

    def processPressedMotionKeys(self):
        for ant in self.ants:
            if not ant.zombie:
                motion_key_set = {ant.motion.key_up, ant.motion.key_down, ant.motion.key_left, ant.motion.key_right}
                diagonal_direction = len(motion_key_set & self.pressed_keys) > 1

                self._processPressedMotionKey(ant, ant.motion.key_up, ant.motion.up, diagonal_direction)
                self._processPressedMotionKey(ant, ant.motion.key_down, ant.motion.down, diagonal_direction)
                self._processPressedMotionKey(ant, ant.motion.key_left, ant.motion.left, diagonal_direction)
                self._processPressedMotionKey(ant, ant.motion.key_right, ant.motion.right, diagonal_direction)

    def processPressedKeys(self):
        self.processPressedMotionKeys()

    def _addPlayer(self):
        if self.field.number_of_ants < 3:
            self.field.number_of_ants += 1
            self.antsInit()
            self.zombieInit()
            self.repaint()

    def _delPlayer(self):
        if self.field.number_of_ants > 0:
            self.field.number_of_ants -= 1
            self.antsInit()
            self.zombieInit()
            self.repaint()

    def _addZombie(self):
        if self.field.number_of_zombie_ants < 3:
            self.field.number_of_zombie_ants += 1
            self.antsInit()
            self.zombieInit()
            self.repaint()

    def _delZombie(self):
        if self.field.number_of_zombie_ants > 0:
            self.field.number_of_zombie_ants -= 1
            self.antsInit()
            self.zombieInit()
            self.repaint()

    def _processGameControlKey(self, key):
        if not self.game_started:
            if Qt.Key_Plus in self.pressed_keys and Qt.Key_Z in self.pressed_keys:
                self._addZombie()
            elif Qt.Key_Minus in self.pressed_keys and Qt.Key_Z in self.pressed_keys:
                self._delZombie()
            elif key == Qt.Key_Plus:
                self._addPlayer()
            elif key == Qt.Key_Minus:
                self._delPlayer()

        if key == Qt.Key_F5:
            self.restart()

        if self.message.getMessageText() not in ('red_ant_wins', 'blue_ant_wins', 'green_ant_wins', 'tie'):
            if key == Qt.Key_F8:
                self.fastStart()
            elif key == Qt.Key_F9:
                self.start()
            elif key == Qt.Key_F10:
                self.stop()

    def keyPressEvent(self, event):
        self.pressed_keys.add(event.key())
        self._processGameControlKey(event.key())

    def keyReleaseEvent(self, event):
        self.pressed_keys.remove(event.key())
Ejemplo n.º 15
0
class DisplayController(QObject):
    """ Controller class for the display app

    Acts as a coordinator between ClimateControlModel and DisplayView
    """
    def __init__(self, parent=None):
        super(DisplayController, self).__init__(parent)
        _, external_version = get_app_version()
        self.__display_app = DisplayView(external_version)
        self.__display_app.init_login_view(UserList().user_names)
        self.__display_app.register_login_callback(self.__login_btn_clicked)
        self.__display_app.close_signal.connect(self.__close)

        self.__model = None
        self.__refresh_graph_timer = QTimer(self)
        self.__refresh_graph_timer.timeout.connect(self.__speculate_data)
        self.temperature_data = collections.OrderedDict()
        self.humidity_data = collections.OrderedDict()
        self.temperature = None
        self.humidity = None
        self.__display_view_initialized = False

    @Slot(str, str)
    def __login_btn_clicked(self, user_name, password):
        """ Slot for the login button

         Tries to create model with the provided user_name and password

        :param str user_name: flow user name
        :param str password: flow user password
        """
        # clear any old status
        self.__display_app.login_view_show_error(None)

        LOGGER.debug("Trying to login with username = {}".format(user_name))

        try:
            # try to initialize model
            self.__model = ClimateControlModel()
            self.__model.connection_status.connect(
                self.__connection_status_result)
            try:
                self.__model.initialize(user_name, password)
            except LoginFailed:
                self.__display_app.login_view_show_error(
                    UIStrings.LOGIN_FAILED)
            except ControllerNotFound:
                self.__display_app.login_view_show_error(
                    UIStrings.CONTROLLER_DEVICE_NOT_FOUND)
            else:
                # Save username if no exception comes
                UserList().add_name(user_name)
                self.__model.setting_received.connect(
                    self.__get_setting_response)
                # define slot for receiving events from Controller
                self.__model.measurement_changed.connect(
                    self.__measurement_changed)
                self.__model.device_status_changed.connect(
                    self.__device_status_changed)
                self.__model.relay_status_changed.connect(
                    self.__relay_status_changed)
                self.__model.controller_status.connect(
                    self.__controller_status)
                self.__model.latency_changed.connect(self.__latency_changed)
                self.__model.get_settings()
        except FlowLibraryError:
            self.__display_app.login_view_show_error(
                UIStrings.FLOW_LIBRARY_ERROR)

    @Slot(dict)
    def __get_setting_response(self, result):
        """ Slot for the get setting response from ClimateControlModel

        Initializes display view with received setting or shows error
        :param dict result: dictionary of the setting to be shown on configuration tab
        """
        if not self.__display_view_initialized:
            self.__display_app.close_login_view()
            self.__display_app.init_display_view(result["setting"])
            self.__display_view_initialized = True
            if result["error"]:
                self.__display_app.update_status_bar(
                    UIStrings.SETTING_NOT_RECEIVED)
                LOGGER.error(result["error"])

        else:
            self.__display_app.update_settings(result["setting"])

    @Slot(dict)
    def __connection_status_result(self, connection_status):
        """Slot function for connection status result

        :param dict connection_status: dictionary containing network and internet status
        """
        status = UIStrings.OK
        color = "green"
        if not connection_status["network"]:
            color = "red"
            status = UIStrings.NETWORK_DOWN
        elif not connection_status["internet"]:
            status = UIStrings.INTERNET_DOWN
            color = "red"
        self.__display_app.set_connection_status(status, color)

    @Slot()
    def __close(self):
        """ Slot function when view gets closed

        Tries to de-initialize the model and stop refresh graph timer if active
        """
        if self.__refresh_graph_timer.isActive():
            self.__refresh_graph_timer.stop()

        LOGGER.debug("closing model")
        if self.__model:
            self.__model.close()

    @Slot(MeasurementEvent)
    def __measurement_changed(self, event):
        """ Slot function which receives measurement event from model

        :param MeasurementEvent event: Measurement Event object received from model
        """
        if self.__refresh_graph_timer.isActive():
            self.__refresh_graph_timer.stop()
        self.temperature = event.temperature
        self.humidity = event.humidity
        self.__update_data(self.temperature, self.humidity)
        self.__display_app.plot_graph(self.temperature_data,
                                      self.humidity_data)
        self.__refresh_graph_timer.start(REFRESH_GRAPH_TIMEOUT)

    @Slot()
    def __speculate_data(self):
        """ This function is a slot for refresh_graph timeout event

        It updates measurement data with last received measurement values
        """
        self.__update_data(self.temperature, self.humidity)
        self.__display_app.plot_graph(self.temperature_data,
                                      self.humidity_data)
        self.__refresh_graph_timer.start(REFRESH_GRAPH_TIMEOUT)

    def __update_data(self, temperature, humidity):
        """  Updates measurement data
        :param float temperature: temperature value
        :param float humidity: humidity value
        """
        current_time = datetime.datetime.now()
        if not (self.temperature_data and self.humidity_data):
            # fill the dictionary with last GRAPH_WINDOW_SIZE seconds of data
            for i in range(GRAPH_WINDOW_SIZE, 0, -1):
                self.temperature_data[current_time -
                                      datetime.timedelta(seconds=i)] = 0
                self.humidity_data[current_time -
                                   datetime.timedelta(seconds=i)] = 0

        self.temperature_data[current_time] = temperature
        self.humidity_data[current_time] = humidity
        if current_time - self.temperature_data.keys()[0] > datetime.\
                timedelta(seconds=GRAPH_WINDOW_SIZE):
            del self.temperature_data[self.temperature_data.keys()[0]]
            del self.humidity_data[self.humidity_data.keys()[0]]

    @Slot(str)
    def __controller_status(self, status):
        """ Slot function which receives controller device status from model

        It configures refresh graph timer according to controller device status
        :param str status: controller device status
        """
        LOGGER.debug("Controller status received - {}".format(status))
        if self.__display_app:
            self.__display_app.\
                update_device_status(DeviceEnum.controller, status == "ONLINE")
            # Latency is not applicable if controller is OFFLINE
            if status == "OFFLINE":
                self.__display_app.update_latency(None)
                LOGGER.debug("Latency set to NA")

            if status == "ONLINE" and not self.__refresh_graph_timer.isActive(
            ):
                self.__refresh_graph_timer.start(REFRESH_GRAPH_TIMEOUT)
                LOGGER.debug("Refresh graph timer started")

            elif status == "OFFLINE" and self.__refresh_graph_timer.isActive():
                self.__refresh_graph_timer.stop()
                LOGGER.debug("Refresh graph timer stopped")

            elif status == "ONLINE" and self.__refresh_graph_timer.isActive():
                LOGGER.debug("Refresh graph timer is already active")

            elif status == "OFFLINE" and not self.__refresh_graph_timer.isActive(
            ):
                LOGGER.debug("Refresh graph timer is already inactive")

        else:
            LOGGER.debug(
                "Controller status received before display window is initialized"
            )

    @Slot(DeviceStatusEvent)
    def __device_status_changed(self, event):
        """ Slot function which receives sensor and actuator status from model

        :param DeviceStatusEvent event: DeviceStatusEvent object
        """
        if self.__display_app:
            self.__display_app.update_device_status(DeviceEnum.sensor,
                                                    event.sensor_alive)
            self.__display_app.update_device_status(DeviceEnum.actuator,
                                                    event.actuator_alive)

        else:
            LOGGER.debug(
                "Device(sensor and actuator) status received before display window is "
                "initialized")

    @Slot(RelayStatusEvent)
    def __relay_status_changed(self, event):
        """ Slot function which receives relay status event from model

        :param RelayStatusEvent event: RelayStatusEvent object
        """
        if self.__display_app:
            self.__display_app.update_relay_status(event)

        else:
            LOGGER.debug(
                "Relay status received before display window is initialized")

    @Slot(float)
    def __latency_changed(self, latency):
        """ Slot function which receives latency from model

        :param float latency: latency value
        """
        if self.__display_app:
            self.__display_app.update_latency(latency)
Ejemplo n.º 16
0
class BasePicButton(QAbstractButton):
    clickObj = QtCore.Signal(ClickEvent)
    def __init__(self, pixmap, buttonHeight, buttonWidth, controller, num, selectedColor = (235, 235, 155), unselectedColor = (235, 235, 155) ):

        super(BasePicButton, self).__init__()
        self.doSize()
        self.curNum = num
        self.pixmap = pixmap
        self.timer = QTimer()
        self.timer.setSingleShot(True)
        #self.timer.timeout.connect(self.clicked.emit)
        self.outerFrame = None
        self.controller = controller
        self.isSelected = False

        self.pressed.connect(self.update)
        self.released.connect(self.update)

        self.doubleClickCallback = None

    def doSize(self):
        self.setMinimumHeight(buttonHeight)
        self.setMinimumWidth(buttonWidth)
        self.setMaximumHeight(buttonHeight)
        self.setMaximumWidth(buttonWidth)
        self.setContentsMargins(10, 10, 10, 10)
        self.setGeometry(3, 3, 100, 100)

    def setSelected(self):
        self.outerFrame.setStyleSheet('background-color: rgb(235, 235, 155);')

    def setUnselected(self):
        self.outerFrame.setStyleSheet('background-color: rgb(235, 235, 155);')

    def __eq__(self, other):
        return self.__dict__ == other.__dict__

    def paintEvent(self, event):
        pix = self.pixmap
        painter = QPainter(self)
        painter.drawPixmap(event.rect(), pix)

    def createOuterFrame(self):
        outerFrame = QWidget()
        height = buttonHeight + 6
        width = buttonWidth + 6
        outerFrame.setMinimumHeight(height)
        outerFrame.setMinimumWidth(width)
        outerFrame.setMaximumHeight(height)
        outerFrame.setMaximumWidth(width)
        outerFrame.setStyleSheet('background-color: rgb(20, 20, 20)')

        self.outerFrame = outerFrame

    def checkDoubleClick(self):
        if self.timer.isActive():
            #self.doubleClicked.emit()
            self.timer.stop()
        else:
            self.timer.start(250)
            if self.doubleclickCallback is not None:
                self.doubleClickCallback()
            pass

    def mousePressEvent(self, event):
        """
        :param event: QtGui.QMouseEvent
        :return:
        """
        modifiers = QtGui.QApplication.keyboardModifiers()

        button = event.button()

        if button == Qt.LeftButton:
            self.onMouseLeftClick(modifiers, event)
        elif button == Qt.RightButton:
            self.onMouseRightClick(event)

    def onMouseLeftClick(self, modifiers, event):
        self.clickObj.emit(self)
        self.checkDoubleClick()

    def onMouseRightClick(self, event):
        pass
Ejemplo n.º 17
0
class PicButton(QAbstractButton):
    def __init__(self, jsonPath, assetObj, num, pixmap, nameField, filePathField, picPath, tags, frame, controller):
        super(PicButton, self).__init__()
        self.doSize()
        self.jsonPath = jsonPath
        self.tags = tags
        self.curNum = num
        self.assetObj = assetObj
        self.fileName = assetObj.fileName
        self.pixmap = pixmap
        self.timer = QTimer()
        self.timer.setSingleShot(True)
        #self.timer.timeout.connect(self.clicked.emit)
        self.nameField = nameField
        self.filePathField = filePathField
        self.outerFrame = frame
        self.controller = controller
        self.picPath = picPath
        self.isSelected = False

        self.pressed.connect(self.update)
        self.released.connect(self.update)

    def doSize(self):
        self.setMinimumHeight(buttonHeight)
        self.setMinimumWidth(buttonWidth)
        self.setMaximumHeight(buttonHeight)
        self.setMaximumWidth(buttonWidth)
        self.setContentsMargins(10,10,10,10)
        self.setGeometry(3, 3, 100, 100)

    def setSelected(self):
        self.outerFrame.setStyleSheet('background-color: rgb(235, 235, 155);')

    def setUnselected(self):
        self.outerFrame.setStyleSheet('background-color: rgb(235, 235, 155);')

    def __eq__(self, other):
        return self.__dict__ == other.__dict__

    def checkDoubleClick(self):
        if self.timer.isActive():
            #self.doubleClicked.emit()
            self.timer.stop()
        else:
            self.timer.start(250)
            pass

    def mousePressEvent (self, event):
        modifiers = QtGui.QApplication.keyboardModifiers()
        controller = self.controller
        if modifiers == QtCore.Qt.ControlModifier:
            self.isSelected = False
            if controller.curPicButtons.count(self) > 0:
                controller.curPicButtons.remove(self)
        elif modifiers == QtCore.Qt.ShiftModifier:
            self.isSelected = True
            if controller.curPicButtons.count(self) == 0:
                controller.curPicButtons.append(self)
        else:
            self.isSelected = True
            controller.curPicButtons = []
            controller.curPicButtons.append(self)

        self.controller.OnClick(self)
        self.checkDoubleClick()

    def paintEvent(self, event):
        pix = self.pixmap
        painter = QPainter(self)
        painter.drawPixmap(event.rect(), pix)
Ejemplo n.º 18
0
class SpiderTab(QWidget):
  """Has handlers for spider data and events. It houses the results table of the
     spider, controls for the spider and progress indication
     It implicitly conforms to IEventHandler interface"""
  TIMER_CHECK_INTERVAL = 3000
  favicon_received = Signal(str) # send the url or path to the handler, which should be the tab widget
  stop_spider_signal = Signal(int)
  became_current = Signal(bool) # tell the table it has become active. it's an interesting property for producers!
  
  def __init__(self, parent=None, **kwargs):
    super(SpiderTab, self).__init__(parent)
    self._event_queue = None
    self._data_queue = None
    self._engine = None
    self._favicon_received = False
    self._spider_id = None
    self._item_count = 0
    self.setContextMenuPolicy(Qt.ContextMenuPolicy.DefaultContextMenu)
    self.initInterface(kwargs)
    self._context_menu = None
    self._setupContextMenu()
    self.became_current.connect(self._set_table_activity)
    self._queue_check_timer = QTimer()
    self._queue_check_timer.setInterval(self.TIMER_CHECK_INTERVAL)
    self._queue_check_timer.timeout.connect(self._checkQueues)
    self._queue_check_timer.start()
    
  def initInterface(self, kwargs):
    layout = QGridLayout()
    self._data_table = SearchTable(name=kwargs.get("name"))
    self._progress_spider = QProgressBar()
    self._label_count = QLabel(self.tr("0 items scraped"))
    # make it a busy indicator. you don't know when it'll finish 
    self._progress_spider.setMinimum(0); self._progress_spider.setMaximum(0)
    self._progress_spider.setTextVisible(False)
    self._btn_stop_spider = QPushButton(self.tr("Stop Spider"))
    self._btn_stop_spider.clicked.connect(self.stop_spider)
    row = 0; col = 0;
    layout.addWidget(self._data_table, row, col, 1, 4)
    row += 1;
    layout.addWidget(self._progress_spider, row, col, 1, 1)
    col += 1
    layout.addWidget(self._label_count, row, col, 1, 2)
    col += 2
    layout.addWidget(self._btn_stop_spider, row, col, 1, 1)
    self.setLayout(layout)
    
  def _setupContextMenu(self):
    from visualscrape.lib.data import ActionStore
    self._context_menu = QMenu(self)
    # get the export action from the action store
    action_store = ActionStore.get_instance()
    for action in action_store:
      if action.get_name() == "export":
        export_action = action
        break
    self._context_menu.addAction(export_action)
    
  def export_table(self):
    export_dialog = ExportDialog()
    export_dialog.exec_()
    export_info = export_dialog.data()
    if export_info:
      data = self._data_table.get_visible_data()
      FileExporter.export(data, self._data_table.name.lower(), export_info.location, export_info.format)
  
  def set_event_queue(self, eq):
    self._event_queue = eq
    
  def set_data_queue(self, dq):
    self._data_queue = dq
    
  def stop_spider(self):
    if self._spider_id is None: # do not stop the the spider before receiving data
      pass
    else:
      if self._queue_check_timer.isActive():
        confirm_stop = QMessageBox(self)
        confirm_stop.setIcon(QMessageBox.Warning)
        confirm_stop.setStandardButtons(QMessageBox.Yes | QMessageBox.No)
        confirm_stop.setText(self.tr("Scraping process still running"))
        confirm_stop.setDetailedText(self.tr("Are you sure you want to stop it?"))
        confirm_stop.setWindowTitle(self.tr("Spider still running"))
        ret = confirm_stop.exec_()
        if ret == QMessageBox.Yes:
          self.stop_spider_signal.emit(self._spider_id)
          return True
        else: return False # I won't whip you if you stop it accidentally
      else: return True # already over
      
  def configure_searchlineedit(self, lineEdit):
    self._data_table.configure_search_lineedit(lineEdit)
    
  def _checkQueues(self):
    while not self._event_queue.empty():
      event = self._event_queue.get(block=False, timeout=0)
      if isinstance(event, SpiderClosed):
        self._queue_check_timer.stop()
        self._progress_spider.setMinimum(0)
        self._progress_spider.setMaximum(100)
        self._progress_spider.setValue(100)
        self._btn_stop_spider.setEnabled(False)
    while not self._data_queue.empty():
      item = self._data_queue.get(block=False, timeout=0)
      if not self._favicon_received: # the first item on the data queue should be the favicon
        favicon_data = item["images"][0]
        self.favicon_received.emit(favicon_data["path"]) # note that icons are not guaranteed to have a path. Not everybody wants to save images
        self._favicon_received = True
        self._spider_id = item["_id"]
      else:
        item.pop("_id") # the table has nothing to do with spider ids
        self._data_table.addItem(item)
      self._item_count += 1
      self._label_count.setText(self.tr("{0:n} items scraped".format(self._item_count)))
      
  def _set_table_activity(self, state):
    self._data_table.set_active(state)
Ejemplo n.º 19
0
class Panel(QWidget):
    def __init__(self,parent=None,instr=None,lock=None,title='Instrument Panel'):
        # This class derivates from a Qt Widget so we have to call
        # the class builder ".__init__()"
        QWidget.__init__(self)
        # "self" is now a Qt Widget, then we load the user interface
        # generated with QtDesigner and call it self.ui
        self.ui = SR830_Ui.Ui_Panel()
        # Now we have to feed the GUI building method of this object (self.ui)
        # with the current Qt Widget 'self', but the widgets from the design will actually be built as children
        # of the object self.ui
        self.ui.setupUi(self)
        self.setWindowTitle(title)
        self.reserved_access_to_instr=lock
        self.instr=instr
        self.monitor_timer = QTimer()
        self.channel=self.ui.channel.currentIndex()
        #The timer would not wait for the completion of the task otherwise
        self.monitor_timer.setSingleShot(True)
        self.monitor_timer.timeout.connect(self.monitor)
        self.firsttime=0
        #bug: if the box is checked in the .ui file, the system freezes
        #if self.ui.monitor.isChecked():self.monitor()
    
    def update_boxes(self):
        with self.reserved_access_to_instr:
            #There are two signals emitted if the current item of a combobox changes,
            #PySide.QtGui.QComboBox.currentIndexChanged() and PySide.QtGui.QComboBox.activated().
            #PySide.QtGui.QComboBox.currentIndexChanged() is always emitted regardless 
            #if the change was done programmatically or by user interaction,
            #while PySide.QtGui.QComboBox.activated() is only emitted when the change is caused by user interaction.
            self.ui.sense.setCurrentIndex(self.instr.query_sensitivity())
            self.ui.TC.setCurrentIndex(self.instr.query_time_cste())
            self.ui.filter.setCurrentIndex(self.instr.query_filter_slop())
            self.ui.channel.setCurrentIndex(self.instr.query_ch1_display())
            self.ch1=self.ui.channel.currentText()
            self.ui.channel_2.setCurrentIndex(self.instr.query_ch2_display())
            self.ch2=self.ui.channel_2.currentText()
            self.ui.ref_source.setCurrentIndex(self.instr.query_ref_mode())
    

    def monitor(self,state=1):
        if state!=1:
            self.monitor_timer.stop()
            self.firsttime=0
        elif state and not(self.monitor_timer.isActive()):
            self.firsttime+=1
            if self.firsttime==1:self.update_boxes()
            with self.reserved_access_to_instr:
                x,y=self.instr.query_ch1_ch2(self.ch1,self.ch2)
                self.ui.x_disp.setText(str(x))
                self.ui.y_disp.setText(str(y))
                self.ui.f_disp.setText(str(self.instr.query_frequency())+' Hz')
                self.ui.a_disp.setText(str(self.instr.query_amplitude())+' V')
                self.ui.ph_disp.setText(str(self.instr.query_phase())+' deg')
            self.monitor_timer.start(self.ui.refresh_rate.value()*1000)
        
    
    def update_timer_timeout(self,secs):
        #The value must be converted to milliseconds            
        self.monitor_timer.setInterval(secs*1000)
    
    def change_f(self,value=0):
        with self.reserved_access_to_instr:
            self.instr.set_frequency(value)                
                
    def change_A(self,value=0):
        with self.reserved_access_to_instr:                
            self.instr.set_amplitude(value)                

    def change_ph(self,value=0):
        with self.reserved_access_to_instr:
            self.instr.set_amplitude(value)              

    def change_x(self,value):
        with self.reserved_access_to_instr:
            self.instr.set_ch1_display(value)
            self.ch1=value#self.instr.query_ch1_display()
    
    def change_y(self,value):
        with self.reserved_access_to_instr:
            self.instr.set_ch2_display(value)
            self.ch2=value#self.instr.query_ch2_display()

    def change_s(self,value=0):
        with self.reserved_access_to_instr:
            self.instr.set_sensitivity(value)

    def change_TC(self,value=0):
        with self.reserved_access_to_instr:
            self.instr.set_time_cste(value)

    def change_filter(self,value=0):
        with self.reserved_access_to_instr:
            self.instr.set_filter_slop(value)
    
    def change_ref(self,value='Internal'):
        with self.reserved_access_to_instr:
            self.instr.set_ref_mode(value)
Ejemplo n.º 20
0
class Quiz(QFrame):
     
    def __init__(self, parent=None):
        super(Quiz, self).__init__(parent)
        
        """Session Info"""
        self.status = QFrame()
        ##session message
        self.status.message = QLabel(u'')
        self.status.layout = QHBoxLayout()
        self.status.layout.addWidget(self.status.message)
        self.status.setLayout(self.status.layout)
        ##mouse event filter
        #self.status.filter = StatusFilter()
        self.status.setAttribute(Qt.WA_Hover, True)
        #self.status.installEventFilter(self.status.filter)

        """Items Info"""
        self.info = QFrame()
        self.info.reading = QLabel(u'')
        self.info.item = QLabel(u'')
        self.info.components = QLabel(u'')
        
        self.info.translation = QLabel(u'')
        self.info.translation.setAlignment(Qt.AlignCenter)
        self.info.translation.setWordWrap(True)

#        separator_one = QFrame()
#        separator_one.setFrameShape(QFrame.HLine)
#        separator_one.setFrameShadow(QFrame.Sunken)
#        
#        separator_two = QFrame()
#        separator_two.setFrameShape(QFrame.HLine)
#        separator_two.setFrameShadow(QFrame.Sunken)
        
        self.info.layout = QVBoxLayout()
        self.info.layout.addWidget(self.info.translation)
#        self.info.layout.addWidget(self.info.reading)
#        self.info.layout.addWidget(separator_one)
#        self.info.layout.addWidget(self.info.item)
#        self.info.layout.addWidget(separator_two)
#        self.info.layout.addWidget(self.info.components)
        self.info.setLayout(self.info.layout)
        
        """Verbose Info"""
        self.allInfo = QFrame()
        self.allInfo.layout = QGridLayout()
        self.allInfo.setLayout(self.allInfo.layout)
        #the rest is (should be) generated on the fly
        
        """Global Flags"""
        #self.correct = False
        
        """Quiz Dialog"""
        self.filter = Filter()
        ####    visual components    ###
        self.countdown = QProgressBar()
        self.sentence = QLabel(u'')
        
#        self.var_1st = QPushButton(u'')
#        self.var_2nd = QPushButton(u'')
#        self.var_3rd = QPushButton(u'')
#        self.var_4th = QPushButton(u'')

        self.isKnown = QPushButton(u'Good')
        self.isNotKnown = QPushButton(u'Again')

        self.answered = QPushButton(u'')
        self.answered.hide()
        
        ###    layouts    ####
        self.layout_vertical = QVBoxLayout()        #main
        self.layout_horizontal = QHBoxLayout()      #buttons
        
#        self.layout_horizontal.addWidget(self.var_1st)
#        self.layout_horizontal.addWidget(self.var_2nd)
#        self.layout_horizontal.addWidget(self.var_3rd)
#        self.layout_horizontal.addWidget(self.var_4th)

        self.layout_horizontal.addWidget(self.isKnown)
        self.layout_horizontal.addWidget(self.isNotKnown)
        
        self.layout_vertical.addWidget(self.countdown)
        self.layout_vertical.addWidget(self.sentence)
        self.layout_vertical.addLayout(self.layout_horizontal)
        
        self.layout_horizontal.addWidget(self.answered)

        self.setLayout(self.layout_vertical)

        ###    utility components    ###
        self.trayIcon = QSystemTrayIcon(self)
        self.trayMenu = QMenu()
        
        self.gifLoading = QMovie('../res/cube.gif')
        self.gifLoading.frameChanged.connect(self.updateTrayIcon)
        
        self.nextQuizTimer = QTimer()
        self.nextQuizTimer.setSingleShot(True)
        self.nextQuizTimer.timeout.connect(self.showQuiz)
        
        self.countdownTimer = QTimer()
        self.countdownTimer.setSingleShot(True)
        self.countdownTimer.timeout.connect(self.timeIsOut)
        
        ### initializing ###
        self.initializeResources()
        
        
        """Start!"""
        if self.options.isQuizStartingAtLaunch():
            self.waitUntilNextTimeslot()
            self.trayIcon.setToolTip('Quiz has started automatically!')
            self.pauseAction.setText('&Pause')
            self.trayIcon.showMessage('Loading complete! (took ~'+ str(self.loadingTime.seconds) + ' seconds) Quiz underway.', 'Lo! Quiz already in progress!', QSystemTrayIcon.MessageIcon.Warning, 10000)
        else:
            self.trayIcon.setToolTip('Quiz is not initiated!')
            self.trayIcon.showMessage('Loading complete! (took ~' + str(self.loadingTime.seconds) + ' seconds) Standing by.', 'Quiz has not started yet! If you wish, you could start it manually or enable autostart by default.', 
                                      QSystemTrayIcon.MessageIcon.Information, 10000 )
            
        """Test calls here:"""
        ###    ...    ###
        #self.connect(self.hooker, SIGNAL('noQdict'), self.noQdict)

    def noQdict(self):
        self.showSessionMessage('Nope, cannot show quick dictionary during actual quiz.')

####################################
#    Initialization procedures     #
####################################

    def initializeResources(self):
        
        """Pre-initialization"""
        self.animationTimer = ()
        self.progressTimer = ()
        self.grid_layout =()
                       
        """Initialize Options"""
        self.options = Options()
        
        """Initialize Statistics"""
        self.stats = Stats()
        
        """Config Here"""
        self.initializeComposition()
        self.initializeComponents()
        self.setMenus()
        self.trayIcon.show()
        #self.startTrayLoading()
        
        """"Initialize Dictionaries    (will take a some time!)"""
        time_start = datetime.now()
        self.dict = EdictParser()
        self.dict.loadDict()
        
        self.morphy = get_morph(PATH_TO_RES + DICT_EN)
        
        self.trayIcon.showMessage('Loading...', 'Initializing dictionaries', QSystemTrayIcon.MessageIcon.Information, 20000 )     #TODO: change into loading dialog... or not
        
        """Initializing srs system"""
        self.trayIcon.showMessage('Loading...', 'Initializing databases', QSystemTrayIcon.MessageIcon.Information, 20000 )
        self.srs = srsScheduler()
        self.srs.initializeAll()
        self.srs.initializeCurrentSession(self.options.getSessionSize())
        
        """Global hotkeys hook"""
        #TODO: add multiple hotkeys and fix stop()
        #self.hooker = GlobalHotkeyManager(toggleQDictFlag, 'Q')
#        self.hooker = GlobalHotkeyManager(toggleWidgetFlag(self.qdict), 'Q')
#        self.hooker.setDaemon(True) #temporarily, should work using stop()
#        self.hooker.start()
        
        time_end = datetime.now()
        self.loadingTime =  time_end - time_start

####################################
#    Composition and appearance    #
####################################

    def initializeComposition(self):
        
        """Main Dialog"""
        self.setWindowFlags(Qt.FramelessWindowHint | Qt.WindowStaysOnTopHint)
        self.setFocusPolicy(Qt.StrongFocus)
        self.setFrameStyle(QFrame.StyledPanel | QFrame.Raised)
        #Font will appear in buttons
        self.setFont(QFont(self.options.getQuizFont(), self.options.getQuizFontSize()))

        desktop = QApplication.desktop().screenGeometry()
        self.setGeometry(QRect(desktop.width() - H_INDENT, desktop.height() - V_INDENT, D_WIDTH, D_HEIGHT))
        
        self.setStyleSheet("QWidget { background-color: rgb(255, 255, 255); }")
        
        """Info dialog"""
        self.info.setWindowFlags(Qt.FramelessWindowHint | Qt.WindowStaysOnTopHint)
        self.info.setFrameStyle(QFrame.StyledPanel | QFrame.Raised)
        self.info.setGeometry(QRect(desktop.width() - H_INDENT - I_WIDTH - I_INDENT, desktop.height() - V_INDENT, I_WIDTH, I_HEIGHT))
        self.info.setFixedSize(I_WIDTH, I_HEIGHT)
        
        self.info.setStyleSheet("QWidget { background-color: rgb(255, 255, 255); }")
        
        """Verbose info dialog"""
        self.allInfo.setWindowFlags(Qt.FramelessWindowHint | Qt.WindowStaysOnTopHint)
        self.allInfo.setFrameStyle(QFrame.StyledPanel | QFrame.Raised)
        self.allInfo.setGeometry(QRect(desktop.width() - H_INDENT - I_WIDTH - I_INDENT, desktop.height() - V_INDENT, I_WIDTH, I_HEIGHT))
        
        self.allInfo.setStyleSheet("QWidget { background-color: rgb(255, 255, 255); }")
        
        """Session message"""
        self.status.setWindowFlags(Qt.FramelessWindowHint | Qt.WindowStaysOnTopHint)
        self.status.setFrameStyle(QFrame.StyledPanel | QFrame.Raised)
        self.status.setGeometry(QRect(desktop.width() - H_INDENT, desktop.height() - V_INDENT - S_HEIGHT - S_INDENT - S_CORRECTION, S_WIDTH, S_HEIGHT))
        
        self.status.setStyleSheet("QWidget { background-color: rgb(255, 255, 255); }")
        
        self.setMask(roundCorners(self.rect(),5))
        self.status.setMask(roundCorners(self.status.rect(),5))
        #self.info.setMask(roundCorners(self.info.rect(),5))
        #self.allInfo.setMask(roundCorners(self.allInfo.rect(),5))

    def initializeComponents(self):
        self.countdown.setMaximumHeight(6)
        self.countdown.setRange(0, self.options.getCountdownInterval() * 100)
        self.countdown.setTextVisible(False)
        self.countdown.setStyleSheet("QProgressbar { background-color: rgb(255, 255, 255); }")
        
        #self.setFont(QFont(Fonts.SyoutyouProEl, 40))#self.options.getQuizFontSize()))
        
        self.sentence.setAlignment(Qt.AlignmentFlag.AlignCenter)
        #self.sentence.setFont(QFont(Fonts.HiragiNoMarugotoProW4, self.options.getSentenceFontSize()))
        self.sentence.setFont(QFont(self.options.getSentenceFont(), self.options.getSentenceFontSize()))
        
        self.sentence.setWordWrap(True)
        self.trayIcon.setIcon(QIcon(PATH_TO_RES + TRAY + 'active.png'))
        
        self.status.message.setFont(QFont('Cambria', self.options.getMessageFontSize()))
        self.status.layout.setAlignment(Qt.AlignCenter)
        self.status.message.setWordWrap(False)
        self.status.layout.setMargin(0)
        
        self.info.item.setFont(QFont(Fonts.HiragiNoMyoutyouProW3, 36))
        self.info.reading.setFont(QFont(Fonts.HiragiNoMyoutyouProW3, 16))
        self.info.components.setFont((QFont(Fonts.HiragiNoMyoutyouProW3, 14)))
        #self.info.item.setWordWrap(True)
        self.info.components.setWordWrap(True)
        #self.info.layout.setAlignment(Qt.AlignCenter)
        self.info.layout.setMargin(0)
        #self.info.layout.setSizeConstraint(self.info.layout.SetFixedSize)       #NB: would work nice, if the anchor point was in right corner
        
        self.info.reading.setAlignment(Qt.AlignCenter)
        self.info.item.setAlignment(Qt.AlignCenter)
        self.info.components.setAlignment(Qt.AlignCenter)
        
        #self.info.setLayoutDirection(Qt.RightToLeft)
        
        self.info.reading.setStyleSheet("QLabel { color: rgb(155, 155, 155); }")
        self.info.components.setStyleSheet("QLabel { color: rgb(100, 100, 100); }")
        
        self.info.gem = self.info.saveGeometry()


####################################
#        Updating content          #
####################################        

    def updateContent(self):
        
        """Resetting multi-label sentence"""
        if self.grid_layout != ():
            for i in range(0, self.grid_layout.count()):
                    self.grid_layout.itemAt(i).widget().hide()
            self.layout_vertical.removeItem(self.grid_layout)
            self.grid_layout.setParent(None)
            self.update()
        if self.sentence.isHidden():    
            self.sentence.show()
        self.showButtonsQuiz()
        
        """Getting actual content"""
        self.srs.getNextItem()
              
        start = datetime.now()  #testing
        #example = self.srs.getCurrentExample().replace(self.srs.getWordFromExample(), u"<font color='blue'>" + self.srs.getWordFromExample() + u"</font>")
        example = self.srs.getCurrentExample().replace(self.srs.getCurrentItem(), u"<font color='blue'>" + self.srs.getCurrentItem() + u"</font>")
        print datetime.now() - start    #testing
        self.sentence.setText(example)
        
#        start = datetime.now()  #testing
#        readings = self.srs.getQuizVariants()
#        print datetime.now() - start    #testing
        
        '''
        changeFont = False
        for item in readings:
            if len(item) > 5 : changeFont = True
            
        if changeFont: self.setStyleSheet('QWidget { font-size: 11pt; }')
        else:   self.setStyleSheet('QWidget { font-size: %spt; }' % self.options.getQuizFontSize())
        '''
        '''
        if len(readings) == 4:                  #NB: HERE LIES THE GREAT ERROR
            self.var_1st.setText(readings[0])
            self.var_2nd.setText(readings[1])
            self.var_3rd.setText(readings[2])
            self.var_4th.setText(readings[3])
        '''
        
#        try:
#            for i in range(0, self.layout_horizontal.count()):
#                    if i > 3: break
#                    self.layout_horizontal.itemAt(i).widget().setText(u'')
#                    #self.layout_horizontal.itemAt(i).setStyleSheet('QPushButton { font-size: 11pt; }')
#                    self.layout_horizontal.itemAt(i).widget().setText(readings[i])
#        except:
#            print 'Not enough quiz variants'
            #TODO: log this
        
    def getReadyPostLayout(self):
        self.sentence.hide()
        self.update()
        
        self.grid_layout = QGridLayout()
        self.grid_layout.setSpacing(0)
        self.labels = []
        
        columns_mod = 0
        
        if len(self.srs.currentExample['eng']) > SENTENCE_MAX: font =  QFont(self.options.getSentenceFont(), MIN_FONT_SIZE); columns_mod = 6
        else: font = QFont(self.options.getSentenceFont(), self.options.getSentenceFontSize())
        
        #row, column, rows span, columns span, max columns
        i = 0; j = 0; r = 1; c = 1; n = COLUMNS_MAX + columns_mod
        for word in self.srs.parseCurrentExample():
            label = QLabel(word)
#            label.setFont(QFont(self.options.getSentenceFont(), self.options.getSentenceFontSize()))
            label.setFont(font)
            
            label.setAttribute(Qt.WA_Hover, True)
            label.installEventFilter(self.filter)
            self.labels.append(label)
            
            if len(label.text()) > 1: c = len(label.text())
            else: c = 1
            #Don't ask, really
            if j + c > n: i = i + 1; j = 0
            
            self.grid_layout.addWidget(self.labels.pop(), i, j, r, c)
            
            if j <= n: j = j + c
            else: j = 0; i = i + 1
        
        self.grid_layout.setAlignment(Qt.AlignCenter)
        self.layout_vertical.insertLayout(1, self.grid_layout)

        self.update()
        
    def hideButtonsQuiz(self):
        self.isKnown.hide()
        self.isNotKnown.hide()
        
        self.answered.clicked.connect(self.hideQuizAndWaitForNext)
        self.answered.show()
        
    def showButtonsQuiz(self):
        self.isKnown.show()
        self.isNotKnown.show()
        
        self.answered.hide()
        self.answered.disconnect()
        
####################################
#        Timers and animations     #
####################################

    def waitUntilNextTimeslot(self):
        #if self.nextQuizTimer.isActive():   self.nextQuizTimer.stop()
        self.nextQuizTimer.start(self.options.getRepetitionInterval() * 60 * 1000)  #options are in minutes    NB: how do neatly I convert minutes to ms?
        
    def beginCountdown(self):
        self.trayIcon.setToolTip('Quiz in progress!')
        self.pauseAction.setText('&Pause')
        self.pauseAction.setShortcut('P')
        
        self.countdownTimer.start(self.options.getCountdownInterval() * 1000)

        self.progressTimer = RepeatTimer(0.01, self.updateCountdownBar, self.options.getCountdownInterval() * 100)
        self.progressTimer.start()
        
    def updateCountdownBar(self):
        self.countdown.setValue(self.countdown.value() - 1)
        #print self.countdown.value()
        self.countdown.update()         #NB: without .update() recursive repaint crushes qt

    def fade(self):
        if self.windowOpacity() == 1:
            self.animationTimer = RepeatTimer(0.025, self.fadeOut, 40)
            self.animationTimer.start()
        else:
            self.animationTimer = RepeatTimer(0.025, self.fadeIn, 40)
            self.animationTimer.start()
    
    def fadeIn(self):
        self.setWindowOpacity(self.windowOpacity() + 0.1)
        
    def fadeOut(self):
        self.setWindowOpacity(self.windowOpacity() - 0.1)
        
    def stopCountdown(self):
        self.progressTimer.cancel()
        self.countdownTimer.stop()
        self.countdown.setValue(0)
        
####################################
#        Actions and events        #
####################################    
    
    def setMenus(self):
        self.trayMenu.addAction(QAction('&Quiz me now!', self, shortcut="Q", triggered=self.showQuiz))
        self.pauseAction = QAction('&Start quiz!', self, shortcut="S", triggered=self.pauseQuiz)
        self.trayMenu.addAction(self.pauseAction)
        self.trayMenu.addSeparator()
        self.trayMenu.addAction(QAction('Quick &dictionary', self, shortcut="D", triggered=self.showQuickDict))
        self.trayMenu.addAction(QAction('&Global &statistics', self, shortcut="G", triggered=self.showGlobalStatistics))
        self.trayMenu.addAction(QAction('&Options', self, shortcut="O", triggered=self.showOptions))
        self.trayMenu.addAction(QAction('&About', self, shortcut="A", triggered=self.showAbout))
        self.trayMenu.addSeparator()
        self.trayMenu.addAction(QAction('&Exit', self, shortcut="E", triggered=self.saveAndExit))

        self.trayIcon.setContextMenu(self.trayMenu)
        self.trayIcon.activated.connect(self.onTrayIconActivated)

    #TODO: show session statistics
    def onTrayIconActivated(self, reason):
        '''
        if reason == QSystemTrayIcon.DoubleClick:
            print 'tray icon double clicked'
        '''
        if reason == QSystemTrayIcon.Trigger:
            if self.isHidden():
                self.trayIcon.showMessage('Current session statistics:', 'Running time:\t\t' + self.stats.getRunningTime() + 
                                          '\nItems seen:\t\t' + str(self.stats.totalItemSeen) + 
                                          '\nCorrect answers:\t\t' + str(self.stats.answeredCorrect) +
                                          '\nWrong answers:\t\t' + self.stats.getIncorrectAnswersCount() +
                                          '\nCorrect ratio:\t\t' + self.stats.getCorrectRatioPercent() +
                                          #'\nQuiz total time:\t\t' + self.stats.getQuizActive() +
                                          '\nQuiz paused time:\t\t' + self.stats.getPausedTime() +
                                          '\nTotal pondering time:\t' + self.stats.getMusingsTime() +
                                          '\nTotal post-quiz time:\t' + self.stats.getQuizTime() +
                                          '\nAverage pondering:\t' + self.stats.getAverageMusingTime() +
                                          '\nAverage post-quiz:\t' + self.stats.getAveragePostQuizTime(), 
                                          QSystemTrayIcon.MessageIcon.Information, 20000)
    
    def setButtonsActions(self):
        self.isKnown.clicked.connect(self.correctAnswer)
        self.isNotKnown.clicked.connect(self.wrongAnswer)
        
    def resetButtonsActions(self):
        self.isKnown.disconnect()
        self.isNotKnown.disconnect()
        
    def postAnswerActions(self):
        self.stats.musingsStopped()
        self.stats.postQuizStarted()
        
        self.stopCountdown()
        self.hideButtonsQuiz()
        
        self.getReadyPostLayout()
        
    def checkTranslationSize(self, translation):
        if len(translation) > TRANSLATION_CHARS_LIMIT:
            self.answered.setStyleSheet('QPushButton { font-size: 9pt; }')
            
            space_indices = [i for i, value in enumerate(translation) if value == ' ']
            find_nearest_index = lambda value,list : min(list, key = lambda x:abs(x - value))
            nearest_index = find_nearest_index(TRANSLATION_CHARS_LIMIT, space_indices)
            translation = translation[:nearest_index] + '\n' + translation[nearest_index + 1:]
        else:
            self.answered.setStyleSheet('QPushButton { font-size: 11pt; }')
        
        self.answered.setText(translation)
        
    def correctAnswer(self):
        '''
        self.stats.musingsStopped()
        self.stats.postQuizStarted()
        
        self.stopCountdown()
        self.hideButtonsQuiz()
        
        self.getReadyPostLayout()
        '''
        self.postAnswerActions()
        
        self.srs.answeredCorrect()
        self.stats.quizAnsweredCorrect()
        #self.answered.setText(u"<font='Cambria'>" + self.srs.getCurrentSentenceTranslation() + "</font>")
#        self.answered.setText(self.srs.getCurrentSentenceTranslation())

        self.checkTranslationSize(self.srs.getCurrentSentenceTranslation())

        #self.answered.setFont(QFont('Calibri', 11))
        self.showSessionMessage(u'<font color=green>Correct: OK</font>\t|\tNext quiz: ' + self.srs.getNextQuizTime() 
                                + '\t|\t<font color=' + self.srs.getLeitnerGradeAndColor()['color'] +  '>Grade: ' + self.srs.getLeitnerGradeAndColor()['grade'] 
                                + ' (' + self.srs.getLeitnerGradeAndColor()['name'] + ')<font>')
        
        #self.answered.setShortcut('5')
        #self.setFocus()
        
    def wrongAnswer(self):
        '''
        self.stats.musingsStopped()
        self.stats.postQuizStarted()
        
        self.stopCountdown()
        self.hideButtonsQuiz()
        
        self.getReadyPostLayout()
        '''
        self.postAnswerActions()
        
        self.srs.answeredWrong()
        self.stats.quizAnsweredWrong()
        
#        self.answered.setText(self.srs.getCurrentSentenceTranslation())

        self.checkTranslationSize(self.srs.getCurrentSentenceTranslation())

        #self.answered.setFont(QFont('Calibri', 11))
        #self.showSessionMessage(u"Wrong! Should be: <font style='font-family:" + Fonts.MSMyoutyou + "'>" 
                                #+ self.srs.getCorrectAnswer() + "</font> - Next quiz: " + self.srs.getNextQuizTime())
        self.showSessionMessage(u'<font color=tomato>Bad</font>\t|\tNext quiz: ' + self.srs.getNextQuizTime()
                                + '\t|\t<font color=' + self.srs.getLeitnerGradeAndColor()['color'] +  '>Grade: ' + self.srs.getLeitnerGradeAndColor()['grade'] 
                                + ' (' + self.srs.getLeitnerGradeAndColor()['name'] + ')<font>')
            
    def timeIsOut(self):
        self.stats.musingsStopped()
        self.stats.postQuizStarted()
        
        QTimer.singleShot(50, self.hideButtonsQuiz)     #NB: slight artificial lag to prevent recursive repaint crush, when mouse is suddenly over appearing button
        self.getReadyPostLayout()
        
        self.srs.answeredWrong()
        self.stats.quizAnsweredWrong()

        #self.showSessionMessage(u'Time is out! Correct answer is:' + self.srs.getCorrectAnswer())
#        self.answered.setFont(QFont('Calibri', 11))
#        self.answered.setText(self.srs.getCurrentSentenceTranslation())

        self.checkTranslationSize(self.srs.getCurrentSentenceTranslation())

        self.showSessionMessage(u'<font color=tomato>Timeout!</font>\t|\tNext quiz: ' + self.srs.getNextQuizTime()
                                + '\t|\t<font color=' + self.srs.getLeitnerGradeAndColor()['color'] +  '>Grade: ' + self.srs.getLeitnerGradeAndColor()['grade'] 
                                + ' (' + self.srs.getLeitnerGradeAndColor()['name'] + ')<font>')        
    
    def hideQuizAndWaitForNext(self):
        self.stats.postQuizEnded()
        
        self.status.hide()
        self.info.hide()
        self.allInfo.hide()
        self.resetButtonsActions()
        
        self.setWindowOpacity(1)
        self.fade()
        QTimer.singleShot(1000, self.hide)
        self.waitUntilNextTimeslot()
        #self.updater.mayUpdate = True
 
    def pauseQuiz(self):
        if self.isHidden():
            if self.pauseAction.text() == '&Pause':
                self.nextQuizTimer.stop()
                self.pauseAction.setText('&Unpause')
                self.pauseAction.setShortcut('U')
                self.trayIcon.setToolTip('Quiz paused!')
                
                self.trayIcon.setIcon(QIcon(PATH_TO_RES + TRAY + 'inactive.png'))
                self.stats.pauseStarted()
                
                #self.updater.mayUpdate = True
                
            elif self.pauseAction.text() == '&Start quiz!':
                self.waitUntilNextTimeslot()
                self.pauseAction.setText('&Pause')
                self.pauseAction.setShortcut('P')
                self.trayIcon.setToolTip('Quiz in progress!')
                
                self.trayIcon.setIcon(QIcon(PATH_TO_RES + TRAY + 'active.png'))
            else:
                self.waitUntilNextTimeslot()
                self.pauseAction.setText('&Pause')
                self.pauseAction.setShortcut('P')
                self.trayIcon.setToolTip('Quiz in progress!')
                
                self.trayIcon.setIcon(QIcon(PATH_TO_RES + TRAY + 'active.png'))
                self.stats.pauseEnded()
                
                #self.updater.mayUpdate = False
        else:
            self.showSessionMessage(u'Sorry, cannot pause while quiz in progress!')
 
    def showQuiz(self):
        if self.isHidden():
            self.updateContent()
            self.setButtonsActions()
            
            self.show()
            self.setWindowOpacity(0)
            self.fade()

            self.countdown.setValue(self.options.getCountdownInterval() * 100)
            self.beginCountdown()
            self.stats.musingsStarted()
            
            if self.nextQuizTimer.isActive():   self.nextQuizTimer.stop()
            #self.updater.mayUpdate = False
        else:
            self.showSessionMessage(u'Quiz is already underway!')
         
    def showOptions(self):
        self.optionsDialog.show()
        
    def showAbout(self):
        self.about.show()
        
    def showQuickDict(self):
        self.qdict.showQDict = True
        
    def showGlobalStatistics(self):
        print '...'
        
    def startTrayLoading(self):
        self.gifLoading.start()
        #self.iconTimer = QTimer()
        #self.iconTimer.timeout.connect(self.updateTrayIcon)
        #self.iconTimer.start(100)
        
    def stopTrayLoading(self):
        self.gifLoading.stop()
        
    def updateTrayIcon(self):
        self.trayIcon.setIcon(self.gifLoading.currentPixmap())
        
    def showSessionMessage(self, message):
        """Shows info message"""
        self.status.message.setText(message)
        self.status.show()
        #self.setFocus() #NB: does not work
 
    def saveAndExit(self):
        self.hide()
        self.status.hide()
        self.allInfo.hide()
        self.trayIcon.showMessage('Shutting down...', 'Saving session', QSystemTrayIcon.MessageIcon.Information, 20000 )
        #self.startTrayLoading()
        
        if self.countdownTimer.isActive():
                self.countdownTimer.stop()
        if self.nextQuizTimer.isActive():
                self.nextQuizTimer.stop()
        if self.progressTimer != () and self.progressTimer.isAlive():
                self.progressTimer.cancel()      
            
        self.srs.endCurrentSession()
        self.trayIcon.hide()

#        self.hooker.stop()

        #self.updater.stop()
        self.optionsDialog.close()
        self.about.close()
        #self.qdict.close()
        self.close()        
        
    def addReferences(self, about, options, qdict, updater):
        self.about = about
        self.optionsDialog = options
        self.qdict = qdict
        self.updater = updater
        
    def initGlobalHotkeys(self):
        def toggleWidgetFlag(): self.qdict.showQDict = True
        self.hooker = GlobalHotkeyManager(toggleWidgetFlag , 'Q')
        self.hooker.setDaemon(True)
        self.hooker.start()
Ejemplo n.º 21
0
class Window(QMainWindow, _Goto.Mixin, _Window.Mixin):
    def __init__(self, debug, parent=None):
        super().__init__(parent)
        Lib.Qt.DEBUGGING = self.debug = debug
        self.resizeTimer = QTimer(self)
        self.resizeTimer.setSingleShot(True)
        self.resizeTimer.timeout.connect(self.onResize)
        self.tooltips = []
        self.state = State.State(self)
        self.statusbar = self.statusBar()
        self.statusbar.setSizeGripEnabled(False)
        self.workTimer = QTimer(self)
        self.createWidgets()
        self.layoutWidgets()
        self.createActions()
        self.createConnections()
        self.state.createConnections()
        self.loadSettings()
        self.createContextMenus()
        self.workTimer.start(60 * 1000)  # 1 minute
        self.state.setMode(ModeKind.NO_INDEX)
        self.initialize()
        self.show()

    def initialize(self):
        self.setWindowTitle("{}".format(QApplication.applicationName()))
        self.state.updateDisplayFonts()
        self.filename = None
        if len(sys.argv) > 1:
            filename = sys.argv[1]
            if (filename.lower().endswith(EXTENSION)
                    and os.path.exists(filename)):
                self.filename = filename
        if self.filename is None:
            settings = QSettings()
            filename = settings.value(Gopt.Key.MainForm_Filename,
                                      Gopt.Default.MainForm_Filename)
            if (filename and filename.lower().endswith(EXTENSION)
                    and os.path.exists(filename)):
                self.filename = filename
        if self.filename is None:
            say("Click File→New or File→Open to create or open an index")
            self.updateWorkTime()
            self.state.updateUi()
        else:
            say("Opening {}".format(os.path.normpath(self.filename)))
            QTimer.singleShot(5, self.openXix)
        self.updateRecentFilesMenu()
        self.updateToolTips()
        Lib.maybe_register_filetype(self.debug)

    def initializePanelSplitter(self):
        size = sum(self.panelSplitter.sizes())
        self.panelSplitter.setSizes([size * 0.1, size * 0.9])

    def loadSettings(self):
        settings = QSettings()
        self.restoreGeometry(settings.value(Gopt.Key.MainForm_Geometry))
        self.restoreState(settings.value(Gopt.Key.MainForm_State))
        self.setIndexViewPosition()
        self.splitter.restoreState(settings.value(Gopt.Key.MainForm_Splitter))
        panelState = settings.value(Gopt.Key.MainForm_PanelSplitter)
        if panelState is None:
            QTimer.singleShot(50, self.initializePanelSplitter)
        else:
            self.panelSplitter.restoreState(panelState)
        self.entrySuggestionSplitter.restoreState(
            settings.value(Gopt.Key.MainForm_EntrySuggestionsSplitter))
        self.spellAndGroupsSplitter.restoreState(
            settings.value(Gopt.Key.MainForm_SpellAndGroupsSplitter))
        viewState = settings.value(Gopt.Key.MainForm_ViewSplitter)
        if viewState is None:
            sizes = self.viewSplitter.sizes()
            self.viewSplitter.setSizes([sum(sizes), 0])
        else:
            self.viewSplitter.restoreState(viewState)
        self.recent_files = settings.value(Gopt.Key.MainForm_RecentFiles) or []
        if isinstance(self.recent_files, str):
            self.recent_files = [self.recent_files]

    def setIndexViewPosition(self):
        settings = QSettings()
        index = int(
            settings.value(Gopt.Key.MainForm_IndexViewPosition,
                           Gopt.Default.MainForm_IndexViewPosition))
        self.splitter.insertWidget(index, self.viewSplitter)

    def updateUi(self):
        if self.resizing:
            return
        enable = bool(self.state.model)
        self.fileActions.saveAction.setEnabled(
            enable and self.state.mode is not ModeKind.VIEW)
        for action in (self.fileActions.backupAction,
                       self.fileActions.saveAsAction,
                       self.fileActions.printAction,
                       self.fileActions.outputRtfAction,
                       self.fileActions.outputDocxAction,
                       self.fileActions.outputAsAction):
            action.setEnabled(enable)
        enable = self.state.mode not in {ModeKind.NO_INDEX, ModeKind.CHANGE}
        for widget in (self.state.viewAllPanel, self.state.viewFilteredPanel,
                       self.editMenu, self.editToolBar, self.formatMenu,
                       self.formatToolBar, self.insertMenu, self.gotoMenu,
                       self.gotoToolBar1, self.gotoToolBar2, self.gotoToolBar3,
                       self.gotoToolBar4, self.entryMenu, self.entryToolBar1,
                       self.entryToolBar2, self.modifyMenu, self.modifyToolBar,
                       self.indexMenu, self.indexToolBar, self.spellingMenu,
                       self.spellingToolBar, self.state.viewFilteredPanel):
            widget.setEnabled(enable)
        enable = bool(self.state.entryPanel.xrefList.count())
        self.entryActions.changeXRefAction.setEnabled(enable)
        self.entryActions.deleteXRefAction.setEnabled(enable)
        self.entryActions.updateUi()
        self.modifyActions.updateUi()
        total, filtered = self.updateIndicatorCounts()
        self.updateGotoActions(total, filtered)

    def updateIndicatorCounts(self):
        top, total, filtered = self.state.indicatorCounts()
        self.countLabel.setText(
            COUNT_LABEL_TEMPLATE.format(total, top,
                                        max(0, total - self.state.startCount),
                                        filtered))
        return total, filtered

    def updateWorkTime(self):
        if not self.state.model:
            message = "0'"
        else:
            secs = self.state.workTime + int(time.monotonic() -
                                             self.state.startTime)
            hours, secs = divmod(secs, 3600)
            if hours:
                message = "{:,}h{}'".format(hours, secs // 60)
            else:
                message = "{}'".format(secs // 60)
        self.worktimeLabel.setText(LABEL_TEMPLATE.format(message))

    def openXix(self, filename=None):
        if self.isReadOnly(
                filename if filename is not None else self.filename):
            return
        self.closeXix()
        if filename is not None:
            self.filename = filename
        try:
            self.recent_files.remove(self.filename)
            self.updateRecentFilesMenu()
        except ValueError:
            pass  # No problem if it isn't there to be removed
        self.state.indexPath = os.path.dirname(self.filename)
        QApplication.setOverrideCursor(Qt.WaitCursor)
        try:
            say("Opening {}…".format(os.path.normpath(self.filename)))
            QApplication.processEvents()
            self.state.entryPanel.clearForm()
            self._openModel("Opened")
            self.state.entryPanel.termEdit.setFocus()
            self.state.updateUi()
            self.updateWorkTime()
            self.updateLanguageIndicator()
            self.state.setMode(ModeKind.VIEW)
            self.refreshBookmarks()
        finally:
            QApplication.restoreOverrideCursor()

    def newXix(self, filename):
        if self.isReadOnly(filename):
            return
        self.closeXix()
        self.filename = filename
        QApplication.setOverrideCursor(Qt.WaitCursor)
        try:
            say("Creating {}…".format(os.path.normpath(self.filename)),
                SAY_TIMEOUT)
            QApplication.processEvents()
            self._openModel("Created")
            self.state.updateUi()
            self.updateWorkTime()
            self.state.entryPanel.clearForm()
            self.updateLanguageIndicator()
            self.state.setMode(ModeKind.VIEW)
        finally:
            QApplication.restoreOverrideCursor()

    def isReadOnly(self, filename):
        if (os.access(filename, os.R_OK) and not os.access(filename, os.W_OK)):
            QMessageBox.information(
                self, "Can't Open Read-Only Index — {}".format(
                    QApplication.applicationName()),
                """<p>Cannot open index<br>“{}”<br>
since the file and/or its folder is read-only.</p>""".format(
                    os.path.normpath(filename)))
            return True
        return False

    def _openModel(self, word):
        self.state.viewAllPanel.clear()
        self.state.viewFilteredPanel.clear()
        language, sortAsRules, pageRangeRules = self._getLanguageAndRules()
        with Lib.Timer("Opened in", 0.2):
            self.state.model.open(self.filename, language, sortAsRules,
                                  pageRangeRules)
            say("{} “{}”".format(word, os.path.normpath(self.filename)),
                self.state.showMessageTime)
        rules = SortAs.RulesForName[self.state.model.sortAsRules()]
        self.sortAsRuleLabel.setText(LABEL_TEMPLATE.format(rules.abbrev))
        self.sortAsRuleLabel.setToolTip(Lib.rulesTip(rules.tip))
        rules = Pages.RulesForName[self.state.model.pageRangeRules()]
        self.pageRangeRulesLabel.setText(LABEL_TEMPLATE.format(rules.abbrev))
        self.pageRangeRulesLabel.setToolTip(Lib.rulesTip(rules.tip, False))
        self.updateTitle()
        self.state.viewAllPanel.view.goHome()

    def importIndex(self, filename, inFilename):
        if self.isReadOnly(filename):
            return
        self.closeXix()
        Lib.remove_file(filename)  # Don't want to merge!
        self.filename = filename
        QApplication.setOverrideCursor(Qt.WaitCursor)
        try:
            say("Importing {}…".format(os.path.normpath(self.filename)),
                SAY_TIMEOUT)
            QApplication.processEvents()
            self._importIndex(filename, inFilename)
            self.state.entryPanel.termEdit.setFocus()
            self.state.updateUi()
            self.updateWorkTime()
            self.updateLanguageIndicator()
            self.state.setMode(ModeKind.VIEW)
        finally:
            QApplication.restoreOverrideCursor()

    # Why is filename ignored in favour of self.filename?
    def _importIndex(self, filename, inFilename):
        language, sortAsRules, pageRangeRules = self._getLanguageAndRules()
        with Lib.Timer("Imported in", 0.2):
            if self.state.model.importIndex(inFilename, self.filename,
                                            language, sortAsRules,
                                            pageRangeRules):
                say(
                    "Imported “{}” from “{}”".format(self.filename,
                                                     inFilename),
                    self.state.showMessageTime)
            else:
                say(
                    "Failed to import “{}” from “{}”".format(
                        self.filename, inFilename), self.state.showMessageTime)
        self.updateTitle()

    def _getLanguageAndRules(self):
        settings = QSettings()
        language = LanguageKind(
            settings.value(Gopt.Key.Language, Gopt.Default.Language))
        sortAsRules = settings.value(Gopt.Key.SortAsRules,
                                     Gopt.Default.SortAsRules)
        pageRangeRules = settings.value(Gopt.Key.PageRangeRules,
                                        Gopt.Default.PageRangeRules)
        return language, sortAsRules, pageRangeRules

    def updateTitle(self):
        if self.filename is None:
            self.setWindowTitle(QApplication.applicationName())
            say("Click File→New or File→Open to create or open an index")
        else:
            filename = os.path.basename(self.filename)
            dirname = os.path.abspath(os.path.dirname(self.filename))
            dirname = (" [{}]".format(os.path.normcase(dirname))
                       if dirname != os.getcwd() else "")
            self.setWindowTitle("{}{} — {}".format(
                filename, dirname, QApplication.applicationName()))

    def updateLanguageIndicator(self):
        language = self.state.model.config(Gconf.Key.Language).value
        self.languageLabel.setPixmap(
            QPixmap(":/{}.png".format(language.replace(" ", "_"))))

    def closeEvent(self, event=None):
        if self.state.saving:
            QTimer.singleShot(100, self.close)
            return
        if self.state.helpForm is not None:
            self.state.helpForm.close()
            self.state.helpForm.deleteLater()
            del self.state.helpForm
            self.state.helpForm = None
        self.indexActions.keepCheckFormsAlive = set()
        self.state.replacePanel.stop()
        self.state.replacePanel.saveSettings()
        self.state.closeModel()
        self.saveSettings()
        event.accept()

    def closeXix(self):
        if self.state.model.filename not in self.recent_files:
            self.recent_files.append(self.state.model.filename)
            self.recent_files = self.recent_files[-9:]
        self.updateRecentFilesMenu()
        self.state.closeModel()

    def saveSettings(self):
        settings = QSettings()
        settings.setValue(Gopt.Key.MainForm_Geometry, self.saveGeometry())
        settings.setValue(Gopt.Key.MainForm_State, self.saveState())
        settings.setValue(Gopt.Key.MainForm_Splitter,
                          self.splitter.saveState())
        settings.setValue(Gopt.Key.MainForm_PanelSplitter,
                          self.panelSplitter.saveState())
        settings.setValue(Gopt.Key.MainForm_EntrySuggestionsSplitter,
                          self.entrySuggestionSplitter.saveState())
        settings.setValue(Gopt.Key.MainForm_SpellAndGroupsSplitter,
                          self.spellAndGroupsSplitter.saveState())
        settings.setValue(Gopt.Key.MainForm_ViewSplitter,
                          self.viewSplitter.saveState())
        settings.setValue(Gopt.Key.MainForm_Filename, self.filename)
        settings.setValue(Gopt.Key.MainForm_RecentFiles, self.recent_files)

    def updateRecentFilesMenu(self):
        recentAction = self.fileActions.openRecentAction
        if self.recent_files:
            self.recent_files = [
                filename for filename in self.recent_files
                if filename and os.path.exists(filename)
            ]
        if not self.recent_files:
            recentAction.setEnabled(False)
        else:
            recentAction.setEnabled(True)
            menu = QMenu(self)
            for i, filename in enumerate(self.recent_files, 1):
                action = QAction(
                    QIcon(":/document-open.svg"),
                    "&{} {}".format(i,
                                    QDir.toNativeSeparators(filename)), menu)
                action.triggered.connect(
                    lambda filename=filename: self.openXix(filename))
                menu.addAction(action)
            recentAction.setMenu(menu)

    def resizeEvent(self, event):
        if not self.resizing:
            self.state.viewAllPanel.view.busy = True
            self.state.viewFilteredPanel.view.busy = True
            self.resizeTimer.start(400)
        super().resizeEvent(event)

    def onResize(self):
        self.resizeTimer.stop()
        self.state.viewAllPanel.view.busy = False
        self.state.viewFilteredPanel.view.busy = False
        self.state.viewAllPanel.view.refresh()
        self.state.viewFilteredPanel.view.refresh()
        self.updateUi()
        if self.debug:
            print("{:,}x{:,}".format(self.frameGeometry().width(),
                                     self.frameGeometry().height()))

    @property
    def resizing(self):
        return self.resizeTimer.isActive()
Ejemplo n.º 22
0
class start_GUI(QMainWindow):
    def __init__(self):
        # This class derivates from a Qt MainWindow so we have to call
        # the class builder ".__init__()"
        QMainWindow.__init__(self)
        # "self" is now a Qt Mainwindow, then we load the user interface
        # generated with QtDesigner and call it self.ui
        self.ui = GUI_compiled.Ui_PyGMI()
        # Now we have to feed the GUI building method of this object (self.ui)
        # with a Qt Mainwindow, but the widgets will actually be built as children
        # of this object (self.ui)
        self.measdata=[[1,3,2],[3,5,7]]
        self.current_header=["index","prime numbers"]
        self.ui.setupUi(self)
        # initialize a QTimer for periodic data transfer with the measuring thread
        self.save_data_timer = QTimer()
        self.save_data_timer.setSingleShot(True)
        self.save_data_timer.timeout.connect(self.save_data)
        # initialize a Queue to retrieve data from the measuring thread
        self.data_queue=Queue.Queue()
        self.measurements_thread_stop_flag=threading.Event()
        self.measurements_thread=threading.Thread()
        # initialize a Lock to reserve access to the instruments
        self.reserved_access_to_instr=threading.Lock()
        #initiate a flag to wait for a parameter to settle
        self.waiting=False
        #initiate the list of available measurements program
        self.update_list_of_meas_program()
        #initialize a list that will retain a reference to the independent plot windows
        self.plotwindows=[]
        #give the reference "self" (the main thread) to the fixed plot windows
        self.ui.Plot2D_1.parent=self
        self.ui.Plot2D_2.parent=self
        self.ui.Plot2D_3.parent=self
        self.ui.Plot2D_4.parent=self
        #give the reference "self" (the main thread) to the Instruments connector
        self.ui.instr_IO.parent=self
        self.ui.instr_IO.set_up_instr_access_lock(self.reserved_access_to_instr)
        self.ui.instr_IO.loaddefaultconfig()
        #give the reference "self" (the main thread) to the Macro Editor
        self.ui.macro_UI.main=self
        
        self.mainconf={}
        self.loaddefaultconfig()
   
    def fixed_plot_conf(self):
        for plot in [self.ui.Plot2D_1,self.ui.Plot2D_2,self.ui.Plot2D_3,self.ui.Plot2D_4]:
            plot.parent=self
            plot.change_line_color(self.mainconf['linecolor'])
            plot.change_point_color(self.mainconf['pointcolor'])
            plot.change_symbol_size(self.mainconf['pointsize'])
        
    def update_list_of_meas_program(self):
        """update the list of available measurements program and recompile them"""
        self.meas_thread_list=[]
        self.ui.measMode.clear()
        already_imported=Measurements_programs.__all__
        reload(Measurements_programs)
        for module in Measurements_programs.__all__:
            if module in already_imported:
                exec('reload(Measurements_programs.'+module+')')
            else:
                exec('import Measurements_programs.'+module)
            exec('Meas_script=Measurements_programs.'+module+'.Script')
            self.meas_thread_list.append(Meas_script)
            self.ui.measMode.addItem(module)
        print "List of Programs Updated" 

    def create_new_plotwidget(self):
        #print "creating new plot widget"
        newwindow=Plot2DDataWidget(self,self.measdata,self.current_header,SymbolSize=self.mainconf['pointsize'],linecolor=self.mainconf['linecolor'],pointcolor=self.mainconf['pointcolor'],title=self.ui.NewPlotWindowTitle.text())
        #a reference to the window must be kept, otherwise the new window is immediately garbage collected !
        self.plotwindows.append(newwindow)
        newwindow.show()
        
    def loaddefaultconfig(self):
        window = Config_menu(self,config_dict=self.mainconf)
        window.update_values()

    def create_config_menu(self):
        newwindow=Config_menu(self,config_dict=self.mainconf)
        #a reference to the window must be kept, otherwise the new window is immediately garbage collected !
        self.plotwindows.append(newwindow)
        newwindow.show()

    def save_data(self):
        """function called periodically by a timer to check if some data
        was put by the measurements thread in the "queue" (buffer),
        and, if so, update the Master dataset "self.measdata" and save
        it to the disk"""
        while not(self.data_queue.empty()):
            #NB:Queues are thread-safe: they won't be accessed by several threads at the same time
            #the lock mechanism is automatically included within them
            #block=False: means do not block execution until some data is put in the queue
            data,note=self.data_queue.get(block=False)
            #the measuring thread may send three different types of
            #information through the Queue, "note" indicates which type it is
            if note=='newfile':
                #close previous file and open a new one
                self.savefile.close()
                try:
                    #write buffer to disk every 256 bytes of data (0.25 Ko)
                    self.savefile=open(data,'a',256)
                except:
                    try:
                        #save data even if the savefile could not be created (the name of which was provided by the user)
                        self.savefile=open("Unsaved-data-"+time.strftime("%a-%d-%b-%Y-%H-%M-%S-UTC", time.gmtime())+".txt",'a',256)
                    except:
                        self.savefile=open("Savefile_of_last_resort",'a',256)
            elif note==True:
                #if note==True, "data" is actually a header for the incoming data
                ######initialize data storage######
                self.current_header=data
                #set-up empty data lists
                self.measdata=[ [] for i in range(len(data))]
                #######Store header to file#######
                self.savefile.write("\t".join(data)+'\n')
            else:
                #good data incoming (hopefully)
                #######Store data to file#########                
                self.savefile.write('\t'.join(map(str,data))+'\n')
                #######Update Master data list####
                for i in range(len(data)):
                    self.measdata[i].append(data[i])
            self.data_queue.task_done()
        self.save_data_timer.start(100)
           
    def switch_measurements_state(self):
        if self.measurements_thread.isAlive():
            self.stop_measurements()
        else:
            self.start_measurements()
    
    def start_measurements(self):
        #fetch the values from the frontpanel
        self.frontpanel_values=Frontpanel_values(self.ui)
        #print "Frontpanel loaded"
        #fetch the measurement type
        meas_thread_class=self.meas_thread_list[self.ui.measMode.currentIndex()]
        #print "Measurements prog loaded"
        ######initialize savefile######
        #it could be done in 'save_data' function, but then you mustn't access 
        #self.frontpanel, because the measurement thread has started, so just do it before starting the measurement thread 
        #256 is the buffer size in bytes, 'a' is for 'append', in order to ensure never to erase any file
        self.savefile=open(self.frontpanel_values.savefile_txt_input,'a',256)
        #initiate a thread to run the measurements without freezing the frontpanel
        #print "launching meas prog"
        self.measurements_thread=meas_thread_class(self,
                                                   self.frontpanel_values,
                                                   self.data_queue,
                                                   self.measurements_thread_stop_flag,
                                                   self.reserved_access_to_instr)
                                                   #self.instr_IO.connected_instr) for next upgrade
        #change the text on the button
        self.ui.pushButton.setText("Stop\nMeasurements")
        #start the measurements thread, which will run independently from the main thread
        self.measurements_thread.start()
        print "Measurements started"
        #start the timer that periodically saves the data
        self.save_data_timer.start(100)
        #initialize a QTimer to check measurement thread activity
        #and do the clean up when the measurement thread stops
        self.check_thread_activity = QTimer()
        self.check_thread_activity.timeout.connect(self.check_measurements)
        self.check_thread_activity.start(200)
    
    def check_measurements(self):
        if not(self.measurements_thread.isAlive()):
            self.stop_measurements()
        
    def stop_measurements(self):
        self.check_thread_activity.stop()
        if self.measurements_thread.isAlive():
            print "Stopping Measurements Thread"
            #tell the measurements thread to stop by setting this flag
            #(this flag is thread safe: it can't be access by both threads at the same time)
            self.measurements_thread_stop_flag.set()
            #wait for the thread to finish safely (it can take a while if the measurement program has few stopping points set up)
            self.measurements_thread.join()
            print "Measurements Thread Stopped"
        #reset the flag
        self.measurements_thread_stop_flag.clear()
        #stop the timer that saves data
        if self.save_data_timer.isActive():
            #abort the waiting time and immediateley finish saving data
            self.save_data_timer.stop()
            self.save_data()
            #previous line will relaunch the timer so stop it again
            self.save_data_timer.stop()
            self.savefile.close()
        #change the text on the button
        self.ui.pushButton.setText("Start\nMeasurements")
   
    def savefile_txt_input_open(self):
        fileName = QFileDialog.getSaveFileName(self,"Savefile",dir= "./measurements data")
        #if the user chooses 'Cancel' in the dialog, a unicode empty string is returned
        if not(fileName[0]==u''):
            self.ui.savefile_txt_input.setText(fileName[0])
Ejemplo n.º 23
0
class MyMainWindow(QtGui.QMainWindow):
    def __init__(self, frequency, parent=None):
        super(MyMainWindow, self).__init__(parent)
        self.ui = Ui_MainWindow()
        self.ui.setupUi(self)

        wd = os.path.dirname(os.path.abspath(__file__))

        self.tick_pixmaps = {True:  QtGui.QPixmap(os.path.join(wd, 'pics', 'blinkenlicht_on.png')),
                             False: QtGui.QPixmap(os.path.join(wd, 'pics', 'blinkenlicht_off.png'))}
        self.speaker_pixmaps = {True:  QtGui.QIcon(QtGui.QPixmap(os.path.join(wd, 'pics', 'speaker_on.png'))),
                                False: QtGui.QIcon(QtGui.QPixmap(os.path.join(wd, 'pics', 'speaker_off.png')))}
        
        self.tick_soundFile=QFile()
        self.tick_soundFile.setFileName(os.path.join(wd, 'sounds', 'tick.raw'))
        self.tick_soundFile.open(QIODevice.ReadOnly)

        self.play_sound = True

        self.format = QAudioFormat()  
        self.format.setChannels(1)  
        self.format.setFrequency(44050)  
        self.format.setSampleSize(16)  
        self.format.setCodec("audio/pcm")  
        self.format.setByteOrder(QAudioFormat.LittleEndian)  
        self.format.setSampleType(QAudioFormat.SignedInt) 

        self.audio_output = QAudioOutput(self.format)

  

       
        self.ui.ping_icon.setPixmap(self.tick_pixmaps[False])
        self.ui.speaker_button.setIcon(self.speaker_pixmaps[self.play_sound])

        self.blink_timer = QTimer()
        self.blink_timer.setSingleShot(True)
        self.blink_timer.timeout.connect(self.clear_blink)
        
        self.timer   = QTimer()                
        self.frequency = frequency
        self.last_played = 0
        self.count = 0
        
        self.ui.f_spin.setValue(self.frequency)
        self.set_speed()
        self.timer.timeout.connect(self.play_tick)

        self.play_ctrl = {True: 'Stop',
                          False: 'Start'}
        self.ui.play_button.setText(self.play_ctrl[False])
        self.ui.statusBar.showMessage('{count:04d} - Dev.: {delta:.3f} ms'.format(delta=0, count=self.count))

    def set_frequency(self, frequency):
        self.frequency = frequency
        self.set_speed()
        self.last_played = 0
        self.count = 0

    def set_speed(self):
        self.speed = 1000.0 / (self.frequency / 60.0)
        self.timer.setInterval(self.speed)

    def clear_blink(self):
        self.ui.ping_icon.setPixmap(self.tick_pixmaps[False])

    def toggle_play(self):
        if self.timer.isActive():
            self.timer.stop()
            self.last_played = 0
            self.count = 0
            self.ui.statusBar.clearMessage()
        else:
            self.timer.start(self.speed)
            self.play_tick()
        self.ui.play_button.setText(self.play_ctrl[self.timer.isActive()])

    def toggle_play_sound(self):
        if self.play_sound:
            self.play_sound = False
            self.audio_output.stop()
        else:
            self.play_sound = True
        self.ui.speaker_button.setIcon(self.speaker_pixmaps[self.play_sound])

    def play_tick(self):
        if self.last_played:
            delta = ((time.time() - self.last_played) * 1000.0) - self.speed
        else:
            delta = 0
        self.last_played = time.time()

        self.audio_output.stop()
        self.audio_output.reset()
        if self.play_sound:
            self.tick_soundFile.seek(0)
            self.audio_output.start(self.tick_soundFile)

        self.count += 1
        self.ui.statusBar.showMessage('{count:04d} - Dev.: {delta:.3f} ms'.format(delta=delta, count=self.count))

        self.blink_timer.stop()
        self.ui.ping_icon.setPixmap(self.tick_pixmaps[True])
        self.blink_timer.start(100)
                
        self.timer.start(self.speed - max(0, delta))
Ejemplo n.º 24
0
class Navigator(Singleton,object):
    
    instance = None
    logger = None
    PATH_SEPARATOR = '-'
    
    JUMP_SIGNAL_PAGE_LOAD_FINISH = 'PAGE LOAD FINISH'
    JUMP_SIGNAL_REQUEST_LOAD_FINISH = 'REQUEST LOAD FINISH'
    
    CROSS_AXIS_METHOD_AJAX = 'AJAX'
    CROSS_AXIS_METHOD_FULL_LOAD = 'FULL LOAD'
    
    def __init__(self):
        self.network_manager = CustomNetworkAccessManager.getInstance()
        self.axis = []
        self.human_events = {}
        self.node_collected_data = {}
        self.last_url = None   
        self.current_url = QUrl()
        self.current_axis_id = None
        self.crossed_axis_method = None
        self.crossed_axes = []
        self.crossed_axes_string = ''
        
        self.cross_axis_delay = QTimer()
        self.cross_axis_delay.setSingleShot(True)
        self.cross_axis_delay.timeout.connect(self.crossedAxis)
        self.cross_axis_params = None
        
        self.loading_timeout = QTimer()
        self.loading_timeout.setSingleShot(True)
        self.loading_timeout.setInterval(30000)
        self.loading_timeout.timeout.connect(self.loadingTimeoutAction)
        
        self.inactivity_timeout = QTimer()
        self.inactivity_timeout.setSingleShot(True)
        self.inactivity_timeout.setInterval(10000)
        self.inactivity_timeout.timeout.connect(self.inactivityTimeoutAction)
        
        self.page_reloads = 0
        
        Logger.getLoggerFor(self.__class__)

    def takeEntryPoint(self):        
        g = GraphParser.getInstance()
        for ax in self.axis:
            self.logger.info('Axis : %s' % ax)
            
        if g.entry_point:
            self.logger.info('Taking the entry point %s' % g.entry_point)
            self.last_url = QUrl(g.entry_point)
            inst =  Jaime.getInstance()
            inst.view.load(QUrl(g.entry_point))    
            return True
        
        self.logger.error("Can't take entry point, graph has't entry point")
        return False
        
    def matchesSelector(self,axis):
        inst =  Jaime.getInstance()
        frame = inst.view.page().mainFrame()
        document = frame.documentElement()
        selector = axis.css_selector_condition
#         print 'document %s ' % document.evaluateJavaScript("""this.querySelector('%s')""" % selector)        
        ret = frame.findAllElements(selector)
        
        print '%s axis %s matches css selector ? %s' % (selector,axis,bool(ret.count()))
        self.logger.debug('axis %s matches css selector ? %s' % (axis,bool(ret.count())))
#         for i in range(ret.count()):
#             self.logger.error(ret.at(i).toInnerXml())
        return ret.count()
    
    def matches(self,axis):
        m_from = False
        m_to = False
        m_method = True
        m_selector = True
        m_route = True
        
        if axis.from_url is not None :            
            if isinstance(axis.from_url,str):
                m_from = axis.from_url == self.last_url.toString()
            elif axis.from_url.match(self.last_url.toString()):                
                m_from = True
        else:
            m_from = True
        
        if axis.to_url is not None:
            if isinstance(axis.to_url,str):
                m_to =  axis.to_url == self.current_url.toString()
            elif axis.to_url.match(self.current_url.toString()):
                m_to = True         
        else:
            m_to = True
        
#         print 'el axis %s tiene method %s, %s '  % (axis,
#                                                     axis.axis_method,
#                                                     self.crossed_axis_method
#                                                     )
        
        if self.crossed_axis_method == RouteAxis.CROSS_AXIS_METHOD_AJAX or \
                axis.axis_method :
            
            if self.crossed_axis_method != axis.axis_method:
                m_method = False           
        
        if axis.css_selector_condition:
            m_selector = axis.not_css_selector ^ bool(self.matchesSelector(axis))            
        
        if len(axis.previous_axis)>0:
            m_route = False
            for path_index in range(len(axis.previous_axis)):
                path =  string.join( axis.previous_axis[path_index], self.PATH_SEPARATOR)
                self.logger.debug( "matching axis (%s) with path %s" % (axis.id,path))
                if self.crossed_axes_string.find(path) == 0:
                    m_route = True 
                    break
        
        self.logger.debug("Matches (%s) %s\nfrom %s  to %s method %s selector %s route %s" % (axis.id,
                                                                                             axis.comment,
                                                                                             m_from,m_to,m_method,m_selector,m_route))
        return m_from and m_to and m_method and m_selector and m_route
    
    def crossedAxis(self,ajax=False):
        if self.cross_axis_params and self.cross_axis_params[0]: 
            ajax = True
            
        self.cross_axis_params = None
        self.crossed_axes_string = string.join(self.crossed_axes,self.PATH_SEPARATOR)
        self.logger.info("crossedAxis, path: \n%s" % string.join(self.crossed_axes,"\n"))
        if ajax:
            self.crossed_axis_method =  RouteAxis.CROSS_AXIS_METHOD_AJAX
        else:
            self.crossed_axis_method =  RouteAxis.CROSS_AXIS_METHOD_FULL_LOAD
            
        print 'Cruse un eje por %s ' % self.crossed_axis_method
        
        for ax in self.axis:
            if self.matches(ax):
                self.current_axis_id = ax.id
                # print 'current axis %s'  % ax.id
                self.logger.info('Axis matched %s' % ax)                
                self.startSecuence(ax)
                return
        self.logger.warning("Can't match any axis")                
        self.inactivity_timeout.start()            
    
    def collectData(self,id,collector,params):
        if not self.getAxis(id):
            self.logger.error("Can't collect data from inexistent axis")
            return 

        if id not  in self.node_collected_data:
            self.node_collected_data[id] = []
            
        self.node_collected_data[id].append((collector,params))
    
    def setAxis(self,route_axis,human_events):
        self.logger.info('setting axis %s ' % route_axis)
        if route_axis.id in self.human_events:
            raise Exception('Axis repetido')
        
        self.human_events[route_axis.id] = human_events
        self.axis.append(route_axis)
#         QtCore.QObject.disconnect(self.human_events[route_axis.id], 
#                                   QtCore.SIGNAL("finished()"), 
#                                   self.secuenceFinished)
        QtCore.QObject.connect(self.human_events[route_axis.id], 
                               QtCore.SIGNAL("finished()"), 
                               self.secuenceFinished)
    
    def startSecuence(self,axis):
        inst =  Jaime.getInstance()
        try :
            if axis.max_crosses > 0 and axis.max_crosses <= axis.cross_counter:
                self.logger.info('el axis supero la cantidad maxima de loops ')
                if axis.exit_point:
                    exit_axis = self.getAxis(axis.exit_point)
                    self.logger.info('Salto hacia el exit_axis %s' %  exit_axis)
                    self.startSecuence(exit_axis)
                else:
                    self.logger.error('No hay exit axis muero %s' % axis.exit_point)
                    inst =  Jaime.getInstance()
                    inst.finishWork()                    
            else:

                axis.cross_counter += 1
                h_ev = self.human_events[axis.id]            
                self.crossed_axes.insert(0,axis.id)
                if len(self.crossed_axes) >= 10: self.crossed_axes.pop()            
                self.logger.info('Stopeo el inactivity timeout')
                self.inactivity_timeout.stop()            
                if axis.id in self.node_collected_data:
                    inst.route_node.doWork(self.node_collected_data[axis.id])                    
                h_ev.fire()
                
        except Exception as e:
            # print 'Excepcion en startSecuence %s' % e
            self.logger.error(e)
            
    def getAxis(self,axis_id):
        for ax in self.axis:
            if ax.id == axis_id:
                return ax
        return None
    
    def secuenceFinished(self):
        self.logger.info('estarteo  el inactivity timeout' )
        self.inactivity_timeout.start()
    
    def inactivityTimeoutAction(self):
        
        inst =  Jaime.getInstance()
        self.logger.info('inactivity timeout action fired after %s seconds' % (self.inactivity_timeout.interval()/1000))
        
        if not len(self.crossed_axes):
            return 
        last_axis = self.crossed_axes[0]
        ax = self.getAxis(last_axis)
        
        retry_axis_id = ax.retry_axis
        retry_axis = self.getAxis(retry_axis_id)
        
        if retry_axis:
            self.logger.info('El axis %s tiene como retry axis a %s lo estarteo' % (ax.id,ax.retry_axis))
            self.startSecuence(retry_axis)
    
    def processLoadStarted(self):
        inst =  Jaime.getInstance()
        self.logger.info('Page started load to %s' % inst.view.url().toString())
        
#         print inst.page.mainFrame().requestedUrl()
        self.inactivity_timeout.stop()            
        self.loading_timeout.stop()
        self.loading_timeout.start()
        
        self.logger.info('Starting loading timeout and stopping inactivity timeout')        
        self.last_url = inst.view.url()
        
    def loadingTimeoutAction(self):
        self.logger.warning('Loading timeout fired')
        inst =  Jaime.getInstance()
        if (not inst.view.url().isEmpty() and re.match('http.?://',inst.view.url().toString()) ) \
                and not self.page_reloads:
            self.logger.info('Timeout fired, reloading last url %s' % inst.view.url().toString())
            self.page_reloads += 1            
            self.loading_timeout.stop()                    
            inst.view.reload()            
            
        else:
            self.logger.error("""Timeout fired, clossing jaime, there isn't last_url or max_reloads reatched""" )
            inst.finishWork()
                        
    def processPageLoadFinished(self,status):
        inst =  Jaime.getInstance()
        self.logger.info('Page finished load to %s with status %s ' % (inst.view.url().toString(),
                                                                       status))
        if status:
            self.current_url = inst.view.url()
            self.loading_timeout.stop()
            self.page_reloads = 0 
            self.logger.info('Stopping loading timeout')    
        else:
            self.current_url = QUrl()           
            
        self.testJumpRules(self.JUMP_SIGNAL_PAGE_LOAD_FINISH,
                           status)       
        
    def testJumpRules(self,signal,*args):
#         self.logger.info('Call to restJumpRules with signal %s' % signal)
#         print 'Call to restJumpRules %s' % ( signal)
        if signal == self.JUMP_SIGNAL_PAGE_LOAD_FINISH:
            print 'llamo a crosed axis'
            self.pushCrossedAxis()            
        
        elif signal == self.JUMP_SIGNAL_REQUEST_LOAD_FINISH:
            if not self.current_axis_id:
                return
            
            req_headers = args[0]
            rep_headers = args[1]
            
            ax = self.getAxis(self.current_axis_id)
#             print '%s tiene exit_method %s' % (ax,ax.axis_exit_method)
            if ax.axis_exit_method and  \
                    ax.axis_exit_method == RouteAxis.CROSS_AXIS_METHOD_AJAX:
                
                if 'X-Requested-With' in req_headers:
                    
                    if ax.axis_exit_method_toggled :
                        ax.axis_exit_method = ax.axis_exit_method_toggled
                        ax.axis_exit_method_toggled = None
                    
                    self.pushCrossedAxis(True)
    
    def pushCrossedAxis(self,*params):
        if self.cross_axis_delay.isActive() or \
                self.cross_axis_params is not None:
            self.logger.warning("""Can't push crossAxis call, there is another call in process""")
            return 
        
        self.cross_axis_params = params                
        
        ax = self.getAxis(self.current_axis_id)        
        
        if ax and  ax.delay_node_test: 
            delay = ax.delay_node_test
        else:
            delay = None
            
        if delay:            
            self.cross_axis_delay.setInterval(delay)
            self.cross_axis_delay.start()
            self.logger.info('Delaying %s seconds crossedAxis call ' % int( int(delay) /1000)  )            
        else:
            self.crossedAxis()
Ejemplo n.º 25
0
class GameOfLifeWidget(QWidget):
    """game of life widget with grid and tools
    """
    def _handle_timeout(self):
        grid = _play(self.grid)
        del self.grid
        self.grid = grid
        self._update_gui()

    def _update_gui(self):
        for y_pos in range(self.height):
            for x_pos in range(self.width):
                _, _, life = self.grid[y_pos][x_pos]
                neighbours = _get_neighbours_of_cell(self.grid,
                                                     self.grid[y_pos][x_pos])
                lifes = [
                    list(n)[INDEX_ALIVE] for n in neighbours
                    if list(n)[INDEX_ALIVE]
                ]
                total_lifes = len(lifes)
                weight = ((float(total_lifes) / 8.0) * 175.0) + 80.0

                if life:
                    color = QColor(weight, weight / 2, weight / 2)
                    self.buttons[y_pos][x_pos].setStyleSheet(
                        """QWidget { background-color: %s; }""" % color.name())
                else:

                    color = QColor(1, 1, weight)
                    self.buttons[y_pos][x_pos].setStyleSheet(
                        """QWidget { background-color: %s; }""" % color.name())
                    # self.buttons[y][x].setStyleSheet(None)

    def _handle_click(self):
        for y_pos, row in enumerate(self.buttons):
            for x_pos, button in enumerate(row):
                if button == self.sender():
                    _, _, life = self.grid[y_pos][x_pos]
                    new_life = not life
                    self.grid[y_pos][x_pos] = (x_pos, y_pos, new_life)
        self._update_gui()

    def __init__(self, width, height, grid):
        super(GameOfLifeWidget, self).__init__()
        self.setWindowTitle('Game Of Life')
        layout = QGridLayout()
        layout.setSpacing(0)
        layout.setContentsMargins(0, 0, 0, 0)
        main_layout = QVBoxLayout()
        self.buttons = []
        self.grid = grid
        self.width = width
        self.height = height
        self.timer = QTimer()
        self.timer.setInterval(INTERVAL)
        self.timer.timeout.connect(self._handle_timeout)

        self.start_button = QPushButton("Start")
        self.start_button.clicked.connect(self._handle_start)
        self.clear_button = QPushButton("Clear")
        self.clear_button.clicked.connect(self._handle_clear)

        main_layout.addLayout(layout)
        main_layout.addWidget(self.start_button)
        main_layout.addWidget(self.clear_button)
        self.setLayout(main_layout)
        for y_pos in range(height):
            row = []
            self.buttons.append(row)
            for x_pos in range(width):
                button = QPushButton()
                row.append(button)
                button.setMaximumWidth(20)
                button.setMaximumHeight(20)
                button.clicked.connect(self._handle_click)
                layout.addWidget(button, y_pos, x_pos)
        self._update_gui()

    def _handle_clear(self):
        grid = _make_grid_from_size(self.width, self.height)
        del self.grid
        self.grid = grid
        self._update_gui()

    def _handle_start(self):
        if self.timer.isActive():
            self.start_button.setText('Start')
            self.timer.stop()
        else:
            self.start_button.setText('Stop')
            self.timer.start()