class TilesEditor(ChildWindow): def __init__(self): super(TilesEditor, self).__init__() self.config = Config() self.setWindowTitle('Tiles Editor') self.setFixedSize(640, 480) self._tab_widget = QtGui.QTabWidget(self) self._tab_widget.setGeometry(0, 0, 640, 480) self.subscribe('menu.trigger.import_tileset', self.on_import_tileset) self.subscribe('picture.mousemove', self.on_picture_mouse_move) self.subscribe('new_project', self.on_new_file) self.subscribe('open_project', self.on_open_file) self._workspaces = [] def on_new_file(self, message): self._workspaces = [] self._tab_widget.clear() def on_open_file(self, message): self.current_file = self.config.current_file() for tileset in self.current_file.get('tilesets', []): workspace = TileSetWorkspace( self, self.current_file['pathes'][tileset], self._queue ) workspace.show() self._tab_widget.addTab(workspace, workspace.base_name()) self._workspaces.append(workspace) self.send('tileset_editor.new_tileset', { 'tileset': workspace.base_name() }) self.send('tileset.update', { 'tileset': workspace.base_name() }) workspace.read_tilesets_config() self.send('open_maps') def on_import_tileset(self, message): file_name = QtGui.QFileDialog.getOpenFileName( self, filter='Tilesets (*.png)', directory=self.config.get( 'tilesets_images_dir', os.path.expanduser('~') ) ) if file_name: self.config.set( 'tilesets_images_dir', os.path.dirname(str(file_name)) ) self.send('config.save') workspace = TileSetWorkspace( self, str(file_name), self._queue ) workspace.show() self._tab_widget.addTab(workspace, workspace.base_name()) self._workspaces.append(workspace) self.send('tileset_editor.new_tileset', { 'tileset': workspace.base_name() }) def on_picture_mouse_move(self, message): for workspace in self._workspaces: workspace.on_picture_mouse_move(message) def remove_current(self): self._workspaces.pop(self._tab_widget.currentIndex()) self._tab_widget.removeTab(self._tab_widget.currentIndex())
class FightEnvironmentWorkspace(QtGui.QFrame): LAYERS = ('screen', 'background3', 'background2', 'background1') def __init__(self, name): super(FightEnvironmentWorkspace, self).__init__() self._name = name self._height_ratio = 1.0 self.config = Config() self.setGeometry(0, 0, 320, 220) self._layers = {} self._buttons = [] self._current_conf = Config().current_file()['fightenv'][name] for layer_name in self.LAYERS: self._layers[layer_name] = self.new_layer() self._layers['screen'].setStyleSheet( 'QLabel {' 'background-color: black;' 'border: 3px solid #ccccff;' '}' ) self.new_button('Set background 1', 2, self.on_choose_background1, False) self.new_button('Set background 2', 104, self.on_choose_background2, False) self.new_button('Set background 3', 206, self.on_choose_background3) self._play_animation = IconButton(self, 'play_animation', 30, 30) self._play_animation.setGeometry(145, 182, 30, 30) self._play_animation.clicked.connect(self.on_play_animation) def on_play_animation(self): self._preview = FightEnvironmentPreview(self._name) self._preview.show() def name(self): return self._name def read_conf(self): for layer_name in self.LAYERS: if layer_name in self._current_conf: path = Config().current_file()['pathes'][self._current_conf[layer_name]] pixmap = QtGui.QPixmap(path) self.show_background(pixmap, layer_name) def show_background(self, pixmap, name): height = self._layers[name].height() width = (pixmap.width() * height) / pixmap.height() self._height_ratio = float(height) / pixmap.height() self._layers[name].setPixmap(pixmap.scaled(width, height)) for button in self._buttons: button.setEnabled(True) def on_choose_background3(self): pixmap = self.choose_and_save_image('background3') if pixmap: self.show_background(pixmap, 'background3') def on_choose_background2(self): pixmap = self.choose_and_save_image('background2') if pixmap: self.show_background(pixmap, 'background2') def on_choose_background1(self): pixmap = self.choose_and_save_image('background1') if pixmap: self.show_background(pixmap, 'background1') def choose_and_save_image(self, image_name): file_name = QtGui.QFileDialog.getOpenFileName( self, filter='Image (*.png)', directory=self.config.get( 'fightenv_image_dir', os.path.expanduser('~') ) ) if file_name: from mqme.mainwindow import MainWindow file_name = unicode(file_name) self.config.set('fightenv_image_dir', os.path.dirname(file_name)) MainWindow.queue.put({'name': 'config.save'}) if not 'pathes' in Config().current_file(): Config().current_file()['pathes'] = {} image_id = str(uuid.uuid1()) Config().current_file()['pathes'][image_id] = file_name self._current_conf[image_name] = image_id return QtGui.QPixmap(file_name) def new_button(self, name, x, callback, enabled=True): button = QtGui.QPushButton(name, self) button.setGeometry(x, 155, 100, 20) button.clicked.connect(callback) button.setEnabled(enabled) button.setStyleSheet( 'QPushButton {' 'font-size: 10px;' '}' ) button.show() self._buttons.append(button) def new_layer(self): layer = QtGui.QLabel(self) layer.setGeometry(7, 0, 300, 150) layer.show() return layer
class MainWindow(QtGui.QMainWindow): resources_directories = ( 'fightenv', 'images', 'maps', 'sprites', 'tiles' ) queue = Queue.Queue() CHILDREN = ( TilesEditor, MapEditor, LayersDialog, SpritesEditor, FightEnvironmentEditor ) def __init__(self, resolution, title): super(MainWindow, self).__init__() self.config = Config() self.config.init_from_file(settings.CONFIG_FILE_PATH) self._timer = QtCore.QTimer(self) self.setWindowIcon(QtGui.QIcon(os.path.join( settings.IMAGES_PATH, 'mqme.png' ))) MainWindow._subscriptions = { 'config.save': self.on_save_config_request, 'menu.trigger.save_project': self.on_save_project_request, 'menu.trigger.new_project': self.on_new_project_request, 'menu.trigger.open_project': self.on_open_project_request, 'menu.trigger.export_to_resources': self.on_export_request } self.connect(self._timer, QtCore.SIGNAL("timeout()"), self.update) self.workspace = QtGui.QWorkspace() width, height = resolution self.resize(width, height) self.setWindowTitle(title) self.setCentralWidget(self.workspace) self.setMenuBar(MenuBar(self, self.on_menu_triggered)) self._children = [] for child_type in self.CHILDREN: child = child_type() self.workspace.addWindow(child) child.set_queue(MainWindow.queue) self._children.append(child) self._timer.start(settings.QUEUE_READ_LOOP_DELAY) def keyPressEvent(self, event): super(MainWindow, self).keyPressEvent(event) self.send('key_press', {'key': event.key()}) def keyReleaseEvent(self, event): super(MainWindow, self).keyReleaseEvent(event) self.send('key_release', {'key': event.key()}) def send(self, name, body=None): message = {'name': name} if body is not None: message.update(body) MainWindow.queue.put(message) @staticmethod def subscribe(message_name, callback): MainWindow._subscriptions[message_name] = callback def receive(self, message): name = message.get('name') if name in MainWindow._subscriptions: MainWindow._subscriptions[name](message) def on_menu_triggered(self, menu_name): self.send('menu.trigger.%s' % menu_name) def update(self): try: index = 0 while index < settings.QUEUE_READ_LOOP_DELAY: message = MainWindow.queue.get_nowait() self.receive(message) for child in self._children: child.receive(message) index += 1 except Queue.Empty: pass def on_save_config_request(self, message): self.config.save(settings.CONFIG_FILE_PATH) def on_save_project_request(self, message): file_name = QtGui.QFileDialog.getSaveFileName( self, filter='MQME Projects (*.mqp)', directory=self.config.get( 'project_dir', os.path.expanduser('~') ) ) if file_name: file_name = unicode(file_name) self.config.set( 'project_dir', os.path.dirname(file_name) ) self.config.save(settings.CONFIG_FILE_PATH) self.config.save_current_file(file_name) def on_open_project_request(self, message): file_name = QtGui.QFileDialog.getOpenFileName( self, filter='MQME Projects (*.mqp)', directory=self.config.get( 'project_dir', os.path.expanduser('~') ) ) if file_name: self.send('new_project') file_name = unicode(file_name) self.config.set( 'project_dir', os.path.dirname(file_name) ) self.config.save(settings.CONFIG_FILE_PATH) self.config.read_current_file(file_name) self.send('open_project') def on_new_project_request(self, message): self.config.reset_current_file() self.send('new_project') def on_export_request(self, message): if self.config.has_changed(): msg_box = QtGui.QMessageBox(self) msg_box.setWindowTitle('Config has changed') msg_box.setText( 'You need to save the project before exporting' ) msg_box.setDefaultButton(QtGui.QMessageBox.Cancel) msg_box.exec_() return current_file = self.config.current_file() directory = None if 'pathes' in current_file: directory = current_file['pathes'].get('resources_dir', None) if directory is None: directory = QtGui.QFileDialog.getExistingDirectory( self, directory=self.config.get( 'resources_dir', os.path.expanduser('~') ) ) if directory is not None: directory = str(directory) if not 'pathes' in current_file: current_file['pathes'] = {} current_file['pathes']['resources_dir'] = directory self.config.set('resources_dir', directory) self.config.save(settings.CONFIG_FILE_PATH) for sub_directory in self.resources_directories: subpath = os.path.join(directory, sub_directory) if not os.path.exists(subpath): os.makedirs(subpath) self.export_maps(directory) self.export_tilesets(directory) self.export_sprites(directory) self.export_fight_environments(directory) msg_box = QtGui.QMessageBox(self) msg_box.setWindowTitle('Export') msg_box.setText( 'Project exported successfully !' ) msg_box.setDefaultButton(QtGui.QMessageBox.Cancel) msg_box.exec_() def export_physics(self, sps_obj, id, directory): svg_path = os.path.join(directory, id + '.svg') if os.path.exists(svg_path): with open(svg_path) as raw_file: dom = parse(raw_file) root = dom.getElementsByTagName('svg')[0] image = root.getElementsByTagName('image')[0] width = float(image.getAttribute('width')) height = float(image.getAttribute('height')) x = float(image.getAttribute('x')) y = float(image.getAttribute('y')) for rect in root.getElementsByTagName('rect'): physics_type = rect.getAttribute('id').split('_')[0] if physics_type != 'platform': physics_type = 'ground' sps_obj['physics'][id].append({ 'x': (float(rect.getAttribute('x')) - x) / width, 'y': (float(rect.getAttribute('y')) - y) / height, 'width': float(rect.getAttribute('width')) / width, 'height': float(rect.getAttribute('height')) / height, 'type': physics_type }) for circle in root.getElementsByTagName('path'): sodipodi_type = circle.getAttribute('sodipodi:type') if sodipodi_type == 'arc': cx = float(circle.getAttribute('sodipodi:cx')) transform_raw = circle.getAttribute('transform') transform_raw = transform_raw.replace('translate(', '').replace(')', '') foe_x = float(transform_raw.split(',')[0]) + cx sps_obj['foes'][id].append({ 'x': (foe_x - x) / width }) if id.find('town_d') > -1: print cx, transform_raw, foe_x, x, (foe_x - x) / width def export_fight_environments(self, directory): fightenv_directory = os.path.join(directory, 'fightenv') environments = self.config.current_file().get('fightenv', {}) layers = ('background3', 'background2') for name, fight_env in environments.items(): sps_obj = {'backgrounds': [], 'physics': {}, 'foes': {}} max_width = 0 max_height = 0 images = {} bg1_path = self.config.current_file()['pathes'][ fight_env['background1'] ] bg1_key = os.path.basename(bg1_path).split('_')[0] bg1_directory = os.path.dirname(bg1_path) for image_name in os.listdir(bg1_directory): if image_name.startswith(bg1_key) and image_name.endswith('.png'): image_id = image_name.split('_', 1)[1].split('.')[0] image_path = os.path.join(bg1_directory, image_name) sps_obj['physics'][name + '_' + image_id] = [] sps_obj['foes'][name + '_' + image_id] = [] self.export_physics( sps_obj, name + '_' + image_id, os.path.dirname(image_path) ) image = utils.get_resized_image(image_path) output_path = os.path.join( fightenv_directory, name + '_' + image_id + '.png' ) image.save(output_path) sps_obj['backgrounds'].append('fightenv/' + name + '_' + image_id) for layer_index in (2, 3): image = utils.get_resized_image( self.config.current_file()['pathes'][ fight_env['background' + str(layer_index)] ] ) output_path = os.path.join( fightenv_directory, name + '_bg' + str(layer_index) + '.png' ) image.save(output_path) sps_obj['backgrounds'].append( 'fightenv/' + name + '_bg' + str(layer_index) ) base_path = os.path.join(fightenv_directory, name) with open(base_path + '.sps', 'w') as sps: sps.write(json.dumps(sps_obj)) def export_sprites(self, directory): sprites_directory = os.path.join(directory, 'sprites') actors = self.config.current_file().get('actors', {}) for actor_name in actors: SpritesGenerator(actor_name).save(sprites_directory) def export_maps(self, directory): maps = self.config.current_file().get('maps', {}) for map_name, map_obj in maps.items(): path = os.path.join(directory, 'maps', map_name + '.map') map_obj['camera'] = [ map_obj['camera_width'], map_obj['camera_height'] ] map_obj['size'] = [ map_obj['width'], map_obj['height'] ] x = 0 for matrix_line in map_obj['specials']: y = 0 for special_conf in matrix_line: if special_conf['class_name'] == 'start_pos': map_obj['start_pos'] = [x, y] y += 1 x += 1 with open(path, 'w') as map_file: map_file.write(json.dumps(map_obj, sort_keys=True, indent=4)) def export_tilesets(self, directory): tilesets = self.config.current_file().get('tilesets', {}) for tileset_name, tileset_obj in tilesets.items(): path = os.path.join(directory, 'tiles', tileset_name + '.sps') with open(path, 'w') as tileset_file: tileset_file.write(json.dumps( tileset_obj, sort_keys=True, indent=4 ))
class AnimationArea(QtGui.QFrame): FPS_VALUES = [40, 35, 30, 25, 20, 15, 10, 5, 2, 1] NB_MULTIPLIERS = 100 def __init__(self, parent, actor_name, animation_name): super(AnimationArea, self).__init__(parent) self._parent = parent self.config = Config() self.current_conf = Config().current_file()[ 'actors' ][actor_name]['animations'][animation_name] self._current_frame = 0 self._actor_name = actor_name self._animation_name = animation_name self.setGeometry(0, 0, 480, 450) self._frames_list = QtGui.QComboBox(self) self._frames_list.setGeometry(5, 420, 100, 20) self._frames_list.currentIndexChanged.connect(self.on_change_frame) self._add_frame_button = IconButton(self, 'add_frame', 50, 50) self._add_frame_button.setGeometry(110, 420, 20, 20) self._add_frame_button.clicked.connect(self.on_add_frame) self._remove_frame_button = IconButton(self, 'remove_frame', 50, 50) self._remove_frame_button.setGeometry(135, 420, 20, 20) self._remove_frame_button.clicked.connect(self.on_remove_frame) self._scroll_areas = {} self._add_picture_button = IconButton(self, 'add_picture', 50, 50) self._add_picture_button.setGeometry(402, 2, 50, 50) self._add_picture_button.clicked.connect(self.on_new_picture) self._remove_animation_button = IconButton( self, 'remove_animation', 50, 50 ) self._remove_animation_button.setGeometry(402, 54, 50, 50) self._remove_animation_button.clicked.connect(self.on_remove_animation) self._resize_button = QtGui.QRadioButton('Resize', self) self._resize_button.setGeometry(170, 420, 70, 20) self._move_button = QtGui.QRadioButton('Move', self) self._move_button.setGeometry(240, 420, 70, 20) self._move_button.setChecked(True) self._multiplier = QtGui.QComboBox(self) self._multiplier.setGeometry(402, 380, 50, 20) for multiplier in range(1, self.NB_MULTIPLIERS + 1): self._multiplier.addItem('x' + str(multiplier)) self._pos_label = QtGui.QLabel(self) self._pos_label.setGeometry(402, 100, 50, 100) self._play_animation = IconButton(self, 'play_animation', 50, 50) self._play_animation.setGeometry(402, 210, 50, 50) self._play_animation.clicked.connect(self.on_play_animation) self._fps_choice = QtGui.QComboBox(self) self._fps_choice.setGeometry(310, 420, 150, 20) if not len(self.current_conf): self._scroll_areas[0] = QtGui.QScrollArea(self) self._scroll_areas[0].setGeometry(0, 0, 400, 400) self._scroll_areas[0].setWidget(SpriteGridWidget(self)) self._frames_list.addItem('Frame 0') self.current_conf['fps'] = 40 for value in self.FPS_VALUES: period = 1000 / value self._fps_choice.addItem('%d FPS - %d ms' % (value, period)) self._fps_choice.setCurrentIndex(self.FPS_VALUES.index( self.current_conf['fps'] )) self._fps_choice.currentIndexChanged.connect(self.on_change_fps) self._multiplier.currentIndexChanged.connect(self.on_change_multiplier) def on_change_multiplier(self): frame_key = 'frame_%d' % self._current_frame conf = None for elem in self.current_conf[frame_key]: if elem.get('type', None) == 'conf': conf = elem break if conf is None: conf = {'type': 'conf'} self.current_conf[frame_key].append(conf) multiplier = self._multiplier.currentIndex() + 1 conf['multiplier'] = multiplier def on_play_animation(self): preview = AnimationPreview( self, self._actor_name, self._animation_name ) preview.show() def read_conf(self): for index in range(len(self.current_conf) - 1): frame_key = 'frame_%d' % index self._scroll_areas[index] = QtGui.QScrollArea(self) self._scroll_areas[index].setGeometry(0, 0, 400, 400) self._scroll_areas[index].setWidget(SpriteGridWidget(self)) for obj in self.current_conf[frame_key]: if obj.get('type', None) != 'conf': frame_image = FrameImage( self._scroll_areas[index].widget(), self.config.current_file()['pathes'][obj['id']], obj['id'], frame_key ) frame_image.setGeometry( 200 + obj['x'] * 100, 200 + obj['y'] * 100, obj['width'] * 100, obj['height'] * 100, ) frame_image.show() self._frames_list.addItem('Frame %d' % index) self._scroll_areas[index].hide() self._current_frame = 0 self._scroll_areas[0].show() self._frames_list.setCurrentIndex(0) frame_key = 'frame_%d' % self._current_frame conf = None for elem in self.current_conf[frame_key]: if elem.get('type', None) == 'conf': conf = elem break if conf is None: conf = {'type': 'conf'} self.current_conf[frame_key].append(conf) multiplier = conf.get('multiplier', 1) self._multiplier.setCurrentIndex(multiplier - 1) def remove_animation(self): animations = Config().current_file()[ 'actors'][self._actor_name]['animations'] del animations[self._animation_name] def on_remove_animation(self): self._parent.remove_animation(self._animation_name) def on_change_fps(self): self.current_conf['fps'] = self.FPS_VALUES[ self._fps_choice.currentIndex() ] def on_remove_frame(self): if len(self._scroll_areas) > 1: if self._current_frame == len(self._scroll_areas) - 1: frame_key = self.get_frame_key() if frame_key in self.current_conf: del self.current_conf[frame_key] self._scroll_areas[self._current_frame].hide() del self._scroll_areas[self._current_frame] self._current_frame -= 1 self._frames_list.setCurrentIndex(self._current_frame) self._frames_list.removeItem(len(self._scroll_areas)) def move_image(self, image, gap): gap_x, gap_y = gap obj = None rect = image.geometry() for image_obj in self.current_conf[image.frame_key()]: if image_obj.get('type', None) != 'conf': if image_obj['id'] == image.image_id(): obj = image_obj if self._move_button.isChecked(): x, y = rect.x() + gap_x, rect.y() + gap_y image.setGeometry(x, y, rect.width(), rect.height()) obj['x'] = (x - 200.0) / 100.0 obj['y'] = (y - 200.0) / 100.0 else: width = rect.width() + gap_x height = width * rect.height() / rect.width() image.setGeometry(rect.x(), rect.y(), width, height) obj['width'] = width / 100.0 obj['height'] = height / 100.0 self.update_label(obj) def update_label(self, image_obj): self._pos_label.setText( 'X : %s\nY : %s\nW : %s\nH : %s' % ( str(image_obj['x']), str(image_obj['y']), str(image_obj['width']), str(image_obj['height']) ) ) def on_change_frame(self): self._scroll_areas[self._current_frame].hide() self._current_frame = self._frames_list.currentIndex() frame_key = 'frame_%d' % self._current_frame if frame_key in self.current_conf: conf = None for elem in self.current_conf.get(frame_key, []): if elem.get('type', None) == 'conf': conf = elem break if conf is None: conf = {'type': 'conf'} self.current_conf[frame_key].append(conf) multiplier = conf.get('multiplier', 1) else: multiplier = 1 self._multiplier.setCurrentIndex(multiplier - 1) self._scroll_areas[self._current_frame].show() def on_add_frame(self): if self._current_frame == len(self._scroll_areas) - 1: self._scroll_areas[self._current_frame].hide() self._current_frame += 1 frame_label = 'Frame %d' % self._current_frame self._scroll_areas[self._current_frame] = QtGui.QScrollArea(self) self._scroll_areas[self._current_frame].setGeometry(0, 0, 400, 400) self._scroll_areas[self._current_frame].setWidget( SpriteGridWidget(self) ) self._frames_list.addItem(frame_label) self._frames_list.setCurrentIndex(self._current_frame) frame_key = self.get_frame_key() if not frame_key in self.current_conf: self.current_conf[frame_key] = [] def get_frame_key(self): return 'frame_%d' % self._current_frame def remove_image(self, frame_image): frame_image.hide() index = 0 for image_obj in self.current_conf[frame_image.frame_key()]: if not 'type' in image_obj or image_obj['type'] != 'conf': if image_obj['id'] == frame_image.image_id(): self.current_conf[frame_image.frame_key()].pop(index) index += 1 def on_new_picture(self): file_name = QtGui.QFileDialog.getOpenFileName( self, filter='Image (*.png)', directory=self.config.get( 'frame_image_dir', os.path.expanduser('~') ) ) if file_name: from mqme.mainwindow import MainWindow file_name = unicode(file_name) self.config.set('frame_image_dir', os.path.dirname(file_name)) MainWindow.queue.put({'name': 'config.save'}) if not 'pathes' in Config().current_file(): Config().current_file()['pathes'] = {} image_id = str(uuid.uuid1()) Config().current_file()['pathes'][image_id] = file_name frame_key = self.get_frame_key() if not frame_key in self.current_conf: self.current_conf[frame_key] = [] frame_image = FrameImage( self._scroll_areas[self._current_frame].widget(), file_name, image_id, frame_key ) frame_image.show() width, height = frame_image.ratio() x = 0.5 - (width / 2.0) y = 1.0 - height self.current_conf[frame_key].append({ 'id': image_id, 'width': width, 'height': height, 'x': x, 'y': y }) frame_image.setGeometry( 200 + x * 100, 200 + y * 100, width * 100, height * 100 )