main.py 23.2 KB
Newer Older
Roberto Leinardi's avatar
Roberto Leinardi committed
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19
# This file is part of gwe.
#
# Copyright (c) 2018 Roberto Leinardi
#
# gwe is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# gwe is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with gwe.  If not, see <http://www.gnu.org/licenses/>.

import logging
from collections import OrderedDict
20
from typing import Optional, Dict, List, Tuple, Any
Roberto Leinardi's avatar
Roberto Leinardi committed
21 22 23 24 25 26

from injector import inject, singleton
from gi.repository import Gtk
from matplotlib.figure import Figure
from matplotlib.backends.backend_gtk3agg import FigureCanvasGTK3Agg as FigureCanvas

27
try:  # AppIndicator3 may not be installed
28 29
    import gi

Roberto Leinardi's avatar
Roberto Leinardi committed
30 31 32 33
    gi.require_version('AppIndicator3', '0.1')
    from gi.repository import AppIndicator3
except (ImportError, ValueError):
    AppIndicator3 = None
34 35 36 37 38 39 40
from gwe.di import MainBuilder
from gwe.view.edit_fan_profile import EditFanProfileView
from gwe.util.view import hide_on_delete, init_plot_chart, get_fan_profile_data, is_dazzle_version_supported
from gwe.interactor import SettingsInteractor
from gwe.view.edit_overclock_profile import EditOverclockProfileView
from gwe.view.historical_data import HistoricalDataView
from gwe.view.preferences import PreferencesView
41
from gwe.model import Status, FanProfile
42
from gwe.conf import APP_PACKAGE_NAME, APP_ID, APP_NAME, APP_VERSION, APP_SOURCE_URL, APP_ICON_NAME_SYMBOLIC
Roberto Leinardi's avatar
Roberto Leinardi committed
43 44 45 46 47 48 49 50 51 52 53 54 55
from gwe.presenter.main import MainPresenter, MainViewInterface

LOG = logging.getLogger(__name__)
if AppIndicator3 is None:
    LOG.warning("AppIndicator3 is not installed. The app indicator will not be shown.")


@singleton
class MainView(MainViewInterface):

    @inject
    def __init__(self,
                 presenter: MainPresenter,
56
                 edit_fan_profile_view: EditFanProfileView,
57
                 edit_overclock_profile_view: EditOverclockProfileView,
Roberto Leinardi's avatar
Roberto Leinardi committed
58
                 historical_data_view: HistoricalDataView,
Roberto Leinardi's avatar
Roberto Leinardi committed
59 60 61 62 63 64
                 preferences_view: PreferencesView,
                 builder: MainBuilder,
                 settings_interactor: SettingsInteractor,
                 ) -> None:
        LOG.debug('init MainView')
        self._presenter: MainPresenter = presenter
65
        self._edit_fan_profile_view = edit_fan_profile_view
66
        self._edit_overclock_profile_view = edit_overclock_profile_view
Roberto Leinardi's avatar
Roberto Leinardi committed
67
        self._historical_data_view = historical_data_view
Roberto Leinardi's avatar
Roberto Leinardi committed
68 69 70 71 72 73 74 75 76 77
        self._preferences_view = preferences_view
        self._presenter.main_view = self
        self._builder: Gtk.Builder = builder
        self._settings_interactor = settings_interactor
        self._first_refresh = True
        self._init_widgets()

    def _init_widgets(self) -> None:
        self._app_indicator: Optional[AppIndicator3.Indicator] = None
        self._window = self._builder.get_object("application_window")
78
        self._edit_fan_profile_view.set_transient_for(self._window)
79
        self._edit_overclock_profile_view.set_transient_for(self._window)
Roberto Leinardi's avatar
Roberto Leinardi committed
80
        self._historical_data_view.set_transient_for(self._window)
