def getLoadSaveFileName(self, title, filter=None, settingname=None, savesuffix=None, multiple=False, settingdefault=None): "Show a file dialog and select one or more files" setting = self.settings.value( settingname, settingdefault) if settingname is not None else None directory = setting if type(setting) is str else None dialog = QFileDialog(parent=self, caption=title, directory=directory, filter=filter) # if setting and type(setting) is not str: # dialog.restoreState(setting) dialog.setOption(QFileDialog.DontUseNativeDialog, True) if savesuffix is not None: dialog.setAcceptMode(QFileDialog.AcceptSave) dialog.setDefaultSuffix(savesuffix) elif multiple: dialog.setFileMode(QFileDialog.ExistingFiles) else: dialog.setFileMode(QFileDialog.ExistingFile) dialog.exec() files = dialog.selectedFiles() if not dialog.result() or not files: return None self.settings.setValue(settingname, os.path.dirname(files[0])) return files if multiple else files[0]
def browse_folder_path(self, name, path=None): """ Browse for an existing directory. :return full directory path (absolute), or empty string if browsing was aborted """ dialog = QFileDialog(self.ui, name, path) dialog.setFileMode(QFileDialog.Directory) dialog.setOptions(QFileDialog.ShowDirsOnly) dialog.exec_() if dialog.result() == QFileDialog.Accepted: # only one directory can be selected in dialog window, # but the selectedFiles method only returns a list, so [0] is selected selected_path = dialog.selectedFiles()[0] return selected_path return ""
def getDirectoryName(self, title, settingname=None, savesetting=True): "Show a file dialog and select a directory" directory = self.settings.value( settingname, None) if settingname is not None else None dialog = QFileDialog(parent=self, caption=title, directory=directory) dialog.setAcceptMode(QFileDialog.AcceptSave) dialog.setFileMode(QFileDialog.Directory) dialog.setOption(QFileDialog.DontUseNativeDialog, True) dialog.setOption(QFileDialog.ShowDirsOnly, True) dialog.exec() dirs = dialog.selectedFiles() if not dialog.result() or not dirs: return None if savesetting: self.settings.setValue(settingname, dirs[0]) return dirs[0]
class MainApplication(QApplication, SignalSlot): rebuild_sprite_signal: pyqtSignal = pyqtSignal(int) region_info_signal: pyqtSignal = pyqtSignal(str, int, str, str, str, str, str) PX_MAPPINGS: Dict[str, Dict[Tuple[int, int, int, int], int]] = { 'climate_id': {(0, 0, 0, 0,): 0, (0, 77, 0, 255,): 1, (0, 118, 132, 255,): 2, (210, 0, 179, 255,): 3, (0, 255, 0, 255,): 4, (215, 129, 0, 255,): 5, (107, 107, 107, 255,): 6, (123, 0, 255, 255,): 7, (223, 223, 0, 255,): 8, }, 'relief_id': { (255, 85, 0, 255,): 4, (0, 21, 255, 255,): 3, }, 'rivers_id': { (0, 255, 251, 255,): 3, (11, 0, 255, 255,): 1 }, } def __init__(self, argv: List[str]): super().__init__(argv) # Initialize "world editor tool" attribute self.cur_paint_id: int = 0 self.cur_brush_id: int = 0 self.brushes: Dict[int, str] = {0: 'climate', 1: 'relief', 2: 'vegetation', 3: 'water', 4: 'world_object', 5: 'utility'} self.logger = get_logger(f'{__name__}: {type(self).__name__}') self.world_loaded: bool = False self.scale: Tuple[int, int] = (1, 1,) # Initialize GUI self.main_window: MainWindow = MainWindow() self.graphics_scene_map: GraphicsWorldmapScene = GraphicsWorldmapScene(self.rebuild_sprite_signal) self.main_window.ui.graphics_view_map.setScene(self.graphics_scene_map) self.message_box: QMessageBox = QMessageBox() # Common message box # Create new world dialog self.new_world_ui = NewWorldDialog() self.new_world_ui.ui.spinBox_2.setValue(10) self.new_world_ui.ui.spinBox.setValue(10) self.new_world_ui.ui.spinBox_2.setMinimum(1) self.new_world_ui.ui.spinBox.setMinimum(1) self.new_world_ui.ui.spinBox_2.setMinimum(1) self.new_world_ui.ui.spinBox.setMaximum(150) self.new_world_ui.ui.spinBox_2.setMaximum(100) # Initialize both open and save file GUI elements self.open_file_dialog = QFileDialog() self.open_file_dialog.setAcceptMode(QFileDialog.AcceptOpen) self.save_file_dialog = QFileDialog() self.save_file_dialog.setAcceptMode(QFileDialog.AcceptSave) # Initialize progress bar for loading etc # self.progress_bar = QProgressBar(self.main_window) # self.progress_bar.setGeometry(200, 80, 250, 20) # Connect editor climate buttons to logic self.main_window.ui.pushButton.pressed.connect(self.press_climate_button_sea) self.main_window.ui.pushButton_2.pressed.connect(self.press_climate_button_cont) self.main_window.ui.pushButton_3.pressed.connect(self.press_climate_button_oceanic) self.main_window.ui.pushButton_8.pressed.connect(self.press_climate_button_medi) self.main_window.ui.pushButton_7.pressed.connect(self.press_climate_button_tropical) self.main_window.ui.pushButton_9.pressed.connect(self.press_climate_button_arid) self.main_window.ui.pushButton_11.pressed.connect(self.press_climate_button_desert) self.main_window.ui.pushButton_10.pressed.connect(self.press_climate_button_nordic) self.main_window.ui.pushButton_12.pressed.connect(self.press_climate_button_polar) # Connect editor relief buttons to logic self.main_window.ui.pushButton_13.pressed.connect(self.press_relief_button_none) self.main_window.ui.pushButton_16.pressed.connect(self.press_relief_button_plains) self.main_window.ui.pushButton_17.pressed.connect(self.press_relief_button_rocky) self.main_window.ui.pushButton_19.pressed.connect(self.press_relief_button_hills) self.main_window.ui.pushButton_20.pressed.connect(self.press_relief_button_mountains) # Connect editor vegetation buttons to logic self.main_window.ui.pushButton_32.pressed.connect(self.press_vegetation_button_none) self.main_window.ui.pushButton_35.pressed.connect(self.press_vegetation_button_forrest) # Connect editor river buttons to logic self.main_window.ui.pushButton_33.pressed.connect(self.press_river_button_none) self.main_window.ui.pushButton_37.pressed.connect(self.press_river_button_estuary) self.main_window.ui.pushButton_38.pressed.connect(self.press_river_button_river) self.main_window.ui.pushButton_39.pressed.connect(self.press_river_button_maw) self.main_window.ui.pushButton_40.pressed.connect(self.press_river_button_lake) self.main_window.ui.pushButton_41.pressed.connect(self.press_river_button_swamp) # Connect editor primitive buttons to logic self.main_window.ui.pushButton_34.pressed.connect(self.press_primitive_button_none) self.main_window.ui.pushButton_36.pressed.connect(self.press_primitive_button_prim) # Connect editor utility buttons to logic self.main_window.ui.pushButton_99.pressed.connect(self.press_utility_button_only_sea) self.main_window.ui.pushButton_97.pressed.connect(self.press_utility_button_cont_flatlands) # Connect new, load and save world buttons to logic self.main_window.ui.actionLoad_world.triggered.connect(self.load_world) self.main_window.ui.actionNew_world.triggered.connect(self.new_world) self.main_window.ui.actionSave_world.triggered.connect(self.save_world) self.main_window.ui.actionTiny_world.triggered.connect(self.load_tiny_world) # Connect world menu self.main_window.ui.actionRemovePrimitives.triggered.connect(self.remove_primitives) self.main_window.ui.actionWorldinfo.triggered.connect(self.show_world_summary) # self.main_window.ui.actionShiftone.triggered.connect(self.shift_region) # Connect sprite rebuild signal # noinspection PyUnresolvedReferences self.rebuild_sprite_signal.connect(self.recreate_sprite_slot) # noinspection PyUnresolvedReferences self.region_info_signal.connect(self.display_region_info) # Create worldmap logic and send signals "downstream" self.worldmap: World = World(self.region_info_signal) # Open application self.main_window.show() def display_region_info(self, name: str, region_id: int, climate_str: str, relief_str: str, vegetation_str: str, water_str: str, world_object_str: str): """Slot that provides logic to populate the region information display""" self.logger.debug(f'Display region information of region {name}, with region ID {region_id}') text = ' \n'.join([f'Region name: {name}', f'Region ID: {region_id}', f'Climate: {climate_str}', f'Relief: {relief_str}', f'Vegetation: {vegetation_str}', f'Water: {water_str}', f'Primitive: {world_object_str}', '------------------']) self.main_window.ui.textBrowser.setPlainText(text) def recreate_sprite_slot(self, region_id: int): """Slot that provides brush functionality""" if self.cur_brush_id not in self.brushes: raise KeyError("Tried to use brush with unmapped brush id: %d. Brush id map: %s", self.cur_brush_id, str(self.brushes)) self.worldmap.paint_region(region_id=region_id, brush=self.brushes[self.cur_brush_id], paint_id=self.cur_paint_id, scale=self.scale) def remove_primitives(self): """Menu function to remove all primitive spawns on the worldmap""" if self.worldmap.regions: for region in self.worldmap.regions: if region.world_object_id == 1: region.world_object_id = 0 self.worldmap.create_region_sprite(region.region_id, self.scale, False) self.message_box.setText('Removed primitives') self.message_box.setIcon(QMessageBox.Information) self.message_box.show() else: self.message_box.setText('No region data present.') self.message_box.setIcon(QMessageBox.Critical) self.message_box.show() # def shift_region(self): # for region in self.worldmap.regions: # region.climate_id = 0 # region.relief_id = 0 # region.world_object_id = 0 # region.vegetation_id = 0 # region.water_id = 0 # for region in self.worldmap.regions: # self.worldmap.create_region_sprite(region.region_id, self.scale, signal_flag=False) def set_region_id_from_pixels(self, region: Region, px_climate: Image, px_relief: Image, px_rivers: Image, id_attrnames: Tuple[str, ...] = ('climate_id', 'relief_id', 'water_id',)): for attrname_an_px_map_key, px_tuple in zip(id_attrnames, (px_climate, px_relief, px_rivers)): try: tempmap = self.PX_MAPPINGS[attrname_an_px_map_key] except KeyError as err: self.logger.error("could not find key %s scorresponding to %s for in PX_CLIMATE_MAPPING: %s.", attrname_an_px_map_key, attrname_an_px_map_key.replace('_', ''), err) raise SystemExit(1) try: setattr(region, attrname_an_px_map_key, tempmap[px_tuple]) except KeyError as err: self.logger.error("could not find pixel key %s in PX_CLIMATE_MAPPING[%s]: %s.", str(px_tuple), attrname_an_px_map_key, err) raise SystemExit(1) except AttributeError as err: self.logger.error("'region' object does not have an attribute %s: %s.", attrname_an_px_map_key, err) raise SystemExit(1) def shift_region(self, climate_img_fname: str = 'europe_ymir.png', relief_img_fname: str = 'europe_relief.png', rivers_img_fname: str = 'europe_rivers.png'): img_climate: Image = Image.open(climate_img_fname) img_relief: Image = Image.open(relief_img_fname) img_rivers: Image = Image.open(rivers_img_fname) width: float = img_climate.size[0] / 100 height: float = img_climate.size[1] / 70 tmplist_px_climate = [] tmplist_px_relief = [] templist_px_rivers = [] for region in self.worldmap.regions: x = (region.x + 0.5) * width y = (region.y + 0.5) * height pixel_climate = img_climate.getpixel((x, y)) pixel_relief = img_relief.getpixel((x, y)) pixel_rivers = img_rivers.getpixel((x, y)) tmplist_px_climate.append(pixel_climate) tmplist_px_relief.append(pixel_relief) templist_px_rivers.append(pixel_rivers) self.set_region_id_from_pixels(region=region, px_climate=pixel_climate, px_relief=pixel_relief, px_rivers=pixel_rivers) # self.worldmap.create_region_sprite(region.region_id, self.scale, False) self.worldmap.create_all_region_sprites() # temp_sortlist = set(templist_px_rivers) # pixel_climate = img.getpixel((1,1)) def show_world_summary(self): """Menu function that gives an summary of the created worldmap""" # CLIMATES = ("SEA", "CONTINENTAL", "OCEANIC", "MEDITERRANEAN", "TROPICAL", "ARID", "DESERT", "NORDIC", # "POLAR", "UNKNOWN",) # RELIEF = ("NONE", "PLAIN", "ROCKY", "HILLS", "MOUNTAINS",) # VEGETATION = ("NONE", "FOREST", ) # WATER = ("NONE", "RIVER_SMALL", "RIVER_MED", "RIVER_LARGE", "LAKE", "SWAMP",) # WORLD_OBJECT = ("NONE", "SPAWN",) if self.worldmap.regions: self.message_box.setText(WorldSummary(regions=self.worldmap.regions).string) self.message_box.setIcon(QMessageBox.Information) self.message_box.show() else: self.message_box.setText('Cannot display summary as worldmap has no regions (yet?).') self.message_box.setIcon(QMessageBox.Critical) self.message_box.show() def save_world(self): self.save_file_dialog.exec_() filename = self.save_file_dialog.selectedFiles() filename = filename[0] if self.save_file_dialog.result() == 1 and self.worldmap.regions: self.worldmap.rebuild_region_list() if min(self.worldmap.region_info_lst) == -1: self.message_box.setText('There are regions of type "Unknown" still present, aborting..') self.message_box.setIcon(QMessageBox.Critical) self.message_box.show() return if '.ybin' in filename and self.worldmap.region_info_lst: with open(filename, mode='wb') as file: self.logger.debug('Saving worldmap to file, please wait..') file.write(bytearray(self.worldmap.region_info_lst)) self.logger.debug('File saved!') elif self.worldmap.region_info_lst: filename = filename + '.ybin' with open(filename, mode='wb') as file: file.write(bytes(self.worldmap.region_info_lst)) else: self.message_box.setText('Something went wrong') self.message_box.setIcon(QMessageBox.Critical) self.message_box.show() else: if not self.worldmap.regions: self.message_box.setText('Cannot save map as it has no regions (yet?).') else: self.message_box.setText('Something went wrong') self.message_box.setIcon(QMessageBox.Critical) self.message_box.show() return def load_tiny_world(self, scale): """Convenience function to load in pre-build tiny world for quick editting.""" world_file_path = Path(RESOURCES_DIR).joinpath('world').with_suffix('.ybin').absolute() self.graphics_scene_map.clear() self.worldmap.regions = [] self.worldmap.load_world(world_file_path) self.scale = (1, 1,) for region in self.worldmap.regions: x, y = region.region_xy_to_scene_coords(_REGION_IMAGE_WIDTH * self.scale[0], _REGION_IMAGE_HEIGHT * self.scale[1]) pos = QPointF(x, y) self.graphics_scene_map.create_scene_items_from_world(region, pos) def load_world(self): # !TODO: Reset view after creating a new scene. self.open_file_dialog.exec_() filename = self.open_file_dialog.selectedFiles() filename = filename[0] if filename else None if not filename: self.logger.debug("File loading dialog was cancelled; no world will be loaded.") return if self.open_file_dialog.result() == 1: if '.ybin' in filename: self.graphics_scene_map.clear() self.worldmap.regions = [] self.worldmap.load_world(filename) self.scale = (1, 1,) for region in self.worldmap.regions: x, y = region.region_xy_to_scene_coords(_REGION_IMAGE_WIDTH * self.scale[0], _REGION_IMAGE_HEIGHT * self.scale[1]) pos = QPointF(x, y) self.graphics_scene_map.create_scene_items_from_world(region, pos) else: self.message_box.setText('You have to load an .ybin world file.') self.message_box.setIcon(QMessageBox.Critical) self.message_box.show() else: return def new_world(self): # !TODO: Reset view after creating a new scene. self.new_world_ui.exec_() if self.new_world_ui.result() == 0: return elif self.new_world_ui.result() == 1: width = self.new_world_ui.ui.spinBox.value() height = self.new_world_ui.ui.spinBox_2.value() name = self.new_world_ui.ui.lineEdit.text() random_climate = self.new_world_ui.ui.checkBox.isChecked() self.worldmap.regions = [] self.graphics_scene_map.clear() self.scale = (1, 1,) self.worldmap.create_new_world(name, width, height, random_climate, self.scale) for region in self.worldmap.regions: pos = QPointF(*region.region_xy_to_scene_coords(_REGION_IMAGE_WIDTH * self.scale[0], _REGION_IMAGE_HEIGHT * self.scale[1])) self.graphics_scene_map.create_scene_items_from_world(item=region, pos=pos)
class ChildDialog(QDialog): def __init__(self, parent): QDialog.__init__(self, parent) self.session = parent.session self.signaler = self.session.signaler self.daemon_manager = self.session.daemon_manager self.signaler.server_status_changed.connect( self._server_status_changed) self.signaler.server_copying.connect(self._server_copying) self._root_folder_file_dialog = None self._root_folder_message_box = QMessageBox( QMessageBox.Critical, _translate('root_folder_dialogs', 'unwritable dir'), '', QMessageBox.NoButton, self) self.server_copying = parent.server_copying @classmethod def to_daemon(cls, *args): server = GuiServerThread.instance() if server: server.to_daemon(*args) else: sys.stderr.write('Error No GUI OSC Server, can not send %s.\n' % args) def _server_status_changed(self, server_status: int): pass def _server_copying(self, copying: bool): self.server_copying = copying self._server_status_changed(self.session.server_status) def _change_root_folder(self): # construct this here only because it can be quite long if self._root_folder_file_dialog is None: self._root_folder_file_dialog = QFileDialog( self, _translate("root_folder_dialogs", "Choose root folder for sessions"), CommandLineArgs.session_root) self._root_folder_file_dialog.setFileMode(QFileDialog.Directory) self._root_folder_file_dialog.setOption(QFileDialog.ShowDirsOnly) else: self._root_folder_file_dialog.setDirectory( CommandLineArgs.session_root) self._root_folder_file_dialog.exec() if not self._root_folder_file_dialog.result(): return selected_files = self._root_folder_file_dialog.selectedFiles() if not selected_files: return root_folder = selected_files[0] # Security, kde dialogs sends $HOME if user type a folder path # that doesn't already exists. if os.getenv('HOME') and root_folder == os.getenv('HOME'): return self._root_folder_message_box.setText( _translate( 'root_folder_dialogs', "<p>You have no permissions for %s,<br>choose another directory !</p>" ) % root_folder) if not os.path.exists(root_folder): try: os.makedirs(root_folder) except: self._root_folder_message_box.exec() return if not os.access(root_folder, os.W_OK): self._root_folder_message_box.exec() return RS.settings.setValue('default_session_root', root_folder) self.to_daemon('/ray/server/change_root', root_folder) def leaveEvent(self, event): if self.isActiveWindow(): self.parent().mouse_is_inside = False QDialog.leaveEvent(self, event) def enterEvent(self, event): self.parent().mouse_is_inside = True QDialog.enterEvent(self, event)