MathArtist

Форк
0
/
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

22
import sys
23
import signal
24
import argparse
25
import time
26
from copy import copy
27
import webbrowser
28
from PIL import Image, ImageDraw, ImageQt
29

30
from PyQt5 import QtCore, QtGui
31
from PyQt5.QtWidgets import (QWidget, QGridLayout, QPushButton, QApplication,
32
    QLabel, QFileDialog, QMessageBox, QTextEdit)
33
from PyQt5.QtGui import QPixmap
34
from PyQt5.QtCore import QThread, pyqtSignal
35

36
from art import Art, APP_NAME, VERSION_MAJOR, VERSION_MINOR, VERSION_BUILD
37
from common import SIZE
38

39
# TODO: use multiprocessing or something similar
40

41
class DrawThread(QThread):
42
    def __init__(self, load_file=""):
43
        self.art = Art(use_checker=True)
44
        QThread.__init__(self)
45

46
    def __del__(self):
47
        self.wait()
48

49
    def run(self):
50
        self.art.redraw()
51

52
    def stop(self):
53
        self.art.stop_drawing() #send signal to art object
54
        self.wait()
55

56
    def get_image(self):
57
        return self.art.img
58

59
    def set_name(self, name):
60
        self.art.set_name(name)
61

62
    def get_name(self):
63
        return self.art.name
64

65
    def reset_name(self):
66
        self.art.reset_name()
67

68
    def save_image(self):
69
        self.art.save_image_text()
70

71
    def get_status(self):
72
        return self.art.status
73

74
    def set_trigger(self, trigger):
75
        self.art.set_trigger(trigger)
76

77
    def set_file(self, filepath):
78
        self.art.set_file(filepath)
79

80
class GUI(QWidget):
81

82
    trigger = pyqtSignal()
83

84
    def __init__(self):
85
        super().__init__()
86
        self.draw_thread = None
87
        self.timer = 0
88
        self.need_name_update = True
89
        self.initGUI()
90

91
    def keyPressEvent(self, event): #Handle keys
92
        key = event.key()
93
        # modifiers = QApplication.keyboardModifiers() #modifiers == QtCore.Qt.ControlModifier
94
        if key == QtCore.Qt.Key_Escape:
95
            print("Closing...")
96
            self.close()
97
        elif key == QtCore.Qt.Key_N or key == QtCore.Qt.Key_R:
98
            self.new_image_thread()
99
        elif key == QtCore.Qt.Key_G:
100
            self.new_image_name_thread()
101
        elif key == QtCore.Qt.Key_O:
102
            self.load_file()
103
        elif key == QtCore.Qt.Key_S:
104
            self.save_image()
105
        elif key == QtCore.Qt.Key_A:
106
            self.show_about_message()
107
        elif key == QtCore.Qt.Key_F1:
108
            self.show_online_help()
109
        event.accept()
110

111
    def show_about_message(self):
112
        QMessageBox.about(self, 'About', get_about_info())
113

114
    def show_online_help(self):
115
        webbrowser.open('https://github.com/qiray/MathArtist')
116

117
    def save_image(self):
118
        if self.draw_thread:
119
            self.draw_thread.save_image()
120

121
    def update_GUI(self):
122
        self.image = ImageQt.ImageQt(copy(self.draw_thread.get_image()))
123
        pixmap = QPixmap.fromImage(self.image)
124
        self.image_label.setPixmap(pixmap)
125
        name = self.draw_thread.get_name()
126
        if name and self.need_name_update:
127
            self.name_label.setText(name)
128
            self.need_name_update = False
129
        self.status_label.setText(self.draw_thread.get_status())
130

131
    def prepare_new_thread(self):
132
        self.need_name_update = True
133
        if time.time() - self.timer < 1: #prevent from very often image updates
134
            return
135
        self.timer = time.time()
136
        if self.draw_thread: #if thread exists
137
            self.draw_thread.stop() #send signal to art object
138
        else: #init thread
139
            self.trigger.connect(self.update_GUI)
140
            self.draw_thread = DrawThread()
141
        self.draw_thread.set_trigger(self.trigger)
142

143
    def new_image_thread(self):
144
        self.prepare_new_thread()
145
        self.draw_thread.reset_name()
146
        self.draw_thread.start()
147

148
    def new_image_name_thread(self):
149
        self.prepare_new_thread()
150
        self.draw_thread.set_name(self.name_label.toPlainText())
151
        self.draw_thread.start()
152

153
    def empty_image(self):
154
        size = SIZE
155
        image = Image.new('RGBA', (size, size))
156
        image_draw = ImageDraw.Draw(image)