Roberto Leinardi's avatar
Roberto Leinardi committed
81 82 83 84 85 86 87 88
        self._preferences_view.set_transient_for(self._window)
        self._main_menu: Gtk.Menu = self._builder.get_object("main_menu")
        self._main_infobar: Gtk.InfoBar = self._builder.get_object("main_infobar")
        self._main_infobar.connect("response", lambda b, _: b.set_revealed(False))
        self._main_infobar_label: Gtk.Label = self._builder.get_object("main_infobar_label")
        self._main_infobar.set_revealed(False)
        self._statusbar: Gtk.Statusbar = self._builder.get_object('statusbar')
        self._context = self._statusbar.get_context_id(APP_PACKAGE_NAME)
89
        self._app_version: Gtk.Label = self._builder.get_object('app_version')
90
        self._app_version.set_label(f"{APP_NAME} v{APP_VERSION}")
Roberto Leinardi's avatar
Roberto Leinardi committed
91 92
        self._about_dialog: Gtk.AboutDialog = self._builder.get_object("about_dialog")
        self._init_about_dialog()
93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118
        self._info_name_entry: Gtk.Entry = self._builder.get_object('info_name_entry')
        self._info_vbios_entry: Gtk.Entry = self._builder.get_object('info_vbios_entry')
        self._info_driver_entry: Gtk.Entry = self._builder.get_object('info_driver_entry')
        self._info_pcie_entry: Gtk.Entry = self._builder.get_object('info_pcie_entry')
        self._info_cuda_entry: Gtk.Entry = self._builder.get_object('info_cuda_entry')
        self._info_uuid_entry: Gtk.Entry = self._builder.get_object('info_uuid_entry')
        self._info_memory_entry: Gtk.Entry = self._builder.get_object('info_memory_entry')
        self._info_memory_interface_entry: Gtk.Entry = self._builder.get_object('info_memory_interface_entry')
        self._info_memory_usage_entry: Gtk.Entry = self._builder.get_object('info_memory_usage_entry')
        self._info_gpu_usage_entry: Gtk.Entry = self._builder.get_object('info_gpu_usage_entry')
        self._info_encoder_usage_entry: Gtk.Entry = self._builder.get_object('info_encoder_usage_entry')
        self._info_decoder_usage_entry: Gtk.Entry = self._builder.get_object('info_decoder_usage_entry')
        self._power_draw_entry: Gtk.Entry = self._builder.get_object('power_draw_entry')
        self._power_limit_entry: Gtk.Entry = self._builder.get_object('power_limit_entry')
        self._power_default_entry: Gtk.Entry = self._builder.get_object('power_default_entry')
        self._power_min_entry: Gtk.Entry = self._builder.get_object('power_min_entry')
        self._power_enforced_entry: Gtk.Entry = self._builder.get_object('power_enforced_entry')
        self._power_max_entry: Gtk.Entry = self._builder.get_object('power_max_entry')
        self._clocks_graphics_current_entry: Gtk.Entry = self._builder.get_object('clocks_graphics_current_entry')
        self._clocks_graphics_max_entry: Gtk.Entry = self._builder.get_object('clocks_graphics_max_entry')
        self._clocks_sm_current_entry: Gtk.Entry = self._builder.get_object('clocks_sm_current_entry')
        self._clocks_sm_max_entry: Gtk.Entry = self._builder.get_object('clocks_sm_max_entry')
        self._clocks_memory_current_entry: Gtk.Entry = self._builder.get_object('clocks_memory_current_entry')
        self._clocks_memory_max_entry: Gtk.Entry = self._builder.get_object('clocks_memory_max_entry')
        self._clocks_video_current_entry: Gtk.Entry = self._builder.get_object('clocks_video_current_entry')
        self._clocks_video_max_entry: Gtk.Entry = self._builder.get_object('clocks_video_max_entry')
119 120
        self._overclock_gpu_offset_entry: Gtk.Entry = self._builder.get_object('overclock_gpu_offset_entry')
        self._overclock_mem_offset_entry: Gtk.Entry = self._builder.get_object('overclock_mem_offset_entry')
