class MainWindow(QtGui.QWidget, CommandBase): def update_nw_versions(self, button): self.get_versions_in_background() def __init__(self, width, height, parent=None): super(MainWindow, self).__init__(parent) CommandBase.__init__(self) self.output_package_json = True self.setWindowIcon( QtGui.QIcon(os.path.join(CWD, 'files', 'images', 'icon.png'))) self.update_json = False self.setup_nw_versions() self.thread = None self.original_packagejson = {} self.resize(width, height) self.extract_error = None self.create_application_layout() self.option_settings_enabled(False) self.setWindowTitle("Web2Executable {}".format(__gui_version__)) self.update_nw_versions(None) def setup_nw_versions(self): nw_version = self.get_setting('nw_version') try: f = open(os.path.join(CWD, 'files', 'nw-versions.txt')) for line in f: nw_version.values.append(line.strip()) except IOError: nw_version.values.append(nw_version.default_value) def create_application_layout(self): self.main_layout = QtGui.QVBoxLayout() self.main_layout.setContentsMargins(10, 5, 10, 5) self.create_layout_widgets() self.addWidgets_to_main_layout() self.setLayout(self.main_layout) def create_layout_widgets(self): self.download_bar_widget = self.create_download_bar() self.app_settings_widget = self.create_application_settings() self.win_settings_widget = self.create_window_settings() self.ex_settings_widget = self.create_export_settings() self.dl_settings_widget = self.create_download_settings() self.directory_chooser_widget = self.create_directory_choose() def addWidgets_to_main_layout(self): self.main_layout.addWidget(self.directory_chooser_widget) self.main_layout.addWidget(self.app_settings_widget) self.main_layout.addWidget(self.win_settings_widget) self.main_layout.addWidget(self.ex_settings_widget) self.main_layout.addWidget(self.dl_settings_widget) self.main_layout.addLayout(self.download_bar_widget) def option_settings_enabled(self, is_enabled): self.ex_button.setEnabled(is_enabled) self.app_settings_widget.setEnabled(is_enabled) self.win_settings_widget.setEnabled(is_enabled) self.ex_settings_widget.setEnabled(is_enabled) self.dl_settings_widget.setEnabled(is_enabled) def export(self, export_button, cancel_button): self.get_files_to_download() self.try_to_download_files() def open_export(self, open_export_button): open_folder_in_explorer(self.output_dir()) def try_to_download_files(self): if self.files_to_download: self.progress_bar.setVisible(True) self.cancel_button.setEnabled(True) self.disable_ui_while_working() self.download_file_with_error_handling() else: # This shouldn't happen since we disable the UI if there are no # options selected # But in the weird event that this does happen, we are prepared! QtGui.QMessageBox.information(self, 'Export Options Empty!', ('Please choose one of ' 'the export options!')) def selected_version(self): return self.get_setting('nw_version').value def enable_ui_after_error(self): self.enable_ui() self.progress_text = '' self.progress_bar.setVisible(False) self.cancel_button.setEnabled(False) def show_error(self, exception): QtGui.QMessageBox.information(self, 'Error!', str(exception)) def disable_ui_while_working(self): self.option_settings_enabled(False) self.directory_chooser_widget.setEnabled(False) def enable_ui(self): self.option_settings_enabled(True) self.directory_chooser_widget.setEnabled(True) def required_settings_filled(self): proj_dir = self.project_dir() out_dir = self.output_dir() if proj_dir and out_dir: if os.path.exists(proj_dir): valid_proj_dirs = True settings_valid = True for sgroup in self.settings['setting_groups']: for sname, setting in sgroup.items(): setting_path = os.path.join(self.project_dir(), str(setting.value)) if setting.required and not setting.value: return False if (setting.type == 'file' and setting.value and not os.path.exists(setting_path)): log(setting.value, "does not exist") settings_valid = False if (setting.type == 'folder' and setting.value and not os.path.exists(setting_path)): settings_valid = False export_chosen = False for setting_name, setting in self.settings['export_settings'].items(): if setting.value: export_chosen = True return export_chosen and valid_proj_dirs and settings_valid def project_dir(self): if hasattr(self, 'input_line'): return self.input_line.text() return '' def output_dir(self): if hasattr(self, 'output_line'): return self.output_line.text() return '' def create_download_bar(self): hlayout = QtGui.QHBoxLayout() vlayout = QtGui.QVBoxLayout() vlayout.setContentsMargins(5, 5, 5, 5) vlayout.setSpacing(5) hlayout.setSpacing(5) hlayout.setContentsMargins(5, 5, 5, 5) progress_label = QtGui.QLabel('') progress_bar = QtGui.QProgressBar() progress_bar.setVisible(False) progress_bar.setContentsMargins(5, 5, 5, 5) vlayout.addWidget(progress_label) vlayout.addWidget(progress_bar) vlayout.addWidget(QtGui.QLabel('')) ex_button = QtGui.QPushButton('Export') ex_button.setEnabled(False) cancel_button = QtGui.QPushButton('Cancel Download') cancel_button.setEnabled(False) open_export_button = QtGui.QPushButton() open_export_button.setEnabled(False) open_export_button.setIcon( QtGui.QIcon(os.path.join('files', 'images', 'folder_open.png'))) open_export_button.setToolTip('Open Export Folder') open_export_button.setMaximumWidth(30) open_export_button.setMaximumHeight(30) ex_button.clicked.connect( self.call_with_object('export', ex_button, cancel_button)) cancel_button.clicked.connect(self.cancel_download) open_export_button.clicked.connect( self.call_with_object('open_export', open_export_button)) button_box = QtGui.QDialogButtonBox() button_box.addButton(open_export_button, QtGui.QDialogButtonBox.NoRole) button_box.addButton(cancel_button, QtGui.QDialogButtonBox.RejectRole) button_box.addButton(ex_button, QtGui.QDialogButtonBox.AcceptRole) hlayout.addLayout(vlayout) hlayout.addWidget(button_box) self.progress_label = progress_label self.progress_bar = progress_bar self.cancel_button = cancel_button self.open_export_button = open_export_button http = QHttp(self) http.requestFinished.connect(self.http_request_finished) http.dataReadProgress.connect(self.update_progress_bar) http.responseHeaderReceived.connect(self.read_response_header) self.http = http self.ex_button = ex_button return hlayout def read_response_header(self, response_header): # Check for genuine error conditions. if response_header.statusCode() not in (200, 300, 301, 302, 303, 307): self.show_error('Download failed: {}.'.format( response_header.reasonPhrase())) self.http_request_aborted = True self.http.abort() self.enable_ui_after_error() def http_request_finished(self, request_id, error): if request_id != self.http_get_id: return if self.http_request_aborted: if self.out_file is not None: self.out_file.close() self.out_file.remove() self.out_file = None return self.out_file.close() self.http.abort() if error: self.out_file.remove() self.show_error('Download failed: {}.'.format( self.http.errorString())) self.enable_ui_after_error() else: self.continue_downloading_or_extract() def continue_downloading_or_extract(self): if self.files_to_download: self.progress_bar.setVisible(True) self.cancel_button.setEnabled(True) self.disable_ui_while_working() self.download_file_with_error_handling() else: self.progress_text = 'Done.' self.cancel_button.setEnabled(False) self.progress_bar.setVisible(False) self.extract_files_in_background() @property def progress_text(self): return self.progress_label.text() @progress_text.setter def progress_text(self, value): self.progress_label.setText(str(value)) def run_in_background(self, method_name, callback): self.thread = BackgroundThread(self, method_name) self.thread.finished.connect(callback) self.thread.start() def get_versions_in_background(self): self.ex_button.setEnabled(False) self.run_in_background('get_versions', self.done_getting_versions) def done_getting_versions(self): self.ex_button.setEnabled(self.required_settings_filled()) self.progress_text = 'Done retrieving versions.' nw_version = self.get_setting('nw_version') combo = self.find_child_by_name(nw_version.name) combo.clear() combo.addItems(nw_version.values) def make_output_files_in_background(self): self.ex_button.setEnabled(False) self.run_in_background('make_output_dirs', self.done_making_files) def done_making_files(self): self.ex_button.setEnabled(self.required_settings_filled()) self.progress_text = 'Done Exporting.' self.enable_ui() self.delete_files() if self.output_err: self.show_error(self.output_err) self.enable_ui_after_error() def extract_files_in_background(self): self.progress_text = 'Extracting.' self.ex_button.setEnabled(False) self.run_in_background('extract_files', self.done_extracting) def done_extracting(self): self.ex_button.setEnabled(self.required_settings_filled()) if self.extract_error: self.progress_text = 'Error extracting.' self.show_error('There were one or more errors with your ' 'zip/tar files. They were deleted. Please ' 'try to export again.') self.enable_ui_after_error() else: self.progress_text = 'Done extracting.' self.make_output_files_in_background() def cancel_download(self): self.progress_text = 'Download cancelled.' self.cancel_button.setEnabled(False) self.http_request_aborted = True self.http.abort() self.enable_ui() self.progress_bar.setValue(0) self.progress_bar.setVisible(False) def update_progress_bar(self, bytes_read, total_bytes): if self.http_request_aborted: self.progress_bar.setValue(0) self.progress_bar.setVisible(False) return self.progress_bar.setMaximum(total_bytes) self.progress_bar.setValue(bytes_read) def download_file(self, path, setting): version_file = self.settings['base_url'].format( self.selected_version()) location = self.get_setting('download_dir').value versions = re.findall('v(\d+)\.(\d+)\.(\d+)', path)[0] minor = int(versions[1]) if minor >= 12: path = path.replace('node-webkit', 'nwjs') self.progress_text = 'Downloading {}'.format( path.replace(version_file, '')) url = QUrl(path) file_name = setting.save_file_path(self.selected_version(), location) archive_exists = QFile.exists(file_name) #dest_files_exist = False # for dest_file in setting.dest_files: # dest_file_path = os.path.join('files', setting.name, dest_file) # dest_files_exist &= QFile.exists(dest_file_path) forced = self.get_setting('force_download').value if archive_exists and not forced: self.continue_downloading_or_extract() return self.out_file = QFile(file_name) if not self.out_file.open(QIODevice.WriteOnly): error = self.out_file.error().name self.show_error('Unable to save the file {}: {}.'.format( file_name, error)) self.out_file = None self.enable_ui() return mode = QHttp.ConnectionModeHttp port = url.port() if port == -1: port = 0 self.http.setHost(url.host(), mode, port) self.http_request_aborted = False path = QUrl.toPercentEncoding(url.path(), "!$&'()*+,;=:@/") if path: path = str(path) else: path = '/' # Download the file. self.http_get_id = self.http.get(path, self.out_file) def create_directory_choose(self): group_box = QtGui.QGroupBox("Choose Your Web Project") input_layout = QtGui.QHBoxLayout() input_label = QtGui.QLabel('Project Directory:') self.input_line = QtGui.QLineEdit() self.input_line.textChanged.connect(self.project_path_changed) input_button = QtGui.QPushButton('...') input_button.clicked.connect(self.browse_dir) input_layout.addWidget(input_label) input_layout.addWidget(self.input_line) input_layout.addWidget(input_button) output_layout = QtGui.QHBoxLayout() output_label = QtGui.QLabel('Output Directory:') self.output_line = QtGui.QLineEdit() self.output_line.textChanged.connect(self.project_path_changed) output_button = QtGui.QPushButton('...') output_button.clicked.connect(self.browse_out_dir) output_layout.addWidget(output_label) output_layout.addWidget(self.output_line) output_layout.addWidget(output_button) vlayout = QtGui.QVBoxLayout() vlayout.setSpacing(5) vlayout.setContentsMargins(10, 5, 10, 5) vlayout.addLayout(input_layout) vlayout.addLayout(output_layout) group_box.setLayout(vlayout) return group_box def call_with_object(self, name, obj, *args, **kwargs): """Allows arguments to be passed to click events""" def call(*cargs, **ckwargs): if hasattr(self, name): func = getattr(self, name) kwargs.update(ckwargs) func(obj, *(args + cargs), **kwargs) return call def find_child_by_name(self, name): return self.findChild(QtCore.QObject, name) def find_all_children(self, names): children = [] for child in self.find_children(QtCore.QObject): if child.object_name() in names: children.append(child) return children def project_name(self): return self.find_child_by_name('app_name').text() def browse_dir(self): self.update_json = False directory = QtGui.QFileDialog.getExistingDirectory( self, "Find Project Directory", self.project_dir() or QtCore.QDir.currentPath()) if directory: self.reset_settings() self.input_line.setText(directory) self.output_line.setText(os.path.join(directory, 'output')) proj_name = os.path.basename(directory) setting_input = self.find_child_by_name('main') files = (glob.glob(os.path.join(directory, 'index.html')) + glob.glob(os.path.join(directory, 'index.php')) + glob.glob(os.path.join(directory, 'index.htm'))) if not setting_input.text(): if files: setting_input.setText(files[0].replace( self.project_dir() + os.path.sep, '')) app_name_input = self.find_child_by_name('app_name') name_input = self.find_child_by_name('name') name_setting = self.get_setting('name') title_input = self.find_child_by_name('title') if not name_input.text(): name_input.setText(name_setting.filter_name(proj_name)) if not app_name_input.text(): app_name_input.setText(proj_name) if not title_input.text(): title_input.setText(proj_name) self.load_package_json() self.open_export_button.setEnabled(True) self.update_json = True def browse_out_dir(self): self.update_json = False directory = QtGui.QFileDialog.getExistingDirectory( self, "Choose Output Directory", (self.output_line.text() or self.project_dir() or QtCore.QDir.currentPath())) if directory: self.output_line.setText(directory) self.update_json = True def get_file(self, obj, text_obj, setting, *args, **kwargs): file_path, _ = QtGui.QFileDialog.getOpenFileName( self, 'Choose File', (setting.last_value or self.project_dir() or QtCore.QDir.currentPath()), setting.file_types) if file_path: file_path = os.path.abspath( file_path) # fixes an issue with windows paths file_path = file_path.replace(self.project_dir() + os.path.sep, '') text_obj.setText(file_path) setting.last_value = file_path def get_folder(self, obj, text_obj, setting, *args, **kwargs): folder = QtGui.QFileDialog.getExistingDirectory( self, 'Choose Folder', (setting.last_value or QtCore.QDir.currentPath())) if folder: folder = folder.replace(self.project_dir() + os.path.sep, '') text_obj.setText(folder) setting.last_value = folder def create_application_settings(self): group_box = QtGui.QGroupBox("Application Settings") vlayout = self.create_layout( self.settings['order']['application_setting_order'], cols=4) group_box.setLayout(vlayout) return group_box def create_setting(self, name): setting = self.get_setting(name) if setting.type == 'string': return self.create_text_input_setting(name) elif setting.type == 'file': return self.create_text_input_with_file_setting(name) elif setting.type == 'folder': return self.create_text_input_with_folder_setting(name) elif setting.type == 'check': return self.create_check_setting(name) elif setting.type == 'list': return self.create_list_setting(name) elif setting.type == 'range': return self.create_range_setting(name) def create_window_settings(self): group_box = QtGui.QGroupBox("Window Settings") vlayout = self.create_layout( self.settings['order']['window_setting_order'], cols=4) group_box.setLayout(vlayout) return group_box def create_export_settings(self): group_box = QtGui.QGroupBox("Export Settings") vlayout = self.create_layout( self.settings['order']['export_setting_order'], cols=4) group_box.setLayout(vlayout) return group_box def create_download_settings(self): group_box = QtGui.QGroupBox("Download Settings") vlayout = self.create_layout( self.settings['order']['download_setting_order'], 2) group_box.setLayout(vlayout) return group_box def create_layout(self, settings, cols=3): glayout = QtGui.QGridLayout() glayout.setContentsMargins(10, 5, 10, 5) glayout.setSpacing(10) col = 0 row = 0 for setting_name in settings: setting = self.get_setting(setting_name) if col >= cols * 2: row += 1 col = 0 display_name = setting.display_name + ':' if setting.required: display_name += '*' setting_label = QtGui.QLabel(display_name) setting_label.setToolTip(setting.description) glayout.addWidget(setting_label, row, col) glayout.addLayout(self.create_setting(setting_name), row, col + 1) col += 2 return glayout def create_text_input_setting(self, name): hlayout = QtGui.QHBoxLayout() setting = self.get_setting(name) text = QtGui.QLineEdit() text.setValidator(Validator(setting.filter, setting.filter_action)) text.setObjectName(setting.name) text.textChanged.connect( self.call_with_object('setting_changed', text, setting)) if setting.value: text.setText(str(setting.value)) hlayout.addWidget(text) return hlayout def create_text_input_with_file_setting(self, name): hlayout = QtGui.QHBoxLayout() setting = self.get_setting(name) text = QtGui.QLineEdit() text.setObjectName(setting.name) button = QtGui.QPushButton('...') button.setMaximumWidth(30) button.setMaximumHeight(26) button.clicked.connect( self.call_with_object('get_file', button, text, setting)) if setting.value: text.setText(str(setting.value)) text.textChanged.connect( self.call_with_object('setting_changed', text, setting)) hlayout.addWidget(text) hlayout.addWidget(button) return hlayout def create_text_input_with_folder_setting(self, name): hlayout = QtGui.QHBoxLayout() setting = self.get_setting(name) text = QtGui.QLineEdit() text.setObjectName(setting.name) button = QtGui.QPushButton('...') button.setMaximumWidth(30) button.setMaximumHeight(26) button.clicked.connect( self.call_with_object('get_folder', button, text, setting)) if setting.value: text.setText(str(setting.value)) text.textChanged.connect( self.call_with_object('setting_changed', text, setting)) hlayout.addWidget(text) hlayout.addWidget(button) return hlayout def reset_settings(self): for sgroup in self.settings['setting_groups']: for setting in sgroup.values(): widget = self.find_child_by_name(setting.name) if (setting.type == 'string' or setting.type == 'file' or setting.type == 'folder'): old_val = '' if setting.default_value is not None: old_val = setting.default_value setting.value = old_val.replace('\\', '\\\\') widget.setText(str(old_val)) elif setting.type == 'check': old_val = False if setting.default_value is not None: old_val = setting.default_value setting.value = old_val widget.setChecked(old_val) elif setting.type == 'range': old_val = 0 if setting.default_value is not None: old_val = setting.default_value setting.value = old_val widget.setValue(old_val) def setting_changed(self, obj, setting, *args, **kwargs): if (setting.type == 'string' or setting.type == 'file' or setting.type == 'folder'): setting.value = obj.text() elif setting.type == 'check': setting.value = obj.isChecked() elif setting.type == 'list': setting.value = obj.currentText() elif setting.type == 'range': setting.value = obj.value() if self.update_json: json_file = os.path.join(self.project_dir(), 'package.json') with open(json_file, 'w+') as f: f.write(self.generate_json()) self.ex_button.setEnabled(self.required_settings_filled()) def project_path_changed(self): self.ex_button.setEnabled(self.required_settings_filled()) dirs_filled_out = False if self.project_dir() and self.output_dir(): if os.path.exists(self.project_dir()): dirs_filled_out = True self.option_settings_enabled(dirs_filled_out) def create_check_setting(self, name): hlayout = QtGui.QHBoxLayout() setting = self.get_setting(name) check = QtGui.QCheckBox() check.setObjectName(setting.name) check.clicked.connect( self.call_with_object('setting_changed', check, setting)) check.setChecked(setting.value) hlayout.addWidget(check) return hlayout def create_list_setting(self, name): hlayout = QtGui.QHBoxLayout() setting = self.get_setting(name) button = None if setting.button: button = QtGui.QPushButton(setting.button) button.clicked.connect(lambda: setting.button_callback(button)) combo = QtGui.QComboBox() combo.setObjectName(setting.name) combo.currentIndexChanged.connect( self.call_with_object('setting_changed', combo, setting)) combo.editTextChanged.connect( self.call_with_object('setting_changed', combo, setting)) for val in setting.values: combo.addItem(val) default_index = combo.findText(setting.default_value) if default_index != -1: combo.setCurrentIndex(default_index) hlayout.addWidget(combo) if button: hlayout.addWidget(button) return hlayout def create_range_setting(self, name): hlayout = QtGui.QHBoxLayout() setting = self.get_setting(name) button = None if setting.button: button = QtGui.QPushButton(setting.button) button.clicked.connect(lambda: setting.button_callback(button)) slider = QtGui.QSlider(QtCore.Qt.Orientation.Horizontal) slider.setRange(0, 9) slider.valueChanged.connect( self.call_with_object('setting_changed', slider, setting)) slider.setObjectName(setting.name) slider.setValue(setting.default_value) range_label = QtGui.QLabel(str(setting.default_value)) range_label.setMaximumWidth(30) slider.valueChanged.connect( self.call_with_object('_update_range_label', range_label)) w = QtGui.QWidget() whlayout = QtGui.QHBoxLayout() whlayout.addWidget(slider) whlayout.addWidget(range_label) w.setLayout(whlayout) hlayout.addWidget(w) return hlayout def _update_range_label(self, label, value): label.setText(str(value)) def load_package_json(self): setting_list = super(MainWindow, self).load_package_json() for setting in setting_list: setting_field = self.find_child_by_name(setting.name) if setting_field: if (setting.type == 'file' or setting.type == 'string' or setting.type == 'folder'): val_str = self.convert_val_to_str(setting.value) setting_field.setText(setting.filter_name(val_str)) if setting.type == 'check': setting_field.setChecked(setting.value) if setting.type == 'list': val_str = self.convert_val_to_str(setting.value) index = setting_field.findText(val_str) if index != -1: setting_field.setCurrentIndex(index) if setting.type == 'range': setting_field.setValue(setting.value) self.ex_button.setEnabled(self.required_settings_filled()) def show_and_raise(self): self.show() self.raise_()
class MainWindow(QtGui.QMainWindow, CommandBase): def update_nw_versions(self, button): self.get_versions_in_background() def load_recent_projects(self): files = [] history_file = get_data_file_path("files/recent_files.txt") if not os.path.exists(history_file): return files with codecs.open(history_file, encoding="utf-8") as f: for line in f: line = line.strip() if line and os.path.exists(line): files.append(line) files.reverse() return files def load_last_project_path(self): proj_path = "" proj_file = get_data_file_path("files/last_project_path.txt") if os.path.exists(proj_file): with codecs.open(proj_file, encoding="utf-8") as f: proj_path = f.read().strip() if not proj_path: proj_path = QtCore.QDir.currentPath() return proj_path def save_project_path(self, path): proj_file = get_data_file_path("files/last_project_path.txt") with codecs.open(proj_file, "w+", encoding="utf-8") as f: f.write(path) def save_recent_project(self, proj): recent_file_path = get_data_file_path("files/recent_files.txt") max_length = MAX_RECENT recent_files = [] if os.path.exists(recent_file_path): recent_files = codecs.open(recent_file_path, encoding="utf-8").read().split(u"\n") try: recent_files.remove(proj) except ValueError: pass recent_files.append(proj) with codecs.open(recent_file_path, "w+", encoding="utf-8") as f: for recent_file in recent_files[-max_length:]: if recent_file and os.path.exists(recent_file): f.write(u"{}\n".format(recent_file)) def update_recent_files(self): previous_files = self.load_recent_projects() self.recent_separator.setVisible(len(previous_files) > 0) for i in xrange(len(previous_files)): text = u"{} - {}".format(i + 1, os.path.basename(previous_files[i])) action = self.recent_file_actions[i] action.setText(text) action.setData(previous_files[i]) action.setVisible(True) def __init__(self, width, height, app, parent=None): super(MainWindow, self).__init__(parent) CommandBase.__init__(self) self.icon_style = ( "width:48px;height:48px;background-color:white;border-radius:5px;border:1px solid rgb(50,50,50);" ) self.last_project_dir = self.load_last_project_path() status_bar = QtGui.QStatusBar() self.setStatusBar(status_bar) self.project_path = "" # if platform.system() == 'Darwin': # self.menuBar().setNativeMenuBar(False) self.project_menu = self.menuBar().addMenu("File") browse_action = QtGui.QAction( "Open Project", self.project_menu, shortcut=QtGui.QKeySequence.Open, statusTip="Open an existing or new project.", triggered=self.browse_dir, ) self.project_menu.addAction(browse_action) self.project_menu.addSeparator() self.recent_file_actions = [] for i in xrange(MAX_RECENT): if i == 9: key = 0 else: key = i + 1 action = QtGui.QAction( self, visible=False, triggered=self.open_recent_file, shortcut=QtGui.QKeySequence("Ctrl+{}".format(key)) ) self.recent_file_actions.append(action) self.project_menu.addAction(action) self.recent_separator = self.project_menu.addSeparator() self.update_recent_files() exit_action = QtGui.QAction("Exit", self.project_menu) exit_action.triggered.connect(QtGui.qApp.closeAllWindows) self.project_menu.addAction(exit_action) self.logger = logger self.gui_app = app self.desktop_width = app.desktop().screenGeometry().width() self.desktop_height = app.desktop().screenGeometry().height() self.options_enabled = False self.output_package_json = True self.setWindowIcon(QtGui.QIcon(get_file("files/images/icon.png"))) self.update_json = False self.setup_nw_versions() self.thread = None self.original_packagejson = {} self.resize(width, height) self.extract_error = None self.create_application_layout() self.option_settings_enabled(False) self.setWindowTitle(u"Web2Executable {}".format(__gui_version__)) self.update_nw_versions(None) def open_recent_file(self): action = self.sender() if action: self.load_project(action.data()) def setup_nw_versions(self): nw_version = self.get_setting("nw_version") try: f = codecs.open(get_data_file_path("files/nw-versions.txt"), encoding="utf-8") for line in f: nw_version.values.append(line.strip()) f.close() except IOError: nw_version.values.append(nw_version.default_value) def create_application_layout(self): self.main_layout = QtGui.QVBoxLayout() self.tab_widget = QtGui.QTabWidget() self.main_layout.setContentsMargins(10, 5, 10, 5) self.create_layout_widgets() self.addWidgets_to_main_layout() w = QtGui.QWidget() w.setLayout(self.main_layout) self.setCentralWidget(w) def create_layout_widgets(self): self.download_bar_widget = self.create_download_bar() self.app_settings_widget = self.create_application_settings() self.comp_settings_widget = self.create_compression_settings() self.win_settings_widget = self.create_window_settings() self.ex_settings_widget = self.create_export_settings() self.dl_settings_widget = self.create_download_settings() self.directory_chooser_widget = self.create_directory_choose() def addWidgets_to_main_layout(self): self.warning_settings_icon = QtGui.QIcon(get_file("files/images/warning.png")) self.app_settings_icon = QtGui.QIcon(get_file("files/images/app_settings.png")) self.win_settings_icon = QtGui.QIcon(get_file("files/images/window_settings.png")) self.ex_settings_icon = QtGui.QIcon(get_file("files/images/export_settings.png")) self.comp_settings_icon = QtGui.QIcon(get_file("files/images/compress_settings.png")) self.download_settings_icon = QtGui.QIcon(get_file("files/images/download_settings.png")) self.tab_icons = [ self.app_settings_icon, self.win_settings_icon, self.ex_settings_icon, self.comp_settings_icon, self.download_settings_icon, ] self.main_layout.addWidget(self.directory_chooser_widget) self.tab_widget.addTab(self.app_settings_widget, self.app_settings_icon, "App Settings") self.tab_widget.addTab(self.win_settings_widget, self.win_settings_icon, "Window Settings") self.tab_widget.addTab(self.ex_settings_widget, self.ex_settings_icon, "Export Settings") self.tab_widget.addTab(self.comp_settings_widget, self.comp_settings_icon, "Compression Settings") self.tab_widget.addTab(self.dl_settings_widget, self.download_settings_icon, "Download Settings") self.main_layout.addWidget(self.tab_widget) self.main_layout.addLayout(self.download_bar_widget) def option_settings_enabled(self, is_enabled): self.ex_button.setEnabled(is_enabled) self.app_settings_widget.setEnabled(is_enabled) self.win_settings_widget.setEnabled(is_enabled) self.ex_settings_widget.setEnabled(is_enabled) self.comp_settings_widget.setEnabled(is_enabled) self.dl_settings_widget.setEnabled(is_enabled) self.options_enabled = is_enabled def export(self, export_button, cancel_button): self.get_files_to_download() self.try_to_download_files() def open_export(self, open_export_button): open_folder_in_explorer(self.output_dir()) def try_to_download_files(self): if self.files_to_download: self.progress_bar.setVisible(True) self.cancel_button.setEnabled(True) self.disable_ui_while_working() self.download_file_with_error_handling() else: # This shouldn't happen since we disable the UI if there are no # options selected # But in the weird event that this does happen, we are prepared! QtGui.QMessageBox.information( self, "Export Options Empty!", ("Please choose one of " "the export options!") ) def selected_version(self): return self.get_setting("nw_version").value def enable_ui_after_error(self): self.enable_ui() self.progress_text = "" self.progress_bar.setVisible(False) self.cancel_button.setEnabled(False) def show_error(self, exception): QtGui.QMessageBox.information(self, "Error!", unicode(exception)) def disable_ui_while_working(self): self.option_settings_enabled(False) self.directory_chooser_widget.setEnabled(False) def enable_ui(self): self.option_settings_enabled(True) self.directory_chooser_widget.setEnabled(True) def get_tab_index_for_setting_name(self, name): options_dict = { "app_settings": 0, "webkit_settings": 0, "window_settings": 1, "export_settings": 2, "compression": 3, "download_settings": 4, } for setting_group_name, setting_group in self._setting_items: if name in setting_group: return options_dict.get(setting_group_name, None) def required_settings_filled(self, ignore_options=False): if not self.options_enabled and not ignore_options: return False red_border = "QLineEdit{border:3px solid rgba(238, 68, 83, 200); border-radius:5px;}" settings_valid = True for sgroup in self.settings["setting_groups"]: for sname, setting in sgroup.items(): setting_path = os.path.join(self.project_dir(), unicode(setting.value)) if setting.required and not setting.value: settings_valid = False widget = self.find_child_by_name(setting.name) if widget is not None: widget.setStyleSheet(red_border) widget.setToolTip("This setting is required.") tab = self.get_tab_index_for_setting_name(setting.name) self.tab_widget.setTabIcon(tab, self.warning_settings_icon) if setting.type == "file" and setting.value: setting_path_invalid = not os.path.exists(setting_path) setting_url_invalid = not url_exists(setting.value) if setting_path_invalid and setting_url_invalid: log(setting.value, "does not exist") settings_valid = False widget = self.find_child_by_name(setting.name) if widget is not None: widget.setStyleSheet(red_border) widget.setToolTip(u'The file or url "{}" does not exist.'.format(setting.value)) tab = self.get_tab_index_for_setting_name(setting.name) self.tab_widget.setTabIcon(tab, self.warning_settings_icon) if setting.type == "folder" and setting.value and not os.path.exists(setting_path): settings_valid = False widget = self.find_child_by_name(setting.name) if widget is not None: widget.setStyleSheet(red_border) widget.setToolTip( u'The folder "{}" does not exist'.format(os.path.join(self.project_dir(), setting.value)) ) tab = self.get_tab_index_for_setting_name(setting.name) self.tab_widget.setTabIcon(tab, self.warning_settings_icon) if settings_valid: widget = self.find_child_by_name(setting.name) if widget is not None: widget.setStyleSheet("") widget.setToolTip("") tab = self.get_tab_index_for_setting_name(setting.name) self.tab_widget.setTabIcon(tab, self.tab_icons[tab]) export_chosen = False for setting_name, setting in self.settings["export_settings"].items(): if setting.value: export_chosen = True for setting_name, setting in self.settings["export_settings"].items(): if not export_chosen: widget = self.find_child_by_name(setting.name) if widget is not None: widget.setStyleSheet("QCheckBox{border:3px solid rgba(238, 68, 83, 200); border-radius:5px;}") widget.setToolTip("At least one of these options should be selected.") tab = self.get_tab_index_for_setting_name(setting.name) self.tab_widget.setTabIcon(tab, self.warning_settings_icon) else: widget = self.find_child_by_name(setting.name) if widget is not None: widget.setStyleSheet("") widget.setToolTip("") tab = self.get_tab_index_for_setting_name(setting.name) self.tab_widget.setTabIcon(tab, self.tab_icons[tab]) return export_chosen and settings_valid def project_dir(self): return self.project_path if hasattr(self, "input_line"): return self.input_line.text() return "" def output_dir(self): if hasattr(self, "output_line"): if os.path.isabs(self.output_line.text()): return self.output_line.text() else: return os.path.join(self.project_dir(), self.output_line.text()) return "" def create_download_bar(self): hlayout = QtGui.QHBoxLayout() vlayout = QtGui.QVBoxLayout() vlayout.setContentsMargins(5, 5, 5, 5) vlayout.setSpacing(5) hlayout.setSpacing(5) hlayout.setContentsMargins(5, 5, 5, 5) progress_label = QtGui.QLabel("") progress_bar = QtGui.QProgressBar() progress_bar.setVisible(False) progress_bar.setContentsMargins(5, 5, 5, 5) vlayout.addWidget(progress_label) vlayout.addWidget(progress_bar) vlayout.addWidget(QtGui.QLabel("")) ex_button = QtGui.QPushButton("Export") ex_button.setEnabled(False) cancel_button = QtGui.QPushButton("Cancel Download") cancel_button.setEnabled(False) open_export_button = QtGui.QPushButton() open_export_button.setEnabled(False) open_export_button.setIcon(QtGui.QIcon(get_file("files/images/folder_open.png"))) open_export_button.setToolTip("Open Export Folder") open_export_button.setStatusTip("Open Export Folder") open_export_button.setMaximumWidth(30) open_export_button.setMaximumHeight(30) ex_button.clicked.connect(self.call_with_object("export", ex_button, cancel_button)) cancel_button.clicked.connect(self.cancel_download) open_export_button.clicked.connect(self.call_with_object("open_export", open_export_button)) button_box = QtGui.QDialogButtonBox() button_box.addButton(open_export_button, QtGui.QDialogButtonBox.NoRole) button_box.addButton(cancel_button, QtGui.QDialogButtonBox.RejectRole) button_box.addButton(ex_button, QtGui.QDialogButtonBox.AcceptRole) hlayout.addLayout(vlayout) hlayout.addWidget(button_box) self.progress_label = progress_label self.progress_bar = progress_bar self.cancel_button = cancel_button self.open_export_button = open_export_button http = QHttp(self) http.requestFinished.connect(self.http_request_finished) http.dataReadProgress.connect(self.update_progress_bar) http.responseHeaderReceived.connect(self.read_response_header) self.http = http self.ex_button = ex_button return hlayout def read_response_header(self, response_header): # Check for genuine error conditions. if response_header.statusCode() not in (200, 300, 301, 302, 303, 307): self.show_error(u"Download failed: {}.".format(response_header.reasonPhrase())) self.http_request_aborted = True self.http.abort() self.enable_ui_after_error() def http_request_finished(self, request_id, error): if request_id != self.http_get_id: return if self.http_request_aborted: if self.out_file is not None: self.out_file.close() self.out_file.remove() self.out_file = None return self.out_file.close() self.http.abort() if error: self.out_file.remove() self.show_error(u"Download failed: {}.".format(self.http.errorString())) self.enable_ui_after_error() else: self.continue_downloading_or_extract() def continue_downloading_or_extract(self): if self.files_to_download: self.progress_bar.setVisible(True) self.cancel_button.setEnabled(True) self.disable_ui_while_working() self.download_file_with_error_handling() else: self.progress_text = "Done." self.cancel_button.setEnabled(False) self.progress_bar.setVisible(False) self.extract_files_in_background() @property def progress_text(self): return self.progress_label.text() @progress_text.setter def progress_text(self, value): self.progress_label.setText(unicode(value)) def run_in_background(self, method_name, callback): self.thread = BackgroundThread(self, method_name) self.thread.finished.connect(callback) self.thread.start() def get_versions_in_background(self): self.ex_button.setEnabled(False) self.run_in_background("get_versions", self.done_getting_versions) def done_getting_versions(self): self.ex_button.setEnabled(self.required_settings_filled()) self.progress_text = "Done retrieving versions." nw_version = self.get_setting("nw_version") combo = self.find_child_by_name(nw_version.name) combo.clear() combo.addItems(nw_version.values) def make_output_files_in_background(self): self.ex_button.setEnabled(False) self.run_in_background("make_output_dirs", self.done_making_files) def done_making_files(self): self.ex_button.setEnabled(self.required_settings_filled()) self.progress_text = "Done Exporting." self.enable_ui() self.delete_files() if self.output_err: self.show_error(self.output_err) self.enable_ui_after_error() def extract_files_in_background(self): self.progress_text = "Extracting." self.ex_button.setEnabled(False) self.run_in_background("extract_files", self.done_extracting) def done_extracting(self): self.ex_button.setEnabled(self.required_settings_filled()) if self.extract_error: self.progress_text = "Error extracting." self.show_error( "There were one or more errors with your " "zip/tar files. They were deleted. Please " "try to export again." ) self.enable_ui_after_error() else: self.progress_text = "Done extracting." self.make_output_files_in_background() def cancel_download(self): self.progress_text = "Download cancelled." self.cancel_button.setEnabled(False) self.http_request_aborted = True self.http.abort() self.enable_ui() self.progress_bar.setValue(0) self.progress_bar.setVisible(False) def update_progress_bar(self, bytes_read, total_bytes): if self.http_request_aborted: self.progress_bar.setValue(0) self.progress_bar.setVisible(False) return self.progress_bar.setMaximum(total_bytes) self.progress_bar.setValue(bytes_read) def download_file(self, path, setting): version_file = self.settings["base_url"].format(self.selected_version()) location = self.get_setting("download_dir").value versions = re.findall("v(\d+)\.(\d+)\.(\d+)", path)[0] minor = int(versions[1]) if minor >= 12: path = path.replace("node-webkit", "nwjs") self.progress_text = u"Downloading {}".format(path.replace(version_file, "")) url = QUrl(path) file_name = setting.save_file_path(self.selected_version(), location) archive_exists = QFile.exists(file_name) # dest_files_exist = False # for dest_file in setting.dest_files: # dest_file_path = os.path.join('files', setting.name, dest_file) # dest_files_exist &= QFile.exists(dest_file_path) forced = self.get_setting("force_download").value if archive_exists and not forced: self.continue_downloading_or_extract() return self.out_file = QFile(file_name) if not self.out_file.open(QIODevice.WriteOnly): error = self.out_file.error().name self.show_error(u"Unable to save the file {}: {}.".format(file_name, error)) self.out_file = None self.enable_ui() return mode = QHttp.ConnectionModeHttp port = url.port() if port == -1: port = 0 self.http.setHost(url.host(), mode, port) self.http_request_aborted = False path = QUrl.toPercentEncoding(url.path(), "!$&'()*+,;=:@/") if path: path = unicode(path) else: path = u"/" # Download the file. self.http_get_id = self.http.get(path, self.out_file) def create_icon_box(self, name, text): style = "width:48px;height:48px;background-color:white;border-radius:5px;border:1px solid rgb(50,50,50);" icon_label = QtGui.QLabel() icon_label.setStyleSheet(style) icon_label.setMaximumWidth(48) icon_label.setMinimumWidth(48) icon_label.setMaximumHeight(48) icon_label.setMinimumHeight(48) setattr(self, name, icon_label) icon_text = QtGui.QLabel(text) icon_text.setStyleSheet("font-size:10px;") icon_text.setAlignment(QtCore.Qt.AlignCenter) vbox = QVBoxLayout() vbox.setAlignment(QtCore.Qt.AlignCenter) vbox.addWidget(icon_label) vbox.addWidget(icon_text) vbox.setContentsMargins(0, 0, 0, 0) w = QtGui.QWidget() w.setLayout(vbox) w.setMaximumWidth(70) return w def create_directory_choose(self): group_box = QtGui.QGroupBox("An awesome web project called:") title_hbox = QHBoxLayout() title_hbox.setContentsMargins(10, 10, 10, 10) win_icon = self.create_icon_box("window_icon", "Window Icon") exe_icon = self.create_icon_box("exe_icon", "Exe Icon") mac_icon = self.create_icon_box("mac_icon", "Mac Icon") self.title_label = QtGui.QLabel("TBD") self.title_label.setStyleSheet("font-size:20px; font-weight:bold;") title_hbox.addWidget(self.title_label) title_hbox.addWidget(QtGui.QLabel()) title_hbox.addWidget(win_icon) title_hbox.addWidget(exe_icon) title_hbox.addWidget(mac_icon) vlayout = QtGui.QVBoxLayout() vlayout.setSpacing(5) vlayout.setContentsMargins(10, 5, 10, 5) vlayout.addLayout(title_hbox) # vlayout.addLayout(input_layout) # vlayout.addLayout(output_layout) group_box.setLayout(vlayout) return group_box def set_window_icon(self): icon_setting = self.get_setting("icon") mac_icon_setting = self.get_setting("mac_icon") exe_icon_setting = self.get_setting("exe_icon") self.set_icon(icon_setting.value, self.window_icon) if not mac_icon_setting.value: self.set_icon(icon_setting.value, self.mac_icon) if not exe_icon_setting.value: self.set_icon(icon_setting.value, self.exe_icon) def set_exe_icon(self): icon_setting = self.get_setting("exe_icon") self.set_icon(icon_setting.value, self.exe_icon) def set_mac_icon(self): icon_setting = self.get_setting("mac_icon") self.set_icon(icon_setting.value, self.mac_icon) def set_icon(self, icon_path, icon): if icon_path: icon_path = os.path.join(self.project_dir(), icon_path) if os.path.exists(icon_path): if icon_path.endswith(".icns"): pngs = pngs_from_icns(icon_path) if pngs: image = QtGui.QImage.fromData(QtCore.QByteArray(pngs[-1].data), "PNG") else: return else: image = QtGui.QImage(icon_path) if image.width() >= image.height(): image = image.scaledToWidth(48, QtCore.Qt.SmoothTransformation) else: image = image.scaledToHeight(48, QtCore.Qt.SmoothTransformation) icon.setPixmap(QtGui.QPixmap.fromImage(image)) icon.setStyleSheet("") else: icon.setPixmap(None) icon.setStyleSheet(self.icon_style) else: icon.setPixmap(None) icon.setStyleSheet(self.icon_style) def call_with_object(self, name, obj, *args, **kwargs): """Allows arguments to be passed to click events""" def call(*cargs, **ckwargs): if hasattr(self, name): func = getattr(self, name) kwargs.update(ckwargs) func(obj, *(args + cargs), **kwargs) return call def find_child_by_name(self, name): return self.findChild(QtCore.QObject, name) def find_all_children(self, names): children = [] for child in self.find_children(QtCore.QObject): if child.object_name() in names: children.append(child) return children def project_name(self): return self.find_child_by_name("app_name").text() def browse_dir(self): directory = QtGui.QFileDialog.getExistingDirectory( self, "Find Project Directory", self.project_dir() or self.last_project_dir ) if directory: self.load_project(directory) def load_project(self, directory): self.update_json = False self.project_path = directory self.save_recent_project(directory) self.save_project_path(directory) self.update_recent_files() self.reset_settings() # self.input_line.setText(directory) proj_name = os.path.basename(directory) self.title_label.setText(proj_name) setting_input = self.find_child_by_name("main") files = ( glob.glob(os.path.join(directory, "index.html")) + glob.glob(os.path.join(directory, "index.php")) + glob.glob(os.path.join(directory, "index.htm")) ) if not setting_input.text(): if files: setting_input.setText(files[0].replace(self.project_dir() + os.path.sep, "")) app_name_input = self.find_child_by_name("app_name") name_input = self.find_child_by_name("name") name_setting = self.get_setting("name") title_input = self.find_child_by_name("title") if not name_input.text(): name_input.setText(name_setting.filter_name(proj_name)) if not app_name_input.text(): app_name_input.setText(proj_name) if not title_input.text(): title_input.setText(proj_name) self.load_package_json() default_dir = "output" export_dir_setting = self.get_setting("export_dir") default_dir = export_dir_setting.value or default_dir self.output_line.setText(default_dir) self.set_window_icon() self.open_export_button.setEnabled(True) self.update_json = True def browse_out_dir(self): self.update_json = False directory = QtGui.QFileDialog.getExistingDirectory( self, "Choose Output Directory", (self.output_line.text() or self.project_dir() or self.last_project_dir) ) if directory: self.update_json = True self.output_line.setText(directory) def get_file(self, obj, text_obj, setting, *args, **kwargs): file_path, _ = QtGui.QFileDialog.getOpenFileName( self, "Choose File", (setting.last_value or self.project_dir() or QtCore.QDir.currentPath()), setting.file_types, ) if file_path: file_path = os.path.abspath(file_path) # fixes an issue with windows paths file_path = file_path.replace(self.project_dir() + os.path.sep, "") text_obj.setText(file_path) setting.last_value = file_path def get_folder(self, obj, text_obj, setting, *args, **kwargs): folder = QtGui.QFileDialog.getExistingDirectory( self, "Choose Folder", (setting.last_value or QtCore.QDir.currentPath()) ) if folder: folder = folder.replace(self.project_dir() + os.path.sep, "") text_obj.setText(folder) setting.last_value = folder def create_application_settings(self): group_box = QtGui.QWidget() vlayout = self.create_layout(self.settings["order"]["application_setting_order"], cols=3) group_box.setLayout(vlayout) return group_box def create_compression_settings(self): group_box = QtGui.QWidget() vlayout = self.create_layout(self.settings["order"]["compression_setting_order"], cols=3) warning_label = QtGui.QLabel( "Note: When using compression (greater than 0) it will decrease the executable size,\nbut will increase the startup time when running it." ) vbox = QtGui.QVBoxLayout() vbox.addLayout(vlayout) vbox.addWidget(warning_label) group_box.setLayout(vbox) return group_box def create_setting(self, name): setting = self.get_setting(name) if setting.type == "string": return self.create_text_input_setting(name) elif setting.type == "file": return self.create_text_input_with_file_setting(name) elif setting.type == "folder": return self.create_text_input_with_folder_setting(name) elif setting.type == "check": return self.create_check_setting(name) elif setting.type == "list": return self.create_list_setting(name) elif setting.type == "range": return self.create_range_setting(name) def create_window_settings(self): group_box = QtGui.QWidget() vlayout = self.create_layout(self.settings["order"]["window_setting_order"], cols=3) group_box.setLayout(vlayout) return group_box def create_export_settings(self): group_box = QtGui.QWidget() vlayout = self.create_layout(self.settings["order"]["export_setting_order"], cols=4) output_layout = QtGui.QHBoxLayout() output_label = QtGui.QLabel("Output Directory:") self.output_line = QtGui.QLineEdit() self.output_line.textChanged.connect( self.call_with_object("setting_changed", self.output_line, self.get_setting("export_dir")) ) self.output_line.textChanged.connect(self.project_path_changed) self.output_line.setStatusTip("The output directory relative to the project directory.") output_button = QtGui.QPushButton("...") output_button.clicked.connect(self.browse_out_dir) output_layout.addWidget(output_label) output_layout.addWidget(self.output_line) output_layout.addWidget(output_button) vbox = QtGui.QVBoxLayout() vbox.addLayout(vlayout) vbox.addLayout(output_layout) group_box.setLayout(vbox) return group_box def create_download_settings(self): group_box = QtGui.QWidget() vlayout = self.create_layout(self.settings["order"]["download_setting_order"], cols=1) group_box.setLayout(vlayout) return group_box def create_layout(self, settings, cols=3): glayout = QtGui.QGridLayout() glayout.setContentsMargins(10, 15, 10, 5) glayout.setAlignment(QtCore.Qt.AlignTop) glayout.setSpacing(10) glayout.setHorizontalSpacing(20) col = 0 row = 0 for setting_name in settings: setting = self.get_setting(setting_name) if col >= cols * 2: row += 1 col = 0 display_name = setting.display_name + ":" if setting.required: display_name += "*" setting_label = QtGui.QLabel(display_name) setting_label.setToolTip(setting.description) setting_label.setStatusTip(setting.description) glayout.addWidget(setting_label, row, col) glayout.addLayout(self.create_setting(setting_name), row, col + 1) col += 2 return glayout def create_text_input_setting(self, name): hlayout = QtGui.QHBoxLayout() setting = self.get_setting(name) text = QtGui.QLineEdit() text.setValidator(Validator(setting.filter, setting.filter_action)) text.setObjectName(setting.name) text.textChanged.connect(self.call_with_object("setting_changed", text, setting)) if setting.value: text.setText(unicode(setting.value)) text.setStatusTip(setting.description) text.setToolTip(setting.description) hlayout.addWidget(text) return hlayout def create_text_input_with_file_setting(self, name): hlayout = QtGui.QHBoxLayout() setting = self.get_setting(name) text = QtGui.QLineEdit() text.setObjectName(setting.name) button = QtGui.QPushButton("...") button.setMaximumWidth(30) button.setMaximumHeight(26) button.clicked.connect(self.call_with_object("get_file", button, text, setting)) if setting.value: text.setText(unicode(setting.value)) text.setStatusTip(setting.description) text.setToolTip(setting.description) text.textChanged.connect(self.call_with_object("setting_changed", text, setting)) hlayout.addWidget(text) hlayout.addWidget(button) return hlayout def create_text_input_with_folder_setting(self, name): hlayout = QtGui.QHBoxLayout() setting = self.get_setting(name) text = QtGui.QLineEdit() text.setObjectName(setting.name) button = QtGui.QPushButton("...") button.setMaximumWidth(30) button.setMaximumHeight(26) button.clicked.connect(self.call_with_object("get_folder", button, text, setting)) if setting.value: text.setText(unicode(setting.value)) text.setStatusTip(setting.description) text.setToolTip(setting.description) text.textChanged.connect(self.call_with_object("setting_changed", text, setting)) hlayout.addWidget(text) hlayout.addWidget(button) return hlayout def reset_settings(self): for sgroup in self.settings["setting_groups"]: for setting in sgroup.values(): widget = self.find_child_by_name(setting.name) if widget is None: continue if setting.type == "string" or setting.type == "file" or setting.type == "folder": old_val = "" if setting.default_value is not None: old_val = setting.default_value setting.value = old_val.replace("\\", "\\\\") widget.setText(unicode(old_val)) elif setting.type == "check": old_val = False if setting.default_value is not None: old_val = setting.default_value setting.value = old_val widget.setChecked(old_val) elif setting.type == "range": old_val = 0 if setting.default_value is not None: old_val = setting.default_value setting.value = old_val widget.setValue(old_val) def set_kiosk_emulation_options(self, is_checked): if is_checked: width_field = self.find_child_by_name("width") width_field.setText(unicode(self.desktop_width)) height_field = self.find_child_by_name("height") height_field.setText(unicode(self.desktop_height)) toolbar_field = self.find_child_by_name("toolbar") toolbar_field.setChecked(not is_checked) frame_field = self.find_child_by_name("frame") frame_field.setChecked(not is_checked) show_field = self.find_child_by_name("show") show_field.setChecked(is_checked) kiosk_field = self.find_child_by_name("kiosk") kiosk_field.setChecked(not is_checked) fullscreen_field = self.find_child_by_name("fullscreen") fullscreen_field.setChecked(not is_checked) always_on_top_field = self.find_child_by_name("always-on-top") always_on_top_field.setChecked(is_checked) resizable_field = self.find_child_by_name("resizable") resizable_field.setChecked(not is_checked) def setting_changed(self, obj, setting, *args, **kwargs): if setting.type == "string" or setting.type == "file" or setting.type == "folder": setting.value = args[0] elif setting.type == "check": setting.value = obj.isChecked() check_action = setting.check_action if hasattr(self, check_action): getattr(self, check_action)(obj.isChecked()) elif setting.type == "list": setting.value = obj.currentText() elif setting.type == "range": setting.value = obj.value() if setting.action is not None: action = getattr(self, setting.action, None) if callable(action): action() if self.update_json: json_file = os.path.join(self.project_dir(), "package.json") with codecs.open(json_file, "w+", encoding="utf-8") as f: f.write(self.generate_json()) self.ex_button.setEnabled(self.required_settings_filled()) def project_path_changed(self, text): self.ex_button.setEnabled(self.required_settings_filled(True)) dirs_filled_out = False if self.project_dir() and self.output_dir(): if os.path.exists(self.project_dir()): dirs_filled_out = True self.option_settings_enabled(dirs_filled_out) def create_check_setting(self, name): hlayout = QtGui.QHBoxLayout() setting = self.get_setting(name) check = QtGui.QCheckBox() check.setObjectName(setting.name) check.clicked.connect(self.call_with_object("setting_changed", check, setting)) check.setChecked(setting.value) check.setStatusTip(setting.description) check.setToolTip(setting.description) hlayout.addWidget(check) return hlayout def create_list_setting(self, name): hlayout = QtGui.QHBoxLayout() setting = self.get_setting(name) button = None if setting.button: button = QtGui.QPushButton(setting.button) button.clicked.connect(lambda: setting.button_callback(button)) combo = QtGui.QComboBox() combo.setObjectName(setting.name) combo.currentIndexChanged.connect(self.call_with_object("setting_changed", combo, setting)) combo.editTextChanged.connect(self.call_with_object("setting_changed", combo, setting)) combo.setStatusTip(setting.description) combo.setToolTip(setting.description) for val in setting.values: combo.addItem(val) default_index = combo.findText(setting.default_value) if default_index != -1: combo.setCurrentIndex(default_index) hlayout.addWidget(QtGui.QLabel()) hlayout.addWidget(combo) if button: hlayout.addWidget(button) return hlayout def create_range_setting(self, name): hlayout = QtGui.QHBoxLayout() setting = self.get_setting(name) button = None if setting.button: button = QtGui.QPushButton(setting.button) button.clicked.connect(lambda: setting.button_callback(button)) slider = QtGui.QSlider(QtCore.Qt.Orientation.Horizontal) slider.setRange(setting.min, setting.max) slider.valueChanged.connect(self.call_with_object("setting_changed", slider, setting)) slider.setObjectName(setting.name) slider.setValue(setting.default_value) slider.setStatusTip(setting.description) slider.setToolTip(setting.description) range_label = QtGui.QLabel(unicode(setting.default_value)) range_label.setMaximumWidth(30) slider.valueChanged.connect(self.call_with_object("_update_range_label", range_label)) w = QtGui.QWidget() whlayout = QtGui.QHBoxLayout() whlayout.addWidget(slider) whlayout.addWidget(range_label) w.setLayout(whlayout) hlayout.addWidget(w) return hlayout def _update_range_label(self, label, value): label.setText(unicode(value)) def load_package_json(self): setting_list = super(MainWindow, self).load_package_json() for setting in setting_list: setting_field = self.find_child_by_name(setting.name) if setting_field: if setting.type == "file" or setting.type == "string" or setting.type == "folder": val_str = self.convert_val_to_str(setting.value) setting_field.setText(setting.filter_name(val_str)) if setting.type == "check": setting_field.setChecked(setting.value) if setting.type == "list": val_str = self.convert_val_to_str(setting.value) index = setting_field.findText(val_str) if index != -1: setting_field.setCurrentIndex(index) if setting.type == "range": setting_field.setValue(setting.value) self.ex_button.setEnabled(self.required_settings_filled()) def show_and_raise(self): self.show() self.raise_()
class MainWindow(QtGui.QWidget, CommandBase): def update_nw_versions(self, button): self.get_versions_in_background() def __init__(self, width, height, parent=None): super(MainWindow, self).__init__(parent) CommandBase.__init__(self) self.output_package_json = True self.setWindowIcon(QtGui.QIcon(os.path.join(CWD, 'files', 'images', 'icon.png'))) self.update_json = False self.setup_nw_versions() self.thread = None self.original_packagejson = {} self.resize(width, height) self.extract_error = None self.create_application_layout() self.option_settings_enabled(False) self.setWindowTitle("Web2Executable {}".format(__gui_version__)) self.update_nw_versions(None) def setup_nw_versions(self): nw_version = self.get_setting('nw_version') try: f = open(os.path.join(CWD, 'files', 'nw-versions.txt')) for line in f: nw_version.values.append(line.strip()) except IOError: nw_version.values.append(nw_version.default_value) def create_application_layout(self): self.main_layout = QtGui.QVBoxLayout() self.main_layout.setContentsMargins(10, 5, 10, 5) self.create_layout_widgets() self.addWidgets_to_main_layout() self.setLayout(self.main_layout) def create_layout_widgets(self): self.download_bar_widget = self.create_download_bar() self.app_settings_widget = self.create_application_settings() self.win_settings_widget = self.create_window_settings() self.ex_settings_widget = self.create_export_settings() self.dl_settings_widget = self.create_download_settings() self.directory_chooser_widget = self.create_directory_choose() def addWidgets_to_main_layout(self): self.main_layout.addWidget(self.directory_chooser_widget) self.main_layout.addWidget(self.app_settings_widget) self.main_layout.addWidget(self.win_settings_widget) self.main_layout.addWidget(self.ex_settings_widget) self.main_layout.addWidget(self.dl_settings_widget) self.main_layout.addLayout(self.download_bar_widget) def option_settings_enabled(self, is_enabled): self.ex_button.setEnabled(is_enabled) self.app_settings_widget.setEnabled(is_enabled) self.win_settings_widget.setEnabled(is_enabled) self.ex_settings_widget.setEnabled(is_enabled) self.dl_settings_widget.setEnabled(is_enabled) def export(self, export_button, cancel_button): self.get_files_to_download() self.try_to_download_files() def open_export(self, open_export_button): open_folder_in_explorer(self.output_dir()) def try_to_download_files(self): if self.files_to_download: self.progress_bar.setVisible(True) self.cancel_button.setEnabled(True) self.disable_ui_while_working() self.download_file_with_error_handling() else: # This shouldn't happen since we disable the UI if there are no # options selected # But in the weird event that this does happen, we are prepared! QtGui.QMessageBox.information(self, 'Export Options Empty!', ('Please choose one of ' 'the export options!')) def selected_version(self): return self.get_setting('nw_version').value def enable_ui_after_error(self): self.enable_ui() self.progress_text = '' self.progress_bar.setVisible(False) self.cancel_button.setEnabled(False) def show_error(self, exception): QtGui.QMessageBox.information(self, 'Error!', str(exception)) def disable_ui_while_working(self): self.option_settings_enabled(False) self.directory_chooser_widget.setEnabled(False) def enable_ui(self): self.option_settings_enabled(True) self.directory_chooser_widget.setEnabled(True) def required_settings_filled(self): proj_dir = self.project_dir() out_dir = self.output_dir() if proj_dir and out_dir: if os.path.exists(proj_dir): valid_proj_dirs = True settings_valid = True for sgroup in self.settings['setting_groups']: for sname, setting in sgroup.items(): setting_path = os.path.join(self.project_dir(), str(setting.value)) if setting.required and not setting.value: return False if (setting.type == 'file' and setting.value and not os.path.exists(setting_path)): log(setting.value, "does not exist") settings_valid = False if (setting.type == 'folder' and setting.value and not os.path.exists(setting_path)): settings_valid = False export_chosen = False for setting_name, setting in self.settings['export_settings'].items(): if setting.value: export_chosen = True return export_chosen and valid_proj_dirs and settings_valid def project_dir(self): if hasattr(self, 'input_line'): return self.input_line.text() return '' def output_dir(self): if hasattr(self, 'output_line'): return self.output_line.text() return '' def create_download_bar(self): hlayout = QtGui.QHBoxLayout() vlayout = QtGui.QVBoxLayout() vlayout.setContentsMargins(5, 5, 5, 5) vlayout.setSpacing(5) hlayout.setSpacing(5) hlayout.setContentsMargins(5, 5, 5, 5) progress_label = QtGui.QLabel('') progress_bar = QtGui.QProgressBar() progress_bar.setVisible(False) progress_bar.setContentsMargins(5, 5, 5, 5) vlayout.addWidget(progress_label) vlayout.addWidget(progress_bar) vlayout.addWidget(QtGui.QLabel('')) ex_button = QtGui.QPushButton('Export') ex_button.setEnabled(False) cancel_button = QtGui.QPushButton('Cancel Download') cancel_button.setEnabled(False) open_export_button = QtGui.QPushButton() open_export_button.setEnabled(False) open_export_button.setIcon(QtGui.QIcon(os.path.join('files', 'images', 'folder_open.png'))) open_export_button.setToolTip('Open Export Folder') open_export_button.setMaximumWidth(30) open_export_button.setMaximumHeight(30) ex_button.clicked.connect(self.call_with_object('export', ex_button, cancel_button)) cancel_button.clicked.connect(self.cancel_download) open_export_button.clicked.connect(self.call_with_object('open_export', open_export_button)) button_box = QtGui.QDialogButtonBox() button_box.addButton(open_export_button, QtGui.QDialogButtonBox.NoRole) button_box.addButton(cancel_button, QtGui.QDialogButtonBox.RejectRole) button_box.addButton(ex_button, QtGui.QDialogButtonBox.AcceptRole) hlayout.addLayout(vlayout) hlayout.addWidget(button_box) self.progress_label = progress_label self.progress_bar = progress_bar self.cancel_button = cancel_button self.open_export_button = open_export_button http = QHttp(self) http.requestFinished.connect(self.http_request_finished) http.dataReadProgress.connect(self.update_progress_bar) http.responseHeaderReceived.connect(self.read_response_header) self.http = http self.ex_button = ex_button return hlayout def read_response_header(self, response_header): # Check for genuine error conditions. if response_header.statusCode() not in (200, 300, 301, 302, 303, 307): self.show_error('Download failed: {}.'.format(response_header.reasonPhrase())) self.http_request_aborted = True self.http.abort() self.enable_ui_after_error() def http_request_finished(self, request_id, error): if request_id != self.http_get_id: return if self.http_request_aborted: if self.out_file is not None: self.out_file.close() self.out_file.remove() self.out_file = None return self.out_file.close() self.http.abort() if error: self.out_file.remove() self.show_error('Download failed: {}.'.format(self.http.errorString())) self.enable_ui_after_error() else: self.continue_downloading_or_extract() def continue_downloading_or_extract(self): if self.files_to_download: self.progress_bar.setVisible(True) self.cancel_button.setEnabled(True) self.disable_ui_while_working() self.download_file_with_error_handling() else: self.progress_text = 'Done.' self.cancel_button.setEnabled(False) self.progress_bar.setVisible(False) self.extract_files_in_background() @property def progress_text(self): return self.progress_label.text() @progress_text.setter def progress_text(self, value): self.progress_label.setText(str(value)) def run_in_background(self, method_name, callback): self.thread = BackgroundThread(self, method_name) self.thread.finished.connect(callback) self.thread.start() def get_versions_in_background(self): self.ex_button.setEnabled(False) self.run_in_background('get_versions', self.done_getting_versions) def done_getting_versions(self): self.ex_button.setEnabled(self.required_settings_filled()) self.progress_text = 'Done retrieving versions.' nw_version = self.get_setting('nw_version') combo = self.find_child_by_name(nw_version.name) combo.clear() combo.addItems(nw_version.values) def make_output_files_in_background(self): self.ex_button.setEnabled(False) self.run_in_background('make_output_dirs', self.done_making_files) def done_making_files(self): self.ex_button.setEnabled(self.required_settings_filled()) self.progress_text = 'Done Exporting.' self.enable_ui() self.delete_files() if self.output_err: self.show_error(self.output_err) self.enable_ui_after_error() def extract_files_in_background(self): self.progress_text = 'Extracting.' self.ex_button.setEnabled(False) self.run_in_background('extract_files', self.done_extracting) def done_extracting(self): self.ex_button.setEnabled(self.required_settings_filled()) if self.extract_error: self.progress_text = 'Error extracting.' self.show_error('There were one or more errors with your ' 'zip/tar files. They were deleted. Please ' 'try to export again.') self.enable_ui_after_error() else: self.progress_text = 'Done extracting.' self.make_output_files_in_background() def cancel_download(self): self.progress_text = 'Download cancelled.' self.cancel_button.setEnabled(False) self.http_request_aborted = True self.http.abort() self.enable_ui() self.progress_bar.setValue(0) self.progress_bar.setVisible(False) def update_progress_bar(self, bytes_read, total_bytes): if self.http_request_aborted: self.progress_bar.setValue(0) self.progress_bar.setVisible(False) return self.progress_bar.setMaximum(total_bytes) self.progress_bar.setValue(bytes_read) def download_file(self, path, setting): version_file = self.settings['base_url'].format(self.selected_version()) location = self.get_setting('download_dir').value versions = re.findall('v(\d+)\.(\d+)\.(\d+)', path)[0] minor = int(versions[1]) if minor >= 12: path = path.replace('node-webkit', 'nwjs') self.progress_text = 'Downloading {}'.format(path.replace(version_file, '')) url = QUrl(path) file_name = setting.save_file_path(self.selected_version(), location) archive_exists = QFile.exists(file_name) #dest_files_exist = False # for dest_file in setting.dest_files: # dest_file_path = os.path.join('files', setting.name, dest_file) # dest_files_exist &= QFile.exists(dest_file_path) forced = self.get_setting('force_download').value if archive_exists and not forced: self.continue_downloading_or_extract() return self.out_file = QFile(file_name) if not self.out_file.open(QIODevice.WriteOnly): error = self.out_file.error().name self.show_error('Unable to save the file {}: {}.'.format(file_name, error)) self.out_file = None self.enable_ui() return mode = QHttp.ConnectionModeHttp port = url.port() if port == -1: port = 0 self.http.setHost(url.host(), mode, port) self.http_request_aborted = False path = QUrl.toPercentEncoding(url.path(), "!$&'()*+,;=:@/") if path: path = str(path) else: path = '/' # Download the file. self.http_get_id = self.http.get(path, self.out_file) def create_directory_choose(self): group_box = QtGui.QGroupBox("Choose Your Web Project") input_layout = QtGui.QHBoxLayout() input_label = QtGui.QLabel('Project Directory:') self.input_line = QtGui.QLineEdit() self.input_line.textChanged.connect(self.project_path_changed) input_button = QtGui.QPushButton('...') input_button.clicked.connect(self.browse_dir) input_layout.addWidget(input_label) input_layout.addWidget(self.input_line) input_layout.addWidget(input_button) output_layout = QtGui.QHBoxLayout() output_label = QtGui.QLabel('Output Directory:') self.output_line = QtGui.QLineEdit() self.output_line.textChanged.connect(self.project_path_changed) output_button = QtGui.QPushButton('...') output_button.clicked.connect(self.browse_out_dir) output_layout.addWidget(output_label) output_layout.addWidget(self.output_line) output_layout.addWidget(output_button) vlayout = QtGui.QVBoxLayout() vlayout.setSpacing(5) vlayout.setContentsMargins(10, 5, 10, 5) vlayout.addLayout(input_layout) vlayout.addLayout(output_layout) group_box.setLayout(vlayout) return group_box def call_with_object(self, name, obj, *args, **kwargs): """Allows arguments to be passed to click events""" def call(*cargs, **ckwargs): if hasattr(self, name): func = getattr(self, name) kwargs.update(ckwargs) func(obj, *(args+cargs), **kwargs) return call def find_child_by_name(self, name): return self.findChild(QtCore.QObject, name) def find_all_children(self, names): children = [] for child in self.find_children(QtCore.QObject): if child.object_name() in names: children.append(child) return children def project_name(self): return self.find_child_by_name('app_name').text() def browse_dir(self): self.update_json = False directory = QtGui.QFileDialog.getExistingDirectory(self, "Find Project Directory", self.project_dir() or QtCore.QDir.currentPath()) if directory: self.reset_settings() self.input_line.setText(directory) self.output_line.setText(os.path.join(directory, 'output')) proj_name = os.path.basename(directory) setting_input = self.find_child_by_name('main') files = (glob.glob(os.path.join(directory, 'index.html')) + glob.glob(os.path.join(directory, 'index.php')) + glob.glob(os.path.join(directory, 'index.htm'))) if not setting_input.text(): if files: setting_input.setText(files[0].replace(self.project_dir() + os.path.sep, '')) app_name_input = self.find_child_by_name('app_name') name_input = self.find_child_by_name('name') name_setting = self.get_setting('name') title_input = self.find_child_by_name('title') if not name_input.text(): name_input.setText(name_setting.filter_name(proj_name)) if not app_name_input.text(): app_name_input.setText(proj_name) if not title_input.text(): title_input.setText(proj_name) self.load_package_json() self.open_export_button.setEnabled(True) self.update_json = True def browse_out_dir(self): self.update_json = False directory = QtGui.QFileDialog.getExistingDirectory(self, "Choose Output Directory", (self.output_line.text() or self.project_dir() or QtCore.QDir.currentPath())) if directory: self.output_line.setText(directory) self.update_json = True def get_file(self, obj, text_obj, setting, *args, **kwargs): file_path, _ = QtGui.QFileDialog.getOpenFileName(self, 'Choose File', (setting.last_value or self.project_dir() or QtCore.QDir.currentPath()), setting.file_types) if file_path: file_path = os.path.abspath(file_path) # fixes an issue with windows paths file_path = file_path.replace(self.project_dir()+os.path.sep, '') text_obj.setText(file_path) setting.last_value = file_path def get_folder(self, obj, text_obj, setting, *args, **kwargs): folder = QtGui.QFileDialog.getExistingDirectory(self, 'Choose Folder', (setting.last_value or QtCore.QDir.currentPath())) if folder: folder = folder.replace(self.project_dir()+os.path.sep, '') text_obj.setText(folder) setting.last_value = folder def create_application_settings(self): group_box = QtGui.QGroupBox("Application Settings") vlayout = self.create_layout(self.settings['order']['application_setting_order'], cols=4) group_box.setLayout(vlayout) return group_box def create_setting(self, name): setting = self.get_setting(name) if setting.type == 'string': return self.create_text_input_setting(name) elif setting.type == 'file': return self.create_text_input_with_file_setting(name) elif setting.type == 'folder': return self.create_text_input_with_folder_setting(name) elif setting.type == 'check': return self.create_check_setting(name) elif setting.type == 'list': return self.create_list_setting(name) elif setting.type == 'range': return self.create_range_setting(name) def create_window_settings(self): group_box = QtGui.QGroupBox("Window Settings") vlayout = self.create_layout(self.settings['order']['window_setting_order'], cols=4) group_box.setLayout(vlayout) return group_box def create_export_settings(self): group_box = QtGui.QGroupBox("Export Settings") vlayout = self.create_layout(self.settings['order']['export_setting_order'], cols=4) group_box.setLayout(vlayout) return group_box def create_download_settings(self): group_box = QtGui.QGroupBox("Download Settings") vlayout = self.create_layout(self.settings['order']['download_setting_order'], 2) group_box.setLayout(vlayout) return group_box def create_layout(self, settings, cols=3): glayout = QtGui.QGridLayout() glayout.setContentsMargins(10, 5, 10, 5) glayout.setSpacing(10) col = 0 row = 0 for setting_name in settings: setting = self.get_setting(setting_name) if col >= cols*2: row += 1 col = 0 display_name = setting.display_name+':' if setting.required: display_name += '*' setting_label = QtGui.QLabel(display_name) setting_label.setToolTip(setting.description) glayout.addWidget(setting_label, row, col) glayout.addLayout(self.create_setting(setting_name), row, col+1) col += 2 return glayout def create_text_input_setting(self, name): hlayout = QtGui.QHBoxLayout() setting = self.get_setting(name) text = QtGui.QLineEdit() text.setValidator(Validator(setting.filter, setting.filter_action)) text.setObjectName(setting.name) text.textChanged.connect(self.call_with_object('setting_changed', text, setting)) if setting.value: text.setText(str(setting.value)) hlayout.addWidget(text) return hlayout def create_text_input_with_file_setting(self, name): hlayout = QtGui.QHBoxLayout() setting = self.get_setting(name) text = QtGui.QLineEdit() text.setObjectName(setting.name) button = QtGui.QPushButton('...') button.setMaximumWidth(30) button.setMaximumHeight(26) button.clicked.connect(self.call_with_object('get_file', button, text, setting)) if setting.value: text.setText(str(setting.value)) text.textChanged.connect(self.call_with_object('setting_changed', text, setting)) hlayout.addWidget(text) hlayout.addWidget(button) return hlayout def create_text_input_with_folder_setting(self, name): hlayout = QtGui.QHBoxLayout() setting = self.get_setting(name) text = QtGui.QLineEdit() text.setObjectName(setting.name) button = QtGui.QPushButton('...') button.setMaximumWidth(30) button.setMaximumHeight(26) button.clicked.connect(self.call_with_object('get_folder', button, text, setting)) if setting.value: text.setText(str(setting.value)) text.textChanged.connect(self.call_with_object('setting_changed', text, setting)) hlayout.addWidget(text) hlayout.addWidget(button) return hlayout def reset_settings(self): for sgroup in self.settings['setting_groups']: for setting in sgroup.values(): widget = self.find_child_by_name(setting.name) if (setting.type == 'string' or setting.type == 'file' or setting.type == 'folder'): old_val = '' if setting.default_value is not None: old_val = setting.default_value setting.value = old_val.replace('\\', '\\\\') widget.setText(str(old_val)) elif setting.type == 'check': old_val = False if setting.default_value is not None: old_val = setting.default_value setting.value = old_val widget.setChecked(old_val) elif setting.type == 'range': old_val = 0 if setting.default_value is not None: old_val = setting.default_value setting.value = old_val widget.setValue(old_val) def setting_changed(self, obj, setting, *args, **kwargs): if (setting.type == 'string' or setting.type == 'file' or setting.type == 'folder'): setting.value = obj.text() elif setting.type == 'check': setting.value = obj.isChecked() elif setting.type == 'list': setting.value = obj.currentText() elif setting.type == 'range': setting.value = obj.value() if self.update_json: json_file = os.path.join(self.project_dir(), 'package.json') with open(json_file, 'w+') as f: f.write(self.generate_json()) self.ex_button.setEnabled(self.required_settings_filled()) def project_path_changed(self): self.ex_button.setEnabled(self.required_settings_filled()) dirs_filled_out = False if self.project_dir() and self.output_dir(): if os.path.exists(self.project_dir()): dirs_filled_out = True self.option_settings_enabled(dirs_filled_out) def create_check_setting(self, name): hlayout = QtGui.QHBoxLayout() setting = self.get_setting(name) check = QtGui.QCheckBox() check.setObjectName(setting.name) check.clicked.connect(self.call_with_object('setting_changed', check, setting)) check.setChecked(setting.value) hlayout.addWidget(check) return hlayout def create_list_setting(self, name): hlayout = QtGui.QHBoxLayout() setting = self.get_setting(name) button = None if setting.button: button = QtGui.QPushButton(setting.button) button.clicked.connect(lambda: setting.button_callback(button)) combo = QtGui.QComboBox() combo.setObjectName(setting.name) combo.currentIndexChanged.connect(self.call_with_object('setting_changed', combo, setting)) combo.editTextChanged.connect(self.call_with_object('setting_changed', combo, setting)) for val in setting.values: combo.addItem(val) default_index = combo.findText(setting.default_value) if default_index != -1: combo.setCurrentIndex(default_index) hlayout.addWidget(combo) if button: hlayout.addWidget(button) return hlayout def create_range_setting(self, name): hlayout = QtGui.QHBoxLayout() setting = self.get_setting(name) button = None if setting.button: button = QtGui.QPushButton(setting.button) button.clicked.connect(lambda: setting.button_callback(button)) slider = QtGui.QSlider(QtCore.Qt.Orientation.Horizontal) slider.setRange(0, 9) slider.valueChanged.connect(self.call_with_object('setting_changed', slider, setting)) slider.setObjectName(setting.name) slider.setValue(setting.default_value) range_label = QtGui.QLabel(str(setting.default_value)) range_label.setMaximumWidth(30) slider.valueChanged.connect(self.call_with_object('_update_range_label', range_label)) w = QtGui.QWidget() whlayout = QtGui.QHBoxLayout() whlayout.addWidget(slider) whlayout.addWidget(range_label) w.setLayout(whlayout) hlayout.addWidget(w) return hlayout def _update_range_label(self, label, value): label.setText(str(value)) def load_package_json(self): setting_list = super(MainWindow, self).load_package_json() for setting in setting_list: setting_field = self.find_child_by_name(setting.name) if setting_field: if (setting.type == 'file' or setting.type == 'string' or setting.type == 'folder'): val_str = self.convert_val_to_str(setting.value) setting_field.setText(setting.filter_name(val_str)) if setting.type == 'check': setting_field.setChecked(setting.value) if setting.type == 'list': val_str = self.convert_val_to_str(setting.value) index = setting_field.findText(val_str) if index != -1: setting_field.setCurrentIndex(index) if setting.type == 'range': setting_field.setValue(setting.value) self.ex_button.setEnabled(self.required_settings_filled()) def show_and_raise(self): self.show() self.raise_()
class MainWindow(QtGui.QMainWindow, CommandBase): def update_nw_versions(self, button): self.get_versions_in_background() def load_recent_projects(self): files = [] history_file = get_data_file_path('files/recent_files.txt') if not os.path.exists(history_file): return files with codecs.open(history_file, encoding='utf-8') as f: for line in f: line = line.strip() if line and os.path.exists(line): files.append(line) files.reverse() return files def load_last_project_path(self): proj_path = '' proj_file = get_data_file_path('files/last_project_path.txt') if os.path.exists(proj_file): with codecs.open(proj_file, encoding='utf-8') as f: proj_path = f.read().strip() if not proj_path: proj_path = QtCore.QDir.currentPath() return proj_path def save_project_path(self, path): proj_file = get_data_file_path('files/last_project_path.txt') with codecs.open(proj_file, 'w+', encoding='utf-8') as f: f.write(path) def save_recent_project(self, proj): recent_file_path = get_data_file_path('files/recent_files.txt') max_length = MAX_RECENT recent_files = [] if os.path.exists(recent_file_path): recent_files = codecs.open(recent_file_path, encoding='utf-8').read().split(u'\n') try: recent_files.remove(proj) except ValueError: pass recent_files.append(proj) with codecs.open(recent_file_path, 'w+', encoding='utf-8') as f: for recent_file in recent_files[-max_length:]: if recent_file and os.path.exists(recent_file): f.write(u'{}\n'.format(recent_file)) def update_recent_files(self): previous_files = self.load_recent_projects() self.recent_separator.setVisible(len(previous_files) > 0) for i in xrange(len(previous_files)): text = u'{} - {}'.format(i + 1, os.path.basename(previous_files[i])) action = self.recent_file_actions[i] action.setText(text) action.setData(previous_files[i]) action.setVisible(True) def __init__(self, width, height, app, parent=None): super(MainWindow, self).__init__(parent) CommandBase.__init__(self) self.icon_style = 'width:48px;height:48px;background-color:white;border-radius:5px;border:1px solid rgb(50,50,50);' self.last_project_dir = self.load_last_project_path() status_bar = QtGui.QStatusBar() self.setStatusBar(status_bar) self.project_path = '' #if platform.system() == 'Darwin': # self.menuBar().setNativeMenuBar(False) self.project_menu = self.menuBar().addMenu('File') browse_action = QtGui.QAction( 'Open Project', self.project_menu, shortcut=QtGui.QKeySequence.Open, statusTip='Open an existing or new project.', triggered=self.browse_dir) self.project_menu.addAction(browse_action) self.project_menu.addSeparator() self.recent_file_actions = [] for i in xrange(MAX_RECENT): if i == 9: key = 0 else: key = i + 1 action = QtGui.QAction(self, visible=False, triggered=self.open_recent_file, shortcut=QtGui.QKeySequence( 'Ctrl+{}'.format(key))) self.recent_file_actions.append(action) self.project_menu.addAction(action) self.recent_separator = self.project_menu.addSeparator() self.update_recent_files() exit_action = QtGui.QAction('Exit', self.project_menu) exit_action.triggered.connect(QtGui.qApp.closeAllWindows) self.project_menu.addAction(exit_action) self.logger = logger self.gui_app = app self.desktop_width = app.desktop().screenGeometry().width() self.desktop_height = app.desktop().screenGeometry().height() self.options_enabled = False self.output_package_json = True self.setWindowIcon(QtGui.QIcon(get_file('files/images/icon.png'))) self.update_json = False self.setup_nw_versions() self.thread = None self.original_packagejson = {} self.resize(width, height) self.extract_error = None self.create_application_layout() self.option_settings_enabled(False) self.setWindowTitle(u"Web2Executable {}".format(__gui_version__)) self.update_nw_versions(None) def open_recent_file(self): action = self.sender() if action: self.load_project(action.data()) def setup_nw_versions(self): nw_version = self.get_setting('nw_version') try: f = codecs.open(get_data_file_path('files/nw-versions.txt'), encoding='utf-8') for line in f: nw_version.values.append(line.strip()) f.close() except IOError: nw_version.values.append(nw_version.default_value) def create_application_layout(self): self.main_layout = QtGui.QVBoxLayout() self.tab_widget = QtGui.QTabWidget() self.main_layout.setContentsMargins(10, 5, 10, 5) self.create_layout_widgets() self.addWidgets_to_main_layout() w = QtGui.QWidget() w.setLayout(self.main_layout) self.setCentralWidget(w) def create_layout_widgets(self): self.download_bar_widget = self.create_download_bar() self.app_settings_widget = self.create_application_settings() self.comp_settings_widget = self.create_compression_settings() self.win_settings_widget = self.create_window_settings() self.ex_settings_widget = self.create_export_settings() self.dl_settings_widget = self.create_download_settings() self.directory_chooser_widget = self.create_directory_choose() def addWidgets_to_main_layout(self): self.warning_settings_icon = QtGui.QIcon( get_file('files/images/warning.png')) self.app_settings_icon = QtGui.QIcon( get_file('files/images/app_settings.png')) self.win_settings_icon = QtGui.QIcon( get_file('files/images/window_settings.png')) self.ex_settings_icon = QtGui.QIcon( get_file('files/images/export_settings.png')) self.comp_settings_icon = QtGui.QIcon( get_file('files/images/compress_settings.png')) self.download_settings_icon = QtGui.QIcon( get_file('files/images/download_settings.png')) self.tab_icons = [ self.app_settings_icon, self.win_settings_icon, self.ex_settings_icon, self.comp_settings_icon, self.download_settings_icon ] self.main_layout.addWidget(self.directory_chooser_widget) self.tab_widget.addTab(self.app_settings_widget, self.app_settings_icon, 'App Settings') self.tab_widget.addTab(self.win_settings_widget, self.win_settings_icon, 'Window Settings') self.tab_widget.addTab(self.ex_settings_widget, self.ex_settings_icon, 'Export Settings') self.tab_widget.addTab(self.comp_settings_widget, self.comp_settings_icon, 'Compression Settings') self.tab_widget.addTab(self.dl_settings_widget, self.download_settings_icon, 'Download Settings') self.main_layout.addWidget(self.tab_widget) self.main_layout.addLayout(self.download_bar_widget) def option_settings_enabled(self, is_enabled): self.ex_button.setEnabled(is_enabled) self.app_settings_widget.setEnabled(is_enabled) self.win_settings_widget.setEnabled(is_enabled) self.ex_settings_widget.setEnabled(is_enabled) self.comp_settings_widget.setEnabled(is_enabled) self.dl_settings_widget.setEnabled(is_enabled) self.options_enabled = is_enabled def export(self, export_button, cancel_button): self.get_files_to_download() self.try_to_download_files() def open_export(self, open_export_button): open_folder_in_explorer(self.output_dir()) def try_to_download_files(self): if self.files_to_download: self.progress_bar.setVisible(True) self.cancel_button.setEnabled(True) self.disable_ui_while_working() self.download_file_with_error_handling() else: # This shouldn't happen since we disable the UI if there are no # options selected # But in the weird event that this does happen, we are prepared! QtGui.QMessageBox.information(self, 'Export Options Empty!', ('Please choose one of ' 'the export options!')) def selected_version(self): return self.get_setting('nw_version').value def enable_ui_after_error(self): self.enable_ui() self.progress_text = '' self.progress_bar.setVisible(False) self.cancel_button.setEnabled(False) def show_error(self, exception): QtGui.QMessageBox.information(self, 'Error!', unicode(exception)) def disable_ui_while_working(self): self.option_settings_enabled(False) self.directory_chooser_widget.setEnabled(False) def enable_ui(self): self.option_settings_enabled(True) self.directory_chooser_widget.setEnabled(True) def get_tab_index_for_setting_name(self, name): options_dict = { 'app_settings': 0, 'webkit_settings': 0, 'window_settings': 1, 'export_settings': 2, 'compression': 3, 'download_settings': 4 } for setting_group_name, setting_group in self._setting_items: if name in setting_group: return options_dict.get(setting_group_name, None) def required_settings_filled(self, ignore_options=False): if not self.options_enabled and not ignore_options: return False red_border = 'QLineEdit{border:3px solid rgba(238, 68, 83, 200); border-radius:5px;}' settings_valid = True for sgroup in self.settings['setting_groups']: for sname, setting in sgroup.items(): setting_path = os.path.join(self.project_dir(), unicode(setting.value)) if setting.required and not setting.value: settings_valid = False widget = self.find_child_by_name(setting.name) if widget is not None: widget.setStyleSheet(red_border) widget.setToolTip('This setting is required.') tab = self.get_tab_index_for_setting_name(setting.name) self.tab_widget.setTabIcon(tab, self.warning_settings_icon) if (setting.type == 'file' and setting.value): setting_path_invalid = not os.path.exists(setting_path) setting_url_invalid = not url_exists(setting.value) if setting_path_invalid and setting_url_invalid: log(setting.value, "does not exist") settings_valid = False widget = self.find_child_by_name(setting.name) if widget is not None: widget.setStyleSheet(red_border) widget.setToolTip( u'The file or url "{}" does not exist.'.format( setting.value)) tab = self.get_tab_index_for_setting_name( setting.name) self.tab_widget.setTabIcon( tab, self.warning_settings_icon) if (setting.type == 'folder' and setting.value and not os.path.exists(setting_path)): settings_valid = False widget = self.find_child_by_name(setting.name) if widget is not None: widget.setStyleSheet(red_border) widget.setToolTip( u'The folder "{}" does not exist'.format( os.path.join(self.project_dir(), setting.value))) tab = self.get_tab_index_for_setting_name(setting.name) self.tab_widget.setTabIcon(tab, self.warning_settings_icon) if settings_valid: widget = self.find_child_by_name(setting.name) if widget is not None: widget.setStyleSheet('') widget.setToolTip('') tab = self.get_tab_index_for_setting_name(setting.name) self.tab_widget.setTabIcon(tab, self.tab_icons[tab]) export_chosen = False for setting_name, setting in self.settings['export_settings'].items(): if setting.value: export_chosen = True for setting_name, setting in self.settings['export_settings'].items(): if not export_chosen: widget = self.find_child_by_name(setting.name) if widget is not None: widget.setStyleSheet( 'QCheckBox{border:3px solid rgba(238, 68, 83, 200); border-radius:5px;}' ) widget.setToolTip( 'At least one of these options should be selected.') tab = self.get_tab_index_for_setting_name(setting.name) self.tab_widget.setTabIcon(tab, self.warning_settings_icon) else: widget = self.find_child_by_name(setting.name) if widget is not None: widget.setStyleSheet('') widget.setToolTip('') tab = self.get_tab_index_for_setting_name(setting.name) self.tab_widget.setTabIcon(tab, self.tab_icons[tab]) return export_chosen and settings_valid def project_dir(self): return self.project_path if hasattr(self, 'input_line'): return self.input_line.text() return '' def output_dir(self): if hasattr(self, 'output_line'): if os.path.isabs(self.output_line.text()): return self.output_line.text() else: return os.path.join(self.project_dir(), self.output_line.text()) return '' def create_download_bar(self): hlayout = QtGui.QHBoxLayout() vlayout = QtGui.QVBoxLayout() vlayout.setContentsMargins(5, 5, 5, 5) vlayout.setSpacing(5) hlayout.setSpacing(5) hlayout.setContentsMargins(5, 5, 5, 5) progress_label = QtGui.QLabel('') progress_bar = QtGui.QProgressBar() progress_bar.setVisible(False) progress_bar.setContentsMargins(5, 5, 5, 5) vlayout.addWidget(progress_label) vlayout.addWidget(progress_bar) vlayout.addWidget(QtGui.QLabel('')) ex_button = QtGui.QPushButton('Export') ex_button.setEnabled(False) cancel_button = QtGui.QPushButton('Cancel Download') cancel_button.setEnabled(False) open_export_button = QtGui.QPushButton() open_export_button.setEnabled(False) open_export_button.setIcon( QtGui.QIcon(get_file('files/images/folder_open.png'))) open_export_button.setToolTip('Open Export Folder') open_export_button.setStatusTip('Open Export Folder') open_export_button.setMaximumWidth(30) open_export_button.setMaximumHeight(30) ex_button.clicked.connect( self.call_with_object('export', ex_button, cancel_button)) cancel_button.clicked.connect(self.cancel_download) open_export_button.clicked.connect( self.call_with_object('open_export', open_export_button)) button_box = QtGui.QDialogButtonBox() button_box.addButton(open_export_button, QtGui.QDialogButtonBox.NoRole) button_box.addButton(cancel_button, QtGui.QDialogButtonBox.RejectRole) button_box.addButton(ex_button, QtGui.QDialogButtonBox.AcceptRole) hlayout.addLayout(vlayout) hlayout.addWidget(button_box) self.progress_label = progress_label self.progress_bar = progress_bar self.cancel_button = cancel_button self.open_export_button = open_export_button http = QHttp(self) http.requestFinished.connect(self.http_request_finished) http.dataReadProgress.connect(self.update_progress_bar) http.responseHeaderReceived.connect(self.read_response_header) self.http = http self.ex_button = ex_button return hlayout def read_response_header(self, response_header): # Check for genuine error conditions. if response_header.statusCode() not in (200, 300, 301, 302, 303, 307): self.show_error(u'Download failed: {}.'.format( response_header.reasonPhrase())) self.http_request_aborted = True self.http.abort() self.enable_ui_after_error() def http_request_finished(self, request_id, error): if request_id != self.http_get_id: return if self.http_request_aborted: if self.out_file is not None: self.out_file.close() self.out_file.remove() self.out_file = None return self.out_file.close() self.http.abort() if error: self.out_file.remove() self.show_error(u'Download failed: {}.'.format( self.http.errorString())) self.enable_ui_after_error() else: self.continue_downloading_or_extract() def continue_downloading_or_extract(self): if self.files_to_download: self.progress_bar.setVisible(True) self.cancel_button.setEnabled(True) self.disable_ui_while_working() self.download_file_with_error_handling() else: self.progress_text = 'Done.' self.cancel_button.setEnabled(False) self.progress_bar.setVisible(False) self.extract_files_in_background() @property def progress_text(self): return self.progress_label.text() @progress_text.setter def progress_text(self, value): self.progress_label.setText(unicode(value)) def run_in_background(self, method_name, callback): self.thread = BackgroundThread(self, method_name) self.thread.finished.connect(callback) self.thread.start() def get_versions_in_background(self): self.ex_button.setEnabled(False) self.run_in_background('get_versions', self.done_getting_versions) def done_getting_versions(self): self.ex_button.setEnabled(self.required_settings_filled()) self.progress_text = 'Done retrieving versions.' nw_version = self.get_setting('nw_version') combo = self.find_child_by_name(nw_version.name) combo.clear() combo.addItems(nw_version.values) def make_output_files_in_background(self): self.ex_button.setEnabled(False) self.run_in_background('make_output_dirs', self.done_making_files) def done_making_files(self): self.ex_button.setEnabled(self.required_settings_filled()) self.progress_text = 'Done Exporting.' self.enable_ui() self.delete_files() if self.output_err: self.show_error(self.output_err) self.enable_ui_after_error() def extract_files_in_background(self): self.progress_text = 'Extracting.' self.ex_button.setEnabled(False) self.run_in_background('extract_files', self.done_extracting) def done_extracting(self): self.ex_button.setEnabled(self.required_settings_filled()) if self.extract_error: self.progress_text = 'Error extracting.' self.show_error('There were one or more errors with your ' 'zip/tar files. They were deleted. Please ' 'try to export again.') self.enable_ui_after_error() else: self.progress_text = 'Done extracting.' self.make_output_files_in_background() def cancel_download(self): self.progress_text = 'Download cancelled.' self.cancel_button.setEnabled(False) self.http_request_aborted = True self.http.abort() self.enable_ui() self.progress_bar.setValue(0) self.progress_bar.setVisible(False) def update_progress_bar(self, bytes_read, total_bytes): if self.http_request_aborted: self.progress_bar.setValue(0) self.progress_bar.setVisible(False) return self.progress_bar.setMaximum(total_bytes) self.progress_bar.setValue(bytes_read) def download_file(self, path, setting): version_file = self.settings['base_url'].format( self.selected_version()) location = self.get_setting('download_dir').value versions = re.findall('v(\d+)\.(\d+)\.(\d+)', path)[0] minor = int(versions[1]) if minor >= 12: path = path.replace('node-webkit', 'nwjs') self.progress_text = u'Downloading {}'.format( path.replace(version_file, '')) url = QUrl(path) file_name = setting.save_file_path(self.selected_version(), location) archive_exists = QFile.exists(file_name) #dest_files_exist = False # for dest_file in setting.dest_files: # dest_file_path = os.path.join('files', setting.name, dest_file) # dest_files_exist &= QFile.exists(dest_file_path) forced = self.get_setting('force_download').value if archive_exists and not forced: self.continue_downloading_or_extract() return self.out_file = QFile(file_name) if not self.out_file.open(QIODevice.WriteOnly): error = self.out_file.error().name self.show_error(u'Unable to save the file {}: {}.'.format( file_name, error)) self.out_file = None self.enable_ui() return mode = QHttp.ConnectionModeHttp port = url.port() if port == -1: port = 0 self.http.setHost(url.host(), mode, port) self.http_request_aborted = False path = QUrl.toPercentEncoding(url.path(), "!$&'()*+,;=:@/") if path: path = unicode(path) else: path = u'/' # Download the file. self.http_get_id = self.http.get(path, self.out_file) def create_icon_box(self, name, text): style = 'width:48px;height:48px;background-color:white;border-radius:5px;border:1px solid rgb(50,50,50);' icon_label = QtGui.QLabel() icon_label.setStyleSheet(style) icon_label.setMaximumWidth(48) icon_label.setMinimumWidth(48) icon_label.setMaximumHeight(48) icon_label.setMinimumHeight(48) setattr(self, name, icon_label) icon_text = QtGui.QLabel(text) icon_text.setStyleSheet('font-size:10px;') icon_text.setAlignment(QtCore.Qt.AlignCenter) vbox = QVBoxLayout() vbox.setAlignment(QtCore.Qt.AlignCenter) vbox.addWidget(icon_label) vbox.addWidget(icon_text) vbox.setContentsMargins(0, 0, 0, 0) w = QtGui.QWidget() w.setLayout(vbox) w.setMaximumWidth(70) return w def create_directory_choose(self): group_box = QtGui.QGroupBox('An awesome web project called:') title_hbox = QHBoxLayout() title_hbox.setContentsMargins(10, 10, 10, 10) win_icon = self.create_icon_box('window_icon', 'Window Icon') exe_icon = self.create_icon_box('exe_icon', 'Exe Icon') mac_icon = self.create_icon_box('mac_icon', 'Mac Icon') self.title_label = QtGui.QLabel('TBD') self.title_label.setStyleSheet('font-size:20px; font-weight:bold;') title_hbox.addWidget(self.title_label) title_hbox.addWidget(QtGui.QLabel()) title_hbox.addWidget(win_icon) title_hbox.addWidget(exe_icon) title_hbox.addWidget(mac_icon) vlayout = QtGui.QVBoxLayout() vlayout.setSpacing(5) vlayout.setContentsMargins(10, 5, 10, 5) vlayout.addLayout(title_hbox) #vlayout.addLayout(input_layout) #vlayout.addLayout(output_layout) group_box.setLayout(vlayout) return group_box def set_window_icon(self): icon_setting = self.get_setting('icon') mac_icon_setting = self.get_setting('mac_icon') exe_icon_setting = self.get_setting('exe_icon') self.set_icon(icon_setting.value, self.window_icon) if not mac_icon_setting.value: self.set_icon(icon_setting.value, self.mac_icon) if not exe_icon_setting.value: self.set_icon(icon_setting.value, self.exe_icon) def set_exe_icon(self): icon_setting = self.get_setting('exe_icon') self.set_icon(icon_setting.value, self.exe_icon) def set_mac_icon(self): icon_setting = self.get_setting('mac_icon') self.set_icon(icon_setting.value, self.mac_icon) def set_icon(self, icon_path, icon): if icon_path: icon_path = os.path.join(self.project_dir(), icon_path) if os.path.exists(icon_path): if icon_path.endswith('.icns'): pngs = pngs_from_icns(icon_path) if pngs: image = QtGui.QImage.fromData( QtCore.QByteArray(pngs[-1].data), 'PNG') else: return else: image = QtGui.QImage(icon_path) if image.width() >= image.height(): image = image.scaledToWidth(48, QtCore.Qt.SmoothTransformation) else: image = image.scaledToHeight( 48, QtCore.Qt.SmoothTransformation) icon.setPixmap(QtGui.QPixmap.fromImage(image)) icon.setStyleSheet('') else: icon.setPixmap(None) icon.setStyleSheet(self.icon_style) else: icon.setPixmap(None) icon.setStyleSheet(self.icon_style) def call_with_object(self, name, obj, *args, **kwargs): """Allows arguments to be passed to click events""" def call(*cargs, **ckwargs): if hasattr(self, name): func = getattr(self, name) kwargs.update(ckwargs) func(obj, *(args + cargs), **kwargs) return call def find_child_by_name(self, name): return self.findChild(QtCore.QObject, name) def find_all_children(self, names): children = [] for child in self.find_children(QtCore.QObject): if child.object_name() in names: children.append(child) return children def project_name(self): return self.find_child_by_name('app_name').text() def browse_dir(self): directory = QtGui.QFileDialog.getExistingDirectory( self, 'Find Project Directory', self.project_dir() or self.last_project_dir) if directory: self.load_project(directory) def load_project(self, directory): self.update_json = False self.project_path = directory self.save_recent_project(directory) self.save_project_path(directory) self.update_recent_files() self.reset_settings() #self.input_line.setText(directory) proj_name = os.path.basename(directory) self.title_label.setText(proj_name) setting_input = self.find_child_by_name('main') files = (glob.glob(os.path.join(directory, 'index.html')) + glob.glob(os.path.join(directory, 'index.php')) + glob.glob(os.path.join(directory, 'index.htm'))) if not setting_input.text(): if files: setting_input.setText(files[0].replace( self.project_dir() + os.path.sep, '')) app_name_input = self.find_child_by_name('app_name') name_input = self.find_child_by_name('name') name_setting = self.get_setting('name') title_input = self.find_child_by_name('title') if not name_input.text(): name_input.setText(name_setting.filter_name(proj_name)) if not app_name_input.text(): app_name_input.setText(proj_name) if not title_input.text(): title_input.setText(proj_name) self.load_package_json() default_dir = 'output' export_dir_setting = self.get_setting('export_dir') default_dir = export_dir_setting.value or default_dir self.output_line.setText(default_dir) self.set_window_icon() self.open_export_button.setEnabled(True) self.update_json = True def browse_out_dir(self): self.update_json = False directory = QtGui.QFileDialog.getExistingDirectory( self, "Choose Output Directory", (self.output_line.text() or self.project_dir() or self.last_project_dir)) if directory: self.update_json = True self.output_line.setText(directory) def get_file(self, obj, text_obj, setting, *args, **kwargs): file_path, _ = QtGui.QFileDialog.getOpenFileName( self, 'Choose File', (setting.last_value or self.project_dir() or QtCore.QDir.currentPath()), setting.file_types) if file_path: file_path = os.path.abspath( file_path) # fixes an issue with windows paths file_path = file_path.replace(self.project_dir() + os.path.sep, '') text_obj.setText(file_path) setting.last_value = file_path def get_folder(self, obj, text_obj, setting, *args, **kwargs): folder = QtGui.QFileDialog.getExistingDirectory( self, 'Choose Folder', (setting.last_value or QtCore.QDir.currentPath())) if folder: folder = folder.replace(self.project_dir() + os.path.sep, '') text_obj.setText(folder) setting.last_value = folder def create_application_settings(self): group_box = QtGui.QWidget() vlayout = self.create_layout( self.settings['order']['application_setting_order'], cols=3) group_box.setLayout(vlayout) return group_box def create_compression_settings(self): group_box = QtGui.QWidget() vlayout = self.create_layout( self.settings['order']['compression_setting_order'], cols=1) warning_label = QtGui.QLabel( 'Note: When using compression (greater than 0) it will decrease the executable size,\nbut will increase the startup time when running it.' ) vbox = QtGui.QVBoxLayout() vbox.addLayout(vlayout) vbox.addWidget(warning_label) group_box.setLayout(vbox) return group_box def create_setting(self, name): setting = self.get_setting(name) if setting.type == 'string': return self.create_text_input_setting(name) elif setting.type == 'strings': return self.create_text_input_setting(name) elif setting.type == 'file': return self.create_text_input_with_file_setting(name) elif setting.type == 'folder': return self.create_text_input_with_folder_setting(name) elif setting.type == 'check': return self.create_check_setting(name) elif setting.type == 'list': return self.create_list_setting(name) elif setting.type == 'range': return self.create_range_setting(name) def create_window_settings(self): group_box = QtGui.QWidget() vlayout = self.create_layout( self.settings['order']['window_setting_order'], cols=3) group_box.setLayout(vlayout) return group_box def create_export_settings(self): group_box = QtGui.QWidget() vlayout = self.create_layout( self.settings['order']['export_setting_order'], cols=4) output_layout = QtGui.QHBoxLayout() output_label = QtGui.QLabel('Output Directory:') self.output_line = QtGui.QLineEdit() self.output_line.textChanged.connect( self.call_with_object('setting_changed', self.output_line, self.get_setting('export_dir'))) self.output_line.textChanged.connect(self.project_path_changed) self.output_line.setStatusTip( 'The output directory relative to the project directory.') output_button = QtGui.QPushButton('...') output_button.clicked.connect(self.browse_out_dir) output_layout.addWidget(output_label) output_layout.addWidget(self.output_line) output_layout.addWidget(output_button) vbox = QtGui.QVBoxLayout() vbox.addLayout(vlayout) vbox.addLayout(output_layout) group_box.setLayout(vbox) return group_box def create_download_settings(self): group_box = QtGui.QWidget() vlayout = self.create_layout( self.settings['order']['download_setting_order'], cols=1) group_box.setLayout(vlayout) return group_box def create_layout(self, settings, cols=3): glayout = QtGui.QGridLayout() glayout.setContentsMargins(10, 15, 10, 5) glayout.setAlignment(QtCore.Qt.AlignTop) glayout.setSpacing(10) glayout.setHorizontalSpacing(20) col = 0 row = 0 for setting_name in settings: setting = self.get_setting(setting_name) if col >= cols * 2: row += 1 col = 0 display_name = setting.display_name + ':' if setting.required: display_name += '*' setting_label = QtGui.QLabel(display_name) setting_label.setToolTip(setting.description) setting_label.setStatusTip(setting.description) glayout.addWidget(setting_label, row, col) glayout.addLayout(self.create_setting(setting_name), row, col + 1) col += 2 return glayout def create_text_input_setting(self, name): hlayout = QtGui.QHBoxLayout() setting = self.get_setting(name) text = QtGui.QLineEdit() text.setValidator(Validator(setting.filter, setting.filter_action)) text.setObjectName(setting.name) text.textChanged.connect( self.call_with_object('setting_changed', text, setting)) if setting.value: text.setText(unicode(setting.value)) text.setStatusTip(setting.description) text.setToolTip(setting.description) hlayout.addWidget(text) return hlayout def create_text_input_with_file_setting(self, name): hlayout = QtGui.QHBoxLayout() setting = self.get_setting(name) text = QtGui.QLineEdit() text.setObjectName(setting.name) button = QtGui.QPushButton('...') button.setMaximumWidth(30) button.setMaximumHeight(26) button.clicked.connect( self.call_with_object('get_file', button, text, setting)) if setting.value: text.setText(unicode(setting.value)) text.setStatusTip(setting.description) text.setToolTip(setting.description) text.textChanged.connect( self.call_with_object('setting_changed', text, setting)) hlayout.addWidget(text) hlayout.addWidget(button) return hlayout def create_text_input_with_folder_setting(self, name): hlayout = QtGui.QHBoxLayout() setting = self.get_setting(name) text = QtGui.QLineEdit() text.setObjectName(setting.name) button = QtGui.QPushButton('...') button.setMaximumWidth(30) button.setMaximumHeight(26) button.clicked.connect( self.call_with_object('get_folder', button, text, setting)) if setting.value: text.setText(unicode(setting.value)) text.setStatusTip(setting.description) text.setToolTip(setting.description) text.textChanged.connect( self.call_with_object('setting_changed', text, setting)) hlayout.addWidget(text) hlayout.addWidget(button) return hlayout def reset_settings(self): for sgroup in self.settings['setting_groups']: for setting in sgroup.values(): widget = self.find_child_by_name(setting.name) if widget is None: continue if (setting.type == 'string' or setting.type == 'file' or setting.type == 'folder'): old_val = '' if setting.default_value is not None: old_val = setting.default_value setting.value = old_val.replace('\\', '\\\\') widget.setText(unicode(old_val)) elif setting.type == 'strings': old_val = [] if setting.default_value is not None: old_val = setting.default_value setting.value = [ unicode(v.replace('\\', '\\\\')) for v in old_val ] widget.setText(','.join(setting.value)) elif setting.type == 'check': old_val = False if setting.default_value is not None: old_val = setting.default_value setting.value = old_val widget.setChecked(old_val) elif setting.type == 'range': old_val = 0 if setting.default_value is not None: old_val = setting.default_value setting.value = old_val widget.setValue(old_val) def set_kiosk_emulation_options(self, is_checked): if is_checked: width_field = self.find_child_by_name('width') width_field.setText(unicode(self.desktop_width)) height_field = self.find_child_by_name('height') height_field.setText(unicode(self.desktop_height)) toolbar_field = self.find_child_by_name('toolbar') toolbar_field.setChecked(not is_checked) frame_field = self.find_child_by_name('frame') frame_field.setChecked(not is_checked) show_field = self.find_child_by_name('show') show_field.setChecked(is_checked) kiosk_field = self.find_child_by_name('kiosk') kiosk_field.setChecked(not is_checked) fullscreen_field = self.find_child_by_name('fullscreen') fullscreen_field.setChecked(not is_checked) always_on_top_field = self.find_child_by_name('always-on-top') always_on_top_field.setChecked(is_checked) resizable_field = self.find_child_by_name('resizable') resizable_field.setChecked(not is_checked) def setting_changed(self, obj, setting, *args, **kwargs): if (setting.type == 'string' or setting.type == 'file' or setting.type == 'folder'): setting.value = args[0] elif setting.type == 'strings': setting.value = args[0].split(',') elif setting.type == 'check': setting.value = obj.isChecked() check_action = setting.check_action if hasattr(self, check_action): getattr(self, check_action)(obj.isChecked()) elif setting.type == 'list': setting.value = obj.currentText() elif setting.type == 'range': setting.value = obj.value() if setting.action is not None: action = getattr(self, setting.action, None) if callable(action): action() if self.update_json: json_file = os.path.join(self.project_dir(), 'package.json') with codecs.open(json_file, 'w+', encoding='utf-8') as f: f.write(self.generate_json()) self.ex_button.setEnabled(self.required_settings_filled()) def project_path_changed(self, text): self.ex_button.setEnabled(self.required_settings_filled(True)) dirs_filled_out = False if self.project_dir() and self.output_dir(): if os.path.exists(self.project_dir()): dirs_filled_out = True self.option_settings_enabled(dirs_filled_out) def create_check_setting(self, name): hlayout = QtGui.QHBoxLayout() setting = self.get_setting(name) check = QtGui.QCheckBox() check.setObjectName(setting.name) check.clicked.connect( self.call_with_object('setting_changed', check, setting)) check.setChecked(setting.value) check.setStatusTip(setting.description) check.setToolTip(setting.description) hlayout.addWidget(check) return hlayout def create_list_setting(self, name): hlayout = QtGui.QHBoxLayout() setting = self.get_setting(name) button = None if setting.button: button = QtGui.QPushButton(setting.button) button.clicked.connect(lambda: setting.button_callback(button)) combo = QtGui.QComboBox() combo.setObjectName(setting.name) combo.currentIndexChanged.connect( self.call_with_object('setting_changed', combo, setting)) combo.editTextChanged.connect( self.call_with_object('setting_changed', combo, setting)) combo.setStatusTip(setting.description) combo.setToolTip(setting.description) for val in setting.values: combo.addItem(val) default_index = combo.findText(setting.default_value) if default_index != -1: combo.setCurrentIndex(default_index) hlayout.addWidget(QtGui.QLabel()) hlayout.addWidget(combo) if button: hlayout.addWidget(button) return hlayout def create_range_setting(self, name): hlayout = QtGui.QHBoxLayout() setting = self.get_setting(name) button = None if setting.button: button = QtGui.QPushButton(setting.button) button.clicked.connect(lambda: setting.button_callback(button)) slider = QtGui.QSlider(QtCore.Qt.Orientation.Horizontal) slider.setRange(setting.min, setting.max) slider.valueChanged.connect( self.call_with_object('setting_changed', slider, setting)) slider.setObjectName(setting.name) slider.setValue(setting.default_value) slider.setStatusTip(setting.description) slider.setToolTip(setting.description) range_label = QtGui.QLabel(unicode(setting.default_value)) range_label.setMaximumWidth(30) slider.valueChanged.connect( self.call_with_object('_update_range_label', range_label)) w = QtGui.QWidget() whlayout = QtGui.QHBoxLayout() whlayout.addWidget(slider) whlayout.addWidget(range_label) w.setLayout(whlayout) hlayout.addWidget(w) return hlayout def _update_range_label(self, label, value): label.setText(unicode(value)) def load_package_json(self): setting_list = super(MainWindow, self).load_package_json() for setting in setting_list: setting_field = self.find_child_by_name(setting.name) if setting_field: if (setting.type == 'file' or setting.type == 'string' or setting.type == 'folder'): val_str = self.convert_val_to_str(setting.value) setting_field.setText(setting.filter_name(val_str)) if setting.type == 'strings': vals = [self.convert_val_to_str(v) for v in setting.value] setting_field.setText(','.join(vals)) if setting.type == 'check': setting_field.setChecked(setting.value) if setting.type == 'list': val_str = self.convert_val_to_str(setting.value) index = setting_field.findText(val_str) if index != -1: setting_field.setCurrentIndex(index) if setting.type == 'range': setting_field.setValue(setting.value) self.ex_button.setEnabled(self.required_settings_filled()) def show_and_raise(self): self.show() self.raise_()