Electrify/main.py

1237 lines
46 KiB
Python

from utils import log, open_folder_in_explorer
__gui_version__ = "v0.2.1b"
import os
import re
import glob
import sys
from PySide import QtGui, QtCore
from PySide.QtGui import QApplication, QHBoxLayout, QVBoxLayout
from PySide.QtNetwork import QHttp
from PySide.QtCore import QUrl, QFile, QIODevice, QCoreApplication
from command_line import CommandBase, logger, get_file
MAX_RECENT = 10
class Validator(QtGui.QRegExpValidator):
def __init__(self, regex, action, parent=None):
self.exp = regex
self.action = unicode
if hasattr(unicode, action):
self.action = getattr(unicode, action)
reg = QtCore.QRegExp(regex)
super(Validator, self).__init__(reg, parent)
def validate(self, text, pos):
result = super(Validator, self).validate(text, pos)
return result
def fixup(self, text):
return ''.join(re.findall(self.exp, self.action(unicode(text))))
class BackgroundThread(QtCore.QThread):
def __init__(self, widget, method_name, parent=None):
QtCore.QThread.__init__(self, parent)
self.widget = widget
self.method_name = method_name
def run(self):
if hasattr(self.widget, self.method_name):
func = getattr(self.widget, self.method_name)
func()
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_file('files/recent_files.txt')
with open(history_file, 'a+') 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_file('files/last_project_path.txt')
with open(proj_file, 'a+') 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_file('files/last_project_path.txt')
with open(proj_file, 'w+') as f:
f.write(path)
def save_recent_project(self, proj):
recent_file_path = get_file('files/recent_files.txt')
max_length = MAX_RECENT
recent_files = open(recent_file_path, 'a+').read().split('\n')
try:
recent_files.remove(proj)
except ValueError:
pass
recent_files.append(proj)
with open(recent_file_path, 'w+') as f:
for recent_file in recent_files[-max_length:]:
if recent_file and os.path.exists(recent_file):
f.write(recent_file+'\n')
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 = '{} - {}'.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 = ''
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("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 = open(get_file('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.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!', 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 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(),
str(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 and
not os.path.exists(setting_path)):
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('The file "{}" 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 (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('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(os.path.join('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('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_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'):
#image = QtGui.QImage.fromData(make_thumbnail(icon_path,48), 'png')
return
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.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(str(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(str(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(str(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(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 set_kiosk_emulation_options(self, is_checked):
if is_checked:
width_field = self.find_child_by_name('width')
width_field.setText(str(self.desktop_width))
height_field = self.find_child_by_name('height')
height_field.setText(str(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 open(json_file, 'w+') 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(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_()
if __name__ == '__main__':
app = QApplication(sys.argv)
QCoreApplication.setApplicationName("Web2Executable")
QCoreApplication.setApplicationVersion(__gui_version__)
QCoreApplication.setOrganizationName("SimplyPixelated")
QCoreApplication.setOrganizationDomain("simplypixelated.com")
frame = MainWindow(900, 500, app)
frame.show_and_raise()
sys.exit(app.exec_())