121 122 123 124
        self._info_memory_usage_levelbar: Gtk.LevelBar = self._builder.get_object('info_memory_usage_levelbar')
        self._info_gpu_usage_levelbar: Gtk.LevelBar = self._builder.get_object('info_gpu_usage_levelbar')
        self._info_encoder_usage_levelbar: Gtk.LevelBar = self._builder.get_object('info_encoder_usage_levelbar')
        self._info_decoder_usage_levelbar: Gtk.LevelBar = self._builder.get_object('info_decoder_usage_levelbar')
Roberto Leinardi's avatar
Roberto Leinardi committed
125 126 127 128
        self._temp_gpu_value: Gtk.Label = self._builder.get_object('temp_gpu_value')
        self._temp_max_gpu_value: Gtk.Label = self._builder.get_object('temp_max_gpu_value')
        self._temp_slowdown_value: Gtk.Label = self._builder.get_object('temp_slowdown_value')
        self._temp_shutdown_value: Gtk.Label = self._builder.get_object('temp_shutdown_value')
129
        self._fan_duty: Tuple = (
Roberto Leinardi's avatar
Roberto Leinardi committed
130 131 132 133 134 135
            self._builder.get_object('fan_duty_0'),
            self._builder.get_object('fan_duty_1'),
            self._builder.get_object('fan_duty_2'),
            self._builder.get_object('fan_duty_3'),
            self._builder.get_object('fan_duty_4')
        )
136
        self._fan_rpm: Tuple = (
Roberto Leinardi's avatar
Roberto Leinardi committed
137 138 139 140 141 142
            self._builder.get_object('fan_rpm_0'),
            self._builder.get_object('fan_rpm_1'),
            self._builder.get_object('fan_rpm_2'),
            self._builder.get_object('fan_rpm_3'),
            self._builder.get_object('fan_rpm_4')
        )
143 144 145
        self._fan_warning_label: Gtk.Label = self._builder.get_object('fan_warning_label')
        self._overclock_warning_label: Gtk.Label = self._builder.get_object('overclock_warning_label')
        self._fan_profile_frame: Gtk.Frame = self._builder.get_object('fan_profile_frame')
146
        self._overclock_frame: Gtk.Frame = self._builder.get_object('overclock_frame')
147 148
        self._power_limit_scale: Gtk.Scale = self._builder.get_object('power_limit_scale')
        self._power_limit_adjustment: Gtk.Adjustment = self._builder.get_object('power_limit_adjustment')
149
        self._fan_apply_button: Gtk.Button = self._builder.get_object('fan_apply_button')
150
        self._overclock_apply_button: Gtk.Button = self._builder.get_object('overclock_apply_button')
151
        self._power_limit_apply_button: Gtk.Button = self._builder.get_object('power_limit_apply_button')
152
        self._fan_liststore: Gtk.ListStore = self._builder.get_object('fan_profile_liststore')
153
        self._overclock_liststore: Gtk.ListStore = self._builder.get_object('overclock_profile_liststore')
154
        self._fan_combobox: Gtk.ComboBox = self._builder.get_object('fan_profile_combobox')
155
        self._overclock_combobox: Gtk.ComboBox = self._builder.get_object('overclock_profile_combobox')
156
        fan_scrolled_window: Gtk.ScrolledWindow = self._builder.get_object('fan_scrolled_window')
157
        self._fan_edit_button: Gtk.Button = self._builder.get_object('fan_edit_button')
158
        self._overclock_edit_button: Gtk.Button = self._builder.get_object('overclock_edit_button')
159
        self._init_plot_charts(fan_scrolled_window)
160 161
        if not is_dazzle_version_supported():
            self._builder.get_object("historical_data_button").set_sensitive(False)
