class MainWindow(QMainWindow): def __init__(self): # load setting json file with open('setting.json') as f: self.app_setting = json.load(f) # Status of window super().__init__() self.title = 'Image Editor' self.left = 70 self.top = 70 self.width = 800 self.height = 700 # Status of view image self.org_qimg = None self.org_img_width = 0 self.org_img_height = 0 self.layer_pixmap = None self.layer_width = 0 self.layer_height = 0 self.layer_alpha = 50.0 # Prepare color bar data self.colormap_gain = self.app_setting["SoftwareSetting"]["process"][ "colormap"]["gain"] self.colormap_offset_x = self.app_setting["SoftwareSetting"][ "process"]["colormap"]["offset_x"] self.colormap_offset_green = self.app_setting["SoftwareSetting"][ "process"]["colormap"]["offset_green"] self.colormap_data = [ colormap.colorBarRGB(x * 0.001, self.colormap_offset_x, self.colormap_offset_green, self.colormap_gain) for x in range(1000) ] self.img_edit_mode = 'cursor' self.draw_color = QColor(255, 0, 0) self.draw_tool_size = 5 self.eraser_color = QColor(0, 0, 0, 0) # setup user interface components self.setup_ui() # Setup user interface components def setup_ui(self): # Set main window title self.setWindowTitle(self.title) # Set main wiodow initial position self.setGeometry(self.left, self.top, self.width, self.height) # Set up mainWindow's layout self.mainWidget = QWidget(self) # Note not to forget this code. self.main_layout = QVBoxLayout() # Set menu for main window self.main_menu = self.menuBar() self.file_menu = self.main_menu.addMenu('File') self.edit_menu = self.main_menu.addMenu('Edit') self.help_menu = self.main_menu.addMenu('Help') self.main_layout.addWidget(self.main_menu) # Set "Original Image Open" menu self.org_img_open_button = QAction( self.style().standardIcon(getattr(QStyle, 'SP_FileDialogStart')), 'Open Orginal Image', self) self.org_img_open_button.setShortcut('Ctrl+O') self.org_img_open_button.triggered.connect(self.open_org_img_dialog) self.file_menu.addAction(self.org_img_open_button) # Set "Save layer image" menu self.layer_img_save_button = QAction( self.style().standardIcon(getattr(QStyle, 'SP_FileDialogEnd')), 'Save Layer Image', self) self.layer_img_save_button.setShortcut('Ctrl+S') self.layer_img_save_button.triggered.connect(self.save_layer_image) self.file_menu.addAction(self.layer_img_save_button) # Set "Save compose image(original + layer image)" menu self.compose_img_save_button = QAction( self.style().standardIcon(getattr(QStyle, 'SP_FileDialogEnd')), 'Save Compose Image', self) self.compose_img_save_button.setShortcut('Ctrl+D') self.compose_img_save_button.triggered.connect(self.save_compose_image) self.file_menu.addAction(self.compose_img_save_button) # Set "exit software" menu self.exit_button = QAction( self.style().standardIcon(getattr(QStyle, 'SP_DialogCloseButton')), 'Exit', self) self.exit_button.setShortcut('Ctrl-Q') self.exit_button.setStatusTip('Exit software') self.exit_button.triggered.connect(self.close) self.file_menu.addAction(self.exit_button) self.upper_layout = QHBoxLayout() self.main_layout.addLayout(self.upper_layout) # Set image display area self.gview_default_size = 500 self.graphics_view = QGraphicsView() self.graphics_view.setFixedSize(self.gview_default_size, self.gview_default_size) self.graphics_view.setObjectName("imageDisplayArea") self.upper_layout.addWidget(self.graphics_view) # image display area's contents self.scene = GraphicsSceneForMainView(self.graphics_view, self) self.imgs_pixmap = [] self.imgs = [] self.img_status_layout = QVBoxLayout() self.upper_layout.addLayout(self.img_status_layout) # Set tranparency value of layer image self.transparency_title_label = QLabel('layer transparency value') self.img_status_layout.addWidget(self.transparency_title_label) transparency = round((1.0 - self.layer_alpha / 255.0) * 100) self.img_transparency_edit = QLineEdit(str(transparency)) self.img_transparency_sld = QSlider(Qt.Horizontal) self.img_transparency_sld.setFocusPolicy(Qt.NoFocus) self.img_transparency_sld.setRange(0, 100) self.img_transparency_sld.setValue(transparency) self.transparency_layout = QFormLayout() self.transparency_layout.addRow(self.img_transparency_sld, self.img_transparency_edit) self.img_status_layout.addLayout(self.transparency_layout) # Signal of transparency value changed self.img_transparency_sld.valueChanged.connect( self.transparency_change_sld) self.img_transparency_edit.textChanged.connect( self.transparency_change_edit) self.img_editor_layout = QVBoxLayout() self.img_status_layout.addLayout(self.img_editor_layout) # Set layer image editor tool self.img_editor_tool1_layout = QHBoxLayout() self.img_editor_layout.addLayout(self.img_editor_tool1_layout) self.tool_button_size = 64 # Set Mouse cursor self.mouse_cursor_button = QPushButton() self.mouse_cursor_button.setIcon(QIcon('icon/cursor.png')) self.mouse_cursor_button.setCheckable(True) self.mouse_cursor_button.setIconSize( QSize(self.tool_button_size, self.tool_button_size)) self.img_editor_tool1_layout.addWidget(self.mouse_cursor_button) # Set Pen self.pen_button = QPushButton() self.pen_button.setIcon(QIcon('icon/pen.png')) self.pen_button.setCheckable(True) self.pen_button.setIconSize( QSize(self.tool_button_size, self.tool_button_size)) self.img_editor_tool1_layout.addWidget(self.pen_button) # Set Eraser self.eraser_button = QPushButton() self.eraser_button.setIcon(QIcon('icon/eraser.png')) self.eraser_button.setCheckable(True) self.eraser_button.setIconSize( QSize(self.tool_button_size, self.tool_button_size)) self.img_editor_tool1_layout.addWidget(self.eraser_button) # Group button of mouse cursor, pen, eraser self.img_editor_tool1_group1 = QButtonGroup() self.img_editor_tool1_group1.addButton(self.mouse_cursor_button, 1) self.img_editor_tool1_group1.addButton(self.pen_button, 2) self.img_editor_tool1_group1.addButton(self.eraser_button, 3) # Set signal-slot of image editor button self.mouse_cursor_button.toggled.connect( self.mouse_cursor_button_toggled) self.pen_button.toggled.connect(self.pen_button_toggled) self.eraser_button.toggled.connect(self.eraser_button_toggled) # Set color bar self.color_bar_width = 64 self.color_bar_height = 256 self.color_bar_view = QGraphicsView() self.color_bar_view.setFixedSize(self.color_bar_width + 3, self.color_bar_height + 3) self.color_bar_scene = GraphicsSceneForTools() self.color_bar_img = QImage(self.color_bar_width, self.color_bar_height, QImage.Format_RGB888) for i in range(self.color_bar_height): # Set drawing pen for colormap ii = round(i * (1000 / 256)) color = QColor(self.colormap_data[ii][0], self.colormap_data[ii][1], self.colormap_data[ii][2]) pen = QPen(color, 1, Qt.SolidLine, \ Qt.SquareCap, Qt.RoundJoin) self.color_bar_scene.addLine(0, self.color_bar_height - i - 1, self.color_bar_width, self.color_bar_height - i - 1, pen=pen) for j in range(self.color_bar_width): self.color_bar_img.setPixelColor(j, self.color_bar_height - i - 1, color) self.color_bar_scene.set_img_content(self.color_bar_img) self.color_bar_view.setScene(self.color_bar_scene) # Connect signal to slot of color_bar_scene self.color_bar_scene.img_info.connect(self.set_selected_color) self.img_editor_tool1_layout.addWidget(self.color_bar_view) # Set thickness of Pen or Eraser self.draw_status_layout = QVBoxLayout() self.draw_thick_title_label = QLabel('thickness of pen or eraser') self.draw_status_layout.addWidget(self.draw_thick_title_label) self.draw_thick_edit = QLineEdit(str(self.draw_tool_size)) self.draw_thick_sld = QSlider(Qt.Horizontal) self.draw_thick_sld.setFocusPolicy(Qt.NoFocus) self.draw_thick_sld.setRange(1, 30) self.draw_thick_sld.setValue(self.draw_tool_size) self.draw_thick_layout = QFormLayout() self.draw_thick_layout.addRow(self.draw_thick_sld, self.draw_thick_edit) self.draw_status_layout.addLayout(self.draw_thick_layout) self.img_editor_layout.addLayout(self.draw_status_layout) # Signal of draw thickness value changed self.draw_thick_sld.valueChanged.connect(self.draw_thick_change_sld) self.draw_thick_edit.textChanged.connect(self.draw_thick_change_edit) # Set view area of selected color self.select_color_view_size = 64 self.select_color_view = QGraphicsView() self.select_color_view.setFixedSize(self.select_color_view_size + 3, self.select_color_view_size + 3) self.select_color_scene = QGraphicsScene() brush = QBrush(self.draw_color) self.select_color_rect = self.select_color_scene.addRect(QRect(0, 0, self.select_color_view_size, self.select_color_view_size), \ brush=brush) self.select_color_view.setScene(self.select_color_scene) self.select_color_title_label = QLabel('Selected color') self.selected_color_layout = QFormLayout() self.selected_color_layout.addRow(self.select_color_title_label, self.select_color_view) self.img_editor_layout.addLayout(self.selected_color_layout) # Set save button self.save_button_layout = QHBoxLayout() self.img_status_layout.addLayout(self.save_button_layout) self.layer_save_button = QPushButton('Save layer image') self.layer_save_button.setIcon(QIcon('icon/layer_save.png')) self.layer_save_button.setIconSize( QSize(self.tool_button_size, self.tool_button_size)) self.compose_save_button = QPushButton( 'Save composed original and layer image') self.compose_save_button.setIcon(QIcon('icon/compose_save.png')) self.compose_save_button.setIconSize( QSize(self.tool_button_size, self.tool_button_size)) self.save_button_layout.addWidget(self.layer_save_button) self.save_button_layout.addWidget(self.compose_save_button) self.layer_save_button.clicked.connect(self.save_layer_image) self.compose_save_button.clicked.connect(self.save_compose_image) # Set display area of selected file path self.org_img_path_title_label = QLabel('original image file: ') self.org_img_path_label = QLabel('') self.file_path_layout = QFormLayout() self.file_path_layout.addRow(self.org_img_path_title_label, self.org_img_path_label) self.bottom_layout = QVBoxLayout() self.bottom_layout.addLayout(self.file_path_layout) self.main_layout.addLayout(self.bottom_layout) self.mainWidget.setLayout(self.main_layout) self.setCentralWidget(self.mainWidget) # Original image select Function def open_org_img_dialog(self): options = QFileDialog.Options() org_img_default_path = self.app_setting["SoftwareSetting"][ "file_path"]["org_img_dir"] self.org_img_file_path, selected_filter = QFileDialog.getOpenFileName(self, 'Select original image', org_img_default_path, \ 'Image files(*.jpg *jpeg *.png)', options=options) org_img_dir_path, org_img_file = os.path.split(self.org_img_file_path) org_img_bare_name, org_img_ext = os.path.splitext(org_img_file) self.org_img_path_label.setText(self.org_img_file_path) self.set_image_on_viewer() def set_image_on_viewer(self): # Delete existing image item if len(self.imgs_pixmap) != 0: for item in self.imgs_pixmap: self.scene.removeItem(item) self.scene.clear_contents() self.imgs_pixmap.clear() self.imgs.clear() # load original image self.org_qimg = QImage(self.org_img_file_path) self.org_pixmap = QPixmap.fromImage(self.org_qimg) org_img_size = self.org_qimg.size() self.org_img_width = org_img_size.width() self.org_img_height = org_img_size.height() # Set layer image self.layer_qimg = QImage(self.org_img_width, self.org_img_height, QImage.Format_RGBA8888) self.layer_qimg.fill(QColor(0, 0, 0, self.layer_alpha)) self.layer_pixmap = QPixmap.fromImage(self.layer_qimg) self.imgs.append(self.org_qimg) self.imgs.append(self.layer_qimg) # Set image to scene self.imgs_pixmap.append(QGraphicsPixmapItem(self.org_pixmap)) self.scene.addItem(self.imgs_pixmap[-1]) self.imgs_pixmap.append(QGraphicsPixmapItem(self.layer_pixmap)) self.scene.addItem(self.imgs_pixmap[-1]) self.scene.set_img_contents(self.imgs) # Set scene to graphics view self.graphics_view.setScene(self.scene) self.graphics_view.setAlignment(Qt.AlignHCenter | Qt.AlignVCenter) self.show() # Slot function of transparency slider changed def transparency_change_sld(self, value): self.img_transparency_edit.setText(str(value)) self.layer_alpha = int(255 * (1.0 - (value / 100))) # Change layer image's transparency(alpha value) for y in range(self.org_img_height): for x in range(self.org_img_width): self.layer_qimg.setPixelColor( QPoint(x, y), QColor(0, 0, 0, self.layer_alpha)) self.layer_pixmap = QPixmap.fromImage(self.layer_qimg) # remove previous layer image self.scene.removeItem(self.imgs_pixmap[-1]) self.imgs_pixmap.pop(-1) # add new layer image to scene self.imgs_pixmap.append(QGraphicsPixmapItem(self.layer_pixmap)) self.scene.addItem(self.imgs_pixmap[-1]) self.show() # Slot function of transparency text edit changed def transparency_change_edit(self, value): if int(value) < 0 or int(value) > 100: return self.img_transparency_sld.setValue(int(value)) self.layer_alpha = int(255 * (1.0 - (int(value) / 100.0))) # Change layer image's transparency(alpha value) for y in range(self.org_img_height): for x in range(self.org_img_width): self.layer_qimg.setPixelColor( QPoint(x, y), QColor(0, 0, 0, self.layer_alpha)) self.layer_pixmap = QPixmap.fromImage(self.layer_qimg) # remove previous layer image self.scene.removeItem(self.imgs_pixmap[-1]) self.imgs_pixmap.pop(-1) # add new layer image to scene self.imgs_pixmap.append(QGraphicsPixmapItem(self.layer_pixmap)) self.scene.addItem(self.imgs_pixmap[-1]) self.show() # slot(receiver of signal) of mouse_cursor_button toggled def mouse_cursor_button_toggled(self, checked): if checked: self.img_edit_mode = 'cursor' self.scene.set_mode(self.img_edit_mode) self.color_bar_scene.set_mode(self.img_edit_mode) # slot(receiver of signal) of pen_button toggled def pen_button_toggled(self, checked): if checked: self.img_edit_mode = 'pen' self.scene.set_mode(self.img_edit_mode) self.color_bar_scene.set_mode(self.img_edit_mode) # slot(receiver of signal) of eraser_button toggled def eraser_button_toggled(self, checked): if checked: self.img_edit_mode = 'eraser' self.scene.set_mode(self.img_edit_mode) self.color_bar_scene.set_mode(self.img_edit_mode) self.draw_color = self.eraser_color self.select_color_scene.removeItem(self.select_color_rect) brush = QBrush(self.draw_color) self.select_color_rect = self.select_color_scene.addRect(QRect(0, 0, self.select_color_view_size, self.select_color_view_size), \ brush=brush) self.select_color_view.setScene(self.select_color_scene) # Slot of color bar clicked for selection color def set_selected_color(self, color): # Delete existng image item self.select_color_scene.removeItem(self.select_color_rect) self.draw_color = color brush = QBrush(self.draw_color) self.select_color_rect = self.select_color_scene.addRect(QRect(0, 0, self.select_color_view_size, self.select_color_view_size), \ brush=brush) self.select_color_view.setScene(self.select_color_scene) # Slot function of draw thicikeness slider changed def draw_thick_change_sld(self, value): self.draw_thickness_edit.setText(str(value)) self.draw_tool_size = value # Slot function of draw thicikeness text editor changed def draw_thick_change_edit(self, value): if int(value) < 1 or int(value) > 30: return self.draw_thickness_sld.setValue(int(value)) def make_layer_image(self): for i, line in enumerate(self.scene.lines): pen = self.scene.pens[i] pen_size = int(pen.width()) pen_color = pen.color() # start pixel of line x1 = int(line.x1()) y1 = int(line.y1()) # end pixel of line x2 = int(line.x2()) y2 = int(line.y2()) dx = int(line.dx()) dy = int(line.dy()) # When only 1pixl line if dx <= 1 and dy <= 1: draw_pix_x1_s = max(x1 - int(pen_size / 2), 0) draw_pix_x1_e = min(x1 + int(pen_size / 2), self.org_img_width - 1) draw_pix_y1_s = max(y1 - int(pen_size / 2), 0) draw_pix_y1_e = min(y1 + int(pen_size / 2), self.org_img_height - 1) # for Pen's size for y in range(draw_pix_y1_s, draw_pix_y1_e): for x in range(draw_pix_x1_s, draw_pix_x1_e): self.layer_qimg.setPixelColor(x, y, pen_color) draw_pix_x2_s = max(x2 - int(pen_size / 2), 0) draw_pix_x2_e = min(x2 + int(pen_size / 2), self.org_img_width - 1) draw_pix_y2_s = max(y2 - int(pen_size / 2), 0) draw_pix_y2_e = min(y2 + int(pen_size / 2), self.org_img_height - 1) # for Pen's size for y in range(draw_pix_y2_s, draw_pix_y2_e): for x in range(draw_pix_x2_s, draw_pix_x2_e): self.layer_qimg.setPixelColor(x, y, pen_color) else: # For avoid devide by 0 if dx == 0: for y in range(y1, y2 + 1): draw_pix_y_s = y - int(pen_size / 2) draw_pix_y_e = y + int(pen_size / 2) # for Pen's size for yy in range(draw_pix_y_s, draw_pix_y_e): self.layer_qimg.setPixelColor(x1, yy, pen_color) else: grad = dy / dx # Choose coordinates with small slope not to skip pixels if grad >= 1.0: for x in range(dx): y = y1 + int(grad * x + 0.5) draw_pix_x_s = max(x1 + x - int(pen_size / 2), 0) draw_pix_x_e = min(x1 + x + int(pen_size / 2), self.org_img_width - 1) draw_pix_y_s = max(y - int(pen_size / 2), 0) draw_pix_y_e = min(y + int(pen_size / 2), self.org_img_height - 1) # for Pen's size for yy in range(draw_pix_y_s, draw_pix_y_e + 1): for xx in range(draw_pix_x_s, draw_pix_x_e + 1): self.layer_qimg.setPixelColor( xx, yy, pen_color) else: for y in range(dy): x = x1 + int(1 / grad * y + 0.5) draw_pix_y_s = max(y1 + y - int(pen_size / 2), 0) draw_pix_y_e = min(y1 + y + int(pen_size / 2), self.org_img_height - 1) draw_pix_x_s = max(x - int(pen_size / 2), 0) draw_pix_x_e = min(x + int(pen_size / 2), self.org_img_width - 1) # for Pen's size for yy in range(draw_pix_y_s, draw_pix_y_e + 1): for xx in range(draw_pix_x_s, draw_pix_x_e + 1): self.layer_qimg.setPixelColor( xx, yy, pen_color) # Slot function of save layer image button clicked def save_layer_image(self): self.make_layer_image() layer_img_default_path = self.app_setting["SoftwareSetting"][ "file_path"]["layer_img_dir"] options = QFileDialog.Options() file_name, selected_filete = QFileDialog.getSaveFileName(self, 'Save layer image', layer_img_default_path, \ 'image files(*.png, *jpg)', options=options) #print('layer image save name:{file}'.format(file=file_name)) self.layer_qimg.save(file_name) ret = QMessageBox(self, 'Success', 'layer image is saved successfully', QMessageBox.Ok) # Make composed orignal and layered image def make_compose_image(self): self.make_layer_image() self.compose_qimg = QImage(self.org_img_width, self.org_img_height, QImage.Format_RGBA8888) painter = QPainter(self.compose_qimg) painter.setCompositionMode(QPainter.CompositionMode_SourceOver) painter.drawImage(0, 0, self.org_qimg) painter.setCompositionMode(QPainter.CompositionMode_SourceOver) painter.drawImage(0, 0, self.layer_qimg) painter.end() # Slot function of save composer original and layer image button clicked def save_compose_image(self): self.make_compose_image() compose_img_default_path = self.app_setting["SoftwareSetting"][ "file_path"]["compose_img_dir"] options = QFileDialog.Options() file_name, selected_fileter = QFileDialog.getSaveFileName(self, 'Save composed image', compose_img_default_path, \ 'image files(*.png, *jpg)', options=options) #print('compose image save name:{file}'.format(file=file_name)) self.compose_qimg.save(file_name) ret = QMessageBox(self, 'Success', 'compose image is saved successfully', QMessageBox.Ok)
class Ui_FE14CharacterEditor(QMainWindow): def __init__(self, parent=None): super().__init__(parent) self.search_bar = QLineEdit() self.search_bar.setPlaceholderText("Search...") self.search_bar.setFixedWidth(225) self.characters_list_view = QListView() self.characters_list_view.setContextMenuPolicy( QtCore.Qt.CustomContextMenu) self.characters_list_view.setFixedWidth(225) self.characters_list_layout = QVBoxLayout() self.characters_list_layout.addWidget(self.search_bar) self.characters_list_layout.addWidget(self.characters_list_view) self.character_details_box = QGroupBox(title="Character Details") self.portrait_display = QGraphicsView() self.portrait_display.setFixedSize(140, 140) self.character_details_form_contents_1 = QWidget() self.character_details_form_contents_2 = QWidget() self.character_details_layout = QHBoxLayout() self.character_details_layout.addWidget(self.portrait_display) self.character_details_layout.addWidget( self.character_details_form_contents_1) self.character_details_layout.addWidget( self.character_details_form_contents_2) self.character_details_box.setLayout(self.character_details_layout) self.character_details_box.setFixedHeight(200) self.tab_widget = QTabWidget() self.ids_tab = QWidget() self.classes_tab = QWidget() self.stats_tab = QScrollArea() self.skills_tab = QScrollArea() self.misc_tab = QScrollArea() self.portraits_tab = PortraitViewer() self.stats_contents = QWidget() self.stats_tab.setWidget(self.stats_contents) self.stats_tab.setWidgetResizable(True) self.stats_layout = QVBoxLayout() self.stats_layout.setAlignment(QtCore.Qt.AlignTop) self.stats_contents.setLayout(self.stats_layout) self.skills_contents = QWidget() self.skills_tab.setWidget(self.skills_contents) self.skills_tab.setWidgetResizable(True) self.misc_contents = QWidget() self.misc_tab.setWidget(self.misc_contents) self.misc_tab.setWidgetResizable(True) self.misc_layout = QVBoxLayout() self.misc_layout.setAlignment(QtCore.Qt.AlignTop) self.misc_contents.setLayout(self.misc_layout) self.tab_widget.addTab(self.ids_tab, "IDs") self.tab_widget.addTab(self.classes_tab, "Classes") self.tab_widget.addTab(self.stats_tab, "Stats") self.tab_widget.addTab(self.skills_tab, "Skills") self.tab_widget.addTab(self.misc_tab, "Misc.") self.tab_widget.addTab(self.portraits_tab, "Portraits") self.editor_layout = QVBoxLayout() self.editor_layout.addWidget(self.character_details_box) self.editor_layout.addWidget(self.tab_widget) self.main_layout = QHBoxLayout() self.main_layout.addLayout(self.characters_list_layout) self.visual_splitter = QFrame() self.visual_splitter.setFrameShape(QFrame.VLine) self.visual_splitter.setFrameShadow(QFrame.Sunken) self.main_layout.addWidget(self.visual_splitter) self.main_layout.addLayout(self.editor_layout) self.tool_bar = QToolBar() self.action_add = QAction(text="Add") self.action_remove = QAction(text="Remove") self.action_copy_to = QAction(text="Copy To") self.tool_bar.addActions( [self.action_add, self.action_remove, self.action_copy_to]) self.addToolBar(self.tool_bar) self.resize(1000, 600) central_widget = QWidget() central_widget.setLayout(self.main_layout) self.setWindowTitle("Character Editor") self.setWindowIcon(QIcon("paragon.ico")) self.setCentralWidget(central_widget)
class BarrenLandsWindow(QMainWindow): """User interface to interact with the barren lands library.""" def __init__(self, width=400, height=600, zones=None): """Initialization of BarrenLands GUI Args: width (int): The width of the canvas/field. height (int): The height of the canvas/field. zones (str): Text data to add to raw input if provided. """ QMainWindow.__init__(self) self.app = QApplication.instance() self.resources = pathlib.Path(__file__).parent self.results = set() self.canvas_width = width self.canvas_height = height self.user_input = zones # Colors for drawing onto the QGraphicsScene self.brush_barren = QBrush(QColor(120, 120, 75), Qt.Dense2Pattern) self.brush_overlay = QBrush(QColor(20, 20, 20, 35), Qt.SolidPattern) self.brush_innerlay = QBrush(QColor(32, 37, 44), Qt.SolidPattern) self.brush_fertile = QBrush(QColor(90, 220, 90, 150), Qt.Dense5Pattern) self.brush_fertile_no_go = QBrush(QColor(90, 220, 90, 150), Qt.BDiagPattern) # Setup the filed class that will handle our calculations. self.field = land.Field(width=self.canvas_width, height=self.canvas_height) self.barren_zones = list() self.fertile_zones = list() self.setup_window() self.build_core_ui() self.build_controls() def setup_window(self): """Setup all the window related settings/flags.""" self.setWindowTitle("Barren Lands") self.setMinimumHeight(600) self.setMinimumWidth(700) self.setWindowFlags(self.windowFlags() | Qt.WindowStaysOnTopHint) self.setWindowIcon(QIcon(utils.get_icon())) self.setStyleSheet(utils.get_css()) def build_core_ui(self): """Sets up the core elements of this window. Notes: Setting up QGraphicsScene: https://stackoverflow.com/questions/23174481/pyside-qt-layout-not-working """ self.base_widget = QWidget(self) self.layout_base = QHBoxLayout(self.base_widget) self.layout_canvas = QVBoxLayout() self.setContentsMargins(0, 0, 0, 0) # Setup the QGraphics items to display out zones as active elements. scene_zone = QRect(0, 0, self.canvas_width, self.canvas_height) self.scene = QGraphicsScene(scene_zone, self) self.canvas = QGraphicsView(self.scene, self) self.canvas.setFixedSize(self.canvas_width + 1, self.canvas_height + 1) # Draw faint bullseye for target. self.scene.addEllipse(self.get_center_rect(300), Qt.NoPen, self.brush_overlay) self.scene.addEllipse(self.get_center_rect(200), Qt.NoPen, self.brush_innerlay) self.scene.addEllipse(self.get_center_rect(100), Qt.NoPen, self.brush_overlay) self.layout_canvas.addWidget(self.canvas) self.lbl_island = QLabel("Island Areas:") self.layout_canvas.addWidget(self.lbl_island) self.layout_base.addLayout(self.layout_canvas) # Set the core widget to the window. self.setCentralWidget(self.base_widget) def get_center_rect(self, size): """Generates a QRect that is at the center of the canvas and it's width/height set to size. Args: size (int): The size to set the bounds to. Returns: (QRectF): A rectangle coordinate that is center on the canvas with a given size. """ return QRectF(self.canvas_width * 0.5 - (size * 0.5), self.canvas_height * 0.5 - (size * 0.5), size, size) def build_controls(self): """Build the control panel that contains all the inputs.""" self.controls = QWidget(self) self.layout_controls = QVBoxLayout(self.controls) self.layout_controls.setContentsMargins(0, 0, 0, 0) # Initialize the text box for user input self.input = self.build_coord_input() # Create the buttons self.btn_add_bzone = BarrenButton(label="Add", parent=self, cmd=self.ctl_add_input) self.btn_run = BarrenButton(label="Analyze", parent=self, cmd=self.ctl_run) # Build the utility buttons self.layout_btm_btn = QHBoxLayout() self.layout_btm_btn.setContentsMargins(0, 0, 0, 0) self.btn_reset = BarrenButton(label="Reset All", parent=self, cmd=self.ctl_clear_zones) self.btn_debug = BarrenButton(label="Debug", parent=self, cmd=self.ctr_debug) self.layout_btm_btn.addWidget(self.btn_debug) self.layout_btm_btn.addWidget(self.btn_reset) self.results_grp = self.build_results_group() self.lbl_area = QLabel("Total Area: 0") self.lbl_area.setAlignment(Qt.AlignCenter) # Add everything to the controls layout self.layout_controls.addLayout(self.input) self.layout_controls.addWidget(self.btn_add_bzone) self.layout_controls.addWidget(self.btn_run) self.layout_controls.addWidget(self.results_grp) self.layout_controls.addWidget(self.lbl_area) self.layout_controls.addLayout(self.layout_btm_btn) # Add to the windows base layout. self.layout_base.addWidget(self.controls) def build_results_group(self): """Build the results group to contain the results of analysis. Returns: results_grp (QGroupBox): The group box that contains the results layout. """ grp_results = QGroupBox("Results: Largest island zone.") grp_results.setSizePolicy(QSizePolicy.Expanding, QSizePolicy.Expanding) # Add a layout to hold all the results from the Analysis. self.layout_results = QVBoxLayout() self.layout_results.setContentsMargins(0, 0, 0, 0) grp_results.setLayout(self.layout_results) return grp_results def build_coord_input(self): """Build the necessary elements for the user input text box. Returns: (QVBoxLayout): The base layout of the coord input. """ layout_base = QVBoxLayout() layout_base.setContentsMargins(0, 0, 0, 0) self.raw_input = QPlainTextEdit() self.raw_input.setMaximumHeight(75) self.raw_input.setPlaceholderText('example: "0 292 399 307"') if self.user_input: self.raw_input.setPlainText(self.user_input) self.layout_input = QVBoxLayout() self.layout_input.setContentsMargins(0, 0, 0, 0) layout_base.addWidget(self.raw_input) layout_base.addLayout(self.layout_input) return layout_base def ctr_debug(self): """CONTROL: Called by the 'debug' button to randomly assign colors to the zones.""" for result in self.results: rand_color = [random.randint(0, 255) for i in range(3)] rand_color = QColor(*rand_color) brush = QBrush(rand_color, Qt.Dense5Pattern) result.rectangle.setBrush(brush) result.base = brush def ctl_add_input(self): """CONTROL: Called by the 'Add' button to handle parsing the raw input.""" raw_data = self.raw_input.toPlainText() if raw_data: parsed_input = utils.format_raw_input(raw_data) for user_input in parsed_input: start_coord = land.Coord(user_input[0], user_input[1]) end_coord = land.Coord(user_input[2], user_input[3]) zone = land.Zone(start_coord, end_coord) self.field.add_zone(zone, barren=True) self.draw_zone(zone, barren=True) def ctl_run(self): """CONTROL: Called by the 'Run' button to initialize the final analysis.""" # Make sure some values are cleared ahead of the analysis. self.clear_results() for rectangle in self.fertile_zones: self.scene.removeItem(rectangle) self.field.fertile_zones = set() # Run the analysis self.field.check_zones() total_area = 0 # Format and draw the results in the QGraphicsScene. for i, island in enumerate(self.field.islands): for zone in island: rectangle = self.draw_zone(zone) size = zone.get_size() if i == 0: # Only add (largest) island to results. Always at 0. total_area += size self.results.add( ResultLabel(label=str(size), rectangle=rectangle, zone=zone)) else: # This zone is fertile, but Inaccessible. rectangle.setBrush(self.brush_fertile_no_go) self.canvas.update() # Pain the update for the user to see the new zone. self.app.processEvents() # Dont show all at the same time. (For more pleasing visual) time.sleep(.015) # Print islands, smallest to largest. print("Islands", self.field.islands_as_area()) island_areas = " ".join(str(i) for i in self.field.islands_as_area()) self.lbl_island.setText(f"Island Areas: {island_areas}") # Set the label as the total area as the largest island. self.lbl_area.setText(f"Total Area: {total_area}") # Sort the results by their zones area. for result in sorted(self.results, key=lambda x: x.zone.get_size()): # Add the result to the results layout in the UI self.layout_results.addWidget(result) def ctl_clear_zones(self): """CONTROL: Called by the 'Reset' button to handle resetting the data and graphics.""" for rectangle in self.barren_zones + self.fertile_zones: self.scene.removeItem(rectangle) self.field.fertile_zones = set() self.fertile_zones = list() self.field.barren_zones = set() self.barren_zones = list() self.clear_results() def clear_results(self): """Clears the results set in preparation for incoming new results.""" for result in self.results: result.deleteLater() self.results = set() self.lbl_area.setText("Total Area:") self.lbl_island.setText("Island Areas:") def draw_zone(self, zone, barren=False): """Creates a QGraphicsRecItem from a Zone and adds it to the scene. Args: zone (Zone): The zone to generate the rectangle from. barren (bool): barren colors if True else fertile color. Returns: (QGraphicsRecItem): The drown rectangle that is added to the canvas. """ # Build the QGraphicsRecItem from the zone data. rectangle = self.scene.addRect(zone.start.x, zone.start.y, zone.end.x - zone.start.x + 1, zone.end.y - zone.start.y + 1) if barren: self.barren_zones.append(rectangle) brush = self.brush_barren else: self.fertile_zones.append(rectangle) brush = self.brush_fertile # Apply the color to the rectangle item. rectangle.setBrush(brush) # Remove the default border rectangle.setPen(Qt.NoPen) return rectangle