Electrify/main.py
2016-02-08 17:30:04 -07:00

1525 lines
57 KiB
Python

from utils import log, open_folder_in_explorer
import os
import re
import glob
import sys
import codecs
import platform
import requests
import validators
from PySide import QtGui, QtCore
from PySide.QtGui import QApplication, QHBoxLayout, QVBoxLayout
from PySide.QtNetwork import QHttp, QHttpRequestHeader
from PySide.QtCore import QUrl, QFile, QIODevice, QCoreApplication
from pycns import pngs_from_icns
from command_line import CommandBase, logger, get_file
from command_line import __version__ as __gui_version__
from utils import get_data_path, get_data_file_path
import utils
COMMAND_LINE = False
MAX_RECENT = 10
def url_exists(path):
if validators.url(path):
return True
return False
class ColorDisplay(QtGui.QWidget):
colorChanged = QtCore.Signal(QtGui.QColor)
def __init__(self, initial, parent=None):
super(ColorDisplay, self).__init__(parent)
self.color = None
self.setColor(initial, False)
def setColor(self, color, emit=True):
if isinstance(color, (str, bytes)):
self.color = QtGui.QColor(color)
else:
self.color = color
self.update()
if emit:
self.colorChanged.emit(self.color)
def paintEvent(self, event=None):
painter = QtGui.QPainter(self)
if self.color is not None:
painter.setBrush(QtGui.QBrush(self.color))
rect = self.rect()
margins = self.contentsMargins()
new_rect = QtCore.QRect(rect.left()+margins.left(),
rect.top()+margins.top(),
rect.width()-margins.right()*2,
rect.height()-margins.bottom()*2)
painter.drawRect(new_rect)
def getColorName(self):
return str(self.color.name())
class ExistingProjectDialog(QtGui.QDialog):
def __init__(self, recent_projects, directory_callback, parent=None):
super(ExistingProjectDialog, self).__init__(parent)
self.setWindowTitle('Open Project Folder')
self.setWindowIcon(QtGui.QIcon(get_file('files/images/icon.png')))
self.setMinimumWidth(500)
group_box = QtGui.QGroupBox('Existing Projects')
gbox_layout = QtGui.QVBoxLayout()
self.project_list = QtGui.QListWidget()
gbox_layout.addWidget(self.project_list)
group_box.setLayout(gbox_layout)
self.callback = directory_callback
self.projects = recent_projects
for i in range(len(recent_projects)):
project = recent_projects[i]
text = u'{} - {}'.format(os.path.basename(project), project)
self.project_list.addItem(text)
self.project_list.itemClicked.connect(self.project_clicked)
self.cancel = QtGui.QPushButton('Cancel')
self.open = QtGui.QPushButton('Open Selected')
self.browse = QtGui.QPushButton('Browse...')
self.open.setEnabled(False)
self.open.clicked.connect(self.open_clicked)
self.browse.clicked.connect(self.browse_clicked)
buttons = QtGui.QWidget()
button_layout = QtGui.QHBoxLayout()
button_layout.addWidget(self.cancel)
button_layout.addWidget(QtGui.QWidget())
button_layout.addWidget(self.browse)
button_layout.addWidget(self.open)
buttons.setLayout(button_layout)
layout = QtGui.QVBoxLayout()
layout.addWidget(group_box)
layout.addWidget(buttons)
self.setLayout(layout)
self.cancel.clicked.connect(self.cancelled)
def browse_clicked(self):
directory = QtGui.QFileDialog.getExistingDirectory(self, 'Find Project Directory',
self.parent().project_dir() or self.parent().last_project_dir)
if directory:
self.callback(directory)
self.close()
def open_clicked(self):
pos = self.project_list.currentRow()
self.callback(self.projects[pos])
self.close()
def project_clicked(self, item):
self.open.setEnabled(True)
def cancelled(self):
self.close()
class Validator(QtGui.QRegExpValidator):
def __init__(self, regex, action, parent=None):
self.exp = regex
self.action = str
if hasattr(str, action):
self.action = getattr(str, 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(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_electron_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 range(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)
recent_projects = self.load_recent_projects()
self.existing_dialog = ExistingProjectDialog(recent_projects, self.load_project, parent=self)
drect = QtGui.QApplication.desktop().availableGeometry(self)
center = drect.center()
self.move(center.x() - self.width() * 0.5, center.y() - self.height()*0.5)
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 range(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_electron_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"Electrify {}".format(__gui_version__))
self.update_electron_versions(None)
def open_recent_file(self):
action = self.sender()
if action:
self.load_project(action.data())
def setup_electron_versions(self):
electron_version = self.get_setting('electron_version')
try:
f = codecs.open(get_data_file_path('files/electron-versions.txt'), encoding='utf-8')
for line in f:
electron_version.values.append(line.strip())
f.close()
except IOError:
electron_version.values.append(electron_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.web_prefs_widget = self.create_web_prefs_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.web_prefs_icon = QtGui.QIcon(get_file('files/images/web_prefs.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.web_prefs_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.web_prefs_widget,
self.web_prefs_icon,
'Web Preferences')
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.web_prefs_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('electron_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!', 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,
'web_preferences': 2,
'export_settings': 3,
'electrify_settings': 3,
'compression': 4,
'download_settings': 5}
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']+[self.settings['electrify_settings']]:
for sname, setting in sgroup.items():
if setting.type in set(['file', 'folder']) and os.path.isabs(setting.value):
setting_path = setting.value
else:
setting_path = utils.path_join(self.project_dir(),
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 setting.exists):
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(setting_path))
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
if not settings_valid:
return export_chosen and settings_valid
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
def output_dir(self):
if hasattr(self, 'output_line'):
if os.path.isabs(self.output_line.text()):
return self.output_line.text()
else:
return utils.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.sslErrors.connect(self.https_error)
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 https_error(self, errors):
print(errors)
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(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.'
electron_version = self.get_setting('electron_version')
combo = self.find_child_by_name(electron_version.name)
combo.clear()
combo.addItems(electron_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 run_custom_script(self):
script = self.get_setting('custom_script').value
self.run_script(script)
def script_done(self):
self.ex_button.setEnabled(self.required_settings_filled())
self.enable_ui()
self.progress_text = 'Done!'
def done_making_files(self):
self.ex_button.setEnabled(self.required_settings_filled())
self.progress_text = 'Done Exporting.'
self.delete_files()
if self.output_err:
self.show_error(self.output_err)
self.enable_ui_after_error()
else:
self.progress_text = 'Running custom script...'
self.ex_button.setEnabled(False)
self.run_in_background('run_custom_script', self.script_done)
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]
self.progress_text = u'Downloading {}'.format(path.replace(version_file, ''))
path = self.get_redirected_url(path)
url = QUrl(path)
file_name = setting.save_file_path(self.selected_version(), location)
archive_exists = QFile.exists(file_name)
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.ConnectionModeHttps
port = url.port()
if port == -1:
port = 0
self.http.setHost(url.host(), mode, port)
self.http_request_aborted = False
# Download the file.
self.http_get_id = self.http.get(path, to=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 = utils.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:
ba = QtCore.QByteArray(pngs[-1].data)
image = QtGui.QImage.fromData(ba, '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_html')
files = (glob.glob(utils.path_join(directory, 'index.html')) +
glob.glob(utils.path_join(directory, 'index.php')) +
glob.glob(utils.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')
title_input = self.find_child_by_name('title')
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(utils.get_data_file_path('files/global.json'))
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)
script_setting = self.get_setting('custom_script')
self.script_line.setText(script_setting.value)
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_color(self, obj, color_disp, initial, setting, *args, **kwargs):
color = QtGui.QColorDialog.getColor(QtGui.QColor(initial), obj, 'Choose Color')
if color:
color_disp.setColor(color)
setting.last_value = color.name()
def get_file_reg(self, obj, text_obj, setting, file_types, *args, **kwargs):
file_path, _ = QtGui.QFileDialog.getOpenFileName(self, 'Choose File',
(setting.last_value or
self.project_dir() or
QtCore.QDir.currentPath()),
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)
elif setting.type == 'color':
return self.create_color_setting(name)
else:
print('Setting "{}" type "{}" not defined.'.format(name, setting.type))
return QtGui.QHBoxLayout()
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_web_prefs_settings(self):
group_box = QtGui.QWidget()
vlayout = self.create_layout(self.settings['order']['web_prefs_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:')
output_label.setMinimumWidth(150)
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)
script_layout = QtGui.QHBoxLayout()
script_label = QtGui.QLabel('Execute Script:')
script_label.setMinimumWidth(150)
self.script_line = QtGui.QLineEdit()
script_setting = self.get_setting('custom_script')
self.script_line.setObjectName(script_setting.name)
self.script_line.textChanged.connect(self.call_with_object('setting_changed',
self.script_line,
script_setting))
self.script_line.setStatusTip('The script to execute after a project was successfully exported.')
script_button = QtGui.QPushButton('...')
file_types = ['*.py']
if platform.system() == 'Windows':
file_types.append('*.bat')
else:
file_types.append('*.bash')
script_button.clicked.connect(self.call_with_object('get_file_reg', script_button,
self.script_line, script_setting,
' '.join(file_types)))
script_layout.addWidget(script_label)
script_layout.addWidget(self.script_line)
script_layout.addWidget(script_button)
vbox = QtGui.QVBoxLayout()
vbox.addLayout(vlayout)
vbox.addLayout(output_layout)
vbox.addLayout(script_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)
sett = self.create_setting(setting_name)
glayout.addLayout(sett, 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(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_color_setting(self, name):
hlayout = QtGui.QHBoxLayout()
setting = self.get_setting(name)
color_disp = ColorDisplay(setting.value or setting.default_value)
color_disp.setObjectName(setting.name)
color_disp.setContentsMargins(5, 5, 5, 5)
button = QtGui.QPushButton('Choose...')
button.setMaximumWidth(100)
button.setMaximumHeight(26)
button.clicked.connect(self.call_with_object('get_color', button,
color_disp,
setting.value or setting.default_value,
setting))
if setting.value:
color_disp.setColor(setting.value)
color_disp.setStatusTip(setting.description)
color_disp.setToolTip(setting.description)
color_disp.colorChanged.connect(self.call_with_object('setting_changed',
color_disp, setting))
hlayout.addWidget(color_disp)
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(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(old_val)
elif setting.type == 'strings':
old_val = []
if setting.default_value is not None:
old_val = setting.default_value
setting.value = [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)
elif setting.type == 'color':
old_val = ''
if setting.default_value is not None:
old_val = setting.default_value
setting.value = old_val
widget.setColor(setting.value)
def set_kiosk_emulation_options(self, is_checked):
if is_checked:
width_field = self.find_child_by_name('width')
width_field.setText(self.desktop_width)
height_field = self.find_child_by_name('height')
height_field.setText(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 = setting.convert(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()
elif setting.type == 'color':
setting.value = args[0].name()
if setting.action is not None:
action = getattr(self, setting.action, None)
if callable(action):
action()
if self.update_json:
json_file = utils.path_join(self.project_dir(), 'package.json')
global_json = utils.get_data_file_path('files/global.json')
with codecs.open(json_file, 'w+', encoding='utf-8') as f:
f.write(self.generate_json())
with codecs.open(global_json, 'w+', encoding='utf-8') as f:
f.write(self.generate_json(global_json=True))
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.setMinimumWidth(60)
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(45)
slider.valueChanged.connect(self.call_with_object('_update_range_label',
range_label, setting))
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, setting, value):
label.setText(str(value)+setting.label_suffix)
def load_package_json(self, json_path=None):
setting_list = super(MainWindow, self).load_package_json(json_path)
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(int(setting.value))
if setting.type == 'color':
setting_field.setColor(setting.value)
self.ex_button.setEnabled(self.required_settings_filled())
def show_and_raise(self):
self.show()
self.raise_()
self.existing_dialog.show()
self.existing_dialog.raise_()
if __name__ == '__main__':
app = QApplication(sys.argv)
QCoreApplication.setApplicationName("Electrify")
QCoreApplication.setApplicationVersion(__gui_version__)
QCoreApplication.setOrganizationName("SimplyPixelated")
QCoreApplication.setOrganizationDomain("simplypixelated.com")
frame = MainWindow(1000, 500, app)
frame.show_and_raise()
sys.exit(app.exec_())