MathArtist
/
main.py
256 строк · 8.6 Кб
1#!/usr/bin/python3
2# -*- coding: utf-8 -*-
3
4# Copyright (c) 2018-2019, Yaroslav Zotov, https://github.com/qiray/
5# All rights reserved.
6
7# This file is part of MathArtist.
8
9# MathArtist is free software: you can redistribute it and/or modify
10# it under the terms of the GNU General Public License as published by
11# the Free Software Foundation, either version 3 of the License, or
12# (at your option) any later version.
13
14# MathArtist is distributed in the hope that it will be useful,
15# but WITHOUT ANY WARRANTY; without even the implied warranty of
16# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
17# GNU General Public License for more details.
18
19# You should have received a copy of the GNU General Public License
20# along with MathArtist. If not, see <https://www.gnu.org/licenses/>.
21
22import sys
23import signal
24import argparse
25import time
26from copy import copy
27import webbrowser
28from PIL import Image, ImageDraw, ImageQt
29
30from PyQt5 import QtCore, QtGui
31from PyQt5.QtWidgets import (QWidget, QGridLayout, QPushButton, QApplication,
32QLabel, QFileDialog, QMessageBox, QTextEdit)
33from PyQt5.QtGui import QPixmap
34from PyQt5.QtCore import QThread, pyqtSignal
35
36from art import Art, APP_NAME, VERSION_MAJOR, VERSION_MINOR, VERSION_BUILD
37from common import SIZE
38
39# TODO: use multiprocessing or something similar
40
41class DrawThread(QThread):
42def __init__(self, load_file=""):
43self.art = Art(use_checker=True)
44QThread.__init__(self)
45
46def __del__(self):
47self.wait()
48
49def run(self):
50self.art.redraw()
51
52def stop(self):
53self.art.stop_drawing() #send signal to art object
54self.wait()
55
56def get_image(self):
57return self.art.img
58
59def set_name(self, name):
60self.art.set_name(name)
61
62def get_name(self):
63return self.art.name
64
65def reset_name(self):
66self.art.reset_name()
67
68def save_image(self):
69self.art.save_image_text()
70
71def get_status(self):
72return self.art.status
73
74def set_trigger(self, trigger):
75self.art.set_trigger(trigger)
76
77def set_file(self, filepath):
78self.art.set_file(filepath)
79
80class GUI(QWidget):
81
82trigger = pyqtSignal()
83
84def __init__(self):
85super().__init__()
86self.draw_thread = None
87self.timer = 0
88self.need_name_update = True
89self.initGUI()
90
91def keyPressEvent(self, event): #Handle keys
92key = event.key()
93# modifiers = QApplication.keyboardModifiers() #modifiers == QtCore.Qt.ControlModifier
94if key == QtCore.Qt.Key_Escape:
95print("Closing...")
96self.close()
97elif key == QtCore.Qt.Key_N or key == QtCore.Qt.Key_R:
98self.new_image_thread()
99elif key == QtCore.Qt.Key_G:
100self.new_image_name_thread()
101elif key == QtCore.Qt.Key_O:
102self.load_file()
103elif key == QtCore.Qt.Key_S:
104self.save_image()
105elif key == QtCore.Qt.Key_A:
106self.show_about_message()
107elif key == QtCore.Qt.Key_F1:
108self.show_online_help()
109event.accept()
110
111def show_about_message(self):
112QMessageBox.about(self, 'About', get_about_info())
113
114def show_online_help(self):
115webbrowser.open('https://github.com/qiray/MathArtist')
116
117def save_image(self):
118if self.draw_thread:
119self.draw_thread.save_image()
120
121def update_GUI(self):
122self.image = ImageQt.ImageQt(copy(self.draw_thread.get_image()))
123pixmap = QPixmap.fromImage(self.image)
124self.image_label.setPixmap(pixmap)
125name = self.draw_thread.get_name()
126if name and self.need_name_update:
127self.name_label.setText(name)
128self.need_name_update = False
129self.status_label.setText(self.draw_thread.get_status())
130
131def prepare_new_thread(self):
132self.need_name_update = True
133if time.time() - self.timer < 1: #prevent from very often image updates
134return
135self.timer = time.time()
136if self.draw_thread: #if thread exists
137self.draw_thread.stop() #send signal to art object
138else: #init thread
139self.trigger.connect(self.update_GUI)
140self.draw_thread = DrawThread()
141self.draw_thread.set_trigger(self.trigger)
142
143def new_image_thread(self):
144self.prepare_new_thread()
145self.draw_thread.reset_name()
146self.draw_thread.start()
147
148def new_image_name_thread(self):
149self.prepare_new_thread()
150self.draw_thread.set_name(self.name_label.toPlainText())
151self.draw_thread.start()
152
153def empty_image(self):
154size = SIZE
155image = Image.new('RGBA', (size, size))
156image_draw = ImageDraw.Draw(image)
157image_draw.rectangle(((0, 0,), (size, size)), fill="#FFFFFF")
158return ImageQt.ImageQt(image)
159
160def initGUI(self):
161grid = QGridLayout()
162self.setLayout(grid)
163#IMPORTANT: this image must exist all application lifetime:
164self.image = self.empty_image()
165pixmap = QPixmap.fromImage(self.image)
166self.image_label = QLabel()
167self.image_label.setPixmap(pixmap)
168grid.addWidget(self.image_label, 0, 0, 1, 2)
169
170new_button = QPushButton('Random image')
171new_button.clicked.connect(self.new_image_thread)
172grid.addWidget(new_button, 2, 0)
173new_button2 = QPushButton('Generate image')
174new_button2.clicked.connect(self.new_image_name_thread)
175grid.addWidget(new_button2, 2, 1)
176save_button = QPushButton('Save image')
177save_button.clicked.connect(self.save_image)
178grid.addWidget(save_button, 3, 0)
179load_button = QPushButton('Load image')
180load_button.clicked.connect(self.load_file)
181grid.addWidget(load_button, 3, 1)
182self.status_label = QLabel('')
183self.status_label.setAlignment(QtCore.Qt.AlignCenter)
184grid.addWidget(self.status_label, 4, 0, 1, 2)
185
186self.name_label = QTextEdit('')
187self.name_label.setMaximumHeight(self.status_label.sizeHint().height()*3)
188grid.addWidget(self.name_label, 1, 0, 1, 2)
189
190self.setWindowTitle('Math Artist')
191self.setWindowIcon(QtGui.QIcon('icon.ico'))
192self.show()
193self.new_image_thread()
194
195def load_file(self):
196filepath, _ = QFileDialog.getOpenFileName(self,"QFileDialog.getOpenFileName()", "","Text Files (*.txt)")
197if time.time() - self.timer < 1: #prevent from very often image updates
198time.sleep(0.5)
199self.timer = time.time()
200if not filepath:
201return
202if self.draw_thread:
203self.draw_thread.stop()
204self.draw_thread.set_trigger(self.trigger)
205self.draw_thread.set_file(filepath)
206self.draw_thread.start()
207
208def sigint_handler(sig, frame):
209print("Closing...")
210sys.exit(0)
211
212def parse_args():
213"""argparse settings"""
214parser = argparse.ArgumentParser(prog=APP_NAME,
215description='Tool for generating pictures using mathematical formulas.')
216parser.add_argument('--console', action='store_true', help='Run in console mode (no window)')
217parser.add_argument('--name', type=str, help='Set image name')
218parser.add_argument('--about', action='store_true', help='Show about info')
219parser.add_argument('--file', type=str, help='Load file')
220parser.add_argument('--generate_list', action='store_true', help='Generate operators\' list (developer option)')
221return parser.parse_args()
222
223def get_new_list():
224from operator_lists import generate_lists
225terminals, nonterminals = generate_lists()
226fulllist = terminals + nonterminals
227result = str([x.__name__ for x in fulllist])
228result = result.replace("'", "")
229result = result.replace("[", "(")
230result = result.replace("]", ")")
231return result
232
233def get_version():
234return "%d.%d.%d" % (VERSION_MAJOR, VERSION_MINOR, VERSION_BUILD)
235
236def get_about_info():
237return ("\n" + APP_NAME + " " + get_version() + " Copyright (C) 2018-2019 Yaroslav Zotov.\n" +
238"Based on \"randomart\" Copyright (C) 2010, Andrej Bauer.\n"
239"This program comes with ABSOLUTELY NO WARRANTY.\n" +
240"This is free software under GNU GPL3; see the source for copying conditions\n")
241
242if __name__ == '__main__':
243signal.signal(signal.SIGINT, sigint_handler)
244args = parse_args() #parse command line arguments
245if args.about:
246print(get_about_info())
247exit(0)
248if args.console:
249if args.generate_list:
250print(get_new_list())
251exit(0)
252art = Art(use_checker=True, name=args.name, console=True, load_file=args.file)
253else:
254app = QApplication(sys.argv)
255window = GUI()
256sys.exit(app.exec_())
257