class BarChartDashboardWidget(DashboardWidget): def __init__(self, display, page, title, value=0, range_low=0, range_high=100, status=DashboardStatus.Passive): super(BarChartDashboardWidget, self).__init__(display, page, status) self.width = 150 self.title = title self.value = value self.panel = StackPanel(display, page) self.lbl_title = TextBlock(display, page, title, is_highlighted=True) self.lbl_title.font = display.fonts.list self.panel.children.append(self.lbl_title) self.chart = BarChart(display, page, value=value, range_low=range_low, range_high=range_high) self.chart.width = self.width self.chart.height = 15 self.panel.children.append(self.chart) def arrange(self): self.lbl_title.text = self.title self.chart.value = self.value self.panel.arrange() self.desired_size = 150 + (self.padding * 2), self.panel.desired_size[1] + (self.padding * 2) return self.desired_size def render(self): # Colorize as needed color = self.get_color() self.chart.color = color self.lbl_title.color = self.get_title_color() # Render an outline around the entire control rect = Rect(self.pos[0], self.pos[1], self.desired_size[0], self.desired_size[1]) # Some statuses need custom backgrounds if self.status == DashboardStatus.Caution: render_rectangle(self.display, self.display.color_scheme.caution_bg, rect, width=0) elif self.status == DashboardStatus.Critical: render_rectangle(self.display, self.display.color_scheme.critical_bg, rect, width=0) # Render the outline render_rectangle(self.display, color, rect) # Render the base content with some padding pos = self.pos[0] + self.padding, self.pos[1] + self.padding self.panel.render_at(pos) # Assume the width of the outer outline return self.set_dimensions_from_rect(rect)
class TextBox(FocusableWidget): """ Represents a text entry control with associated label. """ label_text = None text = '' text_width = 100 max_length = None allow_alpha = True allow_numeric = True allow_space = True allow_negative = True allow_decimal = True def __init__(self, display, page, label=None, text=None, text_width=100): super(TextBox, self).__init__(display, page) if text: text = str(text) self.text = text self.label_text = label self.label = TextBlock(display, page, label) self.glyph = TextGlyph(display, page) self.text_width = text_width self.panel = StackPanel(display, page, is_horizontal=True) self.panel.center_align = True self.panel.children = [self.label, self.glyph] def arrange(self): # Pass along our values to the children self.label.text = self.label_text self.glyph.text_width = self.text_width self.glyph.text = self.text self.desired_size = self.panel.arrange() return super(TextBox, self).arrange() def render(self): """ Renders the TextBox with its current state :return: The rectangle of the TextBox """ # Render the panel's contents self.panel.set_dimensions_from(self) self.panel.render() return self.set_dimensions_from(self.panel) def set_alphanumeric(self): self.allow_alpha = True self.allow_negative = True self.allow_numeric = True self.allow_decimal = True self.allow_space = True def set_numeric(self, allow_negative=True, allow_decimal=True): self.allow_alpha = False self.allow_negative = allow_negative self.allow_decimal = allow_decimal self.allow_numeric = True self.allow_space = False def got_focus(self): """ Occurs when the control gets focus """ self.label.is_highlighted = True self.glyph.render_focus = True super(TextBox, self).got_focus() def lost_focus(self): """ Occurs when the control loses focus """ self.label.is_highlighted = False self.glyph.render_focus = False super(TextBox, self).lost_focus() def can_input_more(self): """ Returns whether or not there is room to enter more characters (according to max_length) :return: whether or not there is room to enter more characters (according to max_length) """ return self.max_length is None or len(self.text) < self.max_length def handle_key(self, key): """ Handles a keypress :param key: The keycode :returns: True if the event was handled; otherwise False """ # ensure we have text in the textbox if not self.text: self.text = '' if key == Keycodes.KEY_BACKSPACE: if self.text and len(self.text) > 0: self.text = self.text[:-1] # TODO: This is simplistic and needs to work with a cursor index return True if key == Keycodes.KEY_DELETE: if self.text and len(self.text) > 0: self.text = self.text[1:] # TODO: This is simplistic and needs to work with a cursor index return True if self.allow_numeric and Keycodes.KEY_0 <= key <= Keycodes.KEY_9 and self.can_input_more(): char = key - Keycodes.KEY_0 self.text += str(char) # TODO: This will need to take cursor location into account self.state_changed() return True if self.allow_alpha and Keycodes.KEY_a <= key <= Keycodes.KEY_z and self.can_input_more(): char = chr(key) self.text += str(char).upper() # TODO: This will need to take cursor location into account self.state_changed() return True if self.allow_negative and key in [Keycodes.KEY_KP_MINUS, Keycodes.KEY_MINUS]: self.text += '-' self.state_changed() return True if self.allow_decimal and key in [Keycodes.KEY_KP_PERIOD, Keycodes.KEY_PERIOD]: self.text += '.' self.state_changed() return True if self.allow_space and key == Keycodes.KEY_SPACE and self.can_input_more(): self.text += ' ' self.state_changed() return True return super(TextBox, self).handle_key(key) def has_text(self): return self.text and len(self.text) > 0
class CpuDashboardWidget(DashboardWidget): charts = None def __init__(self, display, page, title="CPU", values=None, status=DashboardStatus.Passive): super(CpuDashboardWidget, self).__init__(display, page, status) self.title = title self.values = values self.panel = StackPanel(display, page) self.lbl_title = TextBlock(display, page, title, is_highlighted=True) self.lbl_title.font = display.fonts.list self.panel.children.append(self.lbl_title) self.pnl_charts = StackPanel(display, page, is_horizontal=True) self.pnl_charts.padding = 2, 0 self.panel.children.append(self.pnl_charts) def get_percent_status(self, percentage): if percentage > 95: return DashboardStatus.Critical elif percentage > 80: return DashboardStatus.Caution elif percentage < 0: return DashboardStatus.Inactive else: return DashboardStatus.Passive def arrange(self): max_value = -1 if not self.charts and self.values and len(self.values) > 0: self.charts = [] chart_width = (150 - (len(self.values) * 1)) / len(self.values) for value in self.values: chart = BarChart(self.display, self.page, value=value) chart.width = chart_width chart.height = 15 chart.color = self.get_status_color( self.get_percent_status(value)) self.charts.append(chart) self.pnl_charts.children.append(chart) max_value = max(value, max_value) elif self.charts and self.values and len(self.values) > 0: for chart, value in zip(self.charts, self.values): chart.value = value chart.color = self.get_status_color( self.get_percent_status(value)) max_value = max(value, max_value) self.status = self.get_percent_status(max_value) self.lbl_title.text = self.title self.panel.arrange() self.desired_size = 150 + ( self.padding * 2), self.panel.desired_size[1] + (self.padding * 2) return self.desired_size def render(self): # Colorize as needed color = self.get_color() self.lbl_title.color = self.get_title_color() # Render an outline around the entire control rect = Rect(self.pos[0], self.pos[1], self.desired_size[0], self.desired_size[1]) # Some statuses need custom backgrounds if self.status == DashboardStatus.Caution: render_rectangle(self.display, self.display.color_scheme.caution_bg, rect, width=0) elif self.status == DashboardStatus.Critical: render_rectangle(self.display, self.display.color_scheme.critical_bg, rect, width=0) # Render the outline render_rectangle(self.display, color, rect) # Render the base content with some padding pos = self.pos[0] + self.padding, self.pos[1] + self.padding self.panel.render_at(pos) # Assume the width of the outer outline return self.set_dimensions_from_rect(rect)
class TextDashboardWidget(DashboardWidget): """ A simple labeled dashboard widget :type display: PiMFD.UI.DisplayManager.DisplayManager :type page: PiMFD.Applications.Core.DashboardPages.DashboardPage :type title: str The name of the widget :type value: str The value used in the widget """ def __init__(self, display, page, title, value, status=DashboardStatus.Passive): super(TextDashboardWidget, self).__init__(display, page, status) self.title = title self.value = value self.width = 150 self.value_font = display.fonts.list self.panel = StackPanel(display, page) self.lbl_title = TextBlock(display, page, title, is_highlighted=True) self.lbl_title.font = display.fonts.list self.panel.children.append(self.lbl_title) self.lbl_value = TextBlock(display, page, value) self.lbl_value.font = display.fonts.list self.panel.children.append(self.lbl_value) def render(self): # Colorize as needed color = self.get_color() self.lbl_value.color = color self.lbl_title.color = self.get_title_color() # Render an outline around the entire control rect = Rect(self.pos[0], self.pos[1], self.desired_size[0], self.desired_size[1]) # Some statuses need custom backgrounds if self.status == DashboardStatus.Caution: render_rectangle(self.display, self.display.color_scheme.caution_bg, rect, width=0) elif self.status == DashboardStatus.Critical: render_rectangle(self.display, self.display.color_scheme.critical_bg, rect, width=0) # Render the outline render_rectangle(self.display, color, rect) # Render the base content with some padding pos = self.pos[0] + self.padding, self.pos[1] + self.padding self.panel.render_at(pos) # Assume the width of the outer outline return self.set_dimensions_from_rect(rect) def arrange(self): self.lbl_title.text = self.title self.lbl_value.text = self.value self.lbl_value.font = self.value_font self.panel.arrange() self.desired_size = 150 + (self.padding * 2), self.panel.desired_size[1] + (self.padding * 2) return self.desired_size
class WeatherForecastDashboardWidget(DashboardWidget): """ A dashboard widget containing weather condition information :type display: PiMFD.UI.DisplayManager.DisplayManager :type page: PiMFD.Applications.Core.DashboardPages.DashboardPage :type title: str The name of the widget :type value: str The value used in the widget """ def __init__(self, display, page, title, weather=None, forecast=None, is_forecast=True): super(WeatherForecastDashboardWidget, self).__init__(display, page, DashboardStatus.Inactive) self.title = title self.forecast = forecast self.weather = weather self.is_forecast = is_forecast self.minutes_to_clean_frost = None self.panel = StackPanel(display, page) self.lbl_title = TextBlock(display, page, title, is_highlighted=True) self.lbl_title.font = display.fonts.list self.panel.children.append(self.lbl_title) pnl_value = StackPanel(display, page, is_horizontal=True) pnl_value.center_align = True self.lbl_condition = TextBlock(display, page, "{}") self.lbl_condition.font = display.fonts.weather self.lbl_value = TextBlock(display, page, "Offline") self.lbl_value.font = display.fonts.list pnl_value.children = [self.lbl_value, self.lbl_condition] self.panel.children.append(pnl_value) self.chart = BoxChart(display, page) self.chart.width = 150 self.chart.range_low = -20 self.chart.range_high = 120 self.chart.is_highlighted = False self.chart.box_width = 0 self.chart.ticks = (0, 32, 100) self.panel.children.append(self.chart) self.lbl_frost = TextBlock(display, page, None) self.lbl_frost.font = display.fonts.list self.panel.children.append(self.lbl_frost) def render(self): # Colorize as needed color = self.get_color() self.lbl_value.color = color self.lbl_title.color = self.get_title_color() self.lbl_condition.color = color self.chart.color = color # Render an outline around the entire control rect = Rect(self.pos[0], self.pos[1], self.panel.desired_size[0] + (self.padding * 2), self.panel.desired_size[1] + (self.padding * 2)) # Some statuses need custom backgrounds if self.status == DashboardStatus.Caution: render_rectangle(self.display, self.display.color_scheme.caution_bg, rect, width=0) elif self.status == DashboardStatus.Critical: render_rectangle(self.display, self.display.color_scheme.critical_bg, rect, width=0) # Render the outline render_rectangle(self.display, color, rect) # Render the base content with some padding pos = self.pos[0] + self.padding, self.pos[1] + self.padding self.panel.render_at(pos) # Assume the width of the outer outline return self.set_dimensions_from_rect(rect) def arrange(self): self.status = self.get_status() self.lbl_title.text = self.title if self.forecast and self.weather: if not self.is_forecast: self.lbl_condition.text_data = get_condition_icon( self.weather.code) self.lbl_value.text = u'{}{}'.format(self.weather.temperature, self.weather.temp_units) else: self.lbl_condition.text_data = get_condition_icon( self.forecast.code) self.lbl_value.text = u'{}'.format(self.forecast.temp_range) else: self.lbl_condition.text_data = None self.lbl_value.text = 'Offline' if self.minutes_to_clean_frost and self.minutes_to_clean_frost > 0.1: self.lbl_frost.visible = True self.lbl_frost.text = '{} Minutes Frost'.format( round(self.minutes_to_clean_frost, 1)) else: self.lbl_frost.visible = False self.lbl_frost.text = None if self.forecast: if not self.is_forecast: temp = float(self.weather.temperature) if temp >= 0: self.chart.value_low = 0 self.chart.value_high = temp else: self.chart.value_low = temp self.chart.value_high = 0 else: self.chart.value_low = self.forecast.low self.chart.value_high = self.forecast.high self.panel.arrange() self.desired_size = self.panel.desired_size[0] + ( self.padding * 2), self.panel.desired_size[1] + (self.padding * 2) return self.desired_size def get_status(self): if not self.weather or not self.forecast: return DashboardStatus.Inactive temp_status = self.get_temperature_status() cond_status = get_condition_status(self.forecast.code) if cond_status == DashboardStatus.Critical or temp_status == DashboardStatus.Critical: return DashboardStatus.Critical if cond_status == DashboardStatus.Caution or temp_status == DashboardStatus.Caution: return DashboardStatus.Caution return temp_status def get_temperature_status(self): if not self.weather or not self.forecast: return DashboardStatus.Inactive # Certain Temperatures should function as alerts low = self.forecast.low high = self.forecast.high # If it's today, we don't care about forecast - go off of current temperature if not self.is_forecast: low = high = float(self.weather.temperature) if low <= 10 or high >= 100: return DashboardStatus.Critical elif low <= 32 or high >= 90: return DashboardStatus.Caution else: return DashboardStatus.Passive
class SpinnerBox(FocusableWidget): """ Represents a segment of text """ label_block = None label = None value_block = None value = None panel = None items = [] def __init__(self, display, page, label, value, items=None): """ :type items: list """ if not items: items = [] super(SpinnerBox, self).__init__(display, page) self.label = label self.value = value self.label_block = TextBlock(display, page, label) self.value_block = TextBlock(display, page, value) self.panel = StackPanel(display, page, is_horizontal=True) self.panel.children = [self.label_block, self.value_block] self.items = items def arrange(self): self.label_block.text = self.label self.value_block.text = self.value self.label_block.is_highlighted = self.is_focused() self.value_block.is_highlighted = self.is_focused() self.desired_size = self.panel.arrange() return super(SpinnerBox, self).arrange() def render(self): """ Renders the textblock to the default surface using the current properties of this object :rtype : RectType """ return self.set_dimensions_from_rect(self.panel.render_at(self.pos)) def handle_key(self, key): if is_plus_key(key) or is_right_key(key) or is_enter_key(key): self.move_next() return True if is_minus_key(key) or is_left_key(key): self.move_previous() return True return super(SpinnerBox, self).handle_key(key) def get_selected_index(self): if not self.items or self.value not in self.items: return -1 return self.items.index(self.value) def move_next(self): if not self.items or len(self.items) <= 0: return current_index = self.get_selected_index() if current_index < 0: current_index = 0 elif current_index >= len(self.items) - 1: current_index = 0 else: current_index += 1 new_value = self.items[current_index] if self.value != new_value: self.value = new_value self.state_changed() def move_previous(self): if not self.items or len(self.items) <= 0: return current_index = self.get_selected_index() if current_index < 0: current_index = len(self.items) - 1 elif current_index >= len(self.items): current_index = 0 else: current_index -= 1 new_value = self.items[current_index] if self.value != new_value: self.value = new_value self.state_changed()
class CheckBox(FocusableWidget): """ A CheckBox with an associated label. """ text = None panel = None label = None glyph = None checked = False def __init__(self, display, page, label): super(CheckBox, self).__init__(display, page) self.text = label self.label = TextBlock(display, page, label) self.glyph = CheckBoxGlyph(display, page) self.panel = StackPanel(display, page, is_horizontal=True) self.panel.center_align = True self.panel.children = [self.label, self.glyph] def arrange(self): self.desired_size = self.panel.arrange() return super(CheckBox, self).arrange() def render(self): """ Renders the checkbox with its current state :return: The rectangle of the checkbox """ # Pass along our values to the children self.label.text = self.text self.glyph.checked = self.checked # Render the panel's contents self.panel.set_dimensions_from(self) self.panel.render() return self.set_dimensions_from(self.panel) def got_focus(self): """ Occurs when the control gets focus """ self.label.is_highlighted = True self.glyph.render_focus = True super(CheckBox, self).got_focus() def lost_focus(self): """ Occurs when the control loses focus """ self.label.is_highlighted = False self.glyph.render_focus = False super(CheckBox, self).lost_focus() def handle_key(self, key): """ Handles a keypress :param key: The keycode :returns: True if the event was handled; otherwise False """ if is_enter_key(key) or key == Keycodes.KEY_SPACE: if self.checked: self.checked = False else: self.checked = True self.state_changed() return True else: return super(CheckBox, self).handle_key(key)