class UserRequestForm(ModelFormWidget):

    TITLE = 'Access request'

    MODEL = AccessRequest

    FIELDSETS = [
        'resource',
        '_requirements',
        'reason'
    ]

    CREATE_BTN_LABEL = '<i class="plus icon"></i> Submit request'
    HAS_CANCEL_BTN_ON_ADD = False
    HAS_CANCEL_BTN_ON_EDIT = False

    def __init__(self, *args, **kwargs):
        super().__init__(*args, **kwargs)

        self._user_request_app = kwargs.get('user_request_app')

        self._requirements = ControlLabel('Requirements', visible=False)

        self.resource.changed_event = self.__resource_changed_evt


    def __resource_changed_evt(self):

        if self.resource.value:
            resource = Resource.objects.get(pk=self.resource.value)
            self._requirements.value = f'<h3>{resource.name}</h3>{resource.access_req}'
            self._requirements.show()
        else:
            self._requirements.hide()


    def validate_object(self, obj):
        obj.requested_by = PyFormsMiddleware.user()
        return obj

    def save_object(self, obj, **kwargs):
        self._user_request_app.populate_list()
        return super().save_object(obj, **kwargs)

    def delete_event(self):
        res = super().delete_event()
        self._user_request_app.populate_list()
        return res
Пример #2
0
class KaryML_Main(BaseWidget):
    LENGTH_FEATURE = "Chromosome length"
    AREA_FEATURE = "Chromosome area"
    CENTROMERIC_INDEX_FEATURE = "Centromeric Index"
    BANDING_PATTERN_FEATURE = "Banding pattern"
    FEATURES = [
        LENGTH_FEATURE,
        CENTROMERIC_INDEX_FEATURE,
        BANDING_PATTERN_FEATURE
    ]
    APP_NAME = 'KaryML Framework'

    def __init__(self):
        super(KaryML_Main, self).__init__(self.APP_NAME)
        self._app_title = ControlLabel(self.APP_NAME)
        self._input_image_path = ControlFile('Input image')
        self._pairs_path = ControlFile('Expected karyotype (optional)')

        self._features_label = ControlLabel("Chose features to be extracted")

        self._f1_check_box = ControlCheckBox(label=self.LENGTH_FEATURE, default=True)
        self._f1_check_box.changed_event = self._f1_check_box_changed
        self._f2_check_box = ControlCheckBox(label=self.CENTROMERIC_INDEX_FEATURE, default=True)
        self._f2_check_box.changed_event = self._f2_check_box_changed
        self._f3_check_box = ControlCheckBox(label=self.BANDING_PATTERN_FEATURE, default=True)
        self._f3_check_box.changed_event = self._f3_check_box_changed
        self._f4_check_box = ControlCheckBox(label=self.AREA_FEATURE, default=True)
        self._f4_check_box.changed_event = self._f4_check_box_changed

        self._eu_dist = ControlButton(EUCLIDEAN_DISTANCE)
        self._we_eu_dist = ControlButton(WEIGHTED_EUCLIDEAN_DISTANCE)
        self._man_dist = ControlButton(MANHATTAN_DISTANCE)

        self._dist_label = ControlLabel(label="Distance to use: " + EUCLIDEAN_DISTANCE.upper(),
                                        default="Distance to use: " + EUCLIDEAN_DISTANCE)

        self._f1_w = ControlSlider(label="Chromosome length", default=25, minimum=0, maximum=100, visible=False)
        self._f2_w = ControlSlider(label="  Centromeric Index", default=25, minimum=0, maximum=100, visible=False)
        self._f3_w = ControlSlider(label="      Banding pattern", default=25, minimum=0, maximum=100, visible=False)
        self._f4_w = ControlSlider(label="Chromosome area", default=25, minimum=0, maximum=100, visible=False)

        self._epochs_no = ControlSlider(label="    Epochs Nr.", default=200000, minimum=50000, maximum=400000)
        self._rows = ControlSlider(label="     Map rows", default=50, minimum=10, maximum=100)
        self._cols = ControlSlider(label="Map columns", default=50, minimum=10, maximum=100)

        self.errors_label_text = ControlLabel(label="Errors:", default="Errors:", visible=True)
        self.errors_label = ControlLabel(label="Errors", default="", visible=False)
        self.info_label_text = ControlLabel(label="Info:", default="Info:", visible=True)
        self.info_label = ControlLabel(label="Info", default="", visible=False)
        self._button = ControlButton('Start {}'.format(self.APP_NAME))
        self._button.value = self._runKarySomAction

        self._eu_dist.value = self.__dist_changed_eu
        self._we_eu_dist.value = self.__dist_changed_we_eu
        self._man_dist.value = self.__dist_changed_man
        self.t = None

    @staticmethod
    def set_message(label, message):
        label.value = message
        label.show()

    def get_number_of_selected_features(self):
        return len([2 ** j for j in range(4) if [self._f1_check_box,
                                                 self._f2_check_box,
                                                 self._f3_check_box,
                                                 self._f4_check_box][j].value])

    def _runKarySomAction(self):
        """Button action event"""
        dist_lbl = self.manage_pre_run_gui()
        self.errors_label.hide()

        if self.get_number_of_selected_features() < 1:
            self.set_message(self.errors_label, "At least one feature is required")
            return

        cfg = self.__initialize_cfg(dist_lbl)
        if dist_lbl == WEIGHTED_EUCLIDEAN_DISTANCE and self.get_sum_of_weights() != 100:
            self.set_message(self.errors_label, "Sum of weights must be 100")
            return

        if not os.path.exists(self._input_image_path.value):
            self.set_message(self.errors_label, "Image path {} doesn't exists".format(self._input_image_path.value))
            return
        print("Start processing using cfg:")
        pprint.pprint(cfg)

        self.t = Thread(target=start_process_for_one_image, args=[cfg["input"], cfg["mdist"], "", sum(cfg["features"]),
                                                                  cfg["epochs"], cfg["rows"], cfg["cols"],
                                                                  cfg["weights"], self])
        self.t.start()

        msg = ""
        if not os.path.isfile(self._pairs_path.value):
            msg += "Expected karyotype missing or file not exists. PrecKar value won't be calculated."
        else:
            msg += "Karyotype file found. Will compute PrecKar value"
            dir_path = '.'.join(self._input_image_path.value.split('.')[:-1])
            if not os.path.isdir(dir_path):
                os.makedirs(dir_path)
            shutil.copy2(self._pairs_path.value, os.path.join(dir_path, "pairs.txt"))
        self.set_message(self.info_label, msg + "\n" + "Start KarySOM for current configuration. Processing...")

    def manage_pre_run_gui(self):
        if self.info_label.visible is True:
            self.info_label.hide()
        if self._dist_label.value.split()[len(self._dist_label.value.split()) - 2] == "Weighted":
            dist_lbl = " ".join(self._dist_label.value.split()[len(self._dist_label.value.split()) - 2:])
        else:
            dist_lbl = self._dist_label.value.split()[len(self._dist_label.value.split()) - 1]
        return dist_lbl

    def __initialize_cfg(self, dist_lbl):
        cfg = {"distance": dist_lbl, "feature_names": [self.FEATURES[i] for i in [j for j in range(3) if
                                                                                  [self._f1_check_box.value,
                                                                                   self._f2_check_box.value,
                                                                                   self._f3_check_box.value,
                                                                                   self._f4_check_box.value][j]]],
               "features": [2 ** j for j in range(4) if [self._f1_check_box,
                                                         self._f2_check_box,
                                                         self._f3_check_box,
                                                         self._f4_check_box][j].value], "epochs": self._epochs_no.value,
               "input": self._input_image_path.value, "pairs": self._pairs_path.value, "rows": self._rows.value,
               "cols": self._cols.value, "weights": dict()}
        if self._f1_check_box.value:
            cfg["weights"][1] = self._f1_w.value / 100
        if self._f2_check_box.value:
            cfg["weights"][2] = self._f2_w.value / 100
        if self._f3_check_box.value:
            cfg["weights"][3] = self._f3_w.value / 100
        if self._f4_check_box.value:
            cfg["weights"][4] = self._f4_w.value / 100

        cfg["mdist"] = False
        if dist_lbl == MANHATTAN_DISTANCE:
            cfg["mdist"] = True
        return cfg

    def __dist_changed_eu(self):
        self._dist_label.value = "Distance to use: " + EUCLIDEAN_DISTANCE
        self._dist_label.label = "Distance to use: " + EUCLIDEAN_DISTANCE.upper()
        self._f1_w.hide()
        self._f2_w.hide()
        self._f3_w.hide()
        self._f4_w.hide()
        self.info_label.hide()

    def __dist_changed_we_eu(self):
        self._dist_label.value = "Distance to use: " + WEIGHTED_EUCLIDEAN_DISTANCE
        self._dist_label.label = "Distance to use: " + WEIGHTED_EUCLIDEAN_DISTANCE.upper()
        if self._f1_check_box.value:
            self._f1_w.show()
        if self._f2_check_box.value:
            self._f2_w.show()
        if self._f3_check_box.value:
            self._f3_w.show()
        if self._f4_check_box.value:
            self._f4_w.show()
        self.info_label.hide()

    def __dist_changed_man(self):
        self._dist_label.value = "Distance to use: " + MANHATTAN_DISTANCE
        self._dist_label.label = "Distance to use: " + MANHATTAN_DISTANCE.upper()
        self._f1_w.hide()
        self._f2_w.hide()
        self._f3_w.hide()
        self._f4_w.hide()
        self.info_label.hide()

    def get_sum_of_weights(self):
        weights_sum = 0
        if self._f1_check_box.value:
            weights_sum += self._f1_w.value
        if self._f2_check_box.value:
            weights_sum += self._f2_w.value
        if self._f3_check_box.value:
            weights_sum += self._f3_w.value
        if self._f4_check_box.value:
            weights_sum += self._f4_w.value
        return weights_sum

    def _f1_check_box_changed(self):
        if self._f1_check_box.value and "weighted" in self._dist_label.value.lower():
            self._f1_w.show()
        else:
            self._f1_w.hide()

    def _f2_check_box_changed(self):
        if self._f2_check_box.value and "weighted" in self._dist_label.value.lower():
            self._f2_w.show()
        else:
            self._f2_w.hide()

    def _f3_check_box_changed(self):
        if self._f3_check_box.value and "weighted" in self._dist_label.value.lower():
            self._f3_w.show()
        else:
            self._f3_w.hide()

    def _f4_check_box_changed(self):
        if self._f4_check_box.value and "weighted" in self._dist_label.value.lower():
            self._f4_w.show()
        else:
            self._f4_w.hide()