Roberto Leinardi's avatar
Roberto Leinardi committed
162 163 164 165 166 167

    def _init_about_dialog(self) -> None:
        self._about_dialog.set_program_name(APP_NAME)
        self._about_dialog.set_version(APP_VERSION)
        self._about_dialog.set_website(APP_SOURCE_URL)
        self._about_dialog.connect("delete-event", hide_on_delete)
168
        self._about_dialog.connect("response", hide_on_delete)
Roberto Leinardi's avatar
Roberto Leinardi committed
169 170 171 172 173 174 175

    def show(self) -> None:
        self._presenter.on_start()
        self._init_app_indicator()

    def _init_app_indicator(self) -> None:
        if AppIndicator3:
176
            # Setting icon name in new() as '', because new() wants an icon path
Roberto Leinardi's avatar
Roberto Leinardi committed
177
            self._app_indicator = AppIndicator3.Indicator \
178 179 180 181 182
                .new(APP_ID, '', AppIndicator3.IndicatorCategory.HARDWARE)
            # Set the actual icon by name. If the app is not installed system-wide, the icon won't show up,
            # otherwise it will show up correctly. The set_icon_full() function needs a description for accessibility
            # purposes. I gave it the APP_NAME (should be 'gwe', maybe change it to 'GreenWithEnvy' in the future)
            self._app_indicator.set_icon_full(APP_ICON_NAME_SYMBOLIC, APP_NAME)
Roberto Leinardi's avatar
Roberto Leinardi committed
183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201
            if self._settings_interactor.get_bool('settings_show_app_indicator'):
                self._app_indicator.set_status(AppIndicator3.IndicatorStatus.ACTIVE)
            else:
                self._app_indicator.set_status(AppIndicator3.IndicatorStatus.PASSIVE)
            self._app_indicator.set_menu(self._main_menu)

    def show_main_infobar_message(self, message: str, markup: bool = False) -> None:
        if markup:
            self._main_infobar_label.set_markup(message)
        else:
            self._main_infobar_label.set_label(message)
        self._main_infobar.set_revealed(True)

    def toggle_window_visibility(self) -> None:
        if self._window.props.visible:
            self._window.hide()
        else:
            self._window.show()

202 203 204
    def get_power_limit(self) -> Tuple[int, int]:
        return 0, self._power_limit_adjustment.get_value()

Roberto Leinardi's avatar
Roberto Leinardi committed
205 206 207 208 209 210 211
    def show_about_dialog(self) -> None:
        self._about_dialog.show()

    def set_statusbar_text(self, text: str) -> None:
        self._statusbar.remove_all(self._context)
        self._statusbar.push(self._context, text)

212
    def refresh_status(self, status: Optional[Status], gpu_index: int) -> None:
Roberto Leinardi's avatar
Roberto Leinardi committed
213 214
        LOG.debug('view status')
        if status:
215
            gpu_status = status.gpu_status_list[gpu_index]
Roberto Leinardi's avatar
Roberto Leinardi committed
216 217
            if self._first_refresh:
                self._first_refresh = False
218 219 220
                self._set_entry_text(self._info_name_entry, gpu_status.info.name)
                self._set_entry_text(self._info_vbios_entry, gpu_status.info.vbios)
                self._set_entry_text(self._info_driver_entry, gpu_status.info.driver)
221
                self._set_entry_text(self._info_cuda_entry, "{}", gpu_status.info.cuda_cores)
222
                self._set_entry_text(self._info_uuid_entry, gpu_status.info.uuid)
223 224 225
                self._set_entry_text(self._info_memory_interface_entry, "{} bit", gpu_status.info.memory_interface)
                self._set_entry_text(self._power_min_entry, "{} W", gpu_status.power.minimum)
                self._set_entry_text(self._power_max_entry, "{} W", gpu_status.power.maximum)
226
                self._set_label_markup(self._temp_max_gpu_value,
227
                                       "<span size=\"large\">{}</span> °C", gpu_status.temp.maximum)