157
        image_draw.rectangle(((0, 0,), (size, size)), fill="#FFFFFF")
158
        return ImageQt.ImageQt(image)
159

160
    def initGUI(self):
161
        grid = QGridLayout()
162
        self.setLayout(grid)
163
        #IMPORTANT: this image must exist all application lifetime:
164
        self.image = self.empty_image()
165
        pixmap = QPixmap.fromImage(self.image)
166
        self.image_label = QLabel()
167
        self.image_label.setPixmap(pixmap)
168
        grid.addWidget(self.image_label, 0, 0, 1, 2)
169

170
        new_button = QPushButton('Random image')
171
        new_button.clicked.connect(self.new_image_thread)
172
        grid.addWidget(new_button, 2, 0)
173
        new_button2 = QPushButton('Generate image')
174
        new_button2.clicked.connect(self.new_image_name_thread)
175
        grid.addWidget(new_button2, 2, 1)
176
        save_button = QPushButton('Save image')
177
        save_button.clicked.connect(self.save_image)
178
        grid.addWidget(save_button, 3, 0)
179
        load_button = QPushButton('Load image')
180
        load_button.clicked.connect(self.load_file)
181
        grid.addWidget(load_button, 3, 1)
182
        self.status_label = QLabel('')
183
        self.status_label.setAlignment(QtCore.Qt.AlignCenter)
184
        grid.addWidget(self.status_label, 4, 0, 1, 2)
185

186
        self.name_label = QTextEdit('')
187
        self.name_label.setMaximumHeight(self.status_label.sizeHint().height()*3)
188
        grid.addWidget(self.name_label, 1, 0, 1, 2)
189

190
        self.setWindowTitle('Math Artist')
191
        self.setWindowIcon(QtGui.QIcon('icon.ico'))
192
        self.show()
193
        self.new_image_thread()
194

195
    def load_file(self):
196
        filepath, _ = QFileDialog.getOpenFileName(self,"QFileDialog.getOpenFileName()", "","Text Files (*.txt)")
197
        if time.time() - self.timer < 1: #prevent from very often image updates
198
            time.sleep(0.5)
199
        self.timer = time.time()
200
        if not filepath:
201
            return
202
        if self.draw_thread:
203
            self.draw_thread.stop()
204
        self.draw_thread.set_trigger(self.trigger)
205
        self.draw_thread.set_file(filepath)
206
        self.draw_thread.start()
207

208
def sigint_handler(sig, frame):
209
    print("Closing...")
210
    sys.exit(0)
211

212
def parse_args():
213
    """argparse settings"""
214
    parser = argparse.ArgumentParser(prog=APP_NAME, 
215
        description='Tool for generating pictures using mathematical formulas.')
216
    parser.add_argument('--console', action='store_true', help='Run in console mode (no window)')
217
    parser.add_argument('--name', type=str, help='Set image name')
218
    parser.add_argument('--about', action='store_true', help='Show about info')
219
    parser.add_argument('--file', type=str, help='Load file')
220
    parser.add_argument('--generate_list', action='store_true', help='Generate operators\' list (developer option)')
221
    return parser.parse_args()
222

223
def get_new_list():
224
    from operator_lists import generate_lists
225
    terminals, nonterminals = generate_lists()
226
    fulllist = terminals + nonterminals
227
    result = str([x.__name__ for x in fulllist])
228
    result = result.replace("'", "")
229
    result = result.replace("[", "(")
230
    result = result.replace("]", ")")
231
    return result
232

233
def get_version():
234
    return "%d.%d.%d" % (VERSION_MAJOR, VERSION_MINOR, VERSION_BUILD)
235

236
def get_about_info():
237
    return ("\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

242
if __name__ == '__main__':
243
    signal.signal(signal.SIGINT, sigint_handler)
244
    args = parse_args() #parse command line arguments
245
    if args.about:
246
        print(get_about_info())
247
        exit(0)
248
    if args.console:
249
        if args.generate_list:
250
            print(get_new_list())
251
            exit(0)
252
        art = Art(use_checker=True, name=args.name, console=True, load_file=args.file)
253
    else:
254
        app = QApplication(sys.argv)
255
        window = GUI()
256
        sys.exit(app.exec_())
257

Использование cookies

Мы используем файлы cookie в соответствии с Политикой конфиденциальности и Политикой использования cookies.

Нажимая кнопку «Принимаю», Вы даете АО «СберТех» согласие на обработку Ваших персональных данных в целях совершенствования нашего веб-сайта и Сервиса GitVerse, а также повышения удобства их использования.

Запретить использование cookies Вы можете самостоятельно в настройках Вашего браузера.