class PathGUI(DatasetGUI, Path, BaseWidget):

    def __init__(self, object2d=None):
        DatasetGUI.__init__(self)
        Path.__init__(self, object2d)
        BaseWidget.__init__(self, '2D Object', parent_win=object2d)

        self.create_tree_nodes()
        
        self._mark_pto_btn        = ControlButton('&Mark point',   checkable=True, icon=conf.ANNOTATOR_ICON_MARKPLACE )
        self._sel_pto_btn         = ControlButton('&Select point', default=self.__sel_pto_btn_event, icon=conf.ANNOTATOR_ICON_SELECTPOINT)
        self._del_path_btn        = ControlButton('Delete path',   default=self.__del_path_btn_event, icon=conf.ANNOTATOR_ICON_DELETEPATH, visible=False)
        self._del_point_btn       = ControlButton('Delete point',  default=self.__del_point_btn_event, icon=conf.ANNOTATOR_ICON_SELECTPOINT, visible=False)
        self._use_referencial     = ControlCheckBox('Apply')
        self._referencial_pt      = ControlText('Referencial',     changed_event=self.__referencial_pt_changed_event)

        self._interpolation_title = ControlLabel('Interpolation',  default='INTERPOLATION', visible=False)
        self._interpolation_mode  = ControlCombo('Mode',           changed_event=self.__interpolation_mode_changed_event, visible=False)
        self._interpolate_btn     = ControlButton('Apply',         default=self.__interpolate_btn_event, icon=conf.ANNOTATOR_ICON_INTERPOLATE, visible=False)
        self._remove_btn          = ControlButton('Remove dataset',default=self.__remove_path_dataset, icon=conf.ANNOTATOR_ICON_REMOVE)

        self._pickcolor   = ControlButton('Pick a color', default=self.__pick_a_color_event)

        self._show_object_name = ControlCheckBox('Show object name', default=False)
        self._show_name = ControlCheckBox('Show name', default=False)

        self._formset = [ 
            '_name',
            ('_show_name', '_show_object_name'),
            ('_referencial_pt', '_use_referencial'),
            '_remove_btn',            
            ' ',
            '_pickcolor',
            ' ',
            ('_mark_pto_btn', '_sel_pto_btn'),
            '_del_path_btn',
            '_del_point_btn',
            '_interpolation_title',
            ('_interpolation_mode', '_interpolate_btn'),
            ' '
        ]

        #### set controls ##############################################
        self._interpolation_mode.add_item("Auto", -1)
        self._interpolation_mode.add_item("Linear", 'slinear')
        self._interpolation_mode.add_item("Quadratic", 'quadratic')
        self._interpolation_mode.add_item("Cubic", 'cubic')

    ######################################################################
    ### FUNCTIONS ########################################################
    ######################################################################

    def get_velocity(self, index):
        p1 = self.get_position(index)
        p2 = self.get_position(index-1)
        if p1 is None or p2 is None: return None
        return p2[0]-p1[0], p2[1]-p1[1]

    def get_acceleration(self, index):
        v1 = self.get_velocity(index)
        v2 = self.get_velocity(index-1)
        if v1 is None or v2 is None: return None
        return v2[0]-v1[0], v2[1]-v1[1]

    def get_position_x_value(self, index):
        v = self.get_position(index)
        return v[0] if v is not None else None

    def get_position_y_value(self, index):
        v = self.get_position(index)
        return v[1] if v is not None else None

    def get_velocity_x_value(self, index):
        v = self.get_velocity(index)
        return v[0] if v is not None else None

    def get_velocity_y_value(self, index):
        v = self.get_velocity(index)
        return v[1] if v is not None else None

    def get_velocity_absolute_value(self, index):
        v = self.get_velocity(index)
        return math.sqrt(v[1]**2+v[0]**2) if v is not None else None

    def get_acceleration_x_value(self, index):
        v = self.get_acceleration(index)
        return v[0] if v is not None else None

    def get_acceleration_y_value(self, index):
        v = self.get_acceleration(index)
        return v[1] if v is not None else None

    def get_acceleration_absolute_value(self, index):
        v = self.get_acceleration(index)
        return math.sqrt(v[1]**2+v[0]**2) if v is not None else None
        

    ######################################################################
    ### AUX FUNCTIONS ####################################################
    ######################################################################

    def create_popupmenu_actions(self):
        self.tree.add_popup_menu_option(
            label='Duplicate', 
            function_action=self.clone_path, 
            item=self.treenode, icon=conf.ANNOTATOR_ICON_DUPLICATE
        )

        self.tree.add_popup_menu_option(
            label='Remove', 
            function_action=self.__remove_path_dataset, 
            item=self.treenode, icon=conf.ANNOTATOR_ICON_DELETE
        )
        
    def create_tree_nodes(self):
        self.treenode = self.tree.create_child(self.name, icon=conf.ANNOTATOR_ICON_PATH, parent=self.parent_treenode )
        self.treenode.win = self
        self.create_popupmenu_actions()

        self.create_group_node('position',      icon=conf.ANNOTATOR_ICON_POSITION)
        self.create_data_node('position > x',   icon=conf.ANNOTATOR_ICON_X)
        self.create_data_node('position > y',   icon=conf.ANNOTATOR_ICON_Y)

        self.create_group_node('velocity',          icon=conf.ANNOTATOR_ICON_VELOCITY)
        self.create_data_node('velocity > x',       icon=conf.ANNOTATOR_ICON_X)
        self.create_data_node('velocity > y',       icon=conf.ANNOTATOR_ICON_Y)
        self.create_data_node('velocity > absolute', icon=conf.ANNOTATOR_ICON_INFO)

        self.create_group_node('acceleration',          icon=conf.ANNOTATOR_ICON_ACCELERATION)
        self.create_data_node('acceleration > x',       icon=conf.ANNOTATOR_ICON_X)
        self.create_data_node('acceleration > y',       icon=conf.ANNOTATOR_ICON_Y)
        self.create_data_node('acceleration > absolute', icon=conf.ANNOTATOR_ICON_INFO)




    ######################################################################
    ### GUI EVENTS #######################################################
    ######################################################################

    def __del_point_btn_event(self):
        video_index = self.mainwindow._player.video_index-1
        if video_index<0:return

        self.set_position(video_index, None, None)
        try:
            self._sel_pts.remove(video_index)
            self._interpolate_btn.hide()
            self._interpolation_mode.hide()
            self._interpolation_title.hide()
            self._del_path_btn.hide()
            self._tmp_points = []
        except ValueError:
            self.calculate_tmp_interpolation()
        
        self.mainwindow._player.refresh()

        if self.visible:
            if len(self._sel_pts)==2:
                self._del_path_btn.show()
            else:
                self._del_path_btn.hide()

    def __sel_pto_btn_event(self):
        video_index = self.mainwindow._player.video_index-1
        if video_index<0:return 
        
        position = self[video_index]
        if position is None: return

        if video_index in self._sel_pts:
            self._sel_pts.remove(video_index)
        else:
            self._sel_pts.append(video_index)
            self._sel_pts = sorted(self._sel_pts)

        if self.visible:
            #store a temporary path for interpolation visualization
            if len(self._sel_pts) >= 2:
                #########################################################
                #In case 2 frames are selected, draw the temporary path##
                #########################################################
                if self.calculate_tmp_interpolation():
                    self._interpolate_btn.show()
                    self._interpolation_mode.show()
                    self._interpolation_title.show()

                    if len(self._sel_pts)==2:
                        self._del_path_btn.show()
                    else:
                        self._del_path_btn.hide()
                #########################################################
            else:
                self._interpolate_btn.hide()
                self._interpolation_mode.hide()
                self._interpolation_title.hide()
                self._del_path_btn.hide()
                self._tmp_points = []

        self.mainwindow._player.refresh()


    def __remove_path_dataset(self):
        item = self.tree.selected_item
        if item is not None: self.object2d -= item.win


    def __referencial_pt_changed_event(self):
        try:
            self._referencial = eval(self._referencial_pt.value)
        except:
            self._referencial = None

    def __pick_a_color_event(self):
        color = QColor(self.color[2], self.color[1], self.color[0])
        color = QColorDialog.getColor(color, parent = self, title = 'Pick a color for the path')
        self.color = color.blue(), color.green(), color.red()
        self.mainwindow._player.refresh()

    ####################################################################

    def __interpolate_btn_event(self): 
        #store a temporary path for interpolation visualization
        if len(self._sel_pts) >= 2:
            mode = None if self._interpolation_mode.value=='Auto' else self._interpolation_mode.value        #store a temporary path for interpolation visualization
            self.interpolate_range( self._sel_pts[0], self._sel_pts[-1], interpolation_mode=mode)
            self.mainwindow._player.refresh()
        else:
            QMessageBox.about(self, "Error", "You need to select 2 frames.")

    def __interpolation_mode_changed_event(self): 
        #store a temporary path for interpolation visualization
        if len(self._sel_pts) >= 2:
            if self.calculate_tmp_interpolation():
                self.mainwindow._player.refresh()

    def __del_path_btn_event(self): #store a temporary path for interpolation visualization
        if len(self._sel_pts) == 2:
            reply = QMessageBox.question(self, 'Confirmation',
                                               "Are you sure you want to delete this path?", QMessageBox.Yes | QMessageBox.No, QMessageBox.No)
            if reply == QMessageBox.Yes: #store a temporary path for interpolation visualization
                start, end = self._sel_pts[0], self._sel_pts[1]
                self.delete_range(start, end)
                self.calculate_tmp_interpolation()
                self.mainwindow._player.refresh()
        else:
            QMessageBox.about(self, "Error", "You need to select 2 frames.")

    def __lin_dist(self, p1, p2): return np.linalg.norm( (p1[0]-p2[0], p1[1]-p2[1]) )

    ######################################################################
    ### VIDEO EVENTS #####################################################
    ######################################################################


    def on_click(self, event, x, y):
        if event.button== 1:
            frame_index = self.mainwindow._player.video_index-1

            if self._mark_pto_btn.checked:
                frame_index = frame_index if frame_index>=0 else 0
                self.set_position(frame_index, x, y)
                self._mark_pto_btn.checked = False
            else:
                position = self.get_position(frame_index)
                if position is not None and self.__lin_dist(position, (x,y))<10:

                    modifier = int(event.event.modifiers())

                    # If the control button is pressed will add the blob to the previous selections
                    if modifier == QtCore.Qt.ControlModifier:
                        if frame_index not in self._sel_pts: #store a temporary path for interpolation visualization
                            self._sel_pts.append(frame_index)
                            
                        else:
                            # Remove the blob in case it was selected before #store a temporary path for interpolation visualization
                            self._sel_pts.remove(frame_index)
                    else:
                        # The control key was not pressed so will select only one #store a temporary path for interpolation visualization
                        self._sel_pts =[frame_index]
                else: #store a temporary path for interpolation visualization
                    self._sel_pts =[]  # No object selected: remove previous selections #store a temporary path for interpolation visualization
                self._sel_pts = sorted(self._sel_pts)

            if self.visible:
                #store a temporary path for interpolation visualization
                if len(self._sel_pts) >= 2:
                    #########################################################
                    #In case 2 frames are selected, draw the temporary path##
                    #########################################################
                    res = self.calculate_tmp_interpolation()
                    if self.visible & res:
                        self._interpolate_btn.show()
                        self._interpolation_mode.show()
                        self._interpolation_title.show()
                        self._del_path_btn.show()
                        #self._sel_pto_btn.hide()
                    #########################################################
                else:
                    if self.visible:
                        self._interpolate_btn.hide()
                        self._interpolation_mode.hide()
                        self._interpolation_title.hide()
                        self._del_path_btn.hide()
                    self._tmp_points = []

            
            self.mainwindow._player.refresh()

    
        
    def draw(self, frame, frame_index):

        if not self.mainwindow.player.is_playing and self.visible:

            if self[frame_index] is None:
                self._del_point_btn.hide()
            else:
                self._del_point_btn.show()

        Path.draw(self, frame, frame_index)

    ######################################################################
    ### PROPERTIES #######################################################
    ######################################################################

    @property
    def mainwindow(self):    return self._object2d.mainwindow
    @property 
    def tree(self):          return self._object2d.tree
    @property 
    def video_capture(self): return self._object2d.video_capture

    @property 
    def parent_treenode(self):  return self._object2d.treenode

    @property
    def interpolation_mode(self): return None if self._interpolation_mode.value==-1 else self._interpolation_mode.value

    @property
    def mark_point_button(self):
        return self._mark_pto_btn

    @property
    def referencial(self): 
        return Path.referencial.fget(self)

    @referencial.setter
    def referencial(self, value):
        self._referencial_pt.value = str(value)[1:-1] if value else ''

    @property
    def apply_referencial(self):  
        return self._use_referencial.value

    @apply_referencial.setter
    def apply_referencial(self, value): 
        self._use_referencial.value = value

    @property
    def show_object_name(self):
        return self._show_object_name.value

    @show_object_name.setter
    def show_object_name(self, value):
        self._show_object_name.value = value

    @property
    def show_name(self):
        return self._show_name.value

    @show_name.setter
    def show_name(self, value):
        self._show_name.value = value