228
                self._set_label_markup(self._temp_slowdown_value,
229
                                       "<span size=\"large\">{}</span> °C", gpu_status.temp.slowdown)
230
                self._set_label_markup(self._temp_shutdown_value,
231
                                       "<span size=\"large\">{}</span> °C", gpu_status.temp.shutdown)
232
                self._overclock_frame.set_sensitive(gpu_status.overclock.available)
233 234 235
                self._overclock_warning_label.set_visible(not gpu_status.overclock.available)
                self._fan_profile_frame.set_sensitive(gpu_status.fan.control_allowed)
                self._fan_warning_label.set_visible(not gpu_status.fan.control_allowed)
236 237 238 239
                self._remove_level_bar_offsets(self._info_gpu_usage_levelbar)
                self._remove_level_bar_offsets(self._info_memory_usage_levelbar)
                self._remove_level_bar_offsets(self._info_encoder_usage_levelbar)
                self._remove_level_bar_offsets(self._info_decoder_usage_levelbar)
240 241 242 243 244
                minimum = gpu_status.power.minimum
                maximum = gpu_status.power.maximum
                default = gpu_status.power.default
                if minimum is not None and maximum is not None and default is not None and minimum != maximum:
                    limit = gpu_status.power.limit
245 246 247 248
                    self._power_limit_adjustment.set_lower(minimum)
                    self._power_limit_adjustment.set_upper(maximum)
                    self._power_limit_adjustment.set_value(limit)
                    self._power_limit_scale.clear_marks()
249
                    self._power_limit_scale.add_mark(default, Gtk.PositionType.BOTTOM, f"{default:.0f}")
250 251 252 253 254
                    self._power_limit_scale.set_sensitive(True)
                    self._power_limit_apply_button.set_sensitive(True)
                else:
                    self._power_limit_scale.set_sensitive(False)
                    self._power_limit_apply_button.set_sensitive(False)
255

256
            self._set_entry_text(self._info_pcie_entry, "{}x Gen{} @ {}x Gen{}",
257
                                 gpu_status.info.pcie_max_link,
258 259 260
                                 gpu_status.info.pcie_max_generation,
                                 gpu_status.info.pcie_current_link,
                                 gpu_status.info.pcie_current_generation)
261
            self._set_entry_text(self._info_memory_entry, "{} MiB / {} MiB",
262 263
                                 gpu_status.info.memory_used,
                                 gpu_status.info.memory_total)
264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279
            self._set_entry_text(self._info_memory_usage_entry, "{}%", gpu_status.info.memory_usage)
            self._set_entry_text(self._info_gpu_usage_entry, "{}%%", gpu_status.info.gpu_usage)
            self._set_entry_text(self._info_encoder_usage_entry, "{}%", gpu_status.info.encoder_usage)
            self._set_entry_text(self._info_decoder_usage_entry, "{}%", gpu_status.info.decoder_usage)
            self._set_entry_text(self._power_draw_entry, "{:.2f} W", gpu_status.power.draw)
            self._set_entry_text(self._power_limit_entry, "{:.0f} W", gpu_status.power.limit)
            self._set_entry_text(self._power_default_entry, "{:.0f} W", gpu_status.power.default)
            self._set_entry_text(self._power_enforced_entry, "{:.0f} W", gpu_status.power.enforced)
            self._set_entry_text(self._clocks_graphics_current_entry, "{} MHz", gpu_status.clocks.graphic_current)
            self._set_entry_text(self._clocks_graphics_max_entry, "{} MHz", gpu_status.clocks.graphic_max)
            self._set_entry_text(self._clocks_sm_current_entry, "{} MHz", gpu_status.clocks.sm_current)
            self._set_entry_text(self._clocks_sm_max_entry, "{} MHz", gpu_status.clocks.sm_max)
            self._set_entry_text(self._clocks_memory_current_entry, "{} MHz", gpu_status.clocks.memory_current)
            self._set_entry_text(self._clocks_memory_max_entry, "{} MHz", gpu_status.clocks.memory_max)
            self._set_entry_text(self._clocks_video_current_entry, "{} MHz", gpu_status.clocks.video_current)
            self._set_entry_text(self._clocks_video_max_entry, "{} MHz", gpu_status.clocks.video_max)
