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()
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
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
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
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
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()
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)
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)
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)
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()
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)
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())
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)
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
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)
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)
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)
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()
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()
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])
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))
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()
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()