Пример #4
0
class SettingsWindow(BaseWidget):
    def __init__(self, *args, **kwargs):
        global settings_json
        BaseWidget.__init__(self, "Settings")
        self._settingslabel = ControlLabel("Settings")
        self._outputfolder = ControlText("SD Card Path")
        self._outputfolder.value = settings_json["outputfolder"]
        self._hmackey = ControlText("Capsrv HMAC Secret")
        self._hmackey.value = settings_json["hmackey"]
        self._customgameid = ControlText("Custom Game ID")
        self._customgameid.value = settings_json["customgameid"]
        self._typelabel = ControlLabel("Type")
        self._imagecheckbox = ControlCheckBox("Image")
        self._imagecheckbox.value = (settings_json["type"] == "image")
        self._imagecheckbox.changed_event = self.imageCheckbox
        self._mangacheckbox = ControlCheckBox("Manga")
        self._mangacheckbox.value = (settings_json["type"] == "manga")
        self._mangacheckbox.changed_event = self.mangaCheckbox
        self._comiccheckbox = ControlCheckBox("Comics")
        self._comiccheckbox.value = (settings_json["type"] == "comics")
        self._comiccheckbox.changed_event = self.comicCheckbox
        self._directionlabel = ControlLabel("Direction")
        self._directionlabel.hide()
        self._lefttoright = ControlCheckBox("From left to right")
        self._lefttoright.hide()
        self._lefttoright.value = (settings_json["direction"] == "ltr")
        self._lefttoright.changed_event = self.fromLeftToRight
        self._righttoleft = ControlCheckBox("From right to left")
        self._righttoleft.hide()
        self._righttoleft.value = (settings_json["direction"] == "rtl")
        self._righttoleft.changed_event = self.fromRightToLeft
        self._savebutton = ControlButton("Save")
        self._savebutton.value = self.saveButton
        self.formset = [("_settingslabel"), ("_outputfolder"), ("_hmackey"),
                        ("_customgameid"), ("_typelabel"),
                        ("_imagecheckbox", "_mangacheckbox", "_comiccheckbox"),
                        ("_directionlabel"), ("_lefttoright", "_righttoleft"),
                        (" "), (" ", " ", "_savebutton")]
        self._typerequested = False
        self._directionrequested = False

    #Reimplementing radio buttons sure is fun
    def imageCheckbox(self):
        if self._typerequested:
            return
        self._typerequested = True
        self._mangacheckbox.value = False
        self._comiccheckbox.value = False
        self._imagecheckbox.value = True
        self._directionlabel.hide()
        self._lefttoright.hide()
        self._righttoleft.hide()
        self._typerequested = False

    def comicCheckbox(self):
        if self._typerequested:
            return
        self._typerequested = True
        self._mangacheckbox.value = False
        self._comiccheckbox.value = True
        self._imagecheckbox.value = False
        self._directionlabel.show()
        self._lefttoright.show()
        self._righttoleft.show()
        self._typerequested = False

    def mangaCheckbox(self):
        if self._typerequested:
            return
        self._typerequested = True
        self._mangacheckbox.value = True
        self._comiccheckbox.value = False
        self._imagecheckbox.value = False
        self._directionlabel.show()
        self._lefttoright.show()
        self._righttoleft.show()
        self._typerequested = False

    def fromLeftToRight(self):
        if self._directionrequested:
            return
        self._directionrequested = True
        self._lefttoright.value = True
        self._righttoleft.value = False
        self._directionrequested = False

    def fromRightToLeft(self):
        if self._directionrequested:
            return
        self._directionrequested = True
        self._lefttoright.value = False
        self._righttoleft.value = True
        self._directionrequested = False

    def saveButton(self):
        global settings_json
        typ = ""
        if not self._mangacheckbox.value and not self._comiccheckbox.value:
            typ = "image"
        elif self._mangacheckbox.value:
            typ = "manga"
        else:
            typ = "comics"
        direction = ""
        if not self._righttoleft.value:
            direction = "ltr"
        else:
            direction = "rtl"
        try:
            a = bytes.fromhex(self._hmackey.value)
            if (len(a) != 0x20 or sha256(a).hexdigest(
            ) != "e9735dae330300b8bb4b5892c8178f5d57daa32d7b5ef5d15f14491800ce4750"
                ):
                raise
        except:
            self.alert("Invalid HMAC key!", "Error!")
            return
        try:
            a = bytes.fromhex(self._customgameid.value)
            if (len(a) != 16) and self._customgameid.value != "":
                raise ValueError
            if (self._customgameid.value == ""):
                raise
        except ValueError:
            self.alert("Invalid custom game ID!", "Error!")
            return
        except:
            self._customgameid.value = "57B4628D2267231D57E0FC1078C0596D"
        if self._outputfolder.value == "":
            self._outputfolder.value = "."
        settings_json = {
            "outputfolder": self._outputfolder.value,
            "hmackey": self._hmackey.value,
            "customgameid": self._customgameid.value,
            "type": typ,
            "direction": direction
        }
        json.dump(
            settings_json,
            open(
                appdirs.AppDirs("NSScreenshotMaker", "").user_data_dir +
                "/settings.json", "w"))