280
            self._set_level_bar(self._info_gpu_usage_levelbar, gpu_status.info.gpu_usage)
281
            self._set_level_bar(self._info_memory_usage_levelbar, gpu_status.info.memory_usage)
282 283
            self._set_level_bar(self._info_encoder_usage_levelbar, gpu_status.info.encoder_usage)
            self._set_level_bar(self._info_decoder_usage_levelbar, gpu_status.info.decoder_usage)
284
            if gpu_status.overclock.available:
285 286
                self._set_entry_text(self._overclock_gpu_offset_entry, "{} MHz", gpu_status.overclock.gpu_offset)
                self._set_entry_text(self._overclock_mem_offset_entry, "{} MHz", gpu_status.overclock.memory_offset)
287
            self._set_label_markup(self._temp_gpu_value,
288
                                   "<span size=\"xx-large\">{}</span> °C", gpu_status.temp.gpu)
Roberto Leinardi's avatar
Roberto Leinardi committed
289
            for index, value in enumerate(self._fan_duty):
290
                if gpu_status.fan.fan_list and index < len(gpu_status.fan.fan_list):
291
                    self._set_label_markup(value,
292
                                           "<span size=\"large\">{}</span> %", gpu_status.fan.fan_list[index][0])
293
                    self._set_label_markup(self._fan_rpm[index],
294
                                           "<span size=\"large\">{}</span> RPM", gpu_status.fan.fan_list[index][1])
Roberto Leinardi's avatar
Roberto Leinardi committed
295 296 297 298
                else:
                    value.set_visible(False)
                    self._fan_rpm[index].set_visible(False)

299 300 301 302 303
            if self._app_indicator:
                if self._settings_interactor.get_bool('settings_show_app_indicator'):
                    self._app_indicator.set_status(AppIndicator3.IndicatorStatus.ACTIVE)
                else:
                    self._app_indicator.set_status(AppIndicator3.IndicatorStatus.PASSIVE)
304
                if self._settings_interactor.get_bool('settings_app_indicator_show_gpu_temp') and gpu_status.temp.gpu:
305
                    self._app_indicator.set_label(f" {gpu_status.temp.gpu}°C", " XX°C")
306 307
                else:
                    self._app_indicator.set_label("", "")
Roberto Leinardi's avatar
Roberto Leinardi committed
308

309
    @staticmethod
310 311
    def _set_entry_text(label: Gtk.Entry, text: Optional[str], *args: Any) -> None:
        if text is not None and None not in args:
312
            label.set_sensitive(True)
313
            label.set_text(text.format(*args))
314 315 316 317 318
        else:
            label.set_sensitive(False)
            label.set_text('')

    @staticmethod
319 320
    def _set_label_markup(label: Gtk.Label, markup: Optional[str], *args: Any) -> None:
        if markup is not None and None not in args:
321
            label.set_sensitive(True)
322
            label.set_markup(markup.format(*args))
323 324 325 326
        else:
            label.set_sensitive(False)
            label.set_markup('')

327 328 329 330 331 332 333
    @staticmethod
    def _remove_level_bar_offsets(levelbar: Gtk.LevelBar) -> None:
        levelbar.remove_offset_value("low")
        levelbar.remove_offset_value("high")
        levelbar.remove_offset_value("full")
        levelbar.remove_offset_value("alert")

Roberto Leinardi's avatar
Roberto Leinardi committed
334
    @staticmethod
