Exemple #1
0
 def launch_redit(self):
     if self.recipe_editor is None:
         self.recipe_editor = RecipeEditor(self, self.be)
         self.recipe_editor.signal_exit.connect(self.handle_redit_exit)
         self.recipe_editor.signal_backend_updated.connect(self.handle_redit_backend_update)
         self.recipe_editor.show()
Exemple #2
0
class YaccMain(QtGui.QMainWindow):
    def __init__(self, parent=None):
        self.is_init = False
        QtGui.QWidget.__init__(self, parent)
        self.ui = Ui_yacc_main_window()
        self.ui.setupUi(self)

        # extra UI setup: cross-platform monospace font
        mfont = self.get_monospace_font()
        self.ui.output_box.setFont(mfont)

        # set up backend and load recipes
        self.config_file = CONFIG_FILE
        self.load_config()

        # Fill in UI defaults
        self.ui.totalvol_box.setText('10')
        self.ui.nic_box.setText('3')
        self.ui.vg_box.setText('70')

        # add combo box options
        self.ui.mix_box.addItem('Juice from Ingredients', 'from_ingredients')
        self.ui.mix_box.addItem('Juice from Concentrate', 'from_concentrate')
        self.ui.mix_box.addItem('Concentrate', 'concentrate')

        # status bar
        self.status_config_message_label = QtGui.QLabel()
        self.ui.status_bar.addPermanentWidget(self.status_config_message_label)

        # recipe editor
        self.recipe_editor = None

        # saved values of nic/vg for switching in and out of concentrate mode
        self.save_nic = None
        self.save_vg = None

        # signals/slots
        self.ui.actionExit.triggered.connect(self.exit)
        self.ui.update_button.clicked.connect(self.update_mix)
        self.ui.recipe_box.currentIndexChanged.connect(self.update_mix)
        self.ui.recipe_box.currentIndexChanged.connect(self.update_recipe_status)
        self.ui.mix_box.currentIndexChanged.connect(self.handle_mixtype_change)
        self.ui.totalvol_box.textChanged.connect(self.update_mix)
        self.ui.nic_box.textChanged.connect(self.update_mix)
        self.ui.vg_box.textChanged.connect(self.update_mix)
        self.ui.reload_button.clicked.connect(self.load_config)
        self.ui.redit_button.clicked.connect(self.launch_redit)
        self.ui.actionAdd_Recipes.triggered.connect(self.launch_redit)

        self.is_init = True
        self.update_mix_type()
        self.update_mix()
        self.update_config_status()
        self.update_recipe_status()

    def get_monospace_font(self):
        preferred_fonts_windows = ['Courier New', 'Lucida Console']
        preferred_fonts_linux = ['Noto Mono', 'Monospace']

        if 'Windows' in platform.system():
            pfonts = preferred_fonts_windows
        else:
            pfonts = preferred_fonts_linux

        for fontname in pfonts:
            font = QFont(fontname)
            font.setPointSize(12)
            info = QFontInfo(font)
            if info.fixedPitch():
                return font

        print('Warning: no font in preferred list %s found!'%pfonts)
        font = QFont()
        font.setStyleHint(QFont.Monospace)
        info = QFontInfo(font)
        print('Selected font family: %s'%info.family())

    def update_mix(self):
        if not self.is_init:
            return

        mix_inputs = self.check_inputs()
        if mix_inputs is None:
            return

        mix = self.be.calculate_mix(**mix_inputs)
        if mix is None:
            # calculate_mix returns None if the recipe can't be found, so just bail
            # This shouldn't really happen since recipe_box is only populated by items that
            # backend.get_recipes returns
            mix = 'Backend Error!'

        self.ui.output_box.setPlainText(mix)

    def update_mix_type(self):
        if not self.is_init:
            return

        mix_type = self.ui.mix_box.itemData(self.ui.mix_box.currentIndex())
        if mix_type == 'concentrate':
            # making concentrate, so disable nic and VG
            self.save_nic = self.ui.nic_box.text()
            self.save_vg  = self.ui.vg_box.text()
            self.ui.nic_box.setText('')
            self.ui.nic_box.setEnabled(False)
            self.ui.vg_box.setText('')
            self.ui.vg_box.setEnabled(False)

        else:
            # default, unlock nic and vg boxes
            self.ui.nic_box.setEnabled(True)
            if self.save_nic is not None:
                self.ui.nic_box.setText(self.save_nic)
                self.save_nic = None

            self.ui.vg_box.setEnabled(True)
            if self.save_vg is not None:
                self.ui.vg_box.setText(self.save_vg)
                self.save_vg = None
        
    def update_config_status(self):
        cfg = self.be.get_config()
        self.status_config_message_label.setText('Nicotine: %d mg/mL %s; Recipes Loaded: %d'%(
                                                cfg['nic_strength'], cfg['nic_base'].upper(), cfg['n_recipes']))
    
    def load_config(self):
        is_init_last = self.is_init
        self.is_init = False # make sure update_mix doesn't fail when the config is cleared out
        self.be = Backend(self.config_file)
        selected_recipe = self.ui.recipe_box.currentText()
        self.populate_recipe_box(selected_recipe)

        self.is_init = is_init_last
        self.update_mix()

    def populate_recipe_box(self, selected_recipe=None):
        is_init_last = self.is_init
        self.is_init = False
        self.ui.recipe_box.clear()
        for recipe in self.be.get_recipes():
            self.ui.recipe_box.addItem(recipe)

        if selected_recipe is not None:
            selected_index = self.ui.recipe_box.findText(selected_recipe)
            if selected_index != -1:
                self.ui.recipe_box.setCurrentIndex(selected_index)
        self.is_init = is_init_last

    def update_recipe_status(self):
        if not self.is_init:
            return

        current_recipe = str(self.ui.recipe_box.currentText())
        total_flav = self.be.get_total_flavor(current_recipe)
        if total_flav is None:
            self.ui.status_bar.showMessage('Recipe %s not found!'%current_recipe)
        else:
            total_flav = total_flav * 100.0
            self.ui.status_bar.showMessage('Recipe: %s; Total Flavor: %.1f%%; Max VG: %.1f%%'%(
                                            current_recipe, total_flav, 100.0-total_flav))

    def check_inputs(self):
        """ Read the mix parameters from the UI elements and convert numbers to type float.
            If float() returns a value error, mark that input box red and return None, otherwise
            return a tuple of parameters that can be passed directly to backend.calculate_mix()
        """

        # We can always make these strings, and strings are expected anyway so no error checking
        # calculate_mix will make sure that the selected recipe exists
        recipe = str(self.ui.recipe_box.currentText())
        mix = str(self.ui.mix_box.itemData(self.ui.mix_box.currentIndex()))


        # NOTE: names here must match the inputs of Backend.calculate_mix
        inputs_out = {'recipe_name': recipe,
                      'mix': mix}

        inputs_check = [('totalvol', self.ui.totalvol_box)]

        if mix != 'concentrate':
            inputs_check += [('nic', self.ui.nic_box),
                             ('vg', self.ui.vg_box)]

        err = False
        for (field, box) in inputs_check:
            try:
                inputs_out[field] = float(box.text())
                if inputs_out[field] < 0:
                    raise ValueError
                box.setStyleSheet('')
            except ValueError:
                box.setStyleSheet('background-color: rgb(255, 102, 102);')
                err = True

        return None if err else inputs_out

    def launch_redit(self):
        if self.recipe_editor is None:
            self.recipe_editor = RecipeEditor(self, self.be)
            self.recipe_editor.signal_exit.connect(self.handle_redit_exit)
            self.recipe_editor.signal_backend_updated.connect(self.handle_redit_backend_update)
            self.recipe_editor.show()

    def handle_mixtype_change(self):
        # wrapper here for change event on mix type box
        # Do this rather than assigning 2 slots so it executes in definite order
        self.update_mix_type()
        self.update_mix()

    @pyqtSlot()
    def handle_redit_backend_update(self):
        selected_recipe = self.ui.recipe_box.currentText()
        self.populate_recipe_box(selected_recipe)
        self.update_recipe_status()
        self.update_mix()

    @pyqtSlot(str)
    def handle_redit_exit(self, text):
        self.ui.output_box.setPlainText('RBUILD EXIT:' + text)
        self.recipe_editor = None

    def exit(self):
        QtCore.QCoreApplication.instance().quit()