335 336
    def _set_level_bar(levelbar: Gtk.LevelBar, value: Optional[int]) -> None:
        if value is not None:
337
            levelbar.set_value(value / 100)
338
            levelbar.set_sensitive(True)
Roberto Leinardi's avatar
Roberto Leinardi committed
339 340
        else:
            levelbar.set_value(0)
341
            levelbar.set_sensitive(False)
Roberto Leinardi's avatar
Roberto Leinardi committed
342

343
    def refresh_chart(self, profile: Optional[FanProfile] = None, reset: bool = False) -> None:
Roberto Leinardi's avatar
Roberto Leinardi committed
344 345 346
        if profile is None and reset is None:
            raise ValueError("Both parameters are note!")

347
        if reset:
Roberto Leinardi's avatar
Roberto Leinardi committed
348 349
            self._plot_chart({})
        else:
350 351 352 353 354 355 356 357 358 359 360 361
            self._plot_chart(get_fan_profile_data(profile))

    def refresh_fan_profile_combobox(self, data: List[Tuple[int, str]], active: Optional[int]) -> None:
        self._fan_liststore.clear()
        for item in data:
            self._fan_liststore.append([item[0], item[1]])
        self._fan_combobox.set_model(self._fan_liststore)
        self._fan_combobox.set_sensitive(len(self._fan_liststore) > 1)
        if active is not None:
            self._fan_combobox.set_active(active)
        else:
            self.refresh_chart(reset=True)
Roberto Leinardi's avatar
Roberto Leinardi committed
362

363 364 365 366 367
    def set_apply_fan_profile_button_enabled(self, enabled: bool) -> None:
        self._fan_apply_button.set_sensitive(enabled)

    def set_edit_fan_profile_button_enabled(self, enabled: bool) -> None:
        self._fan_edit_button.set_sensitive(enabled)
Roberto Leinardi's avatar
Roberto Leinardi committed
368

369 370 371 372 373 374 375 376 377 378 379 380 381 382 383
    def set_apply_overclock_profile_button_enabled(self, enabled: bool) -> None:
        self._overclock_apply_button.set_sensitive(enabled)

    def set_edit_overclock_profile_button_enabled(self, enabled: bool) -> None:
        self._overclock_edit_button.set_sensitive(enabled)

    def refresh_overclock_profile_combobox(self, data: List[Tuple[int, str]], active: Optional[int]) -> None:
        self._overclock_liststore.clear()
        for item in data:
            self._overclock_liststore.append([item[0], item[1]])
        self._overclock_combobox.set_model(self._overclock_liststore)
        self._overclock_combobox.set_sensitive(len(self._overclock_liststore) > 1)
        if active is not None:
            self._overclock_combobox.set_active(active)

Roberto Leinardi's avatar
Roberto Leinardi committed
384
    # pylint: disable=attribute-defined-outside-init
385
    def _init_plot_charts(self, fan_scrolled_window: Gtk.ScrolledWindow) -> None:
Roberto Leinardi's avatar
Roberto Leinardi committed
386 387 388 389 390 391 392 393 394 395 396 397 398 399 400 401 402 403
        self._fan_figure = Figure(figsize=(8, 6), dpi=72, facecolor='#00000000')
        self._fan_canvas = FigureCanvas(self._fan_figure)  # a Gtk.DrawingArea+
        self._fan_axis = self._fan_figure.add_subplot(111)
        self._fan_line, = init_plot_chart(
            fan_scrolled_window,
            self._fan_figure,
            self._fan_canvas,
            self._fan_axis
        )

    def _plot_chart(self, data: Dict[int, int]) -> None:
        sorted_data = OrderedDict(sorted(data.items()))
        temperature = list(sorted_data.keys())
        duty = list(sorted_data.values())
        self._fan_line.set_xdata(temperature)
        self._fan_line.set_ydata(duty)
        self._fan_canvas.draw()
        self._fan_canvas.